From 95b1e459208b2a1fbb67a3ea918ba88952f015ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Sun, 1 Jun 2008 23:33:51 +0000 Subject: [PATCH 001/200] Initial commit related to plug-in system: - basic PluginManager class that loads plugins from *.py files in given directories - Singleton metaclass was created to use with PluginManager; notice: __init__ of class is called only once (not like in code that is included in Python Cookbook) - variable to keep paths of plugin directories has been created (common.gajim.PLUGINS_DIRS); also added initilization of these paths to common.ConfigPaths - added global variable with PluginManager object: common.gajim.plugin_manager - created customized logger for plugin system ('gajim.plugin_system') - created function decorator plugins.helpers.log_calls which logs each call of function/method; it also logs when function is left - base class Plugin for plug-in implementation added; not much here - only empty class attributes: name, short_name, authors, version, description - based on Plugin class, first plugin was created named LengthNotifierPlugin; it is used to notify users when they exceed given length of message during writing it (text entry field highlights) - first GUI extension points works when ChatControl object is created (it is used in mentioned plugin) - added 'epydoc.conf' file customized a little bit (file is also in trunk now) - fixed indentation in common.sleepy module (also in trunk now) --- epydoc.conf | 28 ++++++++ plugins/length_notifier.py | 95 +++++++++++++++++++++++++ src/chat_control.py | 2 + src/common/configpaths.py | 4 ++ src/common/gajim.py | 3 + src/common/sleepy.py | 24 +++---- src/gajim.py | 4 ++ src/plugins/__init__.py | 30 ++++++++ src/plugins/helpers.py | 113 ++++++++++++++++++++++++++++++ src/plugins/plugin.py | 40 +++++++++++ src/plugins/pluginmanager.py | 130 +++++++++++++++++++++++++++++++++++ test/test_pluginmanager.py | 61 ++++++++++++++++ 12 files changed, 522 insertions(+), 12 deletions(-) create mode 100644 epydoc.conf create mode 100644 plugins/length_notifier.py create mode 100644 src/plugins/__init__.py create mode 100644 src/plugins/helpers.py create mode 100644 src/plugins/plugin.py create mode 100644 src/plugins/pluginmanager.py create mode 100644 test/test_pluginmanager.py diff --git a/epydoc.conf b/epydoc.conf new file mode 100644 index 000000000..092f7f1a5 --- /dev/null +++ b/epydoc.conf @@ -0,0 +1,28 @@ +[epydoc] + +# Information about the project. +name: Gajim +url: http://gajim.org + +verbosity: 3 +imports: yes +redundant-details: yes +docformat: restructuredtext +# top: gajim + +# The list of modules to document. Modules can be named using +# dotted names, module filenames, or package directory names. +# This option may be repeated. +modules: src/plugins/*.py + +# Write html output to the directory "apidocs" +#output: pdf +output: html +target: apidocs/ + +# Include all automatically generated graphs. These graphs are +# generated using Graphviz dot. +graph: all +dotpath: /usr/bin/dot +graph-font: Sans +graph-font-size: 10 diff --git a/plugins/length_notifier.py b/plugins/length_notifier.py new file mode 100644 index 000000000..bd0f9564f --- /dev/null +++ b/plugins/length_notifier.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- + +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## + +''' +Message length notifier plugin. + +:author: Mateusz Biliński +:since: 06/01/2008 +:copyright: Copyright (2008) Mateusz Biliński +:license: GPL +''' + +import sys + +import gtk + +from plugins import GajimPlugin +from plugins.helpers import log, log_calls + +class LengthNotifierPlugin(GajimPlugin): + name = 'Message Length Notifier' + short_name = 'length_notifier' + version = '0.1' + description = '''Highlights message entry field in chat window when given +length of message is exceeded.''' + authors = ['Mateusz Biliński '] + + @log_calls('LengthNotifierPlugin') + def __init__(self): + super(LengthNotifierPlugin, self).__init__() + + self.__class__.gui_extension_points = { + 'chat_control' : (self.connect_with_chat_control, + self.disconnect_from_chat_control) + } + + self.MESSAGE_WARNING_LENGTH = 140 + self.WARNING_COLOR = gtk.gdk.color_parse('#F0DB3E') + self.JIDS = [] + + @log_calls('LengthNotifierPlugin') + def textview_length_warning(self, tb, chat_control): + tv = chat_control.msg_textview + d = chat_control.length_notifier_plugin_data + t = tb.get_text(tb.get_start_iter(), tb.get_end_iter()) + if t: + len_t = len(t) + #print("len_t: %d"%(len_t)) + if len_t>self.MESSAGE_WARNING_LENGTH: + if not d['prev_color']: + d['prev_color'] = tv.style.copy().base[gtk.STATE_NORMAL] + tv.modify_base(gtk.STATE_NORMAL, self.WARNING_COLOR) + elif d['prev_color']: + tv.modify_base(gtk.STATE_NORMAL, d['prev_color']) + d['prev_color'] = None + + @log_calls('LengthNotifierPlugin') + def connect_with_chat_control(self, chat_control): + jid = chat_control.contact.jid + if self.jid_is_ok(jid): + d = {'prev_color' : None} + tv = chat_control.msg_textview + b = tv.get_buffer() + h_id = b.connect('changed', self.textview_length_warning, chat_control) + d['h_id'] = h_id + chat_control.length_notifier_plugin_data = d + + return True + + return False + + @log_calls('LengthNotifierPlugin') + def disconnect_from_chat_control(self, chat_control): + d = chat_control.length_notifier_plugin_data + chat_control.msg_textview.get_buffer().disconnect(d['h_id']) + if d['prev_color']: + tv.modify_base(gtk.STATE_NORMAL, self.PREV_COLOR) + + @log_calls('LengthNotifierPlugin') + def jid_is_ok(self, jid): + return True \ No newline at end of file diff --git a/src/chat_control.py b/src/chat_control.py index cddf9cd35..ecc817bf6 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -1139,6 +1139,8 @@ class ChatControl(ChatControlBase): self.update_ui() # restore previous conversation self.restore_conversation() + + gajim.plugin_manager.gui_extension_point('chat_control', self) def on_avatar_eventbox_enter_notify_event(self, widget, event): '''we enter the eventbox area so we under conditions add a timeout diff --git a/src/common/configpaths.py b/src/common/configpaths.py index 516d849ef..e950b3e1a 100644 --- a/src/common/configpaths.py +++ b/src/common/configpaths.py @@ -92,6 +92,10 @@ class ConfigPaths: self.add('DATA', os.path.join(u'..', windowsify(u'data'))) self.add('HOME', fse(os.path.expanduser('~'))) self.add('TMP', fse(tempfile.gettempdir())) + + # dirs for plugins + self.add('PLUGINS_BASE', os.path.join(u'..', windowsify(u'plugins'))) + self.add_from_root('PLUGINS_USER', u'plugins') try: import svn_config diff --git a/src/common/gajim.py b/src/common/gajim.py index eaa84ec4f..4cbdf609b 100644 --- a/src/common/gajim.py +++ b/src/common/gajim.py @@ -61,6 +61,7 @@ version = config.get('version') connections = {} # 'account name': 'account (connection.Connection) instance' verbose = False ipython_window = None +plugin_manager = None h = logging.StreamHandler() f = logging.Formatter('%(asctime)s %(name)s: %(message)s', '%d %b %Y %H:%M:%S') @@ -85,6 +86,8 @@ MY_CACERTS = gajimpaths['MY_CACERTS'] TMP = gajimpaths['TMP'] DATA_DIR = gajimpaths['DATA'] HOME_DIR = gajimpaths['HOME'] +PLUGINS_DIRS = [gajimpaths['PLUGINS_BASE'], + gajimpaths['PLUGINS_USER']] try: LANG = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc.. diff --git a/src/common/sleepy.py b/src/common/sleepy.py index bcc5f73c3..f56a7e044 100644 --- a/src/common/sleepy.py +++ b/src/common/sleepy.py @@ -35,22 +35,22 @@ STATE_AWAKE = 'awake' SUPPORTED = True try: - if os.name == 'nt': - import ctypes + if os.name == 'nt': + import ctypes - GetTickCount = ctypes.windll.kernel32.GetTickCount - GetLastInputInfo = ctypes.windll.user32.GetLastInputInfo + GetTickCount = ctypes.windll.kernel32.GetTickCount + GetLastInputInfo = ctypes.windll.user32.GetLastInputInfo - class LASTINPUTINFO(ctypes.Structure): - _fields_ = [('cbSize', ctypes.c_uint), ('dwTime', ctypes.c_uint)] + class LASTINPUTINFO(ctypes.Structure): + _fields_ = [('cbSize', ctypes.c_uint), ('dwTime', ctypes.c_uint)] - lastInputInfo = LASTINPUTINFO() - lastInputInfo.cbSize = ctypes.sizeof(lastInputInfo) + lastInputInfo = LASTINPUTINFO() + lastInputInfo.cbSize = ctypes.sizeof(lastInputInfo) - elif sys.platform == 'darwin': - import osx.idle as idle - else: # unix - import idle + elif sys.platform == 'darwin': + import osx.idle as idle + else: # unix + import idle except: gajim.log.debug('Unable to load idle module') SUPPORTED = False diff --git a/src/gajim.py b/src/gajim.py index e34e5f81a..d1d746401 100755 --- a/src/gajim.py +++ b/src/gajim.py @@ -3461,6 +3461,10 @@ class Interface: gobject.timeout_add_seconds(2, self.process_connections) gobject.timeout_add_seconds(gajim.config.get( 'check_idle_every_foo_seconds'), self.read_sleepy) + + # Creating plugin manager + import plugins + gajim.plugin_manager = plugins.PluginManager() if __name__ == '__main__': def sigint_cb(num, stack): diff --git a/src/plugins/__init__.py b/src/plugins/__init__.py new file mode 100644 index 000000000..0d5d18fda --- /dev/null +++ b/src/plugins/__init__.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- + +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## + +''' +Main file of plugins package. + +:author: Mateusz Biliński +:since: 05/30/2008 +:copyright: Copyright (2008) Mateusz Biliński +:license: GPL +''' + +from pluginmanager import PluginManager +from plugin import GajimPlugin + +__all__ = ['PluginManager', 'GajimPlugin'] diff --git a/src/plugins/helpers.py b/src/plugins/helpers.py new file mode 100644 index 000000000..744f209ac --- /dev/null +++ b/src/plugins/helpers.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- + +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## + +''' +Helper code related to plug-ins management system. + +:author: Mateusz Biliński +:since: 05/30/2008 +:copyright: Copyright (2008) Mateusz Biliński +:license: GPL +''' + +__all__ = ['log', 'log_calls', 'Singleton'] + +import logging +log = logging.getLogger('gajim.plugin_system') +''' +Logger for code related to plug-in system. + +:type: logging.Logger +''' + +consoleloghandler = logging.StreamHandler() +consoleloghandler.setLevel(1) +consoleloghandler.setFormatter( + logging.Formatter('%(levelname)s: %(message)s')) + #logging.Formatter('%(asctime)s %(name)s: %(levelname)s: %(message)s')) +log.setLevel(logging.DEBUG) +log.addHandler(consoleloghandler) +log.propagate = False + +import functools + +class log_calls(object): + ''' + Decorator class for functions to easily log when they are entered and left. + ''' + + def __init__(self, classname='', log=log): + ''' + :Keywords: + classname : str + Name of class to prefix function name (if function is a method). + log : logging.Logger + Logger to use when outputing debug information on when function has + been entered and when left. By default: `plugins.helpers.log` + is used. + ''' + + self.full_func_name = '' + ''' + Full name of function, with class name (as prefix) if given + to decorator. + + Otherwise, it's only function name retrieved from function object + for which decorator was called. + + :type: str + ''' + + if classname: + self.full_func_name = classname+'.' + + def __call__(self, f): + ''' + :param f: function to be wrapped with logging statements + + :return: given function wrapped by *log.debug* statements + :rtype: function + ''' + self.full_func_name += f.func_name + @functools.wraps(f) + def wrapper(*args, **kwargs): + log.debug('%(funcname)s() '%{ + 'funcname': self.full_func_name}) + result = f(*args, **kwargs) + log.debug('%(funcname)s() '%{ + 'funcname': self.full_func_name}) + return result + return wrapper + +class Singleton(type): + ''' + Singleton metaclass. + ''' + def __init__(cls,name,bases,dic): + super(Singleton,cls).__init__(name,bases,dic) + cls.instance=None + + def __call__(cls,*args,**kw): + if cls.instance is None: + cls.instance=super(Singleton,cls).__call__(*args,**kw) + log.debug('%(classname)s - new instance created'%{ + 'classname' : cls.__name__}) + else: + log.debug('%(classname)s - returning already existing instance'%{ + 'classname' : cls.__name__}) + + return cls.instance \ No newline at end of file diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py new file mode 100644 index 000000000..d3bfa62c9 --- /dev/null +++ b/src/plugins/plugin.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- + +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## + +''' +Base class for implementing plugin. + +:author: Mateusz Biliński +:since: 06/01/2008 +:copyright: Copyright (2008) Mateusz Biliński +:license: GPL +''' + +import sys +from plugins.helpers import log, log_calls + +class GajimPlugin(object): + name = '' + short_name = '' + version = '' + description = '' + authors = [] + gui_extension_points = {} + + @log_calls('GajimPlugin') + def __init__(self): + pass \ No newline at end of file diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py new file mode 100644 index 000000000..59c3bad11 --- /dev/null +++ b/src/plugins/pluginmanager.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- + +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## + +''' +Helper code related to plug-ins management system. + +:author: Mateusz Biliński +:since: 05/30/2008 +:copyright: Copyright (2008) Mateusz Biliński +:license: GPL +''' + +__all__ = ['PluginManager'] + +import os +import sys +import fnmatch + +import common.gajim as gajim + +from helpers import log, log_calls, Singleton +from plugin import GajimPlugin + +class PluginManager(object): + __metaclass__ = Singleton + + @log_calls('PluginManager') + def __init__(self): + self.plugins = [] + self.active = [] + self.gui_extension_points = {} + + for path in gajim.PLUGINS_DIRS: + self.plugins.extend(self._scan_dir_for_plugins(path)) + + log.debug('plugins: %s'%(self.plugins)) + + self._activate_all_plugins() + + log.debug('active: %s'%(self.active)) + + @log_calls('PluginManager') + def gui_extension_point(self, gui_extpoint_name, *args): + if gui_extpoint_name in self.gui_extension_points: + for handlers in self.gui_extension_points[gui_extpoint_name]: + handlers[0](*args) + + @log_calls('PluginManager') + def _activate_plugin(self, plugin): + ''' + :param plugin: Plugin to be activated. + :type plugin: class object of GajimPlugin subclass + ''' + p = plugin() + + success = True + + # :fix: what if only some handlers are successfully connected? we should + # revert all those connections that where successfully made. Maybe + # call 'self._deactivate_plugin()' or sth similar. + # Looking closer - we only rewrite tuples here. Real check should be + # made in method that invokes gui_extpoints handlers. + for gui_extpoint_name, gui_extpoint_handlers in \ + p.gui_extension_points.iteritems(): + self.gui_extension_points.setdefault(gui_extpoint_name,[]).append( + gui_extpoint_handlers) + + if success: + self.active.append(p) + + return success + + @log_calls('PluginManager') + def _activate_all_plugins(self): + self.active = [] + for plugin in self.plugins: + self._activate_plugin(plugin) + + @log_calls('PluginManager') + def _scan_dir_for_plugins(self, path): + plugins_found = [] + if os.path.isdir(path): + dir_list = os.listdir(path) + log.debug(dir_list) + + sys.path.insert(0, path) + log.debug(sys.path) + + for file in fnmatch.filter(dir_list, '*.py'): + log.debug('- "%s"'%(file)) + file_path = os.path.join(path, file) + log.debug(' "%s"'%(file_path)) + if os.path.isfile(file_path): + module_name = os.path.splitext(file)[0] + module = __import__(module_name) + filter_out_bad_names = \ + lambda x: not (x.startswith('__') or + x.endswith('__')) + for module_attr_name in filter(filter_out_bad_names, + dir(module)): + module_attr = getattr(module, module_attr_name) + log.debug('%s : %s'%(module_attr_name, module_attr)) + + try: + if issubclass(module_attr, GajimPlugin) and \ + not module_attr is GajimPlugin: + log.debug('is subclass of GajimPlugin') + plugins_found.append(module_attr) + except TypeError, e: + log.debug('module_attr: %s, error : %s'%( + module_name+'.'+module_attr_name, + e)) + + log.debug(module) + + return plugins_found \ No newline at end of file diff --git a/test/test_pluginmanager.py b/test/test_pluginmanager.py new file mode 100644 index 000000000..5c6723bcb --- /dev/null +++ b/test/test_pluginmanager.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## + +''' +Testing PluginManager class. + +:author: Mateusz Biliński +:since: 05/30/2008 +:copyright: Copyright (2008) Mateusz Biliński +:license: GPL +''' + +import sys +import os +import unittest + +gajim_root = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..') +sys.path.append(gajim_root + '/src') + +from plugins import PluginManager + +class PluginManagerTestCase(unittest.TestCase): + def setUp(self): + self.pluginmanager = PluginManager() + + def tearDown(self): + pass + + def test_01_Singleton(self): + """ 1. Checking whether PluginManger class is singleton. """ + self.pluginmanager.test_arg = 1 + secondPluginManager = PluginManager() + + self.failUnlessEqual(id(secondPluginManager), id(self.pluginmanager), + 'Different IDs in references to PluginManager objects (not a singleton)') + self.failUnlessEqual(secondPluginManager.test_arg, 1, + 'References point to different PluginManager objects (not a singleton') + +def suite(): + suite = unittest.TestLoader().loadTestsFromTestCase(PluginManagerTestCase) + return suite + +if __name__=='__main__': + runner = unittest.TextTestRunner() + test_suite = suite() + runner.run(test_suite) \ No newline at end of file From f62698e28c8b1525fab7bfaac53b9859d59e5991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Mon, 2 Jun 2008 23:15:08 +0000 Subject: [PATCH 002/200] Added docstrings in reST format (also with todos). Commented out 'print' statements related to roster window. A few modifications to make code prettier (PyLint driven). --- epydoc.conf | 4 +- src/plugins/plugin.py | 52 +++++++++++- src/plugins/pluginmanager.py | 157 ++++++++++++++++++++++++++--------- src/roster_window.py | 20 ++--- test/test_pluginmanager.py | 74 ++++++++++++----- 5 files changed, 232 insertions(+), 75 deletions(-) diff --git a/epydoc.conf b/epydoc.conf index 092f7f1a5..580067d0d 100644 --- a/epydoc.conf +++ b/epydoc.conf @@ -8,12 +8,12 @@ verbosity: 3 imports: yes redundant-details: yes docformat: restructuredtext -# top: gajim +top: plugins # The list of modules to document. Modules can be named using # dotted names, module filenames, or package directory names. # This option may be repeated. -modules: src/plugins/*.py +modules: src/plugins/*.py test/*.py # Write html output to the directory "apidocs" #output: pdf diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py index d3bfa62c9..c8bb95af9 100644 --- a/src/plugins/plugin.py +++ b/src/plugins/plugin.py @@ -24,16 +24,64 @@ Base class for implementing plugin. :license: GPL ''' -import sys -from plugins.helpers import log, log_calls +from plugins.helpers import log_calls class GajimPlugin(object): + ''' + Base class for implementing Gajim plugins. + ''' name = '' + ''' + Name of plugin. + + Will be shown in plugins management GUI. + + :type: unicode + ''' short_name = '' + ''' + Short name of plugin. + + Used for quick indentification of plugin. + + :type: unicode + + :todo: decide whether we really need this one, because class name (with + module name) can act as such short name + ''' version = '' + ''' + Version of plugin. + + :type: unicode + + :todo: decide how to compare version between each other (which one + is higher). Also rethink: do we really need to compare versions + of plugins between each other? This would be only useful if we detect + same plugin class but with different version and we want only the newest + one to be active - is such policy good? + ''' description = '' + ''' + Plugin description. + + :type: unicode + + :todo: should be allow rich text here (like HTML or reStructuredText)? + ''' authors = [] + ''' + Plugin authors. + + :type: [] of unicode + + :todo: should we decide on any particular format of author strings? + Especially: should we force format of giving author's e-mail? + ''' gui_extension_points = {} + ''' + Extension points that plugin wants to connect with. + ''' @log_calls('GajimPlugin') def __init__(self): diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py index 59c3bad11..0498b5fd3 100644 --- a/src/plugins/pluginmanager.py +++ b/src/plugins/pluginmanager.py @@ -16,7 +16,7 @@ ## ''' -Helper code related to plug-ins management system. +Plug-in management related classes. :author: Mateusz Biliński :since: 05/30/2008 @@ -32,29 +32,91 @@ import fnmatch import common.gajim as gajim -from helpers import log, log_calls, Singleton -from plugin import GajimPlugin +from plugins.helpers import log, log_calls, Singleton +from plugins.plugin import GajimPlugin class PluginManager(object): + ''' + Main plug-in management class. + + Currently: + - scans for plugins + - activates them + - handles GUI extension points, when called by GUI objects after plugin + is activated (by dispatching info about call to handlers in plugins) + + :todo: add more info about how GUI extension points work + :todo: add list of available GUI extension points + :todo: implement mechanism to dynamically load plugins where GUI extension + points have been already called (i.e. when plugin is activated + after GUI object creation) + :todo: implement mechanism to dynamically deactive plugins (call plugin's + deactivation handler) + + ''' + __metaclass__ = Singleton @log_calls('PluginManager') def __init__(self): self.plugins = [] - self.active = [] + ''' + Detected plugin classes. + + Each class object in list is `GajimPlugin` subclass. + + :type: [] of class objects + ''' + self.active_plugins = [] + ''' + Objectsof active plugins. + + These are object instances of classes held `plugins`, but only those + that were activated. + + :type: [] of `GajimPlugin` based objects + ''' self.gui_extension_points = {} + ''' + Registered GUI extension points. + ''' for path in gajim.PLUGINS_DIRS: - self.plugins.extend(self._scan_dir_for_plugins(path)) + self.plugins.extend(PluginManager.scan_dir_for_plugins(path)) log.debug('plugins: %s'%(self.plugins)) - + self._activate_all_plugins() - - log.debug('active: %s'%(self.active)) - + + log.debug('active: %s'%(self.active_plugins)) + @log_calls('PluginManager') def gui_extension_point(self, gui_extpoint_name, *args): + ''' + Invokes all handlers (from plugins) for particular GUI extension point. + + :param gui_extpoint_name: name of GUI extension point. + :type gui_extpoint_name: unicode + :param args: parameters to be passed to extension point handlers + (typically and object that invokes `gui_extension_point`; however, + this can be practically anything) + :type args: tuple + + :todo: GUI extension points must be documented well - names with + parameters that will be passed to handlers (in plugins). Such + documentation must be obeyed both in core and in plugins. This + is a loosely coupled approach and is pretty natural in Python. + + :bug: what if only some handlers are successfully connected? we should + revert all those connections that where successfully made. Maybe + call 'self._deactivate_plugin()' or sth similar. + Looking closer - we only rewrite tuples here. Real check should be + made in method that invokes gui_extpoints handlers. + + ''' + + log.debug(type(args)) + if gui_extpoint_name in self.gui_extension_points: for handlers in self.gui_extension_points[gui_extpoint_name]: handlers[0](*args) @@ -62,36 +124,53 @@ class PluginManager(object): @log_calls('PluginManager') def _activate_plugin(self, plugin): ''' - :param plugin: Plugin to be activated. - :type plugin: class object of GajimPlugin subclass + :param plugin: plugin to be activated + :type plugin: class object of `GajimPlugin` subclass ''' - p = plugin() + plugin_object = plugin() + success = True - - # :fix: what if only some handlers are successfully connected? we should - # revert all those connections that where successfully made. Maybe - # call 'self._deactivate_plugin()' or sth similar. - # Looking closer - we only rewrite tuples here. Real check should be - # made in method that invokes gui_extpoints handlers. + for gui_extpoint_name, gui_extpoint_handlers in \ - p.gui_extension_points.iteritems(): - self.gui_extension_points.setdefault(gui_extpoint_name,[]).append( + plugin_object.gui_extension_points.iteritems(): + self.gui_extension_points.setdefault(gui_extpoint_name, []).append( gui_extpoint_handlers) - + if success: - self.active.append(p) - + self.active_plugins.append(plugin_object) + return success - + @log_calls('PluginManager') def _activate_all_plugins(self): - self.active = [] + ''' + Activates all plugins in `plugins`. + + Activated plugins are appended to `active_plugins` list. + ''' + self.active_plugins = [] for plugin in self.plugins: self._activate_plugin(plugin) + @staticmethod @log_calls('PluginManager') - def _scan_dir_for_plugins(self, path): + def scan_dir_for_plugins(path): + ''' + Scans given directory for plugin classes. + + :param path: directory to scan for plugins + :type path: unicode + + :return: list of found plugin classes (subclasses of `GajimPlugin` + :rtype: [] of class objects + + :note: currently it only searches for plugin classes in '\*.py' files + present in given direcotory `path` (no recursion here) + + :todo: add scanning packages + :todo: add scanning zipped modules + ''' plugins_found = [] if os.path.isdir(path): dir_list = os.listdir(path) @@ -100,30 +179,28 @@ class PluginManager(object): sys.path.insert(0, path) log.debug(sys.path) - for file in fnmatch.filter(dir_list, '*.py'): - log.debug('- "%s"'%(file)) - file_path = os.path.join(path, file) + for file_name in fnmatch.filter(dir_list, '*.py'): + log.debug('- "%s"'%(file_name)) + file_path = os.path.join(path, file_name) log.debug(' "%s"'%(file_path)) if os.path.isfile(file_path): - module_name = os.path.splitext(file)[0] + module_name = os.path.splitext(file_name)[0] module = __import__(module_name) - filter_out_bad_names = \ - lambda x: not (x.startswith('__') or - x.endswith('__')) - for module_attr_name in filter(filter_out_bad_names, - dir(module)): + for module_attr_name in [f_name for f_name in dir(module) + if not (f_name.startswith('__') or + f_name.endswith('__'))]: module_attr = getattr(module, module_attr_name) log.debug('%s : %s'%(module_attr_name, module_attr)) - + try: if issubclass(module_attr, GajimPlugin) and \ - not module_attr is GajimPlugin: + not module_attr is GajimPlugin: log.debug('is subclass of GajimPlugin') plugins_found.append(module_attr) - except TypeError, e: + except TypeError, type_error: log.debug('module_attr: %s, error : %s'%( - module_name+'.'+module_attr_name, - e)) + module_name+'.'+module_attr_name, + type_error)) log.debug(module) diff --git a/src/roster_window.py b/src/roster_window.py index bc5268174..9af3d59b6 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -310,13 +310,13 @@ class RosterWindow: if jids: c4 = time.clock() - print "" - print "--- Add account contacts of %s ---------" % account - print "Total Time", c4-c1 - print "Add contact without draw", c6-c5 - print "Draw groups and account", c10-c9 - print "--- contacts added -----------------------------" - print "" + #print "" + #print "--- Add account contacts of %s ---------" % account + #print "Total Time", c4-c1 + #print "Add contact without draw", c6-c5 + #print "Draw groups and account", c10-c9 + #print "--- contacts added -----------------------------" + #print "" def _add_entity(self, contact, account, groups = None, @@ -1216,9 +1216,9 @@ class RosterWindow: self.draw_contact(jid, account) self.draw_avatar(jid, account) yield True - print "--- Idle draw of %s -----------" % account - print "Draw contact and avatar", time.clock() - t - print "-------------------------------" + #print "--- Idle draw of %s -----------" % account + #print "Draw contact and avatar", time.clock() - t + #print "-------------------------------" yield False t = time.clock() diff --git a/test/test_pluginmanager.py b/test/test_pluginmanager.py index 5c6723bcb..861641ac2 100644 --- a/test/test_pluginmanager.py +++ b/test/test_pluginmanager.py @@ -32,30 +32,62 @@ import unittest gajim_root = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..') sys.path.append(gajim_root + '/src') +# a temporary version of ~/.gajim for testing +configdir = gajim_root + '/test/tmp' + +import time + +# define _ for i18n +import __builtin__ +__builtin__._ = lambda x: x + +# wipe config directory +import os +if os.path.isdir(configdir): + import shutil + shutil.rmtree(configdir) + +os.mkdir(configdir) + +import common.configpaths +common.configpaths.gajimpaths.init(configdir) +common.configpaths.gajimpaths.init_profile() + +# for some reason common.gajim needs to be imported before xmpppy? +from common import gajim +from common import xmpp + +gajim.DATA_DIR = gajim_root + '/data' + +from common.stanza_session import StanzaSession + +# name to use for the test account +account_name = 'test' + from plugins import PluginManager class PluginManagerTestCase(unittest.TestCase): - def setUp(self): - self.pluginmanager = PluginManager() - - def tearDown(self): - pass - - def test_01_Singleton(self): - """ 1. Checking whether PluginManger class is singleton. """ - self.pluginmanager.test_arg = 1 - secondPluginManager = PluginManager() - - self.failUnlessEqual(id(secondPluginManager), id(self.pluginmanager), - 'Different IDs in references to PluginManager objects (not a singleton)') - self.failUnlessEqual(secondPluginManager.test_arg, 1, - 'References point to different PluginManager objects (not a singleton') - + def setUp(self): + self.pluginmanager = PluginManager() + + def tearDown(self): + pass + + def test_01_Singleton(self): + """ 1. Checking whether PluginManger class is singleton. """ + self.pluginmanager.test_arg = 1 + secondPluginManager = PluginManager() + + self.failUnlessEqual(id(secondPluginManager), id(self.pluginmanager), + 'Different IDs in references to PluginManager objects (not a singleton)') + self.failUnlessEqual(secondPluginManager.test_arg, 1, + 'References point to different PluginManager objects (not a singleton') + def suite(): - suite = unittest.TestLoader().loadTestsFromTestCase(PluginManagerTestCase) - return suite + suite = unittest.TestLoader().loadTestsFromTestCase(PluginManagerTestCase) + return suite if __name__=='__main__': - runner = unittest.TextTestRunner() - test_suite = suite() - runner.run(test_suite) \ No newline at end of file + runner = unittest.TextTestRunner() + test_suite = suite() + runner.run(test_suite) \ No newline at end of file From 77f10031f1019eb3628e30f5edb3da7699fb0f1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Tue, 3 Jun 2008 08:25:16 +0000 Subject: [PATCH 003/200] Added mechanism to successfully load plugins after GUI extension points have been created, e.g. when we want to modify ChatControl behaviour and objects of this class have already been created. Also: customized IPython console look --- src/gajim.py | 3 +++ src/plugins/pluginmanager.py | 44 ++++++++++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/gajim.py b/src/gajim.py index d1d746401..19c98d5cd 100755 --- a/src/gajim.py +++ b/src/gajim.py @@ -3208,6 +3208,7 @@ class Interface: font = 'Lucida Console 9' else: font = 'Luxi Mono 10' + font="Terminus 10" window = gtk.Window() window.set_size_request(750,550) @@ -3217,6 +3218,8 @@ class Interface: view = IPythonView() view.modify_font(pango.FontDescription(font)) view.set_wrap_mode(gtk.WRAP_CHAR) + view.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("black")); + view.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("white")); sw.add(view) window.add(sw) window.show_all() diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py index 0498b5fd3..14f174df7 100644 --- a/src/plugins/pluginmanager.py +++ b/src/plugins/pluginmanager.py @@ -52,7 +52,6 @@ class PluginManager(object): after GUI object creation) :todo: implement mechanism to dynamically deactive plugins (call plugin's deactivation handler) - ''' __metaclass__ = Singleton @@ -80,13 +79,18 @@ class PluginManager(object): ''' Registered GUI extension points. ''' + + self.gui_extension_points_handlers = {} + ''' + Registered handlers of GUI extension points. + ''' for path in gajim.PLUGINS_DIRS: self.plugins.extend(PluginManager.scan_dir_for_plugins(path)) log.debug('plugins: %s'%(self.plugins)) - self._activate_all_plugins() + #self._activate_all_plugins() log.debug('active: %s'%(self.active_plugins)) @@ -115,10 +119,17 @@ class PluginManager(object): ''' - log.debug(type(args)) - - if gui_extpoint_name in self.gui_extension_points: - for handlers in self.gui_extension_points[gui_extpoint_name]: + self._add_gui_extension_point_call_to_list(gui_extpoint_name, *args) + self._execute_all_handlers_of_gui_extension_point(gui_extpoint_name, *args) + + @log_calls('PluginManager') + def _add_gui_extension_point_call_to_list(self, gui_extpoint_name, *args): + self.gui_extension_points.setdefault(gui_extpoint_name, []).append(args) + + @log_calls('PluginManager') + def _execute_all_handlers_of_gui_extension_point(self, gui_extpoint_name, *args): + if gui_extpoint_name in self.gui_extension_points_handlers: + for handlers in self.gui_extension_points_handlers[gui_extpoint_name]: handlers[0](*args) @log_calls('PluginManager') @@ -132,15 +143,28 @@ class PluginManager(object): success = True - for gui_extpoint_name, gui_extpoint_handlers in \ - plugin_object.gui_extension_points.iteritems(): - self.gui_extension_points.setdefault(gui_extpoint_name, []).append( - gui_extpoint_handlers) + self._add_gui_extension_points_handlers_from_plugin(plugin_object) + self._handle_all_gui_extension_points_with_plugin(plugin_object) if success: self.active_plugins.append(plugin_object) return success + + @log_calls('PluginManager') + def _add_gui_extension_points_handlers_from_plugin(self, plugin_object): + for gui_extpoint_name, gui_extpoint_handlers in \ + plugin_object.gui_extension_points.iteritems(): + self.gui_extension_points_handlers.setdefault(gui_extpoint_name, []).append( + gui_extpoint_handlers) + + @log_calls('PluginManager') + def _handle_all_gui_extension_points_with_plugin(self, plugin_object): + for gui_extpoint_name, gui_extpoint_handlers in \ + plugin_object.gui_extension_points.iteritems(): + if gui_extpoint_name in self.gui_extension_points: + for gui_extension_point_args in self.gui_extension_points[gui_extpoint_name]: + gui_extpoint_handlers[0](*gui_extension_point_args) @log_calls('PluginManager') def _activate_all_plugins(self): From 654e157effd3a808b1727acb0e51d5371f2caada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Tue, 3 Jun 2008 13:40:27 +0000 Subject: [PATCH 004/200] Added plug-in deactivation mechanism, which allows plug-ins to clean up after themselves (eg. disconnecting handlers made in GUI); GUI extension points handlers are removed from list. Updated Length Notifier plug-in so that it can be properly deactivated. --- plugins/length_notifier.py | 17 +++++++++---- src/plugins/pluginmanager.py | 46 +++++++++++++++++++++++++++++------- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/plugins/length_notifier.py b/plugins/length_notifier.py index bd0f9564f..af85ded5f 100644 --- a/plugins/length_notifier.py +++ b/plugins/length_notifier.py @@ -74,9 +74,17 @@ length of message is exceeded.''' if self.jid_is_ok(jid): d = {'prev_color' : None} tv = chat_control.msg_textview - b = tv.get_buffer() - h_id = b.connect('changed', self.textview_length_warning, chat_control) + tb = tv.get_buffer() + h_id = tb.connect('changed', self.textview_length_warning, chat_control) d['h_id'] = h_id + + t = tb.get_text(tb.get_start_iter(), tb.get_end_iter()) + if t: + len_t = len(t) + if len_t>self.MESSAGE_WARNING_LENGTH: + d['prev_color'] = tv.style.copy().base[gtk.STATE_NORMAL] + tv.modify_base(gtk.STATE_NORMAL, self.WARNING_COLOR) + chat_control.length_notifier_plugin_data = d return True @@ -86,9 +94,10 @@ length of message is exceeded.''' @log_calls('LengthNotifierPlugin') def disconnect_from_chat_control(self, chat_control): d = chat_control.length_notifier_plugin_data - chat_control.msg_textview.get_buffer().disconnect(d['h_id']) + tv = chat_control.msg_textview + tv.get_buffer().disconnect(d['h_id']) if d['prev_color']: - tv.modify_base(gtk.STATE_NORMAL, self.PREV_COLOR) + tv.modify_base(gtk.STATE_NORMAL, d['prev_color']) @log_calls('LengthNotifierPlugin') def jid_is_ok(self, jid): diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py index 14f174df7..b9e113793 100644 --- a/src/plugins/pluginmanager.py +++ b/src/plugins/pluginmanager.py @@ -49,14 +49,22 @@ class PluginManager(object): :todo: add list of available GUI extension points :todo: implement mechanism to dynamically load plugins where GUI extension points have been already called (i.e. when plugin is activated - after GUI object creation) + after GUI object creation). [DONE?] :todo: implement mechanism to dynamically deactive plugins (call plugin's - deactivation handler) + deactivation handler) [DONE?] + :todo: when plug-in is deactivated all GUI extension points are removed + from `PluginManager.gui_extension_points_handlers`. But when + object that invoked GUI extension point is abandoned by Gajim, eg. + closed ChatControl object, the reference to called GUI extension + points is still in `PluginManager.gui_extension_points`. These + should be removed, so that object can be destroyed by Python. + Possible solution: add call to clean up method in classes + 'destructors' (classes that register GUI extension points) ''' __metaclass__ = Singleton - @log_calls('PluginManager') + #@log_calls('PluginManager') def __init__(self): self.plugins = [] ''' @@ -68,7 +76,7 @@ class PluginManager(object): ''' self.active_plugins = [] ''' - Objectsof active plugins. + Instance objects of active plugins. These are object instances of classes held `plugins`, but only those that were activated. @@ -90,7 +98,7 @@ class PluginManager(object): log.debug('plugins: %s'%(self.plugins)) - #self._activate_all_plugins() + self.activate_all_plugins() log.debug('active: %s'%(self.active_plugins)) @@ -133,7 +141,7 @@ class PluginManager(object): handlers[0](*args) @log_calls('PluginManager') - def _activate_plugin(self, plugin): + def activate_plugin(self, plugin): ''' :param plugin: plugin to be activated :type plugin: class object of `GajimPlugin` subclass @@ -151,6 +159,28 @@ class PluginManager(object): return success + def deactivate_plugin(self, plugin_object): + # detaching plug-in from handler GUI extension points (calling + # cleaning up method that must be provided by plug-in developer + # for each handled GUI extension point) + for gui_extpoint_name, gui_extpoint_handlers in \ + plugin_object.gui_extension_points.iteritems(): + for gui_extension_point_args in self.gui_extension_points[gui_extpoint_name]: + gui_extpoint_handlers[1](*gui_extension_point_args) + + # remove GUI extension points handlers (provided by plug-in) from + # handlers list + for gui_extpoint_name, gui_extpoint_handlers in \ + plugin_object.gui_extension_points.iteritems(): + self.gui_extension_points_handlers[gui_extpoint_name].remove(gui_extpoint_handlers) + + # removing plug-in from active plug-ins list + self.active_plugins.remove(plugin_object) + + def deactivate_all_plugins(self): + for plugin_object in self.active_plugins: + self.deactivate_plugin(plugin_object) + @log_calls('PluginManager') def _add_gui_extension_points_handlers_from_plugin(self, plugin_object): for gui_extpoint_name, gui_extpoint_handlers in \ @@ -167,7 +197,7 @@ class PluginManager(object): gui_extpoint_handlers[0](*gui_extension_point_args) @log_calls('PluginManager') - def _activate_all_plugins(self): + def activate_all_plugins(self): ''' Activates all plugins in `plugins`. @@ -175,7 +205,7 @@ class PluginManager(object): ''' self.active_plugins = [] for plugin in self.plugins: - self._activate_plugin(plugin) + self.activate_plugin(plugin) @staticmethod @log_calls('PluginManager') From e127925948637812adfde977a6973b52f6968b1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Sat, 7 Jun 2008 17:28:34 +0000 Subject: [PATCH 005/200] Added first version of 'Plugins' window. It's accessible through 'Edit/Plugins' item in roster menu. It seems that you can successfully (de)activate plug-ins through GUI now. Added 'homepage' attribute to Plugin class. Added (commented out) calls of pycallgraph in src/gajim.py for later use. [xbright] Changed 'python' to 'python2.5' because code uses modules not available in previous versions of Python. --- data/glade/plugins_window.glade | 602 ++++++++++++++++++++++++++++++++ data/glade/roster_window.glade | 15 + launch.sh | 2 +- plugins/length_notifier.py | 12 +- src/gajim.py | 7 + src/plugins/gui.py | 166 +++++++++ src/plugins/plugin.py | 17 +- src/plugins/pluginmanager.py | 32 +- src/roster_window.py | 8 + 9 files changed, 844 insertions(+), 17 deletions(-) create mode 100644 data/glade/plugins_window.glade create mode 100644 src/plugins/gui.py diff --git a/data/glade/plugins_window.glade b/data/glade/plugins_window.glade new file mode 100644 index 000000000..d8996b53e --- /dev/null +++ b/data/glade/plugins_window.glade @@ -0,0 +1,602 @@ + + + + + + 650 + 500 + 6 + Plugins + 650 + 500 + + + + True + 6 + + + True + True + + + True + True + 250 + True + + + True + True + 6 + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + True + + + + + False + True + + + + + True + 6 + 6 + + + True + 6 + + + True + 0 + <empty> + True + True + True + + + + + True + + + + + + 1 + + + + + False + + + + + True + 6 + + + True + Version: + + + False + + + + + True + 0 + <empty> + True + + + 1 + + + + + False + 1 + + + + + True + 6 + + + True + 0 + Authors: + + + False + + + + + True + 0 + 0 + <empty> + PANGO_WRAP_WORD_CHAR + True + PANGO_ELLIPSIZE_END + + + 1 + + + + + False + 2 + + + + + True + + + True + Homepage: + + + False + + + + + True + True + True + homepage url + GTK_RELIEF_NONE + False + 0 + 0 + + + + False + 1 + + + + + False + 3 + + + + + True + + + True + + + True + Descrition: + + + False + + + + + True + + + + + + 1 + + + + + False + + + + + True + False + True + 6 + False + GTK_WRAP_WORD + 6 + 6 + 1 + Plug-in decription should be displayed here. This text will be erased during PluginsWindow initialization. + + + 1 + + + + + 4 + + + + + True + 5 + GTK_BUTTONBOX_END + + + True + True + True + 0 + + + True + + + True + gtk-cancel + + + + + True + Uninstall + + + 1 + + + + + + + + + True + True + True + 0 + + + True + + + True + gtk-preferences + + + + + True + Configure + + + 1 + + + + + + + 1 + + + + + False + 5 + + + + + True + True + + + + + + + True + Installed + + + tab + False + + + + + True + True + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + True + + + + + False + True + + + + + True + + + True + + + True + <empty> + True + + + False + + + + + True + + + + + + 1 + + + + + False + + + + + True + + + True + Version: + + + False + + + + + True + <empty> + True + + + False + 1 + + + + + False + 1 + + + + + True + + + True + Authors: + + + False + + + + + True + <empty> + True + + + False + 1 + + + + + False + 2 + + + + + True + + + True + Homepage: + + + False + + + + + True + True + True + button + GTK_RELIEF_NONE + False + 0 + + + + False + 1 + + + + + False + 3 + + + + + True + + + True + + + True + Descrition: + + + False + + + + + True + + + + + + 1 + + + + + False + + + + + True + True + + + 1 + + + + + 4 + + + + + True + GTK_BUTTONBOX_END + + + True + True + True + button + 0 + + + + + True + True + True + button + 0 + + + 1 + + + + + False + 5 + + + + + True + True + + + + + 1 + + + + + Available + + + tab + 1 + False + + + + + + + + + tab + + + + + + + True + 15 + GTK_BUTTONBOX_END + + + True + True + True + gtk-close + True + 0 + + + + + + False + 1 + + + + + + diff --git a/data/glade/roster_window.glade b/data/glade/roster_window.glade index c480f6879..86ca8f2d1 100644 --- a/data/glade/roster_window.glade +++ b/data/glade/roster_window.glade @@ -193,6 +193,21 @@ + + + True + P_lugins + True + + + + True + gtk-disconnect + 1 + + + + diff --git a/launch.sh b/launch.sh index cfaf9cd68..d816e833c 100755 --- a/launch.sh +++ b/launch.sh @@ -15,4 +15,4 @@ if [ "x${OS}" == "xDarwin" ]; then fi cd ${BASE}/src -exec -a gajim python -t gajim.py $@ +exec -a gajim python2.5 -t gajim.py $@ diff --git a/plugins/length_notifier.py b/plugins/length_notifier.py index af85ded5f..7c9f2422a 100644 --- a/plugins/length_notifier.py +++ b/plugins/length_notifier.py @@ -32,12 +32,12 @@ from plugins import GajimPlugin from plugins.helpers import log, log_calls class LengthNotifierPlugin(GajimPlugin): - name = 'Message Length Notifier' - short_name = 'length_notifier' - version = '0.1' - description = '''Highlights message entry field in chat window when given -length of message is exceeded.''' - authors = ['Mateusz Biliński '] + name = u'Message Length Notifier' + short_name = u'length_notifier' + version = u'0.1' + description = u'''Highlights message entry field in chat window when given length of message is exceeded.''' + authors = [u'Mateusz Biliński '] + homepage = u'http://blog.bilinski.it' @log_calls('LengthNotifierPlugin') def __init__(self): diff --git a/src/gajim.py b/src/gajim.py index 19c98d5cd..69d64e0c4 100755 --- a/src/gajim.py +++ b/src/gajim.py @@ -26,6 +26,8 @@ ## import os +import pycallgraph + if os.name == 'nt': import warnings @@ -602,6 +604,8 @@ def on_exit(): if sys.platform == 'darwin': import osx osx.shutdown() + + #pycallgraph.make_dot_graph('common.xmpp-only.dot', format='dot') import atexit atexit.register(on_exit) @@ -3231,6 +3235,9 @@ class Interface: gajim.ipython_window = window def __init__(self): + #filter_func = pycallgraph.GlobbingFilter(include=['common.xmpp.*']) + #pycallgraph.start_trace(filter_func=filter_func) + gajim.interface = self # This is the manager and factory of message windows set by the module self.msg_win_mgr = None diff --git a/src/plugins/gui.py b/src/plugins/gui.py new file mode 100644 index 000000000..c4628958b --- /dev/null +++ b/src/plugins/gui.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- + +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## + +''' +GUI classes related to plug-in management. + +:author: Mateusz Biliński +:since: 06/06/2008 +:copyright: Copyright (2008) Mateusz Biliński +:license: GPL +''' + +__all__ = ['PluginsWindow'] + +import pango +import gtk, gobject + +import gtkgui_helpers +from common import gajim + +from plugins.helpers import log_calls, log + +class PluginsWindow(object): + '''Class for Plugins window''' + + @log_calls('PluginsWindow') + def __init__(self): + '''Initialize Plugins window''' + self.xml = gtkgui_helpers.get_glade('plugins_window.glade') + self.window = self.xml.get_widget('plugins_window') + self.window.set_transient_for(gajim.interface.roster.window) + + widgets_to_extract = ('plugins_notebook', + 'plugin_name_label', + 'plugin_version_label', + 'plugin_authors_label', + 'plugin_homepage_linkbutton', + 'plugin_description_textview', + 'uninstall_plugin_button', + 'configure_plugin_button', + 'installed_plugins_treeview') + + for widget_name in widgets_to_extract: + setattr(self, widget_name, self.xml.get_widget(widget_name)) + + attr_list = pango.AttrList() + attr_list.insert(pango.AttrWeight(pango.WEIGHT_BOLD, 0, -1)) + self.plugin_name_label.set_attributes(attr_list) + + self.installed_plugins_model = gtk.ListStore(gobject.TYPE_PYOBJECT, + gobject.TYPE_STRING, + gobject.TYPE_BOOLEAN) + self.installed_plugins_treeview.set_model(self.installed_plugins_model) + + renderer = gtk.CellRendererText() + col = gtk.TreeViewColumn(_('Plugin'), renderer, text=1) + self.installed_plugins_treeview.append_column(col) + + renderer = gtk.CellRendererToggle() + renderer.set_property('activatable', True) + renderer.connect('toggled', self.installed_plugins_toggled_cb) + col = gtk.TreeViewColumn(_('Active'), renderer, active=2) + self.installed_plugins_treeview.append_column(col) + + # connect signal for selection change + selection = self.installed_plugins_treeview.get_selection() + selection.connect('changed', + self.installed_plugins_treeview_selection_changed) + selection.set_mode(gtk.SELECTION_SINGLE) + + self._clear_installed_plugin_info() + + self.fill_installed_plugins_model() + + self.xml.signal_autoconnect(self) + + self.plugins_notebook.set_current_page(0) + + self.window.show_all() + gtkgui_helpers.possibly_move_window_in_current_desktop(self.window) + + @log_calls('PluginsWindow') + def installed_plugins_treeview_selection_changed(self, treeview_selection): + model, iter = treeview_selection.get_selected() + if iter: + plugin = model.get_value(iter, 0) + plugin_name = model.get_value(iter, 1) + is_active = model.get_value(iter, 2) + + self._display_installed_plugin_info(plugin) + else: + self._clear_installed_plugin_info() + + def _display_installed_plugin_info(self, plugin): + self.plugin_name_label.set_text(plugin.name) + self.plugin_version_label.set_text(plugin.version) + self.plugin_authors_label.set_text(", ".join(plugin.authors)) + self.plugin_homepage_linkbutton.set_uri(plugin.homepage) + self.plugin_homepage_linkbutton.set_label(plugin.homepage) + self.plugin_homepage_linkbutton.set_property('sensitive', True) + + desc_textbuffer = self.plugin_description_textview.get_buffer() + desc_textbuffer.set_text(plugin.description) + self.plugin_description_textview.set_property('sensitive', True) + self.uninstall_plugin_button.set_property('sensitive', True) + self.configure_plugin_button.set_property('sensitive', True) + + def _clear_installed_plugin_info(self): + self.plugin_name_label.set_text('') + self.plugin_version_label.set_text('') + self.plugin_authors_label.set_text('') + self.plugin_homepage_linkbutton.set_uri('') + self.plugin_homepage_linkbutton.set_label('') + self.plugin_homepage_linkbutton.set_property('sensitive', False) + + desc_textbuffer = self.plugin_description_textview.get_buffer() + desc_textbuffer.set_text('') + self.plugin_description_textview.set_property('sensitive', False) + self.uninstall_plugin_button.set_property('sensitive', False) + self.configure_plugin_button.set_property('sensitive', False) + + @log_calls('PluginsWindow') + def fill_installed_plugins_model(self): + pm = gajim.plugin_manager + self.installed_plugins_model.clear() + self.installed_plugins_model.set_sort_column_id(0, gtk.SORT_ASCENDING) + + for plugin_class in pm.plugins: + self.installed_plugins_model.append([plugin_class, + plugin_class.name, + plugin_class._active]) + + @log_calls('PluginsWindow') + def installed_plugins_toggled_cb(self, cell, path): + is_active = self.installed_plugins_model[path][2] + plugin_class = self.installed_plugins_model[path][0] + + if is_active: + gajim.plugin_manager.deactivate_plugin(plugin_class._instance) + else: + gajim.plugin_manager.activate_plugin(plugin_class) + + self.installed_plugins_model[path][2] = not is_active + + @log_calls('PluginsWindow') + def on_plugins_window_destroy(self, widget): + '''Close window''' + del gajim.interface.instances['plugins'] + + @log_calls('PluginsWindow') + def on_close_button_clicked(self, widget): + self.window.destroy() \ No newline at end of file diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py index c8bb95af9..fd49c6a44 100644 --- a/src/plugins/plugin.py +++ b/src/plugins/plugin.py @@ -30,7 +30,7 @@ class GajimPlugin(object): ''' Base class for implementing Gajim plugins. ''' - name = '' + name = u'' ''' Name of plugin. @@ -38,7 +38,7 @@ class GajimPlugin(object): :type: unicode ''' - short_name = '' + short_name = u'' ''' Short name of plugin. @@ -49,7 +49,7 @@ class GajimPlugin(object): :todo: decide whether we really need this one, because class name (with module name) can act as such short name ''' - version = '' + version = u'' ''' Version of plugin. @@ -61,7 +61,7 @@ class GajimPlugin(object): same plugin class but with different version and we want only the newest one to be active - is such policy good? ''' - description = '' + description = u'' ''' Plugin description. @@ -78,6 +78,15 @@ class GajimPlugin(object): :todo: should we decide on any particular format of author strings? Especially: should we force format of giving author's e-mail? ''' + homepage = u'' + ''' + URL to plug-in's homepage. + + :type: unicode + + :todo: should we check whether provided string is valid URI? (Maybe + using 'property') + ''' gui_extension_points = {} ''' Extension points that plugin wants to connect with. diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py index b9e113793..359ec3805 100644 --- a/src/plugins/pluginmanager.py +++ b/src/plugins/pluginmanager.py @@ -94,7 +94,7 @@ class PluginManager(object): ''' for path in gajim.PLUGINS_DIRS: - self.plugins.extend(PluginManager.scan_dir_for_plugins(path)) + self._add_plugins(PluginManager.scan_dir_for_plugins(path)) log.debug('plugins: %s'%(self.plugins)) @@ -102,6 +102,22 @@ class PluginManager(object): log.debug('active: %s'%(self.active_plugins)) + + @log_calls('PluginManager') + def _add_plugin(self, plugin_class): + ''' + :todo: what about adding plug-ins that are already added? Module reload + and adding class from reloaded module or ignoring adding plug-in? + ''' + plugin_class._active = False + plugin_class._instance = None + self.plugins.append(plugin_class) + + @log_calls('PluginManager') + def _add_plugins(self, plugin_classes): + for plugin_class in plugin_classes: + self._add_plugin(plugin_class) + @log_calls('PluginManager') def gui_extension_point(self, gui_extpoint_name, *args): ''' @@ -124,7 +140,6 @@ class PluginManager(object): call 'self._deactivate_plugin()' or sth similar. Looking closer - we only rewrite tuples here. Real check should be made in method that invokes gui_extpoints handlers. - ''' self._add_gui_extension_point_call_to_list(gui_extpoint_name, *args) @@ -141,13 +156,13 @@ class PluginManager(object): handlers[0](*args) @log_calls('PluginManager') - def activate_plugin(self, plugin): + def activate_plugin(self, plugin_class): ''' :param plugin: plugin to be activated :type plugin: class object of `GajimPlugin` subclass ''' - plugin_object = plugin() + plugin_object = plugin_class() success = True @@ -156,6 +171,8 @@ class PluginManager(object): if success: self.active_plugins.append(plugin_object) + plugin_class._instance = plugin_object + plugin_class._active = True return success @@ -165,8 +182,9 @@ class PluginManager(object): # for each handled GUI extension point) for gui_extpoint_name, gui_extpoint_handlers in \ plugin_object.gui_extension_points.iteritems(): - for gui_extension_point_args in self.gui_extension_points[gui_extpoint_name]: - gui_extpoint_handlers[1](*gui_extension_point_args) + if gui_extpoint_name in self.gui_extension_points: + for gui_extension_point_args in self.gui_extension_points[gui_extpoint_name]: + gui_extpoint_handlers[1](*gui_extension_point_args) # remove GUI extension points handlers (provided by plug-in) from # handlers list @@ -176,6 +194,8 @@ class PluginManager(object): # removing plug-in from active plug-ins list self.active_plugins.remove(plugin_object) + plugin_object.__class__._active = False + del plugin_object def deactivate_all_plugins(self): for plugin_object in self.active_plugins: diff --git a/src/roster_window.py b/src/roster_window.py index 9af3d59b6..76b86c81a 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -44,6 +44,8 @@ import message_control import adhoc_commands import notify import features_window +import plugins +import plugins.gui from common import gajim from common import helpers @@ -3129,6 +3131,12 @@ class RosterWindow: gajim.interface.instances['preferences'].window.present() else: gajim.interface.instances['preferences'] = config.PreferencesWindow() + + def on_plugins_menuitem_activate(self, widget): + if gajim.interface.instances.has_key('plugins'): + gajim.interface.instances['plugins'].window.present() + else: + gajim.interface.instances['plugins'] = plugins.gui.PluginsWindow() def on_publish_tune_checkbutton_toggled(self, widget, account): if widget.get_active(): From fdd36483230b5f4602afa157e608a85c873bb9fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Sun, 8 Jun 2008 16:36:46 +0000 Subject: [PATCH 006/200] Small improvements: plug-in instance object dereferenced on deactivation. 'gajim.plugin_system' logger doesn't set level DEBUG by default (have to use command-line option '-l' from now on). --- src/plugins/helpers.py | 4 ++-- src/plugins/pluginmanager.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plugins/helpers.py b/src/plugins/helpers.py index 744f209ac..3379bd300 100644 --- a/src/plugins/helpers.py +++ b/src/plugins/helpers.py @@ -35,11 +35,11 @@ Logger for code related to plug-in system. ''' consoleloghandler = logging.StreamHandler() -consoleloghandler.setLevel(1) +#consoleloghandler.setLevel(1) consoleloghandler.setFormatter( logging.Formatter('%(levelname)s: %(message)s')) #logging.Formatter('%(asctime)s %(name)s: %(levelname)s: %(message)s')) -log.setLevel(logging.DEBUG) +#log.setLevel(logging.DEBUG) log.addHandler(consoleloghandler) log.propagate = False diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py index 359ec3805..fa3ab729e 100644 --- a/src/plugins/pluginmanager.py +++ b/src/plugins/pluginmanager.py @@ -195,6 +195,7 @@ class PluginManager(object): # removing plug-in from active plug-ins list self.active_plugins.remove(plugin_object) plugin_object.__class__._active = False + plugin_object.__class__._instance = None del plugin_object def deactivate_all_plugins(self): From 5dbe58507e2dcf3541a07f6cff0f9e95c0b93899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Mon, 9 Jun 2008 11:46:29 +0000 Subject: [PATCH 007/200] Added 'chat_control_base' GUI extension point (now we are able to modify all chat controls - both normal and GC. Added base version of AcronymsExpanderPlugin with some hardcoded acronyms. It uses newly added 'chat_control_base' extension point. --- plugins/acronyms_expander.py | 101 +++++++++++++++++++++++++++++++++++ src/chat_control.py | 2 + 2 files changed, 103 insertions(+) create mode 100644 plugins/acronyms_expander.py diff --git a/plugins/acronyms_expander.py b/plugins/acronyms_expander.py new file mode 100644 index 000000000..43a33a8d2 --- /dev/null +++ b/plugins/acronyms_expander.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- + +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## + +''' +Acronyms expander plugin. + +:author: Mateusz Biliński +:since: 06/10/2008 +:copyright: Copyright (2008) Mateusz Biliński +:license: GPL +''' + +import sys + +import gtk + +from plugins import GajimPlugin +from plugins.helpers import log, log_calls + +class AcronymsExpanderPlugin(GajimPlugin): + name = u'Acronyms Expander' + short_name = u'acronyms_expander' + version = u'0.1' + description = u'''Replaces acronyms (or other strings) with given expansions/substitutes.''' + authors = [u'Mateusz Biliński '] + homepage = u'http://blog.bilinski.it' + + @log_calls('AcronymsExpanderPlugin') + def __init__(self): + super(AcronymsExpanderPlugin, self).__init__() + + self.__class__.gui_extension_points = { + 'chat_control_base' : (self.connect_with_chat_control_base, + self.disconnect_from_chat_control_base) + } + + self.INVOKER = ' ' + self.ACRONYMS = {'RTFM' : 'Read The Friendly Manual', + '/slap' : '/me slaps', + 'PS-' : 'plug-in system', + 'G-' : 'Gajim', + 'GNT-' : 'http://trac.gajim.org/newticket', + 'GW-' : 'http://trac.gajim.org/', + 'GTS-' : 'http://trac.gajim.org/report' + } + + @log_calls('AcronymsExpanderPlugin') + def textbuffer_live_acronym_expander(self, tb): + """ + @param tb gtk.TextBuffer + """ + assert isinstance(tb,gtk.TextBuffer) + t = tb.get_text(tb.get_start_iter(), tb.get_end_iter()) + log.debug('%s %d'%(t, len(t))) + if t and t[-1] == self.INVOKER: + log.debug("changing msg text") + base,sep,head=t[:-1].rpartition(self.INVOKER) + log.debug('%s | %s | %s'%(base, sep, head)) + if head in self.ACRONYMS: + head = self.ACRONYMS[head] + log.debug("head: %s"%(head)) + t = "".join((base, sep, head, self.INVOKER)) + log.debug("turning off notify") + tb.freeze_notify() + log.debug("setting text: '%s'"%(t)) + tb.set_text(t) + log.debug("turning on notify") + tb.thaw_notify() + + @log_calls('AcronymsExpanderPlugin') + def connect_with_chat_control_base(self, chat_control): + d = {} + tv = chat_control.msg_textview + tb = tv.get_buffer() + h_id = tb.connect('changed', self.textbuffer_live_acronym_expander) + d['h_id'] = h_id + + chat_control.acronyms_expander_plugin_data = d + + return True + + @log_calls('AcronymsExpanderPlugin') + def disconnect_from_chat_control_base(self, chat_control): + d = chat_control.acronyms_expander_plugin_data + tv = chat_control.msg_textview + tv.get_buffer().disconnect(d['h_id']) + \ No newline at end of file diff --git a/src/chat_control.py b/src/chat_control.py index ecc817bf6..c0859fb0f 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -291,6 +291,8 @@ class ChatControlBase(MessageControl): self.user_nick = None self.smooth = True + + gajim.plugin_manager.gui_extension_point('chat_control_base', self) def on_msg_textview_populate_popup(self, textview, menu): '''we override the default context menu and we prepend an option to switch languages''' From 11e61ea2a2892c48599a7421b66afe1362b5c012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Thu, 12 Jun 2008 18:26:08 +0000 Subject: [PATCH 008/200] Small changes toward handling plug-ins configuration. --- data/glade/plugins_window.glade | 6 +++++- plugins/acronyms_expander.py | 2 +- plugins/length_notifier.py | 2 +- src/plugins/plugin.py | 27 ++++++++++++++++++++++++++- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/data/glade/plugins_window.glade b/data/glade/plugins_window.glade index d8996b53e..425d77dd7 100644 --- a/data/glade/plugins_window.glade +++ b/data/glade/plugins_window.glade @@ -1,6 +1,6 @@ - + 650 @@ -322,6 +322,7 @@ True + False True @@ -555,6 +556,9 @@ + True + False + True Available diff --git a/plugins/acronyms_expander.py b/plugins/acronyms_expander.py index 43a33a8d2..f650f45b6 100644 --- a/plugins/acronyms_expander.py +++ b/plugins/acronyms_expander.py @@ -43,7 +43,7 @@ class AcronymsExpanderPlugin(GajimPlugin): def __init__(self): super(AcronymsExpanderPlugin, self).__init__() - self.__class__.gui_extension_points = { + self.gui_extension_points = { 'chat_control_base' : (self.connect_with_chat_control_base, self.disconnect_from_chat_control_base) } diff --git a/plugins/length_notifier.py b/plugins/length_notifier.py index 7c9f2422a..63c0d6d44 100644 --- a/plugins/length_notifier.py +++ b/plugins/length_notifier.py @@ -43,7 +43,7 @@ class LengthNotifierPlugin(GajimPlugin): def __init__(self): super(LengthNotifierPlugin, self).__init__() - self.__class__.gui_extension_points = { + self.gui_extension_points = { 'chat_control' : (self.connect_with_chat_control, self.disconnect_from_chat_control) } diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py index fd49c6a44..1d0ee7cb9 100644 --- a/src/plugins/plugin.py +++ b/src/plugins/plugin.py @@ -94,4 +94,29 @@ class GajimPlugin(object): @log_calls('GajimPlugin') def __init__(self): - pass \ No newline at end of file + self.config = Config() + ''' + Plug-in configuration dictionary. + + Automatically saved and loaded and plug-in (un)load. + + :type: `plugins.plugin.Config` + ''' + + self._load_config() + + @log_calls('GajimPlugin') + def _save_config(self): + pass + + @log_calls('GajimPlugin') + def _load_config(self): + pass + + @log_calls('GajimPlugin') + def __del__(self): + self._save_config() + + +class Config(dict): + pass \ No newline at end of file From 03b982ad504e8f5c98c29084becb7a91c31ea122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Thu, 12 Jun 2008 19:26:55 +0000 Subject: [PATCH 009/200] [Merwok] Commented out 'import pycallgraph' to remove import error. Added pycallgraph.py for those that want to generate callgraphs anyway. --- src/gajim.py | 2 +- src/pycallgraph.py | 412 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 413 insertions(+), 1 deletion(-) create mode 100644 src/pycallgraph.py diff --git a/src/gajim.py b/src/gajim.py index 69d64e0c4..7dfc823b8 100755 --- a/src/gajim.py +++ b/src/gajim.py @@ -26,7 +26,7 @@ ## import os -import pycallgraph +# import pycallgraph if os.name == 'nt': diff --git a/src/pycallgraph.py b/src/pycallgraph.py new file mode 100644 index 000000000..92c6bbdd3 --- /dev/null +++ b/src/pycallgraph.py @@ -0,0 +1,412 @@ +""" +pycallgraph + +U{http://pycallgraph.slowchop.com/} + +Copyright Gerald Kaszuba 2007 + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +__version__ = '0.4.1' +__author__ = 'Gerald Kaszuba' + +import inspect +import sys +import os +import re +import tempfile +import time +from distutils import sysconfig + +# Initialise module variables. +# TODO Move these into settings +trace_filter = None +time_filter = None + + +def colourize_node(calls, total_time): + value = float(total_time * 2 + calls) / 3 + return '%f %f %f' % (value / 2 + .5, value, 0.9) + + +def colourize_edge(calls, total_time): + value = float(total_time * 2 + calls) / 3 + return '%f %f %f' % (value / 2 + .5, value, 0.7) + + +def reset_settings(): + global settings + global graph_attributes + global __version__ + + settings = { + 'node_attributes': { + 'label': r'%(func)s\ncalls: %(hits)i\ntotal time: %(total_time)f', + 'color': '%(col)s', + }, + 'node_colour': colourize_node, + 'edge_colour': colourize_edge, + 'dont_exclude_anything': False, + 'include_stdlib': True, + } + + # TODO: Move this into settings + graph_attributes = { + 'graph': { + 'fontname': 'Verdana', + 'fontsize': 7, + 'fontcolor': '0 0 0.5', + 'label': r'Generated by Python Call Graph v%s\n' \ + r'http://pycallgraph.slowchop.com' % __version__, + }, + 'node': { + 'fontname': 'Verdana', + 'fontsize': 7, + 'color': '.5 0 .9', + 'style': 'filled', + 'shape': 'rect', + }, + } + + +def reset_trace(): + """Resets all collected statistics. This is run automatically by + start_trace(reset=True) and when the module is loaded. + """ + global call_dict + global call_stack + global func_count + global func_count_max + global func_time + global func_time_max + global call_stack_timer + + call_dict = {} + + # current call stack + call_stack = ['__main__'] + + # counters for each function + func_count = {} + func_count_max = 0 + + # accumative time per function + func_time = {} + func_time_max = 0 + + # keeps track of the start time of each call on the stack + call_stack_timer = [] + + +class PyCallGraphException(Exception): + """Exception used for pycallgraph""" + pass + + +class GlobbingFilter(object): + """Filter module names using a set of globs. + + Objects are matched against the exclude list first, then the include list. + Anything that passes through without matching either, is excluded. + """ + + def __init__(self, include=None, exclude=None, max_depth=None, + min_depth=None): + if include is None and exclude is None: + include = ['*'] + exclude = [] + elif include is None: + include = ['*'] + elif exclude is None: + exclude = [] + self.include = include + self.exclude = exclude + self.max_depth = max_depth or 9999 + self.min_depth = min_depth or 0 + + def __call__(self, stack, module_name=None, class_name=None, + func_name=None, full_name=None): + from fnmatch import fnmatch + if len(stack) > self.max_depth: + return False + if len(stack) < self.min_depth: + return False + for pattern in self.exclude: + if fnmatch(full_name, pattern): + return False + for pattern in self.include: + if fnmatch(full_name, pattern): + return True + return False + + +def is_module_stdlib(file_name): + """Returns True if the file_name is in the lib directory.""" + # TODO: Move these calls away from this function so it doesn't have to run + # every time. + lib_path = sysconfig.get_python_lib() + path = os.path.split(lib_path) + if path[1] == 'site-packages': + lib_path = path[0] + return file_name.lower().startswith(lib_path.lower()) + + +def start_trace(reset=True, filter_func=None, time_filter_func=None): + """Begins a trace. Setting reset to True will reset all previously recorded + trace data. filter_func needs to point to a callable function that accepts + the parameters (call_stack, module_name, class_name, func_name, full_name). + Every call will be passed into this function and it is up to the function + to decide if it should be included or not. Returning False means the call + will be filtered out and not included in the call graph. + """ + global trace_filter + global time_filter + if reset: + reset_trace() + + if filter_func: + trace_filter = filter_func + else: + trace_filter = GlobbingFilter(exclude=['pycallgraph.*']) + + if time_filter_func: + time_filter = time_filter_func + else: + time_filter = GlobbingFilter() + + sys.settrace(tracer) + + +def stop_trace(): + """Stops the currently running trace, if any.""" + sys.settrace(None) + + +def tracer(frame, event, arg): + """This is an internal function that is called every time a call is made + during a trace. It keeps track of relationships between calls. + """ + global func_count_max + global func_count + global trace_filter + global time_filter + global call_stack + global func_time + global func_time_max + + if event == 'call': + keep = True + code = frame.f_code + + # Stores all the parts of a human readable name of the current call. + full_name_list = [] + + # Work out the module name + module = inspect.getmodule(code) + if module: + module_name = module.__name__ + module_path = module.__file__ + if not settings['include_stdlib'] \ + and is_module_stdlib(module_path): + keep = False + if module_name == '__main__': + module_name = '' + else: + module_name = '' + if module_name: + full_name_list.append(module_name) + + # Work out the class name. + try: + class_name = frame.f_locals['self'].__class__.__name__ + full_name_list.append(class_name) + except (KeyError, AttributeError): + class_name = '' + + # Work out the current function or method + func_name = code.co_name + if func_name == '?': + func_name = '__main__' + full_name_list.append(func_name) + + # Create a readable representation of the current call + full_name = '.'.join(full_name_list) + + # Load the trace filter, if any. 'keep' determines if we should ignore + # this call + if keep and trace_filter: + keep = trace_filter(call_stack, module_name, class_name, + func_name, full_name) + + # Store the call information + if keep: + + fr = call_stack[-1] + if fr not in call_dict: + call_dict[fr] = {} + if full_name not in call_dict[fr]: + call_dict[fr][full_name] = 0 + call_dict[fr][full_name] += 1 + + if full_name not in func_count: + func_count[full_name] = 0 + func_count[full_name] += 1 + if func_count[full_name] > func_count_max: + func_count_max = func_count[full_name] + + call_stack.append(full_name) + call_stack_timer.append(time.time()) + + else: + call_stack.append('') + call_stack_timer.append(None) + + if event == 'return': + if call_stack: + full_name = call_stack.pop(-1) + t = call_stack_timer.pop(-1) + if t and time_filter(stack=call_stack, full_name=full_name): + if full_name not in func_time: + func_time[full_name] = 0 + call_time = (time.time() - t) + func_time[full_name] += call_time + if func_time[full_name] > func_time_max: + func_time_max = func_time[full_name] + + return tracer + + +def get_dot(stop=True): + """Returns a string containing a DOT file. Setting stop to True will cause + the trace to stop. + """ + global func_time_max + + def frac_calculation(func, count): + global func_count_max + global func_time + global func_time_max + calls_frac = float(count) / func_count_max + try: + total_time = func_time[func] + except KeyError: + total_time = 0 + if func_time_max: + total_time_frac = float(total_time) / func_time_max + else: + total_time_frac = 0 + return calls_frac, total_time_frac, total_time + + if stop: + stop_trace() + ret = ['digraph G {', ] + for comp, comp_attr in graph_attributes.items(): + ret.append('%s [' % comp) + for attr, val in comp_attr.items(): + ret.append('%(attr)s = "%(val)s",' % locals()) + ret.append('];') + for func, hits in func_count.items(): + calls_frac, total_time_frac, total_time = frac_calculation(func, hits) + col = settings['node_colour'](calls_frac, total_time_frac) + attribs = ['%s="%s"' % a for a in settings['node_attributes'].items()] + node_str = '"%s" [%s];' % (func, ','.join(attribs)) + ret.append(node_str % locals()) + for fr_key, fr_val in call_dict.items(): + if fr_key == '': + continue + for to_key, to_val in fr_val.items(): + calls_frac, total_time_frac, totla_time = \ + frac_calculation(to_key, to_val) + col = settings['edge_colour'](calls_frac, total_time_frac) + edge = '[ color = "%s" ]' % col + ret.append('"%s"->"%s" %s' % (fr_key, to_key, edge)) + ret.append('}') + ret = '\n'.join(ret) + return ret + + +def save_dot(filename): + """Generates a DOT file and writes it into filename.""" + open(filename, 'w').write(get_dot()) + + +def make_graph(filename, format=None, tool=None, stop=None): + """This has been changed to make_dot_graph.""" + raise PyCallGraphException( \ + 'make_graph is depricated. Please use make_dot_graph') + + +def make_dot_graph(filename, format='png', tool='dot', stop=True): + """Creates a graph using a Graphviz tool that supports the dot language. It + will output into a file specified by filename with the format specified. + Setting stop to True will stop the current trace. + """ + if stop: + stop_trace() + + # create a temporary file to be used for the dot data + fd, tempname = tempfile.mkstemp() + f = os.fdopen(fd, 'w') + f.write(get_dot()) + f.close() + + # normalize filename + regex_user_expand = re.compile('\A~') + if regex_user_expand.match(filename): + filename = os.path.expanduser(filename) + else: + filename = os.path.expandvars(filename) # expand, just in case + + cmd = '%(tool)s -T%(format)s -o%(filename)s %(tempname)s' % locals() + try: + ret = os.system(cmd) + if ret: + raise PyCallGraphException( \ + 'The command "%(cmd)s" failed with error ' \ + 'code %(ret)i.' % locals()) + finally: + os.unlink(tempname) + + +def simple_memoize(callable_object): + """Simple memoization for functions without keyword arguments. + + This is useful for mapping code objects to module in this context. + inspect.getmodule() requires a number of system calls, which may slow down + the tracing considerably. Caching the mapping from code objects (there is + *one* code object for each function, regardless of how many simultaneous + activations records there are). + + In this context we can ignore keyword arguments, but a generic memoizer + ought to take care of that as well. + """ + + cache = dict() + def wrapper(*rest): + if rest not in cache: + cache[rest] = callable_object(*rest) + return cache[rest] + + return wrapper + + +settings = {} +graph_attributes = {} +reset_settings() +reset_trace() +inspect.getmodule = simple_memoize(inspect.getmodule) + +# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: From b6593b94936579884f405a20961a8661d2dfb7a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Sat, 14 Jun 2008 18:20:24 +0000 Subject: [PATCH 010/200] Plugin can be a package (directory) now. Added example plugin that modifies roster window (with glade file). Added activate and deactivate methods to Plugin (used in forementioned RosterButtonsPlugin). --- plugins/roster_buttons/__init__.py | 4 + plugins/roster_buttons/plugin.py | 88 +++++++++++++++++++++ plugins/roster_buttons/roster_buttons.glade | 64 +++++++++++++++ src/plugins/plugin.py | 13 +++ src/plugins/pluginmanager.py | 55 ++++++++++--- 5 files changed, 212 insertions(+), 12 deletions(-) create mode 100644 plugins/roster_buttons/__init__.py create mode 100644 plugins/roster_buttons/plugin.py create mode 100644 plugins/roster_buttons/roster_buttons.glade diff --git a/plugins/roster_buttons/__init__.py b/plugins/roster_buttons/__init__.py new file mode 100644 index 000000000..48754d57c --- /dev/null +++ b/plugins/roster_buttons/__init__.py @@ -0,0 +1,4 @@ + +__all__ = ['RosterButtonsPlugin'] + +from plugin import RosterButtonsPlugin diff --git a/plugins/roster_buttons/plugin.py b/plugins/roster_buttons/plugin.py new file mode 100644 index 000000000..aa52b95d5 --- /dev/null +++ b/plugins/roster_buttons/plugin.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- + +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## + +''' +Roster buttons plug-in. + +:author: Mateusz Biliński +:since: 06/10/2008 +:copyright: Copyright (2008) Mateusz Biliński +:license: GPL +''' + +import sys + +import gtk +from common import i18n +from common import gajim + +from plugins import GajimPlugin +from plugins.helpers import log, log_calls + +class RosterButtonsPlugin(GajimPlugin): + name = u'Roster Buttons' + short_name = u'roster_buttons' + version = u'0.1' + description = u'''Adds quick action buttons to roster window.''' + authors = [u'Mateusz Biliński '] + homepage = u'http://blog.bilinski.it' + + #@log_calls('RosterButtonsPlugin') + #def __init__(self): + #super(RosterButtonsPlugin, self).__init__() + + @log_calls('RosterButtonsPlugin') + def activate(self): + #log.debug('self.__path__==%s'%(self.__path__)) + self.GLADE_FILE_PATH = self.local_file_path('roster_buttons.glade') + self.xml = gtk.glade.XML(self.GLADE_FILE_PATH, root='roster_buttons_buttonbox', domain=i18n.APP) + self.buttonbox = self.xml.get_widget('roster_buttons_buttonbox') + + self.roster_vbox = gajim.interface.roster.xml.get_widget('roster_vbox2') + self.roster_vbox.pack_start(self.buttonbox, expand=False) + self.roster_vbox.reorder_child(self.buttonbox, 0) + + self.show_offline_contacts_menuitem = gajim.interface.roster.xml.get_widget('show_offline_contacts_menuitem') + + self.xml.signal_autoconnect(self) + + + @log_calls('RosterButtonsPlugin') + def deactivate(self): + self.roster_vbox.remove(self.buttonbox) + + self.buttonbox = None + self.xml = None + + @log_calls('RosterButtonsPlugin') + def on_roster_button_1_clicked(self, button): + #gajim.interface.roster.on_show_offline_contacts_menuitem_activate(None) + self.show_offline_contacts_menuitem.set_active(not self.show_offline_contacts_menuitem.get_active()) + + + @log_calls('RosterButtonsPlugin') + def on_roster_button_2_clicked(self, button): + pass + + @log_calls('RosterButtonsPlugin') + def on_roster_button_3_clicked(self, button): + pass + + @log_calls('RosterButtonsPlugin') + def on_roster_button_4_clicked(self, button): + pass + diff --git a/plugins/roster_buttons/roster_buttons.glade b/plugins/roster_buttons/roster_buttons.glade new file mode 100644 index 000000000..fa2738835 --- /dev/null +++ b/plugins/roster_buttons/roster_buttons.glade @@ -0,0 +1,64 @@ + + + + + + + + True + True + GTK_BUTTONBOX_SPREAD + + + True + True + True + Show/Hide Offline Contacts + 1 + 0 + + + + + + True + True + True + 2 + 0 + + + + 1 + + + + + True + True + True + 3 + 0 + + + + 2 + + + + + True + True + True + 4 + 0 + + + + 3 + + + + + + diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py index 1d0ee7cb9..d87880fad 100644 --- a/src/plugins/plugin.py +++ b/src/plugins/plugin.py @@ -24,6 +24,8 @@ Base class for implementing plugin. :license: GPL ''' +import os + from plugins.helpers import log_calls class GajimPlugin(object): @@ -117,6 +119,17 @@ class GajimPlugin(object): def __del__(self): self._save_config() + @log_calls('GajimPlugin') + def local_file_path(self, file_name): + return os.path.join(self.__path__, file_name) + + @log_calls('GajimPlugin') + def activate(self): + pass + + @log_calls('GajimPlugin') + def deactivate(self): + pass class Config(dict): pass \ No newline at end of file diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py index fa3ab729e..46c88138a 100644 --- a/src/plugins/pluginmanager.py +++ b/src/plugins/pluginmanager.py @@ -171,6 +171,7 @@ class PluginManager(object): if success: self.active_plugins.append(plugin_object) + plugin_object.activate() plugin_class._instance = plugin_object plugin_class._active = True @@ -193,6 +194,7 @@ class PluginManager(object): self.gui_extension_points_handlers[gui_extpoint_name].remove(gui_extpoint_handlers) # removing plug-in from active plug-ins list + plugin_object.deactivate() self.active_plugins.remove(plugin_object) plugin_object.__class__._active = False plugin_object.__class__._instance = None @@ -249,28 +251,57 @@ class PluginManager(object): plugins_found = [] if os.path.isdir(path): dir_list = os.listdir(path) - log.debug(dir_list) + #log.debug(dir_list) sys.path.insert(0, path) - log.debug(sys.path) + #log.debug(sys.path) - for file_name in fnmatch.filter(dir_list, '*.py'): - log.debug('- "%s"'%(file_name)) - file_path = os.path.join(path, file_name) + for elem_name in dir_list: + log.debug('- "%s"'%(elem_name)) + file_path = os.path.join(path, elem_name) log.debug(' "%s"'%(file_path)) - if os.path.isfile(file_path): - module_name = os.path.splitext(file_name)[0] - module = __import__(module_name) - for module_attr_name in [f_name for f_name in dir(module) - if not (f_name.startswith('__') or - f_name.endswith('__'))]: + + module = None + + if os.path.isfile(file_path) and fnmatch.fnmatch(file_path,'*.py'): + module_name = os.path.splitext(elem_name)[0] + log.debug('Possible module detected.') + try: + module = __import__(module_name) + log.debug('Module imported.') + except ValueError, value_error: + log.debug('Module not imported successfully. ValueError: %s'%(value_error)) + except ImportError, import_error: + log.debug('Module not imported successfully. ImportError: %s'%(import_error)) + + elif os.path.isdir(file_path): + module_name = elem_name + file_path += os.path.sep + log.debug('Possible package detected.') + try: + module = __import__(module_name) + log.debug('Package imported.') + except ValueError, value_error: + log.debug('Package not imported successfully. ValueError: %s'%(value_error)) + except ImportError, import_error: + log.debug('Package not imported successfully. ImportError: %s'%(import_error)) + + + if module: + log.debug('Attributes processing started') + for module_attr_name in [attr_name for attr_name in dir(module) + if not (attr_name.startswith('__') or + attr_name.endswith('__'))]: module_attr = getattr(module, module_attr_name) log.debug('%s : %s'%(module_attr_name, module_attr)) - + try: if issubclass(module_attr, GajimPlugin) and \ not module_attr is GajimPlugin: log.debug('is subclass of GajimPlugin') + #log.debug('file_path: %s\nabspath: %s\ndirname: %s'%(file_path, os.path.abspath(file_path), os.path.dirname(os.path.abspath(file_path)))) + #log.debug('file_path: %s\ndirname: %s\nabspath: %s'%(file_path, os.path.dirname(file_path), os.path.abspath(os.path.dirname(file_path)))) + module_attr.__path__ = os.path.abspath(os.path.dirname(file_path)) plugins_found.append(module_attr) except TypeError, type_error: log.debug('module_attr: %s, error : %s'%( From 8581b862e14056966b8aa6efa57832c6f1e1839a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Wed, 18 Jun 2008 20:45:22 +0000 Subject: [PATCH 011/200] Added new 'init' method to Plugin class that plugins can implement to make actions that need to be done only once - when plugin is added (not activated) to Gajim. In this method plugins should declare handlers for GUI extension points. This was created so that __init__ method doesn't have to be reimplemented in specific way (create config, load config) - it is all done by __init__ in Plugin class. If __init__ is reimplemented, it must call Plugin __init__ (eg. using super() ) to plugin work properly. Example plug-ins were modified to use init() instead of __init__(). Added new category in configuration - 'plugins'. It only holds one option for each plugin - 'active', which determines whether plugin should be activated on startup. Now, Gajim remembers which plugins are active on exit, and activates them on next startup. --- plugins/acronyms_expander.py | 14 +++-- plugins/length_notifier.py | 10 +-- plugins/roster_buttons/plugin.py | 20 +++--- src/common/config.py | 3 + src/gajim-remote.py | 2 + src/plugins/gui.py | 14 ++--- src/plugins/plugin.py | 14 +++-- src/plugins/pluginmanager.py | 102 +++++++++++++++++++------------ 8 files changed, 109 insertions(+), 70 deletions(-) diff --git a/plugins/acronyms_expander.py b/plugins/acronyms_expander.py index f650f45b6..bca5fd04a 100644 --- a/plugins/acronyms_expander.py +++ b/plugins/acronyms_expander.py @@ -19,7 +19,7 @@ Acronyms expander plugin. :author: Mateusz Biliński -:since: 06/10/2008 +:since: 9th June 2008 :copyright: Copyright (2008) Mateusz Biliński :license: GPL ''' @@ -39,10 +39,11 @@ class AcronymsExpanderPlugin(GajimPlugin): authors = [u'Mateusz Biliński '] homepage = u'http://blog.bilinski.it' - @log_calls('AcronymsExpanderPlugin') - def __init__(self): - super(AcronymsExpanderPlugin, self).__init__() - + #@log_calls('AcronymsExpanderPlugin') + #def __init__(self): + #super(AcronymsExpanderPlugin, self).__init__() + + def init(self): self.gui_extension_points = { 'chat_control_base' : (self.connect_with_chat_control_base, self.disconnect_from_chat_control_base) @@ -56,7 +57,7 @@ class AcronymsExpanderPlugin(GajimPlugin): 'GNT-' : 'http://trac.gajim.org/newticket', 'GW-' : 'http://trac.gajim.org/', 'GTS-' : 'http://trac.gajim.org/report' - } + } @log_calls('AcronymsExpanderPlugin') def textbuffer_live_acronym_expander(self, tb): @@ -98,4 +99,5 @@ class AcronymsExpanderPlugin(GajimPlugin): d = chat_control.acronyms_expander_plugin_data tv = chat_control.msg_textview tv.get_buffer().disconnect(d['h_id']) + \ No newline at end of file diff --git a/plugins/length_notifier.py b/plugins/length_notifier.py index 63c0d6d44..3845d3061 100644 --- a/plugins/length_notifier.py +++ b/plugins/length_notifier.py @@ -19,7 +19,7 @@ Message length notifier plugin. :author: Mateusz Biliński -:since: 06/01/2008 +:since: 1st June 2008 :copyright: Copyright (2008) Mateusz Biliński :license: GPL ''' @@ -39,10 +39,12 @@ class LengthNotifierPlugin(GajimPlugin): authors = [u'Mateusz Biliński '] homepage = u'http://blog.bilinski.it' + #@log_calls('LengthNotifierPlugin') + #def __init__(self): + #super(LengthNotifierPlugin, self).__init__() + @log_calls('LengthNotifierPlugin') - def __init__(self): - super(LengthNotifierPlugin, self).__init__() - + def init(self): self.gui_extension_points = { 'chat_control' : (self.connect_with_chat_control, self.disconnect_from_chat_control) diff --git a/plugins/roster_buttons/plugin.py b/plugins/roster_buttons/plugin.py index aa52b95d5..7e5671046 100644 --- a/plugins/roster_buttons/plugin.py +++ b/plugins/roster_buttons/plugin.py @@ -19,7 +19,7 @@ Roster buttons plug-in. :author: Mateusz Biliński -:since: 06/10/2008 +:since: 14th June 2008 :copyright: Copyright (2008) Mateusz Biliński :license: GPL ''' @@ -44,23 +44,24 @@ class RosterButtonsPlugin(GajimPlugin): #@log_calls('RosterButtonsPlugin') #def __init__(self): #super(RosterButtonsPlugin, self).__init__() - - @log_calls('RosterButtonsPlugin') - def activate(self): + + @log_calls('RosterButtonsPlugin') + def init(self): #log.debug('self.__path__==%s'%(self.__path__)) self.GLADE_FILE_PATH = self.local_file_path('roster_buttons.glade') + + self.roster_vbox = gajim.interface.roster.xml.get_widget('roster_vbox2') + self.show_offline_contacts_menuitem = gajim.interface.roster.xml.get_widget('show_offline_contacts_menuitem') + + @log_calls('RosterButtonsPlugin') + def activate(self): self.xml = gtk.glade.XML(self.GLADE_FILE_PATH, root='roster_buttons_buttonbox', domain=i18n.APP) self.buttonbox = self.xml.get_widget('roster_buttons_buttonbox') - self.roster_vbox = gajim.interface.roster.xml.get_widget('roster_vbox2') self.roster_vbox.pack_start(self.buttonbox, expand=False) self.roster_vbox.reorder_child(self.buttonbox, 0) - - self.show_offline_contacts_menuitem = gajim.interface.roster.xml.get_widget('show_offline_contacts_menuitem') - self.xml.signal_autoconnect(self) - @log_calls('RosterButtonsPlugin') def deactivate(self): self.roster_vbox.remove(self.buttonbox) @@ -72,7 +73,6 @@ class RosterButtonsPlugin(GajimPlugin): def on_roster_button_1_clicked(self, button): #gajim.interface.roster.on_show_offline_contacts_menuitem_activate(None) self.show_offline_contacts_menuitem.set_active(not self.show_offline_contacts_menuitem.get_active()) - @log_calls('RosterButtonsPlugin') def on_roster_button_2_clicked(self, button): diff --git a/src/common/config.py b/src/common/config.py index 627da7591..6d3542228 100644 --- a/src/common/config.py +++ b/src/common/config.py @@ -390,6 +390,9 @@ class Config: 'roster': [opt_str, '', _("'yes', 'no' or ''")], 'urgency_hint': [opt_bool, False], }, {}), + 'plugins': ({ + 'active': [opt_bool, False, _('State whether plugins should be activated on exit (this is saved on Gajim exit). This option SHOULD NOT be used to (de)activate plug-ins. Use GUI instead.')], + },{}), } statusmsg_default = { diff --git a/src/gajim-remote.py b/src/gajim-remote.py index 4846705bc..9e05c576d 100755 --- a/src/gajim-remote.py +++ b/src/gajim-remote.py @@ -377,6 +377,8 @@ class GajimRemote: except: raise exceptions.SessionBusNotPresent + from pprint import pprint + pprint(list(self.sbus.list_names())) if not self.check_gajim_running(): send_error(_('It seems Gajim is not running. So you can\'t use gajim-remote.')) obj = self.sbus.get_object(SERVICE, OBJ_PATH) diff --git a/src/plugins/gui.py b/src/plugins/gui.py index c4628958b..8aa0f3f79 100644 --- a/src/plugins/gui.py +++ b/src/plugins/gui.py @@ -139,20 +139,20 @@ class PluginsWindow(object): self.installed_plugins_model.clear() self.installed_plugins_model.set_sort_column_id(0, gtk.SORT_ASCENDING) - for plugin_class in pm.plugins: - self.installed_plugins_model.append([plugin_class, - plugin_class.name, - plugin_class._active]) + for plugin in pm.plugins: + self.installed_plugins_model.append([plugin, + plugin.name, + plugin.active]) @log_calls('PluginsWindow') def installed_plugins_toggled_cb(self, cell, path): is_active = self.installed_plugins_model[path][2] - plugin_class = self.installed_plugins_model[path][0] + plugin = self.installed_plugins_model[path][0] if is_active: - gajim.plugin_manager.deactivate_plugin(plugin_class._instance) + gajim.plugin_manager.deactivate_plugin(plugin) else: - gajim.plugin_manager.activate_plugin(plugin_class) + gajim.plugin_manager.activate_plugin(plugin) self.installed_plugins_model[path][2] = not is_active diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py index d87880fad..1d5c1a9dc 100644 --- a/src/plugins/plugin.py +++ b/src/plugins/plugin.py @@ -19,7 +19,7 @@ Base class for implementing plugin. :author: Mateusz Biliński -:since: 06/01/2008 +:since: 1st June 2008 :copyright: Copyright (2008) Mateusz Biliński :license: GPL ''' @@ -104,15 +104,15 @@ class GajimPlugin(object): :type: `plugins.plugin.Config` ''' - - self._load_config() + self.load_config() + self.init() @log_calls('GajimPlugin') - def _save_config(self): + def save_config(self): pass @log_calls('GajimPlugin') - def _load_config(self): + def load_config(self): pass @log_calls('GajimPlugin') @@ -122,6 +122,10 @@ class GajimPlugin(object): @log_calls('GajimPlugin') def local_file_path(self, file_name): return os.path.join(self.__path__, file_name) + + @log_calls('GajimPlugin') + def init(self): + pass @log_calls('GajimPlugin') def activate(self): diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py index 46c88138a..6d74a78a9 100644 --- a/src/plugins/pluginmanager.py +++ b/src/plugins/pluginmanager.py @@ -19,7 +19,7 @@ Plug-in management related classes. :author: Mateusz Biliński -:since: 05/30/2008 +:since: 30th May 2008 :copyright: Copyright (2008) Mateusz Biliński :license: GPL ''' @@ -94,29 +94,42 @@ class PluginManager(object): ''' for path in gajim.PLUGINS_DIRS: - self._add_plugins(PluginManager.scan_dir_for_plugins(path)) + self.add_plugins(PluginManager.scan_dir_for_plugins(path)) log.debug('plugins: %s'%(self.plugins)) - self.activate_all_plugins() + self._activate_all_plugins_from_global_config() log.debug('active: %s'%(self.active_plugins)) + @log_calls('PluginManager') + def _plugin_has_entry_in_global_config(self, plugin): + if gajim.config.get_per('plugins', plugin.short_name) is None: + return False + else: + return True + + @log_calls('PluginManager') + def _create_plugin_entry_in_global_config(self, plugin): + gajim.config.add_per('plugins', plugin.short_name) @log_calls('PluginManager') - def _add_plugin(self, plugin_class): + def add_plugin(self, plugin_class): ''' :todo: what about adding plug-ins that are already added? Module reload and adding class from reloaded module or ignoring adding plug-in? ''' - plugin_class._active = False - plugin_class._instance = None - self.plugins.append(plugin_class) + plugin = plugin_class() + if not self._plugin_has_entry_in_global_config(plugin): + self._create_plugin_entry_in_global_config(plugin) + + self.plugins.append(plugin) + plugin.active = False @log_calls('PluginManager') - def _add_plugins(self, plugin_classes): + def add_plugins(self, plugin_classes): for plugin_class in plugin_classes: - self._add_plugin(plugin_class) + self.add_plugin(plugin_class) @log_calls('PluginManager') def gui_extension_point(self, gui_extpoint_name, *args): @@ -156,33 +169,36 @@ class PluginManager(object): handlers[0](*args) @log_calls('PluginManager') - def activate_plugin(self, plugin_class): + def activate_plugin(self, plugin): ''' :param plugin: plugin to be activated :type plugin: class object of `GajimPlugin` subclass - ''' - plugin_object = plugin_class() - - success = True - - self._add_gui_extension_points_handlers_from_plugin(plugin_object) - self._handle_all_gui_extension_points_with_plugin(plugin_object) - - if success: - self.active_plugins.append(plugin_object) - plugin_object.activate() - plugin_class._instance = plugin_object - plugin_class._active = True + :todo: success checks should be implemented using exceptions. Such + control should also be implemented in deactivation. + ''' + success = False + if not plugin.active: + + self._add_gui_extension_points_handlers_from_plugin(plugin) + self._handle_all_gui_extension_points_with_plugin(plugin) + + success = True + + if success: + self.active_plugins.append(plugin) + plugin.activate() + self._set_plugin_active_in_global_config(plugin) + plugin.active = True return success - def deactivate_plugin(self, plugin_object): + def deactivate_plugin(self, plugin): # detaching plug-in from handler GUI extension points (calling # cleaning up method that must be provided by plug-in developer # for each handled GUI extension point) for gui_extpoint_name, gui_extpoint_handlers in \ - plugin_object.gui_extension_points.iteritems(): + plugin.gui_extension_points.iteritems(): if gui_extpoint_name in self.gui_extension_points: for gui_extension_point_args in self.gui_extension_points[gui_extpoint_name]: gui_extpoint_handlers[1](*gui_extension_point_args) @@ -190,45 +206,55 @@ class PluginManager(object): # remove GUI extension points handlers (provided by plug-in) from # handlers list for gui_extpoint_name, gui_extpoint_handlers in \ - plugin_object.gui_extension_points.iteritems(): + plugin.gui_extension_points.iteritems(): self.gui_extension_points_handlers[gui_extpoint_name].remove(gui_extpoint_handlers) # removing plug-in from active plug-ins list - plugin_object.deactivate() - self.active_plugins.remove(plugin_object) - plugin_object.__class__._active = False - plugin_object.__class__._instance = None - del plugin_object + plugin.deactivate() + self.active_plugins.remove(plugin) + self._set_plugin_active_in_global_config(plugin, False) + plugin.active = False - def deactivate_all_plugins(self): + def _deactivate_all_plugins(self): for plugin_object in self.active_plugins: self.deactivate_plugin(plugin_object) @log_calls('PluginManager') - def _add_gui_extension_points_handlers_from_plugin(self, plugin_object): + def _add_gui_extension_points_handlers_from_plugin(self, plugin): for gui_extpoint_name, gui_extpoint_handlers in \ - plugin_object.gui_extension_points.iteritems(): + plugin.gui_extension_points.iteritems(): self.gui_extension_points_handlers.setdefault(gui_extpoint_name, []).append( gui_extpoint_handlers) @log_calls('PluginManager') - def _handle_all_gui_extension_points_with_plugin(self, plugin_object): + def _handle_all_gui_extension_points_with_plugin(self, plugin): for gui_extpoint_name, gui_extpoint_handlers in \ - plugin_object.gui_extension_points.iteritems(): + plugin.gui_extension_points.iteritems(): if gui_extpoint_name in self.gui_extension_points: for gui_extension_point_args in self.gui_extension_points[gui_extpoint_name]: gui_extpoint_handlers[0](*gui_extension_point_args) @log_calls('PluginManager') - def activate_all_plugins(self): + def _activate_all_plugins(self): ''' Activates all plugins in `plugins`. Activated plugins are appended to `active_plugins` list. ''' - self.active_plugins = [] + #self.active_plugins = [] for plugin in self.plugins: self.activate_plugin(plugin) + + def _activate_all_plugins_from_global_config(self): + for plugin in self.plugins: + if self._plugin_is_active_in_global_config(plugin): + self.activate_plugin(plugin) + + def _plugin_is_active_in_global_config(self, plugin): + return gajim.config.get_per('plugins', plugin.short_name, 'active') + + def _set_plugin_active_in_global_config(self, plugin, active=True): + gajim.config.set_per('plugins', plugin.short_name, 'active', active) @staticmethod @log_calls('PluginManager') From aaf5b30129307ea2b397ac4080953eb1d553949a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Thu, 19 Jun 2008 12:56:45 +0000 Subject: [PATCH 012/200] Added GajimPluginConfigDialog class - dialog that plugins should use to present configuration to user. Now, 'Configure' button is invoked only for plug-ins that have config_dialog. --- data/glade/plugins_window.glade | 4 ++- plugins/acronyms_expander.py | 2 ++ plugins/length_notifier.py | 2 ++ plugins/roster_buttons/plugin.py | 2 ++ src/plugins/gui.py | 57 ++++++++++++++++++++++++++++++-- src/plugins/plugin.py | 22 ++++++++---- 6 files changed, 79 insertions(+), 10 deletions(-) diff --git a/data/glade/plugins_window.glade b/data/glade/plugins_window.glade index 425d77dd7..4830f0c88 100644 --- a/data/glade/plugins_window.glade +++ b/data/glade/plugins_window.glade @@ -1,6 +1,6 @@ - + 650 @@ -242,6 +242,7 @@ True True 0 + True @@ -270,6 +271,7 @@ True True 0 + True diff --git a/plugins/acronyms_expander.py b/plugins/acronyms_expander.py index bca5fd04a..dda47cf17 100644 --- a/plugins/acronyms_expander.py +++ b/plugins/acronyms_expander.py @@ -44,6 +44,8 @@ class AcronymsExpanderPlugin(GajimPlugin): #super(AcronymsExpanderPlugin, self).__init__() def init(self): + self.config_dialog = None + self.gui_extension_points = { 'chat_control_base' : (self.connect_with_chat_control_base, self.disconnect_from_chat_control_base) diff --git a/plugins/length_notifier.py b/plugins/length_notifier.py index 3845d3061..7c14812e5 100644 --- a/plugins/length_notifier.py +++ b/plugins/length_notifier.py @@ -45,6 +45,8 @@ class LengthNotifierPlugin(GajimPlugin): @log_calls('LengthNotifierPlugin') def init(self): + #self.config_dialog = None + self.gui_extension_points = { 'chat_control' : (self.connect_with_chat_control, self.disconnect_from_chat_control) diff --git a/plugins/roster_buttons/plugin.py b/plugins/roster_buttons/plugin.py index 7e5671046..a1aa29482 100644 --- a/plugins/roster_buttons/plugin.py +++ b/plugins/roster_buttons/plugin.py @@ -52,6 +52,8 @@ class RosterButtonsPlugin(GajimPlugin): self.roster_vbox = gajim.interface.roster.xml.get_widget('roster_vbox2') self.show_offline_contacts_menuitem = gajim.interface.roster.xml.get_widget('show_offline_contacts_menuitem') + + self.config_dialog = None @log_calls('RosterButtonsPlugin') def activate(self): diff --git a/src/plugins/gui.py b/src/plugins/gui.py index 8aa0f3f79..5d1576bba 100644 --- a/src/plugins/gui.py +++ b/src/plugins/gui.py @@ -117,7 +117,10 @@ class PluginsWindow(object): desc_textbuffer.set_text(plugin.description) self.plugin_description_textview.set_property('sensitive', True) self.uninstall_plugin_button.set_property('sensitive', True) - self.configure_plugin_button.set_property('sensitive', True) + if plugin.config_dialog is None: + self.configure_plugin_button.set_property('sensitive', False) + else: + self.configure_plugin_button.set_property('sensitive', True) def _clear_installed_plugin_info(self): self.plugin_name_label.set_text('') @@ -163,4 +166,54 @@ class PluginsWindow(object): @log_calls('PluginsWindow') def on_close_button_clicked(self, widget): - self.window.destroy() \ No newline at end of file + self.window.destroy() + + @log_calls('PluginsWindow') + def on_configure_plugin_button_clicked(self, widget): + log.debug('widget: %s'%(widget)) + selection = self.installed_plugins_treeview.get_selection() + model, iter = selection.get_selected() + if iter: + plugin = model.get_value(iter, 0) + plugin_name = model.get_value(iter, 1) + is_active = model.get_value(iter, 2) + + + result = plugin.config_dialog.run(self.window) + + else: + # No plugin selected. this should never be reached. As configure + # plugin button should only my clickable when plugin is selected. + # XXX: maybe throw exception here? + pass + + @log_calls('PluginsWindow') + def on_uninstall_plugin_button_clicked(self, widget): + pass + + +class GajimPluginConfigDialog(gtk.Dialog): + + @log_calls('GajimPluginConfigDialog') + def __init__(self, plugin, **kwargs): + # TRANSLATORS: The window title for the generic configuration dialog of plugins + gtk.Dialog.__init__(self, '%s : %s'%(_('Configuration'), plugin.name), **kwargs) + self.plugin = plugin + self.add_button('gtk-close', gtk.RESPONSE_CLOSE) + + self.main = self.child + self.main.set_spacing(3) + + # TRANSLATORS: Short text stating which plugin a configuration dialog is for + label = gtk.Label(_('%s Configuration') % (plugin.name)) + label.set_markup(label.get_label()) + self.main.pack_start(label, False, False) + + @log_calls('GajimPluginConfigDialog') + def run(self, parent=None): + self.reparent(parent) + self.show_all() + result = super(GajimPluginConfigDialog, self).run() + self.hide() + return result + \ No newline at end of file diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py index 1d5c1a9dc..27406edb0 100644 --- a/src/plugins/plugin.py +++ b/src/plugins/plugin.py @@ -27,6 +27,7 @@ Base class for implementing plugin. import os from plugins.helpers import log_calls +from plugins.gui import GajimPluginConfigDialog class GajimPlugin(object): ''' @@ -96,28 +97,29 @@ class GajimPlugin(object): @log_calls('GajimPlugin') def __init__(self): - self.config = Config() + self.config = GajimPluginConfig() ''' Plug-in configuration dictionary. Automatically saved and loaded and plug-in (un)load. - :type: `plugins.plugin.Config` + :type: `plugins.plugin.GajimPluginConfig` ''' self.load_config() + self.config_dialog = GajimPluginConfigDialog(self) self.init() @log_calls('GajimPlugin') def save_config(self): - pass + self.config.save() @log_calls('GajimPlugin') def load_config(self): - pass + self.config.load() @log_calls('GajimPlugin') def __del__(self): - self._save_config() + self.save_config() @log_calls('GajimPlugin') def local_file_path(self, file_name): @@ -135,5 +137,11 @@ class GajimPlugin(object): def deactivate(self): pass -class Config(dict): - pass \ No newline at end of file +class GajimPluginConfig(dict): + @log_calls('GajimPluginConfig') + def save(self): + pass + + @log_calls('GajimPluginConfig') + def load(self): + pass From b647885d86d9763e408fea019db1199028dd7d91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Sat, 5 Jul 2008 16:44:27 +0000 Subject: [PATCH 013/200] Small sync commit. --- src/plugins/gui.py | 13 +++++-------- src/plugins/plugin.py | 17 +++++++++++++++-- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/plugins/gui.py b/src/plugins/gui.py index 5d1576bba..9b0734215 100644 --- a/src/plugins/gui.py +++ b/src/plugins/gui.py @@ -196,18 +196,15 @@ class GajimPluginConfigDialog(gtk.Dialog): @log_calls('GajimPluginConfigDialog') def __init__(self, plugin, **kwargs): - # TRANSLATORS: The window title for the generic configuration dialog of plugins - gtk.Dialog.__init__(self, '%s : %s'%(_('Configuration'), plugin.name), **kwargs) + gtk.Dialog.__init__(self, '%s %s'%(plugin.name, _('Configuration')), **kwargs) self.plugin = plugin self.add_button('gtk-close', gtk.RESPONSE_CLOSE) - self.main = self.child - self.main.set_spacing(3) + self.child.set_spacing(3) - # TRANSLATORS: Short text stating which plugin a configuration dialog is for - label = gtk.Label(_('%s Configuration') % (plugin.name)) - label.set_markup(label.get_label()) - self.main.pack_start(label, False, False) + #label = gtk.Label(_('%s Configuration') % (plugin.name)) + #label.set_markup(label.get_label()) + #self.child.pack_start(label, False, False) @log_calls('GajimPluginConfigDialog') def run(self, parent=None): diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py index 27406edb0..ffb9ede3d 100644 --- a/src/plugins/plugin.py +++ b/src/plugins/plugin.py @@ -26,9 +26,12 @@ Base class for implementing plugin. import os -from plugins.helpers import log_calls +from common import gajim + +from plugins.helpers import log_calls, log from plugins.gui import GajimPluginConfigDialog + class GajimPlugin(object): ''' Base class for implementing Gajim plugins. @@ -97,7 +100,7 @@ class GajimPlugin(object): @log_calls('GajimPlugin') def __init__(self): - self.config = GajimPluginConfig() + self.config = GajimPluginConfig(self) ''' Plug-in configuration dictionary. @@ -136,8 +139,17 @@ class GajimPlugin(object): @log_calls('GajimPlugin') def deactivate(self): pass + +import shelve class GajimPluginConfig(dict): + @log_calls('GajimPluginConfig') + def __init__(self, plugin): + self.plugin = plugin + self.FILE_PATH = gajim.HOME_DIR + log.debug('FILE_PATH = %s'%(self.FILE_PATH)) + #self.data = shelve.open(self.FILE_PATH) + @log_calls('GajimPluginConfig') def save(self): pass @@ -145,3 +157,4 @@ class GajimPluginConfig(dict): @log_calls('GajimPluginConfig') def load(self): pass + \ No newline at end of file From d8075a23e61ed802cd4d9a0b0335c5eb1ddfdf49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Fri, 18 Jul 2008 07:05:07 +0000 Subject: [PATCH 014/200] Small sync commit. --- src/plugins/gui.py | 4 ++-- src/plugins/helpers.py | 2 +- src/plugins/plugin.py | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/plugins/gui.py b/src/plugins/gui.py index 9b0734215..1aa4d0140 100644 --- a/src/plugins/gui.py +++ b/src/plugins/gui.py @@ -19,7 +19,7 @@ GUI classes related to plug-in management. :author: Mateusz Biliński -:since: 06/06/2008 +:since: 6th June 2008 :copyright: Copyright (2008) Mateusz Biliński :license: GPL ''' @@ -183,7 +183,7 @@ class PluginsWindow(object): else: # No plugin selected. this should never be reached. As configure - # plugin button should only my clickable when plugin is selected. + # plugin button should only be clickable when plugin is selected. # XXX: maybe throw exception here? pass diff --git a/src/plugins/helpers.py b/src/plugins/helpers.py index 3379bd300..fc6ff9c5d 100644 --- a/src/plugins/helpers.py +++ b/src/plugins/helpers.py @@ -19,7 +19,7 @@ Helper code related to plug-ins management system. :author: Mateusz Biliński -:since: 05/30/2008 +:since: 30th May 2008 :copyright: Copyright (2008) Mateusz Biliński :license: GPL ''' diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py index ffb9ede3d..3a6144472 100644 --- a/src/plugins/plugin.py +++ b/src/plugins/plugin.py @@ -97,6 +97,20 @@ class GajimPlugin(object): ''' Extension points that plugin wants to connect with. ''' + config_default_values = {} + ''' + Default values for keys that should be stored in plug-in config. + + This dict is used when when someone calls for config option but it has not + been set yet. + + Values are tuples: (default_value, option_description). The first one can + be anything (this is the advantage of using shelve/pickle instead of + custom-made config I/O handling); the second one should be unicode (gettext + can be used if need and/or translation is planned). + + :type: {} of 2-element tuples + ''' @log_calls('GajimPlugin') def __init__(self): From 8aa9cad2e098bb55e805bbb59ab129e16857eb07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Tue, 29 Jul 2008 19:09:28 +0000 Subject: [PATCH 015/200] Plugin's configuration is now saved to disk (currently: using UserDict and shelve modules). Length Notifier Plugin has configuration dialog (added entry with JIDs to be included when plugin is working) - fully usable. Default values of configuration key has been added to GajimPlugin. Some other minor changes/fixes. --- plugins/acronyms_expander.py | 36 ++--- plugins/length_notifier/__init__.py | 2 + plugins/length_notifier/config_dialog.glade | 152 +++++++++++++++++++ plugins/length_notifier/length_notifier.py | 157 ++++++++++++++++++++ src/common/check_paths.py | 12 +- src/common/configpaths.py | 4 + src/common/gajim.py | 1 + src/plugins/gui.py | 11 +- src/plugins/helpers.py | 41 ++++- src/plugins/plugin.py | 38 +++-- 10 files changed, 415 insertions(+), 39 deletions(-) create mode 100644 plugins/length_notifier/__init__.py create mode 100644 plugins/length_notifier/config_dialog.glade create mode 100644 plugins/length_notifier/length_notifier.py diff --git a/plugins/acronyms_expander.py b/plugins/acronyms_expander.py index dda47cf17..a575fe555 100644 --- a/plugins/acronyms_expander.py +++ b/plugins/acronyms_expander.py @@ -51,32 +51,35 @@ class AcronymsExpanderPlugin(GajimPlugin): self.disconnect_from_chat_control_base) } - self.INVOKER = ' ' - self.ACRONYMS = {'RTFM' : 'Read The Friendly Manual', - '/slap' : '/me slaps', - 'PS-' : 'plug-in system', - 'G-' : 'Gajim', - 'GNT-' : 'http://trac.gajim.org/newticket', - 'GW-' : 'http://trac.gajim.org/', - 'GTS-' : 'http://trac.gajim.org/report' - } + self.config_default_values = {'INVOKER' : (' ', _('')), + 'ACRONYMS' : ({'RTFM' : 'Read The Friendly Manual', + '/slap' : '/me slaps', + 'PS-' : 'plug-in system', + 'G-' : 'Gajim', + 'GNT-' : 'http://trac.gajim.org/newticket', + 'GW-' : 'http://trac.gajim.org/', + 'GTS-' : 'http://trac.gajim.org/report' + }, _('')), + } @log_calls('AcronymsExpanderPlugin') def textbuffer_live_acronym_expander(self, tb): """ @param tb gtk.TextBuffer """ - assert isinstance(tb,gtk.TextBuffer) + #assert isinstance(tb,gtk.TextBuffer) + ACRONYMS = self.config['ACRONYMS'] + INVOKER = self.config['INVOKER'] t = tb.get_text(tb.get_start_iter(), tb.get_end_iter()) log.debug('%s %d'%(t, len(t))) - if t and t[-1] == self.INVOKER: + if t and t[-1] == INVOKER: log.debug("changing msg text") - base,sep,head=t[:-1].rpartition(self.INVOKER) + base,sep,head=t[:-1].rpartition(INVOKER) log.debug('%s | %s | %s'%(base, sep, head)) - if head in self.ACRONYMS: - head = self.ACRONYMS[head] + if head in ACRONYMS: + head = ACRONYMS[head] log.debug("head: %s"%(head)) - t = "".join((base, sep, head, self.INVOKER)) + t = "".join((base, sep, head, INVOKER)) log.debug("turning off notify") tb.freeze_notify() log.debug("setting text: '%s'"%(t)) @@ -101,5 +104,4 @@ class AcronymsExpanderPlugin(GajimPlugin): d = chat_control.acronyms_expander_plugin_data tv = chat_control.msg_textview tv.get_buffer().disconnect(d['h_id']) - - \ No newline at end of file + \ No newline at end of file diff --git a/plugins/length_notifier/__init__.py b/plugins/length_notifier/__init__.py new file mode 100644 index 000000000..fc433a0af --- /dev/null +++ b/plugins/length_notifier/__init__.py @@ -0,0 +1,2 @@ + +from length_notifier import LengthNotifierPlugin \ No newline at end of file diff --git a/plugins/length_notifier/config_dialog.glade b/plugins/length_notifier/config_dialog.glade new file mode 100644 index 000000000..0267bb184 --- /dev/null +++ b/plugins/length_notifier/config_dialog.glade @@ -0,0 +1,152 @@ + + + + + + + + True + 9 + 3 + 2 + 7 + 5 + + + True + + + True + True + Message length at which notification is invoked. + 6 + 0 0 999999 1 10 10 + True + True + + + + False + False + + + + + True + + + + + + 1 + + + + + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + + + True + True + True + Background color of text entry field in chat window when notification is invoked. + 0 + 0 + Pick a color for notification + #000000000000 + + + + False + False + + + + + True + + + + + + 1 + + + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + + True + True + JabberIDs that plugin should be used with (eg. restrict only to one microblogging bot). If empty plugin is used with every JID. [not implemented] + + + + + 1 + 2 + 2 + 3 + GTK_FILL + + + + + True + JabberIDs that plugin should be used with (eg. restrict only to one microblogging bot). If empty plugin is used with every JID. [not implemented] + 0 + JabberIDs to include: + + + 2 + 3 + GTK_FILL + GTK_FILL + + + + + True + Background color of text entry field in chat window when notification is invoked. + 0 + Notification color: + + + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + Message length at which notification is invoked. + 0 + Message length: + + + GTK_FILL + GTK_FILL + + + + + + diff --git a/plugins/length_notifier/length_notifier.py b/plugins/length_notifier/length_notifier.py new file mode 100644 index 000000000..e9e5fbcd5 --- /dev/null +++ b/plugins/length_notifier/length_notifier.py @@ -0,0 +1,157 @@ +# -*- coding: utf-8 -*- + +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## + +''' +Message length notifier plugin. + +:author: Mateusz Biliński +:since: 1st June 2008 +:copyright: Copyright (2008) Mateusz Biliński +:license: GPL +''' + +import sys + +import gtk +from common import i18n + +from plugins import GajimPlugin +from plugins.helpers import log, log_calls +from plugins.gui import GajimPluginConfigDialog + +class LengthNotifierPlugin(GajimPlugin): + name = u'Message Length Notifier' + short_name = u'length_notifier' + version = u'0.1' + description = u'''Highlights message entry field in chat window when given length of message is exceeded.''' + authors = [u'Mateusz Biliński '] + homepage = u'http://blog.bilinski.it' + + #@log_calls('LengthNotifierPlugin') + #def __init__(self): + #super(LengthNotifierPlugin, self).__init__() + + @log_calls('LengthNotifierPlugin') + def init(self): + self.config_dialog = LengthNotifierPluginConfigDialog(self) + + self.gui_extension_points = { + 'chat_control' : (self.connect_with_chat_control, + self.disconnect_from_chat_control) + } + + self.config_default_values = {'MESSAGE_WARNING_LENGTH' : (140, _('Message length at which notification is invoked.')), + 'WARNING_COLOR' : ('#F0DB3E', _('Background color of text entry field in chat window when notification is invoked.')), + 'JIDS' : ([], _('JabberIDs that plugin should be used with (eg. restrict only to one microblogging bot). If empty plugin is used with every JID. [not implemented]')) + } + + @log_calls('LengthNotifierPlugin') + def textview_length_warning(self, tb, chat_control): + tv = chat_control.msg_textview + d = chat_control.length_notifier_plugin_data + t = tb.get_text(tb.get_start_iter(), tb.get_end_iter()) + if t: + len_t = len(t) + #print("len_t: %d"%(len_t)) + if len_t>self.config['MESSAGE_WARNING_LENGTH']: + if not d['prev_color']: + d['prev_color'] = tv.style.copy().base[gtk.STATE_NORMAL] + tv.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.config['WARNING_COLOR'])) + elif d['prev_color']: + tv.modify_base(gtk.STATE_NORMAL, d['prev_color']) + d['prev_color'] = None + + @log_calls('LengthNotifierPlugin') + def connect_with_chat_control(self, chat_control): + jid = chat_control.contact.jid + if self.jid_is_ok(jid): + d = {'prev_color' : None} + tv = chat_control.msg_textview + tb = tv.get_buffer() + h_id = tb.connect('changed', self.textview_length_warning, chat_control) + d['h_id'] = h_id + + t = tb.get_text(tb.get_start_iter(), tb.get_end_iter()) + if t: + len_t = len(t) + if len_t>self.config['MESSAGE_WARNING_LENGTH']: + d['prev_color'] = tv.style.copy().base[gtk.STATE_NORMAL] + tv.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.config['WARNING_COLOR'])) + + chat_control.length_notifier_plugin_data = d + + return True + + return False + + @log_calls('LengthNotifierPlugin') + def disconnect_from_chat_control(self, chat_control): + try: + d = chat_control.length_notifier_plugin_data + tv = chat_control.msg_textview + tv.get_buffer().disconnect(d['h_id']) + if d['prev_color']: + tv.modify_base(gtk.STATE_NORMAL, d['prev_color']) + except AttributeError, error: + log.debug('Length Notifier Plugin was (probably) never connected with this chat window.\n Error: %s' % (error)) + + @log_calls('LengthNotifierPlugin') + def jid_is_ok(self, jid): + if jid in self.config['JIDS'] or not self.config['JIDS']: + return True + + return False + +class LengthNotifierPluginConfigDialog(GajimPluginConfigDialog): + def init(self): + self.GLADE_FILE_PATH = self.plugin.local_file_path('config_dialog.glade') + self.xml = gtk.glade.XML(self.GLADE_FILE_PATH, root='length_notifier_config_table', domain=i18n.APP) + self.config_table = self.xml.get_widget('length_notifier_config_table') + self.child.pack_start(self.config_table) + + self.message_length_spinbutton = self.xml.get_widget('message_length_spinbutton') + self.notification_colorbutton = self.xml.get_widget('notification_colorbutton') + self.jids_entry = self.xml.get_widget('jids_entry') + + self.xml.signal_autoconnect(self) + + def on_run(self): + self.message_length_spinbutton.set_value(self.plugin.config['MESSAGE_WARNING_LENGTH']) + self.notification_colorbutton.set_color(gtk.gdk.color_parse(self.plugin.config['WARNING_COLOR'])) + #self.jids_entry.set_text(self.plugin.config['JIDS']) + self.jids_entry.set_text(','.join(self.plugin.config['JIDS'])) + + @log_calls('LengthNotifierPluginConfigDialog') + def on_message_length_spinbutton_value_changed(self, spinbutton): + self.plugin.config['MESSAGE_WARNING_LENGTH'] = spinbutton.get_value() + + @log_calls('LengthNotifierPluginConfigDialog') + def on_notification_colorbutton_color_set(self, colorbutton): + self.plugin.config['WARNING_COLOR'] = colorbutton.get_color().to_string() + + @log_calls('LengthNotifierPluginConfigDialog') + def on_jids_entry_changed(self, entry): + text = entry.get_text() + if len(text)>0: + self.plugin.config['JIDS'] = entry.get_text().split(',') + else: + self.plugin.config['JIDS'] = [] + + @log_calls('LengthNotifierPluginConfigDialog') + def on_jids_entry_editing_done(self, entry): + pass + \ No newline at end of file diff --git a/src/common/check_paths.py b/src/common/check_paths.py index 970c5a4b2..f63344448 100644 --- a/src/common/check_paths.py +++ b/src/common/check_paths.py @@ -101,6 +101,8 @@ def check_and_possibly_create_paths(): VCARD_PATH = gajim.VCARD_PATH AVATAR_PATH = gajim.AVATAR_PATH dot_gajim = os.path.dirname(VCARD_PATH) + PLUGINS_CONFIG_PATH = gajim.PLUGINS_CONFIG_DIR + if os.path.isfile(dot_gajim): print _('%s is a file but it should be a directory') % dot_gajim print _('Gajim will now exit') @@ -123,7 +125,7 @@ def check_and_possibly_create_paths(): print _('%s is a file but it should be a directory') % AVATAR_PATH print _('Gajim will now exit') sys.exit() - + if not os.path.exists(LOG_DB_PATH): create_log_db() gajim.logger.init_vars() @@ -132,6 +134,14 @@ def check_and_possibly_create_paths(): print _('Gajim will now exit') sys.exit() + if not os.path.exists(PLUGINS_CONFIG_PATH): + create_path(PLUGINS_CONFIG_PATH) + elif os.path.isfile(PLUGINS_CONFIG_PATH): + print _('%s is a file but it should be a directory') % PLUGINS_CONFIG_PATH + print _('Gajim will now exit') + sys.exit() + + else: # dot_gajim doesn't exist if dot_gajim: # is '' on win9x so avoid that create_path(dot_gajim) diff --git a/src/common/configpaths.py b/src/common/configpaths.py index e950b3e1a..986235196 100644 --- a/src/common/configpaths.py +++ b/src/common/configpaths.py @@ -110,15 +110,19 @@ class ConfigPaths: conffile = windowsify(u'config') pidfile = windowsify(u'gajim') secretsfile = windowsify(u'secrets') + pluginsconfdir = windowsify(u'pluginsconfig') if len(profile) > 0: conffile += u'.' + profile pidfile += u'.' + profile secretsfile += u'.' + profile + pluginsconfdir += u'.' + profile + pidfile += u'.pid' self.add_from_root('CONFIG_FILE', conffile) self.add_from_root('PID_FILE', pidfile) self.add_from_root('SECRETS_FILE', secretsfile) + self.add_from_root('PLUGINS_CONFIG_DIR', pluginsconfdir) # for k, v in paths.iteritems(): # print "%s: %s" % (repr(k), repr(v)) diff --git a/src/common/gajim.py b/src/common/gajim.py index 4cbdf609b..ca015d3e1 100644 --- a/src/common/gajim.py +++ b/src/common/gajim.py @@ -88,6 +88,7 @@ DATA_DIR = gajimpaths['DATA'] HOME_DIR = gajimpaths['HOME'] PLUGINS_DIRS = [gajimpaths['PLUGINS_BASE'], gajimpaths['PLUGINS_USER']] +PLUGINS_CONFIG_DIR = gajimpaths['PLUGINS_CONFIG_DIR'] try: LANG = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc.. diff --git a/src/plugins/gui.py b/src/plugins/gui.py index 1aa4d0140..fff2a2ded 100644 --- a/src/plugins/gui.py +++ b/src/plugins/gui.py @@ -202,15 +202,24 @@ class GajimPluginConfigDialog(gtk.Dialog): self.child.set_spacing(3) + self.init() + #label = gtk.Label(_('%s Configuration') % (plugin.name)) #label.set_markup(label.get_label()) #self.child.pack_start(label, False, False) + @log_calls('GajimPluginConfigDialog') def run(self, parent=None): self.reparent(parent) + self.on_run() self.show_all() result = super(GajimPluginConfigDialog, self).run() self.hide() return result - \ No newline at end of file + + def init(self): + pass + + def on_run(self): + pass \ No newline at end of file diff --git a/src/plugins/helpers.py b/src/plugins/helpers.py index fc6ff9c5d..9dce2bf93 100644 --- a/src/plugins/helpers.py +++ b/src/plugins/helpers.py @@ -50,6 +50,12 @@ class log_calls(object): Decorator class for functions to easily log when they are entered and left. ''' + filter_out_classes = ['PluginManager'] + ''' + List of classes from which no logs should be emited when methods are called, + eventhough `log_calls` decorator is used. + ''' + def __init__(self, classname='', log=log): ''' :Keywords: @@ -71,9 +77,19 @@ class log_calls(object): :type: str ''' + self.log_this_class = True + ''' + Determines whether wrapper of given function should log calls of this + function or not. + + :type: bool + ''' if classname: self.full_func_name = classname+'.' + + if classname in self.filter_out_classes: + self.log_this_class = False def __call__(self, f): ''' @@ -82,15 +98,24 @@ class log_calls(object): :return: given function wrapped by *log.debug* statements :rtype: function ''' + self.full_func_name += f.func_name - @functools.wraps(f) - def wrapper(*args, **kwargs): - log.debug('%(funcname)s() '%{ - 'funcname': self.full_func_name}) - result = f(*args, **kwargs) - log.debug('%(funcname)s() '%{ - 'funcname': self.full_func_name}) - return result + if self.log_this_class: + @functools.wraps(f) + def wrapper(*args, **kwargs): + + log.debug('%(funcname)s() '%{ + 'funcname': self.full_func_name}) + result = f(*args, **kwargs) + log.debug('%(funcname)s() '%{ + 'funcname': self.full_func_name}) + return result + else: + @functools.wraps(f) + def wrapper(*args, **kwargs): + result = f(*args, **kwargs) + return result + return wrapper class Singleton(type): diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py index 3a6144472..b08009072 100644 --- a/src/plugins/plugin.py +++ b/src/plugins/plugin.py @@ -133,10 +133,6 @@ class GajimPlugin(object): @log_calls('GajimPlugin') def load_config(self): self.config.load() - - @log_calls('GajimPlugin') - def __del__(self): - self.save_config() @log_calls('GajimPlugin') def local_file_path(self, file_name): @@ -155,20 +151,38 @@ class GajimPlugin(object): pass import shelve +import UserDict -class GajimPluginConfig(dict): +class GajimPluginConfig(UserDict.DictMixin): @log_calls('GajimPluginConfig') def __init__(self, plugin): self.plugin = plugin - self.FILE_PATH = gajim.HOME_DIR - log.debug('FILE_PATH = %s'%(self.FILE_PATH)) - #self.data = shelve.open(self.FILE_PATH) + self.FILE_PATH = os.path.join(gajim.PLUGINS_CONFIG_DIR, self.plugin.short_name) + #log.debug('FILE_PATH = %s'%(self.FILE_PATH)) + self.data = None + self.load() + + @log_calls('GajimPluginConfig') + def __getitem__(self, key): + if not key in self.data: + self.data[key] = self.plugin.config_default_values[key][0] + self.save() + + return self.data[key] + @log_calls('GajimPluginConfig') + def __setitem__(self, key, value): + self.data[key] = value + self.save() + + def keys(self): + return self.data.keys() + @log_calls('GajimPluginConfig') def save(self): - pass - + self.data.sync() + log.debug(str(self.data)) + @log_calls('GajimPluginConfig') def load(self): - pass - \ No newline at end of file + self.data = shelve.open(self.FILE_PATH) From 96cfc420600c67e0466ed143c09c46ba7c1358f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Thu, 31 Jul 2008 15:30:20 +0000 Subject: [PATCH 016/200] Added files with Banner Tweaks plugin (initial version). Few changes to PluginManager. Added new GUI extension point related to draw_banner in ChatControlBase. --- plugins/banner_tweaks/__init__.py | 2 + plugins/banner_tweaks/config_dialog.glade | 73 ++++++++++++ plugins/banner_tweaks/plugin.py | 135 ++++++++++++++++++++++ src/chat_control.py | 2 + src/plugins/pluginmanager.py | 22 ++-- 5 files changed, 225 insertions(+), 9 deletions(-) create mode 100644 plugins/banner_tweaks/__init__.py create mode 100644 plugins/banner_tweaks/config_dialog.glade create mode 100644 plugins/banner_tweaks/plugin.py diff --git a/plugins/banner_tweaks/__init__.py b/plugins/banner_tweaks/__init__.py new file mode 100644 index 000000000..f8266d228 --- /dev/null +++ b/plugins/banner_tweaks/__init__.py @@ -0,0 +1,2 @@ + +from plugin import BannerTweaksPlugin \ No newline at end of file diff --git a/plugins/banner_tweaks/config_dialog.glade b/plugins/banner_tweaks/config_dialog.glade new file mode 100644 index 000000000..fd603b1c5 --- /dev/null +++ b/plugins/banner_tweaks/config_dialog.glade @@ -0,0 +1,73 @@ + + + + + + + + + + diff --git a/plugins/banner_tweaks/plugin.py b/plugins/banner_tweaks/plugin.py new file mode 100644 index 000000000..cecbbb38b --- /dev/null +++ b/plugins/banner_tweaks/plugin.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- + +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## + +''' +Adjustable chat window banner. + +Includes tweaks to make it compact. + +Based on patch by pb in ticket #4133: +http://trac.gajim.org/attachment/ticket/4133/gajim-chatbanneroptions-svn10008.patch + +:author: Mateusz Biliński +:since: 30 July 2008 +:copyright: Copyright (2008) Mateusz Biliński +:license: GPL +''' + +import sys + +import gtk +from common import i18n +from common import gajim + +from plugins import GajimPlugin +from plugins.helpers import log, log_calls +from plugins.gui import GajimPluginConfigDialog + +class BannerTweaksPlugin(GajimPlugin): + name = u'Banner Tweaks' + short_name = u'banner_tweaks' + version = u'0.1' + description = u'''Allows user to tweak chat window banner appearance (eg. make it compact). + +Based on patch by pb in ticket #4133: +http://trac.gajim.org/attachment/ticket/4133''' + authors = [u'Mateusz Biliński '] + homepage = u'http://blog.bilinski.it' + + @log_calls('BannerTweaksPlugin') + def init(self): + self.config_dialog = BannerTweaksPluginConfigDialog(self) + + self.gui_extension_points = { + 'chat_control_base_draw_banner' : (self.chat_control_base_draw_banner_called, + self.chat_control_base_draw_banner_deactivation) + } + + self.config_default_values = {'show_banner_image': (True, _('If True, Gajim will display a status icon in the banner of chat windows.')), + 'show_banner_online_msg': (True, _('If True, Gajim will display the status message of the contact in the banner of chat windows.')), + 'show_banner_resource': (False, _('If True, Gajim will display the resource name of the contact in the banner of chat windows.')), + 'banner_small_fonts': (False, _('If True, Gajim will use small fonts for contact name and resource name in the banner of chat windows.')), + 'old_chat_avatar_height' : (52, _('chat_avatar_height value before plugin was activated')), + } + + def activate(self): + self.config['old_chat_avatar_height'] = gajim.config.get('chat_avatar_height') + #gajim.config.set('chat_avatar_height', 28) + + def deactivate(self): + gajim.config.set('chat_avatar_height', self.config['old_chat_avatar_height']) + + def chat_control_base_draw_banner_called(self, chat_control): + if not self.config['show_banner_online_msg']: + chat_control.banner_status_label.hide() + chat_control.banner_status_label.set_no_show_all(True) + status_text = '' + chat_control.banner_status_label.set_markup(status_text) + + if not self.config['show_banner_image']: + banner_status_img = chat_control.xml.get_widget('banner_status_image') + banner_status_img.clear() + + def chat_control_base_draw_banner_deactivation(self, chat_control): + pass + #chat_control.draw_banner() + + #@log_calls('BannerTweaksPlugin') + #def connect_with_chat_control(self, chat_control): + #d = {} + #banner_status_img = chat_control.xml.get_widget('banner_status_image') + #h_id = banner_status_img.connect('state-changed', self.on_banner_status_img_state_changed, chat_control) + #d['banner_img_h_id'] = h_id + + #chat_control.banner_tweaks_plugin_data = d + + #@log_calls('BannerTweaksPlugin') + #def disconnect_from_chat_control(self, chat_control): + #pass + +class BannerTweaksPluginConfigDialog(GajimPluginConfigDialog): + def init(self): + self.GLADE_FILE_PATH = self.plugin.local_file_path('config_dialog.glade') + self.xml = gtk.glade.XML(self.GLADE_FILE_PATH, root='banner_tweaks_config_vbox', domain=i18n.APP) + self.config_vbox = self.xml.get_widget('banner_tweaks_config_vbox') + self.child.pack_start(self.config_vbox) + + self.show_banner_image_checkbutton = self.xml.get_widget('show_banner_image_checkbutton') + self.show_banner_online_msg_checkbutton = self.xml.get_widget('show_banner_online_msg_checkbutton') + self.show_banner_resource_checkbutton = self.xml.get_widget('show_banner_resource_checkbutton') + self.banner_small_fonts_checkbutton = self.xml.get_widget('banner_small_fonts_checkbutton') + + self.xml.signal_autoconnect(self) + + def on_run(self): + self.show_banner_image_checkbutton.set_active(self.plugin.config['show_banner_image']) + self.show_banner_online_msg_checkbutton.set_active(self.plugin.config['show_banner_online_msg']) + self.show_banner_resource_checkbutton.set_active(self.plugin.config['show_banner_resource']) + self.banner_small_fonts_checkbutton.set_active(self.plugin.config['banner_small_fonts']) + + def on_show_banner_image_checkbutton_toggled(self, button): + self.plugin.config['show_banner_image'] = button.get_active() + + def on_show_banner_online_msg_checkbutton_toggled(self, button): + self.plugin.config['show_banner_online_msg'] = button.get_active() + + def on_show_banner_resource_checkbutton_toggled(self, button): + self.plugin.config['show_banner_resource'] = button.get_active() + + def on_banner_small_fonts_checkbutton_toggled(self, button): + self.plugin.config['banner_small_fonts'] = button.get_active() + \ No newline at end of file diff --git a/src/chat_control.py b/src/chat_control.py index c0859fb0f..9d53dfda1 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -109,6 +109,8 @@ class ChatControlBase(MessageControl): ''' self.draw_banner_text() self._update_banner_state_image() + gajim.plugin_manager.gui_extension_point('chat_control_base_draw_banner', + self) # Derived types MAY implement this def draw_banner_text(self): diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py index 6d74a78a9..135a9b4a0 100644 --- a/src/plugins/pluginmanager.py +++ b/src/plugins/pluginmanager.py @@ -194,6 +194,12 @@ class PluginManager(object): return success def deactivate_plugin(self, plugin): + # remove GUI extension points handlers (provided by plug-in) from + # handlers list + for gui_extpoint_name, gui_extpoint_handlers in \ + plugin.gui_extension_points.iteritems(): + self.gui_extension_points_handlers[gui_extpoint_name].remove(gui_extpoint_handlers) + # detaching plug-in from handler GUI extension points (calling # cleaning up method that must be provided by plug-in developer # for each handled GUI extension point) @@ -201,14 +207,10 @@ class PluginManager(object): plugin.gui_extension_points.iteritems(): if gui_extpoint_name in self.gui_extension_points: for gui_extension_point_args in self.gui_extension_points[gui_extpoint_name]: - gui_extpoint_handlers[1](*gui_extension_point_args) - - # remove GUI extension points handlers (provided by plug-in) from - # handlers list - for gui_extpoint_name, gui_extpoint_handlers in \ - plugin.gui_extension_points.iteritems(): - self.gui_extension_points_handlers[gui_extpoint_name].remove(gui_extpoint_handlers) - + handler = gui_extpoint_handlers[1] + if handler: + handler(*gui_extension_point_args) + # removing plug-in from active plug-ins list plugin.deactivate() self.active_plugins.remove(plugin) @@ -232,7 +234,9 @@ class PluginManager(object): plugin.gui_extension_points.iteritems(): if gui_extpoint_name in self.gui_extension_points: for gui_extension_point_args in self.gui_extension_points[gui_extpoint_name]: - gui_extpoint_handlers[0](*gui_extension_point_args) + handler = gui_extpoint_handlers[0] + if handler: + handler(*gui_extension_point_args) @log_calls('PluginManager') def _activate_all_plugins(self): From 5cce0a8ca9ae1fcca74feee322bc4de966a797e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Sat, 2 Aug 2008 17:29:32 +0000 Subject: [PATCH 017/200] Banner Tweaks plugin has all (four) options implemented. --- plugins/banner_tweaks/plugin.py | 89 ++++++++++++++++++++++---- plugins/length_notifier.py | 108 -------------------------------- 2 files changed, 76 insertions(+), 121 deletions(-) delete mode 100644 plugins/length_notifier.py diff --git a/plugins/banner_tweaks/plugin.py b/plugins/banner_tweaks/plugin.py index cecbbb38b..ee5cf2500 100644 --- a/plugins/banner_tweaks/plugin.py +++ b/plugins/banner_tweaks/plugin.py @@ -32,8 +32,11 @@ http://trac.gajim.org/attachment/ticket/4133/gajim-chatbanneroptions-svn10008.pa import sys import gtk +import gobject +import message_control from common import i18n from common import gajim +from common import helpers from plugins import GajimPlugin from plugins.helpers import log, log_calls @@ -83,23 +86,83 @@ http://trac.gajim.org/attachment/ticket/4133''' if not self.config['show_banner_image']: banner_status_img = chat_control.xml.get_widget('banner_status_image') banner_status_img.clear() + + # TODO: part below repeats a lot of code from ChatControl.draw_banner_text() + # This could be rewritten using re module: getting markup text from + # banner_name_label and replacing some elements based on plugin config. + # Would it be faster? + if self.config['show_banner_resource'] or self.config['banner_small_fonts']: + banner_name_label = chat_control.xml.get_widget('banner_name_label') + label_text = banner_name_label.get_label() + log.debug('label_text = "%s"'%(label_text)) + + contact = chat_control.contact + jid = contact.jid + + name = contact.get_shown_name() + if chat_control.resource: + name += '/' + chat_control.resource + elif contact.resource and self.config['show_banner_resource']: + name += '/' + contact.resource + + if chat_control.TYPE_ID == message_control.TYPE_PM: + name = _('%(nickname)s from group chat %(room_name)s') %\ + {'nickname': name, 'room_name': chat_control.room_name} + name = gobject.markup_escape_text(name) + + # We know our contacts nick, but if another contact has the same nick + # in another account we need to also display the account. + # except if we are talking to two different resources of the same contact + acct_info = '' + for account in gajim.contacts.get_accounts(): + if account == chat_control.account: + continue + if acct_info: # We already found a contact with same nick + break + for jid in gajim.contacts.get_jid_list(account): + other_contact_ = \ + gajim.contacts.get_first_contact_from_jid(account, jid) + if other_contact_.get_shown_name() == chat_control.contact.get_shown_name(): + acct_info = ' (%s)' % \ + gobject.markup_escape_text(chat_control.account) + break + + font_attrs, font_attrs_small = chat_control.get_font_attrs() + if self.config['banner_small_fonts']: + font_attrs = font_attrs_small + + st = gajim.config.get('displayed_chat_state_notifications') + cs = contact.chatstate + if cs and st in ('composing_only', 'all'): + if contact.show == 'offline': + chatstate = '' + elif contact.composing_xep == 'XEP-0085': + if st == 'all' or cs == 'composing': + chatstate = helpers.get_uf_chatstate(cs) + else: + chatstate = '' + elif contact.composing_xep == 'XEP-0022': + if cs in ('composing', 'paused'): + # only print composing, paused + chatstate = helpers.get_uf_chatstate(cs) + else: + chatstate = '' + else: + # When does that happen ? See [7797] and [7804] + chatstate = helpers.get_uf_chatstate(cs) + + label_text = '%s%s %s' % \ + (font_attrs, name, font_attrs_small, acct_info, chatstate) + else: + # weight="heavy" size="x-large" + label_text = '%s%s' % \ + (font_attrs, name, font_attrs_small, acct_info) + + banner_name_label.set_markup(label_text) def chat_control_base_draw_banner_deactivation(self, chat_control): pass #chat_control.draw_banner() - - #@log_calls('BannerTweaksPlugin') - #def connect_with_chat_control(self, chat_control): - #d = {} - #banner_status_img = chat_control.xml.get_widget('banner_status_image') - #h_id = banner_status_img.connect('state-changed', self.on_banner_status_img_state_changed, chat_control) - #d['banner_img_h_id'] = h_id - - #chat_control.banner_tweaks_plugin_data = d - - #@log_calls('BannerTweaksPlugin') - #def disconnect_from_chat_control(self, chat_control): - #pass class BannerTweaksPluginConfigDialog(GajimPluginConfigDialog): def init(self): diff --git a/plugins/length_notifier.py b/plugins/length_notifier.py deleted file mode 100644 index 7c14812e5..000000000 --- a/plugins/length_notifier.py +++ /dev/null @@ -1,108 +0,0 @@ -# -*- coding: utf-8 -*- - -## This file is part of Gajim. -## -## Gajim is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published -## by the Free Software Foundation; version 3 only. -## -## Gajim is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with Gajim. If not, see . -## - -''' -Message length notifier plugin. - -:author: Mateusz Biliński -:since: 1st June 2008 -:copyright: Copyright (2008) Mateusz Biliński -:license: GPL -''' - -import sys - -import gtk - -from plugins import GajimPlugin -from plugins.helpers import log, log_calls - -class LengthNotifierPlugin(GajimPlugin): - name = u'Message Length Notifier' - short_name = u'length_notifier' - version = u'0.1' - description = u'''Highlights message entry field in chat window when given length of message is exceeded.''' - authors = [u'Mateusz Biliński '] - homepage = u'http://blog.bilinski.it' - - #@log_calls('LengthNotifierPlugin') - #def __init__(self): - #super(LengthNotifierPlugin, self).__init__() - - @log_calls('LengthNotifierPlugin') - def init(self): - #self.config_dialog = None - - self.gui_extension_points = { - 'chat_control' : (self.connect_with_chat_control, - self.disconnect_from_chat_control) - } - - self.MESSAGE_WARNING_LENGTH = 140 - self.WARNING_COLOR = gtk.gdk.color_parse('#F0DB3E') - self.JIDS = [] - - @log_calls('LengthNotifierPlugin') - def textview_length_warning(self, tb, chat_control): - tv = chat_control.msg_textview - d = chat_control.length_notifier_plugin_data - t = tb.get_text(tb.get_start_iter(), tb.get_end_iter()) - if t: - len_t = len(t) - #print("len_t: %d"%(len_t)) - if len_t>self.MESSAGE_WARNING_LENGTH: - if not d['prev_color']: - d['prev_color'] = tv.style.copy().base[gtk.STATE_NORMAL] - tv.modify_base(gtk.STATE_NORMAL, self.WARNING_COLOR) - elif d['prev_color']: - tv.modify_base(gtk.STATE_NORMAL, d['prev_color']) - d['prev_color'] = None - - @log_calls('LengthNotifierPlugin') - def connect_with_chat_control(self, chat_control): - jid = chat_control.contact.jid - if self.jid_is_ok(jid): - d = {'prev_color' : None} - tv = chat_control.msg_textview - tb = tv.get_buffer() - h_id = tb.connect('changed', self.textview_length_warning, chat_control) - d['h_id'] = h_id - - t = tb.get_text(tb.get_start_iter(), tb.get_end_iter()) - if t: - len_t = len(t) - if len_t>self.MESSAGE_WARNING_LENGTH: - d['prev_color'] = tv.style.copy().base[gtk.STATE_NORMAL] - tv.modify_base(gtk.STATE_NORMAL, self.WARNING_COLOR) - - chat_control.length_notifier_plugin_data = d - - return True - - return False - - @log_calls('LengthNotifierPlugin') - def disconnect_from_chat_control(self, chat_control): - d = chat_control.length_notifier_plugin_data - tv = chat_control.msg_textview - tv.get_buffer().disconnect(d['h_id']) - if d['prev_color']: - tv.modify_base(gtk.STATE_NORMAL, d['prev_color']) - - @log_calls('LengthNotifierPlugin') - def jid_is_ok(self, jid): - return True \ No newline at end of file From 16ac65e58b12a6e1cd84ea72b3e764a93c8ef27b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Sun, 3 Aug 2008 13:29:11 +0000 Subject: [PATCH 018/200] Improvements to GUI extension points handling - added method to remove these from PluginManager (memory optimization). Removed logging from most of the code. --- plugins/acronyms_expander.py | 17 +-- plugins/banner_tweaks/plugin.py | 1 - plugins/length_notifier/config_dialog.glade | 152 ++++++++++---------- plugins/length_notifier/length_notifier.py | 7 +- plugins/roster_buttons/plugin.py | 5 - src/chat_control.py | 17 +++ src/groupchat_control.py | 5 + src/message_control.py | 2 +- src/plugins/gui.py | 2 +- src/plugins/helpers.py | 12 +- src/plugins/plugin.py | 14 +- src/plugins/pluginmanager.py | 123 ++++++++++++---- 12 files changed, 226 insertions(+), 131 deletions(-) diff --git a/plugins/acronyms_expander.py b/plugins/acronyms_expander.py index a575fe555..6dec95614 100644 --- a/plugins/acronyms_expander.py +++ b/plugins/acronyms_expander.py @@ -38,11 +38,8 @@ class AcronymsExpanderPlugin(GajimPlugin): description = u'''Replaces acronyms (or other strings) with given expansions/substitutes.''' authors = [u'Mateusz Biliński '] homepage = u'http://blog.bilinski.it' - - #@log_calls('AcronymsExpanderPlugin') - #def __init__(self): - #super(AcronymsExpanderPlugin, self).__init__() + @log_calls('AcronymsExpanderPlugin') def init(self): self.config_dialog = None @@ -71,20 +68,20 @@ class AcronymsExpanderPlugin(GajimPlugin): ACRONYMS = self.config['ACRONYMS'] INVOKER = self.config['INVOKER'] t = tb.get_text(tb.get_start_iter(), tb.get_end_iter()) - log.debug('%s %d'%(t, len(t))) + #log.debug('%s %d'%(t, len(t))) if t and t[-1] == INVOKER: - log.debug("changing msg text") + #log.debug("changing msg text") base,sep,head=t[:-1].rpartition(INVOKER) - log.debug('%s | %s | %s'%(base, sep, head)) + #log.debug('%s | %s | %s'%(base, sep, head)) if head in ACRONYMS: head = ACRONYMS[head] log.debug("head: %s"%(head)) t = "".join((base, sep, head, INVOKER)) - log.debug("turning off notify") + #log.debug("turning off notify") tb.freeze_notify() - log.debug("setting text: '%s'"%(t)) + #log.debug("setting text: '%s'"%(t)) tb.set_text(t) - log.debug("turning on notify") + #log.debug("turning on notify") tb.thaw_notify() @log_calls('AcronymsExpanderPlugin') diff --git a/plugins/banner_tweaks/plugin.py b/plugins/banner_tweaks/plugin.py index ee5cf2500..ba059143e 100644 --- a/plugins/banner_tweaks/plugin.py +++ b/plugins/banner_tweaks/plugin.py @@ -94,7 +94,6 @@ http://trac.gajim.org/attachment/ticket/4133''' if self.config['show_banner_resource'] or self.config['banner_small_fonts']: banner_name_label = chat_control.xml.get_widget('banner_name_label') label_text = banner_name_label.get_label() - log.debug('label_text = "%s"'%(label_text)) contact = chat_control.contact jid = contact.jid diff --git a/plugins/length_notifier/config_dialog.glade b/plugins/length_notifier/config_dialog.glade index 0267bb184..be20bc638 100644 --- a/plugins/length_notifier/config_dialog.glade +++ b/plugins/length_notifier/config_dialog.glade @@ -1,6 +1,6 @@ - + @@ -12,40 +12,58 @@ 7 5 - + True - - - True - True - Message length at which notification is invoked. - 6 - 0 0 999999 1 10 10 - True - True - - - - False - False - - - - - True - - - - - - 1 - - + Message length at which notification is invoked. + 0 + Message length: + + + GTK_FILL + GTK_FILL + + + + + True + Background color of text entry field in chat window when notification is invoked. + 0 + Notification color: + + + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + JabberIDs that plugin should be used with (eg. restrict only to one microblogging bot). Use comma (without space) as separator. If empty plugin is used with every JID. + 0 + JabberIDs to include: + + + 2 + 3 + GTK_FILL + GTK_FILL + + + + + True + True + JabberIDs that plugin should be used with (eg. restrict only to one microblogging bot). Use comma (without space) as separator. If empty plugin is used with every JID. + + 1 2 - GTK_FILL + 2 + 3 GTK_FILL @@ -91,57 +109,39 @@ - + True - True - JabberIDs that plugin should be used with (eg. restrict only to one microblogging bot). If empty plugin is used with every JID. [not implemented] - - + + + True + True + Message length at which notification is invoked. + 6 + 0 0 999999 1 10 10 + True + True + + + + False + False + + + + + True + + + + + + 1 + + 1 2 - 2 - 3 - GTK_FILL - - - - - True - JabberIDs that plugin should be used with (eg. restrict only to one microblogging bot). If empty plugin is used with every JID. [not implemented] - 0 - JabberIDs to include: - - - 2 - 3 - GTK_FILL - GTK_FILL - - - - - True - Background color of text entry field in chat window when notification is invoked. - 0 - Notification color: - - - 1 - 2 - GTK_FILL - GTK_FILL - - - - - True - Message length at which notification is invoked. - 0 - Message length: - - GTK_FILL GTK_FILL diff --git a/plugins/length_notifier/length_notifier.py b/plugins/length_notifier/length_notifier.py index e9e5fbcd5..c8b45a284 100644 --- a/plugins/length_notifier/length_notifier.py +++ b/plugins/length_notifier/length_notifier.py @@ -40,10 +40,6 @@ class LengthNotifierPlugin(GajimPlugin): description = u'''Highlights message entry field in chat window when given length of message is exceeded.''' authors = [u'Mateusz Biliński '] homepage = u'http://blog.bilinski.it' - - #@log_calls('LengthNotifierPlugin') - #def __init__(self): - #super(LengthNotifierPlugin, self).__init__() @log_calls('LengthNotifierPlugin') def init(self): @@ -107,7 +103,8 @@ class LengthNotifierPlugin(GajimPlugin): if d['prev_color']: tv.modify_base(gtk.STATE_NORMAL, d['prev_color']) except AttributeError, error: - log.debug('Length Notifier Plugin was (probably) never connected with this chat window.\n Error: %s' % (error)) + pass + #log.debug('Length Notifier Plugin was (probably) never connected with this chat window.\n Error: %s' % (error)) @log_calls('LengthNotifierPlugin') def jid_is_ok(self, jid): diff --git a/plugins/roster_buttons/plugin.py b/plugins/roster_buttons/plugin.py index a1aa29482..a7ebb7d55 100644 --- a/plugins/roster_buttons/plugin.py +++ b/plugins/roster_buttons/plugin.py @@ -40,14 +40,9 @@ class RosterButtonsPlugin(GajimPlugin): description = u'''Adds quick action buttons to roster window.''' authors = [u'Mateusz Biliński '] homepage = u'http://blog.bilinski.it' - - #@log_calls('RosterButtonsPlugin') - #def __init__(self): - #super(RosterButtonsPlugin, self).__init__() @log_calls('RosterButtonsPlugin') def init(self): - #log.debug('self.__path__==%s'%(self.__path__)) self.GLADE_FILE_PATH = self.local_file_path('roster_buttons.glade') self.roster_vbox = gajim.interface.roster.xml.get_widget('roster_vbox2') diff --git a/src/chat_control.py b/src/chat_control.py index 9d53dfda1..07967bf60 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -294,7 +294,15 @@ class ChatControlBase(MessageControl): self.smooth = True + # PluginSystem: adding GUI extension point for ChatControlBase + # instance object (also subclasses, eg. ChatControl or GroupchatControl) gajim.plugin_manager.gui_extension_point('chat_control_base', self) + + def shutdown(self): + # PluginSystem: removing GUI extension points connected with ChatControlBase + # instance object + gajim.plugin_manager.remove_gui_extension_point('chat_control_base', self) + gajim.plugin_manager.remove_gui_extension_point('chat_control_base_draw_banner', self) def on_msg_textview_populate_popup(self, textview, menu): '''we override the default context menu and we prepend an option to switch languages''' @@ -1144,6 +1152,8 @@ class ChatControl(ChatControlBase): # restore previous conversation self.restore_conversation() + # PluginSystem: adding GUI extension point for this ChatControl + # instance object gajim.plugin_manager.gui_extension_point('chat_control', self) def on_avatar_eventbox_enter_notify_event(self, widget, event): @@ -2022,6 +2032,13 @@ class ChatControl(ChatControlBase): self.reset_kbd_mouse_timeout_vars() def shutdown(self): + # PluginSystem: calling shutdown of super class (ChatControlBase) to let it remove + # it's GUI extension points + super(ChatControl, self).shutdown() + # PluginSystem: removing GUI extension points connected with ChatControl + # instance object + gajim.plugin_manager.remove_gui_extension_point('chat_control', self) + # destroy banner tooltip - bug #pygtk for that! self.status_tooltip.destroy() diff --git a/src/groupchat_control.py b/src/groupchat_control.py index 84b31fa0a..5350190dd 100644 --- a/src/groupchat_control.py +++ b/src/groupchat_control.py @@ -1636,6 +1636,11 @@ class GroupchatControl(ChatControlBase): status = self.subject) def shutdown(self, status='offline'): + # PluginSystem: calling shutdown of super class (ChatControlBase) + # to let it remove it's GUI extension points + super(GroupchatControl, self).shutdown() + + # destroy banner tooltip - bug #pygtk for that! self.subject_tooltip.destroy() gajim.connections[self.account].send_gc_status(self.nick, self.room_jid, diff --git a/src/message_control.py b/src/message_control.py index ebff23368..268cd4e8c 100644 --- a/src/message_control.py +++ b/src/message_control.py @@ -28,7 +28,7 @@ TYPE_PM = 'pm' #################### -class MessageControl: +class MessageControl(object): '''An abstract base widget that can embed in the gtk.Notebook of a MessageWindow''' def __init__(self, type_id, parent_win, widget_name, contact, account, resource = None): diff --git a/src/plugins/gui.py b/src/plugins/gui.py index fff2a2ded..e74e86ef5 100644 --- a/src/plugins/gui.py +++ b/src/plugins/gui.py @@ -170,7 +170,7 @@ class PluginsWindow(object): @log_calls('PluginsWindow') def on_configure_plugin_button_clicked(self, widget): - log.debug('widget: %s'%(widget)) + #log.debug('widget: %s'%(widget)) selection = self.installed_plugins_treeview.get_selection() model, iter = selection.get_selected() if iter: diff --git a/src/plugins/helpers.py b/src/plugins/helpers.py index 9dce2bf93..4034a7ebc 100644 --- a/src/plugins/helpers.py +++ b/src/plugins/helpers.py @@ -50,7 +50,8 @@ class log_calls(object): Decorator class for functions to easily log when they are entered and left. ''' - filter_out_classes = ['PluginManager'] + filter_out_classes = ['GajimPlugin', 'GajimPluginConfig', + 'GajimPluginConfigDialog', 'PluginsWindow'] ''' List of classes from which no logs should be emited when methods are called, eventhough `log_calls` decorator is used. @@ -129,10 +130,11 @@ class Singleton(type): def __call__(cls,*args,**kw): if cls.instance is None: cls.instance=super(Singleton,cls).__call__(*args,**kw) - log.debug('%(classname)s - new instance created'%{ - 'classname' : cls.__name__}) + #log.debug('%(classname)s - new instance created'%{ + #'classname' : cls.__name__}) else: - log.debug('%(classname)s - returning already existing instance'%{ - 'classname' : cls.__name__}) + pass + #log.debug('%(classname)s - returning already existing instance'%{ + #'classname' : cls.__name__}) return cls.instance \ No newline at end of file diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py index b08009072..d53b25960 100644 --- a/src/plugins/plugin.py +++ b/src/plugins/plugin.py @@ -130,10 +130,22 @@ class GajimPlugin(object): def save_config(self): self.config.save() - @log_calls('GajimPlugin') + @log_calls('GajimPlugin') def load_config(self): self.config.load() + def __eq__(self, plugin): + if self.short_name == plugin.short_name: + return True + + return False + + def __ne__(self, plugin): + if self.short_name != plugin.short_name: + return True + + return False + @log_calls('GajimPlugin') def local_file_path(self, file_name): return os.path.join(self.__path__, file_name) diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py index 135a9b4a0..038417591 100644 --- a/src/plugins/pluginmanager.py +++ b/src/plugins/pluginmanager.py @@ -96,11 +96,11 @@ class PluginManager(object): for path in gajim.PLUGINS_DIRS: self.add_plugins(PluginManager.scan_dir_for_plugins(path)) - log.debug('plugins: %s'%(self.plugins)) + #log.debug('plugins: %s'%(self.plugins)) self._activate_all_plugins_from_global_config() - log.debug('active: %s'%(self.active_plugins)) + #log.debug('active: %s'%(self.active_plugins)) @log_calls('PluginManager') def _plugin_has_entry_in_global_config(self, plugin): @@ -120,11 +120,16 @@ class PluginManager(object): and adding class from reloaded module or ignoring adding plug-in? ''' plugin = plugin_class() - if not self._plugin_has_entry_in_global_config(plugin): - self._create_plugin_entry_in_global_config(plugin) - - self.plugins.append(plugin) - plugin.active = False + + if plugin not in self.plugins: + if not self._plugin_has_entry_in_global_config(plugin): + self._create_plugin_entry_in_global_config(plugin) + + self.plugins.append(plugin) + plugin.active = False + else: + log.info('Not loading plugin %s v%s from module %s (identified by short name: %s). Plugin already loaded.'%( + plugin.name, plugin.version, plugin.__module__, plugin.short_name)) @log_calls('PluginManager') def add_plugins(self, plugin_classes): @@ -134,7 +139,9 @@ class PluginManager(object): @log_calls('PluginManager') def gui_extension_point(self, gui_extpoint_name, *args): ''' - Invokes all handlers (from plugins) for particular GUI extension point. + Invokes all handlers (from plugins) for particular GUI extension point + and adds it to collection for further processing (eg. by plugins not active + yet). :param gui_extpoint_name: name of GUI extension point. :type gui_extpoint_name: unicode @@ -157,10 +164,69 @@ class PluginManager(object): self._add_gui_extension_point_call_to_list(gui_extpoint_name, *args) self._execute_all_handlers_of_gui_extension_point(gui_extpoint_name, *args) + + @log_calls('PluginManager') + def remove_gui_extension_point(self, gui_extpoint_name, *args): + ''' + Removes GUI extension point from collection held by `PluginManager`. + + From this point this particular extension point won't be visible + to plugins (eg. it won't invoke any handlers when plugin is activated). + + GUI extension point is removed completely (there is no way to recover it + from inside `PluginManager`). + + Removal is needed when instance object that given extension point was + connect with is destroyed (eg. ChatControl is closed or context menu + is hidden). + + Each `PluginManager.gui_extension_point` call should have a call of + `PluginManager.remove_gui_extension_point` related to it. + + :note: in current implementation different arguments mean different + extension points. The same arguments and the same name mean + the same extension point. + :todo: instead of using argument to identify which extpoint should be + removed, maybe add additional 'id' argument - this would work similar + hash in Python objects. 'id' would be calculated based on arguments + passed or on anything else (even could be constant). This would give + core developers (that add new extpoints) more freedom, but is this + necessary? + + :param gui_extpoint_name: name of GUI extension point. + :type gui_extpoint_name: unicode + :param args: arguments that `PluginManager.gui_extension_point` was + called with for this extension point. This is used (along with + extension point name) to identify element to be removed. + :type args: tuple + ''' + log.debug('name: %s\n args: %s'%(gui_extpoint_name, args)) + @log_calls('PluginManager') def _add_gui_extension_point_call_to_list(self, gui_extpoint_name, *args): - self.gui_extension_points.setdefault(gui_extpoint_name, []).append(args) + ''' + Adds GUI extension point call to list of calls. + + This is done only if such call hasn't been added already + (same extension point name and same arguments). + + :note: This is assumption that GUI extension points are different only + if they have different name or different arguments. + + :param gui_extpoint_name: GUI extension point name used to identify it + by plugins. + :type gui_extpoint_name: str + + :param args: parameters to be passed to extension point handlers + (typically and object that invokes `gui_extension_point`; however, + this can be practically anything) + :type args: tuple + + ''' + if ((gui_extpoint_name not in self.gui_extension_points) + or (args not in self.gui_extension_points[gui_extpoint_name])): + self.gui_extension_points.setdefault(gui_extpoint_name, []).append(args) @log_calls('PluginManager') def _execute_all_handlers_of_gui_extension_point(self, gui_extpoint_name, *args): @@ -287,57 +353,62 @@ class PluginManager(object): #log.debug(sys.path) for elem_name in dir_list: - log.debug('- "%s"'%(elem_name)) + #log.debug('- "%s"'%(elem_name)) file_path = os.path.join(path, elem_name) - log.debug(' "%s"'%(file_path)) + #log.debug(' "%s"'%(file_path)) module = None if os.path.isfile(file_path) and fnmatch.fnmatch(file_path,'*.py'): module_name = os.path.splitext(elem_name)[0] - log.debug('Possible module detected.') + #log.debug('Possible module detected.') try: module = __import__(module_name) - log.debug('Module imported.') + #log.debug('Module imported.') except ValueError, value_error: - log.debug('Module not imported successfully. ValueError: %s'%(value_error)) + pass + #log.debug('Module not imported successfully. ValueError: %s'%(value_error)) except ImportError, import_error: - log.debug('Module not imported successfully. ImportError: %s'%(import_error)) + pass + #log.debug('Module not imported successfully. ImportError: %s'%(import_error)) elif os.path.isdir(file_path): module_name = elem_name file_path += os.path.sep - log.debug('Possible package detected.') + #log.debug('Possible package detected.') try: module = __import__(module_name) - log.debug('Package imported.') + #log.debug('Package imported.') except ValueError, value_error: - log.debug('Package not imported successfully. ValueError: %s'%(value_error)) + pass + #log.debug('Package not imported successfully. ValueError: %s'%(value_error)) except ImportError, import_error: - log.debug('Package not imported successfully. ImportError: %s'%(import_error)) + pass + #log.debug('Package not imported successfully. ImportError: %s'%(import_error)) if module: - log.debug('Attributes processing started') + #log.debug('Attributes processing started') for module_attr_name in [attr_name for attr_name in dir(module) if not (attr_name.startswith('__') or attr_name.endswith('__'))]: module_attr = getattr(module, module_attr_name) - log.debug('%s : %s'%(module_attr_name, module_attr)) + #log.debug('%s : %s'%(module_attr_name, module_attr)) try: if issubclass(module_attr, GajimPlugin) and \ not module_attr is GajimPlugin: - log.debug('is subclass of GajimPlugin') + #log.debug('is subclass of GajimPlugin') #log.debug('file_path: %s\nabspath: %s\ndirname: %s'%(file_path, os.path.abspath(file_path), os.path.dirname(os.path.abspath(file_path)))) #log.debug('file_path: %s\ndirname: %s\nabspath: %s'%(file_path, os.path.dirname(file_path), os.path.abspath(os.path.dirname(file_path)))) module_attr.__path__ = os.path.abspath(os.path.dirname(file_path)) plugins_found.append(module_attr) except TypeError, type_error: - log.debug('module_attr: %s, error : %s'%( - module_name+'.'+module_attr_name, - type_error)) + pass + #log.debug('module_attr: %s, error : %s'%( + #module_name+'.'+module_attr_name, + #type_error)) - log.debug(module) + #log.debug(module) return plugins_found \ No newline at end of file From d1d3cc9bbdc9b5b9cc350e5df25c5c6f3af1a654 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Sun, 3 Aug 2008 22:02:53 +0000 Subject: [PATCH 019/200] GUI extension points removal working properly now. --- src/plugins/gui.py | 7 +------ src/plugins/helpers.py | 2 +- src/plugins/plugin.py | 2 +- src/plugins/pluginmanager.py | 5 ++++- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/plugins/gui.py b/src/plugins/gui.py index e74e86ef5..3a472b01b 100644 --- a/src/plugins/gui.py +++ b/src/plugins/gui.py @@ -203,11 +203,6 @@ class GajimPluginConfigDialog(gtk.Dialog): self.child.set_spacing(3) self.init() - - #label = gtk.Label(_('%s Configuration') % (plugin.name)) - #label.set_markup(label.get_label()) - #self.child.pack_start(label, False, False) - @log_calls('GajimPluginConfigDialog') def run(self, parent=None): @@ -222,4 +217,4 @@ class GajimPluginConfigDialog(gtk.Dialog): pass def on_run(self): - pass \ No newline at end of file + pass diff --git a/src/plugins/helpers.py b/src/plugins/helpers.py index 4034a7ebc..a054c7c28 100644 --- a/src/plugins/helpers.py +++ b/src/plugins/helpers.py @@ -50,7 +50,7 @@ class log_calls(object): Decorator class for functions to easily log when they are entered and left. ''' - filter_out_classes = ['GajimPlugin', 'GajimPluginConfig', + filter_out_classes = ['GajimPlugin', 'GajimPluginConfig', 'PluginManager', 'GajimPluginConfigDialog', 'PluginsWindow'] ''' List of classes from which no logs should be emited when methods are called, diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py index d53b25960..53f229761 100644 --- a/src/plugins/plugin.py +++ b/src/plugins/plugin.py @@ -193,7 +193,7 @@ class GajimPluginConfig(UserDict.DictMixin): @log_calls('GajimPluginConfig') def save(self): self.data.sync() - log.debug(str(self.data)) + #log.debug(str(self.data)) @log_calls('GajimPluginConfig') def load(self): diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py index 038417591..589d91e92 100644 --- a/src/plugins/pluginmanager.py +++ b/src/plugins/pluginmanager.py @@ -200,7 +200,10 @@ class PluginManager(object): extension point name) to identify element to be removed. :type args: tuple ''' - log.debug('name: %s\n args: %s'%(gui_extpoint_name, args)) + + if gui_extpoint_name in self.gui_extension_points: + log.debug('Removing GUI extpoint\n name: %s\n args: %s'%(gui_extpoint_name, args)) + self.gui_extension_points[gui_extpoint_name].remove(args) @log_calls('PluginManager') From 06aee9d2d08703b7d4c6e56e766916749cd9e167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Sun, 10 Aug 2008 13:40:49 +0000 Subject: [PATCH 020/200] Initial version of Global Events Dispatcher. Events previously generated for D-Bus support in remote_control.py go through Global Events Dispatcher now - this means any plugin can subscribe to them. Implemented D-Bus support plugin based on remote_control.py. --- plugins/dbus_plugin/__init__.py | 1 + plugins/dbus_plugin/plugin.py | 738 ++++++++++++++++++++++++++++++++ src/common/gajim.py | 1 + src/common/ged.py | 64 +++ src/gajim-remote-plugin.py | 548 ++++++++++++++++++++++++ src/gajim-remote.py | 2 +- src/gajim.py | 31 ++ src/plugins/plugin.py | 23 +- src/plugins/pluginmanager.py | 29 +- src/session.py | 6 + 10 files changed, 1437 insertions(+), 6 deletions(-) create mode 100644 plugins/dbus_plugin/__init__.py create mode 100644 plugins/dbus_plugin/plugin.py create mode 100644 src/common/ged.py create mode 100755 src/gajim-remote-plugin.py diff --git a/plugins/dbus_plugin/__init__.py b/plugins/dbus_plugin/__init__.py new file mode 100644 index 000000000..c5c296ad7 --- /dev/null +++ b/plugins/dbus_plugin/__init__.py @@ -0,0 +1 @@ +from plugin import DBusPlugin \ No newline at end of file diff --git a/plugins/dbus_plugin/plugin.py b/plugins/dbus_plugin/plugin.py new file mode 100644 index 000000000..8141d83d3 --- /dev/null +++ b/plugins/dbus_plugin/plugin.py @@ -0,0 +1,738 @@ +# -*- coding: utf-8 -*- + +## Copyright (C) 2005-2006 Yann Leboulanger +## Copyright (C) 2005-2006 Nikos Kouremenos +## Copyright (C) 2005-2006 Dimitur Kirov +## Copyright (C) 2005-2006 Andrew Sayman +## Copyright (C) 2007 Lukas Petrovicky +## Copyright (C) 2007 Julien Pivotto +## Copyright (C) 2007 Travis Shirk +## Copyright (C) 2008 Mateusz Biliński +## +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## +''' +D-BUS Support plugin. + +Based on src/remote_control.py + +:author: Mateusz Biliński +:since: 8th August 2008 +:copyright: Copyright (2008) Mateusz Biliński +:license: GPL +''' +import os +import new + +import gobject + + +from common import dbus_support +if dbus_support.supported: + import dbus + if dbus_support: + import dbus.service + import dbus.glib + +INTERFACE = 'org.gajim.dbusplugin.RemoteInterface' +OBJ_PATH = '/org/gajim/dbusplugin/RemoteObject' +SERVICE = 'org.gajim.dbusplugin' + +from common import gajim +from common import helpers +from time import time +from dialogs import AddNewContactWindow, NewChatDialog, JoinGroupchatWindow + +from plugins import GajimPlugin +from plugins.helpers import log_calls, log +from common import ged + +class DBusPlugin(GajimPlugin): + name = u'D-Bus Support' + short_name = u'dbus' + version = u'0.1' + description = u'''D-Bus support. Based on remote_control module from +Gajim core but uses new events handling system.''' + authors = [u'Mateusz Biliński '] + homepage = u'http://blog.bilinski.it' + + @log_calls('DBusPlugin') + def init(self): + self.config_dialog = None + #self.gui_extension_points = {} + #self.config_default_values = {} + + self.events_names = ['Roster', 'AccountPresence', 'ContactPresence', + 'ContactAbsence', 'ContactStatus', 'NewMessage', + 'Subscribe', 'Subscribed', 'Unsubscribed', + 'NewAccount', 'VcardInfo', 'LastStatusTime', + 'OsInfo', 'GCPresence', 'GCMessage', 'RosterInfo', + 'NewGmail'] + + self.signal_object = None + + self.events_handlers = {} + self._set_handling_methods() + + + def activate(self): + session_bus = dbus_support.session_bus.SessionBus() + + bus_name = dbus.service.BusName(SERVICE, bus=session_bus) + self.signal_object = SignalObject(bus_name) + + def deactivate(self): + self.signal_object.remove_from_connection() + self.signal_object = None + + def _set_handling_methods(self): + for event_name in self.events_names: + setattr(self, event_name, + new.instancemethod( + self._generate_handling_method(event_name), + self, + DBusPlugin)) + self.events_handlers[event_name] = (ged.POSTCORE, + getattr(self, event_name)) + + def _generate_handling_method(self, event_name): + def handler(self, arg): + print "Handler of event %s called"%(event_name) + if self.signal_object: + getattr(self.signal_object, event_name)(get_dbus_struct(arg)) + + return handler + +# type mapping + +# in most cases it is a utf-8 string +DBUS_STRING = dbus.String + +# general type (for use in dicts, where all values should have the same type) +DBUS_BOOLEAN = dbus.Boolean +DBUS_DOUBLE = dbus.Double +DBUS_INT32 = dbus.Int32 +# dictionary with string key and binary value +DBUS_DICT_SV = lambda : dbus.Dictionary({}, signature="sv") +# dictionary with string key and value +DBUS_DICT_SS = lambda : dbus.Dictionary({}, signature="ss") +# empty type (there is no equivalent of None on D-Bus, but historically gajim +# used 0 instead) +DBUS_NONE = lambda : dbus.Int32(0) + +def get_dbus_struct(obj): + ''' recursively go through all the items and replace + them with their casted dbus equivalents + ''' + if obj is None: + return DBUS_NONE() + if isinstance(obj, (unicode, str)): + return DBUS_STRING(obj) + if isinstance(obj, int): + return DBUS_INT32(obj) + if isinstance(obj, float): + return DBUS_DOUBLE(obj) + if isinstance(obj, bool): + return DBUS_BOOLEAN(obj) + if isinstance(obj, (list, tuple)): + result = dbus.Array([get_dbus_struct(i) for i in obj], + signature='v') + if result == []: + return DBUS_NONE() + return result + if isinstance(obj, dict): + result = DBUS_DICT_SV() + for key, value in obj.items(): + result[DBUS_STRING(key)] = get_dbus_struct(value) + if result == {}: + return DBUS_NONE() + return result + # unknown type + return DBUS_NONE() + +class SignalObject(dbus.service.Object): + ''' Local object definition for /org/gajim/dbus/RemoteObject. + (This docstring is not be visible, because the clients can access only the remote object.)''' + + def __init__(self, bus_name): + self.first_show = True + self.vcard_account = None + + # register our dbus API + dbus.service.Object.__init__(self, bus_name, OBJ_PATH) + + @dbus.service.signal(INTERFACE, signature='av') + def Roster(self, account_and_data): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def AccountPresence(self, status_and_account): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def ContactPresence(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def ContactAbsence(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def ContactStatus(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def NewMessage(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def Subscribe(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def Subscribed(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def Unsubscribed(self, account_and_jid): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def NewAccount(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def VcardInfo(self, account_and_vcard): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def LastStatusTime(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def OsInfo(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def GCPresence(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def GCMessage(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def RosterInfo(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def NewGmail(self, account_and_array): + pass + + def raise_signal(self, signal, arg): + '''raise a signal, with a single argument of unspecified type + Instead of obj.raise_signal("Foo", bar), use obj.Foo(bar).''' + getattr(self, signal)(arg) + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='s') + def get_status(self, account): + '''Returns status (show to be exact) which is the global one + unless account is given''' + if not account: + # If user did not ask for account, returns the global status + return DBUS_STRING(helpers.get_global_show()) + # return show for the given account + index = gajim.connections[account].connected + return DBUS_STRING(gajim.SHOW_LIST[index]) + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='s') + def get_status_message(self, account): + '''Returns status which is the global one + unless account is given''' + if not account: + # If user did not ask for account, returns the global status + return DBUS_STRING(str(helpers.get_global_status())) + # return show for the given account + status = gajim.connections[account].status + return DBUS_STRING(status) + + def _get_account_and_contact(self, account, jid): + '''get the account (if not given) and contact instance from jid''' + connected_account = None + contact = None + accounts = gajim.contacts.get_accounts() + # if there is only one account in roster, take it as default + # if user did not ask for account + if not account and len(accounts) == 1: + account = accounts[0] + if account: + if gajim.connections[account].connected > 1: # account is connected + connected_account = account + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + else: + for account in accounts: + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + if contact and gajim.connections[account].connected > 1: + # account is connected + connected_account = account + break + if not contact: + contact = jid + + return connected_account, contact + + def _get_account_for_groupchat(self, account, room_jid): + '''get the account which is connected to groupchat (if not given) + or check if the given account is connected to the groupchat''' + connected_account = None + accounts = gajim.contacts.get_accounts() + # if there is only one account in roster, take it as default + # if user did not ask for account + if not account and len(accounts) == 1: + account = accounts[0] + if account: + if gajim.connections[account].connected > 1 and \ + room_jid in gajim.gc_connected[account] and \ + gajim.gc_connected[account][room_jid]: + # account and groupchat are connected + connected_account = account + else: + for account in accounts: + if gajim.connections[account].connected > 1 and \ + room_jid in gajim.gc_connected[account] and \ + gajim.gc_connected[account][room_jid]: + # account and groupchat are connected + connected_account = account + break + return connected_account + + @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') + def send_file(self, file_path, jid, account): + '''send file, located at 'file_path' to 'jid', using account + (optional) 'account' ''' + jid = self._get_real_jid(jid, account) + connected_account, contact = self._get_account_and_contact(account, jid) + + if connected_account: + if file_path[:7] == 'file://': + file_path=file_path[7:] + if os.path.isfile(file_path): # is it file? + gajim.interface.instances['file_transfers'].send_file( + connected_account, contact, file_path) + return DBUS_BOOLEAN(True) + return DBUS_BOOLEAN(False) + + def _send_message(self, jid, message, keyID, account, type = 'chat', + subject = None): + '''can be called from send_chat_message (default when send_message) + or send_single_message''' + if not jid or not message: + return DBUS_BOOLEAN(False) + if not keyID: + keyID = '' + + connected_account, contact = self._get_account_and_contact(account, jid) + if connected_account: + connection = gajim.connections[connected_account] + connection.send_message(jid, message, keyID, type, subject) + return DBUS_BOOLEAN(True) + return DBUS_BOOLEAN(False) + + @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='b') + def send_chat_message(self, jid, message, keyID, account): + '''Send chat 'message' to 'jid', using account (optional) 'account'. + if keyID is specified, encrypt the message with the pgp key ''' + jid = self._get_real_jid(jid, account) + return self._send_message(jid, message, keyID, account) + + @dbus.service.method(INTERFACE, in_signature='sssss', out_signature='b') + def send_single_message(self, jid, subject, message, keyID, account): + '''Send single 'message' to 'jid', using account (optional) 'account'. + if keyID is specified, encrypt the message with the pgp key ''' + jid = self._get_real_jid(jid, account) + return self._send_message(jid, message, keyID, account, type, subject) + + @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') + def send_groupchat_message(self, room_jid, message, account): + '''Send 'message' to groupchat 'room_jid', + using account (optional) 'account'.''' + if not room_jid or not message: + return DBUS_BOOLEAN(False) + connected_account = self._get_account_for_groupchat(account, room_jid) + if connected_account: + connection = gajim.connections[connected_account] + connection.send_gc_message(room_jid, message) + return DBUS_BOOLEAN(True) + return DBUS_BOOLEAN(False) + + @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b') + def open_chat(self, jid, account): + '''Shows the tabbed window for new message to 'jid', using account + (optional) 'account' ''' + if not jid: + raise MissingArgument + return DBUS_BOOLEAN(False) + jid = self._get_real_jid(jid, account) + try: + jid = helpers.parse_jid(jid) + except: + # Jid is not conform, ignore it + return DBUS_BOOLEAN(False) + + if account: + accounts = [account] + else: + accounts = gajim.connections.keys() + if len(accounts) == 1: + account = accounts[0] + connected_account = None + first_connected_acct = None + for acct in accounts: + if gajim.connections[acct].connected > 1: # account is online + contact = gajim.contacts.get_first_contact_from_jid(acct, jid) + if gajim.interface.msg_win_mgr.has_window(jid, acct): + connected_account = acct + break + # jid is in roster + elif contact: + connected_account = acct + break + # we send the message to jid not in roster, because account is + # specified, or there is only one account + elif account: + connected_account = acct + elif first_connected_acct is None: + first_connected_acct = acct + + # if jid is not a conntact, open-chat with first connected account + if connected_account is None and first_connected_acct: + connected_account = first_connected_acct + + if connected_account: + gajim.interface.new_chat_from_jid(connected_account, jid) + # preserve the 'steal focus preservation' + win = gajim.interface.msg_win_mgr.get_window(jid, + connected_account).window + if win.get_property('visible'): + win.window.focus() + return DBUS_BOOLEAN(True) + return DBUS_BOOLEAN(False) + + @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') + def change_status(self, status, message, account): + ''' change_status(status, message, account). account is optional - + if not specified status is changed for all accounts. ''' + if status not in ('offline', 'online', 'chat', + 'away', 'xa', 'dnd', 'invisible'): + return DBUS_BOOLEAN(False) + if account: + gobject.idle_add(gajim.interface.roster.send_status, account, + status, message) + else: + # account not specified, so change the status of all accounts + for acc in gajim.contacts.get_accounts(): + if not gajim.config.get_per('accounts', acc, + 'sync_with_global_status'): + continue + gobject.idle_add(gajim.interface.roster.send_status, acc, + status, message) + return DBUS_BOOLEAN(False) + + @dbus.service.method(INTERFACE, in_signature='', out_signature='') + def show_next_pending_event(self): + '''Show the window(s) with next pending event in tabbed/group chats.''' + if gajim.events.get_nb_events(): + gajim.interface.systray.handle_first_event() + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{sv}') + def contact_info(self, jid): + '''get vcard info for a contact. Return cached value of the vcard. + ''' + if not isinstance(jid, unicode): + jid = unicode(jid) + if not jid: + raise MissingArgument + return DBUS_DICT_SV() + jid = self._get_real_jid(jid) + + cached_vcard = gajim.connections.values()[0].get_cached_vcard(jid) + if cached_vcard: + return get_dbus_struct(cached_vcard) + + # return empty dict + return DBUS_DICT_SV() + + @dbus.service.method(INTERFACE, in_signature='', out_signature='as') + def list_accounts(self): + '''list register accounts''' + result = gajim.contacts.get_accounts() + result_array = dbus.Array([], signature='s') + if result and len(result) > 0: + for account in result: + result_array.append(DBUS_STRING(account)) + return result_array + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{ss}') + def account_info(self, account): + '''show info on account: resource, jid, nick, prio, message''' + result = DBUS_DICT_SS() + if gajim.connections.has_key(account): + # account is valid + con = gajim.connections[account] + index = con.connected + result['status'] = DBUS_STRING(gajim.SHOW_LIST[index]) + result['name'] = DBUS_STRING(con.name) + result['jid'] = DBUS_STRING(gajim.get_jid_from_account(con.name)) + result['message'] = DBUS_STRING(con.status) + result['priority'] = DBUS_STRING(unicode(con.priority)) + result['resource'] = DBUS_STRING(unicode(gajim.config.get_per( + 'accounts', con.name, 'resource'))) + return result + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='aa{sv}') + def list_contacts(self, account): + '''list all contacts in the roster. If the first argument is specified, + then return the contacts for the specified account''' + result = dbus.Array([], signature='aa{sv}') + accounts = gajim.contacts.get_accounts() + if len(accounts) == 0: + return result + if account: + accounts_to_search = [account] + else: + accounts_to_search = accounts + for acct in accounts_to_search: + if acct in accounts: + for jid in gajim.contacts.get_jid_list(acct): + item = self._contacts_as_dbus_structure( + gajim.contacts.get_contacts(acct, jid)) + if item: + result.append(item) + return result + + @dbus.service.method(INTERFACE, in_signature='', out_signature='') + def toggle_roster_appearance(self): + ''' shows/hides the roster window ''' + win = gajim.interface.roster.window + if win.get_property('visible'): + gobject.idle_add(win.hide) + else: + win.present() + # preserve the 'steal focus preservation' + if self._is_first(): + win.window.focus() + else: + win.window.focus(long(time())) + + @dbus.service.method(INTERFACE, in_signature='', out_signature='') + def toggle_ipython(self): + ''' shows/hides the ipython window ''' + win = gajim.ipython_window + if win: + if win.window.is_visible(): + gobject.idle_add(win.hide) + else: + win.show_all() + win.present() + else: + gajim.interface.create_ipython_window() + + @dbus.service.method(INTERFACE, in_signature='', out_signature='a{ss}') + def prefs_list(self): + prefs_dict = DBUS_DICT_SS() + def get_prefs(data, name, path, value): + if value is None: + return + key = '' + if path is not None: + for node in path: + key += node + '#' + key += name + prefs_dict[DBUS_STRING(key)] = DBUS_STRING(value[1]) + gajim.config.foreach(get_prefs) + return prefs_dict + + @dbus.service.method(INTERFACE, in_signature='', out_signature='b') + def prefs_store(self): + try: + gajim.interface.save_config() + except Exception, e: + return DBUS_BOOLEAN(False) + return DBUS_BOOLEAN(True) + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') + def prefs_del(self, key): + if not key: + return DBUS_BOOLEAN(False) + key_path = key.split('#', 2) + if len(key_path) != 3: + return DBUS_BOOLEAN(False) + if key_path[2] == '*': + gajim.config.del_per(key_path[0], key_path[1]) + else: + gajim.config.del_per(key_path[0], key_path[1], key_path[2]) + return DBUS_BOOLEAN(True) + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') + def prefs_put(self, key): + if not key: + return DBUS_BOOLEAN(False) + key_path = key.split('#', 2) + if len(key_path) < 3: + subname, value = key.split('=', 1) + gajim.config.set(subname, value) + return DBUS_BOOLEAN(True) + subname, value = key_path[2].split('=', 1) + gajim.config.set_per(key_path[0], key_path[1], subname, value) + return DBUS_BOOLEAN(True) + + @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b') + def add_contact(self, jid, account): + if account: + if account in gajim.connections and \ + gajim.connections[account].connected > 1: + # if given account is active, use it + AddNewContactWindow(account = account, jid = jid) + else: + # wrong account + return DBUS_BOOLEAN(False) + else: + # if account is not given, show account combobox + AddNewContactWindow(account = None, jid = jid) + return DBUS_BOOLEAN(True) + + @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b') + def remove_contact(self, jid, account): + jid = self._get_real_jid(jid, account) + accounts = gajim.contacts.get_accounts() + + # if there is only one account in roster, take it as default + if account: + accounts = [account] + contact_exists = False + for account in accounts: + contacts = gajim.contacts.get_contacts(account, jid) + if contacts: + gajim.connections[account].unsubscribe(jid) + for contact in contacts: + gajim.interface.roster.remove_contact(contact, account) + gajim.contacts.remove_jid(account, jid) + contact_exists = True + return DBUS_BOOLEAN(contact_exists) + + def _is_first(self): + if self.first_show: + self.first_show = False + return True + return False + + def _get_real_jid(self, jid, account = None): + '''get the real jid from the given one: removes xmpp: or get jid from nick + if account is specified, search only in this account + ''' + if account: + accounts = [account] + else: + accounts = gajim.connections.keys() + if jid.startswith('xmpp:'): + return jid[5:] # len('xmpp:') = 5 + nick_in_roster = None # Is jid a nick ? + for account in accounts: + # Does jid exists in roster of one account ? + if gajim.contacts.get_contacts(account, jid): + return jid + if not nick_in_roster: + # look in all contact if one has jid as nick + for jid_ in gajim.contacts.get_jid_list(account): + c = gajim.contacts.get_contacts(account, jid_) + if c[0].name == jid: + nick_in_roster = jid_ + break + if nick_in_roster: + # We have not found jid in roster, but we found is as a nick + return nick_in_roster + # We have not found it as jid nor as nick, probably a not in roster jid + return jid + + def _contacts_as_dbus_structure(self, contacts): + ''' get info from list of Contact objects and create dbus dict ''' + if not contacts: + return None + prim_contact = None # primary contact + for contact in contacts: + if prim_contact is None or contact.priority > prim_contact.priority: + prim_contact = contact + contact_dict = DBUS_DICT_SV() + contact_dict['name'] = DBUS_STRING(prim_contact.name) + contact_dict['show'] = DBUS_STRING(prim_contact.show) + contact_dict['jid'] = DBUS_STRING(prim_contact.jid) + if prim_contact.keyID: + keyID = None + if len(prim_contact.keyID) == 8: + keyID = prim_contact.keyID + elif len(prim_contact.keyID) == 16: + keyID = prim_contact.keyID[8:] + if keyID: + contact_dict['openpgp'] = keyID + contact_dict['resources'] = dbus.Array([], signature='(sis)') + for contact in contacts: + resource_props = dbus.Struct((DBUS_STRING(contact.resource), + dbus.Int32(contact.priority), DBUS_STRING(contact.status))) + contact_dict['resources'].append(resource_props) + contact_dict['groups'] = dbus.Array([], signature='(s)') + for group in prim_contact.groups: + contact_dict['groups'].append((DBUS_STRING(group),)) + return contact_dict + + @dbus.service.method(INTERFACE, in_signature='', out_signature='s') + def get_unread_msgs_number(self): + return DBUS_STRING(str(gajim.events.get_nb_events())) + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') + def start_chat(self, account): + if not account: + # error is shown in gajim-remote check_arguments(..) + return DBUS_BOOLEAN(False) + NewChatDialog(account) + return DBUS_BOOLEAN(True) + + @dbus.service.method(INTERFACE, in_signature='ss', out_signature='') + def send_xml(self, xml, account): + if account: + gajim.connections[account].send_stanza(xml) + else: + for acc in gajim.contacts.get_accounts(): + gajim.connections[acc].send_stanza(xml) + + @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='') + def join_room(self, room_jid, nick, password, account): + if not account: + # get the first connected account + accounts = gajim.connections.keys() + for acct in accounts: + if gajim.account_is_connected(acct): + account = acct + break + if not account: + return + if not nick: + nick = '' + gajim.interface.instances[account]['join_gc'] = \ + JoinGroupchatWindow(account, room_jid, nick) + else: + gajim.interface.join_gc_room(account, room_jid, nick, password) + \ No newline at end of file diff --git a/src/common/gajim.py b/src/common/gajim.py index ca015d3e1..3943f2bd4 100644 --- a/src/common/gajim.py +++ b/src/common/gajim.py @@ -62,6 +62,7 @@ connections = {} # 'account name': 'account (connection.Connection) instance' verbose = False ipython_window = None plugin_manager = None +ged = None # Global Events Dispatcher h = logging.StreamHandler() f = logging.Formatter('%(asctime)s %(name)s: %(message)s', '%d %b %Y %H:%M:%S') diff --git a/src/common/ged.py b/src/common/ged.py new file mode 100644 index 000000000..07a7aea10 --- /dev/null +++ b/src/common/ged.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## + +''' +Global Events Dispatcher module. + +:author: Mateusz Biliński +:since: 8th August 2008 +:copyright: Copyright (2008) Mateusz Biliński +:license: GPL +''' + +from plugins.helpers import log + +PRECORE = 30 +CORE = 40 +POSTCORE = 50 + +class GlobalEventsDispatcher(object): + + def __init__(self): + self.handlers = {} + + def register_event_handler(self, event_name, priority, handler): + if event_name in self.handlers: + handlers_list = self.handlers[event_name] + i = 0 + if len(handlers_list) > 0: + while priority > handlers_list[i][0]: # sorting handlers by priority + i += 1 + + handlers_list.insert(i, (priority, handler)) + else: + self.handlers[event_name] = [(priority, handler)] + + def remove_event_handler(self, event_name, priority, handler): + if event_name in self.handlers: + try: + self.handlers[event_name].remove((priority, handler)) + except ValueError, error: + log.debug('''Function (%s) with priority "%s" never registered + as handler of event "%s". Couldn\'t remove. Error: %s''' + %(handler, priority, event_name, error)) + + def raise_event(self, event_name, *args): + #log.debug('[GED] Event: %s\nArgs: %s'%(event_name, str(args))) + if event_name in self.handlers: + for priority, handler in self.handlers[event_name]: + handler(args) + \ No newline at end of file diff --git a/src/gajim-remote-plugin.py b/src/gajim-remote-plugin.py new file mode 100755 index 000000000..c475d18eb --- /dev/null +++ b/src/gajim-remote-plugin.py @@ -0,0 +1,548 @@ +#!/usr/bin/env python +## +## Copyright (C) 2005-2006 Yann Leboulanger +## Copyright (C) 2005-2006 Nikos Kouremenos +## Copyright (C) 2005 Dimitur Kirov +## +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## + +# gajim-remote help will show you the D-BUS API of Gajim + +import sys +import os +import locale +import signal +signal.signal(signal.SIGINT, signal.SIG_DFL) # ^C exits the application + +from common import exceptions +from common import i18n + +try: + PREFERRED_ENCODING = locale.getpreferredencoding() +except: + PREFERRED_ENCODING = 'UTF-8' + +def send_error(error_message): + '''Writes error message to stderr and exits''' + print >> sys.stderr, error_message.encode(PREFERRED_ENCODING) + sys.exit(1) + +try: + if sys.platform == 'darwin': + import osx.dbus + osx.dbus.load(False) + import dbus + import dbus.service + import dbus.glib +except: + print str(exceptions.DbusNotSupported()) + sys.exit(1) + +OBJ_PATH = '/org/gajim/dbusplugin/RemoteObject' +INTERFACE = 'org.gajim.dbusplugin.RemoteInterface' +SERVICE = 'org.gajim.dbusplugin' +BASENAME = 'gajim-remote-plugin' + + +class GajimRemote: + + def __init__(self): + self.argv_len = len(sys.argv) + # define commands dict. Prototype : + # { + # 'command': [comment, [list of arguments] ] + # } + # + # each argument is defined as a tuple: + # (argument name, help on argument, is mandatory) + # + self.commands = { + 'help':[ + _('Shows a help on specific command'), + [ + #User gets help for the command, specified by this parameter + (_('command'), + _('show help on command'), False) + ] + ], + 'toggle_roster_appearance' : [ + _('Shows or hides the roster window'), + [] + ], + 'show_next_pending_event': [ + _('Pops up a window with the next pending event'), + [] + ], + 'list_contacts': [ + _('Prints a list of all contacts in the roster. Each contact ' + 'appears on a separate line'), + [ + (_('account'), _('show only contacts of the given account'), + False) + ] + + ], + 'list_accounts': [ + _('Prints a list of registered accounts'), + [] + ], + 'change_status': [ + _('Changes the status of account or accounts'), + [ +#offline, online, chat, away, xa, dnd, invisible should not be translated + (_('status'), _('one of: offline, online, chat, away, xa, dnd, invisible '), True), + (_('message'), _('status message'), False), + (_('account'), _('change status of account "account". ' + 'If not specified, try to change status of all accounts that have ' + '"sync with global status" option set'), False) + ] + ], + 'open_chat': [ + _('Shows the chat dialog so that you can send messages to a contact'), + [ + ('jid', _('JID of the contact that you want to chat with'), + True), + (_('account'), _('if specified, contact is taken from the ' + 'contact list of this account'), False) + ] + ], + 'send_chat_message':[ + _('Sends new chat message to a contact in the roster. Both OpenPGP key ' + 'and account are optional. If you want to set only \'account\', ' + 'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'), + [ + ('jid', _('JID of the contact that will receive the message'), True), + (_('message'), _('message contents'), True), + (_('pgp key'), _('if specified, the message will be encrypted ' + 'using this public key'), False), + (_('account'), _('if specified, the message will be sent ' + 'using this account'), False), + ] + ], + 'send_single_message':[ + _('Sends new single message to a contact in the roster. Both OpenPGP key ' + 'and account are optional. If you want to set only \'account\', ' + 'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'), + [ + ('jid', _('JID of the contact that will receive the message'), True), + (_('subject'), _('message subject'), True), + (_('message'), _('message contents'), True), + (_('pgp key'), _('if specified, the message will be encrypted ' + 'using this public key'), False), + (_('account'), _('if specified, the message will be sent ' + 'using this account'), False), + ] + ], + 'send_groupchat_message':[ + _('Sends new message to a groupchat you\'ve joined.'), + [ + ('room_jid', _('JID of the room that will receive the message'), True), + (_('message'), _('message contents'), True), + (_('account'), _('if specified, the message will be sent ' + 'using this account'), False), + ] + ], + 'contact_info': [ + _('Gets detailed info on a contact'), + [ + ('jid', _('JID of the contact'), True) + ] + ], + 'account_info': [ + _('Gets detailed info on a account'), + [ + ('account', _('Name of the account'), True) + ] + ], + 'send_file': [ + _('Sends file to a contact'), + [ + (_('file'), _('File path'), True), + ('jid', _('JID of the contact'), True), + (_('account'), _('if specified, file will be sent using this ' + 'account'), False) + ] + ], + 'prefs_list': [ + _('Lists all preferences and their values'), + [ ] + ], + 'prefs_put': [ + _('Sets value of \'key\' to \'value\'.'), + [ + (_('key=value'), _('\'key\' is the name of the preference, ' + '\'value\' is the value to set it to'), True) + ] + ], + 'prefs_del': [ + _('Deletes a preference item'), + [ + (_('key'), _('name of the preference to be deleted'), True) + ] + ], + 'prefs_store': [ + _('Writes the current state of Gajim preferences to the .config ' + 'file'), + [ ] + ], + 'remove_contact': [ + _('Removes contact from roster'), + [ + ('jid', _('JID of the contact'), True), + (_('account'), _('if specified, contact is taken from the ' + 'contact list of this account'), False) + + ] + ], + 'add_contact': [ + _('Adds contact to roster'), + [ + (_('jid'), _('JID of the contact'), True), + (_('account'), _('Adds new contact to this account'), False) + ] + ], + + 'get_status': [ + _('Returns current status (the global one unless account is specified)'), + [ + (_('account'), _(''), False) + ] + ], + + 'get_status_message': [ + _('Returns current status message(the global one unless account is specified)'), + [ + (_('account'), _(''), False) + ] + ], + + 'get_unread_msgs_number': [ + _('Returns number of unread messages'), + [ ] + ], + 'start_chat': [ + _('Opens \'Start Chat\' dialog'), + [ + (_('account'), _('Starts chat, using this account'), True) + ] + ], + 'send_xml': [ + _('Sends custom XML'), + [ + ('xml', _('XML to send'), True), + ('account', _('Account in which the xml will be sent; ' + 'if not specified, xml will be sent to all accounts'), + False) + ] + ], + 'handle_uri': [ + _('Handle a xmpp:/ uri'), + [ + (_('uri'), _(''), True), + (_('account'), _(''), False) + ] + ], + 'join_room': [ + _('Join a MUC room'), + [ + (_('room'), _(''), True), + (_('nick'), _(''), False), + (_('password'), _(''), False), + (_('account'), _(''), False) + ] + ], + 'check_gajim_running':[ + _('Check if Gajim is running'), + [] + ], + 'toggle_ipython' : [ + _('Shows or hides the ipython window'), + [] + ], + + } + + path = os.getcwd() + if '.svn' in os.listdir(path) or '_svn' in os.listdir(path): + # command only for svn + self.commands['toggle_ipython'] = [ + _('Shows or hides the ipython window'), + [] + ] + self.sbus = None + if self.argv_len < 2 or sys.argv[1] not in self.commands.keys(): + # no args or bad args + send_error(self.compose_help()) + self.command = sys.argv[1] + if self.command == 'help': + if self.argv_len == 3: + print self.help_on_command(sys.argv[2]).encode(PREFERRED_ENCODING) + else: + print self.compose_help().encode(PREFERRED_ENCODING) + sys.exit(0) + if self.command == 'handle_uri': + self.handle_uri() + if self.command == 'check_gajim_running': + print self.check_gajim_running() + sys.exit(0) + self.init_connection() + self.check_arguments() + + if self.command == 'contact_info': + if self.argv_len < 3: + send_error(_('Missing argument "contact_jid"')) + + try: + res = self.call_remote_method() + except exceptions.ServiceNotAvailable: + # At this point an error message has already been displayed + sys.exit(1) + else: + self.print_result(res) + + def print_result(self, res): + ''' Print retrieved result to the output ''' + if res is not None: + if self.command in ('open_chat', 'send_chat_message', 'send_single_message', 'start_chat'): + if self.command in ('send_message', 'send_single_message'): + self.argv_len -= 2 + + if res is False: + if self.argv_len < 4: + send_error(_('\'%s\' is not in your roster.\n' + 'Please specify account for sending the message.') % sys.argv[2]) + else: + send_error(_('You have no active account')) + elif self.command == 'list_accounts': + if isinstance(res, list): + for account in res: + if isinstance(account, unicode): + print account.encode(PREFERRED_ENCODING) + else: + print account + elif self.command == 'account_info': + if res: + print self.print_info(0, res, True) + elif self.command == 'list_contacts': + for account_dict in res: + print self.print_info(0, account_dict, True) + elif self.command == 'prefs_list': + pref_keys = res.keys() + pref_keys.sort() + for pref_key in pref_keys: + result = '%s = %s' % (pref_key, res[pref_key]) + if isinstance(result, unicode): + print result.encode(PREFERRED_ENCODING) + else: + print result + elif self.command == 'contact_info': + print self.print_info(0, res, True) + elif res: + print unicode(res).encode(PREFERRED_ENCODING) + + def check_gajim_running(self): + if not self.sbus: + try: + self.sbus = dbus.SessionBus() + except: + raise exceptions.SessionBusNotPresent + + test = False + if hasattr(self.sbus, 'name_has_owner'): + if self.sbus.name_has_owner(SERVICE): + test = True + elif dbus.dbus_bindings.bus_name_has_owner(self.sbus.get_connection(), + SERVICE): + test = True + return test + + def init_connection(self): + ''' create the onnection to the session dbus, + or exit if it is not possible ''' + try: + self.sbus = dbus.SessionBus() + except: + raise exceptions.SessionBusNotPresent + + from pprint import pprint + pprint(list(self.sbus.list_names())) + if not self.check_gajim_running(): + send_error(_('It seems Gajim is not running. So you can\'t use gajim-remote.')) + obj = self.sbus.get_object(SERVICE, OBJ_PATH) + interface = dbus.Interface(obj, INTERFACE) + + # get the function asked + self.method = interface.__getattr__(self.command) + + def make_arguments_row(self, args): + ''' return arguments list. Mandatory arguments are enclosed with: + '<', '>', optional arguments - with '[', ']' ''' + str = '' + for argument in args: + str += ' ' + if argument[2]: + str += '<' + else: + str += '[' + str += argument[0] + if argument[2]: + str += '>' + else: + str += ']' + return str + + def help_on_command(self, command): + ''' return help message for a given command ''' + if command in self.commands: + command_props = self.commands[command] + arguments_str = self.make_arguments_row(command_props[1]) + str = _('Usage: %s %s %s \n\t %s') % (BASENAME, command, + arguments_str, command_props[0]) + if len(command_props[1]) > 0: + str += '\n\n' + _('Arguments:') + '\n' + for argument in command_props[1]: + str += ' ' + argument[0] + ' - ' + argument[1] + '\n' + return str + send_error(_('%s not found') % command) + + def compose_help(self): + ''' print usage, and list available commands ''' + str = _('Usage: %s command [arguments]\nCommand is one of:\n' ) % BASENAME + commands = self.commands.keys() + commands.sort() + for command in commands: + str += ' ' + command + for argument in self.commands[command][1]: + str += ' ' + if argument[2]: + str += '<' + else: + str += '[' + str += argument[0] + if argument[2]: + str += '>' + else: + str += ']' + str += '\n' + return str + + def print_info(self, level, prop_dict, encode_return = False): + ''' return formated string from data structure ''' + if prop_dict is None or not isinstance(prop_dict, (dict, list, tuple)): + return '' + ret_str = '' + if isinstance(prop_dict, (list, tuple)): + ret_str = '' + spacing = ' ' * level * 4 + for val in prop_dict: + if val is None: + ret_str +='\t' + elif isinstance(val, int): + ret_str +='\t' + str(val) + elif isinstance(val, (str, unicode)): + ret_str +='\t' + val + elif isinstance(val, (list, tuple)): + res = '' + for items in val: + res += self.print_info(level+1, items) + if res != '': + ret_str += '\t' + res + elif isinstance(val, dict): + ret_str += self.print_info(level+1, val) + ret_str = '%s(%s)\n' % (spacing, ret_str[1:]) + elif isinstance(prop_dict, dict): + for key in prop_dict.keys(): + val = prop_dict[key] + spacing = ' ' * level * 4 + if isinstance(val, (unicode, int, str)): + if val is not None: + val = val.strip() + ret_str += '%s%-10s: %s\n' % (spacing, key, val) + elif isinstance(val, (list, tuple)): + res = '' + for items in val: + res += self.print_info(level+1, items) + if res != '': + ret_str += '%s%s: \n%s' % (spacing, key, res) + elif isinstance(val, dict): + res = self.print_info(level+1, val) + if res != '': + ret_str += '%s%s: \n%s' % (spacing, key, res) + if (encode_return): + try: + ret_str = ret_str.encode(PREFERRED_ENCODING) + except: + pass + return ret_str + + def check_arguments(self): + ''' Make check if all necessary arguments are given ''' + argv_len = self.argv_len - 2 + args = self.commands[self.command][1] + if len(args) < argv_len: + send_error(_('Too many arguments. \n' + 'Type "%s help %s" for more info') % (BASENAME, self.command)) + if len(args) > argv_len: + if args[argv_len][2]: + send_error(_('Argument "%s" is not specified. \n' + 'Type "%s help %s" for more info') % + (args[argv_len][0], BASENAME, self.command)) + self.arguments = [] + i = 0 + for arg in sys.argv[2:]: + i += 1 + if i < len(args): + self.arguments.append(arg) + else: + # it's latest argument with spaces + self.arguments.append(' '.join(sys.argv[i+1:])) + break + # add empty string for missing args + self.arguments += ['']*(len(args)-i) + + def handle_uri(self): + if not sys.argv[2:][0].startswith('xmpp:'): + send_error(_('Wrong uri')) + sys.argv[2] = sys.argv[2][5:] + uri = sys.argv[2:][0] + if not '?' in uri: + self.command = sys.argv[1] = 'open_chat' + return + (jid, action) = uri.split('?', 1) + sys.argv[2] = jid + if action == 'join': + self.command = sys.argv[1] = 'join_room' + # Move account parameter from position 3 to 5 + sys.argv.append('') + sys.argv.append(sys.argv[3]) + sys.argv[3] = '' + return + + sys.exit(0) + + def call_remote_method(self): + ''' calls self.method with arguments from sys.argv[2:] ''' + args = [i.decode(PREFERRED_ENCODING) for i in self.arguments] + args = [dbus.String(i) for i in args] + try: + res = self.method(*args) + return res + except Exception: + raise exceptions.ServiceNotAvailable + return None + +if __name__ == '__main__': + GajimRemote() diff --git a/src/gajim-remote.py b/src/gajim-remote.py index 9e05c576d..4e16d7acd 100755 --- a/src/gajim-remote.py +++ b/src/gajim-remote.py @@ -54,7 +54,7 @@ except: OBJ_PATH = '/org/gajim/dbus/RemoteObject' INTERFACE = 'org.gajim.dbus.RemoteInterface' SERVICE = 'org.gajim.dbus' -BASENAME = 'gajim-remote' +BASENAME = 'gajim-remote-plugin' class GajimRemote: diff --git a/src/gajim.py b/src/gajim.py index 7dfc823b8..7b9152646 100755 --- a/src/gajim.py +++ b/src/gajim.py @@ -663,6 +663,8 @@ class Interface: self.roster.fire_up_unread_messages_events(account) if self.remote_ctrl: self.remote_ctrl.raise_signal('Roster', (account, data)) + + gajim.ged.raise_event('Roster', (account, data)) def handle_event_warning(self, unused, data): #('WARNING', account, (title_text, section_text)) @@ -796,6 +798,7 @@ class Interface: self.edit_own_details(account) if self.remote_ctrl: self.remote_ctrl.raise_signal('AccountPresence', (status, account)) + gajim.ged.raise_event('AccountPresence', (status, account)) def edit_own_details(self, account): jid = gajim.get_jid_from_account(account) @@ -950,17 +953,20 @@ class Interface: if self.remote_ctrl: self.remote_ctrl.raise_signal('ContactPresence', (account, array)) + gajim.ged.raise_event('ContactPresence', (account, array)) elif old_show > 1 and new_show < 2: notify.notify('contact_disconnected', jid, account, status_message) if self.remote_ctrl: self.remote_ctrl.raise_signal('ContactAbsence', (account, array)) + gajim.ged.raise_event('ContactAbsence', (account, array)) # FIXME: stop non active file transfers elif new_show > 1: # Status change (not connected/disconnected or error (<1)) notify.notify('status_change', jid, account, [new_show, status_message]) if self.remote_ctrl: self.remote_ctrl.raise_signal('ContactStatus', (account, array)) + gajim.ged.raise_event('ContactStatus', (account, array)) else: # FIXME: Msn transport (CMSN1.2.1 and PyMSN) doesn't follow the XEP # still the case in 2008 @@ -1029,6 +1035,7 @@ class Interface: dialogs.SubscriptionRequestWindow(array[0], array[1], account, array[2]) if self.remote_ctrl: self.remote_ctrl.raise_signal('Subscribe', (account, array)) + gajim.ged.raise_event('Subscribe', (account, array)) def handle_event_subscribed(self, account, array): #('SUBSCRIBED', account, (jid, resource)) @@ -1060,11 +1067,13 @@ class Interface: gajim.connections[account].ack_subscribed(jid) if self.remote_ctrl: self.remote_ctrl.raise_signal('Subscribed', (account, array)) + gajim.ged.raise_event('Subscribed', (account, array)) def handle_event_unsubscribed(self, account, jid): gajim.connections[account].ack_unsubscribed(jid) if self.remote_ctrl: self.remote_ctrl.raise_signal('Unsubscribed', (account, jid)) + gajim.ged.raise_event('Unsubscribed', (account, jid)) contact = gajim.contacts.get_first_contact_from_jid(account, jid) if not contact: @@ -1159,6 +1168,7 @@ class Interface: if self.remote_ctrl: self.remote_ctrl.raise_signal('NewAccount', (account, array)) + gajim.ged.raise_event('NewAccount', (account, array)) def handle_event_acc_not_ok(self, account, array): #('ACC_NOT_OK', account, (reason)) @@ -1227,6 +1237,7 @@ class Interface: self.roster.draw_avatar(jid, account) if self.remote_ctrl: self.remote_ctrl.raise_signal('VcardInfo', (account, vcard)) + gajim.ged.raise_event('VcardInfo', (account, vcard)) def handle_event_last_status_time(self, account, array): # ('LAST_STATUS_TIME', account, (jid, resource, seconds, status)) @@ -1248,6 +1259,7 @@ class Interface: win.set_last_status_time() if self.remote_ctrl: self.remote_ctrl.raise_signal('LastStatusTime', (account, array)) + gajim.ged.raise_event('LastStatusTime', (account, array)) def handle_event_os_info(self, account, array): #'OS_INFO' (account, (jid, resource, client_info, os_info)) @@ -1260,6 +1272,7 @@ class Interface: win.set_os_info(array[1], array[2], array[3]) if self.remote_ctrl: self.remote_ctrl.raise_signal('OsInfo', (account, array)) + gajim.ged.raise_event('OsInfo', (account, array)) def handle_event_gc_notify(self, account, array): #'GC_NOTIFY' (account, (room_jid, show, status, nick, @@ -1321,6 +1334,7 @@ class Interface: ctrl.update_ui() if self.remote_ctrl: self.remote_ctrl.raise_signal('GCPresence', (account, array)) + gajim.ged.raise_event('GCPresence', (account, array)) def handle_event_gc_msg(self, account, array): # ('GC_MSG', account, (jid, msg, time, has_timestamp, htmlmsg, @@ -1350,6 +1364,7 @@ class Interface: if self.remote_ctrl: self.remote_ctrl.raise_signal('GCMessage', (account, array)) + gajim.ged.raise_event('GCMessage', (account, array)) def handle_event_gc_subject(self, account, array): #('GC_SUBJECT', account, (jid, subject, body, has_timestamp)) @@ -1621,6 +1636,7 @@ class Interface: self.roster.draw_contact(jid, account) if self.remote_ctrl: self.remote_ctrl.raise_signal('RosterInfo', (account, array)) + gajim.ged.raise_event('RosterInfo', (account, array)) def handle_event_bookmarks(self, account, bms): # ('BOOKMARKS', account, [{name,jid,autojoin,password,nick}, {}]) @@ -1683,6 +1699,7 @@ class Interface: if self.remote_ctrl: self.remote_ctrl.raise_signal('NewGmail', (account, array)) + gajim.ged.raise_event('NewGmail', (account, array)) def handle_event_file_request_error(self, account, array): # ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg)) @@ -3472,9 +3489,23 @@ class Interface: gobject.timeout_add_seconds(gajim.config.get( 'check_idle_every_foo_seconds'), self.read_sleepy) + # Creating Global Events Dispatcher + from common import ged + gajim.ged = ged.GlobalEventsDispatcher() + self.register_core_handlers() + # Creating plugin manager import plugins gajim.plugin_manager = plugins.PluginManager() + + def register_core_handlers(self): + ''' + Register core handlers in Global Events Dispatcher (GED). + + This part of rewriting whole evetns handling system to use GED. + Of course this can be done anywhere else. + ''' + pass if __name__ == '__main__': def sigint_cb(num, stack): diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py index 53f229761..fb0651cc5 100644 --- a/src/plugins/plugin.py +++ b/src/plugins/plugin.py @@ -95,7 +95,17 @@ class GajimPlugin(object): ''' gui_extension_points = {} ''' - Extension points that plugin wants to connect with. + Extension points that plugin wants to connect with and handlers to be used. + + Keys of this string should be strings with name of GUI extension point + to handles. Values should be 2-element tuples with references to handling + functions. First function will be used to connect plugin with extpoint, + the second one to successfuly disconnect from it. Connecting takes places + when plugin is activated and extpoint already exists, or when plugin is + already activated but extpoint is being created (eg. chat window opens). + Disconnecting takes place when plugin is deactivated and extpoint exists + or when extpoint is destroyed and plugin is activate (eg. chat window + closed). ''' config_default_values = {} ''' @@ -111,6 +121,17 @@ class GajimPlugin(object): :type: {} of 2-element tuples ''' + events_handlers = {} + ''' + Dictionary with events handlers. + + Keys are event names. Values should be 2-element tuples with handler + priority as first element and reference to handler function as second + element. Priority is integer. See `ged` module for predefined priorities + like `ged.PRECORE`, `ged.CORE` or `ged.POSTCORE`. + + :type: {} with 2-element tuples + ''' @log_calls('GajimPlugin') def __init__(self): diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py index 589d91e92..4ee6f20da 100644 --- a/src/plugins/pluginmanager.py +++ b/src/plugins/pluginmanager.py @@ -236,6 +236,22 @@ class PluginManager(object): if gui_extpoint_name in self.gui_extension_points_handlers: for handlers in self.gui_extension_points_handlers[gui_extpoint_name]: handlers[0](*args) + + def _register_events_handlers_in_ged(self, plugin): + for event_name, handler in plugin.events_handlers.iteritems(): + priority = handler[0] + handler_function = handler[1] + gajim.ged.register_event_handler(event_name, + priority, + handler_function) + + def _remove_events_handler_from_ged(self, plugin): + for event_name, handler in plugin.events_handlers.iteritems(): + priority = handler[0] + handler_function = handler[1] + gajim.ged.remove_event_handler(event_name, + priority, + handler_function) @log_calls('PluginManager') def activate_plugin(self, plugin): @@ -244,13 +260,16 @@ class PluginManager(object): :type plugin: class object of `GajimPlugin` subclass :todo: success checks should be implemented using exceptions. Such - control should also be implemented in deactivation. + control should also be implemented in deactivation. Exceptions + should be shown to user inside popup dialog, so the reason + for not activating plugin is known. ''' success = False if not plugin.active: self._add_gui_extension_points_handlers_from_plugin(plugin) self._handle_all_gui_extension_points_with_plugin(plugin) + self._register_events_handlers_in_ged(plugin) success = True @@ -279,6 +298,8 @@ class PluginManager(object): handler = gui_extpoint_handlers[1] if handler: handler(*gui_extension_point_args) + + self._remove_events_handler_from_ged(plugin) # removing plug-in from active plug-ins list plugin.deactivate() @@ -391,17 +412,17 @@ class PluginManager(object): if module: - #log.debug('Attributes processing started') + log.debug('Attributes processing started') for module_attr_name in [attr_name for attr_name in dir(module) if not (attr_name.startswith('__') or attr_name.endswith('__'))]: module_attr = getattr(module, module_attr_name) - #log.debug('%s : %s'%(module_attr_name, module_attr)) + log.debug('%s : %s'%(module_attr_name, module_attr)) try: if issubclass(module_attr, GajimPlugin) and \ not module_attr is GajimPlugin: - #log.debug('is subclass of GajimPlugin') + log.debug('is subclass of GajimPlugin') #log.debug('file_path: %s\nabspath: %s\ndirname: %s'%(file_path, os.path.abspath(file_path), os.path.dirname(os.path.abspath(file_path)))) #log.debug('file_path: %s\ndirname: %s\nabspath: %s'%(file_path, os.path.dirname(file_path), os.path.abspath(os.path.dirname(file_path)))) module_attr.__path__ = os.path.abspath(os.path.dirname(file_path)) diff --git a/src/session.py b/src/session.py index a49d16be2..a905fbfa3 100644 --- a/src/session.py +++ b/src/session.py @@ -224,6 +224,12 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): (self.conn.name, [full_jid_with_resource, msgtxt, tim, encrypted, msg_type, subject, chatstate, msg_id, composing_xep, user_nick, xhtml, form_node])) + + gajim.ged.raise_event('NewMessage', + (self.conn.name, [full_jid_with_resource, msgtxt, tim, + encrypted, msg_type, subject, chatstate, msg_id, + composing_xep, user_nick, xhtml, form_node])) + # display the message or show notification in the roster def roster_message(self, jid, msg, tim, encrypted=False, msg_type='', From 8ec03d822ec2460290364bad77f0168463c490c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Mon, 11 Aug 2008 15:22:56 +0000 Subject: [PATCH 021/200] Added Events Dump plugin, that prints out to console info about selected events when they occur. --- plugins/dbus_plugin/plugin.py | 2 +- plugins/events_dump/__init__.py | 1 + plugins/events_dump/plugin.py | 80 +++++++++++++++++++++++++++++++++ src/gajim.py | 12 +++-- src/plugins/pluginmanager.py | 2 +- 5 files changed, 91 insertions(+), 6 deletions(-) create mode 100644 plugins/events_dump/__init__.py create mode 100644 plugins/events_dump/plugin.py diff --git a/plugins/dbus_plugin/plugin.py b/plugins/dbus_plugin/plugin.py index 8141d83d3..b894a3859 100644 --- a/plugins/dbus_plugin/plugin.py +++ b/plugins/dbus_plugin/plugin.py @@ -109,7 +109,7 @@ Gajim core but uses new events handling system.''' def _generate_handling_method(self, event_name): def handler(self, arg): - print "Handler of event %s called"%(event_name) + #print "Handler of event %s called"%(event_name) if self.signal_object: getattr(self.signal_object, event_name)(get_dbus_struct(arg)) diff --git a/plugins/events_dump/__init__.py b/plugins/events_dump/__init__.py new file mode 100644 index 000000000..fc198d87d --- /dev/null +++ b/plugins/events_dump/__init__.py @@ -0,0 +1 @@ +from plugin import EventsDumpPlugin \ No newline at end of file diff --git a/plugins/events_dump/plugin.py b/plugins/events_dump/plugin.py new file mode 100644 index 000000000..5d1f9bf29 --- /dev/null +++ b/plugins/events_dump/plugin.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## +''' +Events Dump plugin. + +Dumps info about selected events to console. + +:author: Mateusz Biliński +:since: 10th August 2008 +:copyright: Copyright (2008) Mateusz Biliński +:license: GPL +''' + +import new +from pprint import pformat + +from plugins import GajimPlugin +from plugins.helpers import log_calls, log +from common import ged + +class EventsDumpPlugin(GajimPlugin): + name = u'Events Dump' + short_name = u'events_dump' + version = u'0.1' + description = u'''Dumps info about selected events to console.''' + authors = [u'Mateusz Biliński '] + homepage = u'http://blog.bilinski.it' + + @log_calls('DBusPlugin') + def init(self): + self.config_dialog = None + #self.gui_extension_points = {} + #self.config_default_values = {} + + self.events_names = ['Roster', 'AccountPresence', 'ContactPresence', + 'ContactAbsence', 'ContactStatus', 'NewMessage', + 'Subscribe', 'Subscribed', 'Unsubscribed', + 'NewAccount', 'VcardInfo', 'LastStatusTime', + 'OsInfo', 'GCPresence', 'GCMessage', 'RosterInfo', + 'NewGmail'] + + self.events_handlers = {} + self._set_handling_methods() + + + def activate(self): + pass + + def deactivate(self): + pass + + def _set_handling_methods(self): + for event_name in self.events_names: + setattr(self, event_name, + new.instancemethod( + self._generate_handling_method(event_name), + self, + EventsDumpPlugin)) + self.events_handlers[event_name] = (ged.POSTCORE, + getattr(self, event_name)) + + def _generate_handling_method(self, event_name): + def handler(self, *args): + print "Event '%s' occured. Arguments: %s"%(event_name, pformat(*args)) + + return handler \ No newline at end of file diff --git a/src/gajim.py b/src/gajim.py index 7b9152646..19f5232c1 100755 --- a/src/gajim.py +++ b/src/gajim.py @@ -3344,7 +3344,14 @@ class Interface: self.handle_event_file_rcv_completed, self.handle_event_file_progress) gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue) + + # Creating Global Events Dispatcher + from common import ged + gajim.ged = ged.GlobalEventsDispatcher() + self.register_core_handlers() + self.register_handlers() + if gajim.config.get('enable_zeroconf'): gajim.connections[gajim.ZEROCONF_ACC_NAME] = common.zeroconf.connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME) for account in gajim.config.get_per('accounts'): @@ -3489,10 +3496,7 @@ class Interface: gobject.timeout_add_seconds(gajim.config.get( 'check_idle_every_foo_seconds'), self.read_sleepy) - # Creating Global Events Dispatcher - from common import ged - gajim.ged = ged.GlobalEventsDispatcher() - self.register_core_handlers() + # Creating plugin manager import plugins diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py index 4ee6f20da..23d514a1c 100644 --- a/src/plugins/pluginmanager.py +++ b/src/plugins/pluginmanager.py @@ -202,7 +202,7 @@ class PluginManager(object): ''' if gui_extpoint_name in self.gui_extension_points: - log.debug('Removing GUI extpoint\n name: %s\n args: %s'%(gui_extpoint_name, args)) + #log.debug('Removing GUI extpoint\n name: %s\n args: %s'%(gui_extpoint_name, args)) self.gui_extension_points[gui_extpoint_name].remove(args) From 2ee4c7ee5e008038607f72ed8f39d4abede291b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Mon, 11 Aug 2008 15:38:07 +0000 Subject: [PATCH 022/200] Changed log_calls parameter to EventsDumpPlugin in that class. --- plugins/events_dump/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/events_dump/plugin.py b/plugins/events_dump/plugin.py index 5d1f9bf29..44cf46348 100644 --- a/plugins/events_dump/plugin.py +++ b/plugins/events_dump/plugin.py @@ -40,7 +40,7 @@ class EventsDumpPlugin(GajimPlugin): authors = [u'Mateusz Biliński '] homepage = u'http://blog.bilinski.it' - @log_calls('DBusPlugin') + @log_calls('EventsDumpPlugin') def init(self): self.config_dialog = None #self.gui_extension_points = {} From 94f27ecffcbe61b3723f6d77a101e6ee6c21ee2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Sat, 16 Aug 2008 20:30:37 +0000 Subject: [PATCH 023/200] Snarl Notifications plugin initial version added. New events (from current core) go also through GED. --- plugins/events_dump/plugin.py | 34 +- plugins/snarl_notifications/PySnarl.py | 772 ++++++++++++++++++++++++ plugins/snarl_notifications/__init__.py | 1 + plugins/snarl_notifications/plugin.py | 88 +++ src/common/connection.py | 2 + 5 files changed, 895 insertions(+), 2 deletions(-) create mode 100755 plugins/snarl_notifications/PySnarl.py create mode 100644 plugins/snarl_notifications/__init__.py create mode 100644 plugins/snarl_notifications/plugin.py diff --git a/plugins/events_dump/plugin.py b/plugins/events_dump/plugin.py index 44cf46348..055af8772 100644 --- a/plugins/events_dump/plugin.py +++ b/plugins/events_dump/plugin.py @@ -51,7 +51,37 @@ class EventsDumpPlugin(GajimPlugin): 'Subscribe', 'Subscribed', 'Unsubscribed', 'NewAccount', 'VcardInfo', 'LastStatusTime', 'OsInfo', 'GCPresence', 'GCMessage', 'RosterInfo', - 'NewGmail'] + 'NewGmail','ROSTER', 'WARNING', 'ERROR', + 'INFORMATION', 'ERROR_ANSWER', 'STATUS', + 'NOTIFY', 'MSGERROR', 'MSGSENT', 'MSGNOTSENT', + 'SUBSCRIBED', 'UNSUBSCRIBED', 'SUBSCRIBE', + 'AGENT_ERROR_INFO', 'AGENT_ERROR_ITEMS', + 'AGENT_REMOVED', 'REGISTER_AGENT_INFO', + 'AGENT_INFO_ITEMS', 'AGENT_INFO_INFO', + 'QUIT', 'NEW_ACC_CONNECTED', + 'NEW_ACC_NOT_CONNECTED', 'ACC_OK', 'ACC_NOT_OK', + 'MYVCARD', 'VCARD', 'LAST_STATUS_TIME', 'OS_INFO', + 'GC_NOTIFY', 'GC_MSG', 'GC_SUBJECT', 'GC_CONFIG', + 'GC_CONFIG_CHANGE', 'GC_INVITATION', + 'GC_AFFILIATION', 'GC_PASSWORD_REQUIRED', + 'BAD_PASSPHRASE', 'ROSTER_INFO', 'BOOKMARKS', + 'CON_TYPE', 'CONNECTION_LOST', 'FILE_REQUEST', + 'GMAIL_NOTIFY', 'FILE_REQUEST_ERROR', + 'FILE_SEND_ERROR', 'STANZA_ARRIVED', 'STANZA_SENT', + 'HTTP_AUTH', 'VCARD_PUBLISHED', + 'VCARD_NOT_PUBLISHED', 'ASK_NEW_NICK', 'SIGNED_IN', + 'METACONTACTS', 'ATOM_ENTRY', 'FAILED_DECRYPT', + 'PRIVACY_LISTS_RECEIVED', 'PRIVACY_LIST_RECEIVED', + 'PRIVACY_LISTS_ACTIVE_DEFAULT', + 'PRIVACY_LIST_REMOVED', 'ZC_NAME_CONFLICT', + 'PING_SENT', 'PING_REPLY', 'PING_ERROR', + 'SEARCH_FORM', 'SEARCH_RESULT', + 'RESOURCE_CONFLICT', 'PEP_CONFIG', + 'UNIQUE_ROOM_ID_UNSUPPORTED', + 'UNIQUE_ROOM_ID_SUPPORTED', 'SESSION_NEG', + 'GPG_PASSWORD_REQUIRED', 'SSL_ERROR', + 'FINGERPRINT_ERROR', 'PLAIN_CONNECTION', + 'PUBSUB_NODE_REMOVED', 'PUBSUB_NODE_NOT_REMOVED'] self.events_handlers = {} self._set_handling_methods() @@ -75,6 +105,6 @@ class EventsDumpPlugin(GajimPlugin): def _generate_handling_method(self, event_name): def handler(self, *args): - print "Event '%s' occured. Arguments: %s"%(event_name, pformat(*args)) + print "Event '%s' occured. Arguments: %s\n\n===\n"%(event_name, pformat(*args)) return handler \ No newline at end of file diff --git a/plugins/snarl_notifications/PySnarl.py b/plugins/snarl_notifications/PySnarl.py new file mode 100755 index 000000000..f75ccd5c1 --- /dev/null +++ b/plugins/snarl_notifications/PySnarl.py @@ -0,0 +1,772 @@ +""" +A python version of the main functions to use Snarl +(http://www.fullphat.net/snarl) + +Version 1.0 + +This module can be used in two ways. One is the normal way +the other snarl interfaces work. This means you can call snShowMessage +and get an ID back for manipulations. + +The other way is there is a class this module exposes called SnarlMessage. +This allows you to keep track of the message as a python object. If you +use the send without specifying False as the argument it will set the ID +to what the return of the last SendMessage was. This is of course only +useful for the SHOW message. + +Requires one of: + pywin32 extensions from http://pywin32.sourceforge.net + ctypes (included in Python 2.5, downloadable for earlier versions) + +Creator: Sam Listopad II (samlii@users.sourceforge.net) + +Copyright 2006-2008 Samuel Listopad II + +Licensed under the Apache License, Version 2.0 (the "License"); you may not +use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required +by applicable law or agreed to in writing, software distributed under the +License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS +OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +""" + +import array, struct + +def LOWORD(dword): + """Return the low WORD of the passed in integer""" + return dword & 0x0000ffff +#get the hi word +def HIWORD(dword): + """Return the high WORD of the passed in integer""" + return dword >> 16 + +class Win32FuncException(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + +class Win32Funcs: + """Just a little class to hide the details of finding and using the +correct win32 functions. The functions may throw a UnicodeEncodeError if +there is not a unicode version and it is sent a unicode string that cannot +be converted to ASCII.""" + WM_USER = 0x400 + WM_COPYDATA = 0x4a + #Type of String the functions are expecting. + #Used like function(myWin32Funcs.strType(param)). + __strType = str + #FindWindow function to use + __FindWindow = None + #FindWindow function to use + __FindWindowEx = None + #SendMessage function to use + __SendMessage = None + #SendMessageTimeout function to use + __SendMessageTimeout = None + #IsWindow function to use + __IsWindow = None + #RegisterWindowMessage to use + __RegisterWindowMessage = None + #GetWindowText to use + __GetWindowText = None + + def FindWindow(self, lpClassName, lpWindowName): + """Wraps the windows API call of FindWindow""" + if lpClassName is not None: + lpClassName = self.__strType(lpClassName) + if lpWindowName is not None: + lpWindowName = self.__strType(lpWindowName) + return self.__FindWindow(lpClassName, lpWindowName) + + def FindWindowEx(self, hwndParent, hwndChildAfter, lpClassName, lpWindowName): + """Wraps the windows API call of FindWindow""" + if lpClassName is not None: + lpClassName = self.__strType(lpClassName) + if lpWindowName is not None: + lpWindowName = self.__strType(lpWindowName) + return self.__FindWindowEx(hwndParent, hwndChildAfter, lpClassName, lpWindowName) + + def SendMessage(self, hWnd, Msg, wParam, lParam): + """Wraps the windows API call of SendMessage""" + return self.__SendMessage(hWnd, Msg, wParam, lParam) + + def SendMessageTimeout(self, hWnd, Msg, + wParam, lParam, fuFlags, + uTimeout, lpdwResult = None): + """Wraps the windows API call of SendMessageTimeout""" + idToRet = None + try: + idFromMsg = array.array('I', [0]) + result = idFromMsg.buffer_info()[0] + response = self.__SendMessageTimeout(hWnd, Msg, wParam, + lParam, fuFlags, + uTimeout, result) + if response == 0: + raise Win32FuncException, "SendMessageTimeout TimedOut" + + idToRet = idFromMsg[0] + except TypeError: + idToRet = self.__SendMessageTimeout(hWnd, Msg, wParam, + lParam, fuFlags, + uTimeout) + + if lpdwResult is not None and lpdwResult.typecode == 'I': + lpdwResult[0] = idToRet + + return idToRet + + def IsWindow(self, hWnd): + """Wraps the windows API call of IsWindow""" + return self.__IsWindow(hWnd) + + def RegisterWindowMessage(self, lpString): + """Wraps the windows API call of RegisterWindowMessage""" + return self.__RegisterWindowMessage(self.__strType(lpString)) + + def GetWindowText(self, hWnd, lpString = None, nMaxCount = None): + """Wraps the windows API call of SendMessageTimeout""" + text = '' + if hWnd == 0: + return text + + if nMaxCount is None: + nMaxCount = 1025 + + try: + arrayType = 'c' + if self.__strType == unicode: + arrayType = 'u' + path_string = array.array(arrayType, self.__strType('\x00') * nMaxCount) + path_buffer = path_string.buffer_info()[0] + result = self.__GetWindowText(hWnd, + path_buffer, + nMaxCount) + if result > 0: + if self.__strType == unicode: + text = path_string[0:result].tounicode() + else: + text = path_string[0:result].tostring() + except TypeError: + text = self.__GetWindowText(hWnd) + + if lpString is not None and lpString.typecode == 'c': + lpdwResult[0:len(text)] = array.array('c',str(text)); + + if lpString is not None and lpString.typecode == 'u': + lpdwResult[0:len(text)] = array.array('u',unicode(text)); + + return text + + def __init__(self): + """Load up my needed functions""" + # First see if they already have win32gui imported. If so use it. + # This has to be checked first since the auto check looks for ctypes + # first. + try: + self.__FindWindow = win32gui.FindWindow + self.__FindWindowEx = win32gui.FindWindowEx + self.__GetWindowText = win32gui.GetWindowText + self.__IsWindow = win32gui.IsWindow + self.__SendMessage = win32gui.SendMessage + self.__SendMessageTimeout = win32gui.SendMessageTimeout + self.__RegisterWindowMessage = win32gui.RegisterWindowMessage + self.__strType = unicode + + #Something threw a NameError, most likely the win32gui lines + #so do auto check + except NameError: + try: + from ctypes import windll + self.__FindWindow = windll.user32.FindWindowW + self.__FindWindowEx = windll.user32.FindWindowExW + self.__GetWindowText = windll.user32.GetWindowTextW + self.__IsWindow = windll.user32.IsWindow + self.__SendMessage = windll.user32.SendMessageW + self.__SendMessageTimeout = windll.user32.SendMessageTimeoutW + self.__RegisterWindowMessage = windll.user32.RegisterWindowMessageW + self.__strType = unicode + + #FindWindowW wasn't found, look for FindWindowA + except AttributeError: + try: + self.__FindWindow = windll.user32.FindWindowA + self.__FindWindowEx = windll.user32.FindWindowExA + self.__GetWindowText = windll.user32.GetWindowTextA + self.__IsWindow = windll.user32.IsWindow + self.__SendMessage = windll.user32.SendMessageA + self.__SendMessageTimeout = windll.user32.SendMessageTimeoutA + self.__RegisterWindowMessage = windll.user32.RegisterWindowMessageA + # Couldn't find either so Die and tell user why. + except AttributeError: + import sys + sys.stderr.write("Your Windows TM setup seems to be corrupt."+ + " No FindWindow found in user32.\n") + sys.stderr.flush() + sys.exit(3) + + except ImportError: + try: + import win32gui + self.__FindWindow = win32gui.FindWindow + self.__FindWindowEx = win32gui.FindWindowEx + self.__GetWindowText = win32gui.GetWindowText + self.__IsWindow = win32gui.IsWindow + self.__SendMessage = win32gui.SendMessage + self.__SendMessageTimeout = win32gui.SendMessageTimeout + self.__RegisterWindowMessage = win32gui.RegisterWindowMessage + self.__strType = unicode + + except ImportError: + import sys + sys.stderr.write("You need to have either"+ + " ctypes or pywin32 installed.\n") + sys.stderr.flush() + sys.exit(2) + + +myWin32Funcs = Win32Funcs() + + +SHOW = 1 +HIDE = 2 +UPDATE = 3 +IS_VISIBLE = 4 +GET_VERSION = 5 +REGISTER_CONFIG_WINDOW = 6 +REVOKE_CONFIG_WINDOW = 7 +REGISTER_ALERT = 8 +REVOKE_ALERT = 9 +REGISTER_CONFIG_WINDOW_2 = 10 +GET_VERSION_EX = 11 +SET_TIMEOUT = 12 + +EX_SHOW = 32 + +GLOBAL_MESSAGE = "SnarlGlobalMessage" +GLOBAL_MSG = "SnarlGlobalEvent" + +#Messages That may be received from Snarl +SNARL_LAUNCHED = 1 +SNARL_QUIT = 2 +SNARL_ASK_APPLET_VER = 3 +SNARL_SHOW_APP_UI = 4 + +SNARL_NOTIFICATION_CLICKED = 32 #notification was right-clicked by user +SNARL_NOTIFICATION_CANCELLED = SNARL_NOTIFICATION_CLICKED #Name clarified +SNARL_NOTIFICATION_TIMED_OUT = 33 +SNARL_NOTIFICATION_ACK = 34 #notification was left-clicked by user + +#Snarl Test Message +WM_SNARLTEST = myWin32Funcs.WM_USER + 237 + +M_ABORTED = 0x80000007L +M_ACCESS_DENIED = 0x80000009L +M_ALREADY_EXISTS = 0x8000000CL +M_BAD_HANDLE = 0x80000006L +M_BAD_POINTER = 0x80000005L +M_FAILED = 0x80000008L +M_INVALID_ARGS = 0x80000003L +M_NO_INTERFACE = 0x80000004L +M_NOT_FOUND = 0x8000000BL +M_NOT_IMPLEMENTED = 0x80000001L +M_OK = 0x00000000L +M_OUT_OF_MEMORY = 0x80000002L +M_TIMED_OUT = 0x8000000AL + +ErrorCodeRev = { + 0x80000007L : "M_ABORTED", + 0x80000009L : "M_ACCESS_DENIED", + 0x8000000CL : "M_ALREADY_EXISTS", + 0x80000006L : "M_BAD_HANDLE", + 0x80000005L : "M_BAD_POINTER", + 0x80000008L : "M_FAILED", + 0x80000003L : "M_INVALID_ARGS", + 0x80000004L : "M_NO_INTERFACE", + 0x8000000BL : "M_NOT_FOUND", + 0x80000001L : "M_NOT_IMPLEMENTED", + 0x00000000L : "M_OK", + 0x80000002L : "M_OUT_OF_MEMORY", + 0x8000000AL : "M_TIMED_OUT" + } + +class SnarlMessage(object): + """The main Snarl interface object. + + ID = Snarl Message ID for most operations. See SDK for more info + as to other values to put here. + type = Snarl Message Type. Valid values are : SHOW, HIDE, UPDATE, + IS_VISIBLE, GET_VERSION, REGISTER_CONFIG_WINDOW, REVOKE_CONFIG_WINDOW + all which are constants in the PySnarl module. + timeout = Timeout in seconds for the Snarl Message + data = Snarl Message data. This is dependant upon message type. See SDK + title = Snarl Message title. + text = Snarl Message text. + icon = Path to the icon to display in the Snarl Message. + """ + __msgType = 0 + __msgID = 0 + __msgTimeout = 0 + __msgData = 0 + __msgTitle = "" + __msgText = "" + __msgIcon = "" + __msgClass = "" + __msgExtra = "" + __msgExtra2 = "" + __msgRsvd1 = 0 + __msgRsvd2 = 0 + __msgHWnd = 0 + + lastKnownHWnd = 0 + + def getType(self): + """Type Attribute getter.""" + return self.__msgType + def setType(self, value): + """Type Attribute setter.""" + if( isinstance(value, (int, long)) ): + self.__msgType = value + type = property(getType, setType, doc="The Snarl Message Type") + + def getID(self): + """ID Attribute getter.""" + return self.__msgID + def setID(self, value): + """ID Attribute setter.""" + if( isinstance(value, (int, long)) ): + self.__msgID = value + ID = property(getID, setID, doc="The Snarl Message ID") + + def getTimeout(self): + """Timeout Attribute getter.""" + return self.__msgTimeout + def updateTimeout(self, value): + """Timeout Attribute setter.""" + if( isinstance(value, (int, long)) ): + self.__msgTimeout = value + timeout = property(getTimeout, updateTimeout, + doc="The Snarl Message Timeout") + + def getData(self): + """Data Attribute getter.""" + return self.__msgData + def setData(self, value): + """Data Attribute setter.""" + if( isinstance(value, (int, long)) ): + self.__msgData = value + data = property(getData, setData, doc="The Snarl Message Data") + + def getTitle(self): + """Title Attribute getter.""" + return self.__msgTitle + def setTitle(self, value): + """Title Attribute setter.""" + if( isinstance(value, basestring) ): + self.__msgTitle = value + title = property(getTitle, setTitle, doc="The Snarl Message Title") + + def getText(self): + """Text Attribute getter.""" + return self.__msgText + def setText(self, value): + """Text Attribute setter.""" + if( isinstance(value, basestring) ): + self.__msgText = value + text = property(getText, setText, doc="The Snarl Message Text") + + def getIcon(self): + """Icon Attribute getter.""" + return self.__msgIcon + def setIcon(self, value): + """Icon Attribute setter.""" + if( isinstance(value, basestring) ): + self.__msgIcon = value + icon = property(getIcon, setIcon, doc="The Snarl Message Icon") + + def getClass(self): + """Class Attribute getter.""" + return self.__msgClass + def setClass(self, value): + """Class Attribute setter.""" + if( isinstance(value, basestring) ): + self.__msgClass = value + msgclass = property(getClass, setClass, doc="The Snarl Message Class") + + def getExtra(self): + """Extra Attribute getter.""" + return self.__msgExtra + def setExtra(self, value): + """Extra Attribute setter.""" + if( isinstance(value, basestring) ): + self.__msgExtra = value + extra = property(getExtra, setExtra, doc="Extra Info for the Snarl Message") + + def getExtra2(self): + """Extra2 Attribute getter.""" + return self.__msgExtra2 + def setExtra2(self, value): + """Extra2 Attribute setter.""" + if( isinstance(value, basestring) ): + self.__msgExtra2 = value + extra2 = property(getExtra2, setExtra2, + doc="More Extra Info for the Snarl Message") + + def getRsvd1(self): + """Rsvd1 Attribute getter.""" + return self.__msgRsvd1 + def setRsvd1(self, value): + """Rsvd1 Attribute setter.""" + if( isinstance(value, (int, long)) ): + self.__msgRsvd1 = value + rsvd1 = property(getRsvd1, setRsvd1, doc="The Snarl Message Field Rsvd1") + + def getRsvd2(self): + """Rsvd2 Attribute getter.""" + return self.__msgRsvd2 + def setRsvd2(self, value): + """Rsvd2 Attribute setter.""" + if( isinstance(value, (int, long)) ): + self.__msgRsvd2 = value + rsvd2 = property(getRsvd2, setRsvd2, doc="The Snarl Message Field Rsvd2") + + def getHwnd(self): + """hWnd Attribute getter.""" + return self.__msgHWnd + def setHwnd(self, value): + """hWnd Attribute setter.""" + if( isinstance(value, (int, long)) ): + self.__msgHWnd = value + + hWnd = property(getHwnd, setHwnd, doc="The hWnd of the window this message is being sent from") + + + def __init__(self, title="", text="", icon="", msg_type=1, msg_id=0): + self.__msgTimeout = 0 + self.__msgData = 0 + self.__msgClass = "" + self.__msgExtra = "" + self.__msgExtra2 = "" + self.__msgRsvd1 = 0 + self.__msgRsvd2 = 0 + self.__msgType = msg_type + self.__msgText = text + self.__msgTitle = title + self.__msgIcon = icon + self.__msgID = msg_id + + def createCopyStruct(self): + """Creates the struct to send as the copyData in the message.""" + return struct.pack("ILLL1024s1024s1024s1024s1024s1024sLL", + self.__msgType, + self.__msgID, + self.__msgTimeout, + self.__msgData, + self.__msgTitle.encode('utf-8'), + self.__msgText.encode('utf-8'), + self.__msgIcon.encode('utf-8'), + self.__msgClass.encode('utf-8'), + self.__msgExtra.encode('utf-8'), + self.__msgExtra2.encode('utf-8'), + self.__msgRsvd1, + self.__msgRsvd2 + ) + __lpData = None + __cds = None + + def packData(self, dwData): + """This packs the data in the necessary format for a +WM_COPYDATA message.""" + self.__lpData = None + self.__cds = None + item = self.createCopyStruct() + self.__lpData = array.array('c', item) + lpData_ad = self.__lpData.buffer_info()[0] + cbData = self.__lpData.buffer_info()[1] + self.__cds = array.array('c', + struct.pack("IIP", + dwData, + cbData, + lpData_ad) + ) + cds_ad = self.__cds.buffer_info()[0] + return cds_ad + + def reset(self): + """Reset this SnarlMessage to the default state.""" + self.__msgType = 0 + self.__msgID = 0 + self.__msgTimeout = 0 + self.__msgData = 0 + self.__msgTitle = "" + self.__msgText = "" + self.__msgIcon = "" + self.__msgClass = "" + self.__msgExtra = "" + self.__msgExtra2 = "" + self.__msgRsvd1 = 0 + self.__msgRsvd2 = 0 + + + def send(self, setid=True): + """Send this SnarlMessage to the Snarl window. +Args: + setid - Boolean defining whether or not to set the ID + of this SnarlMessage to the return value of + the SendMessage call. Default is True to + make simple case of SHOW easy. + """ + hwnd = myWin32Funcs.FindWindow(None, "Snarl") + if myWin32Funcs.IsWindow(hwnd): + if self.type == REGISTER_CONFIG_WINDOW or self.type == REGISTER_CONFIG_WINDOW_2: + self.hWnd = self.data + try: + response = myWin32Funcs.SendMessageTimeout(hwnd, + myWin32Funcs.WM_COPYDATA, + self.hWnd, self.packData(2), + 2, 500) + except Win32FuncException: + return False + + idFromMsg = response + if setid: + self.ID = idFromMsg + return True + else: + return idFromMsg + print "No snarl window found" + return False + + def hide(self): + """Hide this message. Type will revert to type before calling hide +to allow for better reuse of object.""" + oldType = self.__msgType + self.__msgType = HIDE + retVal = bool(self.send(False)) + self.__msgType = oldType + return retVal + + def isVisible(self): + """Is this message visible. Type will revert to type before calling +hide to allow for better reuse of object.""" + oldType = self.__msgType + self.__msgType = IS_VISIBLE + retVal = bool(self.send(False)) + self.__msgType = oldType + return retVal + + def update(self, title=None, text=None, icon=None): + """Update this message with given title and text. Type will revert +to type before calling hide to allow for better reuse of object.""" + oldType = self.__msgType + self.__msgType = UPDATE + if text: + self.__msgText = text + if title: + self.__msgTitle = title + if icon: + self.__msgIcon = icon + retVal = self.send(False) + self.__msgType = oldType + return retVal + + def setTimeout(self, timeout): + """Set the timeout in seconds of the message""" + oldType = self.__msgType + oldData = self.__msgData + self.__msgType = SET_TIMEOUT + #self.timeout = timeout + #self.__msgData = self.__msgTimeout + self.__msgData = timeout + retVal = self.send(False) + self.__msgType = oldType + self.__msgData = oldData + return retVal + + def show(self, timeout=None, title=None, + text=None, icon=None, + replyWindow=None, replyMsg=None, msgclass=None, soundPath=None): + """Show a message""" + oldType = self.__msgType + oldTimeout = self.__msgTimeout + self.__msgType = SHOW + if text: + self.__msgText = text + if title: + self.__msgTitle = title + if timeout: + self.__msgTimeout = timeout + if icon: + self.__msgIcon = icon + if replyWindow: + self.__msgID = replyMsg + if replyMsg: + self.__msgData = replyWindow + if soundPath: + self.__msgExtra = soundPath + if msgclass: + self.__msgClass = msgclass + + if ((self.__msgClass and self.__msgClass != "") or + (self.__msgExtra and self.__msgExtra != "")): + self.__msgType = EX_SHOW + + + retVal = bool(self.send()) + self.__msgType = oldType + self.__msgTimeout = oldTimeout + return retVal + + +def snGetVersion(): + """ Get the version of Snarl that is running as a tuple. (Major, Minor) + +If Snarl is not running or there was an error it will +return False.""" + msg = SnarlMessage(msg_type=GET_VERSION) + version = msg.send(False) + if not version: + return False + return (HIWORD(version), LOWORD(version)) + +def snGetVersionEx(): + """ Get the internal version of Snarl that is running. + +If Snarl is not running or there was an error it will +return False.""" + sm = SnarlMessage(msg_type=GET_VERSION_EX) + verNum = sm.send(False) + if not verNum: + return False + return verNum + +def snGetGlobalMessage(): + """Get the Snarl global message id from windows.""" + return myWin32Funcs.RegisterWindowMessage(GLOBAL_MSG) + +def snShowMessage(title, text, timeout=0, iconPath="", + replyWindow=0, replyMsg=0): + """Show a message using Snarl and return its ID. See SDK for arguments.""" + sm = SnarlMessage( title, text, iconPath, msg_id=replyMsg) + sm.data = replyWindow + if sm.show(timeout): + return sm.ID + else: + return False + +def snShowMessageEx(msgClass, title, text, timeout=0, iconPath="", + replyWindow=0, replyMsg=0, soundFile=None, hWndFrom=None): + """Show a message using Snarl and return its ID. See SDK for arguments. + One added argument is hWndFrom that allows one to make the messages appear + to come from a specific window. This window should be the one you registered + earlier with RegisterConfig""" + sm = SnarlMessage( title, text, iconPath, msg_id=replyMsg) + sm.data = replyWindow + if hWndFrom is not None: + sm.hWnd = hWndFrom + else: + sm.hWnd = SnarlMessage.lastKnownHWnd + if sm.show(timeout, msgclass=msgClass, soundPath=soundFile): + return sm.ID + else: + return False + +def snUpdateMessage(msgId, msgTitle, msgText, icon=None): + """Update a message""" + sm = SnarlMessage(msg_id=msgId) + if icon: + sm.icon = icon + return sm.update(msgTitle, msgText) + +def snHideMessage(msgId): + """Hide a message""" + return SnarlMessage(msg_id=msgId).hide() + +def snSetTimeout(msgId, timeout): + """Update the timeout of a message already shown.""" + sm = SnarlMessage(msg_id=msgId) + return sm.setTimeout(timeout) + +def snIsMessageVisible(msgId): + """Returns True if the message is visible False otherwise.""" + return SnarlMessage(msg_id=msgId).isVisible() + +def snRegisterConfig(replyWnd, appName, replyMsg): + """Register a config window. See SDK for more info.""" + global lastRegisteredSnarlMsg + sm = SnarlMessage(msg_type=REGISTER_CONFIG_WINDOW, + title=appName, + msg_id=replyMsg) + sm.data = replyWnd + SnarlMessage.lastKnownHWnd = replyWnd + + return sm.send(False) + +def snRegisterConfig2(replyWnd, appName, replyMsg, icon): + """Register a config window. See SDK for more info.""" + global lastRegisteredSnarlMsg + sm = SnarlMessage(msg_type=REGISTER_CONFIG_WINDOW_2, + title=appName, + msg_id=replyMsg, + icon=icon) + sm.data = replyWnd + SnarlMessage.lastKnownHWnd = replyWnd + return sm.send(False) + +def snRegisterAlert(appName, classStr) : + """Register an alert for an already registered config. See SDK for more info.""" + sm = SnarlMessage(msg_type=REGISTER_ALERT, + title=appName, + text=classStr) + return sm.send(False) + +def snRevokeConfig(replyWnd): + """Revoke a config window""" + sm = SnarlMessage(msg_type=REVOKE_CONFIG_WINDOW) + sm.data = replyWnd + if replyWnd == SnarlMessage.lastKnownHWnd: + SnarlMessage.lastKnownHWnd = 0 + return sm.send(False) + +def snGetSnarlWindow(): + """Returns the hWnd of the snarl window""" + return myWin32Funcs.FindWindow(None, "Snarl") + +def snGetAppPath(): + """Returns the application path of the currently running snarl window""" + app_path = None + snarl_handle = snGetSnarlWindow() + if snarl_handle != 0: + pathwin_handle = myWin32Funcs.FindWindowEx(snarl_handle, + 0, + "static", + None) + if pathwin_handle != 0: + try: + result = myWin32Funcs.GetWindowText(pathwin_handle) + app_path = result + except Win32FuncException: + pass + + + return app_path + +def snGetIconsPath(): + """Returns the path to the icons of the program""" + s = snGetAppPath() + if s is None: + return "" + else: + return s + "etc\\icons\\" + +def snSendTestMessage(data=None): + """Sends a test message to Snarl. Used to make sure the +api is connecting""" + param = 0 + command = 0 + if data: + param = struct.pack("I", data) + command = 1 + myWin32Funcs.SendMessage(snGetSnarlWindow(), WM_SNARLTEST, command, param) diff --git a/plugins/snarl_notifications/__init__.py b/plugins/snarl_notifications/__init__.py new file mode 100644 index 000000000..ad9c96aaa --- /dev/null +++ b/plugins/snarl_notifications/__init__.py @@ -0,0 +1 @@ +from plugin import SnarlNotificationsPlugin \ No newline at end of file diff --git a/plugins/snarl_notifications/plugin.py b/plugins/snarl_notifications/plugin.py new file mode 100644 index 000000000..e6ef5ae18 --- /dev/null +++ b/plugins/snarl_notifications/plugin.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## +''' +Events notifications using Snarl + +Fancy events notifications under Windows using Snarl infrastructure. + +:note: plugin is at proof-of-concept state. + +:author: Mateusz Biliński +:since: 15th August 2008 +:copyright: Copyright (2008) Mateusz Biliński +:license: GPL +''' + +import new +from pprint import pformat + +import PySnarl + +from common import gajim +from plugins import GajimPlugin +from plugins.helpers import log_calls, log +from common import ged + +class SnarlNotificationsPlugin(GajimPlugin): + name = u'Snarl Notifications' + short_name = u'snarl_notifications' + version = u'0.1' + description = u'''Shows events notification using Snarl (http://www.fullphat.net/) under Windows. Snarl needs to be installed in system. +PySnarl bindings are used (http://code.google.com/p/pysnarl/).''' + authors = [u'Mateusz Biliński '] + homepage = u'http://blog.bilinski.it' + + @log_calls('SnarlNotificationsPlugin') + def init(self): + self.config_dialog = None + #self.gui_extension_points = {} + #self.config_default_values = {} + + self.events_handlers = {'NewMessage' : (ged.POSTCORE, self.newMessage)} + + def activate(self): + pass + + def deactivate(self): + pass + + def newMessage(self, args): + event_name = "NewMessage" + data = args[0] + account = data[0] + jid = data[1][0] + jid_without_resource = gajim.get_jid_without_resource(jid) + msg = data[1][1] + msg_type = data[1][4] + if msg_type == 'chat': + nickname = gajim.get_contact_name_from_jid(account, + jid_without_resource) + elif msg_type == 'pm': + nickname = gajim.get_resource_from_jid(jid) + + print "Event '%s' occured. Arguments: %s\n\n===\n"%(event_name, pformat(*args)) + print "Event '%s' occured. Arguments: \naccount = %s\njid = %s\nmsg = %s\nnickname = %s"%( + event_name, account, jid, msg, nickname) + + + if PySnarl.snGetVersion() != False: + (major, minor) = PySnarl.snGetVersion() + print "Found Snarl version",str(major)+"."+str(minor),"running." + PySnarl.snShowMessage(nickname, msg[:20]+'...') + else: + print "Sorry Snarl does not appear to be running" + \ No newline at end of file diff --git a/src/common/connection.py b/src/common/connection.py index e9ffba607..c5cc190a2 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -168,12 +168,14 @@ class Connection(ConnectionHandlers): # END __init__ def put_event(self, ev): + if gajim.handlers.has_key(ev[0]): gajim.handlers[ev[0]](self.name, ev[1]) def dispatch(self, event, data): '''always passes account name as first param''' self.put_event((event, data)) + gajim.ged.raise_event(event, data) def _reconnect(self): From ff8eaddf5120bb6b232fae234d58623c0dd6ba68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Sat, 16 Aug 2008 20:53:10 +0000 Subject: [PATCH 024/200] Removed exit() from PySnarl so it doesn't terminate Gajim on non-Windows platforms. --- plugins/snarl_notifications/PySnarl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/snarl_notifications/PySnarl.py b/plugins/snarl_notifications/PySnarl.py index f75ccd5c1..118a7b25c 100755 --- a/plugins/snarl_notifications/PySnarl.py +++ b/plugins/snarl_notifications/PySnarl.py @@ -224,7 +224,7 @@ be converted to ASCII.""" sys.stderr.write("You need to have either"+ " ctypes or pywin32 installed.\n") sys.stderr.flush() - sys.exit(2) + #sys.exit(2) myWin32Funcs = Win32Funcs() From c0a26be684cdbf613939052ef91b37afba5e20f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Mon, 18 Aug 2008 16:35:14 +0000 Subject: [PATCH 025/200] Three core (raw) events (iq, message, presence) go also through Network Events Controller (layer between network library and Global Events Dispatcher, newly added) and from there they are dispatched through Global Events Dispatcher. Ability to register new incoming network events (based on exisiting one) added. Modify-only network events are possible (eg. add some text each message, but don't create any new global event). Events creation can be chained. Examples of new network events classes are in New Events Example plugin. Events from src/gajim.py now all go through Global Events Dispatcher and only through it (easy to modify, in chain, data passed with them). --- plugins/events_dump/plugin.py | 91 +++++++++------- plugins/new_events_example/__init__.py | 1 + plugins/new_events_example/plugin.py | 145 +++++++++++++++++++++++++ plugins/snarl_notifications/plugin.py | 18 +-- src/common/connection.py | 4 +- src/common/connection_handlers.py | 14 ++- src/common/gajim.py | 4 +- src/common/ged.py | 14 +-- src/common/nec.py | 135 +++++++++++++++++++++++ src/gajim.py | 89 ++++++++++++++- src/plugins/plugin.py | 7 ++ src/plugins/pluginmanager.py | 19 +++- 12 files changed, 480 insertions(+), 61 deletions(-) create mode 100644 plugins/new_events_example/__init__.py create mode 100644 plugins/new_events_example/plugin.py create mode 100644 src/common/nec.py diff --git a/plugins/events_dump/plugin.py b/plugins/events_dump/plugin.py index 055af8772..b5d37cd06 100644 --- a/plugins/events_dump/plugin.py +++ b/plugins/events_dump/plugin.py @@ -45,43 +45,60 @@ class EventsDumpPlugin(GajimPlugin): self.config_dialog = None #self.gui_extension_points = {} #self.config_default_values = {} + events_from_old_dbus_support = [ + 'Roster', 'AccountPresence', 'ContactPresence', + 'ContactAbsence', 'ContactStatus', 'NewMessage', + 'Subscribe', 'Subscribed', 'Unsubscribed', + 'NewAccount', 'VcardInfo', 'LastStatusTime', + 'OsInfo', 'GCPresence', 'GCMessage', 'RosterInfo', + 'NewGmail'] - self.events_names = ['Roster', 'AccountPresence', 'ContactPresence', - 'ContactAbsence', 'ContactStatus', 'NewMessage', - 'Subscribe', 'Subscribed', 'Unsubscribed', - 'NewAccount', 'VcardInfo', 'LastStatusTime', - 'OsInfo', 'GCPresence', 'GCMessage', 'RosterInfo', - 'NewGmail','ROSTER', 'WARNING', 'ERROR', - 'INFORMATION', 'ERROR_ANSWER', 'STATUS', - 'NOTIFY', 'MSGERROR', 'MSGSENT', 'MSGNOTSENT', - 'SUBSCRIBED', 'UNSUBSCRIBED', 'SUBSCRIBE', - 'AGENT_ERROR_INFO', 'AGENT_ERROR_ITEMS', - 'AGENT_REMOVED', 'REGISTER_AGENT_INFO', - 'AGENT_INFO_ITEMS', 'AGENT_INFO_INFO', - 'QUIT', 'NEW_ACC_CONNECTED', - 'NEW_ACC_NOT_CONNECTED', 'ACC_OK', 'ACC_NOT_OK', - 'MYVCARD', 'VCARD', 'LAST_STATUS_TIME', 'OS_INFO', - 'GC_NOTIFY', 'GC_MSG', 'GC_SUBJECT', 'GC_CONFIG', - 'GC_CONFIG_CHANGE', 'GC_INVITATION', - 'GC_AFFILIATION', 'GC_PASSWORD_REQUIRED', - 'BAD_PASSPHRASE', 'ROSTER_INFO', 'BOOKMARKS', - 'CON_TYPE', 'CONNECTION_LOST', 'FILE_REQUEST', - 'GMAIL_NOTIFY', 'FILE_REQUEST_ERROR', - 'FILE_SEND_ERROR', 'STANZA_ARRIVED', 'STANZA_SENT', - 'HTTP_AUTH', 'VCARD_PUBLISHED', - 'VCARD_NOT_PUBLISHED', 'ASK_NEW_NICK', 'SIGNED_IN', - 'METACONTACTS', 'ATOM_ENTRY', 'FAILED_DECRYPT', - 'PRIVACY_LISTS_RECEIVED', 'PRIVACY_LIST_RECEIVED', - 'PRIVACY_LISTS_ACTIVE_DEFAULT', - 'PRIVACY_LIST_REMOVED', 'ZC_NAME_CONFLICT', - 'PING_SENT', 'PING_REPLY', 'PING_ERROR', - 'SEARCH_FORM', 'SEARCH_RESULT', - 'RESOURCE_CONFLICT', 'PEP_CONFIG', - 'UNIQUE_ROOM_ID_UNSUPPORTED', - 'UNIQUE_ROOM_ID_SUPPORTED', 'SESSION_NEG', - 'GPG_PASSWORD_REQUIRED', 'SSL_ERROR', - 'FINGERPRINT_ERROR', 'PLAIN_CONNECTION', - 'PUBSUB_NODE_REMOVED', 'PUBSUB_NODE_NOT_REMOVED'] + events_from_src_gajim = [ + 'ROSTER', 'WARNING', 'ERROR', + 'INFORMATION', 'ERROR_ANSWER', 'STATUS', + 'NOTIFY', 'MSGERROR', 'MSGSENT', 'MSGNOTSENT', + 'SUBSCRIBED', 'UNSUBSCRIBED', 'SUBSCRIBE', + 'AGENT_ERROR_INFO', 'AGENT_ERROR_ITEMS', + 'AGENT_REMOVED', 'REGISTER_AGENT_INFO', + 'AGENT_INFO_ITEMS', 'AGENT_INFO_INFO', + 'QUIT', 'NEW_ACC_CONNECTED', + 'NEW_ACC_NOT_CONNECTED', 'ACC_OK', 'ACC_NOT_OK', + 'MYVCARD', 'VCARD', 'LAST_STATUS_TIME', 'OS_INFO', + 'GC_NOTIFY', 'GC_MSG', 'GC_SUBJECT', 'GC_CONFIG', + 'GC_CONFIG_CHANGE', 'GC_INVITATION', + 'GC_AFFILIATION', 'GC_PASSWORD_REQUIRED', + 'BAD_PASSPHRASE', 'ROSTER_INFO', 'BOOKMARKS', + 'CON_TYPE', 'CONNECTION_LOST', 'FILE_REQUEST', + 'GMAIL_NOTIFY', 'FILE_REQUEST_ERROR', + 'FILE_SEND_ERROR', 'STANZA_ARRIVED', 'STANZA_SENT', + 'HTTP_AUTH', 'VCARD_PUBLISHED', + 'VCARD_NOT_PUBLISHED', 'ASK_NEW_NICK', 'SIGNED_IN', + 'METACONTACTS', 'ATOM_ENTRY', 'FAILED_DECRYPT', + 'PRIVACY_LISTS_RECEIVED', 'PRIVACY_LIST_RECEIVED', + 'PRIVACY_LISTS_ACTIVE_DEFAULT', + 'PRIVACY_LIST_REMOVED', 'ZC_NAME_CONFLICT', + 'PING_SENT', 'PING_REPLY', 'PING_ERROR', + 'SEARCH_FORM', 'SEARCH_RESULT', + 'RESOURCE_CONFLICT', 'PEP_CONFIG', + 'UNIQUE_ROOM_ID_UNSUPPORTED', + 'UNIQUE_ROOM_ID_SUPPORTED', 'SESSION_NEG', + 'GPG_PASSWORD_REQUIRED', 'SSL_ERROR', + 'FINGERPRINT_ERROR', 'PLAIN_CONNECTION', + 'PUBSUB_NODE_REMOVED', 'PUBSUB_NODE_NOT_REMOVED'] + + network_events_from_core = ['raw-message-received', + 'raw-iq-received', + 'raw-pres-received'] + + network_events_generated_in_nec = [ + 'customized-message-received', + 'more-customized-message-received', + 'modify-only-message-received', + 'enriched-chat-message-received'] + + self.events_names = [] + self.events_names += network_events_from_core + self.events_names += network_events_generated_in_nec self.events_handlers = {} self._set_handling_methods() @@ -105,6 +122,6 @@ class EventsDumpPlugin(GajimPlugin): def _generate_handling_method(self, event_name): def handler(self, *args): - print "Event '%s' occured. Arguments: %s\n\n===\n"%(event_name, pformat(*args)) + print "Event '%s' occured. Arguments: %s\n\n===\n"%(event_name, pformat(args)) return handler \ No newline at end of file diff --git a/plugins/new_events_example/__init__.py b/plugins/new_events_example/__init__.py new file mode 100644 index 000000000..a0eca896c --- /dev/null +++ b/plugins/new_events_example/__init__.py @@ -0,0 +1 @@ +from plugin import NewEventsExamplePlugin \ No newline at end of file diff --git a/plugins/new_events_example/plugin.py b/plugins/new_events_example/plugin.py new file mode 100644 index 000000000..451f300c2 --- /dev/null +++ b/plugins/new_events_example/plugin.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## +''' +New Events Example plugin. + +Demonstrates how to use Network Events Controller to generate new events +based on existing one. + +:author: Mateusz Biliński +:since: 15th August 2008 +:copyright: Copyright (2008) Mateusz Biliński +:license: GPL +''' + +import new +from pprint import pformat + +from common import helpers +from common import gajim + +from plugins import GajimPlugin +from plugins.helpers import log_calls, log +from common import ged +from common import nec + +class NewEventsExamplePlugin(GajimPlugin): + name = u'New Events Example' + short_name = u'new_events_example' + version = u'0.1' + description = u'''Shows how to generate new network events based on existing one using Network Events Controller.''' + authors = [u'Mateusz Biliński '] + homepage = u'http://blog.bilinski.it' + + @log_calls('NewEventsExamplePlugin') + def init(self): + self.config_dialog = None + #self.gui_extension_points = {} + #self.config_default_values = {} + + self.events_handlers = {'raw-message-received' : + (ged.POSTCORE, + self.raw_message_received), + 'customized-message-received' : + (ged.POSTCORE, + self.customized_message_received), + 'enriched-chat-message-received' : + (ged.POSTCORE, + self.enriched_chat_message_received)} + + self.events = [CustomizedMessageReceivedEvent, + MoreCustomizedMessageReceivedEvent, + ModifyOnlyMessageReceivedEvent, + EnrichedChatMessageReceivedEvent] + + def enriched_chat_message_received(self, event_object): + pass + #print "Event '%s' occured. Event object: %s\n\n===\n"%(event_object.name, + #event_object) + + def raw_message_received(self, event_object): + pass + #print "Event '%s' occured. Event object: %s\n\n===\n"%(event_object.name, + #event_object) + + def customized_message_received(self, event_object): + pass + #print "Event '%s' occured. Event object: %s\n\n===\n"%(event_object.name, + #event_object) + + def activate(self): + pass + + def deactivate(self): + pass + +class CustomizedMessageReceivedEvent(nec.NetworkIncomingEvent): + name = 'customized-message-received' + base_network_events = ['raw-message-received'] + + def generate(self): + return True + +class MoreCustomizedMessageReceivedEvent(nec.NetworkIncomingEvent): + ''' + Shows chain of custom created events. + + This one is based on custom 'customized-messsage-received'. + ''' + name = 'more-customized-message-received' + base_network_events = ['customized-message-received'] + + def generate(self): + return True + +class ModifyOnlyMessageReceivedEvent(nec.NetworkIncomingEvent): + name = 'modify-only-message-received' + base_network_events = ['raw-message-received'] + + def generate(self): + msg_type = self.base_event.xmpp_msg.attrs['type'] + if msg_type == u'chat': + msg_text = "".join(self.base_event.xmpp_msg.kids[0].data) + self.base_event.xmpp_msg.kids[0].setData( + u'%s [MODIFIED BY CUSTOM NETWORK EVENT]'%(msg_text)) + + return False + +class EnrichedChatMessageReceivedEvent(nec.NetworkIncomingEvent): + ''' + Generates more friendly (in use by handlers) network event for + received chat message. + ''' + name = 'enriched-chat-message-received' + base_network_events = ['raw-message-received'] + + def generate(self): + msg_type = self.base_event.xmpp_msg.attrs['type'] + if msg_type == u'chat': + self.xmpp_msg = self.base_event.xmpp_msg + self.conn = self.base_event.conn + self.from_jid = helpers.get_full_jid_from_iq(self.xmpp_msg) + self.from_jid_without_resource = gajim.get_jid_without_resource(self.from_jid) + self.account = unicode(self.xmpp_msg.attrs['to']) + self.from_nickname = gajim.get_contact_name_from_jid( + self.account, + self.from_jid_without_resource) + self.msg_text = "".join(self.xmpp_msg.kids[0].data) + + return True + + return False diff --git a/plugins/snarl_notifications/plugin.py b/plugins/snarl_notifications/plugin.py index e6ef5ae18..9835cca28 100644 --- a/plugins/snarl_notifications/plugin.py +++ b/plugins/snarl_notifications/plugin.py @@ -30,7 +30,7 @@ Fancy events notifications under Windows using Snarl infrastructure. import new from pprint import pformat -import PySnarl +#import PySnarl from common import gajim from plugins import GajimPlugin @@ -62,7 +62,7 @@ PySnarl bindings are used (http://code.google.com/p/pysnarl/).''' def newMessage(self, args): event_name = "NewMessage" - data = args[0] + data = args account = data[0] jid = data[1][0] jid_without_resource = gajim.get_jid_without_resource(jid) @@ -74,15 +74,15 @@ PySnarl bindings are used (http://code.google.com/p/pysnarl/).''' elif msg_type == 'pm': nickname = gajim.get_resource_from_jid(jid) - print "Event '%s' occured. Arguments: %s\n\n===\n"%(event_name, pformat(*args)) + print "Event '%s' occured. Arguments: %s\n\n===\n"%(event_name, pformat(args)) print "Event '%s' occured. Arguments: \naccount = %s\njid = %s\nmsg = %s\nnickname = %s"%( event_name, account, jid, msg, nickname) - if PySnarl.snGetVersion() != False: - (major, minor) = PySnarl.snGetVersion() - print "Found Snarl version",str(major)+"."+str(minor),"running." - PySnarl.snShowMessage(nickname, msg[:20]+'...') - else: - print "Sorry Snarl does not appear to be running" + #if PySnarl.snGetVersion() != False: + #(major, minor) = PySnarl.snGetVersion() + #print "Found Snarl version",str(major)+"."+str(minor),"running." + #PySnarl.snShowMessage(nickname, msg[:20]+'...') + #else: + #print "Sorry Snarl does not appear to be running" \ No newline at end of file diff --git a/src/common/connection.py b/src/common/connection.py index c5cc190a2..9a13488bb 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -174,8 +174,8 @@ class Connection(ConnectionHandlers): def dispatch(self, event, data): '''always passes account name as first param''' - self.put_event((event, data)) - gajim.ged.raise_event(event, data) + #self.put_event((event, data)) + gajim.ged.raise_event(event, self.name, data) def _reconnect(self): diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index 04d633ecb..3e5de416d 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -43,6 +43,7 @@ from common import exceptions from common.commands import ConnectionCommands from common.pubsub import ConnectionPubSub from common.caps import ConnectionCaps +from common.nec import NetworkEvent from common import dbus_support if dbus_support.supported: @@ -1004,6 +1005,10 @@ class ConnectionVcard: def _IqCB(self, con, iq_obj): id = iq_obj.getID() + + gajim.nec.push_incoming_event(NetworkEvent('raw-iq-received', + conn = con, + xmpp_iq = iq_obj)) # Check if we were waiting a timeout for this id found_tim = None @@ -1601,7 +1606,11 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, def _messageCB(self, con, msg): '''Called when we receive a message''' gajim.log.debug('MessageCB') - + + gajim.nec.push_incoming_event(NetworkEvent('raw-message-received', + conn = con, + xmpp_msg = msg)) + frm = helpers.get_full_jid_from_iq(msg) # check if the message is pubsub#event @@ -1916,6 +1925,9 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, def _presenceCB(self, con, prs): '''Called when we receive a presence''' + gajim.nec.push_incoming_event(NetworkEvent('raw-pres-received', + conn = con, + xmpp_pres = prs)) ptype = prs.getType() if ptype == 'available': ptype = None diff --git a/src/common/gajim.py b/src/common/gajim.py index 3943f2bd4..a8533a1fc 100644 --- a/src/common/gajim.py +++ b/src/common/gajim.py @@ -61,8 +61,10 @@ version = config.get('version') connections = {} # 'account name': 'account (connection.Connection) instance' verbose = False ipython_window = None -plugin_manager = None + ged = None # Global Events Dispatcher +nec = None # Network Events Controller +plugin_manager = None # Plugins Manager h = logging.StreamHandler() f = logging.Formatter('%(asctime)s %(name)s: %(message)s', '%d %b %Y %H:%M:%S') diff --git a/src/common/ged.py b/src/common/ged.py index 07a7aea10..342bb13f6 100644 --- a/src/common/ged.py +++ b/src/common/ged.py @@ -24,7 +24,7 @@ Global Events Dispatcher module. :license: GPL ''' -from plugins.helpers import log +#from plugins.helpers import log PRECORE = 30 CORE = 40 @@ -39,9 +39,9 @@ class GlobalEventsDispatcher(object): if event_name in self.handlers: handlers_list = self.handlers[event_name] i = 0 - if len(handlers_list) > 0: - while priority > handlers_list[i][0]: # sorting handlers by priority - i += 1 + for i,h in enumerate(handlers_list): + if priority < h[0]: + break handlers_list.insert(i, (priority, handler)) else: @@ -52,13 +52,13 @@ class GlobalEventsDispatcher(object): try: self.handlers[event_name].remove((priority, handler)) except ValueError, error: - log.debug('''Function (%s) with priority "%s" never registered + print('''Function (%s) with priority "%s" never registered as handler of event "%s". Couldn\'t remove. Error: %s''' %(handler, priority, event_name, error)) - def raise_event(self, event_name, *args): + def raise_event(self, event_name, *args, **kwargs): #log.debug('[GED] Event: %s\nArgs: %s'%(event_name, str(args))) if event_name in self.handlers: for priority, handler in self.handlers[event_name]: - handler(args) + handler(*args, **kwargs) \ No newline at end of file diff --git a/src/common/nec.py b/src/common/nec.py new file mode 100644 index 000000000..6f433ca3c --- /dev/null +++ b/src/common/nec.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- + +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## + +''' +Network Events Controller. + +:author: Mateusz Biliński +:since: 10th August 2008 +:copyright: Copyright (2008) Mateusz Biliński +:license: GPL +''' + +from pprint import pformat + +#from plugins.helpers import log +from common import gajim + +class NetworkEventsController(object): + + def __init__(self): + self.incoming_events_generators = {} + ''' + Keys: names of events + Values: list of class objects that are subclasses + of `NetworkIncomingEvent` + ''' + + def register_incoming_event(self, event_class): + for base_event_name in event_class.base_network_events: + self.incoming_events_generators.setdefault(base_event_name,[]).append(event_class) + + def unregister_incoming_event(self, event_class): + for base_event_name in event_class.base_network_events: + if base_event_name in self.incoming_events_generators: + self.incoming_events_generators[base_event_name].remove(event_class) + + def register_outgoing_event(self, event_class): + pass + + def unregister_outgoing_event(self, event_class): + pass + + def push_incoming_event(self, event_object): + if self._generate_events_based_on_incoming_event(event_object): + gajim.ged.raise_event(event_object.name, event_object) + + def push_outgoing_event(self, event_object): + pass + + def _generate_events_based_on_incoming_event(self, event_object): + ''' + :return: True if even_object should be dispatched through Global + Events Dispatcher, False otherwise. This can be used to replace + base events with those that more data computed (easier to use + by handlers). + :note: replacing mechanism is not implemented currently, but will be + based on attribute in new network events object. + ''' + base_event_name = event_object.name + if base_event_name in self.incoming_events_generators: + for new_event_class in self.incoming_events_generators[base_event_name]: + new_event_object = new_event_class(None, base_event=event_object) + if new_event_object.generate(): + if self._generate_events_based_on_incoming_event(new_event_object): + gajim.ged.raise_event(new_event_object.name, new_event_object) + return True + +class NetworkEvent(object): + name = '' + + def __init__(self, new_name, **kwargs): + if new_name: + self.name = new_name + + self._set_kwargs_as_attributes(**kwargs) + + self.init() + + def init(self): + pass + + def _set_kwargs_as_attributes(self, **kwargs): + for k,v in kwargs.iteritems(): + setattr(self, k, v) + + def __str__(self): + return ' Attributes: %s'%(pformat(self.__dict__)) + + def __repr__(self): + return ' Attributes: %s'%(pformat(self.__dict__)) + +class NetworkIncomingEvent(NetworkEvent): + base_network_events = [] + ''' + Names of base network events that new event is going to be generated on. + ''' + + def init(self): + pass + + def generate(self): + ''' + Generates new event (sets it's attributes) based on event object. + + Base event object name is one of those in `base_network_events`. + + Reference to base event object is stored in `self.base_event` attribute. + + Note that this is a reference, so modifications to that event object + are possible before dispatching to Global Events Dispatcher. + + :return: True if generated event should be dispatched, False otherwise. + ''' + pass + + +class NetworkOutgoingEvent(NetworkEvent): + + def init(self): + pass + \ No newline at end of file diff --git a/src/gajim.py b/src/gajim.py index 19f5232c1..bb1c361c2 100755 --- a/src/gajim.py +++ b/src/gajim.py @@ -615,6 +615,7 @@ parser = optparser.OptionsParser(config_filename) import roster_window import profile_window import config +from common import ged class GlibIdleQueue(idlequeue.IdleQueue): ''' @@ -3348,6 +3349,9 @@ class Interface: # Creating Global Events Dispatcher from common import ged gajim.ged = ged.GlobalEventsDispatcher() + # Creating Network Events Controller + from common import nec + gajim.nec = nec.NetworkEventsController() self.register_core_handlers() self.register_handlers() @@ -3496,8 +3500,6 @@ class Interface: gobject.timeout_add_seconds(gajim.config.get( 'check_idle_every_foo_seconds'), self.read_sleepy) - - # Creating plugin manager import plugins gajim.plugin_manager = plugins.PluginManager() @@ -3509,7 +3511,88 @@ class Interface: This part of rewriting whole evetns handling system to use GED. Of course this can be done anywhere else. ''' - pass + handlers = { + 'ROSTER': self.handle_event_roster, + 'WARNING': self.handle_event_warning, + 'ERROR': self.handle_event_error, + 'INFORMATION': self.handle_event_information, + 'ERROR_ANSWER': self.handle_event_error_answer, + 'STATUS': self.handle_event_status, + 'NOTIFY': self.handle_event_notify, + 'MSGERROR': self.handle_event_msgerror, + 'MSGSENT': self.handle_event_msgsent, + 'MSGNOTSENT': self.handle_event_msgnotsent, + 'SUBSCRIBED': self.handle_event_subscribed, + 'UNSUBSCRIBED': self.handle_event_unsubscribed, + 'SUBSCRIBE': self.handle_event_subscribe, + 'AGENT_ERROR_INFO': self.handle_event_agent_info_error, + 'AGENT_ERROR_ITEMS': self.handle_event_agent_items_error, + 'AGENT_REMOVED': self.handle_event_agent_removed, + 'REGISTER_AGENT_INFO': self.handle_event_register_agent_info, + 'AGENT_INFO_ITEMS': self.handle_event_agent_info_items, + 'AGENT_INFO_INFO': self.handle_event_agent_info_info, + 'QUIT': self.handle_event_quit, + 'NEW_ACC_CONNECTED': self.handle_event_new_acc_connected, + 'NEW_ACC_NOT_CONNECTED': self.handle_event_new_acc_not_connected, + 'ACC_OK': self.handle_event_acc_ok, + 'ACC_NOT_OK': self.handle_event_acc_not_ok, + 'MYVCARD': self.handle_event_myvcard, + 'VCARD': self.handle_event_vcard, + 'LAST_STATUS_TIME': self.handle_event_last_status_time, + 'OS_INFO': self.handle_event_os_info, + 'GC_NOTIFY': self.handle_event_gc_notify, + 'GC_MSG': self.handle_event_gc_msg, + 'GC_SUBJECT': self.handle_event_gc_subject, + 'GC_CONFIG': self.handle_event_gc_config, + 'GC_CONFIG_CHANGE': self.handle_event_gc_config_change, + 'GC_INVITATION': self.handle_event_gc_invitation, + 'GC_AFFILIATION': self.handle_event_gc_affiliation, + 'GC_PASSWORD_REQUIRED': self.handle_event_gc_password_required, + 'BAD_PASSPHRASE': self.handle_event_bad_passphrase, + 'ROSTER_INFO': self.handle_event_roster_info, + 'BOOKMARKS': self.handle_event_bookmarks, + 'CON_TYPE': self.handle_event_con_type, + 'CONNECTION_LOST': self.handle_event_connection_lost, + 'FILE_REQUEST': self.handle_event_file_request, + 'GMAIL_NOTIFY': self.handle_event_gmail_notify, + 'FILE_REQUEST_ERROR': self.handle_event_file_request_error, + 'FILE_SEND_ERROR': self.handle_event_file_send_error, + 'STANZA_ARRIVED': self.handle_event_stanza_arrived, + 'STANZA_SENT': self.handle_event_stanza_sent, + 'HTTP_AUTH': self.handle_event_http_auth, + 'VCARD_PUBLISHED': self.handle_event_vcard_published, + 'VCARD_NOT_PUBLISHED': self.handle_event_vcard_not_published, + 'ASK_NEW_NICK': self.handle_event_ask_new_nick, + 'SIGNED_IN': self.handle_event_signed_in, + 'METACONTACTS': self.handle_event_metacontacts, + 'ATOM_ENTRY': self.handle_atom_entry, + 'FAILED_DECRYPT': self.handle_event_failed_decrypt, + 'PRIVACY_LISTS_RECEIVED': self.handle_event_privacy_lists_received, + 'PRIVACY_LIST_RECEIVED': self.handle_event_privacy_list_received, + 'PRIVACY_LISTS_ACTIVE_DEFAULT': \ + self.handle_event_privacy_lists_active_default, + 'PRIVACY_LIST_REMOVED': self.handle_event_privacy_list_removed, + 'ZC_NAME_CONFLICT': self.handle_event_zc_name_conflict, + 'PING_SENT': self.handle_event_ping_sent, + 'PING_REPLY': self.handle_event_ping_reply, + 'PING_ERROR': self.handle_event_ping_error, + 'SEARCH_FORM': self.handle_event_search_form, + 'SEARCH_RESULT': self.handle_event_search_result, + 'RESOURCE_CONFLICT': self.handle_event_resource_conflict, + 'PEP_CONFIG': self.handle_event_pep_config, + 'UNIQUE_ROOM_ID_UNSUPPORTED': \ + self.handle_event_unique_room_id_unsupported, + 'UNIQUE_ROOM_ID_SUPPORTED': self.handle_event_unique_room_id_supported, + 'SESSION_NEG': self.handle_session_negotiation, + 'GPG_PASSWORD_REQUIRED': self.handle_event_gpg_password_required, + 'SSL_ERROR': self.handle_event_ssl_error, + 'FINGERPRINT_ERROR': self.handle_event_fingerprint_error, + 'PLAIN_CONNECTION': self.handle_event_plain_connection, + 'PUBSUB_NODE_REMOVED': self.handle_event_pubsub_node_removed, + 'PUBSUB_NODE_NOT_REMOVED': self.handle_event_pubsub_node_not_removed, + } + for event_name, handler in handlers.iteritems(): + gajim.ged.register_event_handler(event_name, ged.CORE, handler) if __name__ == '__main__': def sigint_cb(num, stack): diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py index fb0651cc5..5c9edf072 100644 --- a/src/plugins/plugin.py +++ b/src/plugins/plugin.py @@ -132,6 +132,13 @@ class GajimPlugin(object): :type: {} with 2-element tuples ''' + events = [] + ''' + New network event classes to be registered in Network Events Controller. + + :type: [] of `nec.NetworkIncomingEvent` or `nec.NetworkOutgoingEvent` + subclasses. + ''' @log_calls('GajimPlugin') def __init__(self): diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py index 23d514a1c..95952a124 100644 --- a/src/plugins/pluginmanager.py +++ b/src/plugins/pluginmanager.py @@ -30,7 +30,8 @@ import os import sys import fnmatch -import common.gajim as gajim +from common import gajim +from common import nec from plugins.helpers import log, log_calls, Singleton from plugins.plugin import GajimPlugin @@ -252,6 +253,20 @@ class PluginManager(object): gajim.ged.remove_event_handler(event_name, priority, handler_function) + + def _register_network_events_in_nec(self, plugin): + for event_class in plugin.events: + if issubclass(event_class, nec.NetworkIncomingEvent): + gajim.nec.register_incoming_event(event_class) + elif issubclass(event_class, nec.NetworkOutgoingEvent): + gajim.nec.register_outgoing_event(event_class) + + def _remove_network_events_from_nec(self, plugin): + for event_class in plugin.events: + if issubclass(event_class, nec.NetworkIncomingEvent): + gajim.nec.unregister_incoming_event(event_class) + elif issubclass(event_class, nec.NetworkOutgoingEvent): + gajim.nec.unregister_outgoing_event(event_class) @log_calls('PluginManager') def activate_plugin(self, plugin): @@ -270,6 +285,7 @@ class PluginManager(object): self._add_gui_extension_points_handlers_from_plugin(plugin) self._handle_all_gui_extension_points_with_plugin(plugin) self._register_events_handlers_in_ged(plugin) + self._register_network_events_in_nec(plugin) success = True @@ -300,6 +316,7 @@ class PluginManager(object): handler(*gui_extension_point_args) self._remove_events_handler_from_ged(plugin) + self._remove_network_events_from_nec(plugin) # removing plug-in from active plug-ins list plugin.deactivate() From 8c75ed38b1eebd920fba99417ebb7ff273f2fa13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Mon, 25 Aug 2008 17:20:16 +0000 Subject: [PATCH 026/200] Google Translation plugin added (proof-of-concept) that translates incoming chat messages from English to French using Google Translate service. Plugin object that registered new event is accessible in that event now, through self.plugin. --- plugins/google_translation/__init__.py | 1 + plugins/google_translation/plugin.py | 115 +++++++++++++++++++++++++ src/plugins/pluginmanager.py | 1 + 3 files changed, 117 insertions(+) create mode 100644 plugins/google_translation/__init__.py create mode 100644 plugins/google_translation/plugin.py diff --git a/plugins/google_translation/__init__.py b/plugins/google_translation/__init__.py new file mode 100644 index 000000000..33fb27f0f --- /dev/null +++ b/plugins/google_translation/__init__.py @@ -0,0 +1 @@ +from plugin import GoogleTranslationPlugin \ No newline at end of file diff --git a/plugins/google_translation/plugin.py b/plugins/google_translation/plugin.py new file mode 100644 index 000000000..55bf0dd10 --- /dev/null +++ b/plugins/google_translation/plugin.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## +''' +Google Translation plugin. + +Translates (currently only incoming) messages using Google Translate. + +:note: consider this as proof-of-concept +:author: Mateusz Biliński +:since: 25th August 2008 +:copyright: Copyright (2008) Mateusz Biliński +:license: GPL +''' + +import re +import urllib2 +import new +from pprint import pformat + +from common import helpers +from common import gajim + +from plugins import GajimPlugin +from plugins.helpers import log_calls, log +from common import ged +from common import nec + +class GoogleTranslationPlugin(GajimPlugin): + name = u'Google Translation' + short_name = u'google_translation' + version = u'0.1' + description = u'''Translates (currently only incoming) messages using Google Translate.''' + authors = [u'Mateusz Biliński '] + homepage = u'http://blog.bilinski.it' + + @log_calls('GoogleTranslationPlugin') + def init(self): + self.config_dialog = None + #self.gui_extension_points = {} + self.config_default_values = {'from_lang' : ('en', _('Language of text to be translated')), + 'to_lang' : ('fr', _('Language to which translation will be made')), + 'user_agent' : ('Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.12) Gecko/20080213 Firefox/2.0.0.11', + _('User Agent data to be used with urllib2 when connecting to Google Translate service'))} + + #self.events_handlers = {} + + self.events = [GoogleTranslateMessageReceivedEvent] + + self.translated_text_re = \ + re.compile(r'google.language.callbacks.id100\(\'22\',{"translatedText":"(?P[^"]*)"}, 200, null, 200\)') + + def translate_text(self, text, from_lang, to_lang): + text = self.prepare_text_for_url(text) + headers = { 'User-Agent' : self.config['user_agent'] } + translation_url = 'http://www.google.com/uds/Gtranslate?callback=google.language.callbacks.id100&context=22&q=%(text)s&langpair=%(from_lang)s%%7C%(to_lang)s&key=notsupplied&v=1.0'%locals() + + request = urllib2.Request(translation_url, headers=headers) + response = urllib2.urlopen(request) + results = response.read() + + translated_text = self.translated_text_re.search(results).group('text') + + return translated_text + + def prepare_text_for_url(self, text): + ''' + Converts text so it can be used within URL as query to Google Translate. + ''' + + # There should be more replacements for plugin to work in any case: + char_replacements = { ' ' : '%20', + '+' : '%2B'} + + for char, replacement in char_replacements.iteritems(): + text = text.replace(char, replacement) + + return text + + def activate(self): + pass + + def deactivate(self): + pass + +class GoogleTranslateMessageReceivedEvent(nec.NetworkIncomingEvent): + name = 'google-translate-message-received' + base_network_events = ['raw-message-received'] + + def generate(self): + msg_type = self.base_event.xmpp_msg.attrs['type'] + if msg_type == u'chat': + msg_text = "".join(self.base_event.xmpp_msg.kids[0].data) + if msg_text: + from_lang = self.plugin.config['from_lang'] + to_lang = self.plugin.config['to_lang'] + self.base_event.xmpp_msg.kids[0].setData( + self.plugin.translate_text(msg_text, from_lang, to_lang)) + + return False # We only want to modify old event, not emit another, + # so we return False here. + diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py index 95952a124..1b1eb7cf3 100644 --- a/src/plugins/pluginmanager.py +++ b/src/plugins/pluginmanager.py @@ -256,6 +256,7 @@ class PluginManager(object): def _register_network_events_in_nec(self, plugin): for event_class in plugin.events: + setattr(event_class, 'plugin', plugin) if issubclass(event_class, nec.NetworkIncomingEvent): gajim.nec.register_incoming_event(event_class) elif issubclass(event_class, nec.NetworkOutgoingEvent): From 49b5986fe05342c47371b86568c0384d22bc9335 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Fri, 29 Aug 2008 11:54:07 +0000 Subject: [PATCH 027/200] escape < in glade when using markup --- data/glade/plugins_window.glade | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/glade/plugins_window.glade b/data/glade/plugins_window.glade index 4830f0c88..9c9ed4677 100644 --- a/data/glade/plugins_window.glade +++ b/data/glade/plugins_window.glade @@ -1,6 +1,6 @@ - + 650 @@ -57,7 +57,7 @@ True 0 - <empty> + &lt;empty&gt; True True True From fbcc091db0615a855142abd3d42254cdd028bab2 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Fri, 29 Aug 2008 11:54:34 +0000 Subject: [PATCH 028/200] sort plugins by name --- src/plugins/gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/gui.py b/src/plugins/gui.py index 3a472b01b..e2b196f43 100644 --- a/src/plugins/gui.py +++ b/src/plugins/gui.py @@ -140,7 +140,7 @@ class PluginsWindow(object): def fill_installed_plugins_model(self): pm = gajim.plugin_manager self.installed_plugins_model.clear() - self.installed_plugins_model.set_sort_column_id(0, gtk.SORT_ASCENDING) + self.installed_plugins_model.set_sort_column_id(1, gtk.SORT_ASCENDING) for plugin in pm.plugins: self.installed_plugins_model.append([plugin, From af5afb2c2d8f43269e87e6dd2f8ea551f0645eb0 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Fri, 29 Aug 2008 11:58:27 +0000 Subject: [PATCH 029/200] use set_transient_for instead of reparent. We don't reparent a window, but a widget --- src/plugins/gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/gui.py b/src/plugins/gui.py index e2b196f43..ac203ef08 100644 --- a/src/plugins/gui.py +++ b/src/plugins/gui.py @@ -206,7 +206,7 @@ class GajimPluginConfigDialog(gtk.Dialog): @log_calls('GajimPluginConfigDialog') def run(self, parent=None): - self.reparent(parent) + self.set_transient_for(parent) self.on_run() self.show_all() result = super(GajimPluginConfigDialog, self).run() From da928f9183040d9fb291b1210e62a91b303632a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Fri, 10 Apr 2009 17:03:11 +0000 Subject: [PATCH 030/200] Added PyLint config file (for sync). --- src/pylint.rc | 310 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 310 insertions(+) create mode 100644 src/pylint.rc diff --git a/src/pylint.rc b/src/pylint.rc new file mode 100644 index 000000000..7816d3659 --- /dev/null +++ b/src/pylint.rc @@ -0,0 +1,310 @@ +# lint Python modules using external checkers. +# +# This is the main checker controling the other ones and the reports +# generation. It is itself both a raw checker and an astng checker in order +# to: +# * handle message activation / deactivation at the module level +# * handle some basic but necessary stats'data (number of classes, methods...) +# +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Profiled execution. +profile=no + +# Add to the black list. It should be a base name, not a +# path. You may set this option multiple times. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# Set the cache size for astng objects. +cache-size=500 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + + +[MESSAGES CONTROL] + +# Enable only checker(s) with the given id(s). This option conflicts with the +# disable-checker option +#enable-checker= + +# Enable all checker(s) except those with the given id(s). This option +# conflicts with the enable-checker option +#disable-checker= + +# Enable all messages in the listed categories. +#enable-msg-cat= + +# Disable all messages in the listed categories. +#disable-msg-cat= + +# Enable the message(s) with the given id(s). +enable-msg=R0801 + +# Disable the message(s) with the given id(s). +disable-msg=W0312 +# disable-msg= + + +[REPORTS] + +# set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html +output-format=text + +# Include message's id in output +include-ids=yes + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells wether to display a full report or only the messages +reports=yes + +# Python expression which should return a note less than 10 (10 is the highest +# note).You have access to the variables errors warning, statement which +# respectivly contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (R0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (R0004). +comment=no + +# Enable the report(s) with the given id(s). +#enable-report= + +# Disable the report(s) with the given id(s). +#disable-report= + + +# try to find bugs in the code using type inference +# +[TYPECHECK] + +# Tells wether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamicaly set). +ignored-classes=SQLObject + +# When zope mode is activated, consider the acquired-members option to ignore +# access to some undefined attributes. +zope=no + +# List of members which are usually get through zope's acquisition mecanism and +# so shouldn't trigger E0201 when accessed (need zope=yes to be considered). +acquired-members=REQUEST,acl_users,aq_parent + + +# checks for +# * unused variables / imports +# * undefined variables +# * redefinition of variable from builtins or from an outer scope +# * use of variable before assigment +# +[VARIABLES] + +# Tells wether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching names used for dummy variables (i.e. not used). +dummy-variables-rgx=_|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + + +# checks for : +# * doc strings +# * modules / classes / functions / methods / arguments / variables name +# * number of arguments, local variables, branchs, returns and statements in +# functions, methods +# * required module attributes +# * dangerous default values as arguments +# * redefinition of function / method / class +# * uses of the global statement +# +[BASIC] + +# Required attributes for module, separated by a comma +required-attributes= + +# Regular expression which should only match functions or classes name which do +# not require a docstring +no-docstring-rgx=__.*__ + +# Regular expression which should only match correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match correct module level names +const-rgx=(([A-Z_][A-Z1-9_]*)|(__.*__))$ + +# Regular expression which should only match correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression which should only match correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct instance attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct list comprehension / +# generator expression variable names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input + + +# checks for sign of poor/misdesign: +# * number of methods, attributes, local variables... +# * size, complexity of functions, methods +# +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branchs=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + + +# checks for +# * external modules dependencies +# * relative / wildcard imports +# * cyclic imports +# * uses of deprecated modules +# +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,string,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report R0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report R0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report R0402 must +# not be disabled) +int-import-graph= + + +# checks for : +# * methods without self as first argument +# * overridden methods signature +# * access only to existant members via self +# * attributes not defined in the __init__ method +# * supported interfaces implementation +# * unreachable code +# +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + + +# checks for similarities and duplicated code. This computation may be +# memory / CPU intensive, so you should disable it if you experiments some +# problems. +# +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + + +# checks for: +# * warning notes in the code like FIXME, XXX +# * PEP 263: source code with non ascii character but no encoding declaration +# +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +# checks for : +# * unauthorized constructions +# * strict indentation +# * line length +# * use of <> instead of != +# +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=80 + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' From 369ea5544b657e85af7299cd6acaf158bf711d1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Thu, 16 Apr 2009 08:37:16 +0000 Subject: [PATCH 031/200] Moved DBus type related instructions to scope which is executed only when there's a DBus support. No NameError exceptions are thrown anymore. Makes debugging of plugins easier under WingIDE (which does not remember - at least it looks like that - the "ignore exception at this location" option when module is imported 'manually'). This could also be applied to src/remote_control.py . --- plugins/dbus_plugin/plugin.py | 1239 +++++++++++++++++---------------- 1 file changed, 620 insertions(+), 619 deletions(-) diff --git a/plugins/dbus_plugin/plugin.py b/plugins/dbus_plugin/plugin.py index b894a3859..941b74046 100644 --- a/plugins/dbus_plugin/plugin.py +++ b/plugins/dbus_plugin/plugin.py @@ -45,6 +45,626 @@ if dbus_support.supported: if dbus_support: import dbus.service import dbus.glib + # type mapping + + # in most cases it is a utf-8 string + DBUS_STRING = dbus.String + + # general type (for use in dicts, where all values should have the same type) + DBUS_BOOLEAN = dbus.Boolean + DBUS_DOUBLE = dbus.Double + DBUS_INT32 = dbus.Int32 + # dictionary with string key and binary value + DBUS_DICT_SV = lambda : dbus.Dictionary({}, signature="sv") + # dictionary with string key and value + DBUS_DICT_SS = lambda : dbus.Dictionary({}, signature="ss") + # empty type (there is no equivalent of None on D-Bus, but historically gajim + # used 0 instead) + DBUS_NONE = lambda : dbus.Int32(0) + + def get_dbus_struct(obj): + ''' recursively go through all the items and replace + them with their casted dbus equivalents + ''' + if obj is None: + return DBUS_NONE() + if isinstance(obj, (unicode, str)): + return DBUS_STRING(obj) + if isinstance(obj, int): + return DBUS_INT32(obj) + if isinstance(obj, float): + return DBUS_DOUBLE(obj) + if isinstance(obj, bool): + return DBUS_BOOLEAN(obj) + if isinstance(obj, (list, tuple)): + result = dbus.Array([get_dbus_struct(i) for i in obj], + signature='v') + if result == []: + return DBUS_NONE() + return result + if isinstance(obj, dict): + result = DBUS_DICT_SV() + for key, value in obj.items(): + result[DBUS_STRING(key)] = get_dbus_struct(value) + if result == {}: + return DBUS_NONE() + return result + # unknown type + return DBUS_NONE() + + class SignalObject(dbus.service.Object): + ''' Local object definition for /org/gajim/dbus/RemoteObject. + (This docstring is not be visible, because the clients can access only the remote object.)''' + + def __init__(self, bus_name): + self.first_show = True + self.vcard_account = None + + # register our dbus API + dbus.service.Object.__init__(self, bus_name, OBJ_PATH) + + @dbus.service.signal(INTERFACE, signature='av') + def Roster(self, account_and_data): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def AccountPresence(self, status_and_account): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def ContactPresence(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def ContactAbsence(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def ContactStatus(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def NewMessage(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def Subscribe(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def Subscribed(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def Unsubscribed(self, account_and_jid): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def NewAccount(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def VcardInfo(self, account_and_vcard): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def LastStatusTime(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def OsInfo(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def GCPresence(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def GCMessage(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def RosterInfo(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def NewGmail(self, account_and_array): + pass + + def raise_signal(self, signal, arg): + '''raise a signal, with a single argument of unspecified type + Instead of obj.raise_signal("Foo", bar), use obj.Foo(bar).''' + getattr(self, signal)(arg) + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='s') + def get_status(self, account): + '''Returns status (show to be exact) which is the global one + unless account is given''' + if not account: + # If user did not ask for account, returns the global status + return DBUS_STRING(helpers.get_global_show()) + # return show for the given account + index = gajim.connections[account].connected + return DBUS_STRING(gajim.SHOW_LIST[index]) + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='s') + def get_status_message(self, account): + '''Returns status which is the global one + unless account is given''' + if not account: + # If user did not ask for account, returns the global status + return DBUS_STRING(str(helpers.get_global_status())) + # return show for the given account + status = gajim.connections[account].status + return DBUS_STRING(status) + + def _get_account_and_contact(self, account, jid): + '''get the account (if not given) and contact instance from jid''' + connected_account = None + contact = None + accounts = gajim.contacts.get_accounts() + # if there is only one account in roster, take it as default + # if user did not ask for account + if not account and len(accounts) == 1: + account = accounts[0] + if account: + if gajim.connections[account].connected > 1: # account is connected + connected_account = account + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + else: + for account in accounts: + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + if contact and gajim.connections[account].connected > 1: + # account is connected + connected_account = account + break + if not contact: + contact = jid + + return connected_account, contact + + def _get_account_for_groupchat(self, account, room_jid): + '''get the account which is connected to groupchat (if not given) + or check if the given account is connected to the groupchat''' + connected_account = None + accounts = gajim.contacts.get_accounts() + # if there is only one account in roster, take it as default + # if user did not ask for account + if not account and len(accounts) == 1: + account = accounts[0] + if account: + if gajim.connections[account].connected > 1 and \ + room_jid in gajim.gc_connected[account] and \ + gajim.gc_connected[account][room_jid]: + # account and groupchat are connected + connected_account = account + else: + for account in accounts: + if gajim.connections[account].connected > 1 and \ + room_jid in gajim.gc_connected[account] and \ + gajim.gc_connected[account][room_jid]: + # account and groupchat are connected + connected_account = account + break + return connected_account + + @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') + def send_file(self, file_path, jid, account): + '''send file, located at 'file_path' to 'jid', using account + (optional) 'account' ''' + jid = self._get_real_jid(jid, account) + connected_account, contact = self._get_account_and_contact(account, jid) + + if connected_account: + if file_path[:7] == 'file://': + file_path=file_path[7:] + if os.path.isfile(file_path): # is it file? + gajim.interface.instances['file_transfers'].send_file( + connected_account, contact, file_path) + return DBUS_BOOLEAN(True) + return DBUS_BOOLEAN(False) + + def _send_message(self, jid, message, keyID, account, type = 'chat', + subject = None): + '''can be called from send_chat_message (default when send_message) + or send_single_message''' + if not jid or not message: + return DBUS_BOOLEAN(False) + if not keyID: + keyID = '' + + connected_account, contact = self._get_account_and_contact(account, jid) + if connected_account: + connection = gajim.connections[connected_account] + connection.send_message(jid, message, keyID, type, subject) + return DBUS_BOOLEAN(True) + return DBUS_BOOLEAN(False) + + @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='b') + def send_chat_message(self, jid, message, keyID, account): + '''Send chat 'message' to 'jid', using account (optional) 'account'. + if keyID is specified, encrypt the message with the pgp key ''' + jid = self._get_real_jid(jid, account) + return self._send_message(jid, message, keyID, account) + + @dbus.service.method(INTERFACE, in_signature='sssss', out_signature='b') + def send_single_message(self, jid, subject, message, keyID, account): + '''Send single 'message' to 'jid', using account (optional) 'account'. + if keyID is specified, encrypt the message with the pgp key ''' + jid = self._get_real_jid(jid, account) + return self._send_message(jid, message, keyID, account, type, subject) + + @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') + def send_groupchat_message(self, room_jid, message, account): + '''Send 'message' to groupchat 'room_jid', + using account (optional) 'account'.''' + if not room_jid or not message: + return DBUS_BOOLEAN(False) + connected_account = self._get_account_for_groupchat(account, room_jid) + if connected_account: + connection = gajim.connections[connected_account] + connection.send_gc_message(room_jid, message) + return DBUS_BOOLEAN(True) + return DBUS_BOOLEAN(False) + + @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b') + def open_chat(self, jid, account): + '''Shows the tabbed window for new message to 'jid', using account + (optional) 'account' ''' + if not jid: + raise MissingArgument + return DBUS_BOOLEAN(False) + jid = self._get_real_jid(jid, account) + try: + jid = helpers.parse_jid(jid) + except: + # Jid is not conform, ignore it + return DBUS_BOOLEAN(False) + + if account: + accounts = [account] + else: + accounts = gajim.connections.keys() + if len(accounts) == 1: + account = accounts[0] + connected_account = None + first_connected_acct = None + for acct in accounts: + if gajim.connections[acct].connected > 1: # account is online + contact = gajim.contacts.get_first_contact_from_jid(acct, jid) + if gajim.interface.msg_win_mgr.has_window(jid, acct): + connected_account = acct + break + # jid is in roster + elif contact: + connected_account = acct + break + # we send the message to jid not in roster, because account is + # specified, or there is only one account + elif account: + connected_account = acct + elif first_connected_acct is None: + first_connected_acct = acct + + # if jid is not a conntact, open-chat with first connected account + if connected_account is None and first_connected_acct: + connected_account = first_connected_acct + + if connected_account: + gajim.interface.new_chat_from_jid(connected_account, jid) + # preserve the 'steal focus preservation' + win = gajim.interface.msg_win_mgr.get_window(jid, + connected_account).window + if win.get_property('visible'): + win.window.focus() + return DBUS_BOOLEAN(True) + return DBUS_BOOLEAN(False) + + @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') + def change_status(self, status, message, account): + ''' change_status(status, message, account). account is optional - + if not specified status is changed for all accounts. ''' + if status not in ('offline', 'online', 'chat', + 'away', 'xa', 'dnd', 'invisible'): + return DBUS_BOOLEAN(False) + if account: + gobject.idle_add(gajim.interface.roster.send_status, account, + status, message) + else: + # account not specified, so change the status of all accounts + for acc in gajim.contacts.get_accounts(): + if not gajim.config.get_per('accounts', acc, + 'sync_with_global_status'): + continue + gobject.idle_add(gajim.interface.roster.send_status, acc, + status, message) + return DBUS_BOOLEAN(False) + + @dbus.service.method(INTERFACE, in_signature='', out_signature='') + def show_next_pending_event(self): + '''Show the window(s) with next pending event in tabbed/group chats.''' + if gajim.events.get_nb_events(): + gajim.interface.systray.handle_first_event() + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{sv}') + def contact_info(self, jid): + '''get vcard info for a contact. Return cached value of the vcard. + ''' + if not isinstance(jid, unicode): + jid = unicode(jid) + if not jid: + raise MissingArgument + return DBUS_DICT_SV() + jid = self._get_real_jid(jid) + + cached_vcard = gajim.connections.values()[0].get_cached_vcard(jid) + if cached_vcard: + return get_dbus_struct(cached_vcard) + + # return empty dict + return DBUS_DICT_SV() + + @dbus.service.method(INTERFACE, in_signature='', out_signature='as') + def list_accounts(self): + '''list register accounts''' + result = gajim.contacts.get_accounts() + result_array = dbus.Array([], signature='s') + if result and len(result) > 0: + for account in result: + result_array.append(DBUS_STRING(account)) + return result_array + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{ss}') + def account_info(self, account): + '''show info on account: resource, jid, nick, prio, message''' + result = DBUS_DICT_SS() + if gajim.connections.has_key(account): + # account is valid + con = gajim.connections[account] + index = con.connected + result['status'] = DBUS_STRING(gajim.SHOW_LIST[index]) + result['name'] = DBUS_STRING(con.name) + result['jid'] = DBUS_STRING(gajim.get_jid_from_account(con.name)) + result['message'] = DBUS_STRING(con.status) + result['priority'] = DBUS_STRING(unicode(con.priority)) + result['resource'] = DBUS_STRING(unicode(gajim.config.get_per( + 'accounts', con.name, 'resource'))) + return result + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='aa{sv}') + def list_contacts(self, account): + '''list all contacts in the roster. If the first argument is specified, + then return the contacts for the specified account''' + result = dbus.Array([], signature='aa{sv}') + accounts = gajim.contacts.get_accounts() + if len(accounts) == 0: + return result + if account: + accounts_to_search = [account] + else: + accounts_to_search = accounts + for acct in accounts_to_search: + if acct in accounts: + for jid in gajim.contacts.get_jid_list(acct): + item = self._contacts_as_dbus_structure( + gajim.contacts.get_contacts(acct, jid)) + if item: + result.append(item) + return result + + @dbus.service.method(INTERFACE, in_signature='', out_signature='') + def toggle_roster_appearance(self): + ''' shows/hides the roster window ''' + win = gajim.interface.roster.window + if win.get_property('visible'): + gobject.idle_add(win.hide) + else: + win.present() + # preserve the 'steal focus preservation' + if self._is_first(): + win.window.focus() + else: + win.window.focus(long(time())) + + @dbus.service.method(INTERFACE, in_signature='', out_signature='') + def toggle_ipython(self): + ''' shows/hides the ipython window ''' + win = gajim.ipython_window + if win: + if win.window.is_visible(): + gobject.idle_add(win.hide) + else: + win.show_all() + win.present() + else: + gajim.interface.create_ipython_window() + + @dbus.service.method(INTERFACE, in_signature='', out_signature='a{ss}') + def prefs_list(self): + prefs_dict = DBUS_DICT_SS() + def get_prefs(data, name, path, value): + if value is None: + return + key = '' + if path is not None: + for node in path: + key += node + '#' + key += name + prefs_dict[DBUS_STRING(key)] = DBUS_STRING(value[1]) + gajim.config.foreach(get_prefs) + return prefs_dict + + @dbus.service.method(INTERFACE, in_signature='', out_signature='b') + def prefs_store(self): + try: + gajim.interface.save_config() + except Exception, e: + return DBUS_BOOLEAN(False) + return DBUS_BOOLEAN(True) + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') + def prefs_del(self, key): + if not key: + return DBUS_BOOLEAN(False) + key_path = key.split('#', 2) + if len(key_path) != 3: + return DBUS_BOOLEAN(False) + if key_path[2] == '*': + gajim.config.del_per(key_path[0], key_path[1]) + else: + gajim.config.del_per(key_path[0], key_path[1], key_path[2]) + return DBUS_BOOLEAN(True) + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') + def prefs_put(self, key): + if not key: + return DBUS_BOOLEAN(False) + key_path = key.split('#', 2) + if len(key_path) < 3: + subname, value = key.split('=', 1) + gajim.config.set(subname, value) + return DBUS_BOOLEAN(True) + subname, value = key_path[2].split('=', 1) + gajim.config.set_per(key_path[0], key_path[1], subname, value) + return DBUS_BOOLEAN(True) + + @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b') + def add_contact(self, jid, account): + if account: + if account in gajim.connections and \ + gajim.connections[account].connected > 1: + # if given account is active, use it + AddNewContactWindow(account = account, jid = jid) + else: + # wrong account + return DBUS_BOOLEAN(False) + else: + # if account is not given, show account combobox + AddNewContactWindow(account = None, jid = jid) + return DBUS_BOOLEAN(True) + + @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b') + def remove_contact(self, jid, account): + jid = self._get_real_jid(jid, account) + accounts = gajim.contacts.get_accounts() + + # if there is only one account in roster, take it as default + if account: + accounts = [account] + contact_exists = False + for account in accounts: + contacts = gajim.contacts.get_contacts(account, jid) + if contacts: + gajim.connections[account].unsubscribe(jid) + for contact in contacts: + gajim.interface.roster.remove_contact(contact, account) + gajim.contacts.remove_jid(account, jid) + contact_exists = True + return DBUS_BOOLEAN(contact_exists) + + def _is_first(self): + if self.first_show: + self.first_show = False + return True + return False + + def _get_real_jid(self, jid, account = None): + '''get the real jid from the given one: removes xmpp: or get jid from nick + if account is specified, search only in this account + ''' + if account: + accounts = [account] + else: + accounts = gajim.connections.keys() + if jid.startswith('xmpp:'): + return jid[5:] # len('xmpp:') = 5 + nick_in_roster = None # Is jid a nick ? + for account in accounts: + # Does jid exists in roster of one account ? + if gajim.contacts.get_contacts(account, jid): + return jid + if not nick_in_roster: + # look in all contact if one has jid as nick + for jid_ in gajim.contacts.get_jid_list(account): + c = gajim.contacts.get_contacts(account, jid_) + if c[0].name == jid: + nick_in_roster = jid_ + break + if nick_in_roster: + # We have not found jid in roster, but we found is as a nick + return nick_in_roster + # We have not found it as jid nor as nick, probably a not in roster jid + return jid + + def _contacts_as_dbus_structure(self, contacts): + ''' get info from list of Contact objects and create dbus dict ''' + if not contacts: + return None + prim_contact = None # primary contact + for contact in contacts: + if prim_contact is None or contact.priority > prim_contact.priority: + prim_contact = contact + contact_dict = DBUS_DICT_SV() + contact_dict['name'] = DBUS_STRING(prim_contact.name) + contact_dict['show'] = DBUS_STRING(prim_contact.show) + contact_dict['jid'] = DBUS_STRING(prim_contact.jid) + if prim_contact.keyID: + keyID = None + if len(prim_contact.keyID) == 8: + keyID = prim_contact.keyID + elif len(prim_contact.keyID) == 16: + keyID = prim_contact.keyID[8:] + if keyID: + contact_dict['openpgp'] = keyID + contact_dict['resources'] = dbus.Array([], signature='(sis)') + for contact in contacts: + resource_props = dbus.Struct((DBUS_STRING(contact.resource), + dbus.Int32(contact.priority), DBUS_STRING(contact.status))) + contact_dict['resources'].append(resource_props) + contact_dict['groups'] = dbus.Array([], signature='(s)') + for group in prim_contact.groups: + contact_dict['groups'].append((DBUS_STRING(group),)) + return contact_dict + + @dbus.service.method(INTERFACE, in_signature='', out_signature='s') + def get_unread_msgs_number(self): + return DBUS_STRING(str(gajim.events.get_nb_events())) + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') + def start_chat(self, account): + if not account: + # error is shown in gajim-remote check_arguments(..) + return DBUS_BOOLEAN(False) + NewChatDialog(account) + return DBUS_BOOLEAN(True) + + @dbus.service.method(INTERFACE, in_signature='ss', out_signature='') + def send_xml(self, xml, account): + if account: + gajim.connections[account].send_stanza(xml) + else: + for acc in gajim.contacts.get_accounts(): + gajim.connections[acc].send_stanza(xml) + + @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='') + def join_room(self, room_jid, nick, password, account): + if not account: + # get the first connected account + accounts = gajim.connections.keys() + for acct in accounts: + if gajim.account_is_connected(acct): + account = acct + break + if not account: + return + if not nick: + nick = '' + gajim.interface.instances[account]['join_gc'] = \ + JoinGroupchatWindow(account, room_jid, nick) + else: + gajim.interface.join_gc_room(account, room_jid, nick, password) INTERFACE = 'org.gajim.dbusplugin.RemoteInterface' OBJ_PATH = '/org/gajim/dbusplugin/RemoteObject' @@ -115,624 +735,5 @@ Gajim core but uses new events handling system.''' return handler -# type mapping -# in most cases it is a utf-8 string -DBUS_STRING = dbus.String - -# general type (for use in dicts, where all values should have the same type) -DBUS_BOOLEAN = dbus.Boolean -DBUS_DOUBLE = dbus.Double -DBUS_INT32 = dbus.Int32 -# dictionary with string key and binary value -DBUS_DICT_SV = lambda : dbus.Dictionary({}, signature="sv") -# dictionary with string key and value -DBUS_DICT_SS = lambda : dbus.Dictionary({}, signature="ss") -# empty type (there is no equivalent of None on D-Bus, but historically gajim -# used 0 instead) -DBUS_NONE = lambda : dbus.Int32(0) - -def get_dbus_struct(obj): - ''' recursively go through all the items and replace - them with their casted dbus equivalents - ''' - if obj is None: - return DBUS_NONE() - if isinstance(obj, (unicode, str)): - return DBUS_STRING(obj) - if isinstance(obj, int): - return DBUS_INT32(obj) - if isinstance(obj, float): - return DBUS_DOUBLE(obj) - if isinstance(obj, bool): - return DBUS_BOOLEAN(obj) - if isinstance(obj, (list, tuple)): - result = dbus.Array([get_dbus_struct(i) for i in obj], - signature='v') - if result == []: - return DBUS_NONE() - return result - if isinstance(obj, dict): - result = DBUS_DICT_SV() - for key, value in obj.items(): - result[DBUS_STRING(key)] = get_dbus_struct(value) - if result == {}: - return DBUS_NONE() - return result - # unknown type - return DBUS_NONE() - -class SignalObject(dbus.service.Object): - ''' Local object definition for /org/gajim/dbus/RemoteObject. - (This docstring is not be visible, because the clients can access only the remote object.)''' - - def __init__(self, bus_name): - self.first_show = True - self.vcard_account = None - - # register our dbus API - dbus.service.Object.__init__(self, bus_name, OBJ_PATH) - - @dbus.service.signal(INTERFACE, signature='av') - def Roster(self, account_and_data): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def AccountPresence(self, status_and_account): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def ContactPresence(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def ContactAbsence(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def ContactStatus(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def NewMessage(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def Subscribe(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def Subscribed(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def Unsubscribed(self, account_and_jid): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def NewAccount(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def VcardInfo(self, account_and_vcard): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def LastStatusTime(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def OsInfo(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def GCPresence(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def GCMessage(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def RosterInfo(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def NewGmail(self, account_and_array): - pass - - def raise_signal(self, signal, arg): - '''raise a signal, with a single argument of unspecified type - Instead of obj.raise_signal("Foo", bar), use obj.Foo(bar).''' - getattr(self, signal)(arg) - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='s') - def get_status(self, account): - '''Returns status (show to be exact) which is the global one - unless account is given''' - if not account: - # If user did not ask for account, returns the global status - return DBUS_STRING(helpers.get_global_show()) - # return show for the given account - index = gajim.connections[account].connected - return DBUS_STRING(gajim.SHOW_LIST[index]) - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='s') - def get_status_message(self, account): - '''Returns status which is the global one - unless account is given''' - if not account: - # If user did not ask for account, returns the global status - return DBUS_STRING(str(helpers.get_global_status())) - # return show for the given account - status = gajim.connections[account].status - return DBUS_STRING(status) - - def _get_account_and_contact(self, account, jid): - '''get the account (if not given) and contact instance from jid''' - connected_account = None - contact = None - accounts = gajim.contacts.get_accounts() - # if there is only one account in roster, take it as default - # if user did not ask for account - if not account and len(accounts) == 1: - account = accounts[0] - if account: - if gajim.connections[account].connected > 1: # account is connected - connected_account = account - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - else: - for account in accounts: - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - if contact and gajim.connections[account].connected > 1: - # account is connected - connected_account = account - break - if not contact: - contact = jid - - return connected_account, contact - - def _get_account_for_groupchat(self, account, room_jid): - '''get the account which is connected to groupchat (if not given) - or check if the given account is connected to the groupchat''' - connected_account = None - accounts = gajim.contacts.get_accounts() - # if there is only one account in roster, take it as default - # if user did not ask for account - if not account and len(accounts) == 1: - account = accounts[0] - if account: - if gajim.connections[account].connected > 1 and \ - room_jid in gajim.gc_connected[account] and \ - gajim.gc_connected[account][room_jid]: - # account and groupchat are connected - connected_account = account - else: - for account in accounts: - if gajim.connections[account].connected > 1 and \ - room_jid in gajim.gc_connected[account] and \ - gajim.gc_connected[account][room_jid]: - # account and groupchat are connected - connected_account = account - break - return connected_account - - @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') - def send_file(self, file_path, jid, account): - '''send file, located at 'file_path' to 'jid', using account - (optional) 'account' ''' - jid = self._get_real_jid(jid, account) - connected_account, contact = self._get_account_and_contact(account, jid) - - if connected_account: - if file_path[:7] == 'file://': - file_path=file_path[7:] - if os.path.isfile(file_path): # is it file? - gajim.interface.instances['file_transfers'].send_file( - connected_account, contact, file_path) - return DBUS_BOOLEAN(True) - return DBUS_BOOLEAN(False) - - def _send_message(self, jid, message, keyID, account, type = 'chat', - subject = None): - '''can be called from send_chat_message (default when send_message) - or send_single_message''' - if not jid or not message: - return DBUS_BOOLEAN(False) - if not keyID: - keyID = '' - - connected_account, contact = self._get_account_and_contact(account, jid) - if connected_account: - connection = gajim.connections[connected_account] - connection.send_message(jid, message, keyID, type, subject) - return DBUS_BOOLEAN(True) - return DBUS_BOOLEAN(False) - - @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='b') - def send_chat_message(self, jid, message, keyID, account): - '''Send chat 'message' to 'jid', using account (optional) 'account'. - if keyID is specified, encrypt the message with the pgp key ''' - jid = self._get_real_jid(jid, account) - return self._send_message(jid, message, keyID, account) - - @dbus.service.method(INTERFACE, in_signature='sssss', out_signature='b') - def send_single_message(self, jid, subject, message, keyID, account): - '''Send single 'message' to 'jid', using account (optional) 'account'. - if keyID is specified, encrypt the message with the pgp key ''' - jid = self._get_real_jid(jid, account) - return self._send_message(jid, message, keyID, account, type, subject) - - @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') - def send_groupchat_message(self, room_jid, message, account): - '''Send 'message' to groupchat 'room_jid', - using account (optional) 'account'.''' - if not room_jid or not message: - return DBUS_BOOLEAN(False) - connected_account = self._get_account_for_groupchat(account, room_jid) - if connected_account: - connection = gajim.connections[connected_account] - connection.send_gc_message(room_jid, message) - return DBUS_BOOLEAN(True) - return DBUS_BOOLEAN(False) - - @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b') - def open_chat(self, jid, account): - '''Shows the tabbed window for new message to 'jid', using account - (optional) 'account' ''' - if not jid: - raise MissingArgument - return DBUS_BOOLEAN(False) - jid = self._get_real_jid(jid, account) - try: - jid = helpers.parse_jid(jid) - except: - # Jid is not conform, ignore it - return DBUS_BOOLEAN(False) - - if account: - accounts = [account] - else: - accounts = gajim.connections.keys() - if len(accounts) == 1: - account = accounts[0] - connected_account = None - first_connected_acct = None - for acct in accounts: - if gajim.connections[acct].connected > 1: # account is online - contact = gajim.contacts.get_first_contact_from_jid(acct, jid) - if gajim.interface.msg_win_mgr.has_window(jid, acct): - connected_account = acct - break - # jid is in roster - elif contact: - connected_account = acct - break - # we send the message to jid not in roster, because account is - # specified, or there is only one account - elif account: - connected_account = acct - elif first_connected_acct is None: - first_connected_acct = acct - - # if jid is not a conntact, open-chat with first connected account - if connected_account is None and first_connected_acct: - connected_account = first_connected_acct - - if connected_account: - gajim.interface.new_chat_from_jid(connected_account, jid) - # preserve the 'steal focus preservation' - win = gajim.interface.msg_win_mgr.get_window(jid, - connected_account).window - if win.get_property('visible'): - win.window.focus() - return DBUS_BOOLEAN(True) - return DBUS_BOOLEAN(False) - - @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') - def change_status(self, status, message, account): - ''' change_status(status, message, account). account is optional - - if not specified status is changed for all accounts. ''' - if status not in ('offline', 'online', 'chat', - 'away', 'xa', 'dnd', 'invisible'): - return DBUS_BOOLEAN(False) - if account: - gobject.idle_add(gajim.interface.roster.send_status, account, - status, message) - else: - # account not specified, so change the status of all accounts - for acc in gajim.contacts.get_accounts(): - if not gajim.config.get_per('accounts', acc, - 'sync_with_global_status'): - continue - gobject.idle_add(gajim.interface.roster.send_status, acc, - status, message) - return DBUS_BOOLEAN(False) - - @dbus.service.method(INTERFACE, in_signature='', out_signature='') - def show_next_pending_event(self): - '''Show the window(s) with next pending event in tabbed/group chats.''' - if gajim.events.get_nb_events(): - gajim.interface.systray.handle_first_event() - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{sv}') - def contact_info(self, jid): - '''get vcard info for a contact. Return cached value of the vcard. - ''' - if not isinstance(jid, unicode): - jid = unicode(jid) - if not jid: - raise MissingArgument - return DBUS_DICT_SV() - jid = self._get_real_jid(jid) - - cached_vcard = gajim.connections.values()[0].get_cached_vcard(jid) - if cached_vcard: - return get_dbus_struct(cached_vcard) - - # return empty dict - return DBUS_DICT_SV() - - @dbus.service.method(INTERFACE, in_signature='', out_signature='as') - def list_accounts(self): - '''list register accounts''' - result = gajim.contacts.get_accounts() - result_array = dbus.Array([], signature='s') - if result and len(result) > 0: - for account in result: - result_array.append(DBUS_STRING(account)) - return result_array - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{ss}') - def account_info(self, account): - '''show info on account: resource, jid, nick, prio, message''' - result = DBUS_DICT_SS() - if gajim.connections.has_key(account): - # account is valid - con = gajim.connections[account] - index = con.connected - result['status'] = DBUS_STRING(gajim.SHOW_LIST[index]) - result['name'] = DBUS_STRING(con.name) - result['jid'] = DBUS_STRING(gajim.get_jid_from_account(con.name)) - result['message'] = DBUS_STRING(con.status) - result['priority'] = DBUS_STRING(unicode(con.priority)) - result['resource'] = DBUS_STRING(unicode(gajim.config.get_per( - 'accounts', con.name, 'resource'))) - return result - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='aa{sv}') - def list_contacts(self, account): - '''list all contacts in the roster. If the first argument is specified, - then return the contacts for the specified account''' - result = dbus.Array([], signature='aa{sv}') - accounts = gajim.contacts.get_accounts() - if len(accounts) == 0: - return result - if account: - accounts_to_search = [account] - else: - accounts_to_search = accounts - for acct in accounts_to_search: - if acct in accounts: - for jid in gajim.contacts.get_jid_list(acct): - item = self._contacts_as_dbus_structure( - gajim.contacts.get_contacts(acct, jid)) - if item: - result.append(item) - return result - - @dbus.service.method(INTERFACE, in_signature='', out_signature='') - def toggle_roster_appearance(self): - ''' shows/hides the roster window ''' - win = gajim.interface.roster.window - if win.get_property('visible'): - gobject.idle_add(win.hide) - else: - win.present() - # preserve the 'steal focus preservation' - if self._is_first(): - win.window.focus() - else: - win.window.focus(long(time())) - - @dbus.service.method(INTERFACE, in_signature='', out_signature='') - def toggle_ipython(self): - ''' shows/hides the ipython window ''' - win = gajim.ipython_window - if win: - if win.window.is_visible(): - gobject.idle_add(win.hide) - else: - win.show_all() - win.present() - else: - gajim.interface.create_ipython_window() - - @dbus.service.method(INTERFACE, in_signature='', out_signature='a{ss}') - def prefs_list(self): - prefs_dict = DBUS_DICT_SS() - def get_prefs(data, name, path, value): - if value is None: - return - key = '' - if path is not None: - for node in path: - key += node + '#' - key += name - prefs_dict[DBUS_STRING(key)] = DBUS_STRING(value[1]) - gajim.config.foreach(get_prefs) - return prefs_dict - - @dbus.service.method(INTERFACE, in_signature='', out_signature='b') - def prefs_store(self): - try: - gajim.interface.save_config() - except Exception, e: - return DBUS_BOOLEAN(False) - return DBUS_BOOLEAN(True) - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') - def prefs_del(self, key): - if not key: - return DBUS_BOOLEAN(False) - key_path = key.split('#', 2) - if len(key_path) != 3: - return DBUS_BOOLEAN(False) - if key_path[2] == '*': - gajim.config.del_per(key_path[0], key_path[1]) - else: - gajim.config.del_per(key_path[0], key_path[1], key_path[2]) - return DBUS_BOOLEAN(True) - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') - def prefs_put(self, key): - if not key: - return DBUS_BOOLEAN(False) - key_path = key.split('#', 2) - if len(key_path) < 3: - subname, value = key.split('=', 1) - gajim.config.set(subname, value) - return DBUS_BOOLEAN(True) - subname, value = key_path[2].split('=', 1) - gajim.config.set_per(key_path[0], key_path[1], subname, value) - return DBUS_BOOLEAN(True) - - @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b') - def add_contact(self, jid, account): - if account: - if account in gajim.connections and \ - gajim.connections[account].connected > 1: - # if given account is active, use it - AddNewContactWindow(account = account, jid = jid) - else: - # wrong account - return DBUS_BOOLEAN(False) - else: - # if account is not given, show account combobox - AddNewContactWindow(account = None, jid = jid) - return DBUS_BOOLEAN(True) - - @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b') - def remove_contact(self, jid, account): - jid = self._get_real_jid(jid, account) - accounts = gajim.contacts.get_accounts() - - # if there is only one account in roster, take it as default - if account: - accounts = [account] - contact_exists = False - for account in accounts: - contacts = gajim.contacts.get_contacts(account, jid) - if contacts: - gajim.connections[account].unsubscribe(jid) - for contact in contacts: - gajim.interface.roster.remove_contact(contact, account) - gajim.contacts.remove_jid(account, jid) - contact_exists = True - return DBUS_BOOLEAN(contact_exists) - - def _is_first(self): - if self.first_show: - self.first_show = False - return True - return False - - def _get_real_jid(self, jid, account = None): - '''get the real jid from the given one: removes xmpp: or get jid from nick - if account is specified, search only in this account - ''' - if account: - accounts = [account] - else: - accounts = gajim.connections.keys() - if jid.startswith('xmpp:'): - return jid[5:] # len('xmpp:') = 5 - nick_in_roster = None # Is jid a nick ? - for account in accounts: - # Does jid exists in roster of one account ? - if gajim.contacts.get_contacts(account, jid): - return jid - if not nick_in_roster: - # look in all contact if one has jid as nick - for jid_ in gajim.contacts.get_jid_list(account): - c = gajim.contacts.get_contacts(account, jid_) - if c[0].name == jid: - nick_in_roster = jid_ - break - if nick_in_roster: - # We have not found jid in roster, but we found is as a nick - return nick_in_roster - # We have not found it as jid nor as nick, probably a not in roster jid - return jid - - def _contacts_as_dbus_structure(self, contacts): - ''' get info from list of Contact objects and create dbus dict ''' - if not contacts: - return None - prim_contact = None # primary contact - for contact in contacts: - if prim_contact is None or contact.priority > prim_contact.priority: - prim_contact = contact - contact_dict = DBUS_DICT_SV() - contact_dict['name'] = DBUS_STRING(prim_contact.name) - contact_dict['show'] = DBUS_STRING(prim_contact.show) - contact_dict['jid'] = DBUS_STRING(prim_contact.jid) - if prim_contact.keyID: - keyID = None - if len(prim_contact.keyID) == 8: - keyID = prim_contact.keyID - elif len(prim_contact.keyID) == 16: - keyID = prim_contact.keyID[8:] - if keyID: - contact_dict['openpgp'] = keyID - contact_dict['resources'] = dbus.Array([], signature='(sis)') - for contact in contacts: - resource_props = dbus.Struct((DBUS_STRING(contact.resource), - dbus.Int32(contact.priority), DBUS_STRING(contact.status))) - contact_dict['resources'].append(resource_props) - contact_dict['groups'] = dbus.Array([], signature='(s)') - for group in prim_contact.groups: - contact_dict['groups'].append((DBUS_STRING(group),)) - return contact_dict - - @dbus.service.method(INTERFACE, in_signature='', out_signature='s') - def get_unread_msgs_number(self): - return DBUS_STRING(str(gajim.events.get_nb_events())) - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') - def start_chat(self, account): - if not account: - # error is shown in gajim-remote check_arguments(..) - return DBUS_BOOLEAN(False) - NewChatDialog(account) - return DBUS_BOOLEAN(True) - - @dbus.service.method(INTERFACE, in_signature='ss', out_signature='') - def send_xml(self, xml, account): - if account: - gajim.connections[account].send_stanza(xml) - else: - for acc in gajim.contacts.get_accounts(): - gajim.connections[acc].send_stanza(xml) - - @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='') - def join_room(self, room_jid, nick, password, account): - if not account: - # get the first connected account - accounts = gajim.connections.keys() - for acct in accounts: - if gajim.account_is_connected(acct): - account = acct - break - if not account: - return - if not nick: - nick = '' - gajim.interface.instances[account]['join_gc'] = \ - JoinGroupchatWindow(account, room_jid, nick) - else: - gajim.interface.join_gc_room(account, room_jid, nick, password) \ No newline at end of file From 06b6809b29bc65d19944eb79c3d25f6e5e1be791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Tue, 28 Apr 2009 15:14:38 +0000 Subject: [PATCH 032/200] Added basic exception classes for handling plugins-related errors. --- src/plugins/plugin.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py index 5c9edf072..b30d7c7e7 100644 --- a/src/plugins/plugin.py +++ b/src/plugins/plugin.py @@ -226,3 +226,9 @@ class GajimPluginConfig(UserDict.DictMixin): @log_calls('GajimPluginConfig') def load(self): self.data = shelve.open(self.FILE_PATH) + +class GajimPluginException(Exception): + pass + +class GajimPluginInitError(GajimPluginException): + pass From e8d704e96bb554bad8a3f7a1483801d613c1a73e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Tue, 2 Jun 2009 19:59:33 +0200 Subject: [PATCH 033/200] Moved DBUS interface related variables. --- plugins/dbus_plugin/plugin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/dbus_plugin/plugin.py b/plugins/dbus_plugin/plugin.py index 941b74046..d8c022d27 100644 --- a/plugins/dbus_plugin/plugin.py +++ b/plugins/dbus_plugin/plugin.py @@ -43,6 +43,10 @@ from common import dbus_support if dbus_support.supported: import dbus if dbus_support: + INTERFACE = 'org.gajim.dbusplugin.RemoteInterface' + OBJ_PATH = '/org/gajim/dbusplugin/RemoteObject' + SERVICE = 'org.gajim.dbusplugin' + import dbus.service import dbus.glib # type mapping @@ -666,10 +670,6 @@ if dbus_support.supported: else: gajim.interface.join_gc_room(account, room_jid, nick, password) -INTERFACE = 'org.gajim.dbusplugin.RemoteInterface' -OBJ_PATH = '/org/gajim/dbusplugin/RemoteObject' -SERVICE = 'org.gajim.dbusplugin' - from common import gajim from common import helpers from time import time From 6fecd84b16d7f5a4ae9303c757f60651dbbc829a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Mon, 15 Jun 2009 07:56:08 +0200 Subject: [PATCH 034/200] Fixed log_calls decorator arguments in a few plugins. Fixed Acronyms Expander plugin (not working due to bad auto-merge). Added few comments. --- plugins/dbus_plugin/plugin.py | 1 + plugins/events_dump/plugin.py | 5 +++-- plugins/google_translation/plugin.py | 6 ++++-- plugins/new_events_example/plugin.py | 5 +++-- src/chat_control.py | 9 +++++---- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/plugins/dbus_plugin/plugin.py b/plugins/dbus_plugin/plugin.py index b497ce45c..ac26c3b1d 100644 --- a/plugins/dbus_plugin/plugin.py +++ b/plugins/dbus_plugin/plugin.py @@ -718,6 +718,7 @@ Gajim core but uses new events handling system.''' self.signal_object.remove_from_connection() self.signal_object = None + @log_calls('DBusPlugin') def _set_handling_methods(self): for event_name in self.events_names: setattr(self, event_name, diff --git a/plugins/events_dump/plugin.py b/plugins/events_dump/plugin.py index 4e284aab6..9aa4565b1 100644 --- a/plugins/events_dump/plugin.py +++ b/plugins/events_dump/plugin.py @@ -103,14 +103,15 @@ class EventsDumpPlugin(GajimPlugin): self.events_handlers = {} self._set_handling_methods() - @log_calls('DBusPlugin') + @log_calls('EventsDumpPlugin') def activate(self): pass - @log_calls('DBusPlugin') + @log_calls('EventsDumpPlugin') def deactivate(self): pass + @log_calls('EventsDumpPlugin') def _set_handling_methods(self): for event_name in self.events_names: setattr(self, event_name, diff --git a/plugins/google_translation/plugin.py b/plugins/google_translation/plugin.py index faaf56700..3c5d3a5c1 100644 --- a/plugins/google_translation/plugin.py +++ b/plugins/google_translation/plugin.py @@ -63,6 +63,7 @@ class GoogleTranslationPlugin(GajimPlugin): self.translated_text_re = \ re.compile(r'google.language.callbacks.id100\(\'22\',{"translatedText":"(?P[^"]*)"}, 200, null, 200\)') + @log_calls('GoogleTranslationPlugin') def translate_text(self, text, from_lang, to_lang): text = self.prepare_text_for_url(text) headers = { 'User-Agent' : self.config['user_agent'] } @@ -76,6 +77,7 @@ class GoogleTranslationPlugin(GajimPlugin): return translated_text + @log_calls('GoogleTranslationPlugin') def prepare_text_for_url(self, text): ''' Converts text so it can be used within URL as query to Google Translate. @@ -90,11 +92,11 @@ class GoogleTranslationPlugin(GajimPlugin): return text - @log_calls('DBusPlugin') + @log_calls('GoogleTranslationPlugin') def activate(self): pass - @log_calls('DBusPlugin') + @log_calls('GoogleTranslationPlugin') def deactivate(self): pass diff --git a/plugins/new_events_example/plugin.py b/plugins/new_events_example/plugin.py index 496aef04d..85c97bc21 100644 --- a/plugins/new_events_example/plugin.py +++ b/plugins/new_events_example/plugin.py @@ -81,11 +81,11 @@ class NewEventsExamplePlugin(GajimPlugin): #print "Event '%s' occured. Event object: %s\n\n===\n"%(event_object.name, #event_object - @log_calls('DBusPlugin') + @log_calls('NewEventsExamplePlugin') def activate(self): pass - @log_calls('DBusPlugin') + @log_calls('NewEventsExamplePlugin') def deactivate(self): pass @@ -137,6 +137,7 @@ class EnrichedChatMessageReceivedEvent(nec.NetworkIncomingEvent): self.from_jid = helpers.get_full_jid_from_iq(self.xmpp_msg) self.from_jid_without_resource = gajim.get_jid_without_resource(self.from_jid) self.account = unicode(self.xmpp_msg.attrs['to']) + # FIXME: KeyError: u'vardo@jabber.org/Gajim' self.from_nickname = gajim.get_contact_name_from_jid( self.account, self.from_jid_without_resource) diff --git a/src/chat_control.py b/src/chat_control.py index 2fd525fbc..746e2e27d 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -292,6 +292,10 @@ class ChatControlBase(MessageControl): self.smooth = True self.msg_textview.grab_focus() + + # PluginSystem: adding GUI extension point for ChatControlBase + # instance object (also subclasses, eg. ChatControl or GroupchatControl) + gajim.plugin_manager.gui_extension_point('chat_control_base', self) def set_speller(self): try: @@ -334,10 +338,7 @@ class ChatControlBase(MessageControl): menu.reorder_child(item, i) i += 1 menu.show_all() - - # PluginSystem: adding GUI extension point for ChatControlBase - # instance object (also subclasses, eg. ChatControl or GroupchatControl) - gajim.plugin_manager.gui_extension_point('chat_control_base', self) + def shutdown(self): # PluginSystem: removing GUI extension points connected with ChatControlBase From e8c3ba51b2e26d0b346587892c3f78688b8cb2f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Sun, 21 Jun 2009 23:12:30 +0200 Subject: [PATCH 035/200] Fixed bug related to account name in NewEventsExample name. Modified way to acquire message type. --- plugins/google_translation/plugin.py | 12 ++++++------ plugins/new_events_example/plugin.py | 7 +++---- src/common/connection_handlers.py | 3 ++- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/plugins/google_translation/plugin.py b/plugins/google_translation/plugin.py index 3c5d3a5c1..b2f920e37 100644 --- a/plugins/google_translation/plugin.py +++ b/plugins/google_translation/plugin.py @@ -51,10 +51,10 @@ class GoogleTranslationPlugin(GajimPlugin): def init(self): self.config_dialog = None #self.gui_extension_points = {} - self.config_default_values = {'from_lang' : ('en', _('Language of text to be translated')), - 'to_lang' : ('fr', _('Language to which translation will be made')), - 'user_agent' : ('Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.12) Gecko/20080213 Firefox/2.0.0.11', - _('User Agent data to be used with urllib2 when connecting to Google Translate service'))} + self.config_default_values = {'from_lang' : (u'en', _(u'Language of text to be translated')), + 'to_lang' : (u'fr', _(u'Language to which translation will be made')), + 'user_agent' : (u'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.12) Gecko/20080213 Firefox/2.0.0.11', + _(u'User Agent data to be used with urllib2 when connecting to Google Translate service'))} #self.events_handlers = {} @@ -67,7 +67,7 @@ class GoogleTranslationPlugin(GajimPlugin): def translate_text(self, text, from_lang, to_lang): text = self.prepare_text_for_url(text) headers = { 'User-Agent' : self.config['user_agent'] } - translation_url = 'http://www.google.com/uds/Gtranslate?callback=google.language.callbacks.id100&context=22&q=%(text)s&langpair=%(from_lang)s%%7C%(to_lang)s&key=notsupplied&v=1.0'%locals() + translation_url = u'http://www.google.com/uds/Gtranslate?callback=google.language.callbacks.id100&context=22&q=%(text)s&langpair=%(from_lang)s%%7C%(to_lang)s&key=notsupplied&v=1.0'%locals() request = urllib2.Request(translation_url, headers=headers) response = urllib2.urlopen(request) @@ -105,7 +105,7 @@ class GoogleTranslateMessageReceivedEvent(nec.NetworkIncomingEvent): base_network_events = ['raw-message-received'] def generate(self): - msg_type = self.base_event.xmpp_msg.attrs['type'] + msg_type = self.base_event.xmpp_msg.attrs.get('type', None) if msg_type == u'chat': msg_text = "".join(self.base_event.xmpp_msg.kids[0].data) if msg_text: diff --git a/plugins/new_events_example/plugin.py b/plugins/new_events_example/plugin.py index 85c97bc21..29740d1a4 100644 --- a/plugins/new_events_example/plugin.py +++ b/plugins/new_events_example/plugin.py @@ -113,7 +113,7 @@ class ModifyOnlyMessageReceivedEvent(nec.NetworkIncomingEvent): base_network_events = ['raw-message-received'] def generate(self): - msg_type = self.base_event.xmpp_msg.attrs['type'] + msg_type = self.base_event.xmpp_msg.attrs.get('type', None) if msg_type == u'chat': msg_text = "".join(self.base_event.xmpp_msg.kids[0].data) self.base_event.xmpp_msg.kids[0].setData( @@ -130,14 +130,13 @@ class EnrichedChatMessageReceivedEvent(nec.NetworkIncomingEvent): base_network_events = ['raw-message-received'] def generate(self): - msg_type = self.base_event.xmpp_msg.attrs['type'] + msg_type = self.base_event.xmpp_msg.attrs.get('type', None) if msg_type == u'chat': self.xmpp_msg = self.base_event.xmpp_msg self.conn = self.base_event.conn self.from_jid = helpers.get_full_jid_from_iq(self.xmpp_msg) self.from_jid_without_resource = gajim.get_jid_without_resource(self.from_jid) - self.account = unicode(self.xmpp_msg.attrs['to']) - # FIXME: KeyError: u'vardo@jabber.org/Gajim' + self.account = self.base_event.account self.from_nickname = gajim.get_contact_name_from_jid( self.account, self.from_jid_without_resource) diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index ae7b9d4d7..9167e5332 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -1782,7 +1782,8 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, gajim.nec.push_incoming_event(NetworkEvent('raw-message-received', conn = con, - xmpp_msg = msg)) + xmpp_msg = msg, + account = self.name)) # check if the message is pubsub#event if msg.getTag('event') is not None: From b073dfe848c601fbe6ab4229f91833eb5fdca588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=ABl=20Verrier?= Date: Sun, 12 Jul 2009 16:32:01 +0200 Subject: [PATCH 037/200] * first draft of ArchivingPreferences (for XEP-0136) --- src/common/message_archiving.py | 45 +++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/common/message_archiving.py diff --git a/src/common/message_archiving.py b/src/common/message_archiving.py new file mode 100644 index 000000000..ffde9eee3 --- /dev/null +++ b/src/common/message_archiving.py @@ -0,0 +1,45 @@ +# -*- coding:utf-8 -*- +## src/common/message_archiving.py +## +## Copyright (C) 2009 Anaël Verrier +## +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## + + +class ArchivingPreferences: + def __init__(self): + self.auto = None + self.method_auto = None + self.method_local = None + self.method_manual = None + self.default = None + self.items = {} + + def set(self, auto, method_auto, method_local, method_manual, default): + self.auto = auto + self.method_auto = method_auto + self.method_local = method_local + self.method_manual = method_manual + self.default = default + + def set_default(self, expire, otr, save): + self.default = {'expire': expire, 'otr': otr, 'save': save} + + def append_or_update_item(self, jid, expire, otr, save): + self.items[jid] = {'expire': expire, 'otr': otr, 'save': save} + + def remove_item(self, jid): + del self.items[jid] From dde1b3410992722e678e8aea319f3ab90a219ff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=ABl=20Verrier?= Date: Mon, 13 Jul 2009 01:03:43 +0200 Subject: [PATCH 038/200] * added some NS_* in xmpp/protocol.py (for XEP-0136) --- src/common/xmpp/protocol.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py index 7c8b9b716..b6967408d 100644 --- a/src/common/xmpp/protocol.py +++ b/src/common/xmpp/protocol.py @@ -27,6 +27,11 @@ NS_ADDRESS ='http://jabber.org/protocol/address' # XEP-0033 NS_AGENTS ='jabber:iq:agents' NS_AMP ='http://jabber.org/protocol/amp' NS_AMP_ERRORS =NS_AMP+'#errors' +NS_ARCHIVE ='urn:xmpp:archive' +NS_ARCHIVE_AUTO =NS_ARCHIVE+':auto' +NS_ARCHIVE_MANAGE =NS_ARCHIVE+':manage' +NS_ARCHIVE_MANUAL =NS_ARCHIVE+':manual' +NS_ARCHIVE_PREF =NS_ARCHIVE+':pref' NS_AUTH ='jabber:iq:auth' NS_AVATAR ='http://www.xmpp.org/extensions/xep-0084.html#ns-metadata' NS_BIND ='urn:ietf:params:xml:ns:xmpp-bind' From 649aa1ed55e3a0120c58fc4a47a3009aabd14b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=ABl=20Verrier?= Date: Mon, 13 Jul 2009 05:07:38 +0200 Subject: [PATCH 039/200] * second draft of ArchivingPreferences --- src/common/message_archiving.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/common/message_archiving.py b/src/common/message_archiving.py index ffde9eee3..b11b7e779 100644 --- a/src/common/message_archiving.py +++ b/src/common/message_archiving.py @@ -21,22 +21,21 @@ class ArchivingPreferences: def __init__(self): - self.auto = None + self.auto_save = None self.method_auto = None self.method_local = None self.method_manual = None self.default = None self.items = {} - def set(self, auto, method_auto, method_local, method_manual, default): - self.auto = auto + def set(self, auto_save, method_auto, method_local, method_manual): + self.auto_save = auto_save self.method_auto = method_auto self.method_local = method_local self.method_manual = method_manual - self.default = default - def set_default(self, expire, otr, save): - self.default = {'expire': expire, 'otr': otr, 'save': save} + def set_default(self, otr, save, expire=None, unset=False): + self.default = {'expire': expire, 'otr': otr, 'save': save, 'unset': unset} def append_or_update_item(self, jid, expire, otr, save): self.items[jid] = {'expire': expire, 'otr': otr, 'save': save} From 66e7ea153e975a0bf2a3508f09bb06aaf68a5eb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=ABl=20Verrier?= Date: Wed, 15 Jul 2009 15:13:12 +0200 Subject: [PATCH 040/200] * now we handle result for archiving preferences request and also all changes that come from server --- src/common/connection.py | 7 +++++ src/common/connection_handlers.py | 5 ++- src/common/message_archiving.py | 52 ++++++++++++++++++++++++++----- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/src/common/connection.py b/src/common/connection.py index d09846c49..d134b1818 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -1032,6 +1032,13 @@ class Connection(ConnectionHandlers): self.connection.set_send_timeout(self.keepalives, self.send_keepalive) self.connection.set_send_timeout2(self.pingalives, self.sendPing) self.connection.onreceive(None) + + # Request message archiving preferences + iq = common.xmpp.Iq('get') + iq.setTag('pref', namespace=common.xmpp.NS_ARCHIVE) + self.connection.send(iq) + + # Request privacy list iq = common.xmpp.Iq('get', common.xmpp.NS_PRIVACY, xmlns = '') id_ = self.connection.getAnID() iq.setID(id_) diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index 0c3ab981f..ed20e0618 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -51,6 +51,7 @@ from common import exceptions from common.commands import ConnectionCommands from common.pubsub import ConnectionPubSub from common.caps import ConnectionCaps +from common.message_archiving import ConnectionArchive from common import dbus_support if dbus_support.supported: @@ -1433,8 +1434,9 @@ sent a message to.''' return sess -class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionCaps, ConnectionHandlersBase): +class ConnectionHandlers(ConnectionArchive, ConnectionVcard, ConnectionBytestream, ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionCaps, ConnectionHandlersBase): def __init__(self): + ConnectionArchive.__init__(self) ConnectionVcard.__init__(self) ConnectionBytestream.__init__(self) ConnectionCommands.__init__(self) @@ -2739,6 +2741,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, common.xmpp.NS_SEARCH) con.RegisterHandler('iq', self._PrivacySetCB, 'set', common.xmpp.NS_PRIVACY) + con.RegisterHandler('iq', self._ArchiveCB, ns=common.xmpp.NS_ARCHIVE) con.RegisterHandler('iq', self._PubSubCB, 'result') con.RegisterHandler('iq', self._ErrorCB, 'error') con.RegisterHandler('iq', self._IqCB) diff --git a/src/common/message_archiving.py b/src/common/message_archiving.py index b11b7e779..e28691890 100644 --- a/src/common/message_archiving.py +++ b/src/common/message_archiving.py @@ -19,7 +19,7 @@ ## -class ArchivingPreferences: +class ConnectionArchive: def __init__(self): self.auto_save = None self.method_auto = None @@ -28,17 +28,53 @@ class ArchivingPreferences: self.default = None self.items = {} - def set(self, auto_save, method_auto, method_local, method_manual): - self.auto_save = auto_save - self.method_auto = method_auto - self.method_local = method_local - self.method_manual = method_manual - def set_default(self, otr, save, expire=None, unset=False): - self.default = {'expire': expire, 'otr': otr, 'save': save, 'unset': unset} + self.default = {'expire': expire, 'otr': otr, 'save': save, + 'unset': unset} def append_or_update_item(self, jid, expire, otr, save): self.items[jid] = {'expire': expire, 'otr': otr, 'save': save} def remove_item(self, jid): del self.items[jid] + + def _ArchiveCB(self, con, iq_obj): + print '_ArchiveCB', iq_obj.getType() + if iq_obj.getType() not in ('result', 'set'): + return + if iq_obj.getTag('pref'): + pref = iq_obj.getTag('pref') + + if pref.getTag('auto'): + self.auto_save = pref.getTagAttr('auto', 'save') + print 'auto_save:', self.auto_save + + method_auto = pref.getTag('method', attrs={'type': 'auto'}) + if method_auto: + self.method_auto = method_auto.getAttr('use') + + method_local = pref.getTag('method', attrs={'type': 'local'}) + if method_local: + self.method_local = method_local.getAttr('use') + + method_manual = pref.getTag('method', attrs={'type': 'manual'}) + if method_manual: + self.method_manual = method_manual.getAttr('use') + + print 'method alm:', self.method_auto, self.method_local, self.method_manual + + if pref.getTag('default'): + default = pref.getTag('default') + print 'default oseu:', default.getAttr('otr'), default.getAttr('save'), default.getAttr('expire'), default.getAttr('unset') + self.set_default(default.getAttr('otr'), + default.getAttr('save'), default.getAttr('expire'), + default.getAttr('unset')) + for item in pref.getTags('item'): + print item.getAttr('jid'), item.getAttr('otr'), item.getAttr('save'), item.getAttr('expire') + self.append_or_update_item(item.getAttr('jid'), + item.getAttr('otr'), item.getAttr('save'), + item.getAttr('expire')) + elif iq_obj.getTag('itemremove'): + for item in pref.getTags('item'): + print 'del', item.getAttr('jid') + self.remove_item(item.getAttr('jid')) From ee59f1754bdc5246b291baa7d78d3947e28f1646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=ABl=20Verrier?= Date: Wed, 15 Jul 2009 20:44:34 +0200 Subject: [PATCH 041/200] * added an entry in the roster menu to display archiving preferences window * now we check service discovery features for message archiving --- data/glade/advanced_menuitem_menu.glade | 7 +++++++ src/common/connection_handlers.py | 8 ++++++++ src/common/message_archiving.py | 4 ++++ src/dialogs.py | 5 +++++ src/roster_window.py | 27 +++++++++++++++++++------ 5 files changed, 45 insertions(+), 6 deletions(-) diff --git a/data/glade/advanced_menuitem_menu.glade b/data/glade/advanced_menuitem_menu.glade index f90116d81..6ab91d036 100644 --- a/data/glade/advanced_menuitem_menu.glade +++ b/data/glade/advanced_menuitem_menu.glade @@ -9,6 +9,13 @@ True + + + True + Edit Archi_ving Preferences... + True + + Edit _Privacy Lists... diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index ed20e0618..9c8d17659 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -894,6 +894,14 @@ class ConnectionDisco: break if features.__contains__(common.xmpp.NS_PUBSUB): self.pubsub_supported = True + if features.__contains__(common.xmpp.NS_ARCHIVE_AUTO): + self.archive_auto_supported = True + if features.__contains__(common.xmpp.NS_ARCHIVE_MANAGE): + self.archive_manage_supported = True + if features.__contains__(common.xmpp.NS_ARCHIVE_MANUAL): + self.archive_manual_supported = True + if features.__contains__(common.xmpp.NS_ARCHIVE_PREF): + self.archive_pref_supported = True if features.__contains__(common.xmpp.NS_BYTESTREAM): our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name) +\ '/' + self.server_resource) diff --git a/src/common/message_archiving.py b/src/common/message_archiving.py index e28691890..66e058a1f 100644 --- a/src/common/message_archiving.py +++ b/src/common/message_archiving.py @@ -21,6 +21,10 @@ class ConnectionArchive: def __init__(self): + self.archive_auto_supported = False + self.archive_manage_supported = False + self.archive_manual_supported = False + self.archive_pref_supported = False self.auto_save = None self.method_auto = None self.method_local = None diff --git a/src/dialogs.py b/src/dialogs.py index 8358b4a78..7fe7163f4 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -2848,6 +2848,11 @@ class RosterItemExchangeWindow: self.window.destroy() +class ArchivingPreferencesWindow: + def __init__(self, account): + self.account = account + + class PrivacyListWindow: '''Window that is used for creating NEW or EDITING already there privacy lists''' diff --git a/src/roster_window.py b/src/roster_window.py index 4111c9fca..8bc25cde4 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -2336,6 +2336,14 @@ class RosterWindow: gajim.interface.instances[account]['xml_console'] = \ dialogs.XMLConsoleWindow(account) + def on_archiving_preferences_menuitem_activate(self, widget, account): + if 'archiving_preferences' in gajim.interface.instances[account]: + gajim.interface.instances[account]['archiving_preferences'].window.\ + present() + else: + gajim.interface.instances[account]['archiving_preferences'] = \ + dialogs.ArchivingPreferencesWindow(account) + def on_privacy_lists_menuitem_activate(self, widget, account): if 'privacy_lists' in gajim.interface.instances[account]: gajim.interface.instances[account]['privacy_lists'].window.present() @@ -6022,6 +6030,8 @@ class RosterWindow: advanced_menuitem_menu = xml.get_widget('advanced_menuitem_menu') xml_console_menuitem = xml.get_widget('xml_console_menuitem') + archiving_preferences_menuitem = \ + xml.get_widget('archiving_preferences_menuitem') privacy_lists_menuitem = xml.get_widget('privacy_lists_menuitem') administrator_menuitem = xml.get_widget('administrator_menuitem') send_server_message_menuitem = xml.get_widget( @@ -6033,12 +6043,17 @@ class RosterWindow: xml_console_menuitem.connect('activate', self.on_xml_console_menuitem_activate, account) - if gajim.connections[account] and gajim.connections[account].\ - privacy_rules_supported: - privacy_lists_menuitem.connect('activate', - self.on_privacy_lists_menuitem_activate, account) - else: - privacy_lists_menuitem.set_sensitive(False) + if gajim.connections[account]: + if gajim.connections[account].privacy_rules_supported: + privacy_lists_menuitem.connect('activate', + self.on_privacy_lists_menuitem_activate, account) + else: + privacy_lists_menuitem.set_sensitive(False) + if gajim.connections[account].archive_pref_supported: + archiving_preferences_menuitem.connect('activate', + self.on_archiving_preferences_menuitem_activate, account) + else: + archiving_preferences_menuitem.set_sensitive(False) if gajim.connections[account].is_zeroconf: administrator_menuitem.set_sensitive(False) From 7922426464929cdb6bb8c4ccee1813466de4bf92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=ABl=20Verrier?= Date: Sun, 19 Jul 2009 16:39:07 +0200 Subject: [PATCH 042/200] * first draft of message archiving preferences window * fixed a bug in ConnectionArchive.append_or_update_item prototype --- data/glade/archiving_preferences_window.glade | 359 ++++++++++++++++++ src/common/message_archiving.py | 2 +- src/dialogs.py | 88 +++++ 3 files changed, 448 insertions(+), 1 deletion(-) create mode 100644 data/glade/archiving_preferences_window.glade diff --git a/data/glade/archiving_preferences_window.glade b/data/glade/archiving_preferences_window.glade new file mode 100644 index 000000000..bed961b3f --- /dev/null +++ b/data/glade/archiving_preferences_window.glade @@ -0,0 +1,359 @@ + + + + + + 12 + + + + True + vertical + + + True + + + True + <i>Auto Save</i> + True + + + 0 + + + + + Yes + True + True + False + True + True + + + 1 + + + + + No + True + True + False + True + True + auto_save_yes_radiobutton + + + 2 + + + + + False + 0 + + + + + True + 3 + 2 + + + True + <i>Method Auto</i> + True + + + + + True + <i>Method Local</i> + True + + + 1 + 2 + + + + + True + <i>Method Manual</i> + True + + + 2 + 3 + + + + + True + Prefer +Concede +Forbid + + + 1 + 2 + 2 + 3 + + + + + True + Prefer +Concede +Forbid + + + 1 + 2 + 1 + 2 + + + + + True + Prefer +Concede +Forbid + + + 1 + 2 + + + + + False + 1 + + + + + True + + + True + vertical + + + True + True + automatic + automatic + + + True + True + False + + + + + + 0 + + + + + True + spread + + + gtk-add + True + True + True + True + + + + False + False + 0 + + + + + gtk-remove + True + True + True + True + + + + False + False + 1 + + + + + False + 1 + + + + + 0 + + + + + True + True + False + False + + + True + + + + + True + page 1 + + + False + tab + + + + + True + 3 + 2 + + + True + expire + + + + + True + otr + + + 1 + 2 + + + + + True + save + + + 2 + 3 + + + + + True + True + + + + 1 + 2 + + + + + True + approve +concede +forbid +oppose +prefer +require + + + 1 + 2 + 1 + 2 + + + + + True + body +false +message +stream + + + 1 + 2 + 2 + 3 + + + + + 1 + + + + + True + page 2 + + + 1 + False + tab + + + + + 1 + + + + + 2 + + + + + True + end + + + gtk-close + True + True + True + False + True + + + + False + False + 0 + + + + + False + 3 + + + + + + diff --git a/src/common/message_archiving.py b/src/common/message_archiving.py index 66e058a1f..913f6c497 100644 --- a/src/common/message_archiving.py +++ b/src/common/message_archiving.py @@ -36,7 +36,7 @@ class ConnectionArchive: self.default = {'expire': expire, 'otr': otr, 'save': save, 'unset': unset} - def append_or_update_item(self, jid, expire, otr, save): + def append_or_update_item(self, jid, otr, save, expire): self.items[jid] = {'expire': expire, 'otr': otr, 'save': save} def remove_item(self, jid): diff --git a/src/dialogs.py b/src/dialogs.py index 7fe7163f4..c3ab7cb85 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -2852,6 +2852,94 @@ class ArchivingPreferencesWindow: def __init__(self, account): self.account = account + # Connect to glade + self.xml = gtkgui_helpers.get_glade('archiving_preferences_window.glade') + self.window = self.xml.get_widget('archiving_preferences_window') + + # Add Widgets + for widget_to_add in ('auto_save_yes_radiobutton', + 'auto_save_no_radiobutton', 'method_auto_combobox', + 'method_local_combobox', 'method_manual_combobox', 'close_button', + 'item_treeview', 'item_notebook', 'otr_combobox', 'save_combobox', + 'expire_entry'): + self.__dict__[widget_to_add] = self.xml.get_widget(widget_to_add) + + + auto_save = gajim.connections[account].auto_save == 'true' + self.auto_save_yes_radiobutton.set_active(auto_save) + self.auto_save_no_radiobutton.set_active(not auto_save) + + method_index = {'prefer': 0, 'concede': 1, 'forbid': 2} + self.method_auto_combobox.set_active(method_index[gajim.connections[ + self.account].method_auto]) + self.method_local_combobox.set_active(method_index[gajim.connections[ + self.account].method_local]) + self.method_manual_combobox.set_active(method_index[gajim.connections[ + self.account].method_manual]) + + model = gtk.ListStore(str) + self.item_treeview.set_model(model) + col = gtk.TreeViewColumn('name') + self.item_treeview.append_column(col) + renderer = gtk.CellRendererText() + col.pack_start(renderer, True) + col.set_attributes(renderer, text=0) + #renderer.connect('edited', self.on_msg_cell_edited) + renderer.set_property('editable', False) + #self.fill_item_treeview() + iter_ = model.append() + model.set(iter_, 0, 'Default') + for item in gajim.connections[account].items: + iter_ = model.append() + model.set(iter_, 0, item) + self.current_item = None + + self.window.set_title(_('Archiving Preferences for %s') % self.account) + + self.window.show_all() + + self.xml.signal_autoconnect(self) + + def on_archiving_preferences_window_destroy(self, widget): + if 'archiving_preferences' in gajim.interface.instances[self.account]: + del gajim.interface.instances[self.account]['archiving_preferences'] + + def on_add_item_button_clicked(self, widget): + model = self.item_treeview.get_model() + iter_ = model.append() + model.set(iter_, 0, 'jid@example.net') + + def on_item_treeview_cursor_changed(self, widget): + sel = self.item_treeview.get_selection() + (model, iter_) = sel.get_selected() + item = None + if iter_: + item = model[iter_][0] + if self.current_item and self.current_item == item: + return + + if iter: + otr_index = {'approve': 0, 'concede': 1, 'forbid': 2, 'oppose': 3, + 'prefer': 4, 'require': 5} + save_index = {'body': 0, 'false': 1, 'message': 2, 'stream': 3} + item_config = None + if item == 'Default': + item_config = gajim.connections[self.account].default + else: + item_config = gajim.connections[self.account].items[item] + self.otr_combobox.set_active(otr_index[item_config['otr']]) + self.save_combobox.set_active(save_index[item_config['save']]) + expire_value = item_config['expire'] or '' + self.expire_entry.set_text(expire_value) + self.current_item = item + if self.current_item: + self.item_notebook.set_current_page(1) + else: + self.item_notebook.set_current_page(0) + + def on_close_button_clicked(self, widget): + self.window.destroy() + class PrivacyListWindow: '''Window that is used for creating NEW or EDITING already there privacy From 690c5e82ed995e1fe45fbb8670642fac2bbcc094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=ABl=20Verrier?= Date: Fri, 31 Jul 2009 19:16:03 +0200 Subject: [PATCH 043/200] * second draft of message archiving preferences window --- data/glade/archiving_preferences_window.glade | 367 ++++++------------ .../item_archiving_preferences_window.glade | 176 +++++++++ src/common/connection.py | 5 +- src/common/message_archiving.py | 88 ++++- src/dialogs.py | 355 ++++++++++++++--- src/gajim.py | 12 + 6 files changed, 683 insertions(+), 320 deletions(-) create mode 100644 data/glade/item_archiving_preferences_window.glade diff --git a/data/glade/archiving_preferences_window.glade b/data/glade/archiving_preferences_window.glade index bed961b3f..1b74bcc35 100644 --- a/data/glade/archiving_preferences_window.glade +++ b/data/glade/archiving_preferences_window.glade @@ -10,43 +10,104 @@ True vertical - + True + 4 + 2 - + True - <i>Auto Save</i> + <i>Method Manual</i> True - 0 + 3 + 4 - - Yes + True - True - False - True - True + <i>Method Local</i> + True - 1 + 2 + 3 - - No + True - True - False - True - True - auto_save_yes_radiobutton + <i>Method Auto</i> + True - 2 + 1 + 2 + + + + + True + Prefer +Concede +Forbid + + + + 1 + 2 + 3 + 4 + + + + + True + Prefer +Concede +Forbid + + + + 1 + 2 + 2 + 3 + + + + + True + Prefer +Concede +Forbid + + + + 1 + 2 + 1 + 2 + + + + + True + label + + + + + True + No +Yes + + + + 1 + 2 @@ -56,150 +117,21 @@ - + True - 3 - 2 + vertical - + True - <i>Method Auto</i> - True - - - - - True - <i>Method Local</i> - True - - - 1 - 2 - - - - - True - <i>Method Manual</i> - True - - - 2 - 3 - - - - - True - Prefer -Concede -Forbid - - - 1 - 2 - 2 - 3 - - - - - True - Prefer -Concede -Forbid - - - 1 - 2 - 1 - 2 - - - - - True - Prefer -Concede -Forbid - - - 1 - 2 - - - - - False - 1 - - - - - True - - - True - vertical + True + automatic + automatic - + True True - automatic - automatic - - - True - True - False - - - + - - 0 - - - - - True - spread - - - gtk-add - True - True - True - True - - - - False - False - 0 - - - - - gtk-remove - True - True - True - True - - - - False - False - 1 - - - - - False - 1 - @@ -207,124 +139,63 @@ Forbid - + True - True - False - False + spread - + + gtk-add True - - - - - True - page 1 + True + True + True + - False - tab + False + False + 0 - + + gtk-remove True - 3 - 2 - - - True - expire - - - - - True - otr - - - 1 - 2 - - - - - True - save - - - 2 - 3 - - - - - True - True - - - - 1 - 2 - - - - - True - approve -concede -forbid -oppose -prefer -require - - - 1 - 2 - 1 - 2 - - - - - True - body -false -message -stream - - - 1 - 2 - 2 - 3 - - + True + True + True + + False + False 1 - + + gtk-edit True - page 2 + True + True + True + - 1 - False - tab + False + False + 2 + False 1 - 2 + 1 @@ -350,7 +221,7 @@ stream False - 3 + 2 diff --git a/data/glade/item_archiving_preferences_window.glade b/data/glade/item_archiving_preferences_window.glade new file mode 100644 index 000000000..8b4ca61c6 --- /dev/null +++ b/data/glade/item_archiving_preferences_window.glade @@ -0,0 +1,176 @@ + + + + + + 12 + + + + True + vertical + 12 + + + True + 4 + 2 + + + True + expire + + + 1 + 2 + + + + + True + True + + + + 1 + 2 + 1 + 2 + + + + + True + approve +concede +forbid +oppose +prefer +require + + + 1 + 2 + 2 + 3 + + + + + True + body +false +message +stream + + + 1 + 2 + 3 + 4 + + + + + True + save + + + 3 + 4 + + + + + True + otr + + + 2 + 3 + + + + + True + jid + + + + + True + True + + + + 1 + 2 + + + + + False + False + 0 + + + + + True + + + + False + False + 0 + + + + + True + end + + + gtk-cancel + True + True + True + True + + + + False + False + 0 + + + + + gtk-ok + True + True + True + True + + + + False + False + 1 + + + + + 1 + + + + + False + 1 + + + + + + diff --git a/src/common/connection.py b/src/common/connection.py index d134b1818..250e85eb7 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -1033,10 +1033,7 @@ class Connection(ConnectionHandlers): self.connection.set_send_timeout2(self.pingalives, self.sendPing) self.connection.onreceive(None) - # Request message archiving preferences - iq = common.xmpp.Iq('get') - iq.setTag('pref', namespace=common.xmpp.NS_ARCHIVE) - self.connection.send(iq) + self.request_message_archiving_preferences() # Request privacy list iq = common.xmpp.Iq('get', common.xmpp.NS_PRIVACY, xmlns = '') diff --git a/src/common/message_archiving.py b/src/common/message_archiving.py index 913f6c497..1ebab56d8 100644 --- a/src/common/message_archiving.py +++ b/src/common/message_archiving.py @@ -18,6 +18,8 @@ ## along with Gajim. If not, see . ## +import common.xmpp + class ConnectionArchive: def __init__(self): @@ -25,60 +27,110 @@ class ConnectionArchive: self.archive_manage_supported = False self.archive_manual_supported = False self.archive_pref_supported = False - self.auto_save = None + self.auto = None self.method_auto = None self.method_local = None self.method_manual = None self.default = None self.items = {} - - def set_default(self, otr, save, expire=None, unset=False): - self.default = {'expire': expire, 'otr': otr, 'save': save, - 'unset': unset} + + def request_message_archiving_preferences(self): + iq_ = common.xmpp.Iq('get') + iq_.setTag('pref', namespace=common.xmpp.NS_ARCHIVE) + print iq_ + self.connection.send(iq_) + + def set_pref(self, name, **data): + ''' + data contains names and values of pref name attributes. + ''' + iq_ = common.xmpp.Iq('set') + pref = iq_.setTag('pref', namespace=common.xmpp.NS_ARCHIVE) + tag = pref.setTag(name) + for key, value in data.items(): + if value is not None: + tag.setAttr(key, value) + print iq_ + self.connection.send(iq_) + + def set_auto(self, save): + self.set_pref('auto', save=save) + + def set_method(self, type, use): + self.set_pref('method', type=type, use=use) + + def set_default(self, otr, save, expire=None): + self.set_pref('default', otr=otr, save=save, expire=expire) def append_or_update_item(self, jid, otr, save, expire): - self.items[jid] = {'expire': expire, 'otr': otr, 'save': save} - + self.set_pref('item', jid=jid, otr=otr, save=save) + def remove_item(self, jid): - del self.items[jid] + iq_ = common.xmpp.Iq('set') + itemremove = iq_.setTag('itemremove', namespace=common.xmpp.NS_ARCHIVE) + item = itemremove.setTag('item') + item.setAttr('jid', jid) + print iq_ + self.connection.send(iq_) def _ArchiveCB(self, con, iq_obj): print '_ArchiveCB', iq_obj.getType() - if iq_obj.getType() not in ('result', 'set'): + if iq_obj.getType() == 'error': + self.dispatch('ARCHIVING_ERROR', iq_obj.getErrorMsg()) return + elif iq_obj.getType() not in ('result', 'set'): + print iq_obj + return + if iq_obj.getTag('pref'): pref = iq_obj.getTag('pref') if pref.getTag('auto'): - self.auto_save = pref.getTagAttr('auto', 'save') - print 'auto_save:', self.auto_save + self.auto = pref.getTagAttr('auto', 'save') + print 'auto:', self.auto + self.dispatch('ARCHIVING_CHANGED', ('auto', + self.auto)) method_auto = pref.getTag('method', attrs={'type': 'auto'}) if method_auto: self.method_auto = method_auto.getAttr('use') + self.dispatch('ARCHIVING_CHANGED', ('method_auto', + self.method_auto)) method_local = pref.getTag('method', attrs={'type': 'local'}) if method_local: self.method_local = method_local.getAttr('use') + self.dispatch('ARCHIVING_CHANGED', ('method_local', + self.method_local)) method_manual = pref.getTag('method', attrs={'type': 'manual'}) if method_manual: self.method_manual = method_manual.getAttr('use') + self.dispatch('ARCHIVING_CHANGED', ('method_manual', + self.method_manual)) print 'method alm:', self.method_auto, self.method_local, self.method_manual if pref.getTag('default'): default = pref.getTag('default') print 'default oseu:', default.getAttr('otr'), default.getAttr('save'), default.getAttr('expire'), default.getAttr('unset') - self.set_default(default.getAttr('otr'), - default.getAttr('save'), default.getAttr('expire'), - default.getAttr('unset')) + self.default = { + 'expire': default.getAttr('expire'), + 'otr': default.getAttr('otr'), + 'save': default.getAttr('save'), + 'unset': default.getAttr('unset')} + self.dispatch('ARCHIVING_CHANGED', ('default', + self.default)) for item in pref.getTags('item'): print item.getAttr('jid'), item.getAttr('otr'), item.getAttr('save'), item.getAttr('expire') - self.append_or_update_item(item.getAttr('jid'), - item.getAttr('otr'), item.getAttr('save'), - item.getAttr('expire')) + self.items[item.getAttr('jid')] = { + 'expire': item.getAttr('expire'), + 'otr': item.getAttr('otr'), 'save': item.getAttr('save')} + self.dispatch('ARCHIVING_CHANGED', ('item', + item.getAttr('jid'), self.items[item.getAttr('jid')])) elif iq_obj.getTag('itemremove'): for item in pref.getTags('item'): print 'del', item.getAttr('jid') - self.remove_item(item.getAttr('jid')) + del self.items[item.getAttr('jid')] + self.dispatch('ARCHIVING_CHANGED', ('itemremove', + item.getAttr('jid'))) diff --git a/src/dialogs.py b/src/dialogs.py index c3ab7cb85..faebbd36a 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -2848,66 +2848,247 @@ class RosterItemExchangeWindow: self.window.destroy() +class ItemArchivingPreferencesWindow: + otr_name = ('approve', 'concede', 'forbid', 'oppose', 'prefer', 'require') + otr_index = dict([(j, i) for i, j in enumerate(otr_name)]) + save_name = ('body', 'false', 'message', 'stream') + save_index = dict([(j, i) for i, j in enumerate(save_name)]) + + def __init__(self, account, item): + self.account = account + self.item = item + if self.item and self.item != 'Default': + self.item_config = gajim.connections[self.account].items[self.item] + else: + self.item_config = gajim.connections[self.account].default + print self.item, self.item_config + self.waiting = None + + # Connect to glade + self.xml = gtkgui_helpers.get_glade( + 'item_archiving_preferences_window.glade') + self.window = self.xml.get_widget('item_archiving_preferences_window') + + # Add Widgets + for widget_to_add in ('jid_entry', 'expire_entry', 'otr_combobox', + 'save_combobox', 'cancel_button', 'ok_button', 'progressbar'): + self.__dict__[widget_to_add] = self.xml.get_widget(widget_to_add) + + if self.item: + self.jid_entry.set_text(self.item) + expire_value = self.item_config['expire'] or '' + self.otr_combobox.set_active(self.otr_index[self.item_config['otr']]) + self.save_combobox.set_active( + self.save_index[self.item_config['save']]) + self.expire_entry.set_text(expire_value) + + self.window.set_title(_('Archiving Preferences for %s') % self.account) + + self.window.show_all() + self.progressbar.hide() + self.xml.signal_autoconnect(self) + + def update_progressbar(self): + if self.waiting: + self.progressbar.pulse() + return True + return False + + def on_ok_button_clicked(self, widget): + # Return directly if operation in progress + if self.waiting: + return + + item = self.jid_entry.get_text() + otr = self.otr_name[self.otr_combobox.get_active()] + save = self.save_name[self.save_combobox.get_active()] + expire = self.expire_entry.get_text() + + if self.item != 'Default': + try: + item = helpers.parse_jid(item) + except helpers.InvalidFormat, s: + pritext = _('Invalid User ID') + ErrorDialog(pritext, str(s)) + return + + if expire: + try: + if int(expire) < 0 or str(int(expire)) != expire: + raise ValueError + except ValueError: + pritext = _('Invalid expire value') + sectext = _('Expire must be a valid positive integer.') + ErrorDialog(pritext, sectext) + return + + if not (item == self.item and expire == self.item_config['expire'] and + otr == self.item_config['otr'] and save == self.item_config['save']): + if not self.item or self.item == item: + if self.item == 'Default': + self.waiting = 'default' + gajim.connections[self.account].set_default( + otr, save, expire) + else: + self.waiting = 'item' + gajim.connections[self.account].append_or_update_item( + item, otr, save, expire) + else: + self.waiting = 'item' + gajim.connections[self.account].append_or_update_item( + item, otr, save, expire) + gajim.connections[self.account].remove_item(self.item) + self.launch_progressbar() + #self.window.destroy() + + def on_cancel_button_clicked(self, widget): + self.window.destroy() + + def on_item_archiving_preferences_window_destroy(self, widget): + if self.item: + key_name = 'edit_item_archiving_preferences_%s' % self.item + else: + key_name = 'new_item_archiving_preferences' + if key_name in gajim.interface.instances[self.account]: + del gajim.interface.instances[self.account][key_name] + + def launch_progressbar(self): + self.progressbar.show() + self.update_progressbar_timeout_id = gobject.timeout_add( + 100, self.update_progressbar) + + def response_arrived(self, data): + if self.waiting: + self.window.destroy() + + def error_arrived(self, error): + if self.waiting: + self.waiting = None + self.progressbar.hide() + pritext = _('There is an error with the form') + sectext = error + ErrorDialog(pritext, sectext) + + class ArchivingPreferencesWindow: + auto_name = ('false', 'true') + auto_index = dict([(j, i) for i, j in enumerate(auto_name)]) + method_foo_name = ('prefer', 'concede', 'forbid') + method_foo_index = dict([(j, i) for i, j in enumerate(method_foo_name)]) + def __init__(self, account): self.account = account + self.waiting = [] # Connect to glade self.xml = gtkgui_helpers.get_glade('archiving_preferences_window.glade') self.window = self.xml.get_widget('archiving_preferences_window') # Add Widgets - for widget_to_add in ('auto_save_yes_radiobutton', - 'auto_save_no_radiobutton', 'method_auto_combobox', + for widget_to_add in ('auto_combobox', 'method_auto_combobox', 'method_local_combobox', 'method_manual_combobox', 'close_button', 'item_treeview', 'item_notebook', 'otr_combobox', 'save_combobox', - 'expire_entry'): + 'expire_entry', 'remove_button', 'edit_button'): self.__dict__[widget_to_add] = self.xml.get_widget(widget_to_add) + self.auto_combobox.set_active( + self.auto_index[gajim.connections[self.account].auto]) + self.method_auto_combobox.set_active( + self.method_foo_index[gajim.connections[self.account].method_auto]) + self.method_local_combobox.set_active( + self.method_foo_index[gajim.connections[self.account].method_local]) + self.method_manual_combobox.set_active( + self.method_foo_index[gajim.connections[self.account].method_manual]) - auto_save = gajim.connections[account].auto_save == 'true' - self.auto_save_yes_radiobutton.set_active(auto_save) - self.auto_save_no_radiobutton.set_active(not auto_save) - - method_index = {'prefer': 0, 'concede': 1, 'forbid': 2} - self.method_auto_combobox.set_active(method_index[gajim.connections[ - self.account].method_auto]) - self.method_local_combobox.set_active(method_index[gajim.connections[ - self.account].method_local]) - self.method_manual_combobox.set_active(method_index[gajim.connections[ - self.account].method_manual]) - - model = gtk.ListStore(str) + model = gtk.ListStore(str, str, str, str) self.item_treeview.set_model(model) - col = gtk.TreeViewColumn('name') + col = gtk.TreeViewColumn('jid') self.item_treeview.append_column(col) renderer = gtk.CellRendererText() col.pack_start(renderer, True) col.set_attributes(renderer, text=0) - #renderer.connect('edited', self.on_msg_cell_edited) - renderer.set_property('editable', False) - #self.fill_item_treeview() - iter_ = model.append() - model.set(iter_, 0, 'Default') - for item in gajim.connections[account].items: - iter_ = model.append() - model.set(iter_, 0, item) + + col = gtk.TreeViewColumn('expire') + col.pack_start(renderer, True) + col.set_attributes(renderer, text=1) + self.item_treeview.append_column(col) + + col = gtk.TreeViewColumn('otr') + col.pack_start(renderer, True) + col.set_attributes(renderer, text=2) + self.item_treeview.append_column(col) + + col = gtk.TreeViewColumn('save') + col.pack_start(renderer, True) + col.set_attributes(renderer, text=3) + self.item_treeview.append_column(col) + + self.fill_items() + self.current_item = None + def sort_items(model, iter1, iter2): + item1 = model.get_value(iter1, 0) + item2 = model.get_value(iter2, 0) + if item1 == 'Default': + return -1 + if item2 == 'Default': + return 1 + if '@' in item1: + if '@' not in item2: + return 1 + elif '@' in item2: + return -1 + if item1 < item2: + return -1 + if item1 > item2: + return 1 + # item1 == item2 ? WTF? + return 0 + + model.set_sort_column_id(0, gtk.SORT_ASCENDING) + model.set_sort_func(0, sort_items) + + self.remove_button.set_sensitive(False) + self.edit_button.set_sensitive(False) + self.window.set_title(_('Archiving Preferences for %s') % self.account) self.window.show_all() self.xml.signal_autoconnect(self) - def on_archiving_preferences_window_destroy(self, widget): - if 'archiving_preferences' in gajim.interface.instances[self.account]: - del gajim.interface.instances[self.account]['archiving_preferences'] - def on_add_item_button_clicked(self, widget): - model = self.item_treeview.get_model() - iter_ = model.append() - model.set(iter_, 0, 'jid@example.net') + key_name = 'new_item_archiving_preferences' + if key_name in gajim.interface.instances[self.account]: + gajim.interface.instances[self.account][key_name].window.present() + else: + gajim.interface.instances[self.account][key_name] = \ + ItemArchivingPreferencesWindow(self.account, '') + + def on_remove_item_button_clicked(self, widget): + if not self.current_item: + return + + self.waiting.append('itemremove') + sel = self.item_treeview.get_selection() + (model, iter_) = sel.get_selected() + gajim.connections[self.account].remove_item(model[iter_][0]) + model.remove(iter_) + self.remove_button.set_sensitive(False) + self.edit_button.set_sensitive(False) + + def on_edit_item_button_clicked(self, widget): + if not self.current_item: + print 'there is no current item' + return + + key_name = 'edit_item_archiving_preferences_%s' % self.current_item + if key_name in gajim.interface.instances[self.account]: + gajim.interface.instances[self.account][key_name].window.present() + else: + gajim.interface.instances[self.account][key_name] = \ + ItemArchivingPreferencesWindow(self.account, self.current_item) def on_item_treeview_cursor_changed(self, widget): sel = self.item_treeview.get_selection() @@ -2918,27 +3099,101 @@ class ArchivingPreferencesWindow: if self.current_item and self.current_item == item: return - if iter: - otr_index = {'approve': 0, 'concede': 1, 'forbid': 2, 'oppose': 3, - 'prefer': 4, 'require': 5} - save_index = {'body': 0, 'false': 1, 'message': 2, 'stream': 3} - item_config = None - if item == 'Default': - item_config = gajim.connections[self.account].default - else: - item_config = gajim.connections[self.account].items[item] - self.otr_combobox.set_active(otr_index[item_config['otr']]) - self.save_combobox.set_active(save_index[item_config['save']]) - expire_value = item_config['expire'] or '' - self.expire_entry.set_text(expire_value) - self.current_item = item - if self.current_item: - self.item_notebook.set_current_page(1) + self.current_item = item + if self.current_item == 'Default': + self.remove_button.set_sensitive(False) + self.edit_button.set_sensitive(True) + elif self.current_item: + self.remove_button.set_sensitive(True) + self.edit_button.set_sensitive(True) else: - self.item_notebook.set_current_page(0) + self.remove_button.set_sensitive(False) + self.edit_button.set_sensitive(False) + + def on_auto_combobox_changed(self, widget): + save = self.auto_name[widget.get_active()] + gajim.connections[self.account].set_auto(save) + + def on_method_foo_combobox_changed(self, widget): + # We retrieve method type from widget name + # ('foo' in 'method_foo_combobox') + method_type = widget.name.split('_')[1] + use = self.method_foo_name[widget.get_active()] + self.waiting.append('method_%s' % method_type) + gajim.connections[self.account].set_method(method_type, use) + + def get_child_window(self): + edit_key_name = 'edit_item_archiving_preferences_%s' % \ + self.current_item + new_key_name = 'new_item_archiving_preferences' + + if edit_key_name in gajim.interface.instances[self.account]: + return gajim.interface.instances[self.account][edit_key_name] + + if new_key_name in gajim.interface.instances[self.account]: + return gajim.interface.instances[self.account][new_key_name] + + def archiving_changed(self, data): + if data[0] in ('auto', 'method_auto', 'method_local', 'method_manual'): + if data[0] in self.waiting: + self.waiting.remove(data[0]) + elif data[0] == 'default': + key_name = 'edit_item_archiving_preferences_%s' % \ + self.current_item + if key_name in gajim.interface.instances[self.account]: + gajim.interface.instances[self.account][key_name].\ + response_arrived(data[1:]) + self.fill_items(True) + elif data[0] == 'item': + child = self.get_child_window() + if child: + is_new = not child.item + child.response_arrived(data[1:]) + if is_new: + model = self.item_treeview.get_model() + model.append((data[1], data[2]['expire'], data[2]['otr'], + data[2]['save'])) + return + self.fill_items(True) + elif data[0] == 'itemremove' == self.waiting: + if data[0] in self.waiting: + self.waiting.remove(data[0]) + self.fill_items(True) + + def fill_items(self, clear=False): + model = self.item_treeview.get_model() + if clear: + model.clear() + default_config = gajim.connections[self.account].default + expire_value = default_config['expire'] or '' + model.append(('Default', expire_value, + default_config['otr'], default_config['save'])) + for item, item_config in \ + gajim.connections[self.account].items.items(): + expire_value = item_config['expire'] or '' + model.append((item, expire_value, item_config['otr'], + item_config['save'])) + + def archiving_error(self, error): + if self.waiting: + pritext = _('There is an error') + sectext = error + ErrorDialog(pritext, sectext) + self.waiting.pop() + else: + child = self.get_child_window() + if child: + child.error_arrived(error) + print error def on_close_button_clicked(self, widget): - self.window.destroy() + if not self.waiting: + self.window.destroy() + + def on_archiving_preferences_window_destroy(self, widget): + if 'archiving_preferences' in gajim.interface.instances[self.account]: + del gajim.interface.instances[self.account]['archiving_preferences'] + class PrivacyListWindow: diff --git a/src/gajim.py b/src/gajim.py index 5a60993cf..acace5130 100644 --- a/src/gajim.py +++ b/src/gajim.py @@ -2236,6 +2236,16 @@ class Interface: _('PEP node %(node)s was not removed: %(message)s') % { 'node': data[1], 'message': data[2]}) + def handle_event_archiving_changed(self, account, data): + if 'archiving_preferences' in self.instances[account]: + self.instances[account]['archiving_preferences'].archiving_changed( + data) + + def handle_event_archiving_error(self, account, data): + if 'archiving_preferences' in self.instances[account]: + self.instances[account]['archiving_preferences'].archiving_error( + data) + def register_handlers(self): self.handlers = { 'ROSTER': self.handle_event_roster, @@ -2320,6 +2330,8 @@ class Interface: 'INSECURE_SSL_CONNECTION': self.handle_event_insecure_ssl_connection, 'PUBSUB_NODE_REMOVED': self.handle_event_pubsub_node_removed, 'PUBSUB_NODE_NOT_REMOVED': self.handle_event_pubsub_node_not_removed, + 'ARCHIVING_CHANGED': self.handle_event_archiving_changed, + 'ARCHIVING_ERROR': self.handle_event_archiving_error, } gajim.handlers = self.handlers From b7f8a3a93dcc2a06b3160ca541085eb380f79b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=ABl=20Verrier?= Date: Sun, 2 Aug 2009 21:58:12 +0200 Subject: [PATCH 044/200] * fixed a bug with zeroconf (because we didn't have a archive_pref_supported attribute in connection class) --- src/common/zeroconf/connection_zeroconf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/zeroconf/connection_zeroconf.py b/src/common/zeroconf/connection_zeroconf.py index 41fc8be9b..37c1469e4 100644 --- a/src/common/zeroconf/connection_zeroconf.py +++ b/src/common/zeroconf/connection_zeroconf.py @@ -102,6 +102,7 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf): self.muc_jid = {} # jid of muc server for each transport type self.vcard_supported = False self.private_storage_supported = False + self.archive_pref_supported = False def get_config_values_or_default(self): ''' get name, host, port from config, or From 59e25b92c8ff3e7e0d92acf1d4b19572cec1ce7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=ABl=20Verrier?= Date: Sun, 2 Aug 2009 22:29:19 +0200 Subject: [PATCH 045/200] * fixed a bug in item archiving preferences window (the notes in sections 2.2.2.2 and 2.2.3.3 in XEP-0136 say when we change OTR to 'require' we must change save value to 'false') --- data/glade/item_archiving_preferences_window.glade | 1 + src/dialogs.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/data/glade/item_archiving_preferences_window.glade b/data/glade/item_archiving_preferences_window.glade index 8b4ca61c6..21cbcc91f 100644 --- a/data/glade/item_archiving_preferences_window.glade +++ b/data/glade/item_archiving_preferences_window.glade @@ -47,6 +47,7 @@ forbid oppose prefer require + 1 diff --git a/src/dialogs.py b/src/dialogs.py index faebbd36a..fcf1f8a3b 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -2894,6 +2894,11 @@ class ItemArchivingPreferencesWindow: return True return False + def on_otr_combobox_changed(self, widget): + otr = self.otr_name[self.otr_combobox.get_active()] + if otr == 'require': + self.save_combobox.set_active(self.save_index['false']) + def on_ok_button_clicked(self, widget): # Return directly if operation in progress if self.waiting: From c7dbcdac65d91d776e963b945afced89b5dab90b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=ABl=20Verrier?= Date: Wed, 5 Aug 2009 17:41:42 +0200 Subject: [PATCH 046/200] * renamed a label in message archiving preferences window --- data/glade/archiving_preferences_window.glade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/glade/archiving_preferences_window.glade b/data/glade/archiving_preferences_window.glade index 1b74bcc35..89e5bbcc5 100644 --- a/data/glade/archiving_preferences_window.glade +++ b/data/glade/archiving_preferences_window.glade @@ -95,7 +95,7 @@ Forbid True - label + Auto From 18c8805fb4c3e31eb42fc46bbbd8fc4cf7f8c37e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=ABl=20Verrier?= Date: Wed, 5 Aug 2009 18:05:23 +0200 Subject: [PATCH 047/200] * added 'http://jabber.org/protocol/rsm' namespace into xmpp/protocol.py * added comments for NS_ARCHIVE* --- src/common/xmpp/protocol.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py index b6967408d..4eda7c9f3 100644 --- a/src/common/xmpp/protocol.py +++ b/src/common/xmpp/protocol.py @@ -27,11 +27,11 @@ NS_ADDRESS ='http://jabber.org/protocol/address' # XEP-0033 NS_AGENTS ='jabber:iq:agents' NS_AMP ='http://jabber.org/protocol/amp' NS_AMP_ERRORS =NS_AMP+'#errors' -NS_ARCHIVE ='urn:xmpp:archive' -NS_ARCHIVE_AUTO =NS_ARCHIVE+':auto' -NS_ARCHIVE_MANAGE =NS_ARCHIVE+':manage' -NS_ARCHIVE_MANUAL =NS_ARCHIVE+':manual' -NS_ARCHIVE_PREF =NS_ARCHIVE+':pref' +NS_ARCHIVE ='urn:xmpp:archive' #XEP-0136 +NS_ARCHIVE_AUTO =NS_ARCHIVE+':auto' #XEP-0136 +NS_ARCHIVE_MANAGE =NS_ARCHIVE+':manage' #XEP-0136 +NS_ARCHIVE_MANUAL =NS_ARCHIVE+':manual' #XEP-0136 +NS_ARCHIVE_PREF =NS_ARCHIVE+':pref' #XEP-0136 NS_AUTH ='jabber:iq:auth' NS_AVATAR ='http://www.xmpp.org/extensions/xep-0084.html#ns-metadata' NS_BIND ='urn:ietf:params:xml:ns:xmpp-bind' @@ -91,6 +91,7 @@ NS_REGISTER ='jabber:iq:register' NS_ROSTER ='jabber:iq:roster' NS_ROSTERX ='http://jabber.org/protocol/rosterx' # XEP-0144 NS_RPC ='jabber:iq:rpc' # XEP-0009 +NS_RSM ='http://jabber.org/protocol/rsm' #XEP-0059 NS_SASL ='urn:ietf:params:xml:ns:xmpp-sasl' NS_SEARCH ='jabber:iq:search' NS_SERVER ='jabber:server' From dc2eadc8995fb0d371606c53ff82b05051463037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=ABl=20Verrier?= Date: Wed, 5 Aug 2009 19:05:12 +0200 Subject: [PATCH 048/200] * added some methods to request a page of collections list, request a page of a collection, remove a collection and request a page of modifications (XEP-0136) --- src/common/message_archiving.py | 57 +++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/common/message_archiving.py b/src/common/message_archiving.py index 1ebab56d8..6af85bed1 100644 --- a/src/common/message_archiving.py +++ b/src/common/message_archiving.py @@ -134,3 +134,60 @@ class ConnectionArchive: del self.items[item.getAttr('jid')] self.dispatch('ARCHIVING_CHANGED', ('itemremove', item.getAttr('jid'))) + + def request_collections_list_page(self, with='', start=None, end=None, + after=None, max=30, exact_match=False): + iq_ = common.xmpp.Iq('get') + list_ = iq_.setTag('list', namespace=common.xmpp.NS_ARCHIVE) + if with: + list_.setAttr('with', with) + if exact_match: + list_.setAttr('exactmatch', 'true') + if start: + list_.setAttr('start', start) + if end: + list_.setAttr('end', end) + set_ = list_.setTag('set', namespace=common.xmpp.NS_RSM) + set_.setTagData('max', max) + if after: + set_.setTagData('after', after) + self.connection.send(iq_) + + def request_collection_page(self, with, start, end=None, after=None, + max=30, exact_match=False): + iq_ = common.xmpp.Iq('get') + retrieve = iq_.setTag('retrieve', namespace=common.xmpp.NS_ARCHIVE, + attrs={'with': with, 'start': start}) + if exact_match: + retrieve.setAttr('exactmatch', 'true') + set_ = retrieve.setTag('set', namespace=common.xmpp.NS_RSM) + set_.setTagData('max', max) + if after: + set_.setTagData('after', after) + self.connection.send(iq_) + + def remove_collection(self, with='', start=None, end=None, + exact_match=False, open=False): + iq_ = common.xmpp.Iq('set') + remove = iq_.setTag('remove', namespace=common.xmpp.NS_ARCHIVE) + if with: + remove.setAttr('with', with) + if exact_match: + remove.setAttr('exactmatch', 'true') + if start: + remove.setAttr('start', start) + if end: + remove.setAttr('end', end) + if open: + remove.setAttr('open', 'true') + self.connection.send(iq_) + + def request_modifications_page(self, start, version, after=None, max=30): + iq_ = common.xmpp.Iq('get') + moified = iq_.setTag('modified', namespace=common.xmpp.NS_ARCHIVE, + attrs={'start': start, 'version': version}) + set_ = retrieve.setTag('set', namespace=common.xmpp.NS_RSM) + set_.setTagData('max', max) + if after: + set_.setTagData('after', after) + self.connection.send(iq_) From b64475a2d96fe6da613dd45527cd63753e447331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=ABl=20Verrier?= Date: Mon, 17 Aug 2009 19:32:17 +0200 Subject: [PATCH 049/200] * first draft for archiving negotiation --- src/chat_control.py | 10 ++- src/common/message_archiving.py | 41 ++++++++++++ src/common/stanza_session.py | 109 +++++++++++++++++++++++++++++++- src/session.py | 71 +++++++++++++-------- 4 files changed, 203 insertions(+), 28 deletions(-) diff --git a/src/chat_control.py b/src/chat_control.py index 03a40c1a4..a489d04c8 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -2477,6 +2477,8 @@ class ChatControl(ChatControlBase): NS_ESESSION) and not gajim.capscache.is_supported( self.contact, 'notexistant'): self.begin_e2e_negotiation() + elif not self.session.accepted: + self.begin_archiving_negotiation() else: self.send_chatstate('active', self.contact) @@ -2710,7 +2712,7 @@ class ChatControl(ChatControlBase): else: self.begin_e2e_negotiation() - def begin_e2e_negotiation(self): + def begin_negotiation(self): self.no_autonegotiation = True if not self.session: @@ -2718,8 +2720,14 @@ class ChatControl(ChatControlBase): new_sess = gajim.connections[self.account].make_new_session(fjid, type_=self.type_id) self.set_session(new_sess) + def begin_e2e_negotiation(self): + self.begin_negotiation() self.session.negotiate_e2e(False) + def begin_archiving_negotiation(self): + self.begin_negotiation() + self.session.negotiate_archiving() + def got_connected(self): ChatControlBase.got_connected(self) # Refreshing contact diff --git a/src/common/message_archiving.py b/src/common/message_archiving.py index 6af85bed1..0bd0d1dba 100644 --- a/src/common/message_archiving.py +++ b/src/common/message_archiving.py @@ -73,6 +73,47 @@ class ConnectionArchive: print iq_ self.connection.send(iq_) + def get_item_pref(self, jid): + jid = common.xmpp.JID(jid) + if unicode(jid) in self.items: + return self.items[jid] + + if jid.getStripped() in self.items: + return self.items[jid.getStripped()] + + if jid.getDomain() in self.items: + return self.items[jid.getDomain()] + + return self.default + + def logging_preference(self, jid, initiator_options=None): + otr = self.get_item_pref(jid)['otr'] + if initiator_options: + if ((initiator_options == ['mustnot'] and otr == 'forbid') or + (initiator_options == ['may'] and otr == 'require')): + return None + + if (initiator_options == ['mustnot'] or + (initiator_options[0] == 'mustnot' and + otr not in ('opppose', 'forbid')) or + (initiator_options == ['may', 'mustnot'] and + otr in ('require', 'prefer'))): + return 'mustnot' + + return 'may' + + if otr == 'require': + return ['mustnot'] + + if otr in ('prefer', 'approve'): + return ['mustnot', 'may'] + + if otr in ('concede', 'oppose'): + return ['may', 'mustnot'] + + # otr == 'forbid' + return ['may'] + def _ArchiveCB(self, con, iq_obj): print '_ArchiveCB', iq_obj.getType() if iq_obj.getType() == 'error': diff --git a/src/common/stanza_session.py b/src/common/stanza_session.py index d5a5ec9e7..b825a931e 100644 --- a/src/common/stanza_session.py +++ b/src/common/stanza_session.py @@ -166,7 +166,114 @@ class StanzaSession(object): self.status = None -class EncryptedStanzaSession(StanzaSession): +class ArchivingStanzaSession(StanzaSession): + def __init__(self, conn, jid, thread_id, type_='chat'): + StanzaSession.__init__(self, conn, jid, thread_id, type_='chat') + self.accepted = False + + def archiving_logging_preference(self, initiator_options=None): + return self.conn.logging_preference(self.jid, initiator_options) + + def negotiate_archiving(self): + self.negotiated = {} + + request = xmpp.Message() + feature = request.NT.feature + feature.setNamespace(xmpp.NS_FEATURE) + + x = xmpp.DataForm(typ='form') + + x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn', + typ='hidden')) + x.addChild(node=xmpp.DataField(name='accept', value='1', typ='boolean', + required=True)) + + x.addChild(node=xmpp.DataField(name='logging', typ='list-single', + options=self.archiving_logging_preference(), required=True)) + + x.addChild(node=xmpp.DataField(name='disclosure', typ='list-single', + options=['never'], required=True)) + x.addChild(node=xmpp.DataField(name='security', typ='list-single', + options=['none'], required=True)) + + feature.addChild(node=x) + + self.status = 'requested' + + self.send(request) + + def respond_archiving_bob(self, form): + field = form.getField('logging') + options = [x[1] for x in field.getOptions()] + values = field.getValues() + + logging = self.archiving_logging_preference(options) + self.negotiated['logging'] = logging + + response = xmpp.Message() + feature = response.NT.feature + feature.setNamespace(xmpp.NS_FEATURE) + + x = xmpp.DataForm(typ='submit') + + x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn')) + x.addChild(node=xmpp.DataField(name='accept', value='true')) + + x.addChild(node=xmpp.DataField(name='logging', value=logging)) + + self.status = 'responded' + + feature.addChild(node=x) + + if not logging: + response = xmpp.Error(response, xmpp.ERR_NOT_ACCEPTABLE) + + feature = xmpp.Node(xmpp.NS_FEATURE + ' feature') + + n = xmpp.Node('field') + n['var'] = 'logging' + feature.addChild(node=n) + + response.T.error.addChild(node=feature) + + self.send(response) + + def accept_archiving_bob(self, form): + if self.negotiated['logging'] == 'mustnot': + self.loggable = False + print 'SESSION ACCEPTED', self.loggable + self.accepted = True + + def accept_archiving_alice(self, form): + negotiated = {} + ask_user = {} + not_acceptable = [] + + if form['logging'] not in self.archiving_logging_preference(): + raise + + self.negotiated['logging'] = form['logging'] + + accept = xmpp.Message() + feature = accept.NT.feature + feature.setNamespace(xmpp.NS_FEATURE) + + result = xmpp.DataForm(typ='result') + + result.addChild(node=xmpp.DataField(name='FORM_TYPE', + value='urn:xmpp:ssn')) + result.addChild(node=xmpp.DataField(name='accept', value='1')) + + feature.addChild(node=result) + + self.send(accept) + if self.negotiated['logging'] == 'mustnot': + self.loggable = False + print 'SESSION ACCEPTED', self.loggable + self.accepted = True + + +class EncryptedStanzaSession(ArchivingStanzaSession): ''' An encrypted stanza negotiation has several states. They arerepresented as the following values in the 'status' attribute of the session object: diff --git a/src/session.py b/src/session.py index 93fbf2375..5af7d4685 100644 --- a/src/session.py +++ b/src/session.py @@ -114,7 +114,6 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): log_type = 'chat_msg_recv' else: log_type = 'single_msg_recv' - if self.is_loggable() and msgtxt: try: msg_id = gajim.logger.write(log_type, full_jid_with_resource, @@ -386,40 +385,53 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): # encrypted session states. these are described in stanza_session.py try: - # bob responds if form.getType() == 'form' and 'security' in form.asDict(): - # we don't support 3-message negotiation as the responder - if 'dhkeys' in form.asDict(): - self.fail_bad_negotiation('3 message negotiation not supported ' - 'when responding', ('dhkeys',)) - return + security_options = [x[1] for x in form.getField('security').getOptions()] + if security_options == ['none']: + self.respond_archiving_bob(form) + else: + # bob responds - negotiated, not_acceptable, ask_user = self.verify_options_bob(form) + # we don't support 3-message negotiation as the responder + if 'dhkeys' in form.asDict(): + self.fail_bad_negotiation('3 message negotiation not supported ' + 'when responding', ('dhkeys',)) + return - if ask_user: - def accept_nondefault_options(is_checked): - self.dialog.destroy() - negotiated.update(ask_user) - self.respond_e2e_bob(form, negotiated, not_acceptable) + negotiated, not_acceptable, ask_user = self.verify_options_bob(form) - def reject_nondefault_options(): - self.dialog.destroy() - for key in ask_user.keys(): - not_acceptable.append(key) - self.respond_e2e_bob(form, negotiated, not_acceptable) + if ask_user: + def accept_nondefault_options(is_checked): + self.dialog.destroy() + negotiated.update(ask_user) + self.respond_e2e_bob(form, negotiated, not_acceptable) - self.dialog = dialogs.YesNoDialog(_('Confirm these session ' - 'options'), - _('''The remote client wants to negotiate an session with these features: + def reject_nondefault_options(): + self.dialog.destroy() + for key in ask_user.keys(): + not_acceptable.append(key) + self.respond_e2e_bob(form, negotiated, not_acceptable) + + self.dialog = dialogs.YesNoDialog(_('Confirm these session ' + 'options'), + _('''The remote client wants to negotiate an session with these features: %s Are these options acceptable?''') % (negotiation.describe_features( - ask_user)), - on_response_yes=accept_nondefault_options, - on_response_no=reject_nondefault_options) - else: - self.respond_e2e_bob(form, negotiated, not_acceptable) + ask_user)), + on_response_yes=accept_nondefault_options, + on_response_no=reject_nondefault_options) + else: + self.respond_e2e_bob(form, negotiated, not_acceptable) + + return + + elif self.status == 'requested' and form.getType() == 'submit': + try: + self.accept_archiving_alice(form) + except exceptions.NegotiationError, details: + self.fail_bad_negotiation(details) return @@ -455,6 +467,13 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): except exceptions.NegotiationError, details: self.fail_bad_negotiation(details) + return + elif self.status == 'responded' and form.getType() == 'result': + try: + self.accept_archiving_bob(form) + except exceptions.NegotiationError, details: + self.fail_bad_negotiation(details) + return elif self.status == 'responded-e2e' and form.getType() == 'result': try: From b38249c4066cf883ceb347b15410d32bd7e3357b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Sat, 14 Nov 2009 23:41:28 +0100 Subject: [PATCH 050/200] Added core events handlers to GED in src/gui_interface.py (previously kept in src/gajim.py). --- src/common/zeroconf/connection_zeroconf.py | 3 +- src/gui_interface.py | 52 +++++++++++----------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/common/zeroconf/connection_zeroconf.py b/src/common/zeroconf/connection_zeroconf.py index c5a93b4b8..26c7fbf63 100644 --- a/src/common/zeroconf/connection_zeroconf.py +++ b/src/common/zeroconf/connection_zeroconf.py @@ -137,7 +137,8 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf): # END __init__ def dispatch(self, event, data): - gajim.interface.dispatch(event, self.name, data) + #gajim.interface.dispatch(event, self.name, data) + gajim.ged.raise_event(event, self.name, data) def _reconnect(self): # Do not try to reco while we are already trying diff --git a/src/gui_interface.py b/src/gui_interface.py index 812aba08a..8e1243264 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -85,6 +85,7 @@ import roster_window import profile_window import config from threading import Thread +from common import ged gajimpaths = common.configpaths.gajimpaths config_filename = gajimpaths['CONFIG_FILE'] @@ -1988,18 +1989,8 @@ class Interface: dialogs.WarningDialog(_('PEP node was not removed'), _('PEP node %(node)s was not removed: %(message)s') % { 'node': data[1], 'message': data[2]}) - - def register_handler(self, event, handler): - if event not in self.handlers: - self.handlers[event] = [] - if handler not in self.handlers[event]: - self.handlers[event].append(handler) - - def unregister_handler(self, event, handler): - self.handlers[event].remove(handler) - - def register_handlers(self): + def create_core_handlers_list(self): self.handlers = { 'ROSTER': [self.handle_event_roster], 'WARNING': [self.handle_event_warning], @@ -2089,22 +2080,18 @@ class Interface: 'JINGLE_DISCONNECTED': [self.handle_event_jingle_disconnected], 'JINGLE_ERROR': [self.handle_event_jingle_error], } - - def dispatch(self, event, account, data): + + def register_core_handlers(self): ''' - Dispatches an network event to the event handlers of this class. - - Return true if it could be dispatched to alteast one handler. + Register core handlers in Global Events Dispatcher (GED). + + This is part of rewriting whole events handling system to use GED. ''' - if event not in self.handlers: - log.warning('Unknown event %s dispatched to GUI: %s' % (event, data)) - return False - else: - log.debug('Event %s distpached to GUI: %s' % (event, data)) - for handler in self.handlers[event]: - handler(account, data) - return len(self.handlers[event]) - + for event_name, event_handlers in self.handlers.iteritems(): + for event_handler in event_handlers: + gajim.ged.register_event_handler(event_name, + ged.CORE, + event_handler) ################################################################################ ### Methods dealing with gajim.events @@ -3142,6 +3129,10 @@ class Interface: pass gobject.timeout_add_seconds(5, remote_init) + # Creating plugin manager + import plugins + gajim.plugin_manager = plugins.PluginManager() + def __init__(self): gajim.interface = self @@ -3235,7 +3226,16 @@ class Interface: self.handle_event_file_error) gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue) gajim.default_session_type = ChatControlSession - self.register_handlers() + + # Creating Global Events Dispatcher + from common import ged + gajim.ged = ged.GlobalEventsDispatcher() + # Creating Network Events Controller + from common import nec + gajim.nec = nec.NetworkEventsController() + self.create_core_handlers_list() + self.register_core_handlers() + if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \ and gajim.HAVE_ZEROCONF: gajim.connections[gajim.ZEROCONF_ACC_NAME] = \ From 72420b0e73132d7e9b985ea891168e7c8dd7a19a Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Sun, 15 Nov 2009 11:33:05 +0100 Subject: [PATCH 051/200] write code to synchronise server logs from archiving with local DB --- src/common/connection_handlers.py | 69 +++++++++++++++++++++++++++++++ src/common/logger.py | 30 ++++++++++++++ src/common/message_archiving.py | 20 ++++++--- src/gui_interface.py | 1 + 4 files changed, 115 insertions(+), 5 deletions(-) diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index 2d62d6000..d8f9314b6 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -53,6 +53,9 @@ from common.commands import ConnectionCommands from common.pubsub import ConnectionPubSub from common.caps import ConnectionCaps from common.message_archiving import ConnectionArchive +from common.message_archiving import ARCHIVING_COLLECTIONS_ARRIVED +from common.message_archiving import ARCHIVING_COLLECTION_ARRIVED +from common.message_archiving import ARCHIVING_MODIFICATIONS_ARRIVED if gajim.HAVE_FARSIGHT: from common.jingle import ConnectionJingle @@ -1235,6 +1238,72 @@ class ConnectionVcard: form = common.dataforms.ExtendForm(node=form_tag) self.dispatch('PEP_CONFIG', (node, form)) + elif self.awaiting_answers[id_][0] == ARCHIVING_COLLECTIONS_ARRIVED: + # TODO + pass + + elif self.awaiting_answers[id_][0] == ARCHIVING_COLLECTION_ARRIVED: + def save_if_not_exists(with_, direction, tim, payload): + assert len(payload) == 1, 'got several archiving messages in the' +\ + ' same time %s' % ''.join(payload) + if payload[0].getName() == 'body': + gajim.logger.save_if_not_exists(with_, direction, tim, + msg=payload[0].getData()) + elif payload[0].getName() == 'message': + print 'Not implemented' + chat = iq_obj.getTag('chat') + if chat: + with_ = chat.getAttr('with') + start_ = chat.getAttr('start') + tim = helpers.datetime_tuple(start_) + tim = timegm(tim) + nb = 0 + for element in chat.getChildren(): + try: + secs = int(element.getAttr('secs')) + except TypeError: + secs = 0 + if secs: + tim += secs + if element.getName() == 'from': + save_if_not_exists(with_, 'from', localtime(tim), + element.getPayload()) + nb += 1 + if element.getName() == 'to': + save_if_not_exists(with_, 'to', localtime(tim), + element.getPayload()) + nb += 1 + set_ = chat.getTag('set') + first = set_.getTag('first') + if first: + try: + index = int(first.getAttr('index')) + except TypeError: + index = 0 + try: + count = int(set_.getTagData('count')) + except TypeError: + count = 0 + if count > index + nb: + # Request the next page + try: + after = int(element.getTagData('last')) + except TypeError: + after = index + nb + self.request_collection_page(with_, start_, after=after) + + elif self.awaiting_answers[id_][0] == ARCHIVING_MODIFICATIONS_ARRIVED: + modified = iq_obj.getTag('modified') + if modified: + for element in modified.getChildren(): + if element.getName() == 'changed': + with_ = element.getAttr('with') + start_ = element.getAttr('start') + self.request_collection_page(with_, start_) + elif element.getName() == 'removed': + # do nothing + pass + del self.awaiting_answers[id_] def _vCardCB(self, con, vc): diff --git a/src/common/logger.py b/src/common/logger.py index c96282743..34634be6e 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -981,4 +981,34 @@ class Logger: (account_jid_id,)) self.con.commit() + def save_if_not_exists(self, with_, direction, tim, msg=''): + if tim: + time_col = int(float(time.mktime(tim))) + else: + time_col = int(float(time.time())) + if msg: + if self.jid_is_from_pm(with_): + # We cannot know if it's a pm or groupchat message because we only + # get body of the message + type_ = 'gc_msg' + else: + if direction == 'from': + type_ = 'chat_msg_recv' + elif direction == 'to': + type_ = 'chat_msg_sent' + jid_id = self.get_jid_id(with_) + where_sql = 'jid_id = %s AND message="%s"' % (jid_id, msg) + start_time = time_col - 300 # 5 minutes arrount given time + end_time = time_col + 300 # 5 minutes arrount given time + self.cur.execute(''' + SELECT log_line_id FROM logs + WHERE (%s) + AND time BETWEEN %d AND %d + ORDER BY time + ''' % (where_sql, start_time, end_time)) + results = self.cur.fetchall() + if results: + return + self.write(type_, with_, message=msg, tim=tim) + # vim: se ts=3: diff --git a/src/common/message_archiving.py b/src/common/message_archiving.py index 0bd0d1dba..bcb28fcc1 100644 --- a/src/common/message_archiving.py +++ b/src/common/message_archiving.py @@ -20,6 +20,9 @@ import common.xmpp +ARCHIVING_COLLECTIONS_ARRIVED = 'archiving_collections_arrived' +ARCHIVING_COLLECTION_ARRIVED = 'archiving_collection_arrived' +ARCHIVING_MODIFICATIONS_ARRIVED = 'archiving_modifications_arrived' class ConnectionArchive: def __init__(self): @@ -192,6 +195,9 @@ class ConnectionArchive: set_.setTagData('max', max) if after: set_.setTagData('after', after) + id_ = self.connection.getAnID() + iq_.setID(id_) + self.awaiting_answers[id_] = (ARCHIVING_COLLECTIONS_ARRIVED, ) self.connection.send(iq_) def request_collection_page(self, with, start, end=None, after=None, @@ -205,6 +211,9 @@ class ConnectionArchive: set_.setTagData('max', max) if after: set_.setTagData('after', after) + id_ = self.connection.getAnID() + iq_.setID(id_) + self.awaiting_answers[id_] = (ARCHIVING_COLLECTION_ARRIVED, ) self.connection.send(iq_) def remove_collection(self, with='', start=None, end=None, @@ -223,12 +232,13 @@ class ConnectionArchive: remove.setAttr('open', 'true') self.connection.send(iq_) - def request_modifications_page(self, start, version, after=None, max=30): + def request_modifications_page(self, start, max=30): iq_ = common.xmpp.Iq('get') moified = iq_.setTag('modified', namespace=common.xmpp.NS_ARCHIVE, - attrs={'start': start, 'version': version}) - set_ = retrieve.setTag('set', namespace=common.xmpp.NS_RSM) + attrs={'start': start}) + set_ = moified.setTag('set', namespace=common.xmpp.NS_RSM) set_.setTagData('max', max) - if after: - set_.setTagData('after', after) + id_ = self.connection.getAnID() + iq_.setID(id_) + self.awaiting_answers[id_] = (ARCHIVING_MODIFICATIONS_ARRIVED, ) self.connection.send(iq_) diff --git a/src/gui_interface.py b/src/gui_interface.py index bff913616..df0cedef6 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -3405,6 +3405,7 @@ class Interface: self.last_ftwindow_update = 0 self.music_track_changed_signal = None + self.create_ipython_window() class PassphraseRequest: From 623a51d53fdecdfd7aad31a4e58181de87860c71 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Sun, 15 Nov 2009 15:37:41 +0100 Subject: [PATCH 052/200] fix bug when merging server logs of MUC conversations --- src/common/connection_handlers.py | 9 +++++---- src/common/logger.py | 15 ++++++++++----- src/common/message_archiving.py | 16 ++++++++-------- src/gui_interface.py | 1 - 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index d8f9314b6..856653eb2 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -1243,12 +1243,12 @@ class ConnectionVcard: pass elif self.awaiting_answers[id_][0] == ARCHIVING_COLLECTION_ARRIVED: - def save_if_not_exists(with_, direction, tim, payload): + def save_if_not_exists(with_, nick, direction, tim, payload): assert len(payload) == 1, 'got several archiving messages in the' +\ ' same time %s' % ''.join(payload) if payload[0].getName() == 'body': gajim.logger.save_if_not_exists(with_, direction, tim, - msg=payload[0].getData()) + msg=payload[0].getData(), nick=nick) elif payload[0].getName() == 'message': print 'Not implemented' chat = iq_obj.getTag('chat') @@ -1265,12 +1265,13 @@ class ConnectionVcard: secs = 0 if secs: tim += secs + nick = element.getAttr('name') if element.getName() == 'from': - save_if_not_exists(with_, 'from', localtime(tim), + save_if_not_exists(with_, nick, 'from', localtime(tim), element.getPayload()) nb += 1 if element.getName() == 'to': - save_if_not_exists(with_, 'to', localtime(tim), + save_if_not_exists(with_, nick, 'to', localtime(tim), element.getPayload()) nb += 1 set_ = chat.getTag('set') diff --git a/src/common/logger.py b/src/common/logger.py index 34634be6e..0ab6647c2 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -981,16 +981,21 @@ class Logger: (account_jid_id,)) self.con.commit() - def save_if_not_exists(self, with_, direction, tim, msg=''): + def save_if_not_exists(self, with_, direction, tim, msg='', nick=None): if tim: time_col = int(float(time.mktime(tim))) else: time_col = int(float(time.time())) if msg: - if self.jid_is_from_pm(with_): - # We cannot know if it's a pm or groupchat message because we only - # get body of the message - type_ = 'gc_msg' + if self.jid_is_room_jid(with_) or nick: + # It's a groupchat message + if nick: + # It's a message from a groupchat occupent + type_ = 'gc_msg' + with_ = with_ + '/' + nick + else: + # It's a server message message, we don't log them + return else: if direction == 'from': type_ = 'chat_msg_recv' diff --git a/src/common/message_archiving.py b/src/common/message_archiving.py index bcb28fcc1..a029d7086 100644 --- a/src/common/message_archiving.py +++ b/src/common/message_archiving.py @@ -179,12 +179,12 @@ class ConnectionArchive: self.dispatch('ARCHIVING_CHANGED', ('itemremove', item.getAttr('jid'))) - def request_collections_list_page(self, with='', start=None, end=None, + def request_collections_list_page(self, with_='', start=None, end=None, after=None, max=30, exact_match=False): iq_ = common.xmpp.Iq('get') list_ = iq_.setTag('list', namespace=common.xmpp.NS_ARCHIVE) - if with: - list_.setAttr('with', with) + if with_: + list_.setAttr('with', with_) if exact_match: list_.setAttr('exactmatch', 'true') if start: @@ -200,11 +200,11 @@ class ConnectionArchive: self.awaiting_answers[id_] = (ARCHIVING_COLLECTIONS_ARRIVED, ) self.connection.send(iq_) - def request_collection_page(self, with, start, end=None, after=None, + def request_collection_page(self, with_, start, end=None, after=None, max=30, exact_match=False): iq_ = common.xmpp.Iq('get') retrieve = iq_.setTag('retrieve', namespace=common.xmpp.NS_ARCHIVE, - attrs={'with': with, 'start': start}) + attrs={'with': with_, 'start': start}) if exact_match: retrieve.setAttr('exactmatch', 'true') set_ = retrieve.setTag('set', namespace=common.xmpp.NS_RSM) @@ -216,12 +216,12 @@ class ConnectionArchive: self.awaiting_answers[id_] = (ARCHIVING_COLLECTION_ARRIVED, ) self.connection.send(iq_) - def remove_collection(self, with='', start=None, end=None, + def remove_collection(self, with_='', start=None, end=None, exact_match=False, open=False): iq_ = common.xmpp.Iq('set') remove = iq_.setTag('remove', namespace=common.xmpp.NS_ARCHIVE) - if with: - remove.setAttr('with', with) + if with_: + remove.setAttr('with', with_) if exact_match: remove.setAttr('exactmatch', 'true') if start: diff --git a/src/gui_interface.py b/src/gui_interface.py index df0cedef6..bff913616 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -3405,7 +3405,6 @@ class Interface: self.last_ftwindow_update = 0 self.music_track_changed_signal = None - self.create_ipython_window() class PassphraseRequest: From d8405f6e9f140f6829742b0e1f935b7e7f53c16d Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Wed, 25 Nov 2009 19:53:17 +0100 Subject: [PATCH 053/200] prevent to merge several times the same groupchat message. We cannot differentiate pm and gc messages when we only store . So we need to look in both to know if we already have it in logs --- src/common/logger.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/common/logger.py b/src/common/logger.py index 0ab6647c2..c853287b0 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -48,6 +48,9 @@ import configpaths LOG_DB_PATH = configpaths.gajimpaths['LOG_DB'] LOG_DB_FOLDER, LOG_DB_FILE = os.path.split(LOG_DB_PATH) +import logging +log = logging.getLogger('gajim.c.logger') + class Constants: def __init__(self): ( @@ -143,7 +146,7 @@ class Logger: else: self.cur.execute("PRAGMA synchronous = OFF") except sqlite.Error, e: - gajim.log.debug("Failed to set_synchronous(%s): %s" % (sync, str(e))) + log.debug("Failed to set_synchronous(%s): %s" % (sync, str(e))) def init_vars(self): self.open_db() @@ -1003,6 +1006,14 @@ class Logger: type_ = 'chat_msg_sent' jid_id = self.get_jid_id(with_) where_sql = 'jid_id = %s AND message="%s"' % (jid_id, msg) + if type_ == 'gc_msg': + # We cannot differentiate gc message and pm messages, so look in both + # logs + with_2 = gajim.get_jid_without_resource(with_) + if with_ != with_2: + jid_id2 = self.get_jid_id(with_2) + where_sql = 'jid_id in (%s, %s) AND message="%s"' % (jid_id, + jid_id2, msg) start_time = time_col - 300 # 5 minutes arrount given time end_time = time_col + 300 # 5 minutes arrount given time self.cur.execute(''' @@ -1013,7 +1024,9 @@ class Logger: ''' % (where_sql, start_time, end_time)) results = self.cur.fetchall() if results: + log.debug('Log already in DB, ignoring it') return + log.debug('New log received from server archives, storing it') self.write(type_, with_, message=msg, tim=tim) # vim: se ts=3: From bbd740b62f390a636fb43297e7d6455df5c9b9ac Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Wed, 25 Nov 2009 21:15:56 +0100 Subject: [PATCH 054/200] merge logs at connection time --- src/gui_interface.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gui_interface.py b/src/gui_interface.py index 76ab68c0c..864107671 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -1561,6 +1561,9 @@ class Interface: if gajim.connections[account].pep_supported and dbus_support.supported \ and gajim.config.get_per('accounts', account, 'publish_tune'): self.enable_music_listener() + # Start merging logs from server + gajim.connections[account].request_modifications_page( + '1970-01-01T00:00:00Z') def handle_event_metacontacts(self, account, tags_list): gajim.contacts.define_metacontacts(account, tags_list) From 3d4688e9fac9e929016dafc166edee8bc1cb20e3 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Thu, 26 Nov 2009 08:27:31 +0100 Subject: [PATCH 055/200] request archiving logs only since last time we requested it --- src/common/config.py | 1 + src/gui_interface.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/common/config.py b/src/common/config.py index 92279c6d3..d49613b59 100644 --- a/src/common/config.py +++ b/src/common/config.py @@ -352,6 +352,7 @@ class Config: 'send_os_info': [ opt_bool, True ], 'log_encrypted_sessions': [opt_bool, True, _('When negotiating an encrypted session, should Gajim assume you want your messages to be logged?')], 'roster_version': [opt_str, ''], + 'last_archiving_time': [opt_str, '1970-01-01T00:00:00Z', _('Last time we syncronized with logs from server.')], }, {}), 'statusmsg': ({ 'message': [ opt_str, '' ], diff --git a/src/gui_interface.py b/src/gui_interface.py index 864107671..e4dc996d1 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -1563,7 +1563,9 @@ class Interface: self.enable_music_listener() # Start merging logs from server gajim.connections[account].request_modifications_page( - '1970-01-01T00:00:00Z') + gajim.config.get_per('accounts', account, 'last_archiving_time')) + gajim.config.set_per('accounts', account, 'last_archiving_time', + time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())) def handle_event_metacontacts(self, account, tags_list): gajim.contacts.define_metacontacts(account, tags_list) From 5a3ef285a59ac020b16d6ef3eb45d02099936d44 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Wed, 2 Dec 2009 11:52:49 +0100 Subject: [PATCH 056/200] fix some archiving session negotiation bugs print archving negotiation result in chat control --- src/chat_control.py | 22 ++++++++++++++++++++-- src/common/connection_handlers.py | 5 +---- src/common/stanza_session.py | 18 ++++++++++++------ src/message_control.py | 12 +++++++++--- src/session.py | 6 ++++-- 5 files changed, 46 insertions(+), 17 deletions(-) diff --git a/src/chat_control.py b/src/chat_control.py index 7850a2e3e..7f754081b 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -46,6 +46,7 @@ from common import exceptions from message_control import MessageControl from conversation_textview import ConversationTextview from message_textview import MessageTextView +from common.stanza_session import EncryptedStanzaSession, ArchivingStanzaSession from common.contacts import GC_Contact from common.logger import constants from common.pep import MOODS, ACTIVITIES @@ -1394,7 +1395,7 @@ class ChatControl(ChatControlBase): self.session = session if session.enable_encryption: - self.print_esession_details() + self.print_session_details() # Enable encryption if needed self.no_autonegotiation = False @@ -2055,6 +2056,17 @@ class ChatControl(ChatControlBase): msg = _('Session negotiation cancelled') ChatControlBase.print_conversation_line(self, msg, 'status', '', None) + def print_archiving_session_details(self): + """ + Print esession settings to textview + """ + archiving = bool(self.session) and self.session.archiving + if archiving: + msg = _('This session WILL be archived on server') + else: + msg = _('This session WILL NOT be archived on server') + ChatControlBase.print_conversation_line(self, msg, 'status', '', None) + def print_esession_details(self): """ Print esession settings to textview @@ -2079,6 +2091,12 @@ class ChatControl(ChatControlBase): self._show_lock_image(e2e_is_active, 'E2E', e2e_is_active, self.session and \ self.session.is_loggable(), self.session and self.session.verified_identity) + def print_session_details(self): + if isinstance(self.session, EncryptedStanzaSession): + self.print_esession_details() + elif isinstance(self.session, ArchivingStanzaSession): + self.print_archiving_session_details() + def print_conversation(self, text, frm='', tim=None, encrypted=False, subject=None, xhtml=None, simple=False, xep0184_id=None): """ @@ -2511,7 +2529,7 @@ class ChatControl(ChatControlBase): if want_e2e and not self.no_autonegotiation \ and gajim.HAVE_PYCRYPTO and self.contact.supports(NS_ESESSION): self.begin_e2e_negotiation() - elif not self.session.accepted: + elif not self.session or not self.session.status: self.begin_archiving_negotiation() else: self.send_chatstate('active', self.contact) diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index d4a89eca0..46e6b48fc 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -1297,10 +1297,7 @@ class ConnectionVcard: count = 0 if count > index + nb: # Request the next page - try: - after = int(element.getTagData('last')) - except TypeError: - after = index + nb + after = element.getTagData('last') self.request_collection_page(with_, start_, after=after) elif self.awaiting_answers[id_][0] == ARCHIVING_MODIFICATIONS_ARRIVED: diff --git a/src/common/stanza_session.py b/src/common/stanza_session.py index 40e187cef..5ac08db8f 100644 --- a/src/common/stanza_session.py +++ b/src/common/stanza_session.py @@ -177,7 +177,7 @@ class StanzaSession(object): class ArchivingStanzaSession(StanzaSession): def __init__(self, conn, jid, thread_id, type_='chat'): StanzaSession.__init__(self, conn, jid, thread_id, type_='chat') - self.accepted = False + self.archiving = False def archiving_logging_preference(self, initiator_options=None): return self.conn.logging_preference(self.jid, initiator_options) @@ -206,7 +206,7 @@ class ArchivingStanzaSession(StanzaSession): feature.addChild(node=x) - self.status = 'requested' + self.status = 'requested-archiving' self.send(request) @@ -229,7 +229,7 @@ class ArchivingStanzaSession(StanzaSession): x.addChild(node=xmpp.DataField(name='logging', value=logging)) - self.status = 'responded' + self.status = 'responded-archiving' feature.addChild(node=x) @@ -250,7 +250,10 @@ class ArchivingStanzaSession(StanzaSession): if self.negotiated['logging'] == 'mustnot': self.loggable = False print 'SESSION ACCEPTED', self.loggable - self.accepted = True + self.status = 'active' + self.archiving = True + if self.control: + self.control.print_archiving_session_details() def accept_archiving_alice(self, form): negotiated = {} @@ -278,7 +281,10 @@ class ArchivingStanzaSession(StanzaSession): if self.negotiated['logging'] == 'mustnot': self.loggable = False print 'SESSION ACCEPTED', self.loggable - self.accepted = True + self.status = 'active' + self.archiving = True + if self.control: + self.control.print_archiving_session_details() class EncryptedStanzaSession(ArchivingStanzaSession): @@ -307,7 +313,7 @@ class EncryptedStanzaSession(ArchivingStanzaSession): handle_session_negotiation method. ''' def __init__(self, conn, jid, thread_id, type_='chat'): - StanzaSession.__init__(self, conn, jid, thread_id, type_='chat') + ArchivingStanzaSession.__init__(self, conn, jid, thread_id, type_='chat') self.xes = {} self.es = {} diff --git a/src/message_control.py b/src/message_control.py index 7ee724554..e1d3f4919 100644 --- a/src/message_control.py +++ b/src/message_control.py @@ -30,6 +30,7 @@ import gtkgui_helpers from common import gajim from common import helpers +from common.stanza_session import EncryptedStanzaSession, ArchivingStanzaSession # Derived types MUST register their type IDs here if custom behavor is required TYPE_CHAT = 'chat' @@ -163,11 +164,16 @@ class MessageControl: if self.resource: jid += '/' + self.resource - crypto_changed = bool(session and session.enable_encryption) != \ + crypto_changed = bool(session and isinstance(session, + EncryptedStanzaSession) and session.enable_encryption) != \ bool(oldsession and oldsession.enable_encryption) - if crypto_changed: - self.print_esession_details() + archiving_changed = bool(session and isinstance(session, + ArchivingStanzaSession) and session.archiving) != \ + bool(oldsession and oldsession.archiving) + + if crypto_changed or archiving_changed: + self.print_session_details() def send_message(self, message, keyID='', type_='chat', chatstate=None, msg_id=None, composing_xep=None, resource=None, user_nick=None, xhtml=None, diff --git a/src/session.py b/src/session.py index 614bb88ef..3a84976c2 100644 --- a/src/session.py +++ b/src/session.py @@ -440,7 +440,8 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): return - elif self.status == 'requested' and form.getType() == 'submit': + elif self.status == 'requested-archiving' and form.getType() == \ + 'submit': try: self.accept_archiving_alice(form) except exceptions.NegotiationError, details: @@ -481,7 +482,8 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): self.fail_bad_negotiation(details) return - elif self.status == 'responded' and form.getType() == 'result': + elif self.status == 'responded-archiving' and form.getType() == \ + 'result': try: self.accept_archiving_bob(form) except exceptions.NegotiationError, details: From b739802fd7fe0dbc846b938d875994f2b51245b1 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Wed, 2 Dec 2009 12:05:52 +0100 Subject: [PATCH 057/200] convert print to log.debug --- src/common/message_archiving.py | 25 +++++++++++++++---------- src/common/stanza_session.py | 4 ++-- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/common/message_archiving.py b/src/common/message_archiving.py index a029d7086..b7318b500 100644 --- a/src/common/message_archiving.py +++ b/src/common/message_archiving.py @@ -20,6 +20,9 @@ import common.xmpp +import logging +log = logging.getLogger('gajim.c.message_archiving') + ARCHIVING_COLLECTIONS_ARRIVED = 'archiving_collections_arrived' ARCHIVING_COLLECTION_ARRIVED = 'archiving_collection_arrived' ARCHIVING_MODIFICATIONS_ARRIVED = 'archiving_modifications_arrived' @@ -40,7 +43,6 @@ class ConnectionArchive: def request_message_archiving_preferences(self): iq_ = common.xmpp.Iq('get') iq_.setTag('pref', namespace=common.xmpp.NS_ARCHIVE) - print iq_ self.connection.send(iq_) def set_pref(self, name, **data): @@ -53,7 +55,6 @@ class ConnectionArchive: for key, value in data.items(): if value is not None: tag.setAttr(key, value) - print iq_ self.connection.send(iq_) def set_auto(self, save): @@ -73,7 +74,6 @@ class ConnectionArchive: itemremove = iq_.setTag('itemremove', namespace=common.xmpp.NS_ARCHIVE) item = itemremove.setTag('item') item.setAttr('jid', jid) - print iq_ self.connection.send(iq_) def get_item_pref(self, jid): @@ -118,12 +118,11 @@ class ConnectionArchive: return ['may'] def _ArchiveCB(self, con, iq_obj): - print '_ArchiveCB', iq_obj.getType() + log.debug('_ArchiveCB %s' % iq_obj.getType()) if iq_obj.getType() == 'error': self.dispatch('ARCHIVING_ERROR', iq_obj.getErrorMsg()) return elif iq_obj.getType() not in ('result', 'set'): - print iq_obj return if iq_obj.getTag('pref'): @@ -131,7 +130,7 @@ class ConnectionArchive: if pref.getTag('auto'): self.auto = pref.getTagAttr('auto', 'save') - print 'auto:', self.auto + log.debug('archiving preference: auto: %s' % self.auto) self.dispatch('ARCHIVING_CHANGED', ('auto', self.auto)) @@ -153,11 +152,16 @@ class ConnectionArchive: self.dispatch('ARCHIVING_CHANGED', ('method_manual', self.method_manual)) - print 'method alm:', self.method_auto, self.method_local, self.method_manual + log.debug('archiving preferences: method auto: %s, local: %s, ' + 'manual: %s' % (self.method_auto, self.method_local, + self.method_manual)) if pref.getTag('default'): default = pref.getTag('default') - print 'default oseu:', default.getAttr('otr'), default.getAttr('save'), default.getAttr('expire'), default.getAttr('unset') + log.debug('archiving preferences: default otr: %s, save: %s, ' + 'expire: %s, unset: %s' % (default.getAttr('otr'), + default.getAttr('save'), default.getAttr('expire'), + default.getAttr('unset'))) self.default = { 'expire': default.getAttr('expire'), 'otr': default.getAttr('otr'), @@ -166,7 +170,9 @@ class ConnectionArchive: self.dispatch('ARCHIVING_CHANGED', ('default', self.default)) for item in pref.getTags('item'): - print item.getAttr('jid'), item.getAttr('otr'), item.getAttr('save'), item.getAttr('expire') + log.debug('archiving preferences for jid %s: otr: %s, save: %s, ' + 'expire: %s' % (item.getAttr('jid'), item.getAttr('otr'), + item.getAttr('save'), item.getAttr('expire'))) self.items[item.getAttr('jid')] = { 'expire': item.getAttr('expire'), 'otr': item.getAttr('otr'), 'save': item.getAttr('save')} @@ -174,7 +180,6 @@ class ConnectionArchive: item.getAttr('jid'), self.items[item.getAttr('jid')])) elif iq_obj.getTag('itemremove'): for item in pref.getTags('item'): - print 'del', item.getAttr('jid') del self.items[item.getAttr('jid')] self.dispatch('ARCHIVING_CHANGED', ('itemremove', item.getAttr('jid'))) diff --git a/src/common/stanza_session.py b/src/common/stanza_session.py index 5ac08db8f..0d20672ce 100644 --- a/src/common/stanza_session.py +++ b/src/common/stanza_session.py @@ -249,7 +249,7 @@ class ArchivingStanzaSession(StanzaSession): def accept_archiving_bob(self, form): if self.negotiated['logging'] == 'mustnot': self.loggable = False - print 'SESSION ACCEPTED', self.loggable + log.debug('archiving session accepted: %s' % self.loggable) self.status = 'active' self.archiving = True if self.control: @@ -280,7 +280,7 @@ class ArchivingStanzaSession(StanzaSession): self.send(accept) if self.negotiated['logging'] == 'mustnot': self.loggable = False - print 'SESSION ACCEPTED', self.loggable + log.debug('archiving session accepted: %s' % self.loggable) self.status = 'active' self.archiving = True if self.control: From 3f4f07d3267a82903bdab42d15a29e341f913cff Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Thu, 3 Dec 2009 18:59:10 +0100 Subject: [PATCH 058/200] correctly escape msgs when we search in database --- src/common/logger.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/common/logger.py b/src/common/logger.py index c853287b0..9fd3e2dd2 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -1005,15 +1005,14 @@ class Logger: elif direction == 'to': type_ = 'chat_msg_sent' jid_id = self.get_jid_id(with_) - where_sql = 'jid_id = %s AND message="%s"' % (jid_id, msg) + where_sql = 'jid_id = %s AND message=?' % (jid_id) if type_ == 'gc_msg': # We cannot differentiate gc message and pm messages, so look in both # logs with_2 = gajim.get_jid_without_resource(with_) if with_ != with_2: jid_id2 = self.get_jid_id(with_2) - where_sql = 'jid_id in (%s, %s) AND message="%s"' % (jid_id, - jid_id2, msg) + where_sql = 'jid_id in (%s, %s) AND message=?' % (jid_id, jid_id2) start_time = time_col - 300 # 5 minutes arrount given time end_time = time_col + 300 # 5 minutes arrount given time self.cur.execute(''' @@ -1021,7 +1020,7 @@ class Logger: WHERE (%s) AND time BETWEEN %d AND %d ORDER BY time - ''' % (where_sql, start_time, end_time)) + ''' % (where_sql, start_time, end_time), (msg,)) results = self.cur.fetchall() if results: log.debug('Log already in DB, ignoring it') From 47ff962e5b25f45f8760443075057530a2cb55ab Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Tue, 8 Dec 2009 17:09:41 +0100 Subject: [PATCH 059/200] rename some archiving functions --- src/common/stanza_session.py | 6 +++--- src/session.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/common/stanza_session.py b/src/common/stanza_session.py index 0d20672ce..5a4f954b9 100644 --- a/src/common/stanza_session.py +++ b/src/common/stanza_session.py @@ -210,7 +210,7 @@ class ArchivingStanzaSession(StanzaSession): self.send(request) - def respond_archiving_bob(self, form): + def respond_archiving(self, form): field = form.getField('logging') options = [x[1] for x in field.getOptions()] values = field.getValues() @@ -246,7 +246,7 @@ class ArchivingStanzaSession(StanzaSession): self.send(response) - def accept_archiving_bob(self, form): + def we_accept_archiving(self, form): if self.negotiated['logging'] == 'mustnot': self.loggable = False log.debug('archiving session accepted: %s' % self.loggable) @@ -255,7 +255,7 @@ class ArchivingStanzaSession(StanzaSession): if self.control: self.control.print_archiving_session_details() - def accept_archiving_alice(self, form): + def archiving_accepted(self, form): negotiated = {} ask_user = {} not_acceptable = [] diff --git a/src/session.py b/src/session.py index 3a84976c2..6a830fe13 100644 --- a/src/session.py +++ b/src/session.py @@ -401,7 +401,7 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): if form.getType() == 'form' and 'security' in form.asDict(): security_options = [x[1] for x in form.getField('security').getOptions()] if security_options == ['none']: - self.respond_archiving_bob(form) + self.respond_archiving(form) else: # bob responds @@ -443,7 +443,7 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): elif self.status == 'requested-archiving' and form.getType() == \ 'submit': try: - self.accept_archiving_alice(form) + self.archiving_accepted(form) except exceptions.NegotiationError, details: self.fail_bad_negotiation(details) @@ -485,7 +485,7 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): elif self.status == 'responded-archiving' and form.getType() == \ 'result': try: - self.accept_archiving_bob(form) + self.we_accept_archiving(form) except exceptions.NegotiationError, details: self.fail_bad_negotiation(details) From d1a9a6983a7f120a55a668f8165da802e43586e9 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Thu, 10 Dec 2009 17:40:48 +0100 Subject: [PATCH 060/200] EncryptedSession cannot be an ArchivingSession --- src/chat_control.py | 3 ++- src/common/stanza_session.py | 4 ++-- src/message_control.py | 6 ++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/chat_control.py b/src/chat_control.py index 7f754081b..977214875 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -2060,7 +2060,8 @@ class ChatControl(ChatControlBase): """ Print esession settings to textview """ - archiving = bool(self.session) and self.session.archiving + archiving = bool(self.session) and isinstance(self.session, + ArchivingStanzaSession) and self.session.archiving if archiving: msg = _('This session WILL be archived on server') else: diff --git a/src/common/stanza_session.py b/src/common/stanza_session.py index 5a4f954b9..e3e8b84a4 100644 --- a/src/common/stanza_session.py +++ b/src/common/stanza_session.py @@ -287,7 +287,7 @@ class ArchivingStanzaSession(StanzaSession): self.control.print_archiving_session_details() -class EncryptedStanzaSession(ArchivingStanzaSession): +class EncryptedStanzaSession(StanzaSession): ''' An encrypted stanza negotiation has several states. They arerepresented as the following values in the 'status' attribute of the session object: @@ -313,7 +313,7 @@ class EncryptedStanzaSession(ArchivingStanzaSession): handle_session_negotiation method. ''' def __init__(self, conn, jid, thread_id, type_='chat'): - ArchivingStanzaSession.__init__(self, conn, jid, thread_id, type_='chat') + StanzaSession.__init__(self, conn, jid, thread_id, type_='chat') self.xes = {} self.es = {} diff --git a/src/message_control.py b/src/message_control.py index e1d3f4919..ef6bc2868 100644 --- a/src/message_control.py +++ b/src/message_control.py @@ -166,11 +166,13 @@ class MessageControl: crypto_changed = bool(session and isinstance(session, EncryptedStanzaSession) and session.enable_encryption) != \ - bool(oldsession and oldsession.enable_encryption) + bool(oldsession and isinstance(oldsession, EncryptedStanzaSession) and\ + oldsession.enable_encryption) archiving_changed = bool(session and isinstance(session, ArchivingStanzaSession) and session.archiving) != \ - bool(oldsession and oldsession.archiving) + bool(oldsession and isinstance(oldsession, ArchivingStanzaSession) and\ + oldsession.archiving) if crypto_changed or archiving_changed: self.print_session_details() From 0408b35cfb1d92217d53d2a6e512c56e2206bf98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Araujo?= Date: Wed, 10 Feb 2010 17:59:17 +0100 Subject: [PATCH 061/200] convert tabs to spaces in source code thanks to reindent.py also use 2to3 -f ws_comma to fix some whitespace --- data/emoticons/animated/emoticons.py | 96 +- data/emoticons/static-big/emoticons.py | 82 +- data/emoticons/static/emoticons.py | 82 +- data/emoticons/tango/emoticons.py | 2 +- scripts/dev/run-build-test.py | 15 +- scripts/dev/run-pylint.py | 4 +- setup_win32.py | 43 +- src/adhoc_commands.py | 848 +- src/advanced_configuration_window.py | 434 +- src/atom_window.py | 190 +- src/cell_renderer_image.py | 194 +- src/chat_control.py | 5474 ++++---- src/common/GnuPG.py | 360 +- src/common/GnuPGInterface.py | 2 - src/common/__init__.py | 2 - src/common/account.py | 22 +- src/common/atom.py | 192 +- src/common/caps.py | 666 +- src/common/check_paths.py | 234 +- src/common/commands.py | 646 +- src/common/config.py | 1258 +- src/common/configpaths.py | 174 +- src/common/connection.py | 4040 +++--- src/common/connection_handlers.py | 5382 ++++---- src/common/contacts.py | 1370 +- src/common/crypto.py | 88 +- src/common/dataforms.py | 842 +- src/common/dbus_support.py | 222 +- src/common/defs.py | 4 +- src/common/dh.py | 50 +- src/common/events.py | 488 +- src/common/exceptions.py | 100 +- src/common/fuzzyclock.py | 56 +- src/common/gajim.py | 386 +- src/common/helpers.py | 2038 ++- src/common/i18n.py | 92 +- src/common/idle.py | 110 +- src/common/jingle.py | 164 +- src/common/jingle_content.py | 156 +- src/common/jingle_rtp.py | 484 +- src/common/jingle_session.py | 1147 +- src/common/jingle_transport.py | 185 +- src/common/kwalletbinding.py | 78 +- src/common/latex.py | 172 +- src/common/logger.py | 1946 ++- src/common/logging_helpers.py | 252 +- src/common/message_archiving.py | 386 +- src/common/optparser.py | 1474 ++- src/common/passwords.py | 324 +- src/common/pep.py | 992 +- src/common/proxy65_manager.py | 720 +- src/common/pubsub.py | 274 +- src/common/resolver.py | 508 +- src/common/rst_xhtml_generator.py | 224 +- src/common/sleepy.py | 164 +- src/common/socks5.py | 1942 ++- src/common/stanza_session.py | 1864 ++- src/common/xmpp/__init__.py | 2 - src/common/xmpp/auth_nb.py | 886 +- src/common/xmpp/bosh.py | 996 +- src/common/xmpp/c14n.py | 56 +- src/common/xmpp/client_nb.py | 936 +- src/common/xmpp/dispatcher_nb.py | 974 +- src/common/xmpp/features_nb.py | 282 +- src/common/xmpp/idlequeue.py | 828 +- src/common/xmpp/plugin.py | 130 +- src/common/xmpp/protocol.py | 1328 +- src/common/xmpp/proxy_connectors.py | 386 +- src/common/xmpp/roster_nb.py | 436 +- src/common/xmpp/simplexml.py | 856 +- src/common/xmpp/stringprepare.py | 252 +- src/common/xmpp/tls_nb.py | 650 +- src/common/xmpp/transports_nb.py | 1192 +- src/common/zeroconf/__init__.py | 2 - src/common/zeroconf/client_zeroconf.py | 1322 +- .../zeroconf/connection_handlers_zeroconf.py | 248 +- src/common/zeroconf/connection_zeroconf.py | 556 +- src/common/zeroconf/roster_zeroconf.py | 228 +- src/common/zeroconf/zeroconf.py | 36 +- src/common/zeroconf/zeroconf_avahi.py | 734 +- src/common/zeroconf/zeroconf_bonjour.py | 484 +- src/config.py | 7240 ++++++----- src/conversation_textview.py | 2556 ++-- src/dataforms_widget.py | 972 +- src/dialogs.py | 8740 +++++++------ src/disco.py | 3794 +++--- src/features_window.py | 404 +- src/filetransfers_window.py | 1868 ++- src/gajim-remote.py | 1020 +- src/gajim.py | 530 +- src/gajim_themes_window.py | 676 +- src/groupchat_control.py | 4670 ++++--- src/groups.py | 78 +- src/gtkexcepthook.py | 114 +- src/gtkgui_helpers.py | 1698 ++- src/gtkspell.py | 1 - src/gui_interface.py | 6512 +++++----- src/gui_menu_builder.py | 798 +- src/history_manager.py | 1176 +- src/history_window.py | 1130 +- src/htmltextview.py | 1656 ++- src/ipython_view.py | 884 +- src/message_control.py | 272 +- src/message_textview.py | 474 +- src/message_window.py | 2212 ++-- src/music_track_listener.py | 478 +- src/negotiation.py | 84 +- src/network_manager_listener.py | 106 +- src/notify.py | 1138 +- src/profile_window.py | 588 +- src/remote_control.py | 1162 +- src/roster_window.py | 10844 ++++++++-------- src/search_window.py | 350 +- src/secrets.py | 122 +- src/session.py | 998 +- src/statusicon.py | 646 +- src/tooltips.py | 1172 +- src/vcard.py | 918 +- test/integration/__init__.py | 2 +- .../integration/test_gui_event_integration.py | 234 +- test/integration/test_resolver.py | 142 +- test/integration/test_roster.py | 282 +- test/integration/test_xmpp_client_nb.py | 240 +- test/integration/test_xmpp_transports_nb.py | 440 +- test/lib/__init__.py | 40 +- test/lib/data.py | 124 +- test/lib/gajim_mocks.py | 194 +- test/lib/mock.py | 10 +- test/lib/notify.py | 10 +- test/lib/xmpp_mocks.py | 136 +- test/runtests.py | 70 +- test/unit/__init__.py | 2 +- test/unit/test_account.py | 14 +- test/unit/test_caps.py | 240 +- test/unit/test_contacts.py | 214 +- test/unit/test_gui_interface.py | 170 +- test/unit/test_sessions.py | 270 +- test/unit/test_xmpp_dispatcher_nb.py | 140 +- test/unit/test_xmpp_transports_nb.py | 124 +- 139 files changed, 62237 insertions(+), 62486 deletions(-) diff --git a/data/emoticons/animated/emoticons.py b/data/emoticons/animated/emoticons.py index 59591e6cc..222358ef9 100644 --- a/data/emoticons/animated/emoticons.py +++ b/data/emoticons/animated/emoticons.py @@ -1,52 +1,50 @@ # coding=utf-8 emoticons = { - 'smile.png': [':-)', ':)'], - 'coolglasses.png': ['B-)', '(H)'], - 'wink.gif': [';-)', ';)'], - 'biggrin.png': [':-D', ':D'], - 'unhappy.png': [':-(', ':('], - 'cry.gif': [":'-(", ":'(", ';-(', ';(', ";'-("], - 'frowning.png': [':-/', ':/', ':-\\', ':\\', ':-S', ':S'], - 'blush.png': [':-$', ':$'], - 'angry.png': [':-@', ':@'], - 'bat.gif': [':-[', ':['], - 'kiss.png': [':-{}', ':-*', ':*', '(K)'], - 'stare.png': [':-|', ':|'], - 'devil.png': [']:->', '>:-)', '>:)', '(6)'], - 'tongue.png': [':-P', ':P', ':-þ', ':þ'], - 'oh.png': ['=-O', ':-O', ':O'], - 'heart.png': ['<3', '(L)', '*IN LOVE*'], - 'pussy.png': ['(@)'], - 'cuffs.png': ['(%)'], - 'moon.png': ['(S)'], - 'lamp.png': ['(I)'], - 'music.png': ['(8)'], - 'beer.png': ['(B)', '*DRINK*'], - 'brflower.png': ['(W)'], - 'boy.png': ['(Z)'], - 'girl.png': ['(X)'], - 'mail.png': ['(E)'], - 'thumbdown.png': ['(N)'], - 'photo.png': ['(P)'], - 'thumbup.png': ['(Y)', '*THUMBS UP*'], - 'hugleft.png': ['(})'], - 'brheart.png': ['--', '(F)'], - 'drink.png': ['(D)'], - 'phone.png': ['(T)'], - 'coffee.png': ['(C)'], - 'hugright.png': ['({)'], - 'star.png': ['(*)'], - 'rainbow.png': ['(R)'], - 'cigarette.gif': ['(ci)'], - 'cake.gif': ['(^)'], - 'dontknow.gif': [':^)'], - 'eyeroll.gif': ['8-)'], - 'lightning.gif': ['(li)'], - 'party.gif': ['<:o)'], - 'sleepy.gif': ['|-)'], - 'think.gif': ['*-)'], - 'puke.gif': [':-!'], + 'smile.png': [':-)', ':)'], + 'coolglasses.png': ['B-)', '(H)'], + 'wink.gif': [';-)', ';)'], + 'biggrin.png': [':-D', ':D'], + 'unhappy.png': [':-(', ':('], + 'cry.gif': [":'-(", ":'(", ';-(', ';(', ";'-("], + 'frowning.png': [':-/', ':/', ':-\\', ':\\', ':-S', ':S'], + 'blush.png': [':-$', ':$'], + 'angry.png': [':-@', ':@'], + 'bat.gif': [':-[', ':['], + 'kiss.png': [':-{}', ':-*', ':*', '(K)'], + 'stare.png': [':-|', ':|'], + 'devil.png': [']:->', '>:-)', '>:)', '(6)'], + 'tongue.png': [':-P', ':P', ':-þ', ':þ'], + 'oh.png': ['=-O', ':-O', ':O'], + 'heart.png': ['<3', '(L)', '*IN LOVE*'], + 'pussy.png': ['(@)'], + 'cuffs.png': ['(%)'], + 'moon.png': ['(S)'], + 'lamp.png': ['(I)'], + 'music.png': ['(8)'], + 'beer.png': ['(B)', '*DRINK*'], + 'brflower.png': ['(W)'], + 'boy.png': ['(Z)'], + 'girl.png': ['(X)'], + 'mail.png': ['(E)'], + 'thumbdown.png': ['(N)'], + 'photo.png': ['(P)'], + 'thumbup.png': ['(Y)', '*THUMBS UP*'], + 'hugleft.png': ['(})'], + 'brheart.png': ['--', '(F)'], + 'drink.png': ['(D)'], + 'phone.png': ['(T)'], + 'coffee.png': ['(C)'], + 'hugright.png': ['({)'], + 'star.png': ['(*)'], + 'rainbow.png': ['(R)'], + 'cigarette.gif': ['(ci)'], + 'cake.gif': ['(^)'], + 'dontknow.gif': [':^)'], + 'eyeroll.gif': ['8-)'], + 'lightning.gif': ['(li)'], + 'party.gif': ['<:o)'], + 'sleepy.gif': ['|-)'], + 'think.gif': ['*-)'], + 'puke.gif': [':-!'], } - -# vim: se ts=3: diff --git a/data/emoticons/static-big/emoticons.py b/data/emoticons/static-big/emoticons.py index 3c2d08b72..8ed1da834 100644 --- a/data/emoticons/static-big/emoticons.py +++ b/data/emoticons/static-big/emoticons.py @@ -1,45 +1,43 @@ # coding=utf-8 emoticons = { - 'smile.png': [':-)', ':)'], - 'coolglasses.png': ['8-)', 'B-)', '(H)'], - 'wink.png': [';-)', ';)'], - 'biggrin.png': [':-D', ':D'], - 'unhappy.png': [':-(', ':('], - 'cry.png': [":'-(", ":'(", ';-(', ';(', ";'-("], - 'frowning.png': [':-/', ':/', ':-\\', ':\\', ':-S', ':S'], - 'blush.png': [':-$', ':$'], - 'angry.png': [':-@', ':@'], - 'bat.png': [':-[', ':['], - 'kiss.png': [':-{}', ':-*', ':*', '(K)'], - 'stare.png': [':-|', ':|'], - 'devil.png': [']:->', '>:-)', '>:)', '(6)'], - 'tongue.png': [':-P', ':P', ':-þ', ':þ'], - 'oh.png': ['=-O', ':-O', ':O'], - 'heart.png': ['<3', '(L)', '*IN LOVE*'], - 'lion.png': [':3', '>:3'], - 'pussy.png': ['(@)', '=^.^='], - 'cuffs.png': ['(%)'], - 'moon.png': ['(S)'], - 'lamp.png': ['(I)'], - 'music.png': ['(8)'], - 'beer.png': ['(B)', '*DRINK*'], - 'brflower.png': ['(W)'], - 'boy.png': ['(Z)'], - 'girl.png': ['(X)'], - 'mail.png': ['(E)'], - 'thumbdown.png': ['(N)'], - 'photo.png': ['(P)'], - 'thumbup.png': ['(Y)', '*THUMBS UP*'], - 'hugleft.png': ['(})'], - 'brheart.png': ['--', '(F)'], - 'drink.png': ['(D)'], - 'phone.png': ['(T)'], - 'coffee.png': ['(C)'], - 'hugright.png': ['({)'], - 'star.png': ['(*)'], - 'rainbow.png': ['(R)'], - 'puke.png': [':-!'], + 'smile.png': [':-)', ':)'], + 'coolglasses.png': ['8-)', 'B-)', '(H)'], + 'wink.png': [';-)', ';)'], + 'biggrin.png': [':-D', ':D'], + 'unhappy.png': [':-(', ':('], + 'cry.png': [":'-(", ":'(", ';-(', ';(', ";'-("], + 'frowning.png': [':-/', ':/', ':-\\', ':\\', ':-S', ':S'], + 'blush.png': [':-$', ':$'], + 'angry.png': [':-@', ':@'], + 'bat.png': [':-[', ':['], + 'kiss.png': [':-{}', ':-*', ':*', '(K)'], + 'stare.png': [':-|', ':|'], + 'devil.png': [']:->', '>:-)', '>:)', '(6)'], + 'tongue.png': [':-P', ':P', ':-þ', ':þ'], + 'oh.png': ['=-O', ':-O', ':O'], + 'heart.png': ['<3', '(L)', '*IN LOVE*'], + 'lion.png': [':3', '>:3'], + 'pussy.png': ['(@)', '=^.^='], + 'cuffs.png': ['(%)'], + 'moon.png': ['(S)'], + 'lamp.png': ['(I)'], + 'music.png': ['(8)'], + 'beer.png': ['(B)', '*DRINK*'], + 'brflower.png': ['(W)'], + 'boy.png': ['(Z)'], + 'girl.png': ['(X)'], + 'mail.png': ['(E)'], + 'thumbdown.png': ['(N)'], + 'photo.png': ['(P)'], + 'thumbup.png': ['(Y)', '*THUMBS UP*'], + 'hugleft.png': ['(})'], + 'brheart.png': ['--', '(F)'], + 'drink.png': ['(D)'], + 'phone.png': ['(T)'], + 'coffee.png': ['(C)'], + 'hugright.png': ['({)'], + 'star.png': ['(*)'], + 'rainbow.png': ['(R)'], + 'puke.png': [':-!'], } - -# vim: se ts=3: diff --git a/data/emoticons/static/emoticons.py b/data/emoticons/static/emoticons.py index 3c2d08b72..8ed1da834 100644 --- a/data/emoticons/static/emoticons.py +++ b/data/emoticons/static/emoticons.py @@ -1,45 +1,43 @@ # coding=utf-8 emoticons = { - 'smile.png': [':-)', ':)'], - 'coolglasses.png': ['8-)', 'B-)', '(H)'], - 'wink.png': [';-)', ';)'], - 'biggrin.png': [':-D', ':D'], - 'unhappy.png': [':-(', ':('], - 'cry.png': [":'-(", ":'(", ';-(', ';(', ";'-("], - 'frowning.png': [':-/', ':/', ':-\\', ':\\', ':-S', ':S'], - 'blush.png': [':-$', ':$'], - 'angry.png': [':-@', ':@'], - 'bat.png': [':-[', ':['], - 'kiss.png': [':-{}', ':-*', ':*', '(K)'], - 'stare.png': [':-|', ':|'], - 'devil.png': [']:->', '>:-)', '>:)', '(6)'], - 'tongue.png': [':-P', ':P', ':-þ', ':þ'], - 'oh.png': ['=-O', ':-O', ':O'], - 'heart.png': ['<3', '(L)', '*IN LOVE*'], - 'lion.png': [':3', '>:3'], - 'pussy.png': ['(@)', '=^.^='], - 'cuffs.png': ['(%)'], - 'moon.png': ['(S)'], - 'lamp.png': ['(I)'], - 'music.png': ['(8)'], - 'beer.png': ['(B)', '*DRINK*'], - 'brflower.png': ['(W)'], - 'boy.png': ['(Z)'], - 'girl.png': ['(X)'], - 'mail.png': ['(E)'], - 'thumbdown.png': ['(N)'], - 'photo.png': ['(P)'], - 'thumbup.png': ['(Y)', '*THUMBS UP*'], - 'hugleft.png': ['(})'], - 'brheart.png': ['--', '(F)'], - 'drink.png': ['(D)'], - 'phone.png': ['(T)'], - 'coffee.png': ['(C)'], - 'hugright.png': ['({)'], - 'star.png': ['(*)'], - 'rainbow.png': ['(R)'], - 'puke.png': [':-!'], + 'smile.png': [':-)', ':)'], + 'coolglasses.png': ['8-)', 'B-)', '(H)'], + 'wink.png': [';-)', ';)'], + 'biggrin.png': [':-D', ':D'], + 'unhappy.png': [':-(', ':('], + 'cry.png': [":'-(", ":'(", ';-(', ';(', ";'-("], + 'frowning.png': [':-/', ':/', ':-\\', ':\\', ':-S', ':S'], + 'blush.png': [':-$', ':$'], + 'angry.png': [':-@', ':@'], + 'bat.png': [':-[', ':['], + 'kiss.png': [':-{}', ':-*', ':*', '(K)'], + 'stare.png': [':-|', ':|'], + 'devil.png': [']:->', '>:-)', '>:)', '(6)'], + 'tongue.png': [':-P', ':P', ':-þ', ':þ'], + 'oh.png': ['=-O', ':-O', ':O'], + 'heart.png': ['<3', '(L)', '*IN LOVE*'], + 'lion.png': [':3', '>:3'], + 'pussy.png': ['(@)', '=^.^='], + 'cuffs.png': ['(%)'], + 'moon.png': ['(S)'], + 'lamp.png': ['(I)'], + 'music.png': ['(8)'], + 'beer.png': ['(B)', '*DRINK*'], + 'brflower.png': ['(W)'], + 'boy.png': ['(Z)'], + 'girl.png': ['(X)'], + 'mail.png': ['(E)'], + 'thumbdown.png': ['(N)'], + 'photo.png': ['(P)'], + 'thumbup.png': ['(Y)', '*THUMBS UP*'], + 'hugleft.png': ['(})'], + 'brheart.png': ['--', '(F)'], + 'drink.png': ['(D)'], + 'phone.png': ['(T)'], + 'coffee.png': ['(C)'], + 'hugright.png': ['({)'], + 'star.png': ['(*)'], + 'rainbow.png': ['(R)'], + 'puke.png': [':-!'], } - -# vim: se ts=3: diff --git a/data/emoticons/tango/emoticons.py b/data/emoticons/tango/emoticons.py index 43c7ce37f..db3376b93 100644 --- a/data/emoticons/tango/emoticons.py +++ b/data/emoticons/tango/emoticons.py @@ -49,4 +49,4 @@ emoticons = { 'pissed-off.png': ['*WALL*'], 'mail.png': ['*WRITE*', '(E)'], 'tremble.png': ['*SCRATCH*'], -} \ No newline at end of file +} diff --git a/scripts/dev/run-build-test.py b/scripts/dev/run-build-test.py index daf177e87..c8b3b355f 100755 --- a/scripts/dev/run-build-test.py +++ b/scripts/dev/run-build-test.py @@ -4,19 +4,16 @@ import os import sys if os.getcwd().endswith('dev'): - os.chdir('../../') # we were in scripts/dev + os.chdir('../../') # we were in scripts/dev ret = 0 ret += os.system("make clean > " + os.devnull) -ret += os.system("make > " + os.devnull) +ret += os.system("make > " + os.devnull) ret += os.system("make check > " + os.devnull) if ret == 0: - print "Build successfull" - sys.exit(0) + print "Build successfull" + sys.exit(0) else: - print >>sys.stderr, "Build failed" - sys.exit(1) - -# vim: se ts=3: - + print >>sys.stderr, "Build failed" + sys.exit(1) diff --git a/scripts/dev/run-pylint.py b/scripts/dev/run-pylint.py index c840d8b48..cb611b6bf 100755 --- a/scripts/dev/run-pylint.py +++ b/scripts/dev/run-pylint.py @@ -5,9 +5,7 @@ import os import sys if os.getcwd().endswith('dev'): - os.chdir('../../src/') # we were in scripts/dev + os.chdir('../../src/') # we were in scripts/dev os.system("pylint --indent-string='\t' --additional-builtins='_' --disable-msg=C0111,C0103,C0111,C0112 --disable-checker=design " + "".join(sys.argv[1:])) - -# vim: se ts=3: diff --git a/setup_win32.py b/setup_win32.py index 13096217c..23bfcaff6 100644 --- a/setup_win32.py +++ b/setup_win32.py @@ -54,39 +54,34 @@ opts = { # ConfigParser,UserString,roman are needed for docutils 'includes': 'pango,atk,gobject,cairo,pangocairo,gtk.keysyms,encodings,encodings.*,ConfigParser,UserString', 'dll_excludes': [ - 'iconv.dll','intl.dll','libatk-1.0-0.dll', - 'libgdk_pixbuf-2.0-0.dll','libgdk-win32-2.0-0.dll', - 'libglib-2.0-0.dll','libgmodule-2.0-0.dll', - 'libgobject-2.0-0.dll','libgthread-2.0-0.dll', - 'libgtk-win32-2.0-0.dll','libpango-1.0-0.dll', - 'libpangowin32-1.0-0.dll','libcairo-2.dll', - 'libpangocairo-1.0-0.dll','libpangoft2-1.0-0.dll', + 'iconv.dll', 'intl.dll', 'libatk-1.0-0.dll', + 'libgdk_pixbuf-2.0-0.dll', 'libgdk-win32-2.0-0.dll', + 'libglib-2.0-0.dll', 'libgmodule-2.0-0.dll', + 'libgobject-2.0-0.dll', 'libgthread-2.0-0.dll', + 'libgtk-win32-2.0-0.dll', 'libpango-1.0-0.dll', + 'libpangowin32-1.0-0.dll', 'libcairo-2.dll', + 'libpangocairo-1.0-0.dll', 'libpangoft2-1.0-0.dll', ], 'excludes': [ 'docutils' ], - 'optimize': 2, + 'optimize': 2, } } setup( - name = 'Gajim', - version = '0.12.1', - description = 'A full featured Jabber client', - author = 'Gajim Development Team', - url = 'http://www.gajim.org/', - download_url = 'http://www.gajim.org/downloads.php', - license = 'GPL', - - windows = [{'script': 'src/gajim.py', - 'icon_resources': [(1, 'data/pixmaps/gajim.ico')]}, - {'script': 'src/history_manager.py', - 'icon_resources': [(1, 'data/pixmaps/gajim.ico')]}], + name='Gajim', + version='0.12.1', + description='A full featured Jabber client', + author='Gajim Development Team', + url='http://www.gajim.org/', + download_url='http://www.gajim.org/downloads.php', + license='GPL', + windows=[{'script': 'src/gajim.py', + 'icon_resources': [(1, 'data/pixmaps/gajim.ico')]}, + {'script': 'src/history_manager.py', + 'icon_resources': [(1, 'data/pixmaps/gajim.ico')]}], options=opts, - data_files=docutils_files, - ) - -# vim: se ts=3: diff --git a/src/adhoc_commands.py b/src/adhoc_commands.py index 01d34eee6..e16dc1868 100644 --- a/src/adhoc_commands.py +++ b/src/adhoc_commands.py @@ -35,537 +35,535 @@ import dialogs import dataforms_widget class CommandWindow: - """ - Class for a window for single ad-hoc commands session + """ + Class for a window for single ad-hoc commands session - Note, that there might be more than one for one account/jid pair in one - moment. + Note, that there might be more than one for one account/jid pair in one + moment. - TODO: Maybe put this window into MessageWindow? consider this when it will - be possible to manage more than one window of one. - TODO: Account/jid pair in MessageWindowMgr. - TODO: GTK 2.10 has a special wizard-widget, consider using it... - """ + TODO: Maybe put this window into MessageWindow? consider this when it will + be possible to manage more than one window of one. + TODO: Account/jid pair in MessageWindowMgr. + TODO: GTK 2.10 has a special wizard-widget, consider using it... + """ - def __init__(self, account, jid, commandnode=None): - """ - Create new window - """ + def __init__(self, account, jid, commandnode=None): + """ + Create new window + """ - # an account object - self.account = gajim.connections[account] - self.jid = jid + # an account object + self.account = gajim.connections[account] + self.jid = jid - self.pulse_id=None # to satisfy self.setup_pulsing() - self.commandlist=None # a list of (commandname, commanddescription) + self.pulse_id=None # to satisfy self.setup_pulsing() + self.commandlist=None # a list of (commandname, commanddescription) - # command's data - self.commandnode = commandnode - self.sessionid = None - self.dataform = None - self.allow_stage3_close = False + # command's data + self.commandnode = commandnode + self.sessionid = None + self.dataform = None + self.allow_stage3_close = False - # retrieving widgets from xml - self.xml = gtkgui_helpers.get_glade('adhoc_commands_window.glade') - self.window = self.xml.get_widget('adhoc_commands_window') - self.window.connect('delete-event', self.on_adhoc_commands_window_delete_event) - for name in ('back_button', 'forward_button', - 'execute_button','close_button','stages_notebook', - 'retrieving_commands_stage_vbox', - 'command_list_stage_vbox','command_list_vbox', - 'sending_form_stage_vbox','sending_form_progressbar', - 'notes_label','no_commands_stage_vbox','error_stage_vbox', - 'error_description_label'): - self.__dict__[name] = self.xml.get_widget(name) + # retrieving widgets from xml + self.xml = gtkgui_helpers.get_glade('adhoc_commands_window.glade') + self.window = self.xml.get_widget('adhoc_commands_window') + self.window.connect('delete-event', self.on_adhoc_commands_window_delete_event) + for name in ('back_button', 'forward_button', + 'execute_button', 'close_button', 'stages_notebook', + 'retrieving_commands_stage_vbox', + 'command_list_stage_vbox', 'command_list_vbox', + 'sending_form_stage_vbox', 'sending_form_progressbar', + 'notes_label', 'no_commands_stage_vbox', 'error_stage_vbox', + 'error_description_label'): + self.__dict__[name] = self.xml.get_widget(name) - # creating data forms widget - self.data_form_widget = dataforms_widget.DataFormWidget() - self.data_form_widget.show() - self.sending_form_stage_vbox.pack_start(self.data_form_widget) + # creating data forms widget + self.data_form_widget = dataforms_widget.DataFormWidget() + self.data_form_widget.show() + self.sending_form_stage_vbox.pack_start(self.data_form_widget) - if self.commandnode: - # Execute command - self.stage3() - else: - # setting initial stage - self.stage1() + if self.commandnode: + # Execute command + self.stage3() + else: + # setting initial stage + self.stage1() - # displaying the window - self.xml.signal_autoconnect(self) - self.window.show_all() + # displaying the window + self.xml.signal_autoconnect(self) + self.window.show_all() - # These functions are set up by appropriate stageX methods. - def stage_finish(self, *anything): - pass + # These functions are set up by appropriate stageX methods. + def stage_finish(self, *anything): + pass - def stage_back_button_clicked(self, *anything): - assert False + def stage_back_button_clicked(self, *anything): + assert False - def stage_forward_button_clicked(self, *anything): - assert False + def stage_forward_button_clicked(self, *anything): + assert False - def stage_execute_button_clicked(self, *anything): - assert False + def stage_execute_button_clicked(self, *anything): + assert False - def stage_close_button_clicked(self, *anything): - assert False + def stage_close_button_clicked(self, *anything): + assert False - def stage_adhoc_commands_window_delete_event(self, *anything): - assert False + def stage_adhoc_commands_window_delete_event(self, *anything): + assert False - def do_nothing(self, *anything): - return False + def do_nothing(self, *anything): + return False - # Widget callbacks... - def on_back_button_clicked(self, *anything): - return self.stage_back_button_clicked(*anything) + # Widget callbacks... + def on_back_button_clicked(self, *anything): + return self.stage_back_button_clicked(*anything) - def on_forward_button_clicked(self, *anything): - return self.stage_forward_button_clicked(*anything) + def on_forward_button_clicked(self, *anything): + return self.stage_forward_button_clicked(*anything) - def on_execute_button_clicked(self, *anything): - return self.stage_execute_button_clicked(*anything) + def on_execute_button_clicked(self, *anything): + return self.stage_execute_button_clicked(*anything) - def on_close_button_clicked(self, *anything): - return self.stage_close_button_clicked(*anything) + def on_close_button_clicked(self, *anything): + return self.stage_close_button_clicked(*anything) - def on_adhoc_commands_window_destroy(self, *anything): - # TODO: do all actions that are needed to remove this object from memory... - self.remove_pulsing() + def on_adhoc_commands_window_destroy(self, *anything): + # TODO: do all actions that are needed to remove this object from memory... + self.remove_pulsing() - def on_adhoc_commands_window_delete_event(self, *anything): - return self.stage_adhoc_commands_window_delete_event(self.window) + def on_adhoc_commands_window_delete_event(self, *anything): + return self.stage_adhoc_commands_window_delete_event(self.window) - def __del__(self): - print 'Object has been deleted.' + def __del__(self): + print 'Object has been deleted.' # stage 1: waiting for command list - def stage1(self): - """ - Prepare the first stage. Request command list, set appropriate state of - widgets - """ - # close old stage... - self.stage_finish() + def stage1(self): + """ + Prepare the first stage. Request command list, set appropriate state of + widgets + """ + # close old stage... + self.stage_finish() - # show the stage - self.stages_notebook.set_current_page( - self.stages_notebook.page_num( - self.retrieving_commands_stage_vbox)) + # show the stage + self.stages_notebook.set_current_page( + self.stages_notebook.page_num( + self.retrieving_commands_stage_vbox)) - # set widgets' state - self.close_button.set_sensitive(True) - self.back_button.set_sensitive(False) - self.forward_button.set_sensitive(False) - self.execute_button.set_sensitive(False) + # set widgets' state + self.close_button.set_sensitive(True) + self.back_button.set_sensitive(False) + self.forward_button.set_sensitive(False) + self.execute_button.set_sensitive(False) - # request command list - self.request_command_list() - self.setup_pulsing( - self.xml.get_widget('retrieving_commands_progressbar')) + # request command list + self.request_command_list() + self.setup_pulsing( + self.xml.get_widget('retrieving_commands_progressbar')) - # setup the callbacks - self.stage_finish = self.stage1_finish - self.stage_close_button_clicked = self.stage1_close_button_clicked - self.stage_adhoc_commands_window_delete_event = self.stage1_adhoc_commands_window_delete_event + # setup the callbacks + self.stage_finish = self.stage1_finish + self.stage_close_button_clicked = self.stage1_close_button_clicked + self.stage_adhoc_commands_window_delete_event = self.stage1_adhoc_commands_window_delete_event - def stage1_finish(self): - self.remove_pulsing() + def stage1_finish(self): + self.remove_pulsing() - def stage1_close_button_clicked(self, widget): - # cancelling in this stage is not critical, so we don't - # show any popups to user - self.stage1_finish() - self.window.destroy() + def stage1_close_button_clicked(self, widget): + # cancelling in this stage is not critical, so we don't + # show any popups to user + self.stage1_finish() + self.window.destroy() - def stage1_adhoc_commands_window_delete_event(self, widget): - self.stage1_finish() - return True + def stage1_adhoc_commands_window_delete_event(self, widget): + self.stage1_finish() + return True # stage 2: choosing the command to execute - def stage2(self): - """ - Populate the command list vbox with radiobuttons + def stage2(self): + """ + Populate the command list vbox with radiobuttons - FIXME: If there is more commands, maybe some kind of list, set widgets - state - """ - # close old stage - self.stage_finish() + FIXME: If there is more commands, maybe some kind of list, set widgets + state + """ + # close old stage + self.stage_finish() - assert len(self.commandlist)>0 + assert len(self.commandlist)>0 - self.stages_notebook.set_current_page( - self.stages_notebook.page_num( - self.command_list_stage_vbox)) + self.stages_notebook.set_current_page( + self.stages_notebook.page_num( + self.command_list_stage_vbox)) - self.close_button.set_sensitive(True) - self.back_button.set_sensitive(False) - self.forward_button.set_sensitive(True) - self.execute_button.set_sensitive(False) + self.close_button.set_sensitive(True) + self.back_button.set_sensitive(False) + self.forward_button.set_sensitive(True) + self.execute_button.set_sensitive(False) - # build the commands list radiobuttons - first_radio = None - for (commandnode, commandname) in self.commandlist: - radio = gtk.RadioButton(first_radio, label=commandname) - radio.connect("toggled", self.on_command_radiobutton_toggled, commandnode) - if not first_radio: - first_radio = radio - self.commandnode = commandnode - self.command_list_vbox.pack_start(radio, expand=False) - self.command_list_vbox.show_all() + # build the commands list radiobuttons + first_radio = None + for (commandnode, commandname) in self.commandlist: + radio = gtk.RadioButton(first_radio, label=commandname) + radio.connect("toggled", self.on_command_radiobutton_toggled, commandnode) + if not first_radio: + first_radio = radio + self.commandnode = commandnode + self.command_list_vbox.pack_start(radio, expand=False) + self.command_list_vbox.show_all() - self.stage_finish = self.stage2_finish - self.stage_close_button_clicked = self.stage2_close_button_clicked - self.stage_forward_button_clicked = self.stage2_forward_button_clicked - self.stage_adhoc_commands_window_delete_event = self.do_nothing + self.stage_finish = self.stage2_finish + self.stage_close_button_clicked = self.stage2_close_button_clicked + self.stage_forward_button_clicked = self.stage2_forward_button_clicked + self.stage_adhoc_commands_window_delete_event = self.do_nothing - def stage2_finish(self): - """ - Remove widgets we created. Not needed when the window is destroyed - """ - def remove_widget(widget): - self.command_list_vbox.remove(widget) - self.command_list_vbox.foreach(remove_widget) + def stage2_finish(self): + """ + Remove widgets we created. Not needed when the window is destroyed + """ + def remove_widget(widget): + self.command_list_vbox.remove(widget) + self.command_list_vbox.foreach(remove_widget) - def stage2_close_button_clicked(self, widget): - self.stage_finish() - self.window.destroy() + def stage2_close_button_clicked(self, widget): + self.stage_finish() + self.window.destroy() - def stage2_forward_button_clicked(self, widget): - self.stage3() + def stage2_forward_button_clicked(self, widget): + self.stage3() - def on_command_radiobutton_toggled(self, widget, commandnode): - self.commandnode = commandnode + def on_command_radiobutton_toggled(self, widget, commandnode): + self.commandnode = commandnode - def on_check_commands_1_button_clicked(self, widget): - self.stage1() + def on_check_commands_1_button_clicked(self, widget): + self.stage1() # stage 3: command invocation - def stage3(self): - # close old stage - self.stage_finish() + def stage3(self): + # close old stage + self.stage_finish() - assert isinstance(self.commandnode, unicode) + assert isinstance(self.commandnode, unicode) - self.form_status = None + self.form_status = None - self.stages_notebook.set_current_page( - self.stages_notebook.page_num( - self.sending_form_stage_vbox)) + self.stages_notebook.set_current_page( + self.stages_notebook.page_num( + self.sending_form_stage_vbox)) - self.close_button.set_sensitive(True) - self.back_button.set_sensitive(False) - self.forward_button.set_sensitive(False) - self.execute_button.set_sensitive(False) + self.close_button.set_sensitive(True) + self.back_button.set_sensitive(False) + self.forward_button.set_sensitive(False) + self.execute_button.set_sensitive(False) - self.stage3_submit_form() + self.stage3_submit_form() - self.stage_finish = self.stage3_finish - self.stage_back_button_clicked = self.stage3_back_button_clicked - self.stage_forward_button_clicked = self.stage3_forward_button_clicked - self.stage_execute_button_clicked = self.stage3_execute_button_clicked - self.stage_close_button_clicked = self.stage3_close_button_clicked - self.stage_adhoc_commands_window_delete_event = self.stage3_close_button_clicked + self.stage_finish = self.stage3_finish + self.stage_back_button_clicked = self.stage3_back_button_clicked + self.stage_forward_button_clicked = self.stage3_forward_button_clicked + self.stage_execute_button_clicked = self.stage3_execute_button_clicked + self.stage_close_button_clicked = self.stage3_close_button_clicked + self.stage_adhoc_commands_window_delete_event = self.stage3_close_button_clicked - def stage3_finish(self): - pass + def stage3_finish(self): + pass - def stage3_close_button_clicked(self, widget): - """ - We are in the middle of executing command. Ask user if he really want to - cancel the process, then cancel it - """ - # this works also as a handler for window_delete_event, so we have to return appropriate - # values - if self.form_status == 'completed': - if widget!=self.window: - self.window.destroy() - return False + def stage3_close_button_clicked(self, widget): + """ + We are in the middle of executing command. Ask user if he really want to + cancel the process, then cancel it + """ + # this works also as a handler for window_delete_event, so we have to return appropriate + # values + if self.form_status == 'completed': + if widget!=self.window: + self.window.destroy() + return False - if self.allow_stage3_close: - return False + if self.allow_stage3_close: + return False - def on_yes(button): - self.send_cancel() - self.allow_stage3_close = True - self.window.destroy() + def on_yes(button): + self.send_cancel() + self.allow_stage3_close = True + self.window.destroy() - dialog = dialogs.HigDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT | \ - gtk.DIALOG_MODAL, gtk.BUTTONS_YES_NO, _('Cancel confirmation'), - _('You are in process of executing command. Do you really want to ' - 'cancel it?'), on_response_yes=on_yes) - dialog.popup() - return True # Block event, don't close window + dialog = dialogs.HigDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT | \ + gtk.DIALOG_MODAL, gtk.BUTTONS_YES_NO, _('Cancel confirmation'), + _('You are in process of executing command. Do you really want to ' + 'cancel it?'), on_response_yes=on_yes) + dialog.popup() + return True # Block event, don't close window - def stage3_back_button_clicked(self, widget): - self.stage3_submit_form('prev') + def stage3_back_button_clicked(self, widget): + self.stage3_submit_form('prev') - def stage3_forward_button_clicked(self, widget): - self.stage3_submit_form('next') + def stage3_forward_button_clicked(self, widget): + self.stage3_submit_form('next') - def stage3_execute_button_clicked(self, widget): - self.stage3_submit_form('execute') + def stage3_execute_button_clicked(self, widget): + self.stage3_submit_form('execute') - def stage3_submit_form(self, action='execute'): - self.data_form_widget.set_sensitive(False) - if self.data_form_widget.get_data_form(): - self.data_form_widget.data_form.type='submit' - else: - self.data_form_widget.hide() + def stage3_submit_form(self, action='execute'): + self.data_form_widget.set_sensitive(False) + if self.data_form_widget.get_data_form(): + self.data_form_widget.data_form.type='submit' + else: + self.data_form_widget.hide() - self.close_button.set_sensitive(True) - self.back_button.set_sensitive(False) - self.forward_button.set_sensitive(False) - self.execute_button.set_sensitive(False) + self.close_button.set_sensitive(True) + self.back_button.set_sensitive(False) + self.forward_button.set_sensitive(False) + self.execute_button.set_sensitive(False) - self.sending_form_progressbar.show() - self.setup_pulsing(self.sending_form_progressbar) - self.send_command(action) + self.sending_form_progressbar.show() + self.setup_pulsing(self.sending_form_progressbar) + self.send_command(action) - def stage3_next_form(self, command): - if not isinstance(command, xmpp.Node): - self.stage5(error=_('Service sent malformed data'), senderror=True) - return + def stage3_next_form(self, command): + if not isinstance(command, xmpp.Node): + self.stage5(error=_('Service sent malformed data'), senderror=True) + return - self.remove_pulsing() - self.sending_form_progressbar.hide() + self.remove_pulsing() + self.sending_form_progressbar.hide() - if not self.sessionid: - self.sessionid = command.getAttr('sessionid') - elif self.sessionid != command.getAttr('sessionid'): - self.stage5(error=_('Service changed the session identifier.'), - senderror=True) - return + if not self.sessionid: + self.sessionid = command.getAttr('sessionid') + elif self.sessionid != command.getAttr('sessionid'): + self.stage5(error=_('Service changed the session identifier.'), + senderror=True) + return - self.form_status = command.getAttr('status') + self.form_status = command.getAttr('status') - self.commandnode = command.getAttr('node') - if command.getTag('x'): - self.dataform = dataforms.ExtendForm(node=command.getTag('x')) + self.commandnode = command.getAttr('node') + if command.getTag('x'): + self.dataform = dataforms.ExtendForm(node=command.getTag('x')) - self.data_form_widget.set_sensitive(True) - try: - self.data_form_widget.data_form=self.dataform - except dataforms.Error: - self.stage5(error=_('Service sent malformed data'), senderror=True) - return - self.data_form_widget.show() - if self.data_form_widget.title: - self.window.set_title('%s - Ad-hoc Commands - Gajim' % \ - self.data_form_widget.title) - else: - self.data_form_widget.hide() + self.data_form_widget.set_sensitive(True) + try: + self.data_form_widget.data_form=self.dataform + except dataforms.Error: + self.stage5(error=_('Service sent malformed data'), senderror=True) + return + self.data_form_widget.show() + if self.data_form_widget.title: + self.window.set_title('%s - Ad-hoc Commands - Gajim' % \ + self.data_form_widget.title) + else: + self.data_form_widget.hide() - actions = command.getTag('actions') - if actions: - # actions, actions, actions... - self.close_button.set_sensitive(True) - self.back_button.set_sensitive(actions.getTag('prev') is not None) - self.forward_button.set_sensitive(actions.getTag('next') is not None) - self.execute_button.set_sensitive(True) - else: - self.close_button.set_sensitive(True) - self.back_button.set_sensitive(False) - self.forward_button.set_sensitive(False) - self.execute_button.set_sensitive(True) + actions = command.getTag('actions') + if actions: + # actions, actions, actions... + self.close_button.set_sensitive(True) + self.back_button.set_sensitive(actions.getTag('prev') is not None) + self.forward_button.set_sensitive(actions.getTag('next') is not None) + self.execute_button.set_sensitive(True) + else: + self.close_button.set_sensitive(True) + self.back_button.set_sensitive(False) + self.forward_button.set_sensitive(False) + self.execute_button.set_sensitive(True) - if self.form_status == 'completed': - self.close_button.set_sensitive(True) - self.back_button.hide() - self.forward_button.hide() - self.execute_button.hide() - self.close_button.show() - self.stage_adhoc_commands_window_delete_event = self.stage3_close_button_clicked + if self.form_status == 'completed': + self.close_button.set_sensitive(True) + self.back_button.hide() + self.forward_button.hide() + self.execute_button.hide() + self.close_button.show() + self.stage_adhoc_commands_window_delete_event = self.stage3_close_button_clicked - note = command.getTag('note') - if note: - self.notes_label.set_text(note.getData().decode('utf-8')) - self.notes_label.set_no_show_all(False) - self.notes_label.show() - else: - self.notes_label.set_no_show_all(True) - self.notes_label.hide() + note = command.getTag('note') + if note: + self.notes_label.set_text(note.getData().decode('utf-8')) + self.notes_label.set_no_show_all(False) + self.notes_label.show() + else: + self.notes_label.set_no_show_all(True) + self.notes_label.hide() # stage 4: no commands are exposed - def stage4(self): - """ - Display the message. Wait for user to close the window - """ - # close old stage - self.stage_finish() + def stage4(self): + """ + Display the message. Wait for user to close the window + """ + # close old stage + self.stage_finish() - self.stages_notebook.set_current_page( - self.stages_notebook.page_num( - self.no_commands_stage_vbox)) + self.stages_notebook.set_current_page( + self.stages_notebook.page_num( + self.no_commands_stage_vbox)) - self.close_button.set_sensitive(True) - self.back_button.set_sensitive(False) - self.forward_button.set_sensitive(False) - self.execute_button.set_sensitive(False) + self.close_button.set_sensitive(True) + self.back_button.set_sensitive(False) + self.forward_button.set_sensitive(False) + self.execute_button.set_sensitive(False) - self.stage_finish = self.do_nothing - self.stage_close_button_clicked = self.stage4_close_button_clicked - self.stage_adhoc_commands_window_delete_event = self.do_nothing + self.stage_finish = self.do_nothing + self.stage_close_button_clicked = self.stage4_close_button_clicked + self.stage_adhoc_commands_window_delete_event = self.do_nothing - def stage4_close_button_clicked(self, widget): - self.window.destroy() + def stage4_close_button_clicked(self, widget): + self.window.destroy() - def on_check_commands_2_button_clicked(self, widget): - self.stage1() + def on_check_commands_2_button_clicked(self, widget): + self.stage1() # stage 5: an error has occured - def stage5(self, error=None, errorid=None, senderror=False): - """ - Display the error message. Wait for user to close the window - """ - # FIXME: sending error to responder - # close old stage - self.stage_finish() + def stage5(self, error=None, errorid=None, senderror=False): + """ + Display the error message. Wait for user to close the window + """ + # FIXME: sending error to responder + # close old stage + self.stage_finish() - assert errorid or error + assert errorid or error - if errorid: - # we've got error code, display appropriate message - try: - errorname = xmpp.NS_STANZAS + ' ' + str(errorid) - errordesc = xmpp.ERRORS[errorname][2] - error = errordesc.decode('utf-8') - del errorname, errordesc - except KeyError: # when stanza doesn't have error description - error = _('Service returned an error.') - elif error: - # we've got error message - pass - else: - # we don't know what's that, bailing out - assert False + if errorid: + # we've got error code, display appropriate message + try: + errorname = xmpp.NS_STANZAS + ' ' + str(errorid) + errordesc = xmpp.ERRORS[errorname][2] + error = errordesc.decode('utf-8') + del errorname, errordesc + except KeyError: # when stanza doesn't have error description + error = _('Service returned an error.') + elif error: + # we've got error message + pass + else: + # we don't know what's that, bailing out + assert False - self.stages_notebook.set_current_page( - self.stages_notebook.page_num( - self.error_stage_vbox)) + self.stages_notebook.set_current_page( + self.stages_notebook.page_num( + self.error_stage_vbox)) - self.close_button.set_sensitive(True) - self.back_button.hide() - self.forward_button.hide() - self.execute_button.hide() + self.close_button.set_sensitive(True) + self.back_button.hide() + self.forward_button.hide() + self.execute_button.hide() - self.error_description_label.set_text(error) + self.error_description_label.set_text(error) - self.stage_finish = self.do_nothing - self.stage_close_button_clicked = self.stage5_close_button_clicked - self.stage_adhoc_commands_window_delete_event = self.do_nothing + self.stage_finish = self.do_nothing + self.stage_close_button_clicked = self.stage5_close_button_clicked + self.stage_adhoc_commands_window_delete_event = self.do_nothing - def stage5_close_button_clicked(self, widget): - self.window.destroy() + def stage5_close_button_clicked(self, widget): + self.window.destroy() # helpers to handle pulsing in progressbar - def setup_pulsing(self, progressbar): - """ - Set the progressbar to pulse. Makes a custom function to repeatedly call - progressbar.pulse() method - """ - assert not self.pulse_id - assert isinstance(progressbar, gtk.ProgressBar) + def setup_pulsing(self, progressbar): + """ + Set the progressbar to pulse. Makes a custom function to repeatedly call + progressbar.pulse() method + """ + assert not self.pulse_id + assert isinstance(progressbar, gtk.ProgressBar) - def callback(): - progressbar.pulse() - return True # important to keep callback be called back! + def callback(): + progressbar.pulse() + return True # important to keep callback be called back! - # 12 times per second (80 miliseconds) - self.pulse_id = gobject.timeout_add(80, callback) + # 12 times per second (80 miliseconds) + self.pulse_id = gobject.timeout_add(80, callback) - def remove_pulsing(self): - """ - Stop pulsing, useful when especially when removing widget - """ - if self.pulse_id: - gobject.source_remove(self.pulse_id) - self.pulse_id=None + def remove_pulsing(self): + """ + Stop pulsing, useful when especially when removing widget + """ + if self.pulse_id: + gobject.source_remove(self.pulse_id) + self.pulse_id=None # handling xml stanzas - def request_command_list(self): - """ - Request the command list. Change stage on delivery - """ - query = xmpp.Iq(typ='get', to=xmpp.JID(self.jid), queryNS=xmpp.NS_DISCO_ITEMS) - query.setQuerynode(xmpp.NS_COMMANDS) + def request_command_list(self): + """ + Request the command list. Change stage on delivery + """ + query = xmpp.Iq(typ='get', to=xmpp.JID(self.jid), queryNS=xmpp.NS_DISCO_ITEMS) + query.setQuerynode(xmpp.NS_COMMANDS) - def callback(response): - '''Called on response to query.''' - # FIXME: move to connection_handlers.py - # is error => error stage - error = response.getError() - if error: - # extracting error description from xmpp/protocol.py - self.stage5(errorid = error) - return + def callback(response): + '''Called on response to query.''' + # FIXME: move to connection_handlers.py + # is error => error stage + error = response.getError() + if error: + # extracting error description from xmpp/protocol.py + self.stage5(errorid = error) + return - # no commands => no commands stage - # commands => command selection stage - query = response.getTag('query') - if query and query.getAttr('node') == xmpp.NS_COMMANDS: - items = query.getTags('item') - else: - items = [] - if len(items)==0: - self.commandlist = [] - self.stage4() - else: - self.commandlist = [(t.getAttr('node'), t.getAttr('name')) for t in items] - self.stage2() + # no commands => no commands stage + # commands => command selection stage + query = response.getTag('query') + if query and query.getAttr('node') == xmpp.NS_COMMANDS: + items = query.getTags('item') + else: + items = [] + if len(items)==0: + self.commandlist = [] + self.stage4() + else: + self.commandlist = [(t.getAttr('node'), t.getAttr('name')) for t in items] + self.stage2() - self.account.connection.SendAndCallForResponse(query, callback) + self.account.connection.SendAndCallForResponse(query, callback) - def send_command(self, action='execute'): - """ - Send the command with data form. Wait for reply - """ - # create the stanza - assert isinstance(self.commandnode, unicode) - assert action in ('execute', 'prev', 'next', 'complete') + def send_command(self, action='execute'): + """ + Send the command with data form. Wait for reply + """ + # create the stanza + assert isinstance(self.commandnode, unicode) + assert action in ('execute', 'prev', 'next', 'complete') - stanza = xmpp.Iq(typ='set', to=self.jid) - cmdnode = stanza.addChild('command', namespace=xmpp.NS_COMMANDS, attrs={ - 'node':self.commandnode, 'action':action}) + stanza = xmpp.Iq(typ='set', to=self.jid) + cmdnode = stanza.addChild('command', namespace=xmpp.NS_COMMANDS, attrs={ + 'node':self.commandnode, 'action':action}) - if self.sessionid: - cmdnode.setAttr('sessionid', self.sessionid) + if self.sessionid: + cmdnode.setAttr('sessionid', self.sessionid) - if self.data_form_widget.data_form: -# cmdnode.addChild(node=dataforms.DataForm(tofill=self.data_form_widget.data_form)) - # FIXME: simplified form to send + if self.data_form_widget.data_form: +# cmdnode.addChild(node=dataforms.DataForm(tofill=self.data_form_widget.data_form)) + # FIXME: simplified form to send - cmdnode.addChild(node=self.data_form_widget.data_form) + cmdnode.addChild(node=self.data_form_widget.data_form) - def callback(response): - # FIXME: move to connection_handlers.py - err = response.getError() - if err: - self.stage5(errorid = err) - else: - self.stage3_next_form(response.getTag('command')) + def callback(response): + # FIXME: move to connection_handlers.py + err = response.getError() + if err: + self.stage5(errorid = err) + else: + self.stage3_next_form(response.getTag('command')) - self.account.connection.SendAndCallForResponse(stanza, callback) + self.account.connection.SendAndCallForResponse(stanza, callback) - def send_cancel(self): - """ - Send the command with action='cancel' - """ - assert self.commandnode - if self.sessionid and self.account.connection: - # we already have sessionid, so the service sent at least one reply. - stanza = xmpp.Iq(typ='set', to=self.jid) - stanza.addChild('command', namespace=xmpp.NS_COMMANDS, attrs={ - 'node':self.commandnode, - 'sessionid':self.sessionid, - 'action':'cancel' - }) + def send_cancel(self): + """ + Send the command with action='cancel' + """ + assert self.commandnode + if self.sessionid and self.account.connection: + # we already have sessionid, so the service sent at least one reply. + stanza = xmpp.Iq(typ='set', to=self.jid) + stanza.addChild('command', namespace=xmpp.NS_COMMANDS, attrs={ + 'node':self.commandnode, + 'sessionid':self.sessionid, + 'action':'cancel' + }) - self.account.connection.send(stanza) - else: - # we did not received any reply from service; FIXME: we should wait and - # then send cancel; for now we do nothing - pass - -# vim: se ts=3: + self.account.connection.send(stanza) + else: + # we did not received any reply from service; FIXME: we should wait and + # then send cancel; for now we do nothing + pass diff --git a/src/advanced_configuration_window.py b/src/advanced_configuration_window.py index b35461287..736be2bd8 100644 --- a/src/advanced_configuration_window.py +++ b/src/advanced_configuration_window.py @@ -43,250 +43,248 @@ C_TYPE GTKGUI_GLADE = 'manage_accounts_window.glade' def rate_limit(rate): - """ - Call func at most *rate* times per second - """ - def decorator(func): - timeout = [None] - def f(*args, **kwargs): - if timeout[0] is not None: - gobject.source_remove(timeout[0]) - timeout[0] = None - def timeout_func(): - func(*args, **kwargs) - timeout[0] = None - timeout[0] = gobject.timeout_add(int(1000.0 / rate), timeout_func) - return f - return decorator + """ + Call func at most *rate* times per second + """ + def decorator(func): + timeout = [None] + def f(*args, **kwargs): + if timeout[0] is not None: + gobject.source_remove(timeout[0]) + timeout[0] = None + def timeout_func(): + func(*args, **kwargs) + timeout[0] = None + timeout[0] = gobject.timeout_add(int(1000.0 / rate), timeout_func) + return f + return decorator def tree_model_iter_children(model, treeiter): - it = model.iter_children(treeiter) - while it: - yield it - it = model.iter_next(it) + it = model.iter_children(treeiter) + while it: + yield it + it = model.iter_next(it) def tree_model_pre_order(model, treeiter): - yield treeiter - for childiter in tree_model_iter_children(model, treeiter): - for it in tree_model_pre_order(model, childiter): - yield it + yield treeiter + for childiter in tree_model_iter_children(model, treeiter): + for it in tree_model_pre_order(model, childiter): + yield it class AdvancedConfigurationWindow(object): - def __init__(self): - self.xml = gtkgui_helpers.get_glade('advanced_configuration_window.glade') - self.window = self.xml.get_widget('advanced_configuration_window') - self.window.set_transient_for( - gajim.interface.instances['preferences'].window) - self.entry = self.xml.get_widget('advanced_entry') - self.desc_label = self.xml.get_widget('advanced_desc_label') - self.restart_label = self.xml.get_widget('restart_label') + def __init__(self): + self.xml = gtkgui_helpers.get_glade('advanced_configuration_window.glade') + self.window = self.xml.get_widget('advanced_configuration_window') + self.window.set_transient_for( + gajim.interface.instances['preferences'].window) + self.entry = self.xml.get_widget('advanced_entry') + self.desc_label = self.xml.get_widget('advanced_desc_label') + self.restart_label = self.xml.get_widget('restart_label') - # Format: - # key = option name (root/subopt/opt separated by \n then) - # value = array(oldval, newval) - self.changed_opts = {} + # Format: + # key = option name (root/subopt/opt separated by \n then) + # value = array(oldval, newval) + self.changed_opts = {} - # For i18n - self.right_true_dict = {True: _('Activated'), False: _('Deactivated')} - self.types = { - 'boolean': _('Boolean'), - 'integer': _('Integer'), - 'string': _('Text'), - 'color': _('Color')} + # For i18n + self.right_true_dict = {True: _('Activated'), False: _('Deactivated')} + self.types = { + 'boolean': _('Boolean'), + 'integer': _('Integer'), + 'string': _('Text'), + 'color': _('Color')} - treeview = self.xml.get_widget('advanced_treeview') - self.treeview = treeview - self.model = gtk.TreeStore(str, str, str) - self.fill_model() - self.model.set_sort_column_id(0, gtk.SORT_ASCENDING) - self.modelfilter = self.model.filter_new() - self.modelfilter.set_visible_func(self.visible_func) + treeview = self.xml.get_widget('advanced_treeview') + self.treeview = treeview + self.model = gtk.TreeStore(str, str, str) + self.fill_model() + self.model.set_sort_column_id(0, gtk.SORT_ASCENDING) + self.modelfilter = self.model.filter_new() + self.modelfilter.set_visible_func(self.visible_func) - renderer_text = gtk.CellRendererText() - col = treeview.insert_column_with_attributes(-1, _('Preference Name'), - renderer_text, text = 0) - col.set_resizable(True) + renderer_text = gtk.CellRendererText() + col = treeview.insert_column_with_attributes(-1, _('Preference Name'), + renderer_text, text = 0) + col.set_resizable(True) - renderer_text = gtk.CellRendererText() - renderer_text.connect('edited', self.on_config_edited) - col = treeview.insert_column_with_attributes(-1, _('Value'), - renderer_text, text = 1) - col.set_cell_data_func(renderer_text, self.cb_value_column_data) + renderer_text = gtk.CellRendererText() + renderer_text.connect('edited', self.on_config_edited) + col = treeview.insert_column_with_attributes(-1, _('Value'), + renderer_text, text = 1) + col.set_cell_data_func(renderer_text, self.cb_value_column_data) - col.props.resizable = True - col.set_max_width(250) + col.props.resizable = True + col.set_max_width(250) - renderer_text = gtk.CellRendererText() - treeview.insert_column_with_attributes(-1, _('Type'), - renderer_text, text = 2) + renderer_text = gtk.CellRendererText() + treeview.insert_column_with_attributes(-1, _('Type'), + renderer_text, text = 2) - treeview.set_model(self.modelfilter) + treeview.set_model(self.modelfilter) - # connect signal for selection change - treeview.get_selection().connect('changed', - self.on_advanced_treeview_selection_changed) + # connect signal for selection change + treeview.get_selection().connect('changed', + self.on_advanced_treeview_selection_changed) - self.xml.signal_autoconnect(self) - self.window.show_all() - self.restart_label.hide() - gajim.interface.instances['advanced_config'] = self + self.xml.signal_autoconnect(self) + self.window.show_all() + self.restart_label.hide() + gajim.interface.instances['advanced_config'] = self - def cb_value_column_data(self, col, cell, model, iter_): - """ - Check if it's boolen or holds password stuff and if yes make the - cellrenderertext not editable, else - it's editable - """ - optname = model[iter_][C_PREFNAME] - opttype = model[iter_][C_TYPE] - if opttype == self.types['boolean'] or optname == 'password': - cell.set_property('editable', False) - else: - cell.set_property('editable', True) + def cb_value_column_data(self, col, cell, model, iter_): + """ + Check if it's boolen or holds password stuff and if yes make the + cellrenderertext not editable, else - it's editable + """ + optname = model[iter_][C_PREFNAME] + opttype = model[iter_][C_TYPE] + if opttype == self.types['boolean'] or optname == 'password': + cell.set_property('editable', False) + else: + cell.set_property('editable', True) - def get_option_path(self, model, iter_): - # It looks like path made from reversed array - # path[0] is the true one optname - # path[1] is the key name - # path[2] is the root of tree - # last two is optional - path = [model[iter_][0].decode('utf-8')] - parent = model.iter_parent(iter_) - while parent: - path.append(model[parent][0].decode('utf-8')) - parent = model.iter_parent(parent) - return path + def get_option_path(self, model, iter_): + # It looks like path made from reversed array + # path[0] is the true one optname + # path[1] is the key name + # path[2] is the root of tree + # last two is optional + path = [model[iter_][0].decode('utf-8')] + parent = model.iter_parent(iter_) + while parent: + path.append(model[parent][0].decode('utf-8')) + parent = model.iter_parent(parent) + return path - def on_advanced_treeview_selection_changed(self, treeselection): - model, iter_ = treeselection.get_selected() - # Check for GtkTreeIter - if iter_: - opt_path = self.get_option_path(model, iter_) - # Get text from first column in this row - desc = None - if len(opt_path) == 3: - desc = gajim.config.get_desc_per(opt_path[2], opt_path[1], - opt_path[0]) - elif len(opt_path) == 1: - desc = gajim.config.get_desc(opt_path[0]) - if desc: - self.desc_label.set_text(desc) - else: - #we talk about option description in advanced configuration editor - self.desc_label.set_text(_('(None)')) + def on_advanced_treeview_selection_changed(self, treeselection): + model, iter_ = treeselection.get_selected() + # Check for GtkTreeIter + if iter_: + opt_path = self.get_option_path(model, iter_) + # Get text from first column in this row + desc = None + if len(opt_path) == 3: + desc = gajim.config.get_desc_per(opt_path[2], opt_path[1], + opt_path[0]) + elif len(opt_path) == 1: + desc = gajim.config.get_desc(opt_path[0]) + if desc: + self.desc_label.set_text(desc) + else: + #we talk about option description in advanced configuration editor + self.desc_label.set_text(_('(None)')) - def remember_option(self, option, oldval, newval): - if option in self.changed_opts: - self.changed_opts[option] = (self.changed_opts[option][0], newval) - else: - self.changed_opts[option] = (oldval, newval) + def remember_option(self, option, oldval, newval): + if option in self.changed_opts: + self.changed_opts[option] = (self.changed_opts[option][0], newval) + else: + self.changed_opts[option] = (oldval, newval) - def on_advanced_treeview_row_activated(self, treeview, path, column): - modelpath = self.modelfilter.convert_path_to_child_path(path) - modelrow = self.model[modelpath] - option = modelrow[0].decode('utf-8') - if modelrow[2] == self.types['boolean']: - for key in self.right_true_dict.keys(): - if self.right_true_dict[key] == modelrow[1]: - modelrow[1] = key - newval = {'False': True, 'True': False}[modelrow[1]] - if len(modelpath) > 1: - optnamerow = self.model[modelpath[0]] - optname = optnamerow[0].decode('utf-8') - keyrow = self.model[modelpath[:2]] - key = keyrow[0].decode('utf-8') - gajim.config.get_desc_per(optname, key, option) - self.remember_option(option + '\n' + key + '\n' + optname, - modelrow[1], newval) - gajim.config.set_per(optname, key, option, newval) - else: - self.remember_option(option, modelrow[1], newval) - gajim.config.set(option, newval) - gajim.interface.save_config() - modelrow[1] = self.right_true_dict[newval] - self.check_for_restart() + def on_advanced_treeview_row_activated(self, treeview, path, column): + modelpath = self.modelfilter.convert_path_to_child_path(path) + modelrow = self.model[modelpath] + option = modelrow[0].decode('utf-8') + if modelrow[2] == self.types['boolean']: + for key in self.right_true_dict.keys(): + if self.right_true_dict[key] == modelrow[1]: + modelrow[1] = key + newval = {'False': True, 'True': False}[modelrow[1]] + if len(modelpath) > 1: + optnamerow = self.model[modelpath[0]] + optname = optnamerow[0].decode('utf-8') + keyrow = self.model[modelpath[:2]] + key = keyrow[0].decode('utf-8') + gajim.config.get_desc_per(optname, key, option) + self.remember_option(option + '\n' + key + '\n' + optname, + modelrow[1], newval) + gajim.config.set_per(optname, key, option, newval) + else: + self.remember_option(option, modelrow[1], newval) + gajim.config.set(option, newval) + gajim.interface.save_config() + modelrow[1] = self.right_true_dict[newval] + self.check_for_restart() - def check_for_restart(self): - self.restart_label.hide() - for opt in self.changed_opts: - opt_path = opt.split('\n') - if len(opt_path)==3: - restart = gajim.config.get_restart_per(opt_path[2], opt_path[1], - opt_path[0]) - else: - restart = gajim.config.get_restart(opt_path[0]) - if restart: - if self.changed_opts[opt][0] != self.changed_opts[opt][1]: - self.restart_label.show() - break + def check_for_restart(self): + self.restart_label.hide() + for opt in self.changed_opts: + opt_path = opt.split('\n') + if len(opt_path)==3: + restart = gajim.config.get_restart_per(opt_path[2], opt_path[1], + opt_path[0]) + else: + restart = gajim.config.get_restart(opt_path[0]) + if restart: + if self.changed_opts[opt][0] != self.changed_opts[opt][1]: + self.restart_label.show() + break - def on_config_edited(self, cell, path, text): - # convert modelfilter path to model path - modelpath = self.modelfilter.convert_path_to_child_path(path) - modelrow = self.model[modelpath] - option = modelrow[0].decode('utf-8') - text = text.decode('utf-8') - if len(modelpath) > 1: - optnamerow = self.model[modelpath[0]] - optname = optnamerow[0].decode('utf-8') - keyrow = self.model[modelpath[:2]] - key = keyrow[0].decode('utf-8') - self.remember_option(option + '\n' + key + '\n' + optname, modelrow[1], - text) - gajim.config.set_per(optname, key, option, text) - else: - self.remember_option(option, modelrow[1], text) - gajim.config.set(option, text) - gajim.interface.save_config() - modelrow[1] = text - self.check_for_restart() + def on_config_edited(self, cell, path, text): + # convert modelfilter path to model path + modelpath = self.modelfilter.convert_path_to_child_path(path) + modelrow = self.model[modelpath] + option = modelrow[0].decode('utf-8') + text = text.decode('utf-8') + if len(modelpath) > 1: + optnamerow = self.model[modelpath[0]] + optname = optnamerow[0].decode('utf-8') + keyrow = self.model[modelpath[:2]] + key = keyrow[0].decode('utf-8') + self.remember_option(option + '\n' + key + '\n' + optname, modelrow[1], + text) + gajim.config.set_per(optname, key, option, text) + else: + self.remember_option(option, modelrow[1], text) + gajim.config.set(option, text) + gajim.interface.save_config() + modelrow[1] = text + self.check_for_restart() - def on_advanced_configuration_window_destroy(self, widget): - del gajim.interface.instances['advanced_config'] + def on_advanced_configuration_window_destroy(self, widget): + del gajim.interface.instances['advanced_config'] - def on_advanced_close_button_clicked(self, widget): - self.window.destroy() + def on_advanced_close_button_clicked(self, widget): + self.window.destroy() - def fill_model(self, node=None, parent=None): - for item, option in gajim.config.get_children(node): - name = item[-1] - if option is None: # Node - newparent = self.model.append(parent, [name, '', '']) - self.fill_model(item, newparent) - else: # Leaf - type_ = self.types[option[OPT_TYPE][0]] - if name == 'password': - value = _('Hidden') - else: - if type_ == self.types['boolean']: - value = self.right_true_dict[option[OPT_VAL]] - else: - value = option[OPT_VAL] - self.model.append(parent, [name, value, type_]) + def fill_model(self, node=None, parent=None): + for item, option in gajim.config.get_children(node): + name = item[-1] + if option is None: # Node + newparent = self.model.append(parent, [name, '', '']) + self.fill_model(item, newparent) + else: # Leaf + type_ = self.types[option[OPT_TYPE][0]] + if name == 'password': + value = _('Hidden') + else: + if type_ == self.types['boolean']: + value = self.right_true_dict[option[OPT_VAL]] + else: + value = option[OPT_VAL] + self.model.append(parent, [name, value, type_]) - def visible_func(self, model, treeiter): - search_string = self.entry.get_text().decode('utf-8').lower() - for it in tree_model_pre_order(model,treeiter): - if model[it][C_TYPE] != '': - opt_path = self.get_option_path(model, it) - if len(opt_path) == 3: - desc = gajim.config.get_desc_per(opt_path[2], opt_path[1], - opt_path[0]) - elif len(opt_path) == 1: - desc = gajim.config.get_desc(opt_path[0]) - if search_string in model[it][C_PREFNAME] or (desc and \ - search_string in desc.lower()): - return True - return False + def visible_func(self, model, treeiter): + search_string = self.entry.get_text().decode('utf-8').lower() + for it in tree_model_pre_order(model, treeiter): + if model[it][C_TYPE] != '': + opt_path = self.get_option_path(model, it) + if len(opt_path) == 3: + desc = gajim.config.get_desc_per(opt_path[2], opt_path[1], + opt_path[0]) + elif len(opt_path) == 1: + desc = gajim.config.get_desc(opt_path[0]) + if search_string in model[it][C_PREFNAME] or (desc and \ + search_string in desc.lower()): + return True + return False - @rate_limit(3) - def on_advanced_entry_changed(self, widget): - self.modelfilter.refilter() - if not widget.get_text(): - # Maybe the expanded rows should be remembered here ... - self.treeview.collapse_all() - else: - # ... and be restored correctly here - self.treeview.expand_all() - -# vim: se ts=3: + @rate_limit(3) + def on_advanced_entry_changed(self, widget): + self.modelfilter.refilter() + if not widget.get_text(): + # Maybe the expanded rows should be remembered here ... + self.treeview.collapse_all() + else: + # ... and be restored correctly here + self.treeview.expand_all() diff --git a/src/atom_window.py b/src/atom_window.py index 4712dfc41..84d866b4e 100644 --- a/src/atom_window.py +++ b/src/atom_window.py @@ -30,121 +30,119 @@ from common import helpers from common import i18n class AtomWindow: - window = None - entries = [] + window = None + entries = [] - @classmethod - def newAtomEntry(cls, entry): - """ - Queue new entry, open window if there's no one opened - """ - cls.entries.append(entry) + @classmethod + def newAtomEntry(cls, entry): + """ + Queue new entry, open window if there's no one opened + """ + cls.entries.append(entry) - if cls.window is None: - cls.window = AtomWindow() - else: - cls.window.updateCounter() + if cls.window is None: + cls.window = AtomWindow() + else: + cls.window.updateCounter() - @classmethod - def windowClosed(cls): - cls.window = None + @classmethod + def windowClosed(cls): + cls.window = None - def __init__(self): - """ - Create new window... only if we have anything to show - """ - assert len(self.__class__.entries)>0 + def __init__(self): + """ + Create new window... only if we have anything to show + """ + assert len(self.__class__.entries)>0 - self.entry = None # the entry actually displayed + self.entry = None # the entry actually displayed - self.xml = gtkgui_helpers.get_glade('atom_entry_window.glade') - self.window = self.xml.get_widget('atom_entry_window') - for name in ('new_entry_label', 'feed_title_label', 'feed_title_eventbox', - 'feed_tagline_label', 'entry_title_label', 'entry_title_eventbox', - 'last_modified_label', 'close_button', 'next_button'): - self.__dict__[name] = self.xml.get_widget(name) + self.xml = gtkgui_helpers.get_glade('atom_entry_window.glade') + self.window = self.xml.get_widget('atom_entry_window') + for name in ('new_entry_label', 'feed_title_label', 'feed_title_eventbox', + 'feed_tagline_label', 'entry_title_label', 'entry_title_eventbox', + 'last_modified_label', 'close_button', 'next_button'): + self.__dict__[name] = self.xml.get_widget(name) - self.displayNextEntry() + self.displayNextEntry() - self.xml.signal_autoconnect(self) - self.window.show_all() + self.xml.signal_autoconnect(self) + self.window.show_all() - self.entry_title_eventbox.add_events(gtk.gdk.BUTTON_PRESS_MASK) - self.feed_title_eventbox.add_events(gtk.gdk.BUTTON_PRESS_MASK) + self.entry_title_eventbox.add_events(gtk.gdk.BUTTON_PRESS_MASK) + self.feed_title_eventbox.add_events(gtk.gdk.BUTTON_PRESS_MASK) - def displayNextEntry(self): - """ - Get next entry from the queue and display it in the window - """ - assert len(self.__class__.entries)>0 + def displayNextEntry(self): + """ + Get next entry from the queue and display it in the window + """ + assert len(self.__class__.entries)>0 - newentry = self.__class__.entries.pop(0) + newentry = self.__class__.entries.pop(0) - # fill the fields - if newentry.feed_link is not None: - self.feed_title_label.set_markup( - u'%s' % \ - gobject.markup_escape_text(newentry.feed_title)) - else: - self.feed_title_label.set_markup( - gobject.markup_escape_text(newentry.feed_title)) + # fill the fields + if newentry.feed_link is not None: + self.feed_title_label.set_markup( + u'%s' % \ + gobject.markup_escape_text(newentry.feed_title)) + else: + self.feed_title_label.set_markup( + gobject.markup_escape_text(newentry.feed_title)) - self.feed_tagline_label.set_markup( - u'%s' % \ - gobject.markup_escape_text(newentry.feed_tagline)) + self.feed_tagline_label.set_markup( + u'%s' % \ + gobject.markup_escape_text(newentry.feed_tagline)) - if newentry.uri is not None: - self.entry_title_label.set_markup( - u'%s' % \ - gobject.markup_escape_text(newentry.title)) - else: - self.entry_title_label.set_markup( - gobject.markup_escape_text(newentry.title)) + if newentry.uri is not None: + self.entry_title_label.set_markup( + u'%s' % \ + gobject.markup_escape_text(newentry.title)) + else: + self.entry_title_label.set_markup( + gobject.markup_escape_text(newentry.title)) - self.last_modified_label.set_text(newentry.updated) + self.last_modified_label.set_text(newentry.updated) - # update the counters - self.updateCounter() + # update the counters + self.updateCounter() - self.entry = newentry + self.entry = newentry - def updateCounter(self): - """ - Display number of events on the top of window, sometimes it needs to be - changed - """ - count = len(self.__class__.entries) - if count>0: - self.new_entry_label.set_text(i18n.ngettext( - 'You have received new entries (and %d not displayed):', - 'You have received new entries (and %d not displayed):', count, - count, count)) - self.next_button.set_sensitive(True) - else: - self.new_entry_label.set_text(_('You have received new entry:')) - self.next_button.set_sensitive(False) + def updateCounter(self): + """ + Display number of events on the top of window, sometimes it needs to be + changed + """ + count = len(self.__class__.entries) + if count>0: + self.new_entry_label.set_text(i18n.ngettext( + 'You have received new entries (and %d not displayed):', + 'You have received new entries (and %d not displayed):', count, + count, count)) + self.next_button.set_sensitive(True) + else: + self.new_entry_label.set_text(_('You have received new entry:')) + self.next_button.set_sensitive(False) - def on_close_button_clicked(self, widget): - self.window.destroy() - self.windowClosed() + def on_close_button_clicked(self, widget): + self.window.destroy() + self.windowClosed() - def on_next_button_clicked(self, widget): - self.displayNextEntry() + def on_next_button_clicked(self, widget): + self.displayNextEntry() - def on_entry_title_button_press_event(self, widget, event): - #FIXME: make it using special gtk2.10 widget - if event.button == 1: # left click - uri = self.entry.uri - if uri is not None: - helpers.launch_browser_mailer('url', uri) - return True + def on_entry_title_button_press_event(self, widget, event): + #FIXME: make it using special gtk2.10 widget + if event.button == 1: # left click + uri = self.entry.uri + if uri is not None: + helpers.launch_browser_mailer('url', uri) + return True - def on_feed_title_button_press_event(self, widget, event): - #FIXME: make it using special gtk2.10 widget - if event.button == 1: # left click - uri = self.entry.feed_uri - if uri is not None: - helpers.launch_browser_mailer('url', uri) - return True - -# vim: se ts=3: + def on_feed_title_button_press_event(self, widget, event): + #FIXME: make it using special gtk2.10 widget + if event.button == 1: # left click + uri = self.entry.feed_uri + if uri is not None: + helpers.launch_browser_mailer('url', uri) + return True diff --git a/src/cell_renderer_image.py b/src/cell_renderer_image.py index e1cdf8c81..0b85e8c44 100644 --- a/src/cell_renderer_image.py +++ b/src/cell_renderer_image.py @@ -27,113 +27,111 @@ import gobject class CellRendererImage(gtk.GenericCellRenderer): - __gproperties__ = { - 'image': (gobject.TYPE_OBJECT, 'Image', - 'Image', gobject.PARAM_READWRITE), - } + __gproperties__ = { + 'image': (gobject.TYPE_OBJECT, 'Image', + 'Image', gobject.PARAM_READWRITE), + } - def __init__(self, col_index, tv_index): - self.__gobject_init__() - self.image = None - self.col_index = col_index - self.tv_index = tv_index - self.iters = {} + def __init__(self, col_index, tv_index): + self.__gobject_init__() + self.image = None + self.col_index = col_index + self.tv_index = tv_index + self.iters = {} - def do_set_property(self, pspec, value): - setattr(self, pspec.name, value) + def do_set_property(self, pspec, value): + setattr(self, pspec.name, value) - def do_get_property(self, pspec): - return getattr(self, pspec.name) + def do_get_property(self, pspec): + return getattr(self, pspec.name) - def func(self, model, path, iter_, image_tree): - image, tree = image_tree - if model.get_value(iter_, self.tv_index) != image: - return - self.redraw = 1 - col = tree.get_column(self.col_index) - cell_area = tree.get_cell_area(path, col) + def func(self, model, path, iter_, image_tree): + image, tree = image_tree + if model.get_value(iter_, self.tv_index) != image: + return + self.redraw = 1 + col = tree.get_column(self.col_index) + cell_area = tree.get_cell_area(path, col) - tree.queue_draw_area(cell_area.x, cell_area.y, - cell_area.width, cell_area.height) + tree.queue_draw_area(cell_area.x, cell_area.y, + cell_area.width, cell_area.height) - def animation_timeout(self, tree, image): - if image.get_storage_type() != gtk.IMAGE_ANIMATION: - return - self.redraw = 0 - iter_ = self.iters[image] - iter_.advance() - model = tree.get_model() - if model: - model.foreach(self.func, (image, tree)) - if self.redraw: - gobject.timeout_add(iter_.get_delay_time(), - self.animation_timeout, tree, image) - elif image in self.iters: - del self.iters[image] + def animation_timeout(self, tree, image): + if image.get_storage_type() != gtk.IMAGE_ANIMATION: + return + self.redraw = 0 + iter_ = self.iters[image] + iter_.advance() + model = tree.get_model() + if model: + model.foreach(self.func, (image, tree)) + if self.redraw: + gobject.timeout_add(iter_.get_delay_time(), + self.animation_timeout, tree, image) + elif image in self.iters: + del self.iters[image] - def on_render(self, window, widget, background_area, cell_area, - expose_area, flags): - if not self.image: - return - pix_rect = gtk.gdk.Rectangle() - pix_rect.x, pix_rect.y, pix_rect.width, pix_rect.height = \ - self.on_get_size(widget, cell_area) + def on_render(self, window, widget, background_area, cell_area, + expose_area, flags): + if not self.image: + return + pix_rect = gtk.gdk.Rectangle() + pix_rect.x, pix_rect.y, pix_rect.width, pix_rect.height = \ + self.on_get_size(widget, cell_area) - pix_rect.x += cell_area.x - pix_rect.y += cell_area.y - pix_rect.width -= 2 * self.get_property('xpad') - pix_rect.height -= 2 * self.get_property('ypad') + pix_rect.x += cell_area.x + pix_rect.y += cell_area.y + pix_rect.width -= 2 * self.get_property('xpad') + pix_rect.height -= 2 * self.get_property('ypad') - draw_rect = cell_area.intersect(pix_rect) - draw_rect = expose_area.intersect(draw_rect) + draw_rect = cell_area.intersect(pix_rect) + draw_rect = expose_area.intersect(draw_rect) - if self.image.get_storage_type() == gtk.IMAGE_ANIMATION: - if self.image not in self.iters: - if not isinstance(widget, gtk.TreeView): - return - animation = self.image.get_animation() - iter_ = animation.get_iter() - self.iters[self.image] = iter_ - gobject.timeout_add(iter_.get_delay_time(), - self.animation_timeout, widget, self.image) + if self.image.get_storage_type() == gtk.IMAGE_ANIMATION: + if self.image not in self.iters: + if not isinstance(widget, gtk.TreeView): + return + animation = self.image.get_animation() + iter_ = animation.get_iter() + self.iters[self.image] = iter_ + gobject.timeout_add(iter_.get_delay_time(), + self.animation_timeout, widget, self.image) - pix = self.iters[self.image].get_pixbuf() - elif self.image.get_storage_type() == gtk.IMAGE_PIXBUF: - pix = self.image.get_pixbuf() - else: - return - if draw_rect.x < 1: - return - window.draw_pixbuf(widget.style.black_gc, pix, - draw_rect.x - pix_rect.x, - draw_rect.y - pix_rect.y, - draw_rect.x, draw_rect.y, - draw_rect.width, draw_rect.height, - gtk.gdk.RGB_DITHER_NONE, 0, 0) + pix = self.iters[self.image].get_pixbuf() + elif self.image.get_storage_type() == gtk.IMAGE_PIXBUF: + pix = self.image.get_pixbuf() + else: + return + if draw_rect.x < 1: + return + window.draw_pixbuf(widget.style.black_gc, pix, + draw_rect.x - pix_rect.x, + draw_rect.y - pix_rect.y, + draw_rect.x, draw_rect.y, + draw_rect.width, draw_rect.height, + gtk.gdk.RGB_DITHER_NONE, 0, 0) - def on_get_size(self, widget, cell_area): - if not self.image: - return 0, 0, 0, 0 - if self.image.get_storage_type() == gtk.IMAGE_ANIMATION: - animation = self.image.get_animation() - pix = animation.get_iter().get_pixbuf() - elif self.image.get_storage_type() == gtk.IMAGE_PIXBUF: - pix = self.image.get_pixbuf() - else: - return 0, 0, 0, 0 - pixbuf_width = pix.get_width() - pixbuf_height = pix.get_height() - calc_width = self.get_property('xpad') * 2 + pixbuf_width - calc_height = self.get_property('ypad') * 2 + pixbuf_height - x_offset = 0 - y_offset = 0 - if cell_area and pixbuf_width > 0 and pixbuf_height > 0: - x_offset = self.get_property('xalign') * \ - (cell_area.width - calc_width - \ - self.get_property('xpad')) - y_offset = self.get_property('yalign') * \ - (cell_area.height - calc_height - \ - self.get_property('ypad')) - return x_offset, y_offset, calc_width, calc_height - -# vim: se ts=3: + def on_get_size(self, widget, cell_area): + if not self.image: + return 0, 0, 0, 0 + if self.image.get_storage_type() == gtk.IMAGE_ANIMATION: + animation = self.image.get_animation() + pix = animation.get_iter().get_pixbuf() + elif self.image.get_storage_type() == gtk.IMAGE_PIXBUF: + pix = self.image.get_pixbuf() + else: + return 0, 0, 0, 0 + pixbuf_width = pix.get_width() + pixbuf_height = pix.get_height() + calc_width = self.get_property('xpad') * 2 + pixbuf_width + calc_height = self.get_property('ypad') * 2 + pixbuf_height + x_offset = 0 + y_offset = 0 + if cell_area and pixbuf_width > 0 and pixbuf_height > 0: + x_offset = self.get_property('xalign') * \ + (cell_area.width - calc_width - \ + self.get_property('xpad')) + y_offset = self.get_property('yalign') * \ + (cell_area.height - calc_height - \ + self.get_property('ypad')) + return x_offset, y_offset, calc_width, calc_height diff --git a/src/chat_control.py b/src/chat_control.py index 977214875..09bf3c63e 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -63,10 +63,10 @@ from command_system.implementation.hosts import ChatCommands import command_system.implementation.standard try: - import gtkspell - HAS_GTK_SPELL = True + import gtkspell + HAS_GTK_SPELL = True except ImportError: - HAS_GTK_SPELL = False + HAS_GTK_SPELL = False # the next script, executed in the "po" directory, # generates the following list. @@ -76,2744 +76,2742 @@ except ImportError: langs = {_('English'): 'en', _('Belarusian'): 'be', _('Bulgarian'): 'bg', _('Breton'): 'br', _('Czech'): 'cs', _('German'): 'de', _('Greek'): 'el', _('British'): 'en_GB', _('Esperanto'): 'eo', _('Spanish'): 'es', _('Basque'): 'eu', _('French'): 'fr', _('Croatian'): 'hr', _('Italian'): 'it', _('Norwegian (b)'): 'nb', _('Dutch'): 'nl', _('Norwegian'): 'no', _('Polish'): 'pl', _('Portuguese'): 'pt', _('Brazilian Portuguese'): 'pt_BR', _('Russian'): 'ru', _('Serbian'): 'sr', _('Slovak'): 'sk', _('Swedish'): 'sv', _('Chinese (Ch)'): 'zh_CN'} if gajim.config.get('use_speller') and HAS_GTK_SPELL: - # loop removing non-existent dictionaries - # iterating on a copy - tv = gtk.TextView() - spell = gtkspell.Spell(tv) - for lang in dict(langs): - try: - spell.set_language(langs[lang]) - except OSError: - del langs[lang] - if spell: - spell.detach() - del tv + # loop removing non-existent dictionaries + # iterating on a copy + tv = gtk.TextView() + spell = gtkspell.Spell(tv) + for lang in dict(langs): + try: + spell.set_language(langs[lang]) + except OSError: + del langs[lang] + if spell: + spell.detach() + del tv ################################################################################ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): - """ - A base class containing a banner, ConversationTextview, MessageTextView - """ - - def make_href(self, match): - url_color = gajim.config.get('urlmsgcolor') - return '%s' % (match.group(), - url_color, match.group()) - - def get_font_attrs(self): - """ - Get pango font attributes for banner from theme settings - """ - theme = gajim.config.get('roster_theme') - bannerfont = gajim.config.get_per('themes', theme, 'bannerfont') - bannerfontattrs = gajim.config.get_per('themes', theme, 'bannerfontattrs') - - if bannerfont: - font = pango.FontDescription(bannerfont) - else: - font = pango.FontDescription('Normal') - if bannerfontattrs: - # B attribute is set by default - if 'B' in bannerfontattrs: - font.set_weight(pango.WEIGHT_HEAVY) - if 'I' in bannerfontattrs: - font.set_style(pango.STYLE_ITALIC) - - font_attrs = 'font_desc="%s"' % font.to_string() - - # in case there is no font specified we use x-large font size - if font.get_size() == 0: - font_attrs = '%s size="x-large"' % font_attrs - font.set_weight(pango.WEIGHT_NORMAL) - font_attrs_small = 'font_desc="%s" size="small"' % font.to_string() - return (font_attrs, font_attrs_small) - - def get_nb_unread(self): - jid = self.contact.jid - if self.resource: - jid += '/' + self.resource - type_ = self.type_id - return len(gajim.events.get_events(self.account, jid, ['printed_' + type_, - type_])) - - def draw_banner(self): - """ - Draw the fat line at the top of the window that houses the icon, jid, etc - - Derived types MAY implement this. - """ - self.draw_banner_text() - self._update_banner_state_image() - - def draw_banner_text(self): - """ - Derived types SHOULD implement this - """ - pass - - def update_ui(self): - """ - Derived types SHOULD implement this - """ - self.draw_banner() - - def repaint_themed_widgets(self): - """ - Derived types MAY implement this - """ - self._paint_banner() - self.draw_banner() - - def _update_banner_state_image(self): - """ - Derived types MAY implement this - """ - pass - - def handle_message_textview_mykey_press(self, widget, event_keyval, - event_keymod): - """ - Derives types SHOULD implement this, rather than connection to the even - itself - """ - event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) - event.keyval = event_keyval - event.state = event_keymod - event.time = 0 - - buffer = widget.get_buffer() - start, end = buffer.get_bounds() - - if event.keyval -- gtk.keysyms.Tab: - position = buffer.get_insert() - end = buffer.get_iter_at_mark(position) - - text = buffer.get_text(start, end, False) - text = text.decode('utf8') - - splitted = text.split() - - if (text.startswith(self.COMMAND_PREFIX) and not - text.startswith(self.COMMAND_PREFIX * 2) and len(splitted) == 1): - - text = splitted[0] - bare = text.lstrip(self.COMMAND_PREFIX) - - if len(text) == 1: - self.command_hits = [] - for command in self.list_commands(): - for name in command.names: - self.command_hits.append(name) - else: - if (self.last_key_tabs and self.command_hits and - self.command_hits[0].startswith(bare)): - self.command_hits.append(self.command_hits.pop(0)) - else: - self.command_hits = [] - for command in self.list_commands(): - for name in command.names: - if name.startswith(bare): - self.command_hits.append(name) - - if self.command_hits: - buffer.delete(start, end) - buffer.insert_at_cursor(self.COMMAND_PREFIX + self.command_hits[0] + ' ') - self.last_key_tabs = True - - return True - - self.last_key_tabs = False - - def status_url_clicked(self, widget, url): - helpers.launch_browser_mailer('url', url) - - def __init__(self, type_id, parent_win, widget_name, contact, acct, - resource = None): - if resource is None: - # We very likely got a contact with a random resource. - # This is bad, we need the highest for caps etc. - c = gajim.contacts.get_contact_with_highest_priority( - acct, contact.jid) - if c and not isinstance(c, GC_Contact): - contact = c - - MessageControl.__init__(self, type_id, parent_win, widget_name, - contact, acct, resource = resource) - - widget = self.xml.get_widget('history_button') - id_ = widget.connect('clicked', self._on_history_menuitem_activate) - self.handlers[id_] = widget - - # when/if we do XHTML we will put formatting buttons back - widget = self.xml.get_widget('emoticons_button') - id_ = widget.connect('clicked', self.on_emoticons_button_clicked) - self.handlers[id_] = widget - - # Create banner and connect signals - widget = self.xml.get_widget('banner_eventbox') - id_ = widget.connect('button-press-event', - self._on_banner_eventbox_button_press_event) - self.handlers[id_] = widget - - self.urlfinder = re.compile( - r"(www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'\"]+[^!,\.\s<>\)'\"\]]") - - if gajim.HAVE_PYSEXY: - import sexy - self.banner_status_label = sexy.UrlLabel() - self.banner_status_label.connect('url_activated', - self.status_url_clicked) - else: - self.banner_status_label = gtk.Label() - self.banner_status_label.set_selectable(True) - self.banner_status_label.set_alignment(0,0.5) - self.banner_status_label.connect('populate_popup', - self.on_banner_label_populate_popup) - - banner_vbox = self.xml.get_widget('banner_vbox') - banner_vbox.pack_start(self.banner_status_label) - self.banner_status_label.show() - - # Init DND - self.TARGET_TYPE_URI_LIST = 80 - self.dnd_list = [ ( 'text/uri-list', 0, self.TARGET_TYPE_URI_LIST ), - ('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_APP, 0)] - id_ = self.widget.connect('drag_data_received', - self._on_drag_data_received) - self.handlers[id_] = self.widget - self.widget.drag_dest_set(gtk.DEST_DEFAULT_MOTION | - gtk.DEST_DEFAULT_HIGHLIGHT | - gtk.DEST_DEFAULT_DROP, - self.dnd_list, gtk.gdk.ACTION_COPY) - - # Create textviews and connect signals - self.conv_textview = ConversationTextview(self.account) - id_ = self.conv_textview.connect('quote', self.on_quote) - self.handlers[id_] = self.conv_textview.tv - id_ = self.conv_textview.tv.connect('key_press_event', - self._conv_textview_key_press_event) - self.handlers[id_] = self.conv_textview.tv - # FIXME: DND on non editable TextView, find a better way - self.drag_entered = False - id_ = self.conv_textview.tv.connect('drag_data_received', - self._on_drag_data_received) - self.handlers[id_] = self.conv_textview.tv - id_ = self.conv_textview.tv.connect('drag_motion', self._on_drag_motion) - self.handlers[id_] = self.conv_textview.tv - id_ = self.conv_textview.tv.connect('drag_leave', self._on_drag_leave) - self.handlers[id_] = self.conv_textview.tv - self.conv_textview.tv.drag_dest_set(gtk.DEST_DEFAULT_MOTION | - gtk.DEST_DEFAULT_HIGHLIGHT | - gtk.DEST_DEFAULT_DROP, - self.dnd_list, gtk.gdk.ACTION_COPY) - - self.conv_scrolledwindow = self.xml.get_widget( - 'conversation_scrolledwindow') - self.conv_scrolledwindow.add(self.conv_textview.tv) - widget = self.conv_scrolledwindow.get_vadjustment() - id_ = widget.connect('value-changed', - self.on_conversation_vadjustment_value_changed) - self.handlers[id_] = widget - id_ = widget.connect('changed', - self.on_conversation_vadjustment_changed) - self.handlers[id_] = widget - self.scroll_to_end_id = None - self.was_at_the_end = True - - # add MessageTextView to UI and connect signals - self.msg_scrolledwindow = self.xml.get_widget('message_scrolledwindow') - self.msg_textview = MessageTextView() - id_ = self.msg_textview.connect('mykeypress', - self._on_message_textview_mykeypress_event) - self.handlers[id_] = self.msg_textview - self.msg_scrolledwindow.add(self.msg_textview) - id_ = self.msg_textview.connect('key_press_event', - self._on_message_textview_key_press_event) - self.handlers[id_] = self.msg_textview - id_ = self.msg_textview.connect('size-request', self.size_request) - self.handlers[id_] = self.msg_textview - id_ = self.msg_textview.connect('populate_popup', - self.on_msg_textview_populate_popup) - self.handlers[id_] = self.msg_textview - # Setup DND - id_ = self.msg_textview.connect('drag_data_received', - self._on_drag_data_received) - self.handlers[id_] = self.msg_textview - self.msg_textview.drag_dest_set(gtk.DEST_DEFAULT_MOTION | - gtk.DEST_DEFAULT_HIGHLIGHT, - self.dnd_list, gtk.gdk.ACTION_COPY) - - self.update_font() - - # Hook up send button - widget = self.xml.get_widget('send_button') - id_ = widget.connect('clicked', self._on_send_button_clicked) - self.handlers[id_] = widget - - widget = self.xml.get_widget('formattings_button') - id_ = widget.connect('clicked', self.on_formattings_button_clicked) - self.handlers[id_] = widget - - # the following vars are used to keep history of user's messages - self.sent_history = [] - self.sent_history_pos = 0 - self.orig_msg = None - - # Emoticons menu - # set image no matter if user wants at this time emoticons or not - # (so toggle works ok) - img = self.xml.get_widget('emoticons_button_image') - img.set_from_file(os.path.join(gajim.DATA_DIR, 'emoticons', 'static', - 'smile.png')) - self.toggle_emoticons() - - # Attach speller - if gajim.config.get('use_speller') and HAS_GTK_SPELL: - self.set_speller() - self.conv_textview.tv.show() - self._paint_banner() - - # For XEP-0172 - self.user_nick = None - - self.smooth = True - self.msg_textview.grab_focus() - - self.command_hits = [] - self.last_key_tabs = False - - def set_speller(self): - # now set the one the user selected - per_type = 'contacts' - if self.type_id == message_control.TYPE_GC: - per_type = 'rooms' - lang = gajim.config.get_per(per_type, self.contact.jid, - 'speller_language') - if not lang: - # use the default one - lang = gajim.config.get('speller_language') - if not lang: - lang = gajim.LANG - if lang: - try: - gtkspell.Spell(self.msg_textview, lang) - self.msg_textview.lang = lang - except (gobject.GError, RuntimeError, TypeError, OSError): - dialogs.AspellDictError(lang) - - def on_banner_label_populate_popup(self, label, menu): - """ - Override the default context menu and add our own menutiems - """ - item = gtk.SeparatorMenuItem() - menu.prepend(item) - - menu2 = self.prepare_context_menu() - i = 0 - for item in menu2: - menu2.remove(item) - menu.prepend(item) - menu.reorder_child(item, i) - i += 1 - menu.show_all() - - def on_msg_textview_populate_popup(self, textview, menu): - """ - Override the default context menu and we prepend an option to switch - languages - """ - def _on_select_dictionary(widget, lang): - per_type = 'contacts' - if self.type_id == message_control.TYPE_GC: - per_type = 'rooms' - if not gajim.config.get_per(per_type, self.contact.jid): - gajim.config.add_per(per_type, self.contact.jid) - gajim.config.set_per(per_type, self.contact.jid, 'speller_language', - lang) - spell = gtkspell.get_from_text_view(self.msg_textview) - self.msg_textview.lang = lang - spell.set_language(lang) - widget.set_active(True) - - item = gtk.SeparatorMenuItem() - menu.prepend(item) - - item = gtk.ImageMenuItem(gtk.STOCK_CLEAR) - menu.prepend(item) - id_ = item.connect('activate', self.msg_textview.clear) - self.handlers[id_] = item - - if gajim.config.get('use_speller') and HAS_GTK_SPELL: - item = gtk.MenuItem(_('Spelling language')) - menu.prepend(item) - submenu = gtk.Menu() - item.set_submenu(submenu) - for lang in sorted(langs): - item = gtk.CheckMenuItem(lang) - if langs[lang] == self.msg_textview.lang: - item.set_active(True) - submenu.append(item) - id_ = item.connect('activate', _on_select_dictionary, langs[lang]) - self.handlers[id_] = item - - menu.show_all() - - def on_quote(self, widget, text): - text = '>' + text.replace('\n', '\n>') + '\n' - message_buffer = self.msg_textview.get_buffer() - message_buffer.insert_at_cursor(text) - - # moved from ChatControl - def _on_banner_eventbox_button_press_event(self, widget, event): - """ - If right-clicked, show popup - """ - if event.button == 3: # right click - self.parent_win.popup_menu(event) - - def _on_send_button_clicked(self, widget): - """ - When send button is pressed: send the current message - """ - if gajim.connections[self.account].connected < 2: # we are not connected - dialogs.ErrorDialog(_('A connection is not available'), - _('Your message can not be sent until you are connected.')) - return - message_buffer = self.msg_textview.get_buffer() - start_iter = message_buffer.get_start_iter() - end_iter = message_buffer.get_end_iter() - message = message_buffer.get_text(start_iter, end_iter, 0).decode('utf-8') - xhtml = self.msg_textview.get_xhtml() - - # send the message - self.send_message(message, xhtml=xhtml) - - def _paint_banner(self): - """ - Repaint banner with theme color - """ - theme = gajim.config.get('roster_theme') - bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor') - textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor') - # the backgrounds are colored by using an eventbox by - # setting the bg color of the eventbox and the fg of the name_label - banner_eventbox = self.xml.get_widget('banner_eventbox') - banner_name_label = self.xml.get_widget('banner_name_label') - self.disconnect_style_event(banner_name_label) - self.disconnect_style_event(self.banner_status_label) - if bgcolor: - banner_eventbox.modify_bg(gtk.STATE_NORMAL, - gtk.gdk.color_parse(bgcolor)) - default_bg = False - else: - default_bg = True - if textcolor: - banner_name_label.modify_fg(gtk.STATE_NORMAL, - gtk.gdk.color_parse(textcolor)) - self.banner_status_label.modify_fg(gtk.STATE_NORMAL, - gtk.gdk.color_parse(textcolor)) - default_fg = False - else: - default_fg = True - if default_bg or default_fg: - self._on_style_set_event(banner_name_label, None, default_fg, - default_bg) - if self.banner_status_label.flags() & gtk.REALIZED: - # Widget is realized - self._on_style_set_event(self.banner_status_label, None, default_fg, - default_bg) - - def disconnect_style_event(self, widget): - # Try to find the event_id - for id_ in self.handlers.keys(): - if self.handlers[id_] == widget: - widget.disconnect(id_) - del self.handlers[id_] - break - - def connect_style_event(self, widget, set_fg = False, set_bg = False): - self.disconnect_style_event(widget) - id_ = widget.connect('style-set', self._on_style_set_event, set_fg, - set_bg) - self.handlers[id_] = widget - - def _on_style_set_event(self, widget, style, *opts): - """ - Set style of widget from style class *.Frame.Eventbox - opts[0] == True -> set fg color - opts[1] == True -> set bg color - """ - banner_eventbox = self.xml.get_widget('banner_eventbox') - self.disconnect_style_event(widget) - if opts[1]: - bg_color = widget.style.bg[gtk.STATE_SELECTED] - banner_eventbox.modify_bg(gtk.STATE_NORMAL, bg_color) - if opts[0]: - fg_color = widget.style.fg[gtk.STATE_SELECTED] - widget.modify_fg(gtk.STATE_NORMAL, fg_color) - self.connect_style_event(widget, opts[0], opts[1]) - - def _conv_textview_key_press_event(self, widget, event): - if (event.state & gtk.gdk.CONTROL_MASK and event.keyval in (gtk.keysyms.c, - gtk.keysyms.Insert)) or (event.state & gtk.gdk.SHIFT_MASK and \ - event.keyval in (gtk.keysyms.Page_Down, gtk.keysyms.Page_Up)): - return False - self.parent_win.notebook.emit('key_press_event', event) - return True - - def show_emoticons_menu(self): - if not gajim.config.get('emoticons_theme'): - return - def set_emoticons_menu_position(w, msg_tv = self.msg_textview): - window = msg_tv.get_window(gtk.TEXT_WINDOW_WIDGET) - # get the window position - origin = window.get_origin() - size = window.get_size() - buf = msg_tv.get_buffer() - # get the cursor position - cursor = msg_tv.get_iter_location(buf.get_iter_at_mark( - buf.get_insert())) - cursor = msg_tv.buffer_to_window_coords(gtk.TEXT_WINDOW_TEXT, - cursor.x, cursor.y) - x = origin[0] + cursor[0] - y = origin[1] + size[1] - menu_height = gajim.interface.emoticons_menu.size_request()[1] - #FIXME: get_line_count is not so good - #get the iter of cursor, then tv.get_line_yrange - # so we know in which y we are typing (not how many lines we have - # then go show just above the current cursor line for up - # or just below the current cursor line for down - #TEST with having 3 lines and writing in the 2nd - if y + menu_height > gtk.gdk.screen_height(): - # move menu just above cursor - y -= menu_height + (msg_tv.allocation.height / buf.get_line_count()) - #else: # move menu just below cursor - # y -= (msg_tv.allocation.height / buf.get_line_count()) - return (x, y, True) # push_in True - gajim.interface.emoticon_menuitem_clicked = self.append_emoticon - gajim.interface.emoticons_menu.popup(None, None, - set_emoticons_menu_position, 1, 0) - - def _on_message_textview_key_press_event(self, widget, event): - # Ctrl [+ Shift] + Tab are not forwarded to notebook. We handle it here - if self.widget_name == 'muc_child_vbox': - if event.keyval not in (gtk.keysyms.ISO_Left_Tab, gtk.keysyms.Tab): - self.last_key_tabs = False - if event.state & gtk.gdk.SHIFT_MASK: - # CTRL + SHIFT + TAB - if event.state & gtk.gdk.CONTROL_MASK and \ - event.keyval == gtk.keysyms.ISO_Left_Tab: - self.parent_win.move_to_next_unread_tab(False) - return True - # SHIFT + PAGE_[UP|DOWN]: send to conv_textview - elif event.keyval == gtk.keysyms.Page_Down or \ - event.keyval == gtk.keysyms.Page_Up: - self.conv_textview.tv.emit('key_press_event', event) - return True - elif event.state & gtk.gdk.CONTROL_MASK: - if event.keyval == gtk.keysyms.Tab: # CTRL + TAB - self.parent_win.move_to_next_unread_tab(True) - return True - return False - - def _on_message_textview_mykeypress_event(self, widget, event_keyval, - event_keymod): - """ - When a key is pressed: if enter is pressed without the shift key, message - (if not empty) is sent and printed in the conversation - """ - # NOTE: handles mykeypress which is custom signal connected to this - # CB in new_tab(). for this singal see message_textview.py - message_textview = widget - message_buffer = message_textview.get_buffer() - start_iter, end_iter = message_buffer.get_bounds() - message = message_buffer.get_text(start_iter, end_iter, False).decode( - 'utf-8') - xhtml = self.msg_textview.get_xhtml() - - # construct event instance from binding - event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here - event.keyval = event_keyval - event.state = event_keymod - event.time = 0 # assign current time - - if event.keyval == gtk.keysyms.Up: - if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+UP - self.sent_messages_scroll('up', widget.get_buffer()) - elif event.keyval == gtk.keysyms.Down: - if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+Down - self.sent_messages_scroll('down', widget.get_buffer()) - elif event.keyval == gtk.keysyms.Return or \ - event.keyval == gtk.keysyms.KP_Enter: # ENTER - # NOTE: SHIFT + ENTER is not needed to be emulated as it is not - # binding at all (textview's default action is newline) - - if gajim.config.get('send_on_ctrl_enter'): - # here, we emulate GTK default action on ENTER (add new line) - # normally I would add in keypress but it gets way to complex - # to get instant result on changing this advanced setting - if event.state == 0: # no ctrl, no shift just ENTER add newline - end_iter = message_buffer.get_end_iter() - message_buffer.insert_at_cursor('\n') - send_message = False - elif event.state & gtk.gdk.CONTROL_MASK: # CTRL + ENTER - send_message = True - else: # send on Enter, do newline on Ctrl Enter - if event.state & gtk.gdk.CONTROL_MASK: # Ctrl + ENTER - end_iter = message_buffer.get_end_iter() - message_buffer.insert_at_cursor('\n') - send_message = False - else: # ENTER - send_message = True - - if gajim.connections[self.account].connected < 2 and send_message: - # we are not connected - dialogs.ErrorDialog(_('A connection is not available'), - _('Your message can not be sent until you are connected.')) - send_message = False - - if send_message: - self.send_message(message, xhtml=xhtml) # send the message - else: - # Give the control itself a chance to process - self.handle_message_textview_mykey_press(widget, event_keyval, - event_keymod) - - def _on_drag_data_received(self, widget, context, x, y, selection, - target_type, timestamp): - """ - Derived types SHOULD implement this - """ - pass - - def _on_drag_leave(self, widget, context, time): - # FIXME: DND on non editable TextView, find a better way - self.drag_entered = False - self.conv_textview.tv.set_editable(False) - - def _on_drag_motion(self, widget, context, x, y, time): - # FIXME: DND on non editable TextView, find a better way - if not self.drag_entered: - # We drag new data over the TextView, make it editable to catch dnd - self.drag_entered_conv = True - self.conv_textview.tv.set_editable(True) - - def send_message(self, message, keyID='', type_='chat', chatstate=None, - msg_id=None, composing_xep=None, resource=None, xhtml=None, - callback=None, callback_args=[], process_commands=True): - """ - Send the given message to the active tab. Doesn't return None if error - """ - if not message or message == '\n': - return None - - if process_commands and self.process_as_command(message): - return - - MessageControl.send_message(self, message, keyID, type_=type_, - chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep, - resource=resource, user_nick=self.user_nick, xhtml=xhtml, - callback=callback, callback_args=callback_args) - - # Record message history - self.save_sent_message(message) - - # Be sure to send user nickname only once according to JEP-0172 - self.user_nick = None - - # Clear msg input - message_buffer = self.msg_textview.get_buffer() - message_buffer.set_text('') # clear message buffer (and tv of course) - - def save_sent_message(self, message): - # save the message, so user can scroll though the list with key up/down - size = len(self.sent_history) - # we don't want size of the buffer to grow indefinately - max_size = gajim.config.get('key_up_lines') - if size >= max_size: - for i in xrange(0, size - 1): - self.sent_history[i] = self.sent_history[i + 1] - self.sent_history[max_size - 1] = message - # self.sent_history_pos has changed if we browsed sent_history, - # reset to real value - self.sent_history_pos = max_size - else: - self.sent_history.append(message) - self.sent_history_pos = size + 1 - self.orig_msg = None - - def print_conversation_line(self, text, kind, name, tim, - other_tags_for_name=[], other_tags_for_time=[], - other_tags_for_text=[], count_as_new=True, subject=None, - old_kind=None, xhtml=None, simple=False, xep0184_id=None, - graphics=True): - """ - Print 'chat' type messages - """ - jid = self.contact.jid - full_jid = self.get_full_jid() - textview = self.conv_textview - end = False - if self.was_at_the_end or kind == 'outgoing': - end = True - textview.print_conversation_line(text, jid, kind, name, tim, - other_tags_for_name, other_tags_for_time, other_tags_for_text, - subject, old_kind, xhtml, simple=simple, graphics=graphics) - - if xep0184_id is not None: - textview.show_xep0184_warning(xep0184_id) - - if not count_as_new: - return - if kind == 'incoming': - if not self.type_id == message_control.TYPE_GC or \ - gajim.config.get('notify_on_all_muc_messages') or \ - 'marked' in other_tags_for_text: - # it's a normal message, or a muc message with want to be - # notified about if quitting just after - # other_tags_for_text == ['marked'] --> highlighted gc message - gajim.last_message_time[self.account][full_jid] = time.time() - - if kind in ('incoming', 'incoming_queue', 'error'): - gc_message = False - if self.type_id == message_control.TYPE_GC: - gc_message = True - - if ((self.parent_win and (not self.parent_win.get_active_control() or \ - self != self.parent_win.get_active_control() or \ - not self.parent_win.is_active() or not end)) or \ - (gc_message and \ - jid in gajim.interface.minimized_controls[self.account])) and \ - kind in ('incoming', 'incoming_queue', 'error'): - # we want to have save this message in events list - # other_tags_for_text == ['marked'] --> highlighted gc message - if gc_message: - if 'marked' in other_tags_for_text: - type_ = 'printed_marked_gc_msg' - else: - type_ = 'printed_gc_msg' - event = 'gc_message_received' - else: - type_ = 'printed_' + self.type_id - event = 'message_received' - show_in_roster = notify.get_show_in_roster(event, - self.account, self.contact, self.session) - show_in_systray = notify.get_show_in_systray(event, - self.account, self.contact, type_) - - event = gajim.events.create_event(type_, (self,), - show_in_roster = show_in_roster, - show_in_systray = show_in_systray) - gajim.events.add_event(self.account, full_jid, event) - # We need to redraw contact if we show in roster - if show_in_roster: - gajim.interface.roster.draw_contact(self.contact.jid, - self.account) - - if not self.parent_win: - return - - if (not self.parent_win.get_active_control() or \ - self != self.parent_win.get_active_control() or \ - not self.parent_win.is_active() or not end) and \ - kind in ('incoming', 'incoming_queue', 'error'): - self.parent_win.redraw_tab(self) - if not self.parent_win.is_active(): - self.parent_win.show_title(True, self) # Enabled Urgent hint - else: - self.parent_win.show_title(False, self) # Disabled Urgent hint - - def toggle_emoticons(self): - """ - Hide show emoticons_button and make sure emoticons_menu is always there - when needed - """ - emoticons_button = self.xml.get_widget('emoticons_button') - if gajim.config.get('emoticons_theme'): - emoticons_button.show() - emoticons_button.set_no_show_all(False) - else: - emoticons_button.hide() - emoticons_button.set_no_show_all(True) - - def append_emoticon(self, str_): - buffer_ = self.msg_textview.get_buffer() - if buffer_.get_char_count(): - buffer_.insert_at_cursor(' %s ' % str_) - else: # we are the beginning of buffer - buffer_.insert_at_cursor('%s ' % str_) - self.msg_textview.grab_focus() - - def on_emoticons_button_clicked(self, widget): - """ - Popup emoticons menu - """ - gajim.interface.emoticon_menuitem_clicked = self.append_emoticon - gajim.interface.popup_emoticons_under_button(widget, self.parent_win) - - def on_formattings_button_clicked(self, widget): - """ - Popup formattings menu - """ - menu = gtk.Menu() - - menuitems = ((_('Bold'), 'bold'), - (_('Italic'), 'italic'), - (_('Underline'), 'underline'), - (_('Strike'), 'strike')) - - active_tags = self.msg_textview.get_active_tags() - - for menuitem in menuitems: - item = gtk.CheckMenuItem(menuitem[0]) - if menuitem[1] in active_tags: - item.set_active(True) - else: - item.set_active(False) - item.connect('activate', self.msg_textview.set_tag, - menuitem[1]) - menu.append(item) - - item = gtk.SeparatorMenuItem() # separator - menu.append(item) - - item = gtk.ImageMenuItem(_('Color')) - icon = gtk.image_new_from_stock(gtk.STOCK_SELECT_COLOR, gtk.ICON_SIZE_MENU) - item.set_image(icon) - item.connect('activate', self.on_color_menuitem_activale) - menu.append(item) - - item = gtk.ImageMenuItem(_('Font')) - icon = gtk.image_new_from_stock(gtk.STOCK_SELECT_FONT, gtk.ICON_SIZE_MENU) - item.set_image(icon) - item.connect('activate', self.on_font_menuitem_activale) - menu.append(item) - - item = gtk.SeparatorMenuItem() # separator - menu.append(item) - - item = gtk.ImageMenuItem(_('Clear formating')) - icon = gtk.image_new_from_stock(gtk.STOCK_CLEAR, gtk.ICON_SIZE_MENU) - item.set_image(icon) - item.connect('activate', self.msg_textview.clear_tags) - menu.append(item) - - menu.show_all() - gtkgui_helpers.popup_emoticons_under_button(menu, widget, - self.parent_win) - - def on_color_menuitem_activale(self, widget): - color_dialog = gtk.ColorSelectionDialog('Select a color') - color_dialog.connect('response', self.msg_textview.color_set, - color_dialog.colorsel) - color_dialog.show_all() - - def on_font_menuitem_activale(self, widget): - font_dialog = gtk.FontSelectionDialog('Select a font') - font_dialog.connect('response', self.msg_textview.font_set, - font_dialog.fontsel) - font_dialog.show_all() - - - def on_actions_button_clicked(self, widget): - """ - Popup action menu - """ - menu = self.prepare_context_menu(hide_buttonbar_items=True) - menu.show_all() - gtkgui_helpers.popup_emoticons_under_button(menu, widget, - self.parent_win) - - def update_font(self): - font = pango.FontDescription(gajim.config.get('conversation_font')) - self.conv_textview.tv.modify_font(font) - self.msg_textview.modify_font(font) - - def update_tags(self): - self.conv_textview.update_tags() - - def clear(self, tv): - buffer_ = tv.get_buffer() - start, end = buffer_.get_bounds() - buffer_.delete(start, end) - - def _on_history_menuitem_activate(self, widget = None, jid = None): - """ - When history menuitem is pressed: call history window - """ - if not jid: - jid = self.contact.jid - - if 'logs' in gajim.interface.instances: - gajim.interface.instances['logs'].window.present() - gajim.interface.instances['logs'].open_history(jid, self.account) - else: - gajim.interface.instances['logs'] = \ - history_window.HistoryWindow(jid, self.account) - - def _on_send_file(self, gc_contact=None): - """ - gc_contact can be set when we are in a groupchat control - """ - def _on_ok(c): - gajim.interface.instances['file_transfers'].show_file_send_request( - self.account, c) - if self.TYPE_ID == message_control.TYPE_PM: - gc_contact = self.gc_contact - if gc_contact: - # gc or pm - gc_control = gajim.interface.msg_win_mgr.get_gc_control( - gc_contact.room_jid, self.account) - self_contact = gajim.contacts.get_gc_contact(self.account, - gc_control.room_jid, gc_control.nick) - if gc_control.is_anonymous and gc_contact.affiliation not in ['admin', - 'owner'] and self_contact.affiliation in ['admin', 'owner']: - contact = gajim.contacts.get_contact(self.account, gc_contact.jid) - if not contact or contact.sub not in ('both', 'to'): - prim_text = _('Really send file?') - sec_text = _('If you send a file to %s, he/she will know your ' - 'real Jabber ID.') % gc_contact.name - dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text, - on_response_ok = (_on_ok, gc_contact)) - dialog.popup() - return - _on_ok(gc_contact) - return - _on_ok(self.contact) - - def on_minimize_menuitem_toggled(self, widget): - """ - When a grouchat is minimized, unparent the tab, put it in roster etc - """ - old_value = False - minimized_gc = gajim.config.get_per('accounts', self.account, - 'minimized_gc').split() - if self.contact.jid in minimized_gc: - old_value = True - minimize = widget.get_active() - if minimize and not self.contact.jid in minimized_gc: - minimized_gc.append(self.contact.jid) - if not minimize and self.contact.jid in minimized_gc: - minimized_gc.remove(self.contact.jid) - if old_value != minimize: - gajim.config.set_per('accounts', self.account, 'minimized_gc', - ' '.join(minimized_gc)) - - def set_control_active(self, state): - if state: - jid = self.contact.jid - if self.was_at_the_end: - # we are at the end - type_ = ['printed_' + self.type_id] - if self.type_id == message_control.TYPE_GC: - type_ = ['printed_gc_msg', 'printed_marked_gc_msg'] - if not gajim.events.remove_events(self.account, self.get_full_jid(), - types = type_): - # There were events to remove - self.redraw_after_event_removed(jid) - - - def bring_scroll_to_end(self, textview, diff_y = 0): - """ - Scroll to the end of textview if end is not visible - """ - if self.scroll_to_end_id: - # a scroll is already planned - return - buffer_ = textview.get_buffer() - end_iter = buffer_.get_end_iter() - end_rect = textview.get_iter_location(end_iter) - visible_rect = textview.get_visible_rect() - # scroll only if expected end is not visible - if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y): - self.scroll_to_end_id = gobject.idle_add(self.scroll_to_end_iter, - textview) - - def scroll_to_end_iter(self, textview): - buffer_ = textview.get_buffer() - end_iter = buffer_.get_end_iter() - textview.scroll_to_iter(end_iter, 0, False, 1, 1) - self.scroll_to_end_id = None - return False - - def size_request(self, msg_textview , requisition): - """ - When message_textview changes its size: if the new height will enlarge - the window, enable the scrollbar automatic policy. Also enable scrollbar - automatic policy for horizontal scrollbar if message we have in - message_textview is too big - """ - if msg_textview.window is None: - return - - min_height = self.conv_scrolledwindow.get_property('height-request') - conversation_height = self.conv_textview.tv.window.get_size()[1] - message_height = msg_textview.window.get_size()[1] - message_width = msg_textview.window.get_size()[0] - # new tab is not exposed yet - if conversation_height < 2: - return - - if conversation_height < min_height: - min_height = conversation_height - - # we don't want to always resize in height the message_textview - # so we have minimum on conversation_textview's scrolled window - # but we also want to avoid window resizing so if we reach that - # minimum for conversation_textview and maximum for message_textview - # we set to automatic the scrollbar policy - diff_y = message_height - requisition.height - if diff_y != 0: - if conversation_height + diff_y < min_height: - if message_height + conversation_height - min_height > min_height: - policy = self.msg_scrolledwindow.get_property( - 'vscrollbar-policy') - # scroll only when scrollbar appear - if policy != gtk.POLICY_AUTOMATIC: - self.msg_scrolledwindow.set_property('vscrollbar-policy', - gtk.POLICY_AUTOMATIC) - self.msg_scrolledwindow.set_property('height-request', - message_height + conversation_height - min_height) - self.bring_scroll_to_end(msg_textview) - else: - self.msg_scrolledwindow.set_property('vscrollbar-policy', - gtk.POLICY_NEVER) - self.msg_scrolledwindow.set_property('height-request', -1) - self.conv_textview.bring_scroll_to_end(diff_y - 18, False) - else: - self.conv_textview.bring_scroll_to_end(diff_y - 18, self.smooth) - self.smooth = True # reinit the flag - # enable scrollbar automatic policy for horizontal scrollbar - # if message we have in message_textview is too big - if requisition.width > message_width: - self.msg_scrolledwindow.set_property('hscrollbar-policy', - gtk.POLICY_AUTOMATIC) - else: - self.msg_scrolledwindow.set_property('hscrollbar-policy', - gtk.POLICY_NEVER) - - return True - - def on_conversation_vadjustment_changed(self, adjustment): - # used to stay at the end of the textview when we shrink conversation - # textview. - if self.was_at_the_end: - self.conv_textview.bring_scroll_to_end(-18) - self.was_at_the_end = (adjustment.upper - adjustment.value - adjustment.page_size) < 18 - - def on_conversation_vadjustment_value_changed(self, adjustment): - # stop automatic scroll when we manually scroll - if not self.conv_textview.auto_scrolling: - self.conv_textview.stop_scrolling() - self.was_at_the_end = (adjustment.upper - adjustment.value - adjustment.page_size) < 18 - if self.resource: - jid = self.contact.get_full_jid() - else: - jid = self.contact.jid - types_list = [] - type_ = self.type_id - if type_ == message_control.TYPE_GC: - type_ = 'gc_msg' - types_list = ['printed_' + type_, type_, 'printed_marked_gc_msg'] - else: # Not a GC - types_list = ['printed_' + type_, type_] - - if not len(gajim.events.get_events(self.account, jid, types_list)): - return - if not self.parent_win: - return - if self.conv_textview.at_the_end() and \ - self.parent_win.get_active_control() == self and \ - self.parent_win.window.is_active(): - # we are at the end - if self.type_id == message_control.TYPE_GC: - if not gajim.events.remove_events(self.account, jid, - types=types_list): - self.redraw_after_event_removed(jid) - elif self.session and self.session.remove_events(types_list): - # There were events to remove - self.redraw_after_event_removed(jid) - - def redraw_after_event_removed(self, jid): - """ - We just removed a 'printed_*' event, redraw contact in roster or - gc_roster and titles in roster and msg_win - """ - self.parent_win.redraw_tab(self) - self.parent_win.show_title() - # TODO : get the contact and check notify.get_show_in_roster() - if self.type_id == message_control.TYPE_PM: - room_jid, nick = gajim.get_room_and_nick_from_fjid(jid) - groupchat_control = gajim.interface.msg_win_mgr.get_gc_control( - room_jid, self.account) - if room_jid in gajim.interface.minimized_controls[self.account]: - groupchat_control = \ - gajim.interface.minimized_controls[self.account][room_jid] - contact = \ - gajim.contacts.get_contact_with_highest_priority(self.account, \ - room_jid) - if contact: - gajim.interface.roster.draw_contact(room_jid, self.account) - if groupchat_control: - groupchat_control.draw_contact(nick) - if groupchat_control.parent_win: - groupchat_control.parent_win.redraw_tab(groupchat_control) - else: - gajim.interface.roster.draw_contact(jid, self.account) - gajim.interface.roster.show_title() - - def sent_messages_scroll(self, direction, conv_buf): - size = len(self.sent_history) - if self.orig_msg is None: - # user was typing something and then went into history, so save - # whatever is already typed - start_iter = conv_buf.get_start_iter() - end_iter = conv_buf.get_end_iter() - self.orig_msg = conv_buf.get_text(start_iter, end_iter, 0).decode( - 'utf-8') - if direction == 'up': - if self.sent_history_pos == 0: - return - self.sent_history_pos = self.sent_history_pos - 1 - self.smooth = False - conv_buf.set_text(self.sent_history[self.sent_history_pos]) - elif direction == 'down': - if self.sent_history_pos >= size - 1: - conv_buf.set_text(self.orig_msg) - self.orig_msg = None - self.sent_history_pos = size - return - - self.sent_history_pos = self.sent_history_pos + 1 - self.smooth = False - conv_buf.set_text(self.sent_history[self.sent_history_pos]) - - def lighten_color(self, color): - p = 0.4 - mask = 0 - color.red = int((color.red * p) + (mask * (1 - p))) - color.green = int((color.green * p) + (mask * (1 - p))) - color.blue = int((color.blue * p) + (mask * (1 - p))) - return color - - def widget_set_visible(self, widget, state): - """ - Show or hide a widget - """ - # make the last message visible, when changing to "full view" - if not state: - gobject.idle_add(self.conv_textview.scroll_to_end_iter) - - widget.set_no_show_all(state) - if state: - widget.hide() - else: - widget.show_all() - - def chat_buttons_set_visible(self, state): - """ - Toggle chat buttons - """ - MessageControl.chat_buttons_set_visible(self, state) - self.widget_set_visible(self.xml.get_widget('actions_hbox'), state) - - def got_connected(self): - self.msg_textview.set_sensitive(True) - self.msg_textview.set_editable(True) - # FIXME: Set sensitivity for toolbar - - def got_disconnected(self): - self.msg_textview.set_sensitive(False) - self.msg_textview.set_editable(False) - self.conv_textview.tv.grab_focus() - - self.no_autonegotiation = False - # FIXME: Set sensitivity for toolbar + """ + A base class containing a banner, ConversationTextview, MessageTextView + """ + + def make_href(self, match): + url_color = gajim.config.get('urlmsgcolor') + return '%s' % (match.group(), + url_color, match.group()) + + def get_font_attrs(self): + """ + Get pango font attributes for banner from theme settings + """ + theme = gajim.config.get('roster_theme') + bannerfont = gajim.config.get_per('themes', theme, 'bannerfont') + bannerfontattrs = gajim.config.get_per('themes', theme, 'bannerfontattrs') + + if bannerfont: + font = pango.FontDescription(bannerfont) + else: + font = pango.FontDescription('Normal') + if bannerfontattrs: + # B attribute is set by default + if 'B' in bannerfontattrs: + font.set_weight(pango.WEIGHT_HEAVY) + if 'I' in bannerfontattrs: + font.set_style(pango.STYLE_ITALIC) + + font_attrs = 'font_desc="%s"' % font.to_string() + + # in case there is no font specified we use x-large font size + if font.get_size() == 0: + font_attrs = '%s size="x-large"' % font_attrs + font.set_weight(pango.WEIGHT_NORMAL) + font_attrs_small = 'font_desc="%s" size="small"' % font.to_string() + return (font_attrs, font_attrs_small) + + def get_nb_unread(self): + jid = self.contact.jid + if self.resource: + jid += '/' + self.resource + type_ = self.type_id + return len(gajim.events.get_events(self.account, jid, ['printed_' + type_, + type_])) + + def draw_banner(self): + """ + Draw the fat line at the top of the window that houses the icon, jid, etc + + Derived types MAY implement this. + """ + self.draw_banner_text() + self._update_banner_state_image() + + def draw_banner_text(self): + """ + Derived types SHOULD implement this + """ + pass + + def update_ui(self): + """ + Derived types SHOULD implement this + """ + self.draw_banner() + + def repaint_themed_widgets(self): + """ + Derived types MAY implement this + """ + self._paint_banner() + self.draw_banner() + + def _update_banner_state_image(self): + """ + Derived types MAY implement this + """ + pass + + def handle_message_textview_mykey_press(self, widget, event_keyval, + event_keymod): + """ + Derives types SHOULD implement this, rather than connection to the even + itself + """ + event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) + event.keyval = event_keyval + event.state = event_keymod + event.time = 0 + + buffer = widget.get_buffer() + start, end = buffer.get_bounds() + + if event.keyval -- gtk.keysyms.Tab: + position = buffer.get_insert() + end = buffer.get_iter_at_mark(position) + + text = buffer.get_text(start, end, False) + text = text.decode('utf8') + + splitted = text.split() + + if (text.startswith(self.COMMAND_PREFIX) and not + text.startswith(self.COMMAND_PREFIX * 2) and len(splitted) == 1): + + text = splitted[0] + bare = text.lstrip(self.COMMAND_PREFIX) + + if len(text) == 1: + self.command_hits = [] + for command in self.list_commands(): + for name in command.names: + self.command_hits.append(name) + else: + if (self.last_key_tabs and self.command_hits and + self.command_hits[0].startswith(bare)): + self.command_hits.append(self.command_hits.pop(0)) + else: + self.command_hits = [] + for command in self.list_commands(): + for name in command.names: + if name.startswith(bare): + self.command_hits.append(name) + + if self.command_hits: + buffer.delete(start, end) + buffer.insert_at_cursor(self.COMMAND_PREFIX + self.command_hits[0] + ' ') + self.last_key_tabs = True + + return True + + self.last_key_tabs = False + + def status_url_clicked(self, widget, url): + helpers.launch_browser_mailer('url', url) + + def __init__(self, type_id, parent_win, widget_name, contact, acct, + resource = None): + if resource is None: + # We very likely got a contact with a random resource. + # This is bad, we need the highest for caps etc. + c = gajim.contacts.get_contact_with_highest_priority( + acct, contact.jid) + if c and not isinstance(c, GC_Contact): + contact = c + + MessageControl.__init__(self, type_id, parent_win, widget_name, + contact, acct, resource = resource) + + widget = self.xml.get_widget('history_button') + id_ = widget.connect('clicked', self._on_history_menuitem_activate) + self.handlers[id_] = widget + + # when/if we do XHTML we will put formatting buttons back + widget = self.xml.get_widget('emoticons_button') + id_ = widget.connect('clicked', self.on_emoticons_button_clicked) + self.handlers[id_] = widget + + # Create banner and connect signals + widget = self.xml.get_widget('banner_eventbox') + id_ = widget.connect('button-press-event', + self._on_banner_eventbox_button_press_event) + self.handlers[id_] = widget + + self.urlfinder = re.compile( + r"(www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'\"]+[^!,\.\s<>\)'\"\]]") + + if gajim.HAVE_PYSEXY: + import sexy + self.banner_status_label = sexy.UrlLabel() + self.banner_status_label.connect('url_activated', + self.status_url_clicked) + else: + self.banner_status_label = gtk.Label() + self.banner_status_label.set_selectable(True) + self.banner_status_label.set_alignment(0, 0.5) + self.banner_status_label.connect('populate_popup', + self.on_banner_label_populate_popup) + + banner_vbox = self.xml.get_widget('banner_vbox') + banner_vbox.pack_start(self.banner_status_label) + self.banner_status_label.show() + + # Init DND + self.TARGET_TYPE_URI_LIST = 80 + self.dnd_list = [ ( 'text/uri-list', 0, self.TARGET_TYPE_URI_LIST ), + ('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_APP, 0)] + id_ = self.widget.connect('drag_data_received', + self._on_drag_data_received) + self.handlers[id_] = self.widget + self.widget.drag_dest_set(gtk.DEST_DEFAULT_MOTION | + gtk.DEST_DEFAULT_HIGHLIGHT | + gtk.DEST_DEFAULT_DROP, + self.dnd_list, gtk.gdk.ACTION_COPY) + + # Create textviews and connect signals + self.conv_textview = ConversationTextview(self.account) + id_ = self.conv_textview.connect('quote', self.on_quote) + self.handlers[id_] = self.conv_textview.tv + id_ = self.conv_textview.tv.connect('key_press_event', + self._conv_textview_key_press_event) + self.handlers[id_] = self.conv_textview.tv + # FIXME: DND on non editable TextView, find a better way + self.drag_entered = False + id_ = self.conv_textview.tv.connect('drag_data_received', + self._on_drag_data_received) + self.handlers[id_] = self.conv_textview.tv + id_ = self.conv_textview.tv.connect('drag_motion', self._on_drag_motion) + self.handlers[id_] = self.conv_textview.tv + id_ = self.conv_textview.tv.connect('drag_leave', self._on_drag_leave) + self.handlers[id_] = self.conv_textview.tv + self.conv_textview.tv.drag_dest_set(gtk.DEST_DEFAULT_MOTION | + gtk.DEST_DEFAULT_HIGHLIGHT | + gtk.DEST_DEFAULT_DROP, + self.dnd_list, gtk.gdk.ACTION_COPY) + + self.conv_scrolledwindow = self.xml.get_widget( + 'conversation_scrolledwindow') + self.conv_scrolledwindow.add(self.conv_textview.tv) + widget = self.conv_scrolledwindow.get_vadjustment() + id_ = widget.connect('value-changed', + self.on_conversation_vadjustment_value_changed) + self.handlers[id_] = widget + id_ = widget.connect('changed', + self.on_conversation_vadjustment_changed) + self.handlers[id_] = widget + self.scroll_to_end_id = None + self.was_at_the_end = True + + # add MessageTextView to UI and connect signals + self.msg_scrolledwindow = self.xml.get_widget('message_scrolledwindow') + self.msg_textview = MessageTextView() + id_ = self.msg_textview.connect('mykeypress', + self._on_message_textview_mykeypress_event) + self.handlers[id_] = self.msg_textview + self.msg_scrolledwindow.add(self.msg_textview) + id_ = self.msg_textview.connect('key_press_event', + self._on_message_textview_key_press_event) + self.handlers[id_] = self.msg_textview + id_ = self.msg_textview.connect('size-request', self.size_request) + self.handlers[id_] = self.msg_textview + id_ = self.msg_textview.connect('populate_popup', + self.on_msg_textview_populate_popup) + self.handlers[id_] = self.msg_textview + # Setup DND + id_ = self.msg_textview.connect('drag_data_received', + self._on_drag_data_received) + self.handlers[id_] = self.msg_textview + self.msg_textview.drag_dest_set(gtk.DEST_DEFAULT_MOTION | + gtk.DEST_DEFAULT_HIGHLIGHT, + self.dnd_list, gtk.gdk.ACTION_COPY) + + self.update_font() + + # Hook up send button + widget = self.xml.get_widget('send_button') + id_ = widget.connect('clicked', self._on_send_button_clicked) + self.handlers[id_] = widget + + widget = self.xml.get_widget('formattings_button') + id_ = widget.connect('clicked', self.on_formattings_button_clicked) + self.handlers[id_] = widget + + # the following vars are used to keep history of user's messages + self.sent_history = [] + self.sent_history_pos = 0 + self.orig_msg = None + + # Emoticons menu + # set image no matter if user wants at this time emoticons or not + # (so toggle works ok) + img = self.xml.get_widget('emoticons_button_image') + img.set_from_file(os.path.join(gajim.DATA_DIR, 'emoticons', 'static', + 'smile.png')) + self.toggle_emoticons() + + # Attach speller + if gajim.config.get('use_speller') and HAS_GTK_SPELL: + self.set_speller() + self.conv_textview.tv.show() + self._paint_banner() + + # For XEP-0172 + self.user_nick = None + + self.smooth = True + self.msg_textview.grab_focus() + + self.command_hits = [] + self.last_key_tabs = False + + def set_speller(self): + # now set the one the user selected + per_type = 'contacts' + if self.type_id == message_control.TYPE_GC: + per_type = 'rooms' + lang = gajim.config.get_per(per_type, self.contact.jid, + 'speller_language') + if not lang: + # use the default one + lang = gajim.config.get('speller_language') + if not lang: + lang = gajim.LANG + if lang: + try: + gtkspell.Spell(self.msg_textview, lang) + self.msg_textview.lang = lang + except (gobject.GError, RuntimeError, TypeError, OSError): + dialogs.AspellDictError(lang) + + def on_banner_label_populate_popup(self, label, menu): + """ + Override the default context menu and add our own menutiems + """ + item = gtk.SeparatorMenuItem() + menu.prepend(item) + + menu2 = self.prepare_context_menu() + i = 0 + for item in menu2: + menu2.remove(item) + menu.prepend(item) + menu.reorder_child(item, i) + i += 1 + menu.show_all() + + def on_msg_textview_populate_popup(self, textview, menu): + """ + Override the default context menu and we prepend an option to switch + languages + """ + def _on_select_dictionary(widget, lang): + per_type = 'contacts' + if self.type_id == message_control.TYPE_GC: + per_type = 'rooms' + if not gajim.config.get_per(per_type, self.contact.jid): + gajim.config.add_per(per_type, self.contact.jid) + gajim.config.set_per(per_type, self.contact.jid, 'speller_language', + lang) + spell = gtkspell.get_from_text_view(self.msg_textview) + self.msg_textview.lang = lang + spell.set_language(lang) + widget.set_active(True) + + item = gtk.SeparatorMenuItem() + menu.prepend(item) + + item = gtk.ImageMenuItem(gtk.STOCK_CLEAR) + menu.prepend(item) + id_ = item.connect('activate', self.msg_textview.clear) + self.handlers[id_] = item + + if gajim.config.get('use_speller') and HAS_GTK_SPELL: + item = gtk.MenuItem(_('Spelling language')) + menu.prepend(item) + submenu = gtk.Menu() + item.set_submenu(submenu) + for lang in sorted(langs): + item = gtk.CheckMenuItem(lang) + if langs[lang] == self.msg_textview.lang: + item.set_active(True) + submenu.append(item) + id_ = item.connect('activate', _on_select_dictionary, langs[lang]) + self.handlers[id_] = item + + menu.show_all() + + def on_quote(self, widget, text): + text = '>' + text.replace('\n', '\n>') + '\n' + message_buffer = self.msg_textview.get_buffer() + message_buffer.insert_at_cursor(text) + + # moved from ChatControl + def _on_banner_eventbox_button_press_event(self, widget, event): + """ + If right-clicked, show popup + """ + if event.button == 3: # right click + self.parent_win.popup_menu(event) + + def _on_send_button_clicked(self, widget): + """ + When send button is pressed: send the current message + """ + if gajim.connections[self.account].connected < 2: # we are not connected + dialogs.ErrorDialog(_('A connection is not available'), + _('Your message can not be sent until you are connected.')) + return + message_buffer = self.msg_textview.get_buffer() + start_iter = message_buffer.get_start_iter() + end_iter = message_buffer.get_end_iter() + message = message_buffer.get_text(start_iter, end_iter, 0).decode('utf-8') + xhtml = self.msg_textview.get_xhtml() + + # send the message + self.send_message(message, xhtml=xhtml) + + def _paint_banner(self): + """ + Repaint banner with theme color + """ + theme = gajim.config.get('roster_theme') + bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor') + textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor') + # the backgrounds are colored by using an eventbox by + # setting the bg color of the eventbox and the fg of the name_label + banner_eventbox = self.xml.get_widget('banner_eventbox') + banner_name_label = self.xml.get_widget('banner_name_label') + self.disconnect_style_event(banner_name_label) + self.disconnect_style_event(self.banner_status_label) + if bgcolor: + banner_eventbox.modify_bg(gtk.STATE_NORMAL, + gtk.gdk.color_parse(bgcolor)) + default_bg = False + else: + default_bg = True + if textcolor: + banner_name_label.modify_fg(gtk.STATE_NORMAL, + gtk.gdk.color_parse(textcolor)) + self.banner_status_label.modify_fg(gtk.STATE_NORMAL, + gtk.gdk.color_parse(textcolor)) + default_fg = False + else: + default_fg = True + if default_bg or default_fg: + self._on_style_set_event(banner_name_label, None, default_fg, + default_bg) + if self.banner_status_label.flags() & gtk.REALIZED: + # Widget is realized + self._on_style_set_event(self.banner_status_label, None, default_fg, + default_bg) + + def disconnect_style_event(self, widget): + # Try to find the event_id + for id_ in self.handlers.keys(): + if self.handlers[id_] == widget: + widget.disconnect(id_) + del self.handlers[id_] + break + + def connect_style_event(self, widget, set_fg = False, set_bg = False): + self.disconnect_style_event(widget) + id_ = widget.connect('style-set', self._on_style_set_event, set_fg, + set_bg) + self.handlers[id_] = widget + + def _on_style_set_event(self, widget, style, *opts): + """ + Set style of widget from style class *.Frame.Eventbox + opts[0] == True -> set fg color + opts[1] == True -> set bg color + """ + banner_eventbox = self.xml.get_widget('banner_eventbox') + self.disconnect_style_event(widget) + if opts[1]: + bg_color = widget.style.bg[gtk.STATE_SELECTED] + banner_eventbox.modify_bg(gtk.STATE_NORMAL, bg_color) + if opts[0]: + fg_color = widget.style.fg[gtk.STATE_SELECTED] + widget.modify_fg(gtk.STATE_NORMAL, fg_color) + self.connect_style_event(widget, opts[0], opts[1]) + + def _conv_textview_key_press_event(self, widget, event): + if (event.state & gtk.gdk.CONTROL_MASK and event.keyval in (gtk.keysyms.c, + gtk.keysyms.Insert)) or (event.state & gtk.gdk.SHIFT_MASK and \ + event.keyval in (gtk.keysyms.Page_Down, gtk.keysyms.Page_Up)): + return False + self.parent_win.notebook.emit('key_press_event', event) + return True + + def show_emoticons_menu(self): + if not gajim.config.get('emoticons_theme'): + return + def set_emoticons_menu_position(w, msg_tv = self.msg_textview): + window = msg_tv.get_window(gtk.TEXT_WINDOW_WIDGET) + # get the window position + origin = window.get_origin() + size = window.get_size() + buf = msg_tv.get_buffer() + # get the cursor position + cursor = msg_tv.get_iter_location(buf.get_iter_at_mark( + buf.get_insert())) + cursor = msg_tv.buffer_to_window_coords(gtk.TEXT_WINDOW_TEXT, + cursor.x, cursor.y) + x = origin[0] + cursor[0] + y = origin[1] + size[1] + menu_height = gajim.interface.emoticons_menu.size_request()[1] + #FIXME: get_line_count is not so good + #get the iter of cursor, then tv.get_line_yrange + # so we know in which y we are typing (not how many lines we have + # then go show just above the current cursor line for up + # or just below the current cursor line for down + #TEST with having 3 lines and writing in the 2nd + if y + menu_height > gtk.gdk.screen_height(): + # move menu just above cursor + y -= menu_height + (msg_tv.allocation.height / buf.get_line_count()) + #else: # move menu just below cursor + # y -= (msg_tv.allocation.height / buf.get_line_count()) + return (x, y, True) # push_in True + gajim.interface.emoticon_menuitem_clicked = self.append_emoticon + gajim.interface.emoticons_menu.popup(None, None, + set_emoticons_menu_position, 1, 0) + + def _on_message_textview_key_press_event(self, widget, event): + # Ctrl [+ Shift] + Tab are not forwarded to notebook. We handle it here + if self.widget_name == 'muc_child_vbox': + if event.keyval not in (gtk.keysyms.ISO_Left_Tab, gtk.keysyms.Tab): + self.last_key_tabs = False + if event.state & gtk.gdk.SHIFT_MASK: + # CTRL + SHIFT + TAB + if event.state & gtk.gdk.CONTROL_MASK and \ + event.keyval == gtk.keysyms.ISO_Left_Tab: + self.parent_win.move_to_next_unread_tab(False) + return True + # SHIFT + PAGE_[UP|DOWN]: send to conv_textview + elif event.keyval == gtk.keysyms.Page_Down or \ + event.keyval == gtk.keysyms.Page_Up: + self.conv_textview.tv.emit('key_press_event', event) + return True + elif event.state & gtk.gdk.CONTROL_MASK: + if event.keyval == gtk.keysyms.Tab: # CTRL + TAB + self.parent_win.move_to_next_unread_tab(True) + return True + return False + + def _on_message_textview_mykeypress_event(self, widget, event_keyval, + event_keymod): + """ + When a key is pressed: if enter is pressed without the shift key, message + (if not empty) is sent and printed in the conversation + """ + # NOTE: handles mykeypress which is custom signal connected to this + # CB in new_tab(). for this singal see message_textview.py + message_textview = widget + message_buffer = message_textview.get_buffer() + start_iter, end_iter = message_buffer.get_bounds() + message = message_buffer.get_text(start_iter, end_iter, False).decode( + 'utf-8') + xhtml = self.msg_textview.get_xhtml() + + # construct event instance from binding + event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here + event.keyval = event_keyval + event.state = event_keymod + event.time = 0 # assign current time + + if event.keyval == gtk.keysyms.Up: + if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+UP + self.sent_messages_scroll('up', widget.get_buffer()) + elif event.keyval == gtk.keysyms.Down: + if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+Down + self.sent_messages_scroll('down', widget.get_buffer()) + elif event.keyval == gtk.keysyms.Return or \ + event.keyval == gtk.keysyms.KP_Enter: # ENTER + # NOTE: SHIFT + ENTER is not needed to be emulated as it is not + # binding at all (textview's default action is newline) + + if gajim.config.get('send_on_ctrl_enter'): + # here, we emulate GTK default action on ENTER (add new line) + # normally I would add in keypress but it gets way to complex + # to get instant result on changing this advanced setting + if event.state == 0: # no ctrl, no shift just ENTER add newline + end_iter = message_buffer.get_end_iter() + message_buffer.insert_at_cursor('\n') + send_message = False + elif event.state & gtk.gdk.CONTROL_MASK: # CTRL + ENTER + send_message = True + else: # send on Enter, do newline on Ctrl Enter + if event.state & gtk.gdk.CONTROL_MASK: # Ctrl + ENTER + end_iter = message_buffer.get_end_iter() + message_buffer.insert_at_cursor('\n') + send_message = False + else: # ENTER + send_message = True + + if gajim.connections[self.account].connected < 2 and send_message: + # we are not connected + dialogs.ErrorDialog(_('A connection is not available'), + _('Your message can not be sent until you are connected.')) + send_message = False + + if send_message: + self.send_message(message, xhtml=xhtml) # send the message + else: + # Give the control itself a chance to process + self.handle_message_textview_mykey_press(widget, event_keyval, + event_keymod) + + def _on_drag_data_received(self, widget, context, x, y, selection, + target_type, timestamp): + """ + Derived types SHOULD implement this + """ + pass + + def _on_drag_leave(self, widget, context, time): + # FIXME: DND on non editable TextView, find a better way + self.drag_entered = False + self.conv_textview.tv.set_editable(False) + + def _on_drag_motion(self, widget, context, x, y, time): + # FIXME: DND on non editable TextView, find a better way + if not self.drag_entered: + # We drag new data over the TextView, make it editable to catch dnd + self.drag_entered_conv = True + self.conv_textview.tv.set_editable(True) + + def send_message(self, message, keyID='', type_='chat', chatstate=None, + msg_id=None, composing_xep=None, resource=None, xhtml=None, + callback=None, callback_args=[], process_commands=True): + """ + Send the given message to the active tab. Doesn't return None if error + """ + if not message or message == '\n': + return None + + if process_commands and self.process_as_command(message): + return + + MessageControl.send_message(self, message, keyID, type_=type_, + chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep, + resource=resource, user_nick=self.user_nick, xhtml=xhtml, + callback=callback, callback_args=callback_args) + + # Record message history + self.save_sent_message(message) + + # Be sure to send user nickname only once according to JEP-0172 + self.user_nick = None + + # Clear msg input + message_buffer = self.msg_textview.get_buffer() + message_buffer.set_text('') # clear message buffer (and tv of course) + + def save_sent_message(self, message): + # save the message, so user can scroll though the list with key up/down + size = len(self.sent_history) + # we don't want size of the buffer to grow indefinately + max_size = gajim.config.get('key_up_lines') + if size >= max_size: + for i in xrange(0, size - 1): + self.sent_history[i] = self.sent_history[i + 1] + self.sent_history[max_size - 1] = message + # self.sent_history_pos has changed if we browsed sent_history, + # reset to real value + self.sent_history_pos = max_size + else: + self.sent_history.append(message) + self.sent_history_pos = size + 1 + self.orig_msg = None + + def print_conversation_line(self, text, kind, name, tim, + other_tags_for_name=[], other_tags_for_time=[], + other_tags_for_text=[], count_as_new=True, subject=None, + old_kind=None, xhtml=None, simple=False, xep0184_id=None, + graphics=True): + """ + Print 'chat' type messages + """ + jid = self.contact.jid + full_jid = self.get_full_jid() + textview = self.conv_textview + end = False + if self.was_at_the_end or kind == 'outgoing': + end = True + textview.print_conversation_line(text, jid, kind, name, tim, + other_tags_for_name, other_tags_for_time, other_tags_for_text, + subject, old_kind, xhtml, simple=simple, graphics=graphics) + + if xep0184_id is not None: + textview.show_xep0184_warning(xep0184_id) + + if not count_as_new: + return + if kind == 'incoming': + if not self.type_id == message_control.TYPE_GC or \ + gajim.config.get('notify_on_all_muc_messages') or \ + 'marked' in other_tags_for_text: + # it's a normal message, or a muc message with want to be + # notified about if quitting just after + # other_tags_for_text == ['marked'] --> highlighted gc message + gajim.last_message_time[self.account][full_jid] = time.time() + + if kind in ('incoming', 'incoming_queue', 'error'): + gc_message = False + if self.type_id == message_control.TYPE_GC: + gc_message = True + + if ((self.parent_win and (not self.parent_win.get_active_control() or \ + self != self.parent_win.get_active_control() or \ + not self.parent_win.is_active() or not end)) or \ + (gc_message and \ + jid in gajim.interface.minimized_controls[self.account])) and \ + kind in ('incoming', 'incoming_queue', 'error'): + # we want to have save this message in events list + # other_tags_for_text == ['marked'] --> highlighted gc message + if gc_message: + if 'marked' in other_tags_for_text: + type_ = 'printed_marked_gc_msg' + else: + type_ = 'printed_gc_msg' + event = 'gc_message_received' + else: + type_ = 'printed_' + self.type_id + event = 'message_received' + show_in_roster = notify.get_show_in_roster(event, + self.account, self.contact, self.session) + show_in_systray = notify.get_show_in_systray(event, + self.account, self.contact, type_) + + event = gajim.events.create_event(type_, (self,), + show_in_roster = show_in_roster, + show_in_systray = show_in_systray) + gajim.events.add_event(self.account, full_jid, event) + # We need to redraw contact if we show in roster + if show_in_roster: + gajim.interface.roster.draw_contact(self.contact.jid, + self.account) + + if not self.parent_win: + return + + if (not self.parent_win.get_active_control() or \ + self != self.parent_win.get_active_control() or \ + not self.parent_win.is_active() or not end) and \ + kind in ('incoming', 'incoming_queue', 'error'): + self.parent_win.redraw_tab(self) + if not self.parent_win.is_active(): + self.parent_win.show_title(True, self) # Enabled Urgent hint + else: + self.parent_win.show_title(False, self) # Disabled Urgent hint + + def toggle_emoticons(self): + """ + Hide show emoticons_button and make sure emoticons_menu is always there + when needed + """ + emoticons_button = self.xml.get_widget('emoticons_button') + if gajim.config.get('emoticons_theme'): + emoticons_button.show() + emoticons_button.set_no_show_all(False) + else: + emoticons_button.hide() + emoticons_button.set_no_show_all(True) + + def append_emoticon(self, str_): + buffer_ = self.msg_textview.get_buffer() + if buffer_.get_char_count(): + buffer_.insert_at_cursor(' %s ' % str_) + else: # we are the beginning of buffer + buffer_.insert_at_cursor('%s ' % str_) + self.msg_textview.grab_focus() + + def on_emoticons_button_clicked(self, widget): + """ + Popup emoticons menu + """ + gajim.interface.emoticon_menuitem_clicked = self.append_emoticon + gajim.interface.popup_emoticons_under_button(widget, self.parent_win) + + def on_formattings_button_clicked(self, widget): + """ + Popup formattings menu + """ + menu = gtk.Menu() + + menuitems = ((_('Bold'), 'bold'), + (_('Italic'), 'italic'), + (_('Underline'), 'underline'), + (_('Strike'), 'strike')) + + active_tags = self.msg_textview.get_active_tags() + + for menuitem in menuitems: + item = gtk.CheckMenuItem(menuitem[0]) + if menuitem[1] in active_tags: + item.set_active(True) + else: + item.set_active(False) + item.connect('activate', self.msg_textview.set_tag, + menuitem[1]) + menu.append(item) + + item = gtk.SeparatorMenuItem() # separator + menu.append(item) + + item = gtk.ImageMenuItem(_('Color')) + icon = gtk.image_new_from_stock(gtk.STOCK_SELECT_COLOR, gtk.ICON_SIZE_MENU) + item.set_image(icon) + item.connect('activate', self.on_color_menuitem_activale) + menu.append(item) + + item = gtk.ImageMenuItem(_('Font')) + icon = gtk.image_new_from_stock(gtk.STOCK_SELECT_FONT, gtk.ICON_SIZE_MENU) + item.set_image(icon) + item.connect('activate', self.on_font_menuitem_activale) + menu.append(item) + + item = gtk.SeparatorMenuItem() # separator + menu.append(item) + + item = gtk.ImageMenuItem(_('Clear formating')) + icon = gtk.image_new_from_stock(gtk.STOCK_CLEAR, gtk.ICON_SIZE_MENU) + item.set_image(icon) + item.connect('activate', self.msg_textview.clear_tags) + menu.append(item) + + menu.show_all() + gtkgui_helpers.popup_emoticons_under_button(menu, widget, + self.parent_win) + + def on_color_menuitem_activale(self, widget): + color_dialog = gtk.ColorSelectionDialog('Select a color') + color_dialog.connect('response', self.msg_textview.color_set, + color_dialog.colorsel) + color_dialog.show_all() + + def on_font_menuitem_activale(self, widget): + font_dialog = gtk.FontSelectionDialog('Select a font') + font_dialog.connect('response', self.msg_textview.font_set, + font_dialog.fontsel) + font_dialog.show_all() + + + def on_actions_button_clicked(self, widget): + """ + Popup action menu + """ + menu = self.prepare_context_menu(hide_buttonbar_items=True) + menu.show_all() + gtkgui_helpers.popup_emoticons_under_button(menu, widget, + self.parent_win) + + def update_font(self): + font = pango.FontDescription(gajim.config.get('conversation_font')) + self.conv_textview.tv.modify_font(font) + self.msg_textview.modify_font(font) + + def update_tags(self): + self.conv_textview.update_tags() + + def clear(self, tv): + buffer_ = tv.get_buffer() + start, end = buffer_.get_bounds() + buffer_.delete(start, end) + + def _on_history_menuitem_activate(self, widget = None, jid = None): + """ + When history menuitem is pressed: call history window + """ + if not jid: + jid = self.contact.jid + + if 'logs' in gajim.interface.instances: + gajim.interface.instances['logs'].window.present() + gajim.interface.instances['logs'].open_history(jid, self.account) + else: + gajim.interface.instances['logs'] = \ + history_window.HistoryWindow(jid, self.account) + + def _on_send_file(self, gc_contact=None): + """ + gc_contact can be set when we are in a groupchat control + """ + def _on_ok(c): + gajim.interface.instances['file_transfers'].show_file_send_request( + self.account, c) + if self.TYPE_ID == message_control.TYPE_PM: + gc_contact = self.gc_contact + if gc_contact: + # gc or pm + gc_control = gajim.interface.msg_win_mgr.get_gc_control( + gc_contact.room_jid, self.account) + self_contact = gajim.contacts.get_gc_contact(self.account, + gc_control.room_jid, gc_control.nick) + if gc_control.is_anonymous and gc_contact.affiliation not in ['admin', + 'owner'] and self_contact.affiliation in ['admin', 'owner']: + contact = gajim.contacts.get_contact(self.account, gc_contact.jid) + if not contact or contact.sub not in ('both', 'to'): + prim_text = _('Really send file?') + sec_text = _('If you send a file to %s, he/she will know your ' + 'real Jabber ID.') % gc_contact.name + dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text, + on_response_ok = (_on_ok, gc_contact)) + dialog.popup() + return + _on_ok(gc_contact) + return + _on_ok(self.contact) + + def on_minimize_menuitem_toggled(self, widget): + """ + When a grouchat is minimized, unparent the tab, put it in roster etc + """ + old_value = False + minimized_gc = gajim.config.get_per('accounts', self.account, + 'minimized_gc').split() + if self.contact.jid in minimized_gc: + old_value = True + minimize = widget.get_active() + if minimize and not self.contact.jid in minimized_gc: + minimized_gc.append(self.contact.jid) + if not minimize and self.contact.jid in minimized_gc: + minimized_gc.remove(self.contact.jid) + if old_value != minimize: + gajim.config.set_per('accounts', self.account, 'minimized_gc', + ' '.join(minimized_gc)) + + def set_control_active(self, state): + if state: + jid = self.contact.jid + if self.was_at_the_end: + # we are at the end + type_ = ['printed_' + self.type_id] + if self.type_id == message_control.TYPE_GC: + type_ = ['printed_gc_msg', 'printed_marked_gc_msg'] + if not gajim.events.remove_events(self.account, self.get_full_jid(), + types = type_): + # There were events to remove + self.redraw_after_event_removed(jid) + + + def bring_scroll_to_end(self, textview, diff_y = 0): + """ + Scroll to the end of textview if end is not visible + """ + if self.scroll_to_end_id: + # a scroll is already planned + return + buffer_ = textview.get_buffer() + end_iter = buffer_.get_end_iter() + end_rect = textview.get_iter_location(end_iter) + visible_rect = textview.get_visible_rect() + # scroll only if expected end is not visible + if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y): + self.scroll_to_end_id = gobject.idle_add(self.scroll_to_end_iter, + textview) + + def scroll_to_end_iter(self, textview): + buffer_ = textview.get_buffer() + end_iter = buffer_.get_end_iter() + textview.scroll_to_iter(end_iter, 0, False, 1, 1) + self.scroll_to_end_id = None + return False + + def size_request(self, msg_textview, requisition): + """ + When message_textview changes its size: if the new height will enlarge + the window, enable the scrollbar automatic policy. Also enable scrollbar + automatic policy for horizontal scrollbar if message we have in + message_textview is too big + """ + if msg_textview.window is None: + return + + min_height = self.conv_scrolledwindow.get_property('height-request') + conversation_height = self.conv_textview.tv.window.get_size()[1] + message_height = msg_textview.window.get_size()[1] + message_width = msg_textview.window.get_size()[0] + # new tab is not exposed yet + if conversation_height < 2: + return + + if conversation_height < min_height: + min_height = conversation_height + + # we don't want to always resize in height the message_textview + # so we have minimum on conversation_textview's scrolled window + # but we also want to avoid window resizing so if we reach that + # minimum for conversation_textview and maximum for message_textview + # we set to automatic the scrollbar policy + diff_y = message_height - requisition.height + if diff_y != 0: + if conversation_height + diff_y < min_height: + if message_height + conversation_height - min_height > min_height: + policy = self.msg_scrolledwindow.get_property( + 'vscrollbar-policy') + # scroll only when scrollbar appear + if policy != gtk.POLICY_AUTOMATIC: + self.msg_scrolledwindow.set_property('vscrollbar-policy', + gtk.POLICY_AUTOMATIC) + self.msg_scrolledwindow.set_property('height-request', + message_height + conversation_height - min_height) + self.bring_scroll_to_end(msg_textview) + else: + self.msg_scrolledwindow.set_property('vscrollbar-policy', + gtk.POLICY_NEVER) + self.msg_scrolledwindow.set_property('height-request', -1) + self.conv_textview.bring_scroll_to_end(diff_y - 18, False) + else: + self.conv_textview.bring_scroll_to_end(diff_y - 18, self.smooth) + self.smooth = True # reinit the flag + # enable scrollbar automatic policy for horizontal scrollbar + # if message we have in message_textview is too big + if requisition.width > message_width: + self.msg_scrolledwindow.set_property('hscrollbar-policy', + gtk.POLICY_AUTOMATIC) + else: + self.msg_scrolledwindow.set_property('hscrollbar-policy', + gtk.POLICY_NEVER) + + return True + + def on_conversation_vadjustment_changed(self, adjustment): + # used to stay at the end of the textview when we shrink conversation + # textview. + if self.was_at_the_end: + self.conv_textview.bring_scroll_to_end(-18) + self.was_at_the_end = (adjustment.upper - adjustment.value - adjustment.page_size) < 18 + + def on_conversation_vadjustment_value_changed(self, adjustment): + # stop automatic scroll when we manually scroll + if not self.conv_textview.auto_scrolling: + self.conv_textview.stop_scrolling() + self.was_at_the_end = (adjustment.upper - adjustment.value - adjustment.page_size) < 18 + if self.resource: + jid = self.contact.get_full_jid() + else: + jid = self.contact.jid + types_list = [] + type_ = self.type_id + if type_ == message_control.TYPE_GC: + type_ = 'gc_msg' + types_list = ['printed_' + type_, type_, 'printed_marked_gc_msg'] + else: # Not a GC + types_list = ['printed_' + type_, type_] + + if not len(gajim.events.get_events(self.account, jid, types_list)): + return + if not self.parent_win: + return + if self.conv_textview.at_the_end() and \ + self.parent_win.get_active_control() == self and \ + self.parent_win.window.is_active(): + # we are at the end + if self.type_id == message_control.TYPE_GC: + if not gajim.events.remove_events(self.account, jid, + types=types_list): + self.redraw_after_event_removed(jid) + elif self.session and self.session.remove_events(types_list): + # There were events to remove + self.redraw_after_event_removed(jid) + + def redraw_after_event_removed(self, jid): + """ + We just removed a 'printed_*' event, redraw contact in roster or + gc_roster and titles in roster and msg_win + """ + self.parent_win.redraw_tab(self) + self.parent_win.show_title() + # TODO : get the contact and check notify.get_show_in_roster() + if self.type_id == message_control.TYPE_PM: + room_jid, nick = gajim.get_room_and_nick_from_fjid(jid) + groupchat_control = gajim.interface.msg_win_mgr.get_gc_control( + room_jid, self.account) + if room_jid in gajim.interface.minimized_controls[self.account]: + groupchat_control = \ + gajim.interface.minimized_controls[self.account][room_jid] + contact = \ + gajim.contacts.get_contact_with_highest_priority(self.account, \ + room_jid) + if contact: + gajim.interface.roster.draw_contact(room_jid, self.account) + if groupchat_control: + groupchat_control.draw_contact(nick) + if groupchat_control.parent_win: + groupchat_control.parent_win.redraw_tab(groupchat_control) + else: + gajim.interface.roster.draw_contact(jid, self.account) + gajim.interface.roster.show_title() + + def sent_messages_scroll(self, direction, conv_buf): + size = len(self.sent_history) + if self.orig_msg is None: + # user was typing something and then went into history, so save + # whatever is already typed + start_iter = conv_buf.get_start_iter() + end_iter = conv_buf.get_end_iter() + self.orig_msg = conv_buf.get_text(start_iter, end_iter, 0).decode( + 'utf-8') + if direction == 'up': + if self.sent_history_pos == 0: + return + self.sent_history_pos = self.sent_history_pos - 1 + self.smooth = False + conv_buf.set_text(self.sent_history[self.sent_history_pos]) + elif direction == 'down': + if self.sent_history_pos >= size - 1: + conv_buf.set_text(self.orig_msg) + self.orig_msg = None + self.sent_history_pos = size + return + + self.sent_history_pos = self.sent_history_pos + 1 + self.smooth = False + conv_buf.set_text(self.sent_history[self.sent_history_pos]) + + def lighten_color(self, color): + p = 0.4 + mask = 0 + color.red = int((color.red * p) + (mask * (1 - p))) + color.green = int((color.green * p) + (mask * (1 - p))) + color.blue = int((color.blue * p) + (mask * (1 - p))) + return color + + def widget_set_visible(self, widget, state): + """ + Show or hide a widget + """ + # make the last message visible, when changing to "full view" + if not state: + gobject.idle_add(self.conv_textview.scroll_to_end_iter) + + widget.set_no_show_all(state) + if state: + widget.hide() + else: + widget.show_all() + + def chat_buttons_set_visible(self, state): + """ + Toggle chat buttons + """ + MessageControl.chat_buttons_set_visible(self, state) + self.widget_set_visible(self.xml.get_widget('actions_hbox'), state) + + def got_connected(self): + self.msg_textview.set_sensitive(True) + self.msg_textview.set_editable(True) + # FIXME: Set sensitivity for toolbar + + def got_disconnected(self): + self.msg_textview.set_sensitive(False) + self.msg_textview.set_editable(False) + self.conv_textview.tv.grab_focus() + + self.no_autonegotiation = False + # FIXME: Set sensitivity for toolbar ################################################################################ class ChatControl(ChatControlBase): - """ - A control for standard 1-1 chat - """ - ( - JINGLE_STATE_NOT_AVAILABLE, - JINGLE_STATE_AVAILABLE, - JINGLE_STATE_CONNECTING, - JINGLE_STATE_CONNECTION_RECEIVED, - JINGLE_STATE_CONNECTED, - JINGLE_STATE_ERROR - ) = range(6) - - TYPE_ID = message_control.TYPE_CHAT - old_msg_kind = None # last kind of the printed message - - # Set a command host to bound to. Every command given through a chat will be - # processed with this command host. - COMMAND_HOST = ChatCommands - - def __init__(self, parent_win, contact, acct, session, resource = None): - ChatControlBase.__init__(self, self.TYPE_ID, parent_win, - 'chat_child_vbox', contact, acct, resource) - - self.gpg_is_active = False - # for muc use: - # widget = self.xml.get_widget('muc_window_actions_button') - self.actions_button = self.xml.get_widget('message_window_actions_button') - id_ = self.actions_button.connect('clicked', - self.on_actions_button_clicked) - self.handlers[id_] = self.actions_button - - self._formattings_button = self.xml.get_widget('formattings_button') - - self._add_to_roster_button = self.xml.get_widget( - 'add_to_roster_button') - id_ = self._add_to_roster_button.connect('clicked', - self._on_add_to_roster_menuitem_activate) - self.handlers[id_] = self._add_to_roster_button - - self._audio_button = self.xml.get_widget('audio_togglebutton') - id_ = self._audio_button.connect('toggled', self.on_audio_button_toggled) - self.handlers[id_] = self._audio_button - # add a special img - path_to_img = os.path.join(gajim.DATA_DIR, 'pixmaps', - 'mic_inactive.png') - img = gtk.Image() - img.set_from_file(path_to_img) - self._audio_button.set_image(img) - - self._video_button = self.xml.get_widget('video_togglebutton') - id_ = self._video_button.connect('toggled', self.on_video_button_toggled) - self.handlers[id_] = self._video_button - # add a special img - path_to_img = os.path.join(gajim.DATA_DIR, 'pixmaps', - 'cam_inactive.png') - img = gtk.Image() - img.set_from_file(path_to_img) - self._video_button.set_image(img) - - self._send_file_button = self.xml.get_widget('send_file_button') - # add a special img for send file button - path_to_upload_img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'upload.png') - img = gtk.Image() - img.set_from_file(path_to_upload_img) - self._send_file_button.set_image(img) - id_ = self._send_file_button.connect('clicked', - self._on_send_file_menuitem_activate) - self.handlers[id_] = self._send_file_button - - self._convert_to_gc_button = self.xml.get_widget( - 'convert_to_gc_button') - id_ = self._convert_to_gc_button.connect('clicked', - self._on_convert_to_gc_menuitem_activate) - self.handlers[id_] = self._convert_to_gc_button - - contact_information_button = self.xml.get_widget( - 'contact_information_button') - id_ = contact_information_button.connect('clicked', - self._on_contact_information_menuitem_activate) - self.handlers[id_] = contact_information_button - - compact_view = gajim.config.get('compact_view') - self.chat_buttons_set_visible(compact_view) - self.widget_set_visible(self.xml.get_widget('banner_eventbox'), - gajim.config.get('hide_chat_banner')) - - self.authentication_button = self.xml.get_widget( - 'authentication_button') - id_ = self.authentication_button.connect('clicked', - self._on_authentication_button_clicked) - self.handlers[id_] = self.authentication_button - - # Add lock image to show chat encryption - self.lock_image = self.xml.get_widget('lock_image') - - # Convert to GC icon - img = self.xml.get_widget('convert_to_gc_button_image') - img.set_from_pixbuf(gtkgui_helpers.load_icon( - 'muc_active').get_pixbuf()) - - self._audio_banner_image = self.xml.get_widget('audio_banner_image') - self._video_banner_image = self.xml.get_widget('video_banner_image') - self.audio_sid = None - self.audio_state = self.JINGLE_STATE_NOT_AVAILABLE - self.video_sid = None - self.video_state = self.JINGLE_STATE_NOT_AVAILABLE - - self.update_toolbar() - - self._pep_images = {} - self._pep_images['mood'] = self.xml.get_widget('mood_image') - self._pep_images['activity'] = self.xml.get_widget('activity_image') - self._pep_images['tune'] = self.xml.get_widget('tune_image') - self.update_all_pep_types() - - # keep timeout id and window obj for possible big avatar - # it is on enter-notify and leave-notify so no need to be - # per jid - self.show_bigger_avatar_timeout_id = None - self.bigger_avatar_window = None - self.show_avatar(self.contact.resource) - - # chatstate timers and state - self.reset_kbd_mouse_timeout_vars() - self._schedule_activity_timers() - - # Hook up signals - id_ = self.parent_win.window.connect('motion-notify-event', - self._on_window_motion_notify) - self.handlers[id_] = self.parent_win.window - message_tv_buffer = self.msg_textview.get_buffer() - id_ = message_tv_buffer.connect('changed', - self._on_message_tv_buffer_changed) - self.handlers[id_] = message_tv_buffer - - widget = self.xml.get_widget('avatar_eventbox') - widget.set_property('height-request', gajim.config.get( - 'chat_avatar_height')) - id_ = widget.connect('enter-notify-event', - self.on_avatar_eventbox_enter_notify_event) - self.handlers[id_] = widget - - id_ = widget.connect('leave-notify-event', - self.on_avatar_eventbox_leave_notify_event) - self.handlers[id_] = widget - - id_ = widget.connect('button-press-event', - self.on_avatar_eventbox_button_press_event) - self.handlers[id_] = widget - - if not session: - # Don't use previous session if we want to a specific resource - # and it's not the same - if not resource: - resource = contact.resource - session = gajim.connections[self.account].find_controlless_session( - self.contact.jid, resource) - - if session: - session.control = self - self.session = session - - if session.enable_encryption: - self.print_session_details() - - # Enable encryption if needed - self.no_autonegotiation = False - e2e_is_active = self.session and self.session.enable_encryption - gpg_pref = gajim.config.get_per('contacts', contact.jid, - 'gpg_enabled') - - # try GPG first - if not e2e_is_active and gpg_pref and \ - gajim.config.get_per('accounts', self.account, 'keyid') and \ - gajim.connections[self.account].USE_GPG: - self.gpg_is_active = True - gajim.encrypted_chats[self.account].append(contact.jid) - msg = _('GPG encryption enabled') - ChatControlBase.print_conversation_line(self, msg, - 'status', '', None) - - if self.session: - self.session.loggable = gajim.config.get_per('accounts', - self.account, 'log_encrypted_sessions') - # GPG is always authenticated as we use GPG's WoT - self._show_lock_image(self.gpg_is_active, 'GPG', self.gpg_is_active, - self.session and self.session.is_loggable(), True) - - self.update_ui() - # restore previous conversation - self.restore_conversation() - self.msg_textview.grab_focus() - - def update_toolbar(self): - # Formatting - if self.contact.supports(NS_XHTML_IM) and not self.gpg_is_active: - self._formattings_button.set_sensitive(True) - else: - self._formattings_button.set_sensitive(False) - - # Add to roster - if not isinstance(self.contact, GC_Contact) \ - and _('Not in Roster') in self.contact.groups: - self._add_to_roster_button.show() - else: - self._add_to_roster_button.hide() - - # Jingle detection - if self.contact.supports(NS_JINGLE_ICE_UDP) and \ - gajim.HAVE_FARSIGHT and self.contact.resource: - if self.contact.supports(NS_JINGLE_RTP_AUDIO): - if self.audio_state == self.JINGLE_STATE_NOT_AVAILABLE: - self.set_audio_state('available') - else: - self.set_audio_state('not_available') - - if self.contact.supports(NS_JINGLE_RTP_VIDEO): - if self.video_state == self.JINGLE_STATE_NOT_AVAILABLE: - self.set_video_state('available') - else: - self.set_video_state('not_available') - else: - if self.audio_state != self.JINGLE_STATE_NOT_AVAILABLE: - self.set_audio_state('not_available') - if self.video_state != self.JINGLE_STATE_NOT_AVAILABLE: - self.set_video_state('not_available') - - # Audio buttons - if self.audio_state == self.JINGLE_STATE_NOT_AVAILABLE: - self._audio_button.set_sensitive(False) - else: - self._audio_button.set_sensitive(True) - - # Video buttons - if self.video_state == self.JINGLE_STATE_NOT_AVAILABLE: - self._video_button.set_sensitive(False) - else: - self._video_button.set_sensitive(True) - - # Send file - if self.contact.supports(NS_FILE) and self.contact.resource: - self._send_file_button.set_sensitive(True) - else: - self._send_file_button.set_sensitive(False) - if not self.contact.supports(NS_FILE): - self._send_file_button.set_tooltip_text(_( - "This contact does not support file transfer.")) - else: - self._send_file_button.set_tooltip_text( - _("You need to know the real JID of the contact to send him or " - "her a file.")) - - # Convert to GC - if self.contact.supports(NS_MUC): - self._convert_to_gc_button.set_sensitive(True) - else: - self._convert_to_gc_button.set_sensitive(False) - - def update_all_pep_types(self): - for pep_type in self._pep_images: - self.update_pep(pep_type) - - def update_pep(self, pep_type): - if isinstance(self.contact, GC_Contact): - return - if pep_type not in self._pep_images: - return - pep = self.contact.pep - img = self._pep_images[pep_type] - if pep_type in pep: - img.set_from_pixbuf(pep[pep_type].asPixbufIcon()) - img.set_tooltip_markup(pep[pep_type].asMarkupText()) - img.show() - else: - img.hide() - - def _update_jingle(self, jingle_type): - if jingle_type not in ('audio', 'video'): - return - if self.__dict__[jingle_type + '_state'] in ( - self.JINGLE_STATE_NOT_AVAILABLE, self.JINGLE_STATE_AVAILABLE): - self.__dict__['_' + jingle_type + '_banner_image'].hide() - else: - self.__dict__['_' + jingle_type + '_banner_image'].show() - if self.audio_state == self.JINGLE_STATE_CONNECTING: - self.__dict__['_' + jingle_type + '_banner_image'].set_from_stock( - gtk.STOCK_CONVERT, 1) - elif self.audio_state == self.JINGLE_STATE_CONNECTION_RECEIVED: - self.__dict__['_' + jingle_type + '_banner_image'].set_from_stock( - gtk.STOCK_NETWORK, 1) - elif self.audio_state == self.JINGLE_STATE_CONNECTED: - self.__dict__['_' + jingle_type + '_banner_image'].set_from_stock( - gtk.STOCK_CONNECT, 1) - elif self.audio_state == self.JINGLE_STATE_ERROR: - self.__dict__['_' + jingle_type + '_banner_image'].set_from_stock( - gtk.STOCK_DIALOG_WARNING, 1) - self.update_toolbar() - - def update_audio(self): - self._update_jingle('audio') - - def update_video(self): - self._update_jingle('video') - - def change_resource(self, resource): - old_full_jid = self.get_full_jid() - self.resource = resource - new_full_jid = self.get_full_jid() - # update gajim.last_message_time - if old_full_jid in gajim.last_message_time[self.account]: - gajim.last_message_time[self.account][new_full_jid] = \ - gajim.last_message_time[self.account][old_full_jid] - # update events - gajim.events.change_jid(self.account, old_full_jid, new_full_jid) - # update MessageWindow._controls - self.parent_win.change_jid(self.account, old_full_jid, new_full_jid) - - def _set_jingle_state(self, jingle_type, state, sid=None, reason=None): - if jingle_type not in ('audio', 'video'): - return - if state in ('connecting', 'connected', 'stop') and reason: - str = _('%(type)s state : %(state)s, reason: %(reason)s') % { - 'type': jingle_type.capitalize(), 'state': state, 'reason': reason} - self.print_conversation(str, 'info') - - states = {'not_available': self.JINGLE_STATE_NOT_AVAILABLE, - 'available': self.JINGLE_STATE_AVAILABLE, - 'connecting': self.JINGLE_STATE_CONNECTING, - 'connection_received': self.JINGLE_STATE_CONNECTION_RECEIVED, - 'connected': self.JINGLE_STATE_CONNECTED, - 'stop': self.JINGLE_STATE_AVAILABLE, - 'error': self.JINGLE_STATE_ERROR} - - if state in states: - jingle_state = states[state] - if self.__dict__[jingle_type + '_state'] == jingle_state: - return - self.__dict__[jingle_type + '_state'] = jingle_state - - # Destroy existing session with the user when he signs off - # We need to do that before modifying the sid - if state == 'not_available': - gajim.connections[self.account].delete_jingle_session( - self.contact.get_full_jid(), self.__dict__[jingle_type + '_sid']) - - if state in ('not_available', 'available', 'stop'): - self.__dict__[jingle_type + '_sid'] = None - if state in ('connection_received', 'connecting'): - self.__dict__[jingle_type + '_sid'] = sid - - if state in ('connecting', 'connected', 'connection_received'): - self.__dict__['_' + jingle_type + '_button'].set_active(True) - elif state in ('not_available', 'stop'): - self.__dict__['_' + jingle_type + '_button'].set_active(False) - - eval('self.update_' + jingle_type)() - - def set_audio_state(self, state, sid=None, reason=None): - self._set_jingle_state('audio', state, sid=sid, reason=reason) - - def set_video_state(self, state, sid=None, reason=None): - self._set_jingle_state('video', state, sid=sid, reason=reason) - - def on_avatar_eventbox_enter_notify_event(self, widget, event): - """ - Enter the eventbox area so we under conditions add a timeout to show a - bigger avatar after 0.5 sec - """ - jid = self.contact.jid - is_fake = False - if self.type_id == message_control.TYPE_PM: - is_fake = True - avatar_pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid, - is_fake) - if avatar_pixbuf in ('ask', None): - return - avatar_w = avatar_pixbuf.get_width() - avatar_h = avatar_pixbuf.get_height() - - scaled_buf = self.xml.get_widget('avatar_image').get_pixbuf() - scaled_buf_w = scaled_buf.get_width() - scaled_buf_h = scaled_buf.get_height() - - # do we have something bigger to show? - if avatar_w > scaled_buf_w or avatar_h > scaled_buf_h: - # wait for 0.5 sec in case we leave earlier - self.show_bigger_avatar_timeout_id = gobject.timeout_add(500, - self.show_bigger_avatar, widget) - - def on_avatar_eventbox_leave_notify_event(self, widget, event): - """ - Left the eventbox area that holds the avatar img - """ - # did we add a timeout? if yes remove it - if self.show_bigger_avatar_timeout_id is not None: - gobject.source_remove(self.show_bigger_avatar_timeout_id) - - def on_avatar_eventbox_button_press_event(self, widget, event): - """ - If right-clicked, show popup - """ - if event.button == 3: # right click - menu = gtk.Menu() - menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS) - id_ = menuitem.connect('activate', - gtkgui_helpers.on_avatar_save_as_menuitem_activate, - self.contact.jid, self.account, self.contact.get_shown_name() + \ - '.jpeg') - self.handlers[id_] = menuitem - menu.append(menuitem) - menu.show_all() - menu.connect('selection-done', lambda w:w.destroy()) - # show the menu - menu.show_all() - menu.popup(None, None, None, event.button, event.time) - return True - - def _on_window_motion_notify(self, widget, event): - """ - It gets called no matter if it is the active window or not - """ - if self.parent_win.get_active_jid() == self.contact.jid: - # if window is the active one, change vars assisting chatstate - self.mouse_over_in_last_5_secs = True - self.mouse_over_in_last_30_secs = True - - def _schedule_activity_timers(self): - self.possible_paused_timeout_id = gobject.timeout_add_seconds(5, - self.check_for_possible_paused_chatstate, None) - self.possible_inactive_timeout_id = gobject.timeout_add_seconds(30, - self.check_for_possible_inactive_chatstate, None) - - def update_ui(self): - # The name banner is drawn here - ChatControlBase.update_ui(self) - self.update_toolbar() - - def _update_banner_state_image(self): - contact = gajim.contacts.get_contact_with_highest_priority(self.account, - self.contact.jid) - if not contact or self.resource: - # For transient contacts - contact = self.contact - show = contact.show - jid = contact.jid - - # Set banner image - img_32 = gajim.interface.roster.get_appropriate_state_images(jid, - size = '32', icon_name = show) - img_16 = gajim.interface.roster.get_appropriate_state_images(jid, - icon_name = show) - if show in img_32 and img_32[show].get_pixbuf(): - # we have 32x32! use it! - banner_image = img_32[show] - use_size_32 = True - else: - banner_image = img_16[show] - use_size_32 = False - - banner_status_img = self.xml.get_widget('banner_status_image') - if banner_image.get_storage_type() == gtk.IMAGE_ANIMATION: - banner_status_img.set_from_animation(banner_image.get_animation()) - else: - pix = banner_image.get_pixbuf() - if pix is not None: - if use_size_32: - banner_status_img.set_from_pixbuf(pix) - else: # we need to scale 16x16 to 32x32 - scaled_pix = pix.scale_simple(32, 32, - gtk.gdk.INTERP_BILINEAR) - banner_status_img.set_from_pixbuf(scaled_pix) - - def draw_banner_text(self): - """ - Draw the text in the fat line at the top of the window that houses the - name, jid - """ - contact = self.contact - jid = contact.jid - - banner_name_label = self.xml.get_widget('banner_name_label') - - name = contact.get_shown_name() - if self.resource: - name += '/' + self.resource - if self.TYPE_ID == message_control.TYPE_PM: - name = _('%(nickname)s from group chat %(room_name)s') %\ - {'nickname': name, 'room_name': self.room_name} - name = gobject.markup_escape_text(name) - - # We know our contacts nick, but if another contact has the same nick - # in another account we need to also display the account. - # except if we are talking to two different resources of the same contact - acct_info = '' - for account in gajim.contacts.get_accounts(): - if account == self.account: - continue - if acct_info: # We already found a contact with same nick - break - for jid in gajim.contacts.get_jid_list(account): - other_contact_ = \ - gajim.contacts.get_first_contact_from_jid(account, jid) - if other_contact_.get_shown_name() == self.contact.get_shown_name(): - acct_info = ' (%s)' % \ - gobject.markup_escape_text(self.account) - break - - status = contact.status - if status is not None: - banner_name_label.set_ellipsize(pango.ELLIPSIZE_END) - self.banner_status_label.set_ellipsize(pango.ELLIPSIZE_END) - status_reduced = helpers.reduce_chars_newlines(status, max_lines = 1) - status_escaped = gobject.markup_escape_text(status_reduced) - - font_attrs, font_attrs_small = self.get_font_attrs() - st = gajim.config.get('displayed_chat_state_notifications') - cs = contact.chatstate - if cs and st in ('composing_only', 'all'): - if contact.show == 'offline': - chatstate = '' - elif contact.composing_xep == 'XEP-0085': - if st == 'all' or cs == 'composing': - chatstate = helpers.get_uf_chatstate(cs) - else: - chatstate = '' - elif contact.composing_xep == 'XEP-0022': - if cs in ('composing', 'paused'): - # only print composing, paused - chatstate = helpers.get_uf_chatstate(cs) - else: - chatstate = '' - else: - # When does that happen ? See [7797] and [7804] - chatstate = helpers.get_uf_chatstate(cs) - - label_text = '%s%s %s' \ - % (font_attrs, name, font_attrs_small, - acct_info, chatstate) - if acct_info: - acct_info = ' ' + acct_info - label_tooltip = '%s%s %s' % (name, acct_info, chatstate) - else: - # weight="heavy" size="x-large" - label_text = '%s%s' % \ - (font_attrs, name, font_attrs_small, acct_info) - if acct_info: - acct_info = ' ' + acct_info - label_tooltip = '%s%s' % (name, acct_info) - - if status_escaped: - if gajim.HAVE_PYSEXY: - status_text = self.urlfinder.sub(self.make_href, status_escaped) - status_text = '%s' % (font_attrs_small, status_text) - else: - status_text = '%s' % (font_attrs_small, status_escaped) - self.banner_status_label.set_tooltip_text(status) - self.banner_status_label.show() - self.banner_status_label.set_no_show_all(False) - else: - status_text = '' - self.banner_status_label.hide() - self.banner_status_label.set_no_show_all(True) - - self.banner_status_label.set_markup(status_text) - # setup the label that holds name and jid - banner_name_label.set_markup(label_text) - banner_name_label.set_tooltip_text(label_tooltip) - - def on_audio_button_toggled(self, widget): - if widget.get_active(): - path_to_img = os.path.join(gajim.DATA_DIR, 'pixmaps', - 'mic_active.png') - if self.audio_state == self.JINGLE_STATE_AVAILABLE: - sid = gajim.connections[self.account].startVoIP( - self.contact.get_full_jid()) - self.set_audio_state('connecting', sid) - else: - path_to_img = os.path.join(gajim.DATA_DIR, 'pixmaps', - 'mic_inactive.png') - session = gajim.connections[self.account].get_jingle_session( - self.contact.get_full_jid(), self.audio_sid) - if session: - content = session.get_content('audio') - if content: - session.remove_content(content.creator, content.name) - img = self._audio_button.get_property('image') - img.set_from_file(path_to_img) - - def on_video_button_toggled(self, widget): - if widget.get_active(): - path_to_img = os.path.join(gajim.DATA_DIR, 'pixmaps', - 'cam_active.png') - if self.video_state == self.JINGLE_STATE_AVAILABLE: - sid = gajim.connections[self.account].startVideoIP( - self.contact.get_full_jid()) - self.set_video_state('connecting', sid) - else: - path_to_img = os.path.join(gajim.DATA_DIR, 'pixmaps', - 'cam_inactive.png') - session = gajim.connections[self.account].get_jingle_session( - self.contact.get_full_jid(), self.video_sid) - if session: - content = session.get_content('video') - if content: - session.remove_content(content.creator, content.name) - img = self._video_button.get_property('image') - img.set_from_file(path_to_img) - - def _toggle_gpg(self): - if not self.gpg_is_active and not self.contact.keyID: - dialogs.ErrorDialog(_('No GPG key assigned'), - _('No GPG key is assigned to this contact. So you cannot ' - 'encrypt messages with GPG.')) - return - ec = gajim.encrypted_chats[self.account] - if self.gpg_is_active: - # Disable encryption - ec.remove(self.contact.jid) - self.gpg_is_active = False - loggable = False - msg = _('GPG encryption disabled') - ChatControlBase.print_conversation_line(self, msg, - 'status', '', None) - if self.session: - self.session.loggable = True - - else: - # Enable encryption - ec.append(self.contact.jid) - self.gpg_is_active = True - msg = _('GPG encryption enabled') - ChatControlBase.print_conversation_line(self, msg, - 'status', '', None) - - loggable = gajim.config.get_per('accounts', self.account, - 'log_encrypted_sessions') - - if self.session: - self.session.loggable = loggable - - loggable = self.session.is_loggable() - else: - loggable = loggable and gajim.config.should_log(self.account, - self.contact.jid) - - if loggable: - msg = _('Session WILL be logged') - else: - msg = _('Session WILL NOT be logged') - - ChatControlBase.print_conversation_line(self, msg, - 'status', '', None) - - gajim.config.set_per('contacts', self.contact.jid, - 'gpg_enabled', self.gpg_is_active) - - self._show_lock_image(self.gpg_is_active, 'GPG', - self.gpg_is_active, loggable, True) - - def _show_lock_image(self, visible, enc_type = '', enc_enabled = False, - chat_logged = False, authenticated = False): - """ - Set lock icon visibility and create tooltip - """ - #encryption %s active - status_string = enc_enabled and _('is') or _('is NOT') - #chat session %s be logged - logged_string = chat_logged and _('will') or _('will NOT') - - if authenticated: - #About encrypted chat session - authenticated_string = _('and authenticated') - self.lock_image.set_from_file(os.path.join(gajim.DATA_DIR, 'pixmaps', 'security-high.png')) - else: - #About encrypted chat session - authenticated_string = _('and NOT authenticated') - self.lock_image.set_from_file(os.path.join(gajim.DATA_DIR, 'pixmaps', 'security-low.png')) - - #status will become 'is' or 'is not', authentificaed will become - #'and authentificated' or 'and not authentificated', logged will become - #'will' or 'will not' - tooltip = _('%(type)s encryption %(status)s active %(authenticated)s.\n' - 'Your chat session %(logged)s be logged.') % {'type': enc_type, - 'status': status_string, 'authenticated': authenticated_string, - 'logged': logged_string} - - self.authentication_button.set_tooltip_text(tooltip) - self.widget_set_visible(self.authentication_button, not visible) - self.lock_image.set_sensitive(enc_enabled) - - def _on_authentication_button_clicked(self, widget): - if self.gpg_is_active: - dialogs.GPGInfoWindow(self) - elif self.session and self.session.enable_encryption: - dialogs.ESessionInfoWindow(self.session) - - def send_message(self, message, keyID='', chatstate=None, xhtml=None, - process_commands=True): - """ - Send a message to contact - """ - if message in ('', None, '\n'): - return None - - # refresh timers - self.reset_kbd_mouse_timeout_vars() - - contact = self.contact - - encrypted = bool(self.session) and self.session.enable_encryption - - keyID = '' - if self.gpg_is_active: - keyID = contact.keyID - encrypted = True - if not keyID: - keyID = 'UNKNOWN' - - chatstates_on = gajim.config.get('outgoing_chat_state_notifications') != \ - 'disabled' - composing_xep = contact.composing_xep - chatstate_to_send = None - if chatstates_on and contact is not None: - if composing_xep is None: - # no info about peer - # send active to discover chat state capabilities - # this is here (and not in send_chatstate) - # because we want it sent with REAL message - # (not standlone) eg. one that has body - - if contact.our_chatstate: - # We already asked for xep 85, don't ask it twice - composing_xep = 'asked_once' - - chatstate_to_send = 'active' - contact.our_chatstate = 'ask' # pseudo state - # if peer supports jep85 and we are not 'ask', send 'active' - # NOTE: first active and 'ask' is set in gajim.py - elif composing_xep is not False: - # send active chatstate on every message (as XEP says) - chatstate_to_send = 'active' - contact.our_chatstate = 'active' - - gobject.source_remove(self.possible_paused_timeout_id) - gobject.source_remove(self.possible_inactive_timeout_id) - self._schedule_activity_timers() - - def _on_sent(id_, contact, message, encrypted, xhtml): - if contact.supports(NS_RECEIPTS) and gajim.config.get_per('accounts', - self.account, 'request_receipt'): - xep0184_id = id_ - else: - xep0184_id = None - - self.print_conversation(message, self.contact.jid, encrypted=encrypted, - xep0184_id=xep0184_id, xhtml=xhtml) - - ChatControlBase.send_message(self, message, keyID, type_='chat', - chatstate=chatstate_to_send, composing_xep=composing_xep, - xhtml=xhtml, callback=_on_sent, - callback_args=[contact, message, encrypted, xhtml], - process_commands=process_commands) - - def check_for_possible_paused_chatstate(self, arg): - """ - Did we move mouse of that window or write something in message textview - in the last 5 seconds? If yes - we go active for mouse, composing for - kbd. If not - we go paused if we were previously composing - """ - contact = self.contact - jid = contact.jid - current_state = contact.our_chatstate - if current_state is False: # jid doesn't support chatstates - return False # stop looping - - message_buffer = self.msg_textview.get_buffer() - if self.kbd_activity_in_last_5_secs and message_buffer.get_char_count(): - # Only composing if the keyboard activity was in text entry - self.send_chatstate('composing') - elif self.mouse_over_in_last_5_secs and\ - jid == self.parent_win.get_active_jid(): - self.send_chatstate('active') - else: - if current_state == 'composing': - self.send_chatstate('paused') # pause composing - - # assume no activity and let the motion-notify or 'insert-text' make them - # True refresh 30 seconds vars too or else it's 30 - 5 = 25 seconds! - self.reset_kbd_mouse_timeout_vars() - return True # loop forever - - def check_for_possible_inactive_chatstate(self, arg): - """ - Did we move mouse over that window or wrote something in message textview - in the last 30 seconds? if yes - we go active. If no - we go inactive - """ - contact = self.contact - - current_state = contact.our_chatstate - if current_state is False: # jid doesn't support chatstates - return False # stop looping - - if self.mouse_over_in_last_5_secs or self.kbd_activity_in_last_5_secs: - return True # loop forever - - if not self.mouse_over_in_last_30_secs or \ - self.kbd_activity_in_last_30_secs: - self.send_chatstate('inactive', contact) - - # assume no activity and let the motion-notify or 'insert-text' make them - # True refresh 30 seconds too or else it's 30 - 5 = 25 seconds! - self.reset_kbd_mouse_timeout_vars() - return True # loop forever - - def reset_kbd_mouse_timeout_vars(self): - self.kbd_activity_in_last_5_secs = False - self.mouse_over_in_last_5_secs = False - self.mouse_over_in_last_30_secs = False - self.kbd_activity_in_last_30_secs = False - - def on_cancel_session_negotiation(self): - msg = _('Session negotiation cancelled') - ChatControlBase.print_conversation_line(self, msg, 'status', '', None) - - def print_archiving_session_details(self): - """ - Print esession settings to textview - """ - archiving = bool(self.session) and isinstance(self.session, - ArchivingStanzaSession) and self.session.archiving - if archiving: - msg = _('This session WILL be archived on server') - else: - msg = _('This session WILL NOT be archived on server') - ChatControlBase.print_conversation_line(self, msg, 'status', '', None) - - def print_esession_details(self): - """ - Print esession settings to textview - """ - e2e_is_active = bool(self.session) and self.session.enable_encryption - if e2e_is_active: - msg = _('This session is encrypted') - - if self.session.is_loggable(): - msg += _(' and WILL be logged') - else: - msg += _(' and WILL NOT be logged') - - ChatControlBase.print_conversation_line(self, msg, 'status', '', None) - - if not self.session.verified_identity: - ChatControlBase.print_conversation_line(self, _("Remote contact's identity not verified. Click the shield button for more details."), 'status', '', None) - else: - msg = _('E2E encryption disabled') - ChatControlBase.print_conversation_line(self, msg, 'status', '', None) - - self._show_lock_image(e2e_is_active, 'E2E', e2e_is_active, self.session and \ - self.session.is_loggable(), self.session and self.session.verified_identity) - - def print_session_details(self): - if isinstance(self.session, EncryptedStanzaSession): - self.print_esession_details() - elif isinstance(self.session, ArchivingStanzaSession): - self.print_archiving_session_details() - - def print_conversation(self, text, frm='', tim=None, encrypted=False, - subject=None, xhtml=None, simple=False, xep0184_id=None): - """ - Print a line in the conversation - - If frm is set to status: it's a status message. - if frm is set to error: it's an error message. The difference between - status and error is mainly that with error, msg count as a new message - (in systray and in control). - If frm is set to info: it's a information message. - If frm is set to print_queue: it is incomming from queue. - If frm is set to another value: it's an outgoing message. - If frm is not set: it's an incomming message. - """ - contact = self.contact - - if frm == 'status': - if not gajim.config.get('print_status_in_chats'): - return - kind = 'status' - name = '' - elif frm == 'error': - kind = 'error' - name = '' - elif frm == 'info': - kind = 'info' - name = '' - else: - if self.session and self.session.enable_encryption: - # ESessions - if not encrypted: - msg = _('The following message was NOT encrypted') - ChatControlBase.print_conversation_line(self, msg, 'status', '', - tim) - else: - # GPG encryption - if encrypted and not self.gpg_is_active: - msg = _('The following message was encrypted') - ChatControlBase.print_conversation_line(self, msg, 'status', '', - tim) - # turn on OpenPGP if this was in fact a XEP-0027 encrypted message - if encrypted == 'xep27': - self._toggle_gpg() - elif not encrypted and self.gpg_is_active: - msg = _('The following message was NOT encrypted') - ChatControlBase.print_conversation_line(self, msg, 'status', '', - tim) - if not frm: - kind = 'incoming' - name = contact.get_shown_name() - elif frm == 'print_queue': # incoming message, but do not update time - kind = 'incoming_queue' - name = contact.get_shown_name() - else: - kind = 'outgoing' - name = gajim.nicks[self.account] - if not xhtml and not (encrypted and self.gpg_is_active) and \ - gajim.config.get('rst_formatting_outgoing_messages'): - from common.rst_xhtml_generator import create_xhtml - xhtml = create_xhtml(text) - if xhtml: - xhtml = '%s' % (NS_XHTML, xhtml) - ChatControlBase.print_conversation_line(self, text, kind, name, tim, - subject=subject, old_kind=self.old_msg_kind, xhtml=xhtml, - simple=simple, xep0184_id=xep0184_id) - if text.startswith('/me ') or text.startswith('/me\n'): - self.old_msg_kind = None - else: - self.old_msg_kind = kind - - def get_tab_label(self, chatstate): - unread = '' - if self.resource: - jid = self.contact.get_full_jid() - else: - jid = self.contact.jid - num_unread = len(gajim.events.get_events(self.account, jid, - ['printed_' + self.type_id, self.type_id])) - if num_unread == 1 and not gajim.config.get('show_unread_tab_icon'): - unread = '*' - elif num_unread > 1: - unread = '[' + unicode(num_unread) + ']' - - # Draw tab label using chatstate - theme = gajim.config.get('roster_theme') - color = None - if not chatstate: - chatstate = self.contact.chatstate - if chatstate is not None: - if chatstate == 'composing': - color = gajim.config.get_per('themes', theme, - 'state_composing_color') - elif chatstate == 'inactive': - color = gajim.config.get_per('themes', theme, - 'state_inactive_color') - elif chatstate == 'gone': - color = gajim.config.get_per('themes', theme, - 'state_gone_color') - elif chatstate == 'paused': - color = gajim.config.get_per('themes', theme, - 'state_paused_color') - if color: - # We set the color for when it's the current tab or not - color = gtk.gdk.colormap_get_system().alloc_color(color) - # In inactive tab color to be lighter against the darker inactive - # background - if chatstate in ('inactive', 'gone') and\ - self.parent_win.get_active_control() != self: - color = self.lighten_color(color) - else: # active or not chatstate, get color from gtk - color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE] - - - name = self.contact.get_shown_name() - if self.resource: - name += '/' + self.resource - label_str = gobject.markup_escape_text(name) - if num_unread: # if unread, text in the label becomes bold - label_str = '' + unread + label_str + '' - return (label_str, color) - - def get_tab_image(self, count_unread=True): - if self.resource: - jid = self.contact.get_full_jid() - else: - jid = self.contact.jid - if count_unread: - num_unread = len(gajim.events.get_events(self.account, jid, - ['printed_' + self.type_id, self.type_id])) - else: - num_unread = 0 - # Set tab image (always 16x16); unread messages show the 'event' image - tab_img = None - - if num_unread and gajim.config.get('show_unread_tab_icon'): - img_16 = gajim.interface.roster.get_appropriate_state_images( - self.contact.jid, icon_name = 'event') - tab_img = img_16['event'] - else: - contact = gajim.contacts.get_contact_with_highest_priority( - self.account, self.contact.jid) - if not contact or self.resource: - # For transient contacts - contact = self.contact - img_16 = gajim.interface.roster.get_appropriate_state_images( - self.contact.jid, icon_name=contact.show) - tab_img = img_16[contact.show] - - return tab_img - - def prepare_context_menu(self, hide_buttonbar_items=False): - """ - Set compact view menuitem active state sets active and sensitivity state - for toggle_gpg_menuitem sets sensitivity for history_menuitem (False for - tranasports) and file_transfer_menuitem and hide()/show() for - add_to_roster_menuitem - """ - menu = gui_menu_builder.get_contact_menu(self.contact, self.account, - use_multiple_contacts=False, show_start_chat=False, - show_encryption=True, control=self, - show_buttonbar_items=not hide_buttonbar_items) - return menu - - def send_chatstate(self, state, contact = None): - """ - Send OUR chatstate as STANDLONE chat state message (eg. no body) - to contact only if new chatstate is different from the previous one - if jid is not specified, send to active tab - """ - # JEP 85 does not allow resending the same chatstate - # this function checks for that and just returns so it's safe to call it - # with same state. - - # This functions also checks for violation in state transitions - # and raises RuntimeException with appropriate message - # more on that http://www.jabber.org/jeps/jep-0085.html#statechart - - # do not send nothing if we have chat state notifications disabled - # that means we won't reply to the from other peer - # so we do not broadcast jep85 capabalities - chatstate_setting = gajim.config.get('outgoing_chat_state_notifications') - if chatstate_setting == 'disabled': - return - elif chatstate_setting == 'composing_only' and state != 'active' and\ - state != 'composing': - return - - if contact is None: - contact = self.parent_win.get_active_contact() - if contact is None: - # contact was from pm in MUC, and left the room so contact is None - # so we cannot send chatstate anymore - return - - # Don't send chatstates to offline contacts - if contact.show == 'offline': - return - - if contact.composing_xep is False: # jid cannot do xep85 nor xep22 - return - - # if the new state we wanna send (state) equals - # the current state (contact.our_chatstate) then return - if contact.our_chatstate == state: - return - - if contact.composing_xep is None: - # we don't know anything about jid, so return - # NOTE: - # send 'active', set current state to 'ask' and return is done - # in self.send_message() because we need REAL message (with ) - # for that procedure so return to make sure we send only once - # 'active' until we know peer supports jep85 - return - - if contact.our_chatstate == 'ask': - return - - # in JEP22, when we already sent stop composing - # notification on paused, don't resend it - if contact.composing_xep == 'XEP-0022' and \ - contact.our_chatstate in ('paused', 'active', 'inactive') and \ - state is not 'composing': # not composing == in (active, inactive, gone) - contact.our_chatstate = 'active' - self.reset_kbd_mouse_timeout_vars() - return - - # prevent going paused if we we were not composing (JEP violation) - if state == 'paused' and not contact.our_chatstate == 'composing': - # go active before - MessageControl.send_message(self, None, chatstate = 'active') - contact.our_chatstate = 'active' - self.reset_kbd_mouse_timeout_vars() - - # if we're inactive prevent composing (JEP violation) - elif contact.our_chatstate == 'inactive' and state == 'composing': - # go active before - MessageControl.send_message(self, None, chatstate = 'active') - contact.our_chatstate = 'active' - self.reset_kbd_mouse_timeout_vars() - - MessageControl.send_message(self, None, chatstate = state, - msg_id = contact.msg_id, composing_xep = contact.composing_xep) - contact.our_chatstate = state - if contact.our_chatstate == 'active': - self.reset_kbd_mouse_timeout_vars() - - def shutdown(self): - # Send 'gone' chatstate - self.send_chatstate('gone', self.contact) - self.contact.chatstate = None - self.contact.our_chatstate = None - - # disconnect self from session - if self.session: - self.session.control = None - - # Disconnect timer callbacks - gobject.source_remove(self.possible_paused_timeout_id) - gobject.source_remove(self.possible_inactive_timeout_id) - # Remove bigger avatar window - if self.bigger_avatar_window: - self.bigger_avatar_window.destroy() - # Clean events - gajim.events.remove_events(self.account, self.get_full_jid(), - types = ['printed_' + self.type_id, self.type_id]) - # Remove contact instance if contact has been removed - key = (self.contact.jid, self.account) - roster = gajim.interface.roster - if key in roster.contacts_to_be_removed.keys() and \ - not roster.contact_has_pending_roster_events(self.contact, self.account): - backend = roster.contacts_to_be_removed[key]['backend'] - del roster.contacts_to_be_removed[key] - roster.remove_contact(self.contact.jid, self.account, force=True, - backend=backend) - # remove all register handlers on widgets, created by self.xml - # to prevent circular references among objects - for i in self.handlers.keys(): - if self.handlers[i].handler_is_connected(i): - self.handlers[i].disconnect(i) - del self.handlers[i] - self.conv_textview.del_handlers() - if gajim.config.get('use_speller') and HAS_GTK_SPELL: - spell_obj = gtkspell.get_from_text_view(self.msg_textview) - if spell_obj: - spell_obj.detach() - self.msg_textview.destroy() - - def minimizable(self): - return False - - def safe_shutdown(self): - return False - - def allow_shutdown(self, method, on_yes, on_no, on_minimize): - if time.time() - gajim.last_message_time[self.account]\ - [self.get_full_jid()] < 2: - # 2 seconds - def on_ok(): - on_yes(self) - - def on_cancel(): - on_no(self) - - dialogs.ConfirmationDialog( - # %s is being replaced in the code with JID - _('You just received a new message from "%s"') % self.contact.jid, - _('If you close this tab and you have history disabled, '\ - 'this message will be lost.'), on_response_ok=on_ok, - on_response_cancel=on_cancel) - return - on_yes(self) - - def handle_incoming_chatstate(self): - """ - Handle incoming chatstate that jid SENT TO us - """ - self.draw_banner_text() - # update chatstate in tab for this chat - self.parent_win.redraw_tab(self, self.contact.chatstate) - - def set_control_active(self, state): - ChatControlBase.set_control_active(self, state) - # send chatstate inactive to the one we're leaving - # and active to the one we visit - if state: - self.send_chatstate('active', self.contact) - else: - self.send_chatstate('inactive', self.contact) - # Hide bigger avatar window - if self.bigger_avatar_window: - self.bigger_avatar_window.destroy() - self.bigger_avatar_window = None - # Re-show the small avatar - self.show_avatar() - - def show_avatar(self, resource = None): - if not gajim.config.get('show_avatar_in_chat'): - return - - is_fake = False - if self.TYPE_ID == message_control.TYPE_PM: - is_fake = True - jid_with_resource = self.contact.jid # fake jid - else: - jid_with_resource = self.contact.jid - if resource: - jid_with_resource += '/' + resource - - # we assume contact has no avatar - scaled_pixbuf = None - - pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid_with_resource, - is_fake) - if pixbuf == 'ask': - # we don't have the vcard - if self.TYPE_ID == message_control.TYPE_PM: - if self.gc_contact.jid: - # We know the real jid of this contact - real_jid = self.gc_contact.jid - if self.gc_contact.resource: - real_jid += '/' + self.gc_contact.resource - else: - real_jid = jid_with_resource - gajim.connections[self.account].request_vcard(real_jid, - jid_with_resource) - else: - gajim.connections[self.account].request_vcard(jid_with_resource) - return - if pixbuf is not None: - scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'chat') - - image = self.xml.get_widget('avatar_image') - image.set_from_pixbuf(scaled_pixbuf) - image.show_all() - - def _on_drag_data_received(self, widget, context, x, y, selection, - target_type, timestamp): - if not selection.data: - return - if self.TYPE_ID == message_control.TYPE_PM: - c = self.gc_contact - else: - c = self.contact - if target_type == self.TARGET_TYPE_URI_LIST: - if not c.resource: # If no resource is known, we can't send a file - return - uri = selection.data.strip() - uri_splitted = uri.split() # we may have more than one file dropped - for uri in uri_splitted: - path = helpers.get_file_path_from_dnd_dropped_uri(uri) - if os.path.isfile(path): # is it file? - ft = gajim.interface.instances['file_transfers'] - ft.send_file(self.account, c, path) - return - - # chat2muc - treeview = gajim.interface.roster.tree - model = treeview.get_model() - data = selection.data - path = treeview.get_selection().get_selected_rows()[1][0] - iter_ = model.get_iter(path) - type_ = model[iter_][2] - if type_ != 'contact': # source is not a contact - return - dropped_jid = data.decode('utf-8') - - dropped_transport = gajim.get_transport_name_from_jid(dropped_jid) - c_transport = gajim.get_transport_name_from_jid(c.jid) - if dropped_transport or c_transport: - return # transport contacts cannot be invited - - dialogs.TransformChatToMUC(self.account, [c.jid], [dropped_jid]) - - def _on_message_tv_buffer_changed(self, textbuffer): - self.kbd_activity_in_last_5_secs = True - self.kbd_activity_in_last_30_secs = True - if textbuffer.get_char_count(): - self.send_chatstate('composing', self.contact) - - e2e_is_active = self.session and \ - self.session.enable_encryption - e2e_pref = gajim.config.get_per('accounts', self.account, - 'enable_esessions') and gajim.config.get_per('accounts', - self.account, 'autonegotiate_esessions') and gajim.config.get_per( - 'contacts', self.contact.jid, 'autonegotiate_esessions') - want_e2e = not e2e_is_active and not self.gpg_is_active \ - and e2e_pref - - if want_e2e and not self.no_autonegotiation \ - and gajim.HAVE_PYCRYPTO and self.contact.supports(NS_ESESSION): - self.begin_e2e_negotiation() - elif not self.session or not self.session.status: - self.begin_archiving_negotiation() - else: - self.send_chatstate('active', self.contact) - - def restore_conversation(self): - jid = self.contact.jid - # don't restore lines if it's a transport - if gajim.jid_is_transport(jid): - return - - # How many lines to restore and when to time them out - restore_how_many = gajim.config.get('restore_lines') - if restore_how_many <= 0: - return - timeout = gajim.config.get('restore_timeout') # in minutes - - # number of messages that are in queue and are already logged, we want - # to avoid duplication - pending_how_many = len(gajim.events.get_events(self.account, jid, - ['chat', 'pm'])) - if self.resource: - pending_how_many += len(gajim.events.get_events(self.account, - self.contact.get_full_jid(), ['chat', 'pm'])) - - try: - rows = gajim.logger.get_last_conversation_lines(jid, restore_how_many, - pending_how_many, timeout, self.account) - except exceptions.DatabaseMalformed: - import common.logger - dialogs.ErrorDialog(_('Database Error'), - _('The database file (%s) cannot be read. Try to repair it or remove it (all history will be lost).') % common.logger.LOG_DB_PATH) - rows = [] - local_old_kind = None - for row in rows: # row[0] time, row[1] has kind, row[2] the message - if not row[2]: # message is empty, we don't print it - continue - if row[1] in (constants.KIND_CHAT_MSG_SENT, - constants.KIND_SINGLE_MSG_SENT): - kind = 'outgoing' - name = gajim.nicks[self.account] - elif row[1] in (constants.KIND_SINGLE_MSG_RECV, - constants.KIND_CHAT_MSG_RECV): - kind = 'incoming' - name = self.contact.get_shown_name() - elif row[1] == constants.KIND_ERROR: - kind = 'status' - name = self.contact.get_shown_name() - - tim = time.localtime(float(row[0])) - - if gajim.config.get('restored_messages_small'): - small_attr = ['small'] - else: - small_attr = [] - ChatControlBase.print_conversation_line(self, row[2], kind, name, tim, - small_attr, - small_attr + ['restored_message'], - small_attr + ['restored_message'], - False, old_kind = local_old_kind) - if row[2].startswith('/me ') or row[2].startswith('/me\n'): - local_old_kind = None - else: - local_old_kind = kind - if len(rows): - self.conv_textview.print_empty_line() - - def read_queue(self): - """ - Read queue and print messages containted in it - """ - jid = self.contact.jid - jid_with_resource = jid - if self.resource: - jid_with_resource += '/' + self.resource - events = gajim.events.get_events(self.account, jid_with_resource) - - # list of message ids which should be marked as read - message_ids = [] - for event in events: - if event.type_ != self.type_id: - continue - data = event.parameters - kind = data[2] - if kind == 'error': - kind = 'info' - else: - kind = 'print_queue' - self.print_conversation(data[0], kind, tim = data[3], - encrypted = data[4], subject = data[1], xhtml = data[7]) - if len(data) > 6 and isinstance(data[6], int): - message_ids.append(data[6]) - - if len(data) > 8: - self.set_session(data[8]) - if message_ids: - gajim.logger.set_read_messages(message_ids) - gajim.events.remove_events(self.account, jid_with_resource, - types = [self.type_id]) - - typ = 'chat' # Is it a normal chat or a pm ? - - # reset to status image in gc if it is a pm - # Is it a pm ? - room_jid, nick = gajim.get_room_and_nick_from_fjid(jid) - control = gajim.interface.msg_win_mgr.get_gc_control(room_jid, - self.account) - if control and control.type_id == message_control.TYPE_GC: - control.update_ui() - control.parent_win.show_title() - typ = 'pm' - - self.redraw_after_event_removed(jid) - if (self.contact.show in ('offline', 'error')): - show_offline = gajim.config.get('showoffline') - show_transports = gajim.config.get('show_transports_group') - if (not show_transports and gajim.jid_is_transport(jid)) or \ - (not show_offline and typ == 'chat' and \ - len(gajim.contacts.get_contacts(self.account, jid)) < 2): - gajim.interface.roster.remove_to_be_removed(self.contact.jid, - self.account) - elif typ == 'pm': - control.remove_contact(nick) - - def show_bigger_avatar(self, small_avatar): - """ - Resize the avatar, if needed, so it has at max half the screen size and - shows it - """ - if not small_avatar.window: - # Tab has been closed since we hovered the avatar - return - is_fake = False - if self.type_id == message_control.TYPE_PM: - is_fake = True - avatar_pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache( - self.contact.jid, is_fake) - if avatar_pixbuf in ('ask', None): - return - # Hide the small avatar - # this code hides the small avatar when we show a bigger one in case - # the avatar has a transparency hole in the middle - # so when we show the big one we avoid seeing the small one behind. - # It's why I set it transparent. - image = self.xml.get_widget('avatar_image') - pixbuf = image.get_pixbuf() - pixbuf.fill(0xffffff00L) # RGBA - image.queue_draw() - - screen_w = gtk.gdk.screen_width() - screen_h = gtk.gdk.screen_height() - avatar_w = avatar_pixbuf.get_width() - avatar_h = avatar_pixbuf.get_height() - half_scr_w = screen_w / 2 - half_scr_h = screen_h / 2 - if avatar_w > half_scr_w: - avatar_w = half_scr_w - if avatar_h > half_scr_h: - avatar_h = half_scr_h - window = gtk.Window(gtk.WINDOW_POPUP) - self.bigger_avatar_window = window - pixmap, mask = avatar_pixbuf.render_pixmap_and_mask() - window.set_size_request(avatar_w, avatar_h) - # we should make the cursor visible - # gtk+ doesn't make use of the motion notify on gtkwindow by default - # so this line adds that - window.set_events(gtk.gdk.POINTER_MOTION_MASK) - window.set_app_paintable(True) - window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_TOOLTIP) - - window.realize() - window.window.set_back_pixmap(pixmap, False) # make it transparent - window.window.shape_combine_mask(mask, 0, 0) - - # make the bigger avatar window show up centered - x0, y0 = small_avatar.window.get_origin() - x0 += small_avatar.allocation.x - y0 += small_avatar.allocation.y - center_x= x0 + (small_avatar.allocation.width / 2) - center_y = y0 + (small_avatar.allocation.height / 2) - pos_x, pos_y = center_x - (avatar_w / 2), center_y - (avatar_h / 2) - window.move(pos_x, pos_y) - # make the cursor invisible so we can see the image - invisible_cursor = gtkgui_helpers.get_invisible_cursor() - window.window.set_cursor(invisible_cursor) - - # we should hide the window - window.connect('leave_notify_event', - self._on_window_avatar_leave_notify_event) - window.connect('motion-notify-event', - self._on_window_motion_notify_event) - - window.show_all() - - def _on_window_avatar_leave_notify_event(self, widget, event): - """ - Just left the popup window that holds avatar - """ - self.bigger_avatar_window.destroy() - self.bigger_avatar_window = None - # Re-show the small avatar - self.show_avatar() - - def _on_window_motion_notify_event(self, widget, event): - """ - Just moved the mouse so show the cursor - """ - cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR) - self.bigger_avatar_window.window.set_cursor(cursor) - - def _on_send_file_menuitem_activate(self, widget): - self._on_send_file() - - def _on_add_to_roster_menuitem_activate(self, widget): - dialogs.AddNewContactWindow(self.account, self.contact.jid) - - def _on_contact_information_menuitem_activate(self, widget): - gajim.interface.roster.on_info(widget, self.contact, self.account) - - def _on_toggle_gpg_menuitem_activate(self, widget): - self._toggle_gpg() - - def _on_convert_to_gc_menuitem_activate(self, widget): - """ - User wants to invite some friends to chat - """ - dialogs.TransformChatToMUC(self.account, [self.contact.jid]) - - def _on_toggle_e2e_menuitem_activate(self, widget): - if self.session and self.session.enable_encryption: - # e2e was enabled, disable it - jid = str(self.session.jid) - thread_id = self.session.thread_id - - self.session.terminate_e2e() - - gajim.connections[self.account].delete_session(jid, thread_id) - - # presumably the user had a good reason to shut it off, so - # disable autonegotiation too - self.no_autonegotiation = True - else: - self.begin_e2e_negotiation() - - def begin_negotiation(self): - self.no_autonegotiation = True - - if not self.session: - fjid = self.contact.get_full_jid() - new_sess = gajim.connections[self.account].make_new_session(fjid, type_=self.type_id) - self.set_session(new_sess) - - def begin_e2e_negotiation(self): - self.begin_negotiation() - self.session.negotiate_e2e(False) - - def begin_archiving_negotiation(self): - self.begin_negotiation() - self.session.negotiate_archiving() - - def got_connected(self): - ChatControlBase.got_connected(self) - # Refreshing contact - contact = gajim.contacts.get_contact_with_highest_priority( - self.account, self.contact.jid) - if isinstance(contact, GC_Contact): - contact = contact.as_contact() - if contact: - self.contact = contact - self.draw_banner() - - def update_status_display(self, name, uf_show, status): - """ - Print the contact's status and update the status/GPG image - """ - self.update_ui() - self.parent_win.redraw_tab(self) - - self.print_conversation(_('%(name)s is now %(status)s') % {'name': name, - 'status': uf_show}, 'status') - - if status: - self.print_conversation(' (', 'status', simple=True) - self.print_conversation('%s' % (status), 'status', simple=True) - self.print_conversation(')', 'status', simple=True) - -# vim: se ts=3: + """ + A control for standard 1-1 chat + """ + ( + JINGLE_STATE_NOT_AVAILABLE, + JINGLE_STATE_AVAILABLE, + JINGLE_STATE_CONNECTING, + JINGLE_STATE_CONNECTION_RECEIVED, + JINGLE_STATE_CONNECTED, + JINGLE_STATE_ERROR + ) = range(6) + + TYPE_ID = message_control.TYPE_CHAT + old_msg_kind = None # last kind of the printed message + + # Set a command host to bound to. Every command given through a chat will be + # processed with this command host. + COMMAND_HOST = ChatCommands + + def __init__(self, parent_win, contact, acct, session, resource = None): + ChatControlBase.__init__(self, self.TYPE_ID, parent_win, + 'chat_child_vbox', contact, acct, resource) + + self.gpg_is_active = False + # for muc use: + # widget = self.xml.get_widget('muc_window_actions_button') + self.actions_button = self.xml.get_widget('message_window_actions_button') + id_ = self.actions_button.connect('clicked', + self.on_actions_button_clicked) + self.handlers[id_] = self.actions_button + + self._formattings_button = self.xml.get_widget('formattings_button') + + self._add_to_roster_button = self.xml.get_widget( + 'add_to_roster_button') + id_ = self._add_to_roster_button.connect('clicked', + self._on_add_to_roster_menuitem_activate) + self.handlers[id_] = self._add_to_roster_button + + self._audio_button = self.xml.get_widget('audio_togglebutton') + id_ = self._audio_button.connect('toggled', self.on_audio_button_toggled) + self.handlers[id_] = self._audio_button + # add a special img + path_to_img = os.path.join(gajim.DATA_DIR, 'pixmaps', + 'mic_inactive.png') + img = gtk.Image() + img.set_from_file(path_to_img) + self._audio_button.set_image(img) + + self._video_button = self.xml.get_widget('video_togglebutton') + id_ = self._video_button.connect('toggled', self.on_video_button_toggled) + self.handlers[id_] = self._video_button + # add a special img + path_to_img = os.path.join(gajim.DATA_DIR, 'pixmaps', + 'cam_inactive.png') + img = gtk.Image() + img.set_from_file(path_to_img) + self._video_button.set_image(img) + + self._send_file_button = self.xml.get_widget('send_file_button') + # add a special img for send file button + path_to_upload_img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'upload.png') + img = gtk.Image() + img.set_from_file(path_to_upload_img) + self._send_file_button.set_image(img) + id_ = self._send_file_button.connect('clicked', + self._on_send_file_menuitem_activate) + self.handlers[id_] = self._send_file_button + + self._convert_to_gc_button = self.xml.get_widget( + 'convert_to_gc_button') + id_ = self._convert_to_gc_button.connect('clicked', + self._on_convert_to_gc_menuitem_activate) + self.handlers[id_] = self._convert_to_gc_button + + contact_information_button = self.xml.get_widget( + 'contact_information_button') + id_ = contact_information_button.connect('clicked', + self._on_contact_information_menuitem_activate) + self.handlers[id_] = contact_information_button + + compact_view = gajim.config.get('compact_view') + self.chat_buttons_set_visible(compact_view) + self.widget_set_visible(self.xml.get_widget('banner_eventbox'), + gajim.config.get('hide_chat_banner')) + + self.authentication_button = self.xml.get_widget( + 'authentication_button') + id_ = self.authentication_button.connect('clicked', + self._on_authentication_button_clicked) + self.handlers[id_] = self.authentication_button + + # Add lock image to show chat encryption + self.lock_image = self.xml.get_widget('lock_image') + + # Convert to GC icon + img = self.xml.get_widget('convert_to_gc_button_image') + img.set_from_pixbuf(gtkgui_helpers.load_icon( + 'muc_active').get_pixbuf()) + + self._audio_banner_image = self.xml.get_widget('audio_banner_image') + self._video_banner_image = self.xml.get_widget('video_banner_image') + self.audio_sid = None + self.audio_state = self.JINGLE_STATE_NOT_AVAILABLE + self.video_sid = None + self.video_state = self.JINGLE_STATE_NOT_AVAILABLE + + self.update_toolbar() + + self._pep_images = {} + self._pep_images['mood'] = self.xml.get_widget('mood_image') + self._pep_images['activity'] = self.xml.get_widget('activity_image') + self._pep_images['tune'] = self.xml.get_widget('tune_image') + self.update_all_pep_types() + + # keep timeout id and window obj for possible big avatar + # it is on enter-notify and leave-notify so no need to be + # per jid + self.show_bigger_avatar_timeout_id = None + self.bigger_avatar_window = None + self.show_avatar(self.contact.resource) + + # chatstate timers and state + self.reset_kbd_mouse_timeout_vars() + self._schedule_activity_timers() + + # Hook up signals + id_ = self.parent_win.window.connect('motion-notify-event', + self._on_window_motion_notify) + self.handlers[id_] = self.parent_win.window + message_tv_buffer = self.msg_textview.get_buffer() + id_ = message_tv_buffer.connect('changed', + self._on_message_tv_buffer_changed) + self.handlers[id_] = message_tv_buffer + + widget = self.xml.get_widget('avatar_eventbox') + widget.set_property('height-request', gajim.config.get( + 'chat_avatar_height')) + id_ = widget.connect('enter-notify-event', + self.on_avatar_eventbox_enter_notify_event) + self.handlers[id_] = widget + + id_ = widget.connect('leave-notify-event', + self.on_avatar_eventbox_leave_notify_event) + self.handlers[id_] = widget + + id_ = widget.connect('button-press-event', + self.on_avatar_eventbox_button_press_event) + self.handlers[id_] = widget + + if not session: + # Don't use previous session if we want to a specific resource + # and it's not the same + if not resource: + resource = contact.resource + session = gajim.connections[self.account].find_controlless_session( + self.contact.jid, resource) + + if session: + session.control = self + self.session = session + + if session.enable_encryption: + self.print_session_details() + + # Enable encryption if needed + self.no_autonegotiation = False + e2e_is_active = self.session and self.session.enable_encryption + gpg_pref = gajim.config.get_per('contacts', contact.jid, + 'gpg_enabled') + + # try GPG first + if not e2e_is_active and gpg_pref and \ + gajim.config.get_per('accounts', self.account, 'keyid') and \ + gajim.connections[self.account].USE_GPG: + self.gpg_is_active = True + gajim.encrypted_chats[self.account].append(contact.jid) + msg = _('GPG encryption enabled') + ChatControlBase.print_conversation_line(self, msg, + 'status', '', None) + + if self.session: + self.session.loggable = gajim.config.get_per('accounts', + self.account, 'log_encrypted_sessions') + # GPG is always authenticated as we use GPG's WoT + self._show_lock_image(self.gpg_is_active, 'GPG', self.gpg_is_active, + self.session and self.session.is_loggable(), True) + + self.update_ui() + # restore previous conversation + self.restore_conversation() + self.msg_textview.grab_focus() + + def update_toolbar(self): + # Formatting + if self.contact.supports(NS_XHTML_IM) and not self.gpg_is_active: + self._formattings_button.set_sensitive(True) + else: + self._formattings_button.set_sensitive(False) + + # Add to roster + if not isinstance(self.contact, GC_Contact) \ + and _('Not in Roster') in self.contact.groups: + self._add_to_roster_button.show() + else: + self._add_to_roster_button.hide() + + # Jingle detection + if self.contact.supports(NS_JINGLE_ICE_UDP) and \ + gajim.HAVE_FARSIGHT and self.contact.resource: + if self.contact.supports(NS_JINGLE_RTP_AUDIO): + if self.audio_state == self.JINGLE_STATE_NOT_AVAILABLE: + self.set_audio_state('available') + else: + self.set_audio_state('not_available') + + if self.contact.supports(NS_JINGLE_RTP_VIDEO): + if self.video_state == self.JINGLE_STATE_NOT_AVAILABLE: + self.set_video_state('available') + else: + self.set_video_state('not_available') + else: + if self.audio_state != self.JINGLE_STATE_NOT_AVAILABLE: + self.set_audio_state('not_available') + if self.video_state != self.JINGLE_STATE_NOT_AVAILABLE: + self.set_video_state('not_available') + + # Audio buttons + if self.audio_state == self.JINGLE_STATE_NOT_AVAILABLE: + self._audio_button.set_sensitive(False) + else: + self._audio_button.set_sensitive(True) + + # Video buttons + if self.video_state == self.JINGLE_STATE_NOT_AVAILABLE: + self._video_button.set_sensitive(False) + else: + self._video_button.set_sensitive(True) + + # Send file + if self.contact.supports(NS_FILE) and self.contact.resource: + self._send_file_button.set_sensitive(True) + else: + self._send_file_button.set_sensitive(False) + if not self.contact.supports(NS_FILE): + self._send_file_button.set_tooltip_text(_( + "This contact does not support file transfer.")) + else: + self._send_file_button.set_tooltip_text( + _("You need to know the real JID of the contact to send him or " + "her a file.")) + + # Convert to GC + if self.contact.supports(NS_MUC): + self._convert_to_gc_button.set_sensitive(True) + else: + self._convert_to_gc_button.set_sensitive(False) + + def update_all_pep_types(self): + for pep_type in self._pep_images: + self.update_pep(pep_type) + + def update_pep(self, pep_type): + if isinstance(self.contact, GC_Contact): + return + if pep_type not in self._pep_images: + return + pep = self.contact.pep + img = self._pep_images[pep_type] + if pep_type in pep: + img.set_from_pixbuf(pep[pep_type].asPixbufIcon()) + img.set_tooltip_markup(pep[pep_type].asMarkupText()) + img.show() + else: + img.hide() + + def _update_jingle(self, jingle_type): + if jingle_type not in ('audio', 'video'): + return + if self.__dict__[jingle_type + '_state'] in ( + self.JINGLE_STATE_NOT_AVAILABLE, self.JINGLE_STATE_AVAILABLE): + self.__dict__['_' + jingle_type + '_banner_image'].hide() + else: + self.__dict__['_' + jingle_type + '_banner_image'].show() + if self.audio_state == self.JINGLE_STATE_CONNECTING: + self.__dict__['_' + jingle_type + '_banner_image'].set_from_stock( + gtk.STOCK_CONVERT, 1) + elif self.audio_state == self.JINGLE_STATE_CONNECTION_RECEIVED: + self.__dict__['_' + jingle_type + '_banner_image'].set_from_stock( + gtk.STOCK_NETWORK, 1) + elif self.audio_state == self.JINGLE_STATE_CONNECTED: + self.__dict__['_' + jingle_type + '_banner_image'].set_from_stock( + gtk.STOCK_CONNECT, 1) + elif self.audio_state == self.JINGLE_STATE_ERROR: + self.__dict__['_' + jingle_type + '_banner_image'].set_from_stock( + gtk.STOCK_DIALOG_WARNING, 1) + self.update_toolbar() + + def update_audio(self): + self._update_jingle('audio') + + def update_video(self): + self._update_jingle('video') + + def change_resource(self, resource): + old_full_jid = self.get_full_jid() + self.resource = resource + new_full_jid = self.get_full_jid() + # update gajim.last_message_time + if old_full_jid in gajim.last_message_time[self.account]: + gajim.last_message_time[self.account][new_full_jid] = \ + gajim.last_message_time[self.account][old_full_jid] + # update events + gajim.events.change_jid(self.account, old_full_jid, new_full_jid) + # update MessageWindow._controls + self.parent_win.change_jid(self.account, old_full_jid, new_full_jid) + + def _set_jingle_state(self, jingle_type, state, sid=None, reason=None): + if jingle_type not in ('audio', 'video'): + return + if state in ('connecting', 'connected', 'stop') and reason: + str = _('%(type)s state : %(state)s, reason: %(reason)s') % { + 'type': jingle_type.capitalize(), 'state': state, 'reason': reason} + self.print_conversation(str, 'info') + + states = {'not_available': self.JINGLE_STATE_NOT_AVAILABLE, + 'available': self.JINGLE_STATE_AVAILABLE, + 'connecting': self.JINGLE_STATE_CONNECTING, + 'connection_received': self.JINGLE_STATE_CONNECTION_RECEIVED, + 'connected': self.JINGLE_STATE_CONNECTED, + 'stop': self.JINGLE_STATE_AVAILABLE, + 'error': self.JINGLE_STATE_ERROR} + + if state in states: + jingle_state = states[state] + if self.__dict__[jingle_type + '_state'] == jingle_state: + return + self.__dict__[jingle_type + '_state'] = jingle_state + + # Destroy existing session with the user when he signs off + # We need to do that before modifying the sid + if state == 'not_available': + gajim.connections[self.account].delete_jingle_session( + self.contact.get_full_jid(), self.__dict__[jingle_type + '_sid']) + + if state in ('not_available', 'available', 'stop'): + self.__dict__[jingle_type + '_sid'] = None + if state in ('connection_received', 'connecting'): + self.__dict__[jingle_type + '_sid'] = sid + + if state in ('connecting', 'connected', 'connection_received'): + self.__dict__['_' + jingle_type + '_button'].set_active(True) + elif state in ('not_available', 'stop'): + self.__dict__['_' + jingle_type + '_button'].set_active(False) + + eval('self.update_' + jingle_type)() + + def set_audio_state(self, state, sid=None, reason=None): + self._set_jingle_state('audio', state, sid=sid, reason=reason) + + def set_video_state(self, state, sid=None, reason=None): + self._set_jingle_state('video', state, sid=sid, reason=reason) + + def on_avatar_eventbox_enter_notify_event(self, widget, event): + """ + Enter the eventbox area so we under conditions add a timeout to show a + bigger avatar after 0.5 sec + """ + jid = self.contact.jid + is_fake = False + if self.type_id == message_control.TYPE_PM: + is_fake = True + avatar_pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid, + is_fake) + if avatar_pixbuf in ('ask', None): + return + avatar_w = avatar_pixbuf.get_width() + avatar_h = avatar_pixbuf.get_height() + + scaled_buf = self.xml.get_widget('avatar_image').get_pixbuf() + scaled_buf_w = scaled_buf.get_width() + scaled_buf_h = scaled_buf.get_height() + + # do we have something bigger to show? + if avatar_w > scaled_buf_w or avatar_h > scaled_buf_h: + # wait for 0.5 sec in case we leave earlier + self.show_bigger_avatar_timeout_id = gobject.timeout_add(500, + self.show_bigger_avatar, widget) + + def on_avatar_eventbox_leave_notify_event(self, widget, event): + """ + Left the eventbox area that holds the avatar img + """ + # did we add a timeout? if yes remove it + if self.show_bigger_avatar_timeout_id is not None: + gobject.source_remove(self.show_bigger_avatar_timeout_id) + + def on_avatar_eventbox_button_press_event(self, widget, event): + """ + If right-clicked, show popup + """ + if event.button == 3: # right click + menu = gtk.Menu() + menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS) + id_ = menuitem.connect('activate', + gtkgui_helpers.on_avatar_save_as_menuitem_activate, + self.contact.jid, self.account, self.contact.get_shown_name() + \ + '.jpeg') + self.handlers[id_] = menuitem + menu.append(menuitem) + menu.show_all() + menu.connect('selection-done', lambda w:w.destroy()) + # show the menu + menu.show_all() + menu.popup(None, None, None, event.button, event.time) + return True + + def _on_window_motion_notify(self, widget, event): + """ + It gets called no matter if it is the active window or not + """ + if self.parent_win.get_active_jid() == self.contact.jid: + # if window is the active one, change vars assisting chatstate + self.mouse_over_in_last_5_secs = True + self.mouse_over_in_last_30_secs = True + + def _schedule_activity_timers(self): + self.possible_paused_timeout_id = gobject.timeout_add_seconds(5, + self.check_for_possible_paused_chatstate, None) + self.possible_inactive_timeout_id = gobject.timeout_add_seconds(30, + self.check_for_possible_inactive_chatstate, None) + + def update_ui(self): + # The name banner is drawn here + ChatControlBase.update_ui(self) + self.update_toolbar() + + def _update_banner_state_image(self): + contact = gajim.contacts.get_contact_with_highest_priority(self.account, + self.contact.jid) + if not contact or self.resource: + # For transient contacts + contact = self.contact + show = contact.show + jid = contact.jid + + # Set banner image + img_32 = gajim.interface.roster.get_appropriate_state_images(jid, + size = '32', icon_name = show) + img_16 = gajim.interface.roster.get_appropriate_state_images(jid, + icon_name = show) + if show in img_32 and img_32[show].get_pixbuf(): + # we have 32x32! use it! + banner_image = img_32[show] + use_size_32 = True + else: + banner_image = img_16[show] + use_size_32 = False + + banner_status_img = self.xml.get_widget('banner_status_image') + if banner_image.get_storage_type() == gtk.IMAGE_ANIMATION: + banner_status_img.set_from_animation(banner_image.get_animation()) + else: + pix = banner_image.get_pixbuf() + if pix is not None: + if use_size_32: + banner_status_img.set_from_pixbuf(pix) + else: # we need to scale 16x16 to 32x32 + scaled_pix = pix.scale_simple(32, 32, + gtk.gdk.INTERP_BILINEAR) + banner_status_img.set_from_pixbuf(scaled_pix) + + def draw_banner_text(self): + """ + Draw the text in the fat line at the top of the window that houses the + name, jid + """ + contact = self.contact + jid = contact.jid + + banner_name_label = self.xml.get_widget('banner_name_label') + + name = contact.get_shown_name() + if self.resource: + name += '/' + self.resource + if self.TYPE_ID == message_control.TYPE_PM: + name = _('%(nickname)s from group chat %(room_name)s') %\ + {'nickname': name, 'room_name': self.room_name} + name = gobject.markup_escape_text(name) + + # We know our contacts nick, but if another contact has the same nick + # in another account we need to also display the account. + # except if we are talking to two different resources of the same contact + acct_info = '' + for account in gajim.contacts.get_accounts(): + if account == self.account: + continue + if acct_info: # We already found a contact with same nick + break + for jid in gajim.contacts.get_jid_list(account): + other_contact_ = \ + gajim.contacts.get_first_contact_from_jid(account, jid) + if other_contact_.get_shown_name() == self.contact.get_shown_name(): + acct_info = ' (%s)' % \ + gobject.markup_escape_text(self.account) + break + + status = contact.status + if status is not None: + banner_name_label.set_ellipsize(pango.ELLIPSIZE_END) + self.banner_status_label.set_ellipsize(pango.ELLIPSIZE_END) + status_reduced = helpers.reduce_chars_newlines(status, max_lines = 1) + status_escaped = gobject.markup_escape_text(status_reduced) + + font_attrs, font_attrs_small = self.get_font_attrs() + st = gajim.config.get('displayed_chat_state_notifications') + cs = contact.chatstate + if cs and st in ('composing_only', 'all'): + if contact.show == 'offline': + chatstate = '' + elif contact.composing_xep == 'XEP-0085': + if st == 'all' or cs == 'composing': + chatstate = helpers.get_uf_chatstate(cs) + else: + chatstate = '' + elif contact.composing_xep == 'XEP-0022': + if cs in ('composing', 'paused'): + # only print composing, paused + chatstate = helpers.get_uf_chatstate(cs) + else: + chatstate = '' + else: + # When does that happen ? See [7797] and [7804] + chatstate = helpers.get_uf_chatstate(cs) + + label_text = '%s%s %s' \ + % (font_attrs, name, font_attrs_small, + acct_info, chatstate) + if acct_info: + acct_info = ' ' + acct_info + label_tooltip = '%s%s %s' % (name, acct_info, chatstate) + else: + # weight="heavy" size="x-large" + label_text = '%s%s' % \ + (font_attrs, name, font_attrs_small, acct_info) + if acct_info: + acct_info = ' ' + acct_info + label_tooltip = '%s%s' % (name, acct_info) + + if status_escaped: + if gajim.HAVE_PYSEXY: + status_text = self.urlfinder.sub(self.make_href, status_escaped) + status_text = '%s' % (font_attrs_small, status_text) + else: + status_text = '%s' % (font_attrs_small, status_escaped) + self.banner_status_label.set_tooltip_text(status) + self.banner_status_label.show() + self.banner_status_label.set_no_show_all(False) + else: + status_text = '' + self.banner_status_label.hide() + self.banner_status_label.set_no_show_all(True) + + self.banner_status_label.set_markup(status_text) + # setup the label that holds name and jid + banner_name_label.set_markup(label_text) + banner_name_label.set_tooltip_text(label_tooltip) + + def on_audio_button_toggled(self, widget): + if widget.get_active(): + path_to_img = os.path.join(gajim.DATA_DIR, 'pixmaps', + 'mic_active.png') + if self.audio_state == self.JINGLE_STATE_AVAILABLE: + sid = gajim.connections[self.account].startVoIP( + self.contact.get_full_jid()) + self.set_audio_state('connecting', sid) + else: + path_to_img = os.path.join(gajim.DATA_DIR, 'pixmaps', + 'mic_inactive.png') + session = gajim.connections[self.account].get_jingle_session( + self.contact.get_full_jid(), self.audio_sid) + if session: + content = session.get_content('audio') + if content: + session.remove_content(content.creator, content.name) + img = self._audio_button.get_property('image') + img.set_from_file(path_to_img) + + def on_video_button_toggled(self, widget): + if widget.get_active(): + path_to_img = os.path.join(gajim.DATA_DIR, 'pixmaps', + 'cam_active.png') + if self.video_state == self.JINGLE_STATE_AVAILABLE: + sid = gajim.connections[self.account].startVideoIP( + self.contact.get_full_jid()) + self.set_video_state('connecting', sid) + else: + path_to_img = os.path.join(gajim.DATA_DIR, 'pixmaps', + 'cam_inactive.png') + session = gajim.connections[self.account].get_jingle_session( + self.contact.get_full_jid(), self.video_sid) + if session: + content = session.get_content('video') + if content: + session.remove_content(content.creator, content.name) + img = self._video_button.get_property('image') + img.set_from_file(path_to_img) + + def _toggle_gpg(self): + if not self.gpg_is_active and not self.contact.keyID: + dialogs.ErrorDialog(_('No GPG key assigned'), + _('No GPG key is assigned to this contact. So you cannot ' + 'encrypt messages with GPG.')) + return + ec = gajim.encrypted_chats[self.account] + if self.gpg_is_active: + # Disable encryption + ec.remove(self.contact.jid) + self.gpg_is_active = False + loggable = False + msg = _('GPG encryption disabled') + ChatControlBase.print_conversation_line(self, msg, + 'status', '', None) + if self.session: + self.session.loggable = True + + else: + # Enable encryption + ec.append(self.contact.jid) + self.gpg_is_active = True + msg = _('GPG encryption enabled') + ChatControlBase.print_conversation_line(self, msg, + 'status', '', None) + + loggable = gajim.config.get_per('accounts', self.account, + 'log_encrypted_sessions') + + if self.session: + self.session.loggable = loggable + + loggable = self.session.is_loggable() + else: + loggable = loggable and gajim.config.should_log(self.account, + self.contact.jid) + + if loggable: + msg = _('Session WILL be logged') + else: + msg = _('Session WILL NOT be logged') + + ChatControlBase.print_conversation_line(self, msg, + 'status', '', None) + + gajim.config.set_per('contacts', self.contact.jid, + 'gpg_enabled', self.gpg_is_active) + + self._show_lock_image(self.gpg_is_active, 'GPG', + self.gpg_is_active, loggable, True) + + def _show_lock_image(self, visible, enc_type = '', enc_enabled = False, + chat_logged = False, authenticated = False): + """ + Set lock icon visibility and create tooltip + """ + #encryption %s active + status_string = enc_enabled and _('is') or _('is NOT') + #chat session %s be logged + logged_string = chat_logged and _('will') or _('will NOT') + + if authenticated: + #About encrypted chat session + authenticated_string = _('and authenticated') + self.lock_image.set_from_file(os.path.join(gajim.DATA_DIR, 'pixmaps', 'security-high.png')) + else: + #About encrypted chat session + authenticated_string = _('and NOT authenticated') + self.lock_image.set_from_file(os.path.join(gajim.DATA_DIR, 'pixmaps', 'security-low.png')) + + #status will become 'is' or 'is not', authentificaed will become + #'and authentificated' or 'and not authentificated', logged will become + #'will' or 'will not' + tooltip = _('%(type)s encryption %(status)s active %(authenticated)s.\n' + 'Your chat session %(logged)s be logged.') % {'type': enc_type, + 'status': status_string, 'authenticated': authenticated_string, + 'logged': logged_string} + + self.authentication_button.set_tooltip_text(tooltip) + self.widget_set_visible(self.authentication_button, not visible) + self.lock_image.set_sensitive(enc_enabled) + + def _on_authentication_button_clicked(self, widget): + if self.gpg_is_active: + dialogs.GPGInfoWindow(self) + elif self.session and self.session.enable_encryption: + dialogs.ESessionInfoWindow(self.session) + + def send_message(self, message, keyID='', chatstate=None, xhtml=None, + process_commands=True): + """ + Send a message to contact + """ + if message in ('', None, '\n'): + return None + + # refresh timers + self.reset_kbd_mouse_timeout_vars() + + contact = self.contact + + encrypted = bool(self.session) and self.session.enable_encryption + + keyID = '' + if self.gpg_is_active: + keyID = contact.keyID + encrypted = True + if not keyID: + keyID = 'UNKNOWN' + + chatstates_on = gajim.config.get('outgoing_chat_state_notifications') != \ + 'disabled' + composing_xep = contact.composing_xep + chatstate_to_send = None + if chatstates_on and contact is not None: + if composing_xep is None: + # no info about peer + # send active to discover chat state capabilities + # this is here (and not in send_chatstate) + # because we want it sent with REAL message + # (not standlone) eg. one that has body + + if contact.our_chatstate: + # We already asked for xep 85, don't ask it twice + composing_xep = 'asked_once' + + chatstate_to_send = 'active' + contact.our_chatstate = 'ask' # pseudo state + # if peer supports jep85 and we are not 'ask', send 'active' + # NOTE: first active and 'ask' is set in gajim.py + elif composing_xep is not False: + # send active chatstate on every message (as XEP says) + chatstate_to_send = 'active' + contact.our_chatstate = 'active' + + gobject.source_remove(self.possible_paused_timeout_id) + gobject.source_remove(self.possible_inactive_timeout_id) + self._schedule_activity_timers() + + def _on_sent(id_, contact, message, encrypted, xhtml): + if contact.supports(NS_RECEIPTS) and gajim.config.get_per('accounts', + self.account, 'request_receipt'): + xep0184_id = id_ + else: + xep0184_id = None + + self.print_conversation(message, self.contact.jid, encrypted=encrypted, + xep0184_id=xep0184_id, xhtml=xhtml) + + ChatControlBase.send_message(self, message, keyID, type_='chat', + chatstate=chatstate_to_send, composing_xep=composing_xep, + xhtml=xhtml, callback=_on_sent, + callback_args=[contact, message, encrypted, xhtml], + process_commands=process_commands) + + def check_for_possible_paused_chatstate(self, arg): + """ + Did we move mouse of that window or write something in message textview + in the last 5 seconds? If yes - we go active for mouse, composing for + kbd. If not - we go paused if we were previously composing + """ + contact = self.contact + jid = contact.jid + current_state = contact.our_chatstate + if current_state is False: # jid doesn't support chatstates + return False # stop looping + + message_buffer = self.msg_textview.get_buffer() + if self.kbd_activity_in_last_5_secs and message_buffer.get_char_count(): + # Only composing if the keyboard activity was in text entry + self.send_chatstate('composing') + elif self.mouse_over_in_last_5_secs and\ + jid == self.parent_win.get_active_jid(): + self.send_chatstate('active') + else: + if current_state == 'composing': + self.send_chatstate('paused') # pause composing + + # assume no activity and let the motion-notify or 'insert-text' make them + # True refresh 30 seconds vars too or else it's 30 - 5 = 25 seconds! + self.reset_kbd_mouse_timeout_vars() + return True # loop forever + + def check_for_possible_inactive_chatstate(self, arg): + """ + Did we move mouse over that window or wrote something in message textview + in the last 30 seconds? if yes - we go active. If no - we go inactive + """ + contact = self.contact + + current_state = contact.our_chatstate + if current_state is False: # jid doesn't support chatstates + return False # stop looping + + if self.mouse_over_in_last_5_secs or self.kbd_activity_in_last_5_secs: + return True # loop forever + + if not self.mouse_over_in_last_30_secs or \ + self.kbd_activity_in_last_30_secs: + self.send_chatstate('inactive', contact) + + # assume no activity and let the motion-notify or 'insert-text' make them + # True refresh 30 seconds too or else it's 30 - 5 = 25 seconds! + self.reset_kbd_mouse_timeout_vars() + return True # loop forever + + def reset_kbd_mouse_timeout_vars(self): + self.kbd_activity_in_last_5_secs = False + self.mouse_over_in_last_5_secs = False + self.mouse_over_in_last_30_secs = False + self.kbd_activity_in_last_30_secs = False + + def on_cancel_session_negotiation(self): + msg = _('Session negotiation cancelled') + ChatControlBase.print_conversation_line(self, msg, 'status', '', None) + + def print_archiving_session_details(self): + """ + Print esession settings to textview + """ + archiving = bool(self.session) and isinstance(self.session, + ArchivingStanzaSession) and self.session.archiving + if archiving: + msg = _('This session WILL be archived on server') + else: + msg = _('This session WILL NOT be archived on server') + ChatControlBase.print_conversation_line(self, msg, 'status', '', None) + + def print_esession_details(self): + """ + Print esession settings to textview + """ + e2e_is_active = bool(self.session) and self.session.enable_encryption + if e2e_is_active: + msg = _('This session is encrypted') + + if self.session.is_loggable(): + msg += _(' and WILL be logged') + else: + msg += _(' and WILL NOT be logged') + + ChatControlBase.print_conversation_line(self, msg, 'status', '', None) + + if not self.session.verified_identity: + ChatControlBase.print_conversation_line(self, _("Remote contact's identity not verified. Click the shield button for more details."), 'status', '', None) + else: + msg = _('E2E encryption disabled') + ChatControlBase.print_conversation_line(self, msg, 'status', '', None) + + self._show_lock_image(e2e_is_active, 'E2E', e2e_is_active, self.session and \ + self.session.is_loggable(), self.session and self.session.verified_identity) + + def print_session_details(self): + if isinstance(self.session, EncryptedStanzaSession): + self.print_esession_details() + elif isinstance(self.session, ArchivingStanzaSession): + self.print_archiving_session_details() + + def print_conversation(self, text, frm='', tim=None, encrypted=False, + subject=None, xhtml=None, simple=False, xep0184_id=None): + """ + Print a line in the conversation + + If frm is set to status: it's a status message. + if frm is set to error: it's an error message. The difference between + status and error is mainly that with error, msg count as a new message + (in systray and in control). + If frm is set to info: it's a information message. + If frm is set to print_queue: it is incomming from queue. + If frm is set to another value: it's an outgoing message. + If frm is not set: it's an incomming message. + """ + contact = self.contact + + if frm == 'status': + if not gajim.config.get('print_status_in_chats'): + return + kind = 'status' + name = '' + elif frm == 'error': + kind = 'error' + name = '' + elif frm == 'info': + kind = 'info' + name = '' + else: + if self.session and self.session.enable_encryption: + # ESessions + if not encrypted: + msg = _('The following message was NOT encrypted') + ChatControlBase.print_conversation_line(self, msg, 'status', '', + tim) + else: + # GPG encryption + if encrypted and not self.gpg_is_active: + msg = _('The following message was encrypted') + ChatControlBase.print_conversation_line(self, msg, 'status', '', + tim) + # turn on OpenPGP if this was in fact a XEP-0027 encrypted message + if encrypted == 'xep27': + self._toggle_gpg() + elif not encrypted and self.gpg_is_active: + msg = _('The following message was NOT encrypted') + ChatControlBase.print_conversation_line(self, msg, 'status', '', + tim) + if not frm: + kind = 'incoming' + name = contact.get_shown_name() + elif frm == 'print_queue': # incoming message, but do not update time + kind = 'incoming_queue' + name = contact.get_shown_name() + else: + kind = 'outgoing' + name = gajim.nicks[self.account] + if not xhtml and not (encrypted and self.gpg_is_active) and \ + gajim.config.get('rst_formatting_outgoing_messages'): + from common.rst_xhtml_generator import create_xhtml + xhtml = create_xhtml(text) + if xhtml: + xhtml = '%s' % (NS_XHTML, xhtml) + ChatControlBase.print_conversation_line(self, text, kind, name, tim, + subject=subject, old_kind=self.old_msg_kind, xhtml=xhtml, + simple=simple, xep0184_id=xep0184_id) + if text.startswith('/me ') or text.startswith('/me\n'): + self.old_msg_kind = None + else: + self.old_msg_kind = kind + + def get_tab_label(self, chatstate): + unread = '' + if self.resource: + jid = self.contact.get_full_jid() + else: + jid = self.contact.jid + num_unread = len(gajim.events.get_events(self.account, jid, + ['printed_' + self.type_id, self.type_id])) + if num_unread == 1 and not gajim.config.get('show_unread_tab_icon'): + unread = '*' + elif num_unread > 1: + unread = '[' + unicode(num_unread) + ']' + + # Draw tab label using chatstate + theme = gajim.config.get('roster_theme') + color = None + if not chatstate: + chatstate = self.contact.chatstate + if chatstate is not None: + if chatstate == 'composing': + color = gajim.config.get_per('themes', theme, + 'state_composing_color') + elif chatstate == 'inactive': + color = gajim.config.get_per('themes', theme, + 'state_inactive_color') + elif chatstate == 'gone': + color = gajim.config.get_per('themes', theme, + 'state_gone_color') + elif chatstate == 'paused': + color = gajim.config.get_per('themes', theme, + 'state_paused_color') + if color: + # We set the color for when it's the current tab or not + color = gtk.gdk.colormap_get_system().alloc_color(color) + # In inactive tab color to be lighter against the darker inactive + # background + if chatstate in ('inactive', 'gone') and\ + self.parent_win.get_active_control() != self: + color = self.lighten_color(color) + else: # active or not chatstate, get color from gtk + color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE] + + + name = self.contact.get_shown_name() + if self.resource: + name += '/' + self.resource + label_str = gobject.markup_escape_text(name) + if num_unread: # if unread, text in the label becomes bold + label_str = '' + unread + label_str + '' + return (label_str, color) + + def get_tab_image(self, count_unread=True): + if self.resource: + jid = self.contact.get_full_jid() + else: + jid = self.contact.jid + if count_unread: + num_unread = len(gajim.events.get_events(self.account, jid, + ['printed_' + self.type_id, self.type_id])) + else: + num_unread = 0 + # Set tab image (always 16x16); unread messages show the 'event' image + tab_img = None + + if num_unread and gajim.config.get('show_unread_tab_icon'): + img_16 = gajim.interface.roster.get_appropriate_state_images( + self.contact.jid, icon_name = 'event') + tab_img = img_16['event'] + else: + contact = gajim.contacts.get_contact_with_highest_priority( + self.account, self.contact.jid) + if not contact or self.resource: + # For transient contacts + contact = self.contact + img_16 = gajim.interface.roster.get_appropriate_state_images( + self.contact.jid, icon_name=contact.show) + tab_img = img_16[contact.show] + + return tab_img + + def prepare_context_menu(self, hide_buttonbar_items=False): + """ + Set compact view menuitem active state sets active and sensitivity state + for toggle_gpg_menuitem sets sensitivity for history_menuitem (False for + tranasports) and file_transfer_menuitem and hide()/show() for + add_to_roster_menuitem + """ + menu = gui_menu_builder.get_contact_menu(self.contact, self.account, + use_multiple_contacts=False, show_start_chat=False, + show_encryption=True, control=self, + show_buttonbar_items=not hide_buttonbar_items) + return menu + + def send_chatstate(self, state, contact = None): + """ + Send OUR chatstate as STANDLONE chat state message (eg. no body) + to contact only if new chatstate is different from the previous one + if jid is not specified, send to active tab + """ + # JEP 85 does not allow resending the same chatstate + # this function checks for that and just returns so it's safe to call it + # with same state. + + # This functions also checks for violation in state transitions + # and raises RuntimeException with appropriate message + # more on that http://www.jabber.org/jeps/jep-0085.html#statechart + + # do not send nothing if we have chat state notifications disabled + # that means we won't reply to the from other peer + # so we do not broadcast jep85 capabalities + chatstate_setting = gajim.config.get('outgoing_chat_state_notifications') + if chatstate_setting == 'disabled': + return + elif chatstate_setting == 'composing_only' and state != 'active' and\ + state != 'composing': + return + + if contact is None: + contact = self.parent_win.get_active_contact() + if contact is None: + # contact was from pm in MUC, and left the room so contact is None + # so we cannot send chatstate anymore + return + + # Don't send chatstates to offline contacts + if contact.show == 'offline': + return + + if contact.composing_xep is False: # jid cannot do xep85 nor xep22 + return + + # if the new state we wanna send (state) equals + # the current state (contact.our_chatstate) then return + if contact.our_chatstate == state: + return + + if contact.composing_xep is None: + # we don't know anything about jid, so return + # NOTE: + # send 'active', set current state to 'ask' and return is done + # in self.send_message() because we need REAL message (with ) + # for that procedure so return to make sure we send only once + # 'active' until we know peer supports jep85 + return + + if contact.our_chatstate == 'ask': + return + + # in JEP22, when we already sent stop composing + # notification on paused, don't resend it + if contact.composing_xep == 'XEP-0022' and \ + contact.our_chatstate in ('paused', 'active', 'inactive') and \ + state is not 'composing': # not composing == in (active, inactive, gone) + contact.our_chatstate = 'active' + self.reset_kbd_mouse_timeout_vars() + return + + # prevent going paused if we we were not composing (JEP violation) + if state == 'paused' and not contact.our_chatstate == 'composing': + # go active before + MessageControl.send_message(self, None, chatstate = 'active') + contact.our_chatstate = 'active' + self.reset_kbd_mouse_timeout_vars() + + # if we're inactive prevent composing (JEP violation) + elif contact.our_chatstate == 'inactive' and state == 'composing': + # go active before + MessageControl.send_message(self, None, chatstate = 'active') + contact.our_chatstate = 'active' + self.reset_kbd_mouse_timeout_vars() + + MessageControl.send_message(self, None, chatstate = state, + msg_id = contact.msg_id, composing_xep = contact.composing_xep) + contact.our_chatstate = state + if contact.our_chatstate == 'active': + self.reset_kbd_mouse_timeout_vars() + + def shutdown(self): + # Send 'gone' chatstate + self.send_chatstate('gone', self.contact) + self.contact.chatstate = None + self.contact.our_chatstate = None + + # disconnect self from session + if self.session: + self.session.control = None + + # Disconnect timer callbacks + gobject.source_remove(self.possible_paused_timeout_id) + gobject.source_remove(self.possible_inactive_timeout_id) + # Remove bigger avatar window + if self.bigger_avatar_window: + self.bigger_avatar_window.destroy() + # Clean events + gajim.events.remove_events(self.account, self.get_full_jid(), + types = ['printed_' + self.type_id, self.type_id]) + # Remove contact instance if contact has been removed + key = (self.contact.jid, self.account) + roster = gajim.interface.roster + if key in roster.contacts_to_be_removed.keys() and \ + not roster.contact_has_pending_roster_events(self.contact, self.account): + backend = roster.contacts_to_be_removed[key]['backend'] + del roster.contacts_to_be_removed[key] + roster.remove_contact(self.contact.jid, self.account, force=True, + backend=backend) + # remove all register handlers on widgets, created by self.xml + # to prevent circular references among objects + for i in self.handlers.keys(): + if self.handlers[i].handler_is_connected(i): + self.handlers[i].disconnect(i) + del self.handlers[i] + self.conv_textview.del_handlers() + if gajim.config.get('use_speller') and HAS_GTK_SPELL: + spell_obj = gtkspell.get_from_text_view(self.msg_textview) + if spell_obj: + spell_obj.detach() + self.msg_textview.destroy() + + def minimizable(self): + return False + + def safe_shutdown(self): + return False + + def allow_shutdown(self, method, on_yes, on_no, on_minimize): + if time.time() - gajim.last_message_time[self.account]\ + [self.get_full_jid()] < 2: + # 2 seconds + def on_ok(): + on_yes(self) + + def on_cancel(): + on_no(self) + + dialogs.ConfirmationDialog( + # %s is being replaced in the code with JID + _('You just received a new message from "%s"') % self.contact.jid, + _('If you close this tab and you have history disabled, '\ + 'this message will be lost.'), on_response_ok=on_ok, + on_response_cancel=on_cancel) + return + on_yes(self) + + def handle_incoming_chatstate(self): + """ + Handle incoming chatstate that jid SENT TO us + """ + self.draw_banner_text() + # update chatstate in tab for this chat + self.parent_win.redraw_tab(self, self.contact.chatstate) + + def set_control_active(self, state): + ChatControlBase.set_control_active(self, state) + # send chatstate inactive to the one we're leaving + # and active to the one we visit + if state: + self.send_chatstate('active', self.contact) + else: + self.send_chatstate('inactive', self.contact) + # Hide bigger avatar window + if self.bigger_avatar_window: + self.bigger_avatar_window.destroy() + self.bigger_avatar_window = None + # Re-show the small avatar + self.show_avatar() + + def show_avatar(self, resource = None): + if not gajim.config.get('show_avatar_in_chat'): + return + + is_fake = False + if self.TYPE_ID == message_control.TYPE_PM: + is_fake = True + jid_with_resource = self.contact.jid # fake jid + else: + jid_with_resource = self.contact.jid + if resource: + jid_with_resource += '/' + resource + + # we assume contact has no avatar + scaled_pixbuf = None + + pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid_with_resource, + is_fake) + if pixbuf == 'ask': + # we don't have the vcard + if self.TYPE_ID == message_control.TYPE_PM: + if self.gc_contact.jid: + # We know the real jid of this contact + real_jid = self.gc_contact.jid + if self.gc_contact.resource: + real_jid += '/' + self.gc_contact.resource + else: + real_jid = jid_with_resource + gajim.connections[self.account].request_vcard(real_jid, + jid_with_resource) + else: + gajim.connections[self.account].request_vcard(jid_with_resource) + return + if pixbuf is not None: + scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'chat') + + image = self.xml.get_widget('avatar_image') + image.set_from_pixbuf(scaled_pixbuf) + image.show_all() + + def _on_drag_data_received(self, widget, context, x, y, selection, + target_type, timestamp): + if not selection.data: + return + if self.TYPE_ID == message_control.TYPE_PM: + c = self.gc_contact + else: + c = self.contact + if target_type == self.TARGET_TYPE_URI_LIST: + if not c.resource: # If no resource is known, we can't send a file + return + uri = selection.data.strip() + uri_splitted = uri.split() # we may have more than one file dropped + for uri in uri_splitted: + path = helpers.get_file_path_from_dnd_dropped_uri(uri) + if os.path.isfile(path): # is it file? + ft = gajim.interface.instances['file_transfers'] + ft.send_file(self.account, c, path) + return + + # chat2muc + treeview = gajim.interface.roster.tree + model = treeview.get_model() + data = selection.data + path = treeview.get_selection().get_selected_rows()[1][0] + iter_ = model.get_iter(path) + type_ = model[iter_][2] + if type_ != 'contact': # source is not a contact + return + dropped_jid = data.decode('utf-8') + + dropped_transport = gajim.get_transport_name_from_jid(dropped_jid) + c_transport = gajim.get_transport_name_from_jid(c.jid) + if dropped_transport or c_transport: + return # transport contacts cannot be invited + + dialogs.TransformChatToMUC(self.account, [c.jid], [dropped_jid]) + + def _on_message_tv_buffer_changed(self, textbuffer): + self.kbd_activity_in_last_5_secs = True + self.kbd_activity_in_last_30_secs = True + if textbuffer.get_char_count(): + self.send_chatstate('composing', self.contact) + + e2e_is_active = self.session and \ + self.session.enable_encryption + e2e_pref = gajim.config.get_per('accounts', self.account, + 'enable_esessions') and gajim.config.get_per('accounts', + self.account, 'autonegotiate_esessions') and gajim.config.get_per( + 'contacts', self.contact.jid, 'autonegotiate_esessions') + want_e2e = not e2e_is_active and not self.gpg_is_active \ + and e2e_pref + + if want_e2e and not self.no_autonegotiation \ + and gajim.HAVE_PYCRYPTO and self.contact.supports(NS_ESESSION): + self.begin_e2e_negotiation() + elif not self.session or not self.session.status: + self.begin_archiving_negotiation() + else: + self.send_chatstate('active', self.contact) + + def restore_conversation(self): + jid = self.contact.jid + # don't restore lines if it's a transport + if gajim.jid_is_transport(jid): + return + + # How many lines to restore and when to time them out + restore_how_many = gajim.config.get('restore_lines') + if restore_how_many <= 0: + return + timeout = gajim.config.get('restore_timeout') # in minutes + + # number of messages that are in queue and are already logged, we want + # to avoid duplication + pending_how_many = len(gajim.events.get_events(self.account, jid, + ['chat', 'pm'])) + if self.resource: + pending_how_many += len(gajim.events.get_events(self.account, + self.contact.get_full_jid(), ['chat', 'pm'])) + + try: + rows = gajim.logger.get_last_conversation_lines(jid, restore_how_many, + pending_how_many, timeout, self.account) + except exceptions.DatabaseMalformed: + import common.logger + dialogs.ErrorDialog(_('Database Error'), + _('The database file (%s) cannot be read. Try to repair it or remove it (all history will be lost).') % common.logger.LOG_DB_PATH) + rows = [] + local_old_kind = None + for row in rows: # row[0] time, row[1] has kind, row[2] the message + if not row[2]: # message is empty, we don't print it + continue + if row[1] in (constants.KIND_CHAT_MSG_SENT, + constants.KIND_SINGLE_MSG_SENT): + kind = 'outgoing' + name = gajim.nicks[self.account] + elif row[1] in (constants.KIND_SINGLE_MSG_RECV, + constants.KIND_CHAT_MSG_RECV): + kind = 'incoming' + name = self.contact.get_shown_name() + elif row[1] == constants.KIND_ERROR: + kind = 'status' + name = self.contact.get_shown_name() + + tim = time.localtime(float(row[0])) + + if gajim.config.get('restored_messages_small'): + small_attr = ['small'] + else: + small_attr = [] + ChatControlBase.print_conversation_line(self, row[2], kind, name, tim, + small_attr, + small_attr + ['restored_message'], + small_attr + ['restored_message'], + False, old_kind = local_old_kind) + if row[2].startswith('/me ') or row[2].startswith('/me\n'): + local_old_kind = None + else: + local_old_kind = kind + if len(rows): + self.conv_textview.print_empty_line() + + def read_queue(self): + """ + Read queue and print messages containted in it + """ + jid = self.contact.jid + jid_with_resource = jid + if self.resource: + jid_with_resource += '/' + self.resource + events = gajim.events.get_events(self.account, jid_with_resource) + + # list of message ids which should be marked as read + message_ids = [] + for event in events: + if event.type_ != self.type_id: + continue + data = event.parameters + kind = data[2] + if kind == 'error': + kind = 'info' + else: + kind = 'print_queue' + self.print_conversation(data[0], kind, tim = data[3], + encrypted = data[4], subject = data[1], xhtml = data[7]) + if len(data) > 6 and isinstance(data[6], int): + message_ids.append(data[6]) + + if len(data) > 8: + self.set_session(data[8]) + if message_ids: + gajim.logger.set_read_messages(message_ids) + gajim.events.remove_events(self.account, jid_with_resource, + types = [self.type_id]) + + typ = 'chat' # Is it a normal chat or a pm ? + + # reset to status image in gc if it is a pm + # Is it a pm ? + room_jid, nick = gajim.get_room_and_nick_from_fjid(jid) + control = gajim.interface.msg_win_mgr.get_gc_control(room_jid, + self.account) + if control and control.type_id == message_control.TYPE_GC: + control.update_ui() + control.parent_win.show_title() + typ = 'pm' + + self.redraw_after_event_removed(jid) + if (self.contact.show in ('offline', 'error')): + show_offline = gajim.config.get('showoffline') + show_transports = gajim.config.get('show_transports_group') + if (not show_transports and gajim.jid_is_transport(jid)) or \ + (not show_offline and typ == 'chat' and \ + len(gajim.contacts.get_contacts(self.account, jid)) < 2): + gajim.interface.roster.remove_to_be_removed(self.contact.jid, + self.account) + elif typ == 'pm': + control.remove_contact(nick) + + def show_bigger_avatar(self, small_avatar): + """ + Resize the avatar, if needed, so it has at max half the screen size and + shows it + """ + if not small_avatar.window: + # Tab has been closed since we hovered the avatar + return + is_fake = False + if self.type_id == message_control.TYPE_PM: + is_fake = True + avatar_pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache( + self.contact.jid, is_fake) + if avatar_pixbuf in ('ask', None): + return + # Hide the small avatar + # this code hides the small avatar when we show a bigger one in case + # the avatar has a transparency hole in the middle + # so when we show the big one we avoid seeing the small one behind. + # It's why I set it transparent. + image = self.xml.get_widget('avatar_image') + pixbuf = image.get_pixbuf() + pixbuf.fill(0xffffff00L) # RGBA + image.queue_draw() + + screen_w = gtk.gdk.screen_width() + screen_h = gtk.gdk.screen_height() + avatar_w = avatar_pixbuf.get_width() + avatar_h = avatar_pixbuf.get_height() + half_scr_w = screen_w / 2 + half_scr_h = screen_h / 2 + if avatar_w > half_scr_w: + avatar_w = half_scr_w + if avatar_h > half_scr_h: + avatar_h = half_scr_h + window = gtk.Window(gtk.WINDOW_POPUP) + self.bigger_avatar_window = window + pixmap, mask = avatar_pixbuf.render_pixmap_and_mask() + window.set_size_request(avatar_w, avatar_h) + # we should make the cursor visible + # gtk+ doesn't make use of the motion notify on gtkwindow by default + # so this line adds that + window.set_events(gtk.gdk.POINTER_MOTION_MASK) + window.set_app_paintable(True) + window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_TOOLTIP) + + window.realize() + window.window.set_back_pixmap(pixmap, False) # make it transparent + window.window.shape_combine_mask(mask, 0, 0) + + # make the bigger avatar window show up centered + x0, y0 = small_avatar.window.get_origin() + x0 += small_avatar.allocation.x + y0 += small_avatar.allocation.y + center_x= x0 + (small_avatar.allocation.width / 2) + center_y = y0 + (small_avatar.allocation.height / 2) + pos_x, pos_y = center_x - (avatar_w / 2), center_y - (avatar_h / 2) + window.move(pos_x, pos_y) + # make the cursor invisible so we can see the image + invisible_cursor = gtkgui_helpers.get_invisible_cursor() + window.window.set_cursor(invisible_cursor) + + # we should hide the window + window.connect('leave_notify_event', + self._on_window_avatar_leave_notify_event) + window.connect('motion-notify-event', + self._on_window_motion_notify_event) + + window.show_all() + + def _on_window_avatar_leave_notify_event(self, widget, event): + """ + Just left the popup window that holds avatar + """ + self.bigger_avatar_window.destroy() + self.bigger_avatar_window = None + # Re-show the small avatar + self.show_avatar() + + def _on_window_motion_notify_event(self, widget, event): + """ + Just moved the mouse so show the cursor + """ + cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR) + self.bigger_avatar_window.window.set_cursor(cursor) + + def _on_send_file_menuitem_activate(self, widget): + self._on_send_file() + + def _on_add_to_roster_menuitem_activate(self, widget): + dialogs.AddNewContactWindow(self.account, self.contact.jid) + + def _on_contact_information_menuitem_activate(self, widget): + gajim.interface.roster.on_info(widget, self.contact, self.account) + + def _on_toggle_gpg_menuitem_activate(self, widget): + self._toggle_gpg() + + def _on_convert_to_gc_menuitem_activate(self, widget): + """ + User wants to invite some friends to chat + """ + dialogs.TransformChatToMUC(self.account, [self.contact.jid]) + + def _on_toggle_e2e_menuitem_activate(self, widget): + if self.session and self.session.enable_encryption: + # e2e was enabled, disable it + jid = str(self.session.jid) + thread_id = self.session.thread_id + + self.session.terminate_e2e() + + gajim.connections[self.account].delete_session(jid, thread_id) + + # presumably the user had a good reason to shut it off, so + # disable autonegotiation too + self.no_autonegotiation = True + else: + self.begin_e2e_negotiation() + + def begin_negotiation(self): + self.no_autonegotiation = True + + if not self.session: + fjid = self.contact.get_full_jid() + new_sess = gajim.connections[self.account].make_new_session(fjid, type_=self.type_id) + self.set_session(new_sess) + + def begin_e2e_negotiation(self): + self.begin_negotiation() + self.session.negotiate_e2e(False) + + def begin_archiving_negotiation(self): + self.begin_negotiation() + self.session.negotiate_archiving() + + def got_connected(self): + ChatControlBase.got_connected(self) + # Refreshing contact + contact = gajim.contacts.get_contact_with_highest_priority( + self.account, self.contact.jid) + if isinstance(contact, GC_Contact): + contact = contact.as_contact() + if contact: + self.contact = contact + self.draw_banner() + + def update_status_display(self, name, uf_show, status): + """ + Print the contact's status and update the status/GPG image + """ + self.update_ui() + self.parent_win.redraw_tab(self) + + self.print_conversation(_('%(name)s is now %(status)s') % {'name': name, + 'status': uf_show}, 'status') + + if status: + self.print_conversation(' (', 'status', simple=True) + self.print_conversation('%s' % (status), 'status', simple=True) + self.print_conversation(')', 'status', simple=True) diff --git a/src/common/GnuPG.py b/src/common/GnuPG.py index f6d8e7e2b..367660c9c 100644 --- a/src/common/GnuPG.py +++ b/src/common/GnuPG.py @@ -28,213 +28,211 @@ from os import tmpfile from common import helpers if gajim.HAVE_GPG: - import GnuPGInterface + import GnuPGInterface - class GnuPG(GnuPGInterface.GnuPG): - def __init__(self, use_agent = False): - GnuPGInterface.GnuPG.__init__(self) - self.use_agent = use_agent - self._setup_my_options() - self.always_trust = False + class GnuPG(GnuPGInterface.GnuPG): + def __init__(self, use_agent = False): + GnuPGInterface.GnuPG.__init__(self) + self.use_agent = use_agent + self._setup_my_options() + self.always_trust = False - def _setup_my_options(self): - self.options.armor = 1 - self.options.meta_interactive = 0 - self.options.extra_args.append('--no-secmem-warning') - # disable photo viewer when verifying keys - self.options.extra_args.append('--verify-options') - self.options.extra_args.append('no-show-photo') - if self.use_agent: - self.options.extra_args.append('--use-agent') + def _setup_my_options(self): + self.options.armor = 1 + self.options.meta_interactive = 0 + self.options.extra_args.append('--no-secmem-warning') + # disable photo viewer when verifying keys + self.options.extra_args.append('--verify-options') + self.options.extra_args.append('no-show-photo') + if self.use_agent: + self.options.extra_args.append('--use-agent') - def _read_response(self, child_stdout): - # Internal method: reads all the output from GPG, taking notice - # only of lines that begin with the magic [GNUPG:] prefix. - # (See doc/DETAILS in the GPG distribution for info on GPG's - # output when --status-fd is specified.) - # - # Returns a dictionary, mapping GPG's keywords to the arguments - # for that keyword. + def _read_response(self, child_stdout): + # Internal method: reads all the output from GPG, taking notice + # only of lines that begin with the magic [GNUPG:] prefix. + # (See doc/DETAILS in the GPG distribution for info on GPG's + # output when --status-fd is specified.) + # + # Returns a dictionary, mapping GPG's keywords to the arguments + # for that keyword. - resp = {} - while True: - line = helpers.temp_failure_retry(child_stdout.readline) - if line == "": break - line = line.rstrip() - if line[0:9] == '[GNUPG:] ': - # Chop off the prefix - line = line[9:] - L = line.split(None, 1) - keyword = L[0] - if len(L) > 1: - resp[ keyword ] = L[1] - else: - resp[ keyword ] = "" - return resp + resp = {} + while True: + line = helpers.temp_failure_retry(child_stdout.readline) + if line == "": break + line = line.rstrip() + if line[0:9] == '[GNUPG:] ': + # Chop off the prefix + line = line[9:] + L = line.split(None, 1) + keyword = L[0] + if len(L) > 1: + resp[ keyword ] = L[1] + else: + resp[ keyword ] = "" + return resp - def encrypt(self, str_, recipients, always_trust=False): - self.options.recipients = recipients # a list! + def encrypt(self, str_, recipients, always_trust=False): + self.options.recipients = recipients # a list! - opt = ['--encrypt'] - if always_trust or self.always_trust: - opt.append('--always-trust') - proc = self.run(opt, create_fhs=['stdin', 'stdout', 'status', - 'stderr']) - proc.handles['stdin'].write(str_) - try: - proc.handles['stdin'].close() - except IOError: - pass + opt = ['--encrypt'] + if always_trust or self.always_trust: + opt.append('--always-trust') + proc = self.run(opt, create_fhs=['stdin', 'stdout', 'status', + 'stderr']) + proc.handles['stdin'].write(str_) + try: + proc.handles['stdin'].close() + except IOError: + pass - output = proc.handles['stdout'].read() - try: - proc.handles['stdout'].close() - except IOError: - pass + output = proc.handles['stdout'].read() + try: + proc.handles['stdout'].close() + except IOError: + pass - stat = proc.handles['status'] - resp = self._read_response(stat) - try: - proc.handles['status'].close() - except IOError: - pass + stat = proc.handles['status'] + resp = self._read_response(stat) + try: + proc.handles['status'].close() + except IOError: + pass - error = proc.handles['stderr'].read() - proc.handles['stderr'].close() + error = proc.handles['stderr'].read() + proc.handles['stderr'].close() - try: proc.wait() - except IOError: pass - if 'INV_RECP' in resp and resp['INV_RECP'].split()[0] == '10': - # unusable recipient "Key not trusted" - return '', 'NOT_TRUSTED' - if 'BEGIN_ENCRYPTION' in resp and 'END_ENCRYPTION' in resp: - # Encryption succeeded, even if there is output on stderr. Maybe - # verbose is on - error = '' - return self._stripHeaderFooter(output), helpers.decode_string(error) + try: proc.wait() + except IOError: pass + if 'INV_RECP' in resp and resp['INV_RECP'].split()[0] == '10': + # unusable recipient "Key not trusted" + return '', 'NOT_TRUSTED' + if 'BEGIN_ENCRYPTION' in resp and 'END_ENCRYPTION' in resp: + # Encryption succeeded, even if there is output on stderr. Maybe + # verbose is on + error = '' + return self._stripHeaderFooter(output), helpers.decode_string(error) - def decrypt(self, str_, keyID): - proc = self.run(['--decrypt', '-q', '-u %s'%keyID], create_fhs=['stdin', 'stdout']) - enc = self._addHeaderFooter(str_, 'MESSAGE') - proc.handles['stdin'].write(enc) - proc.handles['stdin'].close() + def decrypt(self, str_, keyID): + proc = self.run(['--decrypt', '-q', '-u %s'%keyID], create_fhs=['stdin', 'stdout']) + enc = self._addHeaderFooter(str_, 'MESSAGE') + proc.handles['stdin'].write(enc) + proc.handles['stdin'].close() - output = proc.handles['stdout'].read() - proc.handles['stdout'].close() + output = proc.handles['stdout'].read() + proc.handles['stdout'].close() - try: proc.wait() - except IOError: pass - return output + try: proc.wait() + except IOError: pass + return output - def sign(self, str_, keyID): - proc = self.run(['-b', '-u %s'%keyID], create_fhs=['stdin', 'stdout', 'status', 'stderr']) - proc.handles['stdin'].write(str_) - try: - proc.handles['stdin'].close() - except IOError: - pass + def sign(self, str_, keyID): + proc = self.run(['-b', '-u %s'%keyID], create_fhs=['stdin', 'stdout', 'status', 'stderr']) + proc.handles['stdin'].write(str_) + try: + proc.handles['stdin'].close() + except IOError: + pass - output = proc.handles['stdout'].read() - try: - proc.handles['stdout'].close() - proc.handles['stderr'].close() - except IOError: - pass + output = proc.handles['stdout'].read() + try: + proc.handles['stdout'].close() + proc.handles['stderr'].close() + except IOError: + pass - stat = proc.handles['status'] - resp = self._read_response(stat) - try: - proc.handles['status'].close() - except IOError: - pass + stat = proc.handles['status'] + resp = self._read_response(stat) + try: + proc.handles['status'].close() + except IOError: + pass - try: proc.wait() - except IOError: pass - if 'GOOD_PASSPHRASE' in resp or 'SIG_CREATED' in resp: - return self._stripHeaderFooter(output) - if 'KEYEXPIRED' in resp: - return 'KEYEXPIRED' - return 'BAD_PASSPHRASE' + try: proc.wait() + except IOError: pass + if 'GOOD_PASSPHRASE' in resp or 'SIG_CREATED' in resp: + return self._stripHeaderFooter(output) + if 'KEYEXPIRED' in resp: + return 'KEYEXPIRED' + return 'BAD_PASSPHRASE' - def verify(self, str_, sign): - if str_ is None: - return '' - f = tmpfile() - fd = f.fileno() - f.write(str_) - f.seek(0) + def verify(self, str_, sign): + if str_ is None: + return '' + f = tmpfile() + fd = f.fileno() + f.write(str_) + f.seek(0) - proc = self.run(['--verify', '--enable-special-filenames', '-', '-&%s'%fd], create_fhs=['stdin', 'status', 'stderr']) + proc = self.run(['--verify', '--enable-special-filenames', '-', '-&%s'%fd], create_fhs=['stdin', 'status', 'stderr']) - f.close() - sign = self._addHeaderFooter(sign, 'SIGNATURE') - proc.handles['stdin'].write(sign) - proc.handles['stdin'].close() - proc.handles['stderr'].close() + f.close() + sign = self._addHeaderFooter(sign, 'SIGNATURE') + proc.handles['stdin'].write(sign) + proc.handles['stdin'].close() + proc.handles['stderr'].close() - stat = proc.handles['status'] - resp = self._read_response(stat) - proc.handles['status'].close() + stat = proc.handles['status'] + resp = self._read_response(stat) + proc.handles['status'].close() - try: proc.wait() - except IOError: pass + try: proc.wait() + except IOError: pass - keyid = '' - if 'GOODSIG' in resp: - keyid = resp['GOODSIG'].split()[0] - return keyid + keyid = '' + if 'GOODSIG' in resp: + keyid = resp['GOODSIG'].split()[0] + return keyid - def get_keys(self, secret = False): - if secret: - opt = '--list-secret-keys' - else: - opt = '--list-keys' - proc = self.run(['--with-colons', opt], - create_fhs=['stdout']) - output = proc.handles['stdout'].read() - proc.handles['stdout'].close() + def get_keys(self, secret = False): + if secret: + opt = '--list-secret-keys' + else: + opt = '--list-keys' + proc = self.run(['--with-colons', opt], + create_fhs=['stdout']) + output = proc.handles['stdout'].read() + proc.handles['stdout'].close() - try: proc.wait() - except IOError: pass + try: proc.wait() + except IOError: pass - keys = {} - lines = output.split('\n') - for line in lines: - sline = line.split(':') - if (sline[0] == 'sec' and secret) or \ - (sline[0] == 'pub' and not secret): - # decode escaped chars - name = eval('"' + sline[9].replace('"', '\\"') + '"') - # make it unicode instance - keys[sline[4][8:]] = helpers.decode_string(name) - return keys + keys = {} + lines = output.split('\n') + for line in lines: + sline = line.split(':') + if (sline[0] == 'sec' and secret) or \ + (sline[0] == 'pub' and not secret): + # decode escaped chars + name = eval('"' + sline[9].replace('"', '\\"') + '"') + # make it unicode instance + keys[sline[4][8:]] = helpers.decode_string(name) + return keys - def get_secret_keys(self): - return self.get_keys(True) + def get_secret_keys(self): + return self.get_keys(True) - def _stripHeaderFooter(self, data): - """Remove header and footer from data""" - if not data: return '' - lines = data.split('\n') - while lines[0] != '': - lines.remove(lines[0]) - while lines[0] == '': - lines.remove(lines[0]) - i = 0 - for line in lines: - if line: - if line[0] == '-': break - i = i+1 - line = '\n'.join(lines[0:i]) - return line + def _stripHeaderFooter(self, data): + """Remove header and footer from data""" + if not data: return '' + lines = data.split('\n') + while lines[0] != '': + lines.remove(lines[0]) + while lines[0] == '': + lines.remove(lines[0]) + i = 0 + for line in lines: + if line: + if line[0] == '-': break + i = i+1 + line = '\n'.join(lines[0:i]) + return line - def _addHeaderFooter(self, data, type_): - """Add header and footer from data""" - out = "-----BEGIN PGP %s-----\n" % type_ - out = out + "Version: PGP\n" - out = out + "\n" - out = out + data + "\n" - out = out + "-----END PGP %s-----\n" % type_ - return out - -# vim: set ts=3: + def _addHeaderFooter(self, data, type_): + """Add header and footer from data""" + out = "-----BEGIN PGP %s-----\n" % type_ + out = out + "Version: PGP\n" + out = out + "\n" + out = out + data + "\n" + out = out + "-----END PGP %s-----\n" % type_ + return out diff --git a/src/common/GnuPGInterface.py b/src/common/GnuPGInterface.py index d91f776e3..9226b7219 100644 --- a/src/common/GnuPGInterface.py +++ b/src/common/GnuPGInterface.py @@ -653,5 +653,3 @@ GnuPGInterface = GnuPG if __name__ == '__main__': _run_doctests() - -# vim: se ts=3: diff --git a/src/common/__init__.py b/src/common/__init__.py index de18c8159..e69de29bb 100644 --- a/src/common/__init__.py +++ b/src/common/__init__.py @@ -1,2 +0,0 @@ - -# vim: se ts=3: diff --git a/src/common/account.py b/src/common/account.py index 1c080cad8..fc5702bcd 100644 --- a/src/common/account.py +++ b/src/common/account.py @@ -19,14 +19,14 @@ ## class Account(object): - - def __init__(self, name, contacts, gc_contacts): - self.name = name - self.contacts = contacts - self.gc_contacts = gc_contacts - - def __repr__(self): - return self.name - - def __hash__(self): - return self.name.__hash__() \ No newline at end of file + + def __init__(self, name, contacts, gc_contacts): + self.name = name + self.contacts = contacts + self.gc_contacts = gc_contacts + + def __repr__(self): + return self.name + + def __hash__(self): + return self.name.__hash__() diff --git a/src/common/atom.py b/src/common/atom.py index 18122e9d1..de6b4e85d 100644 --- a/src/common/atom.py +++ b/src/common/atom.py @@ -31,129 +31,127 @@ import xmpp import time class PersonConstruct(xmpp.Node, object): - ''' Not used for now, as we don't need authors/contributors in pubsub.com feeds. - They rarely exist there. ''' - def __init__(self, node): - ''' Create person construct from node. ''' - xmpp.Node.__init__(self, node=node) + ''' Not used for now, as we don't need authors/contributors in pubsub.com feeds. + They rarely exist there. ''' + def __init__(self, node): + ''' Create person construct from node. ''' + xmpp.Node.__init__(self, node=node) - def get_name(self): - return self.getTagData('name') + def get_name(self): + return self.getTagData('name') - name = property(get_name, None, None, - '''Conveys a human-readable name for the person. Should not be None, - although some badly generated atom feeds don't put anything here - (this is non-standard behavior, still pubsub.com sometimes does that.)''') + name = property(get_name, None, None, + '''Conveys a human-readable name for the person. Should not be None, + although some badly generated atom feeds don't put anything here + (this is non-standard behavior, still pubsub.com sometimes does that.)''') - def get_uri(self): - return self.getTagData('uri') + def get_uri(self): + return self.getTagData('uri') - uri = property(get_uri, None, None, - '''Conveys an IRI associated with the person. Might be None when not set.''') + uri = property(get_uri, None, None, + '''Conveys an IRI associated with the person. Might be None when not set.''') - def get_email(self): - return self.getTagData('email') + def get_email(self): + return self.getTagData('email') - email = property(get_email, None, None, - '''Conveys an e-mail address associated with the person. Might be None when - not set.''') + email = property(get_email, None, None, + '''Conveys an e-mail address associated with the person. Might be None when + not set.''') class Entry(xmpp.Node, object): - def __init__(self, node=None): - ''' Create new atom entry object. ''' - xmpp.Node.__init__(self, 'entry', node=node) + def __init__(self, node=None): + ''' Create new atom entry object. ''' + xmpp.Node.__init__(self, 'entry', node=node) - def __repr__(self): - return '' % self.getAttr('id') + def __repr__(self): + return '' % self.getAttr('id') class OldEntry(xmpp.Node, object): - ''' Parser for feeds from pubsub.com. They use old Atom 0.3 format with - their extensions. ''' - def __init__(self, node=None): - ''' Create new Atom 0.3 entry object. ''' - xmpp.Node.__init__(self, 'entry', node=node) + ''' Parser for feeds from pubsub.com. They use old Atom 0.3 format with + their extensions. ''' + def __init__(self, node=None): + ''' Create new Atom 0.3 entry object. ''' + xmpp.Node.__init__(self, 'entry', node=node) - def __repr__(self): - return '' % self.getAttr('id') + def __repr__(self): + return '' % self.getAttr('id') - def get_feed_title(self): - ''' Returns title of feed, where the entry was created. The result is the feed name - concatenated with source-feed title. ''' - if self.parent is not None: - main_feed = self.parent.getTagData('title') - else: - main_feed = None + def get_feed_title(self): + ''' Returns title of feed, where the entry was created. The result is the feed name + concatenated with source-feed title. ''' + if self.parent is not None: + main_feed = self.parent.getTagData('title') + else: + main_feed = None - if self.getTag('feed') is not None: - source_feed = self.getTag('feed').getTagData('title') - else: - source_feed = None + if self.getTag('feed') is not None: + source_feed = self.getTag('feed').getTagData('title') + else: + source_feed = None - if main_feed is not None and source_feed is not None: - return u'%s: %s' % (main_feed, source_feed) - elif main_feed is not None: - return main_feed - elif source_feed is not None: - return source_feed - else: - return u'' + if main_feed is not None and source_feed is not None: + return u'%s: %s' % (main_feed, source_feed) + elif main_feed is not None: + return main_feed + elif source_feed is not None: + return source_feed + else: + return u'' - feed_title = property(get_feed_title, None, None, - ''' Title of feed. It is built from entry''s original feed title and title of feed - which delivered this entry. ''') + feed_title = property(get_feed_title, None, None, + ''' Title of feed. It is built from entry''s original feed title and title of feed + which delivered this entry. ''') - def get_feed_link(self): - ''' Get source link ''' - try: - return self.getTag('feed').getTags('link',{'rel':'alternate'})[1].getData() - except Exception: - return None + def get_feed_link(self): + ''' Get source link ''' + try: + return self.getTag('feed').getTags('link', {'rel':'alternate'})[1].getData() + except Exception: + return None - feed_link = property(get_feed_link, None, None, - ''' Link to main webpage of the feed. ''') + feed_link = property(get_feed_link, None, None, + ''' Link to main webpage of the feed. ''') - def get_title(self): - ''' Get an entry's title. ''' - return self.getTagData('title') + def get_title(self): + ''' Get an entry's title. ''' + return self.getTagData('title') - title = property(get_title, None, None, - ''' Entry's title. ''') + title = property(get_title, None, None, + ''' Entry's title. ''') - def get_uri(self): - ''' Get the uri the entry points to (entry's first link element with rel='alternate' - or without rel attribute). ''' - for element in self.getTags('link'): - if 'rel' in element.attrs and element.attrs['rel']!='alternate': continue - try: - return element.attrs['href'] - except AttributeError: - pass - return None + def get_uri(self): + ''' Get the uri the entry points to (entry's first link element with rel='alternate' + or without rel attribute). ''' + for element in self.getTags('link'): + if 'rel' in element.attrs and element.attrs['rel']!='alternate': continue + try: + return element.attrs['href'] + except AttributeError: + pass + return None - uri = property(get_uri, None, None, - ''' URI that is pointed by the entry. ''') + uri = property(get_uri, None, None, + ''' URI that is pointed by the entry. ''') - def get_updated(self): - ''' Get the time the entry was updated last time. This should be standarized, - but pubsub.com sends it in human-readable format. We won't try to parse it. - (Atom 0.3 uses the word «modified» for that). + def get_updated(self): + ''' Get the time the entry was updated last time. This should be standarized, + but pubsub.com sends it in human-readable format. We won't try to parse it. + (Atom 0.3 uses the word «modified» for that). - If there's no time given in the entry, we try with - and elements. ''' - for name in ('updated', 'modified', 'published', 'issued'): - date = self.getTagData(name) - if date is not None: break + If there's no time given in the entry, we try with + and elements. ''' + for name in ('updated', 'modified', 'published', 'issued'): + date = self.getTagData(name) + if date is not None: break - if date is None: - # it is not in the standard format - return time.asctime() + if date is None: + # it is not in the standard format + return time.asctime() - return date + return date - updated = property(get_updated, None, None, - ''' Last significant modification time. ''') + updated = property(get_updated, None, None, + ''' Last significant modification time. ''') - feed_tagline = u'' - -# vim: se ts=3: + feed_tagline = u'' diff --git a/src/common/caps.py b/src/common/caps.py index f34dfcda4..79a5d3023 100644 --- a/src/common/caps.py +++ b/src/common/caps.py @@ -24,12 +24,12 @@ ## ''' -Module containing all XEP-115 (Entity Capabilities) related classes +Module containing all XEP-115 (Entity Capabilities) related classes Basic Idea: CapsCache caches features to hash relationships. The cache is queried -through ClientCaps objects which are hold by contact instances. -''' +through ClientCaps objects which are hold by contact instances. +''' import gajim import helpers @@ -41,7 +41,7 @@ from common.xmpp import NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO from common.xmpp import NS_JINGLE_RTP_VIDEO # Features where we cannot safely assume that the other side supports them FEATURE_BLACKLIST = [NS_CHATSTATES, NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION, - NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO] + NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO] # Query entry status codes NEW = 0 @@ -54,383 +54,381 @@ CACHED = 2 # got the answer capscache = None def initialize(logger): - ''' Initializes this module ''' - global capscache - capscache = CapsCache(logger) + ''' Initializes this module ''' + global capscache + capscache = CapsCache(logger) def client_supports(client_caps, requested_feature): - lookup_item = client_caps.get_cache_lookup_strategy() - cache_item = lookup_item(capscache) + lookup_item = client_caps.get_cache_lookup_strategy() + cache_item = lookup_item(capscache) - supported_features = cache_item.features - if requested_feature in supported_features: - return True - elif supported_features == [] and cache_item.status in (NEW, QUERIED): - # assume feature is supported, if we don't know yet, what the client - # is capable of - return requested_feature not in FEATURE_BLACKLIST - else: - return False + supported_features = cache_item.features + if requested_feature in supported_features: + return True + elif supported_features == [] and cache_item.status in (NEW, QUERIED): + # assume feature is supported, if we don't know yet, what the client + # is capable of + return requested_feature not in FEATURE_BLACKLIST + else: + return False def compute_caps_hash(identities, features, dataforms=[], hash_method='sha-1'): - '''Compute caps hash according to XEP-0115, V1.5 + '''Compute caps hash according to XEP-0115, V1.5 - dataforms are xmpp.DataForms objects as common.dataforms don't allow several - values without a field type list-multi''' - - def sort_identities_func(i1, i2): - cat1 = i1['category'] - cat2 = i2['category'] - if cat1 < cat2: - return -1 - if cat1 > cat2: - return 1 - type1 = i1.get('type', '') - type2 = i2.get('type', '') - if type1 < type2: - return -1 - if type1 > type2: - return 1 - lang1 = i1.get('xml:lang', '') - lang2 = i2.get('xml:lang', '') - if lang1 < lang2: - return -1 - if lang1 > lang2: - return 1 - return 0 - - def sort_dataforms_func(d1, d2): - f1 = d1.getField('FORM_TYPE') - f2 = d2.getField('FORM_TYPE') - if f1 and f2 and (f1.getValue() < f2.getValue()): - return -1 - return 1 - - S = '' - identities.sort(cmp=sort_identities_func) - for i in identities: - c = i['category'] - type_ = i.get('type', '') - lang = i.get('xml:lang', '') - name = i.get('name', '') - S += '%s/%s/%s/%s<' % (c, type_, lang, name) - features.sort() - for f in features: - S += '%s<' % f - dataforms.sort(cmp=sort_dataforms_func) - for dataform in dataforms: - # fields indexed by var - fields = {} - for f in dataform.getChildren(): - fields[f.getVar()] = f - form_type = fields.get('FORM_TYPE') - if form_type: - S += form_type.getValue() + '<' - del fields['FORM_TYPE'] - for var in sorted(fields.keys()): - S += '%s<' % var - values = sorted(fields[var].getValues()) - for value in values: - S += '%s<' % value + dataforms are xmpp.DataForms objects as common.dataforms don't allow several + values without a field type list-multi''' + + def sort_identities_func(i1, i2): + cat1 = i1['category'] + cat2 = i2['category'] + if cat1 < cat2: + return -1 + if cat1 > cat2: + return 1 + type1 = i1.get('type', '') + type2 = i2.get('type', '') + if type1 < type2: + return -1 + if type1 > type2: + return 1 + lang1 = i1.get('xml:lang', '') + lang2 = i2.get('xml:lang', '') + if lang1 < lang2: + return -1 + if lang1 > lang2: + return 1 + return 0 + + def sort_dataforms_func(d1, d2): + f1 = d1.getField('FORM_TYPE') + f2 = d2.getField('FORM_TYPE') + if f1 and f2 and (f1.getValue() < f2.getValue()): + return -1 + return 1 + + S = '' + identities.sort(cmp=sort_identities_func) + for i in identities: + c = i['category'] + type_ = i.get('type', '') + lang = i.get('xml:lang', '') + name = i.get('name', '') + S += '%s/%s/%s/%s<' % (c, type_, lang, name) + features.sort() + for f in features: + S += '%s<' % f + dataforms.sort(cmp=sort_dataforms_func) + for dataform in dataforms: + # fields indexed by var + fields = {} + for f in dataform.getChildren(): + fields[f.getVar()] = f + form_type = fields.get('FORM_TYPE') + if form_type: + S += form_type.getValue() + '<' + del fields['FORM_TYPE'] + for var in sorted(fields.keys()): + S += '%s<' % var + values = sorted(fields[var].getValues()) + for value in values: + S += '%s<' % value + + if hash_method == 'sha-1': + hash_ = hashlib.sha1(S) + elif hash_method == 'md5': + hash_ = hashlib.md5(S) + else: + return '' + return base64.b64encode(hash_.digest()) - if hash_method == 'sha-1': - hash_ = hashlib.sha1(S) - elif hash_method == 'md5': - hash_ = hashlib.md5(S) - else: - return '' - return base64.b64encode(hash_.digest()) - ################################################################################ ### Internal classes of this module ################################################################################ class AbstractClientCaps(object): - ''' - Base class representing a client and its capabilities as advertised by - a caps tag in a presence. - ''' - def __init__(self, caps_hash, node): - self._hash = caps_hash - self._node = node - - def get_discover_strategy(self): - return self._discover - - def _discover(self, connection, jid): - ''' To be implemented by subclassess ''' - raise NotImplementedError() - - def get_cache_lookup_strategy(self): - return self._lookup_in_cache - - def _lookup_in_cache(self, caps_cache): - ''' To be implemented by subclassess ''' - raise NotImplementedError() - - def get_hash_validation_strategy(self): - return self._is_hash_valid - - def _is_hash_valid(self, identities, features, dataforms): - ''' To be implemented by subclassess ''' - raise NotImplementedError() + ''' + Base class representing a client and its capabilities as advertised by + a caps tag in a presence. + ''' + def __init__(self, caps_hash, node): + self._hash = caps_hash + self._node = node + + def get_discover_strategy(self): + return self._discover + + def _discover(self, connection, jid): + ''' To be implemented by subclassess ''' + raise NotImplementedError() + + def get_cache_lookup_strategy(self): + return self._lookup_in_cache + + def _lookup_in_cache(self, caps_cache): + ''' To be implemented by subclassess ''' + raise NotImplementedError() + + def get_hash_validation_strategy(self): + return self._is_hash_valid + + def _is_hash_valid(self, identities, features, dataforms): + ''' To be implemented by subclassess ''' + raise NotImplementedError() class ClientCaps(AbstractClientCaps): - ''' The current XEP-115 implementation ''' - - def __init__(self, caps_hash, node, hash_method): - AbstractClientCaps.__init__(self, caps_hash, node) - assert hash_method != 'old' - self._hash_method = hash_method - - def _lookup_in_cache(self, caps_cache): - return caps_cache[(self._hash_method, self._hash)] - - def _discover(self, connection, jid): - connection.discoverInfo(jid, '%s#%s' % (self._node, self._hash)) - - def _is_hash_valid(self, identities, features, dataforms): - computed_hash = compute_caps_hash(identities, features, - dataforms=dataforms, hash_method=self._hash_method) - return computed_hash == self._hash + ''' The current XEP-115 implementation ''' + + def __init__(self, caps_hash, node, hash_method): + AbstractClientCaps.__init__(self, caps_hash, node) + assert hash_method != 'old' + self._hash_method = hash_method + + def _lookup_in_cache(self, caps_cache): + return caps_cache[(self._hash_method, self._hash)] + + def _discover(self, connection, jid): + connection.discoverInfo(jid, '%s#%s' % (self._node, self._hash)) + + def _is_hash_valid(self, identities, features, dataforms): + computed_hash = compute_caps_hash(identities, features, + dataforms=dataforms, hash_method=self._hash_method) + return computed_hash == self._hash + - class OldClientCaps(AbstractClientCaps): - ''' Old XEP-115 implemtation. Kept around for background competability. ''' - - def __init__(self, caps_hash, node): - AbstractClientCaps.__init__(self, caps_hash, node) + ''' Old XEP-115 implemtation. Kept around for background competability. ''' + + def __init__(self, caps_hash, node): + AbstractClientCaps.__init__(self, caps_hash, node) + + def _lookup_in_cache(self, caps_cache): + return caps_cache[('old', self._node + '#' + self._hash)] + + def _discover(self, connection, jid): + connection.discoverInfo(jid) + + def _is_hash_valid(self, identities, features, dataforms): + return True - def _lookup_in_cache(self, caps_cache): - return caps_cache[('old', self._node + '#' + self._hash)] - - def _discover(self, connection, jid): - connection.discoverInfo(jid) - - def _is_hash_valid(self, identities, features, dataforms): - return True - class NullClientCaps(AbstractClientCaps): - ''' - This is a NULL-Object to streamline caps handling if a client has not - advertised any caps or has advertised them in an improper way. - - Assumes (almost) everything is supported. - ''' - - def __init__(self): - AbstractClientCaps.__init__(self, None, None) - - def _lookup_in_cache(self, caps_cache): - # lookup something which does not exist to get a new CacheItem created - cache_item = caps_cache[('dummy', '')] - assert cache_item.status != CACHED - return cache_item - - def _discover(self, connection, jid): - pass + ''' + This is a NULL-Object to streamline caps handling if a client has not + advertised any caps or has advertised them in an improper way. - def _is_hash_valid(self, identities, features, dataforms): - return False + Assumes (almost) everything is supported. + ''' + + def __init__(self): + AbstractClientCaps.__init__(self, None, None) + + def _lookup_in_cache(self, caps_cache): + # lookup something which does not exist to get a new CacheItem created + cache_item = caps_cache[('dummy', '')] + assert cache_item.status != CACHED + return cache_item + + def _discover(self, connection, jid): + pass + + def _is_hash_valid(self, identities, features, dataforms): + return False class CapsCache(object): - ''' - This object keeps the mapping between caps data and real disco - features they represent, and provides simple way to query that info. - ''' - def __init__(self, logger=None): - # our containers: - # __cache is a dictionary mapping: pair of hash method and hash maps - # to CapsCacheItem object - # __CacheItem is a class that stores data about particular - # client (hash method/hash pair) - self.__cache = {} + ''' + This object keeps the mapping between caps data and real disco + features they represent, and provides simple way to query that info. + ''' + def __init__(self, logger=None): + # our containers: + # __cache is a dictionary mapping: pair of hash method and hash maps + # to CapsCacheItem object + # __CacheItem is a class that stores data about particular + # client (hash method/hash pair) + self.__cache = {} - class CacheItem(object): - # __names is a string cache; every string long enough is given - # another object, and we will have plenty of identical long - # strings. therefore we can cache them - __names = {} - - def __init__(self, hash_method, hash_, logger): - # cached into db - self.hash_method = hash_method - self.hash = hash_ - self._features = [] - self._identities = [] - self._logger = logger + class CacheItem(object): + # __names is a string cache; every string long enough is given + # another object, and we will have plenty of identical long + # strings. therefore we can cache them + __names = {} - self.status = NEW - self._recently_seen = False + def __init__(self, hash_method, hash_, logger): + # cached into db + self.hash_method = hash_method + self.hash = hash_ + self._features = [] + self._identities = [] + self._logger = logger - def _get_features(self): - return self._features + self.status = NEW + self._recently_seen = False - def _set_features(self, value): - self._features = [] - for feature in value: - self._features.append(self.__names.setdefault(feature, feature)) - - features = property(_get_features, _set_features) + def _get_features(self): + return self._features - def _get_identities(self): - list_ = [] - for i in self._identities: - # transforms it back in a dict - d = dict() - d['category'] = i[0] - if i[1]: - d['type'] = i[1] - if i[2]: - d['xml:lang'] = i[2] - if i[3]: - d['name'] = i[3] - list_.append(d) - return list_ - - def _set_identities(self, value): - self._identities = [] - for identity in value: - # dict are not hashable, so transform it into a tuple - t = (identity['category'], identity.get('type'), - identity.get('xml:lang'), identity.get('name')) - self._identities.append(self.__names.setdefault(t, t)) - - identities = property(_get_identities, _set_identities) + def _set_features(self, value): + self._features = [] + for feature in value: + self._features.append(self.__names.setdefault(feature, feature)) - def set_and_store(self, identities, features): - self.identities = identities - self.features = features - self._logger.add_caps_entry(self.hash_method, self.hash, - identities, features) - self.status = CACHED - - def update_last_seen(self): - if not self._recently_seen: - self._recently_seen = True - self._logger.update_caps_time(self.hash_method, self.hash) + features = property(_get_features, _set_features) - self.__CacheItem = CacheItem - self.logger = logger + def _get_identities(self): + list_ = [] + for i in self._identities: + # transforms it back in a dict + d = dict() + d['category'] = i[0] + if i[1]: + d['type'] = i[1] + if i[2]: + d['xml:lang'] = i[2] + if i[3]: + d['name'] = i[3] + list_.append(d) + return list_ - def initialize_from_db(self): - self._remove_outdated_caps() - for hash_method, hash_, identities, features in \ - self.logger.iter_caps_data(): - x = self[(hash_method, hash_)] - x.identities = identities - x.features = features - x.status = CACHED - - def _remove_outdated_caps(self): - '''Removes outdated values from the db''' - self.logger.clean_caps_table() + def _set_identities(self, value): + self._identities = [] + for identity in value: + # dict are not hashable, so transform it into a tuple + t = (identity['category'], identity.get('type'), + identity.get('xml:lang'), identity.get('name')) + self._identities.append(self.__names.setdefault(t, t)) - def __getitem__(self, caps): - if caps in self.__cache: - return self.__cache[caps] + identities = property(_get_identities, _set_identities) - hash_method, hash_ = caps + def set_and_store(self, identities, features): + self.identities = identities + self.features = features + self._logger.add_caps_entry(self.hash_method, self.hash, + identities, features) + self.status = CACHED - x = self.__CacheItem(hash_method, hash_, self.logger) - self.__cache[(hash_method, hash_)] = x - return x + def update_last_seen(self): + if not self._recently_seen: + self._recently_seen = True + self._logger.update_caps_time(self.hash_method, self.hash) - def query_client_of_jid_if_unknown(self, connection, jid, client_caps): - ''' - Start a disco query to determine caps (node, ver, exts). - Won't query if the data is already in cache. - ''' - lookup_cache_item = client_caps.get_cache_lookup_strategy() - q = lookup_cache_item(self) - - if q.status == NEW: - # do query for bare node+hash pair - # this will create proper object - q.status = QUERIED - discover = client_caps.get_discover_strategy() - discover(connection, jid) - else: - q.update_last_seen() + self.__CacheItem = CacheItem + self.logger = logger + + def initialize_from_db(self): + self._remove_outdated_caps() + for hash_method, hash_, identities, features in \ + self.logger.iter_caps_data(): + x = self[(hash_method, hash_)] + x.identities = identities + x.features = features + x.status = CACHED + + def _remove_outdated_caps(self): + '''Removes outdated values from the db''' + self.logger.clean_caps_table() + + def __getitem__(self, caps): + if caps in self.__cache: + return self.__cache[caps] + + hash_method, hash_ = caps + + x = self.__CacheItem(hash_method, hash_, self.logger) + self.__cache[(hash_method, hash_)] = x + return x + + def query_client_of_jid_if_unknown(self, connection, jid, client_caps): + ''' + Start a disco query to determine caps (node, ver, exts). + Won't query if the data is already in cache. + ''' + lookup_cache_item = client_caps.get_cache_lookup_strategy() + q = lookup_cache_item(self) + + if q.status == NEW: + # do query for bare node+hash pair + # this will create proper object + q.status = QUERIED + discover = client_caps.get_discover_strategy() + discover(connection, jid) + else: + q.update_last_seen() ################################################################################ ### Caps network coding ################################################################################ class ConnectionCaps(object): - ''' - This class highly depends on that it is a part of Connection class. - ''' - def _capsPresenceCB(self, con, presence): - ''' - Handle incoming presence stanzas... This is a callback for xmpp - registered in connection_handlers.py - ''' - # we will put these into proper Contact object and ask - # for disco... so that disco will learn how to interpret - # these caps - pm_ctrl = None - try: - jid = helpers.get_full_jid_from_iq(presence) - except: - # Bad jid - return - contact = gajim.contacts.get_contact_from_full_jid(self.name, jid) - if contact is None: - room_jid, nick = gajim.get_room_and_nick_from_fjid(jid) - contact = gajim.contacts.get_gc_contact( - self.name, room_jid, nick) - pm_ctrl = gajim.interface.msg_win_mgr.get_control(jid, self.name) - if contact is None: - # TODO: a way to put contact not-in-roster - # into Contacts - return + ''' + This class highly depends on that it is a part of Connection class. + ''' + def _capsPresenceCB(self, con, presence): + ''' + Handle incoming presence stanzas... This is a callback for xmpp + registered in connection_handlers.py + ''' + # we will put these into proper Contact object and ask + # for disco... so that disco will learn how to interpret + # these caps + pm_ctrl = None + try: + jid = helpers.get_full_jid_from_iq(presence) + except: + # Bad jid + return + contact = gajim.contacts.get_contact_from_full_jid(self.name, jid) + if contact is None: + room_jid, nick = gajim.get_room_and_nick_from_fjid(jid) + contact = gajim.contacts.get_gc_contact( + self.name, room_jid, nick) + pm_ctrl = gajim.interface.msg_win_mgr.get_control(jid, self.name) + if contact is None: + # TODO: a way to put contact not-in-roster + # into Contacts + return - caps_tag = presence.getTag('c') - if not caps_tag: - # presence did not contain caps_tag - client_caps = NullClientCaps() - else: - hash_method, node, caps_hash = caps_tag['hash'], caps_tag['node'], caps_tag['ver'] + caps_tag = presence.getTag('c') + if not caps_tag: + # presence did not contain caps_tag + client_caps = NullClientCaps() + else: + hash_method, node, caps_hash = caps_tag['hash'], caps_tag['node'], caps_tag['ver'] - if not node or not caps_hash: - # improper caps in stanza, ignore client capabilities. - client_caps = NullClientCaps() - elif not hash_method: - client_caps = OldClientCaps(caps_hash, node) - else: - client_caps = ClientCaps(caps_hash, node, hash_method) + if not node or not caps_hash: + # improper caps in stanza, ignore client capabilities. + client_caps = NullClientCaps() + elif not hash_method: + client_caps = OldClientCaps(caps_hash, node) + else: + client_caps = ClientCaps(caps_hash, node, hash_method) - capscache.query_client_of_jid_if_unknown(self, jid, client_caps) - contact.client_caps = client_caps + capscache.query_client_of_jid_if_unknown(self, jid, client_caps) + contact.client_caps = client_caps - if pm_ctrl: - pm_ctrl.update_contact() + if pm_ctrl: + pm_ctrl.update_contact() - def _capsDiscoCB(self, jid, node, identities, features, dataforms): - contact = gajim.contacts.get_contact_from_full_jid(self.name, jid) - if not contact: - room_jid, nick = gajim.get_room_and_nick_from_fjid(jid) - contact = gajim.contacts.get_gc_contact(self.name, room_jid, nick) - if contact is None: - return + def _capsDiscoCB(self, jid, node, identities, features, dataforms): + contact = gajim.contacts.get_contact_from_full_jid(self.name, jid) + if not contact: + room_jid, nick = gajim.get_room_and_nick_from_fjid(jid) + contact = gajim.contacts.get_gc_contact(self.name, room_jid, nick) + if contact is None: + return - lookup = contact.client_caps.get_cache_lookup_strategy() - cache_item = lookup(capscache) - - if cache_item.status == CACHED: - return - else: - validate = contact.client_caps.get_hash_validation_strategy() - hash_is_valid = validate(identities, features, dataforms) - - if hash_is_valid: - cache_item.set_and_store(identities, features) - else: - contact.client_caps = NullClientCaps() + lookup = contact.client_caps.get_cache_lookup_strategy() + cache_item = lookup(capscache) -# vim: se ts=3: + if cache_item.status == CACHED: + return + else: + validate = contact.client_caps.get_hash_validation_strategy() + hash_is_valid = validate(identities, features, dataforms) + + if hash_is_valid: + cache_item.set_and_store(identities, features) + else: + contact.client_caps = NullClientCaps() diff --git a/src/common/check_paths.py b/src/common/check_paths.py index c4864fc78..c229d1a90 100644 --- a/src/common/check_paths.py +++ b/src/common/check_paths.py @@ -33,142 +33,140 @@ import logger # DO NOT MOVE ABOVE OF import gajim try: - import sqlite3 as sqlite # python 2.5 + import sqlite3 as sqlite # python 2.5 except ImportError: - try: - from pysqlite2 import dbapi2 as sqlite - except ImportError: - raise exceptions.PysqliteNotAvailable + try: + from pysqlite2 import dbapi2 as sqlite + except ImportError: + raise exceptions.PysqliteNotAvailable def create_log_db(): - print _('creating logs database') - con = sqlite.connect(logger.LOG_DB_PATH) - os.chmod(logger.LOG_DB_PATH, 0600) # rw only for us - cur = con.cursor() - # create the tables - # kind can be - # status, gcstatus, gc_msg, (we only recv for those 3), - # single_msg_recv, chat_msg_recv, chat_msg_sent, single_msg_sent - # to meet all our needs - # logs.jid_id --> jids.jid_id but Sqlite doesn't do FK etc so it's done in python code - # jids.jid text column will be JID if TC-related, room_jid if GC-related, - # ROOM_JID/nick if pm-related. - # also check optparser.py, which updates databases on gajim updates - cur.executescript( - ''' - CREATE TABLE jids( - jid_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, - jid TEXT UNIQUE, - type INTEGER - ); + print _('creating logs database') + con = sqlite.connect(logger.LOG_DB_PATH) + os.chmod(logger.LOG_DB_PATH, 0600) # rw only for us + cur = con.cursor() + # create the tables + # kind can be + # status, gcstatus, gc_msg, (we only recv for those 3), + # single_msg_recv, chat_msg_recv, chat_msg_sent, single_msg_sent + # to meet all our needs + # logs.jid_id --> jids.jid_id but Sqlite doesn't do FK etc so it's done in python code + # jids.jid text column will be JID if TC-related, room_jid if GC-related, + # ROOM_JID/nick if pm-related. + # also check optparser.py, which updates databases on gajim updates + cur.executescript( + ''' + CREATE TABLE jids( + jid_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, + jid TEXT UNIQUE, + type INTEGER + ); - CREATE TABLE unread_messages( - message_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, - jid_id INTEGER, - shown BOOLEAN default 0 - ); + CREATE TABLE unread_messages( + message_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, + jid_id INTEGER, + shown BOOLEAN default 0 + ); - CREATE INDEX idx_unread_messages_jid_id ON unread_messages (jid_id); + CREATE INDEX idx_unread_messages_jid_id ON unread_messages (jid_id); - CREATE TABLE transports_cache ( - transport TEXT UNIQUE, - type INTEGER - ); + CREATE TABLE transports_cache ( + transport TEXT UNIQUE, + type INTEGER + ); - CREATE TABLE logs( - log_line_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, - jid_id INTEGER, - contact_name TEXT, - time INTEGER, - kind INTEGER, - show INTEGER, - message TEXT, - subject TEXT - ); + CREATE TABLE logs( + log_line_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, + jid_id INTEGER, + contact_name TEXT, + time INTEGER, + kind INTEGER, + show INTEGER, + message TEXT, + subject TEXT + ); - CREATE INDEX idx_logs_jid_id_kind ON logs (jid_id, kind); + CREATE INDEX idx_logs_jid_id_kind ON logs (jid_id, kind); - CREATE TABLE caps_cache ( - hash_method TEXT, - hash TEXT, - data BLOB, - last_seen INTEGER); + CREATE TABLE caps_cache ( + hash_method TEXT, + hash TEXT, + data BLOB, + last_seen INTEGER); - CREATE TABLE rooms_last_message_time( - jid_id INTEGER PRIMARY KEY UNIQUE, - time INTEGER - ); + CREATE TABLE rooms_last_message_time( + jid_id INTEGER PRIMARY KEY UNIQUE, + time INTEGER + ); - CREATE TABLE IF NOT EXISTS roster_entry( - account_jid_id INTEGER, - jid_id INTEGER, - name TEXT, - subscription INTEGER, - ask BOOLEAN, - PRIMARY KEY (account_jid_id, jid_id) - ); + CREATE TABLE IF NOT EXISTS roster_entry( + account_jid_id INTEGER, + jid_id INTEGER, + name TEXT, + subscription INTEGER, + ask BOOLEAN, + PRIMARY KEY (account_jid_id, jid_id) + ); - CREATE TABLE IF NOT EXISTS roster_group( - account_jid_id INTEGER, - jid_id INTEGER, - group_name TEXT, - PRIMARY KEY (account_jid_id, jid_id, group_name) - ); - ''' - ) + CREATE TABLE IF NOT EXISTS roster_group( + account_jid_id INTEGER, + jid_id INTEGER, + group_name TEXT, + PRIMARY KEY (account_jid_id, jid_id, group_name) + ); + ''' + ) - con.commit() - con.close() + con.commit() + con.close() def check_and_possibly_create_paths(): - LOG_DB_PATH = logger.LOG_DB_PATH - VCARD_PATH = gajim.VCARD_PATH - AVATAR_PATH = gajim.AVATAR_PATH - dot_gajim = os.path.dirname(VCARD_PATH) - if os.path.isfile(dot_gajim): - print _('%s is a file but it should be a directory') % dot_gajim - print _('Gajim will now exit') - sys.exit() - elif os.path.isdir(dot_gajim): - s = os.stat(dot_gajim) - if s.st_mode & stat.S_IROTH: # others have read permission! - os.chmod(dot_gajim, 0700) # rwx------ + LOG_DB_PATH = logger.LOG_DB_PATH + VCARD_PATH = gajim.VCARD_PATH + AVATAR_PATH = gajim.AVATAR_PATH + dot_gajim = os.path.dirname(VCARD_PATH) + if os.path.isfile(dot_gajim): + print _('%s is a file but it should be a directory') % dot_gajim + print _('Gajim will now exit') + sys.exit() + elif os.path.isdir(dot_gajim): + s = os.stat(dot_gajim) + if s.st_mode & stat.S_IROTH: # others have read permission! + os.chmod(dot_gajim, 0700) # rwx------ - if not os.path.exists(VCARD_PATH): - create_path(VCARD_PATH) - elif os.path.isfile(VCARD_PATH): - print _('%s is a file but it should be a directory') % VCARD_PATH - print _('Gajim will now exit') - sys.exit() + if not os.path.exists(VCARD_PATH): + create_path(VCARD_PATH) + elif os.path.isfile(VCARD_PATH): + print _('%s is a file but it should be a directory') % VCARD_PATH + print _('Gajim will now exit') + sys.exit() - if not os.path.exists(AVATAR_PATH): - create_path(AVATAR_PATH) - elif os.path.isfile(AVATAR_PATH): - print _('%s is a file but it should be a directory') % AVATAR_PATH - print _('Gajim will now exit') - sys.exit() + if not os.path.exists(AVATAR_PATH): + create_path(AVATAR_PATH) + elif os.path.isfile(AVATAR_PATH): + print _('%s is a file but it should be a directory') % AVATAR_PATH + print _('Gajim will now exit') + sys.exit() - if not os.path.exists(LOG_DB_PATH): - create_log_db() - gajim.logger.init_vars() - elif os.path.isdir(LOG_DB_PATH): - print _('%s is a directory but should be a file') % LOG_DB_PATH - print _('Gajim will now exit') - sys.exit() + if not os.path.exists(LOG_DB_PATH): + create_log_db() + gajim.logger.init_vars() + elif os.path.isdir(LOG_DB_PATH): + print _('%s is a directory but should be a file') % LOG_DB_PATH + print _('Gajim will now exit') + sys.exit() - else: # dot_gajim doesn't exist - if dot_gajim: # is '' on win9x so avoid that - create_path(dot_gajim) - if not os.path.isdir(VCARD_PATH): - create_path(VCARD_PATH) - if not os.path.exists(AVATAR_PATH): - create_path(AVATAR_PATH) - if not os.path.isfile(LOG_DB_PATH): - create_log_db() - gajim.logger.init_vars() + else: # dot_gajim doesn't exist + if dot_gajim: # is '' on win9x so avoid that + create_path(dot_gajim) + if not os.path.isdir(VCARD_PATH): + create_path(VCARD_PATH) + if not os.path.exists(AVATAR_PATH): + create_path(AVATAR_PATH) + if not os.path.isfile(LOG_DB_PATH): + create_log_db() + gajim.logger.init_vars() def create_path(directory): - print _('creating %s directory') % directory - os.mkdir(directory, 0700) - -# vim: se ts=3: + print _('creating %s directory') % directory + os.mkdir(directory, 0700) diff --git a/src/common/commands.py b/src/common/commands.py index 71b0300bc..7ab4298f1 100644 --- a/src/common/commands.py +++ b/src/common/commands.py @@ -28,400 +28,398 @@ import dataforms import gajim class AdHocCommand: - commandnode = 'command' - commandname = 'The Command' - commandfeatures = (xmpp.NS_DATA,) + commandnode = 'command' + commandname = 'The Command' + commandfeatures = (xmpp.NS_DATA,) - @staticmethod - def isVisibleFor(samejid): - ''' This returns True if that command should be visible and invokable - for others. - samejid - True when command is invoked by an entity with the same bare - jid.''' - return True + @staticmethod + def isVisibleFor(samejid): + ''' This returns True if that command should be visible and invokable + for others. + samejid - True when command is invoked by an entity with the same bare + jid.''' + return True - def __init__(self, conn, jid, sessionid): - self.connection = conn - self.jid = jid - self.sessionid = sessionid + def __init__(self, conn, jid, sessionid): + self.connection = conn + self.jid = jid + self.sessionid = sessionid - def buildResponse(self, request, status = 'executing', defaultaction = None, - actions = None): - assert status in ('executing', 'completed', 'canceled') + def buildResponse(self, request, status = 'executing', defaultaction = None, + actions = None): + assert status in ('executing', 'completed', 'canceled') - response = request.buildReply('result') - cmd = response.addChild('command', namespace=xmpp.NS_COMMANDS, attrs={ - 'sessionid': self.sessionid, - 'node': self.commandnode, - 'status': status}) - if defaultaction is not None or actions is not None: - if defaultaction is not None: - assert defaultaction in ('cancel', 'execute', 'prev', 'next', - 'complete') - attrs = {'action': defaultaction} - else: - attrs = {} + response = request.buildReply('result') + cmd = response.addChild('command', namespace=xmpp.NS_COMMANDS, attrs={ + 'sessionid': self.sessionid, + 'node': self.commandnode, + 'status': status}) + if defaultaction is not None or actions is not None: + if defaultaction is not None: + assert defaultaction in ('cancel', 'execute', 'prev', 'next', + 'complete') + attrs = {'action': defaultaction} + else: + attrs = {} - cmd.addChild('actions', attrs, actions) - return response, cmd + cmd.addChild('actions', attrs, actions) + return response, cmd - def badRequest(self, stanza): - self.connection.connection.send(xmpp.Error(stanza, xmpp.NS_STANZAS + \ - ' bad-request')) + def badRequest(self, stanza): + self.connection.connection.send(xmpp.Error(stanza, xmpp.NS_STANZAS + \ + ' bad-request')) - def cancel(self, request): - response = self.buildResponse(request, status = 'canceled')[0] - self.connection.connection.send(response) - return False # finish the session + def cancel(self, request): + response = self.buildResponse(request, status = 'canceled')[0] + self.connection.connection.send(response) + return False # finish the session class ChangeStatusCommand(AdHocCommand): - commandnode = 'change-status' - commandname = _('Change status information') + commandnode = 'change-status' + commandname = _('Change status information') - @staticmethod - def isVisibleFor(samejid): - ''' Change status is visible only if the entity has the same bare jid. ''' - return samejid + @staticmethod + def isVisibleFor(samejid): + ''' Change status is visible only if the entity has the same bare jid. ''' + return samejid - def execute(self, request): - # first query... - response, cmd = self.buildResponse(request, defaultaction = 'execute', - actions = ['execute']) + def execute(self, request): + # first query... + response, cmd = self.buildResponse(request, defaultaction = 'execute', + actions = ['execute']) - cmd.addChild(node = dataforms.SimpleDataForm( - title = _('Change status'), - instructions = _('Set the presence type and description'), - fields = [ - dataforms.Field('list-single', - var = 'presence-type', - label = 'Type of presence:', - options = [ - (u'chat', _('Free for chat')), - (u'online', _('Online')), - (u'away', _('Away')), - (u'xa', _('Extended away')), - (u'dnd', _('Do not disturb')), - (u'offline', _('Offline - disconnect'))], - value = 'online', - required = True), - dataforms.Field('text-multi', - var = 'presence-desc', - label = _('Presence description:'))])) + cmd.addChild(node = dataforms.SimpleDataForm( + title = _('Change status'), + instructions = _('Set the presence type and description'), + fields = [ + dataforms.Field('list-single', + var = 'presence-type', + label = 'Type of presence:', + options = [ + (u'chat', _('Free for chat')), + (u'online', _('Online')), + (u'away', _('Away')), + (u'xa', _('Extended away')), + (u'dnd', _('Do not disturb')), + (u'offline', _('Offline - disconnect'))], + value = 'online', + required = True), + dataforms.Field('text-multi', + var = 'presence-desc', + label = _('Presence description:'))])) - self.connection.connection.send(response) + self.connection.connection.send(response) - # for next invocation - self.execute = self.changestatus + # for next invocation + self.execute = self.changestatus - return True # keep the session + return True # keep the session - def changestatus(self, request): - # check if the data is correct - try: - form = dataforms.SimpleDataForm(extend = request.getTag('command').\ - getTag('x')) - except Exception: - self.badRequest(request) - return False + def changestatus(self, request): + # check if the data is correct + try: + form = dataforms.SimpleDataForm(extend = request.getTag('command').\ + getTag('x')) + except Exception: + self.badRequest(request) + return False - try: - presencetype = form['presence-type'].value - if not presencetype in \ - ('chat', 'online', 'away', 'xa', 'dnd', 'offline'): - self.badRequest(request) - return False - except Exception: # KeyError if there's no presence-type field in form or - # AttributeError if that field is of wrong type - self.badRequest(request) - return False + try: + presencetype = form['presence-type'].value + if not presencetype in \ + ('chat', 'online', 'away', 'xa', 'dnd', 'offline'): + self.badRequest(request) + return False + except Exception: # KeyError if there's no presence-type field in form or + # AttributeError if that field is of wrong type + self.badRequest(request) + return False - try: - presencedesc = form['presence-desc'].value - except Exception: # same exceptions as in last comment - presencedesc = u'' + try: + presencedesc = form['presence-desc'].value + except Exception: # same exceptions as in last comment + presencedesc = u'' - response, cmd = self.buildResponse(request, status = 'completed') - cmd.addChild('note', {}, _('The status has been changed.')) + response, cmd = self.buildResponse(request, status = 'completed') + cmd.addChild('note', {}, _('The status has been changed.')) - # if going offline, we need to push response so it won't go into - # queue and disappear - self.connection.connection.send(response, now = presencetype == 'offline') + # if going offline, we need to push response so it won't go into + # queue and disappear + self.connection.connection.send(response, now = presencetype == 'offline') - # send new status - gajim.interface.roster.send_status(self.connection.name, presencetype, - presencedesc) + # send new status + gajim.interface.roster.send_status(self.connection.name, presencetype, + presencedesc) - return False # finish the session + return False # finish the session def find_current_groupchats(account): - import message_control - rooms = [] - for gc_control in gajim.interface.msg_win_mgr.get_controls( - message_control.TYPE_GC) + gajim.interface.minimized_controls[account].\ - values(): - acct = gc_control.account - # check if account is the good one - if acct != account: - continue - room_jid = gc_control.room_jid - nick = gc_control.nick - if room_jid in gajim.gc_connected[acct] and \ - gajim.gc_connected[acct][room_jid]: - rooms.append((room_jid, nick,)) - return rooms + import message_control + rooms = [] + for gc_control in gajim.interface.msg_win_mgr.get_controls( + message_control.TYPE_GC) + gajim.interface.minimized_controls[account].\ + values(): + acct = gc_control.account + # check if account is the good one + if acct != account: + continue + room_jid = gc_control.room_jid + nick = gc_control.nick + if room_jid in gajim.gc_connected[acct] and \ + gajim.gc_connected[acct][room_jid]: + rooms.append((room_jid, nick,)) + return rooms class LeaveGroupchatsCommand(AdHocCommand): - commandnode = 'leave-groupchats' - commandname = _('Leave Groupchats') + commandnode = 'leave-groupchats' + commandname = _('Leave Groupchats') - @staticmethod - def isVisibleFor(samejid): - ''' Change status is visible only if the entity has the same bare jid. ''' - return samejid + @staticmethod + def isVisibleFor(samejid): + ''' Change status is visible only if the entity has the same bare jid. ''' + return samejid - def execute(self, request): - # first query... - response, cmd = self.buildResponse(request, defaultaction = 'execute', - actions=['execute']) - options = [] - account = self.connection.name - for gc in find_current_groupchats(account): - options.append((u'%s' %(gc[0]), _('%(nickname)s on %(room_jid)s') % \ - {'nickname': gc[1], 'room_jid': gc[0]})) - if not len(options): - response, cmd = self.buildResponse(request, status = 'completed') - cmd.addChild('note', {}, _('You have not joined a groupchat.')) + def execute(self, request): + # first query... + response, cmd = self.buildResponse(request, defaultaction = 'execute', + actions=['execute']) + options = [] + account = self.connection.name + for gc in find_current_groupchats(account): + options.append((u'%s' %(gc[0]), _('%(nickname)s on %(room_jid)s') % \ + {'nickname': gc[1], 'room_jid': gc[0]})) + if not len(options): + response, cmd = self.buildResponse(request, status = 'completed') + cmd.addChild('note', {}, _('You have not joined a groupchat.')) - self.connection.connection.send(response) - return False + self.connection.connection.send(response) + return False - cmd.addChild(node=dataforms.SimpleDataForm( - title = _('Leave Groupchats'), - instructions = _('Choose the groupchats you want to leave'), - fields=[ - dataforms.Field('list-multi', - var = 'groupchats', - label = _('Groupchats'), - options = options, - required = True)])) + cmd.addChild(node=dataforms.SimpleDataForm( + title = _('Leave Groupchats'), + instructions = _('Choose the groupchats you want to leave'), + fields=[ + dataforms.Field('list-multi', + var = 'groupchats', + label = _('Groupchats'), + options = options, + required = True)])) - self.connection.connection.send(response) + self.connection.connection.send(response) - # for next invocation - self.execute = self.leavegroupchats + # for next invocation + self.execute = self.leavegroupchats - return True # keep the session + return True # keep the session - def leavegroupchats(self, request): - # check if the data is correct - try: - form = dataforms.SimpleDataForm(extend = request.getTag('command').\ - getTag('x')) - except Exception: - self.badRequest(request) - return False + def leavegroupchats(self, request): + # check if the data is correct + try: + form = dataforms.SimpleDataForm(extend = request.getTag('command').\ + getTag('x')) + except Exception: + self.badRequest(request) + return False - try: - gc = form['groupchats'].values - except Exception: # KeyError if there's no groupchats in form - self.badRequest(request) - return False - account = self.connection.name - try: - for room_jid in gc: - gc_control = gajim.interface.msg_win_mgr.get_gc_control(room_jid, - account) - if not gc_control: - gc_control = gajim.interface.minimized_controls[account]\ - [room_jid] - gc_control.shutdown() - gajim.interface.roster.remove_groupchat(room_jid, account) - continue - gc_control.parent_win.remove_tab(gc_control, None, force = True) - except Exception: # KeyError if there's no such room opened - self.badRequest(request) - return False - response, cmd = self.buildResponse(request, status = 'completed') - note = _('You left the following groupchats:') - for room_jid in gc: - note += '\n\t' + room_jid - cmd.addChild('note', {}, note) + try: + gc = form['groupchats'].values + except Exception: # KeyError if there's no groupchats in form + self.badRequest(request) + return False + account = self.connection.name + try: + for room_jid in gc: + gc_control = gajim.interface.msg_win_mgr.get_gc_control(room_jid, + account) + if not gc_control: + gc_control = gajim.interface.minimized_controls[account]\ + [room_jid] + gc_control.shutdown() + gajim.interface.roster.remove_groupchat(room_jid, account) + continue + gc_control.parent_win.remove_tab(gc_control, None, force = True) + except Exception: # KeyError if there's no such room opened + self.badRequest(request) + return False + response, cmd = self.buildResponse(request, status = 'completed') + note = _('You left the following groupchats:') + for room_jid in gc: + note += '\n\t' + room_jid + cmd.addChild('note', {}, note) - self.connection.connection.send(response) - return False + self.connection.connection.send(response) + return False class ForwardMessagesCommand(AdHocCommand): - # http://www.xmpp.org/extensions/xep-0146.html#forward - commandnode = 'forward-messages' - commandname = _('Forward unread messages') + # http://www.xmpp.org/extensions/xep-0146.html#forward + commandnode = 'forward-messages' + commandname = _('Forward unread messages') - @staticmethod - def isVisibleFor(samejid): - ''' Change status is visible only if the entity has the same bare jid. ''' - return samejid + @staticmethod + def isVisibleFor(samejid): + ''' Change status is visible only if the entity has the same bare jid. ''' + return samejid - def execute(self, request): - account = self.connection.name - # Forward messages - events = gajim.events.get_events(account, types=['chat', 'normal']) - j, resource = gajim.get_room_and_nick_from_fjid(self.jid) - for jid in events: - for event in events[jid]: - self.connection.send_message(j, event.parameters[0], '', - type_=event.type_, subject=event.parameters[1], - resource=resource, forward_from=jid, delayed=event.time_) + def execute(self, request): + account = self.connection.name + # Forward messages + events = gajim.events.get_events(account, types=['chat', 'normal']) + j, resource = gajim.get_room_and_nick_from_fjid(self.jid) + for jid in events: + for event in events[jid]: + self.connection.send_message(j, event.parameters[0], '', + type_=event.type_, subject=event.parameters[1], + resource=resource, forward_from=jid, delayed=event.time_) - # Inform other client of completion - response, cmd = self.buildResponse(request, status = 'completed') - cmd.addChild('note', {}, _('All unread messages have been forwarded.')) + # Inform other client of completion + response, cmd = self.buildResponse(request, status = 'completed') + cmd.addChild('note', {}, _('All unread messages have been forwarded.')) - self.connection.connection.send(response) + self.connection.connection.send(response) - return False # finish the session + return False # finish the session class ConnectionCommands: - ''' This class depends on that it is a part of Connection() class. ''' - def __init__(self): - # a list of all commands exposed: node -> command class - self.__commands = {} - for cmdobj in (ChangeStatusCommand, ForwardMessagesCommand, - LeaveGroupchatsCommand): - self.__commands[cmdobj.commandnode] = cmdobj + ''' This class depends on that it is a part of Connection() class. ''' + def __init__(self): + # a list of all commands exposed: node -> command class + self.__commands = {} + for cmdobj in (ChangeStatusCommand, ForwardMessagesCommand, + LeaveGroupchatsCommand): + self.__commands[cmdobj.commandnode] = cmdobj - # a list of sessions; keys are tuples (jid, sessionid, node) - self.__sessions = {} + # a list of sessions; keys are tuples (jid, sessionid, node) + self.__sessions = {} - def getOurBareJID(self): - return gajim.get_jid_from_account(self.name) + def getOurBareJID(self): + return gajim.get_jid_from_account(self.name) - def isSameJID(self, jid): - ''' Tests if the bare jid given is the same as our bare jid. ''' - return xmpp.JID(jid).getStripped() == self.getOurBareJID() + def isSameJID(self, jid): + ''' Tests if the bare jid given is the same as our bare jid. ''' + return xmpp.JID(jid).getStripped() == self.getOurBareJID() - def commandListQuery(self, con, iq_obj): - iq = iq_obj.buildReply('result') - jid = helpers.get_full_jid_from_iq(iq_obj) - q = iq.getTag('query') - # buildReply don't copy the node attribute. Re-add it - q.setAttr('node', xmpp.NS_COMMANDS) + def commandListQuery(self, con, iq_obj): + iq = iq_obj.buildReply('result') + jid = helpers.get_full_jid_from_iq(iq_obj) + q = iq.getTag('query') + # buildReply don't copy the node attribute. Re-add it + q.setAttr('node', xmpp.NS_COMMANDS) - for node, cmd in self.__commands.iteritems(): - if cmd.isVisibleFor(self.isSameJID(jid)): - q.addChild('item', { - # TODO: find the jid - 'jid': self.getOurBareJID() + u'/' + self.server_resource, - 'node': node, - 'name': cmd.commandname}) + for node, cmd in self.__commands.iteritems(): + if cmd.isVisibleFor(self.isSameJID(jid)): + q.addChild('item', { + # TODO: find the jid + 'jid': self.getOurBareJID() + u'/' + self.server_resource, + 'node': node, + 'name': cmd.commandname}) - self.connection.send(iq) + self.connection.send(iq) - def commandInfoQuery(self, con, iq_obj): - ''' Send disco#info result for query for command (JEP-0050, example 6.). - Return True if the result was sent, False if not. ''' - jid = helpers.get_full_jid_from_iq(iq_obj) - node = iq_obj.getTagAttr('query', 'node') + def commandInfoQuery(self, con, iq_obj): + ''' Send disco#info result for query for command (JEP-0050, example 6.). + Return True if the result was sent, False if not. ''' + jid = helpers.get_full_jid_from_iq(iq_obj) + node = iq_obj.getTagAttr('query', 'node') - if node not in self.__commands: return False + if node not in self.__commands: return False - cmd = self.__commands[node] - if cmd.isVisibleFor(self.isSameJID(jid)): - iq = iq_obj.buildReply('result') - q = iq.getTag('query') - q.addChild('identity', attrs = {'type': 'command-node', - 'category': 'automation', - 'name': cmd.commandname}) - q.addChild('feature', attrs = {'var': xmpp.NS_COMMANDS}) - for feature in cmd.commandfeatures: - q.addChild('feature', attrs = {'var': feature}) + cmd = self.__commands[node] + if cmd.isVisibleFor(self.isSameJID(jid)): + iq = iq_obj.buildReply('result') + q = iq.getTag('query') + q.addChild('identity', attrs = {'type': 'command-node', + 'category': 'automation', + 'name': cmd.commandname}) + q.addChild('feature', attrs = {'var': xmpp.NS_COMMANDS}) + for feature in cmd.commandfeatures: + q.addChild('feature', attrs = {'var': feature}) - self.connection.send(iq) - return True + self.connection.send(iq) + return True - return False + return False - def commandItemsQuery(self, con, iq_obj): - ''' Send disco#items result for query for command. - Return True if the result was sent, False if not. ''' - jid = helpers.get_full_jid_from_iq(iq_obj) - node = iq_obj.getTagAttr('query', 'node') + def commandItemsQuery(self, con, iq_obj): + ''' Send disco#items result for query for command. + Return True if the result was sent, False if not. ''' + jid = helpers.get_full_jid_from_iq(iq_obj) + node = iq_obj.getTagAttr('query', 'node') - if node not in self.__commands: return False + if node not in self.__commands: return False - cmd = self.__commands[node] - if cmd.isVisibleFor(self.isSameJID(jid)): - iq = iq_obj.buildReply('result') - self.connection.send(iq) - return True + cmd = self.__commands[node] + if cmd.isVisibleFor(self.isSameJID(jid)): + iq = iq_obj.buildReply('result') + self.connection.send(iq) + return True - return False + return False - def _CommandExecuteCB(self, con, iq_obj): - jid = helpers.get_full_jid_from_iq(iq_obj) + def _CommandExecuteCB(self, con, iq_obj): + jid = helpers.get_full_jid_from_iq(iq_obj) - cmd = iq_obj.getTag('command') - if cmd is None: return + cmd = iq_obj.getTag('command') + if cmd is None: return - node = cmd.getAttr('node') - if node is None: return + node = cmd.getAttr('node') + if node is None: return - sessionid = cmd.getAttr('sessionid') - if sessionid is None: - # we start a new command session... only if we are visible for the jid - # and command exist - if node not in self.__commands.keys(): - self.connection.send( - xmpp.Error(iq_obj, xmpp.NS_STANZAS + ' item-not-found')) - raise xmpp.NodeProcessed + sessionid = cmd.getAttr('sessionid') + if sessionid is None: + # we start a new command session... only if we are visible for the jid + # and command exist + if node not in self.__commands.keys(): + self.connection.send( + xmpp.Error(iq_obj, xmpp.NS_STANZAS + ' item-not-found')) + raise xmpp.NodeProcessed - newcmd = self.__commands[node] - if not newcmd.isVisibleFor(self.isSameJID(jid)): - return + newcmd = self.__commands[node] + if not newcmd.isVisibleFor(self.isSameJID(jid)): + return - # generate new sessionid - sessionid = self.connection.getAnID() + # generate new sessionid + sessionid = self.connection.getAnID() - # create new instance and run it - obj = newcmd(conn = self, jid = jid, sessionid = sessionid) - rc = obj.execute(iq_obj) - if rc: - self.__sessions[(jid, sessionid, node)] = obj - raise xmpp.NodeProcessed - else: - # the command is already running, check for it - magictuple = (jid, sessionid, node) - if magictuple not in self.__sessions: - # we don't have this session... ha! - return + # create new instance and run it + obj = newcmd(conn = self, jid = jid, sessionid = sessionid) + rc = obj.execute(iq_obj) + if rc: + self.__sessions[(jid, sessionid, node)] = obj + raise xmpp.NodeProcessed + else: + # the command is already running, check for it + magictuple = (jid, sessionid, node) + if magictuple not in self.__sessions: + # we don't have this session... ha! + return - action = cmd.getAttr('action') - obj = self.__sessions[magictuple] + action = cmd.getAttr('action') + obj = self.__sessions[magictuple] - try: - if action == 'cancel': - rc = obj.cancel(iq_obj) - elif action == 'prev': - rc = obj.prev(iq_obj) - elif action == 'next': - rc = obj.next(iq_obj) - elif action == 'execute' or action is None: - rc = obj.execute(iq_obj) - elif action == 'complete': - rc = obj.complete(iq_obj) - else: - # action is wrong. stop the session, send error - raise AttributeError - except AttributeError: - # the command probably doesn't handle invoked action... - # stop the session, return error - del self.__sessions[magictuple] - return + try: + if action == 'cancel': + rc = obj.cancel(iq_obj) + elif action == 'prev': + rc = obj.prev(iq_obj) + elif action == 'next': + rc = obj.next(iq_obj) + elif action == 'execute' or action is None: + rc = obj.execute(iq_obj) + elif action == 'complete': + rc = obj.complete(iq_obj) + else: + # action is wrong. stop the session, send error + raise AttributeError + except AttributeError: + # the command probably doesn't handle invoked action... + # stop the session, return error + del self.__sessions[magictuple] + return - # delete the session if rc is False - if not rc: - del self.__sessions[magictuple] + # delete the session if rc is False + if not rc: + del self.__sessions[magictuple] - raise xmpp.NodeProcessed - -# vim: se ts=3: + raise xmpp.NodeProcessed diff --git a/src/common/config.py b/src/common/config.py index d49613b59..32c9f8716 100644 --- a/src/common/config.py +++ b/src/common/config.py @@ -56,672 +56,670 @@ opt_treat_incoming_messages = ['', 'chat', 'normal'] class Config: - DEFAULT_ICONSET = 'dcraven' - DEFAULT_MOOD_ICONSET = 'default' - DEFAULT_ACTIVITY_ICONSET = 'default' - DEFAULT_OPENWITH = 'gnome-open' - DEFAULT_BROWSER = 'firefox' - DEFAULT_MAILAPP = 'mozilla-thunderbird -compose' - DEFAULT_FILE_MANAGER = 'xffm' + DEFAULT_ICONSET = 'dcraven' + DEFAULT_MOOD_ICONSET = 'default' + DEFAULT_ACTIVITY_ICONSET = 'default' + DEFAULT_OPENWITH = 'gnome-open' + DEFAULT_BROWSER = 'firefox' + DEFAULT_MAILAPP = 'mozilla-thunderbird -compose' + DEFAULT_FILE_MANAGER = 'xffm' - __options = { - # name: [ type, default_value, help_string ] - 'verbose': [ opt_bool, False, '', True ], - 'autopopup': [ opt_bool, False ], - 'notify_on_signin': [ opt_bool, True ], - 'notify_on_signout': [ opt_bool, False ], - 'notify_on_new_message': [ opt_bool, True ], - 'autopopupaway': [ opt_bool, False ], - 'sounddnd': [ opt_bool, False, _('Play sound when user is busy')], - 'use_notif_daemon': [ opt_bool, True , _('Use D-Bus and Notification-Daemon to show notifications') ], - 'showoffline': [ opt_bool, False ], - 'show_only_chat_and_online': [ opt_bool, False, _('Show only online and free for chat contacts in roster.')], - 'show_transports_group': [ opt_bool, True ], - 'autoaway': [ opt_bool, True ], - 'autoawaytime': [ opt_int, 5, _('Time in minutes, after which your status changes to away.') ], - 'autoaway_message': [ opt_str, _('$S (Away as a result of being idle more than $T min)'), _('$S will be replaced by current status message, $T by autoaway time.') ], - 'autoxa': [ opt_bool, True ], - 'autoxatime': [ opt_int, 15, _('Time in minutes, after which your status changes to not available.') ], - 'autoxa_message': [ opt_str, _('$S (Not available as a result of being idle more than $T min)'), _('$S will be replaced by current status message, $T by autoxa time.') ], - 'ask_online_status': [ opt_bool, False ], - 'ask_offline_status': [ opt_bool, False ], - 'trayicon': [opt_str, 'always', _("When to show systray icon. Can be 'never', 'on_event', 'always'."), True], - 'iconset': [ opt_str, DEFAULT_ICONSET, '', True ], - 'mood_iconset': [ opt_str, DEFAULT_MOOD_ICONSET, '', True ], - 'activity_iconset': [ opt_str, DEFAULT_ACTIVITY_ICONSET, '', True ], - 'use_transports_iconsets': [ opt_bool, True, '', True ], - 'inmsgcolor': [ opt_color, '#a40000', _('Incoming nickname color.'), True ], - 'outmsgcolor': [ opt_color, '#3465a4', _('Outgoing nickname color.'), True ], - 'inmsgtxtcolor': [ opt_color, '', _('Incoming text color.'), True ], - 'outmsgtxtcolor': [ opt_color, '#555753', _('Outgoing text color.'), True ], - 'statusmsgcolor': [ opt_color, '#4e9a06', _('Status message text color.'), True ], - 'markedmsgcolor': [ opt_color, '#ff8080', '', True ], - 'urlmsgcolor': [ opt_color, '#204a87', '', True ], - 'inmsgfont': [ opt_str, '', _('Incoming nickname font.'), True ], - 'outmsgfont': [ opt_str, '', _('Outgoing nickname font.'), True ], - 'inmsgtxtfont': [ opt_str, '', _('Incoming text font.'), True ], - 'outmsgtxtfont': [ opt_str, '', _('Outgoing text font.'), True ], - 'statusmsgfont': [ opt_str, '', _('Status message text font.'), True ], - 'collapsed_rows': [ opt_str, '', _('List (space separated) of rows (accounts and groups) that are collapsed.'), True ], - 'roster_theme': [ opt_str, _('default'), '', True ], - 'mergeaccounts': [ opt_bool, False, '', True ], - 'sort_by_show_in_roster': [ opt_bool, True, '', True ], - 'sort_by_show_in_muc': [ opt_bool, False, '', True ], - 'use_speller': [ opt_bool, False, ], - 'ignore_incoming_xhtml': [ opt_bool, False, ], - 'speller_language': [ opt_str, '', _('Language used by speller')], - 'print_time': [ opt_str, 'always', _('\'always\' - print time for every message.\n\'sometimes\' - print time every print_ichat_every_foo_minutes minute.\n\'never\' - never print time.')], - 'print_time_fuzzy': [ opt_int, 0, _('Print time in chats using Fuzzy Clock. Value of fuzziness from 1 to 4, or 0 to disable fuzzyclock. 1 is the most precise clock, 4 the least precise one. This is used only if print_time is \'sometimes\'.') ], - 'emoticons_theme': [opt_str, 'static', '', True ], - 'ascii_formatting': [ opt_bool, True, - _('Treat * / _ pairs as possible formatting characters.'), True], - 'show_ascii_formatting_chars': [ opt_bool, True , _('If True, do not ' - 'remove */_ . So *abc* will be bold but with * * not removed.')], - 'rst_formatting_outgoing_messages': [ opt_bool, False, - _('Uses ReStructured text markup to send HTML, plus ascii formatting if selected. For syntax, see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html (If you want to use this, install docutils)')], - 'sounds_on': [ opt_bool, True ], - # 'aplay', 'play', 'esdplay', 'artsplay' detected first time only - 'soundplayer': [ opt_str, '' ], - 'openwith': [ opt_str, DEFAULT_OPENWITH ], - 'custombrowser': [ opt_str, DEFAULT_BROWSER ], - 'custommailapp': [ opt_str, DEFAULT_MAILAPP ], - 'custom_file_manager': [ opt_str, DEFAULT_FILE_MANAGER ], - 'gc-hpaned-position': [opt_int, 430], - 'gc_refer_to_nick_char': [opt_str, ',', _('Character to add after nickname when using nick completion (tab) in group chat.')], - 'gc_proposed_nick_char': [opt_str, '_', _('Character to propose to add after desired nickname when desired nickname is used by someone else in group chat.')], - 'msgwin-max-state': [opt_bool, False], - 'msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide - 'msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide - 'msgwin-width': [opt_int, 500], - 'msgwin-height': [opt_int, 440], - 'chat-msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide - 'chat-msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide - 'chat-msgwin-width': [opt_int, 480], - 'chat-msgwin-height': [opt_int, 440], - 'gc-msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide - 'gc-msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide - 'gc-msgwin-width': [opt_int, 600], - 'gc-msgwin-height': [opt_int, 440], - 'single-msg-x-position': [opt_int, 0], - 'single-msg-y-position': [opt_int, 0], - 'single-msg-width': [opt_int, 400], - 'single-msg-height': [opt_int, 280], - 'roster_x-position': [ opt_int, 0 ], - 'roster_y-position': [ opt_int, 0 ], - 'roster_width': [ opt_int, 200 ], - 'roster_height': [ opt_int, 400 ], - 'history_window_width': [ opt_int, 650 ], - 'history_window_height': [ opt_int, 450 ], - 'history_window_x-position': [ opt_int, 0 ], - 'history_window_y-position': [ opt_int, 0 ], - 'latest_disco_addresses': [ opt_str, '' ], - 'recently_groupchat': [ opt_str, '' ], - 'time_stamp': [ opt_str, '[%X] ', _('This option let you customize timestamp that is printed in conversation. For exemple "[%H:%M] " will show "[hour:minute] ". See python doc on strftime for full documentation: http://docs.python.org/lib/module-time.html') ], - 'before_nickname': [ opt_str, '', _('Characters that are printed before the nickname in conversations') ], - 'after_nickname': [ opt_str, ':', _('Characters that are printed after the nickname in conversations') ], - 'notify_on_new_gmail_email': [ opt_bool, True ], - 'notify_on_new_gmail_email_extra': [ opt_bool, False ], - 'use_gpg_agent': [ opt_bool, False ], - 'change_roster_title': [ opt_bool, True, _('Add * and [n] in roster title?')], - 'restore_lines': [opt_int, 4, _('How many lines to remember from previous conversation when a chat tab/window is reopened.')], - 'restore_timeout': [opt_int, 60, _('How many minutes should last lines from previous conversation last.')], - 'muc_restore_lines': [opt_int, 20, _('How many lines to request to server when entering a groupchat.')], - 'muc_restore_timeout': [opt_int, 60, _('How many minutes back to request logs when a entering a groupchat.')], - 'muc_autorejoin_timeout': [opt_int, 1, _('How many seconds to wait before trying to autorejoin to a conference you are being disconnected from. Set to 0 to disable autorejoining.')], - 'muc_autorejoin_on_kick': [opt_bool, False, 'Should autorejoin be activated when we are being kicked from a conference?'], - 'send_on_ctrl_enter': [opt_bool, False, _('Send message on Ctrl+Enter and with Enter make new line (Mirabilis ICQ Client default behaviour).')], - 'show_roster_on_startup': [opt_bool, True], - 'key_up_lines': [opt_int, 25, _('How many lines to store for Ctrl+KeyUP.')], - 'version': [ opt_str, defs.version ], # which version created the config - 'search_engine': [opt_str, 'http://www.google.com/search?&q=%s&sourceid=gajim'], - 'dictionary_url': [opt_str, 'WIKTIONARY', _("Either custom url with %s in it where %s is the word/phrase or 'WIKTIONARY' which means use wiktionary.")], - 'always_english_wikipedia': [opt_bool, False], - 'always_english_wiktionary': [opt_bool, True], - 'remote_control': [opt_bool, True, _('If checked, Gajim can be controlled remotely using gajim-remote.'), True], - 'networkmanager_support': [opt_bool, True, _('If True, listen to D-Bus signals from NetworkManager and change the status of accounts (provided they do not have listen_to_network_manager set to False and they sync with global status) based upon the status of the network connection.'), True], - 'outgoing_chat_state_notifications': [opt_str, 'all', _('Sent chat state notifications. Can be one of all, composing_only, disabled.')], - 'displayed_chat_state_notifications': [opt_str, 'all', _('Displayed chat state notifications in chat windows. Can be one of all, composing_only, disabled.')], - 'autodetect_browser_mailer': [opt_bool, False, '', True], - 'print_ichat_every_foo_minutes': [opt_int, 5, _('When not printing time for every message (print_time==sometimes), print it every x minutes.')], - 'confirm_close_muc': [opt_bool, True, _('Ask before closing a group chat tab/window.')], - 'confirm_close_muc_rooms': [opt_str, '', _('Always ask before closing group chat tab/window in this space separated list of group chat jids.')], - 'noconfirm_close_muc_rooms': [opt_str, '', _('Never ask before closing group chat tab/window in this space separated list of group chat jids.')], - 'confirm_close_multiple_tabs': [opt_bool, True, _('Ask before closing tabbed chat window if there are control that can loose data (chat, private chat, groupchat that will not be minimized)')], - 'notify_on_file_complete': [opt_bool, True], - 'file_transfers_port': [opt_int, 28011], - 'ft_add_hosts_to_send': [opt_str, '', _('Comma separated list of hosts that we send, in addition of local interfaces, for File Transfer in case of address translation/port forwarding.')], - 'conversation_font': [opt_str, ''], - 'use_kib_mib': [opt_bool, False, _('IEC standard says KiB = 1024 bytes, KB = 1000 bytes.')], - 'notify_on_all_muc_messages': [opt_bool, False], - 'trayicon_notification_on_events': [opt_bool, True, _('Notify of events in the system trayicon.')], - 'last_save_dir': [opt_str, ''], - 'last_send_dir': [opt_str, ''], - 'last_emoticons_dir': [opt_str, ''], - 'last_sounds_dir': [opt_str, ''], - 'tabs_position': [opt_str, 'top'], - 'tabs_always_visible': [opt_bool, False, _('Show tab when only one conversation?')], - 'tabs_border': [opt_bool, False, _('Show tabbed notebook border in chat windows?')], - 'tabs_close_button': [opt_bool, True, _('Show close button in tab?')], - 'esession_modp': [opt_str, '5,14', _('A list of modp groups to use in a Diffie-Hellman, highest preference first, separated by commas. Valid groups are 1, 2, 5, 14, 15, 16, 17 and 18. Higher numbers are more secure, but take longer to calculate when you start a session.')], - 'chat_avatar_width': [opt_int, 52], - 'chat_avatar_height': [opt_int, 52], - 'roster_avatar_width': [opt_int, 32], - 'roster_avatar_height': [opt_int, 32], - 'tooltip_avatar_width': [opt_int, 125], - 'tooltip_avatar_height': [opt_int, 125], - 'vcard_avatar_width': [opt_int, 200], - 'vcard_avatar_height': [opt_int, 200], - 'notification_preview_message': [opt_bool, True, _('Preview new messages in notification popup?')], - 'notification_position_x': [opt_int, -1], - 'notification_position_y': [opt_int, -1], - 'notification_avatar_width': [opt_int, 48], - 'notification_avatar_height': [opt_int, 48], - 'muc_highlight_words': [opt_str, '', _('A semicolon-separated list of words that will be highlighted in group chats.')], - 'quit_on_roster_x_button': [opt_bool, False, _('If True, quits Gajim when X button of Window Manager is clicked. This setting is taken into account only if trayicon is used.')], - 'check_if_gajim_is_default': [opt_bool, True, _('If True, Gajim will check if it\'s the default jabber client on each startup.')], - 'show_unread_tab_icon': [opt_bool, False, _('If True, Gajim will display an icon on each tab containing unread messages. Depending on the theme, this icon may be animated.')], - 'show_status_msgs_in_roster': [opt_bool, True, _('If True, Gajim will display the status message, if not empty, for every contact under the contact name in roster window.'), True], - 'show_avatars_in_roster': [opt_bool, True, '', True], - 'show_mood_in_roster': [opt_bool, True, '', True], - 'show_activity_in_roster': [opt_bool, True, '', True], - 'show_tunes_in_roster': [opt_bool, True, '', True], - 'avatar_position_in_roster': [opt_str, 'right', _('Define the position of the avatar in roster. Can be left or right'), True], - 'ask_avatars_on_startup': [opt_bool, True, _('If True, Gajim will ask for avatar each contact that did not have an avatar last time or has one cached that is too old.')], - 'print_status_in_chats': [opt_bool, True, _('If False, Gajim will no longer print status line in chats when a contact changes his or her status and/or his or her status message.')], - 'print_status_in_muc': [opt_str, 'in_and_out', _('can be "none", "all" or "in_and_out". If "none", Gajim will no longer print status line in groupchats when a member changes his or her status and/or his or her status message. If "all" Gajim will print all status messages. If "in_and_out", Gajim will only print FOO enters/leaves group chat.')], - 'log_contact_status_changes': [opt_bool, False], - 'just_connected_bg_color': [opt_str, '#adc3c6', _('Background color of contacts when they just signed in.')], - 'just_disconnected_bg_color': [opt_str, '#ab6161', _('Background color of contacts when they just signed out.')], - 'restored_messages_color': [opt_color, '#555753'], - 'restored_messages_small': [opt_bool, True, _('If True, restored messages will use a smaller font than the default one.')], - 'hide_avatar_of_transport': [opt_bool, False, _('Don\'t show avatar for the transport itself.')], - 'roster_window_skip_taskbar': [opt_bool, False, _('Don\'t show roster in the system taskbar.')], - 'use_urgency_hint': [opt_bool, True, _('If True and installed GTK+ and PyGTK versions are at least 2.8, make the window flash (the default behaviour in most Window Managers) when holding pending events.')], - 'notification_timeout': [opt_int, 5], - 'send_sha_in_gc_presence': [opt_bool, True, _('Jabberd1.4 does not like sha info when one join a password protected group chat. Turn this option to False to stop sending sha info in group chat presences.')], - 'one_message_window': [opt_str, 'always', + __options = { + # name: [ type, default_value, help_string ] + 'verbose': [ opt_bool, False, '', True ], + 'autopopup': [ opt_bool, False ], + 'notify_on_signin': [ opt_bool, True ], + 'notify_on_signout': [ opt_bool, False ], + 'notify_on_new_message': [ opt_bool, True ], + 'autopopupaway': [ opt_bool, False ], + 'sounddnd': [ opt_bool, False, _('Play sound when user is busy')], + 'use_notif_daemon': [ opt_bool, True, _('Use D-Bus and Notification-Daemon to show notifications') ], + 'showoffline': [ opt_bool, False ], + 'show_only_chat_and_online': [ opt_bool, False, _('Show only online and free for chat contacts in roster.')], + 'show_transports_group': [ opt_bool, True ], + 'autoaway': [ opt_bool, True ], + 'autoawaytime': [ opt_int, 5, _('Time in minutes, after which your status changes to away.') ], + 'autoaway_message': [ opt_str, _('$S (Away as a result of being idle more than $T min)'), _('$S will be replaced by current status message, $T by autoaway time.') ], + 'autoxa': [ opt_bool, True ], + 'autoxatime': [ opt_int, 15, _('Time in minutes, after which your status changes to not available.') ], + 'autoxa_message': [ opt_str, _('$S (Not available as a result of being idle more than $T min)'), _('$S will be replaced by current status message, $T by autoxa time.') ], + 'ask_online_status': [ opt_bool, False ], + 'ask_offline_status': [ opt_bool, False ], + 'trayicon': [opt_str, 'always', _("When to show systray icon. Can be 'never', 'on_event', 'always'."), True], + 'iconset': [ opt_str, DEFAULT_ICONSET, '', True ], + 'mood_iconset': [ opt_str, DEFAULT_MOOD_ICONSET, '', True ], + 'activity_iconset': [ opt_str, DEFAULT_ACTIVITY_ICONSET, '', True ], + 'use_transports_iconsets': [ opt_bool, True, '', True ], + 'inmsgcolor': [ opt_color, '#a40000', _('Incoming nickname color.'), True ], + 'outmsgcolor': [ opt_color, '#3465a4', _('Outgoing nickname color.'), True ], + 'inmsgtxtcolor': [ opt_color, '', _('Incoming text color.'), True ], + 'outmsgtxtcolor': [ opt_color, '#555753', _('Outgoing text color.'), True ], + 'statusmsgcolor': [ opt_color, '#4e9a06', _('Status message text color.'), True ], + 'markedmsgcolor': [ opt_color, '#ff8080', '', True ], + 'urlmsgcolor': [ opt_color, '#204a87', '', True ], + 'inmsgfont': [ opt_str, '', _('Incoming nickname font.'), True ], + 'outmsgfont': [ opt_str, '', _('Outgoing nickname font.'), True ], + 'inmsgtxtfont': [ opt_str, '', _('Incoming text font.'), True ], + 'outmsgtxtfont': [ opt_str, '', _('Outgoing text font.'), True ], + 'statusmsgfont': [ opt_str, '', _('Status message text font.'), True ], + 'collapsed_rows': [ opt_str, '', _('List (space separated) of rows (accounts and groups) that are collapsed.'), True ], + 'roster_theme': [ opt_str, _('default'), '', True ], + 'mergeaccounts': [ opt_bool, False, '', True ], + 'sort_by_show_in_roster': [ opt_bool, True, '', True ], + 'sort_by_show_in_muc': [ opt_bool, False, '', True ], + 'use_speller': [ opt_bool, False, ], + 'ignore_incoming_xhtml': [ opt_bool, False, ], + 'speller_language': [ opt_str, '', _('Language used by speller')], + 'print_time': [ opt_str, 'always', _('\'always\' - print time for every message.\n\'sometimes\' - print time every print_ichat_every_foo_minutes minute.\n\'never\' - never print time.')], + 'print_time_fuzzy': [ opt_int, 0, _('Print time in chats using Fuzzy Clock. Value of fuzziness from 1 to 4, or 0 to disable fuzzyclock. 1 is the most precise clock, 4 the least precise one. This is used only if print_time is \'sometimes\'.') ], + 'emoticons_theme': [opt_str, 'static', '', True ], + 'ascii_formatting': [ opt_bool, True, + _('Treat * / _ pairs as possible formatting characters.'), True], + 'show_ascii_formatting_chars': [ opt_bool, True, _('If True, do not ' + 'remove */_ . So *abc* will be bold but with * * not removed.')], + 'rst_formatting_outgoing_messages': [ opt_bool, False, + _('Uses ReStructured text markup to send HTML, plus ascii formatting if selected. For syntax, see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html (If you want to use this, install docutils)')], + 'sounds_on': [ opt_bool, True ], + # 'aplay', 'play', 'esdplay', 'artsplay' detected first time only + 'soundplayer': [ opt_str, '' ], + 'openwith': [ opt_str, DEFAULT_OPENWITH ], + 'custombrowser': [ opt_str, DEFAULT_BROWSER ], + 'custommailapp': [ opt_str, DEFAULT_MAILAPP ], + 'custom_file_manager': [ opt_str, DEFAULT_FILE_MANAGER ], + 'gc-hpaned-position': [opt_int, 430], + 'gc_refer_to_nick_char': [opt_str, ',', _('Character to add after nickname when using nick completion (tab) in group chat.')], + 'gc_proposed_nick_char': [opt_str, '_', _('Character to propose to add after desired nickname when desired nickname is used by someone else in group chat.')], + 'msgwin-max-state': [opt_bool, False], + 'msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide + 'msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide + 'msgwin-width': [opt_int, 500], + 'msgwin-height': [opt_int, 440], + 'chat-msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide + 'chat-msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide + 'chat-msgwin-width': [opt_int, 480], + 'chat-msgwin-height': [opt_int, 440], + 'gc-msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide + 'gc-msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide + 'gc-msgwin-width': [opt_int, 600], + 'gc-msgwin-height': [opt_int, 440], + 'single-msg-x-position': [opt_int, 0], + 'single-msg-y-position': [opt_int, 0], + 'single-msg-width': [opt_int, 400], + 'single-msg-height': [opt_int, 280], + 'roster_x-position': [ opt_int, 0 ], + 'roster_y-position': [ opt_int, 0 ], + 'roster_width': [ opt_int, 200 ], + 'roster_height': [ opt_int, 400 ], + 'history_window_width': [ opt_int, 650 ], + 'history_window_height': [ opt_int, 450 ], + 'history_window_x-position': [ opt_int, 0 ], + 'history_window_y-position': [ opt_int, 0 ], + 'latest_disco_addresses': [ opt_str, '' ], + 'recently_groupchat': [ opt_str, '' ], + 'time_stamp': [ opt_str, '[%X] ', _('This option let you customize timestamp that is printed in conversation. For exemple "[%H:%M] " will show "[hour:minute] ". See python doc on strftime for full documentation: http://docs.python.org/lib/module-time.html') ], + 'before_nickname': [ opt_str, '', _('Characters that are printed before the nickname in conversations') ], + 'after_nickname': [ opt_str, ':', _('Characters that are printed after the nickname in conversations') ], + 'notify_on_new_gmail_email': [ opt_bool, True ], + 'notify_on_new_gmail_email_extra': [ opt_bool, False ], + 'use_gpg_agent': [ opt_bool, False ], + 'change_roster_title': [ opt_bool, True, _('Add * and [n] in roster title?')], + 'restore_lines': [opt_int, 4, _('How many lines to remember from previous conversation when a chat tab/window is reopened.')], + 'restore_timeout': [opt_int, 60, _('How many minutes should last lines from previous conversation last.')], + 'muc_restore_lines': [opt_int, 20, _('How many lines to request to server when entering a groupchat.')], + 'muc_restore_timeout': [opt_int, 60, _('How many minutes back to request logs when a entering a groupchat.')], + 'muc_autorejoin_timeout': [opt_int, 1, _('How many seconds to wait before trying to autorejoin to a conference you are being disconnected from. Set to 0 to disable autorejoining.')], + 'muc_autorejoin_on_kick': [opt_bool, False, 'Should autorejoin be activated when we are being kicked from a conference?'], + 'send_on_ctrl_enter': [opt_bool, False, _('Send message on Ctrl+Enter and with Enter make new line (Mirabilis ICQ Client default behaviour).')], + 'show_roster_on_startup': [opt_bool, True], + 'key_up_lines': [opt_int, 25, _('How many lines to store for Ctrl+KeyUP.')], + 'version': [ opt_str, defs.version ], # which version created the config + 'search_engine': [opt_str, 'http://www.google.com/search?&q=%s&sourceid=gajim'], + 'dictionary_url': [opt_str, 'WIKTIONARY', _("Either custom url with %s in it where %s is the word/phrase or 'WIKTIONARY' which means use wiktionary.")], + 'always_english_wikipedia': [opt_bool, False], + 'always_english_wiktionary': [opt_bool, True], + 'remote_control': [opt_bool, True, _('If checked, Gajim can be controlled remotely using gajim-remote.'), True], + 'networkmanager_support': [opt_bool, True, _('If True, listen to D-Bus signals from NetworkManager and change the status of accounts (provided they do not have listen_to_network_manager set to False and they sync with global status) based upon the status of the network connection.'), True], + 'outgoing_chat_state_notifications': [opt_str, 'all', _('Sent chat state notifications. Can be one of all, composing_only, disabled.')], + 'displayed_chat_state_notifications': [opt_str, 'all', _('Displayed chat state notifications in chat windows. Can be one of all, composing_only, disabled.')], + 'autodetect_browser_mailer': [opt_bool, False, '', True], + 'print_ichat_every_foo_minutes': [opt_int, 5, _('When not printing time for every message (print_time==sometimes), print it every x minutes.')], + 'confirm_close_muc': [opt_bool, True, _('Ask before closing a group chat tab/window.')], + 'confirm_close_muc_rooms': [opt_str, '', _('Always ask before closing group chat tab/window in this space separated list of group chat jids.')], + 'noconfirm_close_muc_rooms': [opt_str, '', _('Never ask before closing group chat tab/window in this space separated list of group chat jids.')], + 'confirm_close_multiple_tabs': [opt_bool, True, _('Ask before closing tabbed chat window if there are control that can loose data (chat, private chat, groupchat that will not be minimized)')], + 'notify_on_file_complete': [opt_bool, True], + 'file_transfers_port': [opt_int, 28011], + 'ft_add_hosts_to_send': [opt_str, '', _('Comma separated list of hosts that we send, in addition of local interfaces, for File Transfer in case of address translation/port forwarding.')], + 'conversation_font': [opt_str, ''], + 'use_kib_mib': [opt_bool, False, _('IEC standard says KiB = 1024 bytes, KB = 1000 bytes.')], + 'notify_on_all_muc_messages': [opt_bool, False], + 'trayicon_notification_on_events': [opt_bool, True, _('Notify of events in the system trayicon.')], + 'last_save_dir': [opt_str, ''], + 'last_send_dir': [opt_str, ''], + 'last_emoticons_dir': [opt_str, ''], + 'last_sounds_dir': [opt_str, ''], + 'tabs_position': [opt_str, 'top'], + 'tabs_always_visible': [opt_bool, False, _('Show tab when only one conversation?')], + 'tabs_border': [opt_bool, False, _('Show tabbed notebook border in chat windows?')], + 'tabs_close_button': [opt_bool, True, _('Show close button in tab?')], + 'esession_modp': [opt_str, '5,14', _('A list of modp groups to use in a Diffie-Hellman, highest preference first, separated by commas. Valid groups are 1, 2, 5, 14, 15, 16, 17 and 18. Higher numbers are more secure, but take longer to calculate when you start a session.')], + 'chat_avatar_width': [opt_int, 52], + 'chat_avatar_height': [opt_int, 52], + 'roster_avatar_width': [opt_int, 32], + 'roster_avatar_height': [opt_int, 32], + 'tooltip_avatar_width': [opt_int, 125], + 'tooltip_avatar_height': [opt_int, 125], + 'vcard_avatar_width': [opt_int, 200], + 'vcard_avatar_height': [opt_int, 200], + 'notification_preview_message': [opt_bool, True, _('Preview new messages in notification popup?')], + 'notification_position_x': [opt_int, -1], + 'notification_position_y': [opt_int, -1], + 'notification_avatar_width': [opt_int, 48], + 'notification_avatar_height': [opt_int, 48], + 'muc_highlight_words': [opt_str, '', _('A semicolon-separated list of words that will be highlighted in group chats.')], + 'quit_on_roster_x_button': [opt_bool, False, _('If True, quits Gajim when X button of Window Manager is clicked. This setting is taken into account only if trayicon is used.')], + 'check_if_gajim_is_default': [opt_bool, True, _('If True, Gajim will check if it\'s the default jabber client on each startup.')], + 'show_unread_tab_icon': [opt_bool, False, _('If True, Gajim will display an icon on each tab containing unread messages. Depending on the theme, this icon may be animated.')], + 'show_status_msgs_in_roster': [opt_bool, True, _('If True, Gajim will display the status message, if not empty, for every contact under the contact name in roster window.'), True], + 'show_avatars_in_roster': [opt_bool, True, '', True], + 'show_mood_in_roster': [opt_bool, True, '', True], + 'show_activity_in_roster': [opt_bool, True, '', True], + 'show_tunes_in_roster': [opt_bool, True, '', True], + 'avatar_position_in_roster': [opt_str, 'right', _('Define the position of the avatar in roster. Can be left or right'), True], + 'ask_avatars_on_startup': [opt_bool, True, _('If True, Gajim will ask for avatar each contact that did not have an avatar last time or has one cached that is too old.')], + 'print_status_in_chats': [opt_bool, True, _('If False, Gajim will no longer print status line in chats when a contact changes his or her status and/or his or her status message.')], + 'print_status_in_muc': [opt_str, 'in_and_out', _('can be "none", "all" or "in_and_out". If "none", Gajim will no longer print status line in groupchats when a member changes his or her status and/or his or her status message. If "all" Gajim will print all status messages. If "in_and_out", Gajim will only print FOO enters/leaves group chat.')], + 'log_contact_status_changes': [opt_bool, False], + 'just_connected_bg_color': [opt_str, '#adc3c6', _('Background color of contacts when they just signed in.')], + 'just_disconnected_bg_color': [opt_str, '#ab6161', _('Background color of contacts when they just signed out.')], + 'restored_messages_color': [opt_color, '#555753'], + 'restored_messages_small': [opt_bool, True, _('If True, restored messages will use a smaller font than the default one.')], + 'hide_avatar_of_transport': [opt_bool, False, _('Don\'t show avatar for the transport itself.')], + 'roster_window_skip_taskbar': [opt_bool, False, _('Don\'t show roster in the system taskbar.')], + 'use_urgency_hint': [opt_bool, True, _('If True and installed GTK+ and PyGTK versions are at least 2.8, make the window flash (the default behaviour in most Window Managers) when holding pending events.')], + 'notification_timeout': [opt_int, 5], + 'send_sha_in_gc_presence': [opt_bool, True, _('Jabberd1.4 does not like sha info when one join a password protected group chat. Turn this option to False to stop sending sha info in group chat presences.')], + 'one_message_window': [opt_str, 'always', #always, never, peracct, pertype should not be translated - _('Controls the window where new messages are placed.\n\'always\' - All messages are sent to a single window.\n\'always_with_roster\' - Like \'always\' but the messages are in a single window along with the roster.\n\'never\' - All messages get their own window.\n\'peracct\' - Messages for each account are sent to a specific window.\n\'pertype\' - Each message type (e.g., chats vs. groupchats) are sent to a specific window.')], - 'show_avatar_in_chat': [opt_bool, True, _('If False, you will no longer see the avatar in the chat window.')], - 'escape_key_closes': [opt_bool, True, _('If True, pressing the escape key closes a tab/window.')], - 'compact_view': [opt_bool, False, _('Hides the buttons in chat windows.')], - 'hide_groupchat_banner': [opt_bool, False, _('Hides the banner in a group chat window')], - 'hide_chat_banner': [opt_bool, False, _('Hides the banner in two persons chat window')], - 'hide_groupchat_occupants_list': [opt_bool, False, _('Hides the group chat occupants list in group chat window.')], - 'chat_merge_consecutive_nickname': [opt_bool, False, _('In a chat, show the nickname at the beginning of a line only when it\'s not the same person talking than in previous message.')], - 'chat_merge_consecutive_nickname_indent': [opt_str, ' ', _('Indentation when using merge consecutive nickname.')], - 'use_smooth_scrolling': [opt_bool, True, _('Smooth scroll message in conversation window')], - 'gc_nicknames_colors': [ opt_str, '#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000', _('List of colors, separated by ":", that will be used to color nicknames in group chats.'), True ], - 'ctrl_tab_go_to_next_composing': [opt_bool, True, _('Ctrl-Tab go to next composing tab when none is unread.')], - 'confirm_metacontacts': [ opt_str, '', _('Should we show the confirm metacontacts creation dialog or not? Empty string means we never show the dialog.')], - 'confirm_block': [ opt_str, '', _('Should we show the confirm block contact dialog or not? Empty string means we never show the dialog.')], - 'confirm_custom_status': [ opt_str, '', _('Should we show the confirm custom status dialog or not? Empty string means we never show the dialog.')], - 'enable_negative_priority': [ opt_bool, False, _('If True, you will be able to set a negative priority to your account in account modification window. BE CAREFUL, when you are logged in with a negative priority, you will NOT receive any message from your server.')], - 'use_gnomekeyring': [opt_bool, True, _('If True, Gajim will use Gnome Keyring (if available) to store account passwords.')], - 'use_kwalletcli': [opt_bool, True, _('If True, Gajim will use KDE Wallet (if kwalletcli is available) to store account passwords.')], - 'show_contacts_number': [opt_bool, True, _('If True, Gajim will show number of online and total contacts in account and group rows.')], - 'treat_incoming_messages': [ opt_str, '', _('Can be empty, \'chat\' or \'normal\'. If not empty, treat all incoming messages as if they were of this type')], - 'scroll_roster_to_last_message': [opt_bool, True, _('If True, Gajim will scroll and select the contact who sent you the last message, if chat window is not already opened.')], - 'use_latex': [opt_bool, False, _('If True, Gajim will convert string between $$ and $$ to an image using dvips and convert before insterting it in chat window.')], - 'change_status_window_timeout': [opt_int, 15, _('Time of inactivity needed before the change status window closes down.')], - 'max_conversation_lines': [opt_int, 500, _('Maximum number of lines that are printed in conversations. Oldest lines are cleared.')], - 'attach_notifications_to_systray': [opt_bool, False, _('If True, notification windows from notification-daemon will be attached to systray icon.')], - 'check_idle_every_foo_seconds': [opt_int, 2, _('Choose interval between 2 checks of idleness.')], - 'latex_png_dpi': [opt_str, '108',_('Change the value to change the size of latex formulas displayed. The higher is larger.') ], - 'uri_schemes': [opt_str, 'aaa aaas acap cap cid crid data dav dict dns fax file ftp go gopher h323 http https icap im imap info ipp iris iris.beep iris.xpc iris.xpcs iris.lwz ldap mid modem msrp msrps mtqp mupdate news nfs nntp opaquelocktoken pop pres rtsp service shttp sip sips snmp soap.beep soap.beeps tag tel telnet tftp thismessage tip tv urn vemmi xmlrpc.beep xmlrpc.beeps z39.50r z39.50s about cvs daap ed2k feed fish git iax2 irc ircs ldaps magnet mms rsync ssh svn sftp smb webcal', _('Valid uri schemes. Only schemes in this list will be accepted as "real" uri. (mailto and xmpp are handled separately)'), True], - 'ask_offline_status_on_connection': [ opt_bool, False, _('Ask offline status message to all offline contacts when connection to an accoutn is established. WARNING: This causes a lot of requests to be sent!') ], - 'shell_like_completion': [ opt_bool, False, _('If True, completion in groupchats will be like a shell auto-completion')], - 'show_self_contact': [opt_str, 'when_other_resource', _('When is self contact row displayed. Can be "always", "when_other_resource" or "never"'), True], - } + _('Controls the window where new messages are placed.\n\'always\' - All messages are sent to a single window.\n\'always_with_roster\' - Like \'always\' but the messages are in a single window along with the roster.\n\'never\' - All messages get their own window.\n\'peracct\' - Messages for each account are sent to a specific window.\n\'pertype\' - Each message type (e.g., chats vs. groupchats) are sent to a specific window.')], + 'show_avatar_in_chat': [opt_bool, True, _('If False, you will no longer see the avatar in the chat window.')], + 'escape_key_closes': [opt_bool, True, _('If True, pressing the escape key closes a tab/window.')], + 'compact_view': [opt_bool, False, _('Hides the buttons in chat windows.')], + 'hide_groupchat_banner': [opt_bool, False, _('Hides the banner in a group chat window')], + 'hide_chat_banner': [opt_bool, False, _('Hides the banner in two persons chat window')], + 'hide_groupchat_occupants_list': [opt_bool, False, _('Hides the group chat occupants list in group chat window.')], + 'chat_merge_consecutive_nickname': [opt_bool, False, _('In a chat, show the nickname at the beginning of a line only when it\'s not the same person talking than in previous message.')], + 'chat_merge_consecutive_nickname_indent': [opt_str, ' ', _('Indentation when using merge consecutive nickname.')], + 'use_smooth_scrolling': [opt_bool, True, _('Smooth scroll message in conversation window')], + 'gc_nicknames_colors': [ opt_str, '#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000', _('List of colors, separated by ":", that will be used to color nicknames in group chats.'), True ], + 'ctrl_tab_go_to_next_composing': [opt_bool, True, _('Ctrl-Tab go to next composing tab when none is unread.')], + 'confirm_metacontacts': [ opt_str, '', _('Should we show the confirm metacontacts creation dialog or not? Empty string means we never show the dialog.')], + 'confirm_block': [ opt_str, '', _('Should we show the confirm block contact dialog or not? Empty string means we never show the dialog.')], + 'confirm_custom_status': [ opt_str, '', _('Should we show the confirm custom status dialog or not? Empty string means we never show the dialog.')], + 'enable_negative_priority': [ opt_bool, False, _('If True, you will be able to set a negative priority to your account in account modification window. BE CAREFUL, when you are logged in with a negative priority, you will NOT receive any message from your server.')], + 'use_gnomekeyring': [opt_bool, True, _('If True, Gajim will use Gnome Keyring (if available) to store account passwords.')], + 'use_kwalletcli': [opt_bool, True, _('If True, Gajim will use KDE Wallet (if kwalletcli is available) to store account passwords.')], + 'show_contacts_number': [opt_bool, True, _('If True, Gajim will show number of online and total contacts in account and group rows.')], + 'treat_incoming_messages': [ opt_str, '', _('Can be empty, \'chat\' or \'normal\'. If not empty, treat all incoming messages as if they were of this type')], + 'scroll_roster_to_last_message': [opt_bool, True, _('If True, Gajim will scroll and select the contact who sent you the last message, if chat window is not already opened.')], + 'use_latex': [opt_bool, False, _('If True, Gajim will convert string between $$ and $$ to an image using dvips and convert before insterting it in chat window.')], + 'change_status_window_timeout': [opt_int, 15, _('Time of inactivity needed before the change status window closes down.')], + 'max_conversation_lines': [opt_int, 500, _('Maximum number of lines that are printed in conversations. Oldest lines are cleared.')], + 'attach_notifications_to_systray': [opt_bool, False, _('If True, notification windows from notification-daemon will be attached to systray icon.')], + 'check_idle_every_foo_seconds': [opt_int, 2, _('Choose interval between 2 checks of idleness.')], + 'latex_png_dpi': [opt_str, '108', _('Change the value to change the size of latex formulas displayed. The higher is larger.') ], + 'uri_schemes': [opt_str, 'aaa aaas acap cap cid crid data dav dict dns fax file ftp go gopher h323 http https icap im imap info ipp iris iris.beep iris.xpc iris.xpcs iris.lwz ldap mid modem msrp msrps mtqp mupdate news nfs nntp opaquelocktoken pop pres rtsp service shttp sip sips snmp soap.beep soap.beeps tag tel telnet tftp thismessage tip tv urn vemmi xmlrpc.beep xmlrpc.beeps z39.50r z39.50s about cvs daap ed2k feed fish git iax2 irc ircs ldaps magnet mms rsync ssh svn sftp smb webcal', _('Valid uri schemes. Only schemes in this list will be accepted as "real" uri. (mailto and xmpp are handled separately)'), True], + 'ask_offline_status_on_connection': [ opt_bool, False, _('Ask offline status message to all offline contacts when connection to an accoutn is established. WARNING: This causes a lot of requests to be sent!') ], + 'shell_like_completion': [ opt_bool, False, _('If True, completion in groupchats will be like a shell auto-completion')], + 'show_self_contact': [opt_str, 'when_other_resource', _('When is self contact row displayed. Can be "always", "when_other_resource" or "never"'), True], + } - __options_per_key = { - 'accounts': ({ - 'name': [ opt_str, '', '', True ], - 'hostname': [ opt_str, '', '', True ], - 'anonymous_auth': [ opt_bool, False ], - 'savepass': [ opt_bool, False ], - 'password': [ opt_str, '' ], - 'resource': [ opt_str, 'gajim', '', True ], - 'priority': [ opt_int, 5, '', True ], - 'adjust_priority_with_status': [ opt_bool, True, _('Priority will change automatically according to your status. Priorities are defined in autopriority_* options.') ], - 'autopriority_online': [ opt_int, 50], - 'autopriority_chat': [ opt_int, 50], - 'autopriority_away': [ opt_int, 40], - 'autopriority_xa': [ opt_int, 30], - 'autopriority_dnd': [ opt_int, 20], - 'autopriority_invisible': [ opt_int, 10], - 'autoconnect': [ opt_bool, False, '', True ], - 'autoconnect_as': [ opt_str, 'online', _('Status used to autoconnect as. Can be online, chat, away, xa, dnd, invisible. NOTE: this option is used only if restore_last_status is disabled'), True ], - 'restore_last_status': [ opt_bool, False, _('If enabled, restore the last status that was used.') ], - 'autoreconnect': [ opt_bool, True ], - 'autoauth': [ opt_bool, False, _('If True, Contacts requesting authorization will be automatically accepted.')], - 'active': [ opt_bool, True, _('If False, this account will be disabled and will not appear in roster window.'), True], - 'proxy': [ opt_str, '', '', True ], - 'keyid': [ opt_str, '', '', True ], - 'gpg_sign_presence': [ opt_bool, True, _('If disabled, don\'t sign presences with GPG key, even if GPG is configured.') ], - 'keyname': [ opt_str, '', '', True ], - 'enable_esessions': [opt_bool, True, _('Enable ESessions encryption for this account.')], - 'autonegotiate_esessions': [opt_bool, True, _('Should Gajim automatically start an encrypted session when possible?')], - 'connection_types': [ opt_str, 'tls ssl plain', _('Ordered list (space separated) of connection type to try. Can contain tls, ssl or plain')], - 'warn_when_plaintext_connection': [ opt_bool, True, _('Show a warning dialog before sending password on an plaintext connection.') ], - 'warn_when_insecure_ssl_connection': [ opt_bool, True, _('Show a warning dialog before using standard SSL library.') ], - 'ssl_fingerprint_sha1': [ opt_str, '', '', True ], - 'ignore_ssl_errors': [ opt_str, '', _('Space separated list of ssl errors to ignore.') ], - 'use_srv': [ opt_bool, True, '', True ], - 'use_custom_host': [ opt_bool, False, '', True ], - 'custom_port': [ opt_int, 5222, '', True ], - 'custom_host': [ opt_str, '', '', True ], - 'sync_with_global_status': [ opt_bool, False, ], - 'no_log_for': [ opt_str, '' ], - 'minimized_gc': [ opt_str, '' ], - 'attached_gpg_keys': [ opt_str, '' ], - 'keep_alives_enabled': [ opt_bool, True, _('Whitespace sent after inactivity')], - 'ping_alives_enabled': [ opt_bool, True, _('XMPP ping sent after inactivity')], - # send keepalive every N seconds of inactivity - 'keep_alive_every_foo_secs': [ opt_int, 55 ], - 'ping_alive_every_foo_secs': [ opt_int, 120 ], - 'time_for_ping_alive_answer': [ opt_int, 60, _('How many seconds to wait for the answer of ping alive packet before we try to reconnect.') ], - # try for 1 minutes before giving up (aka. timeout after those seconds) - 'try_connecting_for_foo_secs': [ opt_int, 60 ], - 'http_auth': [opt_str, 'ask'], # yes, no, ask - 'dont_ack_subscription': [opt_bool, False, _('Jabberd2 workaround')], - # proxy65 for FT - 'file_transfer_proxies': [opt_str, 'proxy.eu.jabber.org, proxy.jabber.ru, proxy.jabbim.cz'], - 'use_ft_proxies': [opt_bool, True, _('If checked, Gajim will use your IP and proxies defined in file_transfer_proxies option for file transfer.'), True], - 'msgwin-x-position': [opt_int, -1], # Default is to let the wm decide - 'msgwin-y-position': [opt_int, -1], # Default is to let the wm decide - 'msgwin-width': [opt_int, 480], - 'msgwin-height': [opt_int, 440], - 'listen_to_network_manager' : [opt_bool, True], - 'is_zeroconf': [opt_bool, False], - 'last_status': [opt_str, 'online'], - 'last_status_msg': [opt_str, ''], - 'zeroconf_first_name': [ opt_str, '', '', True ], - 'zeroconf_last_name': [ opt_str, '', '', True ], - 'zeroconf_jabber_id': [ opt_str, '', '', True ], - 'zeroconf_email': [ opt_str, '', '', True ], - 'use_env_http_proxy' : [opt_bool, False], - 'answer_receipts' : [opt_bool, True, _('Answer to receipt requests')], - 'request_receipt' : [opt_bool, True, _('Sent receipt requests')], - 'publish_tune': [opt_bool, False], - 'subscribe_mood': [opt_bool, True], - 'subscribe_activity': [opt_bool, True], - 'subscribe_tune': [opt_bool, True], - 'subscribe_nick': [opt_bool, True], - 'ignore_unknown_contacts': [ opt_bool, False ], - 'send_os_info': [ opt_bool, True ], - 'log_encrypted_sessions': [opt_bool, True, _('When negotiating an encrypted session, should Gajim assume you want your messages to be logged?')], - 'roster_version': [opt_str, ''], - 'last_archiving_time': [opt_str, '1970-01-01T00:00:00Z', _('Last time we syncronized with logs from server.')], - }, {}), - 'statusmsg': ({ - 'message': [ opt_str, '' ], - 'activity': [ opt_str, '' ], - 'subactivity': [ opt_str, '' ], - 'activity_text': [ opt_str, '' ], - 'mood': [ opt_str, '' ], - 'mood_text': [ opt_str, '' ], - }, {}), - 'defaultstatusmsg': ({ - 'enabled': [ opt_bool, False ], - 'message': [ opt_str, '' ], - }, {}), - 'soundevents': ({ - 'enabled': [ opt_bool, True ], - 'path': [ opt_str, '' ], - }, {}), - 'proxies': ({ - 'type': [ opt_str, 'http' ], - 'host': [ opt_str, '' ], - 'port': [ opt_int, 3128 ], - 'useauth': [ opt_bool, False ], - 'user': [ opt_str, '' ], - 'pass': [ opt_str, '' ], - 'bosh_uri': [ opt_str, '' ], - 'bosh_useproxy': [ opt_bool, False ], - 'bosh_wait': [ opt_int, 30 ], - 'bosh_hold': [ opt_int, 2 ], - 'bosh_content': [ opt_str, 'text/xml; charset=utf-8' ], - 'bosh_http_pipelining': [ opt_bool, False ], - 'bosh_wait_for_restart_response': [ opt_bool, False ], - }, {}), - 'themes': ({ - 'accounttextcolor': [ opt_color, 'black', '', True ], - 'accountbgcolor': [ opt_color, 'white', '', True ], - 'accountfont': [ opt_str, '', '', True ], - 'accountfontattrs': [ opt_str, 'B', '', True ], - 'grouptextcolor': [ opt_color, 'black', '', True ], - 'groupbgcolor': [ opt_color, 'white', '', True ], - 'groupfont': [ opt_str, '', '', True ], - 'groupfontattrs': [ opt_str, 'I', '', True ], - 'contacttextcolor': [ opt_color, 'black', '', True ], - 'contactbgcolor': [ opt_color, 'white', '', True ], - 'contactfont': [ opt_str, '', '', True ], - 'contactfontattrs': [ opt_str, '', '', True ], - 'bannertextcolor': [ opt_color, 'black', '', True ], - 'bannerbgcolor': [ opt_color, '', '', True ], - 'bannerfont': [ opt_str, '', '', True ], - 'bannerfontattrs': [ opt_str, 'B', '', True ], + __options_per_key = { + 'accounts': ({ + 'name': [ opt_str, '', '', True ], + 'hostname': [ opt_str, '', '', True ], + 'anonymous_auth': [ opt_bool, False ], + 'savepass': [ opt_bool, False ], + 'password': [ opt_str, '' ], + 'resource': [ opt_str, 'gajim', '', True ], + 'priority': [ opt_int, 5, '', True ], + 'adjust_priority_with_status': [ opt_bool, True, _('Priority will change automatically according to your status. Priorities are defined in autopriority_* options.') ], + 'autopriority_online': [ opt_int, 50], + 'autopriority_chat': [ opt_int, 50], + 'autopriority_away': [ opt_int, 40], + 'autopriority_xa': [ opt_int, 30], + 'autopriority_dnd': [ opt_int, 20], + 'autopriority_invisible': [ opt_int, 10], + 'autoconnect': [ opt_bool, False, '', True ], + 'autoconnect_as': [ opt_str, 'online', _('Status used to autoconnect as. Can be online, chat, away, xa, dnd, invisible. NOTE: this option is used only if restore_last_status is disabled'), True ], + 'restore_last_status': [ opt_bool, False, _('If enabled, restore the last status that was used.') ], + 'autoreconnect': [ opt_bool, True ], + 'autoauth': [ opt_bool, False, _('If True, Contacts requesting authorization will be automatically accepted.')], + 'active': [ opt_bool, True, _('If False, this account will be disabled and will not appear in roster window.'), True], + 'proxy': [ opt_str, '', '', True ], + 'keyid': [ opt_str, '', '', True ], + 'gpg_sign_presence': [ opt_bool, True, _('If disabled, don\'t sign presences with GPG key, even if GPG is configured.') ], + 'keyname': [ opt_str, '', '', True ], + 'enable_esessions': [opt_bool, True, _('Enable ESessions encryption for this account.')], + 'autonegotiate_esessions': [opt_bool, True, _('Should Gajim automatically start an encrypted session when possible?')], + 'connection_types': [ opt_str, 'tls ssl plain', _('Ordered list (space separated) of connection type to try. Can contain tls, ssl or plain')], + 'warn_when_plaintext_connection': [ opt_bool, True, _('Show a warning dialog before sending password on an plaintext connection.') ], + 'warn_when_insecure_ssl_connection': [ opt_bool, True, _('Show a warning dialog before using standard SSL library.') ], + 'ssl_fingerprint_sha1': [ opt_str, '', '', True ], + 'ignore_ssl_errors': [ opt_str, '', _('Space separated list of ssl errors to ignore.') ], + 'use_srv': [ opt_bool, True, '', True ], + 'use_custom_host': [ opt_bool, False, '', True ], + 'custom_port': [ opt_int, 5222, '', True ], + 'custom_host': [ opt_str, '', '', True ], + 'sync_with_global_status': [ opt_bool, False, ], + 'no_log_for': [ opt_str, '' ], + 'minimized_gc': [ opt_str, '' ], + 'attached_gpg_keys': [ opt_str, '' ], + 'keep_alives_enabled': [ opt_bool, True, _('Whitespace sent after inactivity')], + 'ping_alives_enabled': [ opt_bool, True, _('XMPP ping sent after inactivity')], + # send keepalive every N seconds of inactivity + 'keep_alive_every_foo_secs': [ opt_int, 55 ], + 'ping_alive_every_foo_secs': [ opt_int, 120 ], + 'time_for_ping_alive_answer': [ opt_int, 60, _('How many seconds to wait for the answer of ping alive packet before we try to reconnect.') ], + # try for 1 minutes before giving up (aka. timeout after those seconds) + 'try_connecting_for_foo_secs': [ opt_int, 60 ], + 'http_auth': [opt_str, 'ask'], # yes, no, ask + 'dont_ack_subscription': [opt_bool, False, _('Jabberd2 workaround')], + # proxy65 for FT + 'file_transfer_proxies': [opt_str, 'proxy.eu.jabber.org, proxy.jabber.ru, proxy.jabbim.cz'], + 'use_ft_proxies': [opt_bool, True, _('If checked, Gajim will use your IP and proxies defined in file_transfer_proxies option for file transfer.'), True], + 'msgwin-x-position': [opt_int, -1], # Default is to let the wm decide + 'msgwin-y-position': [opt_int, -1], # Default is to let the wm decide + 'msgwin-width': [opt_int, 480], + 'msgwin-height': [opt_int, 440], + 'listen_to_network_manager': [opt_bool, True], + 'is_zeroconf': [opt_bool, False], + 'last_status': [opt_str, 'online'], + 'last_status_msg': [opt_str, ''], + 'zeroconf_first_name': [ opt_str, '', '', True ], + 'zeroconf_last_name': [ opt_str, '', '', True ], + 'zeroconf_jabber_id': [ opt_str, '', '', True ], + 'zeroconf_email': [ opt_str, '', '', True ], + 'use_env_http_proxy': [opt_bool, False], + 'answer_receipts': [opt_bool, True, _('Answer to receipt requests')], + 'request_receipt': [opt_bool, True, _('Sent receipt requests')], + 'publish_tune': [opt_bool, False], + 'subscribe_mood': [opt_bool, True], + 'subscribe_activity': [opt_bool, True], + 'subscribe_tune': [opt_bool, True], + 'subscribe_nick': [opt_bool, True], + 'ignore_unknown_contacts': [ opt_bool, False ], + 'send_os_info': [ opt_bool, True ], + 'log_encrypted_sessions': [opt_bool, True, _('When negotiating an encrypted session, should Gajim assume you want your messages to be logged?')], + 'roster_version': [opt_str, ''], + 'last_archiving_time': [opt_str, '1970-01-01T00:00:00Z', _('Last time we syncronized with logs from server.')], + }, {}), + 'statusmsg': ({ + 'message': [ opt_str, '' ], + 'activity': [ opt_str, '' ], + 'subactivity': [ opt_str, '' ], + 'activity_text': [ opt_str, '' ], + 'mood': [ opt_str, '' ], + 'mood_text': [ opt_str, '' ], + }, {}), + 'defaultstatusmsg': ({ + 'enabled': [ opt_bool, False ], + 'message': [ opt_str, '' ], + }, {}), + 'soundevents': ({ + 'enabled': [ opt_bool, True ], + 'path': [ opt_str, '' ], + }, {}), + 'proxies': ({ + 'type': [ opt_str, 'http' ], + 'host': [ opt_str, '' ], + 'port': [ opt_int, 3128 ], + 'useauth': [ opt_bool, False ], + 'user': [ opt_str, '' ], + 'pass': [ opt_str, '' ], + 'bosh_uri': [ opt_str, '' ], + 'bosh_useproxy': [ opt_bool, False ], + 'bosh_wait': [ opt_int, 30 ], + 'bosh_hold': [ opt_int, 2 ], + 'bosh_content': [ opt_str, 'text/xml; charset=utf-8' ], + 'bosh_http_pipelining': [ opt_bool, False ], + 'bosh_wait_for_restart_response': [ opt_bool, False ], + }, {}), + 'themes': ({ + 'accounttextcolor': [ opt_color, 'black', '', True ], + 'accountbgcolor': [ opt_color, 'white', '', True ], + 'accountfont': [ opt_str, '', '', True ], + 'accountfontattrs': [ opt_str, 'B', '', True ], + 'grouptextcolor': [ opt_color, 'black', '', True ], + 'groupbgcolor': [ opt_color, 'white', '', True ], + 'groupfont': [ opt_str, '', '', True ], + 'groupfontattrs': [ opt_str, 'I', '', True ], + 'contacttextcolor': [ opt_color, 'black', '', True ], + 'contactbgcolor': [ opt_color, 'white', '', True ], + 'contactfont': [ opt_str, '', '', True ], + 'contactfontattrs': [ opt_str, '', '', True ], + 'bannertextcolor': [ opt_color, 'black', '', True ], + 'bannerbgcolor': [ opt_color, '', '', True ], + 'bannerfont': [ opt_str, '', '', True ], + 'bannerfontattrs': [ opt_str, 'B', '', True ], - # http://www.pitt.edu/~nisg/cis/web/cgi/rgb.html - 'state_inactive_color': [ opt_color, 'grey62' ], - 'state_composing_color': [ opt_color, 'green4' ], - 'state_paused_color': [ opt_color, 'mediumblue' ], - 'state_gone_color': [ opt_color, 'grey' ], + # http://www.pitt.edu/~nisg/cis/web/cgi/rgb.html + 'state_inactive_color': [ opt_color, 'grey62' ], + 'state_composing_color': [ opt_color, 'green4' ], + 'state_paused_color': [ opt_color, 'mediumblue' ], + 'state_gone_color': [ opt_color, 'grey' ], - # MUC chat states - 'state_muc_msg_color': [ opt_color, 'mediumblue' ], - 'state_muc_directed_msg_color': [ opt_color, 'red2' ], - }, {}), - 'contacts': ({ - 'gpg_enabled': [ opt_bool, False, _('Is OpenPGP enabled for this contact?')], - 'autonegotiate_esessions': [opt_bool, True, _('Should Gajim automatically start an encrypted session with this contact when possible?')], - 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')], - }, {}), - 'rooms': ({ - 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')], - }, {}), - 'notifications': ({ - 'event': [opt_str, ''], - 'recipient_type': [opt_str, 'all'], - 'recipients': [opt_str, ''], - 'status': [opt_str, 'all', _('all or space separated status')], - 'tab_opened': [opt_str, 'both', _("'yes', 'no', or 'both'")], - 'sound': [opt_str, '', _("'yes', 'no' or ''")], - 'sound_file': [opt_str, ''], - 'popup': [opt_str, '', _("'yes', 'no' or ''")], - 'auto_open': [opt_str, '', _("'yes', 'no' or ''")], - 'run_command': [opt_bool, False], - 'command': [opt_str, ''], - 'systray': [opt_str, '', _("'yes', 'no' or ''")], - 'roster': [opt_str, '', _("'yes', 'no' or ''")], - 'urgency_hint': [opt_bool, False], - }, {}), - } + # MUC chat states + 'state_muc_msg_color': [ opt_color, 'mediumblue' ], + 'state_muc_directed_msg_color': [ opt_color, 'red2' ], + }, {}), + 'contacts': ({ + 'gpg_enabled': [ opt_bool, False, _('Is OpenPGP enabled for this contact?')], + 'autonegotiate_esessions': [opt_bool, True, _('Should Gajim automatically start an encrypted session with this contact when possible?')], + 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')], + }, {}), + 'rooms': ({ + 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')], + }, {}), + 'notifications': ({ + 'event': [opt_str, ''], + 'recipient_type': [opt_str, 'all'], + 'recipients': [opt_str, ''], + 'status': [opt_str, 'all', _('all or space separated status')], + 'tab_opened': [opt_str, 'both', _("'yes', 'no', or 'both'")], + 'sound': [opt_str, '', _("'yes', 'no' or ''")], + 'sound_file': [opt_str, ''], + 'popup': [opt_str, '', _("'yes', 'no' or ''")], + 'auto_open': [opt_str, '', _("'yes', 'no' or ''")], + 'run_command': [opt_bool, False], + 'command': [opt_str, ''], + 'systray': [opt_str, '', _("'yes', 'no' or ''")], + 'roster': [opt_str, '', _("'yes', 'no' or ''")], + 'urgency_hint': [opt_bool, False], + }, {}), + } - statusmsg_default = { - _('Sleeping'): [ 'ZZZZzzzzzZZZZZ', 'inactive', 'sleeping', '', 'sleepy', '' ], - _('Back soon'): [ _('Back in some minutes.'), '', '', '', '', '' ], - _('Eating'): [ _("I'm eating, so leave me a message."), 'eating', 'other', '', '', '' ], - _('Movie'): [ _("I'm watching a movie."), 'relaxing', 'watching_a_movie', '', '', '' ], - _('Working'): [ _("I'm working."), 'working', 'other', '', '', '' ], - _('Phone'): [ _("I'm on the phone."), 'talking', 'on_the_phone', '', '', '' ], - _('Out'): [ _("I'm out enjoying life."), 'relaxing', 'going_out', '', '', '' ], - '_last_online': ['', '', '', '', '', ''], - '_last_chat': ['', '', '', '', '', ''], - '_last_away': ['', '', '', '', '', ''], - '_last_xa': ['', '', '', '', '', ''], - '_last_dnd': ['', '', '', '', '', ''], - '_last_invisible': ['', '', '', '', '', ''], - '_last_offline': ['', '', '', '', '', ''], - } + statusmsg_default = { + _('Sleeping'): [ 'ZZZZzzzzzZZZZZ', 'inactive', 'sleeping', '', 'sleepy', '' ], + _('Back soon'): [ _('Back in some minutes.'), '', '', '', '', '' ], + _('Eating'): [ _("I'm eating, so leave me a message."), 'eating', 'other', '', '', '' ], + _('Movie'): [ _("I'm watching a movie."), 'relaxing', 'watching_a_movie', '', '', '' ], + _('Working'): [ _("I'm working."), 'working', 'other', '', '', '' ], + _('Phone'): [ _("I'm on the phone."), 'talking', 'on_the_phone', '', '', '' ], + _('Out'): [ _("I'm out enjoying life."), 'relaxing', 'going_out', '', '', '' ], + '_last_online': ['', '', '', '', '', ''], + '_last_chat': ['', '', '', '', '', ''], + '_last_away': ['', '', '', '', '', ''], + '_last_xa': ['', '', '', '', '', ''], + '_last_dnd': ['', '', '', '', '', ''], + '_last_invisible': ['', '', '', '', '', ''], + '_last_offline': ['', '', '', '', '', ''], + } - defaultstatusmsg_default = { - 'online': [ False, _("I'm available.") ], - 'chat': [ False, _("I'm free for chat.") ], - 'away': [ False, _('Be right back.') ], - 'xa': [ False, _("I'm not available.") ], - 'dnd': [ False, _('Do not disturb.') ], - 'invisible': [ False, _('Bye!') ], - 'offline': [ False, _('Bye!') ], - } + defaultstatusmsg_default = { + 'online': [ False, _("I'm available.") ], + 'chat': [ False, _("I'm free for chat.") ], + 'away': [ False, _('Be right back.') ], + 'xa': [ False, _("I'm not available.") ], + 'dnd': [ False, _('Do not disturb.') ], + 'invisible': [ False, _('Bye!') ], + 'offline': [ False, _('Bye!') ], + } - soundevents_default = { - 'first_message_received': [ True, 'message1.wav' ], - 'next_message_received_focused': [ True, 'message2.wav' ], - 'next_message_received_unfocused': [ True, 'message2.wav' ], - 'contact_connected': [ True, 'connected.wav' ], - 'contact_disconnected': [ True, 'disconnected.wav' ], - 'message_sent': [ True, 'sent.wav' ], - 'muc_message_highlight': [ True, 'gc_message1.wav', _('Sound to play when a group chat message contains one of the words in muc_highlight_words, or when a group chat message contains your nickname.')], - 'muc_message_received': [ False, 'gc_message2.wav', _('Sound to play when any MUC message arrives.') ], - 'gmail_received': [ False, 'message1.wav' ], - } + soundevents_default = { + 'first_message_received': [ True, 'message1.wav' ], + 'next_message_received_focused': [ True, 'message2.wav' ], + 'next_message_received_unfocused': [ True, 'message2.wav' ], + 'contact_connected': [ True, 'connected.wav' ], + 'contact_disconnected': [ True, 'disconnected.wav' ], + 'message_sent': [ True, 'sent.wav' ], + 'muc_message_highlight': [ True, 'gc_message1.wav', _('Sound to play when a group chat message contains one of the words in muc_highlight_words, or when a group chat message contains your nickname.')], + 'muc_message_received': [ False, 'gc_message2.wav', _('Sound to play when any MUC message arrives.') ], + 'gmail_received': [ False, 'message1.wav' ], + } - themes_default = { - # sorted alphanum - _('default'): [ '', '', '', 'B', '', '','', 'I', '', '', '', '', '','', - '', 'B' ], + themes_default = { + # sorted alphanum + _('default'): [ '', '', '', 'B', '', '', '', 'I', '', '', '', '', '', '', + '', 'B' ], - _('green'): [ '', '#94aa8c', '', 'B', '#0000ff', '#eff3e7', - '', 'I', '#000000', '', '', '', '', - '#94aa8c', '', 'B' ], + _('green'): [ '', '#94aa8c', '', 'B', '#0000ff', '#eff3e7', + '', 'I', '#000000', '', '', '', '', + '#94aa8c', '', 'B' ], - _('grocery'): [ '', '#6bbe18', '', 'B', '#12125a', '#ceefad', - '', 'I', '#000000', '#efb26b', '', '', '', - '#108abd', '', 'B' ], + _('grocery'): [ '', '#6bbe18', '', 'B', '#12125a', '#ceefad', + '', 'I', '#000000', '#efb26b', '', '', '', + '#108abd', '', 'B' ], - _('human'): [ '', '#996442', '', 'B', '#ab5920', '#e3ca94', - '', 'I', '#000000', '', '', '', '', - '#996442', '', 'B' ], + _('human'): [ '', '#996442', '', 'B', '#ab5920', '#e3ca94', + '', 'I', '#000000', '', '', '', '', + '#996442', '', 'B' ], - _('marine'): [ '', '#918caa', '', 'B', '', '#e9e7f3', - '', 'I', '#000000', '', '', '', '', - '#918caa', '', 'B' ], + _('marine'): [ '', '#918caa', '', 'B', '', '#e9e7f3', + '', 'I', '#000000', '', '', '', '', + '#918caa', '', 'B' ], - } + } - def foreach(self, cb, data = None): - for opt in self.__options: - cb(data, opt, None, self.__options[opt]) - for opt in self.__options_per_key: - cb(data, opt, None, None) - dict_ = self.__options_per_key[opt][1] - for opt2 in dict_.keys(): - cb(data, opt2, [opt], None) - for opt3 in dict_[opt2]: - cb(data, opt3, [opt, opt2], dict_[opt2][opt3]) + def foreach(self, cb, data = None): + for opt in self.__options: + cb(data, opt, None, self.__options[opt]) + for opt in self.__options_per_key: + cb(data, opt, None, None) + dict_ = self.__options_per_key[opt][1] + for opt2 in dict_.keys(): + cb(data, opt2, [opt], None) + for opt3 in dict_[opt2]: + cb(data, opt3, [opt, opt2], dict_[opt2][opt3]) - def get_children(self, node=None): - ''' Tree-like interface ''' - if node is None: - for child, option in self.__options.iteritems(): - yield (child, ), option - for grandparent in self.__options_per_key: - yield (grandparent, ), None - elif len(node) == 1: - grandparent, = node - for parent in self.__options_per_key[grandparent][1]: - yield (grandparent, parent), None - elif len(node) == 2: - grandparent, parent = node - children = self.__options_per_key[grandparent][1][parent] - for child, option in children.iteritems(): - yield (grandparent, parent, child), option - else: - raise ValueError('Invalid node') + def get_children(self, node=None): + ''' Tree-like interface ''' + if node is None: + for child, option in self.__options.iteritems(): + yield (child, ), option + for grandparent in self.__options_per_key: + yield (grandparent, ), None + elif len(node) == 1: + grandparent, = node + for parent in self.__options_per_key[grandparent][1]: + yield (grandparent, parent), None + elif len(node) == 2: + grandparent, parent = node + children = self.__options_per_key[grandparent][1][parent] + for child, option in children.iteritems(): + yield (grandparent, parent, child), option + else: + raise ValueError('Invalid node') - def is_valid_int(self, val): - try: - ival = int(val) - except Exception: - return None - return ival + def is_valid_int(self, val): + try: + ival = int(val) + except Exception: + return None + return ival - def is_valid_bool(self, val): - if val == 'True': - return True - elif val == 'False': - return False - else: - ival = self.is_valid_int(val) - if ival: - return True - elif ival is None: - return None - return False - return None + def is_valid_bool(self, val): + if val == 'True': + return True + elif val == 'False': + return False + else: + ival = self.is_valid_int(val) + if ival: + return True + elif ival is None: + return None + return False + return None - def is_valid_string(self, val): - return val + def is_valid_string(self, val): + return val - def is_valid(self, type_, val): - if not type_: - return None - if type_[0] == 'boolean': - return self.is_valid_bool(val) - elif type_[0] == 'integer': - return self.is_valid_int(val) - elif type_[0] == 'string': - return self.is_valid_string(val) - else: - if re.match(type_[1], val): - return val - else: - return None + def is_valid(self, type_, val): + if not type_: + return None + if type_[0] == 'boolean': + return self.is_valid_bool(val) + elif type_[0] == 'integer': + return self.is_valid_int(val) + elif type_[0] == 'string': + return self.is_valid_string(val) + else: + if re.match(type_[1], val): + return val + else: + return None - def set(self, optname, value): - if optname not in self.__options: -# raise RuntimeError, 'option %s does not exist' % optname - return - opt = self.__options[optname] - value = self.is_valid(opt[OPT_TYPE], value) - if value is None: -# raise RuntimeError, 'value of %s cannot be None' % optname - return + def set(self, optname, value): + if optname not in self.__options: +# raise RuntimeError, 'option %s does not exist' % optname + return + opt = self.__options[optname] + value = self.is_valid(opt[OPT_TYPE], value) + if value is None: +# raise RuntimeError, 'value of %s cannot be None' % optname + return - opt[OPT_VAL] = value + opt[OPT_VAL] = value - def get(self, optname = None): - if not optname: - return self.__options.keys() - if optname not in self.__options: - return None - return self.__options[optname][OPT_VAL] + def get(self, optname = None): + if not optname: + return self.__options.keys() + if optname not in self.__options: + return None + return self.__options[optname][OPT_VAL] - def get_desc(self, optname): - if optname not in self.__options: - return None - if len(self.__options[optname]) > OPT_DESC: - return self.__options[optname][OPT_DESC] + def get_desc(self, optname): + if optname not in self.__options: + return None + if len(self.__options[optname]) > OPT_DESC: + return self.__options[optname][OPT_DESC] - def get_restart(self, optname): - if optname not in self.__options: - return None - if len(self.__options[optname]) > OPT_RESTART: - return self.__options[optname][OPT_RESTART] + def get_restart(self, optname): + if optname not in self.__options: + return None + if len(self.__options[optname]) > OPT_RESTART: + return self.__options[optname][OPT_RESTART] - def add_per(self, typename, name): # per_group_of_option - if typename not in self.__options_per_key: -# raise RuntimeError, 'option %s does not exist' % typename - return + def add_per(self, typename, name): # per_group_of_option + if typename not in self.__options_per_key: +# raise RuntimeError, 'option %s does not exist' % typename + return - opt = self.__options_per_key[typename] - if name in opt[1]: - # we already have added group name before - return 'you already have added %s before' % name - opt[1][name] = copy.deepcopy(opt[0]) + opt = self.__options_per_key[typename] + if name in opt[1]: + # we already have added group name before + return 'you already have added %s before' % name + opt[1][name] = copy.deepcopy(opt[0]) - def del_per(self, typename, name, subname = None): # per_group_of_option - if typename not in self.__options_per_key: -# raise RuntimeError, 'option %s does not exist' % typename - return + def del_per(self, typename, name, subname = None): # per_group_of_option + if typename not in self.__options_per_key: +# raise RuntimeError, 'option %s does not exist' % typename + return - opt = self.__options_per_key[typename] - if subname is None: - del opt[1][name] - # if subname is specified, delete the item in the group. - elif subname in opt[1][name]: - del opt[1][name][subname] + opt = self.__options_per_key[typename] + if subname is None: + del opt[1][name] + # if subname is specified, delete the item in the group. + elif subname in opt[1][name]: + del opt[1][name][subname] - def set_per(self, optname, key, subname, value): # per_group_of_option - if optname not in self.__options_per_key: -# raise RuntimeError, 'option %s does not exist' % optname - return - if not key: - return - dict_ = self.__options_per_key[optname][1] - if key not in dict_: -# raise RuntimeError, '%s is not a key of %s' % (key, dict_) - self.add_per(optname, key) - obj = dict_[key] - if subname not in obj: -# raise RuntimeError, '%s is not a key of %s' % (subname, obj) - return - subobj = obj[subname] - value = self.is_valid(subobj[OPT_TYPE], value) - if value is None: -# raise RuntimeError, '%s of %s cannot be None' % optname - return - subobj[OPT_VAL] = value + def set_per(self, optname, key, subname, value): # per_group_of_option + if optname not in self.__options_per_key: +# raise RuntimeError, 'option %s does not exist' % optname + return + if not key: + return + dict_ = self.__options_per_key[optname][1] + if key not in dict_: +# raise RuntimeError, '%s is not a key of %s' % (key, dict_) + self.add_per(optname, key) + obj = dict_[key] + if subname not in obj: +# raise RuntimeError, '%s is not a key of %s' % (subname, obj) + return + subobj = obj[subname] + value = self.is_valid(subobj[OPT_TYPE], value) + if value is None: +# raise RuntimeError, '%s of %s cannot be None' % optname + return + subobj[OPT_VAL] = value - def get_per(self, optname, key = None, subname = None): # per_group_of_option - if optname not in self.__options_per_key: - return None - dict_ = self.__options_per_key[optname][1] - if not key: - return dict_.keys() - if key not in dict_: - if optname in self.__options_per_key \ - and subname in self.__options_per_key[optname][0]: - return self.__options_per_key \ - [optname][0][subname][1] - return None - obj = dict_[key] - if not subname: - return obj - if subname not in obj: - return None - return obj[subname][OPT_VAL] + def get_per(self, optname, key = None, subname = None): # per_group_of_option + if optname not in self.__options_per_key: + return None + dict_ = self.__options_per_key[optname][1] + if not key: + return dict_.keys() + if key not in dict_: + if optname in self.__options_per_key \ + and subname in self.__options_per_key[optname][0]: + return self.__options_per_key \ + [optname][0][subname][1] + return None + obj = dict_[key] + if not subname: + return obj + if subname not in obj: + return None + return obj[subname][OPT_VAL] - def get_desc_per(self, optname, key = None, subname = None): - if optname not in self.__options_per_key: - return None - dict_ = self.__options_per_key[optname][1] - if not key: - return None - if key not in dict_: - return None - obj = dict_[key] - if not subname: - return None - if subname not in obj: - return None - if len(obj[subname]) > OPT_DESC: - return obj[subname][OPT_DESC] - return None + def get_desc_per(self, optname, key = None, subname = None): + if optname not in self.__options_per_key: + return None + dict_ = self.__options_per_key[optname][1] + if not key: + return None + if key not in dict_: + return None + obj = dict_[key] + if not subname: + return None + if subname not in obj: + return None + if len(obj[subname]) > OPT_DESC: + return obj[subname][OPT_DESC] + return None - def get_restart_per(self, optname, key = None, subname = None): - if optname not in self.__options_per_key: - return False - dict_ = self.__options_per_key[optname][1] - if not key: - return False - if key not in dict_: - return False - obj = dict_[key] - if not subname: - return False - if subname not in obj: - return False - if len(obj[subname]) > OPT_RESTART: - return obj[subname][OPT_RESTART] - return False + def get_restart_per(self, optname, key = None, subname = None): + if optname not in self.__options_per_key: + return False + dict_ = self.__options_per_key[optname][1] + if not key: + return False + if key not in dict_: + return False + obj = dict_[key] + if not subname: + return False + if subname not in obj: + return False + if len(obj[subname]) > OPT_RESTART: + return obj[subname][OPT_RESTART] + return False - def should_log(self, account, jid): - '''should conversations between a local account and a remote jid be - logged?''' - no_log_for = self.get_per('accounts', account, 'no_log_for') + def should_log(self, account, jid): + '''should conversations between a local account and a remote jid be + logged?''' + no_log_for = self.get_per('accounts', account, 'no_log_for') - if not no_log_for: - no_log_for = '' + if not no_log_for: + no_log_for = '' - no_log_for = no_log_for.split() + no_log_for = no_log_for.split() - return (account not in no_log_for) and (jid not in no_log_for) + return (account not in no_log_for) and (jid not in no_log_for) - def __init__(self): - #init default values - for event in self.soundevents_default: - default = self.soundevents_default[event] - self.add_per('soundevents', event) - self.set_per('soundevents', event, 'enabled', default[0]) - self.set_per('soundevents', event, 'path', default[1]) + def __init__(self): + #init default values + for event in self.soundevents_default: + default = self.soundevents_default[event] + self.add_per('soundevents', event) + self.set_per('soundevents', event, 'enabled', default[0]) + self.set_per('soundevents', event, 'path', default[1]) - for status in self.defaultstatusmsg_default: - default = self.defaultstatusmsg_default[status] - self.add_per('defaultstatusmsg', status) - self.set_per('defaultstatusmsg', status, 'enabled', default[0]) - self.set_per('defaultstatusmsg', status, 'message', default[1]) - -# vim: se ts=3: + for status in self.defaultstatusmsg_default: + default = self.defaultstatusmsg_default[status] + self.add_per('defaultstatusmsg', status) + self.set_per('defaultstatusmsg', status, 'enabled', default[0]) + self.set_per('defaultstatusmsg', status, 'message', default[1]) diff --git a/src/common/configpaths.py b/src/common/configpaths.py index 19e45aef3..c6a424de5 100644 --- a/src/common/configpaths.py +++ b/src/common/configpaths.py @@ -46,114 +46,112 @@ import tempfile # not displayed to the user, Unicode is not really necessary here. def fse(s): - '''Convert from filesystem encoding if not already Unicode''' - return unicode(s, sys.getfilesystemencoding()) + '''Convert from filesystem encoding if not already Unicode''' + return unicode(s, sys.getfilesystemencoding()) def windowsify(s): - if os.name == 'nt': - return s.capitalize() - return s + if os.name == 'nt': + return s.capitalize() + return s class ConfigPaths: - def __init__(self, root=None): - self.root = root - self.paths = {} + def __init__(self, root=None): + self.root = root + self.paths = {} - if self.root is None: - if os.name == 'nt': - try: - # Documents and Settings\[User Name]\Application Data\Gajim + if self.root is None: + if os.name == 'nt': + try: + # Documents and Settings\[User Name]\Application Data\Gajim - # How are we supposed to know what encoding the environment - # variable 'appdata' is in? Assuming it to be in filesystem - # encoding. - self.root = os.path.join(fse(os.environ[u'appdata']), u'Gajim') - except KeyError: - # win9x, in cwd - self.root = u'.' - else: # Unices - # Pass in an Unicode string, and hopefully get one back. - self.root = os.path.expanduser(u'~/.gajim') + # How are we supposed to know what encoding the environment + # variable 'appdata' is in? Assuming it to be in filesystem + # encoding. + self.root = os.path.join(fse(os.environ[u'appdata']), u'Gajim') + except KeyError: + # win9x, in cwd + self.root = u'.' + else: # Unices + # Pass in an Unicode string, and hopefully get one back. + self.root = os.path.expanduser(u'~/.gajim') - def add_from_root(self, name, path): - self.paths[name] = (True, path) + def add_from_root(self, name, path): + self.paths[name] = (True, path) - def add(self, name, path): - self.paths[name] = (False, path) + def add(self, name, path): + self.paths[name] = (False, path) - def __getitem__(self, key): - relative, path = self.paths[key] - if not relative: - return path - return os.path.join(self.root, path) + def __getitem__(self, key): + relative, path = self.paths[key] + if not relative: + return path + return os.path.join(self.root, path) - def get(self, key, default=None): - try: - return self[key] - except KeyError: - return default + def get(self, key, default=None): + try: + return self[key] + except KeyError: + return default - def iteritems(self): - for key in self.paths.iterkeys(): - yield (key, self[key]) + def iteritems(self): + for key in self.paths.iterkeys(): + yield (key, self[key]) - def init(self, root = None): - if root is not None: - self.root = root + def init(self, root = None): + if root is not None: + self.root = root - # LOG is deprecated - k = ( 'LOG', 'LOG_DB', 'VCARD', 'AVATAR', 'MY_EMOTS', - 'MY_ICONSETS', 'MY_MOOD_ICONSETS', - 'MY_ACTIVITY_ICONSETS', 'MY_CACERTS') - v = (u'logs', u'logs.db', u'vcards', u'avatars', u'emoticons', - u'iconsets', u'moods', u'activities', u'cacerts.pem') + # LOG is deprecated + k = ( 'LOG', 'LOG_DB', 'VCARD', 'AVATAR', 'MY_EMOTS', + 'MY_ICONSETS', 'MY_MOOD_ICONSETS', + 'MY_ACTIVITY_ICONSETS', 'MY_CACERTS') + v = (u'logs', u'logs.db', u'vcards', u'avatars', u'emoticons', + u'iconsets', u'moods', u'activities', u'cacerts.pem') - if os.name == 'nt': - v = [x.capitalize() for x in v] + if os.name == 'nt': + v = [x.capitalize() for x in v] - for n, p in zip(k, v): - self.add_from_root(n, p) + for n, p in zip(k, v): + self.add_from_root(n, p) - datadir = '' - if u'datadir' in os.environ: - datadir = fse(os.environ[u'datadir']) - if not datadir: - datadir = u'..' - self.add('DATA', os.path.join(datadir, windowsify(u'data'))) - self.add('HOME', fse(os.path.expanduser('~'))) - try: - self.add('TMP', fse(tempfile.gettempdir())) - except IOError, e: - print >> sys.stderr, 'Error opening tmp folder: %s\nUsing %s' % ( - str(e), os.path.expanduser('~')) - self.add('TMP', fse(os.path.expanduser('~'))) + datadir = '' + if u'datadir' in os.environ: + datadir = fse(os.environ[u'datadir']) + if not datadir: + datadir = u'..' + self.add('DATA', os.path.join(datadir, windowsify(u'data'))) + self.add('HOME', fse(os.path.expanduser('~'))) + try: + self.add('TMP', fse(tempfile.gettempdir())) + except IOError, e: + print >> sys.stderr, 'Error opening tmp folder: %s\nUsing %s' % ( + str(e), os.path.expanduser('~')) + self.add('TMP', fse(os.path.expanduser('~'))) - try: - import svn_config - svn_config.configure(self) - except (ImportError, AttributeError): - pass + try: + import svn_config + svn_config.configure(self) + except (ImportError, AttributeError): + pass - # for k, v in paths.iteritems(): - # print "%s: %s" % (repr(k), repr(v)) + # for k, v in paths.iteritems(): + # print "%s: %s" % (repr(k), repr(v)) - def init_profile(self, profile = ''): - conffile = windowsify(u'config') - pidfile = windowsify(u'gajim') - secretsfile = windowsify(u'secrets') + def init_profile(self, profile = ''): + conffile = windowsify(u'config') + pidfile = windowsify(u'gajim') + secretsfile = windowsify(u'secrets') - if len(profile) > 0: - conffile += u'.' + profile - pidfile += u'.' + profile - secretsfile += u'.' + profile - pidfile += u'.pid' - self.add_from_root('CONFIG_FILE', conffile) - self.add_from_root('PID_FILE', pidfile) - self.add_from_root('SECRETS_FILE', secretsfile) + if len(profile) > 0: + conffile += u'.' + profile + pidfile += u'.' + profile + secretsfile += u'.' + profile + pidfile += u'.pid' + self.add_from_root('CONFIG_FILE', conffile) + self.add_from_root('PID_FILE', pidfile) + self.add_from_root('SECRETS_FILE', secretsfile) - # for k, v in paths.iteritems(): - # print "%s: %s" % (repr(k), repr(v)) + # for k, v in paths.iteritems(): + # print "%s: %s" % (repr(k), repr(v)) gajimpaths = ConfigPaths() - -# vim: se ts=3: diff --git a/src/common/connection.py b/src/common/connection.py index 3abb512de..8c27c2b89 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -42,14 +42,14 @@ import locale import hmac try: - randomsource = random.SystemRandom() + randomsource = random.SystemRandom() except Exception: - randomsource = random.Random() - randomsource.seed() + randomsource = random.Random() + randomsource.seed() import signal if os.name != 'nt': - signal.signal(signal.SIGPIPE, signal.SIG_DFL) + signal.signal(signal.SIGPIPE, signal.SIG_DFL) import common.xmpp from common import helpers @@ -100,2088 +100,2086 @@ ssl_error = { } class CommonConnection: - ''' - Common connection class, can be derivated for normal connection or zeroconf - connection - ''' - def __init__(self, name): - self.name = name - # self.connected: - # 0=>offline, - # 1=>connection in progress, - # 2=>online - # 3=>free for chat - # ... - self.connected = 0 - self.connection = None # xmpppy ClientCommon instance - self.on_purpose = False - self.is_zeroconf = False - self.password = '' - self.server_resource = self._compute_resource() - self.gpg = None - self.USE_GPG = False - if gajim.HAVE_GPG: - self.USE_GPG = True - self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent')) - self.status = '' - self.old_show = '' - self.priority = gajim.get_priority(name, 'offline') - self.time_to_reconnect = None - self.bookmarks = [] + ''' + Common connection class, can be derivated for normal connection or zeroconf + connection + ''' + def __init__(self, name): + self.name = name + # self.connected: + # 0=>offline, + # 1=>connection in progress, + # 2=>online + # 3=>free for chat + # ... + self.connected = 0 + self.connection = None # xmpppy ClientCommon instance + self.on_purpose = False + self.is_zeroconf = False + self.password = '' + self.server_resource = self._compute_resource() + self.gpg = None + self.USE_GPG = False + if gajim.HAVE_GPG: + self.USE_GPG = True + self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent')) + self.status = '' + self.old_show = '' + self.priority = gajim.get_priority(name, 'offline') + self.time_to_reconnect = None + self.bookmarks = [] - self.blocked_list = [] - self.blocked_contacts = [] - self.blocked_groups = [] - self.blocked_all = False + self.blocked_list = [] + self.blocked_contacts = [] + self.blocked_groups = [] + self.blocked_all = False - self.pep_supported = False - self.pep = {} - # Do we continue connection when we get roster (send presence,get vcard..) - self.continue_connect_info = None + self.pep_supported = False + self.pep = {} + # Do we continue connection when we get roster (send presence,get vcard..) + self.continue_connect_info = None - # To know the groupchat jid associated with a sranza ID. Useful to - # request vcard or os info... to a real JID but act as if it comes from - # the fake jid - self.groupchat_jids = {} # {ID : groupchat_jid} + # To know the groupchat jid associated with a sranza ID. Useful to + # request vcard or os info... to a real JID but act as if it comes from + # the fake jid + self.groupchat_jids = {} # {ID : groupchat_jid} - self.privacy_rules_supported = False - self.vcard_supported = False - self.private_storage_supported = False + self.privacy_rules_supported = False + self.vcard_supported = False + self.private_storage_supported = False - self.muc_jid = {} # jid of muc server for each transport type + self.muc_jid = {} # jid of muc server for each transport type - self.get_config_values_or_default() + self.get_config_values_or_default() - def _compute_resource(self): - resource = gajim.config.get_per('accounts', self.name, 'resource') - # All valid resource substitution strings should be added to this hash. - if resource: - resource = Template(resource).safe_substitute({ - 'hostname': socket.gethostname() - }) - return resource + def _compute_resource(self): + resource = gajim.config.get_per('accounts', self.name, 'resource') + # All valid resource substitution strings should be added to this hash. + if resource: + resource = Template(resource).safe_substitute({ + 'hostname': socket.gethostname() + }) + return resource - def dispatch(self, event, data): - '''always passes account name as first param''' - gajim.interface.dispatch(event, self.name, data) + def dispatch(self, event, data): + '''always passes account name as first param''' + gajim.interface.dispatch(event, self.name, data) - def _reconnect(self): - '''To be implemented by derivated classes''' - raise NotImplementedError + def _reconnect(self): + '''To be implemented by derivated classes''' + raise NotImplementedError - def quit(self, kill_core): - if kill_core and gajim.account_is_connected(self.name): - self.disconnect(on_purpose=True) + def quit(self, kill_core): + if kill_core and gajim.account_is_connected(self.name): + self.disconnect(on_purpose=True) - def test_gpg_passphrase(self, password): - '''Returns 'ok', 'bad_pass' or 'expired' ''' - if not self.gpg: - return False - self.gpg.passphrase = password - keyID = gajim.config.get_per('accounts', self.name, 'keyid') - signed = self.gpg.sign('test', keyID) - self.gpg.password = None - if signed == 'KEYEXPIRED': - return 'expired' - elif signed == 'BAD_PASSPHRASE': - return 'bad_pass' - return 'ok' + def test_gpg_passphrase(self, password): + '''Returns 'ok', 'bad_pass' or 'expired' ''' + if not self.gpg: + return False + self.gpg.passphrase = password + keyID = gajim.config.get_per('accounts', self.name, 'keyid') + signed = self.gpg.sign('test', keyID) + self.gpg.password = None + if signed == 'KEYEXPIRED': + return 'expired' + elif signed == 'BAD_PASSPHRASE': + return 'bad_pass' + return 'ok' - def get_signed_msg(self, msg, callback = None): - '''returns the signed message if possible - or an empty string if gpg is not used - or None if waiting for passphrase. - callback is the function to call when user give the passphrase''' - signed = '' - keyID = gajim.config.get_per('accounts', self.name, 'keyid') - if keyID and self.USE_GPG: - use_gpg_agent = gajim.config.get('use_gpg_agent') - if self.gpg.passphrase is None and not use_gpg_agent: - # We didn't set a passphrase - return None - if self.gpg.passphrase is not None or use_gpg_agent: - signed = self.gpg.sign(msg, keyID) - if signed == 'BAD_PASSPHRASE': - self.USE_GPG = False - signed = '' - self.dispatch('BAD_PASSPHRASE', ()) - return signed + def get_signed_msg(self, msg, callback = None): + '''returns the signed message if possible + or an empty string if gpg is not used + or None if waiting for passphrase. + callback is the function to call when user give the passphrase''' + signed = '' + keyID = gajim.config.get_per('accounts', self.name, 'keyid') + if keyID and self.USE_GPG: + use_gpg_agent = gajim.config.get('use_gpg_agent') + if self.gpg.passphrase is None and not use_gpg_agent: + # We didn't set a passphrase + return None + if self.gpg.passphrase is not None or use_gpg_agent: + signed = self.gpg.sign(msg, keyID) + if signed == 'BAD_PASSPHRASE': + self.USE_GPG = False + signed = '' + self.dispatch('BAD_PASSPHRASE', ()) + return signed - def _on_disconnected(self): - ''' called when a disconnect request has completed successfully''' - self.disconnect(on_purpose=True) - self.dispatch('STATUS', 'offline') + def _on_disconnected(self): + ''' called when a disconnect request has completed successfully''' + self.disconnect(on_purpose=True) + self.dispatch('STATUS', 'offline') - def get_status(self): - return gajim.SHOW_LIST[self.connected] + def get_status(self): + return gajim.SHOW_LIST[self.connected] - def check_jid(self, jid): - '''this function must be implemented by derivated classes. - It has to return the valid jid, or raise a helpers.InvalidFormat exception - ''' - raise NotImplementedError + def check_jid(self, jid): + '''this function must be implemented by derivated classes. + It has to return the valid jid, or raise a helpers.InvalidFormat exception + ''' + raise NotImplementedError - def _prepare_message(self, jid, msg, keyID, type_='chat', subject='', - chatstate=None, msg_id=None, composing_xep=None, resource=None, - user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None, - original_message=None, delayed=None, callback=None): - if not self.connection or self.connected < 2: - return 1 - try: - jid = self.check_jid(jid) - except helpers.InvalidFormat: - self.dispatch('ERROR', (_('Invalid Jabber ID'), - _('It is not possible to send a message to %s, this JID is not ' - 'valid.') % jid)) - return + def _prepare_message(self, jid, msg, keyID, type_='chat', subject='', + chatstate=None, msg_id=None, composing_xep=None, resource=None, + user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None, + original_message=None, delayed=None, callback=None): + if not self.connection or self.connected < 2: + return 1 + try: + jid = self.check_jid(jid) + except helpers.InvalidFormat: + self.dispatch('ERROR', (_('Invalid Jabber ID'), + _('It is not possible to send a message to %s, this JID is not ' + 'valid.') % jid)) + return - if msg and not xhtml and gajim.config.get( - 'rst_formatting_outgoing_messages'): - from common.rst_xhtml_generator import create_xhtml - xhtml = create_xhtml(msg) - if not msg and chatstate is None and form_node is None: - return - fjid = jid - if resource: - fjid += '/' + resource - msgtxt = msg - msgenc = '' + if msg and not xhtml and gajim.config.get( + 'rst_formatting_outgoing_messages'): + from common.rst_xhtml_generator import create_xhtml + xhtml = create_xhtml(msg) + if not msg and chatstate is None and form_node is None: + return + fjid = jid + if resource: + fjid += '/' + resource + msgtxt = msg + msgenc = '' - if session: - fjid = session.get_to() + if session: + fjid = session.get_to() - if keyID and self.USE_GPG: - xhtml = None - if keyID == 'UNKNOWN': - error = _('Neither the remote presence is signed, nor a key was ' - 'assigned.') - elif keyID.endswith('MISMATCH'): - error = _('The contact\'s key (%s) does not match the key assigned ' - 'in Gajim.' % keyID[:8]) - else: - def encrypt_thread(msg, keyID, always_trust=False): - # encrypt message. This function returns (msgenc, error) - return self.gpg.encrypt(msg, [keyID], always_trust) - def _on_encrypted(output): - msgenc, error = output - if error == 'NOT_TRUSTED': - def _on_always_trust(answer): - if answer: - gajim.thread_interface(encrypt_thread, [msg, keyID, - True], _on_encrypted, []) - else: - self._message_encrypted_cb(output, type_, msg, msgtxt, - original_message, fjid, resource, jid, xhtml, - subject, chatstate, composing_xep, forward_from, - delayed, session, form_node, user_nick, keyID, - callback) - self.dispatch('GPG_ALWAYS_TRUST', _on_always_trust) - else: - self._message_encrypted_cb(output, type_, msg, msgtxt, - original_message, fjid, resource, jid, xhtml, subject, - chatstate, composing_xep, forward_from, delayed, session, - form_node, user_nick, keyID, callback) - gajim.thread_interface(encrypt_thread, [msg, keyID, False], - _on_encrypted, []) - return + if keyID and self.USE_GPG: + xhtml = None + if keyID == 'UNKNOWN': + error = _('Neither the remote presence is signed, nor a key was ' + 'assigned.') + elif keyID.endswith('MISMATCH'): + error = _('The contact\'s key (%s) does not match the key assigned ' + 'in Gajim.' % keyID[:8]) + else: + def encrypt_thread(msg, keyID, always_trust=False): + # encrypt message. This function returns (msgenc, error) + return self.gpg.encrypt(msg, [keyID], always_trust) + def _on_encrypted(output): + msgenc, error = output + if error == 'NOT_TRUSTED': + def _on_always_trust(answer): + if answer: + gajim.thread_interface(encrypt_thread, [msg, keyID, + True], _on_encrypted, []) + else: + self._message_encrypted_cb(output, type_, msg, msgtxt, + original_message, fjid, resource, jid, xhtml, + subject, chatstate, composing_xep, forward_from, + delayed, session, form_node, user_nick, keyID, + callback) + self.dispatch('GPG_ALWAYS_TRUST', _on_always_trust) + else: + self._message_encrypted_cb(output, type_, msg, msgtxt, + original_message, fjid, resource, jid, xhtml, subject, + chatstate, composing_xep, forward_from, delayed, session, + form_node, user_nick, keyID, callback) + gajim.thread_interface(encrypt_thread, [msg, keyID, False], + _on_encrypted, []) + return - self._message_encrypted_cb(('', error), type_, msg, msgtxt, - original_message, fjid, resource, jid, xhtml, subject, chatstate, - composing_xep, forward_from, delayed, session, form_node, user_nick, - keyID, callback) + self._message_encrypted_cb(('', error), type_, msg, msgtxt, + original_message, fjid, resource, jid, xhtml, subject, chatstate, + composing_xep, forward_from, delayed, session, form_node, user_nick, + keyID, callback) - self._on_continue_message(type_, msg, msgtxt, original_message, fjid, - resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep, - forward_from, delayed, session, form_node, user_nick, callback) + self._on_continue_message(type_, msg, msgtxt, original_message, fjid, + resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep, + forward_from, delayed, session, form_node, user_nick, callback) - def _message_encrypted_cb(self, output, type_, msg, msgtxt, original_message, - fjid, resource, jid, xhtml, subject, chatstate, composing_xep, forward_from, - delayed, session, form_node, user_nick, keyID, callback): - msgenc, error = output + def _message_encrypted_cb(self, output, type_, msg, msgtxt, original_message, + fjid, resource, jid, xhtml, subject, chatstate, composing_xep, forward_from, + delayed, session, form_node, user_nick, keyID, callback): + msgenc, error = output - if msgenc and not error: - msgtxt = '[This message is *encrypted* (See :XEP:`27`]' - lang = os.getenv('LANG') - if lang is not None and lang != 'en': # we're not english - # one in locale and one en - msgtxt = _('[This message is *encrypted* (See :XEP:`27`]') + \ - ' (' + msgtxt + ')' - self._on_continue_message(type_, msg, msgtxt, original_message, fjid, - resource, jid, xhtml, subject, msgenc, keyID, chatstate, - composing_xep, forward_from, delayed, session, form_node, user_nick, - callback) - return - # Encryption failed, do not send message - tim = localtime() - self.dispatch('MSGNOTSENT', (jid, error, msgtxt, tim, session)) + if msgenc and not error: + msgtxt = '[This message is *encrypted* (See :XEP:`27`]' + lang = os.getenv('LANG') + if lang is not None and lang != 'en': # we're not english + # one in locale and one en + msgtxt = _('[This message is *encrypted* (See :XEP:`27`]') + \ + ' (' + msgtxt + ')' + self._on_continue_message(type_, msg, msgtxt, original_message, fjid, + resource, jid, xhtml, subject, msgenc, keyID, chatstate, + composing_xep, forward_from, delayed, session, form_node, user_nick, + callback) + return + # Encryption failed, do not send message + tim = localtime() + self.dispatch('MSGNOTSENT', (jid, error, msgtxt, tim, session)) - def _on_continue_message(self, type_, msg, msgtxt, original_message, fjid, - resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep, - forward_from, delayed, session, form_node, user_nick, callback): - if type_ == 'chat': - msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ=type_, - xhtml=xhtml) - else: - if subject: - msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal', - subject=subject, xhtml=xhtml) - else: - msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal', - xhtml=xhtml) - if msgenc: - msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc) + def _on_continue_message(self, type_, msg, msgtxt, original_message, fjid, + resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep, + forward_from, delayed, session, form_node, user_nick, callback): + if type_ == 'chat': + msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ=type_, + xhtml=xhtml) + else: + if subject: + msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal', + subject=subject, xhtml=xhtml) + else: + msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal', + xhtml=xhtml) + if msgenc: + msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc) - if form_node: - msg_iq.addChild(node=form_node) + if form_node: + msg_iq.addChild(node=form_node) - # XEP-0172: user_nickname - if user_nick: - msg_iq.setTag('nick', namespace = common.xmpp.NS_NICK).setData( - user_nick) + # XEP-0172: user_nickname + if user_nick: + msg_iq.setTag('nick', namespace = common.xmpp.NS_NICK).setData( + user_nick) - # TODO: We might want to write a function so we don't need to - # reproduce that ugly if somewhere else. - if resource: - contact = gajim.contacts.get_contact(self.name, jid, resource) - else: - contact = gajim.contacts.get_contact_with_highest_priority(self.name, - jid) + # TODO: We might want to write a function so we don't need to + # reproduce that ugly if somewhere else. + if resource: + contact = gajim.contacts.get_contact(self.name, jid, resource) + else: + contact = gajim.contacts.get_contact_with_highest_priority(self.name, + jid) - # chatstates - if peer supports xep85 or xep22, send chatstates - # please note that the only valid tag inside a message containing a - # tag is the active event - if chatstate is not None and contact: - if ((composing_xep == 'XEP-0085' or not composing_xep) \ - and composing_xep != 'asked_once') or \ - contact.supports(common.xmpp.NS_CHATSTATES): - # XEP-0085 - msg_iq.setTag(chatstate, namespace=common.xmpp.NS_CHATSTATES) - if composing_xep in ('XEP-0022', 'asked_once') or \ - not composing_xep: - # XEP-0022 - chatstate_node = msg_iq.setTag('x', namespace=common.xmpp.NS_EVENT) - if chatstate is 'composing' or msgtxt: - chatstate_node.addChild(name='composing') + # chatstates - if peer supports xep85 or xep22, send chatstates + # please note that the only valid tag inside a message containing a + # tag is the active event + if chatstate is not None and contact: + if ((composing_xep == 'XEP-0085' or not composing_xep) \ + and composing_xep != 'asked_once') or \ + contact.supports(common.xmpp.NS_CHATSTATES): + # XEP-0085 + msg_iq.setTag(chatstate, namespace=common.xmpp.NS_CHATSTATES) + if composing_xep in ('XEP-0022', 'asked_once') or \ + not composing_xep: + # XEP-0022 + chatstate_node = msg_iq.setTag('x', namespace=common.xmpp.NS_EVENT) + if chatstate is 'composing' or msgtxt: + chatstate_node.addChild(name='composing') - if forward_from: - addresses = msg_iq.addChild('addresses', - namespace=common.xmpp.NS_ADDRESS) - addresses.addChild('address', attrs = {'type': 'ofrom', - 'jid': forward_from}) + if forward_from: + addresses = msg_iq.addChild('addresses', + namespace=common.xmpp.NS_ADDRESS) + addresses.addChild('address', attrs = {'type': 'ofrom', + 'jid': forward_from}) - # XEP-0203 - if delayed: - our_jid = gajim.get_jid_from_account(self.name) + '/' + \ - self.server_resource - timestamp = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(delayed)) - msg_iq.addChild('delay', namespace=common.xmpp.NS_DELAY2, - attrs={'from': our_jid, 'stamp': timestamp}) + # XEP-0203 + if delayed: + our_jid = gajim.get_jid_from_account(self.name) + '/' + \ + self.server_resource + timestamp = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(delayed)) + msg_iq.addChild('delay', namespace=common.xmpp.NS_DELAY2, + attrs={'from': our_jid, 'stamp': timestamp}) - # XEP-0184 - if msgtxt and gajim.config.get_per('accounts', self.name, - 'request_receipt') and contact and contact.supports( - common.xmpp.NS_RECEIPTS): - msg_iq.setTag('request', namespace=common.xmpp.NS_RECEIPTS) + # XEP-0184 + if msgtxt and gajim.config.get_per('accounts', self.name, + 'request_receipt') and contact and contact.supports( + common.xmpp.NS_RECEIPTS): + msg_iq.setTag('request', namespace=common.xmpp.NS_RECEIPTS) - if session: - # XEP-0201 - session.last_send = time.time() - msg_iq.setThread(session.thread_id) + if session: + # XEP-0201 + session.last_send = time.time() + msg_iq.setThread(session.thread_id) - # XEP-0200 - if session.enable_encryption: - msg_iq = session.encrypt_stanza(msg_iq) + # XEP-0200 + if session.enable_encryption: + msg_iq = session.encrypt_stanza(msg_iq) - if callback: - callback(jid, msg, keyID, forward_from, session, original_message, - subject, type_, msg_iq) + if callback: + callback(jid, msg, keyID, forward_from, session, original_message, + subject, type_, msg_iq) - def log_message(self, jid, msg, forward_from, session, original_message, - subject, type_): - if not forward_from and session and session.is_loggable(): - ji = gajim.get_jid_without_resource(jid) - if gajim.config.should_log(self.name, ji): - log_msg = msg - if original_message is not None: - log_msg = original_message - if subject: - log_msg = _('Subject: %(subject)s\n%(message)s') % \ - {'subject': subject, 'message': log_msg} - if log_msg: - if type_ == 'chat': - kind = 'chat_msg_sent' - else: - kind = 'single_msg_sent' - try: - gajim.logger.write(kind, jid, log_msg) - except exceptions.PysqliteOperationalError, e: - self.dispatch('ERROR', (_('Disk Write Error'), str(e))) - except exceptions.DatabaseMalformed: - pritext = _('Database Error') - sectext = _('The database file (%s) cannot be read. Try to ' - 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)' - ' or remove it (all history will be lost).') % \ - common.logger.LOG_DB_PATH + def log_message(self, jid, msg, forward_from, session, original_message, + subject, type_): + if not forward_from and session and session.is_loggable(): + ji = gajim.get_jid_without_resource(jid) + if gajim.config.should_log(self.name, ji): + log_msg = msg + if original_message is not None: + log_msg = original_message + if subject: + log_msg = _('Subject: %(subject)s\n%(message)s') % \ + {'subject': subject, 'message': log_msg} + if log_msg: + if type_ == 'chat': + kind = 'chat_msg_sent' + else: + kind = 'single_msg_sent' + try: + gajim.logger.write(kind, jid, log_msg) + except exceptions.PysqliteOperationalError, e: + self.dispatch('ERROR', (_('Disk Write Error'), str(e))) + except exceptions.DatabaseMalformed: + pritext = _('Database Error') + sectext = _('The database file (%s) cannot be read. Try to ' + 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)' + ' or remove it (all history will be lost).') % \ + common.logger.LOG_DB_PATH - def ack_subscribed(self, jid): - '''To be implemented by derivated classes''' - raise NotImplementedError + def ack_subscribed(self, jid): + '''To be implemented by derivated classes''' + raise NotImplementedError - def ack_unsubscribed(self, jid): - '''To be implemented by derivated classes''' - raise NotImplementedError + def ack_unsubscribed(self, jid): + '''To be implemented by derivated classes''' + raise NotImplementedError - def request_subscription(self, jid, msg='', name='', groups=[], - auto_auth=False): - '''To be implemented by derivated classes''' - raise NotImplementedError + def request_subscription(self, jid, msg='', name='', groups=[], + auto_auth=False): + '''To be implemented by derivated classes''' + raise NotImplementedError - def send_authorization(self, jid): - '''To be implemented by derivated classes''' - raise NotImplementedError + def send_authorization(self, jid): + '''To be implemented by derivated classes''' + raise NotImplementedError - def refuse_authorization(self, jid): - '''To be implemented by derivated classes''' - raise NotImplementedError + def refuse_authorization(self, jid): + '''To be implemented by derivated classes''' + raise NotImplementedError - def unsubscribe(self, jid, remove_auth = True): - '''To be implemented by derivated classes''' - raise NotImplementedError + def unsubscribe(self, jid, remove_auth = True): + '''To be implemented by derivated classes''' + raise NotImplementedError - def unsubscribe_agent(self, agent): - '''To be implemented by derivated classes''' - raise NotImplementedError + def unsubscribe_agent(self, agent): + '''To be implemented by derivated classes''' + raise NotImplementedError - def update_contact(self, jid, name, groups): - if self.connection: - self.connection.getRoster().setItem(jid=jid, name=name, groups=groups) + def update_contact(self, jid, name, groups): + if self.connection: + self.connection.getRoster().setItem(jid=jid, name=name, groups=groups) - def update_contacts(self, contacts): - '''update multiple roster items''' - if self.connection: - self.connection.getRoster().setItemMulti(contacts) + def update_contacts(self, contacts): + '''update multiple roster items''' + if self.connection: + self.connection.getRoster().setItemMulti(contacts) - def new_account(self, name, config, sync=False): - '''To be implemented by derivated classes''' - raise NotImplementedError + def new_account(self, name, config, sync=False): + '''To be implemented by derivated classes''' + raise NotImplementedError - def _on_new_account(self, con=None, con_type=None): - '''To be implemented by derivated classes''' - raise NotImplementedError + def _on_new_account(self, con=None, con_type=None): + '''To be implemented by derivated classes''' + raise NotImplementedError - def account_changed(self, new_name): - self.name = new_name + def account_changed(self, new_name): + self.name = new_name - def request_last_status_time(self, jid, resource): - '''To be implemented by derivated classes''' - raise NotImplementedError + def request_last_status_time(self, jid, resource): + '''To be implemented by derivated classes''' + raise NotImplementedError - def request_os_info(self, jid, resource): - '''To be implemented by derivated classes''' - raise NotImplementedError + def request_os_info(self, jid, resource): + '''To be implemented by derivated classes''' + raise NotImplementedError - def get_settings(self): - '''To be implemented by derivated classes''' - raise NotImplementedError + def get_settings(self): + '''To be implemented by derivated classes''' + raise NotImplementedError - def get_bookmarks(self): - '''To be implemented by derivated classes''' - raise NotImplementedError + def get_bookmarks(self): + '''To be implemented by derivated classes''' + raise NotImplementedError - def store_bookmarks(self): - '''To be implemented by derivated classes''' - raise NotImplementedError + def store_bookmarks(self): + '''To be implemented by derivated classes''' + raise NotImplementedError - def get_metacontacts(self): - '''To be implemented by derivated classes''' - raise NotImplementedError + def get_metacontacts(self): + '''To be implemented by derivated classes''' + raise NotImplementedError - def send_agent_status(self, agent, ptype): - '''To be implemented by derivated classes''' - raise NotImplementedError + def send_agent_status(self, agent, ptype): + '''To be implemented by derivated classes''' + raise NotImplementedError - def gpg_passphrase(self, passphrase): - if self.gpg: - use_gpg_agent = gajim.config.get('use_gpg_agent') - if use_gpg_agent: - self.gpg.passphrase = None - else: - self.gpg.passphrase = passphrase + def gpg_passphrase(self, passphrase): + if self.gpg: + use_gpg_agent = gajim.config.get('use_gpg_agent') + if use_gpg_agent: + self.gpg.passphrase = None + else: + self.gpg.passphrase = passphrase - def ask_gpg_keys(self): - if self.gpg: - keys = self.gpg.get_keys() - return keys - return None + def ask_gpg_keys(self): + if self.gpg: + keys = self.gpg.get_keys() + return keys + return None - def ask_gpg_secrete_keys(self): - if self.gpg: - keys = self.gpg.get_secret_keys() - return keys - return None + def ask_gpg_secrete_keys(self): + if self.gpg: + keys = self.gpg.get_secret_keys() + return keys + return None - def load_roster_from_db(self): - # Do nothing by default - return + def load_roster_from_db(self): + # Do nothing by default + return - def _event_dispatcher(self, realm, event, data): - if realm == '': - if event == common.xmpp.transports_nb.DATA_RECEIVED: - self.dispatch('STANZA_ARRIVED', unicode(data, errors='ignore')) - elif event == common.xmpp.transports_nb.DATA_SENT: - self.dispatch('STANZA_SENT', unicode(data)) + def _event_dispatcher(self, realm, event, data): + if realm == '': + if event == common.xmpp.transports_nb.DATA_RECEIVED: + self.dispatch('STANZA_ARRIVED', unicode(data, errors='ignore')) + elif event == common.xmpp.transports_nb.DATA_SENT: + self.dispatch('STANZA_SENT', unicode(data)) - def change_status(self, show, msg, auto=False): - if not show in ['offline', 'online', 'chat', 'away', 'xa', 'dnd']: - return -1 - if not msg: - msg = '' - sign_msg = False - if not auto and not show == 'offline': - sign_msg = True - if show != 'invisible': - # We save it only when privacy list is accepted - self.status = msg - if show != 'offline' and self.connected < 1: - # set old_show to requested 'show' in case we need to - # recconect before we auth to server - self.old_show = show - self.on_purpose = False - self.server_resource = self._compute_resource() - if gajim.HAVE_GPG: - self.USE_GPG = True - self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent')) - self.connect_and_init(show, msg, sign_msg) + def change_status(self, show, msg, auto=False): + if not show in ['offline', 'online', 'chat', 'away', 'xa', 'dnd']: + return -1 + if not msg: + msg = '' + sign_msg = False + if not auto and not show == 'offline': + sign_msg = True + if show != 'invisible': + # We save it only when privacy list is accepted + self.status = msg + if show != 'offline' and self.connected < 1: + # set old_show to requested 'show' in case we need to + # recconect before we auth to server + self.old_show = show + self.on_purpose = False + self.server_resource = self._compute_resource() + if gajim.HAVE_GPG: + self.USE_GPG = True + self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent')) + self.connect_and_init(show, msg, sign_msg) - elif show == 'offline': - self.connected = 0 - if self.connection: - p = common.xmpp.Presence(typ = 'unavailable') - p = self.add_sha(p, False) - if msg: - p.setStatus(msg) + elif show == 'offline': + self.connected = 0 + if self.connection: + p = common.xmpp.Presence(typ = 'unavailable') + p = self.add_sha(p, False) + if msg: + p.setStatus(msg) - self.connection.RegisterDisconnectHandler(self._on_disconnected) - self.connection.send(p, now=True) - self.connection.start_disconnect() - else: - self._on_disconnected() + self.connection.RegisterDisconnectHandler(self._on_disconnected) + self.connection.send(p, now=True) + self.connection.start_disconnect() + else: + self._on_disconnected() - elif show != 'offline' and self.connected > 0: - # dont'try to connect, when we are in state 'connecting' - if self.connected == 1: - return - if show == 'invisible': - self._change_to_invisible(msg) - return - was_invisible = self.connected == gajim.SHOW_LIST.index('invisible') - self.connected = gajim.SHOW_LIST.index(show) - if was_invisible: - self._change_from_invisible() - self._update_status(show, msg) + elif show != 'offline' and self.connected > 0: + # dont'try to connect, when we are in state 'connecting' + if self.connected == 1: + return + if show == 'invisible': + self._change_to_invisible(msg) + return + was_invisible = self.connected == gajim.SHOW_LIST.index('invisible') + self.connected = gajim.SHOW_LIST.index(show) + if was_invisible: + self._change_from_invisible() + self._update_status(show, msg) class Connection(CommonConnection, ConnectionHandlers): - '''Connection class''' - def __init__(self, name): - CommonConnection.__init__(self, name) - ConnectionHandlers.__init__(self) - # this property is used to prevent double connections - self.last_connection = None # last ClientCommon instance - # If we succeed to connect, remember it so next time we try (after a - # disconnection) we try only this type. - self.last_connection_type = None - self.lang = None - if locale.getdefaultlocale()[0]: - self.lang = locale.getdefaultlocale()[0].split('_')[0] - # increase/decrease default timeout for server responses - self.try_connecting_for_foo_secs = 45 - # holds the actual hostname to which we are connected - self.connected_hostname = None - self.last_time_to_reconnect = None - self.new_account_info = None - self.new_account_form = None - self.annotations = {} - self.last_io = gajim.idlequeue.current_time() - self.last_sent = [] - self.last_history_time = {} - self.password = passwords.get_password(name) - - # Used to ask privacy only once at connection - self.music_track_info = 0 - self.pubsub_supported = False - self.pubsub_publish_options_supported = False - # Do we auto accept insecure connection - self.connection_auto_accepted = False - self.pasword_callback = None - - self.on_connect_success = None - self.on_connect_failure = None - self.retrycount = 0 - self.jids_for_auto_auth = [] # list of jid to auto-authorize - self.available_transports = {} # list of available transports on this - # server {'icq': ['icq.server.com', 'icq2.server.com'], } - self.private_storage_supported = True - self.streamError = '' - self.secret_hmac = str(random.random())[2:] - # END __init__ - - def get_config_values_or_default(self): - if gajim.config.get_per('accounts', self.name, 'keep_alives_enabled'): - self.keepalives = gajim.config.get_per('accounts', self.name, - 'keep_alive_every_foo_secs') - else: - self.keepalives = 0 - if gajim.config.get_per('accounts', self.name, 'ping_alives_enabled'): - self.pingalives = gajim.config.get_per('accounts', self.name, - 'ping_alive_every_foo_secs') - else: - self.pingalives = 0 - - def check_jid(self, jid): - return helpers.parse_jid(jid) - - def _reconnect(self): - # Do not try to reco while we are already trying - self.time_to_reconnect = None - if self.connected < 2: # connection failed - log.debug('reconnect') - self.connected = 1 - self.dispatch('STATUS', 'connecting') - self.retrycount += 1 - self.on_connect_auth = self._discover_server_at_connection - self.connect_and_init(self.old_show, self.status, self.USE_GPG) - else: - # reconnect succeeded - self.time_to_reconnect = None - self.retrycount = 0 - - # We are doing disconnect at so many places, better use one function in all - def disconnect(self, on_purpose=False): - gajim.interface.music_track_changed(None, None, self.name) - self.on_purpose = on_purpose - self.connected = 0 - self.time_to_reconnect = None - self.privacy_rules_supported = False - if self.connection: - # make sure previous connection is completely closed - gajim.proxy65_manager.disconnect(self.connection) - self.terminate_sessions() - self.remove_all_transfers() - self.connection.disconnect() - self.last_connection = None - self.connection = None - - def _disconnectedReconnCB(self): - '''Called when we are disconnected''' - log.info('disconnectedReconnCB called') - if gajim.account_is_connected(self.name): - # we cannot change our status to offline or connecting - # after we auth to server - self.old_show = gajim.SHOW_LIST[self.connected] - self.connected = 0 - if not self.on_purpose: - self.dispatch('STATUS', 'offline') - self.disconnect() - if gajim.config.get_per('accounts', self.name, 'autoreconnect'): - self.connected = -1 - self.dispatch('STATUS', 'error') - if gajim.status_before_autoaway[self.name]: - # We were auto away. So go back online - self.status = gajim.status_before_autoaway[self.name] - gajim.status_before_autoaway[self.name] = '' - self.old_show = 'online' - # this check has moved from _reconnect method - # do exponential backoff until 15 minutes, - # then small linear increase - if self.retrycount < 2 or self.last_time_to_reconnect is None: - self.last_time_to_reconnect = 5 - if self.last_time_to_reconnect < 800: - self.last_time_to_reconnect *= 1.5 - self.last_time_to_reconnect += randomsource.randint(0, 5) - self.time_to_reconnect = int(self.last_time_to_reconnect) - log.info("Reconnect to %s in %ss", self.name, self.time_to_reconnect) - gajim.idlequeue.set_alarm(self._reconnect_alarm, - self.time_to_reconnect) - elif self.on_connect_failure: - self.on_connect_failure() - self.on_connect_failure = None - else: - # show error dialog - self._connection_lost() - else: - self.disconnect() - self.on_purpose = False - # END disconenctedReconnCB - - def _connection_lost(self): - log.debug('_connection_lost') - self.disconnect(on_purpose = False) - self.dispatch('STATUS', 'offline') - self.dispatch('CONNECTION_LOST', - (_('Connection with account "%s" has been lost') % self.name, - _('Reconnect manually.'))) - - def _event_dispatcher(self, realm, event, data): - CommonConnection._event_dispatcher(self, realm, event, data) - if realm == common.xmpp.NS_REGISTER: - if event == common.xmpp.features_nb.REGISTER_DATA_RECEIVED: - # data is (agent, DataFrom, is_form, error_msg) - if self.new_account_info and \ - self.new_account_info['hostname'] == data[0]: - # it's a new account - if not data[1]: # wrong answer - self.dispatch('ACC_NOT_OK', ( - _('Server %(name)s answered wrongly to register request: ' - '%(error)s') % {'name': data[0], 'error': data[3]})) - return - is_form = data[2] - conf = data[1] - if self.new_account_form: - def _on_register_result(result): - if not common.xmpp.isResultNode(result): - self.dispatch('ACC_NOT_OK', (result.getError())) - return - if gajim.HAVE_GPG: - self.USE_GPG = True - self.gpg = GnuPG.GnuPG(gajim.config.get( - 'use_gpg_agent')) - self.dispatch('ACC_OK', (self.new_account_info)) - self.new_account_info = None - self.new_account_form = None - if self.connection: - self.connection.UnregisterDisconnectHandler( - self._on_new_account) - self.disconnect(on_purpose=True) - # it's the second time we get the form, we have info user - # typed, so send them - if is_form: - #TODO: Check if form has changed - iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to=self._hostname) - iq.setTag('query').addChild(node=self.new_account_form) - self.connection.SendAndCallForResponse(iq, - _on_register_result) - else: - if self.new_account_form.keys().sort() != \ - conf.keys().sort(): - # requested config has changed since first connection - self.dispatch('ACC_NOT_OK', (_( - 'Server %s provided a different registration form')\ - % data[0])) - return - common.xmpp.features_nb.register(self.connection, - self._hostname, self.new_account_form, - _on_register_result) - return - try: - errnum = self.connection.Connection.ssl_errnum - except AttributeError: - errnum = -1 # we don't have an errnum - ssl_msg = '' - if errnum > 0: - ssl_msg = ssl_error.get(errnum, _('Unknown SSL error: %d') % errnum) - ssl_cert = '' - if hasattr(self.connection.Connection, 'ssl_cert_pem'): - ssl_cert = self.connection.Connection.ssl_cert_pem - ssl_fingerprint = '' - if hasattr(self.connection.Connection, 'ssl_fingerprint_sha1'): - ssl_fingerprint = \ - self.connection.Connection.ssl_fingerprint_sha1 - self.dispatch('NEW_ACC_CONNECTED', (conf, is_form, ssl_msg, - errnum, ssl_cert, ssl_fingerprint)) - self.connection.UnregisterDisconnectHandler( - self._on_new_account) - self.disconnect(on_purpose=True) - return - if not data[1]: # wrong answer - self.dispatch('ERROR', (_('Invalid answer'), - _('Transport %(name)s answered wrongly to register request: ' - '%(error)s') % {'name': data[0], 'error': data[3]})) - return - is_form = data[2] - conf = data[1] - self.dispatch('REGISTER_AGENT_INFO', (data[0], conf, is_form)) - elif realm == common.xmpp.NS_PRIVACY: - if event == common.xmpp.features_nb.PRIVACY_LISTS_RECEIVED: - # data is (list) - self.dispatch('PRIVACY_LISTS_RECEIVED', (data)) - elif event == common.xmpp.features_nb.PRIVACY_LIST_RECEIVED: - # data is (resp) - if not data: - return - rules = [] - name = data.getTag('query').getTag('list').getAttr('name') - for child in data.getTag('query').getTag('list').getChildren(): - dict_item = child.getAttrs() - childs = [] - if 'type' in dict_item: - for scnd_child in child.getChildren(): - childs += [scnd_child.getName()] - rules.append({'action':dict_item['action'], - 'type':dict_item['type'], 'order':dict_item['order'], - 'value':dict_item['value'], 'child':childs}) - else: - for scnd_child in child.getChildren(): - childs.append(scnd_child.getName()) - rules.append({'action':dict_item['action'], - 'order':dict_item['order'], 'child':childs}) - self.dispatch('PRIVACY_LIST_RECEIVED', (name, rules)) - elif event == common.xmpp.features_nb.PRIVACY_LISTS_ACTIVE_DEFAULT: - # data is (dict) - self.dispatch('PRIVACY_LISTS_ACTIVE_DEFAULT', (data)) - - def _select_next_host(self, hosts): - '''Selects the next host according to RFC2782 p.3 based on it's - priority. Chooses between hosts with the same priority randomly, - where the probability of being selected is proportional to the weight - of the host.''' - - hosts_by_prio = sorted(hosts, key=operator.itemgetter('prio')) - - try: - lowest_prio = hosts_by_prio[0]['prio'] - except IndexError: - raise ValueError("No hosts to choose from!") - - hosts_lowest_prio = [h for h in hosts_by_prio if h['prio'] == lowest_prio] - - if len(hosts_lowest_prio) == 1: - return hosts_lowest_prio[0] - else: - rndint = random.randint(0, sum(h['weight'] for h in hosts_lowest_prio)) - weightsum = 0 - for host in sorted(hosts_lowest_prio, key=operator.itemgetter( - 'weight')): - weightsum += host['weight'] - if weightsum >= rndint: - return host - - def connect(self, data = None): - ''' Start a connection to the Jabber server. - Returns connection, and connection type ('tls', 'ssl', 'plain', '') - data MUST contain hostname, usessl, proxy, use_custom_host, - custom_host (if use_custom_host), custom_port (if use_custom_host)''' - if self.connection: - return self.connection, '' - - if data: - hostname = data['hostname'] - self.try_connecting_for_foo_secs = 45 - p = data['proxy'] - use_srv = True - use_custom = data['use_custom_host'] - if use_custom: - custom_h = data['custom_host'] - custom_p = data['custom_port'] - else: - hostname = gajim.config.get_per('accounts', self.name, 'hostname') - usessl = gajim.config.get_per('accounts', self.name, 'usessl') - self.try_connecting_for_foo_secs = gajim.config.get_per('accounts', - self.name, 'try_connecting_for_foo_secs') - p = gajim.config.get_per('accounts', self.name, 'proxy') - use_srv = gajim.config.get_per('accounts', self.name, 'use_srv') - use_custom = gajim.config.get_per('accounts', self.name, - 'use_custom_host') - custom_h = gajim.config.get_per('accounts', self.name, 'custom_host') - custom_p = gajim.config.get_per('accounts', self.name, 'custom_port') - - # create connection if it doesn't already exist - self.connected = 1 - if p and p in gajim.config.get_per('proxies'): - proxy = {} - proxyptr = gajim.config.get_per('proxies', p) - for key in proxyptr.keys(): - proxy[key] = proxyptr[key][1] - - elif gajim.config.get_per('accounts', self.name, 'use_env_http_proxy'): - try: - try: - env_http_proxy = os.environ['HTTP_PROXY'] - except Exception: - env_http_proxy = os.environ['http_proxy'] - env_http_proxy = env_http_proxy.strip('"') - # Dispose of the http:// prefix - env_http_proxy = env_http_proxy.split('://') - env_http_proxy = env_http_proxy[len(env_http_proxy)-1] - env_http_proxy = env_http_proxy.split('@') - - if len(env_http_proxy) == 2: - login = env_http_proxy[0].split(':') - addr = env_http_proxy[1].split(':') - else: - login = ['', ''] - addr = env_http_proxy[0].split(':') - - proxy = {'host': addr[0], 'type' : u'http', 'user':login[0]} - - if len(addr) == 2: - proxy['port'] = addr[1] - else: - proxy['port'] = 3128 - - if len(login) == 2: - proxy['password'] = login[1] - else: - proxy['password'] = u'' - - except Exception: - proxy = None - else: - proxy = None - h = hostname - p = 5222 - ssl_p = 5223 -# use_srv = False # wants ssl? disable srv lookup - if use_custom: - h = custom_h - p = custom_p - ssl_p = custom_p - use_srv = False - - # SRV resolver - self._proxy = proxy - self._hosts = [ {'host': h, 'port': p, 'ssl_port': ssl_p, 'prio': 10, - 'weight': 10} ] - self._hostname = hostname - if use_srv: - # add request for srv query to the resolve, on result '_on_resolve' - # will be called - gajim.resolver.resolve('_xmpp-client._tcp.' + helpers.idn_to_ascii(h), - self._on_resolve) - else: - self._on_resolve('', []) - - def _on_resolve(self, host, result_array): - # SRV query returned at least one valid result, we put it in hosts dict - if len(result_array) != 0: - self._hosts = [i for i in result_array] - # Add ssl port - ssl_p = 5223 - if gajim.config.get_per('accounts', self.name, 'use_custom_host'): - ssl_p = gajim.config.get_per('accounts', self.name, 'custom_port') - for i in self._hosts: - i['ssl_port'] = ssl_p - self._connect_to_next_host() - - - def _connect_to_next_host(self, retry=False): - log.debug('Connection to next host') - if len(self._hosts): - # No config option exist when creating a new account - if self.last_connection_type: - self._connection_types = [self.last_connection_type] - elif self.name in gajim.config.get_per('accounts'): - self._connection_types = gajim.config.get_per('accounts', self.name, - 'connection_types').split() - else: - self._connection_types = ['tls', 'ssl', 'plain'] - - if self._proxy and self._proxy['type']=='bosh': - # with BOSH, we can't do TLS negotiation with , we do only "plain" - # connection and TLS with handshake right after TCP connecting ("ssl") - scheme = common.xmpp.transports_nb.urisplit(self._proxy['bosh_uri'])[0] - if scheme=='https': - self._connection_types = ['ssl'] - else: - self._connection_types = ['plain'] - - host = self._select_next_host(self._hosts) - self._current_host = host - self._hosts.remove(host) - self.connect_to_next_type() - - else: - if not retry and self.retrycount == 0: - log.debug("Out of hosts, giving up connecting to %s", self.name) - self.time_to_reconnect = None - if self.on_connect_failure: - self.on_connect_failure() - self.on_connect_failure = None - else: - # shown error dialog - self._connection_lost() - else: - # try reconnect if connection has failed before auth to server - self._disconnectedReconnCB() - - def connect_to_next_type(self, retry=False): - if len(self._connection_types): - self._current_type = self._connection_types.pop(0) - if self.last_connection: - self.last_connection.socket.disconnect() - self.last_connection = None - self.connection = None - - if self._current_type == 'ssl': - # SSL (force TLS on different port than plain) - # If we do TLS over BOSH, port of XMPP server should be the standard one - # and TLS should be negotiated because TLS on 5223 is deprecated - if self._proxy and self._proxy['type']=='bosh': - port = self._current_host['port'] - else: - port = self._current_host['ssl_port'] - elif self._current_type == 'tls': - # TLS - negotiate tls after XMPP stream is estabilished - port = self._current_host['port'] - elif self._current_type == 'plain': - # plain connection on defined port - port = self._current_host['port'] - - cacerts = os.path.join(common.gajim.DATA_DIR, 'other', 'cacerts.pem') - mycerts = common.gajim.MY_CACERTS - secure_tuple = (self._current_type, cacerts, mycerts) - - con = common.xmpp.NonBlockingClient( - domain=self._hostname, - caller=self, - idlequeue=gajim.idlequeue) - - self.last_connection = con - # increase default timeout for server responses - common.xmpp.dispatcher_nb.DEFAULT_TIMEOUT_SECONDS = self.try_connecting_for_foo_secs - # FIXME: this is a hack; need a better way - if self.on_connect_success == self._on_new_account: - con.RegisterDisconnectHandler(self._on_new_account) - - self.log_hosttype_info(port) - con.connect( - hostname=self._current_host['host'], - port=port, - on_connect=self.on_connect_success, - on_proxy_failure=self.on_proxy_failure, - on_connect_failure=self.connect_to_next_type, - proxy=self._proxy, - secure_tuple = secure_tuple) - else: - self._connect_to_next_host(retry) - - def log_hosttype_info(self, port): - msg = '>>>>>> Connecting to %s [%s:%d], type = %s' % (self.name, - self._current_host['host'], port, self._current_type) - log.info(msg) - if self._proxy: - msg = '>>>>>> ' - if self._proxy['type']=='bosh': - msg = '%s over BOSH %s' % (msg, self._proxy['bosh_uri']) - if self._proxy['type'] in ['http','socks5'] or self._proxy['bosh_useproxy']: - msg = '%s over proxy %s:%s' % (msg, self._proxy['host'], self._proxy['port']) - log.info(msg) - - def _connect_failure(self, con_type=None): - if not con_type: - # we are not retrying, and not conecting - if not self.retrycount and self.connected != 0: - self.disconnect(on_purpose = True) - self.dispatch('STATUS', 'offline') - pritxt = _('Could not connect to "%s"') % self._hostname - sectxt = _('Check your connection or try again later.') - if self.streamError: - # show error dialog - key = common.xmpp.NS_XMPP_STREAMS + ' ' + self.streamError - if key in common.xmpp.ERRORS: - sectxt2 = _('Server replied: %s') % common.xmpp.ERRORS[key][2] - self.dispatch('ERROR', (pritxt, '%s\n%s' % (sectxt2, sectxt))) - return - # show popup - self.dispatch('CONNECTION_LOST', (pritxt, sectxt)) - - def on_proxy_failure(self, reason): - log.error('Connection to proxy failed: %s' % reason) - self.time_to_reconnect = None - self.on_connect_failure = None - self.disconnect(on_purpose = True) - self.dispatch('STATUS', 'offline') - self.dispatch('CONNECTION_LOST', - (_('Connection to proxy failed'), reason)) - - def _connect_success(self, con, con_type): - if not self.connected: # We went offline during connecting process - # FIXME - not possible, maybe it was when we used threads - return - _con_type = con_type - if _con_type != self._current_type: - log.info('Connecting to next type beacuse desired is %s and returned is %s' - % (self._current_type, _con_type)) - self.connect_to_next_type() - return - con.RegisterDisconnectHandler(self._on_disconnected) - if _con_type == 'plain' and gajim.config.get_per('accounts', self.name, - 'warn_when_plaintext_connection'): - self.dispatch('PLAIN_CONNECTION', (con,)) - return True - if _con_type in ('tls', 'ssl') and con.Connection.ssl_lib != 'PYOPENSSL' \ - and gajim.config.get_per('accounts', self.name, - 'warn_when_insecure_ssl_connection') and \ - not self.connection_auto_accepted: - # Pyopenssl is not used - self.dispatch('INSECURE_SSL_CONNECTION', (con, _con_type)) - return True - return self.connection_accepted(con, con_type) - - def connection_accepted(self, con, con_type): - if not con or not con.Connection: - self.disconnect(on_purpose=True) - self.dispatch('STATUS', 'offline') - self.dispatch('CONNECTION_LOST', - (_('Could not connect to account %s') % self.name, - _('Connection with account %s has been lost. Retry connecting.') % \ - self.name)) - return - self.hosts = [] - self.connection_auto_accepted = False - self.connected_hostname = self._current_host['host'] - self.on_connect_failure = None - con.UnregisterDisconnectHandler(self._on_disconnected) - con.RegisterDisconnectHandler(self._disconnectedReconnCB) - log.debug('Connected to server %s:%s with %s' % ( - self._current_host['host'], self._current_host['port'], con_type)) - - self.last_connection_type = con_type - if gajim.config.get_per('accounts', self.name, 'anonymous_auth'): - name = None - else: - name = gajim.config.get_per('accounts', self.name, 'name') - hostname = gajim.config.get_per('accounts', self.name, 'hostname') - self.connection = con - try: - errnum = con.Connection.ssl_errnum - except AttributeError: - errnum = -1 # we don't have an errnum - if errnum > 0 and str(errnum) not in gajim.config.get_per('accounts', - self.name, 'ignore_ssl_errors'): - text = _('The authenticity of the %s certificate could be invalid.') %\ - hostname - if errnum in ssl_error: - text += _('\nSSL Error: %s') % ssl_error[errnum] - else: - text += _('\nUnknown SSL error: %d') % errnum - self.dispatch('SSL_ERROR', (text, errnum, con.Connection.ssl_cert_pem, - con.Connection.ssl_fingerprint_sha1)) - return True - if hasattr(con.Connection, 'ssl_fingerprint_sha1'): - saved_fingerprint = gajim.config.get_per('accounts', self.name, 'ssl_fingerprint_sha1') - if saved_fingerprint: - # Check sha1 fingerprint - if con.Connection.ssl_fingerprint_sha1 != saved_fingerprint: - self.dispatch('FINGERPRINT_ERROR', - (con.Connection.ssl_fingerprint_sha1,)) - return True - else: - gajim.config.set_per('accounts', self.name, 'ssl_fingerprint_sha1', - con.Connection.ssl_fingerprint_sha1) - self._register_handlers(con, con_type) - con.auth( - user=name, - password=self.password, - resource=self.server_resource, - sasl=1, - on_auth=self.__on_auth) - - def ssl_certificate_accepted(self): - if not self.connection: - self.disconnect(on_purpose=True) - self.dispatch('STATUS', 'offline') - self.dispatch('CONNECTION_LOST', - (_('Could not connect to account %s') % self.name, - _('Connection with account %s has been lost. Retry connecting.') % \ - self.name)) - return - name = gajim.config.get_per('accounts', self.name, 'name') - self._register_handlers(self.connection, 'ssl') - self.connection.auth(name, self.password, self.server_resource, 1, - self.__on_auth) - - def _register_handlers(self, con, con_type): - self.peerhost = con.get_peerhost() - # notify the gui about con_type - self.dispatch('CON_TYPE', con_type) - ConnectionHandlers._register_handlers(self, con, con_type) - - def __on_auth(self, con, auth): - if not con: - self.disconnect(on_purpose=True) - self.dispatch('STATUS', 'offline') - self.dispatch('CONNECTION_LOST', - (_('Could not connect to "%s"') % self._hostname, - _('Check your connection or try again later'))) - if self.on_connect_auth: - self.on_connect_auth(None) - self.on_connect_auth = None - return - if not self.connected: # We went offline during connecting process - if self.on_connect_auth: - self.on_connect_auth(None) - self.on_connect_auth = None - return - if hasattr(con, 'Resource'): - self.server_resource = con.Resource - if gajim.config.get_per('accounts', self.name, 'anonymous_auth'): - # Get jid given by server - old_jid = gajim.get_jid_from_account(self.name) - gajim.config.set_per('accounts', self.name, 'name', con.User) - new_jid = gajim.get_jid_from_account(self.name) - self.dispatch('NEW_JID', (old_jid, new_jid)) - if auth: - self.last_io = gajim.idlequeue.current_time() - self.connected = 2 - self.retrycount = 0 - if self.on_connect_auth: - self.on_connect_auth(con) - self.on_connect_auth = None - else: - # Forget password, it's wrong - self.password = None - gajim.log.debug("Couldn't authenticate to %s" % self._hostname) - self.disconnect(on_purpose = True) - self.dispatch('STATUS', 'offline') - self.dispatch('ERROR', (_('Authentication failed with "%s"') % \ - self._hostname, - _('Please check your login and password for correctness.'))) - if self.on_connect_auth: - self.on_connect_auth(None) - self.on_connect_auth = None - # END connect - - def add_lang(self, stanza): - if self.lang: - stanza.setAttr('xml:lang', self.lang) - - def get_privacy_lists(self): - if not self.connection: - return - common.xmpp.features_nb.getPrivacyLists(self.connection) - - def send_keepalive(self): - # nothing received for the last foo seconds - if self.connection: - self.connection.send(' ') - - def sendPing(self, pingTo=None): - '''Send XMPP Ping (XEP-0199) request. If pingTo is not set, ping is sent - to server to detect connection failure at application level.''' - if not self.connection: - return - id_ = self.connection.getAnID() - if pingTo: - to = pingTo.get_full_jid() - self.dispatch('PING_SENT', (pingTo)) - else: - to = gajim.config.get_per('accounts', self.name, 'hostname') - self.awaiting_xmpp_ping_id = id_ - iq = common.xmpp.Iq('get', to=to) - iq.addChild(name = 'ping', namespace = common.xmpp.NS_PING) - iq.setID(id_) - def _on_response(resp): - timePong = time_time() - if not common.xmpp.isResultNode(resp): - self.dispatch('PING_ERROR', (pingTo)) - return - timeDiff = round(timePong - timePing,2) - self.dispatch('PING_REPLY', (pingTo, timeDiff)) - if pingTo: - timePing = time_time() - self.connection.SendAndCallForResponse(iq, _on_response) - else: - self.connection.send(iq) - gajim.idlequeue.set_alarm(self.check_pingalive, gajim.config.get_per( - 'accounts', self.name, 'time_for_ping_alive_answer')) - - def get_active_default_lists(self): - if not self.connection: - return - common.xmpp.features_nb.getActiveAndDefaultPrivacyLists(self.connection) - - def del_privacy_list(self, privacy_list): - if not self.connection: - return - def _on_del_privacy_list_result(result): - if result: - self.dispatch('PRIVACY_LIST_REMOVED', privacy_list) - else: - self.dispatch('ERROR', (_('Error while removing privacy list'), - _('Privacy list %s has not been removed. It is maybe active in ' - 'one of your connected resources. Deactivate it and try ' - 'again.') % privacy_list)) - common.xmpp.features_nb.delPrivacyList(self.connection, privacy_list, - _on_del_privacy_list_result) - - def get_privacy_list(self, title): - if not self.connection: - return - common.xmpp.features_nb.getPrivacyList(self.connection, title) - - def set_privacy_list(self, listname, tags): - if not self.connection: - return - common.xmpp.features_nb.setPrivacyList(self.connection, listname, tags) - - def set_active_list(self, listname): - if not self.connection: - return - common.xmpp.features_nb.setActivePrivacyList(self.connection, listname, 'active') - - def set_default_list(self, listname): - if not self.connection: - return - common.xmpp.features_nb.setDefaultPrivacyList(self.connection, listname) - - def build_privacy_rule(self, name, action, order=1): - '''Build a Privacy rule stanza for invisibility''' - iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '') - l = iq.getTag('query').setTag('list', {'name': name}) - i = l.setTag('item', {'action': action, 'order': str(order)}) - i.setTag('presence-out') - return iq - - def build_invisible_rule(self): - iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '') - l = iq.getTag('query').setTag('list', {'name': 'invisible'}) - if self.name in gajim.interface.status_sent_to_groups and \ - len(gajim.interface.status_sent_to_groups[self.name]) > 0: - for group in gajim.interface.status_sent_to_groups[self.name]: - i = l.setTag('item', {'type': 'group', 'value': group, - 'action': 'allow', 'order': '1'}) - i.setTag('presence-out') - if self.name in gajim.interface.status_sent_to_users and \ - len(gajim.interface.status_sent_to_users[self.name]) > 0: - for jid in gajim.interface.status_sent_to_users[self.name]: - i = l.setTag('item', {'type': 'jid', 'value': jid, - 'action': 'allow', 'order': '2'}) - i.setTag('presence-out') - i = l.setTag('item', {'action': 'deny', 'order': '3'}) - i.setTag('presence-out') - return iq - - def set_invisible_rule(self): - if not gajim.account_is_connected(self.name): - return - iq = self.build_invisible_rule() - self.connection.send(iq) - - def activate_privacy_rule(self, name): - '''activate a privacy rule''' - if not self.connection: - return - iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '') - iq.getTag('query').setTag('active', {'name': name}) - self.connection.send(iq) - - def send_invisible_presence(self, msg, signed, initial = False): - if not self.connection: - return - if not self.privacy_rules_supported: - self.dispatch('STATUS', gajim.SHOW_LIST[self.connected]) - self.dispatch('ERROR', (_('Invisibility not supported'), - _('Account %s doesn\'t support invisibility.') % self.name)) - return - # If we are already connected, and privacy rules are supported, send - # offline presence first as it's required by XEP-0126 - if self.connected > 1 and self.privacy_rules_supported: - self.on_purpose = True - p = common.xmpp.Presence(typ = 'unavailable') - p = self.add_sha(p, False) - if msg: - p.setStatus(msg) - self.remove_all_transfers() - self.connection.send(p) - - # try to set the privacy rule - iq = self.build_invisible_rule() - self.connection.SendAndCallForResponse(iq, self._continue_invisible, - {'msg': msg, 'signed': signed, 'initial': initial}) - - def _continue_invisible(self, con, iq_obj, msg, signed, initial): - if iq_obj.getType() == 'error': # server doesn't support privacy lists - return - # active the privacy rule - self.privacy_rules_supported = True - self.activate_privacy_rule('invisible') - self.connected = gajim.SHOW_LIST.index('invisible') - self.status = msg - priority = unicode(gajim.get_priority(self.name, 'invisible')) - p = common.xmpp.Presence(priority = priority) - p = self.add_sha(p, True) - if msg: - p.setStatus(msg) - if signed: - p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed) - self.connection.send(p) - self.priority = priority - self.dispatch('STATUS', 'invisible') - if initial: - #ask our VCard - self.request_vcard(None) - - #Get bookmarks from private namespace - self.get_bookmarks() - - #Get annotations - self.get_annotations() - - #Inform GUI we just signed in - self.dispatch('SIGNED_IN', ()) - - def get_signed_presence(self, msg, callback = None): - if gajim.config.get_per('accounts', self.name, 'gpg_sign_presence'): - return self.get_signed_msg(msg, callback) - return '' - - def connect_and_auth(self): - self.on_connect_success = self._connect_success - self.on_connect_failure = self._connect_failure - self.connect() - - def connect_and_init(self, show, msg, sign_msg): - self.continue_connect_info = [show, msg, sign_msg] - self.on_connect_auth = self._discover_server_at_connection - self.connect_and_auth() - - def _discover_server_at_connection(self, con): - self.connection = con - if not self.connection: - return - self.connection.set_send_timeout(self.keepalives, self.send_keepalive) - self.connection.set_send_timeout2(self.pingalives, self.sendPing) - self.connection.onreceive(None) - - self.request_message_archiving_preferences() - - self.discoverInfo(gajim.config.get_per('accounts', self.name, 'hostname'), - id_prefix='Gajim_') - self.privacy_rules_requested = False - - def _request_privacy(self): - iq = common.xmpp.Iq('get', common.xmpp.NS_PRIVACY, xmlns = '') - id_ = self.connection.getAnID() - iq.setID(id_) - self.awaiting_answers[id_] = (PRIVACY_ARRIVED, ) - self.connection.send(iq) - - def send_custom_status(self, show, msg, jid): - if not show in gajim.SHOW_LIST: - return -1 - if not self.connection: - return - sshow = helpers.get_xmpp_show(show) - if not msg: - msg = '' - if show == 'offline': - p = common.xmpp.Presence(typ = 'unavailable', to = jid) - p = self.add_sha(p, False) - if msg: - p.setStatus(msg) - else: - signed = self.get_signed_presence(msg) - priority = unicode(gajim.get_priority(self.name, sshow)) - p = common.xmpp.Presence(typ = None, priority = priority, show = sshow, - to = jid) - p = self.add_sha(p) - if msg: - p.setStatus(msg) - if signed: - p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed) - self.connection.send(p) - - def _change_to_invisible(self, msg): - signed = self.get_signed_presence(msg) - self.send_invisible_presence(msg, signed) - - def _change_from_invisible(self): - if self.privacy_rules_supported: - iq = self.build_privacy_rule('visible', 'allow') - self.connection.send(iq) - self.activate_privacy_rule('visible') - - def _update_status(self, show, msg): - xmpp_show = helpers.get_xmpp_show(show) - priority = unicode(gajim.get_priority(self.name, xmpp_show)) - p = common.xmpp.Presence(typ=None, priority=priority, show=xmpp_show) - p = self.add_sha(p) - if msg: - p.setStatus(msg) - signed = self.get_signed_presence(msg) - if signed: - p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed) - if self.connection: - self.connection.send(p) - self.priority = priority - self.dispatch('STATUS', show) - - def send_motd(self, jid, subject = '', msg = '', xhtml = None): - if not self.connection: - return - msg_iq = common.xmpp.Message(to = jid, body = msg, subject = subject, - xhtml = xhtml) - - self.connection.send(msg_iq) - - def send_message(self, jid, msg, keyID, type_='chat', subject='', - chatstate=None, msg_id=None, composing_xep=None, resource=None, - user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None, - original_message=None, delayed=None, callback=None, callback_args=[]): - - def cb(jid, msg, keyID, forward_from, session, original_message, subject, - type_, msg_iq): - msg_id = self.connection.send(msg_iq) - jid = helpers.parse_jid(jid) - self.dispatch('MSGSENT', (jid, msg, keyID)) - if callback: - callback(msg_id, *callback_args) - - self.log_message(jid, msg, forward_from, session, original_message, - subject, type_) - - self._prepare_message(jid, msg, keyID, type_=type_, subject=subject, - chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep, - resource=resource, user_nick=user_nick, xhtml=xhtml, session=session, - forward_from=forward_from, form_node=form_node, - original_message=original_message, delayed=delayed, callback=cb) - - def send_contacts(self, contacts, jid): - '''Send contacts with RosterX (Xep-0144)''' - if not self.connection: - return - if len(contacts) == 1: - msg = _('Sent contact: "%s" (%s)') % (contacts[0].get_full_jid(), - contacts[0].get_shown_name()) - else: - msg = _('Sent contacts:') - for contact in contacts: - msg += '\n "%s" (%s)' % (contact.get_full_jid(), - contact.get_shown_name()) - msg_iq = common.xmpp.Message(to=jid, body=msg) - x = msg_iq.addChild(name='x', namespace=common.xmpp.NS_ROSTERX) - for contact in contacts: - x.addChild(name='item', attrs={'action': 'add', 'jid': contact.jid, - 'name': contact.get_shown_name()}) - self.connection.send(msg_iq) - - def send_stanza(self, stanza): - ''' send a stanza untouched ''' - if not self.connection: - return - self.connection.send(stanza) - - def ack_subscribed(self, jid): - if not self.connection: - return - log.debug('ack\'ing subscription complete for %s' % jid) - p = common.xmpp.Presence(jid, 'subscribe') - self.connection.send(p) - - def ack_unsubscribed(self, jid): - if not self.connection: - return - log.debug('ack\'ing unsubscription complete for %s' % jid) - p = common.xmpp.Presence(jid, 'unsubscribe') - self.connection.send(p) - - def request_subscription(self, jid, msg = '', name = '', groups = [], - auto_auth = False, user_nick = ''): - if not self.connection: - return - log.debug('subscription request for %s' % jid) - if auto_auth: - self.jids_for_auto_auth.append(jid) - # RFC 3921 section 8.2 - infos = {'jid': jid} - if name: - infos['name'] = name - iq = common.xmpp.Iq('set', common.xmpp.NS_ROSTER) - q = iq.getTag('query') - item = q.addChild('item', attrs = infos) - for g in groups: - item.addChild('group').setData(g) - self.connection.send(iq) - - p = common.xmpp.Presence(jid, 'subscribe') - if user_nick: - p.setTag('nick', namespace = common.xmpp.NS_NICK).setData(user_nick) - p = self.add_sha(p) - if msg: - p.setStatus(msg) - self.connection.send(p) - - def send_authorization(self, jid): - if not self.connection: - return - p = common.xmpp.Presence(jid, 'subscribed') - p = self.add_sha(p) - self.connection.send(p) - - def refuse_authorization(self, jid): - if not self.connection: - return - p = common.xmpp.Presence(jid, 'unsubscribed') - p = self.add_sha(p) - self.connection.send(p) - - def unsubscribe(self, jid, remove_auth = True): - if not self.connection: - return - if remove_auth: - self.connection.getRoster().delItem(jid) - jid_list = gajim.config.get_per('contacts') - for j in jid_list: - if j.startswith(jid): - gajim.config.del_per('contacts', j) - else: - self.connection.getRoster().Unsubscribe(jid) - self.update_contact(jid, '', []) - - def unsubscribe_agent(self, agent): - if not self.connection: - return - iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to = agent) - iq.getTag('query').setTag('remove') - id_ = self.connection.getAnID() - iq.setID(id_) - self.awaiting_answers[id_] = (AGENT_REMOVED, agent) - self.connection.send(iq) - self.connection.getRoster().delItem(agent) - - def send_new_account_infos(self, form, is_form): - if is_form: - # Get username and password and put them in new_account_info - for field in form.iter_fields(): - if field.var == 'username': - self.new_account_info['name'] = field.value - if field.var == 'password': - self.new_account_info['password'] = field.value - else: - # Get username and password and put them in new_account_info - if 'username' in form: - self.new_account_info['name'] = form['username'] - if 'password' in form: - self.new_account_info['password'] = form['password'] - self.new_account_form = form - self.new_account(self.name, self.new_account_info) - - def new_account(self, name, config, sync=False): - # If a connection already exist we cannot create a new account - if self.connection: - return - self._hostname = config['hostname'] - self.new_account_info = config - self.name = name - self.on_connect_success = self._on_new_account - self.on_connect_failure = self._on_new_account - self.connect(config) - - def _on_new_account(self, con=None, con_type=None): - if not con_type: - if len(self._connection_types) or len(self._hosts): - # There are still other way to try to connect - return - self.dispatch('NEW_ACC_NOT_CONNECTED', - (_('Could not connect to "%s"') % self._hostname)) - return - self.on_connect_failure = None - self.connection = con - common.xmpp.features_nb.getRegInfo(con, self._hostname) - - def request_last_status_time(self, jid, resource, groupchat_jid=None): - '''groupchat_jid is used when we want to send a request to a real jid - and act as if the answer comes from the groupchat_jid''' - if not self.connection: - return - to_whom_jid = jid - if resource: - to_whom_jid += '/' + resource - iq = common.xmpp.Iq(to = to_whom_jid, typ = 'get', queryNS =\ - common.xmpp.NS_LAST) - id_ = self.connection.getAnID() - iq.setID(id_) - if groupchat_jid: - self.groupchat_jids[id_] = groupchat_jid - self.last_ids.append(id_) - self.connection.send(iq) - - def request_os_info(self, jid, resource, groupchat_jid=None): - '''groupchat_jid is used when we want to send a request to a real jid - and act as if the answer comes from the groupchat_jid''' - if not self.connection: - return - # If we are invisible, do not request - if self.connected == gajim.SHOW_LIST.index('invisible'): - self.dispatch('OS_INFO', (jid, resource, _('Not fetched because of invisible status'), _('Not fetched because of invisible status'))) - return - to_whom_jid = jid - if resource: - to_whom_jid += '/' + resource - iq = common.xmpp.Iq(to=to_whom_jid, typ='get', queryNS=\ - common.xmpp.NS_VERSION) - id_ = self.connection.getAnID() - iq.setID(id_) - if groupchat_jid: - self.groupchat_jids[id_] = groupchat_jid - self.version_ids.append(id_) - self.connection.send(iq) - - def request_entity_time(self, jid, resource, groupchat_jid=None): - '''groupchat_jid is used when we want to send a request to a real jid - and act as if the answer comes from the groupchat_jid''' - if not self.connection: - return - # If we are invisible, do not request - if self.connected == gajim.SHOW_LIST.index('invisible'): - self.dispatch('ENTITY_TIME', (jid, resource, _('Not fetched because of invisible status'))) - return - to_whom_jid = jid - if resource: - to_whom_jid += '/' + resource - iq = common.xmpp.Iq(to=to_whom_jid, typ='get') - iq.addChild('time', namespace=common.xmpp.NS_TIME_REVISED) - id_ = self.connection.getAnID() - iq.setID(id_) - if groupchat_jid: - self.groupchat_jids[id_] = groupchat_jid - self.entity_time_ids.append(id_) - self.connection.send(iq) - - def get_settings(self): - ''' Get Gajim settings as described in XEP 0049 ''' - if not self.connection: - return - iq = common.xmpp.Iq(typ='get') - iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) - iq2.addChild(name='gajim', namespace='gajim:prefs') - self.connection.send(iq) - - def _request_bookmarks_xml(self): - if not self.connection: - return - iq = common.xmpp.Iq(typ='get') - iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) - iq2.addChild(name='storage', namespace='storage:bookmarks') - self.connection.send(iq) - - def _check_bookmarks_received(self): - if not self.bookmarks: - self._request_bookmarks_xml() - - def get_bookmarks(self, storage_type=None): - '''Get Bookmarks from storage or PubSub if supported as described in - XEP 0048 - storage_type can be set to xml to force request to xml storage''' - if not self.connection: - return - if self.pubsub_supported and storage_type != 'xml': - self.send_pb_retrieve('', 'storage:bookmarks') - # some server (ejabberd) are so slow to answer that we request via XML - # if we don't get answer in the next 30 seconds - gajim.idlequeue.set_alarm(self._check_bookmarks_received, 30) - else: - self._request_bookmarks_xml() - - def store_bookmarks(self, storage_type=None): - ''' Send bookmarks to the storage namespace or PubSub if supported - storage_type can be set to 'pubsub' or 'xml' so store in only one method - else it will be stored on both''' - if not self.connection: - return - iq = common.xmpp.Node(tag='storage', attrs={'xmlns': 'storage:bookmarks'}) - for bm in self.bookmarks: - iq2 = iq.addChild(name = "conference") - iq2.setAttr('jid', bm['jid']) - iq2.setAttr('autojoin', bm['autojoin']) - iq2.setAttr('minimize', bm['minimize']) - iq2.setAttr('name', bm['name']) - # Only add optional elements if not empty - # Note: need to handle both None and '' as empty - # thus shouldn't use "is not None" - if bm.get('nick', None): - iq2.setTagData('nick', bm['nick']) - if bm.get('password', None): - iq2.setTagData('password', bm['password']) - if bm.get('print_status', None): - iq2.setTagData('print_status', bm['print_status']) - - if self.pubsub_supported and storage_type != 'xml': - if self.pubsub_publish_options_supported: - options = common.xmpp.Node(common.xmpp.NS_DATA + ' x', - attrs={'type': 'submit'}) - f = options.addChild('field', attrs={'var': 'FORM_TYPE', - 'type': 'hidden'}) - f.setTagData('value', common.xmpp.NS_PUBSUB_PUBLISH_OPTIONS) - f = options.addChild('field', attrs={'var': 'pubsub#persist_items'}) - f.setTagData('value', 'true') - f = options.addChild('field', attrs={'var': 'pubsub#access_model'}) - f.setTagData('value', 'whitelist') - else: - options = None - self.send_pb_publish('', 'storage:bookmarks', iq, 'current', - options=options) - if storage_type != 'pubsub': - iqA = common.xmpp.Iq(typ='set') - iqB = iqA.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) - iqB.addChild(node=iq) - self.connection.send(iqA) - - def get_annotations(self): - '''Get Annonations from storage as described in XEP 0048, and XEP 0145''' - self.annotations = {} - if not self.connection: - return - iq = common.xmpp.Iq(typ='get') - iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) - iq2.addChild(name='storage', namespace='storage:rosternotes') - self.connection.send(iq) - - def store_annotations(self): - '''Set Annonations in private storage as described in XEP 0048, and XEP 0145''' - if not self.connection: - return - iq = common.xmpp.Iq(typ='set') - iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) - iq3 = iq2.addChild(name='storage', namespace='storage:rosternotes') - for jid in self.annotations.keys(): - if self.annotations[jid]: - iq4 = iq3.addChild(name = "note") - iq4.setAttr('jid', jid) - iq4.setData(self.annotations[jid]) - self.connection.send(iq) - - - def get_metacontacts(self): - '''Get metacontacts list from storage as described in XEP 0049''' - if not self.connection: - return - iq = common.xmpp.Iq(typ='get') - iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) - iq2.addChild(name='storage', namespace='storage:metacontacts') - id_ = self.connection.getAnID() - iq.setID(id_) - self.awaiting_answers[id_] = (METACONTACTS_ARRIVED, ) - self.connection.send(iq) - - def store_metacontacts(self, tags_list): - ''' Send meta contacts to the storage namespace ''' - if not self.connection: - return - iq = common.xmpp.Iq(typ='set') - iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) - iq3 = iq2.addChild(name='storage', namespace='storage:metacontacts') - for tag in tags_list: - for data in tags_list[tag]: - jid = data['jid'] - dict_ = {'jid': jid, 'tag': tag} - if 'order' in data: - dict_['order'] = data['order'] - iq3.addChild(name = 'meta', attrs = dict_) - self.connection.send(iq) - - def send_agent_status(self, agent, ptype): - if not self.connection: - return - show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected]) - p = common.xmpp.Presence(to = agent, typ = ptype, show = show) - p = self.add_sha(p, ptype != 'unavailable') - self.connection.send(p) - - def check_unique_room_id_support(self, server, instance): - if not self.connection: - return - iq = common.xmpp.Iq(typ = 'get', to = server) - iq.setAttr('id', 'unique1') - iq.addChild('unique', namespace=common.xmpp.NS_MUC_UNIQUE) - def _on_response(resp): - if not common.xmpp.isResultNode(resp): - self.dispatch('UNIQUE_ROOM_ID_UNSUPPORTED', (server, instance)) - return - self.dispatch('UNIQUE_ROOM_ID_SUPPORTED', (server, instance, - resp.getTag('unique').getData())) - self.connection.SendAndCallForResponse(iq, _on_response) - - def join_gc(self, nick, room_jid, password, change_nick=False): - # FIXME: This room JID needs to be normalized; see #1364 - if not self.connection: - return - show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected]) - if show == 'invisible': - # Never join a room when invisible - return - - # last date/time in history to avoid duplicate - if room_jid not in self.last_history_time: - # Not in memory, get it from DB - last_log = None - # Do not check if we are not logging for this room - if gajim.config.should_log(self.name, room_jid): - # Check time first in the FAST table - last_log = gajim.logger.get_room_last_message_time(room_jid) - if last_log is None: - # Not in special table, get it from messages DB - last_log = gajim.logger.get_last_date_that_has_logs(room_jid, - is_room=True) - # Create self.last_history_time[room_jid] even if not logging, - # could be used in connection_handlers - if last_log is None: - last_log = 0 - self.last_history_time[room_jid] = last_log - - p = common.xmpp.Presence(to='%s/%s' % (room_jid, nick), - show=show, status=self.status) - h = hmac.new(self.secret_hmac, room_jid).hexdigest()[:6] - id_ = self.connection.getAnID() - id_ = 'gajim_muc_' + id_ + '_' + h - p.setID(id_) - if gajim.config.get('send_sha_in_gc_presence'): - p = self.add_sha(p) - self.add_lang(p) - if not change_nick: - t = p.setTag(common.xmpp.NS_MUC + ' x') - last_date = self.last_history_time[room_jid] - if last_date == 0: - last_date = time.time() - gajim.config.get( - 'muc_restore_timeout') * 60 - else: - last_time = min(last_date, time.time() - gajim.config.get( - 'muc_restore_timeout') * 60) - last_date = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(last_date)) - t.setTag('history', {'maxstanzas': gajim.config.get( - 'muc_restore_lines'), 'since': last_date}) - if password: - t.setTagData('password', password) - self.connection.send(p) - - def send_gc_message(self, jid, msg, xhtml = None): - if not self.connection: - return - if not xhtml and gajim.config.get('rst_formatting_outgoing_messages'): - from common.rst_xhtml_generator import create_xhtml - xhtml = create_xhtml(msg) - msg_iq = common.xmpp.Message(jid, msg, typ = 'groupchat', xhtml = xhtml) - self.connection.send(msg_iq) - self.dispatch('MSGSENT', (jid, msg)) - - def send_gc_subject(self, jid, subject): - if not self.connection: - return - msg_iq = common.xmpp.Message(jid,typ = 'groupchat', subject = subject) - self.connection.send(msg_iq) - - def request_gc_config(self, room_jid): - if not self.connection: - return - iq = common.xmpp.Iq(typ = 'get', queryNS = common.xmpp.NS_MUC_OWNER, - to = room_jid) - self.add_lang(iq) - self.connection.send(iq) - - def destroy_gc_room(self, room_jid, reason = '', jid = ''): - if not self.connection: - return - iq = common.xmpp.Iq(typ = 'set', queryNS = common.xmpp.NS_MUC_OWNER, - to = room_jid) - destroy = iq.getTag('query').setTag('destroy') - if reason: - destroy.setTagData('reason', reason) - if jid: - destroy.setAttr('jid', jid) - self.connection.send(iq) - - def send_gc_status(self, nick, jid, show, status): - if not gajim.account_is_connected(self.name): - return - if show == 'invisible': - show = 'offline' - ptype = None - if show == 'offline': - ptype = 'unavailable' - xmpp_show = helpers.get_xmpp_show(show) - p = common.xmpp.Presence(to = '%s/%s' % (jid, nick), typ = ptype, - show = xmpp_show, status = status) - h = hmac.new(self.secret_hmac, jid).hexdigest()[:6] - id_ = self.connection.getAnID() - id_ = 'gajim_muc_' + id_ + '_' + h - p.setID(id_) - if gajim.config.get('send_sha_in_gc_presence') and show != 'offline': - p = self.add_sha(p, ptype != 'unavailable') - self.add_lang(p) - # send instantly so when we go offline, status is sent to gc before we - # disconnect from jabber server - self.connection.send(p) - - def gc_got_disconnected(self, room_jid): - ''' A groupchat got disconnected. This can be or purpose or not. - Save the time we quit to avoid duplicate logs AND be faster than get that - date from DB. Save it in mem AND in a small table (with fast access) - ''' - log_time = time_time() - self.last_history_time[room_jid] = log_time - gajim.logger.set_room_last_message_time(room_jid, log_time) - - def gc_set_role(self, room_jid, nick, role, reason = ''): - '''role is for all the life of the room so it's based on nick''' - if not self.connection: - return - iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ - common.xmpp.NS_MUC_ADMIN) - item = iq.getTag('query').setTag('item') - item.setAttr('nick', nick) - item.setAttr('role', role) - if reason: - item.addChild(name = 'reason', payload = reason) - self.connection.send(iq) - - def gc_set_affiliation(self, room_jid, jid, affiliation, reason = ''): - '''affiliation is for all the life of the room so it's based on jid''' - if not self.connection: - return - iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ - common.xmpp.NS_MUC_ADMIN) - item = iq.getTag('query').setTag('item') - item.setAttr('jid', jid) - item.setAttr('affiliation', affiliation) - if reason: - item.addChild(name = 'reason', payload = reason) - self.connection.send(iq) - - def send_gc_affiliation_list(self, room_jid, users_dict): - if not self.connection: - return - iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS = \ - common.xmpp.NS_MUC_ADMIN) - item = iq.getTag('query') - for jid in users_dict: - item_tag = item.addChild('item', {'jid': jid, - 'affiliation': users_dict[jid]['affiliation']}) - if 'reason' in users_dict[jid] and users_dict[jid]['reason']: - item_tag.setTagData('reason', users_dict[jid]['reason']) - self.connection.send(iq) - - def get_affiliation_list(self, room_jid, affiliation): - if not self.connection: - return - iq = common.xmpp.Iq(typ = 'get', to = room_jid, queryNS = \ - common.xmpp.NS_MUC_ADMIN) - item = iq.getTag('query').setTag('item') - item.setAttr('affiliation', affiliation) - self.connection.send(iq) - - def send_gc_config(self, room_jid, form): - if not self.connection: - return - iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ - common.xmpp.NS_MUC_OWNER) - query = iq.getTag('query') - form.setAttr('type', 'submit') - query.addChild(node = form) - self.connection.send(iq) - - def change_password(self, password): - if not self.connection: - return - hostname = gajim.config.get_per('accounts', self.name, 'hostname') - username = gajim.config.get_per('accounts', self.name, 'name') - iq = common.xmpp.Iq(typ = 'set', to = hostname) - q = iq.setTag(common.xmpp.NS_REGISTER + ' query') - q.setTagData('username',username) - q.setTagData('password',password) - self.connection.send(iq) - - def get_password(self, callback): - if self.password: - callback(self.password) - return - self.pasword_callback = callback - self.dispatch('PASSWORD_REQUIRED', None) - - def set_password(self, password): - self.password = password - if self.pasword_callback: - self.pasword_callback(password) - self.pasword_callback = None - - def unregister_account(self, on_remove_success): - # no need to write this as a class method and keep the value of - # on_remove_success as a class property as pass it as an argument - def _on_unregister_account_connect(con): - self.on_connect_auth = None - if gajim.account_is_connected(self.name): - hostname = gajim.config.get_per('accounts', self.name, 'hostname') - iq = common.xmpp.Iq(typ = 'set', to = hostname) - iq.setTag(common.xmpp.NS_REGISTER + ' query').setTag('remove') - def _on_answer(result): - if result.getType() == 'result': - on_remove_success(True) - return - self.dispatch('ERROR', (_('Unregister failed'), - _('Unregistration with server %(server)s failed: %(error)s') \ - % {'server': hostname, 'error': result.getErrorMsg()})) - on_remove_success(False) - con.SendAndCallForResponse(iq, _on_answer) - return - on_remove_success(False) - if self.connected == 0: - self.on_connect_auth = _on_unregister_account_connect - self.connect_and_auth() - else: - _on_unregister_account_connect(self.connection) - - def send_invite(self, room, to, reason='', continue_tag=False): - '''sends invitation''' - message=common.xmpp.Message(to = room) - c = message.addChild(name = 'x', namespace = common.xmpp.NS_MUC_USER) - c = c.addChild(name = 'invite', attrs={'to' : to}) - if continue_tag: - c.addChild(name = 'continue') - if reason != '': - c.setTagData('reason', reason) - self.connection.send(message) - - def check_pingalive(self): - if self.awaiting_xmpp_ping_id: - # We haven't got the pong in time, disco and reconnect - self._disconnectedReconnCB() - - def _reconnect_alarm(self): - if self.time_to_reconnect: - if self.connected < 2: - self._reconnect() - else: - self.time_to_reconnect = None - - def request_search_fields(self, jid): - iq = common.xmpp.Iq(typ = 'get', to = jid, queryNS = \ - common.xmpp.NS_SEARCH) - self.connection.send(iq) - - def send_search_form(self, jid, form, is_form): - iq = common.xmpp.Iq(typ = 'set', to = jid, queryNS = \ - common.xmpp.NS_SEARCH) - item = iq.getTag('query') - if is_form: - item.addChild(node = form) - else: - for i in form.keys(): - item.setTagData(i,form[i]) - def _on_response(resp): - jid = jid = helpers.get_jid_from_iq(resp) - tag = resp.getTag('query', namespace = common.xmpp.NS_SEARCH) - if not tag: - self.dispatch('SEARCH_RESULT', (jid, None, False)) - return - df = tag.getTag('x', namespace = common.xmpp.NS_DATA) - if df: - self.dispatch('SEARCH_RESULT', (jid, df, True)) - return - df = [] - for item in tag.getTags('item'): - # We also show attributes. jid is there - f = item.attrs - for i in item.getPayload(): - f[i.getName()] = i.getData() - df.append(f) - self.dispatch('SEARCH_RESULT', (jid, df, False)) - - self.connection.SendAndCallForResponse(iq, _on_response) - - def load_roster_from_db(self): - roster = gajim.logger.get_roster(gajim.get_jid_from_account(self.name)) - self.dispatch('ROSTER', roster) + '''Connection class''' + def __init__(self, name): + CommonConnection.__init__(self, name) + ConnectionHandlers.__init__(self) + # this property is used to prevent double connections + self.last_connection = None # last ClientCommon instance + # If we succeed to connect, remember it so next time we try (after a + # disconnection) we try only this type. + self.last_connection_type = None + self.lang = None + if locale.getdefaultlocale()[0]: + self.lang = locale.getdefaultlocale()[0].split('_')[0] + # increase/decrease default timeout for server responses + self.try_connecting_for_foo_secs = 45 + # holds the actual hostname to which we are connected + self.connected_hostname = None + self.last_time_to_reconnect = None + self.new_account_info = None + self.new_account_form = None + self.annotations = {} + self.last_io = gajim.idlequeue.current_time() + self.last_sent = [] + self.last_history_time = {} + self.password = passwords.get_password(name) + + # Used to ask privacy only once at connection + self.music_track_info = 0 + self.pubsub_supported = False + self.pubsub_publish_options_supported = False + # Do we auto accept insecure connection + self.connection_auto_accepted = False + self.pasword_callback = None + + self.on_connect_success = None + self.on_connect_failure = None + self.retrycount = 0 + self.jids_for_auto_auth = [] # list of jid to auto-authorize + self.available_transports = {} # list of available transports on this + # server {'icq': ['icq.server.com', 'icq2.server.com'], } + self.private_storage_supported = True + self.streamError = '' + self.secret_hmac = str(random.random())[2:] + # END __init__ + + def get_config_values_or_default(self): + if gajim.config.get_per('accounts', self.name, 'keep_alives_enabled'): + self.keepalives = gajim.config.get_per('accounts', self.name, + 'keep_alive_every_foo_secs') + else: + self.keepalives = 0 + if gajim.config.get_per('accounts', self.name, 'ping_alives_enabled'): + self.pingalives = gajim.config.get_per('accounts', self.name, + 'ping_alive_every_foo_secs') + else: + self.pingalives = 0 + + def check_jid(self, jid): + return helpers.parse_jid(jid) + + def _reconnect(self): + # Do not try to reco while we are already trying + self.time_to_reconnect = None + if self.connected < 2: # connection failed + log.debug('reconnect') + self.connected = 1 + self.dispatch('STATUS', 'connecting') + self.retrycount += 1 + self.on_connect_auth = self._discover_server_at_connection + self.connect_and_init(self.old_show, self.status, self.USE_GPG) + else: + # reconnect succeeded + self.time_to_reconnect = None + self.retrycount = 0 + + # We are doing disconnect at so many places, better use one function in all + def disconnect(self, on_purpose=False): + gajim.interface.music_track_changed(None, None, self.name) + self.on_purpose = on_purpose + self.connected = 0 + self.time_to_reconnect = None + self.privacy_rules_supported = False + if self.connection: + # make sure previous connection is completely closed + gajim.proxy65_manager.disconnect(self.connection) + self.terminate_sessions() + self.remove_all_transfers() + self.connection.disconnect() + self.last_connection = None + self.connection = None + + def _disconnectedReconnCB(self): + '''Called when we are disconnected''' + log.info('disconnectedReconnCB called') + if gajim.account_is_connected(self.name): + # we cannot change our status to offline or connecting + # after we auth to server + self.old_show = gajim.SHOW_LIST[self.connected] + self.connected = 0 + if not self.on_purpose: + self.dispatch('STATUS', 'offline') + self.disconnect() + if gajim.config.get_per('accounts', self.name, 'autoreconnect'): + self.connected = -1 + self.dispatch('STATUS', 'error') + if gajim.status_before_autoaway[self.name]: + # We were auto away. So go back online + self.status = gajim.status_before_autoaway[self.name] + gajim.status_before_autoaway[self.name] = '' + self.old_show = 'online' + # this check has moved from _reconnect method + # do exponential backoff until 15 minutes, + # then small linear increase + if self.retrycount < 2 or self.last_time_to_reconnect is None: + self.last_time_to_reconnect = 5 + if self.last_time_to_reconnect < 800: + self.last_time_to_reconnect *= 1.5 + self.last_time_to_reconnect += randomsource.randint(0, 5) + self.time_to_reconnect = int(self.last_time_to_reconnect) + log.info("Reconnect to %s in %ss", self.name, self.time_to_reconnect) + gajim.idlequeue.set_alarm(self._reconnect_alarm, + self.time_to_reconnect) + elif self.on_connect_failure: + self.on_connect_failure() + self.on_connect_failure = None + else: + # show error dialog + self._connection_lost() + else: + self.disconnect() + self.on_purpose = False + # END disconenctedReconnCB + + def _connection_lost(self): + log.debug('_connection_lost') + self.disconnect(on_purpose = False) + self.dispatch('STATUS', 'offline') + self.dispatch('CONNECTION_LOST', + (_('Connection with account "%s" has been lost') % self.name, + _('Reconnect manually.'))) + + def _event_dispatcher(self, realm, event, data): + CommonConnection._event_dispatcher(self, realm, event, data) + if realm == common.xmpp.NS_REGISTER: + if event == common.xmpp.features_nb.REGISTER_DATA_RECEIVED: + # data is (agent, DataFrom, is_form, error_msg) + if self.new_account_info and \ + self.new_account_info['hostname'] == data[0]: + # it's a new account + if not data[1]: # wrong answer + self.dispatch('ACC_NOT_OK', ( + _('Server %(name)s answered wrongly to register request: ' + '%(error)s') % {'name': data[0], 'error': data[3]})) + return + is_form = data[2] + conf = data[1] + if self.new_account_form: + def _on_register_result(result): + if not common.xmpp.isResultNode(result): + self.dispatch('ACC_NOT_OK', (result.getError())) + return + if gajim.HAVE_GPG: + self.USE_GPG = True + self.gpg = GnuPG.GnuPG(gajim.config.get( + 'use_gpg_agent')) + self.dispatch('ACC_OK', (self.new_account_info)) + self.new_account_info = None + self.new_account_form = None + if self.connection: + self.connection.UnregisterDisconnectHandler( + self._on_new_account) + self.disconnect(on_purpose=True) + # it's the second time we get the form, we have info user + # typed, so send them + if is_form: + #TODO: Check if form has changed + iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to=self._hostname) + iq.setTag('query').addChild(node=self.new_account_form) + self.connection.SendAndCallForResponse(iq, + _on_register_result) + else: + if self.new_account_form.keys().sort() != \ + conf.keys().sort(): + # requested config has changed since first connection + self.dispatch('ACC_NOT_OK', (_( + 'Server %s provided a different registration form')\ + % data[0])) + return + common.xmpp.features_nb.register(self.connection, + self._hostname, self.new_account_form, + _on_register_result) + return + try: + errnum = self.connection.Connection.ssl_errnum + except AttributeError: + errnum = -1 # we don't have an errnum + ssl_msg = '' + if errnum > 0: + ssl_msg = ssl_error.get(errnum, _('Unknown SSL error: %d') % errnum) + ssl_cert = '' + if hasattr(self.connection.Connection, 'ssl_cert_pem'): + ssl_cert = self.connection.Connection.ssl_cert_pem + ssl_fingerprint = '' + if hasattr(self.connection.Connection, 'ssl_fingerprint_sha1'): + ssl_fingerprint = \ + self.connection.Connection.ssl_fingerprint_sha1 + self.dispatch('NEW_ACC_CONNECTED', (conf, is_form, ssl_msg, + errnum, ssl_cert, ssl_fingerprint)) + self.connection.UnregisterDisconnectHandler( + self._on_new_account) + self.disconnect(on_purpose=True) + return + if not data[1]: # wrong answer + self.dispatch('ERROR', (_('Invalid answer'), + _('Transport %(name)s answered wrongly to register request: ' + '%(error)s') % {'name': data[0], 'error': data[3]})) + return + is_form = data[2] + conf = data[1] + self.dispatch('REGISTER_AGENT_INFO', (data[0], conf, is_form)) + elif realm == common.xmpp.NS_PRIVACY: + if event == common.xmpp.features_nb.PRIVACY_LISTS_RECEIVED: + # data is (list) + self.dispatch('PRIVACY_LISTS_RECEIVED', (data)) + elif event == common.xmpp.features_nb.PRIVACY_LIST_RECEIVED: + # data is (resp) + if not data: + return + rules = [] + name = data.getTag('query').getTag('list').getAttr('name') + for child in data.getTag('query').getTag('list').getChildren(): + dict_item = child.getAttrs() + childs = [] + if 'type' in dict_item: + for scnd_child in child.getChildren(): + childs += [scnd_child.getName()] + rules.append({'action':dict_item['action'], + 'type':dict_item['type'], 'order':dict_item['order'], + 'value':dict_item['value'], 'child':childs}) + else: + for scnd_child in child.getChildren(): + childs.append(scnd_child.getName()) + rules.append({'action':dict_item['action'], + 'order':dict_item['order'], 'child':childs}) + self.dispatch('PRIVACY_LIST_RECEIVED', (name, rules)) + elif event == common.xmpp.features_nb.PRIVACY_LISTS_ACTIVE_DEFAULT: + # data is (dict) + self.dispatch('PRIVACY_LISTS_ACTIVE_DEFAULT', (data)) + + def _select_next_host(self, hosts): + '''Selects the next host according to RFC2782 p.3 based on it's + priority. Chooses between hosts with the same priority randomly, + where the probability of being selected is proportional to the weight + of the host.''' + + hosts_by_prio = sorted(hosts, key=operator.itemgetter('prio')) + + try: + lowest_prio = hosts_by_prio[0]['prio'] + except IndexError: + raise ValueError("No hosts to choose from!") + + hosts_lowest_prio = [h for h in hosts_by_prio if h['prio'] == lowest_prio] + + if len(hosts_lowest_prio) == 1: + return hosts_lowest_prio[0] + else: + rndint = random.randint(0, sum(h['weight'] for h in hosts_lowest_prio)) + weightsum = 0 + for host in sorted(hosts_lowest_prio, key=operator.itemgetter( + 'weight')): + weightsum += host['weight'] + if weightsum >= rndint: + return host + + def connect(self, data = None): + ''' Start a connection to the Jabber server. + Returns connection, and connection type ('tls', 'ssl', 'plain', '') + data MUST contain hostname, usessl, proxy, use_custom_host, + custom_host (if use_custom_host), custom_port (if use_custom_host)''' + if self.connection: + return self.connection, '' + + if data: + hostname = data['hostname'] + self.try_connecting_for_foo_secs = 45 + p = data['proxy'] + use_srv = True + use_custom = data['use_custom_host'] + if use_custom: + custom_h = data['custom_host'] + custom_p = data['custom_port'] + else: + hostname = gajim.config.get_per('accounts', self.name, 'hostname') + usessl = gajim.config.get_per('accounts', self.name, 'usessl') + self.try_connecting_for_foo_secs = gajim.config.get_per('accounts', + self.name, 'try_connecting_for_foo_secs') + p = gajim.config.get_per('accounts', self.name, 'proxy') + use_srv = gajim.config.get_per('accounts', self.name, 'use_srv') + use_custom = gajim.config.get_per('accounts', self.name, + 'use_custom_host') + custom_h = gajim.config.get_per('accounts', self.name, 'custom_host') + custom_p = gajim.config.get_per('accounts', self.name, 'custom_port') + + # create connection if it doesn't already exist + self.connected = 1 + if p and p in gajim.config.get_per('proxies'): + proxy = {} + proxyptr = gajim.config.get_per('proxies', p) + for key in proxyptr.keys(): + proxy[key] = proxyptr[key][1] + + elif gajim.config.get_per('accounts', self.name, 'use_env_http_proxy'): + try: + try: + env_http_proxy = os.environ['HTTP_PROXY'] + except Exception: + env_http_proxy = os.environ['http_proxy'] + env_http_proxy = env_http_proxy.strip('"') + # Dispose of the http:// prefix + env_http_proxy = env_http_proxy.split('://') + env_http_proxy = env_http_proxy[len(env_http_proxy)-1] + env_http_proxy = env_http_proxy.split('@') + + if len(env_http_proxy) == 2: + login = env_http_proxy[0].split(':') + addr = env_http_proxy[1].split(':') + else: + login = ['', ''] + addr = env_http_proxy[0].split(':') + + proxy = {'host': addr[0], 'type' : u'http', 'user':login[0]} + + if len(addr) == 2: + proxy['port'] = addr[1] + else: + proxy['port'] = 3128 + + if len(login) == 2: + proxy['password'] = login[1] + else: + proxy['password'] = u'' + + except Exception: + proxy = None + else: + proxy = None + h = hostname + p = 5222 + ssl_p = 5223 +# use_srv = False # wants ssl? disable srv lookup + if use_custom: + h = custom_h + p = custom_p + ssl_p = custom_p + use_srv = False + + # SRV resolver + self._proxy = proxy + self._hosts = [ {'host': h, 'port': p, 'ssl_port': ssl_p, 'prio': 10, + 'weight': 10} ] + self._hostname = hostname + if use_srv: + # add request for srv query to the resolve, on result '_on_resolve' + # will be called + gajim.resolver.resolve('_xmpp-client._tcp.' + helpers.idn_to_ascii(h), + self._on_resolve) + else: + self._on_resolve('', []) + + def _on_resolve(self, host, result_array): + # SRV query returned at least one valid result, we put it in hosts dict + if len(result_array) != 0: + self._hosts = [i for i in result_array] + # Add ssl port + ssl_p = 5223 + if gajim.config.get_per('accounts', self.name, 'use_custom_host'): + ssl_p = gajim.config.get_per('accounts', self.name, 'custom_port') + for i in self._hosts: + i['ssl_port'] = ssl_p + self._connect_to_next_host() + + + def _connect_to_next_host(self, retry=False): + log.debug('Connection to next host') + if len(self._hosts): + # No config option exist when creating a new account + if self.last_connection_type: + self._connection_types = [self.last_connection_type] + elif self.name in gajim.config.get_per('accounts'): + self._connection_types = gajim.config.get_per('accounts', self.name, + 'connection_types').split() + else: + self._connection_types = ['tls', 'ssl', 'plain'] + + if self._proxy and self._proxy['type']=='bosh': + # with BOSH, we can't do TLS negotiation with , we do only "plain" + # connection and TLS with handshake right after TCP connecting ("ssl") + scheme = common.xmpp.transports_nb.urisplit(self._proxy['bosh_uri'])[0] + if scheme=='https': + self._connection_types = ['ssl'] + else: + self._connection_types = ['plain'] + + host = self._select_next_host(self._hosts) + self._current_host = host + self._hosts.remove(host) + self.connect_to_next_type() + + else: + if not retry and self.retrycount == 0: + log.debug("Out of hosts, giving up connecting to %s", self.name) + self.time_to_reconnect = None + if self.on_connect_failure: + self.on_connect_failure() + self.on_connect_failure = None + else: + # shown error dialog + self._connection_lost() + else: + # try reconnect if connection has failed before auth to server + self._disconnectedReconnCB() + + def connect_to_next_type(self, retry=False): + if len(self._connection_types): + self._current_type = self._connection_types.pop(0) + if self.last_connection: + self.last_connection.socket.disconnect() + self.last_connection = None + self.connection = None + + if self._current_type == 'ssl': + # SSL (force TLS on different port than plain) + # If we do TLS over BOSH, port of XMPP server should be the standard one + # and TLS should be negotiated because TLS on 5223 is deprecated + if self._proxy and self._proxy['type']=='bosh': + port = self._current_host['port'] + else: + port = self._current_host['ssl_port'] + elif self._current_type == 'tls': + # TLS - negotiate tls after XMPP stream is estabilished + port = self._current_host['port'] + elif self._current_type == 'plain': + # plain connection on defined port + port = self._current_host['port'] + + cacerts = os.path.join(common.gajim.DATA_DIR, 'other', 'cacerts.pem') + mycerts = common.gajim.MY_CACERTS + secure_tuple = (self._current_type, cacerts, mycerts) + + con = common.xmpp.NonBlockingClient( + domain=self._hostname, + caller=self, + idlequeue=gajim.idlequeue) + + self.last_connection = con + # increase default timeout for server responses + common.xmpp.dispatcher_nb.DEFAULT_TIMEOUT_SECONDS = self.try_connecting_for_foo_secs + # FIXME: this is a hack; need a better way + if self.on_connect_success == self._on_new_account: + con.RegisterDisconnectHandler(self._on_new_account) + + self.log_hosttype_info(port) + con.connect( + hostname=self._current_host['host'], + port=port, + on_connect=self.on_connect_success, + on_proxy_failure=self.on_proxy_failure, + on_connect_failure=self.connect_to_next_type, + proxy=self._proxy, + secure_tuple = secure_tuple) + else: + self._connect_to_next_host(retry) + + def log_hosttype_info(self, port): + msg = '>>>>>> Connecting to %s [%s:%d], type = %s' % (self.name, + self._current_host['host'], port, self._current_type) + log.info(msg) + if self._proxy: + msg = '>>>>>> ' + if self._proxy['type']=='bosh': + msg = '%s over BOSH %s' % (msg, self._proxy['bosh_uri']) + if self._proxy['type'] in ['http', 'socks5'] or self._proxy['bosh_useproxy']: + msg = '%s over proxy %s:%s' % (msg, self._proxy['host'], self._proxy['port']) + log.info(msg) + + def _connect_failure(self, con_type=None): + if not con_type: + # we are not retrying, and not conecting + if not self.retrycount and self.connected != 0: + self.disconnect(on_purpose = True) + self.dispatch('STATUS', 'offline') + pritxt = _('Could not connect to "%s"') % self._hostname + sectxt = _('Check your connection or try again later.') + if self.streamError: + # show error dialog + key = common.xmpp.NS_XMPP_STREAMS + ' ' + self.streamError + if key in common.xmpp.ERRORS: + sectxt2 = _('Server replied: %s') % common.xmpp.ERRORS[key][2] + self.dispatch('ERROR', (pritxt, '%s\n%s' % (sectxt2, sectxt))) + return + # show popup + self.dispatch('CONNECTION_LOST', (pritxt, sectxt)) + + def on_proxy_failure(self, reason): + log.error('Connection to proxy failed: %s' % reason) + self.time_to_reconnect = None + self.on_connect_failure = None + self.disconnect(on_purpose = True) + self.dispatch('STATUS', 'offline') + self.dispatch('CONNECTION_LOST', + (_('Connection to proxy failed'), reason)) + + def _connect_success(self, con, con_type): + if not self.connected: # We went offline during connecting process + # FIXME - not possible, maybe it was when we used threads + return + _con_type = con_type + if _con_type != self._current_type: + log.info('Connecting to next type beacuse desired is %s and returned is %s' + % (self._current_type, _con_type)) + self.connect_to_next_type() + return + con.RegisterDisconnectHandler(self._on_disconnected) + if _con_type == 'plain' and gajim.config.get_per('accounts', self.name, + 'warn_when_plaintext_connection'): + self.dispatch('PLAIN_CONNECTION', (con,)) + return True + if _con_type in ('tls', 'ssl') and con.Connection.ssl_lib != 'PYOPENSSL' \ + and gajim.config.get_per('accounts', self.name, + 'warn_when_insecure_ssl_connection') and \ + not self.connection_auto_accepted: + # Pyopenssl is not used + self.dispatch('INSECURE_SSL_CONNECTION', (con, _con_type)) + return True + return self.connection_accepted(con, con_type) + + def connection_accepted(self, con, con_type): + if not con or not con.Connection: + self.disconnect(on_purpose=True) + self.dispatch('STATUS', 'offline') + self.dispatch('CONNECTION_LOST', + (_('Could not connect to account %s') % self.name, + _('Connection with account %s has been lost. Retry connecting.') % \ + self.name)) + return + self.hosts = [] + self.connection_auto_accepted = False + self.connected_hostname = self._current_host['host'] + self.on_connect_failure = None + con.UnregisterDisconnectHandler(self._on_disconnected) + con.RegisterDisconnectHandler(self._disconnectedReconnCB) + log.debug('Connected to server %s:%s with %s' % ( + self._current_host['host'], self._current_host['port'], con_type)) + + self.last_connection_type = con_type + if gajim.config.get_per('accounts', self.name, 'anonymous_auth'): + name = None + else: + name = gajim.config.get_per('accounts', self.name, 'name') + hostname = gajim.config.get_per('accounts', self.name, 'hostname') + self.connection = con + try: + errnum = con.Connection.ssl_errnum + except AttributeError: + errnum = -1 # we don't have an errnum + if errnum > 0 and str(errnum) not in gajim.config.get_per('accounts', + self.name, 'ignore_ssl_errors'): + text = _('The authenticity of the %s certificate could be invalid.') %\ + hostname + if errnum in ssl_error: + text += _('\nSSL Error: %s') % ssl_error[errnum] + else: + text += _('\nUnknown SSL error: %d') % errnum + self.dispatch('SSL_ERROR', (text, errnum, con.Connection.ssl_cert_pem, + con.Connection.ssl_fingerprint_sha1)) + return True + if hasattr(con.Connection, 'ssl_fingerprint_sha1'): + saved_fingerprint = gajim.config.get_per('accounts', self.name, 'ssl_fingerprint_sha1') + if saved_fingerprint: + # Check sha1 fingerprint + if con.Connection.ssl_fingerprint_sha1 != saved_fingerprint: + self.dispatch('FINGERPRINT_ERROR', + (con.Connection.ssl_fingerprint_sha1,)) + return True + else: + gajim.config.set_per('accounts', self.name, 'ssl_fingerprint_sha1', + con.Connection.ssl_fingerprint_sha1) + self._register_handlers(con, con_type) + con.auth( + user=name, + password=self.password, + resource=self.server_resource, + sasl=1, + on_auth=self.__on_auth) + + def ssl_certificate_accepted(self): + if not self.connection: + self.disconnect(on_purpose=True) + self.dispatch('STATUS', 'offline') + self.dispatch('CONNECTION_LOST', + (_('Could not connect to account %s') % self.name, + _('Connection with account %s has been lost. Retry connecting.') % \ + self.name)) + return + name = gajim.config.get_per('accounts', self.name, 'name') + self._register_handlers(self.connection, 'ssl') + self.connection.auth(name, self.password, self.server_resource, 1, + self.__on_auth) + + def _register_handlers(self, con, con_type): + self.peerhost = con.get_peerhost() + # notify the gui about con_type + self.dispatch('CON_TYPE', con_type) + ConnectionHandlers._register_handlers(self, con, con_type) + + def __on_auth(self, con, auth): + if not con: + self.disconnect(on_purpose=True) + self.dispatch('STATUS', 'offline') + self.dispatch('CONNECTION_LOST', + (_('Could not connect to "%s"') % self._hostname, + _('Check your connection or try again later'))) + if self.on_connect_auth: + self.on_connect_auth(None) + self.on_connect_auth = None + return + if not self.connected: # We went offline during connecting process + if self.on_connect_auth: + self.on_connect_auth(None) + self.on_connect_auth = None + return + if hasattr(con, 'Resource'): + self.server_resource = con.Resource + if gajim.config.get_per('accounts', self.name, 'anonymous_auth'): + # Get jid given by server + old_jid = gajim.get_jid_from_account(self.name) + gajim.config.set_per('accounts', self.name, 'name', con.User) + new_jid = gajim.get_jid_from_account(self.name) + self.dispatch('NEW_JID', (old_jid, new_jid)) + if auth: + self.last_io = gajim.idlequeue.current_time() + self.connected = 2 + self.retrycount = 0 + if self.on_connect_auth: + self.on_connect_auth(con) + self.on_connect_auth = None + else: + # Forget password, it's wrong + self.password = None + gajim.log.debug("Couldn't authenticate to %s" % self._hostname) + self.disconnect(on_purpose = True) + self.dispatch('STATUS', 'offline') + self.dispatch('ERROR', (_('Authentication failed with "%s"') % \ + self._hostname, + _('Please check your login and password for correctness.'))) + if self.on_connect_auth: + self.on_connect_auth(None) + self.on_connect_auth = None + # END connect + + def add_lang(self, stanza): + if self.lang: + stanza.setAttr('xml:lang', self.lang) + + def get_privacy_lists(self): + if not self.connection: + return + common.xmpp.features_nb.getPrivacyLists(self.connection) + + def send_keepalive(self): + # nothing received for the last foo seconds + if self.connection: + self.connection.send(' ') + + def sendPing(self, pingTo=None): + '''Send XMPP Ping (XEP-0199) request. If pingTo is not set, ping is sent + to server to detect connection failure at application level.''' + if not self.connection: + return + id_ = self.connection.getAnID() + if pingTo: + to = pingTo.get_full_jid() + self.dispatch('PING_SENT', (pingTo)) + else: + to = gajim.config.get_per('accounts', self.name, 'hostname') + self.awaiting_xmpp_ping_id = id_ + iq = common.xmpp.Iq('get', to=to) + iq.addChild(name = 'ping', namespace = common.xmpp.NS_PING) + iq.setID(id_) + def _on_response(resp): + timePong = time_time() + if not common.xmpp.isResultNode(resp): + self.dispatch('PING_ERROR', (pingTo)) + return + timeDiff = round(timePong - timePing, 2) + self.dispatch('PING_REPLY', (pingTo, timeDiff)) + if pingTo: + timePing = time_time() + self.connection.SendAndCallForResponse(iq, _on_response) + else: + self.connection.send(iq) + gajim.idlequeue.set_alarm(self.check_pingalive, gajim.config.get_per( + 'accounts', self.name, 'time_for_ping_alive_answer')) + + def get_active_default_lists(self): + if not self.connection: + return + common.xmpp.features_nb.getActiveAndDefaultPrivacyLists(self.connection) + + def del_privacy_list(self, privacy_list): + if not self.connection: + return + def _on_del_privacy_list_result(result): + if result: + self.dispatch('PRIVACY_LIST_REMOVED', privacy_list) + else: + self.dispatch('ERROR', (_('Error while removing privacy list'), + _('Privacy list %s has not been removed. It is maybe active in ' + 'one of your connected resources. Deactivate it and try ' + 'again.') % privacy_list)) + common.xmpp.features_nb.delPrivacyList(self.connection, privacy_list, + _on_del_privacy_list_result) + + def get_privacy_list(self, title): + if not self.connection: + return + common.xmpp.features_nb.getPrivacyList(self.connection, title) + + def set_privacy_list(self, listname, tags): + if not self.connection: + return + common.xmpp.features_nb.setPrivacyList(self.connection, listname, tags) + + def set_active_list(self, listname): + if not self.connection: + return + common.xmpp.features_nb.setActivePrivacyList(self.connection, listname, 'active') + + def set_default_list(self, listname): + if not self.connection: + return + common.xmpp.features_nb.setDefaultPrivacyList(self.connection, listname) + + def build_privacy_rule(self, name, action, order=1): + '''Build a Privacy rule stanza for invisibility''' + iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '') + l = iq.getTag('query').setTag('list', {'name': name}) + i = l.setTag('item', {'action': action, 'order': str(order)}) + i.setTag('presence-out') + return iq + + def build_invisible_rule(self): + iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '') + l = iq.getTag('query').setTag('list', {'name': 'invisible'}) + if self.name in gajim.interface.status_sent_to_groups and \ + len(gajim.interface.status_sent_to_groups[self.name]) > 0: + for group in gajim.interface.status_sent_to_groups[self.name]: + i = l.setTag('item', {'type': 'group', 'value': group, + 'action': 'allow', 'order': '1'}) + i.setTag('presence-out') + if self.name in gajim.interface.status_sent_to_users and \ + len(gajim.interface.status_sent_to_users[self.name]) > 0: + for jid in gajim.interface.status_sent_to_users[self.name]: + i = l.setTag('item', {'type': 'jid', 'value': jid, + 'action': 'allow', 'order': '2'}) + i.setTag('presence-out') + i = l.setTag('item', {'action': 'deny', 'order': '3'}) + i.setTag('presence-out') + return iq + + def set_invisible_rule(self): + if not gajim.account_is_connected(self.name): + return + iq = self.build_invisible_rule() + self.connection.send(iq) + + def activate_privacy_rule(self, name): + '''activate a privacy rule''' + if not self.connection: + return + iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '') + iq.getTag('query').setTag('active', {'name': name}) + self.connection.send(iq) + + def send_invisible_presence(self, msg, signed, initial = False): + if not self.connection: + return + if not self.privacy_rules_supported: + self.dispatch('STATUS', gajim.SHOW_LIST[self.connected]) + self.dispatch('ERROR', (_('Invisibility not supported'), + _('Account %s doesn\'t support invisibility.') % self.name)) + return + # If we are already connected, and privacy rules are supported, send + # offline presence first as it's required by XEP-0126 + if self.connected > 1 and self.privacy_rules_supported: + self.on_purpose = True + p = common.xmpp.Presence(typ = 'unavailable') + p = self.add_sha(p, False) + if msg: + p.setStatus(msg) + self.remove_all_transfers() + self.connection.send(p) + + # try to set the privacy rule + iq = self.build_invisible_rule() + self.connection.SendAndCallForResponse(iq, self._continue_invisible, + {'msg': msg, 'signed': signed, 'initial': initial}) + + def _continue_invisible(self, con, iq_obj, msg, signed, initial): + if iq_obj.getType() == 'error': # server doesn't support privacy lists + return + # active the privacy rule + self.privacy_rules_supported = True + self.activate_privacy_rule('invisible') + self.connected = gajim.SHOW_LIST.index('invisible') + self.status = msg + priority = unicode(gajim.get_priority(self.name, 'invisible')) + p = common.xmpp.Presence(priority = priority) + p = self.add_sha(p, True) + if msg: + p.setStatus(msg) + if signed: + p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed) + self.connection.send(p) + self.priority = priority + self.dispatch('STATUS', 'invisible') + if initial: + #ask our VCard + self.request_vcard(None) + + #Get bookmarks from private namespace + self.get_bookmarks() + + #Get annotations + self.get_annotations() + + #Inform GUI we just signed in + self.dispatch('SIGNED_IN', ()) + + def get_signed_presence(self, msg, callback = None): + if gajim.config.get_per('accounts', self.name, 'gpg_sign_presence'): + return self.get_signed_msg(msg, callback) + return '' + + def connect_and_auth(self): + self.on_connect_success = self._connect_success + self.on_connect_failure = self._connect_failure + self.connect() + + def connect_and_init(self, show, msg, sign_msg): + self.continue_connect_info = [show, msg, sign_msg] + self.on_connect_auth = self._discover_server_at_connection + self.connect_and_auth() + + def _discover_server_at_connection(self, con): + self.connection = con + if not self.connection: + return + self.connection.set_send_timeout(self.keepalives, self.send_keepalive) + self.connection.set_send_timeout2(self.pingalives, self.sendPing) + self.connection.onreceive(None) + + self.request_message_archiving_preferences() + + self.discoverInfo(gajim.config.get_per('accounts', self.name, 'hostname'), + id_prefix='Gajim_') + self.privacy_rules_requested = False + + def _request_privacy(self): + iq = common.xmpp.Iq('get', common.xmpp.NS_PRIVACY, xmlns = '') + id_ = self.connection.getAnID() + iq.setID(id_) + self.awaiting_answers[id_] = (PRIVACY_ARRIVED, ) + self.connection.send(iq) + + def send_custom_status(self, show, msg, jid): + if not show in gajim.SHOW_LIST: + return -1 + if not self.connection: + return + sshow = helpers.get_xmpp_show(show) + if not msg: + msg = '' + if show == 'offline': + p = common.xmpp.Presence(typ = 'unavailable', to = jid) + p = self.add_sha(p, False) + if msg: + p.setStatus(msg) + else: + signed = self.get_signed_presence(msg) + priority = unicode(gajim.get_priority(self.name, sshow)) + p = common.xmpp.Presence(typ = None, priority = priority, show = sshow, + to = jid) + p = self.add_sha(p) + if msg: + p.setStatus(msg) + if signed: + p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed) + self.connection.send(p) + + def _change_to_invisible(self, msg): + signed = self.get_signed_presence(msg) + self.send_invisible_presence(msg, signed) + + def _change_from_invisible(self): + if self.privacy_rules_supported: + iq = self.build_privacy_rule('visible', 'allow') + self.connection.send(iq) + self.activate_privacy_rule('visible') + + def _update_status(self, show, msg): + xmpp_show = helpers.get_xmpp_show(show) + priority = unicode(gajim.get_priority(self.name, xmpp_show)) + p = common.xmpp.Presence(typ=None, priority=priority, show=xmpp_show) + p = self.add_sha(p) + if msg: + p.setStatus(msg) + signed = self.get_signed_presence(msg) + if signed: + p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed) + if self.connection: + self.connection.send(p) + self.priority = priority + self.dispatch('STATUS', show) + + def send_motd(self, jid, subject = '', msg = '', xhtml = None): + if not self.connection: + return + msg_iq = common.xmpp.Message(to = jid, body = msg, subject = subject, + xhtml = xhtml) + + self.connection.send(msg_iq) + + def send_message(self, jid, msg, keyID, type_='chat', subject='', + chatstate=None, msg_id=None, composing_xep=None, resource=None, + user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None, + original_message=None, delayed=None, callback=None, callback_args=[]): + + def cb(jid, msg, keyID, forward_from, session, original_message, subject, + type_, msg_iq): + msg_id = self.connection.send(msg_iq) + jid = helpers.parse_jid(jid) + self.dispatch('MSGSENT', (jid, msg, keyID)) + if callback: + callback(msg_id, *callback_args) + + self.log_message(jid, msg, forward_from, session, original_message, + subject, type_) + + self._prepare_message(jid, msg, keyID, type_=type_, subject=subject, + chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep, + resource=resource, user_nick=user_nick, xhtml=xhtml, session=session, + forward_from=forward_from, form_node=form_node, + original_message=original_message, delayed=delayed, callback=cb) + + def send_contacts(self, contacts, jid): + '''Send contacts with RosterX (Xep-0144)''' + if not self.connection: + return + if len(contacts) == 1: + msg = _('Sent contact: "%s" (%s)') % (contacts[0].get_full_jid(), + contacts[0].get_shown_name()) + else: + msg = _('Sent contacts:') + for contact in contacts: + msg += '\n "%s" (%s)' % (contact.get_full_jid(), + contact.get_shown_name()) + msg_iq = common.xmpp.Message(to=jid, body=msg) + x = msg_iq.addChild(name='x', namespace=common.xmpp.NS_ROSTERX) + for contact in contacts: + x.addChild(name='item', attrs={'action': 'add', 'jid': contact.jid, + 'name': contact.get_shown_name()}) + self.connection.send(msg_iq) + + def send_stanza(self, stanza): + ''' send a stanza untouched ''' + if not self.connection: + return + self.connection.send(stanza) + + def ack_subscribed(self, jid): + if not self.connection: + return + log.debug('ack\'ing subscription complete for %s' % jid) + p = common.xmpp.Presence(jid, 'subscribe') + self.connection.send(p) + + def ack_unsubscribed(self, jid): + if not self.connection: + return + log.debug('ack\'ing unsubscription complete for %s' % jid) + p = common.xmpp.Presence(jid, 'unsubscribe') + self.connection.send(p) + + def request_subscription(self, jid, msg = '', name = '', groups = [], + auto_auth = False, user_nick = ''): + if not self.connection: + return + log.debug('subscription request for %s' % jid) + if auto_auth: + self.jids_for_auto_auth.append(jid) + # RFC 3921 section 8.2 + infos = {'jid': jid} + if name: + infos['name'] = name + iq = common.xmpp.Iq('set', common.xmpp.NS_ROSTER) + q = iq.getTag('query') + item = q.addChild('item', attrs = infos) + for g in groups: + item.addChild('group').setData(g) + self.connection.send(iq) + + p = common.xmpp.Presence(jid, 'subscribe') + if user_nick: + p.setTag('nick', namespace = common.xmpp.NS_NICK).setData(user_nick) + p = self.add_sha(p) + if msg: + p.setStatus(msg) + self.connection.send(p) + + def send_authorization(self, jid): + if not self.connection: + return + p = common.xmpp.Presence(jid, 'subscribed') + p = self.add_sha(p) + self.connection.send(p) + + def refuse_authorization(self, jid): + if not self.connection: + return + p = common.xmpp.Presence(jid, 'unsubscribed') + p = self.add_sha(p) + self.connection.send(p) + + def unsubscribe(self, jid, remove_auth = True): + if not self.connection: + return + if remove_auth: + self.connection.getRoster().delItem(jid) + jid_list = gajim.config.get_per('contacts') + for j in jid_list: + if j.startswith(jid): + gajim.config.del_per('contacts', j) + else: + self.connection.getRoster().Unsubscribe(jid) + self.update_contact(jid, '', []) + + def unsubscribe_agent(self, agent): + if not self.connection: + return + iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to = agent) + iq.getTag('query').setTag('remove') + id_ = self.connection.getAnID() + iq.setID(id_) + self.awaiting_answers[id_] = (AGENT_REMOVED, agent) + self.connection.send(iq) + self.connection.getRoster().delItem(agent) + + def send_new_account_infos(self, form, is_form): + if is_form: + # Get username and password and put them in new_account_info + for field in form.iter_fields(): + if field.var == 'username': + self.new_account_info['name'] = field.value + if field.var == 'password': + self.new_account_info['password'] = field.value + else: + # Get username and password and put them in new_account_info + if 'username' in form: + self.new_account_info['name'] = form['username'] + if 'password' in form: + self.new_account_info['password'] = form['password'] + self.new_account_form = form + self.new_account(self.name, self.new_account_info) + + def new_account(self, name, config, sync=False): + # If a connection already exist we cannot create a new account + if self.connection: + return + self._hostname = config['hostname'] + self.new_account_info = config + self.name = name + self.on_connect_success = self._on_new_account + self.on_connect_failure = self._on_new_account + self.connect(config) + + def _on_new_account(self, con=None, con_type=None): + if not con_type: + if len(self._connection_types) or len(self._hosts): + # There are still other way to try to connect + return + self.dispatch('NEW_ACC_NOT_CONNECTED', + (_('Could not connect to "%s"') % self._hostname)) + return + self.on_connect_failure = None + self.connection = con + common.xmpp.features_nb.getRegInfo(con, self._hostname) + + def request_last_status_time(self, jid, resource, groupchat_jid=None): + '''groupchat_jid is used when we want to send a request to a real jid + and act as if the answer comes from the groupchat_jid''' + if not self.connection: + return + to_whom_jid = jid + if resource: + to_whom_jid += '/' + resource + iq = common.xmpp.Iq(to = to_whom_jid, typ = 'get', queryNS =\ + common.xmpp.NS_LAST) + id_ = self.connection.getAnID() + iq.setID(id_) + if groupchat_jid: + self.groupchat_jids[id_] = groupchat_jid + self.last_ids.append(id_) + self.connection.send(iq) + + def request_os_info(self, jid, resource, groupchat_jid=None): + '''groupchat_jid is used when we want to send a request to a real jid + and act as if the answer comes from the groupchat_jid''' + if not self.connection: + return + # If we are invisible, do not request + if self.connected == gajim.SHOW_LIST.index('invisible'): + self.dispatch('OS_INFO', (jid, resource, _('Not fetched because of invisible status'), _('Not fetched because of invisible status'))) + return + to_whom_jid = jid + if resource: + to_whom_jid += '/' + resource + iq = common.xmpp.Iq(to=to_whom_jid, typ='get', queryNS=\ + common.xmpp.NS_VERSION) + id_ = self.connection.getAnID() + iq.setID(id_) + if groupchat_jid: + self.groupchat_jids[id_] = groupchat_jid + self.version_ids.append(id_) + self.connection.send(iq) + + def request_entity_time(self, jid, resource, groupchat_jid=None): + '''groupchat_jid is used when we want to send a request to a real jid + and act as if the answer comes from the groupchat_jid''' + if not self.connection: + return + # If we are invisible, do not request + if self.connected == gajim.SHOW_LIST.index('invisible'): + self.dispatch('ENTITY_TIME', (jid, resource, _('Not fetched because of invisible status'))) + return + to_whom_jid = jid + if resource: + to_whom_jid += '/' + resource + iq = common.xmpp.Iq(to=to_whom_jid, typ='get') + iq.addChild('time', namespace=common.xmpp.NS_TIME_REVISED) + id_ = self.connection.getAnID() + iq.setID(id_) + if groupchat_jid: + self.groupchat_jids[id_] = groupchat_jid + self.entity_time_ids.append(id_) + self.connection.send(iq) + + def get_settings(self): + ''' Get Gajim settings as described in XEP 0049 ''' + if not self.connection: + return + iq = common.xmpp.Iq(typ='get') + iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) + iq2.addChild(name='gajim', namespace='gajim:prefs') + self.connection.send(iq) + + def _request_bookmarks_xml(self): + if not self.connection: + return + iq = common.xmpp.Iq(typ='get') + iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) + iq2.addChild(name='storage', namespace='storage:bookmarks') + self.connection.send(iq) + + def _check_bookmarks_received(self): + if not self.bookmarks: + self._request_bookmarks_xml() + + def get_bookmarks(self, storage_type=None): + '''Get Bookmarks from storage or PubSub if supported as described in + XEP 0048 + storage_type can be set to xml to force request to xml storage''' + if not self.connection: + return + if self.pubsub_supported and storage_type != 'xml': + self.send_pb_retrieve('', 'storage:bookmarks') + # some server (ejabberd) are so slow to answer that we request via XML + # if we don't get answer in the next 30 seconds + gajim.idlequeue.set_alarm(self._check_bookmarks_received, 30) + else: + self._request_bookmarks_xml() + + def store_bookmarks(self, storage_type=None): + ''' Send bookmarks to the storage namespace or PubSub if supported + storage_type can be set to 'pubsub' or 'xml' so store in only one method + else it will be stored on both''' + if not self.connection: + return + iq = common.xmpp.Node(tag='storage', attrs={'xmlns': 'storage:bookmarks'}) + for bm in self.bookmarks: + iq2 = iq.addChild(name = "conference") + iq2.setAttr('jid', bm['jid']) + iq2.setAttr('autojoin', bm['autojoin']) + iq2.setAttr('minimize', bm['minimize']) + iq2.setAttr('name', bm['name']) + # Only add optional elements if not empty + # Note: need to handle both None and '' as empty + # thus shouldn't use "is not None" + if bm.get('nick', None): + iq2.setTagData('nick', bm['nick']) + if bm.get('password', None): + iq2.setTagData('password', bm['password']) + if bm.get('print_status', None): + iq2.setTagData('print_status', bm['print_status']) + + if self.pubsub_supported and storage_type != 'xml': + if self.pubsub_publish_options_supported: + options = common.xmpp.Node(common.xmpp.NS_DATA + ' x', + attrs={'type': 'submit'}) + f = options.addChild('field', attrs={'var': 'FORM_TYPE', + 'type': 'hidden'}) + f.setTagData('value', common.xmpp.NS_PUBSUB_PUBLISH_OPTIONS) + f = options.addChild('field', attrs={'var': 'pubsub#persist_items'}) + f.setTagData('value', 'true') + f = options.addChild('field', attrs={'var': 'pubsub#access_model'}) + f.setTagData('value', 'whitelist') + else: + options = None + self.send_pb_publish('', 'storage:bookmarks', iq, 'current', + options=options) + if storage_type != 'pubsub': + iqA = common.xmpp.Iq(typ='set') + iqB = iqA.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) + iqB.addChild(node=iq) + self.connection.send(iqA) + + def get_annotations(self): + '''Get Annonations from storage as described in XEP 0048, and XEP 0145''' + self.annotations = {} + if not self.connection: + return + iq = common.xmpp.Iq(typ='get') + iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) + iq2.addChild(name='storage', namespace='storage:rosternotes') + self.connection.send(iq) + + def store_annotations(self): + '''Set Annonations in private storage as described in XEP 0048, and XEP 0145''' + if not self.connection: + return + iq = common.xmpp.Iq(typ='set') + iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) + iq3 = iq2.addChild(name='storage', namespace='storage:rosternotes') + for jid in self.annotations.keys(): + if self.annotations[jid]: + iq4 = iq3.addChild(name = "note") + iq4.setAttr('jid', jid) + iq4.setData(self.annotations[jid]) + self.connection.send(iq) + + + def get_metacontacts(self): + '''Get metacontacts list from storage as described in XEP 0049''' + if not self.connection: + return + iq = common.xmpp.Iq(typ='get') + iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) + iq2.addChild(name='storage', namespace='storage:metacontacts') + id_ = self.connection.getAnID() + iq.setID(id_) + self.awaiting_answers[id_] = (METACONTACTS_ARRIVED, ) + self.connection.send(iq) + + def store_metacontacts(self, tags_list): + ''' Send meta contacts to the storage namespace ''' + if not self.connection: + return + iq = common.xmpp.Iq(typ='set') + iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) + iq3 = iq2.addChild(name='storage', namespace='storage:metacontacts') + for tag in tags_list: + for data in tags_list[tag]: + jid = data['jid'] + dict_ = {'jid': jid, 'tag': tag} + if 'order' in data: + dict_['order'] = data['order'] + iq3.addChild(name = 'meta', attrs = dict_) + self.connection.send(iq) + + def send_agent_status(self, agent, ptype): + if not self.connection: + return + show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected]) + p = common.xmpp.Presence(to = agent, typ = ptype, show = show) + p = self.add_sha(p, ptype != 'unavailable') + self.connection.send(p) + + def check_unique_room_id_support(self, server, instance): + if not self.connection: + return + iq = common.xmpp.Iq(typ = 'get', to = server) + iq.setAttr('id', 'unique1') + iq.addChild('unique', namespace=common.xmpp.NS_MUC_UNIQUE) + def _on_response(resp): + if not common.xmpp.isResultNode(resp): + self.dispatch('UNIQUE_ROOM_ID_UNSUPPORTED', (server, instance)) + return + self.dispatch('UNIQUE_ROOM_ID_SUPPORTED', (server, instance, + resp.getTag('unique').getData())) + self.connection.SendAndCallForResponse(iq, _on_response) + + def join_gc(self, nick, room_jid, password, change_nick=False): + # FIXME: This room JID needs to be normalized; see #1364 + if not self.connection: + return + show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected]) + if show == 'invisible': + # Never join a room when invisible + return + + # last date/time in history to avoid duplicate + if room_jid not in self.last_history_time: + # Not in memory, get it from DB + last_log = None + # Do not check if we are not logging for this room + if gajim.config.should_log(self.name, room_jid): + # Check time first in the FAST table + last_log = gajim.logger.get_room_last_message_time(room_jid) + if last_log is None: + # Not in special table, get it from messages DB + last_log = gajim.logger.get_last_date_that_has_logs(room_jid, + is_room=True) + # Create self.last_history_time[room_jid] even if not logging, + # could be used in connection_handlers + if last_log is None: + last_log = 0 + self.last_history_time[room_jid] = last_log + + p = common.xmpp.Presence(to='%s/%s' % (room_jid, nick), + show=show, status=self.status) + h = hmac.new(self.secret_hmac, room_jid).hexdigest()[:6] + id_ = self.connection.getAnID() + id_ = 'gajim_muc_' + id_ + '_' + h + p.setID(id_) + if gajim.config.get('send_sha_in_gc_presence'): + p = self.add_sha(p) + self.add_lang(p) + if not change_nick: + t = p.setTag(common.xmpp.NS_MUC + ' x') + last_date = self.last_history_time[room_jid] + if last_date == 0: + last_date = time.time() - gajim.config.get( + 'muc_restore_timeout') * 60 + else: + last_time = min(last_date, time.time() - gajim.config.get( + 'muc_restore_timeout') * 60) + last_date = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(last_date)) + t.setTag('history', {'maxstanzas': gajim.config.get( + 'muc_restore_lines'), 'since': last_date}) + if password: + t.setTagData('password', password) + self.connection.send(p) + + def send_gc_message(self, jid, msg, xhtml = None): + if not self.connection: + return + if not xhtml and gajim.config.get('rst_formatting_outgoing_messages'): + from common.rst_xhtml_generator import create_xhtml + xhtml = create_xhtml(msg) + msg_iq = common.xmpp.Message(jid, msg, typ = 'groupchat', xhtml = xhtml) + self.connection.send(msg_iq) + self.dispatch('MSGSENT', (jid, msg)) + + def send_gc_subject(self, jid, subject): + if not self.connection: + return + msg_iq = common.xmpp.Message(jid, typ = 'groupchat', subject = subject) + self.connection.send(msg_iq) + + def request_gc_config(self, room_jid): + if not self.connection: + return + iq = common.xmpp.Iq(typ = 'get', queryNS = common.xmpp.NS_MUC_OWNER, + to = room_jid) + self.add_lang(iq) + self.connection.send(iq) + + def destroy_gc_room(self, room_jid, reason = '', jid = ''): + if not self.connection: + return + iq = common.xmpp.Iq(typ = 'set', queryNS = common.xmpp.NS_MUC_OWNER, + to = room_jid) + destroy = iq.getTag('query').setTag('destroy') + if reason: + destroy.setTagData('reason', reason) + if jid: + destroy.setAttr('jid', jid) + self.connection.send(iq) + + def send_gc_status(self, nick, jid, show, status): + if not gajim.account_is_connected(self.name): + return + if show == 'invisible': + show = 'offline' + ptype = None + if show == 'offline': + ptype = 'unavailable' + xmpp_show = helpers.get_xmpp_show(show) + p = common.xmpp.Presence(to = '%s/%s' % (jid, nick), typ = ptype, + show = xmpp_show, status = status) + h = hmac.new(self.secret_hmac, jid).hexdigest()[:6] + id_ = self.connection.getAnID() + id_ = 'gajim_muc_' + id_ + '_' + h + p.setID(id_) + if gajim.config.get('send_sha_in_gc_presence') and show != 'offline': + p = self.add_sha(p, ptype != 'unavailable') + self.add_lang(p) + # send instantly so when we go offline, status is sent to gc before we + # disconnect from jabber server + self.connection.send(p) + + def gc_got_disconnected(self, room_jid): + ''' A groupchat got disconnected. This can be or purpose or not. + Save the time we quit to avoid duplicate logs AND be faster than get that + date from DB. Save it in mem AND in a small table (with fast access) + ''' + log_time = time_time() + self.last_history_time[room_jid] = log_time + gajim.logger.set_room_last_message_time(room_jid, log_time) + + def gc_set_role(self, room_jid, nick, role, reason = ''): + '''role is for all the life of the room so it's based on nick''' + if not self.connection: + return + iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ + common.xmpp.NS_MUC_ADMIN) + item = iq.getTag('query').setTag('item') + item.setAttr('nick', nick) + item.setAttr('role', role) + if reason: + item.addChild(name = 'reason', payload = reason) + self.connection.send(iq) + + def gc_set_affiliation(self, room_jid, jid, affiliation, reason = ''): + '''affiliation is for all the life of the room so it's based on jid''' + if not self.connection: + return + iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ + common.xmpp.NS_MUC_ADMIN) + item = iq.getTag('query').setTag('item') + item.setAttr('jid', jid) + item.setAttr('affiliation', affiliation) + if reason: + item.addChild(name = 'reason', payload = reason) + self.connection.send(iq) + + def send_gc_affiliation_list(self, room_jid, users_dict): + if not self.connection: + return + iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS = \ + common.xmpp.NS_MUC_ADMIN) + item = iq.getTag('query') + for jid in users_dict: + item_tag = item.addChild('item', {'jid': jid, + 'affiliation': users_dict[jid]['affiliation']}) + if 'reason' in users_dict[jid] and users_dict[jid]['reason']: + item_tag.setTagData('reason', users_dict[jid]['reason']) + self.connection.send(iq) + + def get_affiliation_list(self, room_jid, affiliation): + if not self.connection: + return + iq = common.xmpp.Iq(typ = 'get', to = room_jid, queryNS = \ + common.xmpp.NS_MUC_ADMIN) + item = iq.getTag('query').setTag('item') + item.setAttr('affiliation', affiliation) + self.connection.send(iq) + + def send_gc_config(self, room_jid, form): + if not self.connection: + return + iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ + common.xmpp.NS_MUC_OWNER) + query = iq.getTag('query') + form.setAttr('type', 'submit') + query.addChild(node = form) + self.connection.send(iq) + + def change_password(self, password): + if not self.connection: + return + hostname = gajim.config.get_per('accounts', self.name, 'hostname') + username = gajim.config.get_per('accounts', self.name, 'name') + iq = common.xmpp.Iq(typ = 'set', to = hostname) + q = iq.setTag(common.xmpp.NS_REGISTER + ' query') + q.setTagData('username', username) + q.setTagData('password', password) + self.connection.send(iq) + + def get_password(self, callback): + if self.password: + callback(self.password) + return + self.pasword_callback = callback + self.dispatch('PASSWORD_REQUIRED', None) + + def set_password(self, password): + self.password = password + if self.pasword_callback: + self.pasword_callback(password) + self.pasword_callback = None + + def unregister_account(self, on_remove_success): + # no need to write this as a class method and keep the value of + # on_remove_success as a class property as pass it as an argument + def _on_unregister_account_connect(con): + self.on_connect_auth = None + if gajim.account_is_connected(self.name): + hostname = gajim.config.get_per('accounts', self.name, 'hostname') + iq = common.xmpp.Iq(typ = 'set', to = hostname) + iq.setTag(common.xmpp.NS_REGISTER + ' query').setTag('remove') + def _on_answer(result): + if result.getType() == 'result': + on_remove_success(True) + return + self.dispatch('ERROR', (_('Unregister failed'), + _('Unregistration with server %(server)s failed: %(error)s') \ + % {'server': hostname, 'error': result.getErrorMsg()})) + on_remove_success(False) + con.SendAndCallForResponse(iq, _on_answer) + return + on_remove_success(False) + if self.connected == 0: + self.on_connect_auth = _on_unregister_account_connect + self.connect_and_auth() + else: + _on_unregister_account_connect(self.connection) + + def send_invite(self, room, to, reason='', continue_tag=False): + '''sends invitation''' + message=common.xmpp.Message(to = room) + c = message.addChild(name = 'x', namespace = common.xmpp.NS_MUC_USER) + c = c.addChild(name = 'invite', attrs={'to' : to}) + if continue_tag: + c.addChild(name = 'continue') + if reason != '': + c.setTagData('reason', reason) + self.connection.send(message) + + def check_pingalive(self): + if self.awaiting_xmpp_ping_id: + # We haven't got the pong in time, disco and reconnect + self._disconnectedReconnCB() + + def _reconnect_alarm(self): + if self.time_to_reconnect: + if self.connected < 2: + self._reconnect() + else: + self.time_to_reconnect = None + + def request_search_fields(self, jid): + iq = common.xmpp.Iq(typ = 'get', to = jid, queryNS = \ + common.xmpp.NS_SEARCH) + self.connection.send(iq) + + def send_search_form(self, jid, form, is_form): + iq = common.xmpp.Iq(typ = 'set', to = jid, queryNS = \ + common.xmpp.NS_SEARCH) + item = iq.getTag('query') + if is_form: + item.addChild(node = form) + else: + for i in form.keys(): + item.setTagData(i, form[i]) + def _on_response(resp): + jid = jid = helpers.get_jid_from_iq(resp) + tag = resp.getTag('query', namespace = common.xmpp.NS_SEARCH) + if not tag: + self.dispatch('SEARCH_RESULT', (jid, None, False)) + return + df = tag.getTag('x', namespace = common.xmpp.NS_DATA) + if df: + self.dispatch('SEARCH_RESULT', (jid, df, True)) + return + df = [] + for item in tag.getTags('item'): + # We also show attributes. jid is there + f = item.attrs + for i in item.getPayload(): + f[i.getName()] = i.getData() + df.append(f) + self.dispatch('SEARCH_RESULT', (jid, df, False)) + + self.connection.SendAndCallForResponse(iq, _on_response) + + def load_roster_from_db(self): + roster = gajim.logger.get_roster(gajim.get_jid_from_account(self.name)) + self.dispatch('ROSTER', roster) # END Connection - -# vim: se ts=3: diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index 46e6b48fc..f88b60066 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -37,7 +37,7 @@ import hashlib import hmac from time import (altzone, daylight, gmtime, localtime, mktime, strftime, - time as time_time, timezone, tzname) + time as time_time, timezone, tzname) from calendar import timegm import datetime @@ -57,18 +57,18 @@ from common.message_archiving import ARCHIVING_COLLECTION_ARRIVED from common.message_archiving import ARCHIVING_MODIFICATIONS_ARRIVED if gajim.HAVE_FARSIGHT: - from common.jingle import ConnectionJingle + from common.jingle import ConnectionJingle else: - class ConnectionJingle(): - def __init__(self): - pass - def _JingleCB(self, con, stanza): - pass + class ConnectionJingle(): + def __init__(self): + pass + def _JingleCB(self, con, stanza): + pass from common import dbus_support if dbus_support.supported: - import dbus - from music_track_listener import MusicTrackListener + import dbus + from music_track_listener import MusicTrackListener import logging log = logging.getLogger('gajim.c.connection_handlers') @@ -83,2818 +83,2816 @@ PRIVACY_ARRIVED = 'privacy_arrived' PEP_CONFIG = 'pep_config' HAS_IDLE = True try: - import idle + import idle except Exception: - log.debug(_('Unable to load idle module')) - HAS_IDLE = False + log.debug(_('Unable to load idle module')) + HAS_IDLE = False class ConnectionBytestream: - def __init__(self): - self.files_props = {} - self.awaiting_xmpp_ping_id = None + def __init__(self): + self.files_props = {} + self.awaiting_xmpp_ping_id = None - def is_transfer_stopped(self, file_props): - if 'error' in file_props and file_props['error'] != 0: - return True - if 'completed' in file_props and file_props['completed']: - return True - if 'connected' in file_props and file_props['connected'] == False: - return True - if 'stopped' not in file_props or not file_props['stopped']: - return False - return True + def is_transfer_stopped(self, file_props): + if 'error' in file_props and file_props['error'] != 0: + return True + if 'completed' in file_props and file_props['completed']: + return True + if 'connected' in file_props and file_props['connected'] == False: + return True + if 'stopped' not in file_props or not file_props['stopped']: + return False + return True - def send_success_connect_reply(self, streamhost): - ''' send reply to the initiator of FT that we - made a connection - ''' - if not self.connection or self.connected < 2: - return - if streamhost is None: - return None - iq = common.xmpp.Iq(to = streamhost['initiator'], typ = 'result', - frm = streamhost['target']) - iq.setAttr('id', streamhost['id']) - query = iq.setTag('query') - query.setNamespace(common.xmpp.NS_BYTESTREAM) - stream_tag = query.setTag('streamhost-used') - stream_tag.setAttr('jid', streamhost['jid']) - self.connection.send(iq) + def send_success_connect_reply(self, streamhost): + ''' send reply to the initiator of FT that we + made a connection + ''' + if not self.connection or self.connected < 2: + return + if streamhost is None: + return None + iq = common.xmpp.Iq(to = streamhost['initiator'], typ = 'result', + frm = streamhost['target']) + iq.setAttr('id', streamhost['id']) + query = iq.setTag('query') + query.setNamespace(common.xmpp.NS_BYTESTREAM) + stream_tag = query.setTag('streamhost-used') + stream_tag.setAttr('jid', streamhost['jid']) + self.connection.send(iq) - def remove_transfers_for_contact(self, contact): - ''' stop all active transfer for contact ''' - for file_props in self.files_props.values(): - if self.is_transfer_stopped(file_props): - continue - receiver_jid = unicode(file_props['receiver']) - if contact.get_full_jid() == receiver_jid: - file_props['error'] = -5 - self.remove_transfer(file_props) - self.dispatch('FILE_REQUEST_ERROR', (contact.jid, file_props, '')) - sender_jid = unicode(file_props['sender']) - if contact.get_full_jid() == sender_jid: - file_props['error'] = -3 - self.remove_transfer(file_props) + def remove_transfers_for_contact(self, contact): + ''' stop all active transfer for contact ''' + for file_props in self.files_props.values(): + if self.is_transfer_stopped(file_props): + continue + receiver_jid = unicode(file_props['receiver']) + if contact.get_full_jid() == receiver_jid: + file_props['error'] = -5 + self.remove_transfer(file_props) + self.dispatch('FILE_REQUEST_ERROR', (contact.jid, file_props, '')) + sender_jid = unicode(file_props['sender']) + if contact.get_full_jid() == sender_jid: + file_props['error'] = -3 + self.remove_transfer(file_props) - def remove_all_transfers(self): - ''' stops and removes all active connections from the socks5 pool ''' - for file_props in self.files_props.values(): - self.remove_transfer(file_props, remove_from_list = False) - del(self.files_props) - self.files_props = {} + def remove_all_transfers(self): + ''' stops and removes all active connections from the socks5 pool ''' + for file_props in self.files_props.values(): + self.remove_transfer(file_props, remove_from_list = False) + del(self.files_props) + self.files_props = {} - def remove_transfer(self, file_props, remove_from_list = True): - if file_props is None: - return - self.disconnect_transfer(file_props) - sid = file_props['sid'] - gajim.socks5queue.remove_file_props(self.name, sid) + def remove_transfer(self, file_props, remove_from_list = True): + if file_props is None: + return + self.disconnect_transfer(file_props) + sid = file_props['sid'] + gajim.socks5queue.remove_file_props(self.name, sid) - if remove_from_list: - if 'sid' in self.files_props: - del(self.files_props['sid']) + if remove_from_list: + if 'sid' in self.files_props: + del(self.files_props['sid']) - def disconnect_transfer(self, file_props): - if file_props is None: - return - if 'hash' in file_props: - gajim.socks5queue.remove_sender(file_props['hash']) + def disconnect_transfer(self, file_props): + if file_props is None: + return + if 'hash' in file_props: + gajim.socks5queue.remove_sender(file_props['hash']) - if 'streamhosts' in file_props: - for host in file_props['streamhosts']: - if 'idx' in host and host['idx'] > 0: - gajim.socks5queue.remove_receiver(host['idx']) - gajim.socks5queue.remove_sender(host['idx']) + if 'streamhosts' in file_props: + for host in file_props['streamhosts']: + if 'idx' in host and host['idx'] > 0: + gajim.socks5queue.remove_receiver(host['idx']) + gajim.socks5queue.remove_sender(host['idx']) - def send_socks5_info(self, file_props, fast = True, receiver = None, - sender = None): - ''' send iq for the present streamhosts and proxies ''' - if not self.connection or self.connected < 2: - return - if not isinstance(self.peerhost, tuple): - return - port = gajim.config.get('file_transfers_port') - ft_add_hosts_to_send = gajim.config.get('ft_add_hosts_to_send') - cfg_proxies = gajim.config.get_per('accounts', self.name, - 'file_transfer_proxies') - if receiver is None: - receiver = file_props['receiver'] - if sender is None: - sender = file_props['sender'] - proxyhosts = [] - if fast and cfg_proxies: - proxies = [e.strip() for e in cfg_proxies.split(',')] - default = gajim.proxy65_manager.get_default_for_name(self.name) - if default: - # add/move default proxy at top of the others - if proxies.__contains__(default): - proxies.remove(default) - proxies.insert(0, default) + def send_socks5_info(self, file_props, fast = True, receiver = None, + sender = None): + ''' send iq for the present streamhosts and proxies ''' + if not self.connection or self.connected < 2: + return + if not isinstance(self.peerhost, tuple): + return + port = gajim.config.get('file_transfers_port') + ft_add_hosts_to_send = gajim.config.get('ft_add_hosts_to_send') + cfg_proxies = gajim.config.get_per('accounts', self.name, + 'file_transfer_proxies') + if receiver is None: + receiver = file_props['receiver'] + if sender is None: + sender = file_props['sender'] + proxyhosts = [] + if fast and cfg_proxies: + proxies = [e.strip() for e in cfg_proxies.split(',')] + default = gajim.proxy65_manager.get_default_for_name(self.name) + if default: + # add/move default proxy at top of the others + if proxies.__contains__(default): + proxies.remove(default) + proxies.insert(0, default) - for proxy in proxies: - (host, _port, jid) = gajim.proxy65_manager.get_proxy(proxy, self.name) - if host is None: - continue - host_dict={ - 'state': 0, - 'target': unicode(receiver), - 'id': file_props['sid'], - 'sid': file_props['sid'], - 'initiator': proxy, - 'host': host, - 'port': unicode(_port), - 'jid': jid - } - proxyhosts.append(host_dict) - sha_str = helpers.get_auth_sha(file_props['sid'], sender, - receiver) - file_props['sha_str'] = sha_str - ft_add_hosts = [] - if ft_add_hosts_to_send: - ft_add_hosts_to_send = [e.strip() for e in ft_add_hosts_to_send.split(',')] - for ft_host in ft_add_hosts_to_send: - ft_add_hosts.append(ft_host) - listener = gajim.socks5queue.start_listener(port, - sha_str, self._result_socks5_sid, file_props['sid']) - if listener is None: - file_props['error'] = -5 - self.dispatch('FILE_REQUEST_ERROR', (unicode(receiver), file_props, - '')) - self._connect_error(unicode(receiver), file_props['sid'], - file_props['sid'], code = 406) - return + for proxy in proxies: + (host, _port, jid) = gajim.proxy65_manager.get_proxy(proxy, self.name) + if host is None: + continue + host_dict={ + 'state': 0, + 'target': unicode(receiver), + 'id': file_props['sid'], + 'sid': file_props['sid'], + 'initiator': proxy, + 'host': host, + 'port': unicode(_port), + 'jid': jid + } + proxyhosts.append(host_dict) + sha_str = helpers.get_auth_sha(file_props['sid'], sender, + receiver) + file_props['sha_str'] = sha_str + ft_add_hosts = [] + if ft_add_hosts_to_send: + ft_add_hosts_to_send = [e.strip() for e in ft_add_hosts_to_send.split(',')] + for ft_host in ft_add_hosts_to_send: + ft_add_hosts.append(ft_host) + listener = gajim.socks5queue.start_listener(port, + sha_str, self._result_socks5_sid, file_props['sid']) + if listener is None: + file_props['error'] = -5 + self.dispatch('FILE_REQUEST_ERROR', (unicode(receiver), file_props, + '')) + self._connect_error(unicode(receiver), file_props['sid'], + file_props['sid'], code = 406) + return - iq = common.xmpp.Protocol(name = 'iq', to = unicode(receiver), - typ = 'set') - file_props['request-id'] = 'id_' + file_props['sid'] - iq.setID(file_props['request-id']) - query = iq.setTag('query') - query.setNamespace(common.xmpp.NS_BYTESTREAM) - query.setAttr('mode', 'plain') - query.setAttr('sid', file_props['sid']) - for ft_host in ft_add_hosts: - # The streamhost, if set - ostreamhost = common.xmpp.Node(tag = 'streamhost') - query.addChild(node = ostreamhost) - ostreamhost.setAttr('port', unicode(port)) - ostreamhost.setAttr('host', ft_host) - ostreamhost.setAttr('jid', sender) - try: - # The ip we're connected to server with - my_ips = [self.peerhost[0]] - # all IPs from local DNS - for addr in socket.getaddrinfo(socket.gethostname(), None): - if not addr[4][0] in my_ips and not addr[4][0].startswith('127'): - my_ips.append(addr[4][0]) - for ip in my_ips: - streamhost = common.xmpp.Node(tag = 'streamhost') - query.addChild(node = streamhost) - streamhost.setAttr('port', unicode(port)) - streamhost.setAttr('host', ip) - streamhost.setAttr('jid', sender) - except socket.gaierror: - self.dispatch('ERROR', (_('Wrong host'), - _('Invalid local address? :-O'))) + iq = common.xmpp.Protocol(name = 'iq', to = unicode(receiver), + typ = 'set') + file_props['request-id'] = 'id_' + file_props['sid'] + iq.setID(file_props['request-id']) + query = iq.setTag('query') + query.setNamespace(common.xmpp.NS_BYTESTREAM) + query.setAttr('mode', 'plain') + query.setAttr('sid', file_props['sid']) + for ft_host in ft_add_hosts: + # The streamhost, if set + ostreamhost = common.xmpp.Node(tag = 'streamhost') + query.addChild(node = ostreamhost) + ostreamhost.setAttr('port', unicode(port)) + ostreamhost.setAttr('host', ft_host) + ostreamhost.setAttr('jid', sender) + try: + # The ip we're connected to server with + my_ips = [self.peerhost[0]] + # all IPs from local DNS + for addr in socket.getaddrinfo(socket.gethostname(), None): + if not addr[4][0] in my_ips and not addr[4][0].startswith('127'): + my_ips.append(addr[4][0]) + for ip in my_ips: + streamhost = common.xmpp.Node(tag = 'streamhost') + query.addChild(node = streamhost) + streamhost.setAttr('port', unicode(port)) + streamhost.setAttr('host', ip) + streamhost.setAttr('jid', sender) + except socket.gaierror: + self.dispatch('ERROR', (_('Wrong host'), + _('Invalid local address? :-O'))) - if fast and proxyhosts != [] and gajim.config.get_per('accounts', - self.name, 'use_ft_proxies'): - file_props['proxy_receiver'] = unicode(receiver) - file_props['proxy_sender'] = unicode(sender) - file_props['proxyhosts'] = proxyhosts - for proxyhost in proxyhosts: - streamhost = common.xmpp.Node(tag = 'streamhost') - query.addChild(node=streamhost) - streamhost.setAttr('port', proxyhost['port']) - streamhost.setAttr('host', proxyhost['host']) - streamhost.setAttr('jid', proxyhost['jid']) + if fast and proxyhosts != [] and gajim.config.get_per('accounts', + self.name, 'use_ft_proxies'): + file_props['proxy_receiver'] = unicode(receiver) + file_props['proxy_sender'] = unicode(sender) + file_props['proxyhosts'] = proxyhosts + for proxyhost in proxyhosts: + streamhost = common.xmpp.Node(tag = 'streamhost') + query.addChild(node=streamhost) + streamhost.setAttr('port', proxyhost['port']) + streamhost.setAttr('host', proxyhost['host']) + streamhost.setAttr('jid', proxyhost['jid']) - # don't add the proxy child tag for streamhosts, which are proxies - # proxy = streamhost.setTag('proxy') - # proxy.setNamespace(common.xmpp.NS_STREAM) - self.connection.send(iq) + # don't add the proxy child tag for streamhosts, which are proxies + # proxy = streamhost.setTag('proxy') + # proxy.setNamespace(common.xmpp.NS_STREAM) + self.connection.send(iq) - def send_file_rejection(self, file_props, code='403', typ=None): - ''' informs sender that we refuse to download the file - typ is used when code = '400', in this case typ can be 'strean' for - invalid stream or 'profile' for invalid profile''' - # user response to ConfirmationDialog may come after we've disconneted - if not self.connection or self.connected < 2: - return - iq = common.xmpp.Protocol(name = 'iq', - to = unicode(file_props['sender']), typ = 'error') - iq.setAttr('id', file_props['request-id']) - if code == '400' and typ in ('stream', 'profile'): - name = 'bad-request' - text = '' - else: - name = 'forbidden' - text = 'Offer Declined' - err = common.xmpp.ErrorNode(code=code, typ='cancel', name=name, text=text) - if code == '400' and typ in ('stream', 'profile'): - if typ == 'stream': - err.setTag('no-valid-streams', namespace=common.xmpp.NS_SI) - else: - err.setTag('bad-profile', namespace=common.xmpp.NS_SI) - iq.addChild(node=err) - self.connection.send(iq) + def send_file_rejection(self, file_props, code='403', typ=None): + ''' informs sender that we refuse to download the file + typ is used when code = '400', in this case typ can be 'strean' for + invalid stream or 'profile' for invalid profile''' + # user response to ConfirmationDialog may come after we've disconneted + if not self.connection or self.connected < 2: + return + iq = common.xmpp.Protocol(name = 'iq', + to = unicode(file_props['sender']), typ = 'error') + iq.setAttr('id', file_props['request-id']) + if code == '400' and typ in ('stream', 'profile'): + name = 'bad-request' + text = '' + else: + name = 'forbidden' + text = 'Offer Declined' + err = common.xmpp.ErrorNode(code=code, typ='cancel', name=name, text=text) + if code == '400' and typ in ('stream', 'profile'): + if typ == 'stream': + err.setTag('no-valid-streams', namespace=common.xmpp.NS_SI) + else: + err.setTag('bad-profile', namespace=common.xmpp.NS_SI) + iq.addChild(node=err) + self.connection.send(iq) - def send_file_approval(self, file_props): - ''' send iq, confirming that we want to download the file ''' - # user response to ConfirmationDialog may come after we've disconneted - if not self.connection or self.connected < 2: - return - iq = common.xmpp.Protocol(name = 'iq', - to = unicode(file_props['sender']), typ = 'result') - iq.setAttr('id', file_props['request-id']) - si = iq.setTag('si') - si.setNamespace(common.xmpp.NS_SI) - if 'offset' in file_props and file_props['offset']: - file_tag = si.setTag('file') - file_tag.setNamespace(common.xmpp.NS_FILE) - range_tag = file_tag.setTag('range') - range_tag.setAttr('offset', file_props['offset']) - feature = si.setTag('feature') - feature.setNamespace(common.xmpp.NS_FEATURE) - _feature = common.xmpp.DataForm(typ='submit') - feature.addChild(node=_feature) - field = _feature.setField('stream-method') - field.delAttr('type') - field.setValue(common.xmpp.NS_BYTESTREAM) - self.connection.send(iq) + def send_file_approval(self, file_props): + ''' send iq, confirming that we want to download the file ''' + # user response to ConfirmationDialog may come after we've disconneted + if not self.connection or self.connected < 2: + return + iq = common.xmpp.Protocol(name = 'iq', + to = unicode(file_props['sender']), typ = 'result') + iq.setAttr('id', file_props['request-id']) + si = iq.setTag('si') + si.setNamespace(common.xmpp.NS_SI) + if 'offset' in file_props and file_props['offset']: + file_tag = si.setTag('file') + file_tag.setNamespace(common.xmpp.NS_FILE) + range_tag = file_tag.setTag('range') + range_tag.setAttr('offset', file_props['offset']) + feature = si.setTag('feature') + feature.setNamespace(common.xmpp.NS_FEATURE) + _feature = common.xmpp.DataForm(typ='submit') + feature.addChild(node=_feature) + field = _feature.setField('stream-method') + field.delAttr('type') + field.setValue(common.xmpp.NS_BYTESTREAM) + self.connection.send(iq) - def _ft_get_our_jid(self): - our_jid = gajim.get_jid_from_account(self.name) - resource = self.server_resource - return our_jid + '/' + resource + def _ft_get_our_jid(self): + our_jid = gajim.get_jid_from_account(self.name) + resource = self.server_resource + return our_jid + '/' + resource - def _ft_get_receiver_jid(self, file_props): - return file_props['receiver'].jid + '/' + file_props['receiver'].resource + def _ft_get_receiver_jid(self, file_props): + return file_props['receiver'].jid + '/' + file_props['receiver'].resource - def send_file_request(self, file_props): - ''' send iq for new FT request ''' - if not self.connection or self.connected < 2: - return - file_props['sender'] = self._ft_get_our_jid() - fjid = self._ft_get_receiver_jid(file_props) - iq = common.xmpp.Protocol(name = 'iq', to = fjid, - typ = 'set') - iq.setID(file_props['sid']) - self.files_props[file_props['sid']] = file_props - si = iq.setTag('si') - si.setNamespace(common.xmpp.NS_SI) - si.setAttr('profile', common.xmpp.NS_FILE) - si.setAttr('id', file_props['sid']) - file_tag = si.setTag('file') - file_tag.setNamespace(common.xmpp.NS_FILE) - file_tag.setAttr('name', file_props['name']) - file_tag.setAttr('size', file_props['size']) - desc = file_tag.setTag('desc') - if 'desc' in file_props: - desc.setData(file_props['desc']) - file_tag.setTag('range') - feature = si.setTag('feature') - feature.setNamespace(common.xmpp.NS_FEATURE) - _feature = common.xmpp.DataForm(typ='form') - feature.addChild(node=_feature) - field = _feature.setField('stream-method') - field.setAttr('type', 'list-single') - field.addOption(common.xmpp.NS_BYTESTREAM) - self.connection.send(iq) + def send_file_request(self, file_props): + ''' send iq for new FT request ''' + if not self.connection or self.connected < 2: + return + file_props['sender'] = self._ft_get_our_jid() + fjid = self._ft_get_receiver_jid(file_props) + iq = common.xmpp.Protocol(name = 'iq', to = fjid, + typ = 'set') + iq.setID(file_props['sid']) + self.files_props[file_props['sid']] = file_props + si = iq.setTag('si') + si.setNamespace(common.xmpp.NS_SI) + si.setAttr('profile', common.xmpp.NS_FILE) + si.setAttr('id', file_props['sid']) + file_tag = si.setTag('file') + file_tag.setNamespace(common.xmpp.NS_FILE) + file_tag.setAttr('name', file_props['name']) + file_tag.setAttr('size', file_props['size']) + desc = file_tag.setTag('desc') + if 'desc' in file_props: + desc.setData(file_props['desc']) + file_tag.setTag('range') + feature = si.setTag('feature') + feature.setNamespace(common.xmpp.NS_FEATURE) + _feature = common.xmpp.DataForm(typ='form') + feature.addChild(node=_feature) + field = _feature.setField('stream-method') + field.setAttr('type', 'list-single') + field.addOption(common.xmpp.NS_BYTESTREAM) + self.connection.send(iq) - def _result_socks5_sid(self, sid, hash_id): - ''' store the result of sha message from auth. ''' - if sid not in self.files_props: - return - file_props = self.files_props[sid] - file_props['hash'] = hash_id - return + def _result_socks5_sid(self, sid, hash_id): + ''' store the result of sha message from auth. ''' + if sid not in self.files_props: + return + file_props = self.files_props[sid] + file_props['hash'] = hash_id + return - def _connect_error(self, to, _id, sid, code=404): - ''' cb, when there is an error establishing BS connection, or - when connection is rejected''' - if not self.connection or self.connected < 2: - return - msg_dict = { - 404: 'Could not connect to given hosts', - 405: 'Cancel', - 406: 'Not acceptable', - } - msg = msg_dict[code] - iq = None - iq = common.xmpp.Protocol(name = 'iq', to = to, - typ = 'error') - iq.setAttr('id', _id) - err = iq.setTag('error') - err.setAttr('code', unicode(code)) - err.setData(msg) - self.connection.send(iq) - if code == 404: - file_props = gajim.socks5queue.get_file_props(self.name, sid) - if file_props is not None: - self.disconnect_transfer(file_props) - file_props['error'] = -3 - self.dispatch('FILE_REQUEST_ERROR', (to, file_props, msg)) + def _connect_error(self, to, _id, sid, code=404): + ''' cb, when there is an error establishing BS connection, or + when connection is rejected''' + if not self.connection or self.connected < 2: + return + msg_dict = { + 404: 'Could not connect to given hosts', + 405: 'Cancel', + 406: 'Not acceptable', + } + msg = msg_dict[code] + iq = None + iq = common.xmpp.Protocol(name = 'iq', to = to, + typ = 'error') + iq.setAttr('id', _id) + err = iq.setTag('error') + err.setAttr('code', unicode(code)) + err.setData(msg) + self.connection.send(iq) + if code == 404: + file_props = gajim.socks5queue.get_file_props(self.name, sid) + if file_props is not None: + self.disconnect_transfer(file_props) + file_props['error'] = -3 + self.dispatch('FILE_REQUEST_ERROR', (to, file_props, msg)) - def _proxy_auth_ok(self, proxy): - '''cb, called after authentication to proxy server ''' - if not self.connection or self.connected < 2: - return - file_props = self.files_props[proxy['sid']] - iq = common.xmpp.Protocol(name = 'iq', to = proxy['initiator'], - typ = 'set') - auth_id = "au_" + proxy['sid'] - iq.setID(auth_id) - query = iq.setTag('query') - query.setNamespace(common.xmpp.NS_BYTESTREAM) - query.setAttr('sid', proxy['sid']) - activate = query.setTag('activate') - activate.setData(file_props['proxy_receiver']) - iq.setID(auth_id) - self.connection.send(iq) + def _proxy_auth_ok(self, proxy): + '''cb, called after authentication to proxy server ''' + if not self.connection or self.connected < 2: + return + file_props = self.files_props[proxy['sid']] + iq = common.xmpp.Protocol(name = 'iq', to = proxy['initiator'], + typ = 'set') + auth_id = "au_" + proxy['sid'] + iq.setID(auth_id) + query = iq.setTag('query') + query.setNamespace(common.xmpp.NS_BYTESTREAM) + query.setAttr('sid', proxy['sid']) + activate = query.setTag('activate') + activate.setData(file_props['proxy_receiver']) + iq.setID(auth_id) + self.connection.send(iq) - # register xmpppy handlers for bytestream and FT stanzas - def _bytestreamErrorCB(self, con, iq_obj): - log.debug('_bytestreamErrorCB') - id_ = unicode(iq_obj.getAttr('id')) - frm = helpers.get_full_jid_from_iq(iq_obj) - query = iq_obj.getTag('query') - gajim.proxy65_manager.error_cb(frm, query) - jid = helpers.get_jid_from_iq(iq_obj) - id_ = id_[3:] - if id_ not in self.files_props: - return - file_props = self.files_props[id_] - file_props['error'] = -4 - self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, '')) - raise common.xmpp.NodeProcessed + # register xmpppy handlers for bytestream and FT stanzas + def _bytestreamErrorCB(self, con, iq_obj): + log.debug('_bytestreamErrorCB') + id_ = unicode(iq_obj.getAttr('id')) + frm = helpers.get_full_jid_from_iq(iq_obj) + query = iq_obj.getTag('query') + gajim.proxy65_manager.error_cb(frm, query) + jid = helpers.get_jid_from_iq(iq_obj) + id_ = id_[3:] + if id_ not in self.files_props: + return + file_props = self.files_props[id_] + file_props['error'] = -4 + self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, '')) + raise common.xmpp.NodeProcessed - def _ft_get_from(self, iq_obj): - return helpers.get_full_jid_from_iq(iq_obj) + def _ft_get_from(self, iq_obj): + return helpers.get_full_jid_from_iq(iq_obj) - def _bytestreamSetCB(self, con, iq_obj): - log.debug('_bytestreamSetCB') - target = unicode(iq_obj.getAttr('to')) - id_ = unicode(iq_obj.getAttr('id')) - query = iq_obj.getTag('query') - sid = unicode(query.getAttr('sid')) - file_props = gajim.socks5queue.get_file_props( - self.name, sid) - streamhosts=[] - for item in query.getChildren(): - if item.getName() == 'streamhost': - host_dict={ - 'state': 0, - 'target': target, - 'id': id_, - 'sid': sid, - 'initiator': self._ft_get_from(iq_obj) - } - for attr in item.getAttrs(): - host_dict[attr] = item.getAttr(attr) - streamhosts.append(host_dict) - if file_props is None: - if sid in self.files_props: - file_props = self.files_props[sid] - file_props['fast'] = streamhosts - if file_props['type'] == 's': # FIXME: remove fast xmlns - # only psi do this + def _bytestreamSetCB(self, con, iq_obj): + log.debug('_bytestreamSetCB') + target = unicode(iq_obj.getAttr('to')) + id_ = unicode(iq_obj.getAttr('id')) + query = iq_obj.getTag('query') + sid = unicode(query.getAttr('sid')) + file_props = gajim.socks5queue.get_file_props( + self.name, sid) + streamhosts=[] + for item in query.getChildren(): + if item.getName() == 'streamhost': + host_dict={ + 'state': 0, + 'target': target, + 'id': id_, + 'sid': sid, + 'initiator': self._ft_get_from(iq_obj) + } + for attr in item.getAttrs(): + host_dict[attr] = item.getAttr(attr) + streamhosts.append(host_dict) + if file_props is None: + if sid in self.files_props: + file_props = self.files_props[sid] + file_props['fast'] = streamhosts + if file_props['type'] == 's': # FIXME: remove fast xmlns + # only psi do this - if 'streamhosts' in file_props: - file_props['streamhosts'].extend(streamhosts) - else: - file_props['streamhosts'] = streamhosts - if not gajim.socks5queue.get_file_props(self.name, sid): - gajim.socks5queue.add_file_props(self.name, file_props) - gajim.socks5queue.connect_to_hosts(self.name, sid, - self.send_success_connect_reply, None) - raise common.xmpp.NodeProcessed + if 'streamhosts' in file_props: + file_props['streamhosts'].extend(streamhosts) + else: + file_props['streamhosts'] = streamhosts + if not gajim.socks5queue.get_file_props(self.name, sid): + gajim.socks5queue.add_file_props(self.name, file_props) + gajim.socks5queue.connect_to_hosts(self.name, sid, + self.send_success_connect_reply, None) + raise common.xmpp.NodeProcessed - file_props['streamhosts'] = streamhosts - if file_props['type'] == 'r': - gajim.socks5queue.connect_to_hosts(self.name, sid, - self.send_success_connect_reply, self._connect_error) - raise common.xmpp.NodeProcessed + file_props['streamhosts'] = streamhosts + if file_props['type'] == 'r': + gajim.socks5queue.connect_to_hosts(self.name, sid, + self.send_success_connect_reply, self._connect_error) + raise common.xmpp.NodeProcessed - def _ResultCB(self, con, iq_obj): - log.debug('_ResultCB') - # if we want to respect xep-0065 we have to check for proxy - # activation result in any result iq - real_id = unicode(iq_obj.getAttr('id')) - if real_id == self.awaiting_xmpp_ping_id: - self.awaiting_xmpp_ping_id = None - return - if not real_id.startswith('au_'): - return - frm = self._ft_get_from(iq_obj) - id_ = real_id[3:] - if id_ in self.files_props: - file_props = self.files_props[id_] - if file_props['streamhost-used']: - for host in file_props['proxyhosts']: - if host['initiator'] == frm and 'idx' in host: - gajim.socks5queue.activate_proxy(host['idx']) - raise common.xmpp.NodeProcessed + def _ResultCB(self, con, iq_obj): + log.debug('_ResultCB') + # if we want to respect xep-0065 we have to check for proxy + # activation result in any result iq + real_id = unicode(iq_obj.getAttr('id')) + if real_id == self.awaiting_xmpp_ping_id: + self.awaiting_xmpp_ping_id = None + return + if not real_id.startswith('au_'): + return + frm = self._ft_get_from(iq_obj) + id_ = real_id[3:] + if id_ in self.files_props: + file_props = self.files_props[id_] + if file_props['streamhost-used']: + for host in file_props['proxyhosts']: + if host['initiator'] == frm and 'idx' in host: + gajim.socks5queue.activate_proxy(host['idx']) + raise common.xmpp.NodeProcessed - def _ft_get_streamhost_jid_attr(self, streamhost): - return helpers.parse_jid(streamhost.getAttr('jid')) + def _ft_get_streamhost_jid_attr(self, streamhost): + return helpers.parse_jid(streamhost.getAttr('jid')) - def _bytestreamResultCB(self, con, iq_obj): - log.debug('_bytestreamResultCB') - frm = self._ft_get_from(iq_obj) - real_id = unicode(iq_obj.getAttr('id')) - query = iq_obj.getTag('query') - gajim.proxy65_manager.resolve_result(frm, query) + def _bytestreamResultCB(self, con, iq_obj): + log.debug('_bytestreamResultCB') + frm = self._ft_get_from(iq_obj) + real_id = unicode(iq_obj.getAttr('id')) + query = iq_obj.getTag('query') + gajim.proxy65_manager.resolve_result(frm, query) - try: - streamhost = query.getTag('streamhost-used') - except Exception: # this bytestream result is not what we need - pass - id_ = real_id[3:] - if id_ in self.files_props: - file_props = self.files_props[id_] - else: - raise common.xmpp.NodeProcessed - if streamhost is None: - # proxy approves the activate query - if real_id.startswith('au_'): - if 'streamhost-used' not in file_props or \ - file_props['streamhost-used'] is False: - raise common.xmpp.NodeProcessed - if 'proxyhosts' not in file_props: - raise common.xmpp.NodeProcessed - for host in file_props['proxyhosts']: - if host['initiator'] == frm and \ - unicode(query.getAttr('sid')) == file_props['sid']: - gajim.socks5queue.activate_proxy(host['idx']) - break - raise common.xmpp.NodeProcessed - jid = self._ft_get_streamhost_jid_attr(streamhost) - if 'streamhost-used' in file_props and \ - file_props['streamhost-used'] is True: - raise common.xmpp.NodeProcessed + try: + streamhost = query.getTag('streamhost-used') + except Exception: # this bytestream result is not what we need + pass + id_ = real_id[3:] + if id_ in self.files_props: + file_props = self.files_props[id_] + else: + raise common.xmpp.NodeProcessed + if streamhost is None: + # proxy approves the activate query + if real_id.startswith('au_'): + if 'streamhost-used' not in file_props or \ + file_props['streamhost-used'] is False: + raise common.xmpp.NodeProcessed + if 'proxyhosts' not in file_props: + raise common.xmpp.NodeProcessed + for host in file_props['proxyhosts']: + if host['initiator'] == frm and \ + unicode(query.getAttr('sid')) == file_props['sid']: + gajim.socks5queue.activate_proxy(host['idx']) + break + raise common.xmpp.NodeProcessed + jid = self._ft_get_streamhost_jid_attr(streamhost) + if 'streamhost-used' in file_props and \ + file_props['streamhost-used'] is True: + raise common.xmpp.NodeProcessed - if real_id.startswith('au_'): - if 'stopped' in file and file_props['stopped']: - self.remove_transfer(file_props) - else: - gajim.socks5queue.send_file(file_props, self.name) - raise common.xmpp.NodeProcessed + if real_id.startswith('au_'): + if 'stopped' in file and file_props['stopped']: + self.remove_transfer(file_props) + else: + gajim.socks5queue.send_file(file_props, self.name) + raise common.xmpp.NodeProcessed - proxy = None - if 'proxyhosts' in file_props: - for proxyhost in file_props['proxyhosts']: - if proxyhost['jid'] == jid: - proxy = proxyhost + proxy = None + if 'proxyhosts' in file_props: + for proxyhost in file_props['proxyhosts']: + if proxyhost['jid'] == jid: + proxy = proxyhost - if proxy is not None: - file_props['streamhost-used'] = True - if 'streamhosts' not in file_props: - file_props['streamhosts'] = [] - file_props['streamhosts'].append(proxy) - file_props['is_a_proxy'] = True - receiver = socks5.Socks5Receiver(gajim.idlequeue, proxy, - file_props['sid'], file_props) - gajim.socks5queue.add_receiver(self.name, receiver) - proxy['idx'] = receiver.queue_idx - gajim.socks5queue.on_success = self._proxy_auth_ok - raise common.xmpp.NodeProcessed + if proxy is not None: + file_props['streamhost-used'] = True + if 'streamhosts' not in file_props: + file_props['streamhosts'] = [] + file_props['streamhosts'].append(proxy) + file_props['is_a_proxy'] = True + receiver = socks5.Socks5Receiver(gajim.idlequeue, proxy, + file_props['sid'], file_props) + gajim.socks5queue.add_receiver(self.name, receiver) + proxy['idx'] = receiver.queue_idx + gajim.socks5queue.on_success = self._proxy_auth_ok + raise common.xmpp.NodeProcessed - else: - if 'stopped' in file_props and file_props['stopped']: - self.remove_transfer(file_props) - else: - gajim.socks5queue.send_file(file_props, self.name) - if 'fast' in file_props: - fasts = file_props['fast'] - if len(fasts) > 0: - self._connect_error(frm, fasts[0]['id'], file_props['sid'], - code = 406) + else: + if 'stopped' in file_props and file_props['stopped']: + self.remove_transfer(file_props) + else: + gajim.socks5queue.send_file(file_props, self.name) + if 'fast' in file_props: + fasts = file_props['fast'] + if len(fasts) > 0: + self._connect_error(frm, fasts[0]['id'], file_props['sid'], + code = 406) - raise common.xmpp.NodeProcessed + raise common.xmpp.NodeProcessed - def _siResultCB(self, con, iq_obj): - log.debug('_siResultCB') - id_ = iq_obj.getAttr('id') - if id_ not in self.files_props: - # no such jid - return - file_props = self.files_props[id_] - if file_props is None: - # file properties for jid is none - return - if 'request-id' in file_props: - # we have already sent streamhosts info - return - file_props['receiver'] = self._ft_get_from(iq_obj) - si = iq_obj.getTag('si') - file_tag = si.getTag('file') - range_tag = None - if file_tag: - range_tag = file_tag.getTag('range') - if range_tag: - offset = range_tag.getAttr('offset') - if offset: - file_props['offset'] = int(offset) - length = range_tag.getAttr('length') - if length: - file_props['length'] = int(length) - feature = si.setTag('feature') - if feature.getNamespace() != common.xmpp.NS_FEATURE: - return - form_tag = feature.getTag('x') - form = common.xmpp.DataForm(node=form_tag) - field = form.getField('stream-method') - if field.getValue() != common.xmpp.NS_BYTESTREAM: - return - self.send_socks5_info(file_props, fast = True) - raise common.xmpp.NodeProcessed + def _siResultCB(self, con, iq_obj): + log.debug('_siResultCB') + id_ = iq_obj.getAttr('id') + if id_ not in self.files_props: + # no such jid + return + file_props = self.files_props[id_] + if file_props is None: + # file properties for jid is none + return + if 'request-id' in file_props: + # we have already sent streamhosts info + return + file_props['receiver'] = self._ft_get_from(iq_obj) + si = iq_obj.getTag('si') + file_tag = si.getTag('file') + range_tag = None + if file_tag: + range_tag = file_tag.getTag('range') + if range_tag: + offset = range_tag.getAttr('offset') + if offset: + file_props['offset'] = int(offset) + length = range_tag.getAttr('length') + if length: + file_props['length'] = int(length) + feature = si.setTag('feature') + if feature.getNamespace() != common.xmpp.NS_FEATURE: + return + form_tag = feature.getTag('x') + form = common.xmpp.DataForm(node=form_tag) + field = form.getField('stream-method') + if field.getValue() != common.xmpp.NS_BYTESTREAM: + return + self.send_socks5_info(file_props, fast = True) + raise common.xmpp.NodeProcessed - def _siSetCB(self, con, iq_obj): - log.debug('_siSetCB') - jid = self._ft_get_from(iq_obj) - file_props = {'type': 'r'} - file_props['sender'] = jid - file_props['request-id'] = unicode(iq_obj.getAttr('id')) - si = iq_obj.getTag('si') - profile = si.getAttr('profile') - mime_type = si.getAttr('mime-type') - if profile != common.xmpp.NS_FILE: - self.send_file_rejection(file_props, code='400', typ='profile') - raise common.xmpp.NodeProcessed - feature_tag = si.getTag('feature', namespace=common.xmpp.NS_FEATURE) - if not feature_tag: - return - form_tag = feature_tag.getTag('x', namespace=common.xmpp.NS_DATA) - if not form_tag: - return - form = common.dataforms.ExtendForm(node=form_tag) - for f in form.iter_fields(): - if f.var == 'stream-method' and f.type == 'list-single': - values = [o[1] for o in f.options] - if common.xmpp.NS_BYTESTREAM in values: - break - else: - self.send_file_rejection(file_props, code='400', typ='stream') - raise common.xmpp.NodeProcessed - file_tag = si.getTag('file') - for attribute in file_tag.getAttrs(): - if attribute in ('name', 'size', 'hash', 'date'): - val = file_tag.getAttr(attribute) - if val is None: - continue - file_props[attribute] = val - file_desc_tag = file_tag.getTag('desc') - if file_desc_tag is not None: - file_props['desc'] = file_desc_tag.getData() + def _siSetCB(self, con, iq_obj): + log.debug('_siSetCB') + jid = self._ft_get_from(iq_obj) + file_props = {'type': 'r'} + file_props['sender'] = jid + file_props['request-id'] = unicode(iq_obj.getAttr('id')) + si = iq_obj.getTag('si') + profile = si.getAttr('profile') + mime_type = si.getAttr('mime-type') + if profile != common.xmpp.NS_FILE: + self.send_file_rejection(file_props, code='400', typ='profile') + raise common.xmpp.NodeProcessed + feature_tag = si.getTag('feature', namespace=common.xmpp.NS_FEATURE) + if not feature_tag: + return + form_tag = feature_tag.getTag('x', namespace=common.xmpp.NS_DATA) + if not form_tag: + return + form = common.dataforms.ExtendForm(node=form_tag) + for f in form.iter_fields(): + if f.var == 'stream-method' and f.type == 'list-single': + values = [o[1] for o in f.options] + if common.xmpp.NS_BYTESTREAM in values: + break + else: + self.send_file_rejection(file_props, code='400', typ='stream') + raise common.xmpp.NodeProcessed + file_tag = si.getTag('file') + for attribute in file_tag.getAttrs(): + if attribute in ('name', 'size', 'hash', 'date'): + val = file_tag.getAttr(attribute) + if val is None: + continue + file_props[attribute] = val + file_desc_tag = file_tag.getTag('desc') + if file_desc_tag is not None: + file_props['desc'] = file_desc_tag.getData() - if mime_type is not None: - file_props['mime-type'] = mime_type - our_jid = gajim.get_jid_from_account(self.name) - resource = self.server_resource - file_props['receiver'] = self._ft_get_our_jid() - file_props['sid'] = unicode(si.getAttr('id')) - file_props['transfered_size'] = [] - gajim.socks5queue.add_file_props(self.name, file_props) - self.dispatch('FILE_REQUEST', (jid, file_props)) - raise common.xmpp.NodeProcessed + if mime_type is not None: + file_props['mime-type'] = mime_type + our_jid = gajim.get_jid_from_account(self.name) + resource = self.server_resource + file_props['receiver'] = self._ft_get_our_jid() + file_props['sid'] = unicode(si.getAttr('id')) + file_props['transfered_size'] = [] + gajim.socks5queue.add_file_props(self.name, file_props) + self.dispatch('FILE_REQUEST', (jid, file_props)) + raise common.xmpp.NodeProcessed - def _siErrorCB(self, con, iq_obj): - log.debug('_siErrorCB') - si = iq_obj.getTag('si') - profile = si.getAttr('profile') - if profile != common.xmpp.NS_FILE: - return - id_ = iq_obj.getAttr('id') - if id_ not in self.files_props: - # no such jid - return - file_props = self.files_props[id_] - if file_props is None: - # file properties for jid is none - return - jid = self._ft_get_from(iq_obj) - file_props['error'] = -3 - self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, '')) - raise common.xmpp.NodeProcessed + def _siErrorCB(self, con, iq_obj): + log.debug('_siErrorCB') + si = iq_obj.getTag('si') + profile = si.getAttr('profile') + if profile != common.xmpp.NS_FILE: + return + id_ = iq_obj.getAttr('id') + if id_ not in self.files_props: + # no such jid + return + file_props = self.files_props[id_] + if file_props is None: + # file properties for jid is none + return + jid = self._ft_get_from(iq_obj) + file_props['error'] = -3 + self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, '')) + raise common.xmpp.NodeProcessed class ConnectionDisco: - ''' hold xmpppy handlers and public methods for discover services''' - def discoverItems(self, jid, node = None, id_prefix = None): - '''According to XEP-0030: jid is mandatory, - name, node, action is optional.''' - self._discover(common.xmpp.NS_DISCO_ITEMS, jid, node, id_prefix) + ''' hold xmpppy handlers and public methods for discover services''' + def discoverItems(self, jid, node = None, id_prefix = None): + '''According to XEP-0030: jid is mandatory, + name, node, action is optional.''' + self._discover(common.xmpp.NS_DISCO_ITEMS, jid, node, id_prefix) - def discoverInfo(self, jid, node = None, id_prefix = None): - '''According to XEP-0030: - For identity: category, type is mandatory, name is optional. - For feature: var is mandatory''' - self._discover(common.xmpp.NS_DISCO_INFO, jid, node, id_prefix) + def discoverInfo(self, jid, node = None, id_prefix = None): + '''According to XEP-0030: + For identity: category, type is mandatory, name is optional. + For feature: var is mandatory''' + self._discover(common.xmpp.NS_DISCO_INFO, jid, node, id_prefix) - def request_register_agent_info(self, agent): - if not self.connection or self.connected < 2: - return None - iq = common.xmpp.Iq('get', common.xmpp.NS_REGISTER, to=agent) - id_ = self.connection.getAnID() - iq.setID(id_) - # Wait the answer during 30 secondes - self.awaiting_timeouts[gajim.idlequeue.current_time() + 30] = (id_, - _('Registration information for transport %s has not arrived in time')\ - % agent) - self.connection.SendAndCallForResponse(iq, self._ReceivedRegInfo, - {'agent': agent}) + def request_register_agent_info(self, agent): + if not self.connection or self.connected < 2: + return None + iq = common.xmpp.Iq('get', common.xmpp.NS_REGISTER, to=agent) + id_ = self.connection.getAnID() + iq.setID(id_) + # Wait the answer during 30 secondes + self.awaiting_timeouts[gajim.idlequeue.current_time() + 30] = (id_, + _('Registration information for transport %s has not arrived in time')\ + % agent) + self.connection.SendAndCallForResponse(iq, self._ReceivedRegInfo, + {'agent': agent}) - def _agent_registered_cb(self, con, resp, agent): - if resp.getType() == 'result': - self.dispatch('INFORMATION', (_('Registration succeeded'), - _('Registration with agent %s succeeded') % agent)) - if resp.getType() == 'error': - self.dispatch('ERROR', (_('Registration failed'), _('Registration with' - ' agent %(agent)s failed with error %(error)s: %(error_msg)s') % { - 'agent': agent, 'error': resp.getError(), - 'error_msg': resp.getErrorMsg()})) + def _agent_registered_cb(self, con, resp, agent): + if resp.getType() == 'result': + self.dispatch('INFORMATION', (_('Registration succeeded'), + _('Registration with agent %s succeeded') % agent)) + if resp.getType() == 'error': + self.dispatch('ERROR', (_('Registration failed'), _('Registration with' + ' agent %(agent)s failed with error %(error)s: %(error_msg)s') % { + 'agent': agent, 'error': resp.getError(), + 'error_msg': resp.getErrorMsg()})) - def register_agent(self, agent, info, is_form = False): - if not self.connection or self.connected < 2: - return - if is_form: - iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to = agent) - query = iq.getTag('query') - info.setAttr('type', 'submit') - query.addChild(node = info) - self.connection.SendAndCallForResponse(iq, self._agent_registered_cb, - {'agent': agent}) - else: - # fixed: blocking - common.xmpp.features_nb.register(self.connection, agent, info, None) + def register_agent(self, agent, info, is_form = False): + if not self.connection or self.connected < 2: + return + if is_form: + iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to = agent) + query = iq.getTag('query') + info.setAttr('type', 'submit') + query.addChild(node = info) + self.connection.SendAndCallForResponse(iq, self._agent_registered_cb, + {'agent': agent}) + else: + # fixed: blocking + common.xmpp.features_nb.register(self.connection, agent, info, None) - def _discover(self, ns, jid, node = None, id_prefix = None): - if not self.connection or self.connected < 2: - return - iq = common.xmpp.Iq(typ = 'get', to = jid, queryNS = ns) - if id_prefix: - id_ = self.connection.getAnID() - iq.setID('%s%s' % (id_prefix, id_)) - if node: - iq.setQuerynode(node) - self.connection.send(iq) + def _discover(self, ns, jid, node = None, id_prefix = None): + if not self.connection or self.connected < 2: + return + iq = common.xmpp.Iq(typ = 'get', to = jid, queryNS = ns) + if id_prefix: + id_ = self.connection.getAnID() + iq.setID('%s%s' % (id_prefix, id_)) + if node: + iq.setQuerynode(node) + self.connection.send(iq) - def _ReceivedRegInfo(self, con, resp, agent): - common.xmpp.features_nb._ReceivedRegInfo(con, resp, agent) - self._IqCB(con, resp) + def _ReceivedRegInfo(self, con, resp, agent): + common.xmpp.features_nb._ReceivedRegInfo(con, resp, agent) + self._IqCB(con, resp) - def _discoGetCB(self, con, iq_obj): - ''' get disco info ''' - if not self.connection or self.connected < 2: - return - frm = helpers.get_full_jid_from_iq(iq_obj) - to = unicode(iq_obj.getAttr('to')) - id_ = unicode(iq_obj.getAttr('id')) - iq = common.xmpp.Iq(to = frm, typ = 'result', queryNS =\ - common.xmpp.NS_DISCO, frm = to) - iq.setAttr('id', id_) - query = iq.setTag('query') - query.setAttr('node','http://gajim.org#' + gajim.version.split('-', - 1)[0]) - for f in (common.xmpp.NS_BYTESTREAM, common.xmpp.NS_SI, - common.xmpp.NS_FILE, common.xmpp.NS_COMMANDS): - feature = common.xmpp.Node('feature') - feature.setAttr('var', f) - query.addChild(node=feature) + def _discoGetCB(self, con, iq_obj): + ''' get disco info ''' + if not self.connection or self.connected < 2: + return + frm = helpers.get_full_jid_from_iq(iq_obj) + to = unicode(iq_obj.getAttr('to')) + id_ = unicode(iq_obj.getAttr('id')) + iq = common.xmpp.Iq(to = frm, typ = 'result', queryNS =\ + common.xmpp.NS_DISCO, frm = to) + iq.setAttr('id', id_) + query = iq.setTag('query') + query.setAttr('node', 'http://gajim.org#' + gajim.version.split('-', + 1)[0]) + for f in (common.xmpp.NS_BYTESTREAM, common.xmpp.NS_SI, + common.xmpp.NS_FILE, common.xmpp.NS_COMMANDS): + feature = common.xmpp.Node('feature') + feature.setAttr('var', f) + query.addChild(node=feature) - self.connection.send(iq) - raise common.xmpp.NodeProcessed + self.connection.send(iq) + raise common.xmpp.NodeProcessed - def _DiscoverItemsErrorCB(self, con, iq_obj): - log.debug('DiscoverItemsErrorCB') - jid = helpers.get_full_jid_from_iq(iq_obj) - self.dispatch('AGENT_ERROR_ITEMS', (jid)) + def _DiscoverItemsErrorCB(self, con, iq_obj): + log.debug('DiscoverItemsErrorCB') + jid = helpers.get_full_jid_from_iq(iq_obj) + self.dispatch('AGENT_ERROR_ITEMS', (jid)) - def _DiscoverItemsCB(self, con, iq_obj): - log.debug('DiscoverItemsCB') - q = iq_obj.getTag('query') - node = q.getAttr('node') - if not node: - node = '' - qp = iq_obj.getQueryPayload() - items = [] - if not qp: - qp = [] - for i in qp: - # CDATA payload is not processed, only nodes - if not isinstance(i, common.xmpp.simplexml.Node): - continue - attr = {} - for key in i.getAttrs(): - attr[key] = i.getAttrs()[key] - if 'jid' not in attr: - continue - try: - attr['jid'] = helpers.parse_jid(attr['jid']) - except common.helpers.InvalidFormat: - # jid is not conform - continue - items.append(attr) - jid = helpers.get_full_jid_from_iq(iq_obj) - hostname = gajim.config.get_per('accounts', self.name, - 'hostname') - id_ = iq_obj.getID() - if jid == hostname and id_[:6] == 'Gajim_': - for item in items: - self.discoverInfo(item['jid'], id_prefix='Gajim_') - else: - self.dispatch('AGENT_INFO_ITEMS', (jid, node, items)) + def _DiscoverItemsCB(self, con, iq_obj): + log.debug('DiscoverItemsCB') + q = iq_obj.getTag('query') + node = q.getAttr('node') + if not node: + node = '' + qp = iq_obj.getQueryPayload() + items = [] + if not qp: + qp = [] + for i in qp: + # CDATA payload is not processed, only nodes + if not isinstance(i, common.xmpp.simplexml.Node): + continue + attr = {} + for key in i.getAttrs(): + attr[key] = i.getAttrs()[key] + if 'jid' not in attr: + continue + try: + attr['jid'] = helpers.parse_jid(attr['jid']) + except common.helpers.InvalidFormat: + # jid is not conform + continue + items.append(attr) + jid = helpers.get_full_jid_from_iq(iq_obj) + hostname = gajim.config.get_per('accounts', self.name, + 'hostname') + id_ = iq_obj.getID() + if jid == hostname and id_[:6] == 'Gajim_': + for item in items: + self.discoverInfo(item['jid'], id_prefix='Gajim_') + else: + self.dispatch('AGENT_INFO_ITEMS', (jid, node, items)) - def _DiscoverItemsGetCB(self, con, iq_obj): - log.debug('DiscoverItemsGetCB') + def _DiscoverItemsGetCB(self, con, iq_obj): + log.debug('DiscoverItemsGetCB') - if not self.connection or self.connected < 2: - return + if not self.connection or self.connected < 2: + return - if self.commandItemsQuery(con, iq_obj): - raise common.xmpp.NodeProcessed - node = iq_obj.getTagAttr('query', 'node') - if node is None: - result = iq_obj.buildReply('result') - self.connection.send(result) - raise common.xmpp.NodeProcessed - if node==common.xmpp.NS_COMMANDS: - self.commandListQuery(con, iq_obj) - raise common.xmpp.NodeProcessed + if self.commandItemsQuery(con, iq_obj): + raise common.xmpp.NodeProcessed + node = iq_obj.getTagAttr('query', 'node') + if node is None: + result = iq_obj.buildReply('result') + self.connection.send(result) + raise common.xmpp.NodeProcessed + if node==common.xmpp.NS_COMMANDS: + self.commandListQuery(con, iq_obj) + raise common.xmpp.NodeProcessed - def _DiscoverInfoGetCB(self, con, iq_obj): - log.debug('DiscoverInfoGetCB') - if not self.connection or self.connected < 2: - return - q = iq_obj.getTag('query') - node = q.getAttr('node') + def _DiscoverInfoGetCB(self, con, iq_obj): + log.debug('DiscoverInfoGetCB') + if not self.connection or self.connected < 2: + return + q = iq_obj.getTag('query') + node = q.getAttr('node') - if self.commandInfoQuery(con, iq_obj): - raise common.xmpp.NodeProcessed + if self.commandInfoQuery(con, iq_obj): + raise common.xmpp.NodeProcessed - id_ = unicode(iq_obj.getAttr('id')) - if id_[:6] == 'Gajim_': - # We get this request from echo.server - raise common.xmpp.NodeProcessed + id_ = unicode(iq_obj.getAttr('id')) + if id_[:6] == 'Gajim_': + # We get this request from echo.server + raise common.xmpp.NodeProcessed - iq = iq_obj.buildReply('result') - q = iq.getTag('query') - if node: - q.setAttr('node', node) - q.addChild('identity', attrs = gajim.gajim_identity) - client_version = 'http://gajim.org#' + gajim.caps_hash[self.name] + iq = iq_obj.buildReply('result') + q = iq.getTag('query') + if node: + q.setAttr('node', node) + q.addChild('identity', attrs = gajim.gajim_identity) + client_version = 'http://gajim.org#' + gajim.caps_hash[self.name] - if node in (None, client_version): - for f in gajim.gajim_common_features: - q.addChild('feature', attrs = {'var': f}) - for f in gajim.gajim_optional_features[self.name]: - q.addChild('feature', attrs = {'var': f}) + if node in (None, client_version): + for f in gajim.gajim_common_features: + q.addChild('feature', attrs = {'var': f}) + for f in gajim.gajim_optional_features[self.name]: + q.addChild('feature', attrs = {'var': f}) - if q.getChildren(): - self.connection.send(iq) - raise common.xmpp.NodeProcessed + if q.getChildren(): + self.connection.send(iq) + raise common.xmpp.NodeProcessed - def _DiscoverInfoErrorCB(self, con, iq_obj): - log.debug('DiscoverInfoErrorCB') - jid = helpers.get_full_jid_from_iq(iq_obj) - id_ = iq_obj.getID() - if id_[:6] == 'Gajim_': - if not self.privacy_rules_requested: - self.privacy_rules_requested = True - self._request_privacy() - self.dispatch('AGENT_ERROR_INFO', (jid)) + def _DiscoverInfoErrorCB(self, con, iq_obj): + log.debug('DiscoverInfoErrorCB') + jid = helpers.get_full_jid_from_iq(iq_obj) + id_ = iq_obj.getID() + if id_[:6] == 'Gajim_': + if not self.privacy_rules_requested: + self.privacy_rules_requested = True + self._request_privacy() + self.dispatch('AGENT_ERROR_INFO', (jid)) - def _DiscoverInfoCB(self, con, iq_obj): - log.debug('DiscoverInfoCB') - if not self.connection or self.connected < 2: - return - # According to XEP-0030: - # For identity: category, type is mandatory, name is optional. - # For feature: var is mandatory - identities, features, data = [], [], [] - q = iq_obj.getTag('query') - node = q.getAttr('node') - if not node: - node = '' - qc = iq_obj.getQueryChildren() - if not qc: - qc = [] - is_muc = False - transport_type = '' - for i in qc: - if i.getName() == 'identity': - attr = {} - for key in i.getAttrs().keys(): - attr[key] = i.getAttr(key) - if 'category' in attr and \ - attr['category'] in ('gateway', 'headline') and \ - 'type' in attr: - transport_type = attr['type'] - if 'category' in attr and \ - attr['category'] == 'conference' and \ - 'type' in attr and attr['type'] == 'text': - is_muc = True - identities.append(attr) - elif i.getName() == 'feature': - var = i.getAttr('var') - if var: - features.append(var) - elif i.getName() == 'x' and i.getNamespace() == common.xmpp.NS_DATA: - data.append(common.xmpp.DataForm(node=i)) - jid = helpers.get_full_jid_from_iq(iq_obj) - if transport_type and jid not in gajim.transport_type: - gajim.transport_type[jid] = transport_type - gajim.logger.save_transport_type(jid, transport_type) - id_ = iq_obj.getID() - if id_ is None: - log.warn('Invalid IQ received without an ID. Ignoring it: %s' % iq_obj) - return - if not identities: # ejabberd doesn't send identities when we browse online users - #FIXME: see http://www.jabber.ru/bugzilla/show_bug.cgi?id=225 - identities = [{'category': 'server', 'type': 'im', 'name': node}] - if id_[:6] == 'Gajim_': - if jid == gajim.config.get_per('accounts', self.name, 'hostname'): - if features.__contains__(common.xmpp.NS_GMAILNOTIFY): - gajim.gmail_domains.append(jid) - self.request_gmail_notifications() - for identity in identities: - if identity['category'] == 'pubsub' and identity.get('type') == \ - 'pep': - self.pep_supported = True - break - if features.__contains__(common.xmpp.NS_VCARD): - self.vcard_supported = True - if features.__contains__(common.xmpp.NS_PUBSUB): - self.pubsub_supported = True - if features.__contains__(common.xmpp.NS_PUBSUB_PUBLISH_OPTIONS): - self.pubsub_publish_options_supported = True - if features.__contains__(common.xmpp.NS_ARCHIVE_AUTO): - self.archive_auto_supported = True - if features.__contains__(common.xmpp.NS_ARCHIVE_MANAGE): - self.archive_manage_supported = True - if features.__contains__(common.xmpp.NS_ARCHIVE_MANUAL): - self.archive_manual_supported = True - if features.__contains__(common.xmpp.NS_ARCHIVE_PREF): - self.archive_pref_supported = True - if features.__contains__(common.xmpp.NS_BYTESTREAM): - our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name) +\ - '/' + self.server_resource) - gajim.proxy65_manager.resolve(jid, self.connection, our_jid, - self.name) - if features.__contains__(common.xmpp.NS_MUC) and is_muc: - type_ = transport_type or 'jabber' - self.muc_jid[type_] = jid - if transport_type: - if transport_type in self.available_transports: - self.available_transports[transport_type].append(jid) - else: - self.available_transports[transport_type] = [jid] - if not self.privacy_rules_requested: - self.privacy_rules_requested = True - self._request_privacy() + def _DiscoverInfoCB(self, con, iq_obj): + log.debug('DiscoverInfoCB') + if not self.connection or self.connected < 2: + return + # According to XEP-0030: + # For identity: category, type is mandatory, name is optional. + # For feature: var is mandatory + identities, features, data = [], [], [] + q = iq_obj.getTag('query') + node = q.getAttr('node') + if not node: + node = '' + qc = iq_obj.getQueryChildren() + if not qc: + qc = [] + is_muc = False + transport_type = '' + for i in qc: + if i.getName() == 'identity': + attr = {} + for key in i.getAttrs().keys(): + attr[key] = i.getAttr(key) + if 'category' in attr and \ + attr['category'] in ('gateway', 'headline') and \ + 'type' in attr: + transport_type = attr['type'] + if 'category' in attr and \ + attr['category'] == 'conference' and \ + 'type' in attr and attr['type'] == 'text': + is_muc = True + identities.append(attr) + elif i.getName() == 'feature': + var = i.getAttr('var') + if var: + features.append(var) + elif i.getName() == 'x' and i.getNamespace() == common.xmpp.NS_DATA: + data.append(common.xmpp.DataForm(node=i)) + jid = helpers.get_full_jid_from_iq(iq_obj) + if transport_type and jid not in gajim.transport_type: + gajim.transport_type[jid] = transport_type + gajim.logger.save_transport_type(jid, transport_type) + id_ = iq_obj.getID() + if id_ is None: + log.warn('Invalid IQ received without an ID. Ignoring it: %s' % iq_obj) + return + if not identities: # ejabberd doesn't send identities when we browse online users + #FIXME: see http://www.jabber.ru/bugzilla/show_bug.cgi?id=225 + identities = [{'category': 'server', 'type': 'im', 'name': node}] + if id_[:6] == 'Gajim_': + if jid == gajim.config.get_per('accounts', self.name, 'hostname'): + if features.__contains__(common.xmpp.NS_GMAILNOTIFY): + gajim.gmail_domains.append(jid) + self.request_gmail_notifications() + for identity in identities: + if identity['category'] == 'pubsub' and identity.get('type') == \ + 'pep': + self.pep_supported = True + break + if features.__contains__(common.xmpp.NS_VCARD): + self.vcard_supported = True + if features.__contains__(common.xmpp.NS_PUBSUB): + self.pubsub_supported = True + if features.__contains__(common.xmpp.NS_PUBSUB_PUBLISH_OPTIONS): + self.pubsub_publish_options_supported = True + if features.__contains__(common.xmpp.NS_ARCHIVE_AUTO): + self.archive_auto_supported = True + if features.__contains__(common.xmpp.NS_ARCHIVE_MANAGE): + self.archive_manage_supported = True + if features.__contains__(common.xmpp.NS_ARCHIVE_MANUAL): + self.archive_manual_supported = True + if features.__contains__(common.xmpp.NS_ARCHIVE_PREF): + self.archive_pref_supported = True + if features.__contains__(common.xmpp.NS_BYTESTREAM): + our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name) +\ + '/' + self.server_resource) + gajim.proxy65_manager.resolve(jid, self.connection, our_jid, + self.name) + if features.__contains__(common.xmpp.NS_MUC) and is_muc: + type_ = transport_type or 'jabber' + self.muc_jid[type_] = jid + if transport_type: + if transport_type in self.available_transports: + self.available_transports[transport_type].append(jid) + else: + self.available_transports[transport_type] = [jid] + if not self.privacy_rules_requested: + self.privacy_rules_requested = True + self._request_privacy() - self.dispatch('AGENT_INFO_INFO', (jid, node, identities, - features, data)) - self._capsDiscoCB(jid, node, identities, features, data) + self.dispatch('AGENT_INFO_INFO', (jid, node, identities, + features, data)) + self._capsDiscoCB(jid, node, identities, features, data) class ConnectionVcard: - def __init__(self): - self.vcard_sha = None - self.vcard_shas = {} # sha of contacts - self.room_jids = [] # list of gc jids so that vcard are saved in a folder + def __init__(self): + self.vcard_sha = None + self.vcard_shas = {} # sha of contacts + self.room_jids = [] # list of gc jids so that vcard are saved in a folder - def add_sha(self, p, send_caps = True): - c = p.setTag('x', namespace = common.xmpp.NS_VCARD_UPDATE) - if self.vcard_sha is not None: - c.setTagData('photo', self.vcard_sha) - if send_caps: - return self.add_caps(p) - return p + def add_sha(self, p, send_caps = True): + c = p.setTag('x', namespace = common.xmpp.NS_VCARD_UPDATE) + if self.vcard_sha is not None: + c.setTagData('photo', self.vcard_sha) + if send_caps: + return self.add_caps(p) + return p - def add_caps(self, p): - ''' advertise our capabilities in presence stanza (xep-0115)''' - c = p.setTag('c', namespace = common.xmpp.NS_CAPS) - c.setAttr('hash', 'sha-1') - c.setAttr('node', 'http://gajim.org') - c.setAttr('ver', gajim.caps_hash[self.name]) - return p + def add_caps(self, p): + ''' advertise our capabilities in presence stanza (xep-0115)''' + c = p.setTag('c', namespace = common.xmpp.NS_CAPS) + c.setAttr('hash', 'sha-1') + c.setAttr('node', 'http://gajim.org') + c.setAttr('ver', gajim.caps_hash[self.name]) + return p - def node_to_dict(self, node): - dict_ = {} - for info in node.getChildren(): - name = info.getName() - if name in ('ADR', 'TEL', 'EMAIL'): # we can have several - dict_.setdefault(name, []) - entry = {} - for c in info.getChildren(): - entry[c.getName()] = c.getData() - dict_[name].append(entry) - elif info.getChildren() == []: - dict_[name] = info.getData() - else: - dict_[name] = {} - for c in info.getChildren(): - dict_[name][c.getName()] = c.getData() - return dict_ + def node_to_dict(self, node): + dict_ = {} + for info in node.getChildren(): + name = info.getName() + if name in ('ADR', 'TEL', 'EMAIL'): # we can have several + dict_.setdefault(name, []) + entry = {} + for c in info.getChildren(): + entry[c.getName()] = c.getData() + dict_[name].append(entry) + elif info.getChildren() == []: + dict_[name] = info.getData() + else: + dict_[name] = {} + for c in info.getChildren(): + dict_[name][c.getName()] = c.getData() + return dict_ - def save_vcard_to_hd(self, full_jid, card): - jid, nick = gajim.get_room_and_nick_from_fjid(full_jid) - puny_jid = helpers.sanitize_filename(jid) - path = os.path.join(gajim.VCARD_PATH, puny_jid) - if jid in self.room_jids or os.path.isdir(path): - if not nick: - return - # remove room_jid file if needed - if os.path.isfile(path): - os.remove(path) - # create folder if needed - if not os.path.isdir(path): - os.mkdir(path, 0700) - puny_nick = helpers.sanitize_filename(nick) - path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) - else: - path_to_file = path - try: - fil = open(path_to_file, 'w') - fil.write(str(card)) - fil.close() - except IOError, e: - self.dispatch('ERROR', (_('Disk Write Error'), str(e))) + def save_vcard_to_hd(self, full_jid, card): + jid, nick = gajim.get_room_and_nick_from_fjid(full_jid) + puny_jid = helpers.sanitize_filename(jid) + path = os.path.join(gajim.VCARD_PATH, puny_jid) + if jid in self.room_jids or os.path.isdir(path): + if not nick: + return + # remove room_jid file if needed + if os.path.isfile(path): + os.remove(path) + # create folder if needed + if not os.path.isdir(path): + os.mkdir(path, 0700) + puny_nick = helpers.sanitize_filename(nick) + path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) + else: + path_to_file = path + try: + fil = open(path_to_file, 'w') + fil.write(str(card)) + fil.close() + except IOError, e: + self.dispatch('ERROR', (_('Disk Write Error'), str(e))) - def get_cached_vcard(self, fjid, is_fake_jid = False): - '''return the vcard as a dict - return {} if vcard was too old - return None if we don't have cached vcard''' - jid, nick = gajim.get_room_and_nick_from_fjid(fjid) - puny_jid = helpers.sanitize_filename(jid) - if is_fake_jid: - puny_nick = helpers.sanitize_filename(nick) - path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) - else: - path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid) - if not os.path.isfile(path_to_file): - return None - # We have the vcard cached - f = open(path_to_file) - c = f.read() - f.close() - try: - card = common.xmpp.Node(node = c) - except Exception: - # We are unable to parse it. Remove it - os.remove(path_to_file) - return None - vcard = self.node_to_dict(card) - if 'PHOTO' in vcard: - if not isinstance(vcard['PHOTO'], dict): - del vcard['PHOTO'] - elif 'SHA' in vcard['PHOTO']: - cached_sha = vcard['PHOTO']['SHA'] - if jid in self.vcard_shas and self.vcard_shas[jid] != \ - cached_sha: - # user change his vcard so don't use the cached one - return {} - vcard['jid'] = jid - vcard['resource'] = gajim.get_resource_from_jid(fjid) - return vcard + def get_cached_vcard(self, fjid, is_fake_jid = False): + '''return the vcard as a dict + return {} if vcard was too old + return None if we don't have cached vcard''' + jid, nick = gajim.get_room_and_nick_from_fjid(fjid) + puny_jid = helpers.sanitize_filename(jid) + if is_fake_jid: + puny_nick = helpers.sanitize_filename(nick) + path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) + else: + path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid) + if not os.path.isfile(path_to_file): + return None + # We have the vcard cached + f = open(path_to_file) + c = f.read() + f.close() + try: + card = common.xmpp.Node(node = c) + except Exception: + # We are unable to parse it. Remove it + os.remove(path_to_file) + return None + vcard = self.node_to_dict(card) + if 'PHOTO' in vcard: + if not isinstance(vcard['PHOTO'], dict): + del vcard['PHOTO'] + elif 'SHA' in vcard['PHOTO']: + cached_sha = vcard['PHOTO']['SHA'] + if jid in self.vcard_shas and self.vcard_shas[jid] != \ + cached_sha: + # user change his vcard so don't use the cached one + return {} + vcard['jid'] = jid + vcard['resource'] = gajim.get_resource_from_jid(fjid) + return vcard - def request_vcard(self, jid = None, groupchat_jid = None): - '''request the VCARD. If groupchat_jid is not nul, it means we request a vcard - to a fake jid, like in private messages in groupchat. jid can be the - real jid of the contact, but we want to consider it comes from a fake jid''' - if not self.connection or self.connected < 2: - return - iq = common.xmpp.Iq(typ = 'get') - if jid: - iq.setTo(jid) - iq.setTag(common.xmpp.NS_VCARD + ' vCard') + def request_vcard(self, jid = None, groupchat_jid = None): + '''request the VCARD. If groupchat_jid is not nul, it means we request a vcard + to a fake jid, like in private messages in groupchat. jid can be the + real jid of the contact, but we want to consider it comes from a fake jid''' + if not self.connection or self.connected < 2: + return + iq = common.xmpp.Iq(typ = 'get') + if jid: + iq.setTo(jid) + iq.setTag(common.xmpp.NS_VCARD + ' vCard') - id_ = self.connection.getAnID() - iq.setID(id_) - j = jid - if not j: - j = gajim.get_jid_from_account(self.name) - self.awaiting_answers[id_] = (VCARD_ARRIVED, j, groupchat_jid) - if groupchat_jid: - room_jid = gajim.get_room_and_nick_from_fjid(groupchat_jid)[0] - if not room_jid in self.room_jids: - self.room_jids.append(room_jid) - self.groupchat_jids[id_] = groupchat_jid - self.connection.send(iq) + id_ = self.connection.getAnID() + iq.setID(id_) + j = jid + if not j: + j = gajim.get_jid_from_account(self.name) + self.awaiting_answers[id_] = (VCARD_ARRIVED, j, groupchat_jid) + if groupchat_jid: + room_jid = gajim.get_room_and_nick_from_fjid(groupchat_jid)[0] + if not room_jid in self.room_jids: + self.room_jids.append(room_jid) + self.groupchat_jids[id_] = groupchat_jid + self.connection.send(iq) - def send_vcard(self, vcard): - if not self.connection or self.connected < 2: - return - iq = common.xmpp.Iq(typ = 'set') - iq2 = iq.setTag(common.xmpp.NS_VCARD + ' vCard') - for i in vcard: - if i == 'jid': - continue - if isinstance(vcard[i], dict): - iq3 = iq2.addChild(i) - for j in vcard[i]: - iq3.addChild(j).setData(vcard[i][j]) - elif isinstance(vcard[i], list): - for j in vcard[i]: - iq3 = iq2.addChild(i) - for k in j: - iq3.addChild(k).setData(j[k]) - else: - iq2.addChild(i).setData(vcard[i]) + def send_vcard(self, vcard): + if not self.connection or self.connected < 2: + return + iq = common.xmpp.Iq(typ = 'set') + iq2 = iq.setTag(common.xmpp.NS_VCARD + ' vCard') + for i in vcard: + if i == 'jid': + continue + if isinstance(vcard[i], dict): + iq3 = iq2.addChild(i) + for j in vcard[i]: + iq3.addChild(j).setData(vcard[i][j]) + elif isinstance(vcard[i], list): + for j in vcard[i]: + iq3 = iq2.addChild(i) + for k in j: + iq3.addChild(k).setData(j[k]) + else: + iq2.addChild(i).setData(vcard[i]) - id_ = self.connection.getAnID() - iq.setID(id_) - self.connection.send(iq) + id_ = self.connection.getAnID() + iq.setID(id_) + self.connection.send(iq) - our_jid = gajim.get_jid_from_account(self.name) - # Add the sha of the avatar - if 'PHOTO' in vcard and isinstance(vcard['PHOTO'], dict) and \ - 'BINVAL' in vcard['PHOTO']: - photo = vcard['PHOTO']['BINVAL'] - photo_decoded = base64.decodestring(photo) - gajim.interface.save_avatar_files(our_jid, photo_decoded) - avatar_sha = hashlib.sha1(photo_decoded).hexdigest() - iq2.getTag('PHOTO').setTagData('SHA', avatar_sha) - else: - gajim.interface.remove_avatar_files(our_jid) + our_jid = gajim.get_jid_from_account(self.name) + # Add the sha of the avatar + if 'PHOTO' in vcard and isinstance(vcard['PHOTO'], dict) and \ + 'BINVAL' in vcard['PHOTO']: + photo = vcard['PHOTO']['BINVAL'] + photo_decoded = base64.decodestring(photo) + gajim.interface.save_avatar_files(our_jid, photo_decoded) + avatar_sha = hashlib.sha1(photo_decoded).hexdigest() + iq2.getTag('PHOTO').setTagData('SHA', avatar_sha) + else: + gajim.interface.remove_avatar_files(our_jid) - self.awaiting_answers[id_] = (VCARD_PUBLISHED, iq2) + self.awaiting_answers[id_] = (VCARD_PUBLISHED, iq2) - def _IqCB(self, con, iq_obj): - id_ = iq_obj.getID() + def _IqCB(self, con, iq_obj): + id_ = iq_obj.getID() - # Check if we were waiting a timeout for this id - found_tim = None - for tim in self.awaiting_timeouts: - if id_ == self.awaiting_timeouts[tim][0]: - found_tim = tim - break - if found_tim: - del self.awaiting_timeouts[found_tim] + # Check if we were waiting a timeout for this id + found_tim = None + for tim in self.awaiting_timeouts: + if id_ == self.awaiting_timeouts[tim][0]: + found_tim = tim + break + if found_tim: + del self.awaiting_timeouts[found_tim] - if id_ not in self.awaiting_answers: - return - if self.awaiting_answers[id_][0] == VCARD_PUBLISHED: - if iq_obj.getType() == 'result': - vcard_iq = self.awaiting_answers[id_][1] - # Save vcard to HD - if vcard_iq.getTag('PHOTO') and vcard_iq.getTag('PHOTO').getTag('SHA'): - new_sha = vcard_iq.getTag('PHOTO').getTagData('SHA') - else: - new_sha = '' + if id_ not in self.awaiting_answers: + return + if self.awaiting_answers[id_][0] == VCARD_PUBLISHED: + if iq_obj.getType() == 'result': + vcard_iq = self.awaiting_answers[id_][1] + # Save vcard to HD + if vcard_iq.getTag('PHOTO') and vcard_iq.getTag('PHOTO').getTag('SHA'): + new_sha = vcard_iq.getTag('PHOTO').getTagData('SHA') + else: + new_sha = '' - # Save it to file - our_jid = gajim.get_jid_from_account(self.name) - self.save_vcard_to_hd(our_jid, vcard_iq) + # Save it to file + our_jid = gajim.get_jid_from_account(self.name) + self.save_vcard_to_hd(our_jid, vcard_iq) - # Send new presence if sha changed and we are not invisible - if self.vcard_sha != new_sha and gajim.SHOW_LIST[self.connected] !=\ - 'invisible': - if not self.connection or self.connected < 2: - return - self.vcard_sha = new_sha - sshow = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected]) - p = common.xmpp.Presence(typ = None, priority = self.priority, - show = sshow, status = self.status) - p = self.add_sha(p) - self.connection.send(p) - self.dispatch('VCARD_PUBLISHED', ()) - elif iq_obj.getType() == 'error': - self.dispatch('VCARD_NOT_PUBLISHED', ()) - elif self.awaiting_answers[id_][0] == VCARD_ARRIVED: - # If vcard is empty, we send to the interface an empty vcard so that - # it knows it arrived - jid = self.awaiting_answers[id_][1] - groupchat_jid = self.awaiting_answers[id_][2] - frm = jid - if groupchat_jid: - # We do as if it comes from the fake_jid - frm = groupchat_jid - our_jid = gajim.get_jid_from_account(self.name) - if not iq_obj.getTag('vCard') or iq_obj.getType() == 'error': - if frm and frm != our_jid: - # Write an empty file - self.save_vcard_to_hd(frm, '') - jid, resource = gajim.get_room_and_nick_from_fjid(frm) - self.dispatch('VCARD', {'jid': jid, 'resource': resource}) - elif frm == our_jid: - self.dispatch('MYVCARD', {'jid': frm}) - elif self.awaiting_answers[id_][0] == AGENT_REMOVED: - jid = self.awaiting_answers[id_][1] - self.dispatch('AGENT_REMOVED', jid) - elif self.awaiting_answers[id_][0] == METACONTACTS_ARRIVED: - if not self.connection: - return - if iq_obj.getType() == 'result': - # Metacontact tags - # http://www.xmpp.org/extensions/xep-0209.html - meta_list = {} - query = iq_obj.getTag('query') - storage = query.getTag('storage') - metas = storage.getTags('meta') - for meta in metas: - try: - jid = helpers.parse_jid(meta.getAttr('jid')) - except common.helpers.InvalidFormat: - continue - tag = meta.getAttr('tag') - data = {'jid': jid} - order = meta.getAttr('order') - try: - order = int(order) - except Exception: - order = 0 - if order is not None: - data['order'] = order - if tag in meta_list: - meta_list[tag].append(data) - else: - meta_list[tag] = [data] - self.dispatch('METACONTACTS', meta_list) - else: - if iq_obj.getErrorCode() not in ('403', '406', '404'): - self.private_storage_supported = False - # We can now continue connection by requesting the roster - version = gajim.config.get_per('accounts', self.name, - 'roster_version') - iq_id = self.connection.initRoster(version=version) - self.awaiting_answers[iq_id] = (ROSTER_ARRIVED, ) - elif self.awaiting_answers[id_][0] == ROSTER_ARRIVED: - if iq_obj.getType() == 'result': - if not iq_obj.getTag('query'): - account_jid = gajim.get_jid_from_account(self.name) - roster_data = gajim.logger.get_roster(account_jid) - roster = self.connection.getRoster(force=True) - roster.setRaw(roster_data) - self._getRoster() - elif self.awaiting_answers[id_][0] == PRIVACY_ARRIVED: - if iq_obj.getType() != 'error': - self.privacy_rules_supported = True - self.get_privacy_list('block') - elif self.continue_connect_info: - if self.continue_connect_info[0] == 'invisible': - # Trying to login as invisible but privacy list not supported - self.disconnect(on_purpose=True) - self.dispatch('STATUS', 'offline') - self.dispatch('ERROR', (_('Invisibility not supported'), - _('Account %s doesn\'t support invisibility.') % self.name)) - return - # Ask metacontacts before roster - self.get_metacontacts() - elif self.awaiting_answers[id_][0] == PEP_CONFIG: - conf = iq_obj.getTag('pubsub').getTag('configure') - node = conf.getAttr('node') - form_tag = conf.getTag('x', namespace=common.xmpp.NS_DATA) - if form_tag: - form = common.dataforms.ExtendForm(node=form_tag) - self.dispatch('PEP_CONFIG', (node, form)) + # Send new presence if sha changed and we are not invisible + if self.vcard_sha != new_sha and gajim.SHOW_LIST[self.connected] !=\ + 'invisible': + if not self.connection or self.connected < 2: + return + self.vcard_sha = new_sha + sshow = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected]) + p = common.xmpp.Presence(typ = None, priority = self.priority, + show = sshow, status = self.status) + p = self.add_sha(p) + self.connection.send(p) + self.dispatch('VCARD_PUBLISHED', ()) + elif iq_obj.getType() == 'error': + self.dispatch('VCARD_NOT_PUBLISHED', ()) + elif self.awaiting_answers[id_][0] == VCARD_ARRIVED: + # If vcard is empty, we send to the interface an empty vcard so that + # it knows it arrived + jid = self.awaiting_answers[id_][1] + groupchat_jid = self.awaiting_answers[id_][2] + frm = jid + if groupchat_jid: + # We do as if it comes from the fake_jid + frm = groupchat_jid + our_jid = gajim.get_jid_from_account(self.name) + if not iq_obj.getTag('vCard') or iq_obj.getType() == 'error': + if frm and frm != our_jid: + # Write an empty file + self.save_vcard_to_hd(frm, '') + jid, resource = gajim.get_room_and_nick_from_fjid(frm) + self.dispatch('VCARD', {'jid': jid, 'resource': resource}) + elif frm == our_jid: + self.dispatch('MYVCARD', {'jid': frm}) + elif self.awaiting_answers[id_][0] == AGENT_REMOVED: + jid = self.awaiting_answers[id_][1] + self.dispatch('AGENT_REMOVED', jid) + elif self.awaiting_answers[id_][0] == METACONTACTS_ARRIVED: + if not self.connection: + return + if iq_obj.getType() == 'result': + # Metacontact tags + # http://www.xmpp.org/extensions/xep-0209.html + meta_list = {} + query = iq_obj.getTag('query') + storage = query.getTag('storage') + metas = storage.getTags('meta') + for meta in metas: + try: + jid = helpers.parse_jid(meta.getAttr('jid')) + except common.helpers.InvalidFormat: + continue + tag = meta.getAttr('tag') + data = {'jid': jid} + order = meta.getAttr('order') + try: + order = int(order) + except Exception: + order = 0 + if order is not None: + data['order'] = order + if tag in meta_list: + meta_list[tag].append(data) + else: + meta_list[tag] = [data] + self.dispatch('METACONTACTS', meta_list) + else: + if iq_obj.getErrorCode() not in ('403', '406', '404'): + self.private_storage_supported = False + # We can now continue connection by requesting the roster + version = gajim.config.get_per('accounts', self.name, + 'roster_version') + iq_id = self.connection.initRoster(version=version) + self.awaiting_answers[iq_id] = (ROSTER_ARRIVED, ) + elif self.awaiting_answers[id_][0] == ROSTER_ARRIVED: + if iq_obj.getType() == 'result': + if not iq_obj.getTag('query'): + account_jid = gajim.get_jid_from_account(self.name) + roster_data = gajim.logger.get_roster(account_jid) + roster = self.connection.getRoster(force=True) + roster.setRaw(roster_data) + self._getRoster() + elif self.awaiting_answers[id_][0] == PRIVACY_ARRIVED: + if iq_obj.getType() != 'error': + self.privacy_rules_supported = True + self.get_privacy_list('block') + elif self.continue_connect_info: + if self.continue_connect_info[0] == 'invisible': + # Trying to login as invisible but privacy list not supported + self.disconnect(on_purpose=True) + self.dispatch('STATUS', 'offline') + self.dispatch('ERROR', (_('Invisibility not supported'), + _('Account %s doesn\'t support invisibility.') % self.name)) + return + # Ask metacontacts before roster + self.get_metacontacts() + elif self.awaiting_answers[id_][0] == PEP_CONFIG: + conf = iq_obj.getTag('pubsub').getTag('configure') + node = conf.getAttr('node') + form_tag = conf.getTag('x', namespace=common.xmpp.NS_DATA) + if form_tag: + form = common.dataforms.ExtendForm(node=form_tag) + self.dispatch('PEP_CONFIG', (node, form)) - elif self.awaiting_answers[id_][0] == ARCHIVING_COLLECTIONS_ARRIVED: - # TODO - pass + elif self.awaiting_answers[id_][0] == ARCHIVING_COLLECTIONS_ARRIVED: + # TODO + pass - elif self.awaiting_answers[id_][0] == ARCHIVING_COLLECTION_ARRIVED: - def save_if_not_exists(with_, nick, direction, tim, payload): - assert len(payload) == 1, 'got several archiving messages in the' +\ - ' same time %s' % ''.join(payload) - if payload[0].getName() == 'body': - gajim.logger.save_if_not_exists(with_, direction, tim, - msg=payload[0].getData(), nick=nick) - elif payload[0].getName() == 'message': - print 'Not implemented' - chat = iq_obj.getTag('chat') - if chat: - with_ = chat.getAttr('with') - start_ = chat.getAttr('start') - tim = helpers.datetime_tuple(start_) - tim = timegm(tim) - nb = 0 - for element in chat.getChildren(): - try: - secs = int(element.getAttr('secs')) - except TypeError: - secs = 0 - if secs: - tim += secs - nick = element.getAttr('name') - if element.getName() == 'from': - save_if_not_exists(with_, nick, 'from', localtime(tim), - element.getPayload()) - nb += 1 - if element.getName() == 'to': - save_if_not_exists(with_, nick, 'to', localtime(tim), - element.getPayload()) - nb += 1 - set_ = chat.getTag('set') - first = set_.getTag('first') - if first: - try: - index = int(first.getAttr('index')) - except TypeError: - index = 0 - try: - count = int(set_.getTagData('count')) - except TypeError: - count = 0 - if count > index + nb: - # Request the next page - after = element.getTagData('last') - self.request_collection_page(with_, start_, after=after) + elif self.awaiting_answers[id_][0] == ARCHIVING_COLLECTION_ARRIVED: + def save_if_not_exists(with_, nick, direction, tim, payload): + assert len(payload) == 1, 'got several archiving messages in the' +\ + ' same time %s' % ''.join(payload) + if payload[0].getName() == 'body': + gajim.logger.save_if_not_exists(with_, direction, tim, + msg=payload[0].getData(), nick=nick) + elif payload[0].getName() == 'message': + print 'Not implemented' + chat = iq_obj.getTag('chat') + if chat: + with_ = chat.getAttr('with') + start_ = chat.getAttr('start') + tim = helpers.datetime_tuple(start_) + tim = timegm(tim) + nb = 0 + for element in chat.getChildren(): + try: + secs = int(element.getAttr('secs')) + except TypeError: + secs = 0 + if secs: + tim += secs + nick = element.getAttr('name') + if element.getName() == 'from': + save_if_not_exists(with_, nick, 'from', localtime(tim), + element.getPayload()) + nb += 1 + if element.getName() == 'to': + save_if_not_exists(with_, nick, 'to', localtime(tim), + element.getPayload()) + nb += 1 + set_ = chat.getTag('set') + first = set_.getTag('first') + if first: + try: + index = int(first.getAttr('index')) + except TypeError: + index = 0 + try: + count = int(set_.getTagData('count')) + except TypeError: + count = 0 + if count > index + nb: + # Request the next page + after = element.getTagData('last') + self.request_collection_page(with_, start_, after=after) - elif self.awaiting_answers[id_][0] == ARCHIVING_MODIFICATIONS_ARRIVED: - modified = iq_obj.getTag('modified') - if modified: - for element in modified.getChildren(): - if element.getName() == 'changed': - with_ = element.getAttr('with') - start_ = element.getAttr('start') - self.request_collection_page(with_, start_) - elif element.getName() == 'removed': - # do nothing - pass + elif self.awaiting_answers[id_][0] == ARCHIVING_MODIFICATIONS_ARRIVED: + modified = iq_obj.getTag('modified') + if modified: + for element in modified.getChildren(): + if element.getName() == 'changed': + with_ = element.getAttr('with') + start_ = element.getAttr('start') + self.request_collection_page(with_, start_) + elif element.getName() == 'removed': + # do nothing + pass - del self.awaiting_answers[id_] + del self.awaiting_answers[id_] - def _vCardCB(self, con, vc): - '''Called when we receive a vCard - Parse the vCard and send it to plugins''' - if not vc.getTag('vCard'): - return - if not vc.getTag('vCard').getNamespace() == common.xmpp.NS_VCARD: - return - id_ = vc.getID() - frm_iq = vc.getFrom() - our_jid = gajim.get_jid_from_account(self.name) - resource = '' - if id_ in self.groupchat_jids: - who = self.groupchat_jids[id_] - frm, resource = gajim.get_room_and_nick_from_fjid(who) - del self.groupchat_jids[id_] - elif frm_iq: - who = helpers.get_full_jid_from_iq(vc) - frm, resource = gajim.get_room_and_nick_from_fjid(who) - else: - who = frm = our_jid - card = vc.getChildren()[0] - vcard = self.node_to_dict(card) - photo_decoded = None - if 'PHOTO' in vcard and isinstance(vcard['PHOTO'], dict) and \ - 'BINVAL' in vcard['PHOTO']: - photo = vcard['PHOTO']['BINVAL'] - try: - photo_decoded = base64.decodestring(photo) - avatar_sha = hashlib.sha1(photo_decoded).hexdigest() - except Exception: - avatar_sha = '' - else: - avatar_sha = '' + def _vCardCB(self, con, vc): + '''Called when we receive a vCard + Parse the vCard and send it to plugins''' + if not vc.getTag('vCard'): + return + if not vc.getTag('vCard').getNamespace() == common.xmpp.NS_VCARD: + return + id_ = vc.getID() + frm_iq = vc.getFrom() + our_jid = gajim.get_jid_from_account(self.name) + resource = '' + if id_ in self.groupchat_jids: + who = self.groupchat_jids[id_] + frm, resource = gajim.get_room_and_nick_from_fjid(who) + del self.groupchat_jids[id_] + elif frm_iq: + who = helpers.get_full_jid_from_iq(vc) + frm, resource = gajim.get_room_and_nick_from_fjid(who) + else: + who = frm = our_jid + card = vc.getChildren()[0] + vcard = self.node_to_dict(card) + photo_decoded = None + if 'PHOTO' in vcard and isinstance(vcard['PHOTO'], dict) and \ + 'BINVAL' in vcard['PHOTO']: + photo = vcard['PHOTO']['BINVAL'] + try: + photo_decoded = base64.decodestring(photo) + avatar_sha = hashlib.sha1(photo_decoded).hexdigest() + except Exception: + avatar_sha = '' + else: + avatar_sha = '' - if avatar_sha: - card.getTag('PHOTO').setTagData('SHA', avatar_sha) + if avatar_sha: + card.getTag('PHOTO').setTagData('SHA', avatar_sha) - # Save it to file - self.save_vcard_to_hd(who, card) - # Save the decoded avatar to a separate file too, and generate files for dbus notifications - puny_jid = helpers.sanitize_filename(frm) - puny_nick = None - begin_path = os.path.join(gajim.AVATAR_PATH, puny_jid) - frm_jid = frm - if frm in self.room_jids: - puny_nick = helpers.sanitize_filename(resource) - # create folder if needed - if not os.path.isdir(begin_path): - os.mkdir(begin_path, 0700) - begin_path = os.path.join(begin_path, puny_nick) - frm_jid += '/' + resource - if photo_decoded: - avatar_file = begin_path + '_notif_size_colored.png' - if frm_jid == our_jid and avatar_sha != self.vcard_sha: - gajim.interface.save_avatar_files(frm, photo_decoded, puny_nick) - elif frm_jid != our_jid and (not os.path.exists(avatar_file) or \ - frm_jid not in self.vcard_shas or \ - avatar_sha != self.vcard_shas[frm_jid]): - gajim.interface.save_avatar_files(frm, photo_decoded, puny_nick) - if avatar_sha: - self.vcard_shas[frm_jid] = avatar_sha - elif frm in self.vcard_shas: - del self.vcard_shas[frm] - else: - for ext in ('.jpeg', '.png', '_notif_size_bw.png', - '_notif_size_colored.png'): - path = begin_path + ext - if os.path.isfile(path): - os.remove(path) + # Save it to file + self.save_vcard_to_hd(who, card) + # Save the decoded avatar to a separate file too, and generate files for dbus notifications + puny_jid = helpers.sanitize_filename(frm) + puny_nick = None + begin_path = os.path.join(gajim.AVATAR_PATH, puny_jid) + frm_jid = frm + if frm in self.room_jids: + puny_nick = helpers.sanitize_filename(resource) + # create folder if needed + if not os.path.isdir(begin_path): + os.mkdir(begin_path, 0700) + begin_path = os.path.join(begin_path, puny_nick) + frm_jid += '/' + resource + if photo_decoded: + avatar_file = begin_path + '_notif_size_colored.png' + if frm_jid == our_jid and avatar_sha != self.vcard_sha: + gajim.interface.save_avatar_files(frm, photo_decoded, puny_nick) + elif frm_jid != our_jid and (not os.path.exists(avatar_file) or \ + frm_jid not in self.vcard_shas or \ + avatar_sha != self.vcard_shas[frm_jid]): + gajim.interface.save_avatar_files(frm, photo_decoded, puny_nick) + if avatar_sha: + self.vcard_shas[frm_jid] = avatar_sha + elif frm in self.vcard_shas: + del self.vcard_shas[frm] + else: + for ext in ('.jpeg', '.png', '_notif_size_bw.png', + '_notif_size_colored.png'): + path = begin_path + ext + if os.path.isfile(path): + os.remove(path) - vcard['jid'] = frm - vcard['resource'] = resource - if frm_jid == our_jid: - self.dispatch('MYVCARD', vcard) - # we re-send our presence with sha if has changed and if we are - # not invisible - if self.vcard_sha == avatar_sha: - return - self.vcard_sha = avatar_sha - if gajim.SHOW_LIST[self.connected] == 'invisible': - return - if not self.connection: - return - sshow = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected]) - p = common.xmpp.Presence(typ = None, priority = self.priority, - show = sshow, status = self.status) - p = self.add_sha(p) - self.connection.send(p) - else: - #('VCARD', {entry1: data, entry2: {entry21: data, ...}, ...}) - self.dispatch('VCARD', vcard) + vcard['jid'] = frm + vcard['resource'] = resource + if frm_jid == our_jid: + self.dispatch('MYVCARD', vcard) + # we re-send our presence with sha if has changed and if we are + # not invisible + if self.vcard_sha == avatar_sha: + return + self.vcard_sha = avatar_sha + if gajim.SHOW_LIST[self.connected] == 'invisible': + return + if not self.connection: + return + sshow = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected]) + p = common.xmpp.Presence(typ = None, priority = self.priority, + show = sshow, status = self.status) + p = self.add_sha(p) + self.connection.send(p) + else: + #('VCARD', {entry1: data, entry2: {entry21: data, ...}, ...}) + self.dispatch('VCARD', vcard) # basic connection handlers used here and in zeroconf class ConnectionHandlersBase: - def __init__(self): - # List of IDs we are waiting answers for {id: (type_of_request, data), } - self.awaiting_answers = {} - # List of IDs that will produce a timeout is answer doesn't arrive - # {time_of_the_timeout: (id, message to send to gui), } - self.awaiting_timeouts = {} - # keep the jids we auto added (transports contacts) to not send the - # SUBSCRIBED event to gui - self.automatically_added = [] + def __init__(self): + # List of IDs we are waiting answers for {id: (type_of_request, data), } + self.awaiting_answers = {} + # List of IDs that will produce a timeout is answer doesn't arrive + # {time_of_the_timeout: (id, message to send to gui), } + self.awaiting_timeouts = {} + # keep the jids we auto added (transports contacts) to not send the + # SUBSCRIBED event to gui + self.automatically_added = [] - # keep track of sessions this connection has with other JIDs - self.sessions = {} + # keep track of sessions this connection has with other JIDs + self.sessions = {} - def get_sessions(self, jid): - '''get all sessions for the given full jid''' + def get_sessions(self, jid): + '''get all sessions for the given full jid''' - if not gajim.interface.is_pm_contact(jid, self.name): - jid = gajim.get_jid_without_resource(jid) + if not gajim.interface.is_pm_contact(jid, self.name): + jid = gajim.get_jid_without_resource(jid) - try: - return self.sessions[jid].values() - except KeyError: - return [] + try: + return self.sessions[jid].values() + except KeyError: + return [] - def get_or_create_session(self, fjid, thread_id): - '''returns an existing session between this connection and 'jid', returns a - new one if none exist.''' + def get_or_create_session(self, fjid, thread_id): + '''returns an existing session between this connection and 'jid', returns a + new one if none exist.''' - pm = True - jid = fjid + pm = True + jid = fjid - if not gajim.interface.is_pm_contact(fjid, self.name): - pm = False - jid = gajim.get_jid_without_resource(fjid) + if not gajim.interface.is_pm_contact(fjid, self.name): + pm = False + jid = gajim.get_jid_without_resource(fjid) - session = self.find_session(jid, thread_id) + session = self.find_session(jid, thread_id) - if session: - return session + if session: + return session - if pm: - return self.make_new_session(fjid, thread_id, type_='pm') - else: - return self.make_new_session(fjid, thread_id) + if pm: + return self.make_new_session(fjid, thread_id, type_='pm') + else: + return self.make_new_session(fjid, thread_id) - def find_session(self, jid, thread_id): - try: - if not thread_id: - return self.find_null_session(jid) - else: - return self.sessions[jid][thread_id] - except KeyError: - return None + def find_session(self, jid, thread_id): + try: + if not thread_id: + return self.find_null_session(jid) + else: + return self.sessions[jid][thread_id] + except KeyError: + return None - def terminate_sessions(self, send_termination=False): - '''send termination messages and delete all active sessions''' - for jid in self.sessions: - for thread_id in self.sessions[jid]: - self.sessions[jid][thread_id].terminate(send_termination) + def terminate_sessions(self, send_termination=False): + '''send termination messages and delete all active sessions''' + for jid in self.sessions: + for thread_id in self.sessions[jid]: + self.sessions[jid][thread_id].terminate(send_termination) - self.sessions = {} + self.sessions = {} - def delete_session(self, jid, thread_id): - if not jid in self.sessions: - jid = gajim.get_jid_without_resource(jid) - if not jid in self.sessions: - return + def delete_session(self, jid, thread_id): + if not jid in self.sessions: + jid = gajim.get_jid_without_resource(jid) + if not jid in self.sessions: + return - del self.sessions[jid][thread_id] + del self.sessions[jid][thread_id] - if not self.sessions[jid]: - del self.sessions[jid] + if not self.sessions[jid]: + del self.sessions[jid] - def find_null_session(self, jid): - '''finds all of the sessions between us and a remote jid in which we + def find_null_session(self, jid): + '''finds all of the sessions between us and a remote jid in which we haven't received a thread_id yet and returns the session that we last sent a message to.''' - sessions = self.sessions[jid].values() + sessions = self.sessions[jid].values() - # sessions that we haven't received a thread ID in - idless = [s for s in sessions if not s.received_thread_id] + # sessions that we haven't received a thread ID in + idless = [s for s in sessions if not s.received_thread_id] - # filter out everything except the default session type - chat_sessions = [s for s in idless if isinstance(s, - gajim.default_session_type)] + # filter out everything except the default session type + chat_sessions = [s for s in idless if isinstance(s, + gajim.default_session_type)] - if chat_sessions: - # return the session that we last sent a message in - return sorted(chat_sessions, key=operator.attrgetter("last_send"))[-1] - else: - return None + if chat_sessions: + # return the session that we last sent a message in + return sorted(chat_sessions, key=operator.attrgetter("last_send"))[-1] + else: + return None - def find_controlless_session(self, jid, resource=None): - '''find an active session that doesn't have a control attached''' + def find_controlless_session(self, jid, resource=None): + '''find an active session that doesn't have a control attached''' - try: - sessions = self.sessions[jid].values() + try: + sessions = self.sessions[jid].values() - # filter out everything except the default session type - chat_sessions = [s for s in sessions if isinstance(s, - gajim.default_session_type)] + # filter out everything except the default session type + chat_sessions = [s for s in sessions if isinstance(s, + gajim.default_session_type)] - orphaned = [s for s in chat_sessions if not s.control] + orphaned = [s for s in chat_sessions if not s.control] - if resource: - orphaned = [s for s in orphaned if s.resource == resource] + if resource: + orphaned = [s for s in orphaned if s.resource == resource] - return orphaned[0] - except (KeyError, IndexError): - return None + return orphaned[0] + except (KeyError, IndexError): + return None - def make_new_session(self, jid, thread_id=None, type_='chat', cls=None): - '''create and register a new session. thread_id=None to generate one. - type_ should be 'chat' or 'pm'.''' - if not cls: - cls = gajim.default_session_type + def make_new_session(self, jid, thread_id=None, type_='chat', cls=None): + '''create and register a new session. thread_id=None to generate one. + type_ should be 'chat' or 'pm'.''' + if not cls: + cls = gajim.default_session_type - sess = cls(self, common.xmpp.JID(jid), thread_id, type_) + sess = cls(self, common.xmpp.JID(jid), thread_id, type_) - # determine if this session is a pm session - # if not, discard the resource so that all sessions are stored bare - if not type_ == 'pm': - jid = gajim.get_jid_without_resource(jid) + # determine if this session is a pm session + # if not, discard the resource so that all sessions are stored bare + if not type_ == 'pm': + jid = gajim.get_jid_without_resource(jid) - if not jid in self.sessions: - self.sessions[jid] = {} + if not jid in self.sessions: + self.sessions[jid] = {} - self.sessions[jid][sess.thread_id] = sess + self.sessions[jid][sess.thread_id] = sess - return sess + return sess class ConnectionHandlers(ConnectionArchive, ConnectionVcard, ConnectionBytestream, ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionPEP, ConnectionCaps, ConnectionHandlersBase, ConnectionJingle): - def __init__(self): - ConnectionArchive.__init__(self) - ConnectionVcard.__init__(self) - ConnectionBytestream.__init__(self) - ConnectionCommands.__init__(self) - ConnectionPubSub.__init__(self) - ConnectionPEP.__init__(self, account=self.name, dispatcher=self, - pubsub_connection=self) - ConnectionJingle.__init__(self) - ConnectionHandlersBase.__init__(self) - self.gmail_url = None - - # keep the latest subscribed event for each jid to prevent loop when we - # acknowledge presences - self.subscribed_events = {} - # IDs of jabber:iq:last requests - self.last_ids = [] - # IDs of jabber:iq:version requests - self.version_ids = [] - # IDs of urn:xmpp:time requests - self.entity_time_ids = [] - # ID of urn:xmpp:ping requests - self.awaiting_xmpp_ping_id = None - self.continue_connect_info = None - - try: - idle.init() - except Exception: - global HAS_IDLE - HAS_IDLE = False - - self.gmail_last_tid = None - self.gmail_last_time = None - - def build_http_auth_answer(self, iq_obj, answer): - if not self.connection or self.connected < 2: - return - if answer == 'yes': - self.connection.send(iq_obj.buildReply('result')) - elif answer == 'no': - err = common.xmpp.Error(iq_obj, - common.xmpp.protocol.ERR_NOT_AUTHORIZED) - self.connection.send(err) - - def _HttpAuthCB(self, con, iq_obj): - log.debug('HttpAuthCB') - opt = gajim.config.get_per('accounts', self.name, 'http_auth') - if opt in ('yes', 'no'): - self.build_http_auth_answer(iq_obj, opt) - else: - id_ = iq_obj.getTagAttr('confirm', 'id') - method = iq_obj.getTagAttr('confirm', 'method') - url = iq_obj.getTagAttr('confirm', 'url') - msg = iq_obj.getTagData('body') # In case it's a message with a body - self.dispatch('HTTP_AUTH', (method, url, id_, iq_obj, msg)) - raise common.xmpp.NodeProcessed - - def _ErrorCB(self, con, iq_obj): - log.debug('ErrorCB') - jid_from = helpers.get_full_jid_from_iq(iq_obj) - jid_stripped, resource = gajim.get_room_and_nick_from_fjid(jid_from) - id_ = unicode(iq_obj.getID()) - if id_ in self.version_ids: - self.dispatch('OS_INFO', (jid_stripped, resource, '', '')) - self.version_ids.remove(id_) - return - if id_ in self.last_ids: - self.dispatch('LAST_STATUS_TIME', (jid_stripped, resource, -1, '')) - self.last_ids.remove(id_) - return - if id_ in self.entity_time_ids: - self.dispatch('ENTITY_TIME', (jid_stripped, resource, '')) - self.entity_time_ids.remove(id_) - return - if id_ == self.awaiting_xmpp_ping_id: - self.awaiting_xmpp_ping_id = None - errmsg = iq_obj.getErrorMsg() - errcode = iq_obj.getErrorCode() - self.dispatch('ERROR_ANSWER', (id_, jid_from, errmsg, errcode)) - - def _PrivateCB(self, con, iq_obj): - ''' - Private Data (XEP 048 and 049) - ''' - log.debug('PrivateCB') - query = iq_obj.getTag('query') - storage = query.getTag('storage') - if storage: - ns = storage.getNamespace() - if ns == 'storage:bookmarks': - self._parse_bookmarks(storage, 'xml') - elif ns == 'gajim:prefs': - # Preferences data - # http://www.xmpp.org/extensions/xep-0049.html - #TODO: implement this - pass - elif ns == 'storage:rosternotes': - # Annotations - # http://www.xmpp.org/extensions/xep-0145.html - notes = storage.getTags('note') - for note in notes: - try: - jid = helpers.parse_jid(note.getAttr('jid')) - except common.helpers.InvalidFormat: - log.warn('Invalid JID: %s, ignoring it' % note.getAttr('jid')) - continue - annotation = note.getData() - self.annotations[jid] = annotation - - def _parse_bookmarks(self, storage, storage_type): - '''storage_type can be 'pubsub' or 'xml' to tell from where we got - bookmarks''' - # Bookmarked URLs and Conferences - # http://www.xmpp.org/extensions/xep-0048.html - resend_to_pubsub = False - confs = storage.getTags('conference') - for conf in confs: - autojoin_val = conf.getAttr('autojoin') - if autojoin_val is None: # not there (it's optional) - autojoin_val = False - minimize_val = conf.getAttr('minimize') - if minimize_val is None: # not there (it's optional) - minimize_val = False - print_status = conf.getTagData('print_status') - if not print_status: - print_status = conf.getTagData('show_status') - try: - bm = {'name': conf.getAttr('name'), - 'jid': helpers.parse_jid(conf.getAttr('jid')), - 'autojoin': autojoin_val, - 'minimize': minimize_val, - 'password': conf.getTagData('password'), - 'nick': conf.getTagData('nick'), - 'print_status': print_status} - except common.helpers.InvalidFormat: - log.warn('Invalid JID: %s, ignoring it' % conf.getAttr('jid')) - continue - - if bm not in self.bookmarks: - self.bookmarks.append(bm) - if storage_type == 'xml': - # We got a bookmark that was not in pubsub - resend_to_pubsub = True - self.dispatch('BOOKMARKS', self.bookmarks) - if storage_type == 'pubsub': - # We gor bookmarks from pubsub, now get those from xml to merge them - self.get_bookmarks(storage_type='xml') - if self.pubsub_supported and resend_to_pubsub: - self.store_bookmarks('pubsub') - - def _rosterSetCB(self, con, iq_obj): - log.debug('rosterSetCB') - version = iq_obj.getTagAttr('query', 'ver') - for item in iq_obj.getTag('query').getChildren(): - try: - jid = helpers.parse_jid(item.getAttr('jid')) - except common.helpers.InvalidFormat: - log.warn('Invalid JID: %s, ignoring it' % item.getAttr('jid')) - continue - name = item.getAttr('name') - sub = item.getAttr('subscription') - ask = item.getAttr('ask') - groups = [] - for group in item.getTags('group'): - groups.append(group.getData()) - self.dispatch('ROSTER_INFO', (jid, name, sub, ask, groups)) - account_jid = gajim.get_jid_from_account(self.name) - gajim.logger.add_or_update_contact(account_jid, jid, name, sub, ask, - groups) - if version: - gajim.config.set_per('accounts', self.name, 'roster_version', - version) - if not self.connection or self.connected < 2: - raise common.xmpp.NodeProcessed - reply = common.xmpp.Iq(typ='result', attrs={'id': iq_obj.getID()}, - to=iq_obj.getFrom(), frm=iq_obj.getTo(), xmlns=None) - self.connection.send(reply) - raise common.xmpp.NodeProcessed - - def _VersionCB(self, con, iq_obj): - log.debug('VersionCB') - if not self.connection or self.connected < 2: - return - iq_obj = iq_obj.buildReply('result') - qp = iq_obj.getTag('query') - qp.setTagData('name', 'Gajim') - qp.setTagData('version', gajim.version) - send_os = gajim.config.get_per('accounts', self.name, 'send_os_info') - if send_os: - qp.setTagData('os', helpers.get_os_info()) - self.connection.send(iq_obj) - raise common.xmpp.NodeProcessed - - def _LastCB(self, con, iq_obj): - log.debug('LastCB') - if not self.connection or self.connected < 2: - return - iq_obj = iq_obj.buildReply('result') - qp = iq_obj.getTag('query') - if not HAS_IDLE: - qp.attrs['seconds'] = '0' - else: - qp.attrs['seconds'] = idle.getIdleSec() - - self.connection.send(iq_obj) - raise common.xmpp.NodeProcessed - - def _LastResultCB(self, con, iq_obj): - log.debug('LastResultCB') - qp = iq_obj.getTag('query') - seconds = qp.getAttr('seconds') - status = qp.getData() - try: - seconds = int(seconds) - except Exception: - return - id_ = iq_obj.getID() - if id_ in self.groupchat_jids: - who = self.groupchat_jids[id_] - del self.groupchat_jids[id_] - else: - who = helpers.get_full_jid_from_iq(iq_obj) - if id_ in self.last_ids: - self.last_ids.remove(id_) - jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) - self.dispatch('LAST_STATUS_TIME', (jid_stripped, resource, seconds, status)) - - def _VersionResultCB(self, con, iq_obj): - log.debug('VersionResultCB') - client_info = '' - os_info = '' - qp = iq_obj.getTag('query') - if qp.getTag('name'): - client_info += qp.getTag('name').getData() - if qp.getTag('version'): - client_info += ' ' + qp.getTag('version').getData() - if qp.getTag('os'): - os_info += qp.getTag('os').getData() - id_ = iq_obj.getID() - if id_ in self.groupchat_jids: - who = self.groupchat_jids[id_] - del self.groupchat_jids[id_] - else: - who = helpers.get_full_jid_from_iq(iq_obj) - jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) - if id_ in self.version_ids: - self.version_ids.remove(id_) - self.dispatch('OS_INFO', (jid_stripped, resource, client_info, os_info)) - - def _TimeCB(self, con, iq_obj): - log.debug('TimeCB') - if not self.connection or self.connected < 2: - return - iq_obj = iq_obj.buildReply('result') - qp = iq_obj.getTag('query') - qp.setTagData('utc', strftime('%Y%m%dT%H:%M:%S', gmtime())) - qp.setTagData('tz', helpers.decode_string(tzname[daylight])) - qp.setTagData('display', helpers.decode_string(strftime('%c', - localtime()))) - self.connection.send(iq_obj) - raise common.xmpp.NodeProcessed - - def _TimeRevisedCB(self, con, iq_obj): - log.debug('TimeRevisedCB') - if not self.connection or self.connected < 2: - return - iq_obj = iq_obj.buildReply('result') - qp = iq_obj.setTag('time', - namespace=common.xmpp.NS_TIME_REVISED) - qp.setTagData('utc', strftime('%Y-%m-%dT%H:%M:%SZ', gmtime())) - isdst = localtime().tm_isdst - zone = -(timezone, altzone)[isdst] / 60 - tzo = (zone / 60, abs(zone % 60)) - qp.setTagData('tzo', '%+03d:%02d' % (tzo)) - self.connection.send(iq_obj) - raise common.xmpp.NodeProcessed - - def _TimeRevisedResultCB(self, con, iq_obj): - log.debug('TimeRevisedResultCB') - time_info = '' - qp = iq_obj.getTag('time') - if not qp: - # wrong answer - return - tzo = qp.getTag('tzo').getData() - if tzo.lower() == 'z': - tzo = '0:0' - tzoh, tzom = tzo.split(':') - utc_time = qp.getTag('utc').getData() - ZERO = datetime.timedelta(0) - class UTC(datetime.tzinfo): - def utcoffset(self, dt): - return ZERO - def tzname(self, dt): - return "UTC" - def dst(self, dt): - return ZERO - - class contact_tz(datetime.tzinfo): - def utcoffset(self, dt): - return datetime.timedelta(hours=int(tzoh), minutes=int(tzom)) - def tzname(self, dt): - return "remote timezone" - def dst(self, dt): - return ZERO - - try: - t = datetime.datetime.strptime(utc_time, '%Y-%m-%dT%H:%M:%SZ') - t = t.replace(tzinfo=UTC()) - time_info = t.astimezone(contact_tz()).strftime('%c') - except ValueError, e: - log.info('Wrong time format: %s' % str(e)) - - id_ = iq_obj.getID() - if id_ in self.groupchat_jids: - who = self.groupchat_jids[id_] - del self.groupchat_jids[id_] - else: - who = helpers.get_full_jid_from_iq(iq_obj) - jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) - if id_ in self.entity_time_ids: - self.entity_time_ids.remove(id_) - self.dispatch('ENTITY_TIME', (jid_stripped, resource, time_info)) - - def _gMailNewMailCB(self, con, gm): - '''Called when we get notified of new mail messages in gmail account''' - if not self.connection or self.connected < 2: - return - if not gm.getTag('new-mail'): - return - if gm.getTag('new-mail').getNamespace() == common.xmpp.NS_GMAILNOTIFY: - # we'll now ask the server for the exact number of new messages - jid = gajim.get_jid_from_account(self.name) - log.debug('Got notification of new gmail e-mail on %s. Asking the server for more info.' % jid) - iq = common.xmpp.Iq(typ = 'get') - iq.setID(self.connection.getAnID()) - query = iq.setTag('query') - query.setNamespace(common.xmpp.NS_GMAILNOTIFY) - # we want only be notified about newer mails - if self.gmail_last_tid: - query.setAttr('newer-than-tid', self.gmail_last_tid) - if self.gmail_last_time: - query.setAttr('newer-than-time', self.gmail_last_time) - self.connection.send(iq) - raise common.xmpp.NodeProcessed - - def _gMailQueryCB(self, con, gm): - '''Called when we receive results from Querying the server for mail messages in gmail account''' - if not gm.getTag('mailbox'): - return - self.gmail_url = gm.getTag('mailbox').getAttr('url') - if gm.getTag('mailbox').getNamespace() == common.xmpp.NS_GMAILNOTIFY: - newmsgs = gm.getTag('mailbox').getAttr('total-matched') - if newmsgs != '0': - # there are new messages - gmail_messages_list = [] - if gm.getTag('mailbox').getTag('mail-thread-info'): - gmail_messages = gm.getTag('mailbox').getTags('mail-thread-info') - for gmessage in gmail_messages: - unread_senders = [] - for sender in gmessage.getTag('senders').getTags('sender'): - if sender.getAttr('unread') != '1': - continue - if sender.getAttr('name'): - unread_senders.append(sender.getAttr('name') + '< ' + \ - sender.getAttr('address') + '>') - else: - unread_senders.append(sender.getAttr('address')) - - if not unread_senders: - continue - gmail_subject = gmessage.getTag('subject').getData() - gmail_snippet = gmessage.getTag('snippet').getData() - tid = int(gmessage.getAttr('tid')) - if not self.gmail_last_tid or tid > self.gmail_last_tid: - self.gmail_last_tid = tid - gmail_messages_list.append({ \ - 'From': unread_senders, \ - 'Subject': gmail_subject, \ - 'Snippet': gmail_snippet, \ - 'url': gmessage.getAttr('url'), \ - 'participation': gmessage.getAttr('participation'), \ - 'messages': gmessage.getAttr('messages'), \ - 'date': gmessage.getAttr('date')}) - self.gmail_last_time = int(gm.getTag('mailbox').getAttr( - 'result-time')) - - jid = gajim.get_jid_from_account(self.name) - log.debug(('You have %s new gmail e-mails on %s.') % (newmsgs, jid)) - self.dispatch('GMAIL_NOTIFY', (jid, newmsgs, gmail_messages_list)) - raise common.xmpp.NodeProcessed - - def _rosterItemExchangeCB(self, con, msg): - ''' XEP-0144 Roster Item Echange ''' - exchange_items_list = {} - jid_from = helpers.get_full_jid_from_iq(msg) - items_list = msg.getTag('x').getChildren() - if not items_list: - return - action = items_list[0].getAttr('action') - if action == None: - action = 'add' - for item in msg.getTag('x', - namespace=common.xmpp.NS_ROSTERX).getChildren(): - try: - jid = helpers.parse_jid(item.getAttr('jid')) - except common.helpers.InvalidFormat: - log.warn('Invalid JID: %s, ignoring it' % item.getAttr('jid')) - continue - name = item.getAttr('name') - contact = gajim.contacts.get_contact(self.name, jid) - groups = [] - same_groups = True - for group in item.getTags('group'): - groups.append(group.getData()) - # check that all suggested groups are in the groups we have for this - # contact - if not contact or group not in contact.groups: - same_groups = False - if contact: - # check that all groups we have for this contact are in the - # suggested groups - for group in contact.groups: - if group not in groups: - same_groups = False - if contact.sub in ('both', 'to') and same_groups: - continue - exchange_items_list[jid] = [] - exchange_items_list[jid].append(name) - exchange_items_list[jid].append(groups) - if exchange_items_list: - self.dispatch('ROSTERX', (action, exchange_items_list, jid_from)) - raise common.xmpp.NodeProcessed - - def _messageCB(self, con, msg): - '''Called when we receive a message''' - log.debug('MessageCB') - mtype = msg.getType() - - # check if the message is a roster item exchange (XEP-0144) - if msg.getTag('x', namespace=common.xmpp.NS_ROSTERX): - self._rosterItemExchangeCB(con, msg) - return - - # check if the message is a XEP-0070 confirmation request - if msg.getTag('confirm', namespace=common.xmpp.NS_HTTP_AUTH): - self._HttpAuthCB(con, msg) - return - - try: - frm = helpers.get_full_jid_from_iq(msg) - jid = helpers.get_jid_from_iq(msg) - except helpers.InvalidFormat: - self.dispatch('ERROR', (_('Invalid Jabber ID'), - _('A message from a non-valid JID arrived, it has been ignored.'))) - - addressTag = msg.getTag('addresses', namespace = common.xmpp.NS_ADDRESS) - - # Be sure it comes from one of our resource, else ignore address element - if addressTag and jid == gajim.get_jid_from_account(self.name): - address = addressTag.getTag('address', attrs={'type': 'ofrom'}) - if address: - try: - frm = helpers.parse_jid(address.getAttr('jid')) - except common.helpers.InvalidFormat: - log.warn('Invalid JID: %s, ignoring it' % address.getAttr('jid')) - return - jid = gajim.get_jid_without_resource(frm) - - # invitations - invite = None - encTag = msg.getTag('x', namespace=common.xmpp.NS_ENCRYPTED) - - if not encTag: - invite = msg.getTag('x', namespace = common.xmpp.NS_MUC_USER) - if invite and not invite.getTag('invite'): - invite = None - - # FIXME: Msn transport (CMSN1.2.1 and PyMSN0.10) do NOT RECOMMENDED - # invitation - # stanza (MUC XEP) remove in 2007, as we do not do NOT RECOMMENDED - xtags = msg.getTags('x') - for xtag in xtags: - if xtag.getNamespace() == common.xmpp.NS_CONFERENCE and not invite: - try: - room_jid = helpers.parse_jid(xtag.getAttr('jid')) - except common.helpers.InvalidFormat: - log.warn('Invalid JID: %s, ignoring it' % xtag.getAttr('jid')) - continue - is_continued = False - if xtag.getTag('continue'): - is_continued = True - self.dispatch('GC_INVITATION', (room_jid, frm, '', None, - is_continued)) - return - - thread_id = msg.getThread() - - if not mtype: - mtype = 'normal' - - msgtxt = msg.getBody() - - encrypted = False - xep_200_encrypted = msg.getTag('c', namespace=common.xmpp.NS_STANZA_CRYPTO) - - session = None - if mtype != 'groupchat': - session = self.get_or_create_session(frm, thread_id) - - if thread_id and not session.received_thread_id: - session.received_thread_id = True - - session.last_receive = time_time() - - # check if the message is a XEP-0020 feature negotiation request - if msg.getTag('feature', namespace=common.xmpp.NS_FEATURE): - if gajim.HAVE_PYCRYPTO: - feature = msg.getTag(name='feature', namespace=common.xmpp.NS_FEATURE) - form = common.xmpp.DataForm(node=feature.getTag('x')) - - if form['FORM_TYPE'] == 'urn:xmpp:ssn': - session.handle_negotiation(form) - else: - reply = msg.buildReply() - reply.setType('error') - - reply.addChild(feature) - err = common.xmpp.ErrorNode('service-unavailable', typ='cancel') - reply.addChild(node=err) - - con.send(reply) - - raise common.xmpp.NodeProcessed - - return - - if msg.getTag('init', namespace=common.xmpp.NS_ESESSION_INIT): - init = msg.getTag(name='init', namespace=common.xmpp.NS_ESESSION_INIT) - form = common.xmpp.DataForm(node=init.getTag('x')) - - session.handle_negotiation(form) - - raise common.xmpp.NodeProcessed - - tim = msg.getTimestamp() - tim = helpers.datetime_tuple(tim) - tim = localtime(timegm(tim)) - - if xep_200_encrypted: - encrypted = 'xep200' - - try: - msg = session.decrypt_stanza(msg) - msgtxt = msg.getBody() - except Exception: - self.dispatch('FAILED_DECRYPT', (frm, tim, session)) - - # Receipt requested - # TODO: We shouldn't answer if we're invisible! - contact = gajim.contacts.get_contact(self.name, jid) - nick = gajim.get_room_and_nick_from_fjid(frm)[1] - gc_contact = gajim.contacts.get_gc_contact(self.name, jid, nick) - if msg.getTag('request', namespace=common.xmpp.NS_RECEIPTS) \ - and gajim.config.get_per('accounts', self.name, - 'answer_receipts') and ((contact and contact.sub \ - not in (u'to', u'none')) or gc_contact) and mtype != 'error': - receipt = common.xmpp.Message(to=frm, typ='chat') - receipt.setID(msg.getID()) - receipt.setTag('received', - namespace='urn:xmpp:receipts') - - if thread_id: - receipt.setThread(thread_id) - con.send(receipt) - - # We got our message's receipt - if msg.getTag('received', namespace=common.xmpp.NS_RECEIPTS) \ - and session.control and gajim.config.get_per('accounts', - self.name, 'request_receipt'): - session.control.conv_textview.hide_xep0184_warning( - msg.getID()) - - if encTag and self.USE_GPG: - encmsg = encTag.getData() - - keyID = gajim.config.get_per('accounts', self.name, 'keyid') - if keyID: - def decrypt_thread(encmsg, keyID): - decmsg = self.gpg.decrypt(encmsg, keyID) - # \x00 chars are not allowed in C (so in GTK) - msgtxt = helpers.decode_string(decmsg.replace('\x00', '')) - encrypted = 'xep27' - return (msgtxt, encrypted) - gajim.thread_interface(decrypt_thread, [encmsg, keyID], - self._on_message_decrypted, [mtype, msg, session, frm, jid, - invite, tim]) - return - self._on_message_decrypted((msgtxt, encrypted), mtype, msg, session, frm, - jid, invite, tim) - - def _on_message_decrypted(self, output, mtype, msg, session, frm, jid, - invite, tim): - msgtxt, encrypted = output - if mtype == 'error': - self.dispatch_error_message(msg, msgtxt, session, frm, tim) - elif mtype == 'groupchat': - self.dispatch_gc_message(msg, frm, msgtxt, jid, tim) - elif invite is not None: - self.dispatch_invite_message(invite, frm) - else: - if isinstance(session, gajim.default_session_type): - session.received(frm, msgtxt, tim, encrypted, msg) - else: - session.received(msg) - # END messageCB - - # process and dispatch an error message - def dispatch_error_message(self, msg, msgtxt, session, frm, tim): - error_msg = msg.getErrorMsg() - - if not error_msg: - error_msg = msgtxt - msgtxt = None - - subject = msg.getSubject() - - if session.is_loggable(): - try: - gajim.logger.write('error', frm, error_msg, tim=tim, - subject=subject) - except exceptions.PysqliteOperationalError, e: - self.dispatch('ERROR', (_('Disk Write Error'), str(e))) - except exceptions.DatabaseMalformed: - pritext = _('Database Error') - sectext = _('The database file (%s) cannot be read. Try to repair ' - 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove ' - 'it (all history will be lost).') % common.logger.LOG_DB_PATH - self.dispatch('ERROR', (pritext, sectext)) - self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt, - tim, session)) - - # process and dispatch a groupchat message - def dispatch_gc_message(self, msg, frm, msgtxt, jid, tim): - has_timestamp = bool(msg.timestamp) - - subject = msg.getSubject() - - if subject is not None: - self.dispatch('GC_SUBJECT', (frm, subject, msgtxt, has_timestamp)) - return - - statusCode = msg.getStatusCode() - - if not msg.getTag('body'): # no - # It could be a config change. See - # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify - if msg.getTag('x'): - if statusCode != []: - self.dispatch('GC_CONFIG_CHANGE', (jid, statusCode)) - return - - # Ignore message from room in which we are not - if jid not in self.last_history_time: - return - - self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp, msg.getXHTML(), - statusCode)) - - tim_int = int(float(mktime(tim))) - if gajim.config.should_log(self.name, jid) and not \ - tim_int <= self.last_history_time[jid] and msgtxt and frm.find('/') >= 0: - # if frm.find('/') < 0, it means message comes from room itself - # usually it hold description and can be send at each connection - # so don't store it in logs - try: - gajim.logger.write('gc_msg', frm, msgtxt, tim=tim) - except exceptions.PysqliteOperationalError, e: - self.dispatch('ERROR', (_('Disk Write Error'), str(e))) - except exceptions.DatabaseMalformed: - pritext = _('Database Error') - sectext = _('The database file (%s) cannot be read. Try to repair ' - 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove ' - 'it (all history will be lost).') % common.logger.LOG_DB_PATH - self.dispatch('ERROR', (pritext, sectext)) - - def dispatch_invite_message(self, invite, frm): - item = invite.getTag('invite') - try: - jid_from = helpers.parse_jid(item.getAttr('from')) - except common.helpers.InvalidFormat: - log.warn('Invalid JID: %s, ignoring it' % item.getAttr('from')) - return - reason = item.getTagData('reason') - item = invite.getTag('password') - password = invite.getTagData('password') - - is_continued = False - if invite.getTag('invite').getTag('continue'): - is_continued = True - self.dispatch('GC_INVITATION',(frm, jid_from, reason, password, - is_continued)) - - def _presenceCB(self, con, prs): - '''Called when we receive a presence''' - ptype = prs.getType() - if ptype == 'available': - ptype = None - rfc_types = ('unavailable', 'error', 'subscribe', 'subscribed', - 'unsubscribe', 'unsubscribed') - if ptype and not ptype in rfc_types: - ptype = None - log.debug('PresenceCB: %s' % ptype) - if not self.connection or self.connected < 2: - log.debug('account is no more connected') - return - try: - who = helpers.get_full_jid_from_iq(prs) - except Exception: - if prs.getTag('error') and prs.getTag('error').getTag('jid-malformed'): - # wrong jid, we probably tried to change our nick in a room to a non - # valid one - who = str(prs.getFrom()) - jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) - self.dispatch('GC_MSG', (jid_stripped, - _('Nickname not allowed: %s') % resource, None, False, None, [])) - return - jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) - timestamp = None - id_ = prs.getID() - is_gc = False # is it a GC presence ? - sigTag = None - ns_muc_user_x = None - avatar_sha = None - # XEP-0172 User Nickname - user_nick = prs.getTagData('nick') - if not user_nick: - user_nick = '' - contact_nickname = None - transport_auto_auth = False - # XEP-0203 - delay_tag = prs.getTag('delay', namespace=common.xmpp.NS_DELAY2) - if delay_tag: - tim = prs.getTimestamp2() - tim = helpers.datetime_tuple(tim) - timestamp = localtime(timegm(tim)) - xtags = prs.getTags('x') - for x in xtags: - namespace = x.getNamespace() - if namespace.startswith(common.xmpp.NS_MUC): - is_gc = True - if namespace == common.xmpp.NS_MUC_USER and x.getTag('destroy'): - ns_muc_user_x = x - elif namespace == common.xmpp.NS_SIGNED: - sigTag = x - elif namespace == common.xmpp.NS_VCARD_UPDATE: - avatar_sha = x.getTagData('photo') - contact_nickname = x.getTagData('nickname') - elif namespace == common.xmpp.NS_DELAY and not timestamp: - # XEP-0091 - tim = prs.getTimestamp() - tim = helpers.datetime_tuple(tim) - timestamp = localtime(timegm(tim)) - elif namespace == 'http://delx.cjb.net/protocol/roster-subsync': - # see http://trac.gajim.org/ticket/326 - agent = gajim.get_server_from_jid(jid_stripped) - if self.connection.getRoster().getItem(agent): # to be sure it's a transport contact - transport_auto_auth = True - - if not is_gc and id_ and id_.startswith('gajim_muc_') and \ - ptype == 'error': - # Error presences may not include sent stanza, so we don't detect it's - # a muc preence. So detect it by ID - h = hmac.new(self.secret_hmac, jid_stripped).hexdigest()[:6] - if id_.split('_')[-1] == h: - is_gc = True - status = prs.getStatus() or '' - show = prs.getShow() - if show not in ('chat', 'away', 'xa', 'dnd'): - show = '' # We ignore unknown show - if not ptype and not show: - show = 'online' - elif ptype == 'unavailable': - show = 'offline' - - prio = prs.getPriority() - try: - prio = int(prio) - except Exception: - prio = 0 - keyID = '' - if sigTag and self.USE_GPG and ptype != 'error': - # error presences contain our own signature - # verify - sigmsg = sigTag.getData() - keyID = self.gpg.verify(status, sigmsg) - - if is_gc: - if ptype == 'error': - errcon = prs.getError() - errmsg = prs.getErrorMsg() - errcode = prs.getErrorCode() - room_jid, nick = gajim.get_room_and_nick_from_fjid(who) - - gc_control = gajim.interface.msg_win_mgr.get_gc_control(room_jid, - self.name) - - # If gc_control is missing - it may be minimized. Try to get it from - # there. If it's not there - then it's missing anyway and will - # remain set to None. - if gc_control is None: - minimized = gajim.interface.minimized_controls[self.name] - gc_control = minimized.get(room_jid) - - if errcode == '502': - # Internal Timeout: - self.dispatch('NOTIFY', (jid_stripped, 'error', errmsg, resource, - prio, keyID, timestamp, None)) - elif (errcode == '503'): - # maximum user number reached - self.dispatch('ERROR', (_('Unable to join group chat'), - _('Maximum number of users for %s has been reached') % \ - room_jid)) - elif (errcode == '401') or (errcon == 'not-authorized'): - # password required to join - self.dispatch('GC_PASSWORD_REQUIRED', (room_jid, nick)) - elif (errcode == '403') or (errcon == 'forbidden'): - # we are banned - self.dispatch('ERROR', (_('Unable to join group chat'), - _('You are banned from group chat %s.') % room_jid)) - elif (errcode == '404') or (errcon in ('item-not-found', - 'remote-server-not-found')): - if gc_control is None or gc_control.autorejoin is None: - # group chat does not exist - self.dispatch('ERROR', (_('Unable to join group chat'), - _('Group chat %s does not exist.') % room_jid)) - elif (errcode == '405') or (errcon == 'not-allowed'): - self.dispatch('ERROR', (_('Unable to join group chat'), - _('Group chat creation is restricted.'))) - elif (errcode == '406') or (errcon == 'not-acceptable'): - self.dispatch('ERROR', (_('Unable to join group chat'), - _('Your registered nickname must be used in group chat %s.') \ - % room_jid)) - elif (errcode == '407') or (errcon == 'registration-required'): - self.dispatch('ERROR', (_('Unable to join group chat'), - _('You are not in the members list in groupchat %s.') % \ - room_jid)) - elif (errcode == '409') or (errcon == 'conflict'): - # nick conflict - room_jid = gajim.get_room_from_fjid(who) - self.dispatch('ASK_NEW_NICK', (room_jid,)) - else: # print in the window the error - self.dispatch('ERROR_ANSWER', ('', jid_stripped, - errmsg, errcode)) - if not ptype or ptype == 'unavailable': - if gajim.config.get('log_contact_status_changes') and \ - gajim.config.should_log(self.name, jid_stripped): - gc_c = gajim.contacts.get_gc_contact(self.name, jid_stripped, - resource) - st = status or '' - if gc_c: - jid = gc_c.jid - else: - jid = prs.getJid() - if jid: - # we know real jid, save it in db - st += ' (%s)' % jid - try: - gajim.logger.write('gcstatus', who, st, show) - except exceptions.PysqliteOperationalError, e: - self.dispatch('ERROR', (_('Disk Write Error'), str(e))) - except exceptions.DatabaseMalformed: - pritext = _('Database Error') - sectext = _('The database file (%s) cannot be read. Try to ' - 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)' - ' or remove it (all history will be lost).') % \ - common.logger.LOG_DB_PATH - self.dispatch('ERROR', (pritext, sectext)) - if avatar_sha or avatar_sha == '': - if avatar_sha == '': - # contact has no avatar - puny_nick = helpers.sanitize_filename(resource) - gajim.interface.remove_avatar_files(jid_stripped, puny_nick) - # if it's a gc presence, don't ask vcard here. We may ask it to - # real jid in gui part. - if ns_muc_user_x: - # Room has been destroyed. see - # http://www.xmpp.org/extensions/xep-0045.html#destroyroom - reason = _('Room has been destroyed') - destroy = ns_muc_user_x.getTag('destroy') - r = destroy.getTagData('reason') - if r: - reason += ' (%s)' % r - if destroy.getAttr('jid'): - try: - jid = helpers.parse_jid(destroy.getAttr('jid')) - reason += '\n' + _('You can join this room instead: %s') \ - % jid - except common.helpers.InvalidFormat: - pass - statusCode = ['destroyed'] - else: - reason = prs.getReason() - statusCode = prs.getStatusCode() - self.dispatch('GC_NOTIFY', (jid_stripped, show, status, resource, - prs.getRole(), prs.getAffiliation(), prs.getJid(), - reason, prs.getActor(), statusCode, prs.getNewNick(), - avatar_sha)) - return - - if ptype == 'subscribe': - log.debug('subscribe request from %s' % who) - if gajim.config.get_per('accounts', self.name, 'autoauth') or \ - who.find('@') <= 0 or jid_stripped in self.jids_for_auto_auth or \ - transport_auto_auth: - if self.connection: - p = common.xmpp.Presence(who, 'subscribed') - p = self.add_sha(p) - self.connection.send(p) - if who.find('@') <= 0 or transport_auto_auth: - self.dispatch('NOTIFY', (jid_stripped, 'offline', 'offline', - resource, prio, keyID, timestamp, None)) - if transport_auto_auth: - self.automatically_added.append(jid_stripped) - self.request_subscription(jid_stripped, name = user_nick) - else: - if not status: - status = _('I would like to add you to my roster.') - self.dispatch('SUBSCRIBE', (jid_stripped, status, user_nick)) - elif ptype == 'subscribed': - if jid_stripped in self.automatically_added: - self.automatically_added.remove(jid_stripped) - else: - # detect a subscription loop - if jid_stripped not in self.subscribed_events: - self.subscribed_events[jid_stripped] = [] - self.subscribed_events[jid_stripped].append(time_time()) - block = False - if len(self.subscribed_events[jid_stripped]) > 5: - if time_time() - self.subscribed_events[jid_stripped][0] < 5: - block = True - self.subscribed_events[jid_stripped] = self.subscribed_events[jid_stripped][1:] - if block: - gajim.config.set_per('account', self.name, - 'dont_ack_subscription', True) - else: - self.dispatch('SUBSCRIBED', (jid_stripped, resource)) - # BE CAREFUL: no con.updateRosterItem() in a callback - log.debug(_('we are now subscribed to %s') % who) - elif ptype == 'unsubscribe': - log.debug(_('unsubscribe request from %s') % who) - elif ptype == 'unsubscribed': - log.debug(_('we are now unsubscribed from %s') % who) - # detect a unsubscription loop - if jid_stripped not in self.subscribed_events: - self.subscribed_events[jid_stripped] = [] - self.subscribed_events[jid_stripped].append(time_time()) - block = False - if len(self.subscribed_events[jid_stripped]) > 5: - if time_time() - self.subscribed_events[jid_stripped][0] < 5: - block = True - self.subscribed_events[jid_stripped] = self.subscribed_events[jid_stripped][1:] - if block: - gajim.config.set_per('account', self.name, 'dont_ack_subscription', - True) - else: - self.dispatch('UNSUBSCRIBED', jid_stripped) - elif ptype == 'error': - errmsg = prs.getError() - errcode = prs.getErrorCode() - if errcode != '502': # Internal Timeout: - # print in the window the error - self.dispatch('ERROR_ANSWER', ('', jid_stripped, - errmsg, errcode)) - if errcode != '409': # conflict # See #5120 - self.dispatch('NOTIFY', (jid_stripped, 'error', errmsg, resource, - prio, keyID, timestamp, None)) - - if ptype == 'unavailable': - for jid in [jid_stripped, who]: - if jid not in self.sessions: - continue - # automatically terminate sessions that they haven't sent a thread - # ID in, only if other part support thread ID - for sess in self.sessions[jid].values(): - if not sess.received_thread_id: - contact = gajim.contacts.get_contact(self.name, jid) - # FIXME: I don't know if this is the correct behavior here. - # Anyway, it is the old behavior when we assumed that - # not-existing contacts don't support anything - contact_exists = bool(contact) - session_supported = contact_exists and \ - contact.supports(common.xmpp.NS_SSN) or \ - contact.supports(common.xmpp.NS_ESESSION) - if session_supported: - sess.terminate() - del self.sessions[jid][sess.thread_id] - - if avatar_sha is not None and ptype != 'error': - if jid_stripped not in self.vcard_shas: - cached_vcard = self.get_cached_vcard(jid_stripped) - if cached_vcard and 'PHOTO' in cached_vcard and \ - 'SHA' in cached_vcard['PHOTO']: - self.vcard_shas[jid_stripped] = cached_vcard['PHOTO']['SHA'] - else: - self.vcard_shas[jid_stripped] = '' - if avatar_sha != self.vcard_shas[jid_stripped]: - # avatar has been updated - self.request_vcard(jid_stripped) - if not ptype or ptype == 'unavailable': - if gajim.config.get('log_contact_status_changes') and \ - gajim.config.should_log(self.name, jid_stripped): - try: - gajim.logger.write('status', jid_stripped, status, show) - except exceptions.PysqliteOperationalError, e: - self.dispatch('ERROR', (_('Disk Write Error'), str(e))) - except exceptions.DatabaseMalformed: - pritext = _('Database Error') - sectext = _('The database file (%s) cannot be read. Try to ' - 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup) ' - 'or remove it (all history will be lost).') % \ - common.logger.LOG_DB_PATH - self.dispatch('ERROR', (pritext, sectext)) - our_jid = gajim.get_jid_from_account(self.name) - if jid_stripped == our_jid and resource == self.server_resource: - # We got our own presence - self.dispatch('STATUS', show) - else: - self.dispatch('NOTIFY', (jid_stripped, show, status, resource, prio, - keyID, timestamp, contact_nickname)) - # END presenceCB - - def _StanzaArrivedCB(self, con, obj): - self.last_io = gajim.idlequeue.current_time() - - def _MucOwnerCB(self, con, iq_obj): - log.debug('MucOwnerCB') - qp = iq_obj.getQueryPayload() - node = None - for q in qp: - if q.getNamespace() == common.xmpp.NS_DATA: - node = q - if not node: - return - self.dispatch('GC_CONFIG', (helpers.get_full_jid_from_iq(iq_obj), node)) - - def _MucAdminCB(self, con, iq_obj): - log.debug('MucAdminCB') - items = iq_obj.getTag('query', namespace=common.xmpp.NS_MUC_ADMIN).\ - getTags('item') - users_dict = {} - for item in items: - if item.has_attr('jid') and item.has_attr('affiliation'): - try: - jid = helpers.parse_jid(item.getAttr('jid')) - except common.helpers.InvalidFormat: - log.warn('Invalid JID: %s, ignoring it' % item.getAttr('jid')) - continue - affiliation = item.getAttr('affiliation') - users_dict[jid] = {'affiliation': affiliation} - if item.has_attr('nick'): - users_dict[jid]['nick'] = item.getAttr('nick') - if item.has_attr('role'): - users_dict[jid]['role'] = item.getAttr('role') - reason = item.getTagData('reason') - if reason: - users_dict[jid]['reason'] = reason - - self.dispatch('GC_AFFILIATION', (helpers.get_full_jid_from_iq(iq_obj), - users_dict)) - - def _MucErrorCB(self, con, iq_obj): - log.debug('MucErrorCB') - jid = helpers.get_full_jid_from_iq(iq_obj) - errmsg = iq_obj.getError() - errcode = iq_obj.getErrorCode() - self.dispatch('MSGERROR', (jid, errcode, errmsg)) - - def _IqPingCB(self, con, iq_obj): - log.debug('IqPingCB') - if not self.connection or self.connected < 2: - return - iq_obj = iq_obj.buildReply('result') - self.connection.send(iq_obj) - raise common.xmpp.NodeProcessed - - def _PrivacySetCB(self, con, iq_obj): - ''' - Privacy lists (XEP 016) - - A list has been set - ''' - log.debug('PrivacySetCB') - if not self.connection or self.connected < 2: - return - result = iq_obj.buildReply('result') - q = result.getTag('query') - if q: - result.delChild(q) - self.connection.send(result) - raise common.xmpp.NodeProcessed - - def _getRoster(self): - log.debug('getRosterCB') - if not self.connection: - return - self.connection.getRoster(self._on_roster_set) - self.discoverItems(gajim.config.get_per('accounts', self.name, - 'hostname'), id_prefix='Gajim_') - if gajim.config.get_per('accounts', self.name, 'use_ft_proxies'): - self.discover_ft_proxies() - - def discover_ft_proxies(self): - cfg_proxies = gajim.config.get_per('accounts', self.name, - 'file_transfer_proxies') - our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name) + '/' +\ - self.server_resource) - if cfg_proxies: - proxies = [e.strip() for e in cfg_proxies.split(',')] - for proxy in proxies: - gajim.proxy65_manager.resolve(proxy, self.connection, our_jid) - - def _on_roster_set(self, roster): - roster_version = roster.version - received_from_server = roster.received_from_server - raw_roster = roster.getRaw() - roster = {} - our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name)) - if self.connected > 1 and self.continue_connect_info: - msg = self.continue_connect_info[1] - sign_msg = self.continue_connect_info[2] - signed = '' - send_first_presence = True - if sign_msg: - signed = self.get_signed_presence(msg, self._send_first_presence) - if signed is None: - self.dispatch('GPG_PASSWORD_REQUIRED', - (self._send_first_presence,)) - # _send_first_presence will be called when user enter passphrase - send_first_presence = False - if send_first_presence: - self._send_first_presence(signed) - - for jid in raw_roster: - try: - j = helpers.parse_jid(jid) - except Exception: - print >> sys.stderr, _('JID %s is not RFC compliant. It will not be added to your roster. Use roster management tools such as http://jru.jabberstudio.org/ to remove it') % jid - else: - infos = raw_roster[jid] - if jid != our_jid and (not infos['subscription'] or \ - infos['subscription'] == 'none') and (not infos['ask'] or \ - infos['ask'] == 'none') and not infos['name'] and \ - not infos['groups']: - # remove this useless item, it won't be shown in roster anyway - self.connection.getRoster().delItem(jid) - elif jid != our_jid: # don't add our jid - roster[j] = raw_roster[jid] - if gajim.jid_is_transport(jid) and \ - not gajim.get_transport_name_from_jid(jid): - # we can't determine which iconset to use - self.discoverInfo(jid) - - gajim.logger.replace_roster(self.name, roster_version, roster) - if received_from_server: - for contact in gajim.contacts.iter_contacts(self.name): - if not contact.is_groupchat() and contact.jid not in roster and \ - contact.jid != gajim.get_jid_from_account(self.name): - self.dispatch('ROSTER_INFO', (contact.jid, None, None, None, - ())) - for jid in roster: - self.dispatch('ROSTER_INFO', (jid, roster[jid]['name'], - roster[jid]['subscription'], roster[jid]['ask'], - roster[jid]['groups'])) - - def _send_first_presence(self, signed = ''): - show = self.continue_connect_info[0] - msg = self.continue_connect_info[1] - sign_msg = self.continue_connect_info[2] - if sign_msg and not signed: - signed = self.get_signed_presence(msg) - if signed is None: - self.dispatch('BAD_PASSPHRASE', ()) - self.USE_GPG = False - signed = '' - self.connected = gajim.SHOW_LIST.index(show) - sshow = helpers.get_xmpp_show(show) - # send our presence - if show == 'invisible': - self.send_invisible_presence(msg, signed, True) - return - priority = gajim.get_priority(self.name, sshow) - our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name)) - vcard = self.get_cached_vcard(our_jid) - if vcard and 'PHOTO' in vcard and 'SHA' in vcard['PHOTO']: - self.vcard_sha = vcard['PHOTO']['SHA'] - p = common.xmpp.Presence(typ = None, priority = priority, show = sshow) - p = self.add_sha(p) - if msg: - p.setStatus(msg) - if signed: - p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed) - - if self.connection: - self.connection.send(p) - self.priority = priority - self.dispatch('STATUS', show) - if self.vcard_supported: - # ask our VCard - self.request_vcard(None) - - # Get bookmarks from private namespace - self.get_bookmarks() - - # Get annotations from private namespace - self.get_annotations() - - # Inform GUI we just signed in - self.dispatch('SIGNED_IN', ()) - self.continue_connect_info = None - - def request_gmail_notifications(self): - if not self.connection or self.connected < 2: - return - # It's a gmail account, - # inform the server that we want e-mail notifications - our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name)) - log.debug(('%s is a gmail account. Setting option ' - 'to get e-mail notifications on the server.') % (our_jid)) - iq = common.xmpp.Iq(typ = 'set', to = our_jid) - iq.setAttr('id', 'MailNotify') - query = iq.setTag('usersetting') - query.setNamespace(common.xmpp.NS_GTALKSETTING) - query = query.setTag('mailnotifications') - query.setAttr('value', 'true') - self.connection.send(iq) - # Ask how many messages there are now - iq = common.xmpp.Iq(typ = 'get') - iq.setID(self.connection.getAnID()) - query = iq.setTag('query') - query.setNamespace(common.xmpp.NS_GMAILNOTIFY) - self.connection.send(iq) - - - def _search_fields_received(self, con, iq_obj): - jid = jid = helpers.get_jid_from_iq(iq_obj) - tag = iq_obj.getTag('query', namespace = common.xmpp.NS_SEARCH) - if not tag: - self.dispatch('SEARCH_FORM', (jid, None, False)) - return - df = tag.getTag('x', namespace = common.xmpp.NS_DATA) - if df: - self.dispatch('SEARCH_FORM', (jid, df, True)) - return - df = {} - for i in iq_obj.getQueryPayload(): - df[i.getName()] = i.getData() - self.dispatch('SEARCH_FORM', (jid, df, False)) - - def _StreamCB(self, con, obj): - if obj.getTag('conflict'): - # disconnected because of a resource conflict - self.dispatch('RESOURCE_CONFLICT', ()) - - def _register_handlers(self, con, con_type): - # try to find another way to register handlers in each class - # that defines handlers - con.RegisterHandler('message', self._messageCB) - con.RegisterHandler('presence', self._presenceCB) - con.RegisterHandler('presence', self._capsPresenceCB) - # We use makefirst so that this handler is called before _messageCB, and - # can prevent calling it when it's not needed. - # We also don't check for namespace, else it cannot stop _messageCB to be - # called - con.RegisterHandler('message', self._pubsubEventCB, makefirst=True) - con.RegisterHandler('iq', self._vCardCB, 'result', - common.xmpp.NS_VCARD) - con.RegisterHandler('iq', self._rosterSetCB, 'set', - common.xmpp.NS_ROSTER) - con.RegisterHandler('iq', self._siSetCB, 'set', - common.xmpp.NS_SI) - con.RegisterHandler('iq', self._rosterItemExchangeCB, 'set', - common.xmpp.NS_ROSTERX) - con.RegisterHandler('iq', self._siErrorCB, 'error', - common.xmpp.NS_SI) - con.RegisterHandler('iq', self._siResultCB, 'result', - common.xmpp.NS_SI) - con.RegisterHandler('iq', self._discoGetCB, 'get', - common.xmpp.NS_DISCO) - con.RegisterHandler('iq', self._bytestreamSetCB, 'set', - common.xmpp.NS_BYTESTREAM) - con.RegisterHandler('iq', self._bytestreamResultCB, 'result', - common.xmpp.NS_BYTESTREAM) - con.RegisterHandler('iq', self._bytestreamErrorCB, 'error', - common.xmpp.NS_BYTESTREAM) - con.RegisterHandler('iq', self._DiscoverItemsCB, 'result', - common.xmpp.NS_DISCO_ITEMS) - con.RegisterHandler('iq', self._DiscoverItemsErrorCB, 'error', - common.xmpp.NS_DISCO_ITEMS) - con.RegisterHandler('iq', self._DiscoverInfoCB, 'result', - common.xmpp.NS_DISCO_INFO) - con.RegisterHandler('iq', self._DiscoverInfoErrorCB, 'error', - common.xmpp.NS_DISCO_INFO) - con.RegisterHandler('iq', self._VersionCB, 'get', - common.xmpp.NS_VERSION) - con.RegisterHandler('iq', self._TimeCB, 'get', - common.xmpp.NS_TIME) - con.RegisterHandler('iq', self._TimeRevisedCB, 'get', - common.xmpp.NS_TIME_REVISED) - con.RegisterHandler('iq', self._LastCB, 'get', - common.xmpp.NS_LAST) - con.RegisterHandler('iq', self._LastResultCB, 'result', - common.xmpp.NS_LAST) - con.RegisterHandler('iq', self._VersionResultCB, 'result', - common.xmpp.NS_VERSION) - con.RegisterHandler('iq', self._TimeRevisedResultCB, 'result', - common.xmpp.NS_TIME_REVISED) - con.RegisterHandler('iq', self._MucOwnerCB, 'result', - common.xmpp.NS_MUC_OWNER) - con.RegisterHandler('iq', self._MucAdminCB, 'result', - common.xmpp.NS_MUC_ADMIN) - con.RegisterHandler('iq', self._PrivateCB, 'result', - common.xmpp.NS_PRIVATE) - con.RegisterHandler('iq', self._HttpAuthCB, 'get', - common.xmpp.NS_HTTP_AUTH) - con.RegisterHandler('iq', self._CommandExecuteCB, 'set', - common.xmpp.NS_COMMANDS) - con.RegisterHandler('iq', self._gMailNewMailCB, 'set', - common.xmpp.NS_GMAILNOTIFY) - con.RegisterHandler('iq', self._gMailQueryCB, 'result', - common.xmpp.NS_GMAILNOTIFY) - con.RegisterHandler('iq', self._DiscoverInfoGetCB, 'get', - common.xmpp.NS_DISCO_INFO) - con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get', - common.xmpp.NS_DISCO_ITEMS) - con.RegisterHandler('iq', self._IqPingCB, 'get', - common.xmpp.NS_PING) - con.RegisterHandler('iq', self._search_fields_received, 'result', - common.xmpp.NS_SEARCH) - con.RegisterHandler('iq', self._PrivacySetCB, 'set', - common.xmpp.NS_PRIVACY) - con.RegisterHandler('iq', self._ArchiveCB, ns=common.xmpp.NS_ARCHIVE) - con.RegisterHandler('iq', self._PubSubCB, 'result') - con.RegisterHandler('iq', self._PubSubErrorCB, 'error') - con.RegisterHandler('iq', self._JingleCB, 'result') - con.RegisterHandler('iq', self._JingleCB, 'error') - con.RegisterHandler('iq', self._JingleCB, 'set', - common.xmpp.NS_JINGLE) - con.RegisterHandler('iq', self._ErrorCB, 'error') - con.RegisterHandler('iq', self._IqCB) - con.RegisterHandler('iq', self._StanzaArrivedCB) - con.RegisterHandler('iq', self._ResultCB, 'result') - con.RegisterHandler('presence', self._StanzaArrivedCB) - con.RegisterHandler('message', self._StanzaArrivedCB) - con.RegisterHandler('unknown', self._StreamCB, 'urn:ietf:params:xml:ns:xmpp-streams', xmlns='http://etherx.jabber.org/streams') - -# vim: se ts=3: + def __init__(self): + ConnectionArchive.__init__(self) + ConnectionVcard.__init__(self) + ConnectionBytestream.__init__(self) + ConnectionCommands.__init__(self) + ConnectionPubSub.__init__(self) + ConnectionPEP.__init__(self, account=self.name, dispatcher=self, + pubsub_connection=self) + ConnectionJingle.__init__(self) + ConnectionHandlersBase.__init__(self) + self.gmail_url = None + + # keep the latest subscribed event for each jid to prevent loop when we + # acknowledge presences + self.subscribed_events = {} + # IDs of jabber:iq:last requests + self.last_ids = [] + # IDs of jabber:iq:version requests + self.version_ids = [] + # IDs of urn:xmpp:time requests + self.entity_time_ids = [] + # ID of urn:xmpp:ping requests + self.awaiting_xmpp_ping_id = None + self.continue_connect_info = None + + try: + idle.init() + except Exception: + global HAS_IDLE + HAS_IDLE = False + + self.gmail_last_tid = None + self.gmail_last_time = None + + def build_http_auth_answer(self, iq_obj, answer): + if not self.connection or self.connected < 2: + return + if answer == 'yes': + self.connection.send(iq_obj.buildReply('result')) + elif answer == 'no': + err = common.xmpp.Error(iq_obj, + common.xmpp.protocol.ERR_NOT_AUTHORIZED) + self.connection.send(err) + + def _HttpAuthCB(self, con, iq_obj): + log.debug('HttpAuthCB') + opt = gajim.config.get_per('accounts', self.name, 'http_auth') + if opt in ('yes', 'no'): + self.build_http_auth_answer(iq_obj, opt) + else: + id_ = iq_obj.getTagAttr('confirm', 'id') + method = iq_obj.getTagAttr('confirm', 'method') + url = iq_obj.getTagAttr('confirm', 'url') + msg = iq_obj.getTagData('body') # In case it's a message with a body + self.dispatch('HTTP_AUTH', (method, url, id_, iq_obj, msg)) + raise common.xmpp.NodeProcessed + + def _ErrorCB(self, con, iq_obj): + log.debug('ErrorCB') + jid_from = helpers.get_full_jid_from_iq(iq_obj) + jid_stripped, resource = gajim.get_room_and_nick_from_fjid(jid_from) + id_ = unicode(iq_obj.getID()) + if id_ in self.version_ids: + self.dispatch('OS_INFO', (jid_stripped, resource, '', '')) + self.version_ids.remove(id_) + return + if id_ in self.last_ids: + self.dispatch('LAST_STATUS_TIME', (jid_stripped, resource, -1, '')) + self.last_ids.remove(id_) + return + if id_ in self.entity_time_ids: + self.dispatch('ENTITY_TIME', (jid_stripped, resource, '')) + self.entity_time_ids.remove(id_) + return + if id_ == self.awaiting_xmpp_ping_id: + self.awaiting_xmpp_ping_id = None + errmsg = iq_obj.getErrorMsg() + errcode = iq_obj.getErrorCode() + self.dispatch('ERROR_ANSWER', (id_, jid_from, errmsg, errcode)) + + def _PrivateCB(self, con, iq_obj): + ''' + Private Data (XEP 048 and 049) + ''' + log.debug('PrivateCB') + query = iq_obj.getTag('query') + storage = query.getTag('storage') + if storage: + ns = storage.getNamespace() + if ns == 'storage:bookmarks': + self._parse_bookmarks(storage, 'xml') + elif ns == 'gajim:prefs': + # Preferences data + # http://www.xmpp.org/extensions/xep-0049.html + #TODO: implement this + pass + elif ns == 'storage:rosternotes': + # Annotations + # http://www.xmpp.org/extensions/xep-0145.html + notes = storage.getTags('note') + for note in notes: + try: + jid = helpers.parse_jid(note.getAttr('jid')) + except common.helpers.InvalidFormat: + log.warn('Invalid JID: %s, ignoring it' % note.getAttr('jid')) + continue + annotation = note.getData() + self.annotations[jid] = annotation + + def _parse_bookmarks(self, storage, storage_type): + '''storage_type can be 'pubsub' or 'xml' to tell from where we got + bookmarks''' + # Bookmarked URLs and Conferences + # http://www.xmpp.org/extensions/xep-0048.html + resend_to_pubsub = False + confs = storage.getTags('conference') + for conf in confs: + autojoin_val = conf.getAttr('autojoin') + if autojoin_val is None: # not there (it's optional) + autojoin_val = False + minimize_val = conf.getAttr('minimize') + if minimize_val is None: # not there (it's optional) + minimize_val = False + print_status = conf.getTagData('print_status') + if not print_status: + print_status = conf.getTagData('show_status') + try: + bm = {'name': conf.getAttr('name'), + 'jid': helpers.parse_jid(conf.getAttr('jid')), + 'autojoin': autojoin_val, + 'minimize': minimize_val, + 'password': conf.getTagData('password'), + 'nick': conf.getTagData('nick'), + 'print_status': print_status} + except common.helpers.InvalidFormat: + log.warn('Invalid JID: %s, ignoring it' % conf.getAttr('jid')) + continue + + if bm not in self.bookmarks: + self.bookmarks.append(bm) + if storage_type == 'xml': + # We got a bookmark that was not in pubsub + resend_to_pubsub = True + self.dispatch('BOOKMARKS', self.bookmarks) + if storage_type == 'pubsub': + # We gor bookmarks from pubsub, now get those from xml to merge them + self.get_bookmarks(storage_type='xml') + if self.pubsub_supported and resend_to_pubsub: + self.store_bookmarks('pubsub') + + def _rosterSetCB(self, con, iq_obj): + log.debug('rosterSetCB') + version = iq_obj.getTagAttr('query', 'ver') + for item in iq_obj.getTag('query').getChildren(): + try: + jid = helpers.parse_jid(item.getAttr('jid')) + except common.helpers.InvalidFormat: + log.warn('Invalid JID: %s, ignoring it' % item.getAttr('jid')) + continue + name = item.getAttr('name') + sub = item.getAttr('subscription') + ask = item.getAttr('ask') + groups = [] + for group in item.getTags('group'): + groups.append(group.getData()) + self.dispatch('ROSTER_INFO', (jid, name, sub, ask, groups)) + account_jid = gajim.get_jid_from_account(self.name) + gajim.logger.add_or_update_contact(account_jid, jid, name, sub, ask, + groups) + if version: + gajim.config.set_per('accounts', self.name, 'roster_version', + version) + if not self.connection or self.connected < 2: + raise common.xmpp.NodeProcessed + reply = common.xmpp.Iq(typ='result', attrs={'id': iq_obj.getID()}, + to=iq_obj.getFrom(), frm=iq_obj.getTo(), xmlns=None) + self.connection.send(reply) + raise common.xmpp.NodeProcessed + + def _VersionCB(self, con, iq_obj): + log.debug('VersionCB') + if not self.connection or self.connected < 2: + return + iq_obj = iq_obj.buildReply('result') + qp = iq_obj.getTag('query') + qp.setTagData('name', 'Gajim') + qp.setTagData('version', gajim.version) + send_os = gajim.config.get_per('accounts', self.name, 'send_os_info') + if send_os: + qp.setTagData('os', helpers.get_os_info()) + self.connection.send(iq_obj) + raise common.xmpp.NodeProcessed + + def _LastCB(self, con, iq_obj): + log.debug('LastCB') + if not self.connection or self.connected < 2: + return + iq_obj = iq_obj.buildReply('result') + qp = iq_obj.getTag('query') + if not HAS_IDLE: + qp.attrs['seconds'] = '0' + else: + qp.attrs['seconds'] = idle.getIdleSec() + + self.connection.send(iq_obj) + raise common.xmpp.NodeProcessed + + def _LastResultCB(self, con, iq_obj): + log.debug('LastResultCB') + qp = iq_obj.getTag('query') + seconds = qp.getAttr('seconds') + status = qp.getData() + try: + seconds = int(seconds) + except Exception: + return + id_ = iq_obj.getID() + if id_ in self.groupchat_jids: + who = self.groupchat_jids[id_] + del self.groupchat_jids[id_] + else: + who = helpers.get_full_jid_from_iq(iq_obj) + if id_ in self.last_ids: + self.last_ids.remove(id_) + jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) + self.dispatch('LAST_STATUS_TIME', (jid_stripped, resource, seconds, status)) + + def _VersionResultCB(self, con, iq_obj): + log.debug('VersionResultCB') + client_info = '' + os_info = '' + qp = iq_obj.getTag('query') + if qp.getTag('name'): + client_info += qp.getTag('name').getData() + if qp.getTag('version'): + client_info += ' ' + qp.getTag('version').getData() + if qp.getTag('os'): + os_info += qp.getTag('os').getData() + id_ = iq_obj.getID() + if id_ in self.groupchat_jids: + who = self.groupchat_jids[id_] + del self.groupchat_jids[id_] + else: + who = helpers.get_full_jid_from_iq(iq_obj) + jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) + if id_ in self.version_ids: + self.version_ids.remove(id_) + self.dispatch('OS_INFO', (jid_stripped, resource, client_info, os_info)) + + def _TimeCB(self, con, iq_obj): + log.debug('TimeCB') + if not self.connection or self.connected < 2: + return + iq_obj = iq_obj.buildReply('result') + qp = iq_obj.getTag('query') + qp.setTagData('utc', strftime('%Y%m%dT%H:%M:%S', gmtime())) + qp.setTagData('tz', helpers.decode_string(tzname[daylight])) + qp.setTagData('display', helpers.decode_string(strftime('%c', + localtime()))) + self.connection.send(iq_obj) + raise common.xmpp.NodeProcessed + + def _TimeRevisedCB(self, con, iq_obj): + log.debug('TimeRevisedCB') + if not self.connection or self.connected < 2: + return + iq_obj = iq_obj.buildReply('result') + qp = iq_obj.setTag('time', + namespace=common.xmpp.NS_TIME_REVISED) + qp.setTagData('utc', strftime('%Y-%m-%dT%H:%M:%SZ', gmtime())) + isdst = localtime().tm_isdst + zone = -(timezone, altzone)[isdst] / 60 + tzo = (zone / 60, abs(zone % 60)) + qp.setTagData('tzo', '%+03d:%02d' % (tzo)) + self.connection.send(iq_obj) + raise common.xmpp.NodeProcessed + + def _TimeRevisedResultCB(self, con, iq_obj): + log.debug('TimeRevisedResultCB') + time_info = '' + qp = iq_obj.getTag('time') + if not qp: + # wrong answer + return + tzo = qp.getTag('tzo').getData() + if tzo.lower() == 'z': + tzo = '0:0' + tzoh, tzom = tzo.split(':') + utc_time = qp.getTag('utc').getData() + ZERO = datetime.timedelta(0) + class UTC(datetime.tzinfo): + def utcoffset(self, dt): + return ZERO + def tzname(self, dt): + return "UTC" + def dst(self, dt): + return ZERO + + class contact_tz(datetime.tzinfo): + def utcoffset(self, dt): + return datetime.timedelta(hours=int(tzoh), minutes=int(tzom)) + def tzname(self, dt): + return "remote timezone" + def dst(self, dt): + return ZERO + + try: + t = datetime.datetime.strptime(utc_time, '%Y-%m-%dT%H:%M:%SZ') + t = t.replace(tzinfo=UTC()) + time_info = t.astimezone(contact_tz()).strftime('%c') + except ValueError, e: + log.info('Wrong time format: %s' % str(e)) + + id_ = iq_obj.getID() + if id_ in self.groupchat_jids: + who = self.groupchat_jids[id_] + del self.groupchat_jids[id_] + else: + who = helpers.get_full_jid_from_iq(iq_obj) + jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) + if id_ in self.entity_time_ids: + self.entity_time_ids.remove(id_) + self.dispatch('ENTITY_TIME', (jid_stripped, resource, time_info)) + + def _gMailNewMailCB(self, con, gm): + '''Called when we get notified of new mail messages in gmail account''' + if not self.connection or self.connected < 2: + return + if not gm.getTag('new-mail'): + return + if gm.getTag('new-mail').getNamespace() == common.xmpp.NS_GMAILNOTIFY: + # we'll now ask the server for the exact number of new messages + jid = gajim.get_jid_from_account(self.name) + log.debug('Got notification of new gmail e-mail on %s. Asking the server for more info.' % jid) + iq = common.xmpp.Iq(typ = 'get') + iq.setID(self.connection.getAnID()) + query = iq.setTag('query') + query.setNamespace(common.xmpp.NS_GMAILNOTIFY) + # we want only be notified about newer mails + if self.gmail_last_tid: + query.setAttr('newer-than-tid', self.gmail_last_tid) + if self.gmail_last_time: + query.setAttr('newer-than-time', self.gmail_last_time) + self.connection.send(iq) + raise common.xmpp.NodeProcessed + + def _gMailQueryCB(self, con, gm): + '''Called when we receive results from Querying the server for mail messages in gmail account''' + if not gm.getTag('mailbox'): + return + self.gmail_url = gm.getTag('mailbox').getAttr('url') + if gm.getTag('mailbox').getNamespace() == common.xmpp.NS_GMAILNOTIFY: + newmsgs = gm.getTag('mailbox').getAttr('total-matched') + if newmsgs != '0': + # there are new messages + gmail_messages_list = [] + if gm.getTag('mailbox').getTag('mail-thread-info'): + gmail_messages = gm.getTag('mailbox').getTags('mail-thread-info') + for gmessage in gmail_messages: + unread_senders = [] + for sender in gmessage.getTag('senders').getTags('sender'): + if sender.getAttr('unread') != '1': + continue + if sender.getAttr('name'): + unread_senders.append(sender.getAttr('name') + '< ' + \ + sender.getAttr('address') + '>') + else: + unread_senders.append(sender.getAttr('address')) + + if not unread_senders: + continue + gmail_subject = gmessage.getTag('subject').getData() + gmail_snippet = gmessage.getTag('snippet').getData() + tid = int(gmessage.getAttr('tid')) + if not self.gmail_last_tid or tid > self.gmail_last_tid: + self.gmail_last_tid = tid + gmail_messages_list.append({ \ + 'From': unread_senders, \ + 'Subject': gmail_subject, \ + 'Snippet': gmail_snippet, \ + 'url': gmessage.getAttr('url'), \ + 'participation': gmessage.getAttr('participation'), \ + 'messages': gmessage.getAttr('messages'), \ + 'date': gmessage.getAttr('date')}) + self.gmail_last_time = int(gm.getTag('mailbox').getAttr( + 'result-time')) + + jid = gajim.get_jid_from_account(self.name) + log.debug(('You have %s new gmail e-mails on %s.') % (newmsgs, jid)) + self.dispatch('GMAIL_NOTIFY', (jid, newmsgs, gmail_messages_list)) + raise common.xmpp.NodeProcessed + + def _rosterItemExchangeCB(self, con, msg): + ''' XEP-0144 Roster Item Echange ''' + exchange_items_list = {} + jid_from = helpers.get_full_jid_from_iq(msg) + items_list = msg.getTag('x').getChildren() + if not items_list: + return + action = items_list[0].getAttr('action') + if action == None: + action = 'add' + for item in msg.getTag('x', + namespace=common.xmpp.NS_ROSTERX).getChildren(): + try: + jid = helpers.parse_jid(item.getAttr('jid')) + except common.helpers.InvalidFormat: + log.warn('Invalid JID: %s, ignoring it' % item.getAttr('jid')) + continue + name = item.getAttr('name') + contact = gajim.contacts.get_contact(self.name, jid) + groups = [] + same_groups = True + for group in item.getTags('group'): + groups.append(group.getData()) + # check that all suggested groups are in the groups we have for this + # contact + if not contact or group not in contact.groups: + same_groups = False + if contact: + # check that all groups we have for this contact are in the + # suggested groups + for group in contact.groups: + if group not in groups: + same_groups = False + if contact.sub in ('both', 'to') and same_groups: + continue + exchange_items_list[jid] = [] + exchange_items_list[jid].append(name) + exchange_items_list[jid].append(groups) + if exchange_items_list: + self.dispatch('ROSTERX', (action, exchange_items_list, jid_from)) + raise common.xmpp.NodeProcessed + + def _messageCB(self, con, msg): + '''Called when we receive a message''' + log.debug('MessageCB') + mtype = msg.getType() + + # check if the message is a roster item exchange (XEP-0144) + if msg.getTag('x', namespace=common.xmpp.NS_ROSTERX): + self._rosterItemExchangeCB(con, msg) + return + + # check if the message is a XEP-0070 confirmation request + if msg.getTag('confirm', namespace=common.xmpp.NS_HTTP_AUTH): + self._HttpAuthCB(con, msg) + return + + try: + frm = helpers.get_full_jid_from_iq(msg) + jid = helpers.get_jid_from_iq(msg) + except helpers.InvalidFormat: + self.dispatch('ERROR', (_('Invalid Jabber ID'), + _('A message from a non-valid JID arrived, it has been ignored.'))) + + addressTag = msg.getTag('addresses', namespace = common.xmpp.NS_ADDRESS) + + # Be sure it comes from one of our resource, else ignore address element + if addressTag and jid == gajim.get_jid_from_account(self.name): + address = addressTag.getTag('address', attrs={'type': 'ofrom'}) + if address: + try: + frm = helpers.parse_jid(address.getAttr('jid')) + except common.helpers.InvalidFormat: + log.warn('Invalid JID: %s, ignoring it' % address.getAttr('jid')) + return + jid = gajim.get_jid_without_resource(frm) + + # invitations + invite = None + encTag = msg.getTag('x', namespace=common.xmpp.NS_ENCRYPTED) + + if not encTag: + invite = msg.getTag('x', namespace = common.xmpp.NS_MUC_USER) + if invite and not invite.getTag('invite'): + invite = None + + # FIXME: Msn transport (CMSN1.2.1 and PyMSN0.10) do NOT RECOMMENDED + # invitation + # stanza (MUC XEP) remove in 2007, as we do not do NOT RECOMMENDED + xtags = msg.getTags('x') + for xtag in xtags: + if xtag.getNamespace() == common.xmpp.NS_CONFERENCE and not invite: + try: + room_jid = helpers.parse_jid(xtag.getAttr('jid')) + except common.helpers.InvalidFormat: + log.warn('Invalid JID: %s, ignoring it' % xtag.getAttr('jid')) + continue + is_continued = False + if xtag.getTag('continue'): + is_continued = True + self.dispatch('GC_INVITATION', (room_jid, frm, '', None, + is_continued)) + return + + thread_id = msg.getThread() + + if not mtype: + mtype = 'normal' + + msgtxt = msg.getBody() + + encrypted = False + xep_200_encrypted = msg.getTag('c', namespace=common.xmpp.NS_STANZA_CRYPTO) + + session = None + if mtype != 'groupchat': + session = self.get_or_create_session(frm, thread_id) + + if thread_id and not session.received_thread_id: + session.received_thread_id = True + + session.last_receive = time_time() + + # check if the message is a XEP-0020 feature negotiation request + if msg.getTag('feature', namespace=common.xmpp.NS_FEATURE): + if gajim.HAVE_PYCRYPTO: + feature = msg.getTag(name='feature', namespace=common.xmpp.NS_FEATURE) + form = common.xmpp.DataForm(node=feature.getTag('x')) + + if form['FORM_TYPE'] == 'urn:xmpp:ssn': + session.handle_negotiation(form) + else: + reply = msg.buildReply() + reply.setType('error') + + reply.addChild(feature) + err = common.xmpp.ErrorNode('service-unavailable', typ='cancel') + reply.addChild(node=err) + + con.send(reply) + + raise common.xmpp.NodeProcessed + + return + + if msg.getTag('init', namespace=common.xmpp.NS_ESESSION_INIT): + init = msg.getTag(name='init', namespace=common.xmpp.NS_ESESSION_INIT) + form = common.xmpp.DataForm(node=init.getTag('x')) + + session.handle_negotiation(form) + + raise common.xmpp.NodeProcessed + + tim = msg.getTimestamp() + tim = helpers.datetime_tuple(tim) + tim = localtime(timegm(tim)) + + if xep_200_encrypted: + encrypted = 'xep200' + + try: + msg = session.decrypt_stanza(msg) + msgtxt = msg.getBody() + except Exception: + self.dispatch('FAILED_DECRYPT', (frm, tim, session)) + + # Receipt requested + # TODO: We shouldn't answer if we're invisible! + contact = gajim.contacts.get_contact(self.name, jid) + nick = gajim.get_room_and_nick_from_fjid(frm)[1] + gc_contact = gajim.contacts.get_gc_contact(self.name, jid, nick) + if msg.getTag('request', namespace=common.xmpp.NS_RECEIPTS) \ + and gajim.config.get_per('accounts', self.name, + 'answer_receipts') and ((contact and contact.sub \ + not in (u'to', u'none')) or gc_contact) and mtype != 'error': + receipt = common.xmpp.Message(to=frm, typ='chat') + receipt.setID(msg.getID()) + receipt.setTag('received', + namespace='urn:xmpp:receipts') + + if thread_id: + receipt.setThread(thread_id) + con.send(receipt) + + # We got our message's receipt + if msg.getTag('received', namespace=common.xmpp.NS_RECEIPTS) \ + and session.control and gajim.config.get_per('accounts', + self.name, 'request_receipt'): + session.control.conv_textview.hide_xep0184_warning( + msg.getID()) + + if encTag and self.USE_GPG: + encmsg = encTag.getData() + + keyID = gajim.config.get_per('accounts', self.name, 'keyid') + if keyID: + def decrypt_thread(encmsg, keyID): + decmsg = self.gpg.decrypt(encmsg, keyID) + # \x00 chars are not allowed in C (so in GTK) + msgtxt = helpers.decode_string(decmsg.replace('\x00', '')) + encrypted = 'xep27' + return (msgtxt, encrypted) + gajim.thread_interface(decrypt_thread, [encmsg, keyID], + self._on_message_decrypted, [mtype, msg, session, frm, jid, + invite, tim]) + return + self._on_message_decrypted((msgtxt, encrypted), mtype, msg, session, frm, + jid, invite, tim) + + def _on_message_decrypted(self, output, mtype, msg, session, frm, jid, + invite, tim): + msgtxt, encrypted = output + if mtype == 'error': + self.dispatch_error_message(msg, msgtxt, session, frm, tim) + elif mtype == 'groupchat': + self.dispatch_gc_message(msg, frm, msgtxt, jid, tim) + elif invite is not None: + self.dispatch_invite_message(invite, frm) + else: + if isinstance(session, gajim.default_session_type): + session.received(frm, msgtxt, tim, encrypted, msg) + else: + session.received(msg) + # END messageCB + + # process and dispatch an error message + def dispatch_error_message(self, msg, msgtxt, session, frm, tim): + error_msg = msg.getErrorMsg() + + if not error_msg: + error_msg = msgtxt + msgtxt = None + + subject = msg.getSubject() + + if session.is_loggable(): + try: + gajim.logger.write('error', frm, error_msg, tim=tim, + subject=subject) + except exceptions.PysqliteOperationalError, e: + self.dispatch('ERROR', (_('Disk Write Error'), str(e))) + except exceptions.DatabaseMalformed: + pritext = _('Database Error') + sectext = _('The database file (%s) cannot be read. Try to repair ' + 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove ' + 'it (all history will be lost).') % common.logger.LOG_DB_PATH + self.dispatch('ERROR', (pritext, sectext)) + self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt, + tim, session)) + + # process and dispatch a groupchat message + def dispatch_gc_message(self, msg, frm, msgtxt, jid, tim): + has_timestamp = bool(msg.timestamp) + + subject = msg.getSubject() + + if subject is not None: + self.dispatch('GC_SUBJECT', (frm, subject, msgtxt, has_timestamp)) + return + + statusCode = msg.getStatusCode() + + if not msg.getTag('body'): # no + # It could be a config change. See + # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify + if msg.getTag('x'): + if statusCode != []: + self.dispatch('GC_CONFIG_CHANGE', (jid, statusCode)) + return + + # Ignore message from room in which we are not + if jid not in self.last_history_time: + return + + self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp, msg.getXHTML(), + statusCode)) + + tim_int = int(float(mktime(tim))) + if gajim.config.should_log(self.name, jid) and not \ + tim_int <= self.last_history_time[jid] and msgtxt and frm.find('/') >= 0: + # if frm.find('/') < 0, it means message comes from room itself + # usually it hold description and can be send at each connection + # so don't store it in logs + try: + gajim.logger.write('gc_msg', frm, msgtxt, tim=tim) + except exceptions.PysqliteOperationalError, e: + self.dispatch('ERROR', (_('Disk Write Error'), str(e))) + except exceptions.DatabaseMalformed: + pritext = _('Database Error') + sectext = _('The database file (%s) cannot be read. Try to repair ' + 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove ' + 'it (all history will be lost).') % common.logger.LOG_DB_PATH + self.dispatch('ERROR', (pritext, sectext)) + + def dispatch_invite_message(self, invite, frm): + item = invite.getTag('invite') + try: + jid_from = helpers.parse_jid(item.getAttr('from')) + except common.helpers.InvalidFormat: + log.warn('Invalid JID: %s, ignoring it' % item.getAttr('from')) + return + reason = item.getTagData('reason') + item = invite.getTag('password') + password = invite.getTagData('password') + + is_continued = False + if invite.getTag('invite').getTag('continue'): + is_continued = True + self.dispatch('GC_INVITATION', (frm, jid_from, reason, password, + is_continued)) + + def _presenceCB(self, con, prs): + '''Called when we receive a presence''' + ptype = prs.getType() + if ptype == 'available': + ptype = None + rfc_types = ('unavailable', 'error', 'subscribe', 'subscribed', + 'unsubscribe', 'unsubscribed') + if ptype and not ptype in rfc_types: + ptype = None + log.debug('PresenceCB: %s' % ptype) + if not self.connection or self.connected < 2: + log.debug('account is no more connected') + return + try: + who = helpers.get_full_jid_from_iq(prs) + except Exception: + if prs.getTag('error') and prs.getTag('error').getTag('jid-malformed'): + # wrong jid, we probably tried to change our nick in a room to a non + # valid one + who = str(prs.getFrom()) + jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) + self.dispatch('GC_MSG', (jid_stripped, + _('Nickname not allowed: %s') % resource, None, False, None, [])) + return + jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) + timestamp = None + id_ = prs.getID() + is_gc = False # is it a GC presence ? + sigTag = None + ns_muc_user_x = None + avatar_sha = None + # XEP-0172 User Nickname + user_nick = prs.getTagData('nick') + if not user_nick: + user_nick = '' + contact_nickname = None + transport_auto_auth = False + # XEP-0203 + delay_tag = prs.getTag('delay', namespace=common.xmpp.NS_DELAY2) + if delay_tag: + tim = prs.getTimestamp2() + tim = helpers.datetime_tuple(tim) + timestamp = localtime(timegm(tim)) + xtags = prs.getTags('x') + for x in xtags: + namespace = x.getNamespace() + if namespace.startswith(common.xmpp.NS_MUC): + is_gc = True + if namespace == common.xmpp.NS_MUC_USER and x.getTag('destroy'): + ns_muc_user_x = x + elif namespace == common.xmpp.NS_SIGNED: + sigTag = x + elif namespace == common.xmpp.NS_VCARD_UPDATE: + avatar_sha = x.getTagData('photo') + contact_nickname = x.getTagData('nickname') + elif namespace == common.xmpp.NS_DELAY and not timestamp: + # XEP-0091 + tim = prs.getTimestamp() + tim = helpers.datetime_tuple(tim) + timestamp = localtime(timegm(tim)) + elif namespace == 'http://delx.cjb.net/protocol/roster-subsync': + # see http://trac.gajim.org/ticket/326 + agent = gajim.get_server_from_jid(jid_stripped) + if self.connection.getRoster().getItem(agent): # to be sure it's a transport contact + transport_auto_auth = True + + if not is_gc and id_ and id_.startswith('gajim_muc_') and \ + ptype == 'error': + # Error presences may not include sent stanza, so we don't detect it's + # a muc preence. So detect it by ID + h = hmac.new(self.secret_hmac, jid_stripped).hexdigest()[:6] + if id_.split('_')[-1] == h: + is_gc = True + status = prs.getStatus() or '' + show = prs.getShow() + if show not in ('chat', 'away', 'xa', 'dnd'): + show = '' # We ignore unknown show + if not ptype and not show: + show = 'online' + elif ptype == 'unavailable': + show = 'offline' + + prio = prs.getPriority() + try: + prio = int(prio) + except Exception: + prio = 0 + keyID = '' + if sigTag and self.USE_GPG and ptype != 'error': + # error presences contain our own signature + # verify + sigmsg = sigTag.getData() + keyID = self.gpg.verify(status, sigmsg) + + if is_gc: + if ptype == 'error': + errcon = prs.getError() + errmsg = prs.getErrorMsg() + errcode = prs.getErrorCode() + room_jid, nick = gajim.get_room_and_nick_from_fjid(who) + + gc_control = gajim.interface.msg_win_mgr.get_gc_control(room_jid, + self.name) + + # If gc_control is missing - it may be minimized. Try to get it from + # there. If it's not there - then it's missing anyway and will + # remain set to None. + if gc_control is None: + minimized = gajim.interface.minimized_controls[self.name] + gc_control = minimized.get(room_jid) + + if errcode == '502': + # Internal Timeout: + self.dispatch('NOTIFY', (jid_stripped, 'error', errmsg, resource, + prio, keyID, timestamp, None)) + elif (errcode == '503'): + # maximum user number reached + self.dispatch('ERROR', (_('Unable to join group chat'), + _('Maximum number of users for %s has been reached') % \ + room_jid)) + elif (errcode == '401') or (errcon == 'not-authorized'): + # password required to join + self.dispatch('GC_PASSWORD_REQUIRED', (room_jid, nick)) + elif (errcode == '403') or (errcon == 'forbidden'): + # we are banned + self.dispatch('ERROR', (_('Unable to join group chat'), + _('You are banned from group chat %s.') % room_jid)) + elif (errcode == '404') or (errcon in ('item-not-found', + 'remote-server-not-found')): + if gc_control is None or gc_control.autorejoin is None: + # group chat does not exist + self.dispatch('ERROR', (_('Unable to join group chat'), + _('Group chat %s does not exist.') % room_jid)) + elif (errcode == '405') or (errcon == 'not-allowed'): + self.dispatch('ERROR', (_('Unable to join group chat'), + _('Group chat creation is restricted.'))) + elif (errcode == '406') or (errcon == 'not-acceptable'): + self.dispatch('ERROR', (_('Unable to join group chat'), + _('Your registered nickname must be used in group chat %s.') \ + % room_jid)) + elif (errcode == '407') or (errcon == 'registration-required'): + self.dispatch('ERROR', (_('Unable to join group chat'), + _('You are not in the members list in groupchat %s.') % \ + room_jid)) + elif (errcode == '409') or (errcon == 'conflict'): + # nick conflict + room_jid = gajim.get_room_from_fjid(who) + self.dispatch('ASK_NEW_NICK', (room_jid,)) + else: # print in the window the error + self.dispatch('ERROR_ANSWER', ('', jid_stripped, + errmsg, errcode)) + if not ptype or ptype == 'unavailable': + if gajim.config.get('log_contact_status_changes') and \ + gajim.config.should_log(self.name, jid_stripped): + gc_c = gajim.contacts.get_gc_contact(self.name, jid_stripped, + resource) + st = status or '' + if gc_c: + jid = gc_c.jid + else: + jid = prs.getJid() + if jid: + # we know real jid, save it in db + st += ' (%s)' % jid + try: + gajim.logger.write('gcstatus', who, st, show) + except exceptions.PysqliteOperationalError, e: + self.dispatch('ERROR', (_('Disk Write Error'), str(e))) + except exceptions.DatabaseMalformed: + pritext = _('Database Error') + sectext = _('The database file (%s) cannot be read. Try to ' + 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)' + ' or remove it (all history will be lost).') % \ + common.logger.LOG_DB_PATH + self.dispatch('ERROR', (pritext, sectext)) + if avatar_sha or avatar_sha == '': + if avatar_sha == '': + # contact has no avatar + puny_nick = helpers.sanitize_filename(resource) + gajim.interface.remove_avatar_files(jid_stripped, puny_nick) + # if it's a gc presence, don't ask vcard here. We may ask it to + # real jid in gui part. + if ns_muc_user_x: + # Room has been destroyed. see + # http://www.xmpp.org/extensions/xep-0045.html#destroyroom + reason = _('Room has been destroyed') + destroy = ns_muc_user_x.getTag('destroy') + r = destroy.getTagData('reason') + if r: + reason += ' (%s)' % r + if destroy.getAttr('jid'): + try: + jid = helpers.parse_jid(destroy.getAttr('jid')) + reason += '\n' + _('You can join this room instead: %s') \ + % jid + except common.helpers.InvalidFormat: + pass + statusCode = ['destroyed'] + else: + reason = prs.getReason() + statusCode = prs.getStatusCode() + self.dispatch('GC_NOTIFY', (jid_stripped, show, status, resource, + prs.getRole(), prs.getAffiliation(), prs.getJid(), + reason, prs.getActor(), statusCode, prs.getNewNick(), + avatar_sha)) + return + + if ptype == 'subscribe': + log.debug('subscribe request from %s' % who) + if gajim.config.get_per('accounts', self.name, 'autoauth') or \ + who.find('@') <= 0 or jid_stripped in self.jids_for_auto_auth or \ + transport_auto_auth: + if self.connection: + p = common.xmpp.Presence(who, 'subscribed') + p = self.add_sha(p) + self.connection.send(p) + if who.find('@') <= 0 or transport_auto_auth: + self.dispatch('NOTIFY', (jid_stripped, 'offline', 'offline', + resource, prio, keyID, timestamp, None)) + if transport_auto_auth: + self.automatically_added.append(jid_stripped) + self.request_subscription(jid_stripped, name = user_nick) + else: + if not status: + status = _('I would like to add you to my roster.') + self.dispatch('SUBSCRIBE', (jid_stripped, status, user_nick)) + elif ptype == 'subscribed': + if jid_stripped in self.automatically_added: + self.automatically_added.remove(jid_stripped) + else: + # detect a subscription loop + if jid_stripped not in self.subscribed_events: + self.subscribed_events[jid_stripped] = [] + self.subscribed_events[jid_stripped].append(time_time()) + block = False + if len(self.subscribed_events[jid_stripped]) > 5: + if time_time() - self.subscribed_events[jid_stripped][0] < 5: + block = True + self.subscribed_events[jid_stripped] = self.subscribed_events[jid_stripped][1:] + if block: + gajim.config.set_per('account', self.name, + 'dont_ack_subscription', True) + else: + self.dispatch('SUBSCRIBED', (jid_stripped, resource)) + # BE CAREFUL: no con.updateRosterItem() in a callback + log.debug(_('we are now subscribed to %s') % who) + elif ptype == 'unsubscribe': + log.debug(_('unsubscribe request from %s') % who) + elif ptype == 'unsubscribed': + log.debug(_('we are now unsubscribed from %s') % who) + # detect a unsubscription loop + if jid_stripped not in self.subscribed_events: + self.subscribed_events[jid_stripped] = [] + self.subscribed_events[jid_stripped].append(time_time()) + block = False + if len(self.subscribed_events[jid_stripped]) > 5: + if time_time() - self.subscribed_events[jid_stripped][0] < 5: + block = True + self.subscribed_events[jid_stripped] = self.subscribed_events[jid_stripped][1:] + if block: + gajim.config.set_per('account', self.name, 'dont_ack_subscription', + True) + else: + self.dispatch('UNSUBSCRIBED', jid_stripped) + elif ptype == 'error': + errmsg = prs.getError() + errcode = prs.getErrorCode() + if errcode != '502': # Internal Timeout: + # print in the window the error + self.dispatch('ERROR_ANSWER', ('', jid_stripped, + errmsg, errcode)) + if errcode != '409': # conflict # See #5120 + self.dispatch('NOTIFY', (jid_stripped, 'error', errmsg, resource, + prio, keyID, timestamp, None)) + + if ptype == 'unavailable': + for jid in [jid_stripped, who]: + if jid not in self.sessions: + continue + # automatically terminate sessions that they haven't sent a thread + # ID in, only if other part support thread ID + for sess in self.sessions[jid].values(): + if not sess.received_thread_id: + contact = gajim.contacts.get_contact(self.name, jid) + # FIXME: I don't know if this is the correct behavior here. + # Anyway, it is the old behavior when we assumed that + # not-existing contacts don't support anything + contact_exists = bool(contact) + session_supported = contact_exists and \ + contact.supports(common.xmpp.NS_SSN) or \ + contact.supports(common.xmpp.NS_ESESSION) + if session_supported: + sess.terminate() + del self.sessions[jid][sess.thread_id] + + if avatar_sha is not None and ptype != 'error': + if jid_stripped not in self.vcard_shas: + cached_vcard = self.get_cached_vcard(jid_stripped) + if cached_vcard and 'PHOTO' in cached_vcard and \ + 'SHA' in cached_vcard['PHOTO']: + self.vcard_shas[jid_stripped] = cached_vcard['PHOTO']['SHA'] + else: + self.vcard_shas[jid_stripped] = '' + if avatar_sha != self.vcard_shas[jid_stripped]: + # avatar has been updated + self.request_vcard(jid_stripped) + if not ptype or ptype == 'unavailable': + if gajim.config.get('log_contact_status_changes') and \ + gajim.config.should_log(self.name, jid_stripped): + try: + gajim.logger.write('status', jid_stripped, status, show) + except exceptions.PysqliteOperationalError, e: + self.dispatch('ERROR', (_('Disk Write Error'), str(e))) + except exceptions.DatabaseMalformed: + pritext = _('Database Error') + sectext = _('The database file (%s) cannot be read. Try to ' + 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup) ' + 'or remove it (all history will be lost).') % \ + common.logger.LOG_DB_PATH + self.dispatch('ERROR', (pritext, sectext)) + our_jid = gajim.get_jid_from_account(self.name) + if jid_stripped == our_jid and resource == self.server_resource: + # We got our own presence + self.dispatch('STATUS', show) + else: + self.dispatch('NOTIFY', (jid_stripped, show, status, resource, prio, + keyID, timestamp, contact_nickname)) + # END presenceCB + + def _StanzaArrivedCB(self, con, obj): + self.last_io = gajim.idlequeue.current_time() + + def _MucOwnerCB(self, con, iq_obj): + log.debug('MucOwnerCB') + qp = iq_obj.getQueryPayload() + node = None + for q in qp: + if q.getNamespace() == common.xmpp.NS_DATA: + node = q + if not node: + return + self.dispatch('GC_CONFIG', (helpers.get_full_jid_from_iq(iq_obj), node)) + + def _MucAdminCB(self, con, iq_obj): + log.debug('MucAdminCB') + items = iq_obj.getTag('query', namespace=common.xmpp.NS_MUC_ADMIN).\ + getTags('item') + users_dict = {} + for item in items: + if item.has_attr('jid') and item.has_attr('affiliation'): + try: + jid = helpers.parse_jid(item.getAttr('jid')) + except common.helpers.InvalidFormat: + log.warn('Invalid JID: %s, ignoring it' % item.getAttr('jid')) + continue + affiliation = item.getAttr('affiliation') + users_dict[jid] = {'affiliation': affiliation} + if item.has_attr('nick'): + users_dict[jid]['nick'] = item.getAttr('nick') + if item.has_attr('role'): + users_dict[jid]['role'] = item.getAttr('role') + reason = item.getTagData('reason') + if reason: + users_dict[jid]['reason'] = reason + + self.dispatch('GC_AFFILIATION', (helpers.get_full_jid_from_iq(iq_obj), + users_dict)) + + def _MucErrorCB(self, con, iq_obj): + log.debug('MucErrorCB') + jid = helpers.get_full_jid_from_iq(iq_obj) + errmsg = iq_obj.getError() + errcode = iq_obj.getErrorCode() + self.dispatch('MSGERROR', (jid, errcode, errmsg)) + + def _IqPingCB(self, con, iq_obj): + log.debug('IqPingCB') + if not self.connection or self.connected < 2: + return + iq_obj = iq_obj.buildReply('result') + self.connection.send(iq_obj) + raise common.xmpp.NodeProcessed + + def _PrivacySetCB(self, con, iq_obj): + ''' + Privacy lists (XEP 016) + + A list has been set + ''' + log.debug('PrivacySetCB') + if not self.connection or self.connected < 2: + return + result = iq_obj.buildReply('result') + q = result.getTag('query') + if q: + result.delChild(q) + self.connection.send(result) + raise common.xmpp.NodeProcessed + + def _getRoster(self): + log.debug('getRosterCB') + if not self.connection: + return + self.connection.getRoster(self._on_roster_set) + self.discoverItems(gajim.config.get_per('accounts', self.name, + 'hostname'), id_prefix='Gajim_') + if gajim.config.get_per('accounts', self.name, 'use_ft_proxies'): + self.discover_ft_proxies() + + def discover_ft_proxies(self): + cfg_proxies = gajim.config.get_per('accounts', self.name, + 'file_transfer_proxies') + our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name) + '/' +\ + self.server_resource) + if cfg_proxies: + proxies = [e.strip() for e in cfg_proxies.split(',')] + for proxy in proxies: + gajim.proxy65_manager.resolve(proxy, self.connection, our_jid) + + def _on_roster_set(self, roster): + roster_version = roster.version + received_from_server = roster.received_from_server + raw_roster = roster.getRaw() + roster = {} + our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name)) + if self.connected > 1 and self.continue_connect_info: + msg = self.continue_connect_info[1] + sign_msg = self.continue_connect_info[2] + signed = '' + send_first_presence = True + if sign_msg: + signed = self.get_signed_presence(msg, self._send_first_presence) + if signed is None: + self.dispatch('GPG_PASSWORD_REQUIRED', + (self._send_first_presence,)) + # _send_first_presence will be called when user enter passphrase + send_first_presence = False + if send_first_presence: + self._send_first_presence(signed) + + for jid in raw_roster: + try: + j = helpers.parse_jid(jid) + except Exception: + print >> sys.stderr, _('JID %s is not RFC compliant. It will not be added to your roster. Use roster management tools such as http://jru.jabberstudio.org/ to remove it') % jid + else: + infos = raw_roster[jid] + if jid != our_jid and (not infos['subscription'] or \ + infos['subscription'] == 'none') and (not infos['ask'] or \ + infos['ask'] == 'none') and not infos['name'] and \ + not infos['groups']: + # remove this useless item, it won't be shown in roster anyway + self.connection.getRoster().delItem(jid) + elif jid != our_jid: # don't add our jid + roster[j] = raw_roster[jid] + if gajim.jid_is_transport(jid) and \ + not gajim.get_transport_name_from_jid(jid): + # we can't determine which iconset to use + self.discoverInfo(jid) + + gajim.logger.replace_roster(self.name, roster_version, roster) + if received_from_server: + for contact in gajim.contacts.iter_contacts(self.name): + if not contact.is_groupchat() and contact.jid not in roster and \ + contact.jid != gajim.get_jid_from_account(self.name): + self.dispatch('ROSTER_INFO', (contact.jid, None, None, None, + ())) + for jid in roster: + self.dispatch('ROSTER_INFO', (jid, roster[jid]['name'], + roster[jid]['subscription'], roster[jid]['ask'], + roster[jid]['groups'])) + + def _send_first_presence(self, signed = ''): + show = self.continue_connect_info[0] + msg = self.continue_connect_info[1] + sign_msg = self.continue_connect_info[2] + if sign_msg and not signed: + signed = self.get_signed_presence(msg) + if signed is None: + self.dispatch('BAD_PASSPHRASE', ()) + self.USE_GPG = False + signed = '' + self.connected = gajim.SHOW_LIST.index(show) + sshow = helpers.get_xmpp_show(show) + # send our presence + if show == 'invisible': + self.send_invisible_presence(msg, signed, True) + return + priority = gajim.get_priority(self.name, sshow) + our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name)) + vcard = self.get_cached_vcard(our_jid) + if vcard and 'PHOTO' in vcard and 'SHA' in vcard['PHOTO']: + self.vcard_sha = vcard['PHOTO']['SHA'] + p = common.xmpp.Presence(typ = None, priority = priority, show = sshow) + p = self.add_sha(p) + if msg: + p.setStatus(msg) + if signed: + p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed) + + if self.connection: + self.connection.send(p) + self.priority = priority + self.dispatch('STATUS', show) + if self.vcard_supported: + # ask our VCard + self.request_vcard(None) + + # Get bookmarks from private namespace + self.get_bookmarks() + + # Get annotations from private namespace + self.get_annotations() + + # Inform GUI we just signed in + self.dispatch('SIGNED_IN', ()) + self.continue_connect_info = None + + def request_gmail_notifications(self): + if not self.connection or self.connected < 2: + return + # It's a gmail account, + # inform the server that we want e-mail notifications + our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name)) + log.debug(('%s is a gmail account. Setting option ' + 'to get e-mail notifications on the server.') % (our_jid)) + iq = common.xmpp.Iq(typ = 'set', to = our_jid) + iq.setAttr('id', 'MailNotify') + query = iq.setTag('usersetting') + query.setNamespace(common.xmpp.NS_GTALKSETTING) + query = query.setTag('mailnotifications') + query.setAttr('value', 'true') + self.connection.send(iq) + # Ask how many messages there are now + iq = common.xmpp.Iq(typ = 'get') + iq.setID(self.connection.getAnID()) + query = iq.setTag('query') + query.setNamespace(common.xmpp.NS_GMAILNOTIFY) + self.connection.send(iq) + + + def _search_fields_received(self, con, iq_obj): + jid = jid = helpers.get_jid_from_iq(iq_obj) + tag = iq_obj.getTag('query', namespace = common.xmpp.NS_SEARCH) + if not tag: + self.dispatch('SEARCH_FORM', (jid, None, False)) + return + df = tag.getTag('x', namespace = common.xmpp.NS_DATA) + if df: + self.dispatch('SEARCH_FORM', (jid, df, True)) + return + df = {} + for i in iq_obj.getQueryPayload(): + df[i.getName()] = i.getData() + self.dispatch('SEARCH_FORM', (jid, df, False)) + + def _StreamCB(self, con, obj): + if obj.getTag('conflict'): + # disconnected because of a resource conflict + self.dispatch('RESOURCE_CONFLICT', ()) + + def _register_handlers(self, con, con_type): + # try to find another way to register handlers in each class + # that defines handlers + con.RegisterHandler('message', self._messageCB) + con.RegisterHandler('presence', self._presenceCB) + con.RegisterHandler('presence', self._capsPresenceCB) + # We use makefirst so that this handler is called before _messageCB, and + # can prevent calling it when it's not needed. + # We also don't check for namespace, else it cannot stop _messageCB to be + # called + con.RegisterHandler('message', self._pubsubEventCB, makefirst=True) + con.RegisterHandler('iq', self._vCardCB, 'result', + common.xmpp.NS_VCARD) + con.RegisterHandler('iq', self._rosterSetCB, 'set', + common.xmpp.NS_ROSTER) + con.RegisterHandler('iq', self._siSetCB, 'set', + common.xmpp.NS_SI) + con.RegisterHandler('iq', self._rosterItemExchangeCB, 'set', + common.xmpp.NS_ROSTERX) + con.RegisterHandler('iq', self._siErrorCB, 'error', + common.xmpp.NS_SI) + con.RegisterHandler('iq', self._siResultCB, 'result', + common.xmpp.NS_SI) + con.RegisterHandler('iq', self._discoGetCB, 'get', + common.xmpp.NS_DISCO) + con.RegisterHandler('iq', self._bytestreamSetCB, 'set', + common.xmpp.NS_BYTESTREAM) + con.RegisterHandler('iq', self._bytestreamResultCB, 'result', + common.xmpp.NS_BYTESTREAM) + con.RegisterHandler('iq', self._bytestreamErrorCB, 'error', + common.xmpp.NS_BYTESTREAM) + con.RegisterHandler('iq', self._DiscoverItemsCB, 'result', + common.xmpp.NS_DISCO_ITEMS) + con.RegisterHandler('iq', self._DiscoverItemsErrorCB, 'error', + common.xmpp.NS_DISCO_ITEMS) + con.RegisterHandler('iq', self._DiscoverInfoCB, 'result', + common.xmpp.NS_DISCO_INFO) + con.RegisterHandler('iq', self._DiscoverInfoErrorCB, 'error', + common.xmpp.NS_DISCO_INFO) + con.RegisterHandler('iq', self._VersionCB, 'get', + common.xmpp.NS_VERSION) + con.RegisterHandler('iq', self._TimeCB, 'get', + common.xmpp.NS_TIME) + con.RegisterHandler('iq', self._TimeRevisedCB, 'get', + common.xmpp.NS_TIME_REVISED) + con.RegisterHandler('iq', self._LastCB, 'get', + common.xmpp.NS_LAST) + con.RegisterHandler('iq', self._LastResultCB, 'result', + common.xmpp.NS_LAST) + con.RegisterHandler('iq', self._VersionResultCB, 'result', + common.xmpp.NS_VERSION) + con.RegisterHandler('iq', self._TimeRevisedResultCB, 'result', + common.xmpp.NS_TIME_REVISED) + con.RegisterHandler('iq', self._MucOwnerCB, 'result', + common.xmpp.NS_MUC_OWNER) + con.RegisterHandler('iq', self._MucAdminCB, 'result', + common.xmpp.NS_MUC_ADMIN) + con.RegisterHandler('iq', self._PrivateCB, 'result', + common.xmpp.NS_PRIVATE) + con.RegisterHandler('iq', self._HttpAuthCB, 'get', + common.xmpp.NS_HTTP_AUTH) + con.RegisterHandler('iq', self._CommandExecuteCB, 'set', + common.xmpp.NS_COMMANDS) + con.RegisterHandler('iq', self._gMailNewMailCB, 'set', + common.xmpp.NS_GMAILNOTIFY) + con.RegisterHandler('iq', self._gMailQueryCB, 'result', + common.xmpp.NS_GMAILNOTIFY) + con.RegisterHandler('iq', self._DiscoverInfoGetCB, 'get', + common.xmpp.NS_DISCO_INFO) + con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get', + common.xmpp.NS_DISCO_ITEMS) + con.RegisterHandler('iq', self._IqPingCB, 'get', + common.xmpp.NS_PING) + con.RegisterHandler('iq', self._search_fields_received, 'result', + common.xmpp.NS_SEARCH) + con.RegisterHandler('iq', self._PrivacySetCB, 'set', + common.xmpp.NS_PRIVACY) + con.RegisterHandler('iq', self._ArchiveCB, ns=common.xmpp.NS_ARCHIVE) + con.RegisterHandler('iq', self._PubSubCB, 'result') + con.RegisterHandler('iq', self._PubSubErrorCB, 'error') + con.RegisterHandler('iq', self._JingleCB, 'result') + con.RegisterHandler('iq', self._JingleCB, 'error') + con.RegisterHandler('iq', self._JingleCB, 'set', + common.xmpp.NS_JINGLE) + con.RegisterHandler('iq', self._ErrorCB, 'error') + con.RegisterHandler('iq', self._IqCB) + con.RegisterHandler('iq', self._StanzaArrivedCB) + con.RegisterHandler('iq', self._ResultCB, 'result') + con.RegisterHandler('presence', self._StanzaArrivedCB) + con.RegisterHandler('message', self._StanzaArrivedCB) + con.RegisterHandler('unknown', self._StreamCB, 'urn:ietf:params:xml:ns:xmpp-streams', xmlns='http://etherx.jabber.org/streams') diff --git a/src/common/contacts.py b/src/common/contacts.py index 5a9df3499..76754ad9c 100644 --- a/src/common/contacts.py +++ b/src/common/contacts.py @@ -33,765 +33,763 @@ import caps from account import Account class XMPPEntity(object): - '''Base representation of entities in XMPP''' - - def __init__(self, jid, account, resource): - self.jid = jid - self.resource = resource - self.account = account + '''Base representation of entities in XMPP''' + + def __init__(self, jid, account, resource): + self.jid = jid + self.resource = resource + self.account = account class CommonContact(XMPPEntity): - - def __init__(self, jid, account, resource, show, status, name, our_chatstate, - composing_xep, chatstate, client_caps=None): - - XMPPEntity.__init__(self, jid, account, resource) - - self.show = show - self.status = status - self.name = name - - self.client_caps = client_caps or caps.NullClientCaps() - - # please read xep-85 http://www.xmpp.org/extensions/xep-0085.html - # we keep track of xep85 support with the peer by three extra states: - # None, False and 'ask' - # None if no info about peer - # False if peer does not support xep85 - # 'ask' if we sent the first 'active' chatstate and are waiting for reply - # this holds what WE SEND to contact (our current chatstate) - self.our_chatstate = our_chatstate - # tell which XEP we're using for composing state - # None = have to ask, XEP-0022 = use this xep, - # XEP-0085 = use this xep, False = no composing support - self.composing_xep = composing_xep - # this is contact's chatstate - self.chatstate = chatstate - - def get_full_jid(self): - raise NotImplementedError - - def get_shown_name(self): - raise NotImplementedError - def supports(self, requested_feature): - ''' - Returns True if the contact has advertised to support the feature - identified by the given namespace. False otherwise. - ''' - if self.show == 'offline': - # Unfortunately, if all resources are offline, the contact - # includes the last resource that was online. Check for its - # show, so we can be sure it's existant. Otherwise, we still - # return caps for a contact that has no resources left. - return False - else: - return caps.client_supports(self.client_caps, requested_feature) + def __init__(self, jid, account, resource, show, status, name, our_chatstate, + composing_xep, chatstate, client_caps=None): + + XMPPEntity.__init__(self, jid, account, resource) + + self.show = show + self.status = status + self.name = name + + self.client_caps = client_caps or caps.NullClientCaps() + + # please read xep-85 http://www.xmpp.org/extensions/xep-0085.html + # we keep track of xep85 support with the peer by three extra states: + # None, False and 'ask' + # None if no info about peer + # False if peer does not support xep85 + # 'ask' if we sent the first 'active' chatstate and are waiting for reply + # this holds what WE SEND to contact (our current chatstate) + self.our_chatstate = our_chatstate + # tell which XEP we're using for composing state + # None = have to ask, XEP-0022 = use this xep, + # XEP-0085 = use this xep, False = no composing support + self.composing_xep = composing_xep + # this is contact's chatstate + self.chatstate = chatstate + + def get_full_jid(self): + raise NotImplementedError + + def get_shown_name(self): + raise NotImplementedError + + def supports(self, requested_feature): + ''' + Returns True if the contact has advertised to support the feature + identified by the given namespace. False otherwise. + ''' + if self.show == 'offline': + # Unfortunately, if all resources are offline, the contact + # includes the last resource that was online. Check for its + # show, so we can be sure it's existant. Otherwise, we still + # return caps for a contact that has no resources left. + return False + else: + return caps.client_supports(self.client_caps, requested_feature) class Contact(CommonContact): - '''Information concerning each contact''' - def __init__(self, jid, account, name='', groups=[], show='', status='', sub='', - ask='', resource='', priority=0, keyID='', client_caps=None, - our_chatstate=None, chatstate=None, last_status_time=None, msg_id = None, - composing_xep=None): - - CommonContact.__init__(self, jid, account, resource, show, status, name, - our_chatstate, composing_xep, chatstate, client_caps=client_caps) - - self.contact_name = '' # nick choosen by contact - self.groups = [i for i in set(groups)] # filter duplicate values + '''Information concerning each contact''' + def __init__(self, jid, account, name='', groups=[], show='', status='', sub='', + ask='', resource='', priority=0, keyID='', client_caps=None, + our_chatstate=None, chatstate=None, last_status_time=None, msg_id = None, + composing_xep=None): - self.sub = sub - self.ask = ask - - self.priority = priority - self.keyID = keyID - self.msg_id = msg_id - self.last_status_time = last_status_time - - self.pep = {} + CommonContact.__init__(self, jid, account, resource, show, status, name, + our_chatstate, composing_xep, chatstate, client_caps=client_caps) - def get_full_jid(self): - if self.resource: - return self.jid + '/' + self.resource - return self.jid + self.contact_name = '' # nick choosen by contact + self.groups = [i for i in set(groups)] # filter duplicate values - def get_shown_name(self): - if self.name: - return self.name - if self.contact_name: - return self.contact_name - return self.jid.split('@')[0] + self.sub = sub + self.ask = ask - def get_shown_groups(self): - if self.is_observer(): - return [_('Observers')] - elif self.is_groupchat(): - return [_('Groupchats')] - elif self.is_transport(): - return [_('Transports')] - elif not self.groups: - return [_('General')] - else: - return self.groups + self.priority = priority + self.keyID = keyID + self.msg_id = msg_id + self.last_status_time = last_status_time - def is_hidden_from_roster(self): - '''if contact should not be visible in roster''' - # XEP-0162: http://www.xmpp.org/extensions/xep-0162.html - if self.is_transport(): - return False - if self.sub in ('both', 'to'): - return False - if self.sub in ('none', 'from') and self.ask == 'subscribe': - return False - if self.sub in ('none', 'from') and (self.name or len(self.groups)): - return False - if _('Not in Roster') in self.groups: - return False - return True + self.pep = {} - def is_observer(self): - # XEP-0162: http://www.xmpp.org/extensions/xep-0162.html - is_observer = False - if self.sub == 'from' and not self.is_transport()\ - and self.is_hidden_from_roster(): - is_observer = True - return is_observer + def get_full_jid(self): + if self.resource: + return self.jid + '/' + self.resource + return self.jid - def is_groupchat(self): - for account in common.gajim.gc_connected: - if self.jid in common.gajim.gc_connected[account]: - return True - return False + def get_shown_name(self): + if self.name: + return self.name + if self.contact_name: + return self.contact_name + return self.jid.split('@')[0] - def is_transport(self): - # if not '@' or '@' starts the jid then contact is transport - if self.jid.find('@') <= 0: - return True - return False + def get_shown_groups(self): + if self.is_observer(): + return [_('Observers')] + elif self.is_groupchat(): + return [_('Groupchats')] + elif self.is_transport(): + return [_('Transports')] + elif not self.groups: + return [_('General')] + else: + return self.groups + + def is_hidden_from_roster(self): + '''if contact should not be visible in roster''' + # XEP-0162: http://www.xmpp.org/extensions/xep-0162.html + if self.is_transport(): + return False + if self.sub in ('both', 'to'): + return False + if self.sub in ('none', 'from') and self.ask == 'subscribe': + return False + if self.sub in ('none', 'from') and (self.name or len(self.groups)): + return False + if _('Not in Roster') in self.groups: + return False + return True + + def is_observer(self): + # XEP-0162: http://www.xmpp.org/extensions/xep-0162.html + is_observer = False + if self.sub == 'from' and not self.is_transport()\ + and self.is_hidden_from_roster(): + is_observer = True + return is_observer + + def is_groupchat(self): + for account in common.gajim.gc_connected: + if self.jid in common.gajim.gc_connected[account]: + return True + return False + + def is_transport(self): + # if not '@' or '@' starts the jid then contact is transport + if self.jid.find('@') <= 0: + return True + return False class GC_Contact(CommonContact): - '''Information concerning each groupchat contact''' - def __init__(self, room_jid, account, name='', show='', status='', role='', - affiliation='', jid='', resource='', our_chatstate=None, - composing_xep=None, chatstate=None): - - CommonContact.__init__(self, jid, account, resource, show, status, name, - our_chatstate, composing_xep, chatstate) - - self.room_jid = room_jid - self.role = role - self.affiliation = affiliation - - def get_full_jid(self): - return self.room_jid + '/' + self.name + '''Information concerning each groupchat contact''' + def __init__(self, room_jid, account, name='', show='', status='', role='', + affiliation='', jid='', resource='', our_chatstate=None, + composing_xep=None, chatstate=None): + + CommonContact.__init__(self, jid, account, resource, show, status, name, + our_chatstate, composing_xep, chatstate) + + self.room_jid = room_jid + self.role = role + self.affiliation = affiliation + + def get_full_jid(self): + return self.room_jid + '/' + self.name + + def get_shown_name(self): + return self.name + + def as_contact(self): + '''Create a Contact instance from this GC_Contact instance''' + return Contact(jid=self.get_full_jid(), account=self.account, + resource=self.resource, name=self.name, groups=[], show=self.show, + status=self.status, sub='none', client_caps=self.client_caps) - def get_shown_name(self): - return self.name - - def as_contact(self): - '''Create a Contact instance from this GC_Contact instance''' - return Contact(jid=self.get_full_jid(), account=self.account, - resource=self.resource, name=self.name, groups=[], show=self.show, - status=self.status, sub='none', client_caps=self.client_caps) - class Contacts: - '''Information concerning all contacts and groupchat contacts''' - def __init__(self): - - self._metacontact_manager = MetacontactManager(self) - self._accounts = {} - - def change_account_name(self, old_name, new_name): - self._accounts[new_name] = self._accounts[old_name] - self._accounts[new_name].name = new_name - del self._accounts[old_name] - - self._metacontact_manager.change_account_name(old_name, new_name) + '''Information concerning all contacts and groupchat contacts''' + def __init__(self): - def add_account(self, account_name): - self._accounts[account_name] = Account(account_name, - Contacts_New(), GC_Contacts()) - self._metacontact_manager.add_account(account_name) + self._metacontact_manager = MetacontactManager(self) + self._accounts = {} - def get_accounts(self): - return self._accounts.keys() + def change_account_name(self, old_name, new_name): + self._accounts[new_name] = self._accounts[old_name] + self._accounts[new_name].name = new_name + del self._accounts[old_name] - def remove_account(self, account): - del self._accounts[account] - self._metacontact_manager.remove_account(account) + self._metacontact_manager.change_account_name(old_name, new_name) - def create_contact(self, jid, account, name='', groups=[], show='', status='', - sub='', ask='', resource='', priority=0, keyID='', client_caps=None, - our_chatstate=None, chatstate=None, last_status_time=None, - composing_xep=None): - account = self._accounts.get(account, account) # Use Account object if available - return Contact(jid=jid, account=account, name=name, groups=groups, - show=show, status=status, sub=sub, ask=ask, resource=resource, priority=priority, - keyID=keyID, client_caps=client_caps, our_chatstate=our_chatstate, - chatstate=chatstate, last_status_time=last_status_time, - composing_xep=composing_xep) - - def create_self_contact(self, jid, account, resource, show, status, priority, - name='', keyID=''): - conn = common.gajim.connections[account] - nick = name or common.gajim.nicks[account] - account = self._accounts.get(account, account) # Use Account object if available - self_contact = self.create_contact(jid=jid, account=account, - name=nick, groups=['self_contact'], show=show, status=status, - sub='both', ask='none', priority=priority, keyID=keyID, - resource=resource) - self_contact.pep = conn.pep - return self_contact - - def create_not_in_roster_contact(self, jid, account, resource='', name='', keyID=''): - account = self._accounts.get(account, account) # Use Account object if available - return self.create_contact(jid=jid, account=account, resource=resource, - name=name, groups=[_('Not in Roster')], show='not in roster', - status='', sub='none', keyID=keyID) + def add_account(self, account_name): + self._accounts[account_name] = Account(account_name, + Contacts_New(), GC_Contacts()) + self._metacontact_manager.add_account(account_name) - def copy_contact(self, contact): - return self.create_contact(jid=contact.jid, account=contact.account, - name=contact.name, groups=contact.groups, show=contact.show, status=contact.status, - sub=contact.sub, ask=contact.ask, resource=contact.resource, - priority=contact.priority, keyID=contact.keyID, - client_caps=contact.client_caps, our_chatstate=contact.our_chatstate, - chatstate=contact.chatstate, last_status_time=contact.last_status_time) + def get_accounts(self): + return self._accounts.keys() - def add_contact(self, account, contact): - if account not in self._accounts: - self.add_account(account) - return self._accounts[account].contacts.add_contact(contact) + def remove_account(self, account): + del self._accounts[account] + self._metacontact_manager.remove_account(account) - def remove_contact(self, account, contact): - if account not in self._accounts: - return - return self._accounts[account].contacts.remove_contact(contact) + def create_contact(self, jid, account, name='', groups=[], show='', status='', + sub='', ask='', resource='', priority=0, keyID='', client_caps=None, + our_chatstate=None, chatstate=None, last_status_time=None, + composing_xep=None): + account = self._accounts.get(account, account) # Use Account object if available + return Contact(jid=jid, account=account, name=name, groups=groups, + show=show, status=status, sub=sub, ask=ask, resource=resource, priority=priority, + keyID=keyID, client_caps=client_caps, our_chatstate=our_chatstate, + chatstate=chatstate, last_status_time=last_status_time, + composing_xep=composing_xep) - def remove_jid(self, account, jid, remove_meta=True): - self._accounts[account].contacts.remove_jid(jid) - if remove_meta: - self._metacontact_manager.remove_metacontact(account, jid) - - def get_contacts(self, account, jid): - return self._accounts[account].contacts.get_contacts(jid) + def create_self_contact(self, jid, account, resource, show, status, priority, + name='', keyID=''): + conn = common.gajim.connections[account] + nick = name or common.gajim.nicks[account] + account = self._accounts.get(account, account) # Use Account object if available + self_contact = self.create_contact(jid=jid, account=account, + name=nick, groups=['self_contact'], show=show, status=status, + sub='both', ask='none', priority=priority, keyID=keyID, + resource=resource) + self_contact.pep = conn.pep + return self_contact - def get_contact(self, account, jid, resource=None): - return self._accounts[account].contacts.get_contact(jid, resource=resource) + def create_not_in_roster_contact(self, jid, account, resource='', name='', keyID=''): + account = self._accounts.get(account, account) # Use Account object if available + return self.create_contact(jid=jid, account=account, resource=resource, + name=name, groups=[_('Not in Roster')], show='not in roster', + status='', sub='none', keyID=keyID) - def iter_contacts(self, account): - for contact in self._accounts[account].contacts.iter_contacts(): - yield contact + def copy_contact(self, contact): + return self.create_contact(jid=contact.jid, account=contact.account, + name=contact.name, groups=contact.groups, show=contact.show, status=contact.status, + sub=contact.sub, ask=contact.ask, resource=contact.resource, + priority=contact.priority, keyID=contact.keyID, + client_caps=contact.client_caps, our_chatstate=contact.our_chatstate, + chatstate=contact.chatstate, last_status_time=contact.last_status_time) - def get_contact_from_full_jid(self, account, fjid): - return self._accounts[account].contacts.get_contact_from_full_jid(fjid) - - def get_first_contact_from_jid(self, account, jid): - return self._accounts[account].contacts.get_first_contact_from_jid(jid) + def add_contact(self, account, contact): + if account not in self._accounts: + self.add_account(account) + return self._accounts[account].contacts.add_contact(contact) - def get_contacts_from_group(self, account, group): - return self._accounts[account].contacts.get_contacts_from_group(group) - - def get_jid_list(self, account): - return self._accounts[account].contacts.get_jid_list() + def remove_contact(self, account, contact): + if account not in self._accounts: + return + return self._accounts[account].contacts.remove_contact(contact) - def change_contact_jid(self, old_jid, new_jid, account): - return self._accounts[account].change_contact_jid(old_jid, new_jid) + def remove_jid(self, account, jid, remove_meta=True): + self._accounts[account].contacts.remove_jid(jid) + if remove_meta: + self._metacontact_manager.remove_metacontact(account, jid) - def get_highest_prio_contact_from_contacts(self, contacts): - if not contacts: - return None - prim_contact = contacts[0] - for contact in contacts[1:]: - if int(contact.priority) > int(prim_contact.priority): - prim_contact = contact - return prim_contact + def get_contacts(self, account, jid): + return self._accounts[account].contacts.get_contacts(jid) - def get_contact_with_highest_priority(self, account, jid): - contacts = self.get_contacts(account, jid) - if not contacts and '/' in jid: - # jid may be a fake jid, try it - room, nick = jid.split('/', 1) - contact = self.get_gc_contact(account, room, nick) - return contact - return self.get_highest_prio_contact_from_contacts(contacts) - - def get_nb_online_total_contacts(self, accounts=[], groups=[]): - '''Returns the number of online contacts and the total number of - contacts''' - if accounts == []: - accounts = self.get_accounts() - nbr_online = 0 - nbr_total = 0 - for account in accounts: - our_jid = common.gajim.get_jid_from_account(account) - for jid in self.get_jid_list(account): - if jid == our_jid: - continue - if common.gajim.jid_is_transport(jid) and not \ - _('Transports') in groups: - # do not count transports - continue - if self.has_brother(account, jid, accounts) and not \ - self.is_big_brother(account, jid, accounts): - # count metacontacts only once - continue - contact = self.get_contact_with_highest_priority(account, jid) - if _('Not in roster') in contact.groups: - continue - in_groups = False - if groups == []: - in_groups = True - else: - for group in groups: - if group in contact.get_shown_groups(): - in_groups = True - break + def get_contact(self, account, jid, resource=None): + return self._accounts[account].contacts.get_contact(jid, resource=resource) - if in_groups: - if contact.show not in ('offline', 'error'): - nbr_online += 1 - nbr_total += 1 - return nbr_online, nbr_total + def iter_contacts(self, account): + for contact in self._accounts[account].contacts.iter_contacts(): + yield contact - def is_pm_from_jid(self, account, jid): - '''Returns True if the given jid is a private message jid''' - if jid in self._contacts[account]: - return False - return True + def get_contact_from_full_jid(self, account, fjid): + return self._accounts[account].contacts.get_contact_from_full_jid(fjid) - def is_pm_from_contact(self, account, contact): - '''Returns True if the given contact is a private message contact''' - if isinstance(contact, Contact): - return False - return True - - def __getattr__(self, attr_name): - # Only called if self has no attr_name - if hasattr(self._metacontact_manager, attr_name): - return getattr(self._metacontact_manager, attr_name) - else: - raise AttributeError(attr_name) - - def create_gc_contact(self, room_jid, account, name='', show='', status='', - role='', affiliation='', jid='', resource=''): - account = self._accounts.get(account, account) # Use Account object if available - return GC_Contact(room_jid, account, name, show, status, role, affiliation, jid, - resource) + def get_first_contact_from_jid(self, account, jid): + return self._accounts[account].contacts.get_first_contact_from_jid(jid) - def add_gc_contact(self, account, gc_contact): - return self._accounts[account].gc_contacts.add_gc_contact(gc_contact) + def get_contacts_from_group(self, account, group): + return self._accounts[account].contacts.get_contacts_from_group(group) - def remove_gc_contact(self, account, gc_contact): - return self._accounts[account].gc_contacts.remove_gc_contact(gc_contact) + def get_jid_list(self, account): + return self._accounts[account].contacts.get_jid_list() - def remove_room(self, account, room_jid): - return self._accounts[account].gc_contacts.remove_room(room_jid) + def change_contact_jid(self, old_jid, new_jid, account): + return self._accounts[account].change_contact_jid(old_jid, new_jid) - def get_gc_list(self, account): - return self._accounts[account].gc_contacts.get_gc_list() + def get_highest_prio_contact_from_contacts(self, contacts): + if not contacts: + return None + prim_contact = contacts[0] + for contact in contacts[1:]: + if int(contact.priority) > int(prim_contact.priority): + prim_contact = contact + return prim_contact - def get_nick_list(self, account, room_jid): - return self._accounts[account].gc_contacts.get_nick_list(room_jid) + def get_contact_with_highest_priority(self, account, jid): + contacts = self.get_contacts(account, jid) + if not contacts and '/' in jid: + # jid may be a fake jid, try it + room, nick = jid.split('/', 1) + contact = self.get_gc_contact(account, room, nick) + return contact + return self.get_highest_prio_contact_from_contacts(contacts) + + def get_nb_online_total_contacts(self, accounts=[], groups=[]): + '''Returns the number of online contacts and the total number of + contacts''' + if accounts == []: + accounts = self.get_accounts() + nbr_online = 0 + nbr_total = 0 + for account in accounts: + our_jid = common.gajim.get_jid_from_account(account) + for jid in self.get_jid_list(account): + if jid == our_jid: + continue + if common.gajim.jid_is_transport(jid) and not \ + _('Transports') in groups: + # do not count transports + continue + if self.has_brother(account, jid, accounts) and not \ + self.is_big_brother(account, jid, accounts): + # count metacontacts only once + continue + contact = self.get_contact_with_highest_priority(account, jid) + if _('Not in roster') in contact.groups: + continue + in_groups = False + if groups == []: + in_groups = True + else: + for group in groups: + if group in contact.get_shown_groups(): + in_groups = True + break + + if in_groups: + if contact.show not in ('offline', 'error'): + nbr_online += 1 + nbr_total += 1 + return nbr_online, nbr_total + + def is_pm_from_jid(self, account, jid): + '''Returns True if the given jid is a private message jid''' + if jid in self._contacts[account]: + return False + return True + + def is_pm_from_contact(self, account, contact): + '''Returns True if the given contact is a private message contact''' + if isinstance(contact, Contact): + return False + return True + + def __getattr__(self, attr_name): + # Only called if self has no attr_name + if hasattr(self._metacontact_manager, attr_name): + return getattr(self._metacontact_manager, attr_name) + else: + raise AttributeError(attr_name) + + def create_gc_contact(self, room_jid, account, name='', show='', status='', + role='', affiliation='', jid='', resource=''): + account = self._accounts.get(account, account) # Use Account object if available + return GC_Contact(room_jid, account, name, show, status, role, affiliation, jid, + resource) + + def add_gc_contact(self, account, gc_contact): + return self._accounts[account].gc_contacts.add_gc_contact(gc_contact) + + def remove_gc_contact(self, account, gc_contact): + return self._accounts[account].gc_contacts.remove_gc_contact(gc_contact) + + def remove_room(self, account, room_jid): + return self._accounts[account].gc_contacts.remove_room(room_jid) + + def get_gc_list(self, account): + return self._accounts[account].gc_contacts.get_gc_list() + + def get_nick_list(self, account, room_jid): + return self._accounts[account].gc_contacts.get_nick_list(room_jid) + + def get_gc_contact(self, account, room_jid, nick): + return self._accounts[account].gc_contacts.get_gc_contact(room_jid, nick) + + def get_nb_role_total_gc_contacts(self, account, room_jid, role): + return self._accounts[account].gc_contacts.get_nb_role_total_gc_contacts(room_jid, role) - def get_gc_contact(self, account, room_jid, nick): - return self._accounts[account].gc_contacts.get_gc_contact(room_jid, nick) - def get_nb_role_total_gc_contacts(self, account, room_jid, role): - return self._accounts[account].gc_contacts.get_nb_role_total_gc_contacts(room_jid, role) - - class Contacts_New(): - - def __init__(self): - # list of contacts {jid1: [C1, C2]}, } one Contact per resource - self._contacts = {} - - def add_contact(self, contact): - if contact.jid not in self._contacts: - self._contacts[contact.jid] = [contact] - return - contacts = self._contacts[contact.jid] - # We had only one that was offline, remove it - if len(contacts) == 1 and contacts[0].show == 'offline': - # Do not use self.remove_contact: it deteles - # self._contacts[account][contact.jid] - contacts.remove(contacts[0]) - # If same JID with same resource already exists, use the new one - for c in contacts: - if c.resource == contact.resource: - self.remove_contact(c) - break - contacts.append(contact) - - def remove_contact(self, contact): - if contact.jid not in self._contacts: - return - if contact in self._contacts[contact.jid]: - self._contacts[contact.jid].remove(contact) - if len(self._contacts[contact.jid]) == 0: - del self._contacts[contact.jid] - - def remove_jid(self, jid): - '''Removes all contacts for a given jid''' - if jid not in self._contacts: - return - del self._contacts[jid] - - def get_contacts(self, jid): - '''Returns the list of contact instances for this jid.''' - if jid in self._contacts: - return self._contacts[jid] - else: - return [] - - def get_contact(self, jid, resource=None): - ### WARNING ### - # This function returns a *RANDOM* resource if resource = None! - # Do *NOT* use if you need to get the contact to which you - # send a message for example, as a bare JID in Jabber means - # highest available resource, which this function ignores! - '''Returns the contact instance for the given resource if it's given else - the first contact is no resource is given or None if there is not''' - if jid in self._contacts: - if not resource: - return self._contacts[jid][0] - for c in self._contacts[jid]: - if c.resource == resource: - return c - return None - def iter_contacts(self): - for jid in self._contacts.keys(): - for contact in self._contacts[jid][:]: - yield contact - - def get_jid_list(self): - return self._contacts.keys() - - def get_contact_from_full_jid(self, fjid): - ''' Get Contact object for specific resource of given jid''' - barejid, resource = common.gajim.get_room_and_nick_from_fjid(fjid) - return self.get_contact(barejid, resource) - - def get_first_contact_from_jid(self, jid): - if jid in self._contacts: - return self._contacts[jid][0] - return None + def __init__(self): + # list of contacts {jid1: [C1, C2]}, } one Contact per resource + self._contacts = {} - def get_contacts_from_group(self, group): - '''Returns all contacts in the given group''' - group_contacts = [] - for jid in self._contacts: - contacts = self.get_contacts(jid) - if group in contacts[0].groups: - group_contacts += contacts - return group_contacts + def add_contact(self, contact): + if contact.jid not in self._contacts: + self._contacts[contact.jid] = [contact] + return + contacts = self._contacts[contact.jid] + # We had only one that was offline, remove it + if len(contacts) == 1 and contacts[0].show == 'offline': + # Do not use self.remove_contact: it deteles + # self._contacts[account][contact.jid] + contacts.remove(contacts[0]) + # If same JID with same resource already exists, use the new one + for c in contacts: + if c.resource == contact.resource: + self.remove_contact(c) + break + contacts.append(contact) - def change_contact_jid(self, old_jid, new_jid): - if old_jid not in self._contacts: - return - self._contacts[new_jid] = [] - for _contact in self._contacts[old_jid]: - _contact.jid = new_jid - self._contacts[new_jid].append(_contact) - del self._contacts[old_jid] + def remove_contact(self, contact): + if contact.jid not in self._contacts: + return + if contact in self._contacts[contact.jid]: + self._contacts[contact.jid].remove(contact) + if len(self._contacts[contact.jid]) == 0: + del self._contacts[contact.jid] + + def remove_jid(self, jid): + '''Removes all contacts for a given jid''' + if jid not in self._contacts: + return + del self._contacts[jid] + + def get_contacts(self, jid): + '''Returns the list of contact instances for this jid.''' + if jid in self._contacts: + return self._contacts[jid] + else: + return [] + + def get_contact(self, jid, resource=None): + ### WARNING ### + # This function returns a *RANDOM* resource if resource = None! + # Do *NOT* use if you need to get the contact to which you + # send a message for example, as a bare JID in Jabber means + # highest available resource, which this function ignores! + '''Returns the contact instance for the given resource if it's given else + the first contact is no resource is given or None if there is not''' + if jid in self._contacts: + if not resource: + return self._contacts[jid][0] + for c in self._contacts[jid]: + if c.resource == resource: + return c + return None + + def iter_contacts(self): + for jid in self._contacts.keys(): + for contact in self._contacts[jid][:]: + yield contact + + def get_jid_list(self): + return self._contacts.keys() + + def get_contact_from_full_jid(self, fjid): + ''' Get Contact object for specific resource of given jid''' + barejid, resource = common.gajim.get_room_and_nick_from_fjid(fjid) + return self.get_contact(barejid, resource) + + def get_first_contact_from_jid(self, jid): + if jid in self._contacts: + return self._contacts[jid][0] + return None + + def get_contacts_from_group(self, group): + '''Returns all contacts in the given group''' + group_contacts = [] + for jid in self._contacts: + contacts = self.get_contacts(jid) + if group in contacts[0].groups: + group_contacts += contacts + return group_contacts + + def change_contact_jid(self, old_jid, new_jid): + if old_jid not in self._contacts: + return + self._contacts[new_jid] = [] + for _contact in self._contacts[old_jid]: + _contact.jid = new_jid + self._contacts[new_jid].append(_contact) + del self._contacts[old_jid] class GC_Contacts(): - - def __init__(self): - # list of contacts that are in gc {room_jid: {nick: C}}} - self._rooms = {} - - def add_gc_contact(self, gc_contact): - if gc_contact.room_jid not in self._rooms: - self._rooms[gc_contact.room_jid] = {gc_contact.name: gc_contact} - else: - self._rooms[gc_contact.room_jid][gc_contact.name] = gc_contact - def remove_gc_contact(self, gc_contact): - if gc_contact.room_jid not in self._rooms: - return - if gc_contact.name not in self._rooms[gc_contact.room_jid]: - return - del self._rooms[gc_contact.room_jid][gc_contact.name] - # It was the last nick in room ? - if not len(self._rooms[gc_contact.room_jid]): - del self._rooms[gc_contact.room_jid] + def __init__(self): + # list of contacts that are in gc {room_jid: {nick: C}}} + self._rooms = {} - def remove_room(self, room_jid): - if room_jid not in self._rooms: - return - del self._rooms[room_jid] + def add_gc_contact(self, gc_contact): + if gc_contact.room_jid not in self._rooms: + self._rooms[gc_contact.room_jid] = {gc_contact.name: gc_contact} + else: + self._rooms[gc_contact.room_jid][gc_contact.name] = gc_contact - def get_gc_list(self): - return self._rooms.keys() + def remove_gc_contact(self, gc_contact): + if gc_contact.room_jid not in self._rooms: + return + if gc_contact.name not in self._rooms[gc_contact.room_jid]: + return + del self._rooms[gc_contact.room_jid][gc_contact.name] + # It was the last nick in room ? + if not len(self._rooms[gc_contact.room_jid]): + del self._rooms[gc_contact.room_jid] - def get_nick_list(self, room_jid): - gc_list = self.get_gc_list() - if not room_jid in gc_list: - return [] - return self._rooms[room_jid].keys() + def remove_room(self, room_jid): + if room_jid not in self._rooms: + return + del self._rooms[room_jid] - def get_gc_contact(self, room_jid, nick): - nick_list = self.get_nick_list(room_jid) - if not nick in nick_list: - return None - return self._rooms[room_jid][nick] + def get_gc_list(self): + return self._rooms.keys() + + def get_nick_list(self, room_jid): + gc_list = self.get_gc_list() + if not room_jid in gc_list: + return [] + return self._rooms[room_jid].keys() + + def get_gc_contact(self, room_jid, nick): + nick_list = self.get_nick_list(room_jid) + if not nick in nick_list: + return None + return self._rooms[room_jid][nick] + + def get_nb_role_total_gc_contacts(self, room_jid, role): + '''Returns the number of group chat contacts for the given role and the + total number of group chat contacts''' + if room_jid not in self._rooms: + return 0, 0 + nb_role = nb_total = 0 + for nick in self._rooms[room_jid]: + if self._rooms[room_jid][nick].role == role: + nb_role += 1 + nb_total += 1 + return nb_role, nb_total - def get_nb_role_total_gc_contacts(self, room_jid, role): - '''Returns the number of group chat contacts for the given role and the - total number of group chat contacts''' - if room_jid not in self._rooms: - return 0, 0 - nb_role = nb_total = 0 - for nick in self._rooms[room_jid]: - if self._rooms[room_jid][nick].role == role: - nb_role += 1 - nb_total += 1 - return nb_role, nb_total - class MetacontactManager(): - - def __init__(self, contacts): - self._metacontacts_tags = {} - self._contacts = contacts - - def change_account_name(self, old_name, new_name): - self._metacontacts_tags[new_name] = self._metacontacts_tags[old_name] - del self._metacontacts_tags[old_name] - - def add_account(self, account): - if account not in self._metacontacts_tags: - self._metacontacts_tags[account] = {} - - def remove_account(self, account): - del self._metacontacts_tags[account] - - def define_metacontacts(self, account, tags_list): - self._metacontacts_tags[account] = tags_list - def _get_new_metacontacts_tag(self, jid): - if not jid in self._metacontacts_tags: - return jid - #FIXME: can this append ? - assert False + def __init__(self, contacts): + self._metacontacts_tags = {} + self._contacts = contacts - def iter_metacontacts_families(self, account): - for tag in self._metacontacts_tags[account]: - family = self._get_metacontacts_family_from_tag(account, tag) - yield family + def change_account_name(self, old_name, new_name): + self._metacontacts_tags[new_name] = self._metacontacts_tags[old_name] + del self._metacontacts_tags[old_name] - def _get_metacontacts_tag(self, account, jid): - '''Returns the tag of a jid''' - if not account in self._metacontacts_tags: - return None - for tag in self._metacontacts_tags[account]: - for data in self._metacontacts_tags[account][tag]: - if data['jid'] == jid: - return tag - return None + def add_account(self, account): + if account not in self._metacontacts_tags: + self._metacontacts_tags[account] = {} - def add_metacontact(self, brother_account, brother_jid, account, jid, order=None): - tag = self._get_metacontacts_tag(brother_account, brother_jid) - if not tag: - tag = self._get_new_metacontacts_tag(brother_jid) - self._metacontacts_tags[brother_account][tag] = [{'jid': brother_jid, - 'tag': tag}] - if brother_account != account: - common.gajim.connections[brother_account].store_metacontacts( - self._metacontacts_tags[brother_account]) - # be sure jid has no other tag - old_tag = self._get_metacontacts_tag(account, jid) - while old_tag: - self.remove_metacontact(account, jid) - old_tag = self._get_metacontacts_tag(account, jid) - if tag not in self._metacontacts_tags[account]: - self._metacontacts_tags[account][tag] = [{'jid': jid, 'tag': tag}] - else: - if order: - self._metacontacts_tags[account][tag].append({'jid': jid, - 'tag': tag, 'order': order}) - else: - self._metacontacts_tags[account][tag].append({'jid': jid, - 'tag': tag}) - common.gajim.connections[account].store_metacontacts( - self._metacontacts_tags[account]) + def remove_account(self, account): + del self._metacontacts_tags[account] - def remove_metacontact(self, account, jid): - if not account in self._metacontacts_tags: - return None - - found = None - for tag in self._metacontacts_tags[account]: - for data in self._metacontacts_tags[account][tag]: - if data['jid'] == jid: - found = data - break - if found: - self._metacontacts_tags[account][tag].remove(found) - common.gajim.connections[account].store_metacontacts( - self._metacontacts_tags[account]) - break + def define_metacontacts(self, account, tags_list): + self._metacontacts_tags[account] = tags_list - def has_brother(self, account, jid, accounts): - tag = self._get_metacontacts_tag(account, jid) - if not tag: - return False - meta_jids = self._get_metacontacts_jids(tag, accounts) - return len(meta_jids) > 1 or len(meta_jids[account]) > 1 + def _get_new_metacontacts_tag(self, jid): + if not jid in self._metacontacts_tags: + return jid + #FIXME: can this append ? + assert False - def is_big_brother(self, account, jid, accounts): - family = self.get_metacontacts_family(account, jid) - if family: - nearby_family = [data for data in family - if account in accounts] - bb_data = self._get_metacontacts_big_brother(nearby_family) - if bb_data['jid'] == jid and bb_data['account'] == account: - return True - return False + def iter_metacontacts_families(self, account): + for tag in self._metacontacts_tags[account]: + family = self._get_metacontacts_family_from_tag(account, tag) + yield family - def _get_metacontacts_jids(self, tag, accounts): - '''Returns all jid for the given tag in the form {acct: [jid1, jid2],.}''' - answers = {} - for account in self._metacontacts_tags: - if tag in self._metacontacts_tags[account]: - if account not in accounts: - continue - answers[account] = [] - for data in self._metacontacts_tags[account][tag]: - answers[account].append(data['jid']) - return answers + def _get_metacontacts_tag(self, account, jid): + '''Returns the tag of a jid''' + if not account in self._metacontacts_tags: + return None + for tag in self._metacontacts_tags[account]: + for data in self._metacontacts_tags[account][tag]: + if data['jid'] == jid: + return tag + return None - def get_metacontacts_family(self, account, jid): - '''return the family of the given jid, including jid in the form: - [{'account': acct, 'jid': jid, 'order': order}, ] - 'order' is optional''' - tag = self._get_metacontacts_tag(account, jid) - return self._get_metacontacts_family_from_tag(account, tag) + def add_metacontact(self, brother_account, brother_jid, account, jid, order=None): + tag = self._get_metacontacts_tag(brother_account, brother_jid) + if not tag: + tag = self._get_new_metacontacts_tag(brother_jid) + self._metacontacts_tags[brother_account][tag] = [{'jid': brother_jid, + 'tag': tag}] + if brother_account != account: + common.gajim.connections[brother_account].store_metacontacts( + self._metacontacts_tags[brother_account]) + # be sure jid has no other tag + old_tag = self._get_metacontacts_tag(account, jid) + while old_tag: + self.remove_metacontact(account, jid) + old_tag = self._get_metacontacts_tag(account, jid) + if tag not in self._metacontacts_tags[account]: + self._metacontacts_tags[account][tag] = [{'jid': jid, 'tag': tag}] + else: + if order: + self._metacontacts_tags[account][tag].append({'jid': jid, + 'tag': tag, 'order': order}) + else: + self._metacontacts_tags[account][tag].append({'jid': jid, + 'tag': tag}) + common.gajim.connections[account].store_metacontacts( + self._metacontacts_tags[account]) - def _get_metacontacts_family_from_tag(self, account, tag): - if not tag: - return [] - answers = [] - for account in self._metacontacts_tags: - if tag in self._metacontacts_tags[account]: - for data in self._metacontacts_tags[account][tag]: - data['account'] = account - answers.append(data) - return answers + def remove_metacontact(self, account, jid): + if not account in self._metacontacts_tags: + return None - def _compare_metacontacts(self, data1, data2): - '''compare 2 metacontacts. - Data is {'jid': jid, 'account': account, 'order': order} - order is optional''' - jid1 = data1['jid'] - jid2 = data2['jid'] - account1 = data1['account'] - account2 = data2['account'] - contact1 = self._contacts.get_contact_with_highest_priority(account1, jid1) - contact2 = self._contacts.get_contact_with_highest_priority(account2, jid2) - show_list = ['not in roster', 'error', 'offline', 'invisible', 'dnd', - 'xa', 'away', 'chat', 'online', 'requested', 'message'] - # contact can be null when a jid listed in the metacontact data - # is not in our roster - if not contact1: - if contact2: - return -1 # prefer the known contact - else: - show1 = 0 - priority1 = 0 - else: - show1 = show_list.index(contact1.show) - priority1 = contact1.priority - if not contact2: - if contact1: - return 1 # prefer the known contact - else: - show2 = 0 - priority2 = 0 - else: - show2 = show_list.index(contact2.show) - priority2 = contact2.priority - # If only one is offline, it's always second - if show1 > 2 and show2 < 3: - return 1 - if show2 > 2 and show1 < 3: - return -1 - if 'order' in data1 and 'order' in data2: - if data1['order'] > data2['order']: - return 1 - if data1['order'] < data2['order']: - return -1 - if 'order' in data1: - return 1 - if 'order' in data2: - return -1 - transport1 = common.gajim.get_transport_name_from_jid(jid1) - transport2 = common.gajim.get_transport_name_from_jid(jid2) - if transport2 and not transport1: - return 1 - if transport1 and not transport2: - return -1 - if show1 > show2: - return 1 - if show2 > show1: - return -1 - if priority1 > priority2: - return 1 - if priority2 > priority1: - return -1 - server1 = common.gajim.get_server_from_jid(jid1) - server2 = common.gajim.get_server_from_jid(jid2) - myserver1 = common.gajim.config.get_per('accounts', account1, 'hostname') - myserver2 = common.gajim.config.get_per('accounts', account2, 'hostname') - if server1 == myserver1: - if server2 != myserver2: - return 1 - elif server2 == myserver2: - return -1 - if jid1 > jid2: - return 1 - if jid2 > jid1: - return -1 - # If all is the same, compare accounts, they can't be the same - if account1 > account2: - return 1 - if account2 > account1: - return -1 - return 0 - - def get_nearby_family_and_big_brother(self, family, account): - '''Return the nearby family and its Big Brother + found = None + for tag in self._metacontacts_tags[account]: + for data in self._metacontacts_tags[account][tag]: + if data['jid'] == jid: + found = data + break + if found: + self._metacontacts_tags[account][tag].remove(found) + common.gajim.connections[account].store_metacontacts( + self._metacontacts_tags[account]) + break - Nearby family is the part of the family that is grouped with the metacontact. - A metacontact may be over different accounts. If accounts are not merged - then the given family is split account wise. + def has_brother(self, account, jid, accounts): + tag = self._get_metacontacts_tag(account, jid) + if not tag: + return False + meta_jids = self._get_metacontacts_jids(tag, accounts) + return len(meta_jids) > 1 or len(meta_jids[account]) > 1 - (nearby_family, big_brother_jid, big_brother_account) - ''' - if common.gajim.config.get('mergeaccounts'): - # group all together - nearby_family = family - else: - # we want one nearby_family per account - nearby_family = [data for data in family if account == data['account']] + def is_big_brother(self, account, jid, accounts): + family = self.get_metacontacts_family(account, jid) + if family: + nearby_family = [data for data in family + if account in accounts] + bb_data = self._get_metacontacts_big_brother(nearby_family) + if bb_data['jid'] == jid and bb_data['account'] == account: + return True + return False - big_brother_data = self._get_metacontacts_big_brother(nearby_family) - big_brother_jid = big_brother_data['jid'] - big_brother_account = big_brother_data['account'] + def _get_metacontacts_jids(self, tag, accounts): + '''Returns all jid for the given tag in the form {acct: [jid1, jid2],.}''' + answers = {} + for account in self._metacontacts_tags: + if tag in self._metacontacts_tags[account]: + if account not in accounts: + continue + answers[account] = [] + for data in self._metacontacts_tags[account][tag]: + answers[account].append(data['jid']) + return answers - return (nearby_family, big_brother_jid, big_brother_account) + def get_metacontacts_family(self, account, jid): + '''return the family of the given jid, including jid in the form: + [{'account': acct, 'jid': jid, 'order': order}, ] + 'order' is optional''' + tag = self._get_metacontacts_tag(account, jid) + return self._get_metacontacts_family_from_tag(account, tag) - def _get_metacontacts_big_brother(self, family): - '''which of the family will be the big brother under wich all - others will be ?''' - family.sort(cmp=self._compare_metacontacts) - return family[-1] - -# vim: se ts=3: + def _get_metacontacts_family_from_tag(self, account, tag): + if not tag: + return [] + answers = [] + for account in self._metacontacts_tags: + if tag in self._metacontacts_tags[account]: + for data in self._metacontacts_tags[account][tag]: + data['account'] = account + answers.append(data) + return answers + + def _compare_metacontacts(self, data1, data2): + '''compare 2 metacontacts. + Data is {'jid': jid, 'account': account, 'order': order} + order is optional''' + jid1 = data1['jid'] + jid2 = data2['jid'] + account1 = data1['account'] + account2 = data2['account'] + contact1 = self._contacts.get_contact_with_highest_priority(account1, jid1) + contact2 = self._contacts.get_contact_with_highest_priority(account2, jid2) + show_list = ['not in roster', 'error', 'offline', 'invisible', 'dnd', + 'xa', 'away', 'chat', 'online', 'requested', 'message'] + # contact can be null when a jid listed in the metacontact data + # is not in our roster + if not contact1: + if contact2: + return -1 # prefer the known contact + else: + show1 = 0 + priority1 = 0 + else: + show1 = show_list.index(contact1.show) + priority1 = contact1.priority + if not contact2: + if contact1: + return 1 # prefer the known contact + else: + show2 = 0 + priority2 = 0 + else: + show2 = show_list.index(contact2.show) + priority2 = contact2.priority + # If only one is offline, it's always second + if show1 > 2 and show2 < 3: + return 1 + if show2 > 2 and show1 < 3: + return -1 + if 'order' in data1 and 'order' in data2: + if data1['order'] > data2['order']: + return 1 + if data1['order'] < data2['order']: + return -1 + if 'order' in data1: + return 1 + if 'order' in data2: + return -1 + transport1 = common.gajim.get_transport_name_from_jid(jid1) + transport2 = common.gajim.get_transport_name_from_jid(jid2) + if transport2 and not transport1: + return 1 + if transport1 and not transport2: + return -1 + if show1 > show2: + return 1 + if show2 > show1: + return -1 + if priority1 > priority2: + return 1 + if priority2 > priority1: + return -1 + server1 = common.gajim.get_server_from_jid(jid1) + server2 = common.gajim.get_server_from_jid(jid2) + myserver1 = common.gajim.config.get_per('accounts', account1, 'hostname') + myserver2 = common.gajim.config.get_per('accounts', account2, 'hostname') + if server1 == myserver1: + if server2 != myserver2: + return 1 + elif server2 == myserver2: + return -1 + if jid1 > jid2: + return 1 + if jid2 > jid1: + return -1 + # If all is the same, compare accounts, they can't be the same + if account1 > account2: + return 1 + if account2 > account1: + return -1 + return 0 + + def get_nearby_family_and_big_brother(self, family, account): + '''Return the nearby family and its Big Brother + + Nearby family is the part of the family that is grouped with the metacontact. + A metacontact may be over different accounts. If accounts are not merged + then the given family is split account wise. + + (nearby_family, big_brother_jid, big_brother_account) + ''' + if common.gajim.config.get('mergeaccounts'): + # group all together + nearby_family = family + else: + # we want one nearby_family per account + nearby_family = [data for data in family if account == data['account']] + + big_brother_data = self._get_metacontacts_big_brother(nearby_family) + big_brother_jid = big_brother_data['jid'] + big_brother_account = big_brother_data['account'] + + return (nearby_family, big_brother_jid, big_brother_account) + + def _get_metacontacts_big_brother(self, family): + '''which of the family will be the big brother under wich all + others will be ?''' + family.sort(cmp=self._compare_metacontacts) + return family[-1] diff --git a/src/common/crypto.py b/src/common/crypto.py index 785b753bb..c787df6aa 100644 --- a/src/common/crypto.py +++ b/src/common/crypto.py @@ -26,82 +26,80 @@ from hashlib import sha256 as SHA256 # convert a large integer to a big-endian bitstring def encode_mpi(n): - if n >= 256: - return encode_mpi(n / 256) + chr(n % 256) - else: - return chr(n) + if n >= 256: + return encode_mpi(n / 256) + chr(n % 256) + else: + return chr(n) # convert a large integer to a big-endian bitstring, padded with \x00s to # a multiple of 16 bytes def encode_mpi_with_padding(n): - return pad_to_multiple(encode_mpi(n), 16, '\x00', True) + return pad_to_multiple(encode_mpi(n), 16, '\x00', True) # pad 'string' to a multiple of 'multiple_of' with 'char'. # pad on the left if 'left', otherwise pad on the right. def pad_to_multiple(string, multiple_of, char, left): - mod = len(string) % multiple_of - if mod == 0: - return string - else: - padding = (multiple_of - mod) * char + mod = len(string) % multiple_of + if mod == 0: + return string + else: + padding = (multiple_of - mod) * char - if left: - return padding + string - else: - return string + padding + if left: + return padding + string + else: + return string + padding # convert a big-endian bitstring to an integer def decode_mpi(s): - if len(s) == 0: - return 0 - else: - return 256 * decode_mpi(s[:-1]) + ord(s[-1]) + if len(s) == 0: + return 0 + else: + return 256 * decode_mpi(s[:-1]) + ord(s[-1]) def sha256(string): - sh = SHA256() - sh.update(string) - return sh.digest() + sh = SHA256() + sh.update(string) + return sh.digest() base28_chr = "acdefghikmopqruvwxy123456789" def sas_28x5(m_a, form_b): - sha = sha256(m_a + form_b + 'Short Authentication String') - lsb24 = decode_mpi(sha[-3:]) - return base28(lsb24) + sha = sha256(m_a + form_b + 'Short Authentication String') + lsb24 = decode_mpi(sha[-3:]) + return base28(lsb24) def base28(n): - if n >= 28: - return base28(n / 28) + base28_chr[n % 28] - else: - return base28_chr[n] + if n >= 28: + return base28(n / 28) + base28_chr[n % 28] + else: + return base28_chr[n] def random_bytes(bytes_): - return os.urandom(bytes_) + return os.urandom(bytes_) def generate_nonce(): - return random_bytes(8) + return random_bytes(8) # generate a random number between 'bottom' and 'top' def srand(bottom, top): - # minimum number of bytes needed to represent that range - bytes = int(math.ceil(math.log(top - bottom, 256))) + # minimum number of bytes needed to represent that range + bytes = int(math.ceil(math.log(top - bottom, 256))) - # in retrospect, this is horribly inadequate. - return (decode_mpi(random_bytes(bytes)) % (top - bottom)) + bottom + # in retrospect, this is horribly inadequate. + return (decode_mpi(random_bytes(bytes)) % (top - bottom)) + bottom # a faster version of (base ** exp) % mod -# taken from +# taken from def powmod(base, exp, mod): - square = base % mod - result = 1 + square = base % mod + result = 1 - while exp > 0: - if exp & 1: # exponent is odd - result = (result * square) % mod + while exp > 0: + if exp & 1: # exponent is odd + result = (result * square) % mod - square = (square * square) % mod - exp /= 2 + square = (square * square) % mod + exp /= 2 - return result - -# vim: se ts=3: + return result diff --git a/src/common/dataforms.py b/src/common/dataforms.py index bd9c3e15f..008de3730 100644 --- a/src/common/dataforms.py +++ b/src/common/dataforms.py @@ -36,525 +36,523 @@ class WrongFieldValue(Error): pass # helper class to change class of already existing object class ExtendedNode(xmpp.Node, object): - @classmethod - def __new__(cls, *a, **b): - if 'extend' not in b.keys() or not b['extend']: - return object.__new__(cls) + @classmethod + def __new__(cls, *a, **b): + if 'extend' not in b.keys() or not b['extend']: + return object.__new__(cls) - extend = b['extend'] - assert issubclass(cls, extend.__class__) - extend.__class__ = cls - return extend + extend = b['extend'] + assert issubclass(cls, extend.__class__) + extend.__class__ = cls + return extend # helper decorator to create properties in cleaner way def nested_property(f): - ret = f() - p = {'doc': f.__doc__} - for v in ('fget', 'fset', 'fdel', 'doc'): - if v in ret.keys(): p[v]=ret[v] - return property(**p) + ret = f() + p = {'doc': f.__doc__} + for v in ('fget', 'fset', 'fdel', 'doc'): + if v in ret.keys(): p[v]=ret[v] + return property(**p) # helper to create fields from scratch def Field(typ, **attrs): - ''' Helper function to create a field of given type. ''' - f = { - 'boolean': BooleanField, - 'fixed': StringField, - 'hidden': StringField, - 'text-private': StringField, - 'text-single': StringField, - 'jid-multi': ListMultiField, - 'jid-single': ListSingleField, - 'list-multi': ListMultiField, - 'list-single': ListSingleField, - 'text-multi': TextMultiField, - }[typ](typ=typ, **attrs) - return f + ''' Helper function to create a field of given type. ''' + f = { + 'boolean': BooleanField, + 'fixed': StringField, + 'hidden': StringField, + 'text-private': StringField, + 'text-single': StringField, + 'jid-multi': ListMultiField, + 'jid-single': ListSingleField, + 'list-multi': ListMultiField, + 'list-single': ListSingleField, + 'text-multi': TextMultiField, + }[typ](typ=typ, **attrs) + return f def ExtendField(node): - ''' Helper function to extend a node to field of appropriate type. ''' - # when validation (XEP-122) will go in, we could have another classes - # like DateTimeField - so that dicts in Field() and ExtendField() will - # be different... - typ=node.getAttr('type') - f = { - 'boolean': BooleanField, - 'fixed': StringField, - 'hidden': StringField, - 'text-private': StringField, - 'text-single': StringField, - 'jid-multi': ListMultiField, - 'jid-single': ListSingleField, - 'list-multi': ListMultiField, - 'list-single': ListSingleField, - 'text-multi': TextMultiField, - } - if typ not in f: - typ = 'text-single' - return f[typ](extend=node) + ''' Helper function to extend a node to field of appropriate type. ''' + # when validation (XEP-122) will go in, we could have another classes + # like DateTimeField - so that dicts in Field() and ExtendField() will + # be different... + typ=node.getAttr('type') + f = { + 'boolean': BooleanField, + 'fixed': StringField, + 'hidden': StringField, + 'text-private': StringField, + 'text-single': StringField, + 'jid-multi': ListMultiField, + 'jid-single': ListSingleField, + 'list-multi': ListMultiField, + 'list-single': ListSingleField, + 'text-multi': TextMultiField, + } + if typ not in f: + typ = 'text-single' + return f[typ](extend=node) def ExtendForm(node): - ''' Helper function to extend a node to form of appropriate type. ''' - if node.getTag('reported') is not None: - return MultipleDataForm(extend=node) - else: - return SimpleDataForm(extend=node) + ''' Helper function to extend a node to form of appropriate type. ''' + if node.getTag('reported') is not None: + return MultipleDataForm(extend=node) + else: + return SimpleDataForm(extend=node) class DataField(ExtendedNode): - """ Keeps data about one field - var, field type, labels, instructions... - Base class for different kinds of fields. Use Field() function to - construct one of these. """ + """ Keeps data about one field - var, field type, labels, instructions... + Base class for different kinds of fields. Use Field() function to + construct one of these. """ - def __init__(self, typ=None, var=None, value=None, label=None, desc=None, - required=False, options=None, extend=None): + def __init__(self, typ=None, var=None, value=None, label=None, desc=None, + required=False, options=None, extend=None): - if extend is None: - ExtendedNode.__init__(self, 'field') + if extend is None: + ExtendedNode.__init__(self, 'field') - self.type = typ - self.var = var - if value is not None: - self.value = value - if label is not None: - self.label = label - if desc is not None: - self.desc = desc - self.required = required - self.options = options + self.type = typ + self.var = var + if value is not None: + self.value = value + if label is not None: + self.label = label + if desc is not None: + self.desc = desc + self.required = required + self.options = options - @nested_property - def type(): - '''Type of field. Recognized values are: 'boolean', 'fixed', 'hidden', - 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi', - 'text-private', 'text-single'. If you set this to something different, - DataField will store given name, but treat all data as text-single.''' - def fget(self): - t = self.getAttr('type') - if t is None: - return 'text-single' - return t + @nested_property + def type(): + '''Type of field. Recognized values are: 'boolean', 'fixed', 'hidden', + 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi', + 'text-private', 'text-single'. If you set this to something different, + DataField will store given name, but treat all data as text-single.''' + def fget(self): + t = self.getAttr('type') + if t is None: + return 'text-single' + return t - def fset(self, value): - assert isinstance(value, basestring) - self.setAttr('type', value) + def fset(self, value): + assert isinstance(value, basestring) + self.setAttr('type', value) - return locals() + return locals() - @nested_property - def var(): - '''Field identifier.''' - def fget(self): - return self.getAttr('var') + @nested_property + def var(): + '''Field identifier.''' + def fget(self): + return self.getAttr('var') - def fset(self, value): - assert isinstance(value, basestring) - self.setAttr('var', value) + def fset(self, value): + assert isinstance(value, basestring) + self.setAttr('var', value) - def fdel(self): - self.delAttr('var') + def fdel(self): + self.delAttr('var') - return locals() + return locals() - @nested_property - def label(): - '''Human-readable field name.''' - def fget(self): - l = self.getAttr('label') - if not l: - l = self.var - return l + @nested_property + def label(): + '''Human-readable field name.''' + def fget(self): + l = self.getAttr('label') + if not l: + l = self.var + return l - def fset(self, value): - assert isinstance(value, basestring) - self.setAttr('label', value) + def fset(self, value): + assert isinstance(value, basestring) + self.setAttr('label', value) - def fdel(self): - if self.getAttr('label'): - self.delAttr('label') + def fdel(self): + if self.getAttr('label'): + self.delAttr('label') - return locals() + return locals() - @nested_property - def description(): - '''Human-readable description of field meaning.''' - def fget(self): - return self.getTagData('desc') or u'' + @nested_property + def description(): + '''Human-readable description of field meaning.''' + def fget(self): + return self.getTagData('desc') or u'' - def fset(self, value): - assert isinstance(value, basestring) - if value == '': - fdel(self) - else: - self.setTagData('desc', value) + def fset(self, value): + assert isinstance(value, basestring) + if value == '': + fdel(self) + else: + self.setTagData('desc', value) - def fdel(self): - t = self.getTag('desc') - if t is not None: - self.delChild(t) + def fdel(self): + t = self.getTag('desc') + if t is not None: + self.delChild(t) - return locals() + return locals() - @nested_property - def required(): - '''Controls whether this field required to fill. Boolean.''' - def fget(self): - return bool(self.getTag('required')) + @nested_property + def required(): + '''Controls whether this field required to fill. Boolean.''' + def fget(self): + return bool(self.getTag('required')) - def fset(self, value): - t = self.getTag('required') - if t and not value: - self.delChild(t) - elif not t and value: - self.addChild('required') + def fset(self, value): + t = self.getTag('required') + if t and not value: + self.delChild(t) + elif not t and value: + self.addChild('required') - return locals() + return locals() class BooleanField(DataField): - @nested_property - def value(): - '''Value of field. May contain True, False or None.''' - def fget(self): - v = self.getTagData('value') - if v in ('0', 'false'): - return False - if v in ('1', 'true'): - return True - if v is None: - return False # default value is False - raise WrongFieldValue + @nested_property + def value(): + '''Value of field. May contain True, False or None.''' + def fget(self): + v = self.getTagData('value') + if v in ('0', 'false'): + return False + if v in ('1', 'true'): + return True + if v is None: + return False # default value is False + raise WrongFieldValue - def fset(self, value): - self.setTagData('value', value and '1' or '0') + def fset(self, value): + self.setTagData('value', value and '1' or '0') - def fdel(self, value): - t = self.getTag('value') - if t is not None: - self.delChild(t) + def fdel(self, value): + t = self.getTag('value') + if t is not None: + self.delChild(t) - return locals() + return locals() class StringField(DataField): - ''' Covers fields of types: fixed, hidden, text-private, text-single. ''' - @nested_property - def value(): - '''Value of field. May be any unicode string.''' - def fget(self): - return self.getTagData('value') or u'' + ''' Covers fields of types: fixed, hidden, text-private, text-single. ''' + @nested_property + def value(): + '''Value of field. May be any unicode string.''' + def fget(self): + return self.getTagData('value') or u'' - def fset(self, value): - assert isinstance(value, basestring) - if value == '': - return fdel(self) - self.setTagData('value', value) + def fset(self, value): + assert isinstance(value, basestring) + if value == '': + return fdel(self) + self.setTagData('value', value) - def fdel(self): - try: - self.delChild(self.getTag('value')) - except ValueError: # if there already were no value tag - pass + def fdel(self): + try: + self.delChild(self.getTag('value')) + except ValueError: # if there already were no value tag + pass - return locals() + return locals() class ListField(DataField): - '''Covers fields of types: jid-multi, jid-single, list-multi, list-single.''' - @nested_property - def options(): - '''Options.''' - def fget(self): - options = [] - for element in self.getTags('option'): - v = element.getTagData('value') - if v is None: - raise WrongFieldValue - l = element.getAttr('label') - if not l: - l = v - options.append((l, v)) - return options + '''Covers fields of types: jid-multi, jid-single, list-multi, list-single.''' + @nested_property + def options(): + '''Options.''' + def fget(self): + options = [] + for element in self.getTags('option'): + v = element.getTagData('value') + if v is None: + raise WrongFieldValue + l = element.getAttr('label') + if not l: + l = v + options.append((l, v)) + return options - def fset(self, values): - fdel(self) - for value, label in values: - self.addChild('option', {'label': label}).setTagData('value', value) + def fset(self, values): + fdel(self) + for value, label in values: + self.addChild('option', {'label': label}).setTagData('value', value) - def fdel(self): - for element in self.getTags('option'): - self.delChild(element) + def fdel(self): + for element in self.getTags('option'): + self.delChild(element) - return locals() + return locals() - def iter_options(self): - for element in self.iterTags('option'): - v = element.getTagData('value') - if v is None: - raise WrongFieldValue - l = element.getAttr('label') - if not l: - l = v - yield (v, l) + def iter_options(self): + for element in self.iterTags('option'): + v = element.getTagData('value') + if v is None: + raise WrongFieldValue + l = element.getAttr('label') + if not l: + l = v + yield (v, l) class ListSingleField(ListField, StringField): - '''Covers list-single and jid-single fields.''' - pass + '''Covers list-single and jid-single fields.''' + pass class ListMultiField(ListField): - '''Covers list-multi and jid-multi fields.''' - @nested_property - def values(): - '''Values held in field.''' - def fget(self): - values = [] - for element in self.getTags('value'): - values.append(element.getData()) - return values + '''Covers list-multi and jid-multi fields.''' + @nested_property + def values(): + '''Values held in field.''' + def fget(self): + values = [] + for element in self.getTags('value'): + values.append(element.getData()) + return values - def fset(self, values): - fdel(self) - for value in values: - self.addChild('value').setData(value) + def fset(self, values): + fdel(self) + for value in values: + self.addChild('value').setData(value) - def fdel(self): - for element in self.getTags('value'): - self.delChild(element) + def fdel(self): + for element in self.getTags('value'): + self.delChild(element) - return locals() + return locals() - def iter_values(self): - for element in self.getTags('value'): - yield element.getData() + def iter_values(self): + for element in self.getTags('value'): + yield element.getData() class TextMultiField(DataField): - @nested_property - def value(): - '''Value held in field.''' - def fget(self): - value = u'' - for element in self.iterTags('value'): - value += '\n' + element.getData() - return value[1:] + @nested_property + def value(): + '''Value held in field.''' + def fget(self): + value = u'' + for element in self.iterTags('value'): + value += '\n' + element.getData() + return value[1:] - def fset(self, value): - fdel(self) - if value == '': - return - for line in value.split('\n'): - self.addChild('value').setData(line) + def fset(self, value): + fdel(self) + if value == '': + return + for line in value.split('\n'): + self.addChild('value').setData(line) - def fdel(self): - for element in self.getTags('value'): - self.delChild(element) + def fdel(self): + for element in self.getTags('value'): + self.delChild(element) - return locals() + return locals() class DataRecord(ExtendedNode): - '''The container for data fields - an xml element which has DataField - elements as children.''' - def __init__(self, fields=None, associated=None, extend=None): - self.associated = associated - self.vars = {} - if extend is None: - # we have to build this object from scratch - xmpp.Node.__init__(self) + '''The container for data fields - an xml element which has DataField + elements as children.''' + def __init__(self, fields=None, associated=None, extend=None): + self.associated = associated + self.vars = {} + if extend is None: + # we have to build this object from scratch + xmpp.Node.__init__(self) - if fields is not None: - self.fields = fields - else: - # we already have xmpp.Node inside - try to convert all - # fields into DataField objects - if fields is None: - for field in self.iterTags('field'): - if not isinstance(field, DataField): - ExtendField(field) - self.vars[field.var] = field - else: - for field in self.getTags('field'): - self.delChild(field) - self.fields = fields + if fields is not None: + self.fields = fields + else: + # we already have xmpp.Node inside - try to convert all + # fields into DataField objects + if fields is None: + for field in self.iterTags('field'): + if not isinstance(field, DataField): + ExtendField(field) + self.vars[field.var] = field + else: + for field in self.getTags('field'): + self.delChild(field) + self.fields = fields - @nested_property - def fields(): - '''List of fields in this record.''' - def fget(self): - return self.getTags('field') + @nested_property + def fields(): + '''List of fields in this record.''' + def fget(self): + return self.getTags('field') - def fset(self, fields): - fdel(self) - for field in fields: - if not isinstance(field, DataField): - ExtendField(extend=field) - self.addChild(node=field) + def fset(self, fields): + fdel(self) + for field in fields: + if not isinstance(field, DataField): + ExtendField(extend=field) + self.addChild(node=field) - def fdel(self): - for element in self.getTags('field'): - self.delChild(element) + def fdel(self): + for element in self.getTags('field'): + self.delChild(element) - return locals() + return locals() - def iter_fields(self): - ''' Iterate over fields in this record. Do not take associated - into account. ''' - for field in self.iterTags('field'): - yield field + def iter_fields(self): + ''' Iterate over fields in this record. Do not take associated + into account. ''' + for field in self.iterTags('field'): + yield field - def iter_with_associated(self): - ''' Iterate over associated, yielding both our field and - associated one together. ''' - for field in self.associated.iter_fields(): - yield self[field.var], field + def iter_with_associated(self): + ''' Iterate over associated, yielding both our field and + associated one together. ''' + for field in self.associated.iter_fields(): + yield self[field.var], field - def __getitem__(self, item): - return self.vars[item] + def __getitem__(self, item): + return self.vars[item] class DataForm(ExtendedNode): - def __init__(self, type_=None, title=None, instructions=None, extend=None): - if extend is None: - # we have to build form from scratch - xmpp.Node.__init__(self, 'x', attrs={'xmlns': xmpp.NS_DATA}) + def __init__(self, type_=None, title=None, instructions=None, extend=None): + if extend is None: + # we have to build form from scratch + xmpp.Node.__init__(self, 'x', attrs={'xmlns': xmpp.NS_DATA}) - if type_ is not None: - self.type_=type_ - if title is not None: - self.title=title - if instructions is not None: - self.instructions=instructions + if type_ is not None: + self.type_=type_ + if title is not None: + self.title=title + if instructions is not None: + self.instructions=instructions - @nested_property - def type(): - ''' Type of the form. Must be one of: 'form', 'submit', 'cancel', 'result'. - 'form' - this form is to be filled in; you will be able soon to do: - filledform = DataForm(replyto=thisform)...''' - def fget(self): - return self.getAttr('type') + @nested_property + def type(): + ''' Type of the form. Must be one of: 'form', 'submit', 'cancel', 'result'. + 'form' - this form is to be filled in; you will be able soon to do: + filledform = DataForm(replyto=thisform)...''' + def fget(self): + return self.getAttr('type') - def fset(self, type_): - assert type_ in ('form', 'submit', 'cancel', 'result') - self.setAttr('type', type_) + def fset(self, type_): + assert type_ in ('form', 'submit', 'cancel', 'result') + self.setAttr('type', type_) - return locals() + return locals() - @nested_property - def title(): - ''' Title of the form. Human-readable, should not contain any \\r\\n.''' - def fget(self): - return self.getTagData('title') + @nested_property + def title(): + ''' Title of the form. Human-readable, should not contain any \\r\\n.''' + def fget(self): + return self.getTagData('title') - def fset(self, title): - self.setTagData('title', title) + def fset(self, title): + self.setTagData('title', title) - def fdel(self): - try: - self.delChild('title') - except ValueError: - pass + def fdel(self): + try: + self.delChild('title') + except ValueError: + pass - return locals() + return locals() - @nested_property - def instructions(): - ''' Instructions for this form. Human-readable, may contain \\r\\n. ''' - # TODO: the same code is in TextMultiField. join them - def fget(self): - value = u'' - for valuenode in self.getTags('instructions'): - value += '\n' + valuenode.getData() - return value[1:] + @nested_property + def instructions(): + ''' Instructions for this form. Human-readable, may contain \\r\\n. ''' + # TODO: the same code is in TextMultiField. join them + def fget(self): + value = u'' + for valuenode in self.getTags('instructions'): + value += '\n' + valuenode.getData() + return value[1:] - def fset(self, value): - fdel(self) - if value == '': return - for line in value.split('\n'): - self.addChild('instructions').setData(line) + def fset(self, value): + fdel(self) + if value == '': return + for line in value.split('\n'): + self.addChild('instructions').setData(line) - def fdel(self): - for value in self.getTags('instructions'): - self.delChild(value) + def fdel(self): + for value in self.getTags('instructions'): + self.delChild(value) - return locals() + return locals() class SimpleDataForm(DataForm, DataRecord): - def __init__(self, type_=None, title=None, instructions=None, fields=None, \ - extend=None): - DataForm.__init__(self, type_=type_, title=title, - instructions=instructions, extend=extend) - DataRecord.__init__(self, fields=fields, extend=self, associated=self) + def __init__(self, type_=None, title=None, instructions=None, fields=None, \ + extend=None): + DataForm.__init__(self, type_=type_, title=title, + instructions=instructions, extend=extend) + DataRecord.__init__(self, fields=fields, extend=self, associated=self) - def get_purged(self): - c = SimpleDataForm(extend=self) - del c.title - c.instructions = '' - to_be_removed = [] - for f in c.iter_fields(): - if f.required: - # Keep all required fields - continue - if (hasattr(f, 'value') and not f.value) or (hasattr(f, 'values') and \ - len(f.values) == 0): - to_be_removed.append(f) - else: - del f.label - del f.description - for f in to_be_removed: - c.delChild(f) - return c + def get_purged(self): + c = SimpleDataForm(extend=self) + del c.title + c.instructions = '' + to_be_removed = [] + for f in c.iter_fields(): + if f.required: + # Keep all required fields + continue + if (hasattr(f, 'value') and not f.value) or (hasattr(f, 'values') and \ + len(f.values) == 0): + to_be_removed.append(f) + else: + del f.label + del f.description + for f in to_be_removed: + c.delChild(f) + return c class MultipleDataForm(DataForm): - def __init__(self, type_=None, title=None, instructions=None, items=None, - extend=None): - DataForm.__init__(self, type_=type_, title=title, - instructions=instructions, extend=extend) - # all records, recorded into DataRecords - if extend is None: - if items is not None: - self.items = items - else: - # we already have xmpp.Node inside - try to convert all - # fields into DataField objects - if items is None: - self.items = list(self.iterTags('item')) - else: - for item in self.getTags('item'): - self.delChild(item) - self.items = items - reported_tag = self.getTag('reported') - self.reported = DataRecord(extend=reported_tag) + def __init__(self, type_=None, title=None, instructions=None, items=None, + extend=None): + DataForm.__init__(self, type_=type_, title=title, + instructions=instructions, extend=extend) + # all records, recorded into DataRecords + if extend is None: + if items is not None: + self.items = items + else: + # we already have xmpp.Node inside - try to convert all + # fields into DataField objects + if items is None: + self.items = list(self.iterTags('item')) + else: + for item in self.getTags('item'): + self.delChild(item) + self.items = items + reported_tag = self.getTag('reported') + self.reported = DataRecord(extend=reported_tag) - @nested_property - def items(): - ''' A list of all records. ''' - def fget(self): - return list(self.iter_records()) + @nested_property + def items(): + ''' A list of all records. ''' + def fget(self): + return list(self.iter_records()) - def fset(self, records): - fdel(self) - for record in records: - if not isinstance(record, DataRecord): - DataRecord(extend=record) - self.addChild(node=record) + def fset(self, records): + fdel(self) + for record in records: + if not isinstance(record, DataRecord): + DataRecord(extend=record) + self.addChild(node=record) - def fdel(self): - for record in self.getTags('item'): - self.delChild(record) + def fdel(self): + for record in self.getTags('item'): + self.delChild(record) - return locals() + return locals() - def iter_records(self): - for record in self.getTags('item'): - yield record + def iter_records(self): + for record in self.getTags('item'): + yield record -# @nested_property -# def reported(): -# ''' DataRecord that contains descriptions of fields in records.''' -# def fget(self): -# return self.getTag('reported') -# def fset(self, record): -# try: -# self.delChild('reported') -# except: -# pass +# @nested_property +# def reported(): +# ''' DataRecord that contains descriptions of fields in records.''' +# def fget(self): +# return self.getTag('reported') +# def fset(self, record): +# try: +# self.delChild('reported') +# except: +# pass # -# record.setName('reported') -# self.addChild(node=record) -# return locals() +# record.setName('reported') +# self.addChild(node=record) +# return locals() - -# vim: se ts=3: diff --git a/src/common/dbus_support.py b/src/common/dbus_support.py index ae2016603..2dd734947 100644 --- a/src/common/dbus_support.py +++ b/src/common/dbus_support.py @@ -32,142 +32,140 @@ from common import exceptions _GAJIM_ERROR_IFACE = 'org.gajim.dbus.Error' try: - import dbus - import dbus.glib + import dbus + import dbus.glib except ImportError: - supported = False - if not os.name == 'nt': # only say that to non Windows users - print _('D-Bus python bindings are missing in this computer') - print _('D-Bus capabilities of Gajim cannot be used') + supported = False + if not os.name == 'nt': # only say that to non Windows users + print _('D-Bus python bindings are missing in this computer') + print _('D-Bus capabilities of Gajim cannot be used') else: - try: - # test if dbus-x11 is installed - bus = dbus.SessionBus() - supported = True # does user have D-Bus bindings? - except dbus.DBusException: - supported = False - if not os.name == 'nt': # only say that to non Windows users - print _('D-Bus does not run correctly on this machine') - print _('D-Bus capabilities of Gajim cannot be used') + try: + # test if dbus-x11 is installed + bus = dbus.SessionBus() + supported = True # does user have D-Bus bindings? + except dbus.DBusException: + supported = False + if not os.name == 'nt': # only say that to non Windows users + print _('D-Bus does not run correctly on this machine') + print _('D-Bus capabilities of Gajim cannot be used') class SystemBus: - '''A Singleton for the DBus SystemBus''' - def __init__(self): - self.system_bus = None + '''A Singleton for the DBus SystemBus''' + def __init__(self): + self.system_bus = None - def SystemBus(self): - if not supported: - raise exceptions.DbusNotSupported + def SystemBus(self): + if not supported: + raise exceptions.DbusNotSupported - if not self.present(): - raise exceptions.SystemBusNotPresent - return self.system_bus + if not self.present(): + raise exceptions.SystemBusNotPresent + return self.system_bus - def bus(self): - return self.SystemBus() + def bus(self): + return self.SystemBus() - def present(self): - if not supported: - return False - if self.system_bus is None: - try: - self.system_bus = dbus.SystemBus() - except dbus.DBusException: - self.system_bus = None - return False - if self.system_bus is None: - return False - # Don't exit Gajim when dbus is stopped - self.system_bus.set_exit_on_disconnect(False) - return True + def present(self): + if not supported: + return False + if self.system_bus is None: + try: + self.system_bus = dbus.SystemBus() + except dbus.DBusException: + self.system_bus = None + return False + if self.system_bus is None: + return False + # Don't exit Gajim when dbus is stopped + self.system_bus.set_exit_on_disconnect(False) + return True system_bus = SystemBus() class SessionBus: - '''A Singleton for the D-Bus SessionBus''' - def __init__(self): - self.session_bus = None + '''A Singleton for the D-Bus SessionBus''' + def __init__(self): + self.session_bus = None - def SessionBus(self): - if not supported: - raise exceptions.DbusNotSupported + def SessionBus(self): + if not supported: + raise exceptions.DbusNotSupported - if not self.present(): - raise exceptions.SessionBusNotPresent - return self.session_bus + if not self.present(): + raise exceptions.SessionBusNotPresent + return self.session_bus - def bus(self): - return self.SessionBus() + def bus(self): + return self.SessionBus() - def present(self): - if not supported: - return False - if self.session_bus is None: - try: - self.session_bus = dbus.SessionBus() - except dbus.DBusException: - self.session_bus = None - return False - if self.session_bus is None: - return False - return True + def present(self): + if not supported: + return False + if self.session_bus is None: + try: + self.session_bus = dbus.SessionBus() + except dbus.DBusException: + self.session_bus = None + return False + if self.session_bus is None: + return False + return True session_bus = SessionBus() def get_interface(interface, path, start_service=True): - '''Returns an interface on the current SessionBus. If the interface isn\'t - running, it tries to start it first.''' - if not supported: - return None - if session_bus.present(): - bus = session_bus.SessionBus() - else: - return None - try: - obj = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus') - dbus_iface = dbus.Interface(obj, 'org.freedesktop.DBus') - running_services = dbus_iface.ListNames() - started = True - if interface not in running_services: - # try to start the service - if start_service and dbus_iface.StartServiceByName(interface, dbus.UInt32(0)) == 1: - started = True - else: - started = False - if not started: - return None - obj = bus.get_object(interface, path) - return dbus.Interface(obj, interface) - except Exception, e: - gajim.log.debug(str(e)) - return None + '''Returns an interface on the current SessionBus. If the interface isn\'t + running, it tries to start it first.''' + if not supported: + return None + if session_bus.present(): + bus = session_bus.SessionBus() + else: + return None + try: + obj = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus') + dbus_iface = dbus.Interface(obj, 'org.freedesktop.DBus') + running_services = dbus_iface.ListNames() + started = True + if interface not in running_services: + # try to start the service + if start_service and dbus_iface.StartServiceByName(interface, dbus.UInt32(0)) == 1: + started = True + else: + started = False + if not started: + return None + obj = bus.get_object(interface, path) + return dbus.Interface(obj, interface) + except Exception, e: + gajim.log.debug(str(e)) + return None def get_notifications_interface(notif=None): - '''Returns the notifications interface. + '''Returns the notifications interface. - :param notif: DesktopNotification instance''' - # try to see if KDE notifications are available - iface = get_interface('org.kde.VisualNotifications', '/VisualNotifications', - start_service=False) - if iface != None: - if notif != None: - notif.kde_notifications = True - return iface - # KDE notifications don't seem to be available, falling back to - # notification-daemon - else: - if notif != None: - notif.kde_notifications = False - return get_interface('org.freedesktop.Notifications', - '/org/freedesktop/Notifications') + :param notif: DesktopNotification instance''' + # try to see if KDE notifications are available + iface = get_interface('org.kde.VisualNotifications', '/VisualNotifications', + start_service=False) + if iface != None: + if notif != None: + notif.kde_notifications = True + return iface + # KDE notifications don't seem to be available, falling back to + # notification-daemon + else: + if notif != None: + notif.kde_notifications = False + return get_interface('org.freedesktop.Notifications', + '/org/freedesktop/Notifications') if supported: - class MissingArgument(dbus.DBusException): - _dbus_error_name = _GAJIM_ERROR_IFACE + '.MissingArgument' + class MissingArgument(dbus.DBusException): + _dbus_error_name = _GAJIM_ERROR_IFACE + '.MissingArgument' - class InvalidArgument(dbus.DBusException): - '''Raised when one of the provided arguments is invalid.''' - _dbus_error_name = _GAJIM_ERROR_IFACE + '.InvalidArgument' - -# vim: se ts=3: + class InvalidArgument(dbus.DBusException): + '''Raised when one of the provided arguments is invalid.''' + _dbus_error_name = _GAJIM_ERROR_IFACE + '.InvalidArgument' diff --git a/src/common/defs.py b/src/common/defs.py index 6640c3b32..4708ef917 100644 --- a/src/common/defs.py +++ b/src/common/defs.py @@ -31,6 +31,4 @@ version = '0.13.0.1-dev' import sys, os.path for base in ('.', 'common'): - sys.path.append(os.path.join(base, '.libs')) - -# vim: se ts=3: + sys.path.append(os.path.join(base, '.libs')) diff --git a/src/common/dh.py b/src/common/dh.py index 4b038ea93..4f8ce3804 100644 --- a/src/common/dh.py +++ b/src/common/dh.py @@ -27,28 +27,28 @@ These constants have been obtained from RFC2409 and RFC3526. ''' import string -generators = [ None, # one to get the right offset - 2, - 2, - None, - None, - 2, - None, - None, - None, - None, - None, - None, - None, - None, - 2, # group 14 - 2, - 2, - 2, - 2, - ] +generators = [ None, # one to get the right offset + 2, + 2, + None, + None, + 2, + None, + None, + None, + None, + None, + None, + None, + None, + 2, # group 14 + 2, + 2, + 2, + 2, + ] -hex_primes = [ None, +hex_primes = [ None, # group 1 '''FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 @@ -221,11 +221,9 @@ B1D510BD 7EE74D73 FAF36BC3 1ECFA268 359046F4 EB879F92 all_ascii = ''.join(map(chr, range(256))) def hex_to_decimal(stripee): - if not stripee: - return None + if not stripee: + return None - return int(stripee.translate(all_ascii, string.whitespace), 16) + return int(stripee.translate(all_ascii, string.whitespace), 16) primes = map(hex_to_decimal, hex_primes) - -# vim: se ts=3: diff --git a/src/common/events.py b/src/common/events.py index 4e5068652..d591270b9 100644 --- a/src/common/events.py +++ b/src/common/events.py @@ -27,275 +27,273 @@ import time class Event: - '''Information concerning each event''' - def __init__(self, type_, time_, parameters, show_in_roster=False, - show_in_systray=True): - ''' type_ in chat, normal, file-request, file-error, file-completed, - file-request-error, file-send-error, file-stopped, gc_msg, pm, - printed_chat, printed_gc_msg, printed_marked_gc_msg, printed_pm, - gc-invitation, subscription_request, unsubscribedm jingle-incoming - parameters is (per type_): - chat, normal, pm: [message, subject, kind, time, encrypted, resource, - msg_id] - where kind in error, incoming - file-*: file_props - gc_msg: None - printed_chat: control - printed_*: None - messages that are already printed in chat, but not read - gc-invitation: [room_jid, reason, password, is_continued] - subscription_request: [text, nick] - unsubscribed: contact - jingle-incoming: (fulljid, sessionid, content_types) - ''' - self.type_ = type_ - self.time_ = time_ - self.parameters = parameters - self.show_in_roster = show_in_roster - self.show_in_systray = show_in_systray - # Set when adding the event - self.jid = None - self.account = None + '''Information concerning each event''' + def __init__(self, type_, time_, parameters, show_in_roster=False, + show_in_systray=True): + ''' type_ in chat, normal, file-request, file-error, file-completed, + file-request-error, file-send-error, file-stopped, gc_msg, pm, + printed_chat, printed_gc_msg, printed_marked_gc_msg, printed_pm, + gc-invitation, subscription_request, unsubscribedm jingle-incoming + parameters is (per type_): + chat, normal, pm: [message, subject, kind, time, encrypted, resource, + msg_id] + where kind in error, incoming + file-*: file_props + gc_msg: None + printed_chat: control + printed_*: None + messages that are already printed in chat, but not read + gc-invitation: [room_jid, reason, password, is_continued] + subscription_request: [text, nick] + unsubscribed: contact + jingle-incoming: (fulljid, sessionid, content_types) + ''' + self.type_ = type_ + self.time_ = time_ + self.parameters = parameters + self.show_in_roster = show_in_roster + self.show_in_systray = show_in_systray + # Set when adding the event + self.jid = None + self.account = None class Events: - '''Information concerning all events''' - def __init__(self): - self._events = {} # list of events {acct: {jid1: [E1, E2]}, } - self._event_added_listeners = [] - self._event_removed_listeners = [] + '''Information concerning all events''' + def __init__(self): + self._events = {} # list of events {acct: {jid1: [E1, E2]}, } + self._event_added_listeners = [] + self._event_removed_listeners = [] - def event_added_subscribe(self, listener): - '''Add a listener when an event is added to the queue''' - if not listener in self._event_added_listeners: - self._event_added_listeners.append(listener) + def event_added_subscribe(self, listener): + '''Add a listener when an event is added to the queue''' + if not listener in self._event_added_listeners: + self._event_added_listeners.append(listener) - def event_added_unsubscribe(self, listener): - '''Remove a listener when an event is added to the queue''' - if listener in self._event_added_listeners: - self._event_added_listeners.remove(listener) + def event_added_unsubscribe(self, listener): + '''Remove a listener when an event is added to the queue''' + if listener in self._event_added_listeners: + self._event_added_listeners.remove(listener) - def event_removed_subscribe(self, listener): - '''Add a listener when an event is removed from the queue''' - if not listener in self._event_removed_listeners: - self._event_removed_listeners.append(listener) + def event_removed_subscribe(self, listener): + '''Add a listener when an event is removed from the queue''' + if not listener in self._event_removed_listeners: + self._event_removed_listeners.append(listener) - def event_removed_unsubscribe(self, listener): - '''Remove a listener when an event is removed from the queue''' - if listener in self._event_removed_listeners: - self._event_removed_listeners.remove(listener) + def event_removed_unsubscribe(self, listener): + '''Remove a listener when an event is removed from the queue''' + if listener in self._event_removed_listeners: + self._event_removed_listeners.remove(listener) - def fire_event_added(self, event): - for listener in self._event_added_listeners: - listener(event) + def fire_event_added(self, event): + for listener in self._event_added_listeners: + listener(event) - def fire_event_removed(self, event_list): - for listener in self._event_removed_listeners: - listener(event_list) + def fire_event_removed(self, event_list): + for listener in self._event_removed_listeners: + listener(event_list) - def change_account_name(self, old_name, new_name): - if old_name in self._events: - self._events[new_name] = self._events[old_name] - del self._events[old_name] + def change_account_name(self, old_name, new_name): + if old_name in self._events: + self._events[new_name] = self._events[old_name] + del self._events[old_name] - def add_account(self, account): - self._events[account] = {} + def add_account(self, account): + self._events[account] = {} - def get_accounts(self): - return self._events.keys() + def get_accounts(self): + return self._events.keys() - def remove_account(self, account): - del self._events[account] + def remove_account(self, account): + del self._events[account] - def create_event(self, type_, parameters, time_ = time.time(), - show_in_roster = False, show_in_systray = True): - return Event(type_, time_, parameters, show_in_roster, - show_in_systray) + def create_event(self, type_, parameters, time_ = time.time(), + show_in_roster = False, show_in_systray = True): + return Event(type_, time_, parameters, show_in_roster, + show_in_systray) - def add_event(self, account, jid, event): - # No such account before ? - if account not in self._events: - self._events[account] = {jid: [event]} - # no such jid before ? - elif jid not in self._events[account]: - self._events[account][jid] = [event] - else: - self._events[account][jid].append(event) - event.jid = jid - event.account = account - self.fire_event_added(event) + def add_event(self, account, jid, event): + # No such account before ? + if account not in self._events: + self._events[account] = {jid: [event]} + # no such jid before ? + elif jid not in self._events[account]: + self._events[account][jid] = [event] + else: + self._events[account][jid].append(event) + event.jid = jid + event.account = account + self.fire_event_added(event) - def remove_events(self, account, jid, event = None, types = []): - '''if event is not specified, remove all events from this jid, - optionally only from given type - return True if no such event found''' - if account not in self._events: - return True - if jid not in self._events[account]: - return True - if event: # remove only one event - if event in self._events[account][jid]: - if len(self._events[account][jid]) == 1: - del self._events[account][jid] - else: - self._events[account][jid].remove(event) - self.fire_event_removed([event]) - return - else: - return True - if types: - new_list = [] # list of events to keep - removed_list = [] # list of removed events - for ev in self._events[account][jid]: - if ev.type_ not in types: - new_list.append(ev) - else: - removed_list.append(ev) - if len(new_list) == len(self._events[account][jid]): - return True - if new_list: - self._events[account][jid] = new_list - else: - del self._events[account][jid] - self.fire_event_removed(removed_list) - return - # no event nor type given, remove them all - self.fire_event_removed(self._events[account][jid]) - del self._events[account][jid] + def remove_events(self, account, jid, event = None, types = []): + '''if event is not specified, remove all events from this jid, + optionally only from given type + return True if no such event found''' + if account not in self._events: + return True + if jid not in self._events[account]: + return True + if event: # remove only one event + if event in self._events[account][jid]: + if len(self._events[account][jid]) == 1: + del self._events[account][jid] + else: + self._events[account][jid].remove(event) + self.fire_event_removed([event]) + return + else: + return True + if types: + new_list = [] # list of events to keep + removed_list = [] # list of removed events + for ev in self._events[account][jid]: + if ev.type_ not in types: + new_list.append(ev) + else: + removed_list.append(ev) + if len(new_list) == len(self._events[account][jid]): + return True + if new_list: + self._events[account][jid] = new_list + else: + del self._events[account][jid] + self.fire_event_removed(removed_list) + return + # no event nor type given, remove them all + self.fire_event_removed(self._events[account][jid]) + del self._events[account][jid] - def change_jid(self, account, old_jid, new_jid): - if account not in self._events: - return - if old_jid not in self._events[account]: - return - if new_jid in self._events[account]: - self._events[account][new_jid] += self._events[account][old_jid] - else: - self._events[account][new_jid] = self._events[account][old_jid] - del self._events[account][old_jid] + def change_jid(self, account, old_jid, new_jid): + if account not in self._events: + return + if old_jid not in self._events[account]: + return + if new_jid in self._events[account]: + self._events[account][new_jid] += self._events[account][old_jid] + else: + self._events[account][new_jid] = self._events[account][old_jid] + del self._events[account][old_jid] - def get_nb_events(self, types = [], account = None): - return self._get_nb_events(types = types, account = account) + def get_nb_events(self, types = [], account = None): + return self._get_nb_events(types = types, account = account) - def get_events(self, account, jid = None, types = []): - '''returns all events from the given account of the form - {jid1: [], jid2: []} - if jid is given, returns all events from the given jid in a list: [] - optionally only from given type''' - if account not in self._events: - return [] - if not jid: - events_list = {} # list of events - for jid_ in self._events[account]: - events = [] - for ev in self._events[account][jid_]: - if not types or ev.type_ in types: - events.append(ev) - if events: - events_list[jid_] = events - return events_list - if jid not in self._events[account]: - return [] - events_list = [] # list of events - for ev in self._events[account][jid]: - if not types or ev.type_ in types: - events_list.append(ev) - return events_list + def get_events(self, account, jid = None, types = []): + '''returns all events from the given account of the form + {jid1: [], jid2: []} + if jid is given, returns all events from the given jid in a list: [] + optionally only from given type''' + if account not in self._events: + return [] + if not jid: + events_list = {} # list of events + for jid_ in self._events[account]: + events = [] + for ev in self._events[account][jid_]: + if not types or ev.type_ in types: + events.append(ev) + if events: + events_list[jid_] = events + return events_list + if jid not in self._events[account]: + return [] + events_list = [] # list of events + for ev in self._events[account][jid]: + if not types or ev.type_ in types: + events_list.append(ev) + return events_list - def get_first_event(self, account, jid = None, type_ = None): - '''Return the first event of type type_ if given''' - events_list = self.get_events(account, jid, type_) - # be sure it's bigger than latest event - first_event_time = time.time() + 1 - first_event = None - for event in events_list: - if event.time_ < first_event_time: - first_event_time = event.time_ - first_event = event - return first_event + def get_first_event(self, account, jid = None, type_ = None): + '''Return the first event of type type_ if given''' + events_list = self.get_events(account, jid, type_) + # be sure it's bigger than latest event + first_event_time = time.time() + 1 + first_event = None + for event in events_list: + if event.time_ < first_event_time: + first_event_time = event.time_ + first_event = event + return first_event - def _get_nb_events(self, account = None, jid = None, attribute = None, - types = []): - '''return the number of pending events''' - nb = 0 - if account: - accounts = [account] - else: - accounts = self._events.keys() - for acct in accounts: - if acct not in self._events: - continue - if jid: - jids = [jid] - else: - jids = self._events[acct].keys() - for j in jids: - if j not in self._events[acct]: - continue - for event in self._events[acct][j]: - if types and event.type_ not in types: - continue - if not attribute or \ - attribute == 'systray' and event.show_in_systray or \ - attribute == 'roster' and event.show_in_roster: - nb += 1 - return nb + def _get_nb_events(self, account = None, jid = None, attribute = None, + types = []): + '''return the number of pending events''' + nb = 0 + if account: + accounts = [account] + else: + accounts = self._events.keys() + for acct in accounts: + if acct not in self._events: + continue + if jid: + jids = [jid] + else: + jids = self._events[acct].keys() + for j in jids: + if j not in self._events[acct]: + continue + for event in self._events[acct][j]: + if types and event.type_ not in types: + continue + if not attribute or \ + attribute == 'systray' and event.show_in_systray or \ + attribute == 'roster' and event.show_in_roster: + nb += 1 + return nb - def _get_some_events(self, attribute): - '''attribute in systray, roster''' - events = {} - for account in self._events: - events[account] = {} - for jid in self._events[account]: - events[account][jid] = [] - for event in self._events[account][jid]: - if attribute == 'systray' and event.show_in_systray or \ - attribute == 'roster' and event.show_in_roster: - events[account][jid].append(event) - if not events[account][jid]: - del events[account][jid] - if not events[account]: - del events[account] - return events + def _get_some_events(self, attribute): + '''attribute in systray, roster''' + events = {} + for account in self._events: + events[account] = {} + for jid in self._events[account]: + events[account][jid] = [] + for event in self._events[account][jid]: + if attribute == 'systray' and event.show_in_systray or \ + attribute == 'roster' and event.show_in_roster: + events[account][jid].append(event) + if not events[account][jid]: + del events[account][jid] + if not events[account]: + del events[account] + return events - def _get_first_event_with_attribute(self, events): - '''get the first event - events is in the form {account1: {jid1: [ev1, ev2], },. }''' - # be sure it's bigger than latest event - first_event_time = time.time() + 1 - first_account = None - first_jid = None - first_event = None - for account in events: - for jid in events[account]: - for event in events[account][jid]: - if event.time_ < first_event_time: - first_event_time = event.time_ - first_account = account - first_jid = jid - first_event = event - return first_account, first_jid, first_event + def _get_first_event_with_attribute(self, events): + '''get the first event + events is in the form {account1: {jid1: [ev1, ev2], },. }''' + # be sure it's bigger than latest event + first_event_time = time.time() + 1 + first_account = None + first_jid = None + first_event = None + for account in events: + for jid in events[account]: + for event in events[account][jid]: + if event.time_ < first_event_time: + first_event_time = event.time_ + first_account = account + first_jid = jid + first_event = event + return first_account, first_jid, first_event - def get_nb_systray_events(self, types = []): - '''returns the number of events displayed in roster''' - return self._get_nb_events(attribute = 'systray', types = types) + def get_nb_systray_events(self, types = []): + '''returns the number of events displayed in roster''' + return self._get_nb_events(attribute = 'systray', types = types) - def get_systray_events(self): - '''return all events that must be displayed in systray: - {account1: {jid1: [ev1, ev2], },. }''' - return self._get_some_events('systray') + def get_systray_events(self): + '''return all events that must be displayed in systray: + {account1: {jid1: [ev1, ev2], },. }''' + return self._get_some_events('systray') - def get_first_systray_event(self): - events = self.get_systray_events() - return self._get_first_event_with_attribute(events) + def get_first_systray_event(self): + events = self.get_systray_events() + return self._get_first_event_with_attribute(events) - def get_nb_roster_events(self, account = None, jid = None, types = []): - '''returns the number of events displayed in roster''' - return self._get_nb_events(attribute = 'roster', account = account, - jid = jid, types = types) + def get_nb_roster_events(self, account = None, jid = None, types = []): + '''returns the number of events displayed in roster''' + return self._get_nb_events(attribute = 'roster', account = account, + jid = jid, types = types) - def get_roster_events(self): - '''return all events that must be displayed in roster: - {account1: {jid1: [ev1, ev2], },. }''' - return self._get_some_events('roster') - -# vim: se ts=3: + def get_roster_events(self): + '''return all events that must be displayed in roster: + {account1: {jid1: [ev1, ev2], },. }''' + return self._get_some_events('roster') diff --git a/src/common/exceptions.py b/src/common/exceptions.py index 9f20e1d97..c24849172 100644 --- a/src/common/exceptions.py +++ b/src/common/exceptions.py @@ -22,82 +22,80 @@ ## class PysqliteNotAvailable(Exception): - '''sqlite2 is not installed or python bindings are missing''' - def __init__(self): - Exception.__init__(self) + '''sqlite2 is not installed or python bindings are missing''' + def __init__(self): + Exception.__init__(self) - def __str__(self): - return _('pysqlite2 (aka python-pysqlite2) dependency is missing. Exiting...') + def __str__(self): + return _('pysqlite2 (aka python-pysqlite2) dependency is missing. Exiting...') class PysqliteOperationalError(Exception): - '''sqlite2 raised pysqlite2.dbapi2.OperationalError''' - def __init__(self, text=''): - Exception.__init__(self) - self.text = text + '''sqlite2 raised pysqlite2.dbapi2.OperationalError''' + def __init__(self, text=''): + Exception.__init__(self) + self.text = text - def __str__(self): - return self.text + def __str__(self): + return self.text class DatabaseMalformed(Exception): - '''The databas can't be read''' - def __init__(self): - Exception.__init__(self) + '''The databas can't be read''' + def __init__(self): + Exception.__init__(self) - def __str__(self): - return _('Database cannot be read.') + def __str__(self): + return _('Database cannot be read.') class ServiceNotAvailable(Exception): - '''This exception is raised when we cannot use Gajim remotely''' - def __init__(self): - Exception.__init__(self) + '''This exception is raised when we cannot use Gajim remotely''' + def __init__(self): + Exception.__init__(self) - def __str__(self): - return _('Service not available: Gajim is not running, or remote_control is False') + def __str__(self): + return _('Service not available: Gajim is not running, or remote_control is False') class DbusNotSupported(Exception): - '''D-Bus is not installed or python bindings are missing''' - def __init__(self): - Exception.__init__(self) + '''D-Bus is not installed or python bindings are missing''' + def __init__(self): + Exception.__init__(self) - def __str__(self): - return _('D-Bus is not present on this machine or python module is missing') + def __str__(self): + return _('D-Bus is not present on this machine or python module is missing') class SessionBusNotPresent(Exception): - '''This exception indicates that there is no session daemon''' - def __init__(self): - Exception.__init__(self) + '''This exception indicates that there is no session daemon''' + def __init__(self): + Exception.__init__(self) - def __str__(self): - return _('Session bus is not available.\nTry reading http://trac.gajim.org/wiki/GajimDBus') + def __str__(self): + return _('Session bus is not available.\nTry reading http://trac.gajim.org/wiki/GajimDBus') class NegotiationError(Exception): - '''A session negotiation failed''' - pass + '''A session negotiation failed''' + pass class DecryptionError(Exception): - '''A message couldn't be decrypted into usable XML''' - pass + '''A message couldn't be decrypted into usable XML''' + pass class Cancelled(Exception): - '''The user cancelled an operation''' - pass + '''The user cancelled an operation''' + pass class LatexError(Exception): - '''LaTeX processing failed for some reason''' - def __init__(self, text=''): - Exception.__init__(self) - self.text = text + '''LaTeX processing failed for some reason''' + def __init__(self, text=''): + Exception.__init__(self) + self.text = text - def __str__(self): - return self.text + def __str__(self): + return self.text class GajimGeneralException(Exception): - '''This exception is our general exception''' - def __init__(self, text=''): - Exception.__init__(self) - self.text = text + '''This exception is our general exception''' + def __init__(self, text=''): + Exception.__init__(self) + self.text = text - def __str__(self): - return self.text - -# vim: se ts=3: + def __str__(self): + return self.text diff --git a/src/common/fuzzyclock.py b/src/common/fuzzyclock.py index fc285fd8f..394c27d22 100755 --- a/src/common/fuzzyclock.py +++ b/src/common/fuzzyclock.py @@ -35,39 +35,37 @@ So most of the credit goes to this guys, thanks :-) import time class FuzzyClock: - HOUR_NAMES = [ _('twelve'), _('one'), _('two'), _('three'), _('four'), - _('five'), _('six'), _('seven'), _('eight'), _('nine'), _('ten'), - _('eleven') ] + HOUR_NAMES = [ _('twelve'), _('one'), _('two'), _('three'), _('four'), + _('five'), _('six'), _('seven'), _('eight'), _('nine'), _('ten'), + _('eleven') ] - #Strings to use for the output. %(0)s will be replaced with the preceding hour - #(e.g. "x PAST %(0)s"), %(1)s with the coming hour (e.g. "x TO %(0)s"). ''' - FUZZY_TIME = [ _("%(0)s o'clock"), _('five past %(0)s'), _('ten past %(0)s'), - _('quarter past %(0)s'), _('twenty past %(0)s'), _('twenty five past %(0)s'), - _('half past %(0)s'), _('twenty five to %(1)s'), _('twenty to %(1)s'), - _('quarter to %(1)s'), _('ten to %(1)s'), _('five to %(1)s'), _("%(1)s o'clock") ] + #Strings to use for the output. %(0)s will be replaced with the preceding hour + #(e.g. "x PAST %(0)s"), %(1)s with the coming hour (e.g. "x TO %(0)s"). ''' + FUZZY_TIME = [ _("%(0)s o'clock"), _('five past %(0)s'), _('ten past %(0)s'), + _('quarter past %(0)s'), _('twenty past %(0)s'), _('twenty five past %(0)s'), + _('half past %(0)s'), _('twenty five to %(1)s'), _('twenty to %(1)s'), + _('quarter to %(1)s'), _('ten to %(1)s'), _('five to %(1)s'), _("%(1)s o'clock") ] - FUZZY_DAYTIME = [ _('Night'), _('Early morning'), _('Morning'), - _('Almost noon'), _('Noon'), _('Afternoon'), _('Evening'), - _('Late evening'), _('Night') ] + FUZZY_DAYTIME = [ _('Night'), _('Early morning'), _('Morning'), + _('Almost noon'), _('Noon'), _('Afternoon'), _('Evening'), + _('Late evening'), _('Night') ] - FUZZY_WEEK = [ _('Start of week'), _('Middle of week'), _('Middle of week'), - _('Middle of week'), _('End of week'), _('Weekend!'), _('Weekend!') ] + FUZZY_WEEK = [ _('Start of week'), _('Middle of week'), _('Middle of week'), + _('Middle of week'), _('End of week'), _('Weekend!'), _('Weekend!') ] - def fuzzy_time(self, fuzzyness, now): - if fuzzyness == 1 or fuzzyness == 2: - if fuzzyness == 1: - sector = int(round(now.tm_min / 5.0)) - else: - sector = int(round(now.tm_min / 15.0)) * 3 + def fuzzy_time(self, fuzzyness, now): + if fuzzyness == 1 or fuzzyness == 2: + if fuzzyness == 1: + sector = int(round(now.tm_min / 5.0)) + else: + sector = int(round(now.tm_min / 15.0)) * 3 - return self.FUZZY_TIME[sector] % { - '0': self.HOUR_NAMES[now.tm_hour % 12], - '1': self.HOUR_NAMES[(now.tm_hour + 1) % 12]} + return self.FUZZY_TIME[sector] % { + '0': self.HOUR_NAMES[now.tm_hour % 12], + '1': self.HOUR_NAMES[(now.tm_hour + 1) % 12]} - elif fuzzyness == 3: - return self.FUZZY_DAYTIME[int(round(now.tm_hour / 3.0))] + elif fuzzyness == 3: + return self.FUZZY_DAYTIME[int(round(now.tm_hour / 3.0))] - else: - return self.FUZZY_WEEK[now.tm_wday] - -# vim: se ts=3: + else: + return self.FUZZY_WEEK[now.tm_wday] diff --git a/src/common/gajim.py b/src/common/gajim.py index d9181c2de..296ce61d8 100644 --- a/src/common/gajim.py +++ b/src/common/gajim.py @@ -35,30 +35,30 @@ import config import xmpp try: - import defs + import defs except ImportError: - print >> sys.stderr, '''defs.py is missing! + print >> sys.stderr, '''defs.py is missing! If you start gajim from svn: - * Make sure you have GNU autotools installed. - This includes the following packages: - automake >= 1.8 - autoconf >= 2.59 - intltool-0.35 - libtool - * Run - $ sh autogen.sh - * Optionally, install gajim - $ make - $ sudo make install +* Make sure you have GNU autotools installed. +This includes the following packages: +automake >= 1.8 +autoconf >= 2.59 +intltool-0.35 +libtool +* Run +$ sh autogen.sh +* Optionally, install gajim +$ make +$ sudo make install **** Note for translators **** - You can get the latest string updates, by running: - $ cd po/ - $ make update-po +You can get the latest string updates, by running: +$ cd po/ +$ make update-po ''' - sys.exit(1) + sys.exit(1) interface = None # The actual interface (the gtk one for the moment) thread_interface = None # Interface to run a thread and then a callback @@ -88,14 +88,14 @@ DATA_DIR = gajimpaths['DATA'] HOME_DIR = gajimpaths['HOME'] try: - LANG = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc.. + LANG = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc.. except (ValueError, locale.Error): - # unknown locale, use en is better than fail - LANG = None + # unknown locale, use en is better than fail + LANG = None if LANG is None: - LANG = 'en' + LANG = 'en' else: - LANG = LANG[:2] # en, fr, el etc.. + LANG = LANG[:2] # en, fr, el etc.. os_info = None # used to cache os information @@ -107,7 +107,7 @@ gmail_domains = ['gmail.com', 'googlemail.com'] transport_type = {} # list the type of transport last_message_time = {} # list of time of the latest incomming message - # {acct1: {jid1: time1, jid2: time2}, } + # {acct1: {jid1: time1, jid2: time2}, } encrypted_chats = {} # list of encrypted chats {acct1: [jid1, jid2], ..} contacts = Contacts() @@ -145,64 +145,64 @@ transport_avatar = {} # {transport_jid: [jid_list]} # Is Gnome configured to activate on single click ? single_click = False SHOW_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd', - 'invisible', 'error'] + 'invisible', 'error'] # zeroconf account name ZEROCONF_ACC_NAME = 'Local' HAVE_ZEROCONF = True try: - import avahi + import avahi except ImportError: - try: - import pybonjour - except Exception: # Linux raises ImportError, Windows raises WindowsError - HAVE_ZEROCONF = False + try: + import pybonjour + except Exception: # Linux raises ImportError, Windows raises WindowsError + HAVE_ZEROCONF = False HAVE_PYCRYPTO = True try: - import Crypto + import Crypto except ImportError: - HAVE_PYCRYPTO = False + HAVE_PYCRYPTO = False HAVE_PYSEXY = True try: - import sexy + import sexy except ImportError: - HAVE_PYSEXY = False + HAVE_PYSEXY = False HAVE_GPG = True try: - import GnuPGInterface + import GnuPGInterface except ImportError: - HAVE_GPG = False + HAVE_GPG = False else: - from os import system - if system('gpg -h >/dev/null 2>&1'): - HAVE_GPG = False + from os import system + if system('gpg -h >/dev/null 2>&1'): + HAVE_GPG = False import latex HAVE_LATEX = latex.check_for_latex_support() HAVE_INDICATOR = True try: - import indicate + import indicate except ImportError: - HAVE_INDICATOR = False + HAVE_INDICATOR = False HAVE_FARSIGHT = True try: - import farsight, gst + import farsight, gst except ImportError: - HAVE_FARSIGHT = False + HAVE_FARSIGHT = False gajim_identity = {'type': 'pc', 'category': 'client', 'name': 'Gajim'} gajim_common_features = [xmpp.NS_BYTESTREAM, xmpp.NS_SI, xmpp.NS_FILE, - xmpp.NS_MUC, xmpp.NS_MUC_USER, xmpp.NS_MUC_ADMIN, xmpp.NS_MUC_OWNER, - xmpp.NS_MUC_CONFIG, xmpp.NS_COMMANDS, xmpp.NS_DISCO_INFO, 'ipv6', - 'jabber:iq:gateway', xmpp.NS_LAST, xmpp.NS_PRIVACY, xmpp.NS_PRIVATE, - xmpp.NS_REGISTER, xmpp.NS_VERSION, xmpp.NS_DATA, xmpp.NS_ENCRYPTED, 'msglog', - 'sslc2s', 'stringprep', xmpp.NS_PING, xmpp.NS_TIME_REVISED, xmpp.NS_SSN, - xmpp.NS_MOOD, xmpp.NS_ACTIVITY, xmpp.NS_NICK, xmpp.NS_ROSTERX] + xmpp.NS_MUC, xmpp.NS_MUC_USER, xmpp.NS_MUC_ADMIN, xmpp.NS_MUC_OWNER, + xmpp.NS_MUC_CONFIG, xmpp.NS_COMMANDS, xmpp.NS_DISCO_INFO, 'ipv6', + 'jabber:iq:gateway', xmpp.NS_LAST, xmpp.NS_PRIVACY, xmpp.NS_PRIVATE, + xmpp.NS_REGISTER, xmpp.NS_VERSION, xmpp.NS_DATA, xmpp.NS_ENCRYPTED, 'msglog', + 'sslc2s', 'stringprep', xmpp.NS_PING, xmpp.NS_TIME_REVISED, xmpp.NS_SSN, + xmpp.NS_MOOD, xmpp.NS_ACTIVITY, xmpp.NS_NICK, xmpp.NS_ROSTERX] # Optional features gajim supports per account gajim_optional_features = {} @@ -214,12 +214,12 @@ import caps caps.initialize(logger) def get_nick_from_jid(jid): - pos = jid.find('@') - return jid[:pos] + pos = jid.find('@') + return jid[:pos] def get_server_from_jid(jid): - pos = jid.find('@') + 1 # after @ - return jid[pos:] + pos = jid.find('@') + 1 # after @ + return jid[pos:] def get_resource_from_jid(jid): tokens = jid.split('/', 1) @@ -227,59 +227,59 @@ def get_resource_from_jid(jid): return tokens[1] def get_name_and_server_from_jid(jid): - name = get_nick_from_jid(jid) - server = get_server_from_jid(jid) - return name, server + name = get_nick_from_jid(jid) + server = get_server_from_jid(jid) + return name, server def get_room_and_nick_from_fjid(jid): - # fake jid is the jid for a contact in a room - # gaim@conference.jabber.no/nick/nick-continued - # return ('gaim@conference.jabber.no', 'nick/nick-continued') - l = jid.split('/', 1) - if len(l) == 1: # No nick - l.append('') - return l + # fake jid is the jid for a contact in a room + # gaim@conference.jabber.no/nick/nick-continued + # return ('gaim@conference.jabber.no', 'nick/nick-continued') + l = jid.split('/', 1) + if len(l) == 1: # No nick + l.append('') + return l def get_real_jid_from_fjid(account, fjid): - '''returns real jid or returns None - if we don't know the real jid''' - room_jid, nick = get_room_and_nick_from_fjid(fjid) - if not nick: # It's not a fake_jid, it is a real jid - return fjid # we return the real jid - real_jid = fjid - if interface.msg_win_mgr.get_gc_control(room_jid, account): - # It's a pm, so if we have real jid it's in contact.jid - gc_contact = contacts.get_gc_contact(account, room_jid, nick) - if not gc_contact: - return - # gc_contact.jid is None when it's not a real jid (we don't know real jid) - real_jid = gc_contact.jid - return real_jid + '''returns real jid or returns None + if we don't know the real jid''' + room_jid, nick = get_room_and_nick_from_fjid(fjid) + if not nick: # It's not a fake_jid, it is a real jid + return fjid # we return the real jid + real_jid = fjid + if interface.msg_win_mgr.get_gc_control(room_jid, account): + # It's a pm, so if we have real jid it's in contact.jid + gc_contact = contacts.get_gc_contact(account, room_jid, nick) + if not gc_contact: + return + # gc_contact.jid is None when it's not a real jid (we don't know real jid) + real_jid = gc_contact.jid + return real_jid def get_room_from_fjid(jid): - return get_room_and_nick_from_fjid(jid)[0] + return get_room_and_nick_from_fjid(jid)[0] def get_contact_name_from_jid(account, jid): - c = contacts.get_first_contact_from_jid(account, jid) - return c.name + c = contacts.get_first_contact_from_jid(account, jid) + return c.name def get_jid_without_resource(jid): - return jid.split('/')[0] + return jid.split('/')[0] def construct_fjid(room_jid, nick): - ''' nick is in utf8 (taken from treeview); room_jid is in unicode''' - # fake jid is the jid for a contact in a room - # gaim@conference.jabber.org/nick - if isinstance(nick, str): - nick = unicode(nick, 'utf-8') - return room_jid + '/' + nick + ''' nick is in utf8 (taken from treeview); room_jid is in unicode''' + # fake jid is the jid for a contact in a room + # gaim@conference.jabber.org/nick + if isinstance(nick, str): + nick = unicode(nick, 'utf-8') + return room_jid + '/' + nick def get_resource_from_jid(jid): - jids = jid.split('/', 1) - if len(jids) > 1: - return jids[1] # abc@doremi.org/res/res-continued - else: - return '' + jids = jid.split('/', 1) + if len(jids) > 1: + return jids[1] # abc@doremi.org/res/res-continued + else: + return '' # [15:34:28] we should add contact.fake_jid I think # [15:34:46] so if we know real jid, it wil be in contact.jid, or we look in contact.fake_jid @@ -287,138 +287,136 @@ def get_resource_from_jid(jid): # [15:33:07] and that resource is in contact.resource def get_number_of_accounts(): - '''returns the number of ALL accounts''' - return len(connections.keys()) + '''returns the number of ALL accounts''' + return len(connections.keys()) def get_number_of_connected_accounts(accounts_list = None): - '''returns the number of CONNECTED accounts - you can optionally pass an accounts_list - and if you do those will be checked, else all will be checked''' - connected_accounts = 0 - if accounts_list is None: - accounts = connections.keys() - else: - accounts = accounts_list - for account in accounts: - if account_is_connected(account): - connected_accounts = connected_accounts + 1 - return connected_accounts + '''returns the number of CONNECTED accounts + you can optionally pass an accounts_list + and if you do those will be checked, else all will be checked''' + connected_accounts = 0 + if accounts_list is None: + accounts = connections.keys() + else: + accounts = accounts_list + for account in accounts: + if account_is_connected(account): + connected_accounts = connected_accounts + 1 + return connected_accounts def account_is_connected(account): - if account not in connections: - return False - if connections[account].connected > 1: # 0 is offline, 1 is connecting - return True - else: - return False + if account not in connections: + return False + if connections[account].connected > 1: # 0 is offline, 1 is connecting + return True + else: + return False def account_is_disconnected(account): - return not account_is_connected(account) + return not account_is_connected(account) def zeroconf_is_connected(): - return account_is_connected(ZEROCONF_ACC_NAME) and \ - config.get_per('accounts', ZEROCONF_ACC_NAME, 'is_zeroconf') + return account_is_connected(ZEROCONF_ACC_NAME) and \ + config.get_per('accounts', ZEROCONF_ACC_NAME, 'is_zeroconf') def get_number_of_securely_connected_accounts(): - '''returns the number of the accounts that are SSL/TLS connected''' - num_of_secured = 0 - for account in connections.keys(): - if account_is_securely_connected(account): - num_of_secured += 1 - return num_of_secured + '''returns the number of the accounts that are SSL/TLS connected''' + num_of_secured = 0 + for account in connections.keys(): + if account_is_securely_connected(account): + num_of_secured += 1 + return num_of_secured def account_is_securely_connected(account): - if account_is_connected(account) and \ - account in con_types and con_types[account] in ('tls', 'ssl'): - return True - else: - return False + if account_is_connected(account) and \ + account in con_types and con_types[account] in ('tls', 'ssl'): + return True + else: + return False def get_transport_name_from_jid(jid, use_config_setting = True): - '''returns 'aim', 'gg', 'irc' etc - if JID is not from transport returns None''' - #FIXME: jid can be None! one TB I saw had this problem: - # in the code block # it is a groupchat presence in handle_event_notify - # jid was None. Yann why? - if not jid or (use_config_setting and not config.get('use_transports_iconsets')): - return + '''returns 'aim', 'gg', 'irc' etc + if JID is not from transport returns None''' + #FIXME: jid can be None! one TB I saw had this problem: + # in the code block # it is a groupchat presence in handle_event_notify + # jid was None. Yann why? + if not jid or (use_config_setting and not config.get('use_transports_iconsets')): + return - host = get_server_from_jid(jid) - if host in transport_type: - return transport_type[host] + host = get_server_from_jid(jid) + if host in transport_type: + return transport_type[host] - # host is now f.e. icq.foo.org or just icq (sometimes on hacky transports) - host_splitted = host.split('.') - if len(host_splitted) != 0: - # now we support both 'icq.' and 'icq' but not icqsucks.org - host = host_splitted[0] + # host is now f.e. icq.foo.org or just icq (sometimes on hacky transports) + host_splitted = host.split('.') + if len(host_splitted) != 0: + # now we support both 'icq.' and 'icq' but not icqsucks.org + host = host_splitted[0] - if host in ('aim', 'irc', 'icq', 'msn', 'sms', 'tlen', 'weather', 'yahoo', - 'mrim', 'facebook'): - return host - elif host == 'gg': - return 'gadu-gadu' - elif host == 'jit': - return 'icq' - elif host == 'facebook': - return 'facebook' - else: - return None + if host in ('aim', 'irc', 'icq', 'msn', 'sms', 'tlen', 'weather', 'yahoo', + 'mrim', 'facebook'): + return host + elif host == 'gg': + return 'gadu-gadu' + elif host == 'jit': + return 'icq' + elif host == 'facebook': + return 'facebook' + else: + return None def jid_is_transport(jid): - # if not '@' or '@' starts the jid then it is transport - if jid.find('@') <= 0: - return True - return False + # if not '@' or '@' starts the jid then it is transport + if jid.find('@') <= 0: + return True + return False def get_jid_from_account(account_name): - '''return the jid we use in the given account''' - name = config.get_per('accounts', account_name, 'name') - hostname = config.get_per('accounts', account_name, 'hostname') - jid = name + '@' + hostname - return jid + '''return the jid we use in the given account''' + name = config.get_per('accounts', account_name, 'name') + hostname = config.get_per('accounts', account_name, 'hostname') + jid = name + '@' + hostname + return jid def get_our_jids(): - '''returns a list of the jids we use in our accounts''' - our_jids = list() - for account in contacts.get_accounts(): - our_jids.append(get_jid_from_account(account)) - return our_jids + '''returns a list of the jids we use in our accounts''' + our_jids = list() + for account in contacts.get_accounts(): + our_jids.append(get_jid_from_account(account)) + return our_jids def get_hostname_from_account(account_name, use_srv = False): - '''returns hostname (if custom hostname is used, that is returned)''' - if use_srv and connections[account_name].connected_hostname: - return connections[account_name].connected_hostname - if config.get_per('accounts', account_name, 'use_custom_host'): - return config.get_per('accounts', account_name, 'custom_host') - return config.get_per('accounts', account_name, 'hostname') + '''returns hostname (if custom hostname is used, that is returned)''' + if use_srv and connections[account_name].connected_hostname: + return connections[account_name].connected_hostname + if config.get_per('accounts', account_name, 'use_custom_host'): + return config.get_per('accounts', account_name, 'custom_host') + return config.get_per('accounts', account_name, 'hostname') def get_notification_image_prefix(jid): - '''returns the prefix for the notification images''' - transport_name = get_transport_name_from_jid(jid) - if transport_name in ('aim', 'icq', 'msn', 'yahoo', 'facebook'): - prefix = transport_name - else: - prefix = 'jabber' - return prefix + '''returns the prefix for the notification images''' + transport_name = get_transport_name_from_jid(jid) + if transport_name in ('aim', 'icq', 'msn', 'yahoo', 'facebook'): + prefix = transport_name + else: + prefix = 'jabber' + return prefix def get_name_from_jid(account, jid): - '''returns from JID's shown name and if no contact returns jids''' - contact = contacts.get_first_contact_from_jid(account, jid) - if contact: - actor = contact.get_shown_name() - else: - actor = jid - return actor + '''returns from JID's shown name and if no contact returns jids''' + contact = contacts.get_first_contact_from_jid(account, jid) + if contact: + actor = contact.get_shown_name() + else: + actor = jid + return actor def get_priority(account, show): - '''return the priority an account must have''' - if not show: - show = 'online' + '''return the priority an account must have''' + if not show: + show = 'online' - if show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible') and \ - config.get_per('accounts', account, 'adjust_priority_with_status'): - return config.get_per('accounts', account, 'autopriority_' + show) - return config.get_per('accounts', account, 'priority') - -# vim: se ts=3: + if show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible') and \ + config.get_per('accounts', account, 'adjust_priority_with_status'): + return config.get_per('accounts', account, 'autopriority_' + show) + return config.get_per('accounts', account, 'priority') diff --git a/src/common/helpers.py b/src/common/helpers.py index 341ebb15f..5a1a378df 100644 --- a/src/common/helpers.py +++ b/src/common/helpers.py @@ -46,1220 +46,1218 @@ from i18n import Q_ from i18n import ngettext try: - import winsound # windows-only built-in module for playing wav - import win32api - import win32con + import winsound # windows-only built-in module for playing wav + import win32api + import win32con except Exception: - pass + pass special_groups = (_('Transports'), _('Not in Roster'), _('Observers'), _('Groupchats')) class InvalidFormat(Exception): - pass + pass def decompose_jid(jidstring): - user = None - server = None - resource = None + user = None + server = None + resource = None - # Search for delimiters - user_sep = jidstring.find('@') - res_sep = jidstring.find('/') + # Search for delimiters + user_sep = jidstring.find('@') + res_sep = jidstring.find('/') - if user_sep == -1: - if res_sep == -1: - # host - server = jidstring - else: - # host/resource - server = jidstring[0:res_sep] - resource = jidstring[res_sep + 1:] or None - else: - if res_sep == -1: - # user@host - user = jidstring[0:user_sep] or None - server = jidstring[user_sep + 1:] - else: - if user_sep < res_sep: - # user@host/resource - user = jidstring[0:user_sep] or None - server = jidstring[user_sep + 1:user_sep + (res_sep - user_sep)] - resource = jidstring[res_sep + 1:] or None - else: - # server/resource (with an @ in resource) - server = jidstring[0:res_sep] - resource = jidstring[res_sep + 1:] or None - return user, server, resource + if user_sep == -1: + if res_sep == -1: + # host + server = jidstring + else: + # host/resource + server = jidstring[0:res_sep] + resource = jidstring[res_sep + 1:] or None + else: + if res_sep == -1: + # user@host + user = jidstring[0:user_sep] or None + server = jidstring[user_sep + 1:] + else: + if user_sep < res_sep: + # user@host/resource + user = jidstring[0:user_sep] or None + server = jidstring[user_sep + 1:user_sep + (res_sep - user_sep)] + resource = jidstring[res_sep + 1:] or None + else: + # server/resource (with an @ in resource) + server = jidstring[0:res_sep] + resource = jidstring[res_sep + 1:] or None + return user, server, resource def parse_jid(jidstring): - '''Perform stringprep on all JID fragments from a string - and return the full jid''' - # This function comes from http://svn.twistedmatrix.com/cvs/trunk/twisted/words/protocols/jabber/jid.py + '''Perform stringprep on all JID fragments from a string + and return the full jid''' + # This function comes from http://svn.twistedmatrix.com/cvs/trunk/twisted/words/protocols/jabber/jid.py - return prep(*decompose_jid(jidstring)) + return prep(*decompose_jid(jidstring)) def idn_to_ascii(host): - '''convert IDN (Internationalized Domain Names) to ACE - (ASCII-compatible encoding)''' - from encodings import idna - labels = idna.dots.split(host) - converted_labels = [] - for label in labels: - converted_labels.append(idna.ToASCII(label)) - return ".".join(converted_labels) + '''convert IDN (Internationalized Domain Names) to ACE + (ASCII-compatible encoding)''' + from encodings import idna + labels = idna.dots.split(host) + converted_labels = [] + for label in labels: + converted_labels.append(idna.ToASCII(label)) + return ".".join(converted_labels) def ascii_to_idn(host): - '''convert ACE (ASCII-compatible encoding) to IDN - (Internationalized Domain Names)''' - from encodings import idna - labels = idna.dots.split(host) - converted_labels = [] - for label in labels: - converted_labels.append(idna.ToUnicode(label)) - return ".".join(converted_labels) + '''convert ACE (ASCII-compatible encoding) to IDN + (Internationalized Domain Names)''' + from encodings import idna + labels = idna.dots.split(host) + converted_labels = [] + for label in labels: + converted_labels.append(idna.ToUnicode(label)) + return ".".join(converted_labels) def parse_resource(resource): - '''Perform stringprep on resource and return it''' - if resource: - try: - from xmpp.stringprepare import resourceprep - return resourceprep.prepare(unicode(resource)) - except UnicodeError: - raise InvalidFormat, 'Invalid character in resource.' + '''Perform stringprep on resource and return it''' + if resource: + try: + from xmpp.stringprepare import resourceprep + return resourceprep.prepare(unicode(resource)) + except UnicodeError: + raise InvalidFormat, 'Invalid character in resource.' def prep(user, server, resource): - '''Perform stringprep on all JID fragments and return the full jid''' - # This function comes from - #http://svn.twistedmatrix.com/cvs/trunk/twisted/words/protocols/jabber/jid.py + '''Perform stringprep on all JID fragments and return the full jid''' + # This function comes from + #http://svn.twistedmatrix.com/cvs/trunk/twisted/words/protocols/jabber/jid.py - if user: - try: - from xmpp.stringprepare import nodeprep - user = nodeprep.prepare(unicode(user)) - except UnicodeError: - raise InvalidFormat, _('Invalid character in username.') - else: - user = None + if user: + try: + from xmpp.stringprepare import nodeprep + user = nodeprep.prepare(unicode(user)) + except UnicodeError: + raise InvalidFormat, _('Invalid character in username.') + else: + user = None - if not server: - raise InvalidFormat, _('Server address required.') - else: - try: - from xmpp.stringprepare import nameprep - server = nameprep.prepare(unicode(server)) - except UnicodeError: - raise InvalidFormat, _('Invalid character in hostname.') + if not server: + raise InvalidFormat, _('Server address required.') + else: + try: + from xmpp.stringprepare import nameprep + server = nameprep.prepare(unicode(server)) + except UnicodeError: + raise InvalidFormat, _('Invalid character in hostname.') - if resource: - try: - from xmpp.stringprepare import resourceprep - resource = resourceprep.prepare(unicode(resource)) - except UnicodeError: - raise InvalidFormat, _('Invalid character in resource.') - else: - resource = None + if resource: + try: + from xmpp.stringprepare import resourceprep + resource = resourceprep.prepare(unicode(resource)) + except UnicodeError: + raise InvalidFormat, _('Invalid character in resource.') + else: + resource = None - if user: - if resource: - return '%s@%s/%s' % (user, server, resource) - else: - return '%s@%s' % (user, server) - else: - if resource: - return '%s/%s' % (server, resource) - else: - return server + if user: + if resource: + return '%s@%s/%s' % (user, server, resource) + else: + return '%s@%s' % (user, server) + else: + if resource: + return '%s/%s' % (server, resource) + else: + return server def windowsify(s): - if os.name == 'nt': - return s.capitalize() - return s + if os.name == 'nt': + return s.capitalize() + return s def temp_failure_retry(func, *args, **kwargs): - while True: - try: - return func(*args, **kwargs) - except (os.error, IOError, select.error), ex: - if ex.errno == errno.EINTR: - continue - else: - raise + while True: + try: + return func(*args, **kwargs) + except (os.error, IOError, select.error), ex: + if ex.errno == errno.EINTR: + continue + else: + raise def get_uf_show(show, use_mnemonic = False): - '''returns a userfriendly string for dnd/xa/chat - and makes all strings translatable - if use_mnemonic is True, it adds _ so GUI should call with True - for accessibility issues''' - if show == 'dnd': - if use_mnemonic: - uf_show = _('_Busy') - else: - uf_show = _('Busy') - elif show == 'xa': - if use_mnemonic: - uf_show = _('_Not Available') - else: - uf_show = _('Not Available') - elif show == 'chat': - if use_mnemonic: - uf_show = _('_Free for Chat') - else: - uf_show = _('Free for Chat') - elif show == 'online': - if use_mnemonic: - uf_show = _('_Available') - else: - uf_show = _('Available') - elif show == 'connecting': - uf_show = _('Connecting') - elif show == 'away': - if use_mnemonic: - uf_show = _('A_way') - else: - uf_show = _('Away') - elif show == 'offline': - if use_mnemonic: - uf_show = _('_Offline') - else: - uf_show = _('Offline') - elif show == 'invisible': - if use_mnemonic: - uf_show = _('_Invisible') - else: - uf_show = _('Invisible') - elif show == 'not in roster': - uf_show = _('Not in Roster') - elif show == 'requested': - uf_show = Q_('?contact has status:Unknown') - else: - uf_show = Q_('?contact has status:Has errors') - return unicode(uf_show) + '''returns a userfriendly string for dnd/xa/chat + and makes all strings translatable + if use_mnemonic is True, it adds _ so GUI should call with True + for accessibility issues''' + if show == 'dnd': + if use_mnemonic: + uf_show = _('_Busy') + else: + uf_show = _('Busy') + elif show == 'xa': + if use_mnemonic: + uf_show = _('_Not Available') + else: + uf_show = _('Not Available') + elif show == 'chat': + if use_mnemonic: + uf_show = _('_Free for Chat') + else: + uf_show = _('Free for Chat') + elif show == 'online': + if use_mnemonic: + uf_show = _('_Available') + else: + uf_show = _('Available') + elif show == 'connecting': + uf_show = _('Connecting') + elif show == 'away': + if use_mnemonic: + uf_show = _('A_way') + else: + uf_show = _('Away') + elif show == 'offline': + if use_mnemonic: + uf_show = _('_Offline') + else: + uf_show = _('Offline') + elif show == 'invisible': + if use_mnemonic: + uf_show = _('_Invisible') + else: + uf_show = _('Invisible') + elif show == 'not in roster': + uf_show = _('Not in Roster') + elif show == 'requested': + uf_show = Q_('?contact has status:Unknown') + else: + uf_show = Q_('?contact has status:Has errors') + return unicode(uf_show) def get_uf_sub(sub): - if sub == 'none': - uf_sub = Q_('?Subscription we already have:None') - elif sub == 'to': - uf_sub = _('To') - elif sub == 'from': - uf_sub = _('From') - elif sub == 'both': - uf_sub = _('Both') - else: - uf_sub = sub + if sub == 'none': + uf_sub = Q_('?Subscription we already have:None') + elif sub == 'to': + uf_sub = _('To') + elif sub == 'from': + uf_sub = _('From') + elif sub == 'both': + uf_sub = _('Both') + else: + uf_sub = sub - return unicode(uf_sub) + return unicode(uf_sub) def get_uf_ask(ask): - if ask is None: - uf_ask = Q_('?Ask (for Subscription):None') - elif ask == 'subscribe': - uf_ask = _('Subscribe') - else: - uf_ask = ask + if ask is None: + uf_ask = Q_('?Ask (for Subscription):None') + elif ask == 'subscribe': + uf_ask = _('Subscribe') + else: + uf_ask = ask - return unicode(uf_ask) + return unicode(uf_ask) def get_uf_role(role, plural = False): - ''' plural determines if you get Moderators or Moderator''' - if role == 'none': - role_name = Q_('?Group Chat Contact Role:None') - elif role == 'moderator': - if plural: - role_name = _('Moderators') - else: - role_name = _('Moderator') - elif role == 'participant': - if plural: - role_name = _('Participants') - else: - role_name = _('Participant') - elif role == 'visitor': - if plural: - role_name = _('Visitors') - else: - role_name = _('Visitor') - return role_name + ''' plural determines if you get Moderators or Moderator''' + if role == 'none': + role_name = Q_('?Group Chat Contact Role:None') + elif role == 'moderator': + if plural: + role_name = _('Moderators') + else: + role_name = _('Moderator') + elif role == 'participant': + if plural: + role_name = _('Participants') + else: + role_name = _('Participant') + elif role == 'visitor': + if plural: + role_name = _('Visitors') + else: + role_name = _('Visitor') + return role_name def get_uf_affiliation(affiliation): - '''Get a nice and translated affilition for muc''' - if affiliation == 'none': - affiliation_name = Q_('?Group Chat Contact Affiliation:None') - elif affiliation == 'owner': - affiliation_name = _('Owner') - elif affiliation == 'admin': - affiliation_name = _('Administrator') - elif affiliation == 'member': - affiliation_name = _('Member') - else: # Argl ! An unknown affiliation ! - affiliation_name = affiliation.capitalize() - return affiliation_name + '''Get a nice and translated affilition for muc''' + if affiliation == 'none': + affiliation_name = Q_('?Group Chat Contact Affiliation:None') + elif affiliation == 'owner': + affiliation_name = _('Owner') + elif affiliation == 'admin': + affiliation_name = _('Administrator') + elif affiliation == 'member': + affiliation_name = _('Member') + else: # Argl ! An unknown affiliation ! + affiliation_name = affiliation.capitalize() + return affiliation_name def get_sorted_keys(adict): - keys = sorted(adict.keys()) - return keys + keys = sorted(adict.keys()) + return keys def to_one_line(msg): - msg = msg.replace('\\', '\\\\') - msg = msg.replace('\n', '\\n') - # s1 = 'test\ntest\\ntest' - # s11 = s1.replace('\\', '\\\\') - # s12 = s11.replace('\n', '\\n') - # s12 - # 'test\\ntest\\\\ntest' - return msg + msg = msg.replace('\\', '\\\\') + msg = msg.replace('\n', '\\n') + # s1 = 'test\ntest\\ntest' + # s11 = s1.replace('\\', '\\\\') + # s12 = s11.replace('\n', '\\n') + # s12 + # 'test\\ntest\\\\ntest' + return msg def from_one_line(msg): - # (? 48: - hash = hashlib.md5(filename) - filename = base64.b64encode(hash.digest()) + '''makes sure the filename we will write does contain only acceptable and + latin characters, and is not too long (in that case hash it)''' + # 48 is the limit + if len(filename) > 48: + hash = hashlib.md5(filename) + filename = base64.b64encode(hash.digest()) - filename = punycode_encode(filename) # make it latin chars only - filename = filename.replace('/', '_') - if os.name == 'nt': - filename = filename.replace('?', '_').replace(':', '_')\ - .replace('\\', '_').replace('"', "'").replace('|', '_')\ - .replace('*', '_').replace('<', '_').replace('>', '_') + filename = punycode_encode(filename) # make it latin chars only + filename = filename.replace('/', '_') + if os.name == 'nt': + filename = filename.replace('?', '_').replace(':', '_')\ + .replace('\\', '_').replace('"', "'").replace('|', '_')\ + .replace('*', '_').replace('<', '_').replace('>', '_') - return filename + return filename def reduce_chars_newlines(text, max_chars = 0, max_lines = 0): - '''Cut the chars after 'max_chars' on each line - and show only the first 'max_lines'. - If any of the params is not present (None or 0) the action - on it is not performed''' + '''Cut the chars after 'max_chars' on each line + and show only the first 'max_lines'. + If any of the params is not present (None or 0) the action + on it is not performed''' - def _cut_if_long(string): - if len(string) > max_chars: - string = string[:max_chars - 3] + '...' - return string + def _cut_if_long(string): + if len(string) > max_chars: + string = string[:max_chars - 3] + '...' + return string - if isinstance(text, str): - text = text.decode('utf-8') + if isinstance(text, str): + text = text.decode('utf-8') - if max_lines == 0: - lines = text.split('\n') - else: - lines = text.split('\n', max_lines)[:max_lines] - if max_chars > 0: - if lines: - lines = [_cut_if_long(e) for e in lines] - if lines: - reduced_text = '\n'.join(lines) - if reduced_text != text: - reduced_text += '...' - else: - reduced_text = '' - return reduced_text + if max_lines == 0: + lines = text.split('\n') + else: + lines = text.split('\n', max_lines)[:max_lines] + if max_chars > 0: + if lines: + lines = [_cut_if_long(e) for e in lines] + if lines: + reduced_text = '\n'.join(lines) + if reduced_text != text: + reduced_text += '...' + else: + reduced_text = '' + return reduced_text def get_account_status(account): - status = reduce_chars_newlines(account['status_line'], 100, 1) - return status + status = reduce_chars_newlines(account['status_line'], 100, 1) + return status def get_avatar_path(prefix): - '''Returns the filename of the avatar, distinguishes between user- and - contact-provided one. Returns None if no avatar was found at all. - prefix is the path to the requested avatar just before the ".png" or - ".jpeg".''' - # First, scan for a local, user-set avatar - for type_ in ('jpeg', 'png'): - file_ = prefix + '_local.' + type_ - if os.path.exists(file_): - return file_ - # If none available, scan for a contact-provided avatar - for type_ in ('jpeg', 'png'): - file_ = prefix + '.' + type_ - if os.path.exists(file_): - return file_ - return None + '''Returns the filename of the avatar, distinguishes between user- and + contact-provided one. Returns None if no avatar was found at all. + prefix is the path to the requested avatar just before the ".png" or + ".jpeg".''' + # First, scan for a local, user-set avatar + for type_ in ('jpeg', 'png'): + file_ = prefix + '_local.' + type_ + if os.path.exists(file_): + return file_ + # If none available, scan for a contact-provided avatar + for type_ in ('jpeg', 'png'): + file_ = prefix + '.' + type_ + if os.path.exists(file_): + return file_ + return None def datetime_tuple(timestamp): - '''Converts timestamp using strptime and the format: %Y%m%dT%H:%M:%S - Because of various datetime formats are used the following exceptions - are handled: - - Optional milliseconds appened to the string are removed - - Optional Z (that means UTC) appened to the string are removed - - XEP-082 datetime strings have all '-' cahrs removed to meet - the above format.''' - timestamp = timestamp.split('.')[0] - timestamp = timestamp.replace('-', '') - timestamp = timestamp.replace('z', '') - timestamp = timestamp.replace('Z', '') - from time import strptime - return strptime(timestamp, '%Y%m%dT%H:%M:%S') + '''Converts timestamp using strptime and the format: %Y%m%dT%H:%M:%S + Because of various datetime formats are used the following exceptions + are handled: + - Optional milliseconds appened to the string are removed + - Optional Z (that means UTC) appened to the string are removed + - XEP-082 datetime strings have all '-' cahrs removed to meet + the above format.''' + timestamp = timestamp.split('.')[0] + timestamp = timestamp.replace('-', '') + timestamp = timestamp.replace('z', '') + timestamp = timestamp.replace('Z', '') + from time import strptime + return strptime(timestamp, '%Y%m%dT%H:%M:%S') # import gajim only when needed (after decode_string is defined) see #4764 import gajim def convert_bytes(string): - suffix = '' - # IEC standard says KiB = 1024 bytes KB = 1000 bytes - # but do we use the standard? - use_kib_mib = gajim.config.get('use_kib_mib') - align = 1024. - bytes = float(string) - if bytes >= align: - bytes = round(bytes/align, 1) - if bytes >= align: - bytes = round(bytes/align, 1) - if bytes >= align: - bytes = round(bytes/align, 1) - if use_kib_mib: - #GiB means gibibyte - suffix = _('%s GiB') - else: - #GB means gigabyte - suffix = _('%s GB') - else: - if use_kib_mib: - #MiB means mibibyte - suffix = _('%s MiB') - else: - #MB means megabyte - suffix = _('%s MB') - else: - if use_kib_mib: - #KiB means kibibyte - suffix = _('%s KiB') - else: - #KB means kilo bytes - suffix = _('%s KB') - else: - #B means bytes - suffix = _('%s B') - return suffix % unicode(bytes) + suffix = '' + # IEC standard says KiB = 1024 bytes KB = 1000 bytes + # but do we use the standard? + use_kib_mib = gajim.config.get('use_kib_mib') + align = 1024. + bytes = float(string) + if bytes >= align: + bytes = round(bytes/align, 1) + if bytes >= align: + bytes = round(bytes/align, 1) + if bytes >= align: + bytes = round(bytes/align, 1) + if use_kib_mib: + #GiB means gibibyte + suffix = _('%s GiB') + else: + #GB means gigabyte + suffix = _('%s GB') + else: + if use_kib_mib: + #MiB means mibibyte + suffix = _('%s MiB') + else: + #MB means megabyte + suffix = _('%s MB') + else: + if use_kib_mib: + #KiB means kibibyte + suffix = _('%s KiB') + else: + #KB means kilo bytes + suffix = _('%s KB') + else: + #B means bytes + suffix = _('%s B') + return suffix % unicode(bytes) def get_contact_dict_for_account(account): - ''' create a dict of jid, nick -> contact with all contacts of account. - Can be used for completion lists''' - contacts_dict = {} - for jid in gajim.contacts.get_jid_list(account): - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - contacts_dict[jid] = contact - name = contact.name - if name in contacts_dict: - contact1 = contacts_dict[name] - del contacts_dict[name] - contacts_dict['%s (%s)' % (name, contact1.jid)] = contact1 - contacts_dict['%s (%s)' % (name, jid)] = contact - else: - if contact.name == gajim.get_nick_from_jid(jid): - del contacts_dict[jid] - contacts_dict[name] = contact - return contacts_dict + ''' create a dict of jid, nick -> contact with all contacts of account. + Can be used for completion lists''' + contacts_dict = {} + for jid in gajim.contacts.get_jid_list(account): + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + contacts_dict[jid] = contact + name = contact.name + if name in contacts_dict: + contact1 = contacts_dict[name] + del contacts_dict[name] + contacts_dict['%s (%s)' % (name, contact1.jid)] = contact1 + contacts_dict['%s (%s)' % (name, jid)] = contact + else: + if contact.name == gajim.get_nick_from_jid(jid): + del contacts_dict[jid] + contacts_dict[name] = contact + return contacts_dict def launch_browser_mailer(kind, uri): - #kind = 'url' or 'mail' - if os.name == 'nt': - try: - os.startfile(uri) # if pywin32 is installed we open - except Exception: - pass + #kind = 'url' or 'mail' + if os.name == 'nt': + try: + os.startfile(uri) # if pywin32 is installed we open + except Exception: + pass - else: - if kind in ('mail', 'sth_at_sth') and not uri.startswith('mailto:'): - uri = 'mailto:' + uri + else: + if kind in ('mail', 'sth_at_sth') and not uri.startswith('mailto:'): + uri = 'mailto:' + uri - if kind == 'url' and uri.startswith('www.'): - uri = 'http://' + uri + if kind == 'url' and uri.startswith('www.'): + uri = 'http://' + uri - if gajim.config.get('openwith') == 'gnome-open': - command = 'gnome-open' - elif gajim.config.get('openwith') == 'kfmclient exec': - command = 'kfmclient exec' - elif gajim.config.get('openwith') == 'exo-open': - command = 'exo-open' - elif gajim.config.get('openwith') == 'custom': - if kind == 'url': - command = gajim.config.get('custombrowser') - elif kind in ('mail', 'sth_at_sth'): - command = gajim.config.get('custommailapp') - if command == '': # if no app is configured - return + if gajim.config.get('openwith') == 'gnome-open': + command = 'gnome-open' + elif gajim.config.get('openwith') == 'kfmclient exec': + command = 'kfmclient exec' + elif gajim.config.get('openwith') == 'exo-open': + command = 'exo-open' + elif gajim.config.get('openwith') == 'custom': + if kind == 'url': + command = gajim.config.get('custombrowser') + elif kind in ('mail', 'sth_at_sth'): + command = gajim.config.get('custommailapp') + if command == '': # if no app is configured + return - command = build_command(command, uri) - try: - exec_command(command) - except Exception: - pass + command = build_command(command, uri) + try: + exec_command(command) + except Exception: + pass def launch_file_manager(path_to_open): - if os.name == 'nt': - try: - os.startfile(path_to_open) # if pywin32 is installed we open - except Exception: - pass - else: - if gajim.config.get('openwith') == 'gnome-open': - command = 'gnome-open' - elif gajim.config.get('openwith') == 'kfmclient exec': - command = 'kfmclient exec' - elif gajim.config.get('openwith') == 'exo-open': - command = 'exo-open' - elif gajim.config.get('openwith') == 'custom': - command = gajim.config.get('custom_file_manager') - if command == '': # if no app is configured - return - command = build_command(command, path_to_open) - try: - exec_command(command) - except Exception: - pass + if os.name == 'nt': + try: + os.startfile(path_to_open) # if pywin32 is installed we open + except Exception: + pass + else: + if gajim.config.get('openwith') == 'gnome-open': + command = 'gnome-open' + elif gajim.config.get('openwith') == 'kfmclient exec': + command = 'kfmclient exec' + elif gajim.config.get('openwith') == 'exo-open': + command = 'exo-open' + elif gajim.config.get('openwith') == 'custom': + command = gajim.config.get('custom_file_manager') + if command == '': # if no app is configured + return + command = build_command(command, path_to_open) + try: + exec_command(command) + except Exception: + pass def play_sound(event): - if not gajim.config.get('sounds_on'): - return - path_to_soundfile = gajim.config.get_per('soundevents', event, 'path') - play_sound_file(path_to_soundfile) + if not gajim.config.get('sounds_on'): + return + path_to_soundfile = gajim.config.get_per('soundevents', event, 'path') + play_sound_file(path_to_soundfile) def check_soundfile_path(file, - dirs=(gajim.gajimpaths.root, gajim.DATA_DIR)): - '''Check if the sound file exists. - :param file: the file to check, absolute or relative to 'dirs' path - :param dirs: list of knows paths to fallback if the file doesn't exists - (eg: ~/.gajim/sounds/, DATADIR/sounds...). - :return the path to file or None if it doesn't exists.''' - if not file: - return None - elif os.path.exists(file): - return file + dirs=(gajim.gajimpaths.root, gajim.DATA_DIR)): + '''Check if the sound file exists. + :param file: the file to check, absolute or relative to 'dirs' path + :param dirs: list of knows paths to fallback if the file doesn't exists + (eg: ~/.gajim/sounds/, DATADIR/sounds...). + :return the path to file or None if it doesn't exists.''' + if not file: + return None + elif os.path.exists(file): + return file - for d in dirs: - d = os.path.join(d, 'sounds', file) - if os.path.exists(d): - return d - return None + for d in dirs: + d = os.path.join(d, 'sounds', file) + if os.path.exists(d): + return d + return None def strip_soundfile_path(file, - dirs=(gajim.gajimpaths.root, gajim.DATA_DIR), - abs=True): - '''Remove knowns paths from a sound file: - Filechooser returns absolute path. If path is a known fallback path, we remove it. - So config have no hardcoded path to DATA_DIR and text in textfield is shorther. - param: file: the filename to strip. - param: dirs: list of knowns paths from which the filename should be stripped. - param: abs: force absolute path on dirs - ''' - if not file: - return None + dirs=(gajim.gajimpaths.root, gajim.DATA_DIR), + abs=True): + '''Remove knowns paths from a sound file: + Filechooser returns absolute path. If path is a known fallback path, we remove it. + So config have no hardcoded path to DATA_DIR and text in textfield is shorther. + param: file: the filename to strip. + param: dirs: list of knowns paths from which the filename should be stripped. + param: abs: force absolute path on dirs + ''' + if not file: + return None - name = os.path.basename(file) - for d in dirs: - d = os.path.join(d, 'sounds', name) - if abs: - d = os.path.abspath(d) - if file == d: - return name - return file + name = os.path.basename(file) + for d in dirs: + d = os.path.join(d, 'sounds', name) + if abs: + d = os.path.abspath(d) + if file == d: + return name + return file def play_sound_file(path_to_soundfile): - if path_to_soundfile == 'beep': - exec_command('beep') - return - path_to_soundfile = check_soundfile_path(path_to_soundfile) - if path_to_soundfile is None: - return - elif os.name == 'nt': - try: - winsound.PlaySound(path_to_soundfile, - winsound.SND_FILENAME|winsound.SND_ASYNC) - except Exception: - pass - elif os.name == 'posix': - if gajim.config.get('soundplayer') == '': - return - player = gajim.config.get('soundplayer') - command = build_command(player, path_to_soundfile) - exec_command(command) + if path_to_soundfile == 'beep': + exec_command('beep') + return + path_to_soundfile = check_soundfile_path(path_to_soundfile) + if path_to_soundfile is None: + return + elif os.name == 'nt': + try: + winsound.PlaySound(path_to_soundfile, + winsound.SND_FILENAME|winsound.SND_ASYNC) + except Exception: + pass + elif os.name == 'posix': + if gajim.config.get('soundplayer') == '': + return + player = gajim.config.get('soundplayer') + command = build_command(player, path_to_soundfile) + exec_command(command) def get_global_show(): - maxi = 0 - for account in gajim.connections: - if not gajim.config.get_per('accounts', account, - 'sync_with_global_status'): - continue - connected = gajim.connections[account].connected - if connected > maxi: - maxi = connected - return gajim.SHOW_LIST[maxi] + maxi = 0 + for account in gajim.connections: + if not gajim.config.get_per('accounts', account, + 'sync_with_global_status'): + continue + connected = gajim.connections[account].connected + if connected > maxi: + maxi = connected + return gajim.SHOW_LIST[maxi] def get_global_status(): - maxi = 0 - for account in gajim.connections: - if not gajim.config.get_per('accounts', account, - 'sync_with_global_status'): - continue - connected = gajim.connections[account].connected - if connected > maxi: - maxi = connected - status = gajim.connections[account].status - return status + maxi = 0 + for account in gajim.connections: + if not gajim.config.get_per('accounts', account, + 'sync_with_global_status'): + continue + connected = gajim.connections[account].connected + if connected > maxi: + maxi = connected + status = gajim.connections[account].status + return status def statuses_unified(): - '''testing if all statuses are the same.''' - reference = None - for account in gajim.connections: - if not gajim.config.get_per('accounts', account, - 'sync_with_global_status'): - continue - if reference is None: - reference = gajim.connections[account].connected - elif reference != gajim.connections[account].connected: - return False - return True + '''testing if all statuses are the same.''' + reference = None + for account in gajim.connections: + if not gajim.config.get_per('accounts', account, + 'sync_with_global_status'): + continue + if reference is None: + reference = gajim.connections[account].connected + elif reference != gajim.connections[account].connected: + return False + return True def get_icon_name_to_show(contact, account = None): - '''Get the icon name to show in online, away, requested, ...''' - if account and gajim.events.get_nb_roster_events(account, contact.jid): - return 'event' - if account and gajim.events.get_nb_roster_events(account, - contact.get_full_jid()): - return 'event' - if account and account in gajim.interface.minimized_controls and \ - contact.jid in gajim.interface.minimized_controls[account] and gajim.interface.\ - minimized_controls[account][contact.jid].get_nb_unread_pm() > 0: - return 'event' - if account and contact.jid in gajim.gc_connected[account]: - if gajim.gc_connected[account][contact.jid]: - return 'muc_active' - else: - return 'muc_inactive' - if contact.jid.find('@') <= 0: # if not '@' or '@' starts the jid ==> agent - return contact.show - if contact.sub in ('both', 'to'): - return contact.show - if contact.ask == 'subscribe': - return 'requested' - transport = gajim.get_transport_name_from_jid(contact.jid) - if transport: - return contact.show - if contact.show in gajim.SHOW_LIST: - return contact.show - return 'not in roster' + '''Get the icon name to show in online, away, requested, ...''' + if account and gajim.events.get_nb_roster_events(account, contact.jid): + return 'event' + if account and gajim.events.get_nb_roster_events(account, + contact.get_full_jid()): + return 'event' + if account and account in gajim.interface.minimized_controls and \ + contact.jid in gajim.interface.minimized_controls[account] and gajim.interface.\ + minimized_controls[account][contact.jid].get_nb_unread_pm() > 0: + return 'event' + if account and contact.jid in gajim.gc_connected[account]: + if gajim.gc_connected[account][contact.jid]: + return 'muc_active' + else: + return 'muc_inactive' + if contact.jid.find('@') <= 0: # if not '@' or '@' starts the jid ==> agent + return contact.show + if contact.sub in ('both', 'to'): + return contact.show + if contact.ask == 'subscribe': + return 'requested' + transport = gajim.get_transport_name_from_jid(contact.jid) + if transport: + return contact.show + if contact.show in gajim.SHOW_LIST: + return contact.show + return 'not in roster' def get_full_jid_from_iq(iq_obj): - '''return the full jid (with resource) from an iq as unicode''' - return parse_jid(str(iq_obj.getFrom())) + '''return the full jid (with resource) from an iq as unicode''' + return parse_jid(str(iq_obj.getFrom())) def get_jid_from_iq(iq_obj): - '''return the jid (without resource) from an iq as unicode''' - jid = get_full_jid_from_iq(iq_obj) - return gajim.get_jid_without_resource(jid) + '''return the jid (without resource) from an iq as unicode''' + jid = get_full_jid_from_iq(iq_obj) + return gajim.get_jid_without_resource(jid) def get_auth_sha(sid, initiator, target): - ''' return sha of sid + initiator + target used for proxy auth''' - return hashlib.sha1("%s%s%s" % (sid, initiator, target)).hexdigest() + ''' return sha of sid + initiator + target used for proxy auth''' + return hashlib.sha1("%s%s%s" % (sid, initiator, target)).hexdigest() def remove_invalid_xml_chars(string): - if string: - string = re.sub(gajim.interface.invalid_XML_chars_re, '', string) - return string + if string: + string = re.sub(gajim.interface.invalid_XML_chars_re, '', string) + return string distro_info = { - 'Arch Linux': '/etc/arch-release', - 'Aurox Linux': '/etc/aurox-release', - 'Conectiva Linux': '/etc/conectiva-release', - 'CRUX': '/usr/bin/crux', - 'Debian GNU/Linux': '/etc/debian_release', - 'Debian GNU/Linux': '/etc/debian_version', - 'Fedora Linux': '/etc/fedora-release', - 'Gentoo Linux': '/etc/gentoo-release', - 'Linux from Scratch': '/etc/lfs-release', - 'Mandrake Linux': '/etc/mandrake-release', - 'Slackware Linux': '/etc/slackware-release', - 'Slackware Linux': '/etc/slackware-version', - 'Solaris/Sparc': '/etc/release', - 'Source Mage': '/etc/sourcemage_version', - 'SUSE Linux': '/etc/SuSE-release', - 'Sun JDS': '/etc/sun-release', - 'PLD Linux': '/etc/pld-release', - 'Yellow Dog Linux': '/etc/yellowdog-release', - # many distros use the /etc/redhat-release for compatibility - # so Redhat is the last - 'Redhat Linux': '/etc/redhat-release' + 'Arch Linux': '/etc/arch-release', + 'Aurox Linux': '/etc/aurox-release', + 'Conectiva Linux': '/etc/conectiva-release', + 'CRUX': '/usr/bin/crux', + 'Debian GNU/Linux': '/etc/debian_release', + 'Debian GNU/Linux': '/etc/debian_version', + 'Fedora Linux': '/etc/fedora-release', + 'Gentoo Linux': '/etc/gentoo-release', + 'Linux from Scratch': '/etc/lfs-release', + 'Mandrake Linux': '/etc/mandrake-release', + 'Slackware Linux': '/etc/slackware-release', + 'Slackware Linux': '/etc/slackware-version', + 'Solaris/Sparc': '/etc/release', + 'Source Mage': '/etc/sourcemage_version', + 'SUSE Linux': '/etc/SuSE-release', + 'Sun JDS': '/etc/sun-release', + 'PLD Linux': '/etc/pld-release', + 'Yellow Dog Linux': '/etc/yellowdog-release', + # many distros use the /etc/redhat-release for compatibility + # so Redhat is the last + 'Redhat Linux': '/etc/redhat-release' } def get_random_string_16(): - ''' create random string of length 16''' - rng = range(65, 90) - rng.extend(range(48, 57)) - char_sequence = [chr(e) for e in rng] - from random import sample - return ''.join(sample(char_sequence, 16)) + ''' create random string of length 16''' + rng = range(65, 90) + rng.extend(range(48, 57)) + char_sequence = [chr(e) for e in rng] + from random import sample + return ''.join(sample(char_sequence, 16)) def get_os_info(): - if gajim.os_info: - return gajim.os_info - if os.name == 'nt': - ver = os.sys.getwindowsversion() - ver_format = ver[3], ver[0], ver[1] - win_version = { - (1, 4, 0): '95', - (1, 4, 10): '98', - (1, 4, 90): 'ME', - (2, 4, 0): 'NT', - (2, 5, 0): '2000', - (2, 5, 1): 'XP', - (2, 5, 2): '2003', - (2, 6, 0): 'Vista', - } - if ver_format in win_version: - os_info = 'Windows' + ' ' + win_version[ver_format] - else: - os_info = 'Windows' - gajim.os_info = os_info - return os_info - elif os.name == 'posix': - executable = 'lsb_release' - params = ' --description --codename --release --short' - full_path_to_executable = is_in_path(executable, return_abs_path = True) - if full_path_to_executable: - command = executable + params - p = subprocess.Popen([command], shell=True, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, close_fds=True) - p.wait() - output = temp_failure_retry(p.stdout.readline).strip() - # some distros put n/a in places, so remove those - output = output.replace('n/a', '').replace('N/A', '') - gajim.os_info = output - return output + if gajim.os_info: + return gajim.os_info + if os.name == 'nt': + ver = os.sys.getwindowsversion() + ver_format = ver[3], ver[0], ver[1] + win_version = { + (1, 4, 0): '95', + (1, 4, 10): '98', + (1, 4, 90): 'ME', + (2, 4, 0): 'NT', + (2, 5, 0): '2000', + (2, 5, 1): 'XP', + (2, 5, 2): '2003', + (2, 6, 0): 'Vista', + } + if ver_format in win_version: + os_info = 'Windows' + ' ' + win_version[ver_format] + else: + os_info = 'Windows' + gajim.os_info = os_info + return os_info + elif os.name == 'posix': + executable = 'lsb_release' + params = ' --description --codename --release --short' + full_path_to_executable = is_in_path(executable, return_abs_path = True) + if full_path_to_executable: + command = executable + params + p = subprocess.Popen([command], shell=True, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, close_fds=True) + p.wait() + output = temp_failure_retry(p.stdout.readline).strip() + # some distros put n/a in places, so remove those + output = output.replace('n/a', '').replace('N/A', '') + gajim.os_info = output + return output - # lsb_release executable not available, so parse files - for distro_name in distro_info: - path_to_file = distro_info[distro_name] - if os.path.exists(path_to_file): - if os.access(path_to_file, os.X_OK): - # the file is executable (f.e. CRUX) - # yes, then run it and get the first line of output. - text = get_output_of_command(path_to_file)[0] - else: - fd = open(path_to_file) - text = fd.readline().strip() # get only first line - fd.close() - if path_to_file.endswith('version'): - # sourcemage_version and slackware-version files - # have all the info we need (name and version of distro) - if not os.path.basename(path_to_file).startswith( - 'sourcemage') or not\ - os.path.basename(path_to_file).startswith('slackware'): - text = distro_name + ' ' + text - elif path_to_file.endswith('aurox-release') or \ - path_to_file.endswith('arch-release'): - # file doesn't have version - text = distro_name - elif path_to_file.endswith('lfs-release'): # file just has version - text = distro_name + ' ' + text - os_info = text.replace('\n', '') - gajim.os_info = os_info - return os_info + # lsb_release executable not available, so parse files + for distro_name in distro_info: + path_to_file = distro_info[distro_name] + if os.path.exists(path_to_file): + if os.access(path_to_file, os.X_OK): + # the file is executable (f.e. CRUX) + # yes, then run it and get the first line of output. + text = get_output_of_command(path_to_file)[0] + else: + fd = open(path_to_file) + text = fd.readline().strip() # get only first line + fd.close() + if path_to_file.endswith('version'): + # sourcemage_version and slackware-version files + # have all the info we need (name and version of distro) + if not os.path.basename(path_to_file).startswith( + 'sourcemage') or not\ + os.path.basename(path_to_file).startswith('slackware'): + text = distro_name + ' ' + text + elif path_to_file.endswith('aurox-release') or \ + path_to_file.endswith('arch-release'): + # file doesn't have version + text = distro_name + elif path_to_file.endswith('lfs-release'): # file just has version + text = distro_name + ' ' + text + os_info = text.replace('\n', '') + gajim.os_info = os_info + return os_info - # our last chance, ask uname and strip it - uname_output = get_output_of_command('uname -sr') - if uname_output is not None: - os_info = uname_output[0] # only first line - gajim.os_info = os_info - return os_info - os_info = 'N/A' - gajim.os_info = os_info - return os_info + # our last chance, ask uname and strip it + uname_output = get_output_of_command('uname -sr') + if uname_output is not None: + os_info = uname_output[0] # only first line + gajim.os_info = os_info + return os_info + os_info = 'N/A' + gajim.os_info = os_info + return os_info def allow_showing_notification(account, type_ = 'notify_on_new_message', advanced_notif_num = None, is_first_message = True): - '''is it allowed to show nofication? - check OUR status and if we allow notifications for that status - type is the option that need to be True e.g.: notify_on_signing - is_first_message: set it to false when it's not the first message''' - if advanced_notif_num is not None: - popup = gajim.config.get_per('notifications', str(advanced_notif_num), - 'popup') - if popup == 'yes': - return True - if popup == 'no': - return False - if type_ and (not gajim.config.get(type_) or not is_first_message): - return False - if gajim.config.get('autopopupaway'): # always show notification - return True - if gajim.connections[account].connected in (2, 3): # we're online or chat - return True - return False + '''is it allowed to show nofication? + check OUR status and if we allow notifications for that status + type is the option that need to be True e.g.: notify_on_signing + is_first_message: set it to false when it's not the first message''' + if advanced_notif_num is not None: + popup = gajim.config.get_per('notifications', str(advanced_notif_num), + 'popup') + if popup == 'yes': + return True + if popup == 'no': + return False + if type_ and (not gajim.config.get(type_) or not is_first_message): + return False + if gajim.config.get('autopopupaway'): # always show notification + return True + if gajim.connections[account].connected in (2, 3): # we're online or chat + return True + return False def allow_popup_window(account, advanced_notif_num = None): - '''is it allowed to popup windows?''' - if advanced_notif_num is not None: - popup = gajim.config.get_per('notifications', str(advanced_notif_num), - 'auto_open') - if popup == 'yes': - return True - if popup == 'no': - return False - autopopup = gajim.config.get('autopopup') - autopopupaway = gajim.config.get('autopopupaway') - if autopopup and (autopopupaway or \ - gajim.connections[account].connected in (2, 3)): # we're online or chat - return True - return False + '''is it allowed to popup windows?''' + if advanced_notif_num is not None: + popup = gajim.config.get_per('notifications', str(advanced_notif_num), + 'auto_open') + if popup == 'yes': + return True + if popup == 'no': + return False + autopopup = gajim.config.get('autopopup') + autopopupaway = gajim.config.get('autopopupaway') + if autopopup and (autopopupaway or \ + gajim.connections[account].connected in (2, 3)): # we're online or chat + return True + return False def allow_sound_notification(account, sound_event, advanced_notif_num=None): - if advanced_notif_num is not None: - sound = gajim.config.get_per('notifications', str(advanced_notif_num), - 'sound') - if sound == 'yes': - return True - if sound == 'no': - return False - if gajim.config.get('sounddnd') or gajim.connections[account].connected != \ - gajim.SHOW_LIST.index('dnd') and gajim.config.get_per('soundevents', - sound_event, 'enabled'): - return True - return False + if advanced_notif_num is not None: + sound = gajim.config.get_per('notifications', str(advanced_notif_num), + 'sound') + if sound == 'yes': + return True + if sound == 'no': + return False + if gajim.config.get('sounddnd') or gajim.connections[account].connected != \ + gajim.SHOW_LIST.index('dnd') and gajim.config.get_per('soundevents', + sound_event, 'enabled'): + return True + return False def get_chat_control(account, contact): - full_jid_with_resource = contact.jid - if contact.resource: - full_jid_with_resource += '/' + contact.resource - highest_contact = gajim.contacts.get_contact_with_highest_priority( - account, contact.jid) + full_jid_with_resource = contact.jid + if contact.resource: + full_jid_with_resource += '/' + contact.resource + highest_contact = gajim.contacts.get_contact_with_highest_priority( + account, contact.jid) - # Look for a chat control that has the given resource, or default to - # one without resource - ctrl = gajim.interface.msg_win_mgr.get_control(full_jid_with_resource, - account) + # Look for a chat control that has the given resource, or default to + # one without resource + ctrl = gajim.interface.msg_win_mgr.get_control(full_jid_with_resource, + account) - if ctrl: - return ctrl - elif highest_contact and highest_contact.resource and \ - contact.resource != highest_contact.resource: - return None - else: - # unknown contact or offline message - return gajim.interface.msg_win_mgr.get_control(contact.jid, account) + if ctrl: + return ctrl + elif highest_contact and highest_contact.resource and \ + contact.resource != highest_contact.resource: + return None + else: + # unknown contact or offline message + return gajim.interface.msg_win_mgr.get_control(contact.jid, account) def get_notification_icon_tooltip_dict(): - '''returns a dict of the form {acct: {'show': show, 'message': message, - 'event_lines': [list of text lines to show in tooltip]}''' - # How many events must there be before they're shown summarized, not per-user - max_ungrouped_events = 10 + '''returns a dict of the form {acct: {'show': show, 'message': message, + 'event_lines': [list of text lines to show in tooltip]}''' + # How many events must there be before they're shown summarized, not per-user + max_ungrouped_events = 10 - accounts = get_accounts_info() + accounts = get_accounts_info() - # Gather events. (With accounts, when there are more.) - for account in accounts: - account_name = account['name'] - account['event_lines'] = [] - # Gather events per-account - pending_events = gajim.events.get_events(account = account_name) - messages, non_messages, total_messages, total_non_messages = {}, {}, 0, 0 - for jid in pending_events: - for event in pending_events[jid]: - if event.type_.count('file') > 0: - # This is a non-messagee event. - messages[jid] = non_messages.get(jid, 0) + 1 - total_non_messages = total_non_messages + 1 - else: - # This is a message. - messages[jid] = messages.get(jid, 0) + 1 - total_messages = total_messages + 1 - # Display unread messages numbers, if any - if total_messages > 0: - if total_messages > max_ungrouped_events: - text = ngettext( - '%d message pending', - '%d messages pending', - total_messages, total_messages, total_messages) - account['event_lines'].append(text) - else: - for jid in messages.keys(): - text = ngettext( - '%d message pending', - '%d messages pending', - messages[jid], messages[jid], messages[jid]) - contact = gajim.contacts.get_first_contact_from_jid( - account['name'], jid) - if jid in gajim.gc_connected[account['name']]: - text += _(' from room %s') % (jid) - elif contact: - name = contact.get_shown_name() - text += _(' from user %s') % (name) - else: - text += _(' from %s') % (jid) - account['event_lines'].append(text) + # Gather events. (With accounts, when there are more.) + for account in accounts: + account_name = account['name'] + account['event_lines'] = [] + # Gather events per-account + pending_events = gajim.events.get_events(account = account_name) + messages, non_messages, total_messages, total_non_messages = {}, {}, 0, 0 + for jid in pending_events: + for event in pending_events[jid]: + if event.type_.count('file') > 0: + # This is a non-messagee event. + messages[jid] = non_messages.get(jid, 0) + 1 + total_non_messages = total_non_messages + 1 + else: + # This is a message. + messages[jid] = messages.get(jid, 0) + 1 + total_messages = total_messages + 1 + # Display unread messages numbers, if any + if total_messages > 0: + if total_messages > max_ungrouped_events: + text = ngettext( + '%d message pending', + '%d messages pending', + total_messages, total_messages, total_messages) + account['event_lines'].append(text) + else: + for jid in messages.keys(): + text = ngettext( + '%d message pending', + '%d messages pending', + messages[jid], messages[jid], messages[jid]) + contact = gajim.contacts.get_first_contact_from_jid( + account['name'], jid) + if jid in gajim.gc_connected[account['name']]: + text += _(' from room %s') % (jid) + elif contact: + name = contact.get_shown_name() + text += _(' from user %s') % (name) + else: + text += _(' from %s') % (jid) + account['event_lines'].append(text) - # Display unseen events numbers, if any - if total_non_messages > 0: - if total_non_messages > max_ungrouped_events: - text = ngettext( - '%d event pending', - '%d events pending', - total_non_messages, total_non_messages, total_non_messages) - account['event_lines'].append(text) - else: - for jid in non_messages.keys(): - text = ngettext( - '%d event pending', - '%d events pending', - non_messages[jid], non_messages[jid], non_messages[jid]) - text += _(' from user %s') % (jid) - account[account]['event_lines'].append(text) + # Display unseen events numbers, if any + if total_non_messages > 0: + if total_non_messages > max_ungrouped_events: + text = ngettext( + '%d event pending', + '%d events pending', + total_non_messages, total_non_messages, total_non_messages) + account['event_lines'].append(text) + else: + for jid in non_messages.keys(): + text = ngettext( + '%d event pending', + '%d events pending', + non_messages[jid], non_messages[jid], non_messages[jid]) + text += _(' from user %s') % (jid) + account[account]['event_lines'].append(text) - return accounts + return accounts def get_notification_icon_tooltip_text(): - text = None - # How many events must there be before they're shown summarized, not per-user - # max_ungrouped_events = 10 - # Character which should be used to indent in the tooltip. - indent_with = ' ' + text = None + # How many events must there be before they're shown summarized, not per-user + # max_ungrouped_events = 10 + # Character which should be used to indent in the tooltip. + indent_with = ' ' - accounts = get_notification_icon_tooltip_dict() + accounts = get_notification_icon_tooltip_dict() - if len(accounts) == 0: - # No configured account - return _('Gajim') + if len(accounts) == 0: + # No configured account + return _('Gajim') - # at least one account present + # at least one account present - # Is there more that one account? - if len(accounts) == 1: - show_more_accounts = False - else: - show_more_accounts = True + # Is there more that one account? + if len(accounts) == 1: + show_more_accounts = False + else: + show_more_accounts = True - # If there is only one account, its status is shown on the first line. - if show_more_accounts: - text = _('Gajim') - else: - text = _('Gajim - %s') % (get_account_status(accounts[0])) + # If there is only one account, its status is shown on the first line. + if show_more_accounts: + text = _('Gajim') + else: + text = _('Gajim - %s') % (get_account_status(accounts[0])) - # Gather and display events. (With accounts, when there are more.) - for account in accounts: - account_name = account['name'] - # Set account status, if not set above - if (show_more_accounts): - message = '\n' + indent_with + ' %s - %s' - text += message % (account_name, get_account_status(account)) - # Account list shown, messages need to be indented more - indent_how = 2 - else: - # If no account list is shown, messages could have default indenting. - indent_how = 1 - for line in account['event_lines']: - text += '\n' + indent_with * indent_how + ' ' - text += line - return text + # Gather and display events. (With accounts, when there are more.) + for account in accounts: + account_name = account['name'] + # Set account status, if not set above + if (show_more_accounts): + message = '\n' + indent_with + ' %s - %s' + text += message % (account_name, get_account_status(account)) + # Account list shown, messages need to be indented more + indent_how = 2 + else: + # If no account list is shown, messages could have default indenting. + indent_how = 1 + for line in account['event_lines']: + text += '\n' + indent_with * indent_how + ' ' + text += line + return text def get_accounts_info(): - '''helper for notification icon tooltip''' - accounts = [] - accounts_list = sorted(gajim.contacts.get_accounts()) - for account in accounts_list: - status_idx = gajim.connections[account].connected - # uncomment the following to hide offline accounts - # if status_idx == 0: continue - status = gajim.SHOW_LIST[status_idx] - message = gajim.connections[account].status - single_line = get_uf_show(status) - if message is None: - message = '' - else: - message = message.strip() - if message != '': - single_line += ': ' + message - accounts.append({'name': account, 'status_line': single_line, - 'show': status, 'message': message}) - return accounts + '''helper for notification icon tooltip''' + accounts = [] + accounts_list = sorted(gajim.contacts.get_accounts()) + for account in accounts_list: + status_idx = gajim.connections[account].connected + # uncomment the following to hide offline accounts + # if status_idx == 0: continue + status = gajim.SHOW_LIST[status_idx] + message = gajim.connections[account].status + single_line = get_uf_show(status) + if message is None: + message = '' + else: + message = message.strip() + if message != '': + single_line += ': ' + message + accounts.append({'name': account, 'status_line': single_line, + 'show': status, 'message': message}) + return accounts def get_iconset_path(iconset): - if os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', iconset)): - return os.path.join(gajim.DATA_DIR, 'iconsets', iconset) - elif os.path.isdir(os.path.join(gajim.MY_ICONSETS_PATH, iconset)): - return os.path.join(gajim.MY_ICONSETS_PATH, iconset) + if os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', iconset)): + return os.path.join(gajim.DATA_DIR, 'iconsets', iconset) + elif os.path.isdir(os.path.join(gajim.MY_ICONSETS_PATH, iconset)): + return os.path.join(gajim.MY_ICONSETS_PATH, iconset) def get_mood_iconset_path(iconset): - if os.path.isdir(os.path.join(gajim.DATA_DIR, 'moods', iconset)): - return os.path.join(gajim.DATA_DIR, 'moods', iconset) - elif os.path.isdir(os.path.join(gajim.MY_MOOD_ICONSETS_PATH, iconset)): - return os.path.join(gajim.MY_MOOD_ICONSETS_PATH, iconset) + if os.path.isdir(os.path.join(gajim.DATA_DIR, 'moods', iconset)): + return os.path.join(gajim.DATA_DIR, 'moods', iconset) + elif os.path.isdir(os.path.join(gajim.MY_MOOD_ICONSETS_PATH, iconset)): + return os.path.join(gajim.MY_MOOD_ICONSETS_PATH, iconset) def get_activity_iconset_path(iconset): - if os.path.isdir(os.path.join(gajim.DATA_DIR, 'activities', iconset)): - return os.path.join(gajim.DATA_DIR, 'activities', iconset) - elif os.path.isdir(os.path.join(gajim.MY_ACTIVITY_ICONSETS_PATH, - iconset)): - return os.path.join(gajim.MY_ACTIVITY_ICONSETS_PATH, iconset) + if os.path.isdir(os.path.join(gajim.DATA_DIR, 'activities', iconset)): + return os.path.join(gajim.DATA_DIR, 'activities', iconset) + elif os.path.isdir(os.path.join(gajim.MY_ACTIVITY_ICONSETS_PATH, + iconset)): + return os.path.join(gajim.MY_ACTIVITY_ICONSETS_PATH, iconset) def get_transport_path(transport): - if os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', 'transports', - transport)): - return os.path.join(gajim.DATA_DIR, 'iconsets', 'transports', transport) - elif os.path.isdir(os.path.join(gajim.MY_ICONSETS_PATH, 'transports', - transport)): - return os.path.join(gajim.MY_ICONSETS_PATH, 'transports', transport) - # No transport folder found, use default jabber one - return get_iconset_path(gajim.config.get('iconset')) + if os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', 'transports', + transport)): + return os.path.join(gajim.DATA_DIR, 'iconsets', 'transports', transport) + elif os.path.isdir(os.path.join(gajim.MY_ICONSETS_PATH, 'transports', + transport)): + return os.path.join(gajim.MY_ICONSETS_PATH, 'transports', transport) + # No transport folder found, use default jabber one + return get_iconset_path(gajim.config.get('iconset')) def prepare_and_validate_gpg_keyID(account, jid, keyID): - '''Returns an eight char long keyID that can be used with for GPG encryption with this contact. - If the given keyID is None, return UNKNOWN; if the key does not match the assigned key - XXXXXXXXMISMATCH is returned. If the key is trusted and not yet assigned, assign it''' - if gajim.connections[account].USE_GPG: - if keyID and len(keyID) == 16: - keyID = keyID[8:] + '''Returns an eight char long keyID that can be used with for GPG encryption with this contact. + If the given keyID is None, return UNKNOWN; if the key does not match the assigned key + XXXXXXXXMISMATCH is returned. If the key is trusted and not yet assigned, assign it''' + if gajim.connections[account].USE_GPG: + if keyID and len(keyID) == 16: + keyID = keyID[8:] - attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() + attached_keys = gajim.config.get_per('accounts', account, + 'attached_gpg_keys').split() - if jid in attached_keys and keyID: - attachedkeyID = attached_keys[attached_keys.index(jid) + 1] - if attachedkeyID != keyID: - # Mismatch! Another gpg key was expected - keyID += 'MISMATCH' - elif jid in attached_keys: - # An unsigned presence, just use the assigned key - keyID = attached_keys[attached_keys.index(jid) + 1] - elif keyID: - public_keys = gajim.connections[account].ask_gpg_keys() - # Assign the corresponding key, if we have it in our keyring - if keyID in public_keys: - for u in gajim.contacts.get_contacts(account, jid): - u.keyID = keyID - keys_str = gajim.config.get_per('accounts', account, 'attached_gpg_keys') - keys_str += jid + ' ' + keyID + ' ' - gajim.config.set_per('accounts', account, 'attached_gpg_keys', keys_str) - elif keyID is None: - keyID = 'UNKNOWN' - return keyID + if jid in attached_keys and keyID: + attachedkeyID = attached_keys[attached_keys.index(jid) + 1] + if attachedkeyID != keyID: + # Mismatch! Another gpg key was expected + keyID += 'MISMATCH' + elif jid in attached_keys: + # An unsigned presence, just use the assigned key + keyID = attached_keys[attached_keys.index(jid) + 1] + elif keyID: + public_keys = gajim.connections[account].ask_gpg_keys() + # Assign the corresponding key, if we have it in our keyring + if keyID in public_keys: + for u in gajim.contacts.get_contacts(account, jid): + u.keyID = keyID + keys_str = gajim.config.get_per('accounts', account, 'attached_gpg_keys') + keys_str += jid + ' ' + keyID + ' ' + gajim.config.set_per('accounts', account, 'attached_gpg_keys', keys_str) + elif keyID is None: + keyID = 'UNKNOWN' + return keyID def update_optional_features(account = None): - import xmpp - if account: - accounts = [account] - else: - accounts = [a for a in gajim.connections] - for a in accounts: - gajim.gajim_optional_features[a] = [] - if gajim.config.get_per('accounts', a, 'subscribe_mood'): - gajim.gajim_optional_features[a].append(xmpp.NS_MOOD + '+notify') - if gajim.config.get_per('accounts', a, 'subscribe_activity'): - gajim.gajim_optional_features[a].append(xmpp.NS_ACTIVITY + '+notify') - if gajim.config.get_per('accounts', a, 'publish_tune'): - gajim.gajim_optional_features[a].append(xmpp.NS_TUNE) - if gajim.config.get_per('accounts', a, 'subscribe_tune'): - gajim.gajim_optional_features[a].append(xmpp.NS_TUNE + '+notify') - if gajim.config.get_per('accounts', a, 'subscribe_nick'): - gajim.gajim_optional_features[a].append(xmpp.NS_NICK + '+notify') - if gajim.config.get('outgoing_chat_state_notifactions') != 'disabled': - gajim.gajim_optional_features[a].append(xmpp.NS_CHATSTATES) - if not gajim.config.get('ignore_incoming_xhtml'): - gajim.gajim_optional_features[a].append(xmpp.NS_XHTML_IM) - if gajim.HAVE_PYCRYPTO \ - and gajim.config.get_per('accounts', a, 'enable_esessions'): - gajim.gajim_optional_features[a].append(xmpp.NS_ESESSION) - if gajim.config.get_per('accounts', a, 'answer_receipts'): - gajim.gajim_optional_features[a].append(xmpp.NS_RECEIPTS) - if gajim.HAVE_FARSIGHT: - gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE) - gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP) - gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_AUDIO) - gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_VIDEO) - gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_ICE_UDP) - gajim.caps_hash[a] = caps.compute_caps_hash([gajim.gajim_identity], - gajim.gajim_common_features + gajim.gajim_optional_features[a]) - # re-send presence with new hash - connected = gajim.connections[a].connected - if connected > 1 and gajim.SHOW_LIST[connected] != 'invisible': - gajim.connections[a].change_status(gajim.SHOW_LIST[connected], - gajim.connections[a].status) + import xmpp + if account: + accounts = [account] + else: + accounts = [a for a in gajim.connections] + for a in accounts: + gajim.gajim_optional_features[a] = [] + if gajim.config.get_per('accounts', a, 'subscribe_mood'): + gajim.gajim_optional_features[a].append(xmpp.NS_MOOD + '+notify') + if gajim.config.get_per('accounts', a, 'subscribe_activity'): + gajim.gajim_optional_features[a].append(xmpp.NS_ACTIVITY + '+notify') + if gajim.config.get_per('accounts', a, 'publish_tune'): + gajim.gajim_optional_features[a].append(xmpp.NS_TUNE) + if gajim.config.get_per('accounts', a, 'subscribe_tune'): + gajim.gajim_optional_features[a].append(xmpp.NS_TUNE + '+notify') + if gajim.config.get_per('accounts', a, 'subscribe_nick'): + gajim.gajim_optional_features[a].append(xmpp.NS_NICK + '+notify') + if gajim.config.get('outgoing_chat_state_notifactions') != 'disabled': + gajim.gajim_optional_features[a].append(xmpp.NS_CHATSTATES) + if not gajim.config.get('ignore_incoming_xhtml'): + gajim.gajim_optional_features[a].append(xmpp.NS_XHTML_IM) + if gajim.HAVE_PYCRYPTO \ + and gajim.config.get_per('accounts', a, 'enable_esessions'): + gajim.gajim_optional_features[a].append(xmpp.NS_ESESSION) + if gajim.config.get_per('accounts', a, 'answer_receipts'): + gajim.gajim_optional_features[a].append(xmpp.NS_RECEIPTS) + if gajim.HAVE_FARSIGHT: + gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE) + gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP) + gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_AUDIO) + gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_VIDEO) + gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_ICE_UDP) + gajim.caps_hash[a] = caps.compute_caps_hash([gajim.gajim_identity], + gajim.gajim_common_features + gajim.gajim_optional_features[a]) + # re-send presence with new hash + connected = gajim.connections[a].connected + if connected > 1 and gajim.SHOW_LIST[connected] != 'invisible': + gajim.connections[a].change_status(gajim.SHOW_LIST[connected], + gajim.connections[a].status) def jid_is_blocked(account, jid): - return ((jid in gajim.connections[account].blocked_contacts) or \ - gajim.connections[account].blocked_all) + return ((jid in gajim.connections[account].blocked_contacts) or \ + gajim.connections[account].blocked_all) def group_is_blocked(account, group): - return ((group in gajim.connections[account].blocked_groups) or \ - gajim.connections[account].blocked_all) - -# vim: se ts=3: + return ((group in gajim.connections[account].blocked_groups) or \ + gajim.connections[account].blocked_all) diff --git a/src/common/i18n.py b/src/common/i18n.py index 3c47d3da3..54032ed0f 100644 --- a/src/common/i18n.py +++ b/src/common/i18n.py @@ -29,20 +29,20 @@ import defs import unicodedata def paragraph_direction_mark(text): - """ - Determine paragraph writing direction according to - http://www.unicode.org/reports/tr9/#The_Paragraph_Level - - Returns either Unicode LTR mark or RTL mark. - """ - for char in text: - bidi = unicodedata.bidirectional(char) - if bidi == 'L': - return u'\u200E' - elif bidi == 'AL' or bidi == 'R': - return u'\u200F' + """ + Determine paragraph writing direction according to + http://www.unicode.org/reports/tr9/#The_Paragraph_Level - return u'\u200E' + Returns either Unicode LTR mark or RTL mark. + """ + for char in text: + bidi = unicodedata.bidirectional(char) + if bidi == 'L': + return u'\u200E' + elif bidi == 'AL' or bidi == 'R': + return u'\u200F' + + return u'\u200E' APP = 'gajim' DIR = defs.localedir @@ -53,47 +53,45 @@ locale.setlocale(locale.LC_ALL, '') ## For windows: set, if needed, a value in LANG environmental variable ## if os.name == 'nt': - lang = os.getenv('LANG') - if lang is None: - default_lang = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc.. - if default_lang: - lang = default_lang + lang = os.getenv('LANG') + if lang is None: + default_lang = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc.. + if default_lang: + lang = default_lang - if lang: - os.environ['LANG'] = lang + if lang: + os.environ['LANG'] = lang gettext.install(APP, DIR, unicode = True) if gettext._translations: - _translation = gettext._translations.values()[0] + _translation = gettext._translations.values()[0] else: - _translation = gettext.NullTranslations() + _translation = gettext.NullTranslations() def Q_(s): - # Qualified translatable strings - # Some strings are too ambiguous to be easily translated. - # so we must use as: - # s = Q_('?vcard:Unknown') - # widget.set_text(s) - # Q_() removes the ?vcard: - # but gettext while parsing the file detects ?vcard:Unknown as a whole string. - # translator can either put the ?vcard: part or no (easier for him or her to no) - # nothing fails - s = _(s) - if s[0] == '?': - s = s[s.find(':')+1:] # remove ?abc: part - return s + # Qualified translatable strings + # Some strings are too ambiguous to be easily translated. + # so we must use as: + # s = Q_('?vcard:Unknown') + # widget.set_text(s) + # Q_() removes the ?vcard: + # but gettext while parsing the file detects ?vcard:Unknown as a whole string. + # translator can either put the ?vcard: part or no (easier for him or her to no) + # nothing fails + s = _(s) + if s[0] == '?': + s = s[s.find(':')+1:] # remove ?abc: part + return s def ngettext(s_sing, s_plural, n, replace_sing = None, replace_plural = None): - '''use as: - i18n.ngettext('leave room %s', 'leave rooms %s', len(rooms), 'a', 'a, b, c') + '''use as: + i18n.ngettext('leave room %s', 'leave rooms %s', len(rooms), 'a', 'a, b, c') - in other words this is a hack to ngettext() to support %s %d etc.. - ''' - text = _translation.ungettext(s_sing, s_plural, n) - if n == 1 and replace_sing is not None: - text = text % replace_sing - elif n > 1 and replace_plural is not None: - text = text % replace_plural - return text - -# vim: se ts=3: + in other words this is a hack to ngettext() to support %s %d etc.. + ''' + text = _translation.ungettext(s_sing, s_plural, n) + if n == 1 and replace_sing is not None: + text = text % replace_sing + elif n > 1 and replace_plural is not None: + text = text % replace_plural + return text diff --git a/src/common/idle.py b/src/common/idle.py index eb7e1be6a..4cfec6db2 100644 --- a/src/common/idle.py +++ b/src/common/idle.py @@ -20,14 +20,14 @@ import ctypes import ctypes.util class XScreenSaverInfo(ctypes.Structure): - _fields_ = [ - ('window', ctypes.c_ulong), - ('state', ctypes.c_int), - ('kind', ctypes.c_int), - ('til_or_since', ctypes.c_ulong), - ('idle', ctypes.c_ulong), - ('eventMask', ctypes.c_ulong) - ] + _fields_ = [ + ('window', ctypes.c_ulong), + ('state', ctypes.c_int), + ('kind', ctypes.c_int), + ('til_or_since', ctypes.c_ulong), + ('idle', ctypes.c_ulong), + ('eventMask', ctypes.c_ulong) + ] XScreenSaverInfo_p = ctypes.POINTER(XScreenSaverInfo) display_p = ctypes.c_void_p @@ -35,62 +35,62 @@ xid = ctypes.c_ulong c_int_p = ctypes.POINTER(ctypes.c_int) try: - libX11path = ctypes.util.find_library('X11') - if libX11path == None: - raise OSError('libX11 could not be found.') - libX11 = ctypes.cdll.LoadLibrary(libX11path) - libX11.XOpenDisplay.restype = display_p - libX11.XOpenDisplay.argtypes = ctypes.c_char_p, - libX11.XDefaultRootWindow.restype = xid - libX11.XDefaultRootWindow.argtypes = display_p, + libX11path = ctypes.util.find_library('X11') + if libX11path == None: + raise OSError('libX11 could not be found.') + libX11 = ctypes.cdll.LoadLibrary(libX11path) + libX11.XOpenDisplay.restype = display_p + libX11.XOpenDisplay.argtypes = ctypes.c_char_p, + libX11.XDefaultRootWindow.restype = xid + libX11.XDefaultRootWindow.argtypes = display_p, - libXsspath = ctypes.util.find_library('Xss') - if libXsspath == None: - raise OSError('libXss could not be found.') - libXss = ctypes.cdll.LoadLibrary(libXsspath) - libXss.XScreenSaverQueryExtension.argtypes = display_p, c_int_p, c_int_p - libXss.XScreenSaverAllocInfo.restype = XScreenSaverInfo_p - libXss.XScreenSaverQueryInfo.argtypes = (display_p, xid, XScreenSaverInfo_p) + libXsspath = ctypes.util.find_library('Xss') + if libXsspath == None: + raise OSError('libXss could not be found.') + libXss = ctypes.cdll.LoadLibrary(libXsspath) + libXss.XScreenSaverQueryExtension.argtypes = display_p, c_int_p, c_int_p + libXss.XScreenSaverAllocInfo.restype = XScreenSaverInfo_p + libXss.XScreenSaverQueryInfo.argtypes = (display_p, xid, XScreenSaverInfo_p) - dpy_p = libX11.XOpenDisplay(None) - if dpy_p == None: - raise OSError('Could not open X Display.') + dpy_p = libX11.XOpenDisplay(None) + if dpy_p == None: + raise OSError('Could not open X Display.') - _event_basep = ctypes.c_int() - _error_basep = ctypes.c_int() - if libXss.XScreenSaverQueryExtension(dpy_p, ctypes.byref(_event_basep), - ctypes.byref(_error_basep)) == 0: - raise OSError('XScreenSaver Extension not available on display.') + _event_basep = ctypes.c_int() + _error_basep = ctypes.c_int() + if libXss.XScreenSaverQueryExtension(dpy_p, ctypes.byref(_event_basep), + ctypes.byref(_error_basep)) == 0: + raise OSError('XScreenSaver Extension not available on display.') - xss_info_p = libXss.XScreenSaverAllocInfo() - if xss_info_p == None: - raise OSError('XScreenSaverAllocInfo: Out of Memory.') + xss_info_p = libXss.XScreenSaverAllocInfo() + if xss_info_p == None: + raise OSError('XScreenSaverAllocInfo: Out of Memory.') - rootwindow = libX11.XDefaultRootWindow(dpy_p) - xss_available = True + rootwindow = libX11.XDefaultRootWindow(dpy_p) + xss_available = True except OSError, e: - # Logging? - xss_available = False + # Logging? + xss_available = False def getIdleSec(): - """Returns the idle time in seconds""" - if not xss_available: - return 0 - if libXss.XScreenSaverQueryInfo(dpy_p, rootwindow, xss_info_p) == 0: - return 0 - else: - return int(xss_info_p.contents.idle) / 1000 + """Returns the idle time in seconds""" + if not xss_available: + return 0 + if libXss.XScreenSaverQueryInfo(dpy_p, rootwindow, xss_info_p) == 0: + return 0 + else: + return int(xss_info_p.contents.idle) / 1000 def close(): - global xss_available - if xss_available: - libX11.XFree(xss_info_p) - libX11.XCloseDisplay(dpy_p) - xss_available = False + global xss_available + if xss_available: + libX11.XFree(xss_info_p) + libX11.XCloseDisplay(dpy_p) + xss_available = False if __name__ == '__main__': - import time - time.sleep(2.1) - print getIdleSec() - close() - print getIdleSec() + import time + time.sleep(2.1) + print getIdleSec() + close() + print getIdleSec() diff --git a/src/common/jingle.py b/src/common/jingle.py index 8a369ff7e..8f1a2e276 100644 --- a/src/common/jingle.py +++ b/src/common/jingle.py @@ -37,99 +37,99 @@ from jingle_rtp import JingleAudio, JingleVideo class ConnectionJingle(object): - ''' This object depends on that it is a part of Connection class. ''' - def __init__(self): - # dictionary: (jid, sessionid) => JingleSession object - self.__sessions = {} + ''' This object depends on that it is a part of Connection class. ''' + def __init__(self): + # dictionary: (jid, sessionid) => JingleSession object + self.__sessions = {} - # dictionary: (jid, iq stanza id) => JingleSession object, - # one time callbacks - self.__iq_responses = {} + # dictionary: (jid, iq stanza id) => JingleSession object, + # one time callbacks + self.__iq_responses = {} - def add_jingle(self, jingle): - ''' Add a jingle session to a jingle stanza dispatcher - jingle - a JingleSession object. - ''' - self.__sessions[(jingle.peerjid, jingle.sid)] = jingle + def add_jingle(self, jingle): + ''' Add a jingle session to a jingle stanza dispatcher + jingle - a JingleSession object. + ''' + self.__sessions[(jingle.peerjid, jingle.sid)] = jingle - def delete_jingle_session(self, peerjid, sid): - ''' Remove a jingle session from a jingle stanza dispatcher ''' - key = (peerjid, sid) - if key in self.__sessions: - #FIXME: Move this elsewhere? - for content in self.__sessions[key].contents.values(): - content.destroy() - self.__sessions[key].callbacks = [] - del self.__sessions[key] + def delete_jingle_session(self, peerjid, sid): + ''' Remove a jingle session from a jingle stanza dispatcher ''' + key = (peerjid, sid) + if key in self.__sessions: + #FIXME: Move this elsewhere? + for content in self.__sessions[key].contents.values(): + content.destroy() + self.__sessions[key].callbacks = [] + del self.__sessions[key] - def _JingleCB(self, con, stanza): - ''' The jingle stanza dispatcher. - Route jingle stanza to proper JingleSession object, - or create one if it is a new session. - TODO: Also check if the stanza isn't an error stanza, if so - route it adequatelly.''' + def _JingleCB(self, con, stanza): + ''' The jingle stanza dispatcher. + Route jingle stanza to proper JingleSession object, + or create one if it is a new session. + TODO: Also check if the stanza isn't an error stanza, if so + route it adequatelly.''' - # get data - jid = helpers.get_full_jid_from_iq(stanza) - id = stanza.getID() + # get data + jid = helpers.get_full_jid_from_iq(stanza) + id = stanza.getID() - if (jid, id) in self.__iq_responses.keys(): - self.__iq_responses[(jid, id)].stanzaCB(stanza) - del self.__iq_responses[(jid, id)] - raise xmpp.NodeProcessed + if (jid, id) in self.__iq_responses.keys(): + self.__iq_responses[(jid, id)].stanzaCB(stanza) + del self.__iq_responses[(jid, id)] + raise xmpp.NodeProcessed - jingle = stanza.getTag('jingle') - if not jingle: return - sid = jingle.getAttr('sid') + jingle = stanza.getTag('jingle') + if not jingle: return + sid = jingle.getAttr('sid') - # do we need to create a new jingle object - if (jid, sid) not in self.__sessions: - #TODO: tie-breaking and other things... - newjingle = JingleSession(con=self, weinitiate=False, jid=jid, sid=sid) - self.add_jingle(newjingle) + # do we need to create a new jingle object + if (jid, sid) not in self.__sessions: + #TODO: tie-breaking and other things... + newjingle = JingleSession(con=self, weinitiate=False, jid=jid, sid=sid) + self.add_jingle(newjingle) - # we already have such session in dispatcher... - self.__sessions[(jid, sid)].stanzaCB(stanza) + # we already have such session in dispatcher... + self.__sessions[(jid, sid)].stanzaCB(stanza) - raise xmpp.NodeProcessed + raise xmpp.NodeProcessed - def startVoIP(self, jid): - if self.get_jingle_session(jid, media='audio'): - return self.get_jingle_session(jid, media='audio').sid - jingle = self.get_jingle_session(jid, media='video') - if jingle: - jingle.add_content('voice', JingleAudio(jingle)) - else: - jingle = JingleSession(self, weinitiate=True, jid=jid) - self.add_jingle(jingle) - jingle.add_content('voice', JingleAudio(jingle)) - jingle.start_session() - return jingle.sid + def startVoIP(self, jid): + if self.get_jingle_session(jid, media='audio'): + return self.get_jingle_session(jid, media='audio').sid + jingle = self.get_jingle_session(jid, media='video') + if jingle: + jingle.add_content('voice', JingleAudio(jingle)) + else: + jingle = JingleSession(self, weinitiate=True, jid=jid) + self.add_jingle(jingle) + jingle.add_content('voice', JingleAudio(jingle)) + jingle.start_session() + return jingle.sid - def startVideoIP(self, jid): - if self.get_jingle_session(jid, media='video'): - return self.get_jingle_session(jid, media='video').sid - jingle = self.get_jingle_session(jid, media='audio') - if jingle: - jingle.add_content('video', JingleVideo(jingle)) - else: - jingle = JingleSession(self, weinitiate=True, jid=jid) - self.add_jingle(jingle) - jingle.add_content('video', JingleVideo(jingle)) - jingle.start_session() - return jingle.sid + def startVideoIP(self, jid): + if self.get_jingle_session(jid, media='video'): + return self.get_jingle_session(jid, media='video').sid + jingle = self.get_jingle_session(jid, media='audio') + if jingle: + jingle.add_content('video', JingleVideo(jingle)) + else: + jingle = JingleSession(self, weinitiate=True, jid=jid) + self.add_jingle(jingle) + jingle.add_content('video', JingleVideo(jingle)) + jingle.start_session() + return jingle.sid - def get_jingle_session(self, jid, sid=None, media=None): - if sid: - if (jid, sid) in self.__sessions: - return self.__sessions[(jid, sid)] - else: - return None - elif media: - if media not in ('audio', 'video'): - return None - for session in self.__sessions.values(): - if session.peerjid == jid and session.get_content(media): - return session + def get_jingle_session(self, jid, sid=None, media=None): + if sid: + if (jid, sid) in self.__sessions: + return self.__sessions[(jid, sid)] + else: + return None + elif media: + if media not in ('audio', 'video'): + return None + for session in self.__sessions.values(): + if session.peerjid == jid and session.get_content(media): + return session - return None + return None diff --git a/src/common/jingle_content.py b/src/common/jingle_content.py index 2feb5d8de..f298eaab3 100644 --- a/src/common/jingle_content.py +++ b/src/common/jingle_content.py @@ -15,95 +15,95 @@ contents = {} def get_jingle_content(node): - namespace = node.getNamespace() - if namespace in contents: - return contents[namespace](node) - else: - return None + namespace = node.getNamespace() + if namespace in contents: + return contents[namespace](node) + else: + return None class JingleContent(object): - ''' An abstraction of content in Jingle sessions. ''' - def __init__(self, session, transport): - self.session = session - self.transport = transport - # will be filled by JingleSession.add_content() - # don't uncomment these lines, we will catch more buggy code then - # (a JingleContent not added to session shouldn't send anything) - #self.creator = None - #self.name = None - self.accepted = False - self.sent = False + ''' An abstraction of content in Jingle sessions. ''' + def __init__(self, session, transport): + self.session = session + self.transport = transport + # will be filled by JingleSession.add_content() + # don't uncomment these lines, we will catch more buggy code then + # (a JingleContent not added to session shouldn't send anything) + #self.creator = None + #self.name = None + self.accepted = False + self.sent = False - self.media = None + self.media = None - self.senders = 'both' #FIXME - self.allow_sending = True # Used for stream direction, attribute 'senders' + self.senders = 'both' #FIXME + self.allow_sending = True # Used for stream direction, attribute 'senders' - self.callbacks = { - # these are called when *we* get stanzas - 'content-accept': [self.__transportInfoCB], - 'content-add': [self.__transportInfoCB], - 'content-modify': [], - 'content-reject': [], - 'content-remove': [], - 'description-info': [], - 'security-info': [], - 'session-accept': [self.__transportInfoCB], - 'session-info': [], - 'session-initiate': [self.__transportInfoCB], - 'session-terminate': [], - 'transport-info': [self.__transportInfoCB], - 'transport-replace': [], - 'transport-accept': [], - 'transport-reject': [], - 'iq-result': [], - 'iq-error': [], - # these are called when *we* sent these stanzas - 'content-accept-sent': [self.__fillJingleStanza], - 'content-add-sent': [self.__fillJingleStanza], - 'session-initiate-sent': [self.__fillJingleStanza], - 'session-accept-sent': [self.__fillJingleStanza], - 'session-terminate-sent': [], - } + self.callbacks = { + # these are called when *we* get stanzas + 'content-accept': [self.__transportInfoCB], + 'content-add': [self.__transportInfoCB], + 'content-modify': [], + 'content-reject': [], + 'content-remove': [], + 'description-info': [], + 'security-info': [], + 'session-accept': [self.__transportInfoCB], + 'session-info': [], + 'session-initiate': [self.__transportInfoCB], + 'session-terminate': [], + 'transport-info': [self.__transportInfoCB], + 'transport-replace': [], + 'transport-accept': [], + 'transport-reject': [], + 'iq-result': [], + 'iq-error': [], + # these are called when *we* sent these stanzas + 'content-accept-sent': [self.__fillJingleStanza], + 'content-add-sent': [self.__fillJingleStanza], + 'session-initiate-sent': [self.__fillJingleStanza], + 'session-accept-sent': [self.__fillJingleStanza], + 'session-terminate-sent': [], + } - def is_ready(self): - return (self.accepted and not self.sent) + def is_ready(self): + return (self.accepted and not self.sent) - def add_remote_candidates(self, candidates): - ''' Add a list of candidates to the list of remote candidates. ''' - pass + def add_remote_candidates(self, candidates): + ''' Add a list of candidates to the list of remote candidates. ''' + pass - def stanzaCB(self, stanza, content, error, action): - ''' Called when something related to our content was sent by peer. ''' - if action in self.callbacks: - for callback in self.callbacks[action]: - callback(stanza, content, error, action) + def stanzaCB(self, stanza, content, error, action): + ''' Called when something related to our content was sent by peer. ''' + if action in self.callbacks: + for callback in self.callbacks[action]: + callback(stanza, content, error, action) - def __transportInfoCB(self, stanza, content, error, action): - ''' Got a new transport candidate. ''' - candidates = self.transport.parse_transport_stanza( - content.getTag('transport')) - if candidates: - self.add_remote_candidates(candidates) + def __transportInfoCB(self, stanza, content, error, action): + ''' Got a new transport candidate. ''' + candidates = self.transport.parse_transport_stanza( + content.getTag('transport')) + if candidates: + self.add_remote_candidates(candidates) - def __content(self, payload=[]): - ''' Build a XML content-wrapper for our data. ''' - return xmpp.Node('content', - attrs={'name': self.name, 'creator': self.creator}, - payload=payload) + def __content(self, payload=[]): + ''' Build a XML content-wrapper for our data. ''' + return xmpp.Node('content', + attrs={'name': self.name, 'creator': self.creator}, + payload=payload) - def send_candidate(self, candidate): - content = self.__content() - content.addChild(self.transport.make_transport([candidate])) - self.session.send_transport_info(content) + def send_candidate(self, candidate): + content = self.__content() + content.addChild(self.transport.make_transport([candidate])) + self.session.send_transport_info(content) - def __fillJingleStanza(self, stanza, content, error, action): - ''' Add our things to session-initiate stanza. ''' - self._fillContent(content) - self.sent = True - content.addChild(node=self.transport.make_transport()) + def __fillJingleStanza(self, stanza, content, error, action): + ''' Add our things to session-initiate stanza. ''' + self._fillContent(content) + self.sent = True + content.addChild(node=self.transport.make_transport()) - def destroy(self): - self.callbacks = None - del self.session.contents[(self.creator, self.name)] + def destroy(self): + self.callbacks = None + del self.session.contents[(self.creator, self.name)] diff --git a/src/common/jingle_rtp.py b/src/common/jingle_rtp.py index d67b0f465..56f8fdb44 100644 --- a/src/common/jingle_rtp.py +++ b/src/common/jingle_rtp.py @@ -23,299 +23,299 @@ from jingle_content import contents, JingleContent # TODO: Will that be even used? def get_first_gst_element(elements): - ''' Returns, if it exists, the first available element of the list. ''' - for name in elements: - factory = gst.element_factory_find(name) - if factory: - return factory.create() + ''' Returns, if it exists, the first available element of the list. ''' + for name in elements: + factory = gst.element_factory_find(name) + if factory: + return factory.create() class JingleRTPContent(JingleContent): - def __init__(self, session, media, transport=None): - if transport is None: - transport = JingleTransportICEUDP() - JingleContent.__init__(self, session, transport) - self.media = media - self._dtmf_running = False - self.farsight_media = {'audio': farsight.MEDIA_TYPE_AUDIO, - 'video': farsight.MEDIA_TYPE_VIDEO}[media] - self.got_codecs = False + def __init__(self, session, media, transport=None): + if transport is None: + transport = JingleTransportICEUDP() + JingleContent.__init__(self, session, transport) + self.media = media + self._dtmf_running = False + self.farsight_media = {'audio': farsight.MEDIA_TYPE_AUDIO, + 'video': farsight.MEDIA_TYPE_VIDEO}[media] + self.got_codecs = False - self.candidates_ready = False # True when local candidates are prepared + self.candidates_ready = False # True when local candidates are prepared - self.callbacks['session-initiate'] += [self.__getRemoteCodecsCB] - self.callbacks['content-add'] += [self.__getRemoteCodecsCB] - self.callbacks['content-accept'] += [self.__getRemoteCodecsCB, - self.__contentAcceptCB] - self.callbacks['session-accept'] += [self.__getRemoteCodecsCB, - self.__contentAcceptCB] - self.callbacks['session-accept-sent'] += [self.__contentAcceptCB] - self.callbacks['content-accept-sent'] += [self.__contentAcceptCB] - self.callbacks['session-terminate'] += [self.__stop] - self.callbacks['session-terminate-sent'] += [self.__stop] + self.callbacks['session-initiate'] += [self.__getRemoteCodecsCB] + self.callbacks['content-add'] += [self.__getRemoteCodecsCB] + self.callbacks['content-accept'] += [self.__getRemoteCodecsCB, + self.__contentAcceptCB] + self.callbacks['session-accept'] += [self.__getRemoteCodecsCB, + self.__contentAcceptCB] + self.callbacks['session-accept-sent'] += [self.__contentAcceptCB] + self.callbacks['content-accept-sent'] += [self.__contentAcceptCB] + self.callbacks['session-terminate'] += [self.__stop] + self.callbacks['session-terminate-sent'] += [self.__stop] - def setup_stream(self): - # pipeline and bus - self.pipeline = gst.Pipeline() - bus = self.pipeline.get_bus() - bus.add_signal_watch() - bus.connect('message', self._on_gst_message) + def setup_stream(self): + # pipeline and bus + self.pipeline = gst.Pipeline() + bus = self.pipeline.get_bus() + bus.add_signal_watch() + bus.connect('message', self._on_gst_message) - # conference - self.conference = gst.element_factory_make('fsrtpconference') - self.conference.set_property("sdes-cname", self.session.ourjid) - self.pipeline.add(self.conference) - self.funnel = None + # conference + self.conference = gst.element_factory_make('fsrtpconference') + self.conference.set_property("sdes-cname", self.session.ourjid) + self.pipeline.add(self.conference) + self.funnel = None - self.p2psession = self.conference.new_session(self.farsight_media) + self.p2psession = self.conference.new_session(self.farsight_media) - participant = self.conference.new_participant(self.session.peerjid) - #FIXME: Consider a workaround, here... - # pidgin and telepathy-gabble don't follow the XEP, and it won't work - # due to bad controlling-mode - params = {'controlling-mode': self.session.weinitiate,# 'debug': False} - 'stun-ip': '69.0.208.27', 'debug': False} + participant = self.conference.new_participant(self.session.peerjid) + #FIXME: Consider a workaround, here... + # pidgin and telepathy-gabble don't follow the XEP, and it won't work + # due to bad controlling-mode + params = {'controlling-mode': self.session.weinitiate,# 'debug': False} + 'stun-ip': '69.0.208.27', 'debug': False} - self.p2pstream = self.p2psession.new_stream(participant, - farsight.DIRECTION_RECV, 'nice', params) + self.p2pstream = self.p2psession.new_stream(participant, + farsight.DIRECTION_RECV, 'nice', params) - def is_ready(self): - return (JingleContent.is_ready(self) and self.candidates_ready - and self.p2psession.get_property('codecs-ready')) + def is_ready(self): + return (JingleContent.is_ready(self) and self.candidates_ready + and self.p2psession.get_property('codecs-ready')) - def add_remote_candidates(self, candidates): - JingleContent.add_remote_candidates(self, candidates) - #FIXME: connectivity should not be etablished yet - # Instead, it should be etablished after session-accept! - if self.sent: - self.p2pstream.set_remote_candidates(candidates) + def add_remote_candidates(self, candidates): + JingleContent.add_remote_candidates(self, candidates) + #FIXME: connectivity should not be etablished yet + # Instead, it should be etablished after session-accept! + if self.sent: + self.p2pstream.set_remote_candidates(candidates) - def batch_dtmf(self, events): - if self._dtmf_running: - raise Exception #TODO: Proper exception - self._dtmf_running = True - self._start_dtmf(events.pop(0)) - gobject.timeout_add(500, self._next_dtmf, events) + def batch_dtmf(self, events): + if self._dtmf_running: + raise Exception #TODO: Proper exception + self._dtmf_running = True + self._start_dtmf(events.pop(0)) + gobject.timeout_add(500, self._next_dtmf, events) - def _next_dtmf(self, events): - self._stop_dtmf() - if events: - self._start_dtmf(events.pop(0)) - gobject.timeout_add(500, self._next_dtmf, events) - else: - self._dtmf_running = False + def _next_dtmf(self, events): + self._stop_dtmf() + if events: + self._start_dtmf(events.pop(0)) + gobject.timeout_add(500, self._next_dtmf, events) + else: + self._dtmf_running = False - def _start_dtmf(self, event): - if event in ('*', '#'): - event = {'*': farsight.DTMF_EVENT_STAR, - '#': farsight.DTMF_EVENT_POUND}[event] - else: - event = int(event) - self.p2psession.start_telephony_event(event, 2, - farsight.DTMF_METHOD_RTP_RFC4733) + def _start_dtmf(self, event): + if event in ('*', '#'): + event = {'*': farsight.DTMF_EVENT_STAR, + '#': farsight.DTMF_EVENT_POUND}[event] + else: + event = int(event) + self.p2psession.start_telephony_event(event, 2, + farsight.DTMF_METHOD_RTP_RFC4733) - def _stop_dtmf(self): - self.p2psession.stop_telephony_event(farsight.DTMF_METHOD_RTP_RFC4733) + def _stop_dtmf(self): + self.p2psession.stop_telephony_event(farsight.DTMF_METHOD_RTP_RFC4733) - def _fillContent(self, content): - content.addChild(xmpp.NS_JINGLE_RTP + ' description', - attrs={'media': self.media}, payload=self.iter_codecs()) + def _fillContent(self, content): + content.addChild(xmpp.NS_JINGLE_RTP + ' description', + attrs={'media': self.media}, payload=self.iter_codecs()) - def _setup_funnel(self): - self.funnel = gst.element_factory_make('fsfunnel') - self.pipeline.add(self.funnel) - self.funnel.set_state(gst.STATE_PLAYING) - self.sink.set_state(gst.STATE_PLAYING) - self.funnel.link(self.sink) + def _setup_funnel(self): + self.funnel = gst.element_factory_make('fsfunnel') + self.pipeline.add(self.funnel) + self.funnel.set_state(gst.STATE_PLAYING) + self.sink.set_state(gst.STATE_PLAYING) + self.funnel.link(self.sink) - def _on_src_pad_added(self, stream, pad, codec): - if not self.funnel: - self._setup_funnel() - pad.link(self.funnel.get_pad('sink%d')) + def _on_src_pad_added(self, stream, pad, codec): + if not self.funnel: + self._setup_funnel() + pad.link(self.funnel.get_pad('sink%d')) - def _on_gst_message(self, bus, message): - if message.type == gst.MESSAGE_ELEMENT: - name = message.structure.get_name() - if name == 'farsight-new-active-candidate-pair': - pass - elif name == 'farsight-recv-codecs-changed': - pass - elif name == 'farsight-codecs-changed': - if self.is_ready(): - self.session.on_session_state_changed(self) - #TODO: description-info - elif name == 'farsight-local-candidates-prepared': - self.candidates_ready = True - if self.is_ready(): - self.session.on_session_state_changed(self) - elif name == 'farsight-new-local-candidate': - candidate = message.structure['candidate'] - self.transport.candidates.append(candidate) - if self.candidates_ready: - #FIXME: Is this case even possible? - self.send_candidate(candidate) - elif name == 'farsight-component-state-changed': - state = message.structure['state'] - print message.structure['component'], state - if state == farsight.STREAM_STATE_FAILED: - reason = xmpp.Node('reason') - reason.setTag('failed-transport') - self.session._session_terminate(reason) - elif name == 'farsight-error': - print 'Farsight error #%d!' % message.structure['error-no'] - print 'Message: %s' % message.structure['error-msg'] - print 'Debug: %s' % message.structure['debug-msg'] - else: - print name + def _on_gst_message(self, bus, message): + if message.type == gst.MESSAGE_ELEMENT: + name = message.structure.get_name() + if name == 'farsight-new-active-candidate-pair': + pass + elif name == 'farsight-recv-codecs-changed': + pass + elif name == 'farsight-codecs-changed': + if self.is_ready(): + self.session.on_session_state_changed(self) + #TODO: description-info + elif name == 'farsight-local-candidates-prepared': + self.candidates_ready = True + if self.is_ready(): + self.session.on_session_state_changed(self) + elif name == 'farsight-new-local-candidate': + candidate = message.structure['candidate'] + self.transport.candidates.append(candidate) + if self.candidates_ready: + #FIXME: Is this case even possible? + self.send_candidate(candidate) + elif name == 'farsight-component-state-changed': + state = message.structure['state'] + print message.structure['component'], state + if state == farsight.STREAM_STATE_FAILED: + reason = xmpp.Node('reason') + reason.setTag('failed-transport') + self.session._session_terminate(reason) + elif name == 'farsight-error': + print 'Farsight error #%d!' % message.structure['error-no'] + print 'Message: %s' % message.structure['error-msg'] + print 'Debug: %s' % message.structure['debug-msg'] + else: + print name - def __contentAcceptCB(self, stanza, content, error, action): - if self.accepted: - if self.transport.remote_candidates: - self.p2pstream.set_remote_candidates(self.transport.remote_candidates) - self.transport.remote_candidates = [] - #TODO: farsight.DIRECTION_BOTH only if senders='both' - self.p2pstream.set_property('direction', farsight.DIRECTION_BOTH) - self.session.content_negociated(self.media) + def __contentAcceptCB(self, stanza, content, error, action): + if self.accepted: + if self.transport.remote_candidates: + self.p2pstream.set_remote_candidates(self.transport.remote_candidates) + self.transport.remote_candidates = [] + #TODO: farsight.DIRECTION_BOTH only if senders='both' + self.p2pstream.set_property('direction', farsight.DIRECTION_BOTH) + self.session.content_negociated(self.media) - def __getRemoteCodecsCB(self, stanza, content, error, action): - ''' Get peer codecs from what we get from peer. ''' - if self.got_codecs: - return + def __getRemoteCodecsCB(self, stanza, content, error, action): + ''' Get peer codecs from what we get from peer. ''' + if self.got_codecs: + return - codecs = [] - for codec in content.getTag('description').iterTags('payload-type'): - c = farsight.Codec(int(codec['id']), codec['name'], - self.farsight_media, int(codec['clockrate'])) - if 'channels' in codec: - c.channels = int(codec['channels']) - else: - c.channels = 1 - c.optional_params = [(str(p['name']), str(p['value'])) for p in \ - codec.iterTags('parameter')] - codecs.append(c) + codecs = [] + for codec in content.getTag('description').iterTags('payload-type'): + c = farsight.Codec(int(codec['id']), codec['name'], + self.farsight_media, int(codec['clockrate'])) + if 'channels' in codec: + c.channels = int(codec['channels']) + else: + c.channels = 1 + c.optional_params = [(str(p['name']), str(p['value'])) for p in \ + codec.iterTags('parameter')] + codecs.append(c) - if len(codecs) > 0: - #FIXME: Handle this case: - # glib.GError: There was no intersection between the remote codecs and - # the local ones - self.p2pstream.set_remote_codecs(codecs) - self.got_codecs = True + if len(codecs) > 0: + #FIXME: Handle this case: + # glib.GError: There was no intersection between the remote codecs and + # the local ones + self.p2pstream.set_remote_codecs(codecs) + self.got_codecs = True - def iter_codecs(self): - codecs = self.p2psession.get_property('codecs') - for codec in codecs: - attrs = {'name': codec.encoding_name, - 'id': codec.id, - 'channels': codec.channels} - if codec.clock_rate: - attrs['clockrate'] = codec.clock_rate - if codec.optional_params: - payload = (xmpp.Node('parameter', {'name': name, 'value': value}) - for name, value in codec.optional_params) - else: payload = () - yield xmpp.Node('payload-type', attrs, payload) + def iter_codecs(self): + codecs = self.p2psession.get_property('codecs') + for codec in codecs: + attrs = {'name': codec.encoding_name, + 'id': codec.id, + 'channels': codec.channels} + if codec.clock_rate: + attrs['clockrate'] = codec.clock_rate + if codec.optional_params: + payload = (xmpp.Node('parameter', {'name': name, 'value': value}) + for name, value in codec.optional_params) + else: payload = () + yield xmpp.Node('payload-type', attrs, payload) - def __stop(self, *things): - self.pipeline.set_state(gst.STATE_NULL) + def __stop(self, *things): + self.pipeline.set_state(gst.STATE_NULL) - def __del__(self): - self.__stop() + def __del__(self): + self.__stop() - def destroy(self): - JingleContent.destroy(self) - self.p2pstream.disconnect_by_func(self._on_src_pad_added) - self.pipeline.get_bus().disconnect_by_func(self._on_gst_message) + def destroy(self): + JingleContent.destroy(self) + self.p2pstream.disconnect_by_func(self._on_src_pad_added) + self.pipeline.get_bus().disconnect_by_func(self._on_gst_message) class JingleAudio(JingleRTPContent): - ''' Jingle VoIP sessions consist of audio content transported - over an ICE UDP protocol. ''' - def __init__(self, session, transport=None): - JingleRTPContent.__init__(self, session, 'audio', transport) - self.setup_stream() + ''' Jingle VoIP sessions consist of audio content transported + over an ICE UDP protocol. ''' + def __init__(self, session, transport=None): + JingleRTPContent.__init__(self, session, 'audio', transport) + self.setup_stream() - ''' Things to control the gstreamer's pipeline ''' - def setup_stream(self): - JingleRTPContent.setup_stream(self) + ''' Things to control the gstreamer's pipeline ''' + def setup_stream(self): + JingleRTPContent.setup_stream(self) - # Configure SPEEX - # Workaround for psi (not needed since rev - # 147aedcea39b43402fe64c533d1866a25449888a): - # place 16kHz before 8kHz, as buggy psi versions will take in - # account only the first codec + # Configure SPEEX + # Workaround for psi (not needed since rev + # 147aedcea39b43402fe64c533d1866a25449888a): + # place 16kHz before 8kHz, as buggy psi versions will take in + # account only the first codec - codecs = [farsight.Codec(farsight.CODEC_ID_ANY, 'SPEEX', - farsight.MEDIA_TYPE_AUDIO, 16000), - farsight.Codec(farsight.CODEC_ID_ANY, 'SPEEX', - farsight.MEDIA_TYPE_AUDIO, 8000)] - self.p2psession.set_codec_preferences(codecs) + codecs = [farsight.Codec(farsight.CODEC_ID_ANY, 'SPEEX', + farsight.MEDIA_TYPE_AUDIO, 16000), + farsight.Codec(farsight.CODEC_ID_ANY, 'SPEEX', + farsight.MEDIA_TYPE_AUDIO, 8000)] + self.p2psession.set_codec_preferences(codecs) - # the local parts - # TODO: use gconfaudiosink? - # sink = get_first_gst_element(['alsasink', 'osssink', 'autoaudiosink']) - self.sink = gst.element_factory_make('alsasink') - self.sink.set_property('sync', False) - #sink.set_property('latency-time', 20000) - #sink.set_property('buffer-time', 80000) + # the local parts + # TODO: use gconfaudiosink? + # sink = get_first_gst_element(['alsasink', 'osssink', 'autoaudiosink']) + self.sink = gst.element_factory_make('alsasink') + self.sink.set_property('sync', False) + #sink.set_property('latency-time', 20000) + #sink.set_property('buffer-time', 80000) - # TODO: use gconfaudiosrc? - src_mic = gst.element_factory_make('alsasrc') - src_mic.set_property('blocksize', 320) + # TODO: use gconfaudiosrc? + src_mic = gst.element_factory_make('alsasrc') + src_mic.set_property('blocksize', 320) - self.mic_volume = gst.element_factory_make('volume') - self.mic_volume.set_property('volume', 1) + self.mic_volume = gst.element_factory_make('volume') + self.mic_volume.set_property('volume', 1) - # link gst elements - self.pipeline.add(self.sink, src_mic, self.mic_volume) - src_mic.link(self.mic_volume) + # link gst elements + self.pipeline.add(self.sink, src_mic, self.mic_volume) + src_mic.link(self.mic_volume) - self.mic_volume.get_pad('src').link(self.p2psession.get_property( - 'sink-pad')) - self.p2pstream.connect('src-pad-added', self._on_src_pad_added) + self.mic_volume.get_pad('src').link(self.p2psession.get_property( + 'sink-pad')) + self.p2pstream.connect('src-pad-added', self._on_src_pad_added) - # The following is needed for farsight to process ICE requests: - self.pipeline.set_state(gst.STATE_PLAYING) + # The following is needed for farsight to process ICE requests: + self.pipeline.set_state(gst.STATE_PLAYING) class JingleVideo(JingleRTPContent): - def __init__(self, session, transport=None): - JingleRTPContent.__init__(self, session, 'video', transport) - self.setup_stream() + def __init__(self, session, transport=None): + JingleRTPContent.__init__(self, session, 'video', transport) + self.setup_stream() - ''' Things to control the gstreamer's pipeline ''' - def setup_stream(self): - #TODO: Everything is not working properly: - # sometimes, one window won't show up, - # sometimes it'll freeze... - JingleRTPContent.setup_stream(self) - # the local parts - src_vid = gst.element_factory_make('videotestsrc') - src_vid.set_property('is-live', True) - videoscale = gst.element_factory_make('videoscale') - caps = gst.element_factory_make('capsfilter') - caps.set_property('caps', gst.caps_from_string('video/x-raw-yuv, width=320, height=240')) - colorspace = gst.element_factory_make('ffmpegcolorspace') + ''' Things to control the gstreamer's pipeline ''' + def setup_stream(self): + #TODO: Everything is not working properly: + # sometimes, one window won't show up, + # sometimes it'll freeze... + JingleRTPContent.setup_stream(self) + # the local parts + src_vid = gst.element_factory_make('videotestsrc') + src_vid.set_property('is-live', True) + videoscale = gst.element_factory_make('videoscale') + caps = gst.element_factory_make('capsfilter') + caps.set_property('caps', gst.caps_from_string('video/x-raw-yuv, width=320, height=240')) + colorspace = gst.element_factory_make('ffmpegcolorspace') - self.pipeline.add(src_vid, videoscale, caps, colorspace) - gst.element_link_many(src_vid, videoscale, caps, colorspace) + self.pipeline.add(src_vid, videoscale, caps, colorspace) + gst.element_link_many(src_vid, videoscale, caps, colorspace) - self.sink = gst.element_factory_make('xvimagesink') - self.pipeline.add(self.sink) + self.sink = gst.element_factory_make('xvimagesink') + self.pipeline.add(self.sink) - colorspace.get_pad('src').link(self.p2psession.get_property('sink-pad')) - self.p2pstream.connect('src-pad-added', self._on_src_pad_added) + colorspace.get_pad('src').link(self.p2psession.get_property('sink-pad')) + self.p2pstream.connect('src-pad-added', self._on_src_pad_added) - # The following is needed for farsight to process ICE requests: - self.pipeline.set_state(gst.STATE_PLAYING) + # The following is needed for farsight to process ICE requests: + self.pipeline.set_state(gst.STATE_PLAYING) def get_content(desc): - if desc['media'] == 'audio': - return JingleAudio - elif desc['media'] == 'video': - return JingleVideo - else: - return None + if desc['media'] == 'audio': + return JingleAudio + elif desc['media'] == 'video': + return JingleVideo + else: + return None contents[xmpp.NS_JINGLE_RTP] = get_content diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index 9118affdf..28d2a6509 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -31,583 +31,582 @@ from jingle_content import get_jingle_content #FIXME: Move it to JingleSession.States? class JingleStates(object): - ''' States in which jingle session may exist. ''' - ended = 0 - pending = 1 - active = 2 + ''' States in which jingle session may exist. ''' + ended = 0 + pending = 1 + active = 2 class OutOfOrder(Exception): - ''' Exception that should be raised when an action is received when in the wrong state. ''' + ''' Exception that should be raised when an action is received when in the wrong state. ''' class TieBreak(Exception): - ''' Exception that should be raised in case of a tie, when we overrule the other action. ''' + ''' Exception that should be raised in case of a tie, when we overrule the other action. ''' class JingleSession(object): - ''' This represents one jingle session. ''' - def __init__(self, con, weinitiate, jid, sid=None): - ''' con -- connection object, - weinitiate -- boolean, are we the initiator? - jid - jid of the other entity''' - self.contents = {} # negotiated contents - self.connection = con # connection to use - # our full jid - #FIXME: Get rid of gajim here? - self.ourjid = gajim.get_jid_from_account(self.connection.name) + '/' + \ - con.server_resource - self.peerjid = jid # jid we connect to - # jid we use as the initiator - self.initiator = weinitiate and self.ourjid or self.peerjid - # jid we use as the responder - self.responder = weinitiate and self.peerjid or self.ourjid - # are we an initiator? - self.weinitiate = weinitiate - # what state is session in? (one from JingleStates) - self.state = JingleStates.ended - if not sid: - sid = con.connection.getAnID() - self.sid = sid # sessionid - - self.accepted = True # is this session accepted by user - - # callbacks to call on proper contents - # use .prepend() to add new callbacks, especially when you're going - # to send error instead of ack - self.callbacks = { - 'content-accept': [self.__contentAcceptCB, self.__broadcastCB, - self.__defaultCB], - 'content-add': [self.__contentAddCB, self.__broadcastCB, - self.__defaultCB], #TODO - 'content-modify': [self.__defaultCB], #TODO - 'content-reject': [self.__defaultCB, self.__contentRemoveCB], #TODO - 'content-remove': [self.__defaultCB, self.__contentRemoveCB], - 'description-info': [self.__broadcastCB, self.__defaultCB], #TODO - 'security-info': [self.__defaultCB], #TODO - 'session-accept': [self.__sessionAcceptCB, self.__contentAcceptCB, - self.__broadcastCB, self.__defaultCB], - 'session-info': [self.__sessionInfoCB, self.__broadcastCB, self.__defaultCB], - 'session-initiate': [self.__sessionInitiateCB, self.__broadcastCB, - self.__defaultCB], - 'session-terminate': [self.__sessionTerminateCB, self.__broadcastAllCB, - self.__defaultCB], - 'transport-info': [self.__broadcastCB, self.__defaultCB], - 'transport-replace': [self.__broadcastCB, self.__transportReplaceCB], #TODO - 'transport-accept': [self.__defaultCB], #TODO - 'transport-reject': [self.__defaultCB], #TODO - 'iq-result': [], - 'iq-error': [self.__errorCB], - } - - ''' Interaction with user ''' - def approve_session(self): - ''' Called when user accepts session in UI (when we aren't the initiator). - ''' - self.accept_session() - - def decline_session(self): - ''' Called when user declines session in UI (when we aren't the initiator) - ''' - reason = xmpp.Node('reason') - reason.addChild('decline') - self._session_terminate(reason) - - def approve_content(self, media): - content = self.get_content(media) - if content: - content.accepted = True - self.on_session_state_changed(content) - - def reject_content(self, media): - content = self.get_content(media) - if content: - if self.state == JingleStates.active: - self.__content_reject(content) - content.destroy() - self.on_session_state_changed() - - def end_session(self): - ''' Called when user stops or cancel session in UI. ''' - reason = xmpp.Node('reason') - if self.state == JingleStates.active: - reason.addChild('success') - else: - reason.addChild('cancel') - self._session_terminate(reason) - - ''' Middle-level functions to manage contents. Handle local content - cache and send change notifications. ''' - def get_content(self, media=None): - if media is None: - return None - - for content in self.contents.values(): - if content.media == media: - return content - - def add_content(self, name, content, creator='we'): - ''' Add new content to session. If the session is active, - this will send proper stanza to update session. - Creator must be one of ('we', 'peer', 'initiator', 'responder')''' - assert creator in ('we', 'peer', 'initiator', 'responder') - - if (creator == 'we' and self.weinitiate) or (creator == 'peer' and \ - not self.weinitiate): - creator = 'initiator' - elif (creator == 'peer' and self.weinitiate) or (creator == 'we' and \ - not self.weinitiate): - creator = 'responder' - content.creator = creator - content.name = name - self.contents[(creator, name)] = content - - if (creator == 'initiator') == self.weinitiate: - # The content is from us, accept it - content.accepted = True - - def remove_content(self, creator, name): - ''' We do not need this now ''' - #TODO: - if (creator, name) in self.contents: - content = self.contents[(creator, name)] - if len(self.contents) > 1: - self.__content_remove(content) - self.contents[(creator, name)].destroy() - if len(self.contents) == 0: - self.end_session() - - def modify_content(self, creator, name, *someother): - ''' We do not need this now ''' - pass - - def on_session_state_changed(self, content=None): - if self.state == JingleStates.ended: - # Session not yet started, only one action possible: session-initiate - if self.is_ready() and self.weinitiate: - self.__session_initiate() - elif self.state == JingleStates.pending: - # We can either send a session-accept or a content-add - if self.is_ready() and not self.weinitiate: - self.__session_accept() - elif content and (content.creator == 'initiator') == self.weinitiate: - self.__content_add(content) - elif content and self.weinitiate: - self.__content_accept(content) - elif self.state == JingleStates.active: - # We can either send a content-add or a content-accept - if not content: - return - if (content.creator == 'initiator') == self.weinitiate: - # We initiated this content. It's a pending content-add. - self.__content_add(content) - else: - # The other side created this content, we accept it. - self.__content_accept(content) - - def is_ready(self): - ''' Returns True when all codecs and candidates are ready - (for all contents). ''' - return (all((content.is_ready() for content in self.contents.itervalues())) - and self.accepted) - - ''' Middle-level function to do stanza exchange. ''' - def accept_session(self): - ''' Mark the session as accepted. ''' - self.accepted = True - self.on_session_state_changed() - - def start_session(self): - ''' Mark the session as ready to be started. ''' - self.accepted = True - self.on_session_state_changed() - - def send_session_info(self): - pass - - def send_content_accept(self, content): - assert self.state != JingleStates.ended - stanza, jingle = self.__make_jingle('content-accept') - jingle.addChild(node=content) - self.connection.connection.send(stanza) - - def send_transport_info(self, content): - assert self.state != JingleStates.ended - stanza, jingle = self.__make_jingle('transport-info') - jingle.addChild(node=content) - self.connection.connection.send(stanza) - - ''' Session callbacks. ''' - def stanzaCB(self, stanza): - ''' A callback for ConnectionJingle. It gets stanza, then - tries to send it to all internally registered callbacks. - First one to raise xmpp.NodeProcessed breaks function.''' - jingle = stanza.getTag('jingle') - error = stanza.getTag('error') - if error: - # it's an iq-error stanza - action = 'iq-error' - elif jingle: - # it's a jingle action - action = jingle.getAttr('action') - if action not in self.callbacks: - self.__send_error(stanza, 'bad_request') - return - #FIXME: If we aren't initiated and it's not a session-initiate... - if action != 'session-initiate' and self.state == JingleStates.ended: - self.__send_error(stanza, 'item-not-found', 'unknown-session') - return - else: - # it's an iq-result (ack) stanza - action = 'iq-result' - - callables = self.callbacks[action] - - try: - for callable in callables: - callable(stanza=stanza, jingle=jingle, error=error, action=action) - except xmpp.NodeProcessed: - pass - except TieBreak: - self.__send_error(stanza, 'conflict', 'tiebreak') - except OutOfOrder: - self.__send_error(stanza, 'unexpected-request', 'out-of-order')#FIXME - - def __defaultCB(self, stanza, jingle, error, action): - ''' Default callback for action stanzas -- simple ack - and stop processing. ''' - response = stanza.buildReply('result') - self.connection.connection.send(response) - - def __errorCB(self, stanza, jingle, error, action): - #FIXME - text = error.getTagData('text') - jingle_error = None - xmpp_error = None - for child in error.getChildren(): - if child.getNamespace() == xmpp.NS_JINGLE_ERRORS: - jingle_error = child.getName() - elif child.getNamespace() == xmpp.NS_STANZAS: - xmpp_error = child.getName() - self.__dispatch_error(xmpp_error, jingle_error, text) - #FIXME: Not sure when we would want to do that... - if xmpp_error == 'item-not-found': - self.connection.delete_jingle_session(self.peerjid, self.sid) - - def __transportReplaceCB(self, stanza, jingle, error, action): - for content in jingle.iterTags('content'): - creator = content['creator'] - name = content['name'] - if (creator, name) in self.contents: - transport_ns = content.getTag('transport').getNamespace() - if transport_ns == xmpp.JINGLE_ICE_UDP: - #FIXME: We don't manage anything else than ICE-UDP now... - #What was the previous transport?!? - #Anyway, content's transport is not modifiable yet - pass - else: - stanza, jingle = self.__make_jingle('transport-reject') - content = jingle.setTag('content', attrs={'creator': creator, - 'name': name}) - content.setTag('transport', namespace=transport_ns) - self.connection.connection.send(stanza) - raise xmpp.NodeProcessed - else: - #FIXME: This ressource is unknown to us, what should we do? - #For now, reject the transport - stanza, jingle = self.__make_jingle('transport-reject') - c = jingle.setTag('content', attrs={'creator': creator, - 'name': name}) - c.setTag('transport', namespace=transport_ns) - self.connection.connection.send(stanza) - raise xmpp.NodeProcessed - - def __sessionInfoCB(self, stanza, jingle, error, action): - #TODO: ringing, active, (un)hold, (un)mute - payload = jingle.getPayload() - if len(payload) > 0: - self.__send_error(stanza, 'feature-not-implemented', 'unsupported-info') - raise xmpp.NodeProcessed - - def __contentRemoveCB(self, stanza, jingle, error, action): - for content in jingle.iterTags('content'): - creator = content['creator'] - name = content['name'] - if (creator, name) in self.contents: - content = self.contents[(creator, name)] - #TODO: this will fail if content is not an RTP content - self.connection.dispatch('JINGLE_DISCONNECTED', - (self.peerjid, self.sid, content.media, 'removed')) - content.destroy() - if len(self.contents) == 0: - reason = xmpp.Node('reason') - reason.setTag('success') - self._session_terminate(reason) - - def __sessionAcceptCB(self, stanza, jingle, error, action): - if self.state != JingleStates.pending: #FIXME - raise OutOfOrder - self.state = JingleStates.active - - def __contentAcceptCB(self, stanza, jingle, error, action): - ''' Called when we get content-accept stanza or equivalent one - (like session-accept).''' - # check which contents are accepted - for content in jingle.iterTags('content'): - creator = content['creator'] - name = content['name']#TODO... - - def __contentAddCB(self, stanza, jingle, error, action): - if self.state == JingleStates.ended: - raise OutOfOrder - - parse_result = self.__parse_contents(jingle) - contents = parse_result[2] - rejected_contents = parse_result[3] - - for name, creator in rejected_contents: - #TODO: - content = JingleContent() - self.add_content(name, content, creator) - self.__content_reject(content) - self.contents[(content.creator, content.name)].destroy() - - self.connection.dispatch('JINGLE_INCOMING', (self.peerjid, self.sid, - contents)) - - def __sessionInitiateCB(self, stanza, jingle, error, action): - ''' We got a jingle session request from other entity, - therefore we are the receiver... Unpack the data, - inform the user. ''' - - if self.state != JingleStates.ended: - raise OutOfOrder - - self.initiator = jingle['initiator'] - self.responder = self.ourjid - self.peerjid = self.initiator - self.accepted = False # user did not accept this session yet - - # TODO: If the initiator is unknown to the receiver (e.g., via presence - # subscription) and the receiver has a policy of not communicating via - # Jingle with unknown entities, it SHOULD return a - # error. - - # Lets check what kind of jingle session does the peer want - contents_ok, transports_ok, contents, pouet = self.__parse_contents(jingle) - - # If there's no content we understand... - if not contents_ok: - # TODO: http://xmpp.org/extensions/xep-0166.html#session-terminate - reason = xmpp.Node('reason') - reason.setTag('unsupported-applications') - self.__defaultCB(stanza, jingle, error, action) - self._session_terminate(reason) - raise xmpp.NodeProcessed - - if not transports_ok: - # TODO: http://xmpp.org/extensions/xep-0166.html#session-terminate - reason = xmpp.Node('reason') - reason.setTag('unsupported-transports') - self.__defaultCB(stanza, jingle, error, action) - self._session_terminate(reason) - raise xmpp.NodeProcessed - - self.state = JingleStates.pending - - # Send event about starting a session - self.connection.dispatch('JINGLE_INCOMING', (self.peerjid, self.sid, - contents)) - - def __broadcastCB(self, stanza, jingle, error, action): - ''' Broadcast the stanza contents to proper content handlers. ''' - for content in jingle.iterTags('content'): - name = content['name'] - creator = content['creator'] - cn = self.contents[(creator, name)] - cn.stanzaCB(stanza, content, error, action) - - def __sessionTerminateCB(self, stanza, jingle, error, action): - self.connection.delete_jingle_session(self.peerjid, self.sid) - reason, text = self.__reason_from_stanza(jingle) - if reason not in ('success', 'cancel', 'decline'): - self.__dispatch_error(reason, reason, text) - if text: - text = '%s (%s)' % (reason, text) - else: - text = reason#TODO - self.connection.dispatch('JINGLE_DISCONNECTED', - (self.peerjid, self.sid, None, text)) - - def __broadcastAllCB(self, stanza, jingle, error, action): - ''' Broadcast the stanza to all content handlers. ''' - for content in self.contents.itervalues(): - content.stanzaCB(stanza, None, error, action) - - ''' Internal methods. ''' - def __parse_contents(self, jingle): - #TODO: Needs some reworking - contents = [] - contents_rejected = [] - contents_ok = False - transports_ok = False - - for element in jingle.iterTags('content'): - transport = get_jingle_transport(element.getTag('transport')) - content_type = get_jingle_content(element.getTag('description')) - if content_type: - contents_ok = True - if transport: - content = content_type(self, transport) - self.add_content(element['name'], - content, 'peer') - contents.append((content.media,)) - transports_ok = True - else: - contents_rejected.append((element['name'], 'peer')) - else: - contents_rejected.append((element['name'], 'peer')) - - return (contents_ok, transports_ok, contents, contents_rejected) - - def __dispatch_error(self, error, jingle_error=None, text=None): - if jingle_error: - error = jingle_error - if text: - text = '%s (%s)' % (error, text) - else: - text = error - self.connection.dispatch('JINGLE_ERROR', (self.peerjid, self.sid, text)) - - def __reason_from_stanza(self, stanza): - reason = 'success' - reasons = ['success', 'busy', 'cancel', 'connectivity-error', - 'decline', 'expired', 'failed-application', 'failed-transport', - 'general-error', 'gone', 'incompatible-parameters', 'media-error', - 'security-error', 'timeout', 'unsupported-applications', - 'unsupported-transports'] - tag = stanza.getTag('reason') - if tag: - text = tag.getTagData('text') - for r in reasons: - if tag.getTag(r): - reason = r - break - return (reason, text) - - ''' Methods that make/send proper pieces of XML. They check if the session - is in appropriate state. ''' - def __make_jingle(self, action): - stanza = xmpp.Iq(typ='set', to=xmpp.JID(self.peerjid)) - attrs = {'action': action, - 'sid': self.sid} - if action == 'session-initiate': - attrs['initiator'] = self.initiator - elif action == 'session-accept': - attrs['responder'] = self.responder - jingle = stanza.addChild('jingle', attrs=attrs, namespace=xmpp.NS_JINGLE) - return stanza, jingle - - def __send_error(self, stanza, error, jingle_error=None, text=None): - err = xmpp.Error(stanza, error) - err.setNamespace(xmpp.NS_STANZAS) - if jingle_error: - err.setTag(jingle_error, namespace=xmpp.NS_JINGLE_ERRORS) - if text: - err.setTagData('text', text) - self.connection.connection.send(err) - self.__dispatch_error(error, jingle_error, text) - - def __append_content(self, jingle, content): - ''' Append element to element, - with (full=True) or without (full=False) - children. ''' - jingle.addChild('content', - attrs={'name': content.name, 'creator': content.creator}) - - def __append_contents(self, jingle): - ''' Append all elements to .''' - # TODO: integrate with __appendContent? - # TODO: parameters 'name', 'content'? - for content in self.contents.values(): - self.__append_content(jingle, content) - - def __session_initiate(self): - assert self.state == JingleStates.ended - stanza, jingle = self.__make_jingle('session-initiate') - self.__append_contents(jingle) - self.__broadcastCB(stanza, jingle, None, 'session-initiate-sent') - self.connection.connection.send(stanza) - self.state = JingleStates.pending - - def __session_accept(self): - assert self.state == JingleStates.pending - stanza, jingle = self.__make_jingle('session-accept') - self.__append_contents(jingle) - self.__broadcastCB(stanza, jingle, None, 'session-accept-sent') - self.connection.connection.send(stanza) - self.state = JingleStates.active - - def __session_info(self, payload=None): - assert self.state != JingleStates.ended - stanza, jingle = self.__make_jingle('session-info') - if payload: - jingle.addChild(node=payload) - self.connection.connection.send(stanza) - - def _session_terminate(self, reason=None): - assert self.state != JingleStates.ended - stanza, jingle = self.__make_jingle('session-terminate') - if reason is not None: - jingle.addChild(node=reason) - self.__broadcastAllCB(stanza, jingle, None, 'session-terminate-sent') - self.connection.connection.send(stanza) - reason, text = self.__reason_from_stanza(jingle) - if reason not in ('success', 'cancel', 'decline'): - self.__dispatch_error(reason, reason, text) - if text: - text = '%s (%s)' % (reason, text) - else: - text = reason - self.connection.delete_jingle_session(self.peerjid, self.sid) - self.connection.dispatch('JINGLE_DISCONNECTED', - (self.peerjid, self.sid, None, text)) - - def __content_add(self, content): - #TODO: test - assert self.state != JingleStates.ended - stanza, jingle = self.__make_jingle('content-add') - self.__append_content(jingle, content) - self.__broadcastCB(stanza, jingle, None, 'content-add-sent') - self.connection.connection.send(stanza) - - def __content_accept(self, content): - #TODO: test - assert self.state != JingleStates.ended - stanza, jingle = self.__make_jingle('content-accept') - self.__append_content(jingle, content) - self.__broadcastCB(stanza, jingle, None, 'content-accept-sent') - self.connection.connection.send(stanza) - - def __content_reject(self, content): - assert self.state != JingleStates.ended - stanza, jingle = self.__make_jingle('content-reject') - self.__append_content(jingle, content) - self.connection.connection.send(stanza) - #TODO: this will fail if content is not an RTP content - self.connection.dispatch('JINGLE_DISCONNECTED', - (self.peerjid, self.sid, content.media, 'rejected')) - - def __content_modify(self): - assert self.state != JingleStates.ended - - def __content_remove(self, content): - assert self.state != JingleStates.ended - stanza, jingle = self.__make_jingle('content-remove') - self.__append_content(jingle, content) - self.connection.connection.send(stanza) - #TODO: this will fail if content is not an RTP content - self.connection.dispatch('JINGLE_DISCONNECTED', - (self.peerjid, self.sid, content.media, 'removed')) - - def content_negociated(self, media): - self.connection.dispatch('JINGLE_CONNECTED', (self.peerjid, self.sid, - media)) - + ''' This represents one jingle session. ''' + def __init__(self, con, weinitiate, jid, sid=None): + ''' con -- connection object, + weinitiate -- boolean, are we the initiator? + jid - jid of the other entity''' + self.contents = {} # negotiated contents + self.connection = con # connection to use + # our full jid + #FIXME: Get rid of gajim here? + self.ourjid = gajim.get_jid_from_account(self.connection.name) + '/' + \ + con.server_resource + self.peerjid = jid # jid we connect to + # jid we use as the initiator + self.initiator = weinitiate and self.ourjid or self.peerjid + # jid we use as the responder + self.responder = weinitiate and self.peerjid or self.ourjid + # are we an initiator? + self.weinitiate = weinitiate + # what state is session in? (one from JingleStates) + self.state = JingleStates.ended + if not sid: + sid = con.connection.getAnID() + self.sid = sid # sessionid + + self.accepted = True # is this session accepted by user + + # callbacks to call on proper contents + # use .prepend() to add new callbacks, especially when you're going + # to send error instead of ack + self.callbacks = { + 'content-accept': [self.__contentAcceptCB, self.__broadcastCB, + self.__defaultCB], + 'content-add': [self.__contentAddCB, self.__broadcastCB, + self.__defaultCB], #TODO + 'content-modify': [self.__defaultCB], #TODO + 'content-reject': [self.__defaultCB, self.__contentRemoveCB], #TODO + 'content-remove': [self.__defaultCB, self.__contentRemoveCB], + 'description-info': [self.__broadcastCB, self.__defaultCB], #TODO + 'security-info': [self.__defaultCB], #TODO + 'session-accept': [self.__sessionAcceptCB, self.__contentAcceptCB, + self.__broadcastCB, self.__defaultCB], + 'session-info': [self.__sessionInfoCB, self.__broadcastCB, self.__defaultCB], + 'session-initiate': [self.__sessionInitiateCB, self.__broadcastCB, + self.__defaultCB], + 'session-terminate': [self.__sessionTerminateCB, self.__broadcastAllCB, + self.__defaultCB], + 'transport-info': [self.__broadcastCB, self.__defaultCB], + 'transport-replace': [self.__broadcastCB, self.__transportReplaceCB], #TODO + 'transport-accept': [self.__defaultCB], #TODO + 'transport-reject': [self.__defaultCB], #TODO + 'iq-result': [], + 'iq-error': [self.__errorCB], + } + + ''' Interaction with user ''' + def approve_session(self): + ''' Called when user accepts session in UI (when we aren't the initiator). + ''' + self.accept_session() + + def decline_session(self): + ''' Called when user declines session in UI (when we aren't the initiator) + ''' + reason = xmpp.Node('reason') + reason.addChild('decline') + self._session_terminate(reason) + + def approve_content(self, media): + content = self.get_content(media) + if content: + content.accepted = True + self.on_session_state_changed(content) + + def reject_content(self, media): + content = self.get_content(media) + if content: + if self.state == JingleStates.active: + self.__content_reject(content) + content.destroy() + self.on_session_state_changed() + + def end_session(self): + ''' Called when user stops or cancel session in UI. ''' + reason = xmpp.Node('reason') + if self.state == JingleStates.active: + reason.addChild('success') + else: + reason.addChild('cancel') + self._session_terminate(reason) + + ''' Middle-level functions to manage contents. Handle local content + cache and send change notifications. ''' + def get_content(self, media=None): + if media is None: + return None + + for content in self.contents.values(): + if content.media == media: + return content + + def add_content(self, name, content, creator='we'): + ''' Add new content to session. If the session is active, + this will send proper stanza to update session. + Creator must be one of ('we', 'peer', 'initiator', 'responder')''' + assert creator in ('we', 'peer', 'initiator', 'responder') + + if (creator == 'we' and self.weinitiate) or (creator == 'peer' and \ + not self.weinitiate): + creator = 'initiator' + elif (creator == 'peer' and self.weinitiate) or (creator == 'we' and \ + not self.weinitiate): + creator = 'responder' + content.creator = creator + content.name = name + self.contents[(creator, name)] = content + + if (creator == 'initiator') == self.weinitiate: + # The content is from us, accept it + content.accepted = True + + def remove_content(self, creator, name): + ''' We do not need this now ''' + #TODO: + if (creator, name) in self.contents: + content = self.contents[(creator, name)] + if len(self.contents) > 1: + self.__content_remove(content) + self.contents[(creator, name)].destroy() + if len(self.contents) == 0: + self.end_session() + + def modify_content(self, creator, name, *someother): + ''' We do not need this now ''' + pass + + def on_session_state_changed(self, content=None): + if self.state == JingleStates.ended: + # Session not yet started, only one action possible: session-initiate + if self.is_ready() and self.weinitiate: + self.__session_initiate() + elif self.state == JingleStates.pending: + # We can either send a session-accept or a content-add + if self.is_ready() and not self.weinitiate: + self.__session_accept() + elif content and (content.creator == 'initiator') == self.weinitiate: + self.__content_add(content) + elif content and self.weinitiate: + self.__content_accept(content) + elif self.state == JingleStates.active: + # We can either send a content-add or a content-accept + if not content: + return + if (content.creator == 'initiator') == self.weinitiate: + # We initiated this content. It's a pending content-add. + self.__content_add(content) + else: + # The other side created this content, we accept it. + self.__content_accept(content) + + def is_ready(self): + ''' Returns True when all codecs and candidates are ready + (for all contents). ''' + return (all((content.is_ready() for content in self.contents.itervalues())) + and self.accepted) + + ''' Middle-level function to do stanza exchange. ''' + def accept_session(self): + ''' Mark the session as accepted. ''' + self.accepted = True + self.on_session_state_changed() + + def start_session(self): + ''' Mark the session as ready to be started. ''' + self.accepted = True + self.on_session_state_changed() + + def send_session_info(self): + pass + + def send_content_accept(self, content): + assert self.state != JingleStates.ended + stanza, jingle = self.__make_jingle('content-accept') + jingle.addChild(node=content) + self.connection.connection.send(stanza) + + def send_transport_info(self, content): + assert self.state != JingleStates.ended + stanza, jingle = self.__make_jingle('transport-info') + jingle.addChild(node=content) + self.connection.connection.send(stanza) + + ''' Session callbacks. ''' + def stanzaCB(self, stanza): + ''' A callback for ConnectionJingle. It gets stanza, then + tries to send it to all internally registered callbacks. + First one to raise xmpp.NodeProcessed breaks function.''' + jingle = stanza.getTag('jingle') + error = stanza.getTag('error') + if error: + # it's an iq-error stanza + action = 'iq-error' + elif jingle: + # it's a jingle action + action = jingle.getAttr('action') + if action not in self.callbacks: + self.__send_error(stanza, 'bad_request') + return + #FIXME: If we aren't initiated and it's not a session-initiate... + if action != 'session-initiate' and self.state == JingleStates.ended: + self.__send_error(stanza, 'item-not-found', 'unknown-session') + return + else: + # it's an iq-result (ack) stanza + action = 'iq-result' + + callables = self.callbacks[action] + + try: + for callable in callables: + callable(stanza=stanza, jingle=jingle, error=error, action=action) + except xmpp.NodeProcessed: + pass + except TieBreak: + self.__send_error(stanza, 'conflict', 'tiebreak') + except OutOfOrder: + self.__send_error(stanza, 'unexpected-request', 'out-of-order')#FIXME + + def __defaultCB(self, stanza, jingle, error, action): + ''' Default callback for action stanzas -- simple ack + and stop processing. ''' + response = stanza.buildReply('result') + self.connection.connection.send(response) + + def __errorCB(self, stanza, jingle, error, action): + #FIXME + text = error.getTagData('text') + jingle_error = None + xmpp_error = None + for child in error.getChildren(): + if child.getNamespace() == xmpp.NS_JINGLE_ERRORS: + jingle_error = child.getName() + elif child.getNamespace() == xmpp.NS_STANZAS: + xmpp_error = child.getName() + self.__dispatch_error(xmpp_error, jingle_error, text) + #FIXME: Not sure when we would want to do that... + if xmpp_error == 'item-not-found': + self.connection.delete_jingle_session(self.peerjid, self.sid) + + def __transportReplaceCB(self, stanza, jingle, error, action): + for content in jingle.iterTags('content'): + creator = content['creator'] + name = content['name'] + if (creator, name) in self.contents: + transport_ns = content.getTag('transport').getNamespace() + if transport_ns == xmpp.JINGLE_ICE_UDP: + #FIXME: We don't manage anything else than ICE-UDP now... + #What was the previous transport?!? + #Anyway, content's transport is not modifiable yet + pass + else: + stanza, jingle = self.__make_jingle('transport-reject') + content = jingle.setTag('content', attrs={'creator': creator, + 'name': name}) + content.setTag('transport', namespace=transport_ns) + self.connection.connection.send(stanza) + raise xmpp.NodeProcessed + else: + #FIXME: This ressource is unknown to us, what should we do? + #For now, reject the transport + stanza, jingle = self.__make_jingle('transport-reject') + c = jingle.setTag('content', attrs={'creator': creator, + 'name': name}) + c.setTag('transport', namespace=transport_ns) + self.connection.connection.send(stanza) + raise xmpp.NodeProcessed + + def __sessionInfoCB(self, stanza, jingle, error, action): + #TODO: ringing, active, (un)hold, (un)mute + payload = jingle.getPayload() + if len(payload) > 0: + self.__send_error(stanza, 'feature-not-implemented', 'unsupported-info') + raise xmpp.NodeProcessed + + def __contentRemoveCB(self, stanza, jingle, error, action): + for content in jingle.iterTags('content'): + creator = content['creator'] + name = content['name'] + if (creator, name) in self.contents: + content = self.contents[(creator, name)] + #TODO: this will fail if content is not an RTP content + self.connection.dispatch('JINGLE_DISCONNECTED', + (self.peerjid, self.sid, content.media, 'removed')) + content.destroy() + if len(self.contents) == 0: + reason = xmpp.Node('reason') + reason.setTag('success') + self._session_terminate(reason) + + def __sessionAcceptCB(self, stanza, jingle, error, action): + if self.state != JingleStates.pending: #FIXME + raise OutOfOrder + self.state = JingleStates.active + + def __contentAcceptCB(self, stanza, jingle, error, action): + ''' Called when we get content-accept stanza or equivalent one + (like session-accept).''' + # check which contents are accepted + for content in jingle.iterTags('content'): + creator = content['creator'] + name = content['name']#TODO... + + def __contentAddCB(self, stanza, jingle, error, action): + if self.state == JingleStates.ended: + raise OutOfOrder + + parse_result = self.__parse_contents(jingle) + contents = parse_result[2] + rejected_contents = parse_result[3] + + for name, creator in rejected_contents: + #TODO: + content = JingleContent() + self.add_content(name, content, creator) + self.__content_reject(content) + self.contents[(content.creator, content.name)].destroy() + + self.connection.dispatch('JINGLE_INCOMING', (self.peerjid, self.sid, + contents)) + + def __sessionInitiateCB(self, stanza, jingle, error, action): + ''' We got a jingle session request from other entity, + therefore we are the receiver... Unpack the data, + inform the user. ''' + + if self.state != JingleStates.ended: + raise OutOfOrder + + self.initiator = jingle['initiator'] + self.responder = self.ourjid + self.peerjid = self.initiator + self.accepted = False # user did not accept this session yet + + # TODO: If the initiator is unknown to the receiver (e.g., via presence + # subscription) and the receiver has a policy of not communicating via + # Jingle with unknown entities, it SHOULD return a + # error. + + # Lets check what kind of jingle session does the peer want + contents_ok, transports_ok, contents, pouet = self.__parse_contents(jingle) + + # If there's no content we understand... + if not contents_ok: + # TODO: http://xmpp.org/extensions/xep-0166.html#session-terminate + reason = xmpp.Node('reason') + reason.setTag('unsupported-applications') + self.__defaultCB(stanza, jingle, error, action) + self._session_terminate(reason) + raise xmpp.NodeProcessed + + if not transports_ok: + # TODO: http://xmpp.org/extensions/xep-0166.html#session-terminate + reason = xmpp.Node('reason') + reason.setTag('unsupported-transports') + self.__defaultCB(stanza, jingle, error, action) + self._session_terminate(reason) + raise xmpp.NodeProcessed + + self.state = JingleStates.pending + + # Send event about starting a session + self.connection.dispatch('JINGLE_INCOMING', (self.peerjid, self.sid, + contents)) + + def __broadcastCB(self, stanza, jingle, error, action): + ''' Broadcast the stanza contents to proper content handlers. ''' + for content in jingle.iterTags('content'): + name = content['name'] + creator = content['creator'] + cn = self.contents[(creator, name)] + cn.stanzaCB(stanza, content, error, action) + + def __sessionTerminateCB(self, stanza, jingle, error, action): + self.connection.delete_jingle_session(self.peerjid, self.sid) + reason, text = self.__reason_from_stanza(jingle) + if reason not in ('success', 'cancel', 'decline'): + self.__dispatch_error(reason, reason, text) + if text: + text = '%s (%s)' % (reason, text) + else: + text = reason#TODO + self.connection.dispatch('JINGLE_DISCONNECTED', + (self.peerjid, self.sid, None, text)) + + def __broadcastAllCB(self, stanza, jingle, error, action): + ''' Broadcast the stanza to all content handlers. ''' + for content in self.contents.itervalues(): + content.stanzaCB(stanza, None, error, action) + + ''' Internal methods. ''' + def __parse_contents(self, jingle): + #TODO: Needs some reworking + contents = [] + contents_rejected = [] + contents_ok = False + transports_ok = False + + for element in jingle.iterTags('content'): + transport = get_jingle_transport(element.getTag('transport')) + content_type = get_jingle_content(element.getTag('description')) + if content_type: + contents_ok = True + if transport: + content = content_type(self, transport) + self.add_content(element['name'], + content, 'peer') + contents.append((content.media,)) + transports_ok = True + else: + contents_rejected.append((element['name'], 'peer')) + else: + contents_rejected.append((element['name'], 'peer')) + + return (contents_ok, transports_ok, contents, contents_rejected) + + def __dispatch_error(self, error, jingle_error=None, text=None): + if jingle_error: + error = jingle_error + if text: + text = '%s (%s)' % (error, text) + else: + text = error + self.connection.dispatch('JINGLE_ERROR', (self.peerjid, self.sid, text)) + + def __reason_from_stanza(self, stanza): + reason = 'success' + reasons = ['success', 'busy', 'cancel', 'connectivity-error', + 'decline', 'expired', 'failed-application', 'failed-transport', + 'general-error', 'gone', 'incompatible-parameters', 'media-error', + 'security-error', 'timeout', 'unsupported-applications', + 'unsupported-transports'] + tag = stanza.getTag('reason') + if tag: + text = tag.getTagData('text') + for r in reasons: + if tag.getTag(r): + reason = r + break + return (reason, text) + + ''' Methods that make/send proper pieces of XML. They check if the session + is in appropriate state. ''' + def __make_jingle(self, action): + stanza = xmpp.Iq(typ='set', to=xmpp.JID(self.peerjid)) + attrs = {'action': action, + 'sid': self.sid} + if action == 'session-initiate': + attrs['initiator'] = self.initiator + elif action == 'session-accept': + attrs['responder'] = self.responder + jingle = stanza.addChild('jingle', attrs=attrs, namespace=xmpp.NS_JINGLE) + return stanza, jingle + + def __send_error(self, stanza, error, jingle_error=None, text=None): + err = xmpp.Error(stanza, error) + err.setNamespace(xmpp.NS_STANZAS) + if jingle_error: + err.setTag(jingle_error, namespace=xmpp.NS_JINGLE_ERRORS) + if text: + err.setTagData('text', text) + self.connection.connection.send(err) + self.__dispatch_error(error, jingle_error, text) + + def __append_content(self, jingle, content): + ''' Append element to element, + with (full=True) or without (full=False) + children. ''' + jingle.addChild('content', + attrs={'name': content.name, 'creator': content.creator}) + + def __append_contents(self, jingle): + ''' Append all elements to .''' + # TODO: integrate with __appendContent? + # TODO: parameters 'name', 'content'? + for content in self.contents.values(): + self.__append_content(jingle, content) + + def __session_initiate(self): + assert self.state == JingleStates.ended + stanza, jingle = self.__make_jingle('session-initiate') + self.__append_contents(jingle) + self.__broadcastCB(stanza, jingle, None, 'session-initiate-sent') + self.connection.connection.send(stanza) + self.state = JingleStates.pending + + def __session_accept(self): + assert self.state == JingleStates.pending + stanza, jingle = self.__make_jingle('session-accept') + self.__append_contents(jingle) + self.__broadcastCB(stanza, jingle, None, 'session-accept-sent') + self.connection.connection.send(stanza) + self.state = JingleStates.active + + def __session_info(self, payload=None): + assert self.state != JingleStates.ended + stanza, jingle = self.__make_jingle('session-info') + if payload: + jingle.addChild(node=payload) + self.connection.connection.send(stanza) + + def _session_terminate(self, reason=None): + assert self.state != JingleStates.ended + stanza, jingle = self.__make_jingle('session-terminate') + if reason is not None: + jingle.addChild(node=reason) + self.__broadcastAllCB(stanza, jingle, None, 'session-terminate-sent') + self.connection.connection.send(stanza) + reason, text = self.__reason_from_stanza(jingle) + if reason not in ('success', 'cancel', 'decline'): + self.__dispatch_error(reason, reason, text) + if text: + text = '%s (%s)' % (reason, text) + else: + text = reason + self.connection.delete_jingle_session(self.peerjid, self.sid) + self.connection.dispatch('JINGLE_DISCONNECTED', + (self.peerjid, self.sid, None, text)) + + def __content_add(self, content): + #TODO: test + assert self.state != JingleStates.ended + stanza, jingle = self.__make_jingle('content-add') + self.__append_content(jingle, content) + self.__broadcastCB(stanza, jingle, None, 'content-add-sent') + self.connection.connection.send(stanza) + + def __content_accept(self, content): + #TODO: test + assert self.state != JingleStates.ended + stanza, jingle = self.__make_jingle('content-accept') + self.__append_content(jingle, content) + self.__broadcastCB(stanza, jingle, None, 'content-accept-sent') + self.connection.connection.send(stanza) + + def __content_reject(self, content): + assert self.state != JingleStates.ended + stanza, jingle = self.__make_jingle('content-reject') + self.__append_content(jingle, content) + self.connection.connection.send(stanza) + #TODO: this will fail if content is not an RTP content + self.connection.dispatch('JINGLE_DISCONNECTED', + (self.peerjid, self.sid, content.media, 'rejected')) + + def __content_modify(self): + assert self.state != JingleStates.ended + + def __content_remove(self, content): + assert self.state != JingleStates.ended + stanza, jingle = self.__make_jingle('content-remove') + self.__append_content(jingle, content) + self.connection.connection.send(stanza) + #TODO: this will fail if content is not an RTP content + self.connection.dispatch('JINGLE_DISCONNECTED', + (self.peerjid, self.sid, content.media, 'removed')) + + def content_negociated(self, media): + self.connection.dispatch('JINGLE_CONNECTED', (self.peerjid, self.sid, + media)) diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py index 487c66d70..4a20d4a6e 100644 --- a/src/common/jingle_transport.py +++ b/src/common/jingle_transport.py @@ -17,119 +17,118 @@ import xmpp transports = {} def get_jingle_transport(node): - namespace = node.getNamespace() - if namespace in transports: - return transports[namespace]() - else: - return None + namespace = node.getNamespace() + if namespace in transports: + return transports[namespace]() + else: + return None class TransportType(object): - ''' Possible types of a JingleTransport ''' - datagram = 1 - streaming = 2 + ''' Possible types of a JingleTransport ''' + datagram = 1 + streaming = 2 class JingleTransport(object): - ''' An abstraction of a transport in Jingle sessions. ''' - def __init__(self, type_): - self.type = type_ - self.candidates = [] - self.remote_candidates = [] + ''' An abstraction of a transport in Jingle sessions. ''' + def __init__(self, type_): + self.type = type_ + self.candidates = [] + self.remote_candidates = [] - def _iter_candidates(self): - for candidate in self.candidates: - yield self.make_candidate(candidate) + def _iter_candidates(self): + for candidate in self.candidates: + yield self.make_candidate(candidate) - def make_candidate(self, candidate): - ''' Build a candidate stanza for the given candidate. ''' - pass + def make_candidate(self, candidate): + ''' Build a candidate stanza for the given candidate. ''' + pass - def make_transport(self, candidates=None): - ''' Build a transport stanza with the given candidates (or - self.candidates if candidates is None). ''' - if not candidates: - candidates = self._iter_candidates() - transport = xmpp.Node('transport', payload=candidates) - return transport + def make_transport(self, candidates=None): + ''' Build a transport stanza with the given candidates (or + self.candidates if candidates is None). ''' + if not candidates: + candidates = self._iter_candidates() + transport = xmpp.Node('transport', payload=candidates) + return transport - def parse_transport_stanza(self, transport): - ''' Returns the list of transport candidates from a transport stanza. ''' - return [] + def parse_transport_stanza(self, transport): + ''' Returns the list of transport candidates from a transport stanza. ''' + return [] import farsight class JingleTransportICEUDP(JingleTransport): - def __init__(self): - JingleTransport.__init__(self, TransportType.datagram) + def __init__(self): + JingleTransport.__init__(self, TransportType.datagram) - def make_candidate(self, candidate): - types = {farsight.CANDIDATE_TYPE_HOST: 'host', - farsight.CANDIDATE_TYPE_SRFLX: 'srflx', - farsight.CANDIDATE_TYPE_PRFLX: 'prflx', - farsight.CANDIDATE_TYPE_RELAY: 'relay', - farsight.CANDIDATE_TYPE_MULTICAST: 'multicast'} - attrs = { - 'component': candidate.component_id, - 'foundation': '1', # hack - 'generation': '0', - 'ip': candidate.ip, - 'network': '0', - 'port': candidate.port, - 'priority': int(candidate.priority), # hack - } - if candidate.type in types: - attrs['type'] = types[candidate.type] - if candidate.proto == farsight.NETWORK_PROTOCOL_UDP: - attrs['protocol'] = 'udp' - else: - # we actually don't handle properly different tcp options in jingle - attrs['protocol'] = 'tcp' - return xmpp.Node('candidate', attrs=attrs) + def make_candidate(self, candidate): + types = {farsight.CANDIDATE_TYPE_HOST: 'host', + farsight.CANDIDATE_TYPE_SRFLX: 'srflx', + farsight.CANDIDATE_TYPE_PRFLX: 'prflx', + farsight.CANDIDATE_TYPE_RELAY: 'relay', + farsight.CANDIDATE_TYPE_MULTICAST: 'multicast'} + attrs = { + 'component': candidate.component_id, + 'foundation': '1', # hack + 'generation': '0', + 'ip': candidate.ip, + 'network': '0', + 'port': candidate.port, + 'priority': int(candidate.priority), # hack + } + if candidate.type in types: + attrs['type'] = types[candidate.type] + if candidate.proto == farsight.NETWORK_PROTOCOL_UDP: + attrs['protocol'] = 'udp' + else: + # we actually don't handle properly different tcp options in jingle + attrs['protocol'] = 'tcp' + return xmpp.Node('candidate', attrs=attrs) - def make_transport(self, candidates=None): - transport = JingleTransport.make_transport(self, candidates) - transport.setNamespace(xmpp.NS_JINGLE_ICE_UDP) - if self.candidates and self.candidates[0].username and \ - self.candidates[0].password: - transport.setAttr('ufrag', self.candidates[0].username) - transport.setAttr('pwd', self.candidates[0].password) - return transport + def make_transport(self, candidates=None): + transport = JingleTransport.make_transport(self, candidates) + transport.setNamespace(xmpp.NS_JINGLE_ICE_UDP) + if self.candidates and self.candidates[0].username and \ + self.candidates[0].password: + transport.setAttr('ufrag', self.candidates[0].username) + transport.setAttr('pwd', self.candidates[0].password) + return transport - def parse_transport_stanza(self, transport): - candidates = [] - for candidate in transport.iterTags('candidate'): - cand = farsight.Candidate() - cand.component_id = int(candidate['component']) - cand.ip = str(candidate['ip']) - cand.port = int(candidate['port']) - cand.foundation = str(candidate['foundation']) - #cand.type = farsight.CANDIDATE_TYPE_LOCAL - cand.priority = int(candidate['priority']) + def parse_transport_stanza(self, transport): + candidates = [] + for candidate in transport.iterTags('candidate'): + cand = farsight.Candidate() + cand.component_id = int(candidate['component']) + cand.ip = str(candidate['ip']) + cand.port = int(candidate['port']) + cand.foundation = str(candidate['foundation']) + #cand.type = farsight.CANDIDATE_TYPE_LOCAL + cand.priority = int(candidate['priority']) - if candidate['protocol'] == 'udp': - cand.proto = farsight.NETWORK_PROTOCOL_UDP - else: - # we actually don't handle properly different tcp options in jingle - cand.proto = farsight.NETWORK_PROTOCOL_TCP + if candidate['protocol'] == 'udp': + cand.proto = farsight.NETWORK_PROTOCOL_UDP + else: + # we actually don't handle properly different tcp options in jingle + cand.proto = farsight.NETWORK_PROTOCOL_TCP - cand.username = str(transport['ufrag']) - cand.password = str(transport['pwd']) + cand.username = str(transport['ufrag']) + cand.password = str(transport['pwd']) - #FIXME: huh? - types = {'host': farsight.CANDIDATE_TYPE_HOST, - 'srflx': farsight.CANDIDATE_TYPE_SRFLX, - 'prflx': farsight.CANDIDATE_TYPE_PRFLX, - 'relay': farsight.CANDIDATE_TYPE_RELAY, - 'multicast': farsight.CANDIDATE_TYPE_MULTICAST} - if 'type' in candidate and candidate['type'] in types: - cand.type = types[candidate['type']] - else: - print 'Unknown type %s', candidate['type'] - candidates.append(cand) - self.remote_candidates.extend(candidates) - return candidates + #FIXME: huh? + types = {'host': farsight.CANDIDATE_TYPE_HOST, + 'srflx': farsight.CANDIDATE_TYPE_SRFLX, + 'prflx': farsight.CANDIDATE_TYPE_PRFLX, + 'relay': farsight.CANDIDATE_TYPE_RELAY, + 'multicast': farsight.CANDIDATE_TYPE_MULTICAST} + if 'type' in candidate and candidate['type'] in types: + cand.type = types[candidate['type']] + else: + print 'Unknown type %s', candidate['type'] + candidates.append(cand) + self.remote_candidates.extend(candidates) + return candidates transports[xmpp.NS_JINGLE_ICE_UDP] = JingleTransportICEUDP - diff --git a/src/common/kwalletbinding.py b/src/common/kwalletbinding.py index 0de030bd8..6bd9a07b6 100644 --- a/src/common/kwalletbinding.py +++ b/src/common/kwalletbinding.py @@ -25,54 +25,54 @@ import subprocess def kwallet_available(): - """Return True if kwalletcli can be run, False otherwise.""" - try: - p = subprocess.Popen(["kwalletcli", "-qV"]) - except Exception: - return False - p.communicate() - if p.returncode == 0: - return True - return False + """Return True if kwalletcli can be run, False otherwise.""" + try: + p = subprocess.Popen(["kwalletcli", "-qV"]) + except Exception: + return False + p.communicate() + if p.returncode == 0: + return True + return False def kwallet_get(folder, entry): - """Retrieve a passphrase from the KDE Wallet via kwalletcli. + """Retrieve a passphrase from the KDE Wallet via kwalletcli. - Arguments: - • folder: The top-level category to use (normally the programme name) - • entry: The key of the entry to retrieve + Arguments: + • folder: The top-level category to use (normally the programme name) + • entry: The key of the entry to retrieve - Returns the passphrase as unicode, False if it cannot be found, - or None if an error occured. + Returns the passphrase as unicode, False if it cannot be found, + or None if an error occured. - """ - p = subprocess.Popen(["kwalletcli", "-q", "-f", folder.encode('utf-8'), - "-e", entry.encode('utf-8')], stdout=subprocess.PIPE) - pw = p.communicate()[0] - if p.returncode == 0: - return unicode(pw.decode('utf-8')) - if p.returncode == 1 or p.returncode == 4: - # ENOENT - return False - # error - return None + """ + p = subprocess.Popen(["kwalletcli", "-q", "-f", folder.encode('utf-8'), + "-e", entry.encode('utf-8')], stdout=subprocess.PIPE) + pw = p.communicate()[0] + if p.returncode == 0: + return unicode(pw.decode('utf-8')) + if p.returncode == 1 or p.returncode == 4: + # ENOENT + return False + # error + return None def kwallet_put(folder, entry, passphrase): - """Store a passphrase into the KDE Wallet via kwalletcli. + """Store a passphrase into the KDE Wallet via kwalletcli. - Arguments: - • folder: The top-level category to use (normally the programme name) - • entry: The key of the entry to store - • passphrase: The value to store + Arguments: + • folder: The top-level category to use (normally the programme name) + • entry: The key of the entry to store + • passphrase: The value to store - Returns True on success, False otherwise. + Returns True on success, False otherwise. - """ - p = subprocess.Popen(["kwalletcli", "-q", "-f", folder.encode('utf-8'), - "-e", entry.encode('utf-8'), "-P"], stdin=subprocess.PIPE) - p.communicate(passphrase.encode('utf-8')) - if p.returncode == 0: - return True - return False + """ + p = subprocess.Popen(["kwalletcli", "-q", "-f", folder.encode('utf-8'), + "-e", entry.encode('utf-8'), "-P"], stdin=subprocess.PIPE) + p.communicate(passphrase.encode('utf-8')) + if p.returncode == 0: + return True + return False diff --git a/src/common/latex.py b/src/common/latex.py index 777577d1c..8cbc95f52 100644 --- a/src/common/latex.py +++ b/src/common/latex.py @@ -41,120 +41,118 @@ import helpers # some latex commands are really bad blacklist = ['\\def', '\\let', '\\futurelet', - '\\newcommand', '\\renewcomment', '\\else', '\\fi', '\\write', - '\\input', '\\include', '\\chardef', '\\catcode', '\\makeatletter', - '\\noexpand', '\\toksdef', '\\every', '\\errhelp', '\\errorstopmode', - '\\scrollmode', '\\nonstopmode', '\\batchmode', '\\read', '\\csname', - '\\newhelp', '\\relax', '\\afterground', '\\afterassignment', - '\\expandafter', '\\noexpand', '\\special', '\\command', '\\loop', - '\\repeat', '\\toks', '\\output', '\\line', '\\mathcode', '\\name', - '\\item', '\\section', '\\mbox', '\\DeclareRobustCommand', '\\[', - '\\]'] + '\\newcommand', '\\renewcomment', '\\else', '\\fi', '\\write', + '\\input', '\\include', '\\chardef', '\\catcode', '\\makeatletter', + '\\noexpand', '\\toksdef', '\\every', '\\errhelp', '\\errorstopmode', + '\\scrollmode', '\\nonstopmode', '\\batchmode', '\\read', '\\csname', + '\\newhelp', '\\relax', '\\afterground', '\\afterassignment', + '\\expandafter', '\\noexpand', '\\special', '\\command', '\\loop', + '\\repeat', '\\toks', '\\output', '\\line', '\\mathcode', '\\name', + '\\item', '\\section', '\\mbox', '\\DeclareRobustCommand', '\\[', + '\\]'] # True if the string matches the blacklist def check_blacklist(str_): - for word in blacklist: - if word in str_: - return True - return False + for word in blacklist: + if word in str_: + return True + return False def get_tmpfile_name(): - random.seed() - int_ = random.randint(0, 100) - return os.path.join(gettempdir(), 'gajimtex_' + int_.__str__()) + random.seed() + int_ = random.randint(0, 100) + return os.path.join(gettempdir(), 'gajimtex_' + int_.__str__()) def write_latex(filename, str_): - texstr = '\\documentclass[12pt]{article}\\usepackage[dvips]{graphicx}' - texstr += '\\usepackage{amsmath}\\usepackage{amssymb}' - texstr += '\\pagestyle{empty}' - texstr += '\\begin{document}\\begin{large}\\begin{gather*}' - texstr += str_ - texstr += '\\end{gather*}\\end{large}\\end{document}' + texstr = '\\documentclass[12pt]{article}\\usepackage[dvips]{graphicx}' + texstr += '\\usepackage{amsmath}\\usepackage{amssymb}' + texstr += '\\pagestyle{empty}' + texstr += '\\begin{document}\\begin{large}\\begin{gather*}' + texstr += str_ + texstr += '\\end{gather*}\\end{large}\\end{document}' - file_ = open(filename, "w+") - file_.write(texstr) - file_.flush() - file_.close() + file_ = open(filename, "w+") + file_.write(texstr) + file_.flush() + file_.close() # a wrapper for Popen so that no window gets opened on Windows # (i think this is the reason we're using Popen rather than just system()) # stdout goes to a pipe so that it can be read def popen_nt_friendly(command): - if os.name == 'nt': - # CREATE_NO_WINDOW - return Popen(command, creationflags=0x08000000, cwd=gettempdir(), stdout=PIPE) - else: - return Popen(command, cwd=gettempdir(), stdout=PIPE) + if os.name == 'nt': + # CREATE_NO_WINDOW + return Popen(command, creationflags=0x08000000, cwd=gettempdir(), stdout=PIPE) + else: + return Popen(command, cwd=gettempdir(), stdout=PIPE) def check_for_latex_support(): - '''check is latex is available and if it can create a picture.''' + '''check is latex is available and if it can create a picture.''' - try: - filename = latex_to_image("test") - if filename: - # we have a file, conversion succeeded - os.remove(filename) - return True - return False - except LatexError: - return False + try: + filename = latex_to_image("test") + if filename: + # we have a file, conversion succeeded + os.remove(filename) + return True + return False + except LatexError: + return False def try_run(argv): - try: - p = popen_nt_friendly(argv) - out = p.communicate()[0] - log.info(out) - return p.wait() - except Exception, e: - return _('Error executing "%(command)s": %(error)s') % { - 'command': " ".join(argv), - 'error': helpers.decode_string(str(e))} + try: + p = popen_nt_friendly(argv) + out = p.communicate()[0] + log.info(out) + return p.wait() + except Exception, e: + return _('Error executing "%(command)s": %(error)s') % { + 'command': " ".join(argv), + 'error': helpers.decode_string(str(e))} def latex_to_image(str_): - result = None - exitcode = 0 + result = None + exitcode = 0 - try: - bg_str, fg_str = gajim.interface.get_bg_fg_colors() - except: - # interface may not be available when we test latext at startup - bg_str, fg_str = 'rgb 1.0 1.0 1.0', 'rgb 0.0 0.0 0.0' + try: + bg_str, fg_str = gajim.interface.get_bg_fg_colors() + except: + # interface may not be available when we test latext at startup + bg_str, fg_str = 'rgb 1.0 1.0 1.0', 'rgb 0.0 0.0 0.0' - # filter latex code with bad commands - if check_blacklist(str_): - # we triggered the blacklist, immediately return None - return None + # filter latex code with bad commands + if check_blacklist(str_): + # we triggered the blacklist, immediately return None + return None - tmpfile = get_tmpfile_name() + tmpfile = get_tmpfile_name() - # build latex string - write_latex(os.path.join(tmpfile + '.tex'), str_) + # build latex string + write_latex(os.path.join(tmpfile + '.tex'), str_) - # convert TeX to dvi - exitcode = try_run(['latex', '--interaction=nonstopmode', - tmpfile + '.tex']) + # convert TeX to dvi + exitcode = try_run(['latex', '--interaction=nonstopmode', + tmpfile + '.tex']) - if exitcode == 0: - # convert dvi to png - latex_png_dpi = gajim.config.get('latex_png_dpi') - exitcode = try_run(['dvipng', '-bg', bg_str, '-fg', fg_str, '-T', - 'tight', '-D', latex_png_dpi, tmpfile + '.dvi', '-o', - tmpfile + '.png']) + if exitcode == 0: + # convert dvi to png + latex_png_dpi = gajim.config.get('latex_png_dpi') + exitcode = try_run(['dvipng', '-bg', bg_str, '-fg', fg_str, '-T', + 'tight', '-D', latex_png_dpi, tmpfile + '.dvi', '-o', + tmpfile + '.png']) - # remove temp files created by us and TeX - extensions = ['.tex', '.log', '.aux', '.dvi'] - for ext in extensions: - try: - os.remove(tmpfile + ext) - except Exception: - pass + # remove temp files created by us and TeX + extensions = ['.tex', '.log', '.aux', '.dvi'] + for ext in extensions: + try: + os.remove(tmpfile + ext) + except Exception: + pass - if isinstance(exitcode, (unicode, str)): - raise LatexError(exitcode) + if isinstance(exitcode, (unicode, str)): + raise LatexError(exitcode) - if exitcode == 0: - result = tmpfile + '.png' + if exitcode == 0: + result = tmpfile + '.png' - return result - -# vim: se ts=3: + return result diff --git a/src/common/logger.py b/src/common/logger.py index 9fd3e2dd2..5e7955c49 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -37,12 +37,12 @@ import exceptions import gajim try: - import sqlite3 as sqlite # python 2.5 + import sqlite3 as sqlite # python 2.5 except ImportError: - try: - from pysqlite2 import dbapi2 as sqlite - except ImportError: - raise exceptions.PysqliteNotAvailable + try: + from pysqlite2 import dbapi2 as sqlite + except ImportError: + raise exceptions.PysqliteNotAvailable import configpaths LOG_DB_PATH = configpaths.gajimpaths['LOG_DB'] @@ -52,980 +52,978 @@ import logging log = logging.getLogger('gajim.c.logger') class Constants: - def __init__(self): - ( - self.JID_NORMAL_TYPE, - self.JID_ROOM_TYPE - ) = range(2) + def __init__(self): + ( + self.JID_NORMAL_TYPE, + self.JID_ROOM_TYPE + ) = range(2) - ( - self.KIND_STATUS, - self.KIND_GCSTATUS, - self.KIND_GC_MSG, - self.KIND_SINGLE_MSG_RECV, - self.KIND_CHAT_MSG_RECV, - self.KIND_SINGLE_MSG_SENT, - self.KIND_CHAT_MSG_SENT, - self.KIND_ERROR - ) = range(8) + ( + self.KIND_STATUS, + self.KIND_GCSTATUS, + self.KIND_GC_MSG, + self.KIND_SINGLE_MSG_RECV, + self.KIND_CHAT_MSG_RECV, + self.KIND_SINGLE_MSG_SENT, + self.KIND_CHAT_MSG_SENT, + self.KIND_ERROR + ) = range(8) - ( - self.SHOW_ONLINE, - self.SHOW_CHAT, - self.SHOW_AWAY, - self.SHOW_XA, - self.SHOW_DND, - self.SHOW_OFFLINE - ) = range(6) + ( + self.SHOW_ONLINE, + self.SHOW_CHAT, + self.SHOW_AWAY, + self.SHOW_XA, + self.SHOW_DND, + self.SHOW_OFFLINE + ) = range(6) - ( - self.TYPE_AIM, - self.TYPE_GG, - self.TYPE_HTTP_WS, - self.TYPE_ICQ, - self.TYPE_MSN, - self.TYPE_QQ, - self.TYPE_SMS, - self.TYPE_SMTP, - self.TYPE_TLEN, - self.TYPE_YAHOO, - self.TYPE_NEWMAIL, - self.TYPE_RSS, - self.TYPE_WEATHER, - self.TYPE_MRIM, - ) = range(14) + ( + self.TYPE_AIM, + self.TYPE_GG, + self.TYPE_HTTP_WS, + self.TYPE_ICQ, + self.TYPE_MSN, + self.TYPE_QQ, + self.TYPE_SMS, + self.TYPE_SMTP, + self.TYPE_TLEN, + self.TYPE_YAHOO, + self.TYPE_NEWMAIL, + self.TYPE_RSS, + self.TYPE_WEATHER, + self.TYPE_MRIM, + ) = range(14) - ( - self.SUBSCRIPTION_NONE, - self.SUBSCRIPTION_TO, - self.SUBSCRIPTION_FROM, - self.SUBSCRIPTION_BOTH, - ) = range(4) + ( + self.SUBSCRIPTION_NONE, + self.SUBSCRIPTION_TO, + self.SUBSCRIPTION_FROM, + self.SUBSCRIPTION_BOTH, + ) = range(4) constants = Constants() class Logger: - def __init__(self): - self.jids_already_in = [] # holds jids that we already have in DB - self.con = None - - if not os.path.exists(LOG_DB_PATH): - # this can happen only the first time (the time we create the db) - # db is not created here but in src/common/checks_paths.py - return - self.init_vars() - - def close_db(self): - if self.con: - self.con.close() - self.con = None - self.cur = None - - def open_db(self): - self.close_db() - - # FIXME: sqlite3_open wants UTF8 strings. So a path with - # non-ascii chars doesn't work. See #2812 and - # http://lists.initd.org/pipermail/pysqlite/2005-August/000134.html - back = os.getcwd() - os.chdir(LOG_DB_FOLDER) - - # if locked, wait up to 20 sec to unlock - # before raise (hopefully should be enough) - - self.con = sqlite.connect(LOG_DB_FILE, timeout=20.0, - isolation_level='IMMEDIATE') - os.chdir(back) - self.cur = self.con.cursor() - self.set_synchronous(False) - - def set_synchronous(self, sync): - try: - if sync: - self.cur.execute("PRAGMA synchronous = NORMAL") - else: - self.cur.execute("PRAGMA synchronous = OFF") - except sqlite.Error, e: - log.debug("Failed to set_synchronous(%s): %s" % (sync, str(e))) - - def init_vars(self): - self.open_db() - self.get_jids_already_in_db() - - def simple_commit(self, sql_to_commit): - '''helper to commit''' - self.cur.execute(sql_to_commit) - try: - self.con.commit() - except sqlite.OperationalError, e: - print >> sys.stderr, str(e) - - def get_jids_already_in_db(self): - try: - self.cur.execute('SELECT jid FROM jids') - # list of tupples: [(u'aaa@bbb',), (u'cc@dd',)] - rows = self.cur.fetchall() - except sqlite.DatabaseError: - raise exceptions.DatabaseMalformed - self.jids_already_in = [] - for row in rows: - # row[0] is first item of row (the only result here, the jid) - if row[0] == '': - # malformed jid, ignore line - pass - else: - self.jids_already_in.append(row[0]) - - def get_jids_in_db(self): - return self.jids_already_in - - def jid_is_from_pm(self, jid): - '''if jid is gajim@conf/nkour it's likely a pm one, how we know - gajim@conf is not a normal guy and nkour is not his resource? - we ask if gajim@conf is already in jids (with type room jid) - this fails if user disables logging for room and only enables for - pm (so higly unlikely) and if we fail we do not go chaos - (user will see the first pm as if it was message in room's public chat) - and after that all okay''' - - if jid.find('/') > -1: - possible_room_jid = jid.split('/', 1)[0] - return self.jid_is_room_jid(possible_room_jid) - else: - # it's not a full jid, so it's not a pm one - return False - - def jid_is_room_jid(self, jid): - self.cur.execute('SELECT jid_id FROM jids WHERE jid=? AND type=?', - (jid, constants.JID_ROOM_TYPE)) - row = self.cur.fetchone() - if row is None: - return False - else: - return True - - def get_jid_id(self, jid, typestr=None): - '''jids table has jid and jid_id - logs table has log_id, jid_id, contact_name, time, kind, show, message - so to ask logs we need jid_id that matches our jid in jids table - this method wants jid and returns the jid_id for later sql-ing on logs - typestr can be 'ROOM' or anything else depending on the type of JID - and is only needed to be specified when the JID is new in DB - ''' - if jid.find('/') != -1: # if it has a / - jid_is_from_pm = self.jid_is_from_pm(jid) - if not jid_is_from_pm: # it's normal jid with resource - jid = jid.split('/', 1)[0] # remove the resource - if jid in self.jids_already_in: # we already have jids in DB - self.cur.execute('SELECT jid_id FROM jids WHERE jid=?', [jid]) - row = self.cur.fetchone() - if row: - return row[0] - # oh! a new jid :), we add it now - if typestr == 'ROOM': - typ = constants.JID_ROOM_TYPE - else: - typ = constants.JID_NORMAL_TYPE - try: - self.cur.execute('INSERT INTO jids (jid, type) VALUES (?, ?)', (jid, - typ)) - self.con.commit() - except sqlite.IntegrityError, e: - # Jid already in DB, maybe added by another instance. re-read DB - self.get_jids_already_in_db() - return self.get_jid_id(jid, typestr) - except sqlite.OperationalError, e: - raise exceptions.PysqliteOperationalError(str(e)) - jid_id = self.cur.lastrowid - self.jids_already_in.append(jid) - return jid_id - - def convert_human_values_to_db_api_values(self, kind, show): - '''coverts from string style to constant ints for db''' - if kind == 'status': - kind_col = constants.KIND_STATUS - elif kind == 'gcstatus': - kind_col = constants.KIND_GCSTATUS - elif kind == 'gc_msg': - kind_col = constants.KIND_GC_MSG - elif kind == 'single_msg_recv': - kind_col = constants.KIND_SINGLE_MSG_RECV - elif kind == 'single_msg_sent': - kind_col = constants.KIND_SINGLE_MSG_SENT - elif kind == 'chat_msg_recv': - kind_col = constants.KIND_CHAT_MSG_RECV - elif kind == 'chat_msg_sent': - kind_col = constants.KIND_CHAT_MSG_SENT - elif kind == 'error': - kind_col = constants.KIND_ERROR - - if show == 'online': - show_col = constants.SHOW_ONLINE - elif show == 'chat': - show_col = constants.SHOW_CHAT - elif show == 'away': - show_col = constants.SHOW_AWAY - elif show == 'xa': - show_col = constants.SHOW_XA - elif show == 'dnd': - show_col = constants.SHOW_DND - elif show == 'offline': - show_col = constants.SHOW_OFFLINE - elif show is None: - show_col = None - else: # invisible in GC when someone goes invisible - # it's a RFC violation .... but we should not crash - show_col = 'UNKNOWN' - - return kind_col, show_col - - def convert_human_transport_type_to_db_api_values(self, type_): - '''converts from string style to constant ints for db''' - if type_ == 'aim': - return constants.TYPE_AIM - if type_ == 'gadu-gadu': - return constants.TYPE_GG - if type_ == 'http-ws': - return constants.TYPE_HTTP_WS - if type_ == 'icq': - return constants.TYPE_ICQ - if type_ == 'msn': - return constants.TYPE_MSN - if type_ == 'qq': - return constants.TYPE_QQ - if type_ == 'sms': - return constants.TYPE_SMS - if type_ == 'smtp': - return constants.TYPE_SMTP - if type_ in ('tlen', 'x-tlen'): - return constants.TYPE_TLEN - if type_ == 'yahoo': - return constants.TYPE_YAHOO - if type_ == 'newmail': - return constants.TYPE_NEWMAIL - if type_ == 'rss': - return constants.TYPE_RSS - if type_ == 'weather': - return constants.TYPE_WEATHER - if type_ == 'mrim': - return constants.TYPE_MRIM - return None - - def convert_api_values_to_human_transport_type(self, type_id): - '''converts from constant ints for db to string style''' - if type_id == constants.TYPE_AIM: - return 'aim' - if type_id == constants.TYPE_GG: - return 'gadu-gadu' - if type_id == constants.TYPE_HTTP_WS: - return 'http-ws' - if type_id == constants.TYPE_ICQ: - return 'icq' - if type_id == constants.TYPE_MSN: - return 'msn' - if type_id == constants.TYPE_QQ: - return 'qq' - if type_id == constants.TYPE_SMS: - return 'sms' - if type_id == constants.TYPE_SMTP: - return 'smtp' - if type_id == constants.TYPE_TLEN: - return 'tlen' - if type_id == constants.TYPE_YAHOO: - return 'yahoo' - if type_id == constants.TYPE_NEWMAIL: - return 'newmail' - if type_id == constants.TYPE_RSS: - return 'rss' - if type_id == constants.TYPE_WEATHER: - return 'weather' - if type_id == constants.TYPE_MRIM: - return 'mrim' - - def convert_human_subscription_values_to_db_api_values(self, sub): - '''converts from string style to constant ints for db''' - if sub == 'none': - return constants.SUBSCRIPTION_NONE - if sub == 'to': - return constants.SUBSCRIPTION_TO - if sub == 'from': - return constants.SUBSCRIPTION_FROM - if sub == 'both': - return constants.SUBSCRIPTION_BOTH - - def convert_db_api_values_to_human_subscription_values(self, sub): - '''converts from constant ints for db to string style''' - if sub == constants.SUBSCRIPTION_NONE: - return 'none' - if sub == constants.SUBSCRIPTION_TO: - return 'to' - if sub == constants.SUBSCRIPTION_FROM: - return 'from' - if sub == constants.SUBSCRIPTION_BOTH: - return 'both' - - def commit_to_db(self, values, write_unread=False): - sql = '''INSERT INTO logs (jid_id, contact_name, time, kind, show, - message, subject) VALUES (?, ?, ?, ?, ?, ?, ?)''' - try: - self.cur.execute(sql, values) - except sqlite.DatabaseError: - raise exceptions.DatabaseMalformed - except sqlite.OperationalError, e: - raise exceptions.PysqliteOperationalError(str(e)) - message_id = None - try: - self.con.commit() - if write_unread: - message_id = self.cur.lastrowid - except sqlite.OperationalError, e: - print >> sys.stderr, str(e) - if message_id: - self.insert_unread_events(message_id, values[0]) - return message_id - - def insert_unread_events(self, message_id, jid_id): - ''' add unread message with id: message_id''' - sql = 'INSERT INTO unread_messages VALUES (%d, %d, 0)' % (message_id, - jid_id) - self.simple_commit(sql) - - def set_read_messages(self, message_ids): - ''' mark all messages with ids in message_ids as read''' - ids = ','.join([str(i) for i in message_ids]) - sql = 'DELETE FROM unread_messages WHERE message_id IN (%s)' % ids - self.simple_commit(sql) - - def set_shown_unread_msgs(self, msg_id): - ''' mark unread message as shown un GUI ''' - sql = 'UPDATE unread_messages SET shown = 1 where message_id = %s' % \ - msg_id - self.simple_commit(sql) - - def reset_shown_unread_messages(self): - ''' Set shown field to False in unread_messages table ''' - sql = 'UPDATE unread_messages SET shown = 0' - self.simple_commit(sql) - - def get_unread_msgs(self): - ''' get all unread messages ''' - all_messages = [] - try: - self.cur.execute( - 'SELECT message_id, shown from unread_messages') - results = self.cur.fetchall() - except Exception: - pass - for message in results: - msg_id = message[0] - shown = message[1] - # here we get infos for that message, and related jid from jids table - # do NOT change order of SELECTed things, unless you change function(s) - # that called this function - self.cur.execute(''' - SELECT logs.log_line_id, logs.message, logs.time, logs.subject, - jids.jid - FROM logs, jids - WHERE logs.log_line_id = %d AND logs.jid_id = jids.jid_id - ''' % msg_id - ) - results = self.cur.fetchall() - if len(results) == 0: - # Log line is no more in logs table. remove it from unread_messages - self.set_read_messages([msg_id]) - continue - all_messages.append(results[0] + (shown,)) - return all_messages - - def write(self, kind, jid, message=None, show=None, tim=None, subject=None): - '''write a row (status, gcstatus, message etc) to logs database - kind can be status, gcstatus, gc_msg, (we only recv for those 3), - single_msg_recv, chat_msg_recv, chat_msg_sent, single_msg_sent - we cannot know if it is pm or normal chat message, we try to guess - see jid_is_from_pm() - - we analyze jid and store it as follows: - jids.jid text column will hold JID if TC-related, room_jid if GC-related, - ROOM_JID/nick if pm-related.''' - - if self.jids_already_in == []: # only happens if we just created the db - self.open_db() - - contact_name_col = None # holds nickname for kinds gcstatus, gc_msg - # message holds the message unless kind is status or gcstatus, - # then it holds status message - message_col = message - subject_col = subject - if tim: - time_col = int(float(time.mktime(tim))) - else: - time_col = int(float(time.time())) - - kind_col, show_col = self.convert_human_values_to_db_api_values(kind, - show) - - write_unread = False - - # now we may have need to do extra care for some values in columns - if kind == 'status': # we store (not None) time, jid, show, msg - # status for roster items - try: - jid_id = self.get_jid_id(jid) - except exceptions.PysqliteOperationalError, e: - raise exceptions.PysqliteOperationalError(str(e)) - if show is None: # show is None (xmpp), but we say that 'online' - show_col = constants.SHOW_ONLINE - - elif kind == 'gcstatus': - # status in ROOM (for pm status see status) - if show is None: # show is None (xmpp), but we say that 'online' - show_col = constants.SHOW_ONLINE - jid, nick = jid.split('/', 1) - try: - # re-get jid_id for the new jid - jid_id = self.get_jid_id(jid, 'ROOM') - except exceptions.PysqliteOperationalError, e: - raise exceptions.PysqliteOperationalError(str(e)) - contact_name_col = nick - - elif kind == 'gc_msg': - if jid.find('/') != -1: # if it has a / - jid, nick = jid.split('/', 1) - else: - # it's server message f.e. error message - # when user tries to ban someone but he's not allowed to - nick = None - try: - # re-get jid_id for the new jid - jid_id = self.get_jid_id(jid, 'ROOM') - except exceptions.PysqliteOperationalError, e: - raise exceptions.PysqliteOperationalError(str(e)) - contact_name_col = nick - else: - try: - jid_id = self.get_jid_id(jid) - except exceptions.PysqliteOperationalError, e: - raise exceptions.PysqliteOperationalError(str(e)) - if kind == 'chat_msg_recv': - if not self.jid_is_from_pm(jid): - # Save in unread table only if it's not a pm - write_unread = True - - if show_col == 'UNKNOWN': # unknown show, do not log - return - - values = (jid_id, contact_name_col, time_col, kind_col, show_col, - message_col, subject_col) - return self.commit_to_db(values, write_unread) - - def get_last_conversation_lines(self, jid, restore_how_many_rows, - pending_how_many, timeout, account): - '''accepts how many rows to restore and when to time them out (in minutes) - (mark them as too old) and number of messages that are in queue - and are already logged but pending to be viewed, - returns a list of tupples containg time, kind, message, - list with empty tupple if nothing found to meet our demands''' - try: - self.get_jid_id(jid) - except exceptions.PysqliteOperationalError, e: - # Error trying to create a new jid_id. This means there is no log - return [] - where_sql = self._build_contact_where(account, jid) - - now = int(float(time.time())) - timed_out = now - (timeout * 60) # before that they are too old - # so if we ask last 5 lines and we have 2 pending we get - # 3 - 8 (we avoid the last 2 lines but we still return 5 asked) - try: - self.cur.execute(''' - SELECT time, kind, message FROM logs - WHERE (%s) AND kind IN (%d, %d, %d, %d, %d) AND time > %d - ORDER BY time DESC LIMIT %d OFFSET %d - ''' % (where_sql, constants.KIND_SINGLE_MSG_RECV, - constants.KIND_CHAT_MSG_RECV, constants.KIND_SINGLE_MSG_SENT, - constants.KIND_CHAT_MSG_SENT, constants.KIND_ERROR, - timed_out, restore_how_many_rows, pending_how_many) - ) - - results = self.cur.fetchall() - except sqlite.DatabaseError: - raise exceptions.DatabaseMalformed - results.reverse() - return results - - def get_unix_time_from_date(self, year, month, day): - # year (fe 2005), month (fe 11), day (fe 25) - # returns time in seconds for the second that starts that date since epoch - # gimme unixtime from year month day: - d = datetime.date(year, month, day) - local_time = d.timetuple() # time tupple (compat with time.localtime()) - # we have time since epoch baby :) - start_of_day = int(time.mktime(local_time)) - return start_of_day - - def get_conversation_for_date(self, jid, year, month, day, account): - '''returns contact_name, time, kind, show, message, subject - for each row in a list of tupples, - returns list with empty tupple if we found nothing to meet our demands''' - try: - self.get_jid_id(jid) - except exceptions.PysqliteOperationalError, e: - # Error trying to create a new jid_id. This means there is no log - return [] - where_sql = self._build_contact_where(account, jid) - - start_of_day = self.get_unix_time_from_date(year, month, day) - seconds_in_a_day = 86400 # 60 * 60 * 24 - last_second_of_day = start_of_day + seconds_in_a_day - 1 - - self.cur.execute(''' - SELECT contact_name, time, kind, show, message, subject FROM logs - WHERE (%s) - AND time BETWEEN %d AND %d - ORDER BY time - ''' % (where_sql, start_of_day, last_second_of_day)) - - results = self.cur.fetchall() - return results - - def get_search_results_for_query(self, jid, query, account): - '''returns contact_name, time, kind, show, message - for each row in a list of tupples, - returns list with empty tupple if we found nothing to meet our demands''' - try: - self.get_jid_id(jid) - except exceptions.PysqliteOperationalError, e: - # Error trying to create a new jid_id. This means there is no log - return [] - - if False: # query.startswith('SELECT '): # it's SQL query (FIXME) - try: - self.cur.execute(query) - except sqlite.OperationalError, e: - results = [('', '', '', '', str(e))] - return results - - else: # user just typed something, we search in message column - where_sql = self._build_contact_where(account, jid) - like_sql = '%' + query.replace("'", "''") + '%' - self.cur.execute(''' - SELECT contact_name, time, kind, show, message, subject FROM logs - WHERE (%s) AND message LIKE '%s' - ORDER BY time - ''' % (where_sql, like_sql)) - - results = self.cur.fetchall() - return results - - def get_days_with_logs(self, jid, year, month, max_day, account): - '''returns the list of days that have logs (not status messages)''' - try: - self.get_jid_id(jid) - except exceptions.PysqliteOperationalError, e: - # Error trying to create a new jid_id. This means there is no log - return [] - days_with_logs = [] - where_sql = self._build_contact_where(account, jid) - - # First select all date of month whith logs we want - start_of_month = self.get_unix_time_from_date(year, month, 1) - seconds_in_a_day = 86400 # 60 * 60 * 24 - last_second_of_month = start_of_month + (seconds_in_a_day * max_day) - 1 - - # Select times and 'floor' them to time 0:00 - # (by dividing, they are integers) - # and take only one of the same values (distinct) - # Now we have timestamps of time 0:00 of every day with logs - self.cur.execute(''' - SELECT DISTINCT time/(86400)*86400 FROM logs - WHERE (%s) - AND time BETWEEN %d AND %d - AND kind NOT IN (%d, %d) - ORDER BY time - ''' % (where_sql, start_of_month, last_second_of_month, - constants.KIND_STATUS, constants.KIND_GCSTATUS)) - result = self.cur.fetchall() - - # convert timestamps to day of month - for line in result: - days_with_logs[0:0]=[time.gmtime(line[0])[2]] - - return days_with_logs - - def get_last_date_that_has_logs(self, jid, account=None, is_room=False): - '''returns last time (in seconds since EPOCH) for which - we had logs (excluding statuses)''' - where_sql = '' - if not is_room: - where_sql = self._build_contact_where(account, jid) - else: - try: - jid_id = self.get_jid_id(jid, 'ROOM') - except exceptions.PysqliteOperationalError, e: - # Error trying to create a new jid_id. This means there is no log - return None - where_sql = 'jid_id = %s' % jid_id - self.cur.execute(''' - SELECT MAX(time) FROM logs - WHERE (%s) - AND kind NOT IN (%d, %d) - ''' % (where_sql, constants.KIND_STATUS, constants.KIND_GCSTATUS)) - - results = self.cur.fetchone() - if results is not None: - result = results[0] - else: - result = None - return result - - def get_room_last_message_time(self, jid): - '''returns FASTLY last time (in seconds since EPOCH) for which - we had logs for that room from rooms_last_message_time table''' - try: - jid_id = self.get_jid_id(jid, 'ROOM') - except exceptions.PysqliteOperationalError, e: - # Error trying to create a new jid_id. This means there is no log - return None - where_sql = 'jid_id = %s' % jid_id - self.cur.execute(''' - SELECT time FROM rooms_last_message_time - WHERE (%s) - ''' % (where_sql)) - - results = self.cur.fetchone() - if results is not None: - result = results[0] - else: - result = None - return result - - def set_room_last_message_time(self, jid, time): - '''set last time (in seconds since EPOCH) for which - we had logs for that room in rooms_last_message_time table''' - jid_id = self.get_jid_id(jid, 'ROOM') - # jid_id is unique in this table, create or update : - sql = 'REPLACE INTO rooms_last_message_time VALUES (%d, %d)' % \ - (jid_id, time) - self.simple_commit(sql) - - def _build_contact_where(self, account, jid): - '''build the where clause for a jid, including metacontacts - jid(s) if any''' - where_sql = '' - # will return empty list if jid is not associated with - # any metacontacts - family = gajim.contacts.get_metacontacts_family(account, jid) - if family: - for user in family: - try: - jid_id = self.get_jid_id(user['jid']) - except exceptions.PysqliteOperationalError, e: - continue - where_sql += 'jid_id = %s' % jid_id - if user != family[-1]: - where_sql += ' OR ' - else: # if jid was not associated with metacontacts - jid_id = self.get_jid_id(jid) - where_sql = 'jid_id = %s' % jid_id - return where_sql - - def save_transport_type(self, jid, type_): - '''save the type of the transport in DB''' - type_id = self.convert_human_transport_type_to_db_api_values(type_) - if not type_id: - # unknown type - return - self.cur.execute( - 'SELECT type from transports_cache WHERE transport = "%s"' % jid) - results = self.cur.fetchall() - if results: - result = results[0][0] - if result == type_id: - return - sql = 'UPDATE transports_cache SET type = %d WHERE transport = "%s"' %\ - (type_id, jid) - self.simple_commit(sql) - return - sql = 'INSERT INTO transports_cache VALUES ("%s", %d)' % (jid, type_id) - self.simple_commit(sql) - - def get_transports_type(self): - '''return all the type of the transports in DB''' - self.cur.execute( - 'SELECT * from transports_cache') - results = self.cur.fetchall() - if not results: - return {} - answer = {} - for result in results: - answer[result[0]] = self.convert_api_values_to_human_transport_type( - result[1]) - return answer - - # A longer note here: - # The database contains a blob field. Pysqlite seems to need special care for - # such fields. - # When storing, we need to convert string into buffer object (1). - # When retrieving, we need to convert it back to a string to decompress it. - # (2) - # GzipFile needs a file-like object, StringIO emulates file for plain strings - def iter_caps_data(self): - ''' Iterate over caps cache data stored in the database. - The iterator values are pairs of (node, ver, ext, identities, features): - identities == {'category':'foo', 'type':'bar', 'name':'boo'}, - features being a list of feature namespaces. ''' - - # get data from table - # the data field contains binary object (gzipped data), this is a hack - # to get that data without trying to convert it to unicode - try: - self.cur.execute('SELECT hash_method, hash, data FROM caps_cache;') - except sqlite.OperationalError: - # might happen when there's no caps_cache table yet - # -- there's no data to read anyway then - return - - # list of corrupted entries that will be removed - to_be_removed = [] - for hash_method, hash_, data in self.cur: - # for each row: unpack the data field - # (format: (category, type, name, category, type, name, ... - # ..., 'FEAT', feature1, feature2, ...).join(' ')) - # NOTE: if there's a need to do more gzip, put that to a function - try: - data = GzipFile(fileobj=StringIO(str(data))).read().decode( - 'utf-8').split('\0') - except IOError: - # This data is corrupted. It probably contains non-ascii chars - to_be_removed.append((hash_method, hash_)) - continue - i = 0 - identities = list() - features = list() - while i < (len(data) - 3) and data[i] != 'FEAT': - category = data[i] - type_ = data[i + 1] - lang = data[i + 2] - name = data[i + 3] - identities.append({'category': category, 'type': type_, - 'xml:lang': lang, 'name': name}) - i += 4 - i+=1 - while i < len(data): - features.append(data[i]) - i += 1 - - # yield the row - yield hash_method, hash_, identities, features - for hash_method, hash_ in to_be_removed: - sql = '''DELETE FROM caps_cache WHERE hash_method = "%s" AND - hash = "%s"''' % (hash_method, hash_) - self.simple_commit(sql) - - def add_caps_entry(self, hash_method, hash_, identities, features): - data = [] - for identity in identities: - # there is no FEAT category - if identity['category'] == 'FEAT': - return - data.extend((identity.get('category'), identity.get('type', ''), - identity.get('xml:lang', ''), identity.get('name', ''))) - data.append('FEAT') - data.extend(features) - data = '\0'.join(data) - # if there's a need to do more gzip, put that to a function - string = StringIO() - gzip = GzipFile(fileobj=string, mode='w') - data = data.encode('utf-8') # the gzip module can't handle unicode objects - gzip.write(data) - gzip.close() - data = string.getvalue() - self.cur.execute(''' - INSERT INTO caps_cache ( hash_method, hash, data, last_seen ) - VALUES (?, ?, ?, ?); - ''', (hash_method, hash_, buffer(data), int(time.time()))) - # (1) -- note above - try: - self.con.commit() - except sqlite.OperationalError, e: - print >> sys.stderr, str(e) - - def update_caps_time(self, method, hash_): - sql = '''UPDATE caps_cache SET last_seen = %d - WHERE hash_method = "%s" and hash = "%s"''' % \ - (int(time.time()), method, hash_) - self.simple_commit(sql) - - def clean_caps_table(self): - '''Remove caps which was not seen for 3 months''' - sql = '''DELETE FROM caps_cache WHERE last_seen < %d''' % \ - int(time.time() - 3*30*24*3600) - self.simple_commit(sql) - - def replace_roster(self, account_name, roster_version, roster): - ''' Replace current roster in DB by a new one. - accout_name is the name of the account to change - roster_version is the version of the new roster - roster is the new version ''' - # First we must reset roster_version value to ensure that the server - # sends back all the roster at the next connexion if the replacement - # didn't work properly. - gajim.config.set_per('accounts', account_name, 'roster_version', '') - - account_jid = gajim.get_jid_from_account(account_name) - account_jid_id = self.get_jid_id(account_jid) - - # Delete old roster - self.remove_roster(account_jid) - - # Fill roster tables with the new roster - for jid in roster: - self.add_or_update_contact(account_jid, jid, roster[jid]['name'], - roster[jid]['subscription'], roster[jid]['ask'], - roster[jid]['groups']) - - # At this point, we are sure the replacement works properly so we can - # set the new roster_version value. - gajim.config.set_per('accounts', account_name, 'roster_version', - roster_version) - - def del_contact(self, account_jid, jid): - ''' Remove jid from account_jid roster. ''' - try: - account_jid_id = self.get_jid_id(account_jid) - jid_id = self.get_jid_id(jid) - except exceptions.PysqliteOperationalError, e: - raise exceptions.PysqliteOperationalError(str(e)) - self.cur.execute( - 'DELETE FROM roster_group WHERE account_jid_id=? AND jid_id=?', - (account_jid_id, jid_id)) - self.cur.execute( - 'DELETE FROM roster_entry WHERE account_jid_id=? AND jid_id=?', - (account_jid_id, jid_id)) - self.con.commit() - - def add_or_update_contact(self, account_jid, jid, name, sub, ask, groups): - ''' Add or update a contact from account_jid roster. ''' - if sub == 'remove': - self.del_contact(account_jid, jid) - return - - try: - account_jid_id = self.get_jid_id(account_jid) - jid_id = self.get_jid_id(jid) - except exceptions.PysqliteOperationalError, e: - raise exceptions.PysqliteOperationalError(str(e)) - - # Update groups information - # First we delete all previous groups information - self.cur.execute( - 'DELETE FROM roster_group WHERE account_jid_id=? AND jid_id=?', - (account_jid_id, jid_id)) - # Then we add all new groups information - for group in groups: - self.cur.execute('INSERT INTO roster_group VALUES(?, ?, ?)', - (account_jid_id, jid_id, group)) - - if name is None: - name = '' - - self.cur.execute('REPLACE INTO roster_entry VALUES(?, ?, ?, ?, ?)', - (account_jid_id, jid_id, name, - self.convert_human_subscription_values_to_db_api_values(sub), - bool(ask))) - self.con.commit() - - def get_roster(self, account_jid): - ''' Return the accound_jid roster in NonBlockingRoster format. ''' - data = {} - account_jid_id = self.get_jid_id(account_jid) - - # First we fill data with roster_entry informations - self.cur.execute(''' - SELECT j.jid, re.jid_id, re.name, re.subscription, re.ask - FROM roster_entry re, jids j - WHERE re.account_jid_id=? AND j.jid_id=re.jid_id''', (account_jid_id,)) - for jid, jid_id, name, subscription, ask in self.cur: - data[jid] = {} - if name: - data[jid]['name'] = name - else: - data[jid]['name'] = None - data[jid]['subscription'] = \ - self.convert_db_api_values_to_human_subscription_values( - subscription) - data[jid]['groups'] = [] - data[jid]['resources'] = {} - if ask: - data[jid]['ask'] = 'subscribe' - else: - data[jid]['ask'] = None - data[jid]['id'] = jid_id - - # Then we add group for roster entries - for jid in data: - self.cur.execute(''' - SELECT group_name FROM roster_group - WHERE account_jid_id=? AND jid_id=?''', - (account_jid_id, data[jid]['id'])) - for (group_name,) in self.cur: - data[jid]['groups'].append(group_name) - del data[jid]['id'] - - return data - - def remove_roster(self, account_jid): - ''' Remove all entry from account_jid roster. ''' - account_jid_id = self.get_jid_id(account_jid) - - self.cur.execute('DELETE FROM roster_entry WHERE account_jid_id=?', - (account_jid_id,)) - self.cur.execute('DELETE FROM roster_group WHERE account_jid_id=?', - (account_jid_id,)) - self.con.commit() - - def save_if_not_exists(self, with_, direction, tim, msg='', nick=None): - if tim: - time_col = int(float(time.mktime(tim))) - else: - time_col = int(float(time.time())) - if msg: - if self.jid_is_room_jid(with_) or nick: - # It's a groupchat message - if nick: - # It's a message from a groupchat occupent - type_ = 'gc_msg' - with_ = with_ + '/' + nick - else: - # It's a server message message, we don't log them - return - else: - if direction == 'from': - type_ = 'chat_msg_recv' - elif direction == 'to': - type_ = 'chat_msg_sent' - jid_id = self.get_jid_id(with_) - where_sql = 'jid_id = %s AND message=?' % (jid_id) - if type_ == 'gc_msg': - # We cannot differentiate gc message and pm messages, so look in both - # logs - with_2 = gajim.get_jid_without_resource(with_) - if with_ != with_2: - jid_id2 = self.get_jid_id(with_2) - where_sql = 'jid_id in (%s, %s) AND message=?' % (jid_id, jid_id2) - start_time = time_col - 300 # 5 minutes arrount given time - end_time = time_col + 300 # 5 minutes arrount given time - self.cur.execute(''' - SELECT log_line_id FROM logs - WHERE (%s) - AND time BETWEEN %d AND %d - ORDER BY time - ''' % (where_sql, start_time, end_time), (msg,)) - results = self.cur.fetchall() - if results: - log.debug('Log already in DB, ignoring it') - return - log.debug('New log received from server archives, storing it') - self.write(type_, with_, message=msg, tim=tim) - -# vim: se ts=3: + def __init__(self): + self.jids_already_in = [] # holds jids that we already have in DB + self.con = None + + if not os.path.exists(LOG_DB_PATH): + # this can happen only the first time (the time we create the db) + # db is not created here but in src/common/checks_paths.py + return + self.init_vars() + + def close_db(self): + if self.con: + self.con.close() + self.con = None + self.cur = None + + def open_db(self): + self.close_db() + + # FIXME: sqlite3_open wants UTF8 strings. So a path with + # non-ascii chars doesn't work. See #2812 and + # http://lists.initd.org/pipermail/pysqlite/2005-August/000134.html + back = os.getcwd() + os.chdir(LOG_DB_FOLDER) + + # if locked, wait up to 20 sec to unlock + # before raise (hopefully should be enough) + + self.con = sqlite.connect(LOG_DB_FILE, timeout=20.0, + isolation_level='IMMEDIATE') + os.chdir(back) + self.cur = self.con.cursor() + self.set_synchronous(False) + + def set_synchronous(self, sync): + try: + if sync: + self.cur.execute("PRAGMA synchronous = NORMAL") + else: + self.cur.execute("PRAGMA synchronous = OFF") + except sqlite.Error, e: + log.debug("Failed to set_synchronous(%s): %s" % (sync, str(e))) + + def init_vars(self): + self.open_db() + self.get_jids_already_in_db() + + def simple_commit(self, sql_to_commit): + '''helper to commit''' + self.cur.execute(sql_to_commit) + try: + self.con.commit() + except sqlite.OperationalError, e: + print >> sys.stderr, str(e) + + def get_jids_already_in_db(self): + try: + self.cur.execute('SELECT jid FROM jids') + # list of tupples: [(u'aaa@bbb',), (u'cc@dd',)] + rows = self.cur.fetchall() + except sqlite.DatabaseError: + raise exceptions.DatabaseMalformed + self.jids_already_in = [] + for row in rows: + # row[0] is first item of row (the only result here, the jid) + if row[0] == '': + # malformed jid, ignore line + pass + else: + self.jids_already_in.append(row[0]) + + def get_jids_in_db(self): + return self.jids_already_in + + def jid_is_from_pm(self, jid): + '''if jid is gajim@conf/nkour it's likely a pm one, how we know + gajim@conf is not a normal guy and nkour is not his resource? + we ask if gajim@conf is already in jids (with type room jid) + this fails if user disables logging for room and only enables for + pm (so higly unlikely) and if we fail we do not go chaos + (user will see the first pm as if it was message in room's public chat) + and after that all okay''' + + if jid.find('/') > -1: + possible_room_jid = jid.split('/', 1)[0] + return self.jid_is_room_jid(possible_room_jid) + else: + # it's not a full jid, so it's not a pm one + return False + + def jid_is_room_jid(self, jid): + self.cur.execute('SELECT jid_id FROM jids WHERE jid=? AND type=?', + (jid, constants.JID_ROOM_TYPE)) + row = self.cur.fetchone() + if row is None: + return False + else: + return True + + def get_jid_id(self, jid, typestr=None): + '''jids table has jid and jid_id + logs table has log_id, jid_id, contact_name, time, kind, show, message + so to ask logs we need jid_id that matches our jid in jids table + this method wants jid and returns the jid_id for later sql-ing on logs + typestr can be 'ROOM' or anything else depending on the type of JID + and is only needed to be specified when the JID is new in DB + ''' + if jid.find('/') != -1: # if it has a / + jid_is_from_pm = self.jid_is_from_pm(jid) + if not jid_is_from_pm: # it's normal jid with resource + jid = jid.split('/', 1)[0] # remove the resource + if jid in self.jids_already_in: # we already have jids in DB + self.cur.execute('SELECT jid_id FROM jids WHERE jid=?', [jid]) + row = self.cur.fetchone() + if row: + return row[0] + # oh! a new jid :), we add it now + if typestr == 'ROOM': + typ = constants.JID_ROOM_TYPE + else: + typ = constants.JID_NORMAL_TYPE + try: + self.cur.execute('INSERT INTO jids (jid, type) VALUES (?, ?)', (jid, + typ)) + self.con.commit() + except sqlite.IntegrityError, e: + # Jid already in DB, maybe added by another instance. re-read DB + self.get_jids_already_in_db() + return self.get_jid_id(jid, typestr) + except sqlite.OperationalError, e: + raise exceptions.PysqliteOperationalError(str(e)) + jid_id = self.cur.lastrowid + self.jids_already_in.append(jid) + return jid_id + + def convert_human_values_to_db_api_values(self, kind, show): + '''coverts from string style to constant ints for db''' + if kind == 'status': + kind_col = constants.KIND_STATUS + elif kind == 'gcstatus': + kind_col = constants.KIND_GCSTATUS + elif kind == 'gc_msg': + kind_col = constants.KIND_GC_MSG + elif kind == 'single_msg_recv': + kind_col = constants.KIND_SINGLE_MSG_RECV + elif kind == 'single_msg_sent': + kind_col = constants.KIND_SINGLE_MSG_SENT + elif kind == 'chat_msg_recv': + kind_col = constants.KIND_CHAT_MSG_RECV + elif kind == 'chat_msg_sent': + kind_col = constants.KIND_CHAT_MSG_SENT + elif kind == 'error': + kind_col = constants.KIND_ERROR + + if show == 'online': + show_col = constants.SHOW_ONLINE + elif show == 'chat': + show_col = constants.SHOW_CHAT + elif show == 'away': + show_col = constants.SHOW_AWAY + elif show == 'xa': + show_col = constants.SHOW_XA + elif show == 'dnd': + show_col = constants.SHOW_DND + elif show == 'offline': + show_col = constants.SHOW_OFFLINE + elif show is None: + show_col = None + else: # invisible in GC when someone goes invisible + # it's a RFC violation .... but we should not crash + show_col = 'UNKNOWN' + + return kind_col, show_col + + def convert_human_transport_type_to_db_api_values(self, type_): + '''converts from string style to constant ints for db''' + if type_ == 'aim': + return constants.TYPE_AIM + if type_ == 'gadu-gadu': + return constants.TYPE_GG + if type_ == 'http-ws': + return constants.TYPE_HTTP_WS + if type_ == 'icq': + return constants.TYPE_ICQ + if type_ == 'msn': + return constants.TYPE_MSN + if type_ == 'qq': + return constants.TYPE_QQ + if type_ == 'sms': + return constants.TYPE_SMS + if type_ == 'smtp': + return constants.TYPE_SMTP + if type_ in ('tlen', 'x-tlen'): + return constants.TYPE_TLEN + if type_ == 'yahoo': + return constants.TYPE_YAHOO + if type_ == 'newmail': + return constants.TYPE_NEWMAIL + if type_ == 'rss': + return constants.TYPE_RSS + if type_ == 'weather': + return constants.TYPE_WEATHER + if type_ == 'mrim': + return constants.TYPE_MRIM + return None + + def convert_api_values_to_human_transport_type(self, type_id): + '''converts from constant ints for db to string style''' + if type_id == constants.TYPE_AIM: + return 'aim' + if type_id == constants.TYPE_GG: + return 'gadu-gadu' + if type_id == constants.TYPE_HTTP_WS: + return 'http-ws' + if type_id == constants.TYPE_ICQ: + return 'icq' + if type_id == constants.TYPE_MSN: + return 'msn' + if type_id == constants.TYPE_QQ: + return 'qq' + if type_id == constants.TYPE_SMS: + return 'sms' + if type_id == constants.TYPE_SMTP: + return 'smtp' + if type_id == constants.TYPE_TLEN: + return 'tlen' + if type_id == constants.TYPE_YAHOO: + return 'yahoo' + if type_id == constants.TYPE_NEWMAIL: + return 'newmail' + if type_id == constants.TYPE_RSS: + return 'rss' + if type_id == constants.TYPE_WEATHER: + return 'weather' + if type_id == constants.TYPE_MRIM: + return 'mrim' + + def convert_human_subscription_values_to_db_api_values(self, sub): + '''converts from string style to constant ints for db''' + if sub == 'none': + return constants.SUBSCRIPTION_NONE + if sub == 'to': + return constants.SUBSCRIPTION_TO + if sub == 'from': + return constants.SUBSCRIPTION_FROM + if sub == 'both': + return constants.SUBSCRIPTION_BOTH + + def convert_db_api_values_to_human_subscription_values(self, sub): + '''converts from constant ints for db to string style''' + if sub == constants.SUBSCRIPTION_NONE: + return 'none' + if sub == constants.SUBSCRIPTION_TO: + return 'to' + if sub == constants.SUBSCRIPTION_FROM: + return 'from' + if sub == constants.SUBSCRIPTION_BOTH: + return 'both' + + def commit_to_db(self, values, write_unread=False): + sql = '''INSERT INTO logs (jid_id, contact_name, time, kind, show, + message, subject) VALUES (?, ?, ?, ?, ?, ?, ?)''' + try: + self.cur.execute(sql, values) + except sqlite.DatabaseError: + raise exceptions.DatabaseMalformed + except sqlite.OperationalError, e: + raise exceptions.PysqliteOperationalError(str(e)) + message_id = None + try: + self.con.commit() + if write_unread: + message_id = self.cur.lastrowid + except sqlite.OperationalError, e: + print >> sys.stderr, str(e) + if message_id: + self.insert_unread_events(message_id, values[0]) + return message_id + + def insert_unread_events(self, message_id, jid_id): + ''' add unread message with id: message_id''' + sql = 'INSERT INTO unread_messages VALUES (%d, %d, 0)' % (message_id, + jid_id) + self.simple_commit(sql) + + def set_read_messages(self, message_ids): + ''' mark all messages with ids in message_ids as read''' + ids = ','.join([str(i) for i in message_ids]) + sql = 'DELETE FROM unread_messages WHERE message_id IN (%s)' % ids + self.simple_commit(sql) + + def set_shown_unread_msgs(self, msg_id): + ''' mark unread message as shown un GUI ''' + sql = 'UPDATE unread_messages SET shown = 1 where message_id = %s' % \ + msg_id + self.simple_commit(sql) + + def reset_shown_unread_messages(self): + ''' Set shown field to False in unread_messages table ''' + sql = 'UPDATE unread_messages SET shown = 0' + self.simple_commit(sql) + + def get_unread_msgs(self): + ''' get all unread messages ''' + all_messages = [] + try: + self.cur.execute( + 'SELECT message_id, shown from unread_messages') + results = self.cur.fetchall() + except Exception: + pass + for message in results: + msg_id = message[0] + shown = message[1] + # here we get infos for that message, and related jid from jids table + # do NOT change order of SELECTed things, unless you change function(s) + # that called this function + self.cur.execute(''' + SELECT logs.log_line_id, logs.message, logs.time, logs.subject, + jids.jid + FROM logs, jids + WHERE logs.log_line_id = %d AND logs.jid_id = jids.jid_id + ''' % msg_id + ) + results = self.cur.fetchall() + if len(results) == 0: + # Log line is no more in logs table. remove it from unread_messages + self.set_read_messages([msg_id]) + continue + all_messages.append(results[0] + (shown,)) + return all_messages + + def write(self, kind, jid, message=None, show=None, tim=None, subject=None): + '''write a row (status, gcstatus, message etc) to logs database + kind can be status, gcstatus, gc_msg, (we only recv for those 3), + single_msg_recv, chat_msg_recv, chat_msg_sent, single_msg_sent + we cannot know if it is pm or normal chat message, we try to guess + see jid_is_from_pm() + + we analyze jid and store it as follows: + jids.jid text column will hold JID if TC-related, room_jid if GC-related, + ROOM_JID/nick if pm-related.''' + + if self.jids_already_in == []: # only happens if we just created the db + self.open_db() + + contact_name_col = None # holds nickname for kinds gcstatus, gc_msg + # message holds the message unless kind is status or gcstatus, + # then it holds status message + message_col = message + subject_col = subject + if tim: + time_col = int(float(time.mktime(tim))) + else: + time_col = int(float(time.time())) + + kind_col, show_col = self.convert_human_values_to_db_api_values(kind, + show) + + write_unread = False + + # now we may have need to do extra care for some values in columns + if kind == 'status': # we store (not None) time, jid, show, msg + # status for roster items + try: + jid_id = self.get_jid_id(jid) + except exceptions.PysqliteOperationalError, e: + raise exceptions.PysqliteOperationalError(str(e)) + if show is None: # show is None (xmpp), but we say that 'online' + show_col = constants.SHOW_ONLINE + + elif kind == 'gcstatus': + # status in ROOM (for pm status see status) + if show is None: # show is None (xmpp), but we say that 'online' + show_col = constants.SHOW_ONLINE + jid, nick = jid.split('/', 1) + try: + # re-get jid_id for the new jid + jid_id = self.get_jid_id(jid, 'ROOM') + except exceptions.PysqliteOperationalError, e: + raise exceptions.PysqliteOperationalError(str(e)) + contact_name_col = nick + + elif kind == 'gc_msg': + if jid.find('/') != -1: # if it has a / + jid, nick = jid.split('/', 1) + else: + # it's server message f.e. error message + # when user tries to ban someone but he's not allowed to + nick = None + try: + # re-get jid_id for the new jid + jid_id = self.get_jid_id(jid, 'ROOM') + except exceptions.PysqliteOperationalError, e: + raise exceptions.PysqliteOperationalError(str(e)) + contact_name_col = nick + else: + try: + jid_id = self.get_jid_id(jid) + except exceptions.PysqliteOperationalError, e: + raise exceptions.PysqliteOperationalError(str(e)) + if kind == 'chat_msg_recv': + if not self.jid_is_from_pm(jid): + # Save in unread table only if it's not a pm + write_unread = True + + if show_col == 'UNKNOWN': # unknown show, do not log + return + + values = (jid_id, contact_name_col, time_col, kind_col, show_col, + message_col, subject_col) + return self.commit_to_db(values, write_unread) + + def get_last_conversation_lines(self, jid, restore_how_many_rows, + pending_how_many, timeout, account): + '''accepts how many rows to restore and when to time them out (in minutes) + (mark them as too old) and number of messages that are in queue + and are already logged but pending to be viewed, + returns a list of tupples containg time, kind, message, + list with empty tupple if nothing found to meet our demands''' + try: + self.get_jid_id(jid) + except exceptions.PysqliteOperationalError, e: + # Error trying to create a new jid_id. This means there is no log + return [] + where_sql = self._build_contact_where(account, jid) + + now = int(float(time.time())) + timed_out = now - (timeout * 60) # before that they are too old + # so if we ask last 5 lines and we have 2 pending we get + # 3 - 8 (we avoid the last 2 lines but we still return 5 asked) + try: + self.cur.execute(''' + SELECT time, kind, message FROM logs + WHERE (%s) AND kind IN (%d, %d, %d, %d, %d) AND time > %d + ORDER BY time DESC LIMIT %d OFFSET %d + ''' % (where_sql, constants.KIND_SINGLE_MSG_RECV, + constants.KIND_CHAT_MSG_RECV, constants.KIND_SINGLE_MSG_SENT, + constants.KIND_CHAT_MSG_SENT, constants.KIND_ERROR, + timed_out, restore_how_many_rows, pending_how_many) + ) + + results = self.cur.fetchall() + except sqlite.DatabaseError: + raise exceptions.DatabaseMalformed + results.reverse() + return results + + def get_unix_time_from_date(self, year, month, day): + # year (fe 2005), month (fe 11), day (fe 25) + # returns time in seconds for the second that starts that date since epoch + # gimme unixtime from year month day: + d = datetime.date(year, month, day) + local_time = d.timetuple() # time tupple (compat with time.localtime()) + # we have time since epoch baby :) + start_of_day = int(time.mktime(local_time)) + return start_of_day + + def get_conversation_for_date(self, jid, year, month, day, account): + '''returns contact_name, time, kind, show, message, subject + for each row in a list of tupples, + returns list with empty tupple if we found nothing to meet our demands''' + try: + self.get_jid_id(jid) + except exceptions.PysqliteOperationalError, e: + # Error trying to create a new jid_id. This means there is no log + return [] + where_sql = self._build_contact_where(account, jid) + + start_of_day = self.get_unix_time_from_date(year, month, day) + seconds_in_a_day = 86400 # 60 * 60 * 24 + last_second_of_day = start_of_day + seconds_in_a_day - 1 + + self.cur.execute(''' + SELECT contact_name, time, kind, show, message, subject FROM logs + WHERE (%s) + AND time BETWEEN %d AND %d + ORDER BY time + ''' % (where_sql, start_of_day, last_second_of_day)) + + results = self.cur.fetchall() + return results + + def get_search_results_for_query(self, jid, query, account): + '''returns contact_name, time, kind, show, message + for each row in a list of tupples, + returns list with empty tupple if we found nothing to meet our demands''' + try: + self.get_jid_id(jid) + except exceptions.PysqliteOperationalError, e: + # Error trying to create a new jid_id. This means there is no log + return [] + + if False: # query.startswith('SELECT '): # it's SQL query (FIXME) + try: + self.cur.execute(query) + except sqlite.OperationalError, e: + results = [('', '', '', '', str(e))] + return results + + else: # user just typed something, we search in message column + where_sql = self._build_contact_where(account, jid) + like_sql = '%' + query.replace("'", "''") + '%' + self.cur.execute(''' + SELECT contact_name, time, kind, show, message, subject FROM logs + WHERE (%s) AND message LIKE '%s' + ORDER BY time + ''' % (where_sql, like_sql)) + + results = self.cur.fetchall() + return results + + def get_days_with_logs(self, jid, year, month, max_day, account): + '''returns the list of days that have logs (not status messages)''' + try: + self.get_jid_id(jid) + except exceptions.PysqliteOperationalError, e: + # Error trying to create a new jid_id. This means there is no log + return [] + days_with_logs = [] + where_sql = self._build_contact_where(account, jid) + + # First select all date of month whith logs we want + start_of_month = self.get_unix_time_from_date(year, month, 1) + seconds_in_a_day = 86400 # 60 * 60 * 24 + last_second_of_month = start_of_month + (seconds_in_a_day * max_day) - 1 + + # Select times and 'floor' them to time 0:00 + # (by dividing, they are integers) + # and take only one of the same values (distinct) + # Now we have timestamps of time 0:00 of every day with logs + self.cur.execute(''' + SELECT DISTINCT time/(86400)*86400 FROM logs + WHERE (%s) + AND time BETWEEN %d AND %d + AND kind NOT IN (%d, %d) + ORDER BY time + ''' % (where_sql, start_of_month, last_second_of_month, + constants.KIND_STATUS, constants.KIND_GCSTATUS)) + result = self.cur.fetchall() + + # convert timestamps to day of month + for line in result: + days_with_logs[0:0]=[time.gmtime(line[0])[2]] + + return days_with_logs + + def get_last_date_that_has_logs(self, jid, account=None, is_room=False): + '''returns last time (in seconds since EPOCH) for which + we had logs (excluding statuses)''' + where_sql = '' + if not is_room: + where_sql = self._build_contact_where(account, jid) + else: + try: + jid_id = self.get_jid_id(jid, 'ROOM') + except exceptions.PysqliteOperationalError, e: + # Error trying to create a new jid_id. This means there is no log + return None + where_sql = 'jid_id = %s' % jid_id + self.cur.execute(''' + SELECT MAX(time) FROM logs + WHERE (%s) + AND kind NOT IN (%d, %d) + ''' % (where_sql, constants.KIND_STATUS, constants.KIND_GCSTATUS)) + + results = self.cur.fetchone() + if results is not None: + result = results[0] + else: + result = None + return result + + def get_room_last_message_time(self, jid): + '''returns FASTLY last time (in seconds since EPOCH) for which + we had logs for that room from rooms_last_message_time table''' + try: + jid_id = self.get_jid_id(jid, 'ROOM') + except exceptions.PysqliteOperationalError, e: + # Error trying to create a new jid_id. This means there is no log + return None + where_sql = 'jid_id = %s' % jid_id + self.cur.execute(''' + SELECT time FROM rooms_last_message_time + WHERE (%s) + ''' % (where_sql)) + + results = self.cur.fetchone() + if results is not None: + result = results[0] + else: + result = None + return result + + def set_room_last_message_time(self, jid, time): + '''set last time (in seconds since EPOCH) for which + we had logs for that room in rooms_last_message_time table''' + jid_id = self.get_jid_id(jid, 'ROOM') + # jid_id is unique in this table, create or update : + sql = 'REPLACE INTO rooms_last_message_time VALUES (%d, %d)' % \ + (jid_id, time) + self.simple_commit(sql) + + def _build_contact_where(self, account, jid): + '''build the where clause for a jid, including metacontacts + jid(s) if any''' + where_sql = '' + # will return empty list if jid is not associated with + # any metacontacts + family = gajim.contacts.get_metacontacts_family(account, jid) + if family: + for user in family: + try: + jid_id = self.get_jid_id(user['jid']) + except exceptions.PysqliteOperationalError, e: + continue + where_sql += 'jid_id = %s' % jid_id + if user != family[-1]: + where_sql += ' OR ' + else: # if jid was not associated with metacontacts + jid_id = self.get_jid_id(jid) + where_sql = 'jid_id = %s' % jid_id + return where_sql + + def save_transport_type(self, jid, type_): + '''save the type of the transport in DB''' + type_id = self.convert_human_transport_type_to_db_api_values(type_) + if not type_id: + # unknown type + return + self.cur.execute( + 'SELECT type from transports_cache WHERE transport = "%s"' % jid) + results = self.cur.fetchall() + if results: + result = results[0][0] + if result == type_id: + return + sql = 'UPDATE transports_cache SET type = %d WHERE transport = "%s"' %\ + (type_id, jid) + self.simple_commit(sql) + return + sql = 'INSERT INTO transports_cache VALUES ("%s", %d)' % (jid, type_id) + self.simple_commit(sql) + + def get_transports_type(self): + '''return all the type of the transports in DB''' + self.cur.execute( + 'SELECT * from transports_cache') + results = self.cur.fetchall() + if not results: + return {} + answer = {} + for result in results: + answer[result[0]] = self.convert_api_values_to_human_transport_type( + result[1]) + return answer + + # A longer note here: + # The database contains a blob field. Pysqlite seems to need special care for + # such fields. + # When storing, we need to convert string into buffer object (1). + # When retrieving, we need to convert it back to a string to decompress it. + # (2) + # GzipFile needs a file-like object, StringIO emulates file for plain strings + def iter_caps_data(self): + ''' Iterate over caps cache data stored in the database. + The iterator values are pairs of (node, ver, ext, identities, features): + identities == {'category':'foo', 'type':'bar', 'name':'boo'}, + features being a list of feature namespaces. ''' + + # get data from table + # the data field contains binary object (gzipped data), this is a hack + # to get that data without trying to convert it to unicode + try: + self.cur.execute('SELECT hash_method, hash, data FROM caps_cache;') + except sqlite.OperationalError: + # might happen when there's no caps_cache table yet + # -- there's no data to read anyway then + return + + # list of corrupted entries that will be removed + to_be_removed = [] + for hash_method, hash_, data in self.cur: + # for each row: unpack the data field + # (format: (category, type, name, category, type, name, ... + # ..., 'FEAT', feature1, feature2, ...).join(' ')) + # NOTE: if there's a need to do more gzip, put that to a function + try: + data = GzipFile(fileobj=StringIO(str(data))).read().decode( + 'utf-8').split('\0') + except IOError: + # This data is corrupted. It probably contains non-ascii chars + to_be_removed.append((hash_method, hash_)) + continue + i = 0 + identities = list() + features = list() + while i < (len(data) - 3) and data[i] != 'FEAT': + category = data[i] + type_ = data[i + 1] + lang = data[i + 2] + name = data[i + 3] + identities.append({'category': category, 'type': type_, + 'xml:lang': lang, 'name': name}) + i += 4 + i+=1 + while i < len(data): + features.append(data[i]) + i += 1 + + # yield the row + yield hash_method, hash_, identities, features + for hash_method, hash_ in to_be_removed: + sql = '''DELETE FROM caps_cache WHERE hash_method = "%s" AND + hash = "%s"''' % (hash_method, hash_) + self.simple_commit(sql) + + def add_caps_entry(self, hash_method, hash_, identities, features): + data = [] + for identity in identities: + # there is no FEAT category + if identity['category'] == 'FEAT': + return + data.extend((identity.get('category'), identity.get('type', ''), + identity.get('xml:lang', ''), identity.get('name', ''))) + data.append('FEAT') + data.extend(features) + data = '\0'.join(data) + # if there's a need to do more gzip, put that to a function + string = StringIO() + gzip = GzipFile(fileobj=string, mode='w') + data = data.encode('utf-8') # the gzip module can't handle unicode objects + gzip.write(data) + gzip.close() + data = string.getvalue() + self.cur.execute(''' + INSERT INTO caps_cache ( hash_method, hash, data, last_seen ) + VALUES (?, ?, ?, ?); + ''', (hash_method, hash_, buffer(data), int(time.time()))) + # (1) -- note above + try: + self.con.commit() + except sqlite.OperationalError, e: + print >> sys.stderr, str(e) + + def update_caps_time(self, method, hash_): + sql = '''UPDATE caps_cache SET last_seen = %d + WHERE hash_method = "%s" and hash = "%s"''' % \ + (int(time.time()), method, hash_) + self.simple_commit(sql) + + def clean_caps_table(self): + '''Remove caps which was not seen for 3 months''' + sql = '''DELETE FROM caps_cache WHERE last_seen < %d''' % \ + int(time.time() - 3*30*24*3600) + self.simple_commit(sql) + + def replace_roster(self, account_name, roster_version, roster): + ''' Replace current roster in DB by a new one. + accout_name is the name of the account to change + roster_version is the version of the new roster + roster is the new version ''' + # First we must reset roster_version value to ensure that the server + # sends back all the roster at the next connexion if the replacement + # didn't work properly. + gajim.config.set_per('accounts', account_name, 'roster_version', '') + + account_jid = gajim.get_jid_from_account(account_name) + account_jid_id = self.get_jid_id(account_jid) + + # Delete old roster + self.remove_roster(account_jid) + + # Fill roster tables with the new roster + for jid in roster: + self.add_or_update_contact(account_jid, jid, roster[jid]['name'], + roster[jid]['subscription'], roster[jid]['ask'], + roster[jid]['groups']) + + # At this point, we are sure the replacement works properly so we can + # set the new roster_version value. + gajim.config.set_per('accounts', account_name, 'roster_version', + roster_version) + + def del_contact(self, account_jid, jid): + ''' Remove jid from account_jid roster. ''' + try: + account_jid_id = self.get_jid_id(account_jid) + jid_id = self.get_jid_id(jid) + except exceptions.PysqliteOperationalError, e: + raise exceptions.PysqliteOperationalError(str(e)) + self.cur.execute( + 'DELETE FROM roster_group WHERE account_jid_id=? AND jid_id=?', + (account_jid_id, jid_id)) + self.cur.execute( + 'DELETE FROM roster_entry WHERE account_jid_id=? AND jid_id=?', + (account_jid_id, jid_id)) + self.con.commit() + + def add_or_update_contact(self, account_jid, jid, name, sub, ask, groups): + ''' Add or update a contact from account_jid roster. ''' + if sub == 'remove': + self.del_contact(account_jid, jid) + return + + try: + account_jid_id = self.get_jid_id(account_jid) + jid_id = self.get_jid_id(jid) + except exceptions.PysqliteOperationalError, e: + raise exceptions.PysqliteOperationalError(str(e)) + + # Update groups information + # First we delete all previous groups information + self.cur.execute( + 'DELETE FROM roster_group WHERE account_jid_id=? AND jid_id=?', + (account_jid_id, jid_id)) + # Then we add all new groups information + for group in groups: + self.cur.execute('INSERT INTO roster_group VALUES(?, ?, ?)', + (account_jid_id, jid_id, group)) + + if name is None: + name = '' + + self.cur.execute('REPLACE INTO roster_entry VALUES(?, ?, ?, ?, ?)', + (account_jid_id, jid_id, name, + self.convert_human_subscription_values_to_db_api_values(sub), + bool(ask))) + self.con.commit() + + def get_roster(self, account_jid): + ''' Return the accound_jid roster in NonBlockingRoster format. ''' + data = {} + account_jid_id = self.get_jid_id(account_jid) + + # First we fill data with roster_entry informations + self.cur.execute(''' + SELECT j.jid, re.jid_id, re.name, re.subscription, re.ask + FROM roster_entry re, jids j + WHERE re.account_jid_id=? AND j.jid_id=re.jid_id''', (account_jid_id,)) + for jid, jid_id, name, subscription, ask in self.cur: + data[jid] = {} + if name: + data[jid]['name'] = name + else: + data[jid]['name'] = None + data[jid]['subscription'] = \ + self.convert_db_api_values_to_human_subscription_values( + subscription) + data[jid]['groups'] = [] + data[jid]['resources'] = {} + if ask: + data[jid]['ask'] = 'subscribe' + else: + data[jid]['ask'] = None + data[jid]['id'] = jid_id + + # Then we add group for roster entries + for jid in data: + self.cur.execute(''' + SELECT group_name FROM roster_group + WHERE account_jid_id=? AND jid_id=?''', + (account_jid_id, data[jid]['id'])) + for (group_name,) in self.cur: + data[jid]['groups'].append(group_name) + del data[jid]['id'] + + return data + + def remove_roster(self, account_jid): + ''' Remove all entry from account_jid roster. ''' + account_jid_id = self.get_jid_id(account_jid) + + self.cur.execute('DELETE FROM roster_entry WHERE account_jid_id=?', + (account_jid_id,)) + self.cur.execute('DELETE FROM roster_group WHERE account_jid_id=?', + (account_jid_id,)) + self.con.commit() + + def save_if_not_exists(self, with_, direction, tim, msg='', nick=None): + if tim: + time_col = int(float(time.mktime(tim))) + else: + time_col = int(float(time.time())) + if msg: + if self.jid_is_room_jid(with_) or nick: + # It's a groupchat message + if nick: + # It's a message from a groupchat occupent + type_ = 'gc_msg' + with_ = with_ + '/' + nick + else: + # It's a server message message, we don't log them + return + else: + if direction == 'from': + type_ = 'chat_msg_recv' + elif direction == 'to': + type_ = 'chat_msg_sent' + jid_id = self.get_jid_id(with_) + where_sql = 'jid_id = %s AND message=?' % (jid_id) + if type_ == 'gc_msg': + # We cannot differentiate gc message and pm messages, so look in both + # logs + with_2 = gajim.get_jid_without_resource(with_) + if with_ != with_2: + jid_id2 = self.get_jid_id(with_2) + where_sql = 'jid_id in (%s, %s) AND message=?' % (jid_id, jid_id2) + start_time = time_col - 300 # 5 minutes arrount given time + end_time = time_col + 300 # 5 minutes arrount given time + self.cur.execute(''' + SELECT log_line_id FROM logs + WHERE (%s) + AND time BETWEEN %d AND %d + ORDER BY time + ''' % (where_sql, start_time, end_time), (msg,)) + results = self.cur.fetchall() + if results: + log.debug('Log already in DB, ignoring it') + return + log.debug('New log received from server archives, storing it') + self.write(type_, with_, message=msg, tim=tim) diff --git a/src/common/logging_helpers.py b/src/common/logging_helpers.py index ae5d1da3e..73e6c9d02 100644 --- a/src/common/logging_helpers.py +++ b/src/common/logging_helpers.py @@ -22,163 +22,161 @@ import logging import i18n def parseLogLevel(arg): - """ - eiter numeric value or level name from logging module - """ - if arg.isdigit(): - return int(arg) - elif arg.isupper(): - return getattr(logging, arg) - else: - raise ValueError(_('%s is not a valid loglevel'), repr(arg)) + """ + eiter numeric value or level name from logging module + """ + if arg.isdigit(): + return int(arg) + elif arg.isupper(): + return getattr(logging, arg) + else: + raise ValueError(_('%s is not a valid loglevel'), repr(arg)) def parseLogTarget(arg): - """ - [gajim.]c.x.y -> gajim.c.x.y - .other_logger -> other_logger - -> gajim - """ - arg = arg.lower() - if not arg: - return 'gajim' - elif arg.startswith('.'): - return arg[1:] - elif arg.startswith('gajim'): - return arg - else: - return 'gajim.' + arg + """ + [gajim.]c.x.y -> gajim.c.x.y + .other_logger -> other_logger + -> gajim + """ + arg = arg.lower() + if not arg: + return 'gajim' + elif arg.startswith('.'): + return arg[1:] + elif arg.startswith('gajim'): + return arg + else: + return 'gajim.' + arg def parseAndSetLogLevels(arg): - """ - [=]LOGLEVEL -> gajim=LOGLEVEL - gajim=LOGLEVEL -> gajim=LOGLEVEL - .other=10 -> other=10 - .=10 -> - c.x.y=c.z=20 -> gajim.c.x.y=20 - gajim.c.z=20 - gajim=10,c.x=20 -> gajim=10 - gajim.c.x=20 - """ - for directive in arg.split(','): - directive = directive.strip() - if not directive: - continue - if '=' not in directive: - directive = '=' + directive - targets, level = directive.rsplit('=', 1) - level = parseLogLevel(level.strip()) - for target in targets.split('='): - target = parseLogTarget(target.strip()) - if target: - logging.getLogger(target).setLevel(level) - print "Logger %s level set to %d" % (target, level) + """ + [=]LOGLEVEL -> gajim=LOGLEVEL + gajim=LOGLEVEL -> gajim=LOGLEVEL + .other=10 -> other=10 + .=10 -> + c.x.y=c.z=20 -> gajim.c.x.y=20 + gajim.c.z=20 + gajim=10,c.x=20 -> gajim=10 + gajim.c.x=20 + """ + for directive in arg.split(','): + directive = directive.strip() + if not directive: + continue + if '=' not in directive: + directive = '=' + directive + targets, level = directive.rsplit('=', 1) + level = parseLogLevel(level.strip()) + for target in targets.split('='): + target = parseLogTarget(target.strip()) + if target: + logging.getLogger(target).setLevel(level) + print "Logger %s level set to %d" % (target, level) class colors: - NONE = chr(27) + "[0m" - BLACk = chr(27) + "[30m" - RED = chr(27) + "[31m" - GREEN = chr(27) + "[32m" - BROWN = chr(27) + "[33m" - BLUE = chr(27) + "[34m" - MAGENTA = chr(27) + "[35m" - CYAN = chr(27) + "[36m" - LIGHT_GRAY = chr(27) + "[37m" - DARK_GRAY = chr(27) + "[30;1m" - BRIGHT_RED = chr(27) + "[31;1m" - BRIGHT_GREEN = chr(27) + "[32;1m" - YELLOW = chr(27) + "[33;1m" - BRIGHT_BLUE = chr(27) + "[34;1m" - PURPLE = chr(27) + "[35;1m" - BRIGHT_CYAN = chr(27) + "[36;1m" - WHITE = chr(27) + "[37;1m" + NONE = chr(27) + "[0m" + BLACk = chr(27) + "[30m" + RED = chr(27) + "[31m" + GREEN = chr(27) + "[32m" + BROWN = chr(27) + "[33m" + BLUE = chr(27) + "[34m" + MAGENTA = chr(27) + "[35m" + CYAN = chr(27) + "[36m" + LIGHT_GRAY = chr(27) + "[37m" + DARK_GRAY = chr(27) + "[30;1m" + BRIGHT_RED = chr(27) + "[31;1m" + BRIGHT_GREEN = chr(27) + "[32;1m" + YELLOW = chr(27) + "[33;1m" + BRIGHT_BLUE = chr(27) + "[34;1m" + PURPLE = chr(27) + "[35;1m" + BRIGHT_CYAN = chr(27) + "[36;1m" + WHITE = chr(27) + "[37;1m" def colorize(text, color): - return color + text + colors.NONE + return color + text + colors.NONE class FancyFormatter(logging.Formatter): - """ - A Eye-candy formatter with colors - """ - colors_mapping = { - 'DEBUG': colors.BLUE, - 'INFO' : colors.GREEN, - 'WARNING': colors.BROWN, - 'ERROR': colors.RED, - 'CRITICAL': colors.BRIGHT_RED, - } + """ + A Eye-candy formatter with colors + """ + colors_mapping = { + 'DEBUG': colors.BLUE, + 'INFO': colors.GREEN, + 'WARNING': colors.BROWN, + 'ERROR': colors.RED, + 'CRITICAL': colors.BRIGHT_RED, + } - def __init__(self, fmt, datefmt=None, use_color=False): - logging.Formatter.__init__(self, fmt, datefmt) - self.use_color = use_color + def __init__(self, fmt, datefmt=None, use_color=False): + logging.Formatter.__init__(self, fmt, datefmt) + self.use_color = use_color - def formatTime(self, record, datefmt=None): - f = logging.Formatter.formatTime(self, record, datefmt) - if self.use_color: - f = colorize(f, colors.DARK_GRAY) - return f + def formatTime(self, record, datefmt=None): + f = logging.Formatter.formatTime(self, record, datefmt) + if self.use_color: + f = colorize(f, colors.DARK_GRAY) + return f - def format(self, record): - level = record.levelname - record.levelname = '(%s)' % level[0] + def format(self, record): + level = record.levelname + record.levelname = '(%s)' % level[0] - if self.use_color: - c = FancyFormatter.colors_mapping.get(level, '') - record.levelname = colorize(record.levelname, c) - record.name = colorize(record.name, colors.CYAN) - else: - record.name += ':' + if self.use_color: + c = FancyFormatter.colors_mapping.get(level, '') + record.levelname = colorize(record.levelname, c) + record.name = colorize(record.name, colors.CYAN) + else: + record.name += ':' - return logging.Formatter.format(self, record) + return logging.Formatter.format(self, record) def init(use_color=False): - """ - initialize the logging system - """ - consoleloghandler = logging.StreamHandler() - consoleloghandler.setFormatter( - FancyFormatter( - '%(asctime)s %(levelname)s %(name)s %(message)s', - '%H:%M:%S', - use_color - ) - ) + """ + initialize the logging system + """ + consoleloghandler = logging.StreamHandler() + consoleloghandler.setFormatter( + FancyFormatter( + '%(asctime)s %(levelname)s %(name)s %(message)s', + '%H:%M:%S', + use_color + ) + ) - # fake the root logger so we have 'gajim' root name instead of 'root' - root_log = logging.getLogger('gajim') - root_log.setLevel(logging.WARNING) - root_log.addHandler(consoleloghandler) - root_log.propagate = False + # fake the root logger so we have 'gajim' root name instead of 'root' + root_log = logging.getLogger('gajim') + root_log.setLevel(logging.WARNING) + root_log.addHandler(consoleloghandler) + root_log.propagate = False def set_loglevels(loglevels_string): - parseAndSetLogLevels(loglevels_string) + parseAndSetLogLevels(loglevels_string) def set_verbose(): - parseAndSetLogLevels('gajim=1') + parseAndSetLogLevels('gajim=1') def set_quiet(): - parseAndSetLogLevels('gajim=CRITICAL') + parseAndSetLogLevels('gajim=CRITICAL') # tests if __name__ == '__main__': - init(use_color=True) + init(use_color=True) - set_loglevels('gajim.c=DEBUG,INFO') + set_loglevels('gajim.c=DEBUG,INFO') - log = logging.getLogger('gajim') - log.debug('debug') - log.info('info') - log.warn('warn') - log.error('error') - log.critical('critical') + log = logging.getLogger('gajim') + log.debug('debug') + log.info('info') + log.warn('warn') + log.error('error') + log.critical('critical') - log = logging.getLogger('gajim.c.x.dispatcher') - log.debug('debug') - log.info('info') - log.warn('warn') - log.error('error') - log.critical('critical') - -# vim: se ts=3: + log = logging.getLogger('gajim.c.x.dispatcher') + log.debug('debug') + log.info('info') + log.warn('warn') + log.error('error') + log.critical('critical') diff --git a/src/common/message_archiving.py b/src/common/message_archiving.py index b7318b500..56b60aa68 100644 --- a/src/common/message_archiving.py +++ b/src/common/message_archiving.py @@ -28,222 +28,222 @@ ARCHIVING_COLLECTION_ARRIVED = 'archiving_collection_arrived' ARCHIVING_MODIFICATIONS_ARRIVED = 'archiving_modifications_arrived' class ConnectionArchive: - def __init__(self): - self.archive_auto_supported = False - self.archive_manage_supported = False - self.archive_manual_supported = False - self.archive_pref_supported = False - self.auto = None - self.method_auto = None - self.method_local = None - self.method_manual = None - self.default = None - self.items = {} + def __init__(self): + self.archive_auto_supported = False + self.archive_manage_supported = False + self.archive_manual_supported = False + self.archive_pref_supported = False + self.auto = None + self.method_auto = None + self.method_local = None + self.method_manual = None + self.default = None + self.items = {} - def request_message_archiving_preferences(self): - iq_ = common.xmpp.Iq('get') - iq_.setTag('pref', namespace=common.xmpp.NS_ARCHIVE) - self.connection.send(iq_) + def request_message_archiving_preferences(self): + iq_ = common.xmpp.Iq('get') + iq_.setTag('pref', namespace=common.xmpp.NS_ARCHIVE) + self.connection.send(iq_) - def set_pref(self, name, **data): - ''' - data contains names and values of pref name attributes. - ''' - iq_ = common.xmpp.Iq('set') - pref = iq_.setTag('pref', namespace=common.xmpp.NS_ARCHIVE) - tag = pref.setTag(name) - for key, value in data.items(): - if value is not None: - tag.setAttr(key, value) - self.connection.send(iq_) + def set_pref(self, name, **data): + ''' + data contains names and values of pref name attributes. + ''' + iq_ = common.xmpp.Iq('set') + pref = iq_.setTag('pref', namespace=common.xmpp.NS_ARCHIVE) + tag = pref.setTag(name) + for key, value in data.items(): + if value is not None: + tag.setAttr(key, value) + self.connection.send(iq_) - def set_auto(self, save): - self.set_pref('auto', save=save) + def set_auto(self, save): + self.set_pref('auto', save=save) - def set_method(self, type, use): - self.set_pref('method', type=type, use=use) + def set_method(self, type, use): + self.set_pref('method', type=type, use=use) - def set_default(self, otr, save, expire=None): - self.set_pref('default', otr=otr, save=save, expire=expire) + def set_default(self, otr, save, expire=None): + self.set_pref('default', otr=otr, save=save, expire=expire) - def append_or_update_item(self, jid, otr, save, expire): - self.set_pref('item', jid=jid, otr=otr, save=save) - - def remove_item(self, jid): - iq_ = common.xmpp.Iq('set') - itemremove = iq_.setTag('itemremove', namespace=common.xmpp.NS_ARCHIVE) - item = itemremove.setTag('item') - item.setAttr('jid', jid) - self.connection.send(iq_) + def append_or_update_item(self, jid, otr, save, expire): + self.set_pref('item', jid=jid, otr=otr, save=save) - def get_item_pref(self, jid): - jid = common.xmpp.JID(jid) - if unicode(jid) in self.items: - return self.items[jid] + def remove_item(self, jid): + iq_ = common.xmpp.Iq('set') + itemremove = iq_.setTag('itemremove', namespace=common.xmpp.NS_ARCHIVE) + item = itemremove.setTag('item') + item.setAttr('jid', jid) + self.connection.send(iq_) - if jid.getStripped() in self.items: - return self.items[jid.getStripped()] + def get_item_pref(self, jid): + jid = common.xmpp.JID(jid) + if unicode(jid) in self.items: + return self.items[jid] - if jid.getDomain() in self.items: - return self.items[jid.getDomain()] + if jid.getStripped() in self.items: + return self.items[jid.getStripped()] - return self.default + if jid.getDomain() in self.items: + return self.items[jid.getDomain()] - def logging_preference(self, jid, initiator_options=None): - otr = self.get_item_pref(jid)['otr'] - if initiator_options: - if ((initiator_options == ['mustnot'] and otr == 'forbid') or - (initiator_options == ['may'] and otr == 'require')): - return None + return self.default - if (initiator_options == ['mustnot'] or - (initiator_options[0] == 'mustnot' and - otr not in ('opppose', 'forbid')) or - (initiator_options == ['may', 'mustnot'] and - otr in ('require', 'prefer'))): - return 'mustnot' + def logging_preference(self, jid, initiator_options=None): + otr = self.get_item_pref(jid)['otr'] + if initiator_options: + if ((initiator_options == ['mustnot'] and otr == 'forbid') or + (initiator_options == ['may'] and otr == 'require')): + return None - return 'may' + if (initiator_options == ['mustnot'] or + (initiator_options[0] == 'mustnot' and + otr not in ('opppose', 'forbid')) or + (initiator_options == ['may', 'mustnot'] and + otr in ('require', 'prefer'))): + return 'mustnot' - if otr == 'require': - return ['mustnot'] + return 'may' - if otr in ('prefer', 'approve'): - return ['mustnot', 'may'] + if otr == 'require': + return ['mustnot'] - if otr in ('concede', 'oppose'): - return ['may', 'mustnot'] + if otr in ('prefer', 'approve'): + return ['mustnot', 'may'] - # otr == 'forbid' - return ['may'] + if otr in ('concede', 'oppose'): + return ['may', 'mustnot'] - def _ArchiveCB(self, con, iq_obj): - log.debug('_ArchiveCB %s' % iq_obj.getType()) - if iq_obj.getType() == 'error': - self.dispatch('ARCHIVING_ERROR', iq_obj.getErrorMsg()) - return - elif iq_obj.getType() not in ('result', 'set'): - return - - if iq_obj.getTag('pref'): - pref = iq_obj.getTag('pref') + # otr == 'forbid' + return ['may'] - if pref.getTag('auto'): - self.auto = pref.getTagAttr('auto', 'save') - log.debug('archiving preference: auto: %s' % self.auto) - self.dispatch('ARCHIVING_CHANGED', ('auto', - self.auto)) + def _ArchiveCB(self, con, iq_obj): + log.debug('_ArchiveCB %s' % iq_obj.getType()) + if iq_obj.getType() == 'error': + self.dispatch('ARCHIVING_ERROR', iq_obj.getErrorMsg()) + return + elif iq_obj.getType() not in ('result', 'set'): + return - method_auto = pref.getTag('method', attrs={'type': 'auto'}) - if method_auto: - self.method_auto = method_auto.getAttr('use') - self.dispatch('ARCHIVING_CHANGED', ('method_auto', - self.method_auto)) + if iq_obj.getTag('pref'): + pref = iq_obj.getTag('pref') - method_local = pref.getTag('method', attrs={'type': 'local'}) - if method_local: - self.method_local = method_local.getAttr('use') - self.dispatch('ARCHIVING_CHANGED', ('method_local', - self.method_local)) + if pref.getTag('auto'): + self.auto = pref.getTagAttr('auto', 'save') + log.debug('archiving preference: auto: %s' % self.auto) + self.dispatch('ARCHIVING_CHANGED', ('auto', + self.auto)) - method_manual = pref.getTag('method', attrs={'type': 'manual'}) - if method_manual: - self.method_manual = method_manual.getAttr('use') - self.dispatch('ARCHIVING_CHANGED', ('method_manual', - self.method_manual)) + method_auto = pref.getTag('method', attrs={'type': 'auto'}) + if method_auto: + self.method_auto = method_auto.getAttr('use') + self.dispatch('ARCHIVING_CHANGED', ('method_auto', + self.method_auto)) - log.debug('archiving preferences: method auto: %s, local: %s, ' - 'manual: %s' % (self.method_auto, self.method_local, - self.method_manual)) + method_local = pref.getTag('method', attrs={'type': 'local'}) + if method_local: + self.method_local = method_local.getAttr('use') + self.dispatch('ARCHIVING_CHANGED', ('method_local', + self.method_local)) - if pref.getTag('default'): - default = pref.getTag('default') - log.debug('archiving preferences: default otr: %s, save: %s, ' - 'expire: %s, unset: %s' % (default.getAttr('otr'), - default.getAttr('save'), default.getAttr('expire'), - default.getAttr('unset'))) - self.default = { - 'expire': default.getAttr('expire'), - 'otr': default.getAttr('otr'), - 'save': default.getAttr('save'), - 'unset': default.getAttr('unset')} - self.dispatch('ARCHIVING_CHANGED', ('default', - self.default)) - for item in pref.getTags('item'): - log.debug('archiving preferences for jid %s: otr: %s, save: %s, ' - 'expire: %s' % (item.getAttr('jid'), item.getAttr('otr'), - item.getAttr('save'), item.getAttr('expire'))) - self.items[item.getAttr('jid')] = { - 'expire': item.getAttr('expire'), - 'otr': item.getAttr('otr'), 'save': item.getAttr('save')} - self.dispatch('ARCHIVING_CHANGED', ('item', - item.getAttr('jid'), self.items[item.getAttr('jid')])) - elif iq_obj.getTag('itemremove'): - for item in pref.getTags('item'): - del self.items[item.getAttr('jid')] - self.dispatch('ARCHIVING_CHANGED', ('itemremove', - item.getAttr('jid'))) + method_manual = pref.getTag('method', attrs={'type': 'manual'}) + if method_manual: + self.method_manual = method_manual.getAttr('use') + self.dispatch('ARCHIVING_CHANGED', ('method_manual', + self.method_manual)) - def request_collections_list_page(self, with_='', start=None, end=None, - after=None, max=30, exact_match=False): - iq_ = common.xmpp.Iq('get') - list_ = iq_.setTag('list', namespace=common.xmpp.NS_ARCHIVE) - if with_: - list_.setAttr('with', with_) - if exact_match: - list_.setAttr('exactmatch', 'true') - if start: - list_.setAttr('start', start) - if end: - list_.setAttr('end', end) - set_ = list_.setTag('set', namespace=common.xmpp.NS_RSM) - set_.setTagData('max', max) - if after: - set_.setTagData('after', after) - id_ = self.connection.getAnID() - iq_.setID(id_) - self.awaiting_answers[id_] = (ARCHIVING_COLLECTIONS_ARRIVED, ) - self.connection.send(iq_) + log.debug('archiving preferences: method auto: %s, local: %s, ' + 'manual: %s' % (self.method_auto, self.method_local, + self.method_manual)) - def request_collection_page(self, with_, start, end=None, after=None, - max=30, exact_match=False): - iq_ = common.xmpp.Iq('get') - retrieve = iq_.setTag('retrieve', namespace=common.xmpp.NS_ARCHIVE, - attrs={'with': with_, 'start': start}) - if exact_match: - retrieve.setAttr('exactmatch', 'true') - set_ = retrieve.setTag('set', namespace=common.xmpp.NS_RSM) - set_.setTagData('max', max) - if after: - set_.setTagData('after', after) - id_ = self.connection.getAnID() - iq_.setID(id_) - self.awaiting_answers[id_] = (ARCHIVING_COLLECTION_ARRIVED, ) - self.connection.send(iq_) - - def remove_collection(self, with_='', start=None, end=None, - exact_match=False, open=False): - iq_ = common.xmpp.Iq('set') - remove = iq_.setTag('remove', namespace=common.xmpp.NS_ARCHIVE) - if with_: - remove.setAttr('with', with_) - if exact_match: - remove.setAttr('exactmatch', 'true') - if start: - remove.setAttr('start', start) - if end: - remove.setAttr('end', end) - if open: - remove.setAttr('open', 'true') - self.connection.send(iq_) - - def request_modifications_page(self, start, max=30): - iq_ = common.xmpp.Iq('get') - moified = iq_.setTag('modified', namespace=common.xmpp.NS_ARCHIVE, - attrs={'start': start}) - set_ = moified.setTag('set', namespace=common.xmpp.NS_RSM) - set_.setTagData('max', max) - id_ = self.connection.getAnID() - iq_.setID(id_) - self.awaiting_answers[id_] = (ARCHIVING_MODIFICATIONS_ARRIVED, ) - self.connection.send(iq_) + if pref.getTag('default'): + default = pref.getTag('default') + log.debug('archiving preferences: default otr: %s, save: %s, ' + 'expire: %s, unset: %s' % (default.getAttr('otr'), + default.getAttr('save'), default.getAttr('expire'), + default.getAttr('unset'))) + self.default = { + 'expire': default.getAttr('expire'), + 'otr': default.getAttr('otr'), + 'save': default.getAttr('save'), + 'unset': default.getAttr('unset')} + self.dispatch('ARCHIVING_CHANGED', ('default', + self.default)) + for item in pref.getTags('item'): + log.debug('archiving preferences for jid %s: otr: %s, save: %s, ' + 'expire: %s' % (item.getAttr('jid'), item.getAttr('otr'), + item.getAttr('save'), item.getAttr('expire'))) + self.items[item.getAttr('jid')] = { + 'expire': item.getAttr('expire'), + 'otr': item.getAttr('otr'), 'save': item.getAttr('save')} + self.dispatch('ARCHIVING_CHANGED', ('item', + item.getAttr('jid'), self.items[item.getAttr('jid')])) + elif iq_obj.getTag('itemremove'): + for item in pref.getTags('item'): + del self.items[item.getAttr('jid')] + self.dispatch('ARCHIVING_CHANGED', ('itemremove', + item.getAttr('jid'))) + + def request_collections_list_page(self, with_='', start=None, end=None, + after=None, max=30, exact_match=False): + iq_ = common.xmpp.Iq('get') + list_ = iq_.setTag('list', namespace=common.xmpp.NS_ARCHIVE) + if with_: + list_.setAttr('with', with_) + if exact_match: + list_.setAttr('exactmatch', 'true') + if start: + list_.setAttr('start', start) + if end: + list_.setAttr('end', end) + set_ = list_.setTag('set', namespace=common.xmpp.NS_RSM) + set_.setTagData('max', max) + if after: + set_.setTagData('after', after) + id_ = self.connection.getAnID() + iq_.setID(id_) + self.awaiting_answers[id_] = (ARCHIVING_COLLECTIONS_ARRIVED, ) + self.connection.send(iq_) + + def request_collection_page(self, with_, start, end=None, after=None, + max=30, exact_match=False): + iq_ = common.xmpp.Iq('get') + retrieve = iq_.setTag('retrieve', namespace=common.xmpp.NS_ARCHIVE, + attrs={'with': with_, 'start': start}) + if exact_match: + retrieve.setAttr('exactmatch', 'true') + set_ = retrieve.setTag('set', namespace=common.xmpp.NS_RSM) + set_.setTagData('max', max) + if after: + set_.setTagData('after', after) + id_ = self.connection.getAnID() + iq_.setID(id_) + self.awaiting_answers[id_] = (ARCHIVING_COLLECTION_ARRIVED, ) + self.connection.send(iq_) + + def remove_collection(self, with_='', start=None, end=None, + exact_match=False, open=False): + iq_ = common.xmpp.Iq('set') + remove = iq_.setTag('remove', namespace=common.xmpp.NS_ARCHIVE) + if with_: + remove.setAttr('with', with_) + if exact_match: + remove.setAttr('exactmatch', 'true') + if start: + remove.setAttr('start', start) + if end: + remove.setAttr('end', end) + if open: + remove.setAttr('open', 'true') + self.connection.send(iq_) + + def request_modifications_page(self, start, max=30): + iq_ = common.xmpp.Iq('get') + moified = iq_.setTag('modified', namespace=common.xmpp.NS_ARCHIVE, + attrs={'start': start}) + set_ = moified.setTag('set', namespace=common.xmpp.NS_RSM) + set_.setTagData('max', max) + id_ = self.connection.getAnID() + iq_.setID(id_) + self.awaiting_answers[id_] = (ARCHIVING_MODIFICATIONS_ARRIVED, ) + self.connection.send(iq_) diff --git a/src/common/optparser.py b/src/common/optparser.py index f9f738d5e..4ea797a55 100644 --- a/src/common/optparser.py +++ b/src/common/optparser.py @@ -36,807 +36,805 @@ from common import caps import exceptions try: - import sqlite3 as sqlite # python 2.5 + import sqlite3 as sqlite # python 2.5 except ImportError: - try: - from pysqlite2 import dbapi2 as sqlite - except ImportError: - raise exceptions.PysqliteNotAvailable + try: + from pysqlite2 import dbapi2 as sqlite + except ImportError: + raise exceptions.PysqliteNotAvailable import logger class OptionsParser: - def __init__(self, filename): - self.__filename = filename - self.old_values = {} # values that are saved in the file and maybe - # no longer valid + def __init__(self, filename): + self.__filename = filename + self.old_values = {} # values that are saved in the file and maybe + # no longer valid - def read(self): - try: - fd = open(self.__filename) - except Exception: - if os.path.exists(self.__filename): - #we talk about a file - print _('error: cannot open %s for reading') % self.__filename - return False + def read(self): + try: + fd = open(self.__filename) + except Exception: + if os.path.exists(self.__filename): + #we talk about a file + print _('error: cannot open %s for reading') % self.__filename + return False - new_version = gajim.config.get('version') - new_version = new_version.split('-', 1)[0] - seen = set() - regex = re.compile(r"(?P[^.]+)(?:(?:\.(?P.+))?\.(?P[^.]+))?\s=\s(?P.*)") + new_version = gajim.config.get('version') + new_version = new_version.split('-', 1)[0] + seen = set() + regex = re.compile(r"(?P[^.]+)(?:(?:\.(?P.+))?\.(?P[^.]+))?\s=\s(?P.*)") - for line in fd: - try: - line = line.decode('utf-8') - except UnicodeDecodeError: - line = line.decode(locale.getpreferredencoding()) - optname, key, subname, value = regex.match(line).groups() - if key is None: - self.old_values[optname] = value - gajim.config.set(optname, value) - else: - if (optname, key) not in seen: - gajim.config.add_per(optname, key) - seen.add((optname, key)) - gajim.config.set_per(optname, key, subname, value) + for line in fd: + try: + line = line.decode('utf-8') + except UnicodeDecodeError: + line = line.decode(locale.getpreferredencoding()) + optname, key, subname, value = regex.match(line).groups() + if key is None: + self.old_values[optname] = value + gajim.config.set(optname, value) + else: + if (optname, key) not in seen: + gajim.config.add_per(optname, key) + seen.add((optname, key)) + gajim.config.set_per(optname, key, subname, value) - old_version = gajim.config.get('version') - old_version = old_version.split('-', 1)[0] + old_version = gajim.config.get('version') + old_version = old_version.split('-', 1)[0] - self.update_config(old_version, new_version) - self.old_values = {} # clean mem + self.update_config(old_version, new_version) + self.old_values = {} # clean mem - fd.close() - return True + fd.close() + return True - def write_line(self, fd, opt, parents, value): - if value is None: - return - value = value[1] - # convert to utf8 before writing to file if needed - if isinstance(value, unicode): - value = value.encode('utf-8') - else: - value = str(value) - if isinstance(opt, unicode): - opt = opt.encode('utf-8') - s = '' - if parents: - if len(parents) == 1: - return - for p in parents: - if isinstance(p, unicode): - p = p.encode('utf-8') - s += p + '.' - s += opt - fd.write(s + ' = ' + value + '\n') + def write_line(self, fd, opt, parents, value): + if value is None: + return + value = value[1] + # convert to utf8 before writing to file if needed + if isinstance(value, unicode): + value = value.encode('utf-8') + else: + value = str(value) + if isinstance(opt, unicode): + opt = opt.encode('utf-8') + s = '' + if parents: + if len(parents) == 1: + return + for p in parents: + if isinstance(p, unicode): + p = p.encode('utf-8') + s += p + '.' + s += opt + fd.write(s + ' = ' + value + '\n') - def write(self): - (base_dir, filename) = os.path.split(self.__filename) - self.__tempfile = os.path.join(base_dir, '.' + filename) - try: - f = open(self.__tempfile, 'w') - except IOError, e: - return str(e) - try: - gajim.config.foreach(self.write_line, f) - except IOError, e: - return str(e) - f.close() - if os.path.exists(self.__filename): - # win32 needs this - try: - os.remove(self.__filename) - except Exception: - pass - try: - os.rename(self.__tempfile, self.__filename) - except IOError, e: - return str(e) - os.chmod(self.__filename, 0600) + def write(self): + (base_dir, filename) = os.path.split(self.__filename) + self.__tempfile = os.path.join(base_dir, '.' + filename) + try: + f = open(self.__tempfile, 'w') + except IOError, e: + return str(e) + try: + gajim.config.foreach(self.write_line, f) + except IOError, e: + return str(e) + f.close() + if os.path.exists(self.__filename): + # win32 needs this + try: + os.remove(self.__filename) + except Exception: + pass + try: + os.rename(self.__tempfile, self.__filename) + except IOError, e: + return str(e) + os.chmod(self.__filename, 0600) - def update_config(self, old_version, new_version): - old_version_list = old_version.split('.') # convert '0.x.y' to (0, x, y) - old = [] - while len(old_version_list): - old.append(int(old_version_list.pop(0))) - new_version_list = new_version.split('.') - new = [] - while len(new_version_list): - new.append(int(new_version_list.pop(0))) + def update_config(self, old_version, new_version): + old_version_list = old_version.split('.') # convert '0.x.y' to (0, x, y) + old = [] + while len(old_version_list): + old.append(int(old_version_list.pop(0))) + new_version_list = new_version.split('.') + new = [] + while len(new_version_list): + new.append(int(new_version_list.pop(0))) - if old < [0, 9] and new >= [0, 9]: - self.update_config_x_to_09() - if old < [0, 10] and new >= [0, 10]: - self.update_config_09_to_010() - if old < [0, 10, 1, 1] and new >= [0, 10, 1, 1]: - self.update_config_to_01011() - if old < [0, 10, 1, 2] and new >= [0, 10, 1, 2]: - self.update_config_to_01012() - if old < [0, 10, 1, 3] and new >= [0, 10, 1, 3]: - self.update_config_to_01013() - if old < [0, 10, 1, 4] and new >= [0, 10, 1, 4]: - self.update_config_to_01014() - if old < [0, 10, 1, 5] and new >= [0, 10, 1, 5]: - self.update_config_to_01015() - if old < [0, 10, 1, 6] and new >= [0, 10, 1, 6]: - self.update_config_to_01016() - if old < [0, 10, 1, 7] and new >= [0, 10, 1, 7]: - self.update_config_to_01017() - if old < [0, 10, 1, 8] and new >= [0, 10, 1, 8]: - self.update_config_to_01018() - if old < [0, 11, 0, 1] and new >= [0, 11, 0, 1]: - self.update_config_to_01101() - if old < [0, 11, 0, 2] and new >= [0, 11, 0, 2]: - self.update_config_to_01102() - if old < [0, 11, 1, 1] and new >= [0, 11, 1, 1]: - self.update_config_to_01111() - if old < [0, 11, 1, 2] and new >= [0, 11, 1, 2]: - self.update_config_to_01112() - if old < [0, 11, 1, 3] and new >= [0, 11, 1, 3]: - self.update_config_to_01113() - if old < [0, 11, 1, 4] and new >= [0, 11, 1, 4]: - self.update_config_to_01114() - if old < [0, 11, 1, 5] and new >= [0, 11, 1, 5]: - self.update_config_to_01115() - if old < [0, 11, 2, 1] and new >= [0, 11, 2, 1]: - self.update_config_to_01121() - if old < [0, 11, 4, 1] and new >= [0, 11, 4, 1]: - self.update_config_to_01141() - if old < [0, 11, 4, 2] and new >= [0, 11, 4, 2]: - self.update_config_to_01142() - if old < [0, 11, 4, 3] and new >= [0, 11, 4, 3]: - self.update_config_to_01143() - if old < [0, 11, 4, 4] and new >= [0, 11, 4, 4]: - self.update_config_to_01144() - if old < [0, 12, 0, 1] and new >= [0, 12, 0, 1]: - self.update_config_to_01201() - if old < [0, 12, 1, 1] and new >= [0, 12, 1, 1]: - self.update_config_to_01211() - if old < [0, 12, 1, 2] and new >= [0, 12, 1, 2]: - self.update_config_to_01212() - if old < [0, 12, 1, 3] and new >= [0, 12, 1, 3]: - self.update_config_to_01213() - if old < [0, 12, 1, 4] and new >= [0, 12, 1, 4]: - self.update_config_to_01214() - if old < [0, 12, 1, 5] and new >= [0, 12, 1, 5]: - self.update_config_to_01215() - if old < [0, 12, 3, 1] and new >= [0, 12, 3, 1]: - self.update_config_to_01231() - if old < [0, 12, 5, 1] and new >= [0, 12, 5, 1]: - self.update_config_from_0125() - self.update_config_to_01251() - if old < [0, 12, 5, 2] and new >= [0, 12, 5, 2]: - self.update_config_to_01252() - if old < [0, 12, 5, 3] and new >= [0, 12, 5, 3]: - self.update_config_to_01253() - if old < [0, 12, 5, 4] and new >= [0, 12, 5, 4]: - self.update_config_to_01254() - if old < [0, 12, 5, 5] and new >= [0, 12, 5, 5]: - self.update_config_to_01255() - if old < [0, 12, 5, 6] and new >= [0, 12, 5, 6]: - self.update_config_to_01256() - if old < [0, 12, 5, 7] and new >= [0, 12, 5, 7]: - self.update_config_to_01257() - if old < [0, 12, 5, 8] and new >= [0, 12, 5, 8]: - self.update_config_to_01258() - if old < [0, 13, 0, 1] and new >= [0, 13, 0, 1]: - self.update_config_to_01301() + if old < [0, 9] and new >= [0, 9]: + self.update_config_x_to_09() + if old < [0, 10] and new >= [0, 10]: + self.update_config_09_to_010() + if old < [0, 10, 1, 1] and new >= [0, 10, 1, 1]: + self.update_config_to_01011() + if old < [0, 10, 1, 2] and new >= [0, 10, 1, 2]: + self.update_config_to_01012() + if old < [0, 10, 1, 3] and new >= [0, 10, 1, 3]: + self.update_config_to_01013() + if old < [0, 10, 1, 4] and new >= [0, 10, 1, 4]: + self.update_config_to_01014() + if old < [0, 10, 1, 5] and new >= [0, 10, 1, 5]: + self.update_config_to_01015() + if old < [0, 10, 1, 6] and new >= [0, 10, 1, 6]: + self.update_config_to_01016() + if old < [0, 10, 1, 7] and new >= [0, 10, 1, 7]: + self.update_config_to_01017() + if old < [0, 10, 1, 8] and new >= [0, 10, 1, 8]: + self.update_config_to_01018() + if old < [0, 11, 0, 1] and new >= [0, 11, 0, 1]: + self.update_config_to_01101() + if old < [0, 11, 0, 2] and new >= [0, 11, 0, 2]: + self.update_config_to_01102() + if old < [0, 11, 1, 1] and new >= [0, 11, 1, 1]: + self.update_config_to_01111() + if old < [0, 11, 1, 2] and new >= [0, 11, 1, 2]: + self.update_config_to_01112() + if old < [0, 11, 1, 3] and new >= [0, 11, 1, 3]: + self.update_config_to_01113() + if old < [0, 11, 1, 4] and new >= [0, 11, 1, 4]: + self.update_config_to_01114() + if old < [0, 11, 1, 5] and new >= [0, 11, 1, 5]: + self.update_config_to_01115() + if old < [0, 11, 2, 1] and new >= [0, 11, 2, 1]: + self.update_config_to_01121() + if old < [0, 11, 4, 1] and new >= [0, 11, 4, 1]: + self.update_config_to_01141() + if old < [0, 11, 4, 2] and new >= [0, 11, 4, 2]: + self.update_config_to_01142() + if old < [0, 11, 4, 3] and new >= [0, 11, 4, 3]: + self.update_config_to_01143() + if old < [0, 11, 4, 4] and new >= [0, 11, 4, 4]: + self.update_config_to_01144() + if old < [0, 12, 0, 1] and new >= [0, 12, 0, 1]: + self.update_config_to_01201() + if old < [0, 12, 1, 1] and new >= [0, 12, 1, 1]: + self.update_config_to_01211() + if old < [0, 12, 1, 2] and new >= [0, 12, 1, 2]: + self.update_config_to_01212() + if old < [0, 12, 1, 3] and new >= [0, 12, 1, 3]: + self.update_config_to_01213() + if old < [0, 12, 1, 4] and new >= [0, 12, 1, 4]: + self.update_config_to_01214() + if old < [0, 12, 1, 5] and new >= [0, 12, 1, 5]: + self.update_config_to_01215() + if old < [0, 12, 3, 1] and new >= [0, 12, 3, 1]: + self.update_config_to_01231() + if old < [0, 12, 5, 1] and new >= [0, 12, 5, 1]: + self.update_config_from_0125() + self.update_config_to_01251() + if old < [0, 12, 5, 2] and new >= [0, 12, 5, 2]: + self.update_config_to_01252() + if old < [0, 12, 5, 3] and new >= [0, 12, 5, 3]: + self.update_config_to_01253() + if old < [0, 12, 5, 4] and new >= [0, 12, 5, 4]: + self.update_config_to_01254() + if old < [0, 12, 5, 5] and new >= [0, 12, 5, 5]: + self.update_config_to_01255() + if old < [0, 12, 5, 6] and new >= [0, 12, 5, 6]: + self.update_config_to_01256() + if old < [0, 12, 5, 7] and new >= [0, 12, 5, 7]: + self.update_config_to_01257() + if old < [0, 12, 5, 8] and new >= [0, 12, 5, 8]: + self.update_config_to_01258() + if old < [0, 13, 0, 1] and new >= [0, 13, 0, 1]: + self.update_config_to_01301() - gajim.logger.init_vars() - gajim.config.set('version', new_version) + gajim.logger.init_vars() + gajim.config.set('version', new_version) - caps.capscache.initialize_from_db() + caps.capscache.initialize_from_db() - def assert_unread_msgs_table_exists(self): - '''create table unread_messages if there is no such table''' - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - try: - cur.executescript( - ''' - CREATE TABLE unread_messages ( - message_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, - jid_id INTEGER - ); - ''' - ) - con.commit() - gajim.logger.init_vars() - except sqlite.OperationalError: - pass - con.close() + def assert_unread_msgs_table_exists(self): + '''create table unread_messages if there is no such table''' + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + try: + cur.executescript( + ''' + CREATE TABLE unread_messages ( + message_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, + jid_id INTEGER + ); + ''' + ) + con.commit() + gajim.logger.init_vars() + except sqlite.OperationalError: + pass + con.close() - def update_ft_proxies(self, to_remove=[], to_add=[]): - for account in gajim.config.get_per('accounts'): - proxies_str = gajim.config.get_per('accounts', account, - 'file_transfer_proxies') - proxies = [p.strip() for p in proxies_str.split(',')] - for wrong_proxy in to_remove: - if wrong_proxy in proxies: - proxies.remove(wrong_proxy) - for new_proxy in to_add: - if new_proxy not in proxies: - proxies.append(new_proxy) - proxies_str = ', '.join(proxies) - gajim.config.set_per('accounts', account, 'file_transfer_proxies', - proxies_str) + def update_ft_proxies(self, to_remove=[], to_add=[]): + for account in gajim.config.get_per('accounts'): + proxies_str = gajim.config.get_per('accounts', account, + 'file_transfer_proxies') + proxies = [p.strip() for p in proxies_str.split(',')] + for wrong_proxy in to_remove: + if wrong_proxy in proxies: + proxies.remove(wrong_proxy) + for new_proxy in to_add: + if new_proxy not in proxies: + proxies.append(new_proxy) + proxies_str = ', '.join(proxies) + gajim.config.set_per('accounts', account, 'file_transfer_proxies', + proxies_str) - def update_config_x_to_09(self): - # Var name that changed: - # avatar_width /height -> chat_avatar_width / height - if 'avatar_width' in self.old_values: - gajim.config.set('chat_avatar_width', self.old_values['avatar_width']) - if 'avatar_height' in self.old_values: - gajim.config.set('chat_avatar_height', self.old_values['avatar_height']) - if 'use_dbus' in self.old_values: - gajim.config.set('remote_control', self.old_values['use_dbus']) - # always_compact_view -> always_compact_view_chat / _gc - if 'always_compact_view' in self.old_values: - gajim.config.set('always_compact_view_chat', - self.old_values['always_compact_view']) - gajim.config.set('always_compact_view_gc', - self.old_values['always_compact_view']) - # new theme: grocery, plain - d = ['accounttextcolor', 'accountbgcolor', 'accountfont', - 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont', - 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', 'contactfont', - 'contactfontattrs', 'bannertextcolor', 'bannerbgcolor', 'bannerfont', - 'bannerfontattrs'] - for theme_name in (_('grocery'), _('default')): - if theme_name not in gajim.config.get_per('themes'): - gajim.config.add_per('themes', theme_name) - theme = gajim.config.themes_default[theme_name] - for o in d: - gajim.config.set_per('themes', theme_name, o, theme[d.index(o)]) - # Remove cyan theme if it's not the current theme - if 'cyan' in gajim.config.get_per('themes'): - gajim.config.del_per('themes', 'cyan') - if _('cyan') in gajim.config.get_per('themes'): - gajim.config.del_per('themes', _('cyan')) - # If we removed our roster_theme, choose the default green one or another - # one if doesn't exists in config - if gajim.config.get('roster_theme') not in gajim.config.get_per('themes'): - theme = _('green') - if theme not in gajim.config.get_per('themes'): - theme = gajim.config.get_per('themes')[0] - gajim.config.set('roster_theme', theme) - # new proxies in accounts.name.file_transfer_proxies - self.update_ft_proxies(to_add=['proxy.netlab.cz']) + def update_config_x_to_09(self): + # Var name that changed: + # avatar_width /height -> chat_avatar_width / height + if 'avatar_width' in self.old_values: + gajim.config.set('chat_avatar_width', self.old_values['avatar_width']) + if 'avatar_height' in self.old_values: + gajim.config.set('chat_avatar_height', self.old_values['avatar_height']) + if 'use_dbus' in self.old_values: + gajim.config.set('remote_control', self.old_values['use_dbus']) + # always_compact_view -> always_compact_view_chat / _gc + if 'always_compact_view' in self.old_values: + gajim.config.set('always_compact_view_chat', + self.old_values['always_compact_view']) + gajim.config.set('always_compact_view_gc', + self.old_values['always_compact_view']) + # new theme: grocery, plain + d = ['accounttextcolor', 'accountbgcolor', 'accountfont', + 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont', + 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', 'contactfont', + 'contactfontattrs', 'bannertextcolor', 'bannerbgcolor', 'bannerfont', + 'bannerfontattrs'] + for theme_name in (_('grocery'), _('default')): + if theme_name not in gajim.config.get_per('themes'): + gajim.config.add_per('themes', theme_name) + theme = gajim.config.themes_default[theme_name] + for o in d: + gajim.config.set_per('themes', theme_name, o, theme[d.index(o)]) + # Remove cyan theme if it's not the current theme + if 'cyan' in gajim.config.get_per('themes'): + gajim.config.del_per('themes', 'cyan') + if _('cyan') in gajim.config.get_per('themes'): + gajim.config.del_per('themes', _('cyan')) + # If we removed our roster_theme, choose the default green one or another + # one if doesn't exists in config + if gajim.config.get('roster_theme') not in gajim.config.get_per('themes'): + theme = _('green') + if theme not in gajim.config.get_per('themes'): + theme = gajim.config.get_per('themes')[0] + gajim.config.set('roster_theme', theme) + # new proxies in accounts.name.file_transfer_proxies + self.update_ft_proxies(to_add=['proxy.netlab.cz']) - gajim.config.set('version', '0.9') + gajim.config.set('version', '0.9') - def update_config_09_to_010(self): - if 'usetabbedchat' in self.old_values and not \ - self.old_values['usetabbedchat']: - gajim.config.set('one_message_window', 'never') - if 'autodetect_browser_mailer' in self.old_values and \ - self.old_values['autodetect_browser_mailer'] is True: - gajim.config.set('autodetect_browser_mailer', False) - if 'useemoticons' in self.old_values and \ - not self.old_values['useemoticons']: - gajim.config.set('emoticons_theme', '') - if 'always_compact_view_chat' in self.old_values and \ - self.old_values['always_compact_view_chat'] != 'False': - gajim.config.set('always_hide_chat_buttons', True) - if 'always_compact_view_gc' in self.old_values and \ - self.old_values['always_compact_view_gc'] != 'False': - gajim.config.set('always_hide_groupchat_buttons', True) + def update_config_09_to_010(self): + if 'usetabbedchat' in self.old_values and not \ + self.old_values['usetabbedchat']: + gajim.config.set('one_message_window', 'never') + if 'autodetect_browser_mailer' in self.old_values and \ + self.old_values['autodetect_browser_mailer'] is True: + gajim.config.set('autodetect_browser_mailer', False) + if 'useemoticons' in self.old_values and \ + not self.old_values['useemoticons']: + gajim.config.set('emoticons_theme', '') + if 'always_compact_view_chat' in self.old_values and \ + self.old_values['always_compact_view_chat'] != 'False': + gajim.config.set('always_hide_chat_buttons', True) + if 'always_compact_view_gc' in self.old_values and \ + self.old_values['always_compact_view_gc'] != 'False': + gajim.config.set('always_hide_groupchat_buttons', True) - self.update_ft_proxies(to_remove=['proxy65.jabber.autocom.pl', - 'proxy65.jabber.ccc.de'], to_add=['transfer.jabber.freenet.de']) - # create unread_messages table if needed - self.assert_unread_msgs_table_exists() + self.update_ft_proxies(to_remove=['proxy65.jabber.autocom.pl', + 'proxy65.jabber.ccc.de'], to_add=['transfer.jabber.freenet.de']) + # create unread_messages table if needed + self.assert_unread_msgs_table_exists() - gajim.config.set('version', '0.10') + gajim.config.set('version', '0.10') - def update_config_to_01011(self): - if 'print_status_in_muc' in self.old_values and \ - self.old_values['print_status_in_muc'] in (True, False): - gajim.config.set('print_status_in_muc', 'in_and_out') - gajim.config.set('version', '0.10.1.1') + def update_config_to_01011(self): + if 'print_status_in_muc' in self.old_values and \ + self.old_values['print_status_in_muc'] in (True, False): + gajim.config.set('print_status_in_muc', 'in_and_out') + gajim.config.set('version', '0.10.1.1') - def update_config_to_01012(self): - # See [6456] - if 'emoticons_theme' in self.old_values and \ - self.old_values['emoticons_theme'] == 'Disabled': - gajim.config.set('emoticons_theme', '') - gajim.config.set('version', '0.10.1.2') + def update_config_to_01012(self): + # See [6456] + if 'emoticons_theme' in self.old_values and \ + self.old_values['emoticons_theme'] == 'Disabled': + gajim.config.set('emoticons_theme', '') + gajim.config.set('version', '0.10.1.2') - def update_config_to_01013(self): - '''create table transports_cache if there is no such table''' - #FIXME see #2812 - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - try: - cur.executescript( - ''' - CREATE TABLE transports_cache ( - transport TEXT UNIQUE, - type INTEGER - ); - ''' - ) - con.commit() - except sqlite.OperationalError: - pass - con.close() - gajim.config.set('version', '0.10.1.3') + def update_config_to_01013(self): + '''create table transports_cache if there is no such table''' + #FIXME see #2812 + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + try: + cur.executescript( + ''' + CREATE TABLE transports_cache ( + transport TEXT UNIQUE, + type INTEGER + ); + ''' + ) + con.commit() + except sqlite.OperationalError: + pass + con.close() + gajim.config.set('version', '0.10.1.3') - def update_config_to_01014(self): - '''apply indeces to the logs database''' - print _('migrating logs database to indices') - #FIXME see #2812 - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - # apply indeces - try: - cur.executescript( - ''' - CREATE INDEX idx_logs_jid_id_kind ON logs (jid_id, kind); - CREATE INDEX idx_unread_messages_jid_id ON unread_messages (jid_id); - ''' - ) + def update_config_to_01014(self): + '''apply indeces to the logs database''' + print _('migrating logs database to indices') + #FIXME see #2812 + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + # apply indeces + try: + cur.executescript( + ''' + CREATE INDEX idx_logs_jid_id_kind ON logs (jid_id, kind); + CREATE INDEX idx_unread_messages_jid_id ON unread_messages (jid_id); + ''' + ) - con.commit() - except Exception: - pass - con.close() - gajim.config.set('version', '0.10.1.4') + con.commit() + except Exception: + pass + con.close() + gajim.config.set('version', '0.10.1.4') - def update_config_to_01015(self): - '''clean show values in logs database''' - #FIXME see #2812 - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - status = dict((i[5:].lower(), logger.constants.__dict__[i]) for i in \ - logger.constants.__dict__.keys() if i.startswith('SHOW_')) - for show in status: - cur.execute('update logs set show = ? where show = ?;', (status[show], - show)) - cur.execute('update logs set show = NULL where show not in (0, 1, 2, 3, 4, 5);') - con.commit() - cur.close() # remove this in 2007 [pysqlite old versions need this] - con.close() - gajim.config.set('version', '0.10.1.5') + def update_config_to_01015(self): + '''clean show values in logs database''' + #FIXME see #2812 + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + status = dict((i[5:].lower(), logger.constants.__dict__[i]) for i in \ + logger.constants.__dict__.keys() if i.startswith('SHOW_')) + for show in status: + cur.execute('update logs set show = ? where show = ?;', (status[show], + show)) + cur.execute('update logs set show = NULL where show not in (0, 1, 2, 3, 4, 5);') + con.commit() + cur.close() # remove this in 2007 [pysqlite old versions need this] + con.close() + gajim.config.set('version', '0.10.1.5') - def update_config_to_01016(self): - '''#2494 : Now we play gc_received_message sound even if - notify_on_all_muc_messages is false. Keep precedent behaviour.''' - if 'notify_on_all_muc_messages' in self.old_values and \ - self.old_values['notify_on_all_muc_messages'] == 'False' and \ - gajim.config.get_per('soundevents', 'muc_message_received', 'enabled'): - gajim.config.set_per('soundevents',\ - 'muc_message_received', 'enabled', False) - gajim.config.set('version', '0.10.1.6') + def update_config_to_01016(self): + '''#2494 : Now we play gc_received_message sound even if + notify_on_all_muc_messages is false. Keep precedent behaviour.''' + if 'notify_on_all_muc_messages' in self.old_values and \ + self.old_values['notify_on_all_muc_messages'] == 'False' and \ + gajim.config.get_per('soundevents', 'muc_message_received', 'enabled'): + gajim.config.set_per('soundevents',\ + 'muc_message_received', 'enabled', False) + gajim.config.set('version', '0.10.1.6') - def update_config_to_01017(self): - '''trayicon_notification_on_new_messages -> - trayicon_notification_on_events ''' - if 'trayicon_notification_on_new_messages' in self.old_values: - gajim.config.set('trayicon_notification_on_events', - self.old_values['trayicon_notification_on_new_messages']) - gajim.config.set('version', '0.10.1.7') + def update_config_to_01017(self): + '''trayicon_notification_on_new_messages -> + trayicon_notification_on_events ''' + if 'trayicon_notification_on_new_messages' in self.old_values: + gajim.config.set('trayicon_notification_on_events', + self.old_values['trayicon_notification_on_new_messages']) + gajim.config.set('version', '0.10.1.7') - def update_config_to_01018(self): - '''chat_state_notifications -> outgoing_chat_state_notifications''' - if 'chat_state_notifications' in self.old_values: - gajim.config.set('outgoing_chat_state_notifications', - self.old_values['chat_state_notifications']) - gajim.config.set('version', '0.10.1.8') + def update_config_to_01018(self): + '''chat_state_notifications -> outgoing_chat_state_notifications''' + if 'chat_state_notifications' in self.old_values: + gajim.config.set('outgoing_chat_state_notifications', + self.old_values['chat_state_notifications']) + gajim.config.set('version', '0.10.1.8') - def update_config_to_01101(self): - '''fill time_stamp from before_time and after_time''' - if 'before_time' in self.old_values: - gajim.config.set('time_stamp', '%s%%X%s ' % ( - self.old_values['before_time'], self.old_values['after_time'])) - gajim.config.set('version', '0.11.0.1') + def update_config_to_01101(self): + '''fill time_stamp from before_time and after_time''' + if 'before_time' in self.old_values: + gajim.config.set('time_stamp', '%s%%X%s ' % ( + self.old_values['before_time'], self.old_values['after_time'])) + gajim.config.set('version', '0.11.0.1') - def update_config_to_01102(self): - '''fill time_stamp from before_time and after_time''' - if 'ft_override_host_to_send' in self.old_values: - gajim.config.set('ft_add_hosts_to_send', - self.old_values['ft_override_host_to_send']) - gajim.config.set('version', '0.11.0.2') + def update_config_to_01102(self): + '''fill time_stamp from before_time and after_time''' + if 'ft_override_host_to_send' in self.old_values: + gajim.config.set('ft_add_hosts_to_send', + self.old_values['ft_override_host_to_send']) + gajim.config.set('version', '0.11.0.2') - def update_config_to_01111(self): - '''always_hide_chatbuttons -> compact_view''' - if 'always_hide_groupchat_buttons' in self.old_values and \ - 'always_hide_chat_buttons' in self.old_values: - gajim.config.set('compact_view', self.old_values['always_hide_groupchat_buttons'] and \ - self.old_values['always_hide_chat_buttons']) - gajim.config.set('version', '0.11.1.1') + def update_config_to_01111(self): + '''always_hide_chatbuttons -> compact_view''' + if 'always_hide_groupchat_buttons' in self.old_values and \ + 'always_hide_chat_buttons' in self.old_values: + gajim.config.set('compact_view', self.old_values['always_hide_groupchat_buttons'] and \ + self.old_values['always_hide_chat_buttons']) + gajim.config.set('version', '0.11.1.1') - def update_config_to_01112(self): - '''gtk+ theme is renamed to default''' - if 'roster_theme' in self.old_values and \ - self.old_values['roster_theme'] == 'gtk+': - gajim.config.set('roster_theme', _('default')) - gajim.config.set('version', '0.11.1.2') + def update_config_to_01112(self): + '''gtk+ theme is renamed to default''' + if 'roster_theme' in self.old_values and \ + self.old_values['roster_theme'] == 'gtk+': + gajim.config.set('roster_theme', _('default')) + gajim.config.set('version', '0.11.1.2') - def update_config_to_01113(self): - # copy&pasted from update_config_to_01013, possibly 'FIXME see #2812' applies too - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - try: - cur.executescript( - ''' - CREATE TABLE caps_cache ( - node TEXT, - ver TEXT, - ext TEXT, - data BLOB - ); - ''' - ) - con.commit() - except sqlite.OperationalError: - pass - con.close() - gajim.config.set('version', '0.11.1.3') + def update_config_to_01113(self): + # copy&pasted from update_config_to_01013, possibly 'FIXME see #2812' applies too + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + try: + cur.executescript( + ''' + CREATE TABLE caps_cache ( + node TEXT, + ver TEXT, + ext TEXT, + data BLOB + ); + ''' + ) + con.commit() + except sqlite.OperationalError: + pass + con.close() + gajim.config.set('version', '0.11.1.3') - def update_config_to_01114(self): - # add default theme if it doesn't exist - d = ['accounttextcolor', 'accountbgcolor', 'accountfont', - 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont', - 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', 'contactfont', - 'contactfontattrs', 'bannertextcolor', 'bannerbgcolor', 'bannerfont', - 'bannerfontattrs'] - theme_name = _('default') - if theme_name not in gajim.config.get_per('themes'): - gajim.config.add_per('themes', theme_name) - if gajim.config.get_per('themes', 'gtk+'): - # copy from old gtk+ theme - for o in d: - val = gajim.config.get_per('themes', 'gtk+', o) - gajim.config.set_per('themes', theme_name, o, val) - gajim.config.del_per('themes', 'gtk+') - else: - # copy from default theme - theme = gajim.config.themes_default[theme_name] - for o in d: - gajim.config.set_per('themes', theme_name, o, theme[d.index(o)]) - gajim.config.set('version', '0.11.1.4') + def update_config_to_01114(self): + # add default theme if it doesn't exist + d = ['accounttextcolor', 'accountbgcolor', 'accountfont', + 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont', + 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', 'contactfont', + 'contactfontattrs', 'bannertextcolor', 'bannerbgcolor', 'bannerfont', + 'bannerfontattrs'] + theme_name = _('default') + if theme_name not in gajim.config.get_per('themes'): + gajim.config.add_per('themes', theme_name) + if gajim.config.get_per('themes', 'gtk+'): + # copy from old gtk+ theme + for o in d: + val = gajim.config.get_per('themes', 'gtk+', o) + gajim.config.set_per('themes', theme_name, o, val) + gajim.config.del_per('themes', 'gtk+') + else: + # copy from default theme + theme = gajim.config.themes_default[theme_name] + for o in d: + gajim.config.set_per('themes', theme_name, o, theme[d.index(o)]) + gajim.config.set('version', '0.11.1.4') - def update_config_to_01115(self): - # copy&pasted from update_config_to_01013, possibly 'FIXME see #2812' applies too - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - try: - cur.executescript( - ''' - DELETE FROM caps_cache; - ''' - ) - con.commit() - except sqlite.OperationalError: - pass - con.close() - gajim.config.set('version', '0.11.1.5') + def update_config_to_01115(self): + # copy&pasted from update_config_to_01013, possibly 'FIXME see #2812' applies too + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + try: + cur.executescript( + ''' + DELETE FROM caps_cache; + ''' + ) + con.commit() + except sqlite.OperationalError: + pass + con.close() + gajim.config.set('version', '0.11.1.5') - def update_config_to_01121(self): - # remove old unencrypted secrets file - from common.configpaths import gajimpaths + def update_config_to_01121(self): + # remove old unencrypted secrets file + from common.configpaths import gajimpaths - new_file = gajimpaths['SECRETS_FILE'] + new_file = gajimpaths['SECRETS_FILE'] - old_file = os.path.dirname(new_file) + '/secrets' + old_file = os.path.dirname(new_file) + '/secrets' - if os.path.exists(old_file): - os.remove(old_file) + if os.path.exists(old_file): + os.remove(old_file) - gajim.config.set('version', '0.11.2.1') + gajim.config.set('version', '0.11.2.1') - def update_config_to_01141(self): - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - try: - cur.executescript( - ''' - CREATE TABLE IF NOT EXISTS caps_cache ( - node TEXT, - ver TEXT, - ext TEXT, - data BLOB - ); - ''' - ) - con.commit() - except sqlite.OperationalError: - pass - con.close() - gajim.config.set('version', '0.11.4.1') + def update_config_to_01141(self): + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + try: + cur.executescript( + ''' + CREATE TABLE IF NOT EXISTS caps_cache ( + node TEXT, + ver TEXT, + ext TEXT, + data BLOB + ); + ''' + ) + con.commit() + except sqlite.OperationalError: + pass + con.close() + gajim.config.set('version', '0.11.4.1') - def update_config_to_01142(self): - '''next_message_received sound event is splittedin 2 events''' - gajim.config.add_per('soundevents', 'next_message_received_focused') - gajim.config.add_per('soundevents', 'next_message_received_unfocused') - if gajim.config.get_per('soundevents', 'next_message_received'): - enabled = gajim.config.get_per('soundevents', 'next_message_received', - 'enabled') - path = gajim.config.get_per('soundevents', 'next_message_received', - 'path') - gajim.config.del_per('soundevents', 'next_message_received') - gajim.config.set_per('soundevents', 'next_message_received_focused', - 'enabled', enabled) - gajim.config.set_per('soundevents', 'next_message_received_focused', - 'path', path) - gajim.config.set('version', '0.11.1.2') + def update_config_to_01142(self): + '''next_message_received sound event is splittedin 2 events''' + gajim.config.add_per('soundevents', 'next_message_received_focused') + gajim.config.add_per('soundevents', 'next_message_received_unfocused') + if gajim.config.get_per('soundevents', 'next_message_received'): + enabled = gajim.config.get_per('soundevents', 'next_message_received', + 'enabled') + path = gajim.config.get_per('soundevents', 'next_message_received', + 'path') + gajim.config.del_per('soundevents', 'next_message_received') + gajim.config.set_per('soundevents', 'next_message_received_focused', + 'enabled', enabled) + gajim.config.set_per('soundevents', 'next_message_received_focused', + 'path', path) + gajim.config.set('version', '0.11.1.2') - def update_config_to_01143(self): - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - try: - cur.executescript( - ''' - CREATE TABLE IF NOT EXISTS rooms_last_message_time( - jid_id INTEGER PRIMARY KEY UNIQUE, - time INTEGER - ); - ''' - ) - con.commit() - except sqlite.OperationalError: - pass - con.close() - gajim.config.set('version', '0.11.4.3') + def update_config_to_01143(self): + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + try: + cur.executescript( + ''' + CREATE TABLE IF NOT EXISTS rooms_last_message_time( + jid_id INTEGER PRIMARY KEY UNIQUE, + time INTEGER + ); + ''' + ) + con.commit() + except sqlite.OperationalError: + pass + con.close() + gajim.config.set('version', '0.11.4.3') - def update_config_to_01144(self): - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - try: - cur.executescript('DROP TABLE caps_cache;') - con.commit() - except sqlite.OperationalError: - pass - try: - cur.executescript( - ''' - CREATE TABLE caps_cache ( - hash_method TEXT, - hash TEXT, - data BLOB - ); - ''' - ) - con.commit() - except sqlite.OperationalError, e: - pass - con.close() - gajim.config.set('version', '0.11.4.4') + def update_config_to_01144(self): + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + try: + cur.executescript('DROP TABLE caps_cache;') + con.commit() + except sqlite.OperationalError: + pass + try: + cur.executescript( + ''' + CREATE TABLE caps_cache ( + hash_method TEXT, + hash TEXT, + data BLOB + ); + ''' + ) + con.commit() + except sqlite.OperationalError, e: + pass + con.close() + gajim.config.set('version', '0.11.4.4') - def update_config_to_01201(self): - if 'uri_schemes' in self.old_values: - new_values = self.old_values['uri_schemes'].replace(' mailto', '').\ - replace(' xmpp', '') - gajim.config.set('uri_schemes', new_values) - gajim.config.set('version', '0.12.0.1') + def update_config_to_01201(self): + if 'uri_schemes' in self.old_values: + new_values = self.old_values['uri_schemes'].replace(' mailto', '').\ + replace(' xmpp', '') + gajim.config.set('uri_schemes', new_values) + gajim.config.set('version', '0.12.0.1') - def update_config_to_01211(self): - if 'trayicon' in self.old_values: - if self.old_values['trayicon'] == 'False': - gajim.config.set('trayicon', 'never') - else: - gajim.config.set('trayicon', 'always') - gajim.config.set('version', '0.12.1.1') + def update_config_to_01211(self): + if 'trayicon' in self.old_values: + if self.old_values['trayicon'] == 'False': + gajim.config.set('trayicon', 'never') + else: + gajim.config.set('trayicon', 'always') + gajim.config.set('version', '0.12.1.1') - def update_config_to_01212(self): - for opt in ('ignore_unknown_contacts', 'send_os_info', - 'log_encrypted_sessions'): - if opt in self.old_values: - val = self.old_values[opt] - for account in gajim.config.get_per('accounts'): - gajim.config.set_per('accounts', account, opt, val) - gajim.config.set('version', '0.12.1.2') + def update_config_to_01212(self): + for opt in ('ignore_unknown_contacts', 'send_os_info', + 'log_encrypted_sessions'): + if opt in self.old_values: + val = self.old_values[opt] + for account in gajim.config.get_per('accounts'): + gajim.config.set_per('accounts', account, opt, val) + gajim.config.set('version', '0.12.1.2') - def update_config_to_01213(self): - msgs = gajim.config.statusmsg_default - for msg_name in gajim.config.get_per('statusmsg'): - if msg_name in msgs: - gajim.config.set_per('statusmsg', msg_name, 'activity', - msgs[msg_name][1]) - gajim.config.set_per('statusmsg', msg_name, 'subactivity', - msgs[msg_name][2]) - gajim.config.set_per('statusmsg', msg_name, 'activity_text', - msgs[msg_name][3]) - gajim.config.set_per('statusmsg', msg_name, 'mood', - msgs[msg_name][4]) - gajim.config.set_per('statusmsg', msg_name, 'mood_text', - msgs[msg_name][5]) - gajim.config.set('version', '0.12.1.3') + def update_config_to_01213(self): + msgs = gajim.config.statusmsg_default + for msg_name in gajim.config.get_per('statusmsg'): + if msg_name in msgs: + gajim.config.set_per('statusmsg', msg_name, 'activity', + msgs[msg_name][1]) + gajim.config.set_per('statusmsg', msg_name, 'subactivity', + msgs[msg_name][2]) + gajim.config.set_per('statusmsg', msg_name, 'activity_text', + msgs[msg_name][3]) + gajim.config.set_per('statusmsg', msg_name, 'mood', + msgs[msg_name][4]) + gajim.config.set_per('statusmsg', msg_name, 'mood_text', + msgs[msg_name][5]) + gajim.config.set('version', '0.12.1.3') - def update_config_to_01214(self): - for status in ['online', 'chat', 'away', 'xa', 'dnd', 'invisible', - 'offline']: - if 'last_status_msg_' + status in self.old_values: - gajim.config.add_per('statusmsg', '_last_' + status) - gajim.config.set_per('statusmsg', '_last_' + status, 'message', - self.old_values['last_status_msg_' + status]) - gajim.config.set('version', '0.12.1.4') + def update_config_to_01214(self): + for status in ['online', 'chat', 'away', 'xa', 'dnd', 'invisible', + 'offline']: + if 'last_status_msg_' + status in self.old_values: + gajim.config.add_per('statusmsg', '_last_' + status) + gajim.config.set_per('statusmsg', '_last_' + status, 'message', + self.old_values['last_status_msg_' + status]) + gajim.config.set('version', '0.12.1.4') - def update_config_to_01215(self): - '''Remove hardcoded ../data/sounds from config''' - dirs = ('../data', gajim.gajimpaths.root, gajim.DATA_DIR) - for evt in gajim.config.get_per('soundevents'): - path = gajim.config.get_per('soundevents', evt ,'path') - # absolute and relative passes are necessary - path = helpers.strip_soundfile_path(path, dirs, abs=False) - path = helpers.strip_soundfile_path(path, dirs, abs=True) - gajim.config.set_per('soundevents', evt, 'path', path) - gajim.config.set('version', '0.12.1.5') + def update_config_to_01215(self): + '''Remove hardcoded ../data/sounds from config''' + dirs = ('../data', gajim.gajimpaths.root, gajim.DATA_DIR) + for evt in gajim.config.get_per('soundevents'): + path = gajim.config.get_per('soundevents', evt, 'path') + # absolute and relative passes are necessary + path = helpers.strip_soundfile_path(path, dirs, abs=False) + path = helpers.strip_soundfile_path(path, dirs, abs=True) + gajim.config.set_per('soundevents', evt, 'path', path) + gajim.config.set('version', '0.12.1.5') - def update_config_to_01231(self): - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - try: - cur.executescript( - ''' - CREATE TABLE IF NOT EXISTS roster_entry( - account_jid_id INTEGER, - jid_id INTEGER, - name TEXT, - subscription INTEGER, - ask BOOLEAN, - PRIMARY KEY (account_jid_id, jid_id) - ); + def update_config_to_01231(self): + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + try: + cur.executescript( + ''' + CREATE TABLE IF NOT EXISTS roster_entry( + account_jid_id INTEGER, + jid_id INTEGER, + name TEXT, + subscription INTEGER, + ask BOOLEAN, + PRIMARY KEY (account_jid_id, jid_id) + ); - CREATE TABLE IF NOT EXISTS roster_group( - account_jid_id INTEGER, - jid_id INTEGER, - group_name TEXT, - PRIMARY KEY (account_jid_id, jid_id, group_name) - ); - ''' - ) - con.commit() - except sqlite.OperationalError: - pass - con.close() - gajim.config.set('version', '0.12.3.1') + CREATE TABLE IF NOT EXISTS roster_group( + account_jid_id INTEGER, + jid_id INTEGER, + group_name TEXT, + PRIMARY KEY (account_jid_id, jid_id, group_name) + ); + ''' + ) + con.commit() + except sqlite.OperationalError: + pass + con.close() + gajim.config.set('version', '0.12.3.1') - def update_config_from_0125(self): - # All those functions need to be called for 0.12.5 to 0.13 transition - self.update_config_to_01211() - self.update_config_to_01213() - self.update_config_to_01214() - self.update_config_to_01215() - self.update_config_to_01231() + def update_config_from_0125(self): + # All those functions need to be called for 0.12.5 to 0.13 transition + self.update_config_to_01211() + self.update_config_to_01213() + self.update_config_to_01214() + self.update_config_to_01215() + self.update_config_to_01231() - def update_config_to_01251(self): - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - try: - cur.executescript( - ''' - ALTER TABLE unread_messages - ADD shown BOOLEAN default 0; - ''' - ) - con.commit() - except sqlite.OperationalError: - pass - con.close() - gajim.config.set('version', '0.12.5.1') + def update_config_to_01251(self): + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + try: + cur.executescript( + ''' + ALTER TABLE unread_messages + ADD shown BOOLEAN default 0; + ''' + ) + con.commit() + except sqlite.OperationalError: + pass + con.close() + gajim.config.set('version', '0.12.5.1') - def update_config_to_01252(self): - if 'alwaysauth' in self.old_values: - val = self.old_values['alwaysauth'] - for account in gajim.config.get_per('accounts'): - gajim.config.set_per('accounts', account, 'autoauth', val) - gajim.config.set('version', '0.12.5.2') + def update_config_to_01252(self): + if 'alwaysauth' in self.old_values: + val = self.old_values['alwaysauth'] + for account in gajim.config.get_per('accounts'): + gajim.config.set_per('accounts', account, 'autoauth', val) + gajim.config.set('version', '0.12.5.2') - def update_config_to_01253(self): - if 'enable_zeroconf' in self.old_values: - val = self.old_values['enable_zeroconf'] - for account in gajim.config.get_per('accounts'): - if gajim.config.get_per('accounts', account, 'is_zeroconf'): - gajim.config.set_per('accounts', account, 'active', val) - else: - gajim.config.set_per('accounts', account, 'active', True) - gajim.config.set('version', '0.12.5.3') + def update_config_to_01253(self): + if 'enable_zeroconf' in self.old_values: + val = self.old_values['enable_zeroconf'] + for account in gajim.config.get_per('accounts'): + if gajim.config.get_per('accounts', account, 'is_zeroconf'): + gajim.config.set_per('accounts', account, 'active', val) + else: + gajim.config.set_per('accounts', account, 'active', True) + gajim.config.set('version', '0.12.5.3') - def update_config_to_01254(self): - vals = {'inmsgcolor': ['#a34526', '#a40000'], - 'outmsgcolor': ['#164e6f', '#3465a4'], - 'restored_messages_color': ['grey', '#555753'], - 'statusmsgcolor': ['#1eaa1e', '#73d216'], - 'urlmsgcolor': ['#0000ff', '#204a87'], - 'gc_nicknames_colors': ['#a34526:#c000ff:#0012ff:#388a99:#045723:#7c7c7c:#ff8a00:#94452d:#244b5a:#32645a', '#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000']} - for c in vals: - if c not in self.old_values: - continue - val = self.old_values[c] - if val == vals[c][0]: - # We didn't change default value, so update it with new default - gajim.config.set(c, vals[c][1]) - gajim.config.set('version', '0.12.5.4') + def update_config_to_01254(self): + vals = {'inmsgcolor': ['#a34526', '#a40000'], + 'outmsgcolor': ['#164e6f', '#3465a4'], + 'restored_messages_color': ['grey', '#555753'], + 'statusmsgcolor': ['#1eaa1e', '#73d216'], + 'urlmsgcolor': ['#0000ff', '#204a87'], + 'gc_nicknames_colors': ['#a34526:#c000ff:#0012ff:#388a99:#045723:#7c7c7c:#ff8a00:#94452d:#244b5a:#32645a', '#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000']} + for c in vals: + if c not in self.old_values: + continue + val = self.old_values[c] + if val == vals[c][0]: + # We didn't change default value, so update it with new default + gajim.config.set(c, vals[c][1]) + gajim.config.set('version', '0.12.5.4') - def update_config_to_01255(self): - vals = {'statusmsgcolor': ['#73d216', '#4e9a06'], - 'outmsgtxtcolor': ['#a2a2a2', '#555753']} - for c in vals: - if c not in self.old_values: - continue - val = self.old_values[c] - if val == vals[c][0]: - # We didn't change default value, so update it with new default - gajim.config.set(c, vals[c][1]) - gajim.config.set('version', '0.12.5.5') + def update_config_to_01255(self): + vals = {'statusmsgcolor': ['#73d216', '#4e9a06'], + 'outmsgtxtcolor': ['#a2a2a2', '#555753']} + for c in vals: + if c not in self.old_values: + continue + val = self.old_values[c] + if val == vals[c][0]: + # We didn't change default value, so update it with new default + gajim.config.set(c, vals[c][1]) + gajim.config.set('version', '0.12.5.5') - def update_config_to_01256(self): - vals = {'gc_nicknames_colors': ['#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000', '#f57900:#ce5c00:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000']} - for c in vals: - if c not in self.old_values: - continue - val = self.old_values[c] - if val == vals[c][0]: - # We didn't change default value, so update it with new default - gajim.config.set(c, vals[c][1]) - gajim.config.set('version', '0.12.5.6') + def update_config_to_01256(self): + vals = {'gc_nicknames_colors': ['#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000', '#f57900:#ce5c00:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000']} + for c in vals: + if c not in self.old_values: + continue + val = self.old_values[c] + if val == vals[c][0]: + # We didn't change default value, so update it with new default + gajim.config.set(c, vals[c][1]) + gajim.config.set('version', '0.12.5.6') - def update_config_to_01257(self): - if 'iconset' in self.old_values: - if self.old_values['iconset'] in ('nuvola', 'crystal', 'gossip', - 'simplebulb', 'stellar'): - gajim.config.set('iconset', gajim.config.DEFAULT_ICONSET) - gajim.config.set('version', '0.12.5.7') + def update_config_to_01257(self): + if 'iconset' in self.old_values: + if self.old_values['iconset'] in ('nuvola', 'crystal', 'gossip', + 'simplebulb', 'stellar'): + gajim.config.set('iconset', gajim.config.DEFAULT_ICONSET) + gajim.config.set('version', '0.12.5.7') - def update_config_to_01258(self): - self.update_ft_proxies(to_remove=['proxy65.talkonaut.com', - 'proxy.jabber.org', 'proxy.netlab.cz', 'transfer.jabber.freenet.de', - 'proxy.jabber.cd.chalmers.se'], to_add=['proxy.eu.jabber.org', - 'proxy.jabber.ru', 'proxy.jabbim.cz']) - gajim.config.set('version', '0.12.5.8') + def update_config_to_01258(self): + self.update_ft_proxies(to_remove=['proxy65.talkonaut.com', + 'proxy.jabber.org', 'proxy.netlab.cz', 'transfer.jabber.freenet.de', + 'proxy.jabber.cd.chalmers.se'], to_add=['proxy.eu.jabber.org', + 'proxy.jabber.ru', 'proxy.jabbim.cz']) + gajim.config.set('version', '0.12.5.8') - def update_config_to_01301(self): - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - try: - cur.executescript( - ''' - ALTER TABLE caps_cache - ADD last_seen INTEGER default %d; - ''' % int(time()) - ) - con.commit() - except sqlite.OperationalError: - pass - con.close() - gajim.config.set('version', '0.13.0.1') - -# vim: se ts=3: + def update_config_to_01301(self): + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + try: + cur.executescript( + ''' + ALTER TABLE caps_cache + ADD last_seen INTEGER default %d; + ''' % int(time()) + ) + con.commit() + except sqlite.OperationalError: + pass + con.close() + gajim.config.set('version', '0.13.0.1') diff --git a/src/common/passwords.py b/src/common/passwords.py index 9000085c4..15607976d 100644 --- a/src/common/passwords.py +++ b/src/common/passwords.py @@ -36,189 +36,187 @@ USER_HAS_KWALLETCLI = False gnomekeyring = None class PasswordStorage(object): - def get_password(self, account_name): - raise NotImplementedError - def save_password(self, account_name, password): - raise NotImplementedError + def get_password(self, account_name): + raise NotImplementedError + def save_password(self, account_name, password): + raise NotImplementedError class SimplePasswordStorage(PasswordStorage): - def get_password(self, account_name): - passwd = gajim.config.get_per('accounts', account_name, 'password') - if passwd and (passwd.startswith('gnomekeyring:') or \ - passwd == ''): - # this is not a real password, it's either a gnome - # keyring token or stored in the KDE wallet - return None - else: - return passwd + def get_password(self, account_name): + passwd = gajim.config.get_per('accounts', account_name, 'password') + if passwd and (passwd.startswith('gnomekeyring:') or \ + passwd == ''): + # this is not a real password, it's either a gnome + # keyring token or stored in the KDE wallet + return None + else: + return passwd - def save_password(self, account_name, password): - gajim.config.set_per('accounts', account_name, 'password', password) - if account_name in gajim.connections: - gajim.connections[account_name].password = password + def save_password(self, account_name, password): + gajim.config.set_per('accounts', account_name, 'password', password) + if account_name in gajim.connections: + gajim.connections[account_name].password = password class GnomePasswordStorage(PasswordStorage): - def __init__(self): - self.keyring = gnomekeyring.get_default_keyring_sync() - if self.keyring is None: - self.keyring = 'default' - try: - gnomekeyring.create_sync(self.keyring, None) - except gnomekeyring.AlreadyExistsError: - pass + def __init__(self): + self.keyring = gnomekeyring.get_default_keyring_sync() + if self.keyring is None: + self.keyring = 'default' + try: + gnomekeyring.create_sync(self.keyring, None) + except gnomekeyring.AlreadyExistsError: + pass - def get_password(self, account_name): - conf = gajim.config.get_per('accounts', account_name, 'password') - if conf is None or conf == '': - return None - if not conf.startswith('gnomekeyring:'): - password = conf - ## migrate the password over to keyring - try: - self.save_password(account_name, password, update=False) - except gnomekeyring.NoKeyringDaemonError: - ## no keyring daemon: in the future, stop using it - set_storage(SimplePasswordStorage()) - return password - try: - server = gajim.config.get_per('accounts', account_name, 'hostname') - user = gajim.config.get_per('accounts', account_name, 'name') - attributes1 = dict(server=str(server), user=str(user), protocol='xmpp') - attributes2 = dict(account_name=str(account_name), gajim=1) - try: - items = gnomekeyring.find_items_sync( - gnomekeyring.ITEM_NETWORK_PASSWORD, attributes1) - except gnomekeyring.Error: - try: - items = gnomekeyring.find_items_sync( - gnomekeyring.ITEM_GENERIC_SECRET, attributes2) - if items: - # We found an old item, move it to new way of storing - password = items[0].secret - self.save_password(account_name, password) - gnomekeyring.item_delete_sync(items[0].keyring, - int(items[0].item_id)) - except gnomekeyring.Error: - items = [] - if len(items) > 1: - warnings.warn("multiple gnome keyring items found for account %s;" - " trying to use the first one..." - % account_name) - if items: - return items[0].secret - else: - return None - except gnomekeyring.DeniedError: - return None - except gnomekeyring.NoKeyringDaemonError: - ## no keyring daemon: in the future, stop using it - set_storage(SimplePasswordStorage()) - return None + def get_password(self, account_name): + conf = gajim.config.get_per('accounts', account_name, 'password') + if conf is None or conf == '': + return None + if not conf.startswith('gnomekeyring:'): + password = conf + ## migrate the password over to keyring + try: + self.save_password(account_name, password, update=False) + except gnomekeyring.NoKeyringDaemonError: + ## no keyring daemon: in the future, stop using it + set_storage(SimplePasswordStorage()) + return password + try: + server = gajim.config.get_per('accounts', account_name, 'hostname') + user = gajim.config.get_per('accounts', account_name, 'name') + attributes1 = dict(server=str(server), user=str(user), protocol='xmpp') + attributes2 = dict(account_name=str(account_name), gajim=1) + try: + items = gnomekeyring.find_items_sync( + gnomekeyring.ITEM_NETWORK_PASSWORD, attributes1) + except gnomekeyring.Error: + try: + items = gnomekeyring.find_items_sync( + gnomekeyring.ITEM_GENERIC_SECRET, attributes2) + if items: + # We found an old item, move it to new way of storing + password = items[0].secret + self.save_password(account_name, password) + gnomekeyring.item_delete_sync(items[0].keyring, + int(items[0].item_id)) + except gnomekeyring.Error: + items = [] + if len(items) > 1: + warnings.warn("multiple gnome keyring items found for account %s;" + " trying to use the first one..." + % account_name) + if items: + return items[0].secret + else: + return None + except gnomekeyring.DeniedError: + return None + except gnomekeyring.NoKeyringDaemonError: + ## no keyring daemon: in the future, stop using it + set_storage(SimplePasswordStorage()) + return None - def save_password(self, account_name, password, update=True): - server = gajim.config.get_per('accounts', account_name, 'hostname') - user = gajim.config.get_per('accounts', account_name, 'name') - display_name = _('XMPP account %s@%s') % (user, server) - attributes1 = dict(server=str(server), user=str(user), protocol='xmpp') - if password is None: - password = str() - try: - auth_token = gnomekeyring.item_create_sync( - self.keyring, gnomekeyring.ITEM_NETWORK_PASSWORD, - display_name, attributes1, password, update) - except gnomekeyring.DeniedError: - set_storage(SimplePasswordStorage()) - storage.save_password(account_name, password) - return - gajim.config.set_per('accounts', account_name, 'password', - 'gnomekeyring:') - if account_name in gajim.connections: - gajim.connections[account_name].password = password + def save_password(self, account_name, password, update=True): + server = gajim.config.get_per('accounts', account_name, 'hostname') + user = gajim.config.get_per('accounts', account_name, 'name') + display_name = _('XMPP account %s@%s') % (user, server) + attributes1 = dict(server=str(server), user=str(user), protocol='xmpp') + if password is None: + password = str() + try: + auth_token = gnomekeyring.item_create_sync( + self.keyring, gnomekeyring.ITEM_NETWORK_PASSWORD, + display_name, attributes1, password, update) + except gnomekeyring.DeniedError: + set_storage(SimplePasswordStorage()) + storage.save_password(account_name, password) + return + gajim.config.set_per('accounts', account_name, 'password', + 'gnomekeyring:') + if account_name in gajim.connections: + gajim.connections[account_name].password = password class KWalletPasswordStorage(PasswordStorage): - def get_password(self, account_name): - pw = gajim.config.get_per('accounts', account_name, 'password') - if not pw or pw.startswith('gnomekeyring:'): - # unset, empty or not ours - return None - if pw != '': - # migrate the password - if kwalletbinding.kwallet_put('gajim', account_name, pw): - gajim.config.set_per('accounts', account_name, 'password', - '') - else: - # stop using the KDE Wallet - set_storage(SimplePasswordStorage()) - return pw - pw = kwalletbinding.kwallet_get('gajim', account_name) - if pw is None: - # stop using the KDE Wallet - set_storage(SimplePasswordStorage()) - if not pw: - # False, None, or the empty string - return None - return pw + def get_password(self, account_name): + pw = gajim.config.get_per('accounts', account_name, 'password') + if not pw or pw.startswith('gnomekeyring:'): + # unset, empty or not ours + return None + if pw != '': + # migrate the password + if kwalletbinding.kwallet_put('gajim', account_name, pw): + gajim.config.set_per('accounts', account_name, 'password', + '') + else: + # stop using the KDE Wallet + set_storage(SimplePasswordStorage()) + return pw + pw = kwalletbinding.kwallet_get('gajim', account_name) + if pw is None: + # stop using the KDE Wallet + set_storage(SimplePasswordStorage()) + if not pw: + # False, None, or the empty string + return None + return pw - def save_password(self, account_name, password): - if not kwalletbinding.kwallet_put('gajim', account_name, password): - # stop using the KDE Wallet - set_storage(SimplePasswordStorage()) - storage.save_password(account_name, password) - return - pwtoken = '' - if not password: - # no sense in looking up the empty string in the KWallet - pwtoken = '' - gajim.config.set_per('accounts', account_name, 'password', pwtoken) - if account_name in gajim.connections: - gajim.connections[account_name].password = password + def save_password(self, account_name, password): + if not kwalletbinding.kwallet_put('gajim', account_name, password): + # stop using the KDE Wallet + set_storage(SimplePasswordStorage()) + storage.save_password(account_name, password) + return + pwtoken = '' + if not password: + # no sense in looking up the empty string in the KWallet + pwtoken = '' + gajim.config.set_per('accounts', account_name, 'password', pwtoken) + if account_name in gajim.connections: + gajim.connections[account_name].password = password storage = None def get_storage(): - global storage - if storage is None: # None is only in first time get_storage is called - if gajim.config.get('use_gnomekeyring'): - global gnomekeyring - try: - import gnomekeyring - except ImportError: - pass - else: - global USER_HAS_GNOMEKEYRING - global USER_USES_GNOMEKEYRING - USER_HAS_GNOMEKEYRING = True - if gnomekeyring.is_available(): - USER_USES_GNOMEKEYRING = True - else: - USER_USES_GNOMEKEYRING = False - if USER_USES_GNOMEKEYRING: - try: - storage = GnomePasswordStorage() - except (gnomekeyring.NoKeyringDaemonError, gnomekeyring.DeniedError): - storage = None - if storage is None: - if gajim.config.get('use_kwalletcli'): - global USER_HAS_KWALLETCLI - if kwalletbinding.kwallet_available(): - USER_HAS_KWALLETCLI = True - if USER_HAS_KWALLETCLI: - storage = KWalletPasswordStorage() - if storage is None: - storage = SimplePasswordStorage() - return storage + global storage + if storage is None: # None is only in first time get_storage is called + if gajim.config.get('use_gnomekeyring'): + global gnomekeyring + try: + import gnomekeyring + except ImportError: + pass + else: + global USER_HAS_GNOMEKEYRING + global USER_USES_GNOMEKEYRING + USER_HAS_GNOMEKEYRING = True + if gnomekeyring.is_available(): + USER_USES_GNOMEKEYRING = True + else: + USER_USES_GNOMEKEYRING = False + if USER_USES_GNOMEKEYRING: + try: + storage = GnomePasswordStorage() + except (gnomekeyring.NoKeyringDaemonError, gnomekeyring.DeniedError): + storage = None + if storage is None: + if gajim.config.get('use_kwalletcli'): + global USER_HAS_KWALLETCLI + if kwalletbinding.kwallet_available(): + USER_HAS_KWALLETCLI = True + if USER_HAS_KWALLETCLI: + storage = KWalletPasswordStorage() + if storage is None: + storage = SimplePasswordStorage() + return storage def set_storage(storage_): - global storage - storage = storage_ + global storage + storage = storage_ def get_password(account_name): - return get_storage().get_password(account_name) + return get_storage().get_password(account_name) def save_password(account_name, password): - return get_storage().save_password(account_name, password) - -# vim: se ts=3: + return get_storage().save_password(account_name, password) diff --git a/src/common/pep.py b/src/common/pep.py index 5ba476c0c..0b3ee7438 100644 --- a/src/common/pep.py +++ b/src/common/pep.py @@ -24,170 +24,170 @@ ## MOODS = { - 'afraid': _('Afraid'), - 'amazed': _('Amazed'), - 'amorous': _('Amorous'), - 'angry': _('Angry'), - 'annoyed': _('Annoyed'), - 'anxious': _('Anxious'), - 'aroused': _('Aroused'), - 'ashamed': _('Ashamed'), - 'bored': _('Bored'), - 'brave': _('Brave'), - 'calm': _('Calm'), - 'cautious': _('Cautious'), - 'cold': _('Cold'), - 'confident': _('Confident'), - 'confused': _('Confused'), - 'contemplative': _('Contemplative'), - 'contented': _('Contented'), - 'cranky': _('Cranky'), - 'crazy': _('Crazy'), - 'creative': _('Creative'), - 'curious': _('Curious'), - 'dejected': _('Dejected'), - 'depressed': _('Depressed'), - 'disappointed': _('Disappointed'), - 'disgusted': _('Disgusted'), - 'dismayed': _('Dismayed'), - 'distracted': _('Distracted'), - 'embarrassed': _('Embarrassed'), - 'envious': _('Envious'), - 'excited': _('Excited'), - 'flirtatious': _('Flirtatious'), - 'frustrated': _('Frustrated'), - 'grateful': _('Grateful'), - 'grieving': _('Grieving'), - 'grumpy': _('Grumpy'), - 'guilty': _('Guilty'), - 'happy': _('Happy'), - 'hopeful': _('Hopeful'), - 'hot': _('Hot'), - 'humbled': _('Humbled'), - 'humiliated': _('Humiliated'), - 'hungry': _('Hungry'), - 'hurt': _('Hurt'), - 'impressed': _('Impressed'), - 'in_awe': _('In Awe'), - 'in_love': _('In Love'), - 'indignant': _('Indignant'), - 'interested': _('Interested'), - 'intoxicated': _('Intoxicated'), - 'invincible': _('Invincible'), - 'jealous': _('Jealous'), - 'lonely': _('Lonely'), - 'lost': _('Lost'), - 'lucky': _('Lucky'), - 'mean': _('Mean'), - 'moody': _('Moody'), - 'nervous': _('Nervous'), - 'neutral': _('Neutral'), - 'offended': _('Offended'), - 'outraged': _('Outraged'), - 'playful': _('Playful'), - 'proud': _('Proud'), - 'relaxed': _('Relaxed'), - 'relieved': _('Relieved'), - 'remorseful': _('Remorseful'), - 'restless': _('Restless'), - 'sad': _('Sad'), - 'sarcastic': _('Sarcastic'), - 'satisfied': _('Satisfied'), - 'serious': _('Serious'), - 'shocked': _('Shocked'), - 'shy': _('Shy'), - 'sick': _('Sick'), - 'sleepy': _('Sleepy'), - 'spontaneous': _('Spontaneous'), - 'stressed': _('Stressed'), - 'strong': _('Strong'), - 'surprised': _('Surprised'), - 'thankful': _('Thankful'), - 'thirsty': _('Thirsty'), - 'tired': _('Tired'), - 'undefined': _('Undefined'), - 'weak': _('Weak'), - 'worried': _('Worried')} + 'afraid': _('Afraid'), + 'amazed': _('Amazed'), + 'amorous': _('Amorous'), + 'angry': _('Angry'), + 'annoyed': _('Annoyed'), + 'anxious': _('Anxious'), + 'aroused': _('Aroused'), + 'ashamed': _('Ashamed'), + 'bored': _('Bored'), + 'brave': _('Brave'), + 'calm': _('Calm'), + 'cautious': _('Cautious'), + 'cold': _('Cold'), + 'confident': _('Confident'), + 'confused': _('Confused'), + 'contemplative': _('Contemplative'), + 'contented': _('Contented'), + 'cranky': _('Cranky'), + 'crazy': _('Crazy'), + 'creative': _('Creative'), + 'curious': _('Curious'), + 'dejected': _('Dejected'), + 'depressed': _('Depressed'), + 'disappointed': _('Disappointed'), + 'disgusted': _('Disgusted'), + 'dismayed': _('Dismayed'), + 'distracted': _('Distracted'), + 'embarrassed': _('Embarrassed'), + 'envious': _('Envious'), + 'excited': _('Excited'), + 'flirtatious': _('Flirtatious'), + 'frustrated': _('Frustrated'), + 'grateful': _('Grateful'), + 'grieving': _('Grieving'), + 'grumpy': _('Grumpy'), + 'guilty': _('Guilty'), + 'happy': _('Happy'), + 'hopeful': _('Hopeful'), + 'hot': _('Hot'), + 'humbled': _('Humbled'), + 'humiliated': _('Humiliated'), + 'hungry': _('Hungry'), + 'hurt': _('Hurt'), + 'impressed': _('Impressed'), + 'in_awe': _('In Awe'), + 'in_love': _('In Love'), + 'indignant': _('Indignant'), + 'interested': _('Interested'), + 'intoxicated': _('Intoxicated'), + 'invincible': _('Invincible'), + 'jealous': _('Jealous'), + 'lonely': _('Lonely'), + 'lost': _('Lost'), + 'lucky': _('Lucky'), + 'mean': _('Mean'), + 'moody': _('Moody'), + 'nervous': _('Nervous'), + 'neutral': _('Neutral'), + 'offended': _('Offended'), + 'outraged': _('Outraged'), + 'playful': _('Playful'), + 'proud': _('Proud'), + 'relaxed': _('Relaxed'), + 'relieved': _('Relieved'), + 'remorseful': _('Remorseful'), + 'restless': _('Restless'), + 'sad': _('Sad'), + 'sarcastic': _('Sarcastic'), + 'satisfied': _('Satisfied'), + 'serious': _('Serious'), + 'shocked': _('Shocked'), + 'shy': _('Shy'), + 'sick': _('Sick'), + 'sleepy': _('Sleepy'), + 'spontaneous': _('Spontaneous'), + 'stressed': _('Stressed'), + 'strong': _('Strong'), + 'surprised': _('Surprised'), + 'thankful': _('Thankful'), + 'thirsty': _('Thirsty'), + 'tired': _('Tired'), + 'undefined': _('Undefined'), + 'weak': _('Weak'), + 'worried': _('Worried')} ACTIVITIES = { - 'doing_chores': {'category': _('Doing Chores'), - 'buying_groceries': _('Buying Groceries'), - 'cleaning': _('Cleaning'), - 'cooking': _('Cooking'), - 'doing_maintenance': _('Doing Maintenance'), - 'doing_the_dishes': _('Doing the Dishes'), - 'doing_the_laundry': _('Doing the Laundry'), - 'gardening': _('Gardening'), - 'running_an_errand': _('Running an Errand'), - 'walking_the_dog': _('Walking the Dog')}, - 'drinking': {'category': _('Drinking'), - 'having_a_beer': _('Having a Beer'), - 'having_coffee': _('Having Coffee'), - 'having_tea': _('Having Tea')}, - 'eating': {'category': _('Eating'), - 'having_a_snack': _('Having a Snack'), - 'having_breakfast': _('Having Breakfast'), - 'having_dinner': _('Having Dinner'), - 'having_lunch': _('Having Lunch')}, - 'exercising': {'category': _('Exercising'), - 'cycling': _('Cycling'), - 'dancing': _('Dancing'), - 'hiking': _('Hiking'), - 'jogging': _('Jogging'), - 'playing_sports': _('Playing Sports'), - 'running': _('Running'), - 'skiing': _('Skiing'), - 'swimming': _('Swimming'), - 'working_out': _('Working out')}, - 'grooming': {'category': _('Grooming'), - 'at_the_spa': _('At the Spa'), - 'brushing_teeth': _('Brushing Teeth'), - 'getting_a_haircut': _('Getting a Haircut'), - 'shaving': _('Shaving'), - 'taking_a_bath': _('Taking a Bath'), - 'taking_a_shower': _('Taking a Shower')}, - 'having_appointment': {'category': _('Having an Appointment')}, - 'inactive': {'category': _('Inactive'), - 'day_off': _('Day Off'), - 'hanging_out': _('Hanging out'), - 'hiding': _('Hiding'), - 'on_vacation': _('On Vacation'), - 'praying': _('Praying'), - 'scheduled_holiday': _('Scheduled Holiday'), - 'sleeping': _('Sleeping'), - 'thinking': _('Thinking')}, - 'relaxing': {'category': _('Relaxing'), - 'fishing': _('Fishing'), - 'gaming': _('Gaming'), - 'going_out': _('Going out'), - 'partying': _('Partying'), - 'reading': _('Reading'), - 'rehearsing': _('Rehearsing'), - 'shopping': _('Shopping'), - 'smoking': _('Smoking'), - 'socializing': _('Socializing'), - 'sunbathing': _('Sunbathing'), - 'watching_tv': _('Watching TV'), - 'watching_a_movie': _('Watching a Movie')}, - 'talking': {'category': _('Talking'), - 'in_real_life': _('In Real Life'), - 'on_the_phone': _('On the Phone'), - 'on_video_phone': _('On Video Phone')}, - 'traveling': {'category': _('Traveling'), - 'commuting': _('Commuting'), - 'cycling': _('Cycling'), - 'driving': _('Driving'), - 'in_a_car': _('In a Car'), - 'on_a_bus': _('On a Bus'), - 'on_a_plane': _('On a Plane'), - 'on_a_train': _('On a Train'), - 'on_a_trip': _('On a Trip'), - 'walking': _('Walking')}, - 'working': {'category': _('Working'), - 'coding': _('Coding'), - 'in_a_meeting': _('In a Meeting'), - 'studying': _('Studying'), - 'writing': _('Writing')}} + 'doing_chores': {'category': _('Doing Chores'), + 'buying_groceries': _('Buying Groceries'), + 'cleaning': _('Cleaning'), + 'cooking': _('Cooking'), + 'doing_maintenance': _('Doing Maintenance'), + 'doing_the_dishes': _('Doing the Dishes'), + 'doing_the_laundry': _('Doing the Laundry'), + 'gardening': _('Gardening'), + 'running_an_errand': _('Running an Errand'), + 'walking_the_dog': _('Walking the Dog')}, + 'drinking': {'category': _('Drinking'), + 'having_a_beer': _('Having a Beer'), + 'having_coffee': _('Having Coffee'), + 'having_tea': _('Having Tea')}, + 'eating': {'category': _('Eating'), + 'having_a_snack': _('Having a Snack'), + 'having_breakfast': _('Having Breakfast'), + 'having_dinner': _('Having Dinner'), + 'having_lunch': _('Having Lunch')}, + 'exercising': {'category': _('Exercising'), + 'cycling': _('Cycling'), + 'dancing': _('Dancing'), + 'hiking': _('Hiking'), + 'jogging': _('Jogging'), + 'playing_sports': _('Playing Sports'), + 'running': _('Running'), + 'skiing': _('Skiing'), + 'swimming': _('Swimming'), + 'working_out': _('Working out')}, + 'grooming': {'category': _('Grooming'), + 'at_the_spa': _('At the Spa'), + 'brushing_teeth': _('Brushing Teeth'), + 'getting_a_haircut': _('Getting a Haircut'), + 'shaving': _('Shaving'), + 'taking_a_bath': _('Taking a Bath'), + 'taking_a_shower': _('Taking a Shower')}, + 'having_appointment': {'category': _('Having an Appointment')}, + 'inactive': {'category': _('Inactive'), + 'day_off': _('Day Off'), + 'hanging_out': _('Hanging out'), + 'hiding': _('Hiding'), + 'on_vacation': _('On Vacation'), + 'praying': _('Praying'), + 'scheduled_holiday': _('Scheduled Holiday'), + 'sleeping': _('Sleeping'), + 'thinking': _('Thinking')}, + 'relaxing': {'category': _('Relaxing'), + 'fishing': _('Fishing'), + 'gaming': _('Gaming'), + 'going_out': _('Going out'), + 'partying': _('Partying'), + 'reading': _('Reading'), + 'rehearsing': _('Rehearsing'), + 'shopping': _('Shopping'), + 'smoking': _('Smoking'), + 'socializing': _('Socializing'), + 'sunbathing': _('Sunbathing'), + 'watching_tv': _('Watching TV'), + 'watching_a_movie': _('Watching a Movie')}, + 'talking': {'category': _('Talking'), + 'in_real_life': _('In Real Life'), + 'on_the_phone': _('On the Phone'), + 'on_video_phone': _('On Video Phone')}, + 'traveling': {'category': _('Traveling'), + 'commuting': _('Commuting'), + 'cycling': _('Cycling'), + 'driving': _('Driving'), + 'in_a_car': _('In a Car'), + 'on_a_bus': _('On a Bus'), + 'on_a_plane': _('On a Plane'), + 'on_a_train': _('On a Train'), + 'on_a_trip': _('On a Trip'), + 'walking': _('Walking')}, + 'working': {'category': _('Working'), + 'coding': _('Coding'), + 'in_a_meeting': _('In a Meeting'), + 'studying': _('Studying'), + 'writing': _('Writing')}} TUNE_DATA = ['artist', 'title', 'source', 'track', 'length'] @@ -206,360 +206,358 @@ import gtkgui_helpers class AbstractPEP(object): - - type = '' - namespace = '' - - @classmethod - def get_tag_as_PEP(cls, jid, account, event_tag): - items = event_tag.getTag('items', {'node': cls.namespace}) - if items: - log.debug("Received PEP 'user %s' from %s" % (cls.type, jid)) - return cls(jid, account, items) - else: - return None - - def __init__(self, jid, account, items): - self._pep_specific_data, self._retracted = self._extract_info(items) - - self._update_contacts(jid, account) - if jid == gajim.get_jid_from_account(account): - self._update_account(account) - - def _extract_info(self, items): - '''To be implemented by subclasses''' - raise NotImplementedError - - def _update_contacts(self, jid, account): - for contact in gajim.contacts.get_contacts(account, jid): - if self._retracted: - if self.type in contact.pep: - del contact.pep[self.type] - else: - contact.pep[self.type] = self - - def _update_account(self, account): - acc = gajim.connections[account] - if self._retracted: - if self.type in acc.pep: - del acc.pep[self.type] - else: - acc.pep[self.type] = self - - def asPixbufIcon(self): - '''SHOULD be implemented by subclasses''' - return None - - def asMarkupText(self): - '''SHOULD be implemented by subclasses''' - return '' - - -class UserMoodPEP(AbstractPEP): - '''XEP-0107: User Mood''' - - type = 'mood' - namespace = xmpp.NS_MOOD - - def _extract_info(self, items): - mood_dict = {} - - for item in items.getTags('item'): - mood_tag = item.getTag('mood') - if mood_tag: - for child in mood_tag.getChildren(): - name = child.getName().strip() - if name == 'text': - mood_dict['text'] = child.getData() - else: - mood_dict['mood'] = name - - retracted = items.getTag('retract') or not 'mood' in mood_dict - return (mood_dict, retracted) - - def asPixbufIcon(self): - assert not self._retracted - received_mood = self._pep_specific_data['mood'] - mood = received_mood if received_mood in MOODS else 'unknown' - pixbuf = gtkgui_helpers.load_mood_icon(mood).get_pixbuf() - return pixbuf - - def asMarkupText(self): - assert not self._retracted - untranslated_mood = self._pep_specific_data['mood'] - mood = self._translate_mood(untranslated_mood) - markuptext = '%s' % gobject.markup_escape_text(mood) - if 'text' in self._pep_specific_data: - text = self._pep_specific_data['text'] - markuptext += ' (%s)' % gobject.markup_escape_text(text) - return markuptext - def _translate_mood(self, mood): - if mood in MOODS: - return MOODS[mood] - else: - return mood + type = '' + namespace = '' + + @classmethod + def get_tag_as_PEP(cls, jid, account, event_tag): + items = event_tag.getTag('items', {'node': cls.namespace}) + if items: + log.debug("Received PEP 'user %s' from %s" % (cls.type, jid)) + return cls(jid, account, items) + else: + return None + + def __init__(self, jid, account, items): + self._pep_specific_data, self._retracted = self._extract_info(items) + + self._update_contacts(jid, account) + if jid == gajim.get_jid_from_account(account): + self._update_account(account) + + def _extract_info(self, items): + '''To be implemented by subclasses''' + raise NotImplementedError + + def _update_contacts(self, jid, account): + for contact in gajim.contacts.get_contacts(account, jid): + if self._retracted: + if self.type in contact.pep: + del contact.pep[self.type] + else: + contact.pep[self.type] = self + + def _update_account(self, account): + acc = gajim.connections[account] + if self._retracted: + if self.type in acc.pep: + del acc.pep[self.type] + else: + acc.pep[self.type] = self + + def asPixbufIcon(self): + '''SHOULD be implemented by subclasses''' + return None + + def asMarkupText(self): + '''SHOULD be implemented by subclasses''' + return '' + + +class UserMoodPEP(AbstractPEP): + '''XEP-0107: User Mood''' + + type = 'mood' + namespace = xmpp.NS_MOOD + + def _extract_info(self, items): + mood_dict = {} + + for item in items.getTags('item'): + mood_tag = item.getTag('mood') + if mood_tag: + for child in mood_tag.getChildren(): + name = child.getName().strip() + if name == 'text': + mood_dict['text'] = child.getData() + else: + mood_dict['mood'] = name + + retracted = items.getTag('retract') or not 'mood' in mood_dict + return (mood_dict, retracted) + + def asPixbufIcon(self): + assert not self._retracted + received_mood = self._pep_specific_data['mood'] + mood = received_mood if received_mood in MOODS else 'unknown' + pixbuf = gtkgui_helpers.load_mood_icon(mood).get_pixbuf() + return pixbuf + + def asMarkupText(self): + assert not self._retracted + untranslated_mood = self._pep_specific_data['mood'] + mood = self._translate_mood(untranslated_mood) + markuptext = '%s' % gobject.markup_escape_text(mood) + if 'text' in self._pep_specific_data: + text = self._pep_specific_data['text'] + markuptext += ' (%s)' % gobject.markup_escape_text(text) + return markuptext + + def _translate_mood(self, mood): + if mood in MOODS: + return MOODS[mood] + else: + return mood class UserTunePEP(AbstractPEP): - '''XEP-0118: User Tune''' - - type = 'tune' - namespace = xmpp.NS_TUNE - - def _extract_info(self, items): - tune_dict = {} - - for item in items.getTags('item'): - tune_tag = item.getTag('tune') - if tune_tag: - for child in tune_tag.getChildren(): - name = child.getName().strip() - data = child.getData().strip() - if child.getName() in TUNE_DATA: - tune_dict[name] = data - - retracted = items.getTag('retract') or not ('artist' in tune_dict or - 'title' in tune_dict) - return (tune_dict, retracted) - - def asPixbufIcon(self): - import os - path = os.path.join(gajim.DATA_DIR, 'emoticons', 'static', 'music.png') - return gtk.gdk.pixbuf_new_from_file(path) - - def asMarkupText(self): - assert not self._retracted - tune = self._pep_specific_data + '''XEP-0118: User Tune''' - artist = tune.get('artist', _('Unknown Artist')) - artist = gobject.markup_escape_text(artist) - - title = tune.get('title', _('Unknown Title')) - title = gobject.markup_escape_text(title) + type = 'tune' + namespace = xmpp.NS_TUNE - source = tune.get('source', _('Unknown Source')) - source = gobject.markup_escape_text(source) + def _extract_info(self, items): + tune_dict = {} + + for item in items.getTags('item'): + tune_tag = item.getTag('tune') + if tune_tag: + for child in tune_tag.getChildren(): + name = child.getName().strip() + data = child.getData().strip() + if child.getName() in TUNE_DATA: + tune_dict[name] = data + + retracted = items.getTag('retract') or not ('artist' in tune_dict or + 'title' in tune_dict) + return (tune_dict, retracted) + + def asPixbufIcon(self): + import os + path = os.path.join(gajim.DATA_DIR, 'emoticons', 'static', 'music.png') + return gtk.gdk.pixbuf_new_from_file(path) + + def asMarkupText(self): + assert not self._retracted + tune = self._pep_specific_data + + artist = tune.get('artist', _('Unknown Artist')) + artist = gobject.markup_escape_text(artist) + + title = tune.get('title', _('Unknown Title')) + title = gobject.markup_escape_text(title) + + source = tune.get('source', _('Unknown Source')) + source = gobject.markup_escape_text(source) + + tune_string = _('"%(title)s" by %(artist)s\n' + 'from %(source)s') % {'title': title, + 'artist': artist, 'source': source} + return tune_string - tune_string = _('"%(title)s" by %(artist)s\n' - 'from %(source)s') % {'title': title, - 'artist': artist, 'source': source} - return tune_string - class UserActivityPEP(AbstractPEP): - '''XEP-0108: User Activity''' - - type = 'activity' - namespace = xmpp.NS_ACTIVITY - - def _extract_info(self, items): - activity_dict = {} - - for item in items.getTags('item'): - activity_tag = item.getTag('activity') - if activity_tag: - for child in activity_tag.getChildren(): - name = child.getName().strip() - data = child.getData().strip() - if name == 'text': - activity_dict['text'] = data - else: - activity_dict['activity'] = name - for subactivity in child.getChildren(): - subactivity_name = subactivity.getName().strip() - activity_dict['subactivity'] = subactivity_name - - retracted = items.getTag('retract') or not 'activity' in activity_dict - return (activity_dict, retracted) - - def asPixbufIcon(self): - assert not self._retracted - pep = self._pep_specific_data - activity = pep['activity'] - - has_known_activity = activity in ACTIVITIES - has_known_subactivity = (has_known_activity and ('subactivity' in pep) - and (pep['subactivity'] in ACTIVITIES[activity])) - - if has_known_activity: - if has_known_subactivity: - subactivity = pep['subactivity'] - return gtkgui_helpers.load_activity_icon(activity, subactivity).get_pixbuf() - else: - return gtkgui_helpers.load_activity_icon(activity).get_pixbuf() - else: - return gtkgui_helpers.load_activity_icon('unknown').get_pixbuf() - - def asMarkupText(self): - assert not self._retracted - pep = self._pep_specific_data - activity = pep['activity'] - subactivity = pep['subactivity'] if 'subactivity' in pep else None - text = pep['text'] if 'text' in pep else None + '''XEP-0108: User Activity''' + + type = 'activity' + namespace = xmpp.NS_ACTIVITY + + def _extract_info(self, items): + activity_dict = {} + + for item in items.getTags('item'): + activity_tag = item.getTag('activity') + if activity_tag: + for child in activity_tag.getChildren(): + name = child.getName().strip() + data = child.getData().strip() + if name == 'text': + activity_dict['text'] = data + else: + activity_dict['activity'] = name + for subactivity in child.getChildren(): + subactivity_name = subactivity.getName().strip() + activity_dict['subactivity'] = subactivity_name + + retracted = items.getTag('retract') or not 'activity' in activity_dict + return (activity_dict, retracted) + + def asPixbufIcon(self): + assert not self._retracted + pep = self._pep_specific_data + activity = pep['activity'] + + has_known_activity = activity in ACTIVITIES + has_known_subactivity = (has_known_activity and ('subactivity' in pep) + and (pep['subactivity'] in ACTIVITIES[activity])) + + if has_known_activity: + if has_known_subactivity: + subactivity = pep['subactivity'] + return gtkgui_helpers.load_activity_icon(activity, subactivity).get_pixbuf() + else: + return gtkgui_helpers.load_activity_icon(activity).get_pixbuf() + else: + return gtkgui_helpers.load_activity_icon('unknown').get_pixbuf() + + def asMarkupText(self): + assert not self._retracted + pep = self._pep_specific_data + activity = pep['activity'] + subactivity = pep['subactivity'] if 'subactivity' in pep else None + text = pep['text'] if 'text' in pep else None + + if activity in ACTIVITIES: + # Translate standard activities + if subactivity in ACTIVITIES[activity]: + subactivity = ACTIVITIES[activity][subactivity] + activity = ACTIVITIES[activity]['category'] + + markuptext = '' + gobject.markup_escape_text(activity) + if subactivity: + markuptext += ': ' + gobject.markup_escape_text(subactivity) + markuptext += '' + if text: + markuptext += ' (%s)' % gobject.markup_escape_text(text) + return markuptext + - if activity in ACTIVITIES: - # Translate standard activities - if subactivity in ACTIVITIES[activity]: - subactivity = ACTIVITIES[activity][subactivity] - activity = ACTIVITIES[activity]['category'] - - markuptext = '' + gobject.markup_escape_text(activity) - if subactivity: - markuptext += ': ' + gobject.markup_escape_text(subactivity) - markuptext += '' - if text: - markuptext += ' (%s)' % gobject.markup_escape_text(text) - return markuptext - - class UserNicknamePEP(AbstractPEP): - '''XEP-0172: User Nickname''' - - type = 'nickname' - namespace = xmpp.NS_NICK - - def _extract_info(self, items): - nick = '' - for item in items.getTags('item'): - child = item.getTag('nick') - if child: - nick = child.getData() - break - - retracted = items.getTag('retract') or not nick - return (nick, retracted) - - def _update_contacts(self, jid, account): - nick = '' if self._retracted else self._pep_specific_data - for contact in gajim.contacts.get_contacts(account, jid): - contact.contact_name = nick - - def _update_account(self, account): - if self._retracted: - gajim.nicks[account] = gajim.config.get_per('accounts', account, 'name') - else: - gajim.nicks[account] = self._pep_specific_data + '''XEP-0172: User Nickname''' + + type = 'nickname' + namespace = xmpp.NS_NICK + + def _extract_info(self, items): + nick = '' + for item in items.getTags('item'): + child = item.getTag('nick') + if child: + nick = child.getData() + break + + retracted = items.getTag('retract') or not nick + return (nick, retracted) + + def _update_contacts(self, jid, account): + nick = '' if self._retracted else self._pep_specific_data + for contact in gajim.contacts.get_contacts(account, jid): + contact.contact_name = nick + + def _update_account(self, account): + if self._retracted: + gajim.nicks[account] = gajim.config.get_per('accounts', account, 'name') + else: + gajim.nicks[account] = self._pep_specific_data + - SUPPORTED_PERSONAL_USER_EVENTS = [UserMoodPEP, UserTunePEP, UserActivityPEP, - UserNicknamePEP] + UserNicknamePEP] class ConnectionPEP(object): - def __init__(self, account, dispatcher, pubsub_connection): - self._account = account - self._dispatcher = dispatcher - self._pubsub_connection = pubsub_connection - - def _pubsubEventCB(self, xmpp_dispatcher, msg): - ''' Called when we receive with pubsub event. ''' - if not msg.getTag('event'): - return - if msg.getTag('error'): - log.debug('PubsubEventCB received error stanza. Ignoring') - raise xmpp.NodeProcessed - - jid = helpers.get_full_jid_from_iq(msg) - event_tag = msg.getTag('event') + def __init__(self, account, dispatcher, pubsub_connection): + self._account = account + self._dispatcher = dispatcher + self._pubsub_connection = pubsub_connection - for pep_class in SUPPORTED_PERSONAL_USER_EVENTS: - pep = pep_class.get_tag_as_PEP(jid, self._account, event_tag) - if pep: - self._dispatcher.dispatch('PEP_RECEIVED', (jid, pep.type)) - - items = event_tag.getTag('items') - if items: - for item in items.getTags('item'): - entry = item.getTag('entry') - if entry: - # for each entry in feed (there shouldn't be more than one, - # but to be sure... - self._dispatcher.dispatch('ATOM_ENTRY', - (atom.OldEntry(node=entry),)) - - raise xmpp.NodeProcessed - - def send_activity(self, activity, subactivity=None, message=None): - if not self.pep_supported: - return - item = xmpp.Node('activity', {'xmlns': xmpp.NS_ACTIVITY}) - if activity: - i = item.addChild(activity) - if subactivity: - i.addChild(subactivity) - if message: - i = item.addChild('text') - i.addData(message) - self._pubsub_connection.send_pb_publish('', xmpp.NS_ACTIVITY, item, '0') - - def retract_activity(self): - if not self.pep_supported: - return - # not all server support retract, so send empty pep first - self.send_activity(None) - self._pubsub_connection.send_pb_retract('', xmpp.NS_ACTIVITY, '0') + def _pubsubEventCB(self, xmpp_dispatcher, msg): + ''' Called when we receive with pubsub event. ''' + if not msg.getTag('event'): + return + if msg.getTag('error'): + log.debug('PubsubEventCB received error stanza. Ignoring') + raise xmpp.NodeProcessed - def send_mood(self, mood, message=None): - if not self.pep_supported: - return - item = xmpp.Node('mood', {'xmlns': xmpp.NS_MOOD}) - if mood: - item.addChild(mood) - if message: - i = item.addChild('text') - i.addData(message) - self._pubsub_connection.send_pb_publish('', xmpp.NS_MOOD, item, '0') - - def retract_mood(self): - if not self.pep_supported: - return - self.send_mood(None) - self._pubsub_connection.send_pb_retract('', xmpp.NS_MOOD, '0') - - def send_tune(self, artist='', title='', source='', track=0, length=0, - items=None): - if not self.pep_supported: - return - item = xmpp.Node('tune', {'xmlns': xmpp.NS_TUNE}) - if artist: - i = item.addChild('artist') - i.addData(artist) - if title: - i = item.addChild('title') - i.addData(title) - if source: - i = item.addChild('source') - i.addData(source) - if track: - i = item.addChild('track') - i.addData(track) - if length: - i = item.addChild('length') - i.addData(length) - if items: - item.addChild(payload=items) - self._pubsub_connection.send_pb_publish('', xmpp.NS_TUNE, item, '0') + jid = helpers.get_full_jid_from_iq(msg) + event_tag = msg.getTag('event') - def retract_tune(self): - if not self.pep_supported: - return - # not all server support retract, so send empty pep first - self.send_tune(None) - self._pubsub_connection.send_pb_retract('', xmpp.NS_TUNE, '0') + for pep_class in SUPPORTED_PERSONAL_USER_EVENTS: + pep = pep_class.get_tag_as_PEP(jid, self._account, event_tag) + if pep: + self._dispatcher.dispatch('PEP_RECEIVED', (jid, pep.type)) - def send_nickname(self, nick): - if not self.pep_supported: - return - item = xmpp.Node('nick', {'xmlns': xmpp.NS_NICK}) - item.addData(nick) - self._pubsub_connection.send_pb_publish('', xmpp.NS_NICK, item, '0') + items = event_tag.getTag('items') + if items: + for item in items.getTags('item'): + entry = item.getTag('entry') + if entry: + # for each entry in feed (there shouldn't be more than one, + # but to be sure... + self._dispatcher.dispatch('ATOM_ENTRY', + (atom.OldEntry(node=entry),)) - def retract_nickname(self): - if not self.pep_supported: - return - # not all server support retract, so send empty pep first - self.send_nickname(None) - self._pubsub_connection.send_pb_retract('', xmpp.NS_NICK, '0') + raise xmpp.NodeProcessed -# vim: se ts=3: + def send_activity(self, activity, subactivity=None, message=None): + if not self.pep_supported: + return + item = xmpp.Node('activity', {'xmlns': xmpp.NS_ACTIVITY}) + if activity: + i = item.addChild(activity) + if subactivity: + i.addChild(subactivity) + if message: + i = item.addChild('text') + i.addData(message) + self._pubsub_connection.send_pb_publish('', xmpp.NS_ACTIVITY, item, '0') + + def retract_activity(self): + if not self.pep_supported: + return + # not all server support retract, so send empty pep first + self.send_activity(None) + self._pubsub_connection.send_pb_retract('', xmpp.NS_ACTIVITY, '0') + + def send_mood(self, mood, message=None): + if not self.pep_supported: + return + item = xmpp.Node('mood', {'xmlns': xmpp.NS_MOOD}) + if mood: + item.addChild(mood) + if message: + i = item.addChild('text') + i.addData(message) + self._pubsub_connection.send_pb_publish('', xmpp.NS_MOOD, item, '0') + + def retract_mood(self): + if not self.pep_supported: + return + self.send_mood(None) + self._pubsub_connection.send_pb_retract('', xmpp.NS_MOOD, '0') + + def send_tune(self, artist='', title='', source='', track=0, length=0, + items=None): + if not self.pep_supported: + return + item = xmpp.Node('tune', {'xmlns': xmpp.NS_TUNE}) + if artist: + i = item.addChild('artist') + i.addData(artist) + if title: + i = item.addChild('title') + i.addData(title) + if source: + i = item.addChild('source') + i.addData(source) + if track: + i = item.addChild('track') + i.addData(track) + if length: + i = item.addChild('length') + i.addData(length) + if items: + item.addChild(payload=items) + self._pubsub_connection.send_pb_publish('', xmpp.NS_TUNE, item, '0') + + def retract_tune(self): + if not self.pep_supported: + return + # not all server support retract, so send empty pep first + self.send_tune(None) + self._pubsub_connection.send_pb_retract('', xmpp.NS_TUNE, '0') + + def send_nickname(self, nick): + if not self.pep_supported: + return + item = xmpp.Node('nick', {'xmlns': xmpp.NS_NICK}) + item.addData(nick) + self._pubsub_connection.send_pb_publish('', xmpp.NS_NICK, item, '0') + + def retract_nickname(self): + if not self.pep_supported: + return + # not all server support retract, so send empty pep first + self.send_nickname(None) + self._pubsub_connection.send_pb_retract('', xmpp.NS_NICK, '0') diff --git a/src/common/proxy65_manager.py b/src/common/proxy65_manager.py index b1bbea79d..703823e47 100644 --- a/src/common/proxy65_manager.py +++ b/src/common/proxy65_manager.py @@ -41,400 +41,398 @@ S_FINISHED = 4 CONNECT_TIMEOUT = 20 class Proxy65Manager: - ''' keep records for file transfer proxies. Each time account - establishes a connection to its server call proxy65manger.resolve(proxy) - for every proxy that is convigured within the account. The class takes - care to resolve and test each proxy only once.''' - def __init__(self, idlequeue): - # dict {proxy: proxy properties} - self.idlequeue = idlequeue - self.proxies = {} - # dict {account: proxy} default proxy for account - self.default_proxies = {} + ''' keep records for file transfer proxies. Each time account + establishes a connection to its server call proxy65manger.resolve(proxy) + for every proxy that is convigured within the account. The class takes + care to resolve and test each proxy only once.''' + def __init__(self, idlequeue): + # dict {proxy: proxy properties} + self.idlequeue = idlequeue + self.proxies = {} + # dict {account: proxy} default proxy for account + self.default_proxies = {} - def resolve(self, proxy, connection, sender_jid, default=None): - ''' start ''' - if proxy in self.proxies: - resolver = self.proxies[proxy] - else: - # proxy is being ressolved for the first time - resolver = ProxyResolver(proxy, sender_jid) - self.proxies[proxy] = resolver - resolver.add_connection(connection) - if default: - # add this proxy as default for account - self.default_proxies[default] = proxy + def resolve(self, proxy, connection, sender_jid, default=None): + ''' start ''' + if proxy in self.proxies: + resolver = self.proxies[proxy] + else: + # proxy is being ressolved for the first time + resolver = ProxyResolver(proxy, sender_jid) + self.proxies[proxy] = resolver + resolver.add_connection(connection) + if default: + # add this proxy as default for account + self.default_proxies[default] = proxy - def disconnect(self, connection): - for resolver in self.proxies.values(): - resolver.disconnect(connection) + def disconnect(self, connection): + for resolver in self.proxies.values(): + resolver.disconnect(connection) - def resolve_result(self, proxy, query): - if proxy not in self.proxies: - return - jid = None - for item in query.getChildren(): - if item.getName() == 'streamhost': - host = item.getAttr('host') - port = item.getAttr('port') - jid = item.getAttr('jid') - self.proxies[proxy].resolve_result(host, port, jid) - # we can have only one streamhost - raise common.xmpp.NodeProcessed + def resolve_result(self, proxy, query): + if proxy not in self.proxies: + return + jid = None + for item in query.getChildren(): + if item.getName() == 'streamhost': + host = item.getAttr('host') + port = item.getAttr('port') + jid = item.getAttr('jid') + self.proxies[proxy].resolve_result(host, port, jid) + # we can have only one streamhost + raise common.xmpp.NodeProcessed - def error_cb(self, proxy, query): - sid = query.getAttr('sid') - for resolver in self.proxies.values(): - if resolver.sid == sid: - resolver.keep_conf() - break + def error_cb(self, proxy, query): + sid = query.getAttr('sid') + for resolver in self.proxies.values(): + if resolver.sid == sid: + resolver.keep_conf() + break - def get_default_for_name(self, account): - if account in self.default_proxies: - return self.default_proxies[account] + def get_default_for_name(self, account): + if account in self.default_proxies: + return self.default_proxies[account] - def get_proxy(self, proxy, account): - if proxy in self.proxies: - resolver = self.proxies[proxy] - if resolver.state == S_FINISHED: - return (resolver.host, resolver.port, resolver.jid) - return (None, 0, None) + def get_proxy(self, proxy, account): + if proxy in self.proxies: + resolver = self.proxies[proxy] + if resolver.state == S_FINISHED: + return (resolver.host, resolver.port, resolver.jid) + return (None, 0, None) class ProxyResolver: - def resolve_result(self, host, port, jid): - ''' test if host has a real proxy65 listening on port ''' - self.host = str(host) - self.port = int(port) - self.jid = unicode(jid) - self.state = S_RESOLVED - #FIXME: re-enable proxy testing - log.info('start resolving %s:%s' % (self.host, self.port)) - self.receiver_tester = ReceiverTester(self.host, self.port, self.jid, - self.sid, self.sender_jid, self._on_receiver_success, - self._on_connect_failure) - self.receiver_tester.connect() + def resolve_result(self, host, port, jid): + ''' test if host has a real proxy65 listening on port ''' + self.host = str(host) + self.port = int(port) + self.jid = unicode(jid) + self.state = S_RESOLVED + #FIXME: re-enable proxy testing + log.info('start resolving %s:%s' % (self.host, self.port)) + self.receiver_tester = ReceiverTester(self.host, self.port, self.jid, + self.sid, self.sender_jid, self._on_receiver_success, + self._on_connect_failure) + self.receiver_tester.connect() - def _on_receiver_success(self): - log.debug('Receiver successfully connected %s:%s' % (self.host, - self.port)) - self.host_tester = HostTester(self.host, self.port, self.jid, - self.sid, self.sender_jid, self._on_connect_success, - self._on_connect_failure) - self.host_tester.connect() + def _on_receiver_success(self): + log.debug('Receiver successfully connected %s:%s' % (self.host, + self.port)) + self.host_tester = HostTester(self.host, self.port, self.jid, + self.sid, self.sender_jid, self._on_connect_success, + self._on_connect_failure) + self.host_tester.connect() - def _on_connect_success(self): - log.debug('Host successfully connected %s:%s' % (self.host, self.port)) - iq = common.xmpp.Protocol(name='iq', to=self.jid, typ='set') - query = iq.setTag('query') - query.setNamespace(common.xmpp.NS_BYTESTREAM) - query.setAttr('sid', self.sid) + def _on_connect_success(self): + log.debug('Host successfully connected %s:%s' % (self.host, self.port)) + iq = common.xmpp.Protocol(name='iq', to=self.jid, typ='set') + query = iq.setTag('query') + query.setNamespace(common.xmpp.NS_BYTESTREAM) + query.setAttr('sid', self.sid) - activate = query.setTag('activate') - activate.setData('test@gajim.org/test2') + activate = query.setTag('activate') + activate.setData('test@gajim.org/test2') - if self.active_connection: - log.debug('Activating bytestream on %s:%s' % (self.host, self.port)) - self.active_connection.SendAndCallForResponse(iq, - self._result_received) - self.state = S_ACTIVATED - else: - self.state = S_INITIAL + if self.active_connection: + log.debug('Activating bytestream on %s:%s' % (self.host, self.port)) + self.active_connection.SendAndCallForResponse(iq, + self._result_received) + self.state = S_ACTIVATED + else: + self.state = S_INITIAL - def _result_received(self, data): - self.disconnect(self.active_connection) - if data.getType() == 'result': - self.keep_conf() - else: - self._on_connect_failure() + def _result_received(self, data): + self.disconnect(self.active_connection) + if data.getType() == 'result': + self.keep_conf() + else: + self._on_connect_failure() - def keep_conf(self): - log.debug('Bytestream activated %s:%s' % (self.host, self.port)) - self.state = S_FINISHED + def keep_conf(self): + log.debug('Bytestream activated %s:%s' % (self.host, self.port)) + self.state = S_FINISHED - def _on_connect_failure(self): - self.state = S_FINISHED - self.host = None - self.port = 0 - self.jid = None + def _on_connect_failure(self): + self.state = S_FINISHED + self.host = None + self.port = 0 + self.jid = None - def disconnect(self, connection): - if self.host_tester: - self.host_tester.disconnect() - self.host_tester = None - if self.receiver_tester: - self.receiver_tester.disconnect() - self.receiver_tester = None - try: - self.connections.remove(connection) - except ValueError: - pass - if connection == self.active_connection: - self.active_connection = None - if self.state != S_FINISHED: - self.state = S_INITIAL - self.try_next_connection() + def disconnect(self, connection): + if self.host_tester: + self.host_tester.disconnect() + self.host_tester = None + if self.receiver_tester: + self.receiver_tester.disconnect() + self.receiver_tester = None + try: + self.connections.remove(connection) + except ValueError: + pass + if connection == self.active_connection: + self.active_connection = None + if self.state != S_FINISHED: + self.state = S_INITIAL + self.try_next_connection() - def try_next_connection(self): - ''' try to resolve proxy with the next possible connection ''' - if self.connections: - connection = self.connections.pop(0) - self.start_resolve(connection) + def try_next_connection(self): + ''' try to resolve proxy with the next possible connection ''' + if self.connections: + connection = self.connections.pop(0) + self.start_resolve(connection) - def add_connection(self, connection): - ''' add a new connection in case the first fails ''' - self.connections.append(connection) - if self.state == S_INITIAL: - self.start_resolve(connection) + def add_connection(self, connection): + ''' add a new connection in case the first fails ''' + self.connections.append(connection) + if self.state == S_INITIAL: + self.start_resolve(connection) - def start_resolve(self, connection): - ''' request network address from proxy ''' - self.state = S_STARTED - self.active_connection = connection - iq = common.xmpp.Protocol(name='iq', to=self.proxy, typ='get') - query = iq.setTag('query') - query.setNamespace(common.xmpp.NS_BYTESTREAM) - connection.send(iq) + def start_resolve(self, connection): + ''' request network address from proxy ''' + self.state = S_STARTED + self.active_connection = connection + iq = common.xmpp.Protocol(name='iq', to=self.proxy, typ='get') + query = iq.setTag('query') + query.setNamespace(common.xmpp.NS_BYTESTREAM) + connection.send(iq) - def __init__(self, proxy, sender_jid): - self.proxy = proxy - self.state = S_INITIAL - self.active_connection = None - self.connections = [] - self.host_tester = None - self.receiver_tester = None - self.jid = None - self.host = None - self.port = None - self.sid = helpers.get_random_string_16() - self.sender_jid = sender_jid + def __init__(self, proxy, sender_jid): + self.proxy = proxy + self.state = S_INITIAL + self.active_connection = None + self.connections = [] + self.host_tester = None + self.receiver_tester = None + self.jid = None + self.host = None + self.port = None + self.sid = helpers.get_random_string_16() + self.sender_jid = sender_jid class HostTester(Socks5, IdleObject): - ''' fake proxy tester. ''' - def __init__(self, host, port, jid, sid, sender_jid, on_success, on_failure): - ''' try to establish and auth to proxy at (host, port) - call on_success, or on_failure according to the result''' - self.host = host - self.port = port - self.jid = jid - self.on_success = on_success - self.on_failure = on_failure - self._sock = None - self.file_props = {'is_a_proxy': True, - 'proxy_sender': sender_jid, - 'proxy_receiver': 'test@gajim.org/test2'} - Socks5.__init__(self, gajim.idlequeue, host, port, None, None, None) - self.sid = sid + ''' fake proxy tester. ''' + def __init__(self, host, port, jid, sid, sender_jid, on_success, on_failure): + ''' try to establish and auth to proxy at (host, port) + call on_success, or on_failure according to the result''' + self.host = host + self.port = port + self.jid = jid + self.on_success = on_success + self.on_failure = on_failure + self._sock = None + self.file_props = {'is_a_proxy': True, + 'proxy_sender': sender_jid, + 'proxy_receiver': 'test@gajim.org/test2'} + Socks5.__init__(self, gajim.idlequeue, host, port, None, None, None) + self.sid = sid - def connect(self): - ''' create the socket and plug it to the idlequeue ''' - if self.host is None: - self.on_failure() - return None - self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self._sock.setblocking(False) - self.fd = self._sock.fileno() - self.state = 0 # about to be connected - gajim.idlequeue.plug_idle(self, True, False) - self.do_connect() - self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - return None + def connect(self): + ''' create the socket and plug it to the idlequeue ''' + if self.host is None: + self.on_failure() + return None + self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._sock.setblocking(False) + self.fd = self._sock.fileno() + self.state = 0 # about to be connected + gajim.idlequeue.plug_idle(self, True, False) + self.do_connect() + self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + return None - def read_timeout(self): - self.idlequeue.remove_timeout(self.fd) - self.pollend() + def read_timeout(self): + self.idlequeue.remove_timeout(self.fd) + self.pollend() - def pollend(self): - self.disconnect() - self.on_failure() + def pollend(self): + self.disconnect() + self.on_failure() - def pollout(self): - self.idlequeue.remove_timeout(self.fd) - if self.state == 0: - self.do_connect() - return - elif self.state == 1: # send initially: version and auth types - data = self._get_auth_buff() - self.send_raw(data) - else: - return - self.state += 1 - # unplug and plug for reading - gajim.idlequeue.plug_idle(self, False, True) - gajim.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + def pollout(self): + self.idlequeue.remove_timeout(self.fd) + if self.state == 0: + self.do_connect() + return + elif self.state == 1: # send initially: version and auth types + data = self._get_auth_buff() + self.send_raw(data) + else: + return + self.state += 1 + # unplug and plug for reading + gajim.idlequeue.plug_idle(self, False, True) + gajim.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - def pollin(self): - self.idlequeue.remove_timeout(self.fd) - if self.state == 2: - self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - # begin negotiation. on success 'address' != 0 - buff = self.receive() - if buff == '': - # end connection - self.pollend() - return - # read auth response - if buff is None or len(buff) != 2: - return None - version, method = struct.unpack('!BB', buff[:2]) - if version != 0x05 or method == 0xff: - self.pollend() - return - data = self._get_request_buff(self._get_sha1_auth()) - self.send_raw(data) - self.state += 1 - log.debug('Host authenticating to %s:%s' % (self.host, self.port)) - elif self.state == 3: - log.debug('Host authenticated to %s:%s' % (self.host, self.port)) - self.on_success() - self.disconnect() - self.state += 1 - else: - assert False, 'unexpected state: %d' % self.state + def pollin(self): + self.idlequeue.remove_timeout(self.fd) + if self.state == 2: + self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + # begin negotiation. on success 'address' != 0 + buff = self.receive() + if buff == '': + # end connection + self.pollend() + return + # read auth response + if buff is None or len(buff) != 2: + return None + version, method = struct.unpack('!BB', buff[:2]) + if version != 0x05 or method == 0xff: + self.pollend() + return + data = self._get_request_buff(self._get_sha1_auth()) + self.send_raw(data) + self.state += 1 + log.debug('Host authenticating to %s:%s' % (self.host, self.port)) + elif self.state == 3: + log.debug('Host authenticated to %s:%s' % (self.host, self.port)) + self.on_success() + self.disconnect() + self.state += 1 + else: + assert False, 'unexpected state: %d' % self.state - def do_connect(self): - try: - self._sock.connect((self.host, self.port)) - self._sock.setblocking(False) - log.debug('Host Connecting to %s:%s' % (self.host, self.port)) - self._send = self._sock.send - self._recv = self._sock.recv - except Exception, ee: - errnum = ee[0] - # 56 is for freebsd - if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): - # still trying to connect - return - # win32 needs this - if errnum not in (0, 10056, errno.EISCONN): - # connection failed - self.on_failure() - return - # socket is already connected - self._sock.setblocking(False) - self._send = self._sock.send - self._recv = self._sock.recv - self.buff = '' - self.state = 1 # connected - log.debug('Host connected to %s:%s' % (self.host, self.port)) - self.idlequeue.plug_idle(self, True, False) - return + def do_connect(self): + try: + self._sock.connect((self.host, self.port)) + self._sock.setblocking(False) + log.debug('Host Connecting to %s:%s' % (self.host, self.port)) + self._send = self._sock.send + self._recv = self._sock.recv + except Exception, ee: + errnum = ee[0] + # 56 is for freebsd + if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): + # still trying to connect + return + # win32 needs this + if errnum not in (0, 10056, errno.EISCONN): + # connection failed + self.on_failure() + return + # socket is already connected + self._sock.setblocking(False) + self._send = self._sock.send + self._recv = self._sock.recv + self.buff = '' + self.state = 1 # connected + log.debug('Host connected to %s:%s' % (self.host, self.port)) + self.idlequeue.plug_idle(self, True, False) + return class ReceiverTester(Socks5, IdleObject): - ''' fake proxy tester. ''' - def __init__(self, host, port, jid, sid, sender_jid, on_success, on_failure): - ''' try to establish and auth to proxy at (host, port) - call on_success, or on_failure according to the result''' - self.host = host - self.port = port - self.jid = jid - self.on_success = on_success - self.on_failure = on_failure - self._sock = None - self.file_props = {'is_a_proxy': True, - 'proxy_sender': sender_jid, - 'proxy_receiver': 'test@gajim.org/test2'} - Socks5.__init__(self, gajim.idlequeue, host, port, None, None, None) - self.sid = sid + ''' fake proxy tester. ''' + def __init__(self, host, port, jid, sid, sender_jid, on_success, on_failure): + ''' try to establish and auth to proxy at (host, port) + call on_success, or on_failure according to the result''' + self.host = host + self.port = port + self.jid = jid + self.on_success = on_success + self.on_failure = on_failure + self._sock = None + self.file_props = {'is_a_proxy': True, + 'proxy_sender': sender_jid, + 'proxy_receiver': 'test@gajim.org/test2'} + Socks5.__init__(self, gajim.idlequeue, host, port, None, None, None) + self.sid = sid - def connect(self): - ''' create the socket and plug it to the idlequeue ''' - if self.host is None: - self.on_failure() - return None - self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self._sock.setblocking(False) - self.fd = self._sock.fileno() - self.state = 0 # about to be connected - gajim.idlequeue.plug_idle(self, True, False) - self.do_connect() - self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - return None + def connect(self): + ''' create the socket and plug it to the idlequeue ''' + if self.host is None: + self.on_failure() + return None + self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._sock.setblocking(False) + self.fd = self._sock.fileno() + self.state = 0 # about to be connected + gajim.idlequeue.plug_idle(self, True, False) + self.do_connect() + self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + return None - def read_timeout(self): - self.idlequeue.remove_timeout(self.fd) - self.pollend() + def read_timeout(self): + self.idlequeue.remove_timeout(self.fd) + self.pollend() - def pollend(self): - self.disconnect() - self.on_failure() + def pollend(self): + self.disconnect() + self.on_failure() - def pollout(self): - self.idlequeue.remove_timeout(self.fd) - if self.state == 0: - self.do_connect() - return - elif self.state == 1: # send initially: version and auth types - data = self._get_auth_buff() - self.send_raw(data) - else: - return - self.state += 1 - # unplug and plug for reading - gajim.idlequeue.plug_idle(self, False, True) - gajim.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + def pollout(self): + self.idlequeue.remove_timeout(self.fd) + if self.state == 0: + self.do_connect() + return + elif self.state == 1: # send initially: version and auth types + data = self._get_auth_buff() + self.send_raw(data) + else: + return + self.state += 1 + # unplug and plug for reading + gajim.idlequeue.plug_idle(self, False, True) + gajim.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - def pollin(self): - self.idlequeue.remove_timeout(self.fd) - if self.state in (2, 3): - self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - # begin negotiation. on success 'address' != 0 - buff = self.receive() - if buff == '': - # end connection - self.pollend() - return - if self.state == 2: - # read auth response - if buff is None or len(buff) != 2: - return None - version, method = struct.unpack('!BB', buff[:2]) - if version != 0x05 or method == 0xff: - self.pollend() - return - log.debug('Receiver authenticating to %s:%s' % (self.host, self.port)) - data = self._get_request_buff(self._get_sha1_auth()) - self.send_raw(data) - self.state += 1 - elif self.state == 3: - # read connect response - if buff is None or len(buff) < 2: - return None - version, reply = struct.unpack('!BB', buff[:2]) - if version != 0x05 or reply != 0x00: - self.pollend() - return - log.debug('Receiver authenticated to %s:%s' % (self.host, self.port)) - self.on_success() - self.disconnect() - self.state += 1 - else: - assert False, 'unexpected state: %d' % self.state + def pollin(self): + self.idlequeue.remove_timeout(self.fd) + if self.state in (2, 3): + self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + # begin negotiation. on success 'address' != 0 + buff = self.receive() + if buff == '': + # end connection + self.pollend() + return + if self.state == 2: + # read auth response + if buff is None or len(buff) != 2: + return None + version, method = struct.unpack('!BB', buff[:2]) + if version != 0x05 or method == 0xff: + self.pollend() + return + log.debug('Receiver authenticating to %s:%s' % (self.host, self.port)) + data = self._get_request_buff(self._get_sha1_auth()) + self.send_raw(data) + self.state += 1 + elif self.state == 3: + # read connect response + if buff is None or len(buff) < 2: + return None + version, reply = struct.unpack('!BB', buff[:2]) + if version != 0x05 or reply != 0x00: + self.pollend() + return + log.debug('Receiver authenticated to %s:%s' % (self.host, self.port)) + self.on_success() + self.disconnect() + self.state += 1 + else: + assert False, 'unexpected state: %d' % self.state - def do_connect(self): - try: - self._sock.setblocking(False) - self._sock.connect((self.host, self.port)) - log.debug('Receiver Connecting to %s:%s' % (self.host, self.port)) - self._send = self._sock.send - self._recv = self._sock.recv - except Exception, ee: - errnum = ee[0] - # 56 is for freebsd - if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): - # still trying to connect - return - # win32 needs this - if errnum not in (0, 10056, errno.EISCONN): - # connection failed - self.on_failure() - return - # socket is already connected - self._sock.setblocking(False) - self._send = self._sock.send - self._recv = self._sock.recv - self.buff = '' - self.state = 1 # connected - log.debug('Receiver connected to %s:%s' % (self.host, self.port)) - self.idlequeue.plug_idle(self, True, False) - -# vim: se ts=3: + def do_connect(self): + try: + self._sock.setblocking(False) + self._sock.connect((self.host, self.port)) + log.debug('Receiver Connecting to %s:%s' % (self.host, self.port)) + self._send = self._sock.send + self._recv = self._sock.recv + except Exception, ee: + errnum = ee[0] + # 56 is for freebsd + if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): + # still trying to connect + return + # win32 needs this + if errnum not in (0, 10056, errno.EISCONN): + # connection failed + self.on_failure() + return + # socket is already connected + self._sock.setblocking(False) + self._send = self._sock.send + self._recv = self._sock.recv + self.buff = '' + self.state = 1 # connected + log.debug('Receiver connected to %s:%s' % (self.host, self.port)) + self.idlequeue.plug_idle(self, True, False) diff --git a/src/common/pubsub.py b/src/common/pubsub.py index 8f65ff1a8..ad9060394 100644 --- a/src/common/pubsub.py +++ b/src/common/pubsub.py @@ -28,167 +28,165 @@ import logging log = logging.getLogger('gajim.c.pubsub') class ConnectionPubSub: - def __init__(self): - self.__callbacks={} + def __init__(self): + self.__callbacks={} - def send_pb_subscription_query(self, jid, cb, *args, **kwargs): - if not self.connection or self.connected < 2: - return - query = xmpp.Iq('get', to=jid) - pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) - pb.addChild('subscriptions') + def send_pb_subscription_query(self, jid, cb, *args, **kwargs): + if not self.connection or self.connected < 2: + return + query = xmpp.Iq('get', to=jid) + pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) + pb.addChild('subscriptions') - id_ = self.connection.send(query) + id_ = self.connection.send(query) - self.__callbacks[id_]=(cb, args, kwargs) + self.__callbacks[id_]=(cb, args, kwargs) - def send_pb_subscribe(self, jid, node, cb, *args, **kwargs): - if not self.connection or self.connected < 2: - return - our_jid = gajim.get_jid_from_account(self.name) - query = xmpp.Iq('set', to=jid) - pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) - pb.addChild('subscribe', {'node': node, 'jid': our_jid}) + def send_pb_subscribe(self, jid, node, cb, *args, **kwargs): + if not self.connection or self.connected < 2: + return + our_jid = gajim.get_jid_from_account(self.name) + query = xmpp.Iq('set', to=jid) + pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) + pb.addChild('subscribe', {'node': node, 'jid': our_jid}) - id_ = self.connection.send(query) + id_ = self.connection.send(query) - self.__callbacks[id_]=(cb, args, kwargs) + self.__callbacks[id_]=(cb, args, kwargs) - def send_pb_unsubscribe(self, jid, node, cb, *args, **kwargs): - if not self.connection or self.connected < 2: - return - our_jid = gajim.get_jid_from_account(self.name) - query = xmpp.Iq('set', to=jid) - pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) - pb.addChild('unsubscribe', {'node': node, 'jid': our_jid}) + def send_pb_unsubscribe(self, jid, node, cb, *args, **kwargs): + if not self.connection or self.connected < 2: + return + our_jid = gajim.get_jid_from_account(self.name) + query = xmpp.Iq('set', to=jid) + pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) + pb.addChild('unsubscribe', {'node': node, 'jid': our_jid}) - id_ = self.connection.send(query) + id_ = self.connection.send(query) - self.__callbacks[id_]=(cb, args, kwargs) + self.__callbacks[id_]=(cb, args, kwargs) - def send_pb_publish(self, jid, node, item, id_, options=None): - '''Publish item to a node.''' - if not self.connection or self.connected < 2: - return - query = xmpp.Iq('set', to=jid) - e = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) - p = e.addChild('publish', {'node': node}) - p.addChild('item', {'id': id_}, [item]) - if options: - p = e.addChild('publish-options') - p.addChild(node=options) + def send_pb_publish(self, jid, node, item, id_, options=None): + '''Publish item to a node.''' + if not self.connection or self.connected < 2: + return + query = xmpp.Iq('set', to=jid) + e = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) + p = e.addChild('publish', {'node': node}) + p.addChild('item', {'id': id_}, [item]) + if options: + p = e.addChild('publish-options') + p.addChild(node=options) - self.connection.send(query) + self.connection.send(query) - def send_pb_retrieve(self, jid, node, cb=None, *args, **kwargs): - '''Get items from a node''' - if not self.connection or self.connected < 2: - return - query = xmpp.Iq('get', to=jid) - r = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) - r = r.addChild('items', {'node': node}) - id_ = self.connection.send(query) + def send_pb_retrieve(self, jid, node, cb=None, *args, **kwargs): + '''Get items from a node''' + if not self.connection or self.connected < 2: + return + query = xmpp.Iq('get', to=jid) + r = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) + r = r.addChild('items', {'node': node}) + id_ = self.connection.send(query) - if cb: - self.__callbacks[id_]=(cb, args, kwargs) + if cb: + self.__callbacks[id_]=(cb, args, kwargs) - def send_pb_retract(self, jid, node, id_): - '''Delete item from a node''' - if not self.connection or self.connected < 2: - return - query = xmpp.Iq('set', to=jid) - r = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) - r = r.addChild('retract', {'node': node, 'notify': '1'}) - r = r.addChild('item', {'id': id_}) + def send_pb_retract(self, jid, node, id_): + '''Delete item from a node''' + if not self.connection or self.connected < 2: + return + query = xmpp.Iq('set', to=jid) + r = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) + r = r.addChild('retract', {'node': node, 'notify': '1'}) + r = r.addChild('item', {'id': id_}) - self.connection.send(query) + self.connection.send(query) - def send_pb_delete(self, jid, node): - '''Deletes node.''' - if not self.connection or self.connected < 2: - return - query = xmpp.Iq('set', to=jid) - d = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) - d = d.addChild('delete', {'node': node}) + def send_pb_delete(self, jid, node): + '''Deletes node.''' + if not self.connection or self.connected < 2: + return + query = xmpp.Iq('set', to=jid) + d = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) + d = d.addChild('delete', {'node': node}) - def response(con, resp, jid, node): - if resp.getType() == 'result': - self.dispatch('PUBSUB_NODE_REMOVED', (jid, node)) - else: - msg = resp.getErrorMsg() - self.dispatch('PUBSUB_NODE_NOT_REMOVED', (jid, node, msg)) + def response(con, resp, jid, node): + if resp.getType() == 'result': + self.dispatch('PUBSUB_NODE_REMOVED', (jid, node)) + else: + msg = resp.getErrorMsg() + self.dispatch('PUBSUB_NODE_NOT_REMOVED', (jid, node, msg)) - self.connection.SendAndCallForResponse(query, response, {'jid': jid, - 'node': node}) + self.connection.SendAndCallForResponse(query, response, {'jid': jid, + 'node': node}) - def send_pb_create(self, jid, node, configure = False, configure_form = None): - '''Creates new node.''' - if not self.connection or self.connected < 2: - return - query = xmpp.Iq('set', to=jid) - c = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) - c = c.addChild('create', {'node': node}) - if configure: - conf = c.addChild('configure') - if configure_form is not None: - conf.addChild(node=configure_form) + def send_pb_create(self, jid, node, configure = False, configure_form = None): + '''Creates new node.''' + if not self.connection or self.connected < 2: + return + query = xmpp.Iq('set', to=jid) + c = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) + c = c.addChild('create', {'node': node}) + if configure: + conf = c.addChild('configure') + if configure_form is not None: + conf.addChild(node=configure_form) - self.connection.send(query) + self.connection.send(query) - def send_pb_configure(self, jid, node, form): - if not self.connection or self.connected < 2: - return - query = xmpp.Iq('set', to=jid) - c = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB_OWNER) - c = c.addChild('configure', {'node': node}) - c.addChild(node=form) + def send_pb_configure(self, jid, node, form): + if not self.connection or self.connected < 2: + return + query = xmpp.Iq('set', to=jid) + c = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB_OWNER) + c = c.addChild('configure', {'node': node}) + c.addChild(node=form) - self.connection.send(query) + self.connection.send(query) - def _PubSubCB(self, conn, stanza): - log.debug('_PubsubCB') - try: - cb, args, kwargs = self.__callbacks.pop(stanza.getID()) - cb(conn, stanza, *args, **kwargs) - except Exception: - pass + def _PubSubCB(self, conn, stanza): + log.debug('_PubsubCB') + try: + cb, args, kwargs = self.__callbacks.pop(stanza.getID()) + cb(conn, stanza, *args, **kwargs) + except Exception: + pass - pubsub = stanza.getTag('pubsub') - if not pubsub: - return - items = pubsub.getTag('items') - if not items: - return - item = items.getTag('item') - if not item: - return - storage = item.getTag('storage') - if storage: - ns = storage.getNamespace() - if ns == 'storage:bookmarks': - self._parse_bookmarks(storage, 'pubsub') + pubsub = stanza.getTag('pubsub') + if not pubsub: + return + items = pubsub.getTag('items') + if not items: + return + item = items.getTag('item') + if not item: + return + storage = item.getTag('storage') + if storage: + ns = storage.getNamespace() + if ns == 'storage:bookmarks': + self._parse_bookmarks(storage, 'pubsub') - def _PubSubErrorCB(self, conn, stanza): - log.debug('_PubsubErrorCB') - pubsub = stanza.getTag('pubsub') - if not pubsub: - return - items = pubsub.getTag('items') - if not items: - return - if items.getAttr('node') == 'storage:bookmarks': - # Receiving bookmarks from pubsub failed, so take them from xml - self.get_bookmarks(storage_type='xml') + def _PubSubErrorCB(self, conn, stanza): + log.debug('_PubsubErrorCB') + pubsub = stanza.getTag('pubsub') + if not pubsub: + return + items = pubsub.getTag('items') + if not items: + return + if items.getAttr('node') == 'storage:bookmarks': + # Receiving bookmarks from pubsub failed, so take them from xml + self.get_bookmarks(storage_type='xml') - def request_pb_configuration(self, jid, node): - if not self.connection or self.connected < 2: - return - query = xmpp.Iq('get', to=jid) - e = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB_OWNER) - e = e.addChild('configure', {'node': node}) - id_ = self.connection.getAnID() - query.setID(id_) - self.awaiting_answers[id_] = (connection_handlers.PEP_CONFIG,) - self.connection.send(query) - -# vim: se ts=3: + def request_pb_configuration(self, jid, node): + if not self.connection or self.connected < 2: + return + query = xmpp.Iq('get', to=jid) + e = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB_OWNER) + e = e.addChild('configure', {'node': node}) + id_ = self.connection.getAnID() + query.setID(id_) + self.awaiting_answers[id_] = (connection_handlers.PEP_CONFIG,) + self.connection.send(query) diff --git a/src/common/resolver.py b/src/common/resolver.py index b11abcd98..7c6826f8b 100644 --- a/src/common/resolver.py +++ b/src/common/resolver.py @@ -1,4 +1,4 @@ -## common/resolver.py +## common/resolver.py ## ## Copyright (C) 2006 Dimitur Kirov ## @@ -33,292 +33,290 @@ ns_type_pattern = re.compile('^[a-z]+$') host_pattern = re.compile('^[a-z0-9\-._]*[a-z0-9]\.[a-z]{2,}$') try: - #raise ImportError("Manually disabled libasync") - import libasyncns - USE_LIBASYNCNS = True - log.info("libasyncns-python loaded") + #raise ImportError("Manually disabled libasync") + import libasyncns + USE_LIBASYNCNS = True + log.info("libasyncns-python loaded") except ImportError: - USE_LIBASYNCNS = False - log.debug("Import of libasyncns-python failed, getaddrinfo will block", exc_info=True) + USE_LIBASYNCNS = False + log.debug("Import of libasyncns-python failed, getaddrinfo will block", exc_info=True) def get_resolver(idlequeue): - if USE_LIBASYNCNS: - return LibAsyncNSResolver() - else: - return NSLookupResolver(idlequeue) + if USE_LIBASYNCNS: + return LibAsyncNSResolver() + else: + return NSLookupResolver(idlequeue) class CommonResolver(): - def __init__(self): - # dict {"host+type" : list of records} - self.resolved_hosts = {} - # dict {"host+type" : list of callbacks} - self.handlers = {} + def __init__(self): + # dict {"host+type" : list of records} + self.resolved_hosts = {} + # dict {"host+type" : list of callbacks} + self.handlers = {} - def resolve(self, host, on_ready, type='srv'): - assert(type in ['srv', 'txt']) - if not host: - # empty host, return empty list of srv records - on_ready([]) - return - if self.resolved_hosts.has_key(host+type): - # host is already resolved, return cached values - on_ready(host, self.resolved_hosts[host+type]) - return - if self.handlers.has_key(host+type): - # host is about to be resolved by another connection, - # attach our callback - self.handlers[host+type].append(on_ready) - else: - # host has never been resolved, start now - self.handlers[host+type] = [on_ready] - self.start_resolve(host, type) + def resolve(self, host, on_ready, type='srv'): + assert(type in ['srv', 'txt']) + if not host: + # empty host, return empty list of srv records + on_ready([]) + return + if self.resolved_hosts.has_key(host+type): + # host is already resolved, return cached values + on_ready(host, self.resolved_hosts[host+type]) + return + if self.handlers.has_key(host+type): + # host is about to be resolved by another connection, + # attach our callback + self.handlers[host+type].append(on_ready) + else: + # host has never been resolved, start now + self.handlers[host+type] = [on_ready] + self.start_resolve(host, type) - def _on_ready(self, host, type, result_list): - # practically it is impossible to be the opposite, but who knows :) - if not self.resolved_hosts.has_key(host+type): - self.resolved_hosts[host+type] = result_list - if self.handlers.has_key(host+type): - for callback in self.handlers[host+type]: - callback(host, result_list) - del(self.handlers[host+type]) + def _on_ready(self, host, type, result_list): + # practically it is impossible to be the opposite, but who knows :) + if not self.resolved_hosts.has_key(host+type): + self.resolved_hosts[host+type] = result_list + if self.handlers.has_key(host+type): + for callback in self.handlers[host+type]: + callback(host, result_list) + del(self.handlers[host+type]) - def start_resolve(self, host, type): - pass + def start_resolve(self, host, type): + pass # FIXME: API usage is not consistent! This one requires that process is called class LibAsyncNSResolver(CommonResolver): - ''' - Asynchronous resolver using libasyncns-python. process() method has to be - called in order to proceed the pending requests. - Based on patch submitted by Damien Thebault. - ''' - def __init__(self): - self.asyncns = libasyncns.Asyncns() - CommonResolver.__init__(self) + ''' + Asynchronous resolver using libasyncns-python. process() method has to be + called in order to proceed the pending requests. + Based on patch submitted by Damien Thebault. + ''' + def __init__(self): + self.asyncns = libasyncns.Asyncns() + CommonResolver.__init__(self) - def start_resolve(self, host, type): - type = libasyncns.ns_t_srv - if type == 'txt': type = libasyncns.ns_t_txt - resq = self.asyncns.res_query(host, libasyncns.ns_c_in, type) - resq.userdata = {'host':host, 'type':type} + def start_resolve(self, host, type): + type = libasyncns.ns_t_srv + if type == 'txt': type = libasyncns.ns_t_txt + resq = self.asyncns.res_query(host, libasyncns.ns_c_in, type) + resq.userdata = {'host':host, 'type':type} - # getaddrinfo to be done - #def resolve_name(self, dname, callback): - #resq = self.asyncns.getaddrinfo(dname) - #resq.userdata = {'callback':callback, 'dname':dname} + # getaddrinfo to be done + #def resolve_name(self, dname, callback): + #resq = self.asyncns.getaddrinfo(dname) + #resq.userdata = {'callback':callback, 'dname':dname} - def _on_ready(self, host, type, result_list): - if type == libasyncns.ns_t_srv: type = 'srv' - elif type == libasyncns.ns_t_txt: type = 'txt' + def _on_ready(self, host, type, result_list): + if type == libasyncns.ns_t_srv: type = 'srv' + elif type == libasyncns.ns_t_txt: type = 'txt' - CommonResolver._on_ready(self, host, type, result_list) + CommonResolver._on_ready(self, host, type, result_list) - def process(self): - try: - self.asyncns.wait(False) - resq = self.asyncns.get_next() - except: - return True - if type(resq) == libasyncns.ResQuery: - # TXT or SRV result - while resq is not None: - try: - rl = resq.get_done() - except: - rl = [] - if rl: - for r in rl: - r['prio'] = r['pref'] - self._on_ready( - host = resq.userdata['host'], - type = resq.userdata['type'], - result_list = rl) - try: - resq = self.asyncns.get_next() - except: - resq = None - elif type(resq) == libasyncns.AddrInfoQuery: - # getaddrinfo result (A or AAAA) - rl = resq.get_done() - resq.userdata['callback'](resq.userdata['dname'], rl) - return True + def process(self): + try: + self.asyncns.wait(False) + resq = self.asyncns.get_next() + except: + return True + if type(resq) == libasyncns.ResQuery: + # TXT or SRV result + while resq is not None: + try: + rl = resq.get_done() + except: + rl = [] + if rl: + for r in rl: + r['prio'] = r['pref'] + self._on_ready( + host = resq.userdata['host'], + type = resq.userdata['type'], + result_list = rl) + try: + resq = self.asyncns.get_next() + except: + resq = None + elif type(resq) == libasyncns.AddrInfoQuery: + # getaddrinfo result (A or AAAA) + rl = resq.get_done() + resq.userdata['callback'](resq.userdata['dname'], rl) + return True class NSLookupResolver(CommonResolver): - ''' - Asynchronous DNS resolver calling nslookup. Processing of pending requests - is invoked from idlequeue which is watching file descriptor of pipe of stdout - of nslookup process. - ''' - def __init__(self, idlequeue): - self.idlequeue = idlequeue - self.process = False - CommonResolver.__init__(self) + ''' + Asynchronous DNS resolver calling nslookup. Processing of pending requests + is invoked from idlequeue which is watching file descriptor of pipe of stdout + of nslookup process. + ''' + def __init__(self, idlequeue): + self.idlequeue = idlequeue + self.process = False + CommonResolver.__init__(self) - def parse_srv_result(self, fqdn, result): - ''' parse the output of nslookup command and return list of - properties: 'host', 'port','weight', 'priority' corresponding to the found - srv hosts ''' - if os.name == 'nt': - return self._parse_srv_result_nt(fqdn, result) - elif os.name == 'posix': - return self._parse_srv_result_posix(fqdn, result) + def parse_srv_result(self, fqdn, result): + ''' parse the output of nslookup command and return list of + properties: 'host', 'port','weight', 'priority' corresponding to the found + srv hosts ''' + if os.name == 'nt': + return self._parse_srv_result_nt(fqdn, result) + elif os.name == 'posix': + return self._parse_srv_result_posix(fqdn, result) - def _parse_srv_result_nt(self, fqdn, result): - # output from win32 nslookup command - if not result: - return [] - hosts = [] - lines = result.replace('\r','').split('\n') - current_host = None - for line in lines: - line = line.lstrip() - if line == '': - continue - if line.startswith(fqdn): - rest = line[len(fqdn):] - if rest.find('service') > -1: - current_host = {} - elif isinstance(current_host, dict): - res = line.strip().split('=') - if len(res) != 2: - if len(current_host) == 4: - hosts.append(current_host) - current_host = None - continue - prop_type = res[0].strip() - prop_value = res[1].strip() - if prop_type.find('prio') > -1: - try: - current_host['prio'] = int(prop_value) - except ValueError: - continue - elif prop_type.find('weight') > -1: - try: - current_host['weight'] = int(prop_value) - except ValueError: - continue - elif prop_type.find('port') > -1: - try: - current_host['port'] = int(prop_value) - except ValueError: - continue - elif prop_type.find('host') > -1: - # strip '.' at the end of hostname - if prop_value[-1] == '.': - prop_value = prop_value[:-1] - current_host['host'] = prop_value - if len(current_host) == 4: - hosts.append(current_host) - current_host = None - return hosts + def _parse_srv_result_nt(self, fqdn, result): + # output from win32 nslookup command + if not result: + return [] + hosts = [] + lines = result.replace('\r', '').split('\n') + current_host = None + for line in lines: + line = line.lstrip() + if line == '': + continue + if line.startswith(fqdn): + rest = line[len(fqdn):] + if rest.find('service') > -1: + current_host = {} + elif isinstance(current_host, dict): + res = line.strip().split('=') + if len(res) != 2: + if len(current_host) == 4: + hosts.append(current_host) + current_host = None + continue + prop_type = res[0].strip() + prop_value = res[1].strip() + if prop_type.find('prio') > -1: + try: + current_host['prio'] = int(prop_value) + except ValueError: + continue + elif prop_type.find('weight') > -1: + try: + current_host['weight'] = int(prop_value) + except ValueError: + continue + elif prop_type.find('port') > -1: + try: + current_host['port'] = int(prop_value) + except ValueError: + continue + elif prop_type.find('host') > -1: + # strip '.' at the end of hostname + if prop_value[-1] == '.': + prop_value = prop_value[:-1] + current_host['host'] = prop_value + if len(current_host) == 4: + hosts.append(current_host) + current_host = None + return hosts - def _parse_srv_result_posix(self, fqdn, result): - # typical output of bind-tools nslookup command: - # _xmpp-client._tcp.jabber.org service = 30 30 5222 jabber.org. - if not result: - return [] - ufqdn = helpers.ascii_to_idn(fqdn) # Unicode domain name - hosts = [] - lines = result.split('\n') - for line in lines: - if line == '': - continue - domain = None - if line.startswith(fqdn): - domain = fqdn # For nslookup 9.5 - elif helpers.decode_string(line).startswith(ufqdn): - line = helpers.decode_string(line) - domain = ufqdn # For nslookup 9.6 - if domain: - rest = line[len(domain):].split('=') - if len(rest) != 2: - continue - answer_type, props_str = rest - if answer_type.strip() != 'service': - continue - props = props_str.strip().split(' ') - if len(props) < 4: - continue - prio, weight, port, host = props[-4:] - if host[-1] == '.': - host = host[:-1] - try: - prio = int(prio) - weight = int(weight) - port = int(port) - except ValueError: - continue - hosts.append({'host': host, 'port': port, 'weight': weight, - 'prio': prio}) - return hosts + def _parse_srv_result_posix(self, fqdn, result): + # typical output of bind-tools nslookup command: + # _xmpp-client._tcp.jabber.org service = 30 30 5222 jabber.org. + if not result: + return [] + ufqdn = helpers.ascii_to_idn(fqdn) # Unicode domain name + hosts = [] + lines = result.split('\n') + for line in lines: + if line == '': + continue + domain = None + if line.startswith(fqdn): + domain = fqdn # For nslookup 9.5 + elif helpers.decode_string(line).startswith(ufqdn): + line = helpers.decode_string(line) + domain = ufqdn # For nslookup 9.6 + if domain: + rest = line[len(domain):].split('=') + if len(rest) != 2: + continue + answer_type, props_str = rest + if answer_type.strip() != 'service': + continue + props = props_str.strip().split(' ') + if len(props) < 4: + continue + prio, weight, port, host = props[-4:] + if host[-1] == '.': + host = host[:-1] + try: + prio = int(prio) + weight = int(weight) + port = int(port) + except ValueError: + continue + hosts.append({'host': host, 'port': port, 'weight': weight, + 'prio': prio}) + return hosts - def _on_ready(self, host, type, result): - # nslookup finished, parse the result and call the handlers - result_list = self.parse_srv_result(host, result) - CommonResolver._on_ready(self, host, type, result_list) + def _on_ready(self, host, type, result): + # nslookup finished, parse the result and call the handlers + result_list = self.parse_srv_result(host, result) + CommonResolver._on_ready(self, host, type, result_list) - def start_resolve(self, host, type): - ''' spawn new nslookup process and start waiting for results ''' - ns = NsLookup(self._on_ready, host, type) - ns.set_idlequeue(self.idlequeue) - ns.commandtimeout = 10 - ns.start() + def start_resolve(self, host, type): + ''' spawn new nslookup process and start waiting for results ''' + ns = NsLookup(self._on_ready, host, type) + ns.set_idlequeue(self.idlequeue) + ns.commandtimeout = 10 + ns.start() class NsLookup(IdleCommand): - def __init__(self, on_result, host='_xmpp-client', type='srv'): - IdleCommand.__init__(self, on_result) - self.commandtimeout = 10 - self.host = host.lower() - self.type = type.lower() - if not host_pattern.match(self.host): - # invalid host name - log.error('Invalid host: %s' % self.host) - self.canexecute = False - return - if not ns_type_pattern.match(self.type): - log.error('Invalid querytype: %s' % self.type) - self.canexecute = False - return + def __init__(self, on_result, host='_xmpp-client', type='srv'): + IdleCommand.__init__(self, on_result) + self.commandtimeout = 10 + self.host = host.lower() + self.type = type.lower() + if not host_pattern.match(self.host): + # invalid host name + log.error('Invalid host: %s' % self.host) + self.canexecute = False + return + if not ns_type_pattern.match(self.type): + log.error('Invalid querytype: %s' % self.type) + self.canexecute = False + return - def _compose_command_args(self): - return ['nslookup', '-type=' + self.type , self.host] + def _compose_command_args(self): + return ['nslookup', '-type=' + self.type, self.host] - def _return_result(self): - if self.result_handler: - self.result_handler(self.host, self.type, self.result) - self.result_handler = None + def _return_result(self): + if self.result_handler: + self.result_handler(self.host, self.type, self.result) + self.result_handler = None # below lines is on how to use API and assist in testing if __name__ == '__main__': - import gobject - import gtk - from xmpp import idlequeue + import gobject + import gtk + from xmpp import idlequeue - idlequeue = idlequeue.get_idlequeue() - resolver = get_resolver(idlequeue) + idlequeue = idlequeue.get_idlequeue() + resolver = get_resolver(idlequeue) - def clicked(widget): - global resolver - host = text_view.get_text() - def on_result(host, result_array): - print 'Result:\n' + repr(result_array) - resolver.resolve(host, on_result) - win = gtk.Window() - win.set_border_width(6) - text_view = gtk.Entry() - text_view.set_text('_xmpp-client._tcp.jabber.org') - hbox = gtk.HBox() - hbox.set_spacing(3) - but = gtk.Button(' Lookup SRV ') - hbox.pack_start(text_view, 5) - hbox.pack_start(but, 0) - but.connect('clicked', clicked) - win.add(hbox) - win.show_all() - gobject.timeout_add(200, idlequeue.process) - gtk.main() - -# vim: se ts=3: + def clicked(widget): + global resolver + host = text_view.get_text() + def on_result(host, result_array): + print 'Result:\n' + repr(result_array) + resolver.resolve(host, on_result) + win = gtk.Window() + win.set_border_width(6) + text_view = gtk.Entry() + text_view.set_text('_xmpp-client._tcp.jabber.org') + hbox = gtk.HBox() + hbox.set_spacing(3) + but = gtk.Button(' Lookup SRV ') + hbox.pack_start(text_view, 5) + hbox.pack_start(but, 0) + but.connect('clicked', clicked) + win.add(hbox) + win.show_all() + gobject.timeout_add(200, idlequeue.process) + gtk.main() diff --git a/src/common/rst_xhtml_generator.py b/src/common/rst_xhtml_generator.py index 8f57bb698..fe572e054 100644 --- a/src/common/rst_xhtml_generator.py +++ b/src/common/rst_xhtml_generator.py @@ -22,131 +22,131 @@ ## try: - from docutils import io - from docutils.core import Publisher - from docutils.parsers.rst import roles - from docutils import nodes,utils - from docutils.parsers.rst.roles import set_classes + from docutils import io + from docutils.core import Publisher + from docutils.parsers.rst import roles + from docutils import nodes, utils + from docutils.parsers.rst.roles import set_classes except ImportError: - print "Requires docutils 0.4 for set_classes to be available" - def create_xhtml(text): - return None + print "Requires docutils 0.4 for set_classes to be available" + def create_xhtml(text): + return None else: - def pos_int_validator(text): - """Validates that text can be evaluated as a positive integer.""" - result = int(text) - if result < 0: - raise ValueError("Error: value '%(text)s' " - "must be a positive integer") - return result + def pos_int_validator(text): + """Validates that text can be evaluated as a positive integer.""" + result = int(text) + if result < 0: + raise ValueError("Error: value '%(text)s' " + "must be a positive integer") + return result - def generate_uri_role( role_name, aliases, - anchor_text, base_url, - interpret_url, validator): - '''Creates and register a uri based "interpreted role". + def generate_uri_role( role_name, aliases, + anchor_text, base_url, + interpret_url, validator): + '''Creates and register a uri based "interpreted role". - Those are similar to the RFC, and PEP ones, and take - role_name: - name that will be registered - aliases: - list of alternate names - anchor_text: - text that will be used, together with the role - base_url: - base url for the link - interpret_url: - this, modulo the validated text, will be added to it - validator: - should return the validated text, or raise ValueError - ''' - def uri_reference_role(role, rawtext, text, lineno, inliner, - options={}, content=[]): - try: - valid_text = validator(text) - except ValueError, e: - msg = inliner.reporter.error( e.message % dict(text=text), line=lineno) - prb = inliner.problematic(rawtext, rawtext, msg) - return [prb], [msg] - ref = base_url + interpret_url % valid_text - set_classes(options) - node = nodes.reference(rawtext, anchor_text + utils.unescape(text), refuri=ref, - **options) - return [node], [] + Those are similar to the RFC, and PEP ones, and take + role_name: + name that will be registered + aliases: + list of alternate names + anchor_text: + text that will be used, together with the role + base_url: + base url for the link + interpret_url: + this, modulo the validated text, will be added to it + validator: + should return the validated text, or raise ValueError + ''' + def uri_reference_role(role, rawtext, text, lineno, inliner, + options={}, content=[]): + try: + valid_text = validator(text) + except ValueError, e: + msg = inliner.reporter.error( e.message % dict(text=text), line=lineno) + prb = inliner.problematic(rawtext, rawtext, msg) + return [prb], [msg] + ref = base_url + interpret_url % valid_text + set_classes(options) + node = nodes.reference(rawtext, anchor_text + utils.unescape(text), refuri=ref, + **options) + return [node], [] - uri_reference_role.__doc__ = """Role to make handy references to URIs. + uri_reference_role.__doc__ = """Role to make handy references to URIs. - Use as :%(role_name)s:`71` (or any of %(aliases)s). - It will use %(base_url)s+%(interpret_url)s - validator should throw a ValueError, containing optionally - a %%(text)s format, if the interpreted text is not valid. - """ % locals() - roles.register_canonical_role(role_name, uri_reference_role) - from docutils.parsers.rst.languages import en - en.roles[role_name] = role_name - for alias in aliases: - en.roles[alias] = role_name + Use as :%(role_name)s:`71` (or any of %(aliases)s). + It will use %(base_url)s+%(interpret_url)s + validator should throw a ValueError, containing optionally + a %%(text)s format, if the interpreted text is not valid. + """ % locals() + roles.register_canonical_role(role_name, uri_reference_role) + from docutils.parsers.rst.languages import en + en.roles[role_name] = role_name + for alias in aliases: + en.roles[alias] = role_name - generate_uri_role('xep-reference', ('jep', 'xep'), - 'XEP #', 'http://www.xmpp.org/extensions/', 'xep-%04d.html', - pos_int_validator) - generate_uri_role('gajim-ticket-reference', ('ticket','gtrack'), - 'Gajim Ticket #', 'http://trac.gajim.org/ticket/', '%d', - pos_int_validator) + generate_uri_role('xep-reference', ('jep', 'xep'), + 'XEP #', 'http://www.xmpp.org/extensions/', 'xep-%04d.html', + pos_int_validator) + generate_uri_role('gajim-ticket-reference', ('ticket', 'gtrack'), + 'Gajim Ticket #', 'http://trac.gajim.org/ticket/', '%d', + pos_int_validator) - class HTMLGenerator: - '''Really simple HTMLGenerator starting from publish_parts. + class HTMLGenerator: + '''Really simple HTMLGenerator starting from publish_parts. - It reuses the docutils.core.Publisher class, which means it is *not* - threadsafe. - ''' - def __init__(self, - settings_spec=None, - settings_overrides=dict(report_level=5, halt_level=5), - config_section='general'): - self.pub = Publisher(reader=None, parser=None, writer=None, - settings=None, - source_class=io.StringInput, - destination_class=io.StringOutput) - self.pub.set_components(reader_name='standalone', - parser_name='restructuredtext', - writer_name='html') - # hack: JEP-0071 does not allow HTML char entities, so we hack our way - # out of it. - # — == u"\u2014" - # a setting to only emit charater entities in the writer would be nice - # FIXME: several   are emitted, and they are explicitly forbidden - # in the JEP - #   == u"\u00a0" - self.pub.writer.translator_class.attribution_formats['dash'] = ( - u'\u2014', '') - self.pub.process_programmatic_settings(settings_spec, - settings_overrides, - config_section) + It reuses the docutils.core.Publisher class, which means it is *not* + threadsafe. + ''' + def __init__(self, + settings_spec=None, + settings_overrides=dict(report_level=5, halt_level=5), + config_section='general'): + self.pub = Publisher(reader=None, parser=None, writer=None, + settings=None, + source_class=io.StringInput, + destination_class=io.StringOutput) + self.pub.set_components(reader_name='standalone', + parser_name='restructuredtext', + writer_name='html') + # hack: JEP-0071 does not allow HTML char entities, so we hack our way + # out of it. + # — == u"\u2014" + # a setting to only emit charater entities in the writer would be nice + # FIXME: several   are emitted, and they are explicitly forbidden + # in the JEP + #   == u"\u00a0" + self.pub.writer.translator_class.attribution_formats['dash'] = ( + u'\u2014', '') + self.pub.process_programmatic_settings(settings_spec, + settings_overrides, + config_section) - def create_xhtml(self, text, - destination=None, - destination_path=None, - enable_exit_status=None): - ''' Create xhtml for a fragment of IM dialog. - We can use the source_name to store info about - the message.''' - self.pub.set_source(text, None) - self.pub.set_destination(destination, destination_path) - output = self.pub.publish(enable_exit_status=enable_exit_status) - # kludge until we can get docutils to stop generating (rare)   - # entities - return u'\u00a0'.join(self.pub.writer.parts['fragment'].strip().split( - ' ')) + def create_xhtml(self, text, + destination=None, + destination_path=None, + enable_exit_status=None): + ''' Create xhtml for a fragment of IM dialog. + We can use the source_name to store info about + the message.''' + self.pub.set_source(text, None) + self.pub.set_destination(destination, destination_path) + output = self.pub.publish(enable_exit_status=enable_exit_status) + # kludge until we can get docutils to stop generating (rare)   + # entities + return u'\u00a0'.join(self.pub.writer.parts['fragment'].strip().split( + ' ')) - Generator = HTMLGenerator() + Generator = HTMLGenerator() - def create_xhtml(text): - return Generator.create_xhtml(text) + def create_xhtml(text): + return Generator.create_xhtml(text) if __name__ == '__main__': - print "test 1\n", Generator.create_xhtml(""" + print "test 1\n", Generator.create_xhtml(""" test:: >>> print 1 @@ -157,11 +157,9 @@ test:: this `` should trigger`` should trigger the   problem. """) - print "test 2\n", Generator.create_xhtml(""" + print "test 2\n", Generator.create_xhtml(""" *test1 test2_ """) - print "test 3\n", Generator.create_xhtml(""":ticket:`316` implements :xep:`71`""") - -# vim: se ts=3: + print "test 3\n", Generator.create_xhtml(""":ticket:`316` implements :xep:`71`""") diff --git a/src/common/sleepy.py b/src/common/sleepy.py index 507452ea9..6d3c388b2 100644 --- a/src/common/sleepy.py +++ b/src/common/sleepy.py @@ -28,115 +28,113 @@ import os, sys STATE_UNKNOWN = 'OS probably not supported' STATE_XA = 'extended away' STATE_AWAY = 'away' -STATE_AWAKE = 'awake' +STATE_AWAKE = 'awake' SUPPORTED = True try: - if os.name == 'nt': - import ctypes + if os.name == 'nt': + import ctypes - GetTickCount = ctypes.windll.kernel32.GetTickCount - GetLastInputInfo = ctypes.windll.user32.GetLastInputInfo + GetTickCount = ctypes.windll.kernel32.GetTickCount + GetLastInputInfo = ctypes.windll.user32.GetLastInputInfo - class LASTINPUTINFO(ctypes.Structure): - _fields_ = [('cbSize', ctypes.c_uint), ('dwTime', ctypes.c_uint)] + class LASTINPUTINFO(ctypes.Structure): + _fields_ = [('cbSize', ctypes.c_uint), ('dwTime', ctypes.c_uint)] - lastInputInfo = LASTINPUTINFO() - lastInputInfo.cbSize = ctypes.sizeof(lastInputInfo) + lastInputInfo = LASTINPUTINFO() + lastInputInfo.cbSize = ctypes.sizeof(lastInputInfo) - # one or more of these may not be supported before XP. - OpenInputDesktop = ctypes.windll.user32.OpenInputDesktop - CloseDesktop = ctypes.windll.user32.CloseDesktop - SystemParametersInfo = ctypes.windll.user32.SystemParametersInfoW - else: # unix - from common import idle + # one or more of these may not be supported before XP. + OpenInputDesktop = ctypes.windll.user32.OpenInputDesktop + CloseDesktop = ctypes.windll.user32.CloseDesktop + SystemParametersInfo = ctypes.windll.user32.SystemParametersInfoW + else: # unix + from common import idle except Exception: - gajim.log.debug('Unable to load idle module') - SUPPORTED = False + gajim.log.debug('Unable to load idle module') + SUPPORTED = False class SleepyWindows: - def __init__(self, away_interval = 60, xa_interval = 120): - self.away_interval = away_interval - self.xa_interval = xa_interval - self.state = STATE_AWAKE # assume we are awake + def __init__(self, away_interval = 60, xa_interval = 120): + self.away_interval = away_interval + self.xa_interval = xa_interval + self.state = STATE_AWAKE # assume we are awake - def getIdleSec(self): - GetLastInputInfo(ctypes.byref(lastInputInfo)) - idleDelta = float(GetTickCount() - lastInputInfo.dwTime) / 1000 - return idleDelta + def getIdleSec(self): + GetLastInputInfo(ctypes.byref(lastInputInfo)) + idleDelta = float(GetTickCount() - lastInputInfo.dwTime) / 1000 + return idleDelta - def poll(self): - '''checks to see if we should change state''' - if not SUPPORTED: - return False + def poll(self): + '''checks to see if we should change state''' + if not SUPPORTED: + return False - # screen saver, in windows >= XP - saver_runing = ctypes.c_int(0) - # 0x72 is SPI_GETSCREENSAVERRUNNING - if SystemParametersInfo(0x72, 0, ctypes.byref(saver_runing), 0) and \ - saver_runing.value: - self.state = STATE_XA - return True + # screen saver, in windows >= XP + saver_runing = ctypes.c_int(0) + # 0x72 is SPI_GETSCREENSAVERRUNNING + if SystemParametersInfo(0x72, 0, ctypes.byref(saver_runing), 0) and \ + saver_runing.value: + self.state = STATE_XA + return True - desk = OpenInputDesktop(0, False, 0) - if not desk: - # Screen locked - self.state = STATE_XA - return True - CloseDesktop(desk) + desk = OpenInputDesktop(0, False, 0) + if not desk: + # Screen locked + self.state = STATE_XA + return True + CloseDesktop(desk) - idleTime = self.getIdleSec() + idleTime = self.getIdleSec() - # xa is stronger than away so check for xa first - if idleTime > self.xa_interval: - self.state = STATE_XA - elif idleTime > self.away_interval: - self.state = STATE_AWAY - else: - self.state = STATE_AWAKE - return True + # xa is stronger than away so check for xa first + if idleTime > self.xa_interval: + self.state = STATE_XA + elif idleTime > self.away_interval: + self.state = STATE_AWAY + else: + self.state = STATE_AWAKE + return True - def getState(self): - return self.state + def getState(self): + return self.state - def setState(self, val): - self.state = val + def setState(self, val): + self.state = val class SleepyUnix: - def __init__(self, away_interval = 60, xa_interval = 120): - global SUPPORTED - self.away_interval = away_interval - self.xa_interval = xa_interval - self.state = STATE_AWAKE # assume we are awake + def __init__(self, away_interval = 60, xa_interval = 120): + global SUPPORTED + self.away_interval = away_interval + self.xa_interval = xa_interval + self.state = STATE_AWAKE # assume we are awake - def getIdleSec(self): - return idle.getIdleSec() + def getIdleSec(self): + return idle.getIdleSec() - def poll(self): - '''checks to see if we should change state''' - if not SUPPORTED: - return False + def poll(self): + '''checks to see if we should change state''' + if not SUPPORTED: + return False - idleTime = self.getIdleSec() + idleTime = self.getIdleSec() - # xa is stronger than away so check for xa first - if idleTime > self.xa_interval: - self.state = STATE_XA - elif idleTime > self.away_interval: - self.state = STATE_AWAY - else: - self.state = STATE_AWAKE - return True + # xa is stronger than away so check for xa first + if idleTime > self.xa_interval: + self.state = STATE_XA + elif idleTime > self.away_interval: + self.state = STATE_AWAY + else: + self.state = STATE_AWAKE + return True - def getState(self): - return self.state + def getState(self): + return self.state - def setState(self, val): - self.state = val + def setState(self, val): + self.state = val if os.name == 'nt': - Sleepy = SleepyWindows + Sleepy = SleepyWindows else: - Sleepy = SleepyUnix - -# vim: se ts=3: + Sleepy = SleepyUnix diff --git a/src/common/socks5.py b/src/common/socks5.py index e1c1099ad..45203641d 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -52,1047 +52,1045 @@ READ_TIMEOUT = 180 SEND_TIMEOUT = 180 class SocksQueue: - ''' queue for all file requests objects ''' - def __init__(self, idlequeue, complete_transfer_cb=None, - progress_transfer_cb=None, error_cb=None): - self.connected = 0 - self.readers = {} - self.files_props = {} - self.senders = {} - self.idx = 1 - self.listener = None - self.sha_handlers = {} - # handle all io events in the global idle queue, instead of processing - # each foo seconds - self.idlequeue = idlequeue - self.complete_transfer_cb = complete_transfer_cb - self.progress_transfer_cb = progress_transfer_cb - self.error_cb = error_cb - self.on_success = None - self.on_failure = None + ''' queue for all file requests objects ''' + def __init__(self, idlequeue, complete_transfer_cb=None, + progress_transfer_cb=None, error_cb=None): + self.connected = 0 + self.readers = {} + self.files_props = {} + self.senders = {} + self.idx = 1 + self.listener = None + self.sha_handlers = {} + # handle all io events in the global idle queue, instead of processing + # each foo seconds + self.idlequeue = idlequeue + self.complete_transfer_cb = complete_transfer_cb + self.progress_transfer_cb = progress_transfer_cb + self.error_cb = error_cb + self.on_success = None + self.on_failure = None - def start_listener(self, port, sha_str, sha_handler, sid): - ''' start waiting for incomming connections on (host, port) - and do a socks5 authentication using sid for generated sha - ''' - self.sha_handlers[sha_str] = (sha_handler, sid) - if self.listener is None: - self.listener = Socks5Listener(self.idlequeue, port) - self.listener.queue = self - self.listener.bind() - if self.listener.started is False: - self.listener = None - # We cannot bind port, call error callback and fail - self.error_cb(_('Unable to bind to port %s.') % port, - _('Maybe you have another running instance of Gajim. File ' - 'Transfer will be cancelled.')) - return None - self.connected += 1 - return self.listener + def start_listener(self, port, sha_str, sha_handler, sid): + ''' start waiting for incomming connections on (host, port) + and do a socks5 authentication using sid for generated sha + ''' + self.sha_handlers[sha_str] = (sha_handler, sid) + if self.listener is None: + self.listener = Socks5Listener(self.idlequeue, port) + self.listener.queue = self + self.listener.bind() + if self.listener.started is False: + self.listener = None + # We cannot bind port, call error callback and fail + self.error_cb(_('Unable to bind to port %s.') % port, + _('Maybe you have another running instance of Gajim. File ' + 'Transfer will be cancelled.')) + return None + self.connected += 1 + return self.listener - def send_success_reply(self, file_props, streamhost): - if 'streamhost-used' in file_props and \ - file_props['streamhost-used'] is True: - if 'proxyhosts' in file_props: - for proxy in file_props['proxyhosts']: - if proxy == streamhost: - self.on_success(streamhost) - return 2 - return 0 - if 'streamhosts' in file_props: - for host in file_props['streamhosts']: - if streamhost['state'] == 1: - return 0 - streamhost['state'] = 1 - self.on_success(streamhost) - return 1 - return 0 + def send_success_reply(self, file_props, streamhost): + if 'streamhost-used' in file_props and \ + file_props['streamhost-used'] is True: + if 'proxyhosts' in file_props: + for proxy in file_props['proxyhosts']: + if proxy == streamhost: + self.on_success(streamhost) + return 2 + return 0 + if 'streamhosts' in file_props: + for host in file_props['streamhosts']: + if streamhost['state'] == 1: + return 0 + streamhost['state'] = 1 + self.on_success(streamhost) + return 1 + return 0 - def connect_to_hosts(self, account, sid, on_success=None, on_failure=None): - self.on_success = on_success - self.on_failure = on_failure - file_props = self.files_props[account][sid] - file_props['failure_cb'] = on_failure + def connect_to_hosts(self, account, sid, on_success=None, on_failure=None): + self.on_success = on_success + self.on_failure = on_failure + file_props = self.files_props[account][sid] + file_props['failure_cb'] = on_failure - # add streamhosts to the queue - for streamhost in file_props['streamhosts']: - receiver = Socks5Receiver(self.idlequeue, streamhost, sid, file_props) - self.add_receiver(account, receiver) - streamhost['idx'] = receiver.queue_idx + # add streamhosts to the queue + for streamhost in file_props['streamhosts']: + receiver = Socks5Receiver(self.idlequeue, streamhost, sid, file_props) + self.add_receiver(account, receiver) + streamhost['idx'] = receiver.queue_idx - def _socket_connected(self, streamhost, file_props): - ''' called when there is a host connected to one of the - senders's streamhosts. Stop othere attempts for connections ''' - for host in file_props['streamhosts']: - if host != streamhost and 'idx' in host: - if host['state'] == 1: - # remove current - self.remove_receiver(streamhost['idx']) - return - # set state -2, meaning that this streamhost is stopped, - # but it may be connectected later - if host['state'] >= 0: - self.remove_receiver(host['idx']) - host['idx'] = -1 - host['state'] = -2 + def _socket_connected(self, streamhost, file_props): + ''' called when there is a host connected to one of the + senders's streamhosts. Stop othere attempts for connections ''' + for host in file_props['streamhosts']: + if host != streamhost and 'idx' in host: + if host['state'] == 1: + # remove current + self.remove_receiver(streamhost['idx']) + return + # set state -2, meaning that this streamhost is stopped, + # but it may be connectected later + if host['state'] >= 0: + self.remove_receiver(host['idx']) + host['idx'] = -1 + host['state'] = -2 - def reconnect_receiver(self, receiver, streamhost): - ''' Check the state of all streamhosts and if all has failed, then - emit connection failure cb. If there are some which are still - not connected try to establish connection to one of them. - ''' - self.idlequeue.remove_timeout(receiver.fd) - self.idlequeue.unplug_idle(receiver.fd) - file_props = receiver.file_props - streamhost['state'] = -1 - # boolean, indicates that there are hosts, which are not tested yet - unused_hosts = False - for host in file_props['streamhosts']: - if 'idx' in host: - if host['state'] >= 0: - return - elif host['state'] == -2: - unused_hosts = True - if unused_hosts: - for host in file_props['streamhosts']: - if host['state'] == -2: - host['state'] = 0 - receiver = Socks5Receiver(self.idlequeue, host, host['sid'], - file_props) - self.add_receiver(receiver.account, receiver) - host['idx'] = receiver.queue_idx - # we still have chances to connect - return - if 'received-len' not in file_props or file_props['received-len'] == 0: - # there are no other streamhosts and transfer hasn't started - self._connection_refused(streamhost, file_props, receiver.queue_idx) - else: - # transfer stopped, it is most likely stopped from sender - receiver.disconnect() - file_props['error'] = -1 - self.process_result(-1, receiver) + def reconnect_receiver(self, receiver, streamhost): + ''' Check the state of all streamhosts and if all has failed, then + emit connection failure cb. If there are some which are still + not connected try to establish connection to one of them. + ''' + self.idlequeue.remove_timeout(receiver.fd) + self.idlequeue.unplug_idle(receiver.fd) + file_props = receiver.file_props + streamhost['state'] = -1 + # boolean, indicates that there are hosts, which are not tested yet + unused_hosts = False + for host in file_props['streamhosts']: + if 'idx' in host: + if host['state'] >= 0: + return + elif host['state'] == -2: + unused_hosts = True + if unused_hosts: + for host in file_props['streamhosts']: + if host['state'] == -2: + host['state'] = 0 + receiver = Socks5Receiver(self.idlequeue, host, host['sid'], + file_props) + self.add_receiver(receiver.account, receiver) + host['idx'] = receiver.queue_idx + # we still have chances to connect + return + if 'received-len' not in file_props or file_props['received-len'] == 0: + # there are no other streamhosts and transfer hasn't started + self._connection_refused(streamhost, file_props, receiver.queue_idx) + else: + # transfer stopped, it is most likely stopped from sender + receiver.disconnect() + file_props['error'] = -1 + self.process_result(-1, receiver) - def _connection_refused(self, streamhost, file_props, idx): - ''' cb, called when we loose connection during transfer''' - if file_props is None: - return - streamhost['state'] = -1 - self.remove_receiver(idx, False) - if 'streamhosts' in file_props: - for host in file_props['streamhosts']: - if host['state'] != -1: - return - # failure_cb exists - this means that it has never been called - if 'failure_cb' in file_props and file_props['failure_cb']: - file_props['failure_cb'](streamhost['initiator'], streamhost['id'], - file_props['sid'], code = 404) - del(file_props['failure_cb']) + def _connection_refused(self, streamhost, file_props, idx): + ''' cb, called when we loose connection during transfer''' + if file_props is None: + return + streamhost['state'] = -1 + self.remove_receiver(idx, False) + if 'streamhosts' in file_props: + for host in file_props['streamhosts']: + if host['state'] != -1: + return + # failure_cb exists - this means that it has never been called + if 'failure_cb' in file_props and file_props['failure_cb']: + file_props['failure_cb'](streamhost['initiator'], streamhost['id'], + file_props['sid'], code = 404) + del(file_props['failure_cb']) - def add_receiver(self, account, sock5_receiver): - ''' add new file request ''' - self.readers[self.idx] = sock5_receiver - sock5_receiver.queue_idx = self.idx - sock5_receiver.queue = self - sock5_receiver.account = account - self.idx += 1 - result = sock5_receiver.connect() - self.connected += 1 - if result is not None: - result = sock5_receiver.main() - self.process_result(result, sock5_receiver) - return 1 - return None + def add_receiver(self, account, sock5_receiver): + ''' add new file request ''' + self.readers[self.idx] = sock5_receiver + sock5_receiver.queue_idx = self.idx + sock5_receiver.queue = self + sock5_receiver.account = account + self.idx += 1 + result = sock5_receiver.connect() + self.connected += 1 + if result is not None: + result = sock5_receiver.main() + self.process_result(result, sock5_receiver) + return 1 + return None - def get_file_from_sender(self, file_props, account): - if file_props is None: - return - if 'hash' in file_props and file_props['hash'] in self.senders: - sender = self.senders[file_props['hash']] - sender.account = account - result = self.get_file_contents(0) - self.process_result(result, sender) + def get_file_from_sender(self, file_props, account): + if file_props is None: + return + if 'hash' in file_props and file_props['hash'] in self.senders: + sender = self.senders[file_props['hash']] + sender.account = account + result = self.get_file_contents(0) + self.process_result(result, sender) - def result_sha(self, sha_str, idx): - if sha_str in self.sha_handlers: - props = self.sha_handlers[sha_str] - props[0](props[1], idx) + def result_sha(self, sha_str, idx): + if sha_str in self.sha_handlers: + props = self.sha_handlers[sha_str] + props[0](props[1], idx) - def activate_proxy(self, idx): - if idx not in self.readers: - return - reader = self.readers[idx] - if reader.file_props['type'] != 's': - return - if reader.state != 5: - return - reader.state = 6 - if reader.connected: - reader.file_props['error'] = 0 - reader.file_props['disconnect_cb'] = reader.disconnect - reader.file_props['started'] = True - reader.file_props['completed'] = False - reader.file_props['paused'] = False - reader.file_props['stalled'] = False - reader.file_props['elapsed-time'] = 0 - reader.file_props['last-time'] = self.idlequeue.current_time() - reader.file_props['received-len'] = 0 - reader.pauses = 0 - # start sending file to proxy - self.idlequeue.set_read_timeout(reader.fd, STALLED_TIMEOUT) - self.idlequeue.plug_idle(reader, True, False) - result = reader.write_next() - self.process_result(result, reader) + def activate_proxy(self, idx): + if idx not in self.readers: + return + reader = self.readers[idx] + if reader.file_props['type'] != 's': + return + if reader.state != 5: + return + reader.state = 6 + if reader.connected: + reader.file_props['error'] = 0 + reader.file_props['disconnect_cb'] = reader.disconnect + reader.file_props['started'] = True + reader.file_props['completed'] = False + reader.file_props['paused'] = False + reader.file_props['stalled'] = False + reader.file_props['elapsed-time'] = 0 + reader.file_props['last-time'] = self.idlequeue.current_time() + reader.file_props['received-len'] = 0 + reader.pauses = 0 + # start sending file to proxy + self.idlequeue.set_read_timeout(reader.fd, STALLED_TIMEOUT) + self.idlequeue.plug_idle(reader, True, False) + result = reader.write_next() + self.process_result(result, reader) - def send_file(self, file_props, account): - if 'hash' in file_props and file_props['hash'] in self.senders: - sender = self.senders[file_props['hash']] - file_props['streamhost-used'] = True - sender.account = account - if file_props['type'] == 's': - sender.file_props = file_props - result = sender.send_file() - self.process_result(result, sender) - else: - file_props['elapsed-time'] = 0 - file_props['last-time'] = self.idlequeue.current_time() - file_props['received-len'] = 0 - sender.file_props = file_props + def send_file(self, file_props, account): + if 'hash' in file_props and file_props['hash'] in self.senders: + sender = self.senders[file_props['hash']] + file_props['streamhost-used'] = True + sender.account = account + if file_props['type'] == 's': + sender.file_props = file_props + result = sender.send_file() + self.process_result(result, sender) + else: + file_props['elapsed-time'] = 0 + file_props['last-time'] = self.idlequeue.current_time() + file_props['received-len'] = 0 + sender.file_props = file_props - def add_file_props(self, account, file_props): - ''' file_prop to the dict of current file_props. - It is identified by account name and sid - ''' - if file_props is None or ('sid' in file_props) is False: - return - _id = file_props['sid'] - if account not in self.files_props: - self.files_props[account] = {} - self.files_props[account][_id] = file_props + def add_file_props(self, account, file_props): + ''' file_prop to the dict of current file_props. + It is identified by account name and sid + ''' + if file_props is None or ('sid' in file_props) is False: + return + _id = file_props['sid'] + if account not in self.files_props: + self.files_props[account] = {} + self.files_props[account][_id] = file_props - def remove_file_props(self, account, sid): - if account in self.files_props: - fl_props = self.files_props[account] - if sid in fl_props: - del(fl_props[sid]) + def remove_file_props(self, account, sid): + if account in self.files_props: + fl_props = self.files_props[account] + if sid in fl_props: + del(fl_props[sid]) - if len(self.files_props) == 0: - self.connected = 0 + if len(self.files_props) == 0: + self.connected = 0 - def get_file_props(self, account, sid): - ''' get fil_prop by account name and session id ''' - if account in self.files_props: - fl_props = self.files_props[account] - if sid in fl_props: - return fl_props[sid] - return None + def get_file_props(self, account, sid): + ''' get fil_prop by account name and session id ''' + if account in self.files_props: + fl_props = self.files_props[account] + if sid in fl_props: + return fl_props[sid] + return None - def on_connection_accepted(self, sock): - sock_hash = sock.__hash__() - if sock_hash not in self.senders: - self.senders[sock_hash] = Socks5Sender(self.idlequeue, sock_hash, self, - sock[0], sock[1][0], sock[1][1]) - self.connected += 1 + def on_connection_accepted(self, sock): + sock_hash = sock.__hash__() + if sock_hash not in self.senders: + self.senders[sock_hash] = Socks5Sender(self.idlequeue, sock_hash, self, + sock[0], sock[1][0], sock[1][1]) + self.connected += 1 - def process_result(self, result, actor): - ''' Take appropriate actions upon the result: - [ 0, - 1 ] complete/end transfer - [ > 0 ] send progress message - [ None ] do nothing - ''' - if result is None: - return - if result in (0, -1) and self.complete_transfer_cb is not None: - account = actor.account - if account is None and 'tt_account' in actor.file_props: - account = actor.file_props['tt_account'] - self.complete_transfer_cb(account, actor.file_props) - elif self.progress_transfer_cb is not None: - self.progress_transfer_cb(actor.account, actor.file_props) + def process_result(self, result, actor): + ''' Take appropriate actions upon the result: + [ 0, - 1 ] complete/end transfer + [ > 0 ] send progress message + [ None ] do nothing + ''' + if result is None: + return + if result in (0, -1) and self.complete_transfer_cb is not None: + account = actor.account + if account is None and 'tt_account' in actor.file_props: + account = actor.file_props['tt_account'] + self.complete_transfer_cb(account, actor.file_props) + elif self.progress_transfer_cb is not None: + self.progress_transfer_cb(actor.account, actor.file_props) - def remove_receiver(self, idx, do_disconnect=True): - ''' Remove reciver from the list and decrease - the number of active connections with 1''' - if idx != -1: - if idx in self.readers: - reader = self.readers[idx] - self.idlequeue.unplug_idle(reader.fd) - self.idlequeue.remove_timeout(reader.fd) - if do_disconnect: - reader.disconnect() - else: - if reader.streamhost is not None: - reader.streamhost['state'] = -1 - del(self.readers[idx]) + def remove_receiver(self, idx, do_disconnect=True): + ''' Remove reciver from the list and decrease + the number of active connections with 1''' + if idx != -1: + if idx in self.readers: + reader = self.readers[idx] + self.idlequeue.unplug_idle(reader.fd) + self.idlequeue.remove_timeout(reader.fd) + if do_disconnect: + reader.disconnect() + else: + if reader.streamhost is not None: + reader.streamhost['state'] = -1 + del(self.readers[idx]) - def remove_sender(self, idx, do_disconnect=True): - ''' Remove sender from the list of senders and decrease the - number of active connections with 1''' - if idx != -1: - if idx in self.senders: - if do_disconnect: - self.senders[idx].disconnect() - return - else: - del(self.senders[idx]) - if self.connected > 0: - self.connected -= 1 - if len(self.senders) == 0 and self.listener is not None: - self.listener.disconnect() - self.listener = None - self.connected -= 1 + def remove_sender(self, idx, do_disconnect=True): + ''' Remove sender from the list of senders and decrease the + number of active connections with 1''' + if idx != -1: + if idx in self.senders: + if do_disconnect: + self.senders[idx].disconnect() + return + else: + del(self.senders[idx]) + if self.connected > 0: + self.connected -= 1 + if len(self.senders) == 0 and self.listener is not None: + self.listener.disconnect() + self.listener = None + self.connected -= 1 class Socks5: - def __init__(self, idlequeue, host, port, initiator, target, sid): - if host is not None: - try: - self.host = host - self.ais = socket.getaddrinfo(host, port, socket.AF_UNSPEC, - socket.SOCK_STREAM) - except socket.gaierror: - self.ais = None - self.idlequeue = idlequeue - self.fd = -1 - self.port = port - self.initiator = initiator - self.target = target - self.sid = sid - self._sock = None - self.account = None - self.state = 0 # not connected - self.pauses = 0 - self.size = 0 - self.remaining_buff = '' - self.file = None + def __init__(self, idlequeue, host, port, initiator, target, sid): + if host is not None: + try: + self.host = host + self.ais = socket.getaddrinfo(host, port, socket.AF_UNSPEC, + socket.SOCK_STREAM) + except socket.gaierror: + self.ais = None + self.idlequeue = idlequeue + self.fd = -1 + self.port = port + self.initiator = initiator + self.target = target + self.sid = sid + self._sock = None + self.account = None + self.state = 0 # not connected + self.pauses = 0 + self.size = 0 + self.remaining_buff = '' + self.file = None - def open_file_for_reading(self): - if self.file is None: - try: - self.file = open(self.file_props['file-name'],'rb') - if 'offset' in self.file_props and self.file_props['offset']: - self.size = self.file_props['offset'] - self.file.seek(self.size) - self.file_props['received-len'] = self.size - except IOError, e: - self.close_file() - raise IOError, e + def open_file_for_reading(self): + if self.file is None: + try: + self.file = open(self.file_props['file-name'], 'rb') + if 'offset' in self.file_props and self.file_props['offset']: + self.size = self.file_props['offset'] + self.file.seek(self.size) + self.file_props['received-len'] = self.size + except IOError, e: + self.close_file() + raise IOError, e - def close_file(self): - if self.file: - if not self.file.closed: - try: - self.file.close() - except Exception: - pass - self.file = None + def close_file(self): + if self.file: + if not self.file.closed: + try: + self.file.close() + except Exception: + pass + self.file = None - def get_fd(self): - ''' Test if file is already open and return its fd, - or just open the file and return the fd. - ''' - if 'fd' in self.file_props: - fd = self.file_props['fd'] - else: - offset = 0 - opt = 'wb' - if 'offset' in self.file_props and self.file_props['offset']: - offset = self.file_props['offset'] - opt = 'ab' - fd = open(self.file_props['file-name'], opt) - self.file_props['fd'] = fd - self.file_props['elapsed-time'] = 0 - self.file_props['last-time'] = self.idlequeue.current_time() - self.file_props['received-len'] = offset - return fd + def get_fd(self): + ''' Test if file is already open and return its fd, + or just open the file and return the fd. + ''' + if 'fd' in self.file_props: + fd = self.file_props['fd'] + else: + offset = 0 + opt = 'wb' + if 'offset' in self.file_props and self.file_props['offset']: + offset = self.file_props['offset'] + opt = 'ab' + fd = open(self.file_props['file-name'], opt) + self.file_props['fd'] = fd + self.file_props['elapsed-time'] = 0 + self.file_props['last-time'] = self.idlequeue.current_time() + self.file_props['received-len'] = offset + return fd - def rem_fd(self, fd): - if 'fd' in self.file_props: - del(self.file_props['fd']) - try: - fd.close() - except Exception: - pass + def rem_fd(self, fd): + if 'fd' in self.file_props: + del(self.file_props['fd']) + try: + fd.close() + except Exception: + pass - def receive(self): - ''' Reads small chunks of data. - Calls owner's disconnected() method if appropriate.''' - received = '' - try: - add = self._recv(64) - except Exception: - add = '' - received += add - if len(add) == 0: - self.disconnect() - return add + def receive(self): + ''' Reads small chunks of data. + Calls owner's disconnected() method if appropriate.''' + received = '' + try: + add = self._recv(64) + except Exception: + add = '' + received += add + if len(add) == 0: + self.disconnect() + return add - def send_raw(self,raw_data): - ''' Writes raw outgoing data. ''' - try: - self._send(raw_data) - except Exception: - self.disconnect() - return len(raw_data) + def send_raw(self, raw_data): + ''' Writes raw outgoing data. ''' + try: + self._send(raw_data) + except Exception: + self.disconnect() + return len(raw_data) - def write_next(self): - if self.remaining_buff != '': - buff = self.remaining_buff - self.remaining_buff = '' - else: - try: - self.open_file_for_reading() - except IOError, e: - self.state = 8 # end connection - self.disconnect() - self.file_props['error'] = -7 # unable to read from file - return -1 - buff = self.file.read(MAX_BUFF_LEN) - if len(buff) > 0: - lenn = 0 - try: - lenn = self._send(buff) - except Exception, e: - if e.args[0] not in (EINTR, ENOBUFS, EWOULDBLOCK): - # peer stopped reading - self.state = 8 # end connection - self.disconnect() - self.file_props['error'] = -1 - return -1 - self.size += lenn - current_time = self.idlequeue.current_time() - self.file_props['elapsed-time'] += current_time - \ - self.file_props['last-time'] - self.file_props['last-time'] = current_time - self.file_props['received-len'] = self.size - if self.size >= int(self.file_props['size']): - self.state = 8 # end connection - self.file_props['error'] = 0 - self.disconnect() - return -1 - if lenn != len(buff): - self.remaining_buff = buff[lenn:] - else: - self.remaining_buff = '' - self.state = 7 # continue to write in the socket - if lenn == 0: - return None - self.file_props['stalled'] = False - return lenn - else: - self.state = 8 # end connection - self.disconnect() - return -1 + def write_next(self): + if self.remaining_buff != '': + buff = self.remaining_buff + self.remaining_buff = '' + else: + try: + self.open_file_for_reading() + except IOError, e: + self.state = 8 # end connection + self.disconnect() + self.file_props['error'] = -7 # unable to read from file + return -1 + buff = self.file.read(MAX_BUFF_LEN) + if len(buff) > 0: + lenn = 0 + try: + lenn = self._send(buff) + except Exception, e: + if e.args[0] not in (EINTR, ENOBUFS, EWOULDBLOCK): + # peer stopped reading + self.state = 8 # end connection + self.disconnect() + self.file_props['error'] = -1 + return -1 + self.size += lenn + current_time = self.idlequeue.current_time() + self.file_props['elapsed-time'] += current_time - \ + self.file_props['last-time'] + self.file_props['last-time'] = current_time + self.file_props['received-len'] = self.size + if self.size >= int(self.file_props['size']): + self.state = 8 # end connection + self.file_props['error'] = 0 + self.disconnect() + return -1 + if lenn != len(buff): + self.remaining_buff = buff[lenn:] + else: + self.remaining_buff = '' + self.state = 7 # continue to write in the socket + if lenn == 0: + return None + self.file_props['stalled'] = False + return lenn + else: + self.state = 8 # end connection + self.disconnect() + return -1 - def get_file_contents(self, timeout): - ''' read file contents from socket and write them to file ''' - if self.file_props is None or ('file-name' in self.file_props) is False: - self.file_props['error'] = -2 - return None - fd = None - if self.remaining_buff != '': - try: - fd = self.get_fd() - except IOError, e: - self.disconnect(False) - self.file_props['error'] = -6 # file system error - return 0 - fd.write(self.remaining_buff) - lenn = len(self.remaining_buff) - current_time = self.idlequeue.current_time() - self.file_props['elapsed-time'] += current_time - \ - self.file_props['last-time'] - self.file_props['last-time'] = current_time - self.file_props['received-len'] += lenn - self.remaining_buff = '' - if self.file_props['received-len'] == int(self.file_props['size']): - self.rem_fd(fd) - self.disconnect() - self.file_props['error'] = 0 - self.file_props['completed'] = True - return 0 - else: - try: - fd = self.get_fd() - except IOError, e: - self.disconnect(False) - self.file_props['error'] = -6 # file system error - return 0 - try: - buff = self._recv(MAX_BUFF_LEN) - except Exception: - buff = '' - current_time = self.idlequeue.current_time() - self.file_props['elapsed-time'] += current_time - \ - self.file_props['last-time'] - self.file_props['last-time'] = current_time - self.file_props['received-len'] += len(buff) - if len(buff) == 0: - # Transfer stopped somehow: - # reset, paused or network error - self.rem_fd(fd) - self.disconnect(False) - self.file_props['error'] = -1 - return 0 - try: - fd.write(buff) - except IOError, e: - self.rem_fd(fd) - self.disconnect(False) - self.file_props['error'] = -6 # file system error - return 0 - if self.file_props['received-len'] >= int(self.file_props['size']): - # transfer completed - self.rem_fd(fd) - self.disconnect() - self.file_props['error'] = 0 - self.file_props['completed'] = True - return 0 - # return number of read bytes. It can be used in progressbar - if fd is not None: - self.file_props['stalled'] = False - if fd is None and self.file_props['stalled'] is False: - return None - if 'received-len' in self.file_props: - if self.file_props['received-len'] != 0: - return self.file_props['received-len'] - return None + def get_file_contents(self, timeout): + ''' read file contents from socket and write them to file ''' + if self.file_props is None or ('file-name' in self.file_props) is False: + self.file_props['error'] = -2 + return None + fd = None + if self.remaining_buff != '': + try: + fd = self.get_fd() + except IOError, e: + self.disconnect(False) + self.file_props['error'] = -6 # file system error + return 0 + fd.write(self.remaining_buff) + lenn = len(self.remaining_buff) + current_time = self.idlequeue.current_time() + self.file_props['elapsed-time'] += current_time - \ + self.file_props['last-time'] + self.file_props['last-time'] = current_time + self.file_props['received-len'] += lenn + self.remaining_buff = '' + if self.file_props['received-len'] == int(self.file_props['size']): + self.rem_fd(fd) + self.disconnect() + self.file_props['error'] = 0 + self.file_props['completed'] = True + return 0 + else: + try: + fd = self.get_fd() + except IOError, e: + self.disconnect(False) + self.file_props['error'] = -6 # file system error + return 0 + try: + buff = self._recv(MAX_BUFF_LEN) + except Exception: + buff = '' + current_time = self.idlequeue.current_time() + self.file_props['elapsed-time'] += current_time - \ + self.file_props['last-time'] + self.file_props['last-time'] = current_time + self.file_props['received-len'] += len(buff) + if len(buff) == 0: + # Transfer stopped somehow: + # reset, paused or network error + self.rem_fd(fd) + self.disconnect(False) + self.file_props['error'] = -1 + return 0 + try: + fd.write(buff) + except IOError, e: + self.rem_fd(fd) + self.disconnect(False) + self.file_props['error'] = -6 # file system error + return 0 + if self.file_props['received-len'] >= int(self.file_props['size']): + # transfer completed + self.rem_fd(fd) + self.disconnect() + self.file_props['error'] = 0 + self.file_props['completed'] = True + return 0 + # return number of read bytes. It can be used in progressbar + if fd is not None: + self.file_props['stalled'] = False + if fd is None and self.file_props['stalled'] is False: + return None + if 'received-len' in self.file_props: + if self.file_props['received-len'] != 0: + return self.file_props['received-len'] + return None - def disconnect(self): - ''' Closes open descriptors and remover socket descr. from idleque ''' - # be sure that we don't leave open file - self.close_file() - self.idlequeue.remove_timeout(self.fd) - self.idlequeue.unplug_idle(self.fd) - try: - self._sock.shutdown(socket.SHUT_RDWR) - self._sock.close() - except Exception: - # socket is already closed - pass - self.connected = False - self.fd = -1 - self.state = -1 + def disconnect(self): + ''' Closes open descriptors and remover socket descr. from idleque ''' + # be sure that we don't leave open file + self.close_file() + self.idlequeue.remove_timeout(self.fd) + self.idlequeue.unplug_idle(self.fd) + try: + self._sock.shutdown(socket.SHUT_RDWR) + self._sock.close() + except Exception: + # socket is already closed + pass + self.connected = False + self.fd = -1 + self.state = -1 - def _get_auth_buff(self): - ''' Message, that we support 1 one auth mechanism: - the 'no auth' mechanism. ''' - return struct.pack('!BBB', 0x05, 0x01, 0x00) + def _get_auth_buff(self): + ''' Message, that we support 1 one auth mechanism: + the 'no auth' mechanism. ''' + return struct.pack('!BBB', 0x05, 0x01, 0x00) - def _parse_auth_buff(self, buff): - ''' Parse the initial message and create a list of auth - mechanisms ''' - auth_mechanisms = [] - try: - num_auth = struct.unpack('!xB', buff[:2])[0] - for i in xrange(num_auth): - mechanism, = struct.unpack('!B', buff[1 + i]) - auth_mechanisms.append(mechanism) - except Exception: - return None - return auth_mechanisms + def _parse_auth_buff(self, buff): + ''' Parse the initial message and create a list of auth + mechanisms ''' + auth_mechanisms = [] + try: + num_auth = struct.unpack('!xB', buff[:2])[0] + for i in xrange(num_auth): + mechanism, = struct.unpack('!B', buff[1 + i]) + auth_mechanisms.append(mechanism) + except Exception: + return None + return auth_mechanisms - def _get_auth_response(self): - ''' socks version(5), number of extra auth methods (we send - 0x00 - no auth - ) ''' - return struct.pack('!BB', 0x05, 0x00) + def _get_auth_response(self): + ''' socks version(5), number of extra auth methods (we send + 0x00 - no auth + ) ''' + return struct.pack('!BB', 0x05, 0x00) - def _get_connect_buff(self): - ''' Connect request by domain name ''' - buff = struct.pack('!BBBBB%dsBB' % len(self.host), - 0x05, 0x01, 0x00, 0x03, len(self.host), self.host, - self.port >> 8, self.port & 0xff) - return buff + def _get_connect_buff(self): + ''' Connect request by domain name ''' + buff = struct.pack('!BBBBB%dsBB' % len(self.host), + 0x05, 0x01, 0x00, 0x03, len(self.host), self.host, + self.port >> 8, self.port & 0xff) + return buff - def _get_request_buff(self, msg, command = 0x01): - ''' Connect request by domain name, - sid sha, instead of domain name (jep 0096) ''' - buff = struct.pack('!BBBBB%dsBB' % len(msg), - 0x05, command, 0x00, 0x03, len(msg), msg, 0, 0) - return buff + def _get_request_buff(self, msg, command = 0x01): + ''' Connect request by domain name, + sid sha, instead of domain name (jep 0096) ''' + buff = struct.pack('!BBBBB%dsBB' % len(msg), + 0x05, command, 0x00, 0x03, len(msg), msg, 0, 0) + return buff - def _parse_request_buff(self, buff): - try: # don't trust on what comes from the outside - req_type, host_type, = struct.unpack('!xBxB', buff[:4]) - if host_type == 0x01: - host_arr = struct.unpack('!iiii', buff[4:8]) - host, = '.'.join(str(s) for s in host_arr) - host_len = len(host) - elif host_type == 0x03: - host_len, = struct.unpack('!B' , buff[4]) - host, = struct.unpack('!%ds' % host_len, buff[5:5 + host_len]) - portlen = len(buff[host_len + 5:]) - if portlen == 1: - port, = struct.unpack('!B', buff[host_len + 5]) - elif portlen == 2: - port, = struct.unpack('!H', buff[host_len + 5:]) - # file data, comes with auth message (Gaim bug) - else: - port, = struct.unpack('!H', buff[host_len + 5: host_len + 7]) - self.remaining_buff = buff[host_len + 7:] - except Exception: - return (None, None, None) - return (req_type, host, port) + def _parse_request_buff(self, buff): + try: # don't trust on what comes from the outside + req_type, host_type, = struct.unpack('!xBxB', buff[:4]) + if host_type == 0x01: + host_arr = struct.unpack('!iiii', buff[4:8]) + host, = '.'.join(str(s) for s in host_arr) + host_len = len(host) + elif host_type == 0x03: + host_len, = struct.unpack('!B', buff[4]) + host, = struct.unpack('!%ds' % host_len, buff[5:5 + host_len]) + portlen = len(buff[host_len + 5:]) + if portlen == 1: + port, = struct.unpack('!B', buff[host_len + 5]) + elif portlen == 2: + port, = struct.unpack('!H', buff[host_len + 5:]) + # file data, comes with auth message (Gaim bug) + else: + port, = struct.unpack('!H', buff[host_len + 5: host_len + 7]) + self.remaining_buff = buff[host_len + 7:] + except Exception: + return (None, None, None) + return (req_type, host, port) - def read_connect(self): - ''' connect responce: version, auth method ''' - buff = self._recv() - try: - version, method = struct.unpack('!BB', buff) - except Exception: - version, method = None, None - if version != 0x05 or method == 0xff: - self.disconnect() + def read_connect(self): + ''' connect responce: version, auth method ''' + buff = self._recv() + try: + version, method = struct.unpack('!BB', buff) + except Exception: + version, method = None, None + if version != 0x05 or method == 0xff: + self.disconnect() - def continue_paused_transfer(self): - if self.state < 5: - return - if self.file_props['type'] == 'r': - self.idlequeue.plug_idle(self, False, True) - else: - self.idlequeue.plug_idle(self, True, False) + def continue_paused_transfer(self): + if self.state < 5: + return + if self.file_props['type'] == 'r': + self.idlequeue.plug_idle(self, False, True) + else: + self.idlequeue.plug_idle(self, True, False) - def _get_sha1_auth(self): - ''' get sha of sid + Initiator jid + Target jid ''' - if 'is_a_proxy' in self.file_props: - del(self.file_props['is_a_proxy']) - return hashlib.sha1('%s%s%s' % (self.sid, - self.file_props['proxy_sender'], - self.file_props['proxy_receiver'])).hexdigest() - return hashlib.sha1('%s%s%s' % (self.sid, self.initiator, self.target)).\ - hexdigest() + def _get_sha1_auth(self): + ''' get sha of sid + Initiator jid + Target jid ''' + if 'is_a_proxy' in self.file_props: + del(self.file_props['is_a_proxy']) + return hashlib.sha1('%s%s%s' % (self.sid, + self.file_props['proxy_sender'], + self.file_props['proxy_receiver'])).hexdigest() + return hashlib.sha1('%s%s%s' % (self.sid, self.initiator, self.target)).\ + hexdigest() class Socks5Sender(Socks5, IdleObject): - ''' class for sending file to socket over socks5 ''' - def __init__(self, idlequeue, sock_hash, parent, _sock, host=None, - port=None): - self.queue_idx = sock_hash - self.queue = parent - Socks5.__init__(self, idlequeue, host, port, None, None, None) - self._sock = _sock - self._sock.setblocking(False) - self.fd = _sock.fileno() - self._recv = _sock.recv - self._send = _sock.send - self.connected = True - self.state = 1 # waiting for first bytes - self.file_props = None - # start waiting for data - self.idlequeue.plug_idle(self, False, True) + ''' class for sending file to socket over socks5 ''' + def __init__(self, idlequeue, sock_hash, parent, _sock, host=None, + port=None): + self.queue_idx = sock_hash + self.queue = parent + Socks5.__init__(self, idlequeue, host, port, None, None, None) + self._sock = _sock + self._sock.setblocking(False) + self.fd = _sock.fileno() + self._recv = _sock.recv + self._send = _sock.send + self.connected = True + self.state = 1 # waiting for first bytes + self.file_props = None + # start waiting for data + self.idlequeue.plug_idle(self, False, True) - def read_timeout(self): - self.idlequeue.remove_timeout(self.fd) - if self.state > 5: - # no activity for foo seconds - if self.file_props['stalled'] == False: - self.file_props['stalled'] = True - self.queue.process_result(-1, self) - if SEND_TIMEOUT > 0: - self.idlequeue.set_read_timeout(self.fd, SEND_TIMEOUT) - else: - # stop transfer, there is no error code for this - self.pollend() + def read_timeout(self): + self.idlequeue.remove_timeout(self.fd) + if self.state > 5: + # no activity for foo seconds + if self.file_props['stalled'] == False: + self.file_props['stalled'] = True + self.queue.process_result(-1, self) + if SEND_TIMEOUT > 0: + self.idlequeue.set_read_timeout(self.fd, SEND_TIMEOUT) + else: + # stop transfer, there is no error code for this + self.pollend() - def pollout(self): - if not self.connected: - self.disconnect() - return - self.idlequeue.remove_timeout(self.fd) - if self.state == 2: # send reply with desired auth type - self.send_raw(self._get_auth_response()) - elif self.state == 4: # send positive response to the 'connect' - self.send_raw(self._get_request_buff(self.sha_msg, 0x00)) - elif self.state == 7: - if self.file_props['paused']: - self.file_props['continue_cb'] = self.continue_paused_transfer - self.idlequeue.plug_idle(self, False, False) - return - result = self.write_next() - self.queue.process_result(result, self) - if result is None or result <= 0: - self.disconnect() - return - self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) - elif self.state == 8: - self.disconnect() - return - else: - self.disconnect() - if self.state < 5: - self.state += 1 - # unplug and plug this time for reading - self.idlequeue.plug_idle(self, False, True) + def pollout(self): + if not self.connected: + self.disconnect() + return + self.idlequeue.remove_timeout(self.fd) + if self.state == 2: # send reply with desired auth type + self.send_raw(self._get_auth_response()) + elif self.state == 4: # send positive response to the 'connect' + self.send_raw(self._get_request_buff(self.sha_msg, 0x00)) + elif self.state == 7: + if self.file_props['paused']: + self.file_props['continue_cb'] = self.continue_paused_transfer + self.idlequeue.plug_idle(self, False, False) + return + result = self.write_next() + self.queue.process_result(result, self) + if result is None or result <= 0: + self.disconnect() + return + self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) + elif self.state == 8: + self.disconnect() + return + else: + self.disconnect() + if self.state < 5: + self.state += 1 + # unplug and plug this time for reading + self.idlequeue.plug_idle(self, False, True) - def pollend(self): - self.state = 8 # end connection - self.disconnect() - self.file_props['error'] = -1 - self.queue.process_result(-1, self) + def pollend(self): + self.state = 8 # end connection + self.disconnect() + self.file_props['error'] = -1 + self.queue.process_result(-1, self) - def pollin(self): - if self.connected: - if self.state < 5: - result = self.main() - if self.state == 4: - self.queue.result_sha(self.sha_msg, self.queue_idx) - if result == -1: - self.disconnect() + def pollin(self): + if self.connected: + if self.state < 5: + result = self.main() + if self.state == 4: + self.queue.result_sha(self.sha_msg, self.queue_idx) + if result == -1: + self.disconnect() - elif self.state == 5: - if self.file_props is not None and self.file_props['type'] == 'r': - result = self.get_file_contents(0) - self.queue.process_result(result, self) - else: - self.disconnect() + elif self.state == 5: + if self.file_props is not None and self.file_props['type'] == 'r': + result = self.get_file_contents(0) + self.queue.process_result(result, self) + else: + self.disconnect() - def send_file(self): - ''' start sending the file over verified connection ''' - if self.file_props['started']: - return - self.file_props['error'] = 0 - self.file_props['disconnect_cb'] = self.disconnect - self.file_props['started'] = True - self.file_props['completed'] = False - self.file_props['paused'] = False - self.file_props['continue_cb'] = self.continue_paused_transfer - self.file_props['stalled'] = False - self.file_props['connected'] = True - self.file_props['elapsed-time'] = 0 - self.file_props['last-time'] = self.idlequeue.current_time() - self.file_props['received-len'] = 0 - self.pauses = 0 - self.state = 7 - # plug for writing - self.idlequeue.plug_idle(self, True, False) - return self.write_next() # initial for nl byte + def send_file(self): + ''' start sending the file over verified connection ''' + if self.file_props['started']: + return + self.file_props['error'] = 0 + self.file_props['disconnect_cb'] = self.disconnect + self.file_props['started'] = True + self.file_props['completed'] = False + self.file_props['paused'] = False + self.file_props['continue_cb'] = self.continue_paused_transfer + self.file_props['stalled'] = False + self.file_props['connected'] = True + self.file_props['elapsed-time'] = 0 + self.file_props['last-time'] = self.idlequeue.current_time() + self.file_props['received-len'] = 0 + self.pauses = 0 + self.state = 7 + # plug for writing + self.idlequeue.plug_idle(self, True, False) + return self.write_next() # initial for nl byte - def main(self): - ''' initial requests for verifying the connection ''' - if self.state == 1: # initial read - buff = self.receive() - if not self.connected: - return -1 - mechs = self._parse_auth_buff(buff) - if mechs is None: - return -1 # invalid auth methods received - elif self.state == 3: # get next request - buff = self.receive() - req_type, self.sha_msg = self._parse_request_buff(buff)[:2] - if req_type != 0x01: - return -1 # request is not of type 'connect' - self.state += 1 # go to the next step - # unplug & plug for writing - self.idlequeue.plug_idle(self, True, False) - return None + def main(self): + ''' initial requests for verifying the connection ''' + if self.state == 1: # initial read + buff = self.receive() + if not self.connected: + return -1 + mechs = self._parse_auth_buff(buff) + if mechs is None: + return -1 # invalid auth methods received + elif self.state == 3: # get next request + buff = self.receive() + req_type, self.sha_msg = self._parse_request_buff(buff)[:2] + if req_type != 0x01: + return -1 # request is not of type 'connect' + self.state += 1 # go to the next step + # unplug & plug for writing + self.idlequeue.plug_idle(self, True, False) + return None - def disconnect(self, cb=True): - ''' Closes the socket. ''' - # close connection and remove us from the queue - Socks5.disconnect(self) - if self.file_props is not None: - self.file_props['connected'] = False - self.file_props['disconnect_cb'] = None - if self.queue is not None: - self.queue.remove_sender(self.queue_idx, False) + def disconnect(self, cb=True): + ''' Closes the socket. ''' + # close connection and remove us from the queue + Socks5.disconnect(self) + if self.file_props is not None: + self.file_props['connected'] = False + self.file_props['disconnect_cb'] = None + if self.queue is not None: + self.queue.remove_sender(self.queue_idx, False) class Socks5Listener(IdleObject): - def __init__(self, idlequeue, port): - ''' handle all incomming connections on (0.0.0.0, port) - This class implements IdleObject, but we will expect - only pollin events though - ''' - self.port = port - self.ais = socket.getaddrinfo(None, port, socket.AF_UNSPEC, - socket.SOCK_STREAM, socket.SOL_TCP, socket.AI_PASSIVE) - self.ais.sort(reverse=True) # Try IPv6 first - self.queue_idx = -1 - self.idlequeue = idlequeue - self.queue = None - self.started = False - self._sock = None - self.fd = -1 + def __init__(self, idlequeue, port): + ''' handle all incomming connections on (0.0.0.0, port) + This class implements IdleObject, but we will expect + only pollin events though + ''' + self.port = port + self.ais = socket.getaddrinfo(None, port, socket.AF_UNSPEC, + socket.SOCK_STREAM, socket.SOL_TCP, socket.AI_PASSIVE) + self.ais.sort(reverse=True) # Try IPv6 first + self.queue_idx = -1 + self.idlequeue = idlequeue + self.queue = None + self.started = False + self._sock = None + self.fd = -1 - def bind(self): - for ai in self.ais: - #try the different possibilities (ipv6, ipv4, etc.) - self._serv = socket.socket(*ai[:3]) - self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) - self._serv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - # Under windows Vista, we need that to listen on ipv6 AND ipv4 - # Doesn't work under windows XP - if os.name == 'nt': - ver = os.sys.getwindowsversion() - if (ver[3], ver[0], ver[1]) == (2, 6, 0): - # 27 is socket.IPV6_V6ONLY under windows, but not defined ... - self._serv.setsockopt(socket.IPPROTO_IPV6, 27, 1) - # will fail when port as busy, or we don't have rights to bind - try: - self._serv.bind(ai[4]) - self.ai = ai - break - except Exception: - self.ai = None - continue - if not self.ai: - # unable to bind, show error dialog - return None - self._serv.listen(socket.SOMAXCONN) - self._serv.setblocking(False) - self.fd = self._serv.fileno() - self.idlequeue.plug_idle(self, False, True) - self.started = True + def bind(self): + for ai in self.ais: + #try the different possibilities (ipv6, ipv4, etc.) + self._serv = socket.socket(*ai[:3]) + self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + self._serv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + # Under windows Vista, we need that to listen on ipv6 AND ipv4 + # Doesn't work under windows XP + if os.name == 'nt': + ver = os.sys.getwindowsversion() + if (ver[3], ver[0], ver[1]) == (2, 6, 0): + # 27 is socket.IPV6_V6ONLY under windows, but not defined ... + self._serv.setsockopt(socket.IPPROTO_IPV6, 27, 1) + # will fail when port as busy, or we don't have rights to bind + try: + self._serv.bind(ai[4]) + self.ai = ai + break + except Exception: + self.ai = None + continue + if not self.ai: + # unable to bind, show error dialog + return None + self._serv.listen(socket.SOMAXCONN) + self._serv.setblocking(False) + self.fd = self._serv.fileno() + self.idlequeue.plug_idle(self, False, True) + self.started = True - def pollend(self): - ''' called when we stop listening on (host, port) ''' - self.disconnect() + def pollend(self): + ''' called when we stop listening on (host, port) ''' + self.disconnect() - def pollin(self): - ''' accept a new incomming connection and notify queue''' - sock = self.accept_conn() - self.queue.on_connection_accepted(sock) + def pollin(self): + ''' accept a new incomming connection and notify queue''' + sock = self.accept_conn() + self.queue.on_connection_accepted(sock) - def disconnect(self): - ''' free all resources, we are not listening anymore ''' - self.idlequeue.remove_timeout(self.fd) - self.idlequeue.unplug_idle(self.fd) - self.fd = -1 - self.state = -1 - self.started = False - try: - self._serv.close() - except Exception: - pass + def disconnect(self): + ''' free all resources, we are not listening anymore ''' + self.idlequeue.remove_timeout(self.fd) + self.idlequeue.unplug_idle(self.fd) + self.fd = -1 + self.state = -1 + self.started = False + try: + self._serv.close() + except Exception: + pass - def accept_conn(self): - ''' accepts a new incomming connection ''' - _sock = self._serv.accept() - _sock[0].setblocking(False) - return _sock + def accept_conn(self): + ''' accepts a new incomming connection ''' + _sock = self._serv.accept() + _sock[0].setblocking(False) + return _sock class Socks5Receiver(Socks5, IdleObject): - def __init__(self, idlequeue, streamhost, sid, file_props = None): - self.queue_idx = -1 - self.streamhost = streamhost - self.queue = None - self.file_props = file_props - self.connect_timeout = 0 - self.connected = False - self.pauses = 0 - if not self.file_props: - self.file_props = {} - self.file_props['disconnect_cb'] = self.disconnect - self.file_props['error'] = 0 - self.file_props['started'] = True - self.file_props['completed'] = False - self.file_props['paused'] = False - self.file_props['continue_cb'] = self.continue_paused_transfer - self.file_props['stalled'] = False - Socks5.__init__(self, idlequeue, streamhost['host'], - int(streamhost['port']), streamhost['initiator'], streamhost['target'], - sid) + def __init__(self, idlequeue, streamhost, sid, file_props = None): + self.queue_idx = -1 + self.streamhost = streamhost + self.queue = None + self.file_props = file_props + self.connect_timeout = 0 + self.connected = False + self.pauses = 0 + if not self.file_props: + self.file_props = {} + self.file_props['disconnect_cb'] = self.disconnect + self.file_props['error'] = 0 + self.file_props['started'] = True + self.file_props['completed'] = False + self.file_props['paused'] = False + self.file_props['continue_cb'] = self.continue_paused_transfer + self.file_props['stalled'] = False + Socks5.__init__(self, idlequeue, streamhost['host'], + int(streamhost['port']), streamhost['initiator'], streamhost['target'], + sid) - def read_timeout(self): - self.idlequeue.remove_timeout(self.fd) - if self.state > 5: - # no activity for foo seconds - if self.file_props['stalled'] == False: - self.file_props['stalled'] = True - if 'received-len' not in self.file_props: - self.file_props['received-len'] = 0 - self.queue.process_result(-1, self) - if READ_TIMEOUT > 0: - self.idlequeue.set_read_timeout(self.fd, READ_TIMEOUT) - else: - # stop transfer, there is no error code for this - self.pollend() - else: - self.queue.reconnect_receiver(self, self.streamhost) + def read_timeout(self): + self.idlequeue.remove_timeout(self.fd) + if self.state > 5: + # no activity for foo seconds + if self.file_props['stalled'] == False: + self.file_props['stalled'] = True + if 'received-len' not in self.file_props: + self.file_props['received-len'] = 0 + self.queue.process_result(-1, self) + if READ_TIMEOUT > 0: + self.idlequeue.set_read_timeout(self.fd, READ_TIMEOUT) + else: + # stop transfer, there is no error code for this + self.pollend() + else: + self.queue.reconnect_receiver(self, self.streamhost) - def connect(self): - ''' create the socket and plug it to the idlequeue ''' - if self.ais is None: - return None + def connect(self): + ''' create the socket and plug it to the idlequeue ''' + if self.ais is None: + return None - for ai in self.ais: - try: - self._sock = socket.socket(*ai[:3]) - # this will not block the GUI - self._sock.setblocking(False) - self._server = ai[4] - break - except socket.error, e: - if not isinstance(e, basestring) and e[0] == EINPROGRESS: - break - # for all other errors, we try other addresses - continue - self.fd = self._sock.fileno() - self.state = 0 # about to be connected - self.idlequeue.plug_idle(self, True, False) - self.do_connect() - self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - return None + for ai in self.ais: + try: + self._sock = socket.socket(*ai[:3]) + # this will not block the GUI + self._sock.setblocking(False) + self._server = ai[4] + break + except socket.error, e: + if not isinstance(e, basestring) and e[0] == EINPROGRESS: + break + # for all other errors, we try other addresses + continue + self.fd = self._sock.fileno() + self.state = 0 # about to be connected + self.idlequeue.plug_idle(self, True, False) + self.do_connect() + self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + return None - def _is_connected(self): - if self.state < 5: - return False - return True + def _is_connected(self): + if self.state < 5: + return False + return True - def pollout(self): - self.idlequeue.remove_timeout(self.fd) - if self.state == 0: - self.do_connect() - return - elif self.state == 1: # send initially: version and auth types - self.send_raw(self._get_auth_buff()) - elif self.state == 3: # send 'connect' request - self.send_raw(self._get_request_buff(self._get_sha1_auth())) - elif self.file_props['type'] != 'r': - if self.file_props['paused']: - self.idlequeue.plug_idle(self, False, False) - return - result = self.write_next() - self.queue.process_result(result, self) - return - self.state += 1 - # unplug and plug for reading - self.idlequeue.plug_idle(self, False, True) - self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + def pollout(self): + self.idlequeue.remove_timeout(self.fd) + if self.state == 0: + self.do_connect() + return + elif self.state == 1: # send initially: version and auth types + self.send_raw(self._get_auth_buff()) + elif self.state == 3: # send 'connect' request + self.send_raw(self._get_request_buff(self._get_sha1_auth())) + elif self.file_props['type'] != 'r': + if self.file_props['paused']: + self.idlequeue.plug_idle(self, False, False) + return + result = self.write_next() + self.queue.process_result(result, self) + return + self.state += 1 + # unplug and plug for reading + self.idlequeue.plug_idle(self, False, True) + self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - def pollend(self): - if self.state >= 5: - # error during transfer - self.disconnect() - self.file_props['error'] = -1 - self.queue.process_result(-1, self) - else: - self.queue.reconnect_receiver(self, self.streamhost) + def pollend(self): + if self.state >= 5: + # error during transfer + self.disconnect() + self.file_props['error'] = -1 + self.queue.process_result(-1, self) + else: + self.queue.reconnect_receiver(self, self.streamhost) - def pollin(self): - self.idlequeue.remove_timeout(self.fd) - if self.connected: - if self.file_props['paused']: - self.idlequeue.plug_idle(self, False, False) - return - if self.state < 5: - self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - result = self.main(0) - self.queue.process_result(result, self) - elif self.state == 5: # wait for proxy reply - pass - elif self.file_props['type'] == 'r': - self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) - result = self.get_file_contents(0) - self.queue.process_result(result, self) - else: - self.disconnect() + def pollin(self): + self.idlequeue.remove_timeout(self.fd) + if self.connected: + if self.file_props['paused']: + self.idlequeue.plug_idle(self, False, False) + return + if self.state < 5: + self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + result = self.main(0) + self.queue.process_result(result, self) + elif self.state == 5: # wait for proxy reply + pass + elif self.file_props['type'] == 'r': + self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) + result = self.get_file_contents(0) + self.queue.process_result(result, self) + else: + self.disconnect() - def do_connect(self): - try: - self._sock.connect(self._server) - self._sock.setblocking(False) - self._send=self._sock.send - self._recv=self._sock.recv - except Exception, ee: - errnum = ee[0] - self.connect_timeout += 1 - if errnum == 111 or self.connect_timeout > 1000: - self.queue._connection_refused(self.streamhost, - self.file_props, self.queue_idx) - return None - # win32 needs this - elif errnum not in (10056, EISCONN) or self.state != 0: - return None - else: # socket is already connected - self._sock.setblocking(False) - self._send=self._sock.send - self._recv=self._sock.recv - self.buff = '' - self.connected = True - self.file_props['connected'] = True - self.file_props['disconnect_cb'] = self.disconnect - self.state = 1 # connected + def do_connect(self): + try: + self._sock.connect(self._server) + self._sock.setblocking(False) + self._send=self._sock.send + self._recv=self._sock.recv + except Exception, ee: + errnum = ee[0] + self.connect_timeout += 1 + if errnum == 111 or self.connect_timeout > 1000: + self.queue._connection_refused(self.streamhost, + self.file_props, self.queue_idx) + return None + # win32 needs this + elif errnum not in (10056, EISCONN) or self.state != 0: + return None + else: # socket is already connected + self._sock.setblocking(False) + self._send=self._sock.send + self._recv=self._sock.recv + self.buff = '' + self.connected = True + self.file_props['connected'] = True + self.file_props['disconnect_cb'] = self.disconnect + self.state = 1 # connected - # stop all others connections to sender's streamhosts - self.queue._socket_connected(self.streamhost, self.file_props) - self.idlequeue.plug_idle(self, True, False) - return 1 # we are connected + # stop all others connections to sender's streamhosts + self.queue._socket_connected(self.streamhost, self.file_props) + self.idlequeue.plug_idle(self, True, False) + return 1 # we are connected - def main(self, timeout=0): - ''' begin negotiation. on success 'address' != 0 ''' - result = 1 - buff = self.receive() - if buff == '': - # end connection - self.pollend() - return + def main(self, timeout=0): + ''' begin negotiation. on success 'address' != 0 ''' + result = 1 + buff = self.receive() + if buff == '': + # end connection + self.pollend() + return - if self.state == 2: # read auth response - if buff is None or len(buff) != 2: - return None - version, method = struct.unpack('!BB', buff[:2]) - if version != 0x05 or method == 0xff: - self.disconnect() - elif self.state == 4: # get approve of our request - if buff is None: - return None - sub_buff = buff[:4] - if len(sub_buff) < 4: - return None - version, address_type = struct.unpack('!BxxB', buff[:4]) - addrlen = 0 - if address_type == 0x03: - addrlen = ord(buff[4]) - address = struct.unpack('!%ds' % addrlen, buff[5:addrlen + 5]) - portlen = len(buff[addrlen + 5:]) - if portlen == 1: - port, = struct.unpack('!B', buff[addrlen + 5]) - elif portlen == 2: - port, = struct.unpack('!H', buff[addrlen + 5:]) - else: # Gaim bug :) - port, = struct.unpack('!H', buff[addrlen + 5:addrlen + 7]) - self.remaining_buff = buff[addrlen + 7:] - self.state = 5 # for senders: init file_props and send '\n' - if self.queue.on_success: - result = self.queue.send_success_reply(self.file_props, - self.streamhost) - if result == 0: - self.state = 8 - self.disconnect() + if self.state == 2: # read auth response + if buff is None or len(buff) != 2: + return None + version, method = struct.unpack('!BB', buff[:2]) + if version != 0x05 or method == 0xff: + self.disconnect() + elif self.state == 4: # get approve of our request + if buff is None: + return None + sub_buff = buff[:4] + if len(sub_buff) < 4: + return None + version, address_type = struct.unpack('!BxxB', buff[:4]) + addrlen = 0 + if address_type == 0x03: + addrlen = ord(buff[4]) + address = struct.unpack('!%ds' % addrlen, buff[5:addrlen + 5]) + portlen = len(buff[addrlen + 5:]) + if portlen == 1: + port, = struct.unpack('!B', buff[addrlen + 5]) + elif portlen == 2: + port, = struct.unpack('!H', buff[addrlen + 5:]) + else: # Gaim bug :) + port, = struct.unpack('!H', buff[addrlen + 5:addrlen + 7]) + self.remaining_buff = buff[addrlen + 7:] + self.state = 5 # for senders: init file_props and send '\n' + if self.queue.on_success: + result = self.queue.send_success_reply(self.file_props, + self.streamhost) + if result == 0: + self.state = 8 + self.disconnect() - # for senders: init file_props - if result == 1 and self.state == 5: - if self.file_props['type'] == 's': - self.file_props['error'] = 0 - self.file_props['disconnect_cb'] = self.disconnect - self.file_props['started'] = True - self.file_props['completed'] = False - self.file_props['paused'] = False - self.file_props['stalled'] = False - self.file_props['elapsed-time'] = 0 - self.file_props['last-time'] = self.idlequeue.current_time() - self.file_props['received-len'] = 0 - self.pauses = 0 - # start sending file contents to socket - self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) - self.idlequeue.plug_idle(self, True, False) - else: - # receiving file contents from socket - self.idlequeue.plug_idle(self, False, True) - self.file_props['continue_cb'] = self.continue_paused_transfer - # we have set up the connection, next - retrieve file - self.state = 6 - if self.state < 5: - self.idlequeue.plug_idle(self, True, False) - self.state += 1 - return None + # for senders: init file_props + if result == 1 and self.state == 5: + if self.file_props['type'] == 's': + self.file_props['error'] = 0 + self.file_props['disconnect_cb'] = self.disconnect + self.file_props['started'] = True + self.file_props['completed'] = False + self.file_props['paused'] = False + self.file_props['stalled'] = False + self.file_props['elapsed-time'] = 0 + self.file_props['last-time'] = self.idlequeue.current_time() + self.file_props['received-len'] = 0 + self.pauses = 0 + # start sending file contents to socket + self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) + self.idlequeue.plug_idle(self, True, False) + else: + # receiving file contents from socket + self.idlequeue.plug_idle(self, False, True) + self.file_props['continue_cb'] = self.continue_paused_transfer + # we have set up the connection, next - retrieve file + self.state = 6 + if self.state < 5: + self.idlequeue.plug_idle(self, True, False) + self.state += 1 + return None - def disconnect(self, cb=True): - ''' Closes the socket. Remove self from queue if cb is True''' - # close connection - Socks5.disconnect(self) - if cb is True: - self.file_props['disconnect_cb'] = None - if self.queue is not None: - self.queue.remove_receiver(self.queue_idx, False) - -# vim: se ts=3: + def disconnect(self, cb=True): + ''' Closes the socket. Remove self from queue if cb is True''' + # close connection + Socks5.disconnect(self) + if cb is True: + self.file_props['disconnect_cb'] = None + if self.queue is not None: + self.queue.remove_receiver(self.queue_idx, False) diff --git a/src/common/stanza_session.py b/src/common/stanza_session.py index e3e8b84a4..cccdc8508 100644 --- a/src/common/stanza_session.py +++ b/src/common/stanza_session.py @@ -38,1129 +38,1127 @@ from hmac import HMAC from common import crypto if gajim.HAVE_PYCRYPTO: - from Crypto.Cipher import AES - from Crypto.PublicKey import RSA + from Crypto.Cipher import AES + from Crypto.PublicKey import RSA - from common import dh - import secrets + from common import dh + import secrets XmlDsig = 'http://www.w3.org/2000/09/xmldsig#' class StanzaSession(object): - ''' - ''' - def __init__(self, conn, jid, thread_id, type_): - ''' - ''' - self.conn = conn - self.jid = jid - self.type = type_ - self.resource = None + ''' + ''' + def __init__(self, conn, jid, thread_id, type_): + ''' + ''' + self.conn = conn + self.jid = jid + self.type = type_ + self.resource = None - if thread_id: - self.received_thread_id = True - self.thread_id = thread_id - else: - self.received_thread_id = False - if type_ == 'normal': - self.thread_id = None - else: - self.thread_id = self.generate_thread_id() + if thread_id: + self.received_thread_id = True + self.thread_id = thread_id + else: + self.received_thread_id = False + if type_ == 'normal': + self.thread_id = None + else: + self.thread_id = self.generate_thread_id() - self.loggable = True + self.loggable = True - self.last_send = 0 - self.last_receive = 0 - self.status = None - self.negotiated = {} + self.last_send = 0 + self.last_receive = 0 + self.status = None + self.negotiated = {} - def is_loggable(self): - return self.loggable and gajim.config.should_log(self.conn.name, self.jid) + def is_loggable(self): + return self.loggable and gajim.config.should_log(self.conn.name, self.jid) - def get_to(self): - to = str(self.jid) - if self.resource and not to.endswith(self.resource): - to += '/' + self.resource - return to + def get_to(self): + to = str(self.jid) + if self.resource and not to.endswith(self.resource): + to += '/' + self.resource + return to - def remove_events(self, types): - ''' - Remove events associated with this session from the queue. - returns True if any events were removed (unlike events.py remove_events) - ''' - any_removed = False + def remove_events(self, types): + ''' + Remove events associated with this session from the queue. + returns True if any events were removed (unlike events.py remove_events) + ''' + any_removed = False - for j in (self.jid, self.jid.getStripped()): - for event in gajim.events.get_events(self.conn.name, j, types=types): - # the event wasn't in this session - if (event.type_ == 'chat' and event.parameters[8] != self) or \ - (event.type_ == 'printed_chat' and event.parameters[0].session != \ - self): - continue + for j in (self.jid, self.jid.getStripped()): + for event in gajim.events.get_events(self.conn.name, j, types=types): + # the event wasn't in this session + if (event.type_ == 'chat' and event.parameters[8] != self) or \ + (event.type_ == 'printed_chat' and event.parameters[0].session != \ + self): + continue - # events.remove_events returns True when there were no events - # for some reason - r = gajim.events.remove_events(self.conn.name, j, event) + # events.remove_events returns True when there were no events + # for some reason + r = gajim.events.remove_events(self.conn.name, j, event) - if not r: - any_removed = True + if not r: + any_removed = True - return any_removed + return any_removed - def generate_thread_id(self): - return ''.join([f(string.ascii_letters) for f in itertools.repeat( - random.choice, 32)]) + def generate_thread_id(self): + return ''.join([f(string.ascii_letters) for f in itertools.repeat( + random.choice, 32)]) - def send(self, msg): - if self.thread_id: - msg.NT.thread = self.thread_id + def send(self, msg): + if self.thread_id: + msg.NT.thread = self.thread_id - msg.setAttr('to', self.get_to()) - self.conn.send_stanza(msg) + msg.setAttr('to', self.get_to()) + self.conn.send_stanza(msg) - if isinstance(msg, xmpp.Message): - self.last_send = time.time() + if isinstance(msg, xmpp.Message): + self.last_send = time.time() - def reject_negotiation(self, body=None): - msg = xmpp.Message() - feature = msg.NT.feature - feature.setNamespace(xmpp.NS_FEATURE) + def reject_negotiation(self, body=None): + msg = xmpp.Message() + feature = msg.NT.feature + feature.setNamespace(xmpp.NS_FEATURE) - x = xmpp.DataForm(typ='submit') - x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn')) - x.addChild(node=xmpp.DataField(name='accept', value='0')) + x = xmpp.DataForm(typ='submit') + x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn')) + x.addChild(node=xmpp.DataField(name='accept', value='0')) - feature.addChild(node=x) + feature.addChild(node=x) - if body: - msg.setBody(body) + if body: + msg.setBody(body) - self.send(msg) + self.send(msg) - self.cancelled_negotiation() + self.cancelled_negotiation() - def cancelled_negotiation(self): - ''' - A negotiation has been cancelled, so reset this session to its default - state. - ''' - if self.control: - self.control.on_cancel_session_negotiation() + def cancelled_negotiation(self): + ''' + A negotiation has been cancelled, so reset this session to its default + state. + ''' + if self.control: + self.control.on_cancel_session_negotiation() - self.status = None - self.negotiated = {} + self.status = None + self.negotiated = {} - def terminate(self, send_termination = True): - # only send termination message if we've sent a message and think they - # have XEP-0201 support - if send_termination and self.last_send > 0 and \ - (self.received_thread_id or self.last_receive == 0): - msg = xmpp.Message() - feature = msg.NT.feature - feature.setNamespace(xmpp.NS_FEATURE) + def terminate(self, send_termination = True): + # only send termination message if we've sent a message and think they + # have XEP-0201 support + if send_termination and self.last_send > 0 and \ + (self.received_thread_id or self.last_receive == 0): + msg = xmpp.Message() + feature = msg.NT.feature + feature.setNamespace(xmpp.NS_FEATURE) - x = xmpp.DataForm(typ='submit') - x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn')) - x.addChild(node=xmpp.DataField(name='terminate', value='1')) + x = xmpp.DataForm(typ='submit') + x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn')) + x.addChild(node=xmpp.DataField(name='terminate', value='1')) - feature.addChild(node=x) + feature.addChild(node=x) - self.send(msg) + self.send(msg) - self.status = None + self.status = None - def acknowledge_termination(self): - # we could send an acknowledgement message to the remote client here - self.status = None + def acknowledge_termination(self): + # we could send an acknowledgement message to the remote client here + self.status = None class ArchivingStanzaSession(StanzaSession): - def __init__(self, conn, jid, thread_id, type_='chat'): - StanzaSession.__init__(self, conn, jid, thread_id, type_='chat') - self.archiving = False + def __init__(self, conn, jid, thread_id, type_='chat'): + StanzaSession.__init__(self, conn, jid, thread_id, type_='chat') + self.archiving = False - def archiving_logging_preference(self, initiator_options=None): - return self.conn.logging_preference(self.jid, initiator_options) + def archiving_logging_preference(self, initiator_options=None): + return self.conn.logging_preference(self.jid, initiator_options) - def negotiate_archiving(self): - self.negotiated = {} + def negotiate_archiving(self): + self.negotiated = {} - request = xmpp.Message() - feature = request.NT.feature - feature.setNamespace(xmpp.NS_FEATURE) + request = xmpp.Message() + feature = request.NT.feature + feature.setNamespace(xmpp.NS_FEATURE) - x = xmpp.DataForm(typ='form') + x = xmpp.DataForm(typ='form') - x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn', - typ='hidden')) - x.addChild(node=xmpp.DataField(name='accept', value='1', typ='boolean', - required=True)) + x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn', + typ='hidden')) + x.addChild(node=xmpp.DataField(name='accept', value='1', typ='boolean', + required=True)) - x.addChild(node=xmpp.DataField(name='logging', typ='list-single', - options=self.archiving_logging_preference(), required=True)) + x.addChild(node=xmpp.DataField(name='logging', typ='list-single', + options=self.archiving_logging_preference(), required=True)) - x.addChild(node=xmpp.DataField(name='disclosure', typ='list-single', - options=['never'], required=True)) - x.addChild(node=xmpp.DataField(name='security', typ='list-single', - options=['none'], required=True)) + x.addChild(node=xmpp.DataField(name='disclosure', typ='list-single', + options=['never'], required=True)) + x.addChild(node=xmpp.DataField(name='security', typ='list-single', + options=['none'], required=True)) - feature.addChild(node=x) + feature.addChild(node=x) - self.status = 'requested-archiving' + self.status = 'requested-archiving' - self.send(request) + self.send(request) - def respond_archiving(self, form): - field = form.getField('logging') - options = [x[1] for x in field.getOptions()] - values = field.getValues() + def respond_archiving(self, form): + field = form.getField('logging') + options = [x[1] for x in field.getOptions()] + values = field.getValues() - logging = self.archiving_logging_preference(options) - self.negotiated['logging'] = logging + logging = self.archiving_logging_preference(options) + self.negotiated['logging'] = logging - response = xmpp.Message() - feature = response.NT.feature - feature.setNamespace(xmpp.NS_FEATURE) + response = xmpp.Message() + feature = response.NT.feature + feature.setNamespace(xmpp.NS_FEATURE) - x = xmpp.DataForm(typ='submit') + x = xmpp.DataForm(typ='submit') - x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn')) - x.addChild(node=xmpp.DataField(name='accept', value='true')) + x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn')) + x.addChild(node=xmpp.DataField(name='accept', value='true')) - x.addChild(node=xmpp.DataField(name='logging', value=logging)) + x.addChild(node=xmpp.DataField(name='logging', value=logging)) - self.status = 'responded-archiving' + self.status = 'responded-archiving' - feature.addChild(node=x) + feature.addChild(node=x) - if not logging: - response = xmpp.Error(response, xmpp.ERR_NOT_ACCEPTABLE) + if not logging: + response = xmpp.Error(response, xmpp.ERR_NOT_ACCEPTABLE) - feature = xmpp.Node(xmpp.NS_FEATURE + ' feature') + feature = xmpp.Node(xmpp.NS_FEATURE + ' feature') - n = xmpp.Node('field') - n['var'] = 'logging' - feature.addChild(node=n) + n = xmpp.Node('field') + n['var'] = 'logging' + feature.addChild(node=n) - response.T.error.addChild(node=feature) + response.T.error.addChild(node=feature) - self.send(response) + self.send(response) - def we_accept_archiving(self, form): - if self.negotiated['logging'] == 'mustnot': - self.loggable = False - log.debug('archiving session accepted: %s' % self.loggable) - self.status = 'active' - self.archiving = True - if self.control: - self.control.print_archiving_session_details() + def we_accept_archiving(self, form): + if self.negotiated['logging'] == 'mustnot': + self.loggable = False + log.debug('archiving session accepted: %s' % self.loggable) + self.status = 'active' + self.archiving = True + if self.control: + self.control.print_archiving_session_details() - def archiving_accepted(self, form): - negotiated = {} - ask_user = {} - not_acceptable = [] + def archiving_accepted(self, form): + negotiated = {} + ask_user = {} + not_acceptable = [] - if form['logging'] not in self.archiving_logging_preference(): - raise + if form['logging'] not in self.archiving_logging_preference(): + raise - self.negotiated['logging'] = form['logging'] + self.negotiated['logging'] = form['logging'] - accept = xmpp.Message() - feature = accept.NT.feature - feature.setNamespace(xmpp.NS_FEATURE) + accept = xmpp.Message() + feature = accept.NT.feature + feature.setNamespace(xmpp.NS_FEATURE) - result = xmpp.DataForm(typ='result') + result = xmpp.DataForm(typ='result') - result.addChild(node=xmpp.DataField(name='FORM_TYPE', - value='urn:xmpp:ssn')) - result.addChild(node=xmpp.DataField(name='accept', value='1')) + result.addChild(node=xmpp.DataField(name='FORM_TYPE', + value='urn:xmpp:ssn')) + result.addChild(node=xmpp.DataField(name='accept', value='1')) - feature.addChild(node=result) + feature.addChild(node=result) - self.send(accept) - if self.negotiated['logging'] == 'mustnot': - self.loggable = False - log.debug('archiving session accepted: %s' % self.loggable) - self.status = 'active' - self.archiving = True - if self.control: - self.control.print_archiving_session_details() + self.send(accept) + if self.negotiated['logging'] == 'mustnot': + self.loggable = False + log.debug('archiving session accepted: %s' % self.loggable) + self.status = 'active' + self.archiving = True + if self.control: + self.control.print_archiving_session_details() class EncryptedStanzaSession(StanzaSession): - ''' - An encrypted stanza negotiation has several states. They arerepresented as - the following values in the 'status' attribute of the session object: - - 1. None: - default state - 2. 'requested-e2e': - this client has initiated an esession negotiation and is waiting - for a response - 3. 'responded-e2e': - this client has responded to an esession negotiation request and - is waiting for the initiator to identify itself and complete the - negotiation - 4. 'identified-alice': - this client identified itself and is waiting for the responder to - identify itself and complete the negotiation - 5. 'active': - an encrypted session has been successfully negotiated. messages - of any of the types listed in 'encryptable_stanzas' should be - encrypted before they're sent. - - The transition between these states is handled in gajim.py's - handle_session_negotiation method. - ''' - def __init__(self, conn, jid, thread_id, type_='chat'): - StanzaSession.__init__(self, conn, jid, thread_id, type_='chat') - - self.xes = {} - self.es = {} - self.n = 128 - self.enable_encryption = False - - # _s denotes 'self' (ie. this client) - self._kc_s = None - # _o denotes 'other' (ie. the client at the other end of the session) - self._kc_o = None - - # has the remote contact's identity ever been verified? - self.verified_identity = False - - def _get_contact(self): - c = gajim.contacts.get_contact(self.conn.name, self.jid, self.resource) - if not c: - c = gajim.contacts.get_contact(self.conn.name, self.jid) - return c - - def _is_buggy_gajim(self): - c = self._get_contact() - if c and c.supports(xmpp.NS_ROSTERX): - return False - return True - - def set_kc_s(self, value): - ''' - keep the encrypter updated with my latest cipher key - ''' - self._kc_s = value - self.encrypter = self.cipher.new(self._kc_s, self.cipher.MODE_CTR, - counter=self.encryptcounter) - - def get_kc_s(self): - return self._kc_s - - def set_kc_o(self, value): - ''' - keep the decrypter updated with the other party's latest cipher key - ''' - self._kc_o = value - self.decrypter = self.cipher.new(self._kc_o, self.cipher.MODE_CTR, - counter=self.decryptcounter) - - def get_kc_o(self): - return self._kc_o - - kc_s = property(get_kc_s, set_kc_s) - kc_o = property(get_kc_o, set_kc_o) - - def encryptcounter(self): - self.c_s = (self.c_s + 1) % (2 ** self.n) - return crypto.encode_mpi_with_padding(self.c_s) - - def decryptcounter(self): - self.c_o = (self.c_o + 1) % (2 ** self.n) - return crypto.encode_mpi_with_padding(self.c_o) - - def sign(self, string): - if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'): - hash_ = crypto.sha256(string) - return crypto.encode_mpi(gajim.pubkey.sign(hash_, '')[0]) - - def encrypt_stanza(self, stanza): - encryptable = [x for x in stanza.getChildren() if x.getName() not in - ('error', 'amp', 'thread')] - - # FIXME can also encrypt contents of elements in stanzas @type = - # 'error' - # (except for child elements) - - old_en_counter = self.c_s - - for element in encryptable: - stanza.delChild(element) - - plaintext = ''.join(map(str, encryptable)) - - m_compressed = self.compress(plaintext) - m_final = self.encrypt(m_compressed) - - c = stanza.NT.c - c.setNamespace('http://www.xmpp.org/extensions/xep-0200.html#ns') - c.NT.data = base64.b64encode(m_final) - - # FIXME check for rekey request, handle elements - - m_content = ''.join(map(str, c.getChildren())) - c.NT.mac = base64.b64encode(self.hmac(self.km_s, m_content + \ - crypto.encode_mpi(old_en_counter))) - - msgtxt = '[This is part of an encrypted session. ' \ - 'If you see this message, something went wrong.]' - lang = os.getenv('LANG') - if lang is not None and lang != 'en': # we're not english - msgtxt = _('[This is part of an encrypted session. ' - 'If you see this message, something went wrong.]') + ' (' + \ - msgtxt + ')' - stanza.setBody(msgtxt) + ''' + An encrypted stanza negotiation has several states. They arerepresented as + the following values in the 'status' attribute of the session object: + + 1. None: + default state + 2. 'requested-e2e': + this client has initiated an esession negotiation and is waiting + for a response + 3. 'responded-e2e': + this client has responded to an esession negotiation request and + is waiting for the initiator to identify itself and complete the + negotiation + 4. 'identified-alice': + this client identified itself and is waiting for the responder to + identify itself and complete the negotiation + 5. 'active': + an encrypted session has been successfully negotiated. messages + of any of the types listed in 'encryptable_stanzas' should be + encrypted before they're sent. + + The transition between these states is handled in gajim.py's + handle_session_negotiation method. + ''' + def __init__(self, conn, jid, thread_id, type_='chat'): + StanzaSession.__init__(self, conn, jid, thread_id, type_='chat') + + self.xes = {} + self.es = {} + self.n = 128 + self.enable_encryption = False + + # _s denotes 'self' (ie. this client) + self._kc_s = None + # _o denotes 'other' (ie. the client at the other end of the session) + self._kc_o = None + + # has the remote contact's identity ever been verified? + self.verified_identity = False + + def _get_contact(self): + c = gajim.contacts.get_contact(self.conn.name, self.jid, self.resource) + if not c: + c = gajim.contacts.get_contact(self.conn.name, self.jid) + return c + + def _is_buggy_gajim(self): + c = self._get_contact() + if c and c.supports(xmpp.NS_ROSTERX): + return False + return True + + def set_kc_s(self, value): + ''' + keep the encrypter updated with my latest cipher key + ''' + self._kc_s = value + self.encrypter = self.cipher.new(self._kc_s, self.cipher.MODE_CTR, + counter=self.encryptcounter) + + def get_kc_s(self): + return self._kc_s + + def set_kc_o(self, value): + ''' + keep the decrypter updated with the other party's latest cipher key + ''' + self._kc_o = value + self.decrypter = self.cipher.new(self._kc_o, self.cipher.MODE_CTR, + counter=self.decryptcounter) + + def get_kc_o(self): + return self._kc_o + + kc_s = property(get_kc_s, set_kc_s) + kc_o = property(get_kc_o, set_kc_o) + + def encryptcounter(self): + self.c_s = (self.c_s + 1) % (2 ** self.n) + return crypto.encode_mpi_with_padding(self.c_s) + + def decryptcounter(self): + self.c_o = (self.c_o + 1) % (2 ** self.n) + return crypto.encode_mpi_with_padding(self.c_o) + + def sign(self, string): + if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'): + hash_ = crypto.sha256(string) + return crypto.encode_mpi(gajim.pubkey.sign(hash_, '')[0]) + + def encrypt_stanza(self, stanza): + encryptable = [x for x in stanza.getChildren() if x.getName() not in + ('error', 'amp', 'thread')] + + # FIXME can also encrypt contents of elements in stanzas @type = + # 'error' + # (except for child elements) + + old_en_counter = self.c_s + + for element in encryptable: + stanza.delChild(element) + + plaintext = ''.join(map(str, encryptable)) + + m_compressed = self.compress(plaintext) + m_final = self.encrypt(m_compressed) + + c = stanza.NT.c + c.setNamespace('http://www.xmpp.org/extensions/xep-0200.html#ns') + c.NT.data = base64.b64encode(m_final) + + # FIXME check for rekey request, handle elements + + m_content = ''.join(map(str, c.getChildren())) + c.NT.mac = base64.b64encode(self.hmac(self.km_s, m_content + \ + crypto.encode_mpi(old_en_counter))) + + msgtxt = '[This is part of an encrypted session. ' \ + 'If you see this message, something went wrong.]' + lang = os.getenv('LANG') + if lang is not None and lang != 'en': # we're not english + msgtxt = _('[This is part of an encrypted session. ' + 'If you see this message, something went wrong.]') + ' (' + \ + msgtxt + ')' + stanza.setBody(msgtxt) - return stanza + return stanza - def is_xep_200_encrypted(self, msg): - msg.getTag('c', namespace=xmpp.NS_STANZA_CRYPTO) + def is_xep_200_encrypted(self, msg): + msg.getTag('c', namespace=xmpp.NS_STANZA_CRYPTO) - def hmac(self, key, content): - return HMAC(key, content, self.hash_alg).digest() + def hmac(self, key, content): + return HMAC(key, content, self.hash_alg).digest() - def generate_initiator_keys(self, k): - return (self.hmac(k, 'Initiator Cipher Key'), - self.hmac(k, 'Initiator MAC Key'), - self.hmac(k, 'Initiator SIGMA Key')) - - def generate_responder_keys(self, k): - return (self.hmac(k, 'Responder Cipher Key'), - self.hmac(k, 'Responder MAC Key'), - self.hmac(k, 'Responder SIGMA Key')) + def generate_initiator_keys(self, k): + return (self.hmac(k, 'Initiator Cipher Key'), + self.hmac(k, 'Initiator MAC Key'), + self.hmac(k, 'Initiator SIGMA Key')) + + def generate_responder_keys(self, k): + return (self.hmac(k, 'Responder Cipher Key'), + self.hmac(k, 'Responder MAC Key'), + self.hmac(k, 'Responder SIGMA Key')) - def compress(self, plaintext): - if self.compression is None: - return plaintext + def compress(self, plaintext): + if self.compression is None: + return plaintext - def decompress(self, compressed): - if self.compression is None: - return compressed + def decompress(self, compressed): + if self.compression is None: + return compressed - def encrypt(self, encryptable): - padded = crypto.pad_to_multiple(encryptable, 16, ' ', False) + def encrypt(self, encryptable): + padded = crypto.pad_to_multiple(encryptable, 16, ' ', False) - return self.encrypter.encrypt(padded) + return self.encrypter.encrypt(padded) - def decrypt_stanza(self, stanza): - ''' delete the unencrypted explanation body, if it exists ''' - orig_body = stanza.getTag('body') - if orig_body: - stanza.delChild(orig_body) + def decrypt_stanza(self, stanza): + ''' delete the unencrypted explanation body, if it exists ''' + orig_body = stanza.getTag('body') + if orig_body: + stanza.delChild(orig_body) - c = stanza.getTag(name='c', - namespace='http://www.xmpp.org/extensions/xep-0200.html#ns') + c = stanza.getTag(name='c', + namespace='http://www.xmpp.org/extensions/xep-0200.html#ns') - stanza.delChild(c) + stanza.delChild(c) - # contents of , minus , minus whitespace - macable = ''.join(str(x) for x in c.getChildren() if x.getName() != 'mac') + # contents of , minus , minus whitespace + macable = ''.join(str(x) for x in c.getChildren() if x.getName() != 'mac') - received_mac = base64.b64decode(c.getTagData('mac')) - calculated_mac = self.hmac(self.km_o, macable + \ - crypto.encode_mpi_with_padding(self.c_o)) + received_mac = base64.b64decode(c.getTagData('mac')) + calculated_mac = self.hmac(self.km_o, macable + \ + crypto.encode_mpi_with_padding(self.c_o)) - if not calculated_mac == received_mac: - raise DecryptionError('bad signature') + if not calculated_mac == received_mac: + raise DecryptionError('bad signature') - m_final = base64.b64decode(c.getTagData('data')) - m_compressed = self.decrypt(m_final) - plaintext = self.decompress(m_compressed) + m_final = base64.b64decode(c.getTagData('data')) + m_compressed = self.decrypt(m_final) + plaintext = self.decompress(m_compressed) - try: - parsed = xmpp.Node(node='' + plaintext + '') - except Exception: - raise DecryptionError('decrypted not parseable as XML') + try: + parsed = xmpp.Node(node='' + plaintext + '') + except Exception: + raise DecryptionError('decrypted not parseable as XML') - for child in parsed.getChildren(): - stanza.addChild(node=child) + for child in parsed.getChildren(): + stanza.addChild(node=child) - return stanza + return stanza - def decrypt(self, ciphertext): - return self.decrypter.decrypt(ciphertext) + def decrypt(self, ciphertext): + return self.decrypter.decrypt(ciphertext) - def logging_preference(self): - if gajim.config.get_per('accounts', self.conn.name, - 'log_encrypted_sessions'): - return ['may', 'mustnot'] - else: - return ['mustnot', 'may'] + def logging_preference(self): + if gajim.config.get_per('accounts', self.conn.name, + 'log_encrypted_sessions'): + return ['may', 'mustnot'] + else: + return ['mustnot', 'may'] - def get_shared_secret(self, e, y, p): - if (not 1 < e < (p - 1)): - raise NegotiationError('invalid DH value') + def get_shared_secret(self, e, y, p): + if (not 1 < e < (p - 1)): + raise NegotiationError('invalid DH value') - return crypto.sha256(crypto.encode_mpi(crypto.powmod(e, y, p))) + return crypto.sha256(crypto.encode_mpi(crypto.powmod(e, y, p))) - def c7lize_mac_id(self, form): - kids = form.getChildren() - macable = [x for x in kids if x.getVar() not in ('mac', 'identity')] - return ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el in \ - macable) + def c7lize_mac_id(self, form): + kids = form.getChildren() + macable = [x for x in kids if x.getVar() not in ('mac', 'identity')] + return ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el in \ + macable) - def verify_identity(self, form, dh_i, sigmai, i_o): - m_o = base64.b64decode(form['mac']) - id_o = base64.b64decode(form['identity']) + def verify_identity(self, form, dh_i, sigmai, i_o): + m_o = base64.b64decode(form['mac']) + id_o = base64.b64decode(form['identity']) - m_o_calculated = self.hmac(self.km_o, crypto.encode_mpi(self.c_o) + id_o) + m_o_calculated = self.hmac(self.km_o, crypto.encode_mpi(self.c_o) + id_o) - if m_o_calculated != m_o: - raise NegotiationError('calculated m_%s differs from received m_%s' % - (i_o, i_o)) + if m_o_calculated != m_o: + raise NegotiationError('calculated m_%s differs from received m_%s' % + (i_o, i_o)) - if i_o == 'a' and self.sas_algs == 'sas28x5': - # we don't need to calculate this if there's a verified retained secret - # (but we do anyways) - self.sas = crypto.sas_28x5(m_o, self.form_s) + if i_o == 'a' and self.sas_algs == 'sas28x5': + # we don't need to calculate this if there's a verified retained secret + # (but we do anyways) + self.sas = crypto.sas_28x5(m_o, self.form_s) - if self.negotiated['recv_pubkey']: - plaintext = self.decrypt(id_o) - parsed = xmpp.Node(node='' + plaintext + '') + if self.negotiated['recv_pubkey']: + plaintext = self.decrypt(id_o) + parsed = xmpp.Node(node='' + plaintext + '') - if self.negotiated['recv_pubkey'] == 'hash': - # fingerprint = parsed.getTagData('fingerprint') - # FIXME find stored pubkey or terminate session - raise NotImplementedError() - else: - if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'): - keyvalue = parsed.getTag(name='RSAKeyValue', namespace=XmlDsig) + if self.negotiated['recv_pubkey'] == 'hash': + # fingerprint = parsed.getTagData('fingerprint') + # FIXME find stored pubkey or terminate session + raise NotImplementedError() + else: + if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'): + keyvalue = parsed.getTag(name='RSAKeyValue', namespace=XmlDsig) - n, e = (crypto.decode_mpi(base64.b64decode( - keyvalue.getTagData(x))) for x in ('Modulus', 'Exponent')) - eir_pubkey = RSA.construct((n,long(e))) + n, e = (crypto.decode_mpi(base64.b64decode( + keyvalue.getTagData(x))) for x in ('Modulus', 'Exponent')) + eir_pubkey = RSA.construct((n, long(e))) - pubkey_o = xmpp.c14n.c14n(keyvalue, self._is_buggy_gajim()) - else: - # FIXME DSA, etc. - raise NotImplementedError() + pubkey_o = xmpp.c14n.c14n(keyvalue, self._is_buggy_gajim()) + else: + # FIXME DSA, etc. + raise NotImplementedError() - enc_sig = parsed.getTag(name='SignatureValue', - namespace=XmlDsig).getData() - signature = (crypto.decode_mpi(base64.b64decode(enc_sig)), ) - else: - mac_o = self.decrypt(id_o) - pubkey_o = '' + enc_sig = parsed.getTag(name='SignatureValue', + namespace=XmlDsig).getData() + signature = (crypto.decode_mpi(base64.b64decode(enc_sig)), ) + else: + mac_o = self.decrypt(id_o) + pubkey_o = '' - c7l_form = self.c7lize_mac_id(form) + c7l_form = self.c7lize_mac_id(form) - content = self.n_s + self.n_o + crypto.encode_mpi(dh_i) + pubkey_o + content = self.n_s + self.n_o + crypto.encode_mpi(dh_i) + pubkey_o - if sigmai: - self.form_o = c7l_form - content += self.form_o - else: - form_o2 = c7l_form - content += self.form_o + form_o2 + if sigmai: + self.form_o = c7l_form + content += self.form_o + else: + form_o2 = c7l_form + content += self.form_o + form_o2 - mac_o_calculated = self.hmac(self.ks_o, content) + mac_o_calculated = self.hmac(self.ks_o, content) - if self.negotiated['recv_pubkey']: - hash_ = crypto.sha256(mac_o_calculated) + if self.negotiated['recv_pubkey']: + hash_ = crypto.sha256(mac_o_calculated) - if not eir_pubkey.verify(hash_, signature): - raise NegotiationError('public key signature verification failed!') + if not eir_pubkey.verify(hash_, signature): + raise NegotiationError('public key signature verification failed!') - elif mac_o_calculated != mac_o: - raise NegotiationError('calculated mac_%s differs from received mac_%s' - % (i_o, i_o)) + elif mac_o_calculated != mac_o: + raise NegotiationError('calculated mac_%s differs from received mac_%s' + % (i_o, i_o)) - def make_identity(self, form, dh_i): - if self.negotiated['send_pubkey']: - if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'): - pubkey = secrets.secrets().my_pubkey(self.conn.name) - fields = (pubkey.n, pubkey.e) + def make_identity(self, form, dh_i): + if self.negotiated['send_pubkey']: + if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'): + pubkey = secrets.secrets().my_pubkey(self.conn.name) + fields = (pubkey.n, pubkey.e) - cb_fields = [base64.b64encode(crypto.encode_mpi(f)) for f in - fields] + cb_fields = [base64.b64encode(crypto.encode_mpi(f)) for f in + fields] - pubkey_s = '%s%s' % \ - tuple(cb_fields) - else: - pubkey_s = '' + pubkey_s = '%s%s' % \ + tuple(cb_fields) + else: + pubkey_s = '' - form_s2 = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el in \ - form.getChildren()) + form_s2 = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el in \ + form.getChildren()) - old_c_s = self.c_s - content = self.n_o + self.n_s + crypto.encode_mpi(dh_i) + pubkey_s + \ - self.form_s + form_s2 + old_c_s = self.c_s + content = self.n_o + self.n_s + crypto.encode_mpi(dh_i) + pubkey_s + \ + self.form_s + form_s2 - mac_s = self.hmac(self.ks_s, content) + mac_s = self.hmac(self.ks_s, content) - if self.negotiated['send_pubkey']: - signature = self.sign(mac_s) + if self.negotiated['send_pubkey']: + signature = self.sign(mac_s) - sign_s = '' - '%s' % base64.b64encode(signature) + sign_s = '' + '%s' % base64.b64encode(signature) - if self.negotiated['send_pubkey'] == 'hash': - b64ed = base64.b64encode(self.hash(pubkey_s)) - pubkey_s = '%s' % b64ed + if self.negotiated['send_pubkey'] == 'hash': + b64ed = base64.b64encode(self.hash(pubkey_s)) + pubkey_s = '%s' % b64ed - id_s = self.encrypt(pubkey_s + sign_s) - else: - id_s = self.encrypt(mac_s) + id_s = self.encrypt(pubkey_s + sign_s) + else: + id_s = self.encrypt(mac_s) - m_s = self.hmac(self.km_s, crypto.encode_mpi(old_c_s) + id_s) + m_s = self.hmac(self.km_s, crypto.encode_mpi(old_c_s) + id_s) - if self.status == 'requested-e2e' and self.sas_algs == 'sas28x5': - # we're alice; check for a retained secret - # if none exists, prompt the user with the SAS - self.sas = crypto.sas_28x5(m_s, self.form_o) + if self.status == 'requested-e2e' and self.sas_algs == 'sas28x5': + # we're alice; check for a retained secret + # if none exists, prompt the user with the SAS + self.sas = crypto.sas_28x5(m_s, self.form_o) - if self.sigmai: - # FIXME save retained secret? - self.check_identity(tuple) + if self.sigmai: + # FIXME save retained secret? + self.check_identity(tuple) - return (xmpp.DataField(name='identity', value=base64.b64encode(id_s)), - xmpp.DataField(name='mac', value=base64.b64encode(m_s))) + return (xmpp.DataField(name='identity', value=base64.b64encode(id_s)), + xmpp.DataField(name='mac', value=base64.b64encode(m_s))) - def negotiate_e2e(self, sigmai): - self.negotiated = {} + def negotiate_e2e(self, sigmai): + self.negotiated = {} - request = xmpp.Message() - feature = request.NT.feature - feature.setNamespace(xmpp.NS_FEATURE) + request = xmpp.Message() + feature = request.NT.feature + feature.setNamespace(xmpp.NS_FEATURE) - x = xmpp.DataForm(typ='form') + x = xmpp.DataForm(typ='form') - x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn', - typ='hidden')) - x.addChild(node=xmpp.DataField(name='accept', value='1', typ='boolean', - required=True)) + x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn', + typ='hidden')) + x.addChild(node=xmpp.DataField(name='accept', value='1', typ='boolean', + required=True)) - # this field is incorrectly called 'otr' in XEPs 0116 and 0217 - x.addChild(node=xmpp.DataField(name='logging', typ='list-single', - options=self.logging_preference(), required=True)) + # this field is incorrectly called 'otr' in XEPs 0116 and 0217 + x.addChild(node=xmpp.DataField(name='logging', typ='list-single', + options=self.logging_preference(), required=True)) - # unsupported options: 'disabled', 'enabled' - x.addChild(node=xmpp.DataField(name='disclosure', typ='list-single', - options=['never'], required=True)) - x.addChild(node=xmpp.DataField(name='security', typ='list-single', - options=['e2e'], required=True)) - x.addChild(node=xmpp.DataField(name='crypt_algs', value='aes128-ctr', - typ='hidden')) - x.addChild(node=xmpp.DataField(name='hash_algs', value='sha256', - typ='hidden')) - x.addChild(node=xmpp.DataField(name='compress', value='none', - typ='hidden')) - - # unsupported options: 'iq', 'presence' - x.addChild(node=xmpp.DataField(name='stanzas', typ='list-multi', - options=['message'])) - - x.addChild(node=xmpp.DataField(name='init_pubkey', options=['none', 'key', - 'hash'], typ='list-single')) - - # FIXME store key, use hash - x.addChild(node=xmpp.DataField(name='resp_pubkey', options=['none', - 'key'], typ='list-single')) - - x.addChild(node=xmpp.DataField(name='ver', value='1.0', typ='hidden')) - - x.addChild(node=xmpp.DataField(name='rekey_freq', value='4294967295', - typ='hidden')) - - x.addChild(node=xmpp.DataField(name='sas_algs', value='sas28x5', - typ='hidden')) - x.addChild(node=xmpp.DataField(name='sign_algs', - value='http://www.w3.org/2000/09/xmldsig#rsa-sha256', typ='hidden')) - - self.n_s = crypto.generate_nonce() - - x.addChild(node=xmpp.DataField(name='my_nonce', - value=base64.b64encode(self.n_s), typ='hidden')) - - modp_options = [ int(g) for g in gajim.config.get('esession_modp').split( - ',') ] - - x.addChild(node=xmpp.DataField(name='modp', typ='list-single', - options=[[None, y] for y in modp_options])) - - x.addChild(node=self.make_dhfield(modp_options, sigmai)) - self.sigmai = sigmai - - self.form_s = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \ - in x.getChildren()) - - feature.addChild(node=x) - - self.status = 'requested-e2e' - - self.send(request) - - def verify_options_bob(self, form): - ''' 4.3 esession response (bob) ''' - negotiated = {'recv_pubkey': None, 'send_pubkey': None} - not_acceptable = [] - ask_user = {} - - fixed = { 'disclosure': 'never', 'security': 'e2e', - 'crypt_algs': 'aes128-ctr', 'hash_algs': 'sha256', 'compress': 'none', - 'stanzas': 'message', 'init_pubkey': 'none', 'resp_pubkey': 'none', - 'ver': '1.0', 'sas_algs': 'sas28x5' } - - self.encryptable_stanzas = ['message'] - - self.sas_algs = 'sas28x5' - self.cipher = AES - self.hash_alg = sha256 - self.compression = None - - for name in form.asDict(): - field = form.getField(name) - options = [x[1] for x in field.getOptions()] - values = field.getValues() - - if not field.getType() in ('list-single', 'list-multi'): - options = values - - if name in fixed: - if fixed[name] in options: - negotiated[name] = fixed[name] - else: - not_acceptable.append(name) - elif name == 'rekey_freq': - preferred = int(options[0]) - negotiated['rekey_freq'] = preferred - self.rekey_freq = preferred - elif name == 'logging': - my_prefs = self.logging_preference() - - if my_prefs[0] in options: # our first choice is offered, select it - pref = my_prefs[0] - negotiated['logging'] = pref - else: # see if other acceptable choices are offered - for pref in my_prefs: - if pref in options: - ask_user['logging'] = pref - break - - if not 'logging' in ask_user: - not_acceptable.append(name) - elif name == 'init_pubkey': - for x in ('key'): - if x in options: - negotiated['recv_pubkey'] = x - break - elif name == 'resp_pubkey': - for x in ('hash', 'key'): - if x in options: - negotiated['send_pubkey'] = x - break - elif name == 'sign_algs': - if (XmlDsig + 'rsa-sha256') in options: - negotiated['sign_algs'] = XmlDsig + 'rsa-sha256' - else: - # FIXME some things are handled elsewhere, some things are - # not-implemented - pass + # unsupported options: 'disabled', 'enabled' + x.addChild(node=xmpp.DataField(name='disclosure', typ='list-single', + options=['never'], required=True)) + x.addChild(node=xmpp.DataField(name='security', typ='list-single', + options=['e2e'], required=True)) + x.addChild(node=xmpp.DataField(name='crypt_algs', value='aes128-ctr', + typ='hidden')) + x.addChild(node=xmpp.DataField(name='hash_algs', value='sha256', + typ='hidden')) + x.addChild(node=xmpp.DataField(name='compress', value='none', + typ='hidden')) + + # unsupported options: 'iq', 'presence' + x.addChild(node=xmpp.DataField(name='stanzas', typ='list-multi', + options=['message'])) + + x.addChild(node=xmpp.DataField(name='init_pubkey', options=['none', 'key', + 'hash'], typ='list-single')) + + # FIXME store key, use hash + x.addChild(node=xmpp.DataField(name='resp_pubkey', options=['none', + 'key'], typ='list-single')) + + x.addChild(node=xmpp.DataField(name='ver', value='1.0', typ='hidden')) + + x.addChild(node=xmpp.DataField(name='rekey_freq', value='4294967295', + typ='hidden')) + + x.addChild(node=xmpp.DataField(name='sas_algs', value='sas28x5', + typ='hidden')) + x.addChild(node=xmpp.DataField(name='sign_algs', + value='http://www.w3.org/2000/09/xmldsig#rsa-sha256', typ='hidden')) + + self.n_s = crypto.generate_nonce() + + x.addChild(node=xmpp.DataField(name='my_nonce', + value=base64.b64encode(self.n_s), typ='hidden')) + + modp_options = [ int(g) for g in gajim.config.get('esession_modp').split( + ',') ] + + x.addChild(node=xmpp.DataField(name='modp', typ='list-single', + options=[[None, y] for y in modp_options])) + + x.addChild(node=self.make_dhfield(modp_options, sigmai)) + self.sigmai = sigmai + + self.form_s = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \ + in x.getChildren()) + + feature.addChild(node=x) + + self.status = 'requested-e2e' + + self.send(request) + + def verify_options_bob(self, form): + ''' 4.3 esession response (bob) ''' + negotiated = {'recv_pubkey': None, 'send_pubkey': None} + not_acceptable = [] + ask_user = {} + + fixed = { 'disclosure': 'never', 'security': 'e2e', + 'crypt_algs': 'aes128-ctr', 'hash_algs': 'sha256', 'compress': 'none', + 'stanzas': 'message', 'init_pubkey': 'none', 'resp_pubkey': 'none', + 'ver': '1.0', 'sas_algs': 'sas28x5' } + + self.encryptable_stanzas = ['message'] + + self.sas_algs = 'sas28x5' + self.cipher = AES + self.hash_alg = sha256 + self.compression = None + + for name in form.asDict(): + field = form.getField(name) + options = [x[1] for x in field.getOptions()] + values = field.getValues() + + if not field.getType() in ('list-single', 'list-multi'): + options = values + + if name in fixed: + if fixed[name] in options: + negotiated[name] = fixed[name] + else: + not_acceptable.append(name) + elif name == 'rekey_freq': + preferred = int(options[0]) + negotiated['rekey_freq'] = preferred + self.rekey_freq = preferred + elif name == 'logging': + my_prefs = self.logging_preference() + + if my_prefs[0] in options: # our first choice is offered, select it + pref = my_prefs[0] + negotiated['logging'] = pref + else: # see if other acceptable choices are offered + for pref in my_prefs: + if pref in options: + ask_user['logging'] = pref + break + + if not 'logging' in ask_user: + not_acceptable.append(name) + elif name == 'init_pubkey': + for x in ('key'): + if x in options: + negotiated['recv_pubkey'] = x + break + elif name == 'resp_pubkey': + for x in ('hash', 'key'): + if x in options: + negotiated['send_pubkey'] = x + break + elif name == 'sign_algs': + if (XmlDsig + 'rsa-sha256') in options: + negotiated['sign_algs'] = XmlDsig + 'rsa-sha256' + else: + # FIXME some things are handled elsewhere, some things are + # not-implemented + pass - return (negotiated, not_acceptable, ask_user) - - def respond_e2e_bob(self, form, negotiated, not_acceptable): - ''' 4.3 esession response (bob) ''' - response = xmpp.Message() - feature = response.NT.feature - feature.setNamespace(xmpp.NS_FEATURE) - - x = xmpp.DataForm(typ='submit') - - x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn')) - x.addChild(node=xmpp.DataField(name='accept', value='true')) - - for name in negotiated: - # some fields are internal and should not be sent - if not name in ('send_pubkey', 'recv_pubkey'): - x.addChild(node=xmpp.DataField(name=name, value=negotiated[name])) - - self.negotiated = negotiated - - # the offset of the group we chose (need it to match up with the dhhash) - group_order = 0 - self.modp = int(form.getField('modp').getOptions()[group_order][1]) - x.addChild(node=xmpp.DataField(name='modp', value=self.modp)) - - g = dh.generators[self.modp] - p = dh.primes[self.modp] - - self.n_o = base64.b64decode(form['my_nonce']) - - dhhashes = form.getField('dhhashes').getValues() - self.negotiated['He'] = base64.b64decode(dhhashes[group_order].encode( - 'utf8')) - - bytes = int(self.n / 8) - - self.n_s = crypto.generate_nonce() - - # n-bit random number - self.c_o = crypto.decode_mpi(crypto.random_bytes(bytes)) - self.c_s = self.c_o ^ (2 ** (self.n - 1)) - - self.y = crypto.srand(2 ** (2 * self.n - 1), p - 1) - self.d = crypto.powmod(g, self.y, p) - - to_add = {'my_nonce': self.n_s, - 'dhkeys': crypto.encode_mpi(self.d), - 'counter': crypto.encode_mpi(self.c_o), - 'nonce': self.n_o} - - for name in to_add: - b64ed = base64.b64encode(to_add[name]) - x.addChild(node=xmpp.DataField(name=name, value=b64ed)) - - self.form_o = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \ - in form.getChildren()) - self.form_s = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \ - in x.getChildren()) - - self.status = 'responded-e2e' - - feature.addChild(node=x) - - if not_acceptable: - response = xmpp.Error(response, xmpp.ERR_NOT_ACCEPTABLE) - - feature = xmpp.Node(xmpp.NS_FEATURE + ' feature') - - for f in not_acceptable: - n = xmpp.Node('field') - n['var'] = f - feature.addChild(node=n) - - response.T.error.addChild(node=feature) - - self.send(response) - - def verify_options_alice(self, form): - ''' 'Alice Accepts' ''' - negotiated = {} - ask_user = {} - not_acceptable = [] - - if not form['logging'] in self.logging_preference(): - not_acceptable.append(form['logging']) - elif form['logging'] != self.logging_preference()[0]: - ask_user['logging'] = form['logging'] - else: - negotiated['logging'] = self.logging_preference()[0] + return (negotiated, not_acceptable, ask_user) + + def respond_e2e_bob(self, form, negotiated, not_acceptable): + ''' 4.3 esession response (bob) ''' + response = xmpp.Message() + feature = response.NT.feature + feature.setNamespace(xmpp.NS_FEATURE) + + x = xmpp.DataForm(typ='submit') + + x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn')) + x.addChild(node=xmpp.DataField(name='accept', value='true')) + + for name in negotiated: + # some fields are internal and should not be sent + if not name in ('send_pubkey', 'recv_pubkey'): + x.addChild(node=xmpp.DataField(name=name, value=negotiated[name])) + + self.negotiated = negotiated + + # the offset of the group we chose (need it to match up with the dhhash) + group_order = 0 + self.modp = int(form.getField('modp').getOptions()[group_order][1]) + x.addChild(node=xmpp.DataField(name='modp', value=self.modp)) + + g = dh.generators[self.modp] + p = dh.primes[self.modp] + + self.n_o = base64.b64decode(form['my_nonce']) + + dhhashes = form.getField('dhhashes').getValues() + self.negotiated['He'] = base64.b64decode(dhhashes[group_order].encode( + 'utf8')) + + bytes = int(self.n / 8) + + self.n_s = crypto.generate_nonce() + + # n-bit random number + self.c_o = crypto.decode_mpi(crypto.random_bytes(bytes)) + self.c_s = self.c_o ^ (2 ** (self.n - 1)) + + self.y = crypto.srand(2 ** (2 * self.n - 1), p - 1) + self.d = crypto.powmod(g, self.y, p) + + to_add = {'my_nonce': self.n_s, + 'dhkeys': crypto.encode_mpi(self.d), + 'counter': crypto.encode_mpi(self.c_o), + 'nonce': self.n_o} + + for name in to_add: + b64ed = base64.b64encode(to_add[name]) + x.addChild(node=xmpp.DataField(name=name, value=b64ed)) + + self.form_o = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \ + in form.getChildren()) + self.form_s = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \ + in x.getChildren()) + + self.status = 'responded-e2e' + + feature.addChild(node=x) + + if not_acceptable: + response = xmpp.Error(response, xmpp.ERR_NOT_ACCEPTABLE) + + feature = xmpp.Node(xmpp.NS_FEATURE + ' feature') + + for f in not_acceptable: + n = xmpp.Node('field') + n['var'] = f + feature.addChild(node=n) + + response.T.error.addChild(node=feature) + + self.send(response) + + def verify_options_alice(self, form): + ''' 'Alice Accepts' ''' + negotiated = {} + ask_user = {} + not_acceptable = [] + + if not form['logging'] in self.logging_preference(): + not_acceptable.append(form['logging']) + elif form['logging'] != self.logging_preference()[0]: + ask_user['logging'] = form['logging'] + else: + negotiated['logging'] = self.logging_preference()[0] - for r,a in (('recv_pubkey', 'resp_pubkey'), ('send_pubkey', - 'init_pubkey')): - negotiated[r] = None + for r, a in (('recv_pubkey', 'resp_pubkey'), ('send_pubkey', + 'init_pubkey')): + negotiated[r] = None - if a in form.asDict() and form[a] in ('key', 'hash'): - negotiated[r] = form[a] + if a in form.asDict() and form[a] in ('key', 'hash'): + negotiated[r] = form[a] - if 'sign_algs' in form.asDict(): - if form['sign_algs'] in (XmlDsig + 'rsa-sha256', ): - negotiated['sign_algs'] = form['sign_algs'] - else: - not_acceptable.append(form['sign_algs']) + if 'sign_algs' in form.asDict(): + if form['sign_algs'] in (XmlDsig + 'rsa-sha256', ): + negotiated['sign_algs'] = form['sign_algs'] + else: + not_acceptable.append(form['sign_algs']) - return (negotiated, not_acceptable, ask_user) + return (negotiated, not_acceptable, ask_user) - def accept_e2e_alice(self, form, negotiated): - ''' 'Alice Accepts', continued ''' - self.encryptable_stanzas = ['message'] - self.sas_algs = 'sas28x5' - self.cipher = AES - self.hash_alg = sha256 - self.compression = None + def accept_e2e_alice(self, form, negotiated): + ''' 'Alice Accepts', continued ''' + self.encryptable_stanzas = ['message'] + self.sas_algs = 'sas28x5' + self.cipher = AES + self.hash_alg = sha256 + self.compression = None - self.negotiated = negotiated + self.negotiated = negotiated - accept = xmpp.Message() - feature = accept.NT.feature - feature.setNamespace(xmpp.NS_FEATURE) + accept = xmpp.Message() + feature = accept.NT.feature + feature.setNamespace(xmpp.NS_FEATURE) - result = xmpp.DataForm(typ='result') + result = xmpp.DataForm(typ='result') - self.c_s = crypto.decode_mpi(base64.b64decode(form['counter'])) - self.c_o = self.c_s ^ (2 ** (self.n - 1)) - self.n_o = base64.b64decode(form['my_nonce']) + self.c_s = crypto.decode_mpi(base64.b64decode(form['counter'])) + self.c_o = self.c_s ^ (2 ** (self.n - 1)) + self.n_o = base64.b64decode(form['my_nonce']) - mod_p = int(form['modp']) - p = dh.primes[mod_p] - x = self.xes[mod_p] - e = self.es[mod_p] + mod_p = int(form['modp']) + p = dh.primes[mod_p] + x = self.xes[mod_p] + e = self.es[mod_p] - self.d = crypto.decode_mpi(base64.b64decode(form['dhkeys'])) - self.k = self.get_shared_secret(self.d, x, p) + self.d = crypto.decode_mpi(base64.b64decode(form['dhkeys'])) + self.k = self.get_shared_secret(self.d, x, p) - result.addChild(node=xmpp.DataField(name='FORM_TYPE', - value='urn:xmpp:ssn')) - result.addChild(node=xmpp.DataField(name='accept', value='1')) - result.addChild(node=xmpp.DataField(name='nonce', - value=base64.b64encode(self.n_o))) + result.addChild(node=xmpp.DataField(name='FORM_TYPE', + value='urn:xmpp:ssn')) + result.addChild(node=xmpp.DataField(name='accept', value='1')) + result.addChild(node=xmpp.DataField(name='nonce', + value=base64.b64encode(self.n_o))) - self.kc_s, self.km_s, self.ks_s = self.generate_initiator_keys(self.k) + self.kc_s, self.km_s, self.ks_s = self.generate_initiator_keys(self.k) - if self.sigmai: - self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(self.k) - self.verify_identity(form, self.d, True, 'b') - else: - srses = secrets.secrets().retained_secrets(self.conn.name, - self.jid.getStripped()) - rshashes = [self.hmac(self.n_s, rs[0]) for rs in srses] + if self.sigmai: + self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(self.k) + self.verify_identity(form, self.d, True, 'b') + else: + srses = secrets.secrets().retained_secrets(self.conn.name, + self.jid.getStripped()) + rshashes = [self.hmac(self.n_s, rs[0]) for rs in srses] - if not rshashes: - # we've never spoken before, but we'll pretend we have - rshash_size = self.hash_alg().digest_size - rshashes.append(crypto.random_bytes(rshash_size)) + if not rshashes: + # we've never spoken before, but we'll pretend we have + rshash_size = self.hash_alg().digest_size + rshashes.append(crypto.random_bytes(rshash_size)) - rshashes = [base64.b64encode(rshash) for rshash in rshashes] - result.addChild(node=xmpp.DataField(name='rshashes', value=rshashes)) - result.addChild(node=xmpp.DataField(name='dhkeys', - value=base64.b64encode(crypto.encode_mpi(e)))) + rshashes = [base64.b64encode(rshash) for rshash in rshashes] + result.addChild(node=xmpp.DataField(name='rshashes', value=rshashes)) + result.addChild(node=xmpp.DataField(name='dhkeys', + value=base64.b64encode(crypto.encode_mpi(e)))) - self.form_o = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for \ - el in form.getChildren()) + self.form_o = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for \ + el in form.getChildren()) - # MUST securely destroy K unless it will be used later to generate the - # final shared secret + # MUST securely destroy K unless it will be used later to generate the + # final shared secret - for datafield in self.make_identity(result, e): - result.addChild(node=datafield) + for datafield in self.make_identity(result, e): + result.addChild(node=datafield) - feature.addChild(node=result) - self.send(accept) + feature.addChild(node=result) + self.send(accept) - if self.sigmai: - self.status = 'active' - self.enable_encryption = True - else: - self.status = 'identified-alice' + if self.sigmai: + self.status = 'active' + self.enable_encryption = True + else: + self.status = 'identified-alice' - def accept_e2e_bob(self, form): - ''' 4.5 esession accept (bob) ''' - response = xmpp.Message() + def accept_e2e_bob(self, form): + ''' 4.5 esession accept (bob) ''' + response = xmpp.Message() - init = response.NT.init - init.setNamespace(xmpp.NS_ESESSION_INIT) + init = response.NT.init + init.setNamespace(xmpp.NS_ESESSION_INIT) - x = xmpp.DataForm(typ='result') + x = xmpp.DataForm(typ='result') - for field in ('nonce', 'dhkeys', 'rshashes', 'identity', 'mac'): - # FIXME: will do nothing in real world... - assert field in form.asDict(), "alice's form didn't have a %s field" \ - % field + for field in ('nonce', 'dhkeys', 'rshashes', 'identity', 'mac'): + # FIXME: will do nothing in real world... + assert field in form.asDict(), "alice's form didn't have a %s field" \ + % field - # 4.5.1 generating provisory session keys - e = crypto.decode_mpi(base64.b64decode(form['dhkeys'])) - p = dh.primes[self.modp] + # 4.5.1 generating provisory session keys + e = crypto.decode_mpi(base64.b64decode(form['dhkeys'])) + p = dh.primes[self.modp] - if crypto.sha256(crypto.encode_mpi(e)) != self.negotiated['He']: - raise NegotiationError('SHA256(e) != He') + if crypto.sha256(crypto.encode_mpi(e)) != self.negotiated['He']: + raise NegotiationError('SHA256(e) != He') - k = self.get_shared_secret(e, self.y, p) - self.kc_o, self.km_o, self.ks_o = self.generate_initiator_keys(k) + k = self.get_shared_secret(e, self.y, p) + self.kc_o, self.km_o, self.ks_o = self.generate_initiator_keys(k) - # 4.5.2 verifying alice's identity - self.verify_identity(form, e, False, 'a') + # 4.5.2 verifying alice's identity + self.verify_identity(form, e, False, 'a') - # 4.5.4 generating bob's final session keys - srs = '' + # 4.5.4 generating bob's final session keys + srs = '' - srses = secrets.secrets().retained_secrets(self.conn.name, - self.jid.getStripped()) - rshashes = [base64.b64decode(rshash) for rshash in form.getField( - 'rshashes').getValues()] + srses = secrets.secrets().retained_secrets(self.conn.name, + self.jid.getStripped()) + rshashes = [base64.b64decode(rshash) for rshash in form.getField( + 'rshashes').getValues()] - for s in srses: - secret = s[0] - if self.hmac(self.n_o, secret) in rshashes: - srs = secret - break + for s in srses: + secret = s[0] + if self.hmac(self.n_o, secret) in rshashes: + srs = secret + break - # other shared secret - # (we're not using one) - oss = '' - - k = crypto.sha256(k + srs + oss) - - self.kc_s, self.km_s, self.ks_s = self.generate_responder_keys(k) - self.kc_o, self.km_o, self.ks_o = self.generate_initiator_keys(k) - - # 4.5.5 - if srs: - srshash = self.hmac(srs, 'Shared Retained Secret') - else: - srshash = crypto.random_bytes(32) + # other shared secret + # (we're not using one) + oss = '' + + k = crypto.sha256(k + srs + oss) + + self.kc_s, self.km_s, self.ks_s = self.generate_responder_keys(k) + self.kc_o, self.km_o, self.ks_o = self.generate_initiator_keys(k) + + # 4.5.5 + if srs: + srshash = self.hmac(srs, 'Shared Retained Secret') + else: + srshash = crypto.random_bytes(32) - x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn')) - x.addChild(node=xmpp.DataField(name='nonce', value=base64.b64encode( - self.n_o))) - x.addChild(node=xmpp.DataField(name='srshash', value=base64.b64encode( - srshash))) + x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn')) + x.addChild(node=xmpp.DataField(name='nonce', value=base64.b64encode( + self.n_o))) + x.addChild(node=xmpp.DataField(name='srshash', value=base64.b64encode( + srshash))) - for datafield in self.make_identity(x, self.d): - x.addChild(node=datafield) + for datafield in self.make_identity(x, self.d): + x.addChild(node=datafield) - init.addChild(node=x) + init.addChild(node=x) - self.send(response) + self.send(response) - self.do_retained_secret(k, srs) + self.do_retained_secret(k, srs) - if self.negotiated['logging'] == 'mustnot': - self.loggable = False + if self.negotiated['logging'] == 'mustnot': + self.loggable = False - self.status = 'active' - self.enable_encryption = True + self.status = 'active' + self.enable_encryption = True - if self.control: - self.control.print_esession_details() + if self.control: + self.control.print_esession_details() - def final_steps_alice(self, form): - srs = '' - srses = secrets.secrets().retained_secrets(self.conn.name, - self.jid.getStripped()) + def final_steps_alice(self, form): + srs = '' + srses = secrets.secrets().retained_secrets(self.conn.name, + self.jid.getStripped()) - try: - srshash = base64.b64decode(form['srshash']) - except IndexError: - return + try: + srshash = base64.b64decode(form['srshash']) + except IndexError: + return - for s in srses: - secret = s[0] - if self.hmac(secret, 'Shared Retained Secret') == srshash: - srs = secret - break + for s in srses: + secret = s[0] + if self.hmac(secret, 'Shared Retained Secret') == srshash: + srs = secret + break - oss = '' - k = crypto.sha256(self.k + srs + oss) - del self.k + oss = '' + k = crypto.sha256(self.k + srs + oss) + del self.k - self.do_retained_secret(k, srs) + self.do_retained_secret(k, srs) - # ks_s doesn't need to be calculated here - self.kc_s, self.km_s, self.ks_s = self.generate_initiator_keys(k) - self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(k) + # ks_s doesn't need to be calculated here + self.kc_s, self.km_s, self.ks_s = self.generate_initiator_keys(k) + self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(k) - # 4.6.2 Verifying Bob's Identity - self.verify_identity(form, self.d, False, 'b') - # Note: If Alice discovers an error then she SHOULD ignore any encrypted - # content she received in the stanza. + # 4.6.2 Verifying Bob's Identity + self.verify_identity(form, self.d, False, 'b') + # Note: If Alice discovers an error then she SHOULD ignore any encrypted + # content she received in the stanza. - if self.negotiated['logging'] == 'mustnot': - self.loggable = False + if self.negotiated['logging'] == 'mustnot': + self.loggable = False - self.status = 'active' - self.enable_encryption = True + self.status = 'active' + self.enable_encryption = True - if self.control: - self.control.print_esession_details() + if self.control: + self.control.print_esession_details() - def do_retained_secret(self, k, old_srs): - ''' - Calculate the new retained secret. determine if the user needs to check - the remote party's identity. Set up callbacks for when the identity has - been verified. - ''' - new_srs = self.hmac(k, 'New Retained Secret') - self.srs = new_srs + def do_retained_secret(self, k, old_srs): + ''' + Calculate the new retained secret. determine if the user needs to check + the remote party's identity. Set up callbacks for when the identity has + been verified. + ''' + new_srs = self.hmac(k, 'New Retained Secret') + self.srs = new_srs - account = self.conn.name - bjid = self.jid.getStripped() + account = self.conn.name + bjid = self.jid.getStripped() - self.verified_identity = False + self.verified_identity = False - if old_srs: - if secrets.secrets().srs_verified(account, bjid, old_srs): - # already had a stored secret verified by the user. - secrets.secrets().replace_srs(account, bjid, old_srs, new_srs, True) - # continue without warning. - self.verified_identity = True - else: - # had a secret, but it wasn't verified. - secrets.secrets().replace_srs(account, bjid, old_srs, new_srs, - False) - else: - # we don't even have an SRS - secrets.secrets().save_new_srs(account, bjid, new_srs, False) + if old_srs: + if secrets.secrets().srs_verified(account, bjid, old_srs): + # already had a stored secret verified by the user. + secrets.secrets().replace_srs(account, bjid, old_srs, new_srs, True) + # continue without warning. + self.verified_identity = True + else: + # had a secret, but it wasn't verified. + secrets.secrets().replace_srs(account, bjid, old_srs, new_srs, + False) + else: + # we don't even have an SRS + secrets.secrets().save_new_srs(account, bjid, new_srs, False) - def _verified_srs_cb(self): - secrets.secrets().replace_srs(self.conn.name, self.jid.getStripped(), - self.srs, self.srs, True) + def _verified_srs_cb(self): + secrets.secrets().replace_srs(self.conn.name, self.jid.getStripped(), + self.srs, self.srs, True) - def _unverified_srs_cb(self): - secrets.secrets().replace_srs(self.conn.name, self.jid.getStripped(), - self.srs, self.srs, False) + def _unverified_srs_cb(self): + secrets.secrets().replace_srs(self.conn.name, self.jid.getStripped(), + self.srs, self.srs, False) - def make_dhfield(self, modp_options, sigmai): - dhs = [] + def make_dhfield(self, modp_options, sigmai): + dhs = [] - for modp in modp_options: - p = dh.primes[modp] - g = dh.generators[modp] + for modp in modp_options: + p = dh.primes[modp] + g = dh.generators[modp] - x = crypto.srand(2 ** (2 * self.n - 1), p - 1) + x = crypto.srand(2 ** (2 * self.n - 1), p - 1) - # FIXME this may be a source of performance issues - e = crypto.powmod(g, x, p) + # FIXME this may be a source of performance issues + e = crypto.powmod(g, x, p) - self.xes[modp] = x - self.es[modp] = e - - if sigmai: - dhs.append(base64.b64encode(crypto.encode_mpi(e))) - name = 'dhkeys' - else: - He = crypto.sha256(crypto.encode_mpi(e)) - dhs.append(base64.b64encode(He)) - name = 'dhhashes' - - return xmpp.DataField(name=name, typ='hidden', value=dhs) - - def terminate_e2e(self): - self.terminate() - self.enable_encryption = False - - def acknowledge_termination(self): - StanzaSession.acknowledge_termination(self) - self.enable_encryption = False - - def fail_bad_negotiation(self, reason, fields=None): - ''' - Sends an error and cancels everything. - - If fields is None, the remote party has given us a bad cryptographic - value of some kind. Otherwise, list the fields we haven't implemented - ''' - err = xmpp.Error(xmpp.Message(), xmpp.ERR_FEATURE_NOT_IMPLEMENTED) - err.T.error.T.text.setData(reason) - - if fields: - feature = xmpp.Node(xmpp.NS_FEATURE + ' feature') - - for field in fields: - fn = xmpp.Node('field') - fn['var'] = field - feature.addChild(node=feature) - - err.addChild(node=feature) - - self.send(err) - - self.status = None - self.enable_encryption = False - - # this prevents the MAC check on decryption from succeeding, - # preventing falsified messages from going through. - self.km_o = '' - - def cancelled_negotiation(self): - StanzaSession.cancelled_negotiation(self) - self.enable_encryption = False - self.km_o = '' - -# vim: se ts=3: + self.xes[modp] = x + self.es[modp] = e + + if sigmai: + dhs.append(base64.b64encode(crypto.encode_mpi(e))) + name = 'dhkeys' + else: + He = crypto.sha256(crypto.encode_mpi(e)) + dhs.append(base64.b64encode(He)) + name = 'dhhashes' + + return xmpp.DataField(name=name, typ='hidden', value=dhs) + + def terminate_e2e(self): + self.terminate() + self.enable_encryption = False + + def acknowledge_termination(self): + StanzaSession.acknowledge_termination(self) + self.enable_encryption = False + + def fail_bad_negotiation(self, reason, fields=None): + ''' + Sends an error and cancels everything. + + If fields is None, the remote party has given us a bad cryptographic + value of some kind. Otherwise, list the fields we haven't implemented + ''' + err = xmpp.Error(xmpp.Message(), xmpp.ERR_FEATURE_NOT_IMPLEMENTED) + err.T.error.T.text.setData(reason) + + if fields: + feature = xmpp.Node(xmpp.NS_FEATURE + ' feature') + + for field in fields: + fn = xmpp.Node('field') + fn['var'] = field + feature.addChild(node=feature) + + err.addChild(node=feature) + + self.send(err) + + self.status = None + self.enable_encryption = False + + # this prevents the MAC check on decryption from succeeding, + # preventing falsified messages from going through. + self.km_o = '' + + def cancelled_negotiation(self): + StanzaSession.cancelled_negotiation(self) + self.enable_encryption = False + self.km_o = '' diff --git a/src/common/xmpp/__init__.py b/src/common/xmpp/__init__.py index 8f0fb3d48..4ebc4cfa3 100644 --- a/src/common/xmpp/__init__.py +++ b/src/common/xmpp/__init__.py @@ -15,5 +15,3 @@ import simplexml, protocol, auth_nb, transports_nb, roster_nb import dispatcher_nb, features_nb, idlequeue, bosh, tls_nb, proxy_connectors from client_nb import NonBlockingClient from plugin import PlugIn - -# vim: se ts=3: diff --git a/src/common/xmpp/auth_nb.py b/src/common/xmpp/auth_nb.py index e5e51f6b8..5daca772e 100644 --- a/src/common/xmpp/auth_nb.py +++ b/src/common/xmpp/auth_nb.py @@ -36,10 +36,10 @@ def H(some): return hashlib.md5(some).digest() def C(some): return ':'.join(some) try: - import kerberos - have_kerberos = True + import kerberos + have_kerberos = True except ImportError: - have_kerberos = False + have_kerberos = False GSS_STATE_STEP = 0 GSS_STATE_WRAP = 1 @@ -49,486 +49,484 @@ SASL_UNSUPPORTED = 'not-supported' SASL_IN_PROCESS = 'in-process' def challenge_splitter(data): - ''' Helper function that creates a dict from challenge string. + ''' Helper function that creates a dict from challenge string. - Sample challenge string: - username="example.org",realm="somerealm",\ - nonce="OA6MG9tEQGm2hh",cnonce="OA6MHXh6VqTrRk",\ - nc=00000001,qop="auth,auth-int,auth-conf",charset=utf-8 + Sample challenge string: + username="example.org",realm="somerealm",\ + nonce="OA6MG9tEQGm2hh",cnonce="OA6MHXh6VqTrRk",\ + nc=00000001,qop="auth,auth-int,auth-conf",charset=utf-8 - Expected result for challan: - dict['qop'] = ('auth','auth-int','auth-conf') - dict['realm'] = 'somerealm' - ''' - X_KEYWORD, X_VALUE, X_END = 0, 1, 2 - quotes_open = False - keyword, value = '', '' - dict_ = {} - arr = None + Expected result for challan: + dict['qop'] = ('auth','auth-int','auth-conf') + dict['realm'] = 'somerealm' + ''' + X_KEYWORD, X_VALUE, X_END = 0, 1, 2 + quotes_open = False + keyword, value = '', '' + dict_ = {} + arr = None - expecting = X_KEYWORD - for iter_ in range(len(data) + 1): - end = False - if iter_ == len(data): - expecting = X_END - end = True - else: - char = data[iter_] - if expecting == X_KEYWORD: - if char == '=': - expecting = X_VALUE - elif char in (',', ' ', '\t'): - pass - else: - keyword = '%s%c' % (keyword, char) - elif expecting == X_VALUE: - if char == '"': - if quotes_open: - end = True - else: - quotes_open = True - elif char in (',', ' ', '\t'): - if quotes_open: - if not arr: - arr = [value] - else: - arr.append(value) - value = "" - else: - end = True - else: - value = '%s%c' % (value, char) - if end: - if arr: - arr.append(value) - dict_[keyword] = arr - arr = None - else: - dict_[keyword] = value - value, keyword = '', '' - expecting = X_KEYWORD - quotes_open = False - return dict_ + expecting = X_KEYWORD + for iter_ in range(len(data) + 1): + end = False + if iter_ == len(data): + expecting = X_END + end = True + else: + char = data[iter_] + if expecting == X_KEYWORD: + if char == '=': + expecting = X_VALUE + elif char in (',', ' ', '\t'): + pass + else: + keyword = '%s%c' % (keyword, char) + elif expecting == X_VALUE: + if char == '"': + if quotes_open: + end = True + else: + quotes_open = True + elif char in (',', ' ', '\t'): + if quotes_open: + if not arr: + arr = [value] + else: + arr.append(value) + value = "" + else: + end = True + else: + value = '%s%c' % (value, char) + if end: + if arr: + arr.append(value) + dict_[keyword] = arr + arr = None + else: + dict_[keyword] = value + value, keyword = '', '' + expecting = X_KEYWORD + quotes_open = False + return dict_ class SASL(PlugIn): - ''' - Implements SASL authentication. Can be plugged into NonBlockingClient - to start authentication. - ''' - def __init__(self, username, password, on_sasl): - ''' - :param user: XMPP username - :param password: XMPP password - :param on_sasl: Callback, will be called after each SASL auth-step. - ''' - PlugIn.__init__(self) - self.username = username - self.password = password - self.on_sasl = on_sasl - self.realm = None + ''' + Implements SASL authentication. Can be plugged into NonBlockingClient + to start authentication. + ''' + def __init__(self, username, password, on_sasl): + ''' + :param user: XMPP username + :param password: XMPP password + :param on_sasl: Callback, will be called after each SASL auth-step. + ''' + PlugIn.__init__(self) + self.username = username + self.password = password + self.on_sasl = on_sasl + self.realm = None - def plugin(self, owner): - if 'version' not in self._owner.Dispatcher.Stream._document_attrs: - self.startsasl = SASL_UNSUPPORTED - elif self._owner.Dispatcher.Stream.features: - try: - self.FeaturesHandler(self._owner.Dispatcher, - self._owner.Dispatcher.Stream.features) - except NodeProcessed: - pass - else: - self.startsasl = None + def plugin(self, owner): + if 'version' not in self._owner.Dispatcher.Stream._document_attrs: + self.startsasl = SASL_UNSUPPORTED + elif self._owner.Dispatcher.Stream.features: + try: + self.FeaturesHandler(self._owner.Dispatcher, + self._owner.Dispatcher.Stream.features) + except NodeProcessed: + pass + else: + self.startsasl = None - def plugout(self): - ''' Remove SASL handlers from owner's dispatcher. Used internally. ''' - if 'features' in self._owner.__dict__: - self._owner.UnregisterHandler('features', self.FeaturesHandler, - xmlns=NS_STREAMS) - if 'challenge' in self._owner.__dict__: - self._owner.UnregisterHandler('challenge', self.SASLHandler, - xmlns=NS_SASL) - if 'failure' in self._owner.__dict__: - self._owner.UnregisterHandler('failure', self.SASLHandler, - xmlns=NS_SASL) - if 'success' in self._owner.__dict__: - self._owner.UnregisterHandler('success', self.SASLHandler, - xmlns=NS_SASL) + def plugout(self): + ''' Remove SASL handlers from owner's dispatcher. Used internally. ''' + if 'features' in self._owner.__dict__: + self._owner.UnregisterHandler('features', self.FeaturesHandler, + xmlns=NS_STREAMS) + if 'challenge' in self._owner.__dict__: + self._owner.UnregisterHandler('challenge', self.SASLHandler, + xmlns=NS_SASL) + if 'failure' in self._owner.__dict__: + self._owner.UnregisterHandler('failure', self.SASLHandler, + xmlns=NS_SASL) + if 'success' in self._owner.__dict__: + self._owner.UnregisterHandler('success', self.SASLHandler, + xmlns=NS_SASL) - def auth(self): - ''' - Start authentication. Result can be obtained via "SASL.startsasl" - attribute and will be either SASL_SUCCESS or SASL_FAILURE. + def auth(self): + ''' + Start authentication. Result can be obtained via "SASL.startsasl" + attribute and will be either SASL_SUCCESS or SASL_FAILURE. - Note that successfull auth will take at least two Dispatcher.Process() - calls. - ''' - if self.startsasl: - pass - elif self._owner.Dispatcher.Stream.features: - try: - self.FeaturesHandler(self._owner.Dispatcher, - self._owner.Dispatcher.Stream.features) - except NodeProcessed: - pass - else: - self._owner.RegisterHandler('features', - self.FeaturesHandler, xmlns=NS_STREAMS) + Note that successfull auth will take at least two Dispatcher.Process() + calls. + ''' + if self.startsasl: + pass + elif self._owner.Dispatcher.Stream.features: + try: + self.FeaturesHandler(self._owner.Dispatcher, + self._owner.Dispatcher.Stream.features) + except NodeProcessed: + pass + else: + self._owner.RegisterHandler('features', + self.FeaturesHandler, xmlns=NS_STREAMS) - def FeaturesHandler(self, conn, feats): - ''' Used to determine if server supports SASL auth. Used internally. ''' - if not feats.getTag('mechanisms', namespace=NS_SASL): - self.startsasl='not-supported' - log.error('SASL not supported by server') - return - self.mecs = [] - for mec in feats.getTag('mechanisms', namespace=NS_SASL).getTags( - 'mechanism'): - self.mecs.append(mec.getData()) + def FeaturesHandler(self, conn, feats): + ''' Used to determine if server supports SASL auth. Used internally. ''' + if not feats.getTag('mechanisms', namespace=NS_SASL): + self.startsasl='not-supported' + log.error('SASL not supported by server') + return + self.mecs = [] + for mec in feats.getTag('mechanisms', namespace=NS_SASL).getTags( + 'mechanism'): + self.mecs.append(mec.getData()) - self._owner.RegisterHandler('challenge', self.SASLHandler, xmlns=NS_SASL) - self._owner.RegisterHandler('failure', self.SASLHandler, xmlns=NS_SASL) - self._owner.RegisterHandler('success', self.SASLHandler, xmlns=NS_SASL) - self.MechanismHandler() + self._owner.RegisterHandler('challenge', self.SASLHandler, xmlns=NS_SASL) + self._owner.RegisterHandler('failure', self.SASLHandler, xmlns=NS_SASL) + self._owner.RegisterHandler('success', self.SASLHandler, xmlns=NS_SASL) + self.MechanismHandler() - def MechanismHandler(self): - if 'ANONYMOUS' in self.mecs and self.username is None: - self.mecs.remove('ANONYMOUS') - node = Node('auth',attrs={'xmlns': NS_SASL, 'mechanism': 'ANONYMOUS'}) - self.mechanism = 'ANONYMOUS' - self.startsasl = SASL_IN_PROCESS - self._owner.send(str(node)) - raise NodeProcessed - if 'GSSAPI' in self.mecs and have_kerberos: - self.mecs.remove('GSSAPI') - try: - self.gss_vc = kerberos.authGSSClientInit('xmpp@' + \ - self._owner.xmpp_hostname)[1] - kerberos.authGSSClientStep(self.gss_vc, '') - response = kerberos.authGSSClientResponse(self.gss_vc) - node=Node('auth',attrs={'xmlns': NS_SASL, 'mechanism': 'GSSAPI'}, - payload=(response or '')) - self.mechanism = 'GSSAPI' - self.gss_step = GSS_STATE_STEP - self.startsasl = SASL_IN_PROCESS - self._owner.send(str(node)) - raise NodeProcessed - except kerberos.GSSError, e: - log.info('GSSAPI authentication failed: %s' % str(e)) - if 'DIGEST-MD5' in self.mecs: - self.mecs.remove('DIGEST-MD5') - node = Node('auth',attrs={'xmlns': NS_SASL, 'mechanism': 'DIGEST-MD5'}) - self.mechanism = 'DIGEST-MD5' - self.startsasl = SASL_IN_PROCESS - self._owner.send(str(node)) - raise NodeProcessed - if 'PLAIN' in self.mecs: - self.mecs.remove('PLAIN') - self.mechanism = 'PLAIN' - self._owner._caller.get_password(self.set_password) - self.startsasl = SASL_IN_PROCESS - raise NodeProcessed - self.startsasl = SASL_FAILURE - log.error('I can only use DIGEST-MD5, GSSAPI and PLAIN mecanisms.') - if self.on_sasl: - self.on_sasl() - return + def MechanismHandler(self): + if 'ANONYMOUS' in self.mecs and self.username is None: + self.mecs.remove('ANONYMOUS') + node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'ANONYMOUS'}) + self.mechanism = 'ANONYMOUS' + self.startsasl = SASL_IN_PROCESS + self._owner.send(str(node)) + raise NodeProcessed + if 'GSSAPI' in self.mecs and have_kerberos: + self.mecs.remove('GSSAPI') + try: + self.gss_vc = kerberos.authGSSClientInit('xmpp@' + \ + self._owner.xmpp_hostname)[1] + kerberos.authGSSClientStep(self.gss_vc, '') + response = kerberos.authGSSClientResponse(self.gss_vc) + node=Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'GSSAPI'}, + payload=(response or '')) + self.mechanism = 'GSSAPI' + self.gss_step = GSS_STATE_STEP + self.startsasl = SASL_IN_PROCESS + self._owner.send(str(node)) + raise NodeProcessed + except kerberos.GSSError, e: + log.info('GSSAPI authentication failed: %s' % str(e)) + if 'DIGEST-MD5' in self.mecs: + self.mecs.remove('DIGEST-MD5') + node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'DIGEST-MD5'}) + self.mechanism = 'DIGEST-MD5' + self.startsasl = SASL_IN_PROCESS + self._owner.send(str(node)) + raise NodeProcessed + if 'PLAIN' in self.mecs: + self.mecs.remove('PLAIN') + self.mechanism = 'PLAIN' + self._owner._caller.get_password(self.set_password) + self.startsasl = SASL_IN_PROCESS + raise NodeProcessed + self.startsasl = SASL_FAILURE + log.error('I can only use DIGEST-MD5, GSSAPI and PLAIN mecanisms.') + if self.on_sasl: + self.on_sasl() + return - def SASLHandler(self, conn, challenge): - ''' Perform next SASL auth step. Used internally. ''' - if challenge.getNamespace() != NS_SASL: - return - ### Handle Auth result - if challenge.getName() == 'failure': - self.startsasl = SASL_FAILURE - try: - reason = challenge.getChildren()[0] - except Exception: - reason = challenge - log.error('Failed SASL authentification: %s' % reason) - if len(self.mecs) > 0: - # There are other mechanisms to test - self.MechanismHandler() - raise NodeProcessed - if self.on_sasl: - self.on_sasl() - raise NodeProcessed - elif challenge.getName() == 'success': - self.startsasl = SASL_SUCCESS - log.info('Successfully authenticated with remote server.') - handlers = self._owner.Dispatcher.dumpHandlers() + def SASLHandler(self, conn, challenge): + ''' Perform next SASL auth step. Used internally. ''' + if challenge.getNamespace() != NS_SASL: + return + ### Handle Auth result + if challenge.getName() == 'failure': + self.startsasl = SASL_FAILURE + try: + reason = challenge.getChildren()[0] + except Exception: + reason = challenge + log.error('Failed SASL authentification: %s' % reason) + if len(self.mecs) > 0: + # There are other mechanisms to test + self.MechanismHandler() + raise NodeProcessed + if self.on_sasl: + self.on_sasl() + raise NodeProcessed + elif challenge.getName() == 'success': + self.startsasl = SASL_SUCCESS + log.info('Successfully authenticated with remote server.') + handlers = self._owner.Dispatcher.dumpHandlers() - # Bosh specific dispatcher replugging - # save old features. They will be used in case we won't get response on - # stream restart after SASL auth (happens with XMPP over BOSH with - # Openfire) - old_features = self._owner.Dispatcher.Stream.features - self._owner.Dispatcher.PlugOut() - dispatcher_nb.Dispatcher.get_instance().PlugIn(self._owner, - after_SASL=True, old_features=old_features) - self._owner.Dispatcher.restoreHandlers(handlers) - self._owner.User = self.username + # Bosh specific dispatcher replugging + # save old features. They will be used in case we won't get response on + # stream restart after SASL auth (happens with XMPP over BOSH with + # Openfire) + old_features = self._owner.Dispatcher.Stream.features + self._owner.Dispatcher.PlugOut() + dispatcher_nb.Dispatcher.get_instance().PlugIn(self._owner, + after_SASL=True, old_features=old_features) + self._owner.Dispatcher.restoreHandlers(handlers) + self._owner.User = self.username - if self.on_sasl: - self.on_sasl() - raise NodeProcessed + if self.on_sasl: + self.on_sasl() + raise NodeProcessed - ### Perform auth step - incoming_data = challenge.getData() - data=base64.decodestring(incoming_data) - log.info('Got challenge:' + data) + ### Perform auth step + incoming_data = challenge.getData() + data=base64.decodestring(incoming_data) + log.info('Got challenge:' + data) - if self.mechanism == 'GSSAPI': - if self.gss_step == GSS_STATE_STEP: - rc = kerberos.authGSSClientStep(self.gss_vc, incoming_data) - if rc != kerberos.AUTH_GSS_CONTINUE: - self.gss_step = GSS_STATE_WRAP - elif self.gss_step == GSS_STATE_WRAP: - rc = kerberos.authGSSClientUnwrap(self.gss_vc, incoming_data) - response = kerberos.authGSSClientResponse(self.gss_vc) - rc = kerberos.authGSSClientWrap(self.gss_vc, response, - kerberos.authGSSClientUserName(self.gss_vc)) - response = kerberos.authGSSClientResponse(self.gss_vc) - if not response: - response = '' - self._owner.send(Node('response', attrs={'xmlns':NS_SASL}, - payload=response).__str__()) - raise NodeProcessed + if self.mechanism == 'GSSAPI': + if self.gss_step == GSS_STATE_STEP: + rc = kerberos.authGSSClientStep(self.gss_vc, incoming_data) + if rc != kerberos.AUTH_GSS_CONTINUE: + self.gss_step = GSS_STATE_WRAP + elif self.gss_step == GSS_STATE_WRAP: + rc = kerberos.authGSSClientUnwrap(self.gss_vc, incoming_data) + response = kerberos.authGSSClientResponse(self.gss_vc) + rc = kerberos.authGSSClientWrap(self.gss_vc, response, + kerberos.authGSSClientUserName(self.gss_vc)) + response = kerberos.authGSSClientResponse(self.gss_vc) + if not response: + response = '' + self._owner.send(Node('response', attrs={'xmlns':NS_SASL}, + payload=response).__str__()) + raise NodeProcessed - # magic foo... - chal = challenge_splitter(data) - if not self.realm and 'realm' in chal: - self.realm = chal['realm'] - if 'qop' in chal and ((isinstance(chal['qop'], str) and \ - chal['qop'] =='auth') or (isinstance(chal['qop'], list) and 'auth' in \ - chal['qop'])): - self.resp = {} - self.resp['username'] = self.username - if self.realm: - self.resp['realm'] = self.realm - else: - self.resp['realm'] = self._owner.Server - self.resp['nonce'] = chal['nonce'] - self.resp['cnonce'] = ''.join("%x" % randint(0, 2**28) for randint in - itertools.repeat(random.randint, 7)) - self.resp['nc'] = ('00000001') - self.resp['qop'] = 'auth' - self.resp['digest-uri'] = 'xmpp/' + self._owner.Server - self.resp['charset'] = 'utf-8' - # Password is now required - self._owner._caller.get_password(self.set_password) - elif 'rspauth' in chal: - self._owner.send(str(Node('response', attrs={'xmlns':NS_SASL}))) - else: - self.startsasl = SASL_FAILURE - log.error('Failed SASL authentification: unknown challenge') - if self.on_sasl: - self.on_sasl() - raise NodeProcessed + # magic foo... + chal = challenge_splitter(data) + if not self.realm and 'realm' in chal: + self.realm = chal['realm'] + if 'qop' in chal and ((isinstance(chal['qop'], str) and \ + chal['qop'] =='auth') or (isinstance(chal['qop'], list) and 'auth' in \ + chal['qop'])): + self.resp = {} + self.resp['username'] = self.username + if self.realm: + self.resp['realm'] = self.realm + else: + self.resp['realm'] = self._owner.Server + self.resp['nonce'] = chal['nonce'] + self.resp['cnonce'] = ''.join("%x" % randint(0, 2**28) for randint in + itertools.repeat(random.randint, 7)) + self.resp['nc'] = ('00000001') + self.resp['qop'] = 'auth' + self.resp['digest-uri'] = 'xmpp/' + self._owner.Server + self.resp['charset'] = 'utf-8' + # Password is now required + self._owner._caller.get_password(self.set_password) + elif 'rspauth' in chal: + self._owner.send(str(Node('response', attrs={'xmlns':NS_SASL}))) + else: + self.startsasl = SASL_FAILURE + log.error('Failed SASL authentification: unknown challenge') + if self.on_sasl: + self.on_sasl() + raise NodeProcessed - def set_password(self, password): - if password is None: - self.password = '' - else: - self.password = password - if self.mechanism == 'DIGEST-MD5': - A1 = C([H(C([self.resp['username'], self.resp['realm'], - self.password])), self.resp['nonce'], self.resp['cnonce']]) - A2 = C(['AUTHENTICATE', self.resp['digest-uri']]) - response= HH(C([HH(A1), self.resp['nonce'], self.resp['nc'], - self.resp['cnonce'], self.resp['qop'], HH(A2)])) - self.resp['response'] = response - sasl_data = u'' - for key in ('charset', 'username', 'realm', 'nonce', 'nc', 'cnonce', - 'digest-uri', 'response', 'qop'): - if key in ('nc','qop','response','charset'): - sasl_data += u"%s=%s," % (key, self.resp[key]) - else: - sasl_data += u'%s="%s",' % (key, self.resp[key]) - sasl_data = sasl_data[:-1].encode('utf-8').encode('base64').replace( - '\r', '').replace('\n', '') - node = Node('response', attrs={'xmlns':NS_SASL}, payload=[sasl_data]) - elif self.mechanism == 'PLAIN': - sasl_data = u'%s\x00%s\x00%s' % (self.username + '@' + \ - self._owner.Server, self.username, self.password) - sasl_data = sasl_data.encode('utf-8').encode('base64').replace( - '\n', '') - node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'PLAIN'}, - payload=[sasl_data]) - self._owner.send(str(node)) + def set_password(self, password): + if password is None: + self.password = '' + else: + self.password = password + if self.mechanism == 'DIGEST-MD5': + A1 = C([H(C([self.resp['username'], self.resp['realm'], + self.password])), self.resp['nonce'], self.resp['cnonce']]) + A2 = C(['AUTHENTICATE', self.resp['digest-uri']]) + response= HH(C([HH(A1), self.resp['nonce'], self.resp['nc'], + self.resp['cnonce'], self.resp['qop'], HH(A2)])) + self.resp['response'] = response + sasl_data = u'' + for key in ('charset', 'username', 'realm', 'nonce', 'nc', 'cnonce', + 'digest-uri', 'response', 'qop'): + if key in ('nc', 'qop', 'response', 'charset'): + sasl_data += u"%s=%s," % (key, self.resp[key]) + else: + sasl_data += u'%s="%s",' % (key, self.resp[key]) + sasl_data = sasl_data[:-1].encode('utf-8').encode('base64').replace( + '\r', '').replace('\n', '') + node = Node('response', attrs={'xmlns':NS_SASL}, payload=[sasl_data]) + elif self.mechanism == 'PLAIN': + sasl_data = u'%s\x00%s\x00%s' % (self.username + '@' + \ + self._owner.Server, self.username, self.password) + sasl_data = sasl_data.encode('utf-8').encode('base64').replace( + '\n', '') + node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'PLAIN'}, + payload=[sasl_data]) + self._owner.send(str(node)) class NonBlockingNonSASL(PlugIn): - ''' - Implements old Non-SASL (JEP-0078) authentication used in jabberd1.4 and - transport authentication. - ''' - def __init__(self, user, password, resource, on_auth): - ''' Caches username, password and resource for auth. ''' - PlugIn.__init__(self) - self.user = user - if password is None: - self.password = '' - else: - self.password = password - self.resource = resource - self.on_auth = on_auth + ''' + Implements old Non-SASL (JEP-0078) authentication used in jabberd1.4 and + transport authentication. + ''' + def __init__(self, user, password, resource, on_auth): + ''' Caches username, password and resource for auth. ''' + PlugIn.__init__(self) + self.user = user + if password is None: + self.password = '' + else: + self.password = password + self.resource = resource + self.on_auth = on_auth - def plugin(self, owner): - ''' - Determine the best auth method (digest/0k/plain) and use it for auth. - Returns used method name on success. Used internally. - ''' - log.info('Querying server about possible auth methods') - self.owner = owner + def plugin(self, owner): + ''' + Determine the best auth method (digest/0k/plain) and use it for auth. + Returns used method name on success. Used internally. + ''' + log.info('Querying server about possible auth methods') + self.owner = owner - owner.Dispatcher.SendAndWaitForResponse( - Iq('get', NS_AUTH, payload=[Node('username', payload=[self.user])]), - func=self._on_username) + owner.Dispatcher.SendAndWaitForResponse( + Iq('get', NS_AUTH, payload=[Node('username', payload=[self.user])]), + func=self._on_username) - def _on_username(self, resp): - if not isResultNode(resp): - log.error('No result node arrived! Aborting...') - return self.on_auth(None) + def _on_username(self, resp): + if not isResultNode(resp): + log.error('No result node arrived! Aborting...') + return self.on_auth(None) - iq=Iq(typ='set',node=resp) - query = iq.getTag('query') - query.setTagData('username',self.user) - query.setTagData('resource',self.resource) + iq=Iq(typ='set', node=resp) + query = iq.getTag('query') + query.setTagData('username', self.user) + query.setTagData('resource', self.resource) - if query.getTag('digest'): - log.info("Performing digest authentication") - query.setTagData('digest', - hashlib.sha1(self.owner.Dispatcher.Stream._document_attrs['id'] - + self.password).hexdigest()) - if query.getTag('password'): - query.delChild('password') - self._method = 'digest' - elif query.getTag('token'): - token = query.getTagData('token') - seq = query.getTagData('sequence') - log.info("Performing zero-k authentication") + if query.getTag('digest'): + log.info("Performing digest authentication") + query.setTagData('digest', + hashlib.sha1(self.owner.Dispatcher.Stream._document_attrs['id'] + + self.password).hexdigest()) + if query.getTag('password'): + query.delChild('password') + self._method = 'digest' + elif query.getTag('token'): + token = query.getTagData('token') + seq = query.getTagData('sequence') + log.info("Performing zero-k authentication") - def hasher(s): - return hashlib.sha1(s).hexdigest() + def hasher(s): + return hashlib.sha1(s).hexdigest() - def hash_n_times(s, count): - return count and hasher(hash_n_times(s, count-1)) or s + def hash_n_times(s, count): + return count and hasher(hash_n_times(s, count-1)) or s - hash_ = hash_n_times(hasher(hasher(self.password) + token), int(seq)) - query.setTagData('hash', hash_) - self._method='0k' - else: - log.warn("Sequre methods unsupported, performing plain text \ - authentication") - query.setTagData('password', self.password) - self._method = 'plain' - resp = self.owner.Dispatcher.SendAndWaitForResponse(iq,func=self._on_auth) + hash_ = hash_n_times(hasher(hasher(self.password) + token), int(seq)) + query.setTagData('hash', hash_) + self._method='0k' + else: + log.warn("Sequre methods unsupported, performing plain text \ + authentication") + query.setTagData('password', self.password) + self._method = 'plain' + resp = self.owner.Dispatcher.SendAndWaitForResponse(iq, func=self._on_auth) - def _on_auth(self, resp): - if isResultNode(resp): - log.info('Sucessfully authenticated with remote host.') - self.owner.User = self.user - self.owner.Resource = self.resource - self.owner._registered_name = self.owner.User+'@'+self.owner.Server+\ - '/'+self.owner.Resource - return self.on_auth(self._method) - log.error('Authentication failed!') - return self.on_auth(None) + def _on_auth(self, resp): + if isResultNode(resp): + log.info('Sucessfully authenticated with remote host.') + self.owner.User = self.user + self.owner.Resource = self.resource + self.owner._registered_name = self.owner.User+'@'+self.owner.Server+\ + '/'+self.owner.Resource + return self.on_auth(self._method) + log.error('Authentication failed!') + return self.on_auth(None) class NonBlockingBind(PlugIn): - ''' - Bind some JID to the current connection to allow router know of our - location. Must be plugged after successful SASL auth. - ''' - def __init__(self): - PlugIn.__init__(self) - self.bound = None + ''' + Bind some JID to the current connection to allow router know of our + location. Must be plugged after successful SASL auth. + ''' + def __init__(self): + PlugIn.__init__(self) + self.bound = None - def plugin(self, owner): - ''' Start resource binding, if allowed at this time. Used internally. ''' - if self._owner.Dispatcher.Stream.features: - try: - self.FeaturesHandler(self._owner.Dispatcher, - self._owner.Dispatcher.Stream.features) - except NodeProcessed: - pass - else: - self._owner.RegisterHandler('features', self.FeaturesHandler, - xmlns=NS_STREAMS) + def plugin(self, owner): + ''' Start resource binding, if allowed at this time. Used internally. ''' + if self._owner.Dispatcher.Stream.features: + try: + self.FeaturesHandler(self._owner.Dispatcher, + self._owner.Dispatcher.Stream.features) + except NodeProcessed: + pass + else: + self._owner.RegisterHandler('features', self.FeaturesHandler, + xmlns=NS_STREAMS) - def FeaturesHandler(self, conn, feats): - ''' - Determine if server supports resource binding and set some internal - attributes accordingly. - ''' - if not feats.getTag('bind', namespace=NS_BIND): - log.error('Server does not requested binding.') - # we try to bind resource anyway - #self.bound='failure' - self.bound = [] - return - if feats.getTag('session', namespace=NS_SESSION): - self.session = 1 - else: - self.session = -1 - self.bound = [] + def FeaturesHandler(self, conn, feats): + ''' + Determine if server supports resource binding and set some internal + attributes accordingly. + ''' + if not feats.getTag('bind', namespace=NS_BIND): + log.error('Server does not requested binding.') + # we try to bind resource anyway + #self.bound='failure' + self.bound = [] + return + if feats.getTag('session', namespace=NS_SESSION): + self.session = 1 + else: + self.session = -1 + self.bound = [] - def plugout(self): - ''' Remove Bind handler from owner's dispatcher. Used internally. ''' - self._owner.UnregisterHandler('features', self.FeaturesHandler, - xmlns=NS_STREAMS) + def plugout(self): + ''' Remove Bind handler from owner's dispatcher. Used internally. ''' + self._owner.UnregisterHandler('features', self.FeaturesHandler, + xmlns=NS_STREAMS) - def NonBlockingBind(self, resource=None, on_bound=None): - ''' Perform binding. - Use provided resource name or random (if not provided). - ''' - self.on_bound = on_bound - self._resource = resource - if self._resource: - self._resource = [Node('resource', payload=[self._resource])] - else: - self._resource = [] + def NonBlockingBind(self, resource=None, on_bound=None): + ''' Perform binding. + Use provided resource name or random (if not provided). + ''' + self.on_bound = on_bound + self._resource = resource + if self._resource: + self._resource = [Node('resource', payload=[self._resource])] + else: + self._resource = [] - self._owner.onreceive(None) - self._owner.Dispatcher.SendAndWaitForResponse( - Protocol('iq',typ='set', payload=[Node('bind', attrs={'xmlns':NS_BIND}, - payload=self._resource)]), func=self._on_bound) + self._owner.onreceive(None) + self._owner.Dispatcher.SendAndWaitForResponse( + Protocol('iq', typ='set', payload=[Node('bind', attrs={'xmlns':NS_BIND}, + payload=self._resource)]), func=self._on_bound) - def _on_bound(self, resp): - if isResultNode(resp): - if resp.getTag('bind') and resp.getTag('bind').getTagData('jid'): - self.bound.append(resp.getTag('bind').getTagData('jid')) - log.info('Successfully bound %s.' % self.bound[-1]) - jid = JID(resp.getTag('bind').getTagData('jid')) - self._owner.User = jid.getNode() - self._owner.Resource = jid.getResource() - if hasattr(self, 'session') and self.session == -1: - # Server don't want us to initialize a session - log.info('No session required.') - self.on_bound('ok') - else: - self._owner.SendAndWaitForResponse(Protocol('iq', typ='set', - payload=[Node('session', attrs={'xmlns':NS_SESSION})]), - func=self._on_session) - return - if resp: - log.error('Binding failed: %s.' % resp.getTag('error')) - self.on_bound(None) - else: - log.error('Binding failed: timeout expired.') - self.on_bound(None) + def _on_bound(self, resp): + if isResultNode(resp): + if resp.getTag('bind') and resp.getTag('bind').getTagData('jid'): + self.bound.append(resp.getTag('bind').getTagData('jid')) + log.info('Successfully bound %s.' % self.bound[-1]) + jid = JID(resp.getTag('bind').getTagData('jid')) + self._owner.User = jid.getNode() + self._owner.Resource = jid.getResource() + if hasattr(self, 'session') and self.session == -1: + # Server don't want us to initialize a session + log.info('No session required.') + self.on_bound('ok') + else: + self._owner.SendAndWaitForResponse(Protocol('iq', typ='set', + payload=[Node('session', attrs={'xmlns':NS_SESSION})]), + func=self._on_session) + return + if resp: + log.error('Binding failed: %s.' % resp.getTag('error')) + self.on_bound(None) + else: + log.error('Binding failed: timeout expired.') + self.on_bound(None) - def _on_session(self, resp): - self._owner.onreceive(None) - if isResultNode(resp): - log.info('Successfully opened session.') - self.session = 1 - self.on_bound('ok') - else: - log.error('Session open failed.') - self.session = 0 - self.on_bound(None) - -# vim: se ts=3: + def _on_session(self, resp): + self._owner.onreceive(None) + if isResultNode(resp): + log.info('Successfully opened session.') + self.session = 1 + self.on_bound('ok') + else: + log.error('Session open failed.') + self.session = 0 + self.on_bound(None) diff --git a/src/common/xmpp/bosh.py b/src/common/xmpp/bosh.py index bb6ea4dd8..cbb90abbd 100644 --- a/src/common/xmpp/bosh.py +++ b/src/common/xmpp/bosh.py @@ -21,8 +21,8 @@ import locale, random from hashlib import sha1 from transports_nb import NonBlockingTransport, NonBlockingHTTPBOSH,\ - CONNECTED, CONNECTING, DISCONNECTED, DISCONNECTING,\ - urisplit, DISCONNECT_TIMEOUT_SECONDS + CONNECTED, CONNECTING, DISCONNECTED, DISCONNECTING,\ + urisplit, DISCONNECT_TIMEOUT_SECONDS from protocol import BOSHBody from simplexml import Node @@ -37,528 +37,528 @@ FAKE_DESCRIPTOR = -1337 class NonBlockingBOSH(NonBlockingTransport): - def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, certs, - xmpp_server, domain, bosh_dict, proxy_creds): - NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue, - estabilish_tls, certs) - - self.bosh_sid = None - if locale.getdefaultlocale()[0]: - self.bosh_xml_lang = locale.getdefaultlocale()[0].split('_')[0] - else: - self.bosh_xml_lang = 'en' - - self.http_version = 'HTTP/1.1' - self.http_persistent = True - self.http_pipelining = bosh_dict['bosh_http_pipelining'] - self.bosh_to = domain - - self.route_host, self.route_port = xmpp_server - - self.bosh_wait = bosh_dict['bosh_wait'] - if not self.http_pipelining: - self.bosh_hold = 1 - else: - self.bosh_hold = bosh_dict['bosh_hold'] - self.bosh_requests = self.bosh_hold - self.bosh_uri = bosh_dict['bosh_uri'] - self.bosh_content = bosh_dict['bosh_content'] - self.over_proxy = bosh_dict['bosh_useproxy'] - if estabilish_tls: - self.bosh_secure = 'true' - else: - self.bosh_secure = 'false' - self.use_proxy_auth = bosh_dict['useauth'] - self.proxy_creds = proxy_creds - self.wait_cb_time = None - self.http_socks = [] - self.stanza_buffer = [] - self.prio_bosh_stanzas = [] - self.current_recv_handler = None - self.current_recv_socket = None - self.key_stack = None - self.ack_checker = None - self.after_init = False - self.proxy_dict = {} - if self.over_proxy and self.estabilish_tls: - self.proxy_dict['type'] = 'http' - # with SSL over proxy, we do HTTP CONNECT to proxy to open a channel to - # BOSH Connection Manager - host, port = urisplit(self.bosh_uri)[1:3] - self.proxy_dict['xmpp_server'] = (host, port) - self.proxy_dict['credentials'] = self.proxy_creds - - - def connect(self, conn_5tuple, on_connect, on_connect_failure): - NonBlockingTransport.connect(self, conn_5tuple, on_connect, on_connect_failure) - - global FAKE_DESCRIPTOR - FAKE_DESCRIPTOR = FAKE_DESCRIPTOR - 1 - self.fd = FAKE_DESCRIPTOR - - self.stanza_buffer = [] - self.prio_bosh_stanzas = [] - - self.key_stack = KeyStack(KEY_COUNT) - self.ack_checker = AckChecker() - self.after_init = True - - self.http_socks.append(self.get_new_http_socket()) - self._tcp_connecting_started() - - self.http_socks[0].connect( - conn_5tuple = conn_5tuple, - on_connect = self._on_connect, - on_connect_failure = self._on_connect_failure) - - def _on_connect(self): - self.peerhost = self.http_socks[0].peerhost - self.ssl_lib = self.http_socks[0].ssl_lib - NonBlockingTransport._on_connect(self) - - - - def set_timeout(self, timeout): - if self.get_state() != DISCONNECTED and self.fd != -1: - NonBlockingTransport.set_timeout(self, timeout) - else: - log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % (self.get_state(), self.fd)) - - def on_http_request_possible(self): - ''' - Called when HTTP request it's possible to send a HTTP request. It can be when - socket is connected or when HTTP response arrived. - There should be always one pending request to BOSH CM. - ''' - log.debug('on_http_req possible, state:\n%s' % self.get_current_state()) - if self.get_state()==DISCONNECTED: return - - #Hack for making the non-secure warning dialog work - if self._owner.got_features: - if (hasattr(self._owner, 'NonBlockingNonSASL') or hasattr(self._owner, 'SASL')): - self.send_BOSH(None) - else: - # If we already got features and no auth module was plugged yet, we are - # probably waiting for confirmation of the "not-secure-connection" dialog. - # We don't send HTTP request in that case. - # see http://lists.jabber.ru/pipermail/ejabberd/2008-August/004027.html - return - else: - self.send_BOSH(None) - - - - def get_socket_in(self, state): - ''' gets sockets in desired state ''' - for s in self.http_socks: - if s.get_state()==state: return s - return None - - - def get_free_socket(self): - ''' Selects and returns socket eligible for sending a data to.''' - if self.http_pipelining: - return self.get_socket_in(CONNECTED) - else: - last_recv_time, tmpsock = 0, None - for s in self.http_socks: - # we're interested only in CONNECTED socket with no requests pending - if s.get_state()==CONNECTED and s.pending_requests==0: - # if there's more of them, we want the one with the least recent data receive - # (lowest last_recv_time) - if (last_recv_time==0) or (s.last_recv_time < last_recv_time): - last_recv_time = s.last_recv_time - tmpsock = s - if tmpsock: - return tmpsock - else: - return None - - - def send_BOSH(self, payload): - ''' - Tries to send a stanza in payload by appeding it to a buffer and plugging a - free socket for writing. - ''' - total_pending_reqs = sum([s.pending_requests for s in self.http_socks]) - - # when called after HTTP response (Payload=None) and when there are already - # some pending requests and no data to send, or when the socket is - # disconnected, we do nothing - if payload is None and \ - total_pending_reqs > 0 and \ - self.stanza_buffer == [] and \ - self.prio_bosh_stanzas == [] or \ - self.get_state()==DISCONNECTED: - return - - # now the payload is put to buffer and will be sent at some point - self.append_stanza(payload) - - # if we're about to make more requests than allowed, we don't send - stanzas will be - # sent after HTTP response from CM, exception is when we're disconnecting - then we - # send anyway - if total_pending_reqs >= self.bosh_requests and self.get_state()!=DISCONNECTING: - log.warn('attemp to make more requests than allowed by Connection Manager:\n%s' % - self.get_current_state()) - return - - # when there's free CONNECTED socket, we plug it for write and the data will - # be sent when write is possible - if self.get_free_socket(): - self.plug_socket() - return - - # if there is a connecting socket, we just wait for when it connects, - # payload will be sent in a sec when the socket connects - if self.get_socket_in(CONNECTING): return - - # being here means there are either DISCONNECTED sockets or all sockets are - # CONNECTED with too many pending requests - s = self.get_socket_in(DISCONNECTED) - - # if we have DISCONNECTED socket, lets connect it and plug for send - if s: - self.connect_and_flush(s) - else: - # otherwise create and connect a new one - ss = self.get_new_http_socket() - self.http_socks.append(ss) - self.connect_and_flush(ss) - return - - def plug_socket(self): - stanza = None - s = self.get_free_socket() - if s: - s._plug_idle(writable=True, readable=True) - else: - log.error('=====!!!!!!!!====> Couldn\'t get free socket in plug_socket())') - - def build_stanza(self, socket): - ''' - Builds a BOSH body tag from data in buffers and adds key, rid and ack - attributes to it. - This method is called from _do_send() of underlying transport. This is to - ensure rid and keys will be processed in correct order. If I generate them - before plugging a socket for write (and did it for two sockets/HTTP - connections) in parallel, they might be sent in wrong order, which results - in violating the BOSH session and server-side disconnect. - ''' - if self.prio_bosh_stanzas: - stanza, add_payload = self.prio_bosh_stanzas.pop(0) - if add_payload: - stanza.setPayload(self.stanza_buffer) - self.stanza_buffer = [] - else: - stanza = self.boshify_stanzas(self.stanza_buffer) - self.stanza_buffer = [] - - stanza = self.ack_checker.backup_stanza(stanza, socket) - - key, newkey = self.key_stack.get() - if key: - stanza.setAttr('key', key) - if newkey: - stanza.setAttr('newkey', newkey) - - - log.info('sending msg with rid=%s to sock %s' % (stanza.getAttr('rid'), id(socket))) - self.renew_bosh_wait_timeout(self.bosh_wait + 3) - return stanza - - - def on_bosh_wait_timeout(self): - log.error('Connection Manager didn\'t respond within %s + 3 seconds --> forcing disconnect' % self.bosh_wait) - self.disconnect() - - - def renew_bosh_wait_timeout(self, timeout): - if self.wait_cb_time is not None: - self.remove_bosh_wait_timeout() - sched_time = self.idlequeue.set_alarm(self.on_bosh_wait_timeout, timeout) - self.wait_cb_time = sched_time - - def remove_bosh_wait_timeout(self): - self.idlequeue.remove_alarm( - self.on_bosh_wait_timeout, - self.wait_cb_time) - - def on_persistent_fallback(self, socket): - ''' - Called from underlying transport when server closes TCP connection. - :param socket: disconnected transport object - ''' - if socket.http_persistent: - log.warn('Fallback to nonpersistent HTTP (no pipelining as well)') - socket.http_persistent = False - self.http_persistent = False - self.http_pipelining = False - socket.disconnect(do_callback=False) - self.connect_and_flush(socket) - else: - socket.disconnect() - - - - def handle_body_attrs(self, stanza_attrs): - ''' - Called for each incoming body stanza from dispatcher. Checks body attributes. - ''' - self.remove_bosh_wait_timeout() - - if self.after_init: - if stanza_attrs.has_key('sid'): - # session ID should be only in init response - self.bosh_sid = stanza_attrs['sid'] - - if stanza_attrs.has_key('requests'): - self.bosh_requests = int(stanza_attrs['requests']) - - if stanza_attrs.has_key('wait'): - self.bosh_wait = int(stanza_attrs['wait']) - self.after_init = False - - ack = None - if stanza_attrs.has_key('ack'): - ack = stanza_attrs['ack'] - self.ack_checker.process_incoming_ack(ack=ack, - socket=self.current_recv_socket) - - if stanza_attrs.has_key('type'): - if stanza_attrs['type'] in ['terminate', 'terminal']: - condition = 'n/a' - if stanza_attrs.has_key('condition'): - condition = stanza_attrs['condition'] - if condition == 'n/a': - log.info('Received sesion-ending terminating stanza') - else: - log.error('Received terminating stanza: %s - %s' % (condition, - bosh_errors[condition])) - self.disconnect() - return - - if stanza_attrs['type'] == 'error': - # recoverable error - pass - return - - - def append_stanza(self, stanza): - ''' appends stanza to a buffer to send ''' - if stanza: - if isinstance(stanza, tuple): - # stanza is tuple of BOSH stanza and bool value for whether to add payload - self.prio_bosh_stanzas.append(stanza) - else: - # stanza is XMPP stanza. Will be boshified before send. - self.stanza_buffer.append(stanza) - - - def send(self, stanza, now=False): - self.send_BOSH(stanza) - - - - def get_current_state(self): - t = '------ SOCKET_ID\tSOCKET_STATE\tPENDING_REQS\n' - for s in self.http_socks: - t = '%s------ %s\t%s\t%s\n' % (t,id(s), s.get_state(), s.pending_requests) - t = '%s------ prio stanzas: %s, queued XMPP stanzas: %s, not_acked stanzas: %s' \ - % (t, self.prio_bosh_stanzas, self.stanza_buffer, - self.ack_checker.get_not_acked_rids()) - return t - - - def connect_and_flush(self, socket): - socket.connect( - conn_5tuple = self.conn_5tuple, - on_connect = self.on_http_request_possible, - on_connect_failure = self.disconnect) - - - def boshify_stanzas(self, stanzas=[], body_attrs=None): - ''' wraps zero to many stanzas by body tag with xmlns and sid ''' - log.debug('boshify_staza - type is: %s, stanza is %s' % (type(stanzas), stanzas)) - tag = BOSHBody(attrs={'sid': self.bosh_sid}) - tag.setPayload(stanzas) - return tag - - - def send_init(self, after_SASL=False): - if after_SASL: - t = BOSHBody( - attrs={ 'to': self.bosh_to, - 'sid': self.bosh_sid, - 'xml:lang': self.bosh_xml_lang, - 'xmpp:restart': 'true', - 'secure': self.bosh_secure, - 'xmlns:xmpp': 'urn:xmpp:xbosh'}) - else: - t = BOSHBody( - attrs={ 'content': self.bosh_content, - 'hold': str(self.bosh_hold), - 'route': '%s:%s' % (self.route_host, self.route_port), - 'to': self.bosh_to, - 'wait': str(self.bosh_wait), - 'xml:lang': self.bosh_xml_lang, - 'xmpp:version': '1.0', - 'ver': '1.6', - 'xmlns:xmpp': 'urn:xmpp:xbosh'}) - self.send_BOSH((t,True)) - - def start_disconnect(self): - NonBlockingTransport.start_disconnect(self) - self.renew_bosh_wait_timeout(DISCONNECT_TIMEOUT_SECONDS) - self.send_BOSH( - (BOSHBody(attrs={'sid': self.bosh_sid, 'type': 'terminate'}), True)) - - - def get_new_http_socket(self): - http_dict = {'http_uri': self.bosh_uri, - 'http_version': self.http_version, - 'http_persistent': self.http_persistent, - 'add_proxy_headers': self.over_proxy and not self.estabilish_tls} - if self.use_proxy_auth: - http_dict['proxy_user'], http_dict['proxy_pass'] = self.proxy_creds - - s = NonBlockingHTTPBOSH( - raise_event=self.raise_event, - on_disconnect=self.disconnect, - idlequeue = self.idlequeue, - estabilish_tls = self.estabilish_tls, - certs = self.certs, - on_http_request_possible = self.on_http_request_possible, - http_dict = http_dict, - proxy_dict = self.proxy_dict, - on_persistent_fallback = self.on_persistent_fallback) - - s.onreceive(self.on_received_http) - s.set_stanza_build_cb(self.build_stanza) - return s - - - def onreceive(self, recv_handler): - if recv_handler is None: - recv_handler = self._owner.Dispatcher.ProcessNonBlocking - self.current_recv_handler = recv_handler - - - def on_received_http(self, data, socket): - self.current_recv_socket = socket - self.current_recv_handler(data) - - - def disconnect(self, do_callback=True): - self.remove_bosh_wait_timeout() - if self.get_state() == DISCONNECTED: return - self.fd = -1 - for s in self.http_socks: - s.disconnect(do_callback=False) - NonBlockingTransport.disconnect(self, do_callback) + def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, certs, + xmpp_server, domain, bosh_dict, proxy_creds): + NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue, + estabilish_tls, certs) + + self.bosh_sid = None + if locale.getdefaultlocale()[0]: + self.bosh_xml_lang = locale.getdefaultlocale()[0].split('_')[0] + else: + self.bosh_xml_lang = 'en' + + self.http_version = 'HTTP/1.1' + self.http_persistent = True + self.http_pipelining = bosh_dict['bosh_http_pipelining'] + self.bosh_to = domain + + self.route_host, self.route_port = xmpp_server + + self.bosh_wait = bosh_dict['bosh_wait'] + if not self.http_pipelining: + self.bosh_hold = 1 + else: + self.bosh_hold = bosh_dict['bosh_hold'] + self.bosh_requests = self.bosh_hold + self.bosh_uri = bosh_dict['bosh_uri'] + self.bosh_content = bosh_dict['bosh_content'] + self.over_proxy = bosh_dict['bosh_useproxy'] + if estabilish_tls: + self.bosh_secure = 'true' + else: + self.bosh_secure = 'false' + self.use_proxy_auth = bosh_dict['useauth'] + self.proxy_creds = proxy_creds + self.wait_cb_time = None + self.http_socks = [] + self.stanza_buffer = [] + self.prio_bosh_stanzas = [] + self.current_recv_handler = None + self.current_recv_socket = None + self.key_stack = None + self.ack_checker = None + self.after_init = False + self.proxy_dict = {} + if self.over_proxy and self.estabilish_tls: + self.proxy_dict['type'] = 'http' + # with SSL over proxy, we do HTTP CONNECT to proxy to open a channel to + # BOSH Connection Manager + host, port = urisplit(self.bosh_uri)[1:3] + self.proxy_dict['xmpp_server'] = (host, port) + self.proxy_dict['credentials'] = self.proxy_creds + + + def connect(self, conn_5tuple, on_connect, on_connect_failure): + NonBlockingTransport.connect(self, conn_5tuple, on_connect, on_connect_failure) + + global FAKE_DESCRIPTOR + FAKE_DESCRIPTOR = FAKE_DESCRIPTOR - 1 + self.fd = FAKE_DESCRIPTOR + + self.stanza_buffer = [] + self.prio_bosh_stanzas = [] + + self.key_stack = KeyStack(KEY_COUNT) + self.ack_checker = AckChecker() + self.after_init = True + + self.http_socks.append(self.get_new_http_socket()) + self._tcp_connecting_started() + + self.http_socks[0].connect( + conn_5tuple = conn_5tuple, + on_connect = self._on_connect, + on_connect_failure = self._on_connect_failure) + + def _on_connect(self): + self.peerhost = self.http_socks[0].peerhost + self.ssl_lib = self.http_socks[0].ssl_lib + NonBlockingTransport._on_connect(self) + + + + def set_timeout(self, timeout): + if self.get_state() != DISCONNECTED and self.fd != -1: + NonBlockingTransport.set_timeout(self, timeout) + else: + log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % (self.get_state(), self.fd)) + + def on_http_request_possible(self): + ''' + Called when HTTP request it's possible to send a HTTP request. It can be when + socket is connected or when HTTP response arrived. + There should be always one pending request to BOSH CM. + ''' + log.debug('on_http_req possible, state:\n%s' % self.get_current_state()) + if self.get_state()==DISCONNECTED: return + + #Hack for making the non-secure warning dialog work + if self._owner.got_features: + if (hasattr(self._owner, 'NonBlockingNonSASL') or hasattr(self._owner, 'SASL')): + self.send_BOSH(None) + else: + # If we already got features and no auth module was plugged yet, we are + # probably waiting for confirmation of the "not-secure-connection" dialog. + # We don't send HTTP request in that case. + # see http://lists.jabber.ru/pipermail/ejabberd/2008-August/004027.html + return + else: + self.send_BOSH(None) + + + + def get_socket_in(self, state): + ''' gets sockets in desired state ''' + for s in self.http_socks: + if s.get_state()==state: return s + return None + + + def get_free_socket(self): + ''' Selects and returns socket eligible for sending a data to.''' + if self.http_pipelining: + return self.get_socket_in(CONNECTED) + else: + last_recv_time, tmpsock = 0, None + for s in self.http_socks: + # we're interested only in CONNECTED socket with no requests pending + if s.get_state()==CONNECTED and s.pending_requests==0: + # if there's more of them, we want the one with the least recent data receive + # (lowest last_recv_time) + if (last_recv_time==0) or (s.last_recv_time < last_recv_time): + last_recv_time = s.last_recv_time + tmpsock = s + if tmpsock: + return tmpsock + else: + return None + + + def send_BOSH(self, payload): + ''' + Tries to send a stanza in payload by appeding it to a buffer and plugging a + free socket for writing. + ''' + total_pending_reqs = sum([s.pending_requests for s in self.http_socks]) + + # when called after HTTP response (Payload=None) and when there are already + # some pending requests and no data to send, or when the socket is + # disconnected, we do nothing + if payload is None and \ + total_pending_reqs > 0 and \ + self.stanza_buffer == [] and \ + self.prio_bosh_stanzas == [] or \ + self.get_state()==DISCONNECTED: + return + + # now the payload is put to buffer and will be sent at some point + self.append_stanza(payload) + + # if we're about to make more requests than allowed, we don't send - stanzas will be + # sent after HTTP response from CM, exception is when we're disconnecting - then we + # send anyway + if total_pending_reqs >= self.bosh_requests and self.get_state()!=DISCONNECTING: + log.warn('attemp to make more requests than allowed by Connection Manager:\n%s' % + self.get_current_state()) + return + + # when there's free CONNECTED socket, we plug it for write and the data will + # be sent when write is possible + if self.get_free_socket(): + self.plug_socket() + return + + # if there is a connecting socket, we just wait for when it connects, + # payload will be sent in a sec when the socket connects + if self.get_socket_in(CONNECTING): return + + # being here means there are either DISCONNECTED sockets or all sockets are + # CONNECTED with too many pending requests + s = self.get_socket_in(DISCONNECTED) + + # if we have DISCONNECTED socket, lets connect it and plug for send + if s: + self.connect_and_flush(s) + else: + # otherwise create and connect a new one + ss = self.get_new_http_socket() + self.http_socks.append(ss) + self.connect_and_flush(ss) + return + + def plug_socket(self): + stanza = None + s = self.get_free_socket() + if s: + s._plug_idle(writable=True, readable=True) + else: + log.error('=====!!!!!!!!====> Couldn\'t get free socket in plug_socket())') + + def build_stanza(self, socket): + ''' + Builds a BOSH body tag from data in buffers and adds key, rid and ack + attributes to it. + This method is called from _do_send() of underlying transport. This is to + ensure rid and keys will be processed in correct order. If I generate them + before plugging a socket for write (and did it for two sockets/HTTP + connections) in parallel, they might be sent in wrong order, which results + in violating the BOSH session and server-side disconnect. + ''' + if self.prio_bosh_stanzas: + stanza, add_payload = self.prio_bosh_stanzas.pop(0) + if add_payload: + stanza.setPayload(self.stanza_buffer) + self.stanza_buffer = [] + else: + stanza = self.boshify_stanzas(self.stanza_buffer) + self.stanza_buffer = [] + + stanza = self.ack_checker.backup_stanza(stanza, socket) + + key, newkey = self.key_stack.get() + if key: + stanza.setAttr('key', key) + if newkey: + stanza.setAttr('newkey', newkey) + + + log.info('sending msg with rid=%s to sock %s' % (stanza.getAttr('rid'), id(socket))) + self.renew_bosh_wait_timeout(self.bosh_wait + 3) + return stanza + + + def on_bosh_wait_timeout(self): + log.error('Connection Manager didn\'t respond within %s + 3 seconds --> forcing disconnect' % self.bosh_wait) + self.disconnect() + + + def renew_bosh_wait_timeout(self, timeout): + if self.wait_cb_time is not None: + self.remove_bosh_wait_timeout() + sched_time = self.idlequeue.set_alarm(self.on_bosh_wait_timeout, timeout) + self.wait_cb_time = sched_time + + def remove_bosh_wait_timeout(self): + self.idlequeue.remove_alarm( + self.on_bosh_wait_timeout, + self.wait_cb_time) + + def on_persistent_fallback(self, socket): + ''' + Called from underlying transport when server closes TCP connection. + :param socket: disconnected transport object + ''' + if socket.http_persistent: + log.warn('Fallback to nonpersistent HTTP (no pipelining as well)') + socket.http_persistent = False + self.http_persistent = False + self.http_pipelining = False + socket.disconnect(do_callback=False) + self.connect_and_flush(socket) + else: + socket.disconnect() + + + + def handle_body_attrs(self, stanza_attrs): + ''' + Called for each incoming body stanza from dispatcher. Checks body attributes. + ''' + self.remove_bosh_wait_timeout() + + if self.after_init: + if stanza_attrs.has_key('sid'): + # session ID should be only in init response + self.bosh_sid = stanza_attrs['sid'] + + if stanza_attrs.has_key('requests'): + self.bosh_requests = int(stanza_attrs['requests']) + + if stanza_attrs.has_key('wait'): + self.bosh_wait = int(stanza_attrs['wait']) + self.after_init = False + + ack = None + if stanza_attrs.has_key('ack'): + ack = stanza_attrs['ack'] + self.ack_checker.process_incoming_ack(ack=ack, + socket=self.current_recv_socket) + + if stanza_attrs.has_key('type'): + if stanza_attrs['type'] in ['terminate', 'terminal']: + condition = 'n/a' + if stanza_attrs.has_key('condition'): + condition = stanza_attrs['condition'] + if condition == 'n/a': + log.info('Received sesion-ending terminating stanza') + else: + log.error('Received terminating stanza: %s - %s' % (condition, + bosh_errors[condition])) + self.disconnect() + return + + if stanza_attrs['type'] == 'error': + # recoverable error + pass + return + + + def append_stanza(self, stanza): + ''' appends stanza to a buffer to send ''' + if stanza: + if isinstance(stanza, tuple): + # stanza is tuple of BOSH stanza and bool value for whether to add payload + self.prio_bosh_stanzas.append(stanza) + else: + # stanza is XMPP stanza. Will be boshified before send. + self.stanza_buffer.append(stanza) + + + def send(self, stanza, now=False): + self.send_BOSH(stanza) + + + + def get_current_state(self): + t = '------ SOCKET_ID\tSOCKET_STATE\tPENDING_REQS\n' + for s in self.http_socks: + t = '%s------ %s\t%s\t%s\n' % (t, id(s), s.get_state(), s.pending_requests) + t = '%s------ prio stanzas: %s, queued XMPP stanzas: %s, not_acked stanzas: %s' \ + % (t, self.prio_bosh_stanzas, self.stanza_buffer, + self.ack_checker.get_not_acked_rids()) + return t + + + def connect_and_flush(self, socket): + socket.connect( + conn_5tuple = self.conn_5tuple, + on_connect = self.on_http_request_possible, + on_connect_failure = self.disconnect) + + + def boshify_stanzas(self, stanzas=[], body_attrs=None): + ''' wraps zero to many stanzas by body tag with xmlns and sid ''' + log.debug('boshify_staza - type is: %s, stanza is %s' % (type(stanzas), stanzas)) + tag = BOSHBody(attrs={'sid': self.bosh_sid}) + tag.setPayload(stanzas) + return tag + + + def send_init(self, after_SASL=False): + if after_SASL: + t = BOSHBody( + attrs={ 'to': self.bosh_to, + 'sid': self.bosh_sid, + 'xml:lang': self.bosh_xml_lang, + 'xmpp:restart': 'true', + 'secure': self.bosh_secure, + 'xmlns:xmpp': 'urn:xmpp:xbosh'}) + else: + t = BOSHBody( + attrs={ 'content': self.bosh_content, + 'hold': str(self.bosh_hold), + 'route': '%s:%s' % (self.route_host, self.route_port), + 'to': self.bosh_to, + 'wait': str(self.bosh_wait), + 'xml:lang': self.bosh_xml_lang, + 'xmpp:version': '1.0', + 'ver': '1.6', + 'xmlns:xmpp': 'urn:xmpp:xbosh'}) + self.send_BOSH((t, True)) + + def start_disconnect(self): + NonBlockingTransport.start_disconnect(self) + self.renew_bosh_wait_timeout(DISCONNECT_TIMEOUT_SECONDS) + self.send_BOSH( + (BOSHBody(attrs={'sid': self.bosh_sid, 'type': 'terminate'}), True)) + + + def get_new_http_socket(self): + http_dict = {'http_uri': self.bosh_uri, + 'http_version': self.http_version, + 'http_persistent': self.http_persistent, + 'add_proxy_headers': self.over_proxy and not self.estabilish_tls} + if self.use_proxy_auth: + http_dict['proxy_user'], http_dict['proxy_pass'] = self.proxy_creds + + s = NonBlockingHTTPBOSH( + raise_event=self.raise_event, + on_disconnect=self.disconnect, + idlequeue = self.idlequeue, + estabilish_tls = self.estabilish_tls, + certs = self.certs, + on_http_request_possible = self.on_http_request_possible, + http_dict = http_dict, + proxy_dict = self.proxy_dict, + on_persistent_fallback = self.on_persistent_fallback) + + s.onreceive(self.on_received_http) + s.set_stanza_build_cb(self.build_stanza) + return s + + + def onreceive(self, recv_handler): + if recv_handler is None: + recv_handler = self._owner.Dispatcher.ProcessNonBlocking + self.current_recv_handler = recv_handler + + + def on_received_http(self, data, socket): + self.current_recv_socket = socket + self.current_recv_handler(data) + + + def disconnect(self, do_callback=True): + self.remove_bosh_wait_timeout() + if self.get_state() == DISCONNECTED: return + self.fd = -1 + for s in self.http_socks: + s.disconnect(do_callback=False) + NonBlockingTransport.disconnect(self, do_callback) def get_rand_number(): - # with 50-bit random initial rid, session would have to go up - # to 7881299347898368 messages to raise rid over 2**53 - # (see http://www.xmpp.org/extensions/xep-0124.html#rids) - # it's also used for sequence key initialization - r = random.Random() - r.seed() - return r.getrandbits(50) + # with 50-bit random initial rid, session would have to go up + # to 7881299347898368 messages to raise rid over 2**53 + # (see http://www.xmpp.org/extensions/xep-0124.html#rids) + # it's also used for sequence key initialization + r = random.Random() + r.seed() + return r.getrandbits(50) class AckChecker(): - ''' - Class for generating rids and generating and checking acknowledgements in - BOSH messages. - ''' - def __init__(self): - self.rid = get_rand_number() - self.ack = 1 - self.last_rids = {} - self.not_acked = [] + ''' + Class for generating rids and generating and checking acknowledgements in + BOSH messages. + ''' + def __init__(self): + self.rid = get_rand_number() + self.ack = 1 + self.last_rids = {} + self.not_acked = [] - def get_not_acked_rids(self): return [rid for rid, st in self.not_acked] + def get_not_acked_rids(self): return [rid for rid, st in self.not_acked] - def backup_stanza(self, stanza, socket): - socket.pending_requests += 1 - rid = self.get_rid() - self.not_acked.append((rid, stanza)) - stanza.setAttr('rid', str(rid)) - self.last_rids[socket]=rid + def backup_stanza(self, stanza, socket): + socket.pending_requests += 1 + rid = self.get_rid() + self.not_acked.append((rid, stanza)) + stanza.setAttr('rid', str(rid)) + self.last_rids[socket]=rid - if self.rid != self.ack + 1: - stanza.setAttr('ack', str(self.ack)) - return stanza + if self.rid != self.ack + 1: + stanza.setAttr('ack', str(self.ack)) + return stanza - def process_incoming_ack(self, socket, ack=None): - socket.pending_requests -= 1 - if ack: - ack = int(ack) - else: - ack = self.last_rids[socket] + def process_incoming_ack(self, socket, ack=None): + socket.pending_requests -= 1 + if ack: + ack = int(ack) + else: + ack = self.last_rids[socket] - i = len([rid for rid, st in self.not_acked if ack >= rid]) - self.not_acked = self.not_acked[i:] + i = len([rid for rid, st in self.not_acked if ack >= rid]) + self.not_acked = self.not_acked[i:] - self.ack = ack + self.ack = ack - def get_rid(self): - self.rid = self.rid + 1 - return self.rid + def get_rid(self): + self.rid = self.rid + 1 + return self.rid class KeyStack(): - ''' - Class implementing key sequences for BOSH messages - ''' - def __init__(self, count): - self.count = count - self.keys = [] - self.reset() - self.first_call = True + ''' + Class implementing key sequences for BOSH messages + ''' + def __init__(self, count): + self.count = count + self.keys = [] + self.reset() + self.first_call = True - def reset(self): - seed = str(get_rand_number()) - self.keys = [sha1(seed).hexdigest()] - for i in range(self.count-1): - curr_seed = self.keys[i] - self.keys.append(sha1(curr_seed).hexdigest()) + def reset(self): + seed = str(get_rand_number()) + self.keys = [sha1(seed).hexdigest()] + for i in range(self.count-1): + curr_seed = self.keys[i] + self.keys.append(sha1(curr_seed).hexdigest()) - def get(self): - if self.first_call: - self.first_call = False - return (None, self.keys.pop()) + def get(self): + if self.first_call: + self.first_call = False + return (None, self.keys.pop()) - if len(self.keys)>1: - return (self.keys.pop(), None) - else: - last_key = self.keys.pop() - self.reset() - new_key = self.keys.pop() - return (last_key, new_key) + if len(self.keys)>1: + return (self.keys.pop(), None) + else: + last_key = self.keys.pop() + self.reset() + new_key = self.keys.pop() + return (last_key, new_key) # http://www.xmpp.org/extensions/xep-0124.html#errorstatus-terminal bosh_errors = { - 'n/a': 'none or unknown condition in terminating body stanza', - 'bad-request': 'The format of an HTTP header or binding element received from the client is unacceptable (e.g., syntax error), or Script Syntax is not supported.', - 'host-gone': 'The target domain specified in the "to" attribute or the target host or port specified in the "route" attribute is no longer serviced by the connection manager.', - 'host-unknown': 'The target domain specified in the "to" attribute or the target host or port specified in the "route" attribute is unknown to the connection manager.', - 'improper-addressing': 'The initialization element lacks a "to" or "route" attribute (or the attribute has no value) but the connection manager requires one.', - 'internal-server-error': 'The connection manager has experienced an internal error that prevents it from servicing the request.', - 'item-not-found': '(1) "sid" is not valid, (2) "stream" is not valid, (3) "rid" is larger than the upper limit of the expected window, (4) connection manager is unable to resend response, (5) "key" sequence is invalid', - 'other-request': 'Another request being processed at the same time as this request caused the session to terminate.', - 'policy-violation': 'The client has broken the session rules (polling too frequently, requesting too frequently, too many simultaneous requests).', - 'remote-connection-failed': 'The connection manager was unable to connect to, or unable to connect securely to, or has lost its connection to, the server.', - 'remote-stream-error': 'Encapsulates an error in the protocol being transported.', - 'see-other-uri': 'The connection manager does not operate at this URI (e.g., the connection manager accepts only SSL or TLS connections at some https: URI rather than the http: URI requested by the client). The client may try POSTing to the URI in the content of the child element.', - 'system-shutdown': 'The connection manager is being shut down. All active HTTP sessions are being terminated. No new sessions can be created.', - 'undefined-condition': 'The error is not one of those defined herein; the connection manager SHOULD include application-specific information in the content of the wrapper.' + 'n/a': 'none or unknown condition in terminating body stanza', + 'bad-request': 'The format of an HTTP header or binding element received from the client is unacceptable (e.g., syntax error), or Script Syntax is not supported.', + 'host-gone': 'The target domain specified in the "to" attribute or the target host or port specified in the "route" attribute is no longer serviced by the connection manager.', + 'host-unknown': 'The target domain specified in the "to" attribute or the target host or port specified in the "route" attribute is unknown to the connection manager.', + 'improper-addressing': 'The initialization element lacks a "to" or "route" attribute (or the attribute has no value) but the connection manager requires one.', + 'internal-server-error': 'The connection manager has experienced an internal error that prevents it from servicing the request.', + 'item-not-found': '(1) "sid" is not valid, (2) "stream" is not valid, (3) "rid" is larger than the upper limit of the expected window, (4) connection manager is unable to resend response, (5) "key" sequence is invalid', + 'other-request': 'Another request being processed at the same time as this request caused the session to terminate.', + 'policy-violation': 'The client has broken the session rules (polling too frequently, requesting too frequently, too many simultaneous requests).', + 'remote-connection-failed': 'The connection manager was unable to connect to, or unable to connect securely to, or has lost its connection to, the server.', + 'remote-stream-error': 'Encapsulates an error in the protocol being transported.', + 'see-other-uri': 'The connection manager does not operate at this URI (e.g., the connection manager accepts only SSL or TLS connections at some https: URI rather than the http: URI requested by the client). The client may try POSTing to the URI in the content of the child element.', + 'system-shutdown': 'The connection manager is being shut down. All active HTTP sessions are being terminated. No new sessions can be created.', + 'undefined-condition': 'The error is not one of those defined herein; the connection manager SHOULD include application-specific information in the content of the wrapper.' } diff --git a/src/common/xmpp/c14n.py b/src/common/xmpp/c14n.py index bccce8155..8a771a2af 100644 --- a/src/common/xmpp/c14n.py +++ b/src/common/xmpp/c14n.py @@ -22,38 +22,36 @@ from simplexml import ustr def c14n(node, is_buggy): - s = "<" + node.name - if node.namespace: - if not node.parent or node.parent.namespace != node.namespace: - s = s + ' xmlns="%s"' % node.namespace + s = "<" + node.name + if node.namespace: + if not node.parent or node.parent.namespace != node.namespace: + s = s + ' xmlns="%s"' % node.namespace - sorted_attrs = sorted(node.attrs.keys()) - for key in sorted_attrs: - if not is_buggy and key == 'xmlns': - continue - val = ustr(node.attrs[key]) - # like XMLescape() but with whitespace and without > - s = s + ' %s="%s"' % ( key, normalise_attr(val) ) - s = s + ">" - cnt = 0 - if node.kids: - for a in node.kids: - if (len(node.data)-1) >= cnt: - s = s + normalise_text(node.data[cnt]) - s = s + c14n(a, is_buggy) - cnt=cnt+1 - if (len(node.data)-1) >= cnt: s = s + normalise_text(node.data[cnt]) - if not node.kids and s.endswith('>'): - s=s[:-1]+' />' - else: - s = s + "" - return s.encode('utf-8') + sorted_attrs = sorted(node.attrs.keys()) + for key in sorted_attrs: + if not is_buggy and key == 'xmlns': + continue + val = ustr(node.attrs[key]) + # like XMLescape() but with whitespace and without > + s = s + ' %s="%s"' % ( key, normalise_attr(val) ) + s = s + ">" + cnt = 0 + if node.kids: + for a in node.kids: + if (len(node.data)-1) >= cnt: + s = s + normalise_text(node.data[cnt]) + s = s + c14n(a, is_buggy) + cnt=cnt+1 + if (len(node.data)-1) >= cnt: s = s + normalise_text(node.data[cnt]) + if not node.kids and s.endswith('>'): + s=s[:-1]+' />' + else: + s = s + "" + return s.encode('utf-8') def normalise_attr(val): - return val.replace('&', '&').replace('<', '<').replace('"', '"').replace('\t', ' ').replace('\n', ' ').replace('\r', ' ') + return val.replace('&', '&').replace('<', '<').replace('"', '"').replace('\t', ' ').replace('\n', ' ').replace('\r', ' ') def normalise_text(val): - return val.replace('&', '&').replace('<', '<').replace('>', '>').replace('\r', ' ') + return val.replace('&', '&').replace('<', '<').replace('>', '>').replace('\r', ' ') - -# vim: se ts=3: diff --git a/src/common/xmpp/client_nb.py b/src/common/xmpp/client_nb.py index 7d82ae4e3..7ebbae26e 100644 --- a/src/common/xmpp/client_nb.py +++ b/src/common/xmpp/client_nb.py @@ -1,8 +1,8 @@ ## client_nb.py -## based on client.py, changes backported up to revision 1.60 +## based on client.py, changes backported up to revision 1.60 ## ## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov -## modified by Dimitur Kirov +## modified by Dimitur Kirov ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -28,533 +28,531 @@ log = logging.getLogger('gajim.c.x.client_nb') class NonBlockingClient: - ''' - Client class is XMPP connection mountpoint. Objects for authentication, - network communication, roster, xml parsing ... are plugged to client object. - Client implements the abstract behavior - mostly negotioation and callbacks - handling, whereas underlying modules take care of feature-specific logic. - ''' - def __init__(self, domain, idlequeue, caller=None): - ''' - Caches connection data: + ''' + Client class is XMPP connection mountpoint. Objects for authentication, + network communication, roster, xml parsing ... are plugged to client object. + Client implements the abstract behavior - mostly negotioation and callbacks + handling, whereas underlying modules take care of feature-specific logic. + ''' + def __init__(self, domain, idlequeue, caller=None): + ''' + Caches connection data: - :param domain: domain - for to: attribute (from account info) - :param idlequeue: processing idlequeue - :param caller: calling object - it has to implement methods - _event_dispatcher which is called from dispatcher instance - ''' - self.Namespace = protocol.NS_CLIENT - self.defaultNamespace = self.Namespace + :param domain: domain - for to: attribute (from account info) + :param idlequeue: processing idlequeue + :param caller: calling object - it has to implement methods + _event_dispatcher which is called from dispatcher instance + ''' + self.Namespace = protocol.NS_CLIENT + self.defaultNamespace = self.Namespace - self.idlequeue = idlequeue - self.disconnect_handlers = [] + self.idlequeue = idlequeue + self.disconnect_handlers = [] - self.Server = domain - self.xmpp_hostname = None # FQDN hostname to connect to + self.Server = domain + self.xmpp_hostname = None # FQDN hostname to connect to - # caller is who initiated this client, it is in needed to register - # the EventDispatcher - self._caller = caller - self._owner = self - self._registered_name = None # our full jid, set after successful auth - self.connected = '' - self.socket = None - self.on_connect = None - self.on_proxy_failure = None - self.on_connect_failure = None - self.proxy = None - self.got_features = False - self.stream_started = False - self.disconnecting = False - self.protocol_type = 'XMPP' + # caller is who initiated this client, it is in needed to register + # the EventDispatcher + self._caller = caller + self._owner = self + self._registered_name = None # our full jid, set after successful auth + self.connected = '' + self.socket = None + self.on_connect = None + self.on_proxy_failure = None + self.on_connect_failure = None + self.proxy = None + self.got_features = False + self.stream_started = False + self.disconnecting = False + self.protocol_type = 'XMPP' - def disconnect(self, message=''): - ''' - Called on disconnection - disconnect callback is picked based on state - of the client. - ''' - # to avoid recursive calls - if self.disconnecting: return + def disconnect(self, message=''): + ''' + Called on disconnection - disconnect callback is picked based on state + of the client. + ''' + # to avoid recursive calls + if self.disconnecting: return - log.info('Disconnecting NBClient: %s' % message) + log.info('Disconnecting NBClient: %s' % message) - if 'NonBlockingRoster' in self.__dict__: - self.NonBlockingRoster.PlugOut() - if 'NonBlockingBind' in self.__dict__: - self.NonBlockingBind.PlugOut() - if 'NonBlockingNonSASL' in self.__dict__: - self.NonBlockingNonSASL.PlugOut() - if 'SASL' in self.__dict__: - self.SASL.PlugOut() - if 'NonBlockingTCP' in self.__dict__: - self.NonBlockingTCP.PlugOut() - if 'NonBlockingHTTP' in self.__dict__: - self.NonBlockingHTTP.PlugOut() - if 'NonBlockingBOSH' in self.__dict__: - self.NonBlockingBOSH.PlugOut() - # FIXME: we never unplug dispatcher, only on next connect - # See _xmpp_connect_machine and SASLHandler + if 'NonBlockingRoster' in self.__dict__: + self.NonBlockingRoster.PlugOut() + if 'NonBlockingBind' in self.__dict__: + self.NonBlockingBind.PlugOut() + if 'NonBlockingNonSASL' in self.__dict__: + self.NonBlockingNonSASL.PlugOut() + if 'SASL' in self.__dict__: + self.SASL.PlugOut() + if 'NonBlockingTCP' in self.__dict__: + self.NonBlockingTCP.PlugOut() + if 'NonBlockingHTTP' in self.__dict__: + self.NonBlockingHTTP.PlugOut() + if 'NonBlockingBOSH' in self.__dict__: + self.NonBlockingBOSH.PlugOut() + # FIXME: we never unplug dispatcher, only on next connect + # See _xmpp_connect_machine and SASLHandler - connected = self.connected - stream_started = self.stream_started + connected = self.connected + stream_started = self.stream_started - self.connected = '' - self.stream_started = False + self.connected = '' + self.stream_started = False - self.disconnecting = True + self.disconnecting = True - log.debug('Client disconnected..') - if connected == '': - # if we're disconnecting before connection to XMPP sever is opened, - # we don't call disconnect handlers but on_connect_failure callback - if self.proxy: - # with proxy, we have different failure callback - log.debug('calling on_proxy_failure cb') - self.on_proxy_failure(reason=message) - else: - log.debug('calling on_connect_failure cb') - self.on_connect_failure() - else: - # we are connected to XMPP server - if not stream_started: - # if error occur before XML stream was opened, e.g. no response on - # init request, we call the on_connect_failure callback because - # proper connection is not established yet and it's not a proxy - # issue - log.debug('calling on_connect_failure cb') - self._caller.streamError = message - self.on_connect_failure() - else: - # with open connection, we are calling the disconnect handlers - for i in reversed(self.disconnect_handlers): - log.debug('Calling disconnect handler %s' % i) - i() - self.disconnecting = False + log.debug('Client disconnected..') + if connected == '': + # if we're disconnecting before connection to XMPP sever is opened, + # we don't call disconnect handlers but on_connect_failure callback + if self.proxy: + # with proxy, we have different failure callback + log.debug('calling on_proxy_failure cb') + self.on_proxy_failure(reason=message) + else: + log.debug('calling on_connect_failure cb') + self.on_connect_failure() + else: + # we are connected to XMPP server + if not stream_started: + # if error occur before XML stream was opened, e.g. no response on + # init request, we call the on_connect_failure callback because + # proper connection is not established yet and it's not a proxy + # issue + log.debug('calling on_connect_failure cb') + self._caller.streamError = message + self.on_connect_failure() + else: + # with open connection, we are calling the disconnect handlers + for i in reversed(self.disconnect_handlers): + log.debug('Calling disconnect handler %s' % i) + i() + self.disconnecting = False - def connect(self, on_connect, on_connect_failure, hostname=None, port=5222, - on_proxy_failure=None, proxy=None, secure_tuple=('plain', None, None)): - ''' - Open XMPP connection (open XML streams in both directions). + def connect(self, on_connect, on_connect_failure, hostname=None, port=5222, + on_proxy_failure=None, proxy=None, secure_tuple=('plain', None, None)): + ''' + Open XMPP connection (open XML streams in both directions). - :param on_connect: called after stream is successfully opened - :param on_connect_failure: called when error occures during connection - :param hostname: hostname of XMPP server from SRV request - :param port: port number of XMPP server - :param on_proxy_failure: called if error occurres during TCP connection to - proxy server or during proxy connecting process - :param proxy: dictionary with proxy data. It should contain at least - values for keys 'host' and 'port' - connection details for proxy serve - and optionally keys 'user' and 'pass' as proxy credentials - :param secure_tuple: tuple of (desired connection type, cacerts, mycerts) - connection type can be 'ssl' - TLS established after TCP connection, - 'tls' - TLS established after negotiation with starttls, or 'plain'. - cacerts, mycerts - see tls_nb.NonBlockingTLS constructor for more - details - ''' - self.on_connect = on_connect - self.on_connect_failure=on_connect_failure - self.on_proxy_failure = on_proxy_failure - self.desired_security, self.cacerts, self.mycerts = secure_tuple - self.Connection = None - self.Port = port - self.proxy = proxy + :param on_connect: called after stream is successfully opened + :param on_connect_failure: called when error occures during connection + :param hostname: hostname of XMPP server from SRV request + :param port: port number of XMPP server + :param on_proxy_failure: called if error occurres during TCP connection to + proxy server or during proxy connecting process + :param proxy: dictionary with proxy data. It should contain at least + values for keys 'host' and 'port' - connection details for proxy serve + and optionally keys 'user' and 'pass' as proxy credentials + :param secure_tuple: tuple of (desired connection type, cacerts, mycerts) + connection type can be 'ssl' - TLS established after TCP connection, + 'tls' - TLS established after negotiation with starttls, or 'plain'. + cacerts, mycerts - see tls_nb.NonBlockingTLS constructor for more + details + ''' + self.on_connect = on_connect + self.on_connect_failure=on_connect_failure + self.on_proxy_failure = on_proxy_failure + self.desired_security, self.cacerts, self.mycerts = secure_tuple + self.Connection = None + self.Port = port + self.proxy = proxy - if hostname: - self.xmpp_hostname = hostname - else: - self.xmpp_hostname = self.Server + if hostname: + self.xmpp_hostname = hostname + else: + self.xmpp_hostname = self.Server - # We only check for SSL here as for TLS we will first have to start a - # PLAIN connection and negotiate TLS afterwards. - # establish_tls will instruct transport to start secure connection - # directly - establish_tls = self.desired_security == 'ssl' - certs = (self.cacerts, self.mycerts) + # We only check for SSL here as for TLS we will first have to start a + # PLAIN connection and negotiate TLS afterwards. + # establish_tls will instruct transport to start secure connection + # directly + establish_tls = self.desired_security == 'ssl' + certs = (self.cacerts, self.mycerts) - proxy_dict = {} - tcp_host = self.xmpp_hostname - tcp_port = self.Port + proxy_dict = {} + tcp_host = self.xmpp_hostname + tcp_port = self.Port - if proxy: - # with proxies, client connects to proxy instead of directly to - # XMPP server ((hostname, port)) - # tcp_host is hostname of machine used for socket connection - # (DNS request will be done for proxy or BOSH CM hostname) - tcp_host, tcp_port, proxy_user, proxy_pass = \ - transports_nb.get_proxy_data_from_dict(proxy) + if proxy: + # with proxies, client connects to proxy instead of directly to + # XMPP server ((hostname, port)) + # tcp_host is hostname of machine used for socket connection + # (DNS request will be done for proxy or BOSH CM hostname) + tcp_host, tcp_port, proxy_user, proxy_pass = \ + transports_nb.get_proxy_data_from_dict(proxy) - if proxy['type'] == 'bosh': - # Setup BOSH transport - self.socket = bosh.NonBlockingBOSH.get_instance( - on_disconnect=self.disconnect, - raise_event=self.raise_event, - idlequeue=self.idlequeue, - estabilish_tls=establish_tls, - certs=certs, - proxy_creds=(proxy_user, proxy_pass), - xmpp_server=(self.xmpp_hostname, self.Port), - domain=self.Server, - bosh_dict=proxy) - self.protocol_type = 'BOSH' - self.wait_for_restart_response = \ - proxy['bosh_wait_for_restart_response'] - else: - # http proxy - proxy_dict['type'] = proxy['type'] - proxy_dict['xmpp_server'] = (self.xmpp_hostname, self.Port) - proxy_dict['credentials'] = (proxy_user, proxy_pass) + if proxy['type'] == 'bosh': + # Setup BOSH transport + self.socket = bosh.NonBlockingBOSH.get_instance( + on_disconnect=self.disconnect, + raise_event=self.raise_event, + idlequeue=self.idlequeue, + estabilish_tls=establish_tls, + certs=certs, + proxy_creds=(proxy_user, proxy_pass), + xmpp_server=(self.xmpp_hostname, self.Port), + domain=self.Server, + bosh_dict=proxy) + self.protocol_type = 'BOSH' + self.wait_for_restart_response = \ + proxy['bosh_wait_for_restart_response'] + else: + # http proxy + proxy_dict['type'] = proxy['type'] + proxy_dict['xmpp_server'] = (self.xmpp_hostname, self.Port) + proxy_dict['credentials'] = (proxy_user, proxy_pass) - if not proxy or proxy['type'] != 'bosh': - # Setup ordinary TCP transport - self.socket = transports_nb.NonBlockingTCP.get_instance( - on_disconnect=self.disconnect, - raise_event=self.raise_event, - idlequeue=self.idlequeue, - estabilish_tls=establish_tls, - certs=certs, - proxy_dict=proxy_dict) + if not proxy or proxy['type'] != 'bosh': + # Setup ordinary TCP transport + self.socket = transports_nb.NonBlockingTCP.get_instance( + on_disconnect=self.disconnect, + raise_event=self.raise_event, + idlequeue=self.idlequeue, + estabilish_tls=establish_tls, + certs=certs, + proxy_dict=proxy_dict) - # plug transport into client as self.Connection - self.socket.PlugIn(self) + # plug transport into client as self.Connection + self.socket.PlugIn(self) - self._resolve_hostname( - hostname=tcp_host, - port=tcp_port, - on_success=self._try_next_ip) + self._resolve_hostname( + hostname=tcp_host, + port=tcp_port, + on_success=self._try_next_ip) - def _resolve_hostname(self, hostname, port, on_success): - ''' wrapper for getaddinfo call. FIXME: getaddinfo blocks''' - try: - self.ip_addresses = socket.getaddrinfo(hostname, port, - socket.AF_UNSPEC, socket.SOCK_STREAM) - except socket.gaierror, (errnum, errstr): - self.disconnect(message='Lookup failure for %s:%s, hostname: %s - %s' % - (self.Server, self.Port, hostname, errstr)) - else: - on_success() + def _resolve_hostname(self, hostname, port, on_success): + ''' wrapper for getaddinfo call. FIXME: getaddinfo blocks''' + try: + self.ip_addresses = socket.getaddrinfo(hostname, port, + socket.AF_UNSPEC, socket.SOCK_STREAM) + except socket.gaierror, (errnum, errstr): + self.disconnect(message='Lookup failure for %s:%s, hostname: %s - %s' % + (self.Server, self.Port, hostname, errstr)) + else: + on_success() - def _try_next_ip(self, err_message=None): - '''Iterates over IP addresses tries to connect to it''' - if err_message: - log.debug('While looping over DNS A records: %s' % err_message) - if self.ip_addresses == []: - msg = 'Run out of hosts for name %s:%s.' % (self.Server, self.Port) - msg = msg + ' Error for last IP: %s' % err_message - self.disconnect(msg) - else: - self.current_ip = self.ip_addresses.pop(0) - self.socket.connect( - conn_5tuple=self.current_ip, - on_connect=lambda: self._xmpp_connect(), - on_connect_failure=self._try_next_ip) + def _try_next_ip(self, err_message=None): + '''Iterates over IP addresses tries to connect to it''' + if err_message: + log.debug('While looping over DNS A records: %s' % err_message) + if self.ip_addresses == []: + msg = 'Run out of hosts for name %s:%s.' % (self.Server, self.Port) + msg = msg + ' Error for last IP: %s' % err_message + self.disconnect(msg) + else: + self.current_ip = self.ip_addresses.pop(0) + self.socket.connect( + conn_5tuple=self.current_ip, + on_connect=lambda: self._xmpp_connect(), + on_connect_failure=self._try_next_ip) - def incoming_stream_version(self): - ''' gets version of xml stream''' - if 'version' in self.Dispatcher.Stream._document_attrs: - return self.Dispatcher.Stream._document_attrs['version'] - else: - return None + def incoming_stream_version(self): + ''' gets version of xml stream''' + if 'version' in self.Dispatcher.Stream._document_attrs: + return self.Dispatcher.Stream._document_attrs['version'] + else: + return None - def _xmpp_connect(self, socket_type=None): - ''' - Starts XMPP connecting process - opens the XML stream. Is called after TCP - connection is established or after switch to TLS when successfully - negotiated with . - ''' - # socket_type contains info which transport connection was established - if not socket_type: - if self.Connection.ssl_lib: - # When ssl_lib is set we connected via SSL - socket_type = 'ssl' - else: - # PLAIN is default - socket_type = 'plain' - self.connected = socket_type - self._xmpp_connect_machine() + def _xmpp_connect(self, socket_type=None): + ''' + Starts XMPP connecting process - opens the XML stream. Is called after TCP + connection is established or after switch to TLS when successfully + negotiated with . + ''' + # socket_type contains info which transport connection was established + if not socket_type: + if self.Connection.ssl_lib: + # When ssl_lib is set we connected via SSL + socket_type = 'ssl' + else: + # PLAIN is default + socket_type = 'plain' + self.connected = socket_type + self._xmpp_connect_machine() - def _xmpp_connect_machine(self, mode=None, data=None): - ''' - Finite automaton taking care of stream opening and features tag - handling. Calls _on_stream_start when stream is started, and disconnect() - on failure. - ''' - log.info('-------------xmpp_connect_machine() >> mode: %s, data: %s...' % - (mode, str(data)[:20])) + def _xmpp_connect_machine(self, mode=None, data=None): + ''' + Finite automaton taking care of stream opening and features tag + handling. Calls _on_stream_start when stream is started, and disconnect() + on failure. + ''' + log.info('-------------xmpp_connect_machine() >> mode: %s, data: %s...' % + (mode, str(data)[:20])) - def on_next_receive(mode): - ''' - Sets desired on_receive callback on transport based on the state of - connect_machine. - ''' - log.info('setting %s on next receive' % mode) - if mode is None: - self.onreceive(None) # switch to Dispatcher.ProcessNonBlocking - else: - self.onreceive(lambda _data:self._xmpp_connect_machine(mode, _data)) + def on_next_receive(mode): + ''' + Sets desired on_receive callback on transport based on the state of + connect_machine. + ''' + log.info('setting %s on next receive' % mode) + if mode is None: + self.onreceive(None) # switch to Dispatcher.ProcessNonBlocking + else: + self.onreceive(lambda _data:self._xmpp_connect_machine(mode, _data)) - if not mode: - # starting state - if self.__dict__.has_key('Dispatcher'): - self.Dispatcher.PlugOut() - self.got_features = False - dispatcher_nb.Dispatcher.get_instance().PlugIn(self) - on_next_receive('RECEIVE_DOCUMENT_ATTRIBUTES') + if not mode: + # starting state + if self.__dict__.has_key('Dispatcher'): + self.Dispatcher.PlugOut() + self.got_features = False + dispatcher_nb.Dispatcher.get_instance().PlugIn(self) + on_next_receive('RECEIVE_DOCUMENT_ATTRIBUTES') - elif mode == 'FAILURE': - self.disconnect('During XMPP connect: %s' % data) + elif mode == 'FAILURE': + self.disconnect('During XMPP connect: %s' % data) - elif mode == 'RECEIVE_DOCUMENT_ATTRIBUTES': - if data: - self.Dispatcher.ProcessNonBlocking(data) - if not hasattr(self, 'Dispatcher') or \ - self.Dispatcher.Stream._document_attrs is None: - self._xmpp_connect_machine( - mode='FAILURE', - data='Error on stream open') - return + elif mode == 'RECEIVE_DOCUMENT_ATTRIBUTES': + if data: + self.Dispatcher.ProcessNonBlocking(data) + if not hasattr(self, 'Dispatcher') or \ + self.Dispatcher.Stream._document_attrs is None: + self._xmpp_connect_machine( + mode='FAILURE', + data='Error on stream open') + return - # if terminating stanza was received after init request then client gets - # disconnected from bosh transport plugin and we have to end the stream - # negotiating process straight away. - # fixes #4657 - if not self.connected: return + # if terminating stanza was received after init request then client gets + # disconnected from bosh transport plugin and we have to end the stream + # negotiating process straight away. + # fixes #4657 + if not self.connected: return - if self.incoming_stream_version() == '1.0': - if not self.got_features: - on_next_receive('RECEIVE_STREAM_FEATURES') - else: - log.info('got STREAM FEATURES in first recv') - self._xmpp_connect_machine(mode='STREAM_STARTED') - else: - log.info('incoming stream version less than 1.0') - self._xmpp_connect_machine(mode='STREAM_STARTED') + if self.incoming_stream_version() == '1.0': + if not self.got_features: + on_next_receive('RECEIVE_STREAM_FEATURES') + else: + log.info('got STREAM FEATURES in first recv') + self._xmpp_connect_machine(mode='STREAM_STARTED') + else: + log.info('incoming stream version less than 1.0') + self._xmpp_connect_machine(mode='STREAM_STARTED') - elif mode == 'RECEIVE_STREAM_FEATURES': - if data: - # sometimes are received together with document - # attributes and sometimes on next receive... - self.Dispatcher.ProcessNonBlocking(data) - if not self.got_features: - self._xmpp_connect_machine( - mode='FAILURE', - data='Missing in 1.0 stream') - else: - log.info('got STREAM FEATURES in second recv') - self._xmpp_connect_machine(mode='STREAM_STARTED') + elif mode == 'RECEIVE_STREAM_FEATURES': + if data: + # sometimes are received together with document + # attributes and sometimes on next receive... + self.Dispatcher.ProcessNonBlocking(data) + if not self.got_features: + self._xmpp_connect_machine( + mode='FAILURE', + data='Missing in 1.0 stream') + else: + log.info('got STREAM FEATURES in second recv') + self._xmpp_connect_machine(mode='STREAM_STARTED') - elif mode == 'STREAM_STARTED': - self._on_stream_start() + elif mode == 'STREAM_STARTED': + self._on_stream_start() - def _on_stream_start(self): - ''' - Called after XMPP stream is opened. TLS negotiation may follow if - supported and desired. - ''' - self.stream_started = True - self.onreceive(None) + def _on_stream_start(self): + ''' + Called after XMPP stream is opened. TLS negotiation may follow if + supported and desired. + ''' + self.stream_started = True + self.onreceive(None) - if self.connected == 'plain': - if self.desired_security == 'plain': - # if we want and have plain connection, we're done now - self._on_connect() - else: - # try to negotiate TLS - if self.incoming_stream_version() != '1.0': - # if stream version is less than 1.0, we can't do more - log.warn('While connecting with type = "tls": stream version ' + - 'is less than 1.0') - self._on_connect() - return - if self.Dispatcher.Stream.features.getTag('starttls'): - # Server advertises TLS support, start negotiation - self.stream_started = False - log.info('TLS supported by remote server. Requesting TLS start.') - self._tls_negotiation_handler() - else: - log.warn('While connecting with type = "tls": TLS unsupported ' + - 'by remote server') - self._on_connect() + if self.connected == 'plain': + if self.desired_security == 'plain': + # if we want and have plain connection, we're done now + self._on_connect() + else: + # try to negotiate TLS + if self.incoming_stream_version() != '1.0': + # if stream version is less than 1.0, we can't do more + log.warn('While connecting with type = "tls": stream version ' + + 'is less than 1.0') + self._on_connect() + return + if self.Dispatcher.Stream.features.getTag('starttls'): + # Server advertises TLS support, start negotiation + self.stream_started = False + log.info('TLS supported by remote server. Requesting TLS start.') + self._tls_negotiation_handler() + else: + log.warn('While connecting with type = "tls": TLS unsupported ' + + 'by remote server') + self._on_connect() - elif self.connected in ['ssl', 'tls']: - self._on_connect() - else: - assert False, 'Stream opened for unsupported connection' + elif self.connected in ['ssl', 'tls']: + self._on_connect() + else: + assert False, 'Stream opened for unsupported connection' - def _tls_negotiation_handler(self, con=None, tag=None): - ''' takes care of TLS negotioation with ''' - log.info('-------------tls_negotiaton_handler() >> tag: %s' % tag) - if not con and not tag: - # starting state when we send the - self.RegisterHandlerOnce('proceed', self._tls_negotiation_handler, - xmlns=NS_TLS) - self.RegisterHandlerOnce('failure', self._tls_negotiation_handler, - xmlns=NS_TLS) - self.send('' % NS_TLS) - else: - # we got or - if tag.getNamespace() != NS_TLS: - self.disconnect('Unknown namespace: %s' % tag.getNamespace()) - return - tagname = tag.getName() - if tagname == 'failure': - self.disconnect('TLS received: %s' % tag) - return - log.info('Got starttls proceed response. Switching to TLS/SSL...') - # following call wouldn't work for BOSH transport but it doesn't matter - # because negotiation with BOSH is forbidden - self.Connection.tls_init( - on_succ = lambda: self._xmpp_connect(socket_type='tls'), - on_fail = lambda: self.disconnect('error while etabilishing TLS')) + def _tls_negotiation_handler(self, con=None, tag=None): + ''' takes care of TLS negotioation with ''' + log.info('-------------tls_negotiaton_handler() >> tag: %s' % tag) + if not con and not tag: + # starting state when we send the + self.RegisterHandlerOnce('proceed', self._tls_negotiation_handler, + xmlns=NS_TLS) + self.RegisterHandlerOnce('failure', self._tls_negotiation_handler, + xmlns=NS_TLS) + self.send('' % NS_TLS) + else: + # we got or + if tag.getNamespace() != NS_TLS: + self.disconnect('Unknown namespace: %s' % tag.getNamespace()) + return + tagname = tag.getName() + if tagname == 'failure': + self.disconnect('TLS received: %s' % tag) + return + log.info('Got starttls proceed response. Switching to TLS/SSL...') + # following call wouldn't work for BOSH transport but it doesn't matter + # because negotiation with BOSH is forbidden + self.Connection.tls_init( + on_succ = lambda: self._xmpp_connect(socket_type='tls'), + on_fail = lambda: self.disconnect('error while etabilishing TLS')) - def _on_connect(self): - ''' preceeds call of on_connect callback ''' - self.onreceive(None) - self.on_connect(self, self.connected) + def _on_connect(self): + ''' preceeds call of on_connect callback ''' + self.onreceive(None) + self.on_connect(self, self.connected) - def raise_event(self, event_type, data): - ''' - Raises event to connection instance. DATA_SENT and DATA_RECIVED events - are used in XML console to show XMPP traffic - ''' - log.info('raising event from transport: :::::%s::::\n_____________\n%s\n_____________\n' % (event_type,data)) - if hasattr(self, 'Dispatcher'): - self.Dispatcher.Event('', event_type, data) + def raise_event(self, event_type, data): + ''' + Raises event to connection instance. DATA_SENT and DATA_RECIVED events + are used in XML console to show XMPP traffic + ''' + log.info('raising event from transport: :::::%s::::\n_____________\n%s\n_____________\n' % (event_type, data)) + if hasattr(self, 'Dispatcher'): + self.Dispatcher.Event('', event_type, data) ############################################################################### ### follows code for authentication, resource bind, session and roster download ############################################################################### - def auth(self, user, password, resource='', sasl=True, on_auth=None): - ''' - Authenticate connnection and bind resource. If resource is not provided - random one or library name used. + def auth(self, user, password, resource='', sasl=True, on_auth=None): + ''' + Authenticate connnection and bind resource. If resource is not provided + random one or library name used. - :param user: XMPP username - :param password: XMPP password - :param resource: resource that shall be used for auth/connecting - :param sasl: Boolean indicating if SASL shall be used. (default: True) - :param on_auth: Callback, called after auth. On auth failure, argument - is None. - ''' - self._User, self._Password = user, password - self._Resource, self._sasl = resource, sasl - self.on_auth = on_auth - self._on_doc_attrs() - return + :param user: XMPP username + :param password: XMPP password + :param resource: resource that shall be used for auth/connecting + :param sasl: Boolean indicating if SASL shall be used. (default: True) + :param on_auth: Callback, called after auth. On auth failure, argument + is None. + ''' + self._User, self._Password = user, password + self._Resource, self._sasl = resource, sasl + self.on_auth = on_auth + self._on_doc_attrs() + return - def _on_old_auth(self, res): - ''' Callback used by NON-SASL auth. On auth failure, res is None. ''' - if res: - self.connected += '+old_auth' - self.on_auth(self, 'old_auth') - else: - self.on_auth(self, None) + def _on_old_auth(self, res): + ''' Callback used by NON-SASL auth. On auth failure, res is None. ''' + if res: + self.connected += '+old_auth' + self.on_auth(self, 'old_auth') + else: + self.on_auth(self, None) - def _on_sasl_auth(self, res): - ''' Used internally. On auth failure, res is None. ''' - self.onreceive(None) - if res: - self.connected += '+sasl' - self.on_auth(self, 'sasl') - else: - self.on_auth(self, None) + def _on_sasl_auth(self, res): + ''' Used internally. On auth failure, res is None. ''' + self.onreceive(None) + if res: + self.connected += '+sasl' + self.on_auth(self, 'sasl') + else: + self.on_auth(self, None) - def _on_doc_attrs(self): - ''' Plug authentication objects and start auth. ''' - if self._sasl: - auth_nb.SASL.get_instance(self._User, self._Password, - self._on_start_sasl).PlugIn(self) - if not self._sasl or self.SASL.startsasl == 'not-supported': - if not self._Resource: - self._Resource = 'xmpppy' - auth_nb.NonBlockingNonSASL.get_instance(self._User, self._Password, - self._Resource, self._on_old_auth).PlugIn(self) - return - self.SASL.auth() - return True + def _on_doc_attrs(self): + ''' Plug authentication objects and start auth. ''' + if self._sasl: + auth_nb.SASL.get_instance(self._User, self._Password, + self._on_start_sasl).PlugIn(self) + if not self._sasl or self.SASL.startsasl == 'not-supported': + if not self._Resource: + self._Resource = 'xmpppy' + auth_nb.NonBlockingNonSASL.get_instance(self._User, self._Password, + self._Resource, self._on_old_auth).PlugIn(self) + return + self.SASL.auth() + return True - def _on_start_sasl(self, data=None): - ''' Callback used by SASL, called on each auth step.''' - if data: - self.Dispatcher.ProcessNonBlocking(data) - if not 'SASL' in self.__dict__: - # SASL is pluged out, possible disconnect - return - if self.SASL.startsasl == 'in-process': - return - self.onreceive(None) - if self.SASL.startsasl == 'failure': - # wrong user/pass, stop auth - if 'SASL' in self.__dict__: - self.SASL.PlugOut() - self.connected = None # FIXME: is this intended? We use ''elsewhere - self._on_sasl_auth(None) - elif self.SASL.startsasl == 'success': - auth_nb.NonBlockingBind.get_instance().PlugIn(self) - self.onreceive(self._on_auth_bind) - return True + def _on_start_sasl(self, data=None): + ''' Callback used by SASL, called on each auth step.''' + if data: + self.Dispatcher.ProcessNonBlocking(data) + if not 'SASL' in self.__dict__: + # SASL is pluged out, possible disconnect + return + if self.SASL.startsasl == 'in-process': + return + self.onreceive(None) + if self.SASL.startsasl == 'failure': + # wrong user/pass, stop auth + if 'SASL' in self.__dict__: + self.SASL.PlugOut() + self.connected = None # FIXME: is this intended? We use ''elsewhere + self._on_sasl_auth(None) + elif self.SASL.startsasl == 'success': + auth_nb.NonBlockingBind.get_instance().PlugIn(self) + self.onreceive(self._on_auth_bind) + return True - def _on_auth_bind(self, data): - # FIXME: Why use this callback and not bind directly? - if data: - self.Dispatcher.ProcessNonBlocking(data) - if self.NonBlockingBind.bound is None: - return - self.NonBlockingBind.NonBlockingBind(self._Resource, self._on_sasl_auth) - return True + def _on_auth_bind(self, data): + # FIXME: Why use this callback and not bind directly? + if data: + self.Dispatcher.ProcessNonBlocking(data) + if self.NonBlockingBind.bound is None: + return + self.NonBlockingBind.NonBlockingBind(self._Resource, self._on_sasl_auth) + return True - def initRoster(self, version=''): - ''' Plug in the roster. ''' - if not self.__dict__.has_key('NonBlockingRoster'): - return roster_nb.NonBlockingRoster.get_instance(version=version).PlugIn(self) + def initRoster(self, version=''): + ''' Plug in the roster. ''' + if not self.__dict__.has_key('NonBlockingRoster'): + return roster_nb.NonBlockingRoster.get_instance(version=version).PlugIn(self) - def getRoster(self, on_ready=None, force=False): - ''' Return the Roster instance, previously plugging it in and - requesting roster from server if needed. ''' - if self.__dict__.has_key('NonBlockingRoster'): - return self.NonBlockingRoster.getRoster(on_ready, force) - return None + def getRoster(self, on_ready=None, force=False): + ''' Return the Roster instance, previously plugging it in and + requesting roster from server if needed. ''' + if self.__dict__.has_key('NonBlockingRoster'): + return self.NonBlockingRoster.getRoster(on_ready, force) + return None - def sendPresence(self, jid=None, typ=None, requestRoster=0): - ''' Send some specific presence state. - Can also request roster from server if according agrument is set.''' - if requestRoster: - # FIXME: used somewhere? - roster_nb.NonBlockingRoster.get_instance().PlugIn(self) - self.send(dispatcher_nb.Presence(to=jid, typ=typ)) + def sendPresence(self, jid=None, typ=None, requestRoster=0): + ''' Send some specific presence state. + Can also request roster from server if according agrument is set.''' + if requestRoster: + # FIXME: used somewhere? + roster_nb.NonBlockingRoster.get_instance().PlugIn(self) + self.send(dispatcher_nb.Presence(to=jid, typ=typ)) ############################################################################### ### following methods are moved from blocking client class of xmpppy ############################################################################### - def RegisterDisconnectHandler(self,handler): - ''' Register handler that will be called on disconnect.''' - self.disconnect_handlers.append(handler) + def RegisterDisconnectHandler(self, handler): + ''' Register handler that will be called on disconnect.''' + self.disconnect_handlers.append(handler) - def UnregisterDisconnectHandler(self,handler): - ''' Unregister handler that is called on disconnect.''' - self.disconnect_handlers.remove(handler) + def UnregisterDisconnectHandler(self, handler): + ''' Unregister handler that is called on disconnect.''' + self.disconnect_handlers.remove(handler) - def DisconnectHandler(self): - ''' - Default disconnect handler. Just raises an IOError. If you choosed to use - this class in your production client, override this method or at least - unregister it. - ''' - raise IOError('Disconnected from server.') + def DisconnectHandler(self): + ''' + Default disconnect handler. Just raises an IOError. If you choosed to use + this class in your production client, override this method or at least + unregister it. + ''' + raise IOError('Disconnected from server.') - def get_connect_type(self): - ''' Returns connection state. F.e.: None / 'tls' / 'plain+non_sasl'. ''' - return self.connected + def get_connect_type(self): + ''' Returns connection state. F.e.: None / 'tls' / 'plain+non_sasl'. ''' + return self.connected - def get_peerhost(self): - ''' - Gets the ip address of the account, from which is made connection to the - server (e.g. IP and port of gajim's socket). - We will create listening socket on the same ip - ''' - # FIXME: tuple (ip, port) is expected (and checked for) but port num is - # useless - return self.socket.peerhost - -# vim: se ts=3: + def get_peerhost(self): + ''' + Gets the ip address of the account, from which is made connection to the + server (e.g. IP and port of gajim's socket). + We will create listening socket on the same ip + ''' + # FIXME: tuple (ip, port) is expected (and checked for) but port num is + # useless + return self.socket.peerhost diff --git a/src/common/xmpp/dispatcher_nb.py b/src/common/xmpp/dispatcher_nb.py index 477d3e6c4..6d3df376f 100644 --- a/src/common/xmpp/dispatcher_nb.py +++ b/src/common/xmpp/dispatcher_nb.py @@ -24,7 +24,7 @@ import simplexml, sys, locale from xml.parsers.expat import ExpatError from plugin import PlugIn from protocol import (NS_STREAMS, NS_XMPP_STREAMS, NS_HTTP_BIND, Iq, Presence, - Message, Protocol, Node, Error, ERR_FEATURE_NOT_IMPLEMENTED, StreamError) + Message, Protocol, Node, Error, ERR_FEATURE_NOT_IMPLEMENTED, StreamError) import logging log = logging.getLogger('gajim.c.x.dispatcher_nb') @@ -36,551 +36,549 @@ XML_DECLARATION = '' # FIXME: ugly class Dispatcher(): - ''' - Why is this here - I needed to redefine Dispatcher for BOSH and easiest way - was to inherit original Dispatcher (now renamed to XMPPDispatcher). Trouble - is that reference used to access dispatcher instance is in Client attribute - named by __class__.__name__ of the dispatcher instance .. long story short: + ''' + Why is this here - I needed to redefine Dispatcher for BOSH and easiest way + was to inherit original Dispatcher (now renamed to XMPPDispatcher). Trouble + is that reference used to access dispatcher instance is in Client attribute + named by __class__.__name__ of the dispatcher instance .. long story short: - I wrote following to avoid changing each client.Dispatcher.whatever() in xmpp + I wrote following to avoid changing each client.Dispatcher.whatever() in xmpp - If having two kinds of dispatcher will go well, I will rewrite the dispatcher - references in other scripts - ''' - def PlugIn(self, client_obj, after_SASL=False, old_features=None): - if client_obj.protocol_type == 'XMPP': - XMPPDispatcher().PlugIn(client_obj) - elif client_obj.protocol_type == 'BOSH': - BOSHDispatcher().PlugIn(client_obj, after_SASL, old_features) - else: - assert False # should never be reached + If having two kinds of dispatcher will go well, I will rewrite the dispatcher + references in other scripts + ''' + def PlugIn(self, client_obj, after_SASL=False, old_features=None): + if client_obj.protocol_type == 'XMPP': + XMPPDispatcher().PlugIn(client_obj) + elif client_obj.protocol_type == 'BOSH': + BOSHDispatcher().PlugIn(client_obj, after_SASL, old_features) + else: + assert False # should never be reached - @classmethod - def get_instance(cls, *args, **kwargs): - ''' - Factory Method for object creation. + @classmethod + def get_instance(cls, *args, **kwargs): + ''' + Factory Method for object creation. - Use this instead of directly initializing the class in order to make - unit testing much easier. - ''' - return cls(*args, **kwargs) + Use this instead of directly initializing the class in order to make + unit testing much easier. + ''' + return cls(*args, **kwargs) class XMPPDispatcher(PlugIn): - ''' - Handles XMPP stream and is the first who takes control over a fresh stanza. + ''' + Handles XMPP stream and is the first who takes control over a fresh stanza. - Is plugged into NonBlockingClient but can be replugged to restart handled - stream headers (used by SASL f.e.). - ''' - def __init__(self): - PlugIn.__init__(self) - self.handlers = {} - self._expected = {} - self._defaultHandler = None - self._pendingExceptions = [] - self._eventHandler = None - self._cycleHandlers = [] - self._exported_methods=[self.RegisterHandler, self.RegisterDefaultHandler, - self.RegisterEventHandler, self.UnregisterCycleHandler, - self.RegisterCycleHandler, self.RegisterHandlerOnce, - self.UnregisterHandler, self.RegisterProtocol, - self.SendAndWaitForResponse, self.SendAndCallForResponse, - self.getAnID, self.Event, self.send] + Is plugged into NonBlockingClient but can be replugged to restart handled + stream headers (used by SASL f.e.). + ''' + def __init__(self): + PlugIn.__init__(self) + self.handlers = {} + self._expected = {} + self._defaultHandler = None + self._pendingExceptions = [] + self._eventHandler = None + self._cycleHandlers = [] + self._exported_methods=[self.RegisterHandler, self.RegisterDefaultHandler, + self.RegisterEventHandler, self.UnregisterCycleHandler, + self.RegisterCycleHandler, self.RegisterHandlerOnce, + self.UnregisterHandler, self.RegisterProtocol, + self.SendAndWaitForResponse, self.SendAndCallForResponse, + self.getAnID, self.Event, self.send] - def getAnID(self): - global outgoingID - outgoingID += 1 - return repr(outgoingID) + def getAnID(self): + global outgoingID + outgoingID += 1 + return repr(outgoingID) - def dumpHandlers(self): - ''' - Return set of user-registered callbacks in it's internal format. - Used within the library to carry user handlers set over Dispatcher - replugins. - ''' - return self.handlers + def dumpHandlers(self): + ''' + Return set of user-registered callbacks in it's internal format. + Used within the library to carry user handlers set over Dispatcher + replugins. + ''' + return self.handlers - def restoreHandlers(self, handlers): - ''' - Restores user-registered callbacks structure from dump previously - obtained via dumpHandlers. Used within the library to carry user - handlers set over Dispatcher replugins. - ''' - self.handlers = handlers + def restoreHandlers(self, handlers): + ''' + Restores user-registered callbacks structure from dump previously + obtained via dumpHandlers. Used within the library to carry user + handlers set over Dispatcher replugins. + ''' + self.handlers = handlers - def _init(self): - ''' - Registers default namespaces/protocols/handlers. Used internally. - ''' - # FIXME: inject dependencies, do not rely that they are defined by our - # owner - self.RegisterNamespace('unknown') - self.RegisterNamespace(NS_STREAMS) - self.RegisterNamespace(self._owner.defaultNamespace) - self.RegisterProtocol('iq', Iq) - self.RegisterProtocol('presence', Presence) - self.RegisterProtocol('message', Message) - self.RegisterDefaultHandler(self.returnStanzaHandler) - self.RegisterEventHandler(self._owner._caller._event_dispatcher) - self.on_responses = {} + def _init(self): + ''' + Registers default namespaces/protocols/handlers. Used internally. + ''' + # FIXME: inject dependencies, do not rely that they are defined by our + # owner + self.RegisterNamespace('unknown') + self.RegisterNamespace(NS_STREAMS) + self.RegisterNamespace(self._owner.defaultNamespace) + self.RegisterProtocol('iq', Iq) + self.RegisterProtocol('presence', Presence) + self.RegisterProtocol('message', Message) + self.RegisterDefaultHandler(self.returnStanzaHandler) + self.RegisterEventHandler(self._owner._caller._event_dispatcher) + self.on_responses = {} - def plugin(self, owner): - ''' - Plug the Dispatcher instance into Client class instance and send - initial stream header. Used internally. - ''' - self._init() - self._owner.lastErrNode = None - self._owner.lastErr = None - self._owner.lastErrCode = None - if hasattr(self._owner, 'StreamInit'): - self._owner.StreamInit() - else: - self.StreamInit() + def plugin(self, owner): + ''' + Plug the Dispatcher instance into Client class instance and send + initial stream header. Used internally. + ''' + self._init() + self._owner.lastErrNode = None + self._owner.lastErr = None + self._owner.lastErrCode = None + if hasattr(self._owner, 'StreamInit'): + self._owner.StreamInit() + else: + self.StreamInit() - def plugout(self): - ''' Prepares instance to be destructed. ''' - self.Stream.dispatch = None - self.Stream.features = None - self.Stream.destroy() - self._owner = None - self.Stream = None + def plugout(self): + ''' Prepares instance to be destructed. ''' + self.Stream.dispatch = None + self.Stream.features = None + self.Stream.destroy() + self._owner = None + self.Stream = None - def StreamInit(self): - ''' Send an initial stream header. ''' - self.Stream = simplexml.NodeBuilder() - self.Stream.dispatch = self.dispatch - self.Stream._dispatch_depth = 2 - self.Stream.stream_header_received = self._check_stream_start - self.Stream.features = None - self._metastream = Node('stream:stream') - self._metastream.setNamespace(self._owner.Namespace) - self._metastream.setAttr('version', '1.0') - self._metastream.setAttr('xmlns:stream', NS_STREAMS) - self._metastream.setAttr('to', self._owner.Server) - if locale.getdefaultlocale()[0]: - self._metastream.setAttr('xml:lang', - locale.getdefaultlocale()[0].split('_')[0]) - self._owner.send("%s%s>" % (XML_DECLARATION, str(self._metastream)[:-2])) + def StreamInit(self): + ''' Send an initial stream header. ''' + self.Stream = simplexml.NodeBuilder() + self.Stream.dispatch = self.dispatch + self.Stream._dispatch_depth = 2 + self.Stream.stream_header_received = self._check_stream_start + self.Stream.features = None + self._metastream = Node('stream:stream') + self._metastream.setNamespace(self._owner.Namespace) + self._metastream.setAttr('version', '1.0') + self._metastream.setAttr('xmlns:stream', NS_STREAMS) + self._metastream.setAttr('to', self._owner.Server) + if locale.getdefaultlocale()[0]: + self._metastream.setAttr('xml:lang', + locale.getdefaultlocale()[0].split('_')[0]) + self._owner.send("%s%s>" % (XML_DECLARATION, str(self._metastream)[:-2])) - def _check_stream_start(self, ns, tag, attrs): - if ns != NS_STREAMS or tag!='stream': - raise ValueError('Incorrect stream start: (%s,%s). Terminating.' - % (tag, ns)) + def _check_stream_start(self, ns, tag, attrs): + if ns != NS_STREAMS or tag!='stream': + raise ValueError('Incorrect stream start: (%s,%s). Terminating.' + % (tag, ns)) - def ProcessNonBlocking(self, data): - ''' - Check incoming stream for data waiting. + def ProcessNonBlocking(self, data): + ''' + Check incoming stream for data waiting. - :param data: data received from transports/IO sockets - :return: - 1) length of processed data if some data were processed; - 2) '0' string if no data were processed but link is alive; - 3) 0 (zero) if underlying connection is closed. - ''' - # FIXME: - # When an error occurs we disconnect the transport directly. Client's - # disconnect method will never be called. - # Is this intended? - # also look at transports start_disconnect() - for handler in self._cycleHandlers: - handler(self) - if len(self._pendingExceptions) > 0: - _pendingException = self._pendingExceptions.pop() - raise _pendingException[0], _pendingException[1], _pendingException[2] - try: - self.Stream.Parse(data) - # end stream:stream tag received - if self.Stream and self.Stream.has_received_endtag(): - self._owner.disconnect(self.Stream.streamError) - return 0 - except ExpatError: - log.error('Invalid XML received from server. Forcing disconnect.') - self._owner.Connection.disconnect() - return 0 - except ValueError, e: - log.debug('ValueError: %s' % str(e)) - self._owner.Connection.pollend() - return 0 - if len(self._pendingExceptions) > 0: - _pendingException = self._pendingExceptions.pop() - raise _pendingException[0], _pendingException[1], _pendingException[2] - if len(data) == 0: - return '0' - return len(data) + :param data: data received from transports/IO sockets + :return: + 1) length of processed data if some data were processed; + 2) '0' string if no data were processed but link is alive; + 3) 0 (zero) if underlying connection is closed. + ''' + # FIXME: + # When an error occurs we disconnect the transport directly. Client's + # disconnect method will never be called. + # Is this intended? + # also look at transports start_disconnect() + for handler in self._cycleHandlers: + handler(self) + if len(self._pendingExceptions) > 0: + _pendingException = self._pendingExceptions.pop() + raise _pendingException[0], _pendingException[1], _pendingException[2] + try: + self.Stream.Parse(data) + # end stream:stream tag received + if self.Stream and self.Stream.has_received_endtag(): + self._owner.disconnect(self.Stream.streamError) + return 0 + except ExpatError: + log.error('Invalid XML received from server. Forcing disconnect.') + self._owner.Connection.disconnect() + return 0 + except ValueError, e: + log.debug('ValueError: %s' % str(e)) + self._owner.Connection.pollend() + return 0 + if len(self._pendingExceptions) > 0: + _pendingException = self._pendingExceptions.pop() + raise _pendingException[0], _pendingException[1], _pendingException[2] + if len(data) == 0: + return '0' + return len(data) - def RegisterNamespace(self, xmlns, order='info'): - ''' - Creates internal structures for newly registered namespace. - You can register handlers for this namespace afterwards. By default - one namespace is already registered - (jabber:client or jabber:component:accept depending on context. - ''' - log.debug('Registering namespace "%s"' % xmlns) - self.handlers[xmlns] = {} - self.RegisterProtocol('unknown', Protocol, xmlns=xmlns) - self.RegisterProtocol('default', Protocol, xmlns=xmlns) + def RegisterNamespace(self, xmlns, order='info'): + ''' + Creates internal structures for newly registered namespace. + You can register handlers for this namespace afterwards. By default + one namespace is already registered + (jabber:client or jabber:component:accept depending on context. + ''' + log.debug('Registering namespace "%s"' % xmlns) + self.handlers[xmlns] = {} + self.RegisterProtocol('unknown', Protocol, xmlns=xmlns) + self.RegisterProtocol('default', Protocol, xmlns=xmlns) - def RegisterProtocol(self, tag_name, Proto, xmlns=None, order='info'): - ''' - Used to declare some top-level stanza name to dispatcher. - Needed to start registering handlers for such stanzas. + def RegisterProtocol(self, tag_name, Proto, xmlns=None, order='info'): + ''' + Used to declare some top-level stanza name to dispatcher. + Needed to start registering handlers for such stanzas. - Iq, message and presence protocols are registered by default. - ''' - if not xmlns: - xmlns=self._owner.defaultNamespace - log.debug('Registering protocol "%s" as %s(%s)' %(tag_name, Proto, xmlns)) - self.handlers[xmlns][tag_name] = {type:Proto, 'default':[]} + Iq, message and presence protocols are registered by default. + ''' + if not xmlns: + xmlns=self._owner.defaultNamespace + log.debug('Registering protocol "%s" as %s(%s)' %(tag_name, Proto, xmlns)) + self.handlers[xmlns][tag_name] = {type:Proto, 'default':[]} - def RegisterNamespaceHandler(self, xmlns, handler, typ='', ns='', - makefirst=0, system=0): - ''' - Register handler for processing all stanzas for specified namespace. - ''' - self.RegisterHandler('default', handler, typ, ns, xmlns, makefirst, - system) + def RegisterNamespaceHandler(self, xmlns, handler, typ='', ns='', + makefirst=0, system=0): + ''' + Register handler for processing all stanzas for specified namespace. + ''' + self.RegisterHandler('default', handler, typ, ns, xmlns, makefirst, + system) - def RegisterHandler(self, name, handler, typ='', ns='', xmlns=None, - makefirst=False, system=False): - ''' - Register user callback as stanzas handler of declared type. + def RegisterHandler(self, name, handler, typ='', ns='', xmlns=None, + makefirst=False, system=False): + ''' + Register user callback as stanzas handler of declared type. - Callback arguments: - dispatcher instance (for replying), incoming return of previous handlers. - The callback must raise xmpp.NodeProcessed just before return if it wants - to prevent other callbacks to be called with the same stanza as argument - _and_, more importantly library from returning stanza to sender with error set. + Callback arguments: + dispatcher instance (for replying), incoming return of previous handlers. + The callback must raise xmpp.NodeProcessed just before return if it wants + to prevent other callbacks to be called with the same stanza as argument + _and_, more importantly library from returning stanza to sender with error set. - :param name: name of stanza. F.e. "iq". - :param handler: user callback. - :param typ: value of stanza's "type" attribute. If not specified any - value will match - :param ns: namespace of child that stanza must contain. - :param makefirst: insert handler in the beginning of handlers list instead - of adding it to the end. Note that more common handlers i.e. w/o "typ" - and " will be called first nevertheless. - :param system: call handler even if NodeProcessed Exception were raised - already. - ''' - if not xmlns: - xmlns=self._owner.defaultNamespace - log.debug('Registering handler %s for "%s" type->%s ns->%s(%s)' % - (handler, name, typ, ns, xmlns)) - if not typ and not ns: - typ='default' - if xmlns not in self.handlers: - self.RegisterNamespace(xmlns,'warn') - if name not in self.handlers[xmlns]: - self.RegisterProtocol(name,Protocol,xmlns,'warn') - if typ+ns not in self.handlers[xmlns][name]: - self.handlers[xmlns][name][typ+ns]=[] - if makefirst: - self.handlers[xmlns][name][typ+ns].insert(0,{'func':handler, - 'system':system}) - else: - self.handlers[xmlns][name][typ+ns].append({'func':handler, - 'system':system}) + :param name: name of stanza. F.e. "iq". + :param handler: user callback. + :param typ: value of stanza's "type" attribute. If not specified any + value will match + :param ns: namespace of child that stanza must contain. + :param makefirst: insert handler in the beginning of handlers list instead + of adding it to the end. Note that more common handlers i.e. w/o "typ" + and " will be called first nevertheless. + :param system: call handler even if NodeProcessed Exception were raised + already. + ''' + if not xmlns: + xmlns=self._owner.defaultNamespace + log.debug('Registering handler %s for "%s" type->%s ns->%s(%s)' % + (handler, name, typ, ns, xmlns)) + if not typ and not ns: + typ='default' + if xmlns not in self.handlers: + self.RegisterNamespace(xmlns, 'warn') + if name not in self.handlers[xmlns]: + self.RegisterProtocol(name, Protocol, xmlns, 'warn') + if typ+ns not in self.handlers[xmlns][name]: + self.handlers[xmlns][name][typ+ns]=[] + if makefirst: + self.handlers[xmlns][name][typ+ns].insert(0, {'func':handler, + 'system':system}) + else: + self.handlers[xmlns][name][typ+ns].append({'func':handler, + 'system':system}) - def RegisterHandlerOnce(self, name, handler, typ='', ns='', xmlns=None, - makefirst=0, system=0): - ''' Unregister handler after first call (not implemented yet). ''' - # FIXME Drop or implement - if not xmlns: - xmlns = self._owner.defaultNamespace - self.RegisterHandler(name, handler, typ, ns, xmlns, makefirst, system) + def RegisterHandlerOnce(self, name, handler, typ='', ns='', xmlns=None, + makefirst=0, system=0): + ''' Unregister handler after first call (not implemented yet). ''' + # FIXME Drop or implement + if not xmlns: + xmlns = self._owner.defaultNamespace + self.RegisterHandler(name, handler, typ, ns, xmlns, makefirst, system) - def UnregisterHandler(self, name, handler, typ='', ns='', xmlns=None): - ''' - Unregister handler. "typ" and "ns" must be specified exactly the same as - with registering. - ''' - if not xmlns: - xmlns = self._owner.defaultNamespace - if not typ and not ns: - typ='default' - if xmlns not in self.handlers: - return - if name not in self.handlers[xmlns]: - return - if typ+ns not in self.handlers[xmlns][name]: - return - for pack in self.handlers[xmlns][name][typ+ns]: - if pack['func'] == handler: - try: - self.handlers[xmlns][name][typ+ns].remove(pack) - except ValueError: - pass + def UnregisterHandler(self, name, handler, typ='', ns='', xmlns=None): + ''' + Unregister handler. "typ" and "ns" must be specified exactly the same as + with registering. + ''' + if not xmlns: + xmlns = self._owner.defaultNamespace + if not typ and not ns: + typ='default' + if xmlns not in self.handlers: + return + if name not in self.handlers[xmlns]: + return + if typ+ns not in self.handlers[xmlns][name]: + return + for pack in self.handlers[xmlns][name][typ+ns]: + if pack['func'] == handler: + try: + self.handlers[xmlns][name][typ+ns].remove(pack) + except ValueError: + pass - def RegisterDefaultHandler(self, handler): - ''' - Specify the handler that will be used if no NodeProcessed exception were - raised. This is returnStanzaHandler by default. - ''' - self._defaultHandler = handler + def RegisterDefaultHandler(self, handler): + ''' + Specify the handler that will be used if no NodeProcessed exception were + raised. This is returnStanzaHandler by default. + ''' + self._defaultHandler = handler - def RegisterEventHandler(self, handler): - ''' - Register handler that will process events. F.e. - "FILERECEIVED" event. See common/connection: _event_dispatcher() - ''' - self._eventHandler = handler + def RegisterEventHandler(self, handler): + ''' + Register handler that will process events. F.e. + "FILERECEIVED" event. See common/connection: _event_dispatcher() + ''' + self._eventHandler = handler - def returnStanzaHandler(self, conn, stanza): - ''' - Return stanza back to the sender with error set - ''' - if stanza.getType() in ('get','set'): - conn._owner.send(Error(stanza, ERR_FEATURE_NOT_IMPLEMENTED)) + def returnStanzaHandler(self, conn, stanza): + ''' + Return stanza back to the sender with error set + ''' + if stanza.getType() in ('get', 'set'): + conn._owner.send(Error(stanza, ERR_FEATURE_NOT_IMPLEMENTED)) - def RegisterCycleHandler(self, handler): - ''' - Register handler that will be called on every Dispatcher.Process() call. - ''' - if handler not in self._cycleHandlers: - self._cycleHandlers.append(handler) + def RegisterCycleHandler(self, handler): + ''' + Register handler that will be called on every Dispatcher.Process() call. + ''' + if handler not in self._cycleHandlers: + self._cycleHandlers.append(handler) - def UnregisterCycleHandler(self, handler): - ''' - Unregister handler that will is called on every Dispatcher.Process() call - ''' - if handler in self._cycleHandlers: - self._cycleHandlers.remove(handler) + def UnregisterCycleHandler(self, handler): + ''' + Unregister handler that will is called on every Dispatcher.Process() call + ''' + if handler in self._cycleHandlers: + self._cycleHandlers.remove(handler) - def Event(self, realm, event, data): - ''' - Raise some event. + def Event(self, realm, event, data): + ''' + Raise some event. - :param realm: scope of event. Usually a namespace. - :param event: the event itself. F.e. "SUCCESSFUL SEND". - :param data: data that comes along with event. Depends on event. - ''' - if self._eventHandler: - self._eventHandler(realm, event, data) - else: - log.warning('Received unhandled event: %s' % event) + :param realm: scope of event. Usually a namespace. + :param event: the event itself. F.e. "SUCCESSFUL SEND". + :param data: data that comes along with event. Depends on event. + ''' + if self._eventHandler: + self._eventHandler(realm, event, data) + else: + log.warning('Received unhandled event: %s' % event) - def dispatch(self, stanza, session=None, direct=0): - ''' - Main procedure that performs XMPP stanza recognition and calling - apppropriate handlers for it. Called by simplexml. - ''' - # FIXME: Where do we set session and direct. Why? What are those intended - # to do? + def dispatch(self, stanza, session=None, direct=0): + ''' + Main procedure that performs XMPP stanza recognition and calling + apppropriate handlers for it. Called by simplexml. + ''' + # FIXME: Where do we set session and direct. Why? What are those intended + # to do? - #log.info('dispatch called: stanza = %s, session = %s, direct= %s' - # % (stanza, session, direct)) - if not session: - session = self - session.Stream._mini_dom = None - name = stanza.getName() + #log.info('dispatch called: stanza = %s, session = %s, direct= %s' + # % (stanza, session, direct)) + if not session: + session = self + session.Stream._mini_dom = None + name = stanza.getName() - if name == 'features': - self._owner.got_features = True - session.Stream.features = stanza + if name == 'features': + self._owner.got_features = True + session.Stream.features = stanza - xmlns = stanza.getNamespace() + xmlns = stanza.getNamespace() - # log.info('in dispatch, getting ns for %s, and the ns is %s' - # % (stanza, xmlns)) - if xmlns not in self.handlers: - log.warn("Unknown namespace: " + xmlns) - xmlns = 'unknown' - # features stanza has been handled before - if name not in self.handlers[xmlns]: - if name != 'features': - log.warn("Unknown stanza: " + name) - else: - log.debug("Got %s/%s stanza" % (xmlns, name)) - name='unknown' - else: - log.debug("Got %s/%s stanza" % (xmlns, name)) + # log.info('in dispatch, getting ns for %s, and the ns is %s' + # % (stanza, xmlns)) + if xmlns not in self.handlers: + log.warn("Unknown namespace: " + xmlns) + xmlns = 'unknown' + # features stanza has been handled before + if name not in self.handlers[xmlns]: + if name != 'features': + log.warn("Unknown stanza: " + name) + else: + log.debug("Got %s/%s stanza" % (xmlns, name)) + name='unknown' + else: + log.debug("Got %s/%s stanza" % (xmlns, name)) - if stanza.__class__.__name__ == 'Node': - # FIXME: this cannot work - stanza=self.handlers[xmlns][name][type](node=stanza) + if stanza.__class__.__name__ == 'Node': + # FIXME: this cannot work + stanza=self.handlers[xmlns][name][type](node=stanza) - typ = stanza.getType() - if not typ: - typ = '' - stanza.props = stanza.getProperties() - ID = stanza.getID() + typ = stanza.getType() + if not typ: + typ = '' + stanza.props = stanza.getProperties() + ID = stanza.getID() - list_ = ['default'] # we will use all handlers: - if typ in self.handlers[xmlns][name]: - list_.append(typ) # from very common... - for prop in stanza.props: - if prop in self.handlers[xmlns][name]: - list_.append(prop) - if typ and typ+prop in self.handlers[xmlns][name]: - list_.append(typ+prop) # ...to very particular + list_ = ['default'] # we will use all handlers: + if typ in self.handlers[xmlns][name]: + list_.append(typ) # from very common... + for prop in stanza.props: + if prop in self.handlers[xmlns][name]: + list_.append(prop) + if typ and typ+prop in self.handlers[xmlns][name]: + list_.append(typ+prop) # ...to very particular - chain = self.handlers[xmlns]['default']['default'] - for key in list_: - if key: - chain = chain + self.handlers[xmlns][name][key] + chain = self.handlers[xmlns]['default']['default'] + for key in list_: + if key: + chain = chain + self.handlers[xmlns][name][key] - if ID in session._expected: - user = 0 - if isinstance(session._expected[ID], tuple): - cb, args = session._expected[ID] - log.debug("Expected stanza arrived. Callback %s(%s) found!" % - (cb, args)) - try: - cb(session,stanza,**args) - except Exception, typ: - if typ.__class__.__name__ != 'NodeProcessed': - raise - else: - log.debug("Expected stanza arrived!") - session._expected[ID] = stanza - else: - user = 1 - for handler in chain: - if user or handler['system']: - try: - handler['func'](session, stanza) - except Exception, typ: - if typ.__class__.__name__ != 'NodeProcessed': - self._pendingExceptions.insert(0, sys.exc_info()) - return - user=0 - if user and self._defaultHandler: - self._defaultHandler(session, stanza) + if ID in session._expected: + user = 0 + if isinstance(session._expected[ID], tuple): + cb, args = session._expected[ID] + log.debug("Expected stanza arrived. Callback %s(%s) found!" % + (cb, args)) + try: + cb(session,stanza,**args) + except Exception, typ: + if typ.__class__.__name__ != 'NodeProcessed': + raise + else: + log.debug("Expected stanza arrived!") + session._expected[ID] = stanza + else: + user = 1 + for handler in chain: + if user or handler['system']: + try: + handler['func'](session, stanza) + except Exception, typ: + if typ.__class__.__name__ != 'NodeProcessed': + self._pendingExceptions.insert(0, sys.exc_info()) + return + user=0 + if user and self._defaultHandler: + self._defaultHandler(session, stanza) - def _WaitForData(self, data): - ''' - Internal wrapper around ProcessNonBlocking. Will check for - ''' - if data is None: - return - res = self.ProcessNonBlocking(data) - # 0 result indicates that we have closed the connection, e.g. - # we have released dispatcher, so self._owner has no methods - if not res: - return - if 'remove_timeout' in self._owner.__dict__: - # When we receive data after we started disconnecting, Transport may - # already be plugged out - self._owner.remove_timeout() - for (_id, _iq) in self._expected.items(): - if _iq is None: - # If the expected Stanza would have arrived, ProcessNonBlocking - # would have placed the reply stanza in there - continue - if _id in self.on_responses: - if len(self._expected) == 1: - self._owner.onreceive(None) - resp, args = self.on_responses[_id] - del self.on_responses[_id] - if args is None: - resp(_iq) - else: - resp(self._owner, _iq, **args) - del self._expected[_id] + def _WaitForData(self, data): + ''' + Internal wrapper around ProcessNonBlocking. Will check for + ''' + if data is None: + return + res = self.ProcessNonBlocking(data) + # 0 result indicates that we have closed the connection, e.g. + # we have released dispatcher, so self._owner has no methods + if not res: + return + if 'remove_timeout' in self._owner.__dict__: + # When we receive data after we started disconnecting, Transport may + # already be plugged out + self._owner.remove_timeout() + for (_id, _iq) in self._expected.items(): + if _iq is None: + # If the expected Stanza would have arrived, ProcessNonBlocking + # would have placed the reply stanza in there + continue + if _id in self.on_responses: + if len(self._expected) == 1: + self._owner.onreceive(None) + resp, args = self.on_responses[_id] + del self.on_responses[_id] + if args is None: + resp(_iq) + else: + resp(self._owner, _iq, **args) + del self._expected[_id] - def SendAndWaitForResponse(self, stanza, timeout=None, func=None, args=None): - ''' - Send stanza and wait for recipient's response to it. Will call transports - on_timeout callback if response is not retrieved in time. + def SendAndWaitForResponse(self, stanza, timeout=None, func=None, args=None): + ''' + Send stanza and wait for recipient's response to it. Will call transports + on_timeout callback if response is not retrieved in time. - Be aware: Only timeout of latest call of SendAndWait is active. - ''' - if timeout is None: - timeout = DEFAULT_TIMEOUT_SECONDS - _waitid = self.send(stanza) - if func: - self.on_responses[_waitid] = (func, args) - if timeout: - self._owner.set_timeout(timeout) - self._owner.onreceive(self._WaitForData) - self._expected[_waitid] = None - return _waitid + Be aware: Only timeout of latest call of SendAndWait is active. + ''' + if timeout is None: + timeout = DEFAULT_TIMEOUT_SECONDS + _waitid = self.send(stanza) + if func: + self.on_responses[_waitid] = (func, args) + if timeout: + self._owner.set_timeout(timeout) + self._owner.onreceive(self._WaitForData) + self._expected[_waitid] = None + return _waitid - def SendAndCallForResponse(self, stanza, func=None, args=None): - ''' Put stanza on the wire and call back when recipient replies. - Additional callback arguments can be specified in args. ''' - self.SendAndWaitForResponse(stanza, 0, func, args) + def SendAndCallForResponse(self, stanza, func=None, args=None): + ''' Put stanza on the wire and call back when recipient replies. + Additional callback arguments can be specified in args. ''' + self.SendAndWaitForResponse(stanza, 0, func, args) - def send(self, stanza, now=False): - ''' - Wraps transports send method when plugged into NonBlockingClient. - Makes sure stanzas get ID and from tag. - ''' - ID = None - if type(stanza) not in [type(''), type(u'')]: - if isinstance(stanza, Protocol): - ID = stanza.getID() - if ID is None: - stanza.setID(self.getAnID()) - ID = stanza.getID() - if self._owner._registered_name and not stanza.getAttr('from'): - stanza.setAttr('from', self._owner._registered_name) - self._owner.Connection.send(stanza, now) - return ID + def send(self, stanza, now=False): + ''' + Wraps transports send method when plugged into NonBlockingClient. + Makes sure stanzas get ID and from tag. + ''' + ID = None + if type(stanza) not in [type(''), type(u'')]: + if isinstance(stanza, Protocol): + ID = stanza.getID() + if ID is None: + stanza.setID(self.getAnID()) + ID = stanza.getID() + if self._owner._registered_name and not stanza.getAttr('from'): + stanza.setAttr('from', self._owner._registered_name) + self._owner.Connection.send(stanza, now) + return ID class BOSHDispatcher(XMPPDispatcher): - def PlugIn(self, owner, after_SASL=False, old_features=None): - self.old_features = old_features - self.after_SASL = after_SASL - XMPPDispatcher.PlugIn(self, owner) + def PlugIn(self, owner, after_SASL=False, old_features=None): + self.old_features = old_features + self.after_SASL = after_SASL + XMPPDispatcher.PlugIn(self, owner) - def StreamInit(self): - ''' Send an initial stream header. ''' - self.Stream = simplexml.NodeBuilder() - self.Stream.dispatch = self.dispatch - self.Stream._dispatch_depth = 2 - self.Stream.stream_header_received = self._check_stream_start - self.Stream.features = self.old_features + def StreamInit(self): + ''' Send an initial stream header. ''' + self.Stream = simplexml.NodeBuilder() + self.Stream.dispatch = self.dispatch + self.Stream._dispatch_depth = 2 + self.Stream.stream_header_received = self._check_stream_start + self.Stream.features = self.old_features - self._metastream = Node('stream:stream') - self._metastream.setNamespace(self._owner.Namespace) - self._metastream.setAttr('version', '1.0') - self._metastream.setAttr('xmlns:stream', NS_STREAMS) - self._metastream.setAttr('to', self._owner.Server) - if locale.getdefaultlocale()[0]: - self._metastream.setAttr('xml:lang', - locale.getdefaultlocale()[0].split('_')[0]) + self._metastream = Node('stream:stream') + self._metastream.setNamespace(self._owner.Namespace) + self._metastream.setAttr('version', '1.0') + self._metastream.setAttr('xmlns:stream', NS_STREAMS) + self._metastream.setAttr('to', self._owner.Server) + if locale.getdefaultlocale()[0]: + self._metastream.setAttr('xml:lang', + locale.getdefaultlocale()[0].split('_')[0]) - self.restart = True - self._owner.Connection.send_init(after_SASL=self.after_SASL) + self.restart = True + self._owner.Connection.send_init(after_SASL=self.after_SASL) - def StreamTerminate(self): - ''' Send a stream terminator. ''' - self._owner.Connection.send_terminator() + def StreamTerminate(self): + ''' Send a stream terminator. ''' + self._owner.Connection.send_terminator() - def ProcessNonBlocking(self, data=None): - if self.restart: - fromstream = self._metastream - fromstream.setAttr('from', fromstream.getAttr('to')) - fromstream.delAttr('to') - data = '%s%s>%s' % (XML_DECLARATION,str(fromstream)[:-2] ,data) - self.restart = False - return XMPPDispatcher.ProcessNonBlocking(self, data) + def ProcessNonBlocking(self, data=None): + if self.restart: + fromstream = self._metastream + fromstream.setAttr('from', fromstream.getAttr('to')) + fromstream.delAttr('to') + data = '%s%s>%s' % (XML_DECLARATION, str(fromstream)[:-2], data) + self.restart = False + return XMPPDispatcher.ProcessNonBlocking(self, data) - def dispatch(self, stanza, session=None, direct=0): - if stanza.getName() == 'body' and stanza.getNamespace() == NS_HTTP_BIND: + def dispatch(self, stanza, session=None, direct=0): + if stanza.getName() == 'body' and stanza.getNamespace() == NS_HTTP_BIND: - stanza_attrs = stanza.getAttrs() - if 'authid' in stanza_attrs: - # should be only in init response - # auth module expects id of stream in document attributes - self.Stream._document_attrs['id'] = stanza_attrs['authid'] - self._owner.Connection.handle_body_attrs(stanza_attrs) + stanza_attrs = stanza.getAttrs() + if 'authid' in stanza_attrs: + # should be only in init response + # auth module expects id of stream in document attributes + self.Stream._document_attrs['id'] = stanza_attrs['authid'] + self._owner.Connection.handle_body_attrs(stanza_attrs) - children = stanza.getChildren() - if children: - for child in children: - # if child doesn't have any ns specified, simplexml (or expat) - # thinks it's of parent's (BOSH body) namespace, so we have to - # rewrite it to jabber:client - if child.getNamespace() == NS_HTTP_BIND: - child.setNamespace(self._owner.defaultNamespace) - XMPPDispatcher.dispatch(self, child, session, direct) - else: - XMPPDispatcher.dispatch(self, stanza, session, direct) - -# vim: se ts=3: + children = stanza.getChildren() + if children: + for child in children: + # if child doesn't have any ns specified, simplexml (or expat) + # thinks it's of parent's (BOSH body) namespace, so we have to + # rewrite it to jabber:client + if child.getNamespace() == NS_HTTP_BIND: + child.setNamespace(self._owner.defaultNamespace) + XMPPDispatcher.dispatch(self, child, session, direct) + else: + XMPPDispatcher.dispatch(self, stanza, session, direct) diff --git a/src/common/xmpp/features_nb.py b/src/common/xmpp/features_nb.py index b713e38b2..d0a802c4d 100644 --- a/src/common/xmpp/features_nb.py +++ b/src/common/xmpp/features_nb.py @@ -23,13 +23,13 @@ Different stuff that wasn't worth separating it into modules from protocol import NS_REGISTER, NS_PRIVACY, NS_DATA, Iq, isResultNode, Node def _on_default_response(disp, iq, cb): - def _on_response(resp): - if isResultNode(resp): - if cb: - cb(True) - elif cb: - cb(False) - disp.SendAndCallForResponse(iq, _on_response) + def _on_response(resp): + if isResultNode(resp): + if cb: + cb(True) + elif cb: + cb(False) + disp.SendAndCallForResponse(iq, _on_response) ############################################################################### ### Registration @@ -38,75 +38,75 @@ def _on_default_response(disp, iq, cb): REGISTER_DATA_RECEIVED = 'REGISTER DATA RECEIVED' def getRegInfo(disp, host, info={}, sync=True): - ''' - Gets registration form from remote host. Info dict can be prefilled - :param disp: plugged dispatcher instance - :param info: dict, like {'username':'joey'}. + ''' + Gets registration form from remote host. Info dict can be prefilled + :param disp: plugged dispatcher instance + :param info: dict, like {'username':'joey'}. - See JEP-0077 for details. - ''' - iq=Iq('get',NS_REGISTER,to=host) - for i in info.keys(): - iq.setTagData(i,info[i]) - if sync: - disp.SendAndCallForResponse(iq, lambda resp: - _ReceivedRegInfo(disp.Dispatcher, resp, host)) - else: - disp.SendAndCallForResponse(iq, _ReceivedRegInfo, {'agent': host }) + See JEP-0077 for details. + ''' + iq=Iq('get', NS_REGISTER, to=host) + for i in info.keys(): + iq.setTagData(i, info[i]) + if sync: + disp.SendAndCallForResponse(iq, lambda resp: + _ReceivedRegInfo(disp.Dispatcher, resp, host)) + else: + disp.SendAndCallForResponse(iq, _ReceivedRegInfo, {'agent': host }) def _ReceivedRegInfo(con, resp, agent): - Iq('get',NS_REGISTER,to=agent) - if not isResultNode(resp): - error_msg = resp.getErrorMsg() - con.Event(NS_REGISTER,REGISTER_DATA_RECEIVED,(agent,None,False,error_msg)) - return - tag=resp.getTag('query',namespace=NS_REGISTER) - if not tag: - error_msg = resp.getErrorMsg() - con.Event(NS_REGISTER,REGISTER_DATA_RECEIVED,(agent,None,False,error_msg)) - return - df=tag.getTag('x',namespace=NS_DATA) - if df: - con.Event(NS_REGISTER,REGISTER_DATA_RECEIVED,(agent,df,True,'')) - return - df={} - for i in resp.getQueryPayload(): - if not isinstance(i, Node): - continue - df[i.getName()] = i.getData() - con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent,df,False,'')) + Iq('get', NS_REGISTER, to=agent) + if not isResultNode(resp): + error_msg = resp.getErrorMsg() + con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent, None, False, error_msg)) + return + tag=resp.getTag('query', namespace=NS_REGISTER) + if not tag: + error_msg = resp.getErrorMsg() + con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent, None, False, error_msg)) + return + df=tag.getTag('x', namespace=NS_DATA) + if df: + con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent, df, True, '')) + return + df={} + for i in resp.getQueryPayload(): + if not isinstance(i, Node): + continue + df[i.getName()] = i.getData() + con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent, df, False, '')) def register(disp, host, info, cb): - ''' - Perform registration on remote server with provided info. + ''' + Perform registration on remote server with provided info. - If registration fails you can get additional info from the dispatcher's - owner attributes lastErrNode, lastErr and lastErrCode. - ''' - iq=Iq('set', NS_REGISTER, to=host) - if not isinstance(info, dict): - info=info.asDict() - for i in info.keys(): - iq.setTag('query').setTagData(i,info[i]) - disp.SendAndCallForResponse(iq, cb) + If registration fails you can get additional info from the dispatcher's + owner attributes lastErrNode, lastErr and lastErrCode. + ''' + iq=Iq('set', NS_REGISTER, to=host) + if not isinstance(info, dict): + info=info.asDict() + for i in info.keys(): + iq.setTag('query').setTagData(i, info[i]) + disp.SendAndCallForResponse(iq, cb) def unregister(disp, host, cb): - ''' - Unregisters with host (permanently removes account). Returns true on success - ''' - iq = Iq('set', NS_REGISTER, to=host, payload=[Node('remove')]) - _on_default_response(disp, iq, cb) + ''' + Unregisters with host (permanently removes account). Returns true on success + ''' + iq = Iq('set', NS_REGISTER, to=host, payload=[Node('remove')]) + _on_default_response(disp, iq, cb) def changePasswordTo(disp, newpassword, host=None, cb = None): - ''' - Changes password on specified or current (if not specified) server. - Returns true on success. - ''' - if not host: - host = disp._owner.Server - iq = Iq('set',NS_REGISTER,to=host, payload=[Node('username', - payload=[disp._owner.Server]),Node('password',payload=[newpassword])]) - _on_default_response(disp, iq, cb) + ''' + Changes password on specified or current (if not specified) server. + Returns true on success. + ''' + if not host: + host = disp._owner.Server + iq = Iq('set', NS_REGISTER, to=host, payload=[Node('username', + payload=[disp._owner.Server]), Node('password', payload=[newpassword])]) + _on_default_response(disp, iq, cb) ############################################################################### ### Privacy List @@ -123,92 +123,90 @@ PRIVACY_LIST_RECEIVED = 'PRIVACY LIST RECEIVED' PRIVACY_LISTS_ACTIVE_DEFAULT = 'PRIVACY LISTS ACTIVE DEFAULT' def getPrivacyLists(disp): - ''' - Requests privacy lists from connected server. - Returns dictionary of existing lists on success. - ''' - iq = Iq('get', NS_PRIVACY) - def _on_response(resp): - dict_ = {'lists': []} - if not isResultNode(resp): - disp.Event(NS_PRIVACY, PRIVACY_LISTS_RECEIVED, (False)) - return - for list_ in resp.getQueryPayload(): - if list_.getName()=='list': - dict_['lists'].append(list_.getAttr('name')) - else: - dict_[list_.getName()]=list_.getAttr('name') - disp.Event(NS_PRIVACY, PRIVACY_LISTS_RECEIVED, (dict_)) - disp.SendAndCallForResponse(iq, _on_response) + ''' + Requests privacy lists from connected server. + Returns dictionary of existing lists on success. + ''' + iq = Iq('get', NS_PRIVACY) + def _on_response(resp): + dict_ = {'lists': []} + if not isResultNode(resp): + disp.Event(NS_PRIVACY, PRIVACY_LISTS_RECEIVED, (False)) + return + for list_ in resp.getQueryPayload(): + if list_.getName()=='list': + dict_['lists'].append(list_.getAttr('name')) + else: + dict_[list_.getName()]=list_.getAttr('name') + disp.Event(NS_PRIVACY, PRIVACY_LISTS_RECEIVED, (dict_)) + disp.SendAndCallForResponse(iq, _on_response) def getActiveAndDefaultPrivacyLists(disp): - iq = Iq('get', NS_PRIVACY) - def _on_response(resp): - dict_ = {'active': '', 'default': ''} - if not isResultNode(resp): - disp.Event(NS_PRIVACY, PRIVACY_LISTS_ACTIVE_DEFAULT, (False)) - return - for list_ in resp.getQueryPayload(): - if list_.getName() == 'active': - dict_['active'] = list_.getAttr('name') - elif list_.getName() == 'default': - dict_['default'] = list_.getAttr('name') - disp.Event(NS_PRIVACY, PRIVACY_LISTS_ACTIVE_DEFAULT, (dict_)) - disp.SendAndCallForResponse(iq, _on_response) + iq = Iq('get', NS_PRIVACY) + def _on_response(resp): + dict_ = {'active': '', 'default': ''} + if not isResultNode(resp): + disp.Event(NS_PRIVACY, PRIVACY_LISTS_ACTIVE_DEFAULT, (False)) + return + for list_ in resp.getQueryPayload(): + if list_.getName() == 'active': + dict_['active'] = list_.getAttr('name') + elif list_.getName() == 'default': + dict_['default'] = list_.getAttr('name') + disp.Event(NS_PRIVACY, PRIVACY_LISTS_ACTIVE_DEFAULT, (dict_)) + disp.SendAndCallForResponse(iq, _on_response) def getPrivacyList(disp, listname): - ''' - Requests specific privacy list listname. Returns list of XML nodes (rules) - taken from the server responce. - ''' - def _on_response(resp): - if not isResultNode(resp): - disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (False)) - return - disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (resp)) - iq = Iq('get', NS_PRIVACY, payload=[Node('list', {'name': listname})]) - disp.SendAndCallForResponse(iq, _on_response) + ''' + Requests specific privacy list listname. Returns list of XML nodes (rules) + taken from the server responce. + ''' + def _on_response(resp): + if not isResultNode(resp): + disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (False)) + return + disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (resp)) + iq = Iq('get', NS_PRIVACY, payload=[Node('list', {'name': listname})]) + disp.SendAndCallForResponse(iq, _on_response) def setActivePrivacyList(disp, listname=None, typ='active', cb=None): - ''' - Switches privacy list 'listname' to specified type. - By default the type is 'active'. Returns true on success. - ''' - if listname: - attrs={'name':listname} - else: - attrs={} - iq = Iq('set',NS_PRIVACY,payload=[Node(typ,attrs)]) - _on_default_response(disp, iq, cb) + ''' + Switches privacy list 'listname' to specified type. + By default the type is 'active'. Returns true on success. + ''' + if listname: + attrs={'name':listname} + else: + attrs={} + iq = Iq('set', NS_PRIVACY, payload=[Node(typ, attrs)]) + _on_default_response(disp, iq, cb) def setDefaultPrivacyList(disp, listname=None): - ''' Sets the default privacy list as 'listname'. Returns true on success. ''' - return setActivePrivacyList(disp, listname,'default') + ''' Sets the default privacy list as 'listname'. Returns true on success. ''' + return setActivePrivacyList(disp, listname, 'default') def setPrivacyList(disp, listname, tags): - ''' - Set the ruleset. + ''' + Set the ruleset. - 'list' should be the simpleXML node formatted according to RFC 3921 (XMPP-IM) I.e. Node('list',{'name':listname},payload=[...]). Returns true on success. - ''' - iq = Iq('set', NS_PRIVACY, xmlns = '') - list_query = iq.getTag('query').setTag('list', {'name': listname}) - for item in tags: - if 'type' in item and 'value' in item: - item_tag = list_query.setTag('item', {'action': item['action'], - 'order': item['order'], 'type': item['type'], - 'value': item['value']}) - else: - item_tag = list_query.setTag('item', {'action': item['action'], - 'order': item['order']}) - if 'child' in item: - for child_tag in item['child']: - item_tag.setTag(child_tag) - _on_default_response(disp, iq, None) + 'list' should be the simpleXML node formatted according to RFC 3921 (XMPP-IM) I.e. Node('list',{'name':listname},payload=[...]). Returns true on success. + ''' + iq = Iq('set', NS_PRIVACY, xmlns = '') + list_query = iq.getTag('query').setTag('list', {'name': listname}) + for item in tags: + if 'type' in item and 'value' in item: + item_tag = list_query.setTag('item', {'action': item['action'], + 'order': item['order'], 'type': item['type'], + 'value': item['value']}) + else: + item_tag = list_query.setTag('item', {'action': item['action'], + 'order': item['order']}) + if 'child' in item: + for child_tag in item['child']: + item_tag.setTag(child_tag) + _on_default_response(disp, iq, None) def delPrivacyList(disp, listname, cb=None): - ''' Deletes privacy list 'listname'. Returns true on success. ''' - iq = Iq('set',NS_PRIVACY,payload=[Node('list',{'name':listname})]) - _on_default_response(disp, iq, cb) - -# vim: se ts=3: + ''' Deletes privacy list 'listname'. Returns true on success. ''' + iq = Iq('set', NS_PRIVACY, payload=[Node('list', {'name':listname})]) + _on_default_response(disp, iq, cb) diff --git a/src/common/xmpp/idlequeue.py b/src/common/xmpp/idlequeue.py index 63cb26bf6..d521f8ecb 100644 --- a/src/common/xmpp/idlequeue.py +++ b/src/common/xmpp/idlequeue.py @@ -23,494 +23,492 @@ log = logging.getLogger('gajim.c.x.idlequeue') # needed for get_idleqeue try: - import gobject - HAVE_GOBJECT = True + import gobject + HAVE_GOBJECT = True except ImportError: - HAVE_GOBJECT = False + HAVE_GOBJECT = False # needed for idlecommand if os.name == 'nt': - from subprocess import * # python24 only. we ask this for Windows + from subprocess import * # python24 only. we ask this for Windows elif os.name == 'posix': - import fcntl + import fcntl -FLAG_WRITE = 20 # write only -FLAG_READ = 19 # read only -FLAG_READ_WRITE = 23 # read and write -FLAG_CLOSE = 16 # wait for close +FLAG_WRITE = 20 # write only +FLAG_READ = 19 # read only +FLAG_READ_WRITE = 23 # read and write +FLAG_CLOSE = 16 # wait for close -PENDING_READ = 3 # waiting read event -PENDING_WRITE = 4 # waiting write event -IS_CLOSED = 16 # channel closed +PENDING_READ = 3 # waiting read event +PENDING_WRITE = 4 # waiting write event +IS_CLOSED = 16 # channel closed def get_idlequeue(): - ''' Get an appropriate idlequeue ''' - if os.name == 'nt': - # gobject.io_add_watch does not work on windows - return SelectIdleQueue() - else: - if HAVE_GOBJECT: - # Gajim's default Idlequeue - return GlibIdleQueue() - else: - # GUI less implementation - return SelectIdleQueue() + ''' Get an appropriate idlequeue ''' + if os.name == 'nt': + # gobject.io_add_watch does not work on windows + return SelectIdleQueue() + else: + if HAVE_GOBJECT: + # Gajim's default Idlequeue + return GlibIdleQueue() + else: + # GUI less implementation + return SelectIdleQueue() class IdleObject: - ''' - Idle listener interface. Listed methods are called by IdleQueue. - ''' - def __init__(self): - self.fd = -1 #: filedescriptor, must be unique for each IdleObject + ''' + Idle listener interface. Listed methods are called by IdleQueue. + ''' + def __init__(self): + self.fd = -1 #: filedescriptor, must be unique for each IdleObject - def pollend(self): - ''' called on stream failure ''' - pass + def pollend(self): + ''' called on stream failure ''' + pass - def pollin(self): - ''' called on new read event ''' - pass + def pollin(self): + ''' called on new read event ''' + pass - def pollout(self): - ''' called on new write event (connect in sockets is a pollout) ''' - pass + def pollout(self): + ''' called on new write event (connect in sockets is a pollout) ''' + pass - def read_timeout(self): - ''' called when timeout happened ''' - pass + def read_timeout(self): + ''' called when timeout happened ''' + pass class IdleCommand(IdleObject): - ''' - Can be subclassed to execute commands asynchronously by the idlequeue. - Result will be optained via file descriptor of created pipe - ''' - def __init__(self, on_result): - IdleObject.__init__(self) - # how long (sec.) to wait for result ( 0 - forever ) - # it is a class var, instead of a constant and we can override it. - self.commandtimeout = 0 - # when we have some kind of result (valid, ot not) we call this handler - self.result_handler = on_result - # if it is True, we can safetely execute the command - self.canexecute = True - self.idlequeue = None - self.result ='' + ''' + Can be subclassed to execute commands asynchronously by the idlequeue. + Result will be optained via file descriptor of created pipe + ''' + def __init__(self, on_result): + IdleObject.__init__(self) + # how long (sec.) to wait for result ( 0 - forever ) + # it is a class var, instead of a constant and we can override it. + self.commandtimeout = 0 + # when we have some kind of result (valid, ot not) we call this handler + self.result_handler = on_result + # if it is True, we can safetely execute the command + self.canexecute = True + self.idlequeue = None + self.result ='' - def set_idlequeue(self, idlequeue): - self.idlequeue = idlequeue + def set_idlequeue(self, idlequeue): + self.idlequeue = idlequeue - def _return_result(self): - if self.result_handler: - self.result_handler(self.result) - self.result_handler = None + def _return_result(self): + if self.result_handler: + self.result_handler(self.result) + self.result_handler = None - def _compose_command_args(self): - return ['echo', 'da'] + def _compose_command_args(self): + return ['echo', 'da'] - def _compose_command_line(self): - ''' return one line representation of command and its arguments ''' - return reduce(lambda left, right: left + ' ' + right, - self._compose_command_args()) + def _compose_command_line(self): + ''' return one line representation of command and its arguments ''' + return reduce(lambda left, right: left + ' ' + right, + self._compose_command_args()) - def wait_child(self): - if self.pipe.poll() is None: - # result timeout - if self.endtime < self.idlequeue.current_time(): - self._return_result() - self.pipe.stdout.close() - self.pipe.stdin.close() - else: - # child is still active, continue to wait - self.idlequeue.set_alarm(self.wait_child, 0.1) - else: - # child has quit - self.result = self.pipe.stdout.read() - self._return_result() - self.pipe.stdout.close() - self.pipe.stdin.close() + def wait_child(self): + if self.pipe.poll() is None: + # result timeout + if self.endtime < self.idlequeue.current_time(): + self._return_result() + self.pipe.stdout.close() + self.pipe.stdin.close() + else: + # child is still active, continue to wait + self.idlequeue.set_alarm(self.wait_child, 0.1) + else: + # child has quit + self.result = self.pipe.stdout.read() + self._return_result() + self.pipe.stdout.close() + self.pipe.stdin.close() - def start(self): - if not self.canexecute: - self.result = '' - self._return_result() - return - if os.name == 'nt': - self._start_nt() - elif os.name == 'posix': - self._start_posix() + def start(self): + if not self.canexecute: + self.result = '' + self._return_result() + return + if os.name == 'nt': + self._start_nt() + elif os.name == 'posix': + self._start_posix() - def _start_nt(self): - # if gajim is started from noninteraactive shells stdin is closed and - # cannot be forwarded, so we have to keep it open - self.pipe = Popen(self._compose_command_args(), stdout=PIPE, - bufsize = 1024, shell = True, stderr = STDOUT, stdin = PIPE) - if self.commandtimeout >= 0: - self.endtime = self.idlequeue.current_time() + self.commandtimeout - self.idlequeue.set_alarm(self.wait_child, 0.1) + def _start_nt(self): + # if gajim is started from noninteraactive shells stdin is closed and + # cannot be forwarded, so we have to keep it open + self.pipe = Popen(self._compose_command_args(), stdout=PIPE, + bufsize = 1024, shell = True, stderr = STDOUT, stdin = PIPE) + if self.commandtimeout >= 0: + self.endtime = self.idlequeue.current_time() + self.commandtimeout + self.idlequeue.set_alarm(self.wait_child, 0.1) - def _start_posix(self): - self.pipe = os.popen(self._compose_command_line()) - self.fd = self.pipe.fileno() - fcntl.fcntl(self.pipe, fcntl.F_SETFL, os.O_NONBLOCK) - self.idlequeue.plug_idle(self, False, True) - if self.commandtimeout >= 0: - self.idlequeue.set_read_timeout(self.fd, self.commandtimeout) + def _start_posix(self): + self.pipe = os.popen(self._compose_command_line()) + self.fd = self.pipe.fileno() + fcntl.fcntl(self.pipe, fcntl.F_SETFL, os.O_NONBLOCK) + self.idlequeue.plug_idle(self, False, True) + if self.commandtimeout >= 0: + self.idlequeue.set_read_timeout(self.fd, self.commandtimeout) - def end(self): - self.idlequeue.unplug_idle(self.fd) - try: - self.pipe.close() - except: - pass + def end(self): + self.idlequeue.unplug_idle(self.fd) + try: + self.pipe.close() + except: + pass - def pollend(self): - self.idlequeue.remove_timeout(self.fd) - self.end() - self._return_result() + def pollend(self): + self.idlequeue.remove_timeout(self.fd) + self.end() + self._return_result() - def pollin(self): - try: - res = self.pipe.read() - except Exception, e: - res = '' - if res == '': - return self.pollend() - else: - self.result += res + def pollin(self): + try: + res = self.pipe.read() + except Exception, e: + res = '' + if res == '': + return self.pollend() + else: + self.result += res - def read_timeout(self): - self.end() - self._return_result() + def read_timeout(self): + self.end() + self._return_result() class IdleQueue: - ''' - IdleQueue provide three distinct time based features. Uses select.poll() + ''' + IdleQueue provide three distinct time based features. Uses select.poll() - 1. Alarm timeout: Execute a callback after foo seconds - 2. Timeout event: Call read_timeout() of an plugged object if a timeout - has been set, but not removed in time. - 3. Check file descriptor of plugged objects for read, write and error - events - ''' - # (timeout, boolean) - # Boolean is True if timeout is specified in seconds, False means miliseconds - PROCESS_TIMEOUT = (200, False) + 1. Alarm timeout: Execute a callback after foo seconds + 2. Timeout event: Call read_timeout() of an plugged object if a timeout + has been set, but not removed in time. + 3. Check file descriptor of plugged objects for read, write and error + events + ''' + # (timeout, boolean) + # Boolean is True if timeout is specified in seconds, False means miliseconds + PROCESS_TIMEOUT = (200, False) - def __init__(self): - self.queue = {} + def __init__(self): + self.queue = {} - # when there is a timeout it executes obj.read_timeout() - # timeout is not removed automatically! - # {fd1: {timeout1: func1, timeout2: func2}} - # timout are unique (timeout1 must be != timeout2) - # If func1 is None, read_time function is called - self.read_timeouts = {} + # when there is a timeout it executes obj.read_timeout() + # timeout is not removed automatically! + # {fd1: {timeout1: func1, timeout2: func2}} + # timout are unique (timeout1 must be != timeout2) + # If func1 is None, read_time function is called + self.read_timeouts = {} - # cb, which are executed after XX sec., alarms are removed automatically - self.alarms = {} - self._init_idle() + # cb, which are executed after XX sec., alarms are removed automatically + self.alarms = {} + self._init_idle() - def _init_idle(self): - ''' Hook method for subclassed. Will be called by __init__. ''' - self.selector = select.poll() + def _init_idle(self): + ''' Hook method for subclassed. Will be called by __init__. ''' + self.selector = select.poll() - def set_alarm(self, alarm_cb, seconds): - ''' - Sets up a new alarm. alarm_cb will be called after specified seconds. - ''' - alarm_time = self.current_time() + seconds - # almost impossible, but in case we have another alarm_cb at this time - if alarm_time in self.alarms: - self.alarms[alarm_time].append(alarm_cb) - else: - self.alarms[alarm_time] = [alarm_cb] - return alarm_time + def set_alarm(self, alarm_cb, seconds): + ''' + Sets up a new alarm. alarm_cb will be called after specified seconds. + ''' + alarm_time = self.current_time() + seconds + # almost impossible, but in case we have another alarm_cb at this time + if alarm_time in self.alarms: + self.alarms[alarm_time].append(alarm_cb) + else: + self.alarms[alarm_time] = [alarm_cb] + return alarm_time - def remove_alarm(self, alarm_cb, alarm_time): - ''' - Removes alarm callback alarm_cb scheduled on alarm_time. - Returns True if it was removed sucessfully, otherwise False - ''' - if not alarm_time in self.alarms: - return False - i = -1 - for i in range(len(self.alarms[alarm_time])): - # let's not modify the list inside the loop - if self.alarms[alarm_time][i] is alarm_cb: - break - if i != -1: - del self.alarms[alarm_time][i] - if self.alarms[alarm_time] == []: - del self.alarms[alarm_time] - return True - else: - return False + def remove_alarm(self, alarm_cb, alarm_time): + ''' + Removes alarm callback alarm_cb scheduled on alarm_time. + Returns True if it was removed sucessfully, otherwise False + ''' + if not alarm_time in self.alarms: + return False + i = -1 + for i in range(len(self.alarms[alarm_time])): + # let's not modify the list inside the loop + if self.alarms[alarm_time][i] is alarm_cb: + break + if i != -1: + del self.alarms[alarm_time][i] + if self.alarms[alarm_time] == []: + del self.alarms[alarm_time] + return True + else: + return False - def remove_timeout(self, fd, timeout=None): - ''' Removes the read timeout ''' - log.info('read timeout removed for fd %s' % fd) - if fd in self.read_timeouts: - if timeout: - if timeout in self.read_timeouts[fd]: - del(self.read_timeouts[fd][timeout]) - if len(self.read_timeouts[fd]) == 0: - del(self.read_timeouts[fd]) - else: - del(self.read_timeouts[fd]) + def remove_timeout(self, fd, timeout=None): + ''' Removes the read timeout ''' + log.info('read timeout removed for fd %s' % fd) + if fd in self.read_timeouts: + if timeout: + if timeout in self.read_timeouts[fd]: + del(self.read_timeouts[fd][timeout]) + if len(self.read_timeouts[fd]) == 0: + del(self.read_timeouts[fd]) + else: + del(self.read_timeouts[fd]) - def set_read_timeout(self, fd, seconds, func=None): - ''' - Sets a new timeout. If it is not removed after specified seconds, - func or obj.read_timeout() will be called. + def set_read_timeout(self, fd, seconds, func=None): + ''' + Sets a new timeout. If it is not removed after specified seconds, + func or obj.read_timeout() will be called. - A filedescriptor fd can have several timeouts. - ''' - log_txt = 'read timeout set for fd %s on %s seconds' % (fd, seconds) - if func: - log_txt += ' with function ' + str(func) - log.info(log_txt) - timeout = self.current_time() + seconds - if fd in self.read_timeouts: - self.read_timeouts[fd][timeout] = func - else: - self.read_timeouts[fd] = {timeout: func} + A filedescriptor fd can have several timeouts. + ''' + log_txt = 'read timeout set for fd %s on %s seconds' % (fd, seconds) + if func: + log_txt += ' with function ' + str(func) + log.info(log_txt) + timeout = self.current_time() + seconds + if fd in self.read_timeouts: + self.read_timeouts[fd][timeout] = func + else: + self.read_timeouts[fd] = {timeout: func} - def _check_time_events(self): - ''' - Execute and remove alarm callbacks and execute func() or read_timeout() - for plugged objects if specified time has ellapsed. - ''' - log.info('check time evs') - current_time = self.current_time() + def _check_time_events(self): + ''' + Execute and remove alarm callbacks and execute func() or read_timeout() + for plugged objects if specified time has ellapsed. + ''' + log.info('check time evs') + current_time = self.current_time() - for fd, timeouts in self.read_timeouts.items(): - if fd not in self.queue: - self.remove_timeout(fd) - continue - for timeout, func in timeouts.items(): - if timeout > current_time: - continue - if func: - log.debug('Calling %s for fd %s' % (func, fd)) - func() - else: - log.debug('Calling read_timeout for fd %s' % fd) - self.queue[fd].read_timeout() - self.remove_timeout(fd, timeout) + for fd, timeouts in self.read_timeouts.items(): + if fd not in self.queue: + self.remove_timeout(fd) + continue + for timeout, func in timeouts.items(): + if timeout > current_time: + continue + if func: + log.debug('Calling %s for fd %s' % (func, fd)) + func() + else: + log.debug('Calling read_timeout for fd %s' % fd) + self.queue[fd].read_timeout() + self.remove_timeout(fd, timeout) - times = self.alarms.keys() - for alarm_time in times: - if alarm_time > current_time: - continue - if alarm_time in self.alarms: - for callback in self.alarms[alarm_time]: - callback() - if alarm_time in self.alarms: - del(self.alarms[alarm_time]) + times = self.alarms.keys() + for alarm_time in times: + if alarm_time > current_time: + continue + if alarm_time in self.alarms: + for callback in self.alarms[alarm_time]: + callback() + if alarm_time in self.alarms: + del(self.alarms[alarm_time]) - def plug_idle(self, obj, writable=True, readable=True): - ''' - Plug an IdleObject into idlequeue. Filedescriptor fd must be set. + def plug_idle(self, obj, writable=True, readable=True): + ''' + Plug an IdleObject into idlequeue. Filedescriptor fd must be set. - :param obj: the IdleObject - :param writable: True if obj has data to sent - :param readable: True if obj expects data to be reiceived - ''' - if obj.fd == -1: - return - if obj.fd in self.queue: - self.unplug_idle(obj.fd) - self.queue[obj.fd] = obj - if writable: - if not readable: - flags = FLAG_WRITE - else: - flags = FLAG_READ_WRITE - else: - if readable: - flags = FLAG_READ - else: - # when we paused a FT, we expect only a close event - flags = FLAG_CLOSE - self._add_idle(obj.fd, flags) + :param obj: the IdleObject + :param writable: True if obj has data to sent + :param readable: True if obj expects data to be reiceived + ''' + if obj.fd == -1: + return + if obj.fd in self.queue: + self.unplug_idle(obj.fd) + self.queue[obj.fd] = obj + if writable: + if not readable: + flags = FLAG_WRITE + else: + flags = FLAG_READ_WRITE + else: + if readable: + flags = FLAG_READ + else: + # when we paused a FT, we expect only a close event + flags = FLAG_CLOSE + self._add_idle(obj.fd, flags) - def _add_idle(self, fd, flags): - ''' Hook method for subclasses, called by plug_idle ''' - self.selector.register(fd, flags) + def _add_idle(self, fd, flags): + ''' Hook method for subclasses, called by plug_idle ''' + self.selector.register(fd, flags) - def unplug_idle(self, fd): - ''' Removed plugged IdleObject, specified by filedescriptor fd. ''' - if fd in self.queue: - del(self.queue[fd]) - self._remove_idle(fd) + def unplug_idle(self, fd): + ''' Removed plugged IdleObject, specified by filedescriptor fd. ''' + if fd in self.queue: + del(self.queue[fd]) + self._remove_idle(fd) - def current_time(self): - from time import time - return time() + def current_time(self): + from time import time + return time() - def _remove_idle(self, fd): - ''' Hook method for subclassed, called by unplug_idle ''' - self.selector.unregister(fd) + def _remove_idle(self, fd): + ''' Hook method for subclassed, called by unplug_idle ''' + self.selector.unregister(fd) - def _process_events(self, fd, flags): - obj = self.queue.get(fd) - if obj is None: - self.unplug_idle(fd) - return False + def _process_events(self, fd, flags): + obj = self.queue.get(fd) + if obj is None: + self.unplug_idle(fd) + return False - if flags & PENDING_READ: - #print 'waiting read on %d, flags are %d' % (fd, flags) - obj.pollin() - return True + if flags & PENDING_READ: + #print 'waiting read on %d, flags are %d' % (fd, flags) + obj.pollin() + return True - elif flags & PENDING_WRITE: - obj.pollout() - return True + elif flags & PENDING_WRITE: + obj.pollout() + return True - elif flags & IS_CLOSED: - # io error, don't expect more events - self.remove_timeout(obj.fd) - self.unplug_idle(obj.fd) - obj.pollend() - return False + elif flags & IS_CLOSED: + # io error, don't expect more events + self.remove_timeout(obj.fd) + self.unplug_idle(obj.fd) + obj.pollend() + return False - def process(self): - ''' - Process idlequeue. Check for any pending timeout or alarm events. - Call IdleObjects on possible and requested read, write and error events - on their file descriptors. + def process(self): + ''' + Process idlequeue. Check for any pending timeout or alarm events. + Call IdleObjects on possible and requested read, write and error events + on their file descriptors. - Call this in regular intervals. - ''' - if not self.queue: - # check for timeouts/alert also when there are no active fds - self._check_time_events() - return True - try: - waiting_descriptors = self.selector.poll(0) - except select.error, e: - waiting_descriptors = [] - if e[0] != 4: # interrupt - raise - for fd, flags in waiting_descriptors: - self._process_events(fd, flags) - self._check_time_events() - return True + Call this in regular intervals. + ''' + if not self.queue: + # check for timeouts/alert also when there are no active fds + self._check_time_events() + return True + try: + waiting_descriptors = self.selector.poll(0) + except select.error, e: + waiting_descriptors = [] + if e[0] != 4: # interrupt + raise + for fd, flags in waiting_descriptors: + self._process_events(fd, flags) + self._check_time_events() + return True class SelectIdleQueue(IdleQueue): - ''' - Extends IdleQueue to use select.select() for polling + ''' + Extends IdleQueue to use select.select() for polling - This class exisists for the sake of gtk2.8 on windows, which - doesn't seem to support io_add_watch properly (yet) - ''' - def _init_idle(self): - ''' - Creates a dict, which maps file/pipe/sock descriptor to glib event id - ''' - self.read_fds = {} - self.write_fds = {} - self.error_fds = {} + This class exisists for the sake of gtk2.8 on windows, which + doesn't seem to support io_add_watch properly (yet) + ''' + def _init_idle(self): + ''' + Creates a dict, which maps file/pipe/sock descriptor to glib event id + ''' + self.read_fds = {} + self.write_fds = {} + self.error_fds = {} - def _add_idle(self, fd, flags): - ''' this method is called when we plug a new idle object. - Remove descriptor to read/write/error lists, according flags - ''' - if flags & 3: - self.read_fds[fd] = fd - if flags & 4: - self.write_fds[fd] = fd - self.error_fds[fd] = fd + def _add_idle(self, fd, flags): + ''' this method is called when we plug a new idle object. + Remove descriptor to read/write/error lists, according flags + ''' + if flags & 3: + self.read_fds[fd] = fd + if flags & 4: + self.write_fds[fd] = fd + self.error_fds[fd] = fd - def _remove_idle(self, fd): - ''' this method is called when we unplug a new idle object. - Remove descriptor from read/write/error lists - ''' - if fd in self.read_fds: - del(self.read_fds[fd]) - if fd in self.write_fds: - del(self.write_fds[fd]) - if fd in self.error_fds: - del(self.error_fds[fd]) + def _remove_idle(self, fd): + ''' this method is called when we unplug a new idle object. + Remove descriptor from read/write/error lists + ''' + if fd in self.read_fds: + del(self.read_fds[fd]) + if fd in self.write_fds: + del(self.write_fds[fd]) + if fd in self.error_fds: + del(self.error_fds[fd]) - def process(self): - if not self.write_fds and not self.read_fds: - self._check_time_events() - return True - try: - waiting_descriptors = select.select(self.read_fds.keys(), - self.write_fds.keys(), self.error_fds.keys(), 0) - except select.error, e: - waiting_descriptors = ((),(),()) - if e[0] != 4: # interrupt - raise - for fd in waiting_descriptors[0]: - q = self.queue.get(fd) - if q: - q.pollin() - for fd in waiting_descriptors[1]: - q = self.queue.get(fd) - if q: - q.pollout() - for fd in waiting_descriptors[2]: - q = self.queue.get(fd) - if q: - q.pollend() - self._check_time_events() - return True + def process(self): + if not self.write_fds and not self.read_fds: + self._check_time_events() + return True + try: + waiting_descriptors = select.select(self.read_fds.keys(), + self.write_fds.keys(), self.error_fds.keys(), 0) + except select.error, e: + waiting_descriptors = ((), (), ()) + if e[0] != 4: # interrupt + raise + for fd in waiting_descriptors[0]: + q = self.queue.get(fd) + if q: + q.pollin() + for fd in waiting_descriptors[1]: + q = self.queue.get(fd) + if q: + q.pollout() + for fd in waiting_descriptors[2]: + q = self.queue.get(fd) + if q: + q.pollend() + self._check_time_events() + return True class GlibIdleQueue(IdleQueue): - ''' - Extends IdleQueue to use glib io_add_wath, instead of select/poll - In another 'non gui' implementation of Gajim IdleQueue can be used safetly. - ''' - # (timeout, boolean) - # Boolean is True if timeout is specified in seconds, False means miliseconds - PROCESS_TIMEOUT = (2, True) + ''' + Extends IdleQueue to use glib io_add_wath, instead of select/poll + In another 'non gui' implementation of Gajim IdleQueue can be used safetly. + ''' + # (timeout, boolean) + # Boolean is True if timeout is specified in seconds, False means miliseconds + PROCESS_TIMEOUT = (2, True) - def _init_idle(self): - ''' - Creates a dict, which maps file/pipe/sock descriptor to glib event id - ''' - self.events = {} - # time() is already called in glib, we just get the last value - # overrides IdleQueue.current_time() - self.current_time = gobject.get_current_time + def _init_idle(self): + ''' + Creates a dict, which maps file/pipe/sock descriptor to glib event id + ''' + self.events = {} + # time() is already called in glib, we just get the last value + # overrides IdleQueue.current_time() + self.current_time = gobject.get_current_time - def _add_idle(self, fd, flags): - ''' this method is called when we plug a new idle object. - Start listening for events from fd - ''' - res = gobject.io_add_watch(fd, flags, self._process_events, - priority=gobject.PRIORITY_LOW) - # store the id of the watch, so that we can remove it on unplug - self.events[fd] = res + def _add_idle(self, fd, flags): + ''' this method is called when we plug a new idle object. + Start listening for events from fd + ''' + res = gobject.io_add_watch(fd, flags, self._process_events, + priority=gobject.PRIORITY_LOW) + # store the id of the watch, so that we can remove it on unplug + self.events[fd] = res - def _process_events(self, fd, flags): - try: - return IdleQueue._process_events(self, fd, flags) - except Exception: - self._remove_idle(fd) - self._add_idle(fd, flags) - raise + def _process_events(self, fd, flags): + try: + return IdleQueue._process_events(self, fd, flags) + except Exception: + self._remove_idle(fd) + self._add_idle(fd, flags) + raise - def _remove_idle(self, fd): - ''' this method is called when we unplug a new idle object. - Stop listening for events from fd - ''' - if not fd in self.events: - return - gobject.source_remove(self.events[fd]) - del(self.events[fd]) + def _remove_idle(self, fd): + ''' this method is called when we unplug a new idle object. + Stop listening for events from fd + ''' + if not fd in self.events: + return + gobject.source_remove(self.events[fd]) + del(self.events[fd]) - def process(self): - self._check_time_events() + def process(self): + self._check_time_events() - -# vim: se ts=3: diff --git a/src/common/xmpp/plugin.py b/src/common/xmpp/plugin.py index 7f5d91ee1..836e60477 100644 --- a/src/common/xmpp/plugin.py +++ b/src/common/xmpp/plugin.py @@ -22,76 +22,74 @@ import logging log = logging.getLogger('gajim.c.x.plugin') class PlugIn: - ''' - Abstract xmpppy plugin infrastructure code, providing plugging in/out and - debugging functionality. + ''' + Abstract xmpppy plugin infrastructure code, providing plugging in/out and + debugging functionality. - Inherit to develop pluggable objects. No code change on the owner class - required (the object where we plug into) + Inherit to develop pluggable objects. No code change on the owner class + required (the object where we plug into) - For every instance of PlugIn class the 'owner' is the class in what the plug - was plugged. - ''' - def __init__(self): - self._exported_methods=[] + For every instance of PlugIn class the 'owner' is the class in what the plug + was plugged. + ''' + def __init__(self): + self._exported_methods=[] - def PlugIn(self, owner): - ''' - Attach to owner and register ourself and our _exported_methods in it. - If defined by a subclass, call self.plugin(owner) to execute hook - code after plugging. - ''' - self._owner=owner - log.info('Plugging %s __INTO__ %s' % (self, self._owner)) - if self.__class__.__name__ in owner.__dict__: - log.debug('Plugging ignored: another instance already plugged.') - return - self._old_owners_methods=[] - for method in self._exported_methods: - if method.__name__ in owner.__dict__: - self._old_owners_methods.append(owner.__dict__[method.__name__]) - owner.__dict__[method.__name__]=method - if self.__class__.__name__.endswith('Dispatcher'): - # FIXME: I need BOSHDispatcher or XMPPDispatcher on .Dispatcher - # there must be a better way.. - owner.__dict__['Dispatcher']=self - else: - owner.__dict__[self.__class__.__name__]=self + def PlugIn(self, owner): + ''' + Attach to owner and register ourself and our _exported_methods in it. + If defined by a subclass, call self.plugin(owner) to execute hook + code after plugging. + ''' + self._owner=owner + log.info('Plugging %s __INTO__ %s' % (self, self._owner)) + if self.__class__.__name__ in owner.__dict__: + log.debug('Plugging ignored: another instance already plugged.') + return + self._old_owners_methods=[] + for method in self._exported_methods: + if method.__name__ in owner.__dict__: + self._old_owners_methods.append(owner.__dict__[method.__name__]) + owner.__dict__[method.__name__]=method + if self.__class__.__name__.endswith('Dispatcher'): + # FIXME: I need BOSHDispatcher or XMPPDispatcher on .Dispatcher + # there must be a better way.. + owner.__dict__['Dispatcher']=self + else: + owner.__dict__[self.__class__.__name__]=self - # Execute hook - if hasattr(self,'plugin'): - return self.plugin(owner) + # Execute hook + if hasattr(self, 'plugin'): + return self.plugin(owner) - def PlugOut(self): - ''' - Unregister our _exported_methods from owner and detach from it. - If defined by a subclass, call self.plugout() after unplugging to execute - hook code. - ''' - log.info('Plugging %s __OUT__ of %s.' % (self, self._owner)) - for method in self._exported_methods: - del self._owner.__dict__[method.__name__] - for method in self._old_owners_methods: - self._owner.__dict__[method.__name__]=method - # FIXME: Dispatcher workaround - if self.__class__.__name__.endswith('Dispatcher'): - del self._owner.__dict__['Dispatcher'] - else: - del self._owner.__dict__[self.__class__.__name__] - # Execute hook - if hasattr(self,'plugout'): - return self.plugout() - del self._owner + def PlugOut(self): + ''' + Unregister our _exported_methods from owner and detach from it. + If defined by a subclass, call self.plugout() after unplugging to execute + hook code. + ''' + log.info('Plugging %s __OUT__ of %s.' % (self, self._owner)) + for method in self._exported_methods: + del self._owner.__dict__[method.__name__] + for method in self._old_owners_methods: + self._owner.__dict__[method.__name__]=method + # FIXME: Dispatcher workaround + if self.__class__.__name__.endswith('Dispatcher'): + del self._owner.__dict__['Dispatcher'] + else: + del self._owner.__dict__[self.__class__.__name__] + # Execute hook + if hasattr(self, 'plugout'): + return self.plugout() + del self._owner - @classmethod - def get_instance(cls, *args, **kwargs): - ''' - Factory Method for object creation. + @classmethod + def get_instance(cls, *args, **kwargs): + ''' + Factory Method for object creation. - Use this instead of directly initializing the class in order to make - unit testing easier. For testing, this method can be patched to inject - mock objects. - ''' - return cls(*args, **kwargs) - -# vim: se ts=3: + Use this instead of directly initializing the class in order to make + unit testing easier. For testing, this method can be patched to inject + mock objects. + ''' + return cls(*args, **kwargs) diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py index 3cf75fea3..deddb8a12 100644 --- a/src/common/xmpp/protocol.py +++ b/src/common/xmpp/protocol.py @@ -22,52 +22,52 @@ stanzas) handling routines. from simplexml import Node, NodeBuilder import time -NS_ACTIVITY ='http://jabber.org/protocol/activity' # XEP-0108 -NS_ADDRESS ='http://jabber.org/protocol/address' # XEP-0033 -NS_AGENTS ='jabber:iq:agents' -NS_AMP ='http://jabber.org/protocol/amp' +NS_ACTIVITY ='http://jabber.org/protocol/activity' # XEP-0108 +NS_ADDRESS ='http://jabber.org/protocol/address' # XEP-0033 +NS_AGENTS ='jabber:iq:agents' +NS_AMP ='http://jabber.org/protocol/amp' NS_AMP_ERRORS =NS_AMP+'#errors' -NS_ARCHIVE ='urn:xmpp:archive' #XEP-0136 -NS_ARCHIVE_AUTO =NS_ARCHIVE+':auto' #XEP-0136 -NS_ARCHIVE_MANAGE =NS_ARCHIVE+':manage' #XEP-0136 -NS_ARCHIVE_MANUAL =NS_ARCHIVE+':manual' #XEP-0136 -NS_ARCHIVE_PREF =NS_ARCHIVE+':pref' #XEP-0136 -NS_AUTH ='jabber:iq:auth' -NS_AVATAR ='http://www.xmpp.org/extensions/xep-0084.html#ns-metadata' -NS_BIND ='urn:ietf:params:xml:ns:xmpp-bind' -NS_BROWSE ='jabber:iq:browse' -NS_BROWSING ='http://jabber.org/protocol/browsing' # XEP-0195 -NS_BYTESTREAM ='http://jabber.org/protocol/bytestreams' # JEP-0065 -NS_CAPS ='http://jabber.org/protocol/caps' # JEP-0115 -NS_CHATSTATES ='http://jabber.org/protocol/chatstates' # JEP-0085 -NS_CHATTING ='http://jabber.org/protocol/chatting' # XEP-0194 -NS_CLIENT ='jabber:client' -NS_COMMANDS ='http://jabber.org/protocol/commands' +NS_ARCHIVE ='urn:xmpp:archive' #XEP-0136 +NS_ARCHIVE_AUTO =NS_ARCHIVE+':auto' #XEP-0136 +NS_ARCHIVE_MANAGE =NS_ARCHIVE+':manage' #XEP-0136 +NS_ARCHIVE_MANUAL =NS_ARCHIVE+':manual' #XEP-0136 +NS_ARCHIVE_PREF =NS_ARCHIVE+':pref' #XEP-0136 +NS_AUTH ='jabber:iq:auth' +NS_AVATAR ='http://www.xmpp.org/extensions/xep-0084.html#ns-metadata' +NS_BIND ='urn:ietf:params:xml:ns:xmpp-bind' +NS_BROWSE ='jabber:iq:browse' +NS_BROWSING ='http://jabber.org/protocol/browsing' # XEP-0195 +NS_BYTESTREAM ='http://jabber.org/protocol/bytestreams' # JEP-0065 +NS_CAPS ='http://jabber.org/protocol/caps' # JEP-0115 +NS_CHATSTATES ='http://jabber.org/protocol/chatstates' # JEP-0085 +NS_CHATTING ='http://jabber.org/protocol/chatting' # XEP-0194 +NS_CLIENT ='jabber:client' +NS_COMMANDS ='http://jabber.org/protocol/commands' NS_COMPONENT_ACCEPT='jabber:component:accept' NS_COMPONENT_1 ='http://jabberd.jabberstudio.org/ns/component/1.0' -NS_COMPRESS ='http://jabber.org/protocol/compress' # XEP-0138 +NS_COMPRESS ='http://jabber.org/protocol/compress' # XEP-0138 NS_CONFERENCE ='jabber:x:conference' -NS_DATA ='jabber:x:data' # XEP-0004 -NS_DELAY ='jabber:x:delay' -NS_DELAY2 ='urn:xmpp:delay' -NS_DIALBACK ='jabber:server:dialback' -NS_DISCO ='http://jabber.org/protocol/disco' +NS_DATA ='jabber:x:data' # XEP-0004 +NS_DELAY ='jabber:x:delay' +NS_DELAY2 ='urn:xmpp:delay' +NS_DIALBACK ='jabber:server:dialback' +NS_DISCO ='http://jabber.org/protocol/disco' NS_DISCO_INFO =NS_DISCO+'#info' NS_DISCO_ITEMS =NS_DISCO+'#items' -NS_ENCRYPTED ='jabber:x:encrypted' # XEP-0027 -NS_ESESSION ='http://www.xmpp.org/extensions/xep-0116.html#ns' +NS_ENCRYPTED ='jabber:x:encrypted' # XEP-0027 +NS_ESESSION ='http://www.xmpp.org/extensions/xep-0116.html#ns' NS_ESESSION_INIT='http://www.xmpp.org/extensions/xep-0116.html#ns-init' # XEP-0116 -NS_EVENT ='jabber:x:event' # XEP-0022 -NS_FEATURE ='http://jabber.org/protocol/feature-neg' -NS_FILE ='http://jabber.org/protocol/si/profile/file-transfer' # JEP-0096 -NS_GAMING ='http://jabber.org/protocol/gaming' # XEP-0196 -NS_GEOLOC ='http://jabber.org/protocol/geoloc' # JEP-0080 -NS_GROUPCHAT ='gc-1.0' -NS_HTTP_AUTH ='http://jabber.org/protocol/http-auth' # XEP-0070 -NS_HTTP_BIND ='http://jabber.org/protocol/httpbind' # XEP-0124 -NS_IBB ='http://jabber.org/protocol/ibb' -NS_INVISIBLE ='presence-invisible' # Jabberd2 -NS_IQ ='iq' # Jabberd2 +NS_EVENT ='jabber:x:event' # XEP-0022 +NS_FEATURE ='http://jabber.org/protocol/feature-neg' +NS_FILE ='http://jabber.org/protocol/si/profile/file-transfer' # JEP-0096 +NS_GAMING ='http://jabber.org/protocol/gaming' # XEP-0196 +NS_GEOLOC ='http://jabber.org/protocol/geoloc' # JEP-0080 +NS_GROUPCHAT ='gc-1.0' +NS_HTTP_AUTH ='http://jabber.org/protocol/http-auth' # XEP-0070 +NS_HTTP_BIND ='http://jabber.org/protocol/httpbind' # XEP-0124 +NS_IBB ='http://jabber.org/protocol/ibb' +NS_INVISIBLE ='presence-invisible' # Jabberd2 +NS_IQ ='iq' # Jabberd2 NS_JINGLE ='urn:xmpp:jingle:1' # XEP-0166 NS_JINGLE_ERRORS='urn:xmpp:jingle:errors:1' # XEP-0166 NS_JINGLE_RTP ='urn:xmpp:jingle:apps:rtp:1' # XEP-0167 @@ -75,62 +75,62 @@ NS_JINGLE_RTP_AUDIO='urn:xmpp:jingle:apps:rtp:audio' # XEP-01 NS_JINGLE_RTP_VIDEO='urn:xmpp:jingle:apps:rtp:video' # XEP-0167 NS_JINGLE_RAW_UDP='urn:xmpp:jingle:transports:raw-udp:1' # XEP-0177 NS_JINGLE_ICE_UDP='urn:xmpp:jingle:transports:ice-udp:1' # XEP-0176 -NS_LAST ='jabber:iq:last' -NS_MESSAGE ='message' # Jabberd2 -NS_MOOD ='http://jabber.org/protocol/mood' # XEP-0107 -NS_MUC ='http://jabber.org/protocol/muc' -NS_MUC_USER =NS_MUC+'#user' -NS_MUC_ADMIN =NS_MUC+'#admin' -NS_MUC_OWNER =NS_MUC+'#owner' +NS_LAST ='jabber:iq:last' +NS_MESSAGE ='message' # Jabberd2 +NS_MOOD ='http://jabber.org/protocol/mood' # XEP-0107 +NS_MUC ='http://jabber.org/protocol/muc' +NS_MUC_USER =NS_MUC+'#user' +NS_MUC_ADMIN =NS_MUC+'#admin' +NS_MUC_OWNER =NS_MUC+'#owner' NS_MUC_UNIQUE =NS_MUC+'#unique' NS_MUC_CONFIG =NS_MUC+'#roomconfig' -NS_NICK ='http://jabber.org/protocol/nick' # XEP-0172 -NS_OFFLINE ='http://www.jabber.org/jeps/jep-0030.html' # XEP-0013 -NS_PHYSLOC ='http://jabber.org/protocol/physloc' # XEP-0112 -NS_PING ='urn:xmpp:ping' # SEP-0199 -NS_PRESENCE ='presence' # Jabberd2 -NS_PRIVACY ='jabber:iq:privacy' -NS_PRIVATE ='jabber:iq:private' -NS_PROFILE ='http://jabber.org/protocol/profile' # XEP-0154 -NS_PUBSUB ='http://jabber.org/protocol/pubsub' # XEP-0060 +NS_NICK ='http://jabber.org/protocol/nick' # XEP-0172 +NS_OFFLINE ='http://www.jabber.org/jeps/jep-0030.html' # XEP-0013 +NS_PHYSLOC ='http://jabber.org/protocol/physloc' # XEP-0112 +NS_PING ='urn:xmpp:ping' # SEP-0199 +NS_PRESENCE ='presence' # Jabberd2 +NS_PRIVACY ='jabber:iq:privacy' +NS_PRIVATE ='jabber:iq:private' +NS_PROFILE ='http://jabber.org/protocol/profile' # XEP-0154 +NS_PUBSUB ='http://jabber.org/protocol/pubsub' # XEP-0060 NS_PUBSUB_EVENT = 'http://jabber.org/protocol/pubsub#event' -NS_PUBSUB_PUBLISH_OPTIONS = NS_PUBSUB + '#publish-options' # XEP-0060 -NS_PUBSUB_OWNER ='http://jabber.org/protocol/pubsub#owner' # JEP-0060 -NS_REGISTER ='jabber:iq:register' -NS_ROSTER ='jabber:iq:roster' -NS_ROSTERX ='http://jabber.org/protocol/rosterx' # XEP-0144 -NS_RPC ='jabber:iq:rpc' # XEP-0009 -NS_RSM ='http://jabber.org/protocol/rsm' #XEP-0059 -NS_SASL ='urn:ietf:params:xml:ns:xmpp-sasl' -NS_SEARCH ='jabber:iq:search' -NS_SERVER ='jabber:server' -NS_SESSION ='urn:ietf:params:xml:ns:xmpp-session' -NS_SI ='http://jabber.org/protocol/si' # XEP-0096 -NS_SI_PUB ='http://jabber.org/protocol/sipub' # XEP-0137 -NS_SIGNED ='jabber:x:signed' # XEP-0027 -NS_SSN ='urn:xmpp:ssn' # XEP-0155 -NS_STANZA_CRYPTO='http://www.xmpp.org/extensions/xep-0200.html#ns' # XEP-0200 -NS_STANZAS ='urn:ietf:params:xml:ns:xmpp-stanzas' -NS_STREAM ='http://affinix.com/jabber/stream' -NS_STREAMS ='http://etherx.jabber.org/streams' -NS_TIME ='jabber:iq:time' # XEP-0900 -NS_TIME_REVISED ='urn:xmpp:time' # XEP-0202 -NS_TLS ='urn:ietf:params:xml:ns:xmpp-tls' -NS_TUNE ='http://jabber.org/protocol/tune' # XEP-0118 -NS_VACATION ='http://jabber.org/protocol/vacation' -NS_VCARD ='vcard-temp' +NS_PUBSUB_PUBLISH_OPTIONS = NS_PUBSUB + '#publish-options' # XEP-0060 +NS_PUBSUB_OWNER ='http://jabber.org/protocol/pubsub#owner' # JEP-0060 +NS_REGISTER ='jabber:iq:register' +NS_ROSTER ='jabber:iq:roster' +NS_ROSTERX ='http://jabber.org/protocol/rosterx' # XEP-0144 +NS_RPC ='jabber:iq:rpc' # XEP-0009 +NS_RSM ='http://jabber.org/protocol/rsm' #XEP-0059 +NS_SASL ='urn:ietf:params:xml:ns:xmpp-sasl' +NS_SEARCH ='jabber:iq:search' +NS_SERVER ='jabber:server' +NS_SESSION ='urn:ietf:params:xml:ns:xmpp-session' +NS_SI ='http://jabber.org/protocol/si' # XEP-0096 +NS_SI_PUB ='http://jabber.org/protocol/sipub' # XEP-0137 +NS_SIGNED ='jabber:x:signed' # XEP-0027 +NS_SSN ='urn:xmpp:ssn' # XEP-0155 +NS_STANZA_CRYPTO='http://www.xmpp.org/extensions/xep-0200.html#ns' # XEP-0200 +NS_STANZAS ='urn:ietf:params:xml:ns:xmpp-stanzas' +NS_STREAM ='http://affinix.com/jabber/stream' +NS_STREAMS ='http://etherx.jabber.org/streams' +NS_TIME ='jabber:iq:time' # XEP-0900 +NS_TIME_REVISED ='urn:xmpp:time' # XEP-0202 +NS_TLS ='urn:ietf:params:xml:ns:xmpp-tls' +NS_TUNE ='http://jabber.org/protocol/tune' # XEP-0118 +NS_VACATION ='http://jabber.org/protocol/vacation' +NS_VCARD ='vcard-temp' NS_GMAILNOTIFY ='google:mail:notify' NS_GTALKSETTING ='google:setting' NS_VCARD_UPDATE =NS_VCARD+':x:update' -NS_VERSION ='jabber:iq:version' -NS_VIEWING ='http://jabber.org/protocol/viewing' # XEP--197 -NS_WAITINGLIST ='http://jabber.org/protocol/waitinglist' # XEP-0130 -NS_XHTML_IM ='http://jabber.org/protocol/xhtml-im' # XEP-0071 -NS_XHTML = 'http://www.w3.org/1999/xhtml' # " -NS_DATA_LAYOUT ='http://jabber.org/protocol/xdata-layout' # XEP-0141 -NS_DATA_VALIDATE='http://jabber.org/protocol/xdata-validate' # XEP-0122 +NS_VERSION ='jabber:iq:version' +NS_VIEWING ='http://jabber.org/protocol/viewing' # XEP--197 +NS_WAITINGLIST ='http://jabber.org/protocol/waitinglist' # XEP-0130 +NS_XHTML_IM ='http://jabber.org/protocol/xhtml-im' # XEP-0071 +NS_XHTML = 'http://www.w3.org/1999/xhtml' # " +NS_DATA_LAYOUT ='http://jabber.org/protocol/xdata-layout' # XEP-0141 +NS_DATA_VALIDATE='http://jabber.org/protocol/xdata-validate' # XEP-0122 NS_XMPP_STREAMS ='urn:ietf:params:xml:ns:xmpp-streams' -NS_RECEIPTS ='urn:xmpp:receipts' +NS_RECEIPTS ='urn:xmpp:receipts' xmpp_stream_error_conditions=''' bad-format -- -- -- The entity has sent XML that cannot be processed. @@ -189,29 +189,29 @@ mechanism-too-weak -- -- -- The mechanism requested by the initiating entity i not-authorized -- -- -- The authentication failed because the initiating entity did not provide valid credentials (this includes but is not limited to the case of an unknown username); sent in reply to a element or an element with initial response data. temporary-auth-failure -- -- -- The authentication failed because of a temporary error condition within the receiving entity; sent in reply to an element or element.''' -ERRORS,_errorcodes={},{} -for ns,errname,errpool in ((NS_XMPP_STREAMS,'STREAM',xmpp_stream_error_conditions), - (NS_STANZAS ,'ERR' ,xmpp_stanza_error_conditions), - (NS_SASL ,'SASL' ,sasl_error_conditions)): - for err in errpool.split('\n')[1:]: - cond,code,typ,text=err.split(' -- ') - name=errname+'_'+cond.upper().replace('-','_') - locals()[name]=ns+' '+cond - ERRORS[ns+' '+cond]=[code,typ,text] - if code: _errorcodes[code]=cond -del ns,errname,errpool,err,cond,code,typ,text +ERRORS, _errorcodes={}, {} +for ns, errname, errpool in ((NS_XMPP_STREAMS, 'STREAM', xmpp_stream_error_conditions), + (NS_STANZAS, 'ERR', xmpp_stanza_error_conditions), + (NS_SASL, 'SASL', sasl_error_conditions)): + for err in errpool.split('\n')[1:]: + cond, code, typ, text=err.split(' -- ') + name=errname+'_'+cond.upper().replace('-', '_') + locals()[name]=ns+' '+cond + ERRORS[ns+' '+cond]=[code, typ, text] + if code: _errorcodes[code]=cond +del ns, errname, errpool, err, cond, code, typ, text def isResultNode(node): - ''' Returns true if the node is a positive reply. ''' - return node and node.getType()=='result' + ''' Returns true if the node is a positive reply. ''' + return node and node.getType()=='result' def isErrorNode(node): - ''' Returns true if the node is a negative reply. ''' - return node and node.getType()=='error' + ''' Returns true if the node is a negative reply. ''' + return node and node.getType()=='error' class NodeProcessed(Exception): - ''' Exception that should be raised by handler when the handling should be stopped. ''' + ''' Exception that should be raised by handler when the handling should be stopped. ''' class StreamError(Exception): - ''' Base exception class for stream errors.''' + ''' Base exception class for stream errors.''' class BadFormat(StreamError): pass class BadNamespacePrefix(StreamError): pass class Conflict(StreamError): pass @@ -238,589 +238,587 @@ class UnsupportedVersion(StreamError): pass class XMLNotWellFormed(StreamError): pass stream_exceptions = {'bad-format': BadFormat, - 'bad-namespace-prefix': BadNamespacePrefix, - 'conflict': Conflict, - 'connection-timeout': ConnectionTimeout, - 'host-gone': HostGone, - 'host-unknown': HostUnknown, - 'improper-addressing': ImproperAddressing, - 'internal-server-error': InternalServerError, - 'invalid-from': InvalidFrom, - 'invalid-id': InvalidID, - 'invalid-namespace': InvalidNamespace, - 'invalid-xml': InvalidXML, - 'not-authorized': NotAuthorized, - 'policy-violation': PolicyViolation, - 'remote-connection-failed': RemoteConnectionFailed, - 'resource-constraint': ResourceConstraint, - 'restricted-xml': RestrictedXML, - 'see-other-host': SeeOtherHost, - 'system-shutdown': SystemShutdown, - 'undefined-condition': UndefinedCondition, - 'unsupported-encoding': UnsupportedEncoding, - 'unsupported-stanza-type': UnsupportedStanzaType, - 'unsupported-version': UnsupportedVersion, - 'xml-not-well-formed': XMLNotWellFormed} + 'bad-namespace-prefix': BadNamespacePrefix, + 'conflict': Conflict, + 'connection-timeout': ConnectionTimeout, + 'host-gone': HostGone, + 'host-unknown': HostUnknown, + 'improper-addressing': ImproperAddressing, + 'internal-server-error': InternalServerError, + 'invalid-from': InvalidFrom, + 'invalid-id': InvalidID, + 'invalid-namespace': InvalidNamespace, + 'invalid-xml': InvalidXML, + 'not-authorized': NotAuthorized, + 'policy-violation': PolicyViolation, + 'remote-connection-failed': RemoteConnectionFailed, + 'resource-constraint': ResourceConstraint, + 'restricted-xml': RestrictedXML, + 'see-other-host': SeeOtherHost, + 'system-shutdown': SystemShutdown, + 'undefined-condition': UndefinedCondition, + 'unsupported-encoding': UnsupportedEncoding, + 'unsupported-stanza-type': UnsupportedStanzaType, + 'unsupported-version': UnsupportedVersion, + 'xml-not-well-formed': XMLNotWellFormed} class JID: - ''' JID object. JID can be built from string, modified, compared, serialised into string. ''' - def __init__(self, jid=None, node='', domain='', resource=''): - ''' Constructor. JID can be specified as string (jid argument) or as separate parts. - Examples: - JID('node@domain/resource') - JID(node='node',domain='domain.org') - ''' - if not jid and not domain: raise ValueError('JID must contain at least domain name') - elif type(jid)==type(self): self.node,self.domain,self.resource=jid.node,jid.domain,jid.resource - elif domain: self.node,self.domain,self.resource=node,domain,resource - else: - if jid.find('@')+1: self.node,jid=jid.split('@',1) - else: self.node='' - if jid.find('/')+1: self.domain,self.resource=jid.split('/',1) - else: self.domain,self.resource=jid,'' - def getNode(self): - ''' Return the node part of the JID ''' - return self.node - def setNode(self,node): - ''' Set the node part of the JID to new value. Specify None to remove the node part.''' - self.node=node.lower() - def getDomain(self): - ''' Return the domain part of the JID ''' - return self.domain - def setDomain(self,domain): - ''' Set the domain part of the JID to new value.''' - self.domain=domain.lower() - def getResource(self): - ''' Return the resource part of the JID ''' - return self.resource - def setResource(self,resource): - ''' Set the resource part of the JID to new value. Specify None to remove the resource part.''' - self.resource=resource - def getStripped(self): - ''' Return the bare representation of JID. I.e. string value w/o resource. ''' - return self.__str__(0) - def __eq__(self, other): - ''' Compare the JID to another instance or to string for equality. ''' - try: other=JID(other) - except ValueError: return 0 - return self.resource==other.resource and self.__str__(0) == other.__str__(0) - def __ne__(self, other): - ''' Compare the JID to another instance or to string for non-equality. ''' - return not self.__eq__(other) - def bareMatch(self, other): - ''' Compare the node and domain parts of the JID's for equality. ''' - return self.__str__(0) == JID(other).__str__(0) - def __str__(self,wresource=1): - ''' Serialise JID into string. ''' - if self.node: jid=self.node+'@'+self.domain - else: jid=self.domain - if wresource and self.resource: return jid+'/'+self.resource - return jid - def __hash__(self): - ''' Produce hash of the JID, Allows to use JID objects as keys of the dictionary. ''' - return hash(self.__str__()) + ''' JID object. JID can be built from string, modified, compared, serialised into string. ''' + def __init__(self, jid=None, node='', domain='', resource=''): + ''' Constructor. JID can be specified as string (jid argument) or as separate parts. + Examples: + JID('node@domain/resource') + JID(node='node',domain='domain.org') + ''' + if not jid and not domain: raise ValueError('JID must contain at least domain name') + elif type(jid)==type(self): self.node, self.domain, self.resource=jid.node, jid.domain, jid.resource + elif domain: self.node, self.domain, self.resource=node, domain, resource + else: + if jid.find('@')+1: self.node, jid=jid.split('@', 1) + else: self.node='' + if jid.find('/')+1: self.domain, self.resource=jid.split('/', 1) + else: self.domain, self.resource=jid, '' + def getNode(self): + ''' Return the node part of the JID ''' + return self.node + def setNode(self, node): + ''' Set the node part of the JID to new value. Specify None to remove the node part.''' + self.node=node.lower() + def getDomain(self): + ''' Return the domain part of the JID ''' + return self.domain + def setDomain(self, domain): + ''' Set the domain part of the JID to new value.''' + self.domain=domain.lower() + def getResource(self): + ''' Return the resource part of the JID ''' + return self.resource + def setResource(self, resource): + ''' Set the resource part of the JID to new value. Specify None to remove the resource part.''' + self.resource=resource + def getStripped(self): + ''' Return the bare representation of JID. I.e. string value w/o resource. ''' + return self.__str__(0) + def __eq__(self, other): + ''' Compare the JID to another instance or to string for equality. ''' + try: other=JID(other) + except ValueError: return 0 + return self.resource==other.resource and self.__str__(0) == other.__str__(0) + def __ne__(self, other): + ''' Compare the JID to another instance or to string for non-equality. ''' + return not self.__eq__(other) + def bareMatch(self, other): + ''' Compare the node and domain parts of the JID's for equality. ''' + return self.__str__(0) == JID(other).__str__(0) + def __str__(self,wresource=1): + ''' Serialise JID into string. ''' + if self.node: jid=self.node+'@'+self.domain + else: jid=self.domain + if wresource and self.resource: return jid+'/'+self.resource + return jid + def __hash__(self): + ''' Produce hash of the JID, Allows to use JID objects as keys of the dictionary. ''' + return hash(self.__str__()) class BOSHBody(Node): - ''' - tag that wraps usual XMPP stanzas in XMPP over BOSH - ''' - def __init__(self, attrs={}, payload=[], node=None): - Node.__init__(self, tag='body', attrs=attrs, payload=payload, node=node) - self.setNamespace(NS_HTTP_BIND) + ''' + tag that wraps usual XMPP stanzas in XMPP over BOSH + ''' + def __init__(self, attrs={}, payload=[], node=None): + Node.__init__(self, tag='body', attrs=attrs, payload=payload, node=node) + self.setNamespace(NS_HTTP_BIND) class Protocol(Node): - ''' A "stanza" object class. Contains methods that are common for presences, iqs and messages. ''' - def __init__(self, name=None, to=None, typ=None, frm=None, attrs={}, payload=[], timestamp=None, xmlns=None, node=None): - ''' Constructor, name is the name of the stanza i.e. 'message' or 'presence' or 'iq'. - to is the value of 'to' attribure, 'typ' - 'type' attribute - frn - from attribure, attrs - other attributes mapping, - payload - same meaning as for simplexml payload definition - timestamp - the time value that needs to be stamped over stanza - xmlns - namespace of top stanza node - node - parsed or unparsed stana to be taken as prototype. - ''' - if not attrs: attrs={} - if to: attrs['to']=to - if frm: attrs['from']=frm - if typ: attrs['type']=typ - Node.__init__(self, tag=name, attrs=attrs, payload=payload, node=node) - if not node and xmlns: self.setNamespace(xmlns) - if self['to']: self.setTo(self['to']) - if self['from']: self.setFrom(self['from']) - if node and type(self)==type(node) and self.__class__==node.__class__ and self.attrs.has_key('id'): del self.attrs['id'] - self.timestamp=None - for d in self.getTags('delay',namespace=NS_DELAY2): - try: - if d.getAttr('stamp') < self.getTimestamp2(): - self.setTimestamp(d.getAttr('stamp')) - except Exception: - pass - if not self.timestamp: - for x in self.getTags('x',namespace=NS_DELAY): - try: - if x.getAttr('stamp') < self.getTimestamp(): - self.setTimestamp(x.getAttr('stamp')) - except Exception: - pass - if timestamp is not None: self.setTimestamp(timestamp) # To auto-timestamp stanza just pass timestamp='' - def getTo(self): - ''' Return value of the 'to' attribute. ''' - try: return self['to'] - except: return None - def getFrom(self): - ''' Return value of the 'from' attribute. ''' - try: return self['from'] - except: return None - def getTimestamp(self): - ''' Return the timestamp in the 'yyyymmddThhmmss' format. ''' - if self.timestamp: return self.timestamp - return time.strftime('%Y%m%dT%H:%M:%S', time.gmtime()) - def getTimestamp2(self): - """ Return the timestamp in the 'yyyymmddThhmmss' format. """ - if self.timestamp: return self.timestamp - return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) - def getID(self): - ''' Return the value of the 'id' attribute. ''' - return self.getAttr('id') - def setTo(self,val): - ''' Set the value of the 'to' attribute. ''' - self.setAttr('to', JID(val)) - def getType(self): - ''' Return the value of the 'type' attribute. ''' - return self.getAttr('type') - def setFrom(self,val): - ''' Set the value of the 'from' attribute. ''' - self.setAttr('from', JID(val)) - def setType(self,val): - ''' Set the value of the 'type' attribute. ''' - self.setAttr('type', val) - def setID(self,val): - ''' Set the value of the 'id' attribute. ''' - self.setAttr('id', val) - def getError(self): - ''' Return the error-condition (if present) or the textual description of the error (otherwise). ''' - errtag=self.getTag('error') - if errtag: - for tag in errtag.getChildren(): - if tag.getName()<>'text': return tag.getName() - return errtag.getData() - def getErrorMsg(self): - ''' Return the textual description of the error (if present) or the error condition ''' - errtag=self.getTag('error') - if errtag: - for tag in errtag.getChildren(): - if tag.getName()=='text': return tag.getData() - return self.getError() - def getErrorCode(self): - ''' Return the error code. Obsolete. ''' - return self.getTagAttr('error','code') - def setError(self,error,code=None): - ''' Set the error code. Obsolete. Use error-conditions instead. ''' - if code: - if str(code) in _errorcodes.keys(): error=ErrorNode(_errorcodes[str(code)],text=error) - else: error=ErrorNode(ERR_UNDEFINED_CONDITION,code=code,typ='cancel',text=error) - elif type(error) in [type(''),type(u'')]: error=ErrorNode(error) - self.setType('error') - self.addChild(node=error) - def setTimestamp(self,val=None): - '''Set the timestamp. timestamp should be the yyyymmddThhmmss string.''' - if not val: val=time.strftime('%Y%m%dT%H:%M:%S', time.gmtime()) - self.timestamp=val - self.setTag('x',{'stamp':self.timestamp},namespace=NS_DELAY) - def getProperties(self): - ''' Return the list of namespaces to which belongs the direct childs of element''' - props=[] - for child in self.getChildren(): - prop=child.getNamespace() - if prop not in props: props.append(prop) - return props - def __setitem__(self,item,val): - ''' Set the item 'item' to the value 'val'.''' - if item in ['to','from']: val=JID(val) - return self.setAttr(item,val) + ''' A "stanza" object class. Contains methods that are common for presences, iqs and messages. ''' + def __init__(self, name=None, to=None, typ=None, frm=None, attrs={}, payload=[], timestamp=None, xmlns=None, node=None): + ''' Constructor, name is the name of the stanza i.e. 'message' or 'presence' or 'iq'. + to is the value of 'to' attribure, 'typ' - 'type' attribute + frn - from attribure, attrs - other attributes mapping, + payload - same meaning as for simplexml payload definition + timestamp - the time value that needs to be stamped over stanza + xmlns - namespace of top stanza node + node - parsed or unparsed stana to be taken as prototype. + ''' + if not attrs: attrs={} + if to: attrs['to']=to + if frm: attrs['from']=frm + if typ: attrs['type']=typ + Node.__init__(self, tag=name, attrs=attrs, payload=payload, node=node) + if not node and xmlns: self.setNamespace(xmlns) + if self['to']: self.setTo(self['to']) + if self['from']: self.setFrom(self['from']) + if node and type(self)==type(node) and self.__class__==node.__class__ and self.attrs.has_key('id'): del self.attrs['id'] + self.timestamp=None + for d in self.getTags('delay', namespace=NS_DELAY2): + try: + if d.getAttr('stamp') < self.getTimestamp2(): + self.setTimestamp(d.getAttr('stamp')) + except Exception: + pass + if not self.timestamp: + for x in self.getTags('x', namespace=NS_DELAY): + try: + if x.getAttr('stamp') < self.getTimestamp(): + self.setTimestamp(x.getAttr('stamp')) + except Exception: + pass + if timestamp is not None: self.setTimestamp(timestamp) # To auto-timestamp stanza just pass timestamp='' + def getTo(self): + ''' Return value of the 'to' attribute. ''' + try: return self['to'] + except: return None + def getFrom(self): + ''' Return value of the 'from' attribute. ''' + try: return self['from'] + except: return None + def getTimestamp(self): + ''' Return the timestamp in the 'yyyymmddThhmmss' format. ''' + if self.timestamp: return self.timestamp + return time.strftime('%Y%m%dT%H:%M:%S', time.gmtime()) + def getTimestamp2(self): + """ Return the timestamp in the 'yyyymmddThhmmss' format. """ + if self.timestamp: return self.timestamp + return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) + def getID(self): + ''' Return the value of the 'id' attribute. ''' + return self.getAttr('id') + def setTo(self, val): + ''' Set the value of the 'to' attribute. ''' + self.setAttr('to', JID(val)) + def getType(self): + ''' Return the value of the 'type' attribute. ''' + return self.getAttr('type') + def setFrom(self, val): + ''' Set the value of the 'from' attribute. ''' + self.setAttr('from', JID(val)) + def setType(self, val): + ''' Set the value of the 'type' attribute. ''' + self.setAttr('type', val) + def setID(self, val): + ''' Set the value of the 'id' attribute. ''' + self.setAttr('id', val) + def getError(self): + ''' Return the error-condition (if present) or the textual description of the error (otherwise). ''' + errtag=self.getTag('error') + if errtag: + for tag in errtag.getChildren(): + if tag.getName()<>'text': return tag.getName() + return errtag.getData() + def getErrorMsg(self): + ''' Return the textual description of the error (if present) or the error condition ''' + errtag=self.getTag('error') + if errtag: + for tag in errtag.getChildren(): + if tag.getName()=='text': return tag.getData() + return self.getError() + def getErrorCode(self): + ''' Return the error code. Obsolete. ''' + return self.getTagAttr('error', 'code') + def setError(self,error,code=None): + ''' Set the error code. Obsolete. Use error-conditions instead. ''' + if code: + if str(code) in _errorcodes.keys(): error=ErrorNode(_errorcodes[str(code)], text=error) + else: error=ErrorNode(ERR_UNDEFINED_CONDITION, code=code, typ='cancel', text=error) + elif type(error) in [type(''), type(u'')]: error=ErrorNode(error) + self.setType('error') + self.addChild(node=error) + def setTimestamp(self,val=None): + '''Set the timestamp. timestamp should be the yyyymmddThhmmss string.''' + if not val: val=time.strftime('%Y%m%dT%H:%M:%S', time.gmtime()) + self.timestamp=val + self.setTag('x', {'stamp':self.timestamp}, namespace=NS_DELAY) + def getProperties(self): + ''' Return the list of namespaces to which belongs the direct childs of element''' + props=[] + for child in self.getChildren(): + prop=child.getNamespace() + if prop not in props: props.append(prop) + return props + def __setitem__(self, item, val): + ''' Set the item 'item' to the value 'val'.''' + if item in ['to', 'from']: val=JID(val) + return self.setAttr(item, val) class Message(Protocol): - ''' XMPP Message stanza - "push" mechanism.''' - def __init__(self, to=None, body=None, xhtml=None, typ=None, subject=None, attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, node=None): - ''' Create message object. You can specify recipient, text of message, type of message - any additional attributes, sender of the message, any additional payload (f.e. jabber:x:delay element) and namespace in one go. - Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as message. ''' - Protocol.__init__(self, 'message', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node) - if body: self.setBody(body) - if xhtml: self.setXHTML(xhtml) - if subject is not None: self.setSubject(subject) - def getBody(self): - ''' Returns text of the message. ''' - return self.getTagData('body') - def getXHTML(self, xmllang=None): - ''' Returns serialized xhtml-im element text of the message. + ''' XMPP Message stanza - "push" mechanism.''' + def __init__(self, to=None, body=None, xhtml=None, typ=None, subject=None, attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, node=None): + ''' Create message object. You can specify recipient, text of message, type of message + any additional attributes, sender of the message, any additional payload (f.e. jabber:x:delay element) and namespace in one go. + Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as message. ''' + Protocol.__init__(self, 'message', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node) + if body: self.setBody(body) + if xhtml: self.setXHTML(xhtml) + if subject is not None: self.setSubject(subject) + def getBody(self): + ''' Returns text of the message. ''' + return self.getTagData('body') + def getXHTML(self, xmllang=None): + ''' Returns serialized xhtml-im element text of the message. - TODO: Returning a DOM could make rendering faster.''' - xhtml = self.getTag('html') - if xhtml: - if xmllang: - body = xhtml.getTag('body', attrs={'xml:lang':xmllang}) - else: - body = xhtml.getTag('body') - return str(body) - return None - def getSubject(self): - ''' Returns subject of the message. ''' - return self.getTagData('subject') - def getThread(self): - ''' Returns thread of the message. ''' - return self.getTagData('thread') - def setBody(self,val): - ''' Sets the text of the message. ''' - self.setTagData('body',val) + TODO: Returning a DOM could make rendering faster.''' + xhtml = self.getTag('html') + if xhtml: + if xmllang: + body = xhtml.getTag('body', attrs={'xml:lang':xmllang}) + else: + body = xhtml.getTag('body') + return str(body) + return None + def getSubject(self): + ''' Returns subject of the message. ''' + return self.getTagData('subject') + def getThread(self): + ''' Returns thread of the message. ''' + return self.getTagData('thread') + def setBody(self, val): + ''' Sets the text of the message. ''' + self.setTagData('body', val) - def setXHTML(self,val,xmllang=None): - ''' Sets the xhtml text of the message (XEP-0071). - The parameter is the "inner html" to the body.''' - try: - if xmllang: - dom = NodeBuilder('' + val + '').getDom() - else: - dom = NodeBuilder(''+val+'',0).getDom() - if self.getTag('html'): - self.getTag('html').addChild(node=dom) - else: - self.setTag('html',namespace=NS_XHTML_IM).addChild(node=dom) - except Exception, e: - print "Error", e - #FIXME: log. we could not set xhtml (parse error, whatever) - def setSubject(self,val): - ''' Sets the subject of the message. ''' - self.setTagData('subject',val) - def setThread(self,val): - ''' Sets the thread of the message. ''' - self.setTagData('thread',val) - def buildReply(self,text=None): - ''' Builds and returns another message object with specified text. - The to, from and thread properties of new message are pre-set as reply to this message. ''' - m=Message(to=self.getFrom(),frm=self.getTo(),body=text) - th=self.getThread() - if th: m.setThread(th) - return m - def getStatusCode(self): - '''Returns the status code of the message (for groupchat config - change)''' - attrs = [] - for xtag in self.getTags('x'): - for child in xtag.getTags('status'): - attrs.append(child.getAttr('code')) - return attrs + def setXHTML(self,val,xmllang=None): + ''' Sets the xhtml text of the message (XEP-0071). + The parameter is the "inner html" to the body.''' + try: + if xmllang: + dom = NodeBuilder('' + val + '').getDom() + else: + dom = NodeBuilder(''+val+'', 0).getDom() + if self.getTag('html'): + self.getTag('html').addChild(node=dom) + else: + self.setTag('html', namespace=NS_XHTML_IM).addChild(node=dom) + except Exception, e: + print "Error", e + #FIXME: log. we could not set xhtml (parse error, whatever) + def setSubject(self, val): + ''' Sets the subject of the message. ''' + self.setTagData('subject', val) + def setThread(self, val): + ''' Sets the thread of the message. ''' + self.setTagData('thread', val) + def buildReply(self,text=None): + ''' Builds and returns another message object with specified text. + The to, from and thread properties of new message are pre-set as reply to this message. ''' + m=Message(to=self.getFrom(), frm=self.getTo(), body=text) + th=self.getThread() + if th: m.setThread(th) + return m + def getStatusCode(self): + '''Returns the status code of the message (for groupchat config + change)''' + attrs = [] + for xtag in self.getTags('x'): + for child in xtag.getTags('status'): + attrs.append(child.getAttr('code')) + return attrs class Presence(Protocol): - ''' XMPP Presence object.''' - def __init__(self, to=None, typ=None, priority=None, show=None, status=None, attrs={}, frm=None, timestamp=None, payload=[], xmlns=NS_CLIENT, node=None): - ''' Create presence object. You can specify recipient, type of message, priority, show and status values - any additional attributes, sender of the presence, timestamp, any additional payload (f.e. jabber:x:delay element) and namespace in one go. - Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as presence. ''' - Protocol.__init__(self, 'presence', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node) - if priority: self.setPriority(priority) - if show: self.setShow(show) - if status: self.setStatus(status) - def getPriority(self): - ''' Returns the priority of the message. ''' - return self.getTagData('priority') - def getShow(self): - ''' Returns the show value of the message. ''' - return self.getTagData('show') - def getStatus(self): - ''' Returns the status string of the message. ''' - return self.getTagData('status') - def setPriority(self,val): - ''' Sets the priority of the message. ''' - self.setTagData('priority',val) - def setShow(self,val): - ''' Sets the show value of the message. ''' - self.setTagData('show',val) - def setStatus(self,val): - ''' Sets the status string of the message. ''' - self.setTagData('status',val) + ''' XMPP Presence object.''' + def __init__(self, to=None, typ=None, priority=None, show=None, status=None, attrs={}, frm=None, timestamp=None, payload=[], xmlns=NS_CLIENT, node=None): + ''' Create presence object. You can specify recipient, type of message, priority, show and status values + any additional attributes, sender of the presence, timestamp, any additional payload (f.e. jabber:x:delay element) and namespace in one go. + Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as presence. ''' + Protocol.__init__(self, 'presence', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node) + if priority: self.setPriority(priority) + if show: self.setShow(show) + if status: self.setStatus(status) + def getPriority(self): + ''' Returns the priority of the message. ''' + return self.getTagData('priority') + def getShow(self): + ''' Returns the show value of the message. ''' + return self.getTagData('show') + def getStatus(self): + ''' Returns the status string of the message. ''' + return self.getTagData('status') + def setPriority(self, val): + ''' Sets the priority of the message. ''' + self.setTagData('priority', val) + def setShow(self, val): + ''' Sets the show value of the message. ''' + self.setTagData('show', val) + def setStatus(self, val): + ''' Sets the status string of the message. ''' + self.setTagData('status', val) - def _muc_getItemAttr(self,tag,attr): - for xtag in self.getTags('x'): - if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN): - continue - for child in xtag.getTags(tag): - return child.getAttr(attr) - def _muc_getSubTagDataAttr(self,tag,attr): - for xtag in self.getTags('x'): - if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN): - continue - for child in xtag.getTags('item'): - for cchild in child.getTags(tag): - return cchild.getData(),cchild.getAttr(attr) - return None,None - def getRole(self): - '''Returns the presence role (for groupchat)''' - return self._muc_getItemAttr('item','role') - def getAffiliation(self): - '''Returns the presence affiliation (for groupchat)''' - return self._muc_getItemAttr('item','affiliation') - def getNewNick(self): - '''Returns the status code of the presence (for groupchat)''' - return self._muc_getItemAttr('item','nick') - def getJid(self): - '''Returns the presence jid (for groupchat)''' - return self._muc_getItemAttr('item','jid') - def getReason(self): - '''Returns the reason of the presence (for groupchat)''' - return self._muc_getSubTagDataAttr('reason','')[0] - def getActor(self): - '''Returns the reason of the presence (for groupchat)''' - return self._muc_getSubTagDataAttr('actor','jid')[1] - def getStatusCode(self): - '''Returns the status code of the presence (for groupchat)''' - attrs = [] - for xtag in self.getTags('x'): - for child in xtag.getTags('status'): - attrs.append(child.getAttr('code')) - return attrs + def _muc_getItemAttr(self, tag, attr): + for xtag in self.getTags('x'): + if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN): + continue + for child in xtag.getTags(tag): + return child.getAttr(attr) + def _muc_getSubTagDataAttr(self, tag, attr): + for xtag in self.getTags('x'): + if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN): + continue + for child in xtag.getTags('item'): + for cchild in child.getTags(tag): + return cchild.getData(), cchild.getAttr(attr) + return None, None + def getRole(self): + '''Returns the presence role (for groupchat)''' + return self._muc_getItemAttr('item', 'role') + def getAffiliation(self): + '''Returns the presence affiliation (for groupchat)''' + return self._muc_getItemAttr('item', 'affiliation') + def getNewNick(self): + '''Returns the status code of the presence (for groupchat)''' + return self._muc_getItemAttr('item', 'nick') + def getJid(self): + '''Returns the presence jid (for groupchat)''' + return self._muc_getItemAttr('item', 'jid') + def getReason(self): + '''Returns the reason of the presence (for groupchat)''' + return self._muc_getSubTagDataAttr('reason', '')[0] + def getActor(self): + '''Returns the reason of the presence (for groupchat)''' + return self._muc_getSubTagDataAttr('actor', 'jid')[1] + def getStatusCode(self): + '''Returns the status code of the presence (for groupchat)''' + attrs = [] + for xtag in self.getTags('x'): + for child in xtag.getTags('status'): + attrs.append(child.getAttr('code')) + return attrs class Iq(Protocol): - ''' XMPP Iq object - get/set dialog mechanism. ''' - def __init__(self, typ=None, queryNS=None, attrs={}, to=None, frm=None, payload=[], xmlns=NS_CLIENT, node=None): - ''' Create Iq object. You can specify type, query namespace - any additional attributes, recipient of the iq, sender of the iq, any additional payload (f.e. jabber:x:data node) and namespace in one go. - Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as an iq. ''' - Protocol.__init__(self, 'iq', to=to, typ=typ, attrs=attrs, frm=frm, xmlns=xmlns, node=node) - if payload: self.setQueryPayload(payload) - if queryNS: self.setQueryNS(queryNS) - def getQueryNS(self): - ''' Return the namespace of the 'query' child element.''' - tag=self.getTag('query') - if tag: return tag.getNamespace() - def getQuerynode(self): - ''' Return the 'node' attribute value of the 'query' child element.''' - return self.getTagAttr('query','node') - def getQueryPayload(self): - ''' Return the 'query' child element payload.''' - tag=self.getTag('query') - if tag: return tag.getPayload() - def getQueryChildren(self): - ''' Return the 'query' child element child nodes.''' - tag=self.getTag('query') - if tag: return tag.getChildren() - def setQueryNS(self,namespace): - ''' Set the namespace of the 'query' child element.''' - self.setTag('query').setNamespace(namespace) - def setQueryPayload(self,payload): - ''' Set the 'query' child element payload.''' - self.setTag('query').setPayload(payload) - def setQuerynode(self,node): - ''' Set the 'node' attribute value of the 'query' child element.''' - self.setTagAttr('query','node',node) - def buildReply(self,typ): - ''' Builds and returns another Iq object of specified type. - The to, from and query child node of new Iq are pre-set as reply to this Iq. ''' - iq=Iq(typ,to=self.getFrom(),frm=self.getTo(),attrs={'id':self.getID()}) - if self.getTag('query'): iq.setQueryNS(self.getQueryNS()) - return iq + ''' XMPP Iq object - get/set dialog mechanism. ''' + def __init__(self, typ=None, queryNS=None, attrs={}, to=None, frm=None, payload=[], xmlns=NS_CLIENT, node=None): + ''' Create Iq object. You can specify type, query namespace + any additional attributes, recipient of the iq, sender of the iq, any additional payload (f.e. jabber:x:data node) and namespace in one go. + Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as an iq. ''' + Protocol.__init__(self, 'iq', to=to, typ=typ, attrs=attrs, frm=frm, xmlns=xmlns, node=node) + if payload: self.setQueryPayload(payload) + if queryNS: self.setQueryNS(queryNS) + def getQueryNS(self): + ''' Return the namespace of the 'query' child element.''' + tag=self.getTag('query') + if tag: return tag.getNamespace() + def getQuerynode(self): + ''' Return the 'node' attribute value of the 'query' child element.''' + return self.getTagAttr('query', 'node') + def getQueryPayload(self): + ''' Return the 'query' child element payload.''' + tag=self.getTag('query') + if tag: return tag.getPayload() + def getQueryChildren(self): + ''' Return the 'query' child element child nodes.''' + tag=self.getTag('query') + if tag: return tag.getChildren() + def setQueryNS(self, namespace): + ''' Set the namespace of the 'query' child element.''' + self.setTag('query').setNamespace(namespace) + def setQueryPayload(self, payload): + ''' Set the 'query' child element payload.''' + self.setTag('query').setPayload(payload) + def setQuerynode(self, node): + ''' Set the 'node' attribute value of the 'query' child element.''' + self.setTagAttr('query', 'node', node) + def buildReply(self, typ): + ''' Builds and returns another Iq object of specified type. + The to, from and query child node of new Iq are pre-set as reply to this Iq. ''' + iq=Iq(typ, to=self.getFrom(), frm=self.getTo(), attrs={'id':self.getID()}) + if self.getTag('query'): iq.setQueryNS(self.getQueryNS()) + return iq class ErrorNode(Node): - ''' XMPP-style error element. - In the case of stanza error should be attached to XMPP stanza. - In the case of stream-level errors should be used separately. ''' - def __init__(self,name,code=None,typ=None,text=None): - ''' Create new error node object. - Mandatory parameter: name - name of error condition. - Optional parameters: code, typ, text. Used for backwards compartibility with older jabber protocol.''' - if name in ERRORS: - cod,type_,txt=ERRORS[name] - ns=name.split()[0] - else: cod,ns,type_,txt='500',NS_STANZAS,'cancel','' - if typ: type_=typ - if code: cod=code - if text: txt=text - Node.__init__(self,'error',{},[Node(name)]) - if type_: self.setAttr('type',type_) - if not cod: self.setName('stream:error') - if txt: self.addChild(node=Node(ns+' text',{},[txt])) - if cod: self.setAttr('code',cod) + ''' XMPP-style error element. + In the case of stanza error should be attached to XMPP stanza. + In the case of stream-level errors should be used separately. ''' + def __init__(self,name,code=None,typ=None,text=None): + ''' Create new error node object. + Mandatory parameter: name - name of error condition. + Optional parameters: code, typ, text. Used for backwards compartibility with older jabber protocol.''' + if name in ERRORS: + cod, type_, txt=ERRORS[name] + ns=name.split()[0] + else: cod, ns, type_, txt='500', NS_STANZAS, 'cancel', '' + if typ: type_=typ + if code: cod=code + if text: txt=text + Node.__init__(self, 'error', {}, [Node(name)]) + if type_: self.setAttr('type', type_) + if not cod: self.setName('stream:error') + if txt: self.addChild(node=Node(ns+' text', {}, [txt])) + if cod: self.setAttr('code', cod) class Error(Protocol): - ''' Used to quickly transform received stanza into error reply.''' - def __init__(self,node,error,reply=1): - ''' Create error reply basing on the received 'node' stanza and the 'error' error condition. - If the 'node' is not the received stanza but locally created ('to' and 'from' fields needs not swapping) - specify the 'reply' argument as false.''' - if reply: Protocol.__init__(self,to=node.getFrom(),frm=node.getTo(),node=node) - else: Protocol.__init__(self,node=node) - self.setError(error) - if node.getType()=='error': self.__str__=self.__dupstr__ - def __dupstr__(self,dup1=None,dup2=None): - ''' Dummy function used as preventor of creating error node in reply to error node. - I.e. you will not be able to serialise "double" error into string. - ''' - return '' + ''' Used to quickly transform received stanza into error reply.''' + def __init__(self,node,error,reply=1): + ''' Create error reply basing on the received 'node' stanza and the 'error' error condition. + If the 'node' is not the received stanza but locally created ('to' and 'from' fields needs not swapping) + specify the 'reply' argument as false.''' + if reply: Protocol.__init__(self, to=node.getFrom(), frm=node.getTo(), node=node) + else: Protocol.__init__(self, node=node) + self.setError(error) + if node.getType()=='error': self.__str__=self.__dupstr__ + def __dupstr__(self,dup1=None,dup2=None): + ''' Dummy function used as preventor of creating error node in reply to error node. + I.e. you will not be able to serialise "double" error into string. + ''' + return '' class DataField(Node): - ''' This class is used in the DataForm class to describe the single data item. - If you are working with jabber:x:data (XEP-0004, XEP-0068, XEP-0122) - then you will need to work with instances of this class. ''' - def __init__(self,name=None,value=None,typ=None,required=0,desc=None,options=[],node=None): - ''' Create new data field of specified name,value and type. - Also 'required','desc' and 'options' fields can be set. - Alternatively other XML object can be passed in as the 'node' parameted to replicate it as a new datafiled. - ''' - Node.__init__(self,'field',node=node) - if name: self.setVar(name) - if isinstance(value, (list, tuple)): self.setValues(value) - elif value: self.setValue(value) - if typ: self.setType(typ) - elif not typ and not node: self.setType('text-single') - if required: self.setRequired(required) - if desc: self.setDesc(desc) - if options: self.setOptions(options) - def setRequired(self,req=1): - ''' Change the state of the 'required' flag. ''' - if req: self.setTag('required') - else: - try: self.delChild('required') - except ValueError: return - def isRequired(self): - ''' Returns in this field a required one. ''' - return self.getTag('required') - def setDesc(self,desc): - ''' Set the description of this field. ''' - self.setTagData('desc',desc) - def getDesc(self): - ''' Return the description of this field. ''' - return self.getTagData('desc') - def setValue(self,val): - ''' Set the value of this field. ''' - self.setTagData('value',val) - def getValue(self): - return self.getTagData('value') - def setValues(self,lst): - ''' Set the values of this field as values-list. - Replaces all previous filed values! If you need to just add a value - use addValue method.''' - while self.getTag('value'): self.delChild('value') - for val in lst: self.addValue(val) - def addValue(self,val): - ''' Add one more value to this field. Used in 'get' iq's or such.''' - self.addChild('value',{},[val]) - def getValues(self): - ''' Return the list of values associated with this field.''' - ret=[] - for tag in self.getTags('value'): ret.append(tag.getData()) - return ret - def getOptions(self): - ''' Return label-option pairs list associated with this field.''' - ret=[] - for tag in self.getTags('option'): ret.append([tag.getAttr('label'),tag.getTagData('value')]) - return ret - def setOptions(self,lst): - ''' Set label-option pairs list associated with this field.''' - while self.getTag('option'): self.delChild('option') - for opt in lst: self.addOption(opt) - def addOption(self,opt): - ''' Add one more label-option pair to this field.''' - if isinstance(opt, basestring): self.addChild('option').setTagData('value',opt) - else: self.addChild('option',{'label':opt[0]}).setTagData('value',opt[1]) - def getType(self): - ''' Get type of this field. ''' - return self.getAttr('type') - def setType(self,val): - ''' Set type of this field. ''' - return self.setAttr('type',val) - def getVar(self): - ''' Get 'var' attribute value of this field. ''' - return self.getAttr('var') - def setVar(self,val): - ''' Set 'var' attribute value of this field. ''' - return self.setAttr('var',val) + ''' This class is used in the DataForm class to describe the single data item. + If you are working with jabber:x:data (XEP-0004, XEP-0068, XEP-0122) + then you will need to work with instances of this class. ''' + def __init__(self,name=None,value=None,typ=None,required=0,desc=None,options=[],node=None): + ''' Create new data field of specified name,value and type. + Also 'required','desc' and 'options' fields can be set. + Alternatively other XML object can be passed in as the 'node' parameted to replicate it as a new datafiled. + ''' + Node.__init__(self, 'field', node=node) + if name: self.setVar(name) + if isinstance(value, (list, tuple)): self.setValues(value) + elif value: self.setValue(value) + if typ: self.setType(typ) + elif not typ and not node: self.setType('text-single') + if required: self.setRequired(required) + if desc: self.setDesc(desc) + if options: self.setOptions(options) + def setRequired(self,req=1): + ''' Change the state of the 'required' flag. ''' + if req: self.setTag('required') + else: + try: self.delChild('required') + except ValueError: return + def isRequired(self): + ''' Returns in this field a required one. ''' + return self.getTag('required') + def setDesc(self, desc): + ''' Set the description of this field. ''' + self.setTagData('desc', desc) + def getDesc(self): + ''' Return the description of this field. ''' + return self.getTagData('desc') + def setValue(self, val): + ''' Set the value of this field. ''' + self.setTagData('value', val) + def getValue(self): + return self.getTagData('value') + def setValues(self, lst): + ''' Set the values of this field as values-list. + Replaces all previous filed values! If you need to just add a value - use addValue method.''' + while self.getTag('value'): self.delChild('value') + for val in lst: self.addValue(val) + def addValue(self, val): + ''' Add one more value to this field. Used in 'get' iq's or such.''' + self.addChild('value', {}, [val]) + def getValues(self): + ''' Return the list of values associated with this field.''' + ret=[] + for tag in self.getTags('value'): ret.append(tag.getData()) + return ret + def getOptions(self): + ''' Return label-option pairs list associated with this field.''' + ret=[] + for tag in self.getTags('option'): ret.append([tag.getAttr('label'), tag.getTagData('value')]) + return ret + def setOptions(self, lst): + ''' Set label-option pairs list associated with this field.''' + while self.getTag('option'): self.delChild('option') + for opt in lst: self.addOption(opt) + def addOption(self, opt): + ''' Add one more label-option pair to this field.''' + if isinstance(opt, basestring): self.addChild('option').setTagData('value', opt) + else: self.addChild('option', {'label':opt[0]}).setTagData('value', opt[1]) + def getType(self): + ''' Get type of this field. ''' + return self.getAttr('type') + def setType(self, val): + ''' Set type of this field. ''' + return self.setAttr('type', val) + def getVar(self): + ''' Get 'var' attribute value of this field. ''' + return self.getAttr('var') + def setVar(self, val): + ''' Set 'var' attribute value of this field. ''' + return self.setAttr('var', val) class DataForm(Node): - ''' DataForm class. Used for manipulating dataforms in XMPP. - Relevant XEPs: 0004, 0068, 0122. - Can be used in disco, pub-sub and many other applications.''' - def __init__(self, typ=None, data=[], title=None, node=None): - ''' - Create new dataform of type 'typ'. 'data' is the list of DataField - instances that this dataform contains, 'title' - the title string. - You can specify the 'node' argument as the other node to be used as - base for constructing this dataform. + ''' DataForm class. Used for manipulating dataforms in XMPP. + Relevant XEPs: 0004, 0068, 0122. + Can be used in disco, pub-sub and many other applications.''' + def __init__(self, typ=None, data=[], title=None, node=None): + ''' + Create new dataform of type 'typ'. 'data' is the list of DataField + instances that this dataform contains, 'title' - the title string. + You can specify the 'node' argument as the other node to be used as + base for constructing this dataform. - title and instructions is optional and SHOULD NOT contain newlines. - Several instructions MAY be present. - 'typ' can be one of ('form' | 'submit' | 'cancel' | 'result' ) - 'typ' of reply iq can be ( 'result' | 'set' | 'set' | 'result' ) respectively. - 'cancel' form can not contain any fields. All other forms contains AT LEAST one field. - 'title' MAY be included in forms of type "form" and "result" - ''' - Node.__init__(self,'x',node=node) - if node: - newkids=[] - for n in self.getChildren(): - if n.getName()=='field': newkids.append(DataField(node=n)) - else: newkids.append(n) - self.kids=newkids - if typ: self.setType(typ) - self.setNamespace(NS_DATA) - if title: self.setTitle(title) - if isinstance(data, dict): - newdata=[] - for name in data.keys(): newdata.append(DataField(name,data[name])) - data=newdata - for child in data: - if isinstance(child, basestring): self.addInstructions(child) - elif child.__class__.__name__=='DataField': self.kids.append(child) - else: self.kids.append(DataField(node=child)) - def getType(self): - ''' Return the type of dataform. ''' - return self.getAttr('type') - def setType(self,typ): - ''' Set the type of dataform. ''' - self.setAttr('type',typ) - def getTitle(self): - ''' Return the title of dataform. ''' - return self.getTagData('title') - def setTitle(self,text): - ''' Set the title of dataform. ''' - self.setTagData('title',text) - def getInstructions(self): - ''' Return the instructions of dataform. ''' - return self.getTagData('instructions') - def setInstructions(self,text): - ''' Set the instructions of dataform. ''' - self.setTagData('instructions',text) - def addInstructions(self,text): - ''' Add one more instruction to the dataform. ''' - self.addChild('instructions',{},[text]) - def getField(self,name): - ''' Return the datafield object with name 'name' (if exists). ''' - return self.getTag('field',attrs={'var':name}) - def setField(self,name): - ''' Create if nessessary or get the existing datafield object with name 'name' and return it. ''' - f=self.getField(name) - if f: return f - return self.addChild(node=DataField(name)) - def asDict(self): - ''' Represent dataform as simple dictionary mapping of datafield names to their values.''' - ret={} - for field in self.getTags('field'): - name=field.getAttr('var') - typ=field.getType() - if isinstance(typ, basestring) and typ.endswith('-multi'): - val=[] - for i in field.getTags('value'): val.append(i.getData()) - else: val=field.getTagData('value') - ret[name]=val - if self.getTag('instructions'): ret['instructions']=self.getInstructions() - return ret - def __getitem__(self,name): - ''' Simple dictionary interface for getting datafields values by their names.''' - item=self.getField(name) - if item: return item.getValue() - raise IndexError('No such field') - def __setitem__(self,name,val): - ''' Simple dictionary interface for setting datafields values by their names.''' - return self.setField(name).setValue(val) - -# vim: se ts=3: + title and instructions is optional and SHOULD NOT contain newlines. + Several instructions MAY be present. + 'typ' can be one of ('form' | 'submit' | 'cancel' | 'result' ) + 'typ' of reply iq can be ( 'result' | 'set' | 'set' | 'result' ) respectively. + 'cancel' form can not contain any fields. All other forms contains AT LEAST one field. + 'title' MAY be included in forms of type "form" and "result" + ''' + Node.__init__(self, 'x', node=node) + if node: + newkids=[] + for n in self.getChildren(): + if n.getName()=='field': newkids.append(DataField(node=n)) + else: newkids.append(n) + self.kids=newkids + if typ: self.setType(typ) + self.setNamespace(NS_DATA) + if title: self.setTitle(title) + if isinstance(data, dict): + newdata=[] + for name in data.keys(): newdata.append(DataField(name, data[name])) + data=newdata + for child in data: + if isinstance(child, basestring): self.addInstructions(child) + elif child.__class__.__name__=='DataField': self.kids.append(child) + else: self.kids.append(DataField(node=child)) + def getType(self): + ''' Return the type of dataform. ''' + return self.getAttr('type') + def setType(self, typ): + ''' Set the type of dataform. ''' + self.setAttr('type', typ) + def getTitle(self): + ''' Return the title of dataform. ''' + return self.getTagData('title') + def setTitle(self, text): + ''' Set the title of dataform. ''' + self.setTagData('title', text) + def getInstructions(self): + ''' Return the instructions of dataform. ''' + return self.getTagData('instructions') + def setInstructions(self, text): + ''' Set the instructions of dataform. ''' + self.setTagData('instructions', text) + def addInstructions(self, text): + ''' Add one more instruction to the dataform. ''' + self.addChild('instructions', {}, [text]) + def getField(self, name): + ''' Return the datafield object with name 'name' (if exists). ''' + return self.getTag('field', attrs={'var':name}) + def setField(self, name): + ''' Create if nessessary or get the existing datafield object with name 'name' and return it. ''' + f=self.getField(name) + if f: return f + return self.addChild(node=DataField(name)) + def asDict(self): + ''' Represent dataform as simple dictionary mapping of datafield names to their values.''' + ret={} + for field in self.getTags('field'): + name=field.getAttr('var') + typ=field.getType() + if isinstance(typ, basestring) and typ.endswith('-multi'): + val=[] + for i in field.getTags('value'): val.append(i.getData()) + else: val=field.getTagData('value') + ret[name]=val + if self.getTag('instructions'): ret['instructions']=self.getInstructions() + return ret + def __getitem__(self, name): + ''' Simple dictionary interface for getting datafields values by their names.''' + item=self.getField(name) + if item: return item.getValue() + raise IndexError('No such field') + def __setitem__(self, name, val): + ''' Simple dictionary interface for setting datafields values by their names.''' + return self.setField(name).setValue(val) diff --git a/src/common/xmpp/proxy_connectors.py b/src/common/xmpp/proxy_connectors.py index e4fb4dbd8..3587ae5b2 100644 --- a/src/common/xmpp/proxy_connectors.py +++ b/src/common/xmpp/proxy_connectors.py @@ -25,212 +25,210 @@ import logging log = logging.getLogger('gajim.c.x.proxy_connectors') class ProxyConnector: - ''' - Interface for proxy-connecting object - when tunnneling XMPP over proxies, - some connecting process usually has to be done before opening stream. - Proxy connectors are used right after TCP connection is estabilished. - ''' - def __init__(self, send_method, onreceive, old_on_receive, on_success, - on_failure, xmpp_server, proxy_creds=(None,None)): - ''' - Creates proxy connector, starts connecting immediately and gives control - back to transport afterwards. + ''' + Interface for proxy-connecting object - when tunnneling XMPP over proxies, + some connecting process usually has to be done before opening stream. + Proxy connectors are used right after TCP connection is estabilished. + ''' + def __init__(self, send_method, onreceive, old_on_receive, on_success, + on_failure, xmpp_server, proxy_creds=(None, None)): + ''' + Creates proxy connector, starts connecting immediately and gives control + back to transport afterwards. - :param send_method: transport send method - :param onreceive: method to set on_receive callbacks - :param old_on_receive: on_receive callback that should be set when - proxy connection was successful - :param on_success: called after proxy connection was successfully opened - :param on_failure: called when errors occured while connecting - :param xmpp_server: tuple of (hostname, port) - :param proxy_creds: tuple of (proxy_user, proxy_credentials) - ''' - self.send = send_method - self.onreceive = onreceive - self.old_on_receive = old_on_receive - self.on_success = on_success - self.on_failure = on_failure - self.xmpp_server = xmpp_server - self.proxy_user, self.proxy_pass = proxy_creds - self.old_on_receive = old_on_receive + :param send_method: transport send method + :param onreceive: method to set on_receive callbacks + :param old_on_receive: on_receive callback that should be set when + proxy connection was successful + :param on_success: called after proxy connection was successfully opened + :param on_failure: called when errors occured while connecting + :param xmpp_server: tuple of (hostname, port) + :param proxy_creds: tuple of (proxy_user, proxy_credentials) + ''' + self.send = send_method + self.onreceive = onreceive + self.old_on_receive = old_on_receive + self.on_success = on_success + self.on_failure = on_failure + self.xmpp_server = xmpp_server + self.proxy_user, self.proxy_pass = proxy_creds + self.old_on_receive = old_on_receive - self.start_connecting() + self.start_connecting() - @classmethod - def get_instance(cls, *args, **kwargs): - ''' - Factory Method for object creation. + @classmethod + def get_instance(cls, *args, **kwargs): + ''' + Factory Method for object creation. - Use this instead of directly initializing the class in order to make - unit testing much easier. - ''' - return cls(*args, **kwargs) + Use this instead of directly initializing the class in order to make + unit testing much easier. + ''' + return cls(*args, **kwargs) - def start_connecting(self): - raise NotImplementedError + def start_connecting(self): + raise NotImplementedError - def connecting_over(self): - self.onreceive(self.old_on_receive) - self.on_success() + def connecting_over(self): + self.onreceive(self.old_on_receive) + self.on_success() class HTTPCONNECTConnector(ProxyConnector): - def start_connecting(self): - ''' - Connects to proxy, supplies login and password to it - (if were specified while creating instance). Instructs proxy to make - connection to the target server. - ''' - log.info('Proxy server contacted, performing authentification') - connector = ['CONNECT %s:%s HTTP/1.1' % self.xmpp_server, - 'Proxy-Connection: Keep-Alive', - 'Pragma: no-cache', - 'Host: %s:%s' % self.xmpp_server, - 'User-Agent: Gajim'] - if self.proxy_user and self.proxy_pass: - credentials = '%s:%s' % (self.proxy_user, self.proxy_pass) - credentials = base64.encodestring(credentials).strip() - connector.append('Proxy-Authorization: Basic '+credentials) - connector.append('\r\n') - self.onreceive(self._on_headers_sent) - self.send('\r\n'.join(connector)) + def start_connecting(self): + ''' + Connects to proxy, supplies login and password to it + (if were specified while creating instance). Instructs proxy to make + connection to the target server. + ''' + log.info('Proxy server contacted, performing authentification') + connector = ['CONNECT %s:%s HTTP/1.1' % self.xmpp_server, + 'Proxy-Connection: Keep-Alive', + 'Pragma: no-cache', + 'Host: %s:%s' % self.xmpp_server, + 'User-Agent: Gajim'] + if self.proxy_user and self.proxy_pass: + credentials = '%s:%s' % (self.proxy_user, self.proxy_pass) + credentials = base64.encodestring(credentials).strip() + connector.append('Proxy-Authorization: Basic '+credentials) + connector.append('\r\n') + self.onreceive(self._on_headers_sent) + self.send('\r\n'.join(connector)) - def _on_headers_sent(self, reply): - if reply is None: - return - self.reply = reply.replace('\r', '') - try: - proto, code, desc = reply.split('\n')[0].split(' ', 2) - except: - log.error("_on_headers_sent:", exc_info=True) - #traceback.print_exc() - self.on_failure('Invalid proxy reply') - return - if code <> '200': - log.error('Invalid proxy reply: %s %s %s' % (proto, code, desc)) - self.on_failure('Invalid proxy reply') - return - if len(reply) != 2: - pass - self.connecting_over() + def _on_headers_sent(self, reply): + if reply is None: + return + self.reply = reply.replace('\r', '') + try: + proto, code, desc = reply.split('\n')[0].split(' ', 2) + except: + log.error("_on_headers_sent:", exc_info=True) + #traceback.print_exc() + self.on_failure('Invalid proxy reply') + return + if code <> '200': + log.error('Invalid proxy reply: %s %s %s' % (proto, code, desc)) + self.on_failure('Invalid proxy reply') + return + if len(reply) != 2: + pass + self.connecting_over() class SOCKS5Connector(ProxyConnector): - ''' - SOCKS5 proxy connection class. Allows to use SOCKS5 proxies with - (optionally) simple authentication (only USERNAME/PASSWORD auth). - ''' - def start_connecting(self): - log.info('Proxy server contacted, performing authentification') - if self.proxy_user and self.proxy_pass: - to_send = '\x05\x02\x00\x02' - else: - to_send = '\x05\x01\x00' - self.onreceive(self._on_greeting_sent) - self.send(to_send) + ''' + SOCKS5 proxy connection class. Allows to use SOCKS5 proxies with + (optionally) simple authentication (only USERNAME/PASSWORD auth). + ''' + def start_connecting(self): + log.info('Proxy server contacted, performing authentification') + if self.proxy_user and self.proxy_pass: + to_send = '\x05\x02\x00\x02' + else: + to_send = '\x05\x01\x00' + self.onreceive(self._on_greeting_sent) + self.send(to_send) - def _on_greeting_sent(self, reply): - if reply is None: - return - if len(reply) != 2: - self.on_failure('Invalid proxy reply') - return - if reply[0] != '\x05': - log.info('Invalid proxy reply') - self.on_failure('Invalid proxy reply') - return - if reply[1] == '\x00': - return self._on_proxy_auth('\x01\x00') - elif reply[1] == '\x02': - to_send = '\x01' + chr(len(self.proxy_user)) + self.proxy_user +\ - chr(len(self.proxy_pass)) + self.proxy_pass - self.onreceive(self._on_proxy_auth) - self.send(to_send) - else: - if reply[1] == '\xff': - log.error('Authentification to proxy impossible: no acceptable ' - 'auth method') - self.on_failure('Authentification to proxy impossible: no ' - 'acceptable authentification method') - return - log.error('Invalid proxy reply') - self.on_failure('Invalid proxy reply') - return + def _on_greeting_sent(self, reply): + if reply is None: + return + if len(reply) != 2: + self.on_failure('Invalid proxy reply') + return + if reply[0] != '\x05': + log.info('Invalid proxy reply') + self.on_failure('Invalid proxy reply') + return + if reply[1] == '\x00': + return self._on_proxy_auth('\x01\x00') + elif reply[1] == '\x02': + to_send = '\x01' + chr(len(self.proxy_user)) + self.proxy_user +\ + chr(len(self.proxy_pass)) + self.proxy_pass + self.onreceive(self._on_proxy_auth) + self.send(to_send) + else: + if reply[1] == '\xff': + log.error('Authentification to proxy impossible: no acceptable ' + 'auth method') + self.on_failure('Authentification to proxy impossible: no ' + 'acceptable authentification method') + return + log.error('Invalid proxy reply') + self.on_failure('Invalid proxy reply') + return - def _on_proxy_auth(self, reply): - if reply is None: - return - if len(reply) != 2: - log.error('Invalid proxy reply') - self.on_failure('Invalid proxy reply') - return - if reply[0] != '\x01': - log.error('Invalid proxy reply') - self.on_failure('Invalid proxy reply') - return - if reply[1] != '\x00': - log.error('Authentification to proxy failed') - self.on_failure('Authentification to proxy failed') - return - log.info('Authentification successfull. Jabber server contacted.') - # Request connection - req = "\x05\x01\x00" - # If the given destination address is an IP address, we'll - # use the IPv4 address request even if remote resolving was specified. - try: - self.ipaddr = socket.inet_aton(self.xmpp_server[0]) - req = req + "\x01" + self.ipaddr - except socket.error: - # Well it's not an IP number, so it's probably a DNS name. -# if self.__proxy[3]==True: - # Resolve remotely - self.ipaddr = None - req = req + "\x03" + chr(len(self.xmpp_server[0])) + self.xmpp_server[0] -# else: -# # Resolve locally -# self.ipaddr = socket.inet_aton(socket.gethostbyname(self.xmpp_server[0])) -# req = req + "\x01" + ipaddr - req = req + struct.pack(">H", self.xmpp_server[1]) - self.onreceive(self._on_req_sent) - self.send(req) + def _on_proxy_auth(self, reply): + if reply is None: + return + if len(reply) != 2: + log.error('Invalid proxy reply') + self.on_failure('Invalid proxy reply') + return + if reply[0] != '\x01': + log.error('Invalid proxy reply') + self.on_failure('Invalid proxy reply') + return + if reply[1] != '\x00': + log.error('Authentification to proxy failed') + self.on_failure('Authentification to proxy failed') + return + log.info('Authentification successfull. Jabber server contacted.') + # Request connection + req = "\x05\x01\x00" + # If the given destination address is an IP address, we'll + # use the IPv4 address request even if remote resolving was specified. + try: + self.ipaddr = socket.inet_aton(self.xmpp_server[0]) + req = req + "\x01" + self.ipaddr + except socket.error: + # Well it's not an IP number, so it's probably a DNS name. +# if self.__proxy[3]==True: + # Resolve remotely + self.ipaddr = None + req = req + "\x03" + chr(len(self.xmpp_server[0])) + self.xmpp_server[0] +# else: +# # Resolve locally +# self.ipaddr = socket.inet_aton(socket.gethostbyname(self.xmpp_server[0])) +# req = req + "\x01" + ipaddr + req = req + struct.pack(">H", self.xmpp_server[1]) + self.onreceive(self._on_req_sent) + self.send(req) - def _on_req_sent(self, reply): - if reply is None: - return - if len(reply) < 10: - log.error('Invalid proxy reply') - self.on_failure('Invalid proxy reply') - return - if reply[0] != '\x05': - log.error('Invalid proxy reply') - self.on_failure('Invalid proxy reply') - return - if reply[1] != "\x00": - # Connection failed - if ord(reply[1])<9: - errors = ['general SOCKS server failure', - 'connection not allowed by ruleset', - 'Network unreachable', - 'Host unreachable', - 'Connection refused', - 'TTL expired', - 'Command not supported', - 'Address type not supported' - ] - txt = errors[ord(reply[1])-1] - else: - txt = 'Invalid proxy reply' - log.error(txt) - self.on_failure(txt) - return - # Get the bound address/port - elif reply[3] == "\x01": - begin, end = 3, 7 - elif reply[3] == "\x03": - begin, end = 4, 4 + reply[4] - else: - log.error('Invalid proxy reply') - self.on_failure('Invalid proxy reply') - return - self.connecting_over() - -# vim: se ts=3: + def _on_req_sent(self, reply): + if reply is None: + return + if len(reply) < 10: + log.error('Invalid proxy reply') + self.on_failure('Invalid proxy reply') + return + if reply[0] != '\x05': + log.error('Invalid proxy reply') + self.on_failure('Invalid proxy reply') + return + if reply[1] != "\x00": + # Connection failed + if ord(reply[1])<9: + errors = ['general SOCKS server failure', + 'connection not allowed by ruleset', + 'Network unreachable', + 'Host unreachable', + 'Connection refused', + 'TTL expired', + 'Command not supported', + 'Address type not supported' + ] + txt = errors[ord(reply[1])-1] + else: + txt = 'Invalid proxy reply' + log.error(txt) + self.on_failure(txt) + return + # Get the bound address/port + elif reply[3] == "\x01": + begin, end = 3, 7 + elif reply[3] == "\x03": + begin, end = 4, 4 + reply[4] + else: + log.error('Invalid proxy reply') + self.on_failure('Invalid proxy reply') + return + self.connecting_over() diff --git a/src/common/xmpp/roster_nb.py b/src/common/xmpp/roster_nb.py index 715476b9e..f4732f606 100644 --- a/src/common/xmpp/roster_nb.py +++ b/src/common/xmpp/roster_nb.py @@ -1,8 +1,8 @@ ## roster_nb.py -## based on roster.py +## based on roster.py ## ## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov -## modified by Dimitur Kirov +## modified by Dimitur Kirov ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -29,230 +29,228 @@ log = logging.getLogger('gajim.c.x.roster_nb') class NonBlockingRoster(PlugIn): - ''' Defines a plenty of methods that will allow you to manage roster. - Also automatically track presences from remote JIDs taking into - account that every JID can have multiple resources connected. Does not - currently support 'error' presences. - You can also use mapping interface for access to the internal representation of - contacts in roster. - ''' - def __init__(self, version=''): - ''' Init internal variables. ''' - PlugIn.__init__(self) - self.version = version - self._data = {} - self.set=None - self._exported_methods=[self.getRoster] - self.received_from_server = False + ''' Defines a plenty of methods that will allow you to manage roster. + Also automatically track presences from remote JIDs taking into + account that every JID can have multiple resources connected. Does not + currently support 'error' presences. + You can also use mapping interface for access to the internal representation of + contacts in roster. + ''' + def __init__(self, version=''): + ''' Init internal variables. ''' + PlugIn.__init__(self) + self.version = version + self._data = {} + self.set=None + self._exported_methods=[self.getRoster] + self.received_from_server = False - def Request(self,force=0): - ''' Request roster from server if it were not yet requested - (or if the 'force' argument is set). ''' - if self.set is None: self.set=0 - elif not force: return + def Request(self,force=0): + ''' Request roster from server if it were not yet requested + (or if the 'force' argument is set). ''' + if self.set is None: self.set=0 + elif not force: return - iq = Iq('get',NS_ROSTER) - iq.setTagAttr('query', 'ver', self.version) - id_ = self._owner.getAnID() - iq.setID(id_) - self._owner.send(iq) - log.info('Roster requested from server') - return id_ + iq = Iq('get', NS_ROSTER) + iq.setTagAttr('query', 'ver', self.version) + id_ = self._owner.getAnID() + iq.setID(id_) + self._owner.send(iq) + log.info('Roster requested from server') + return id_ - def RosterIqHandler(self,dis,stanza): - ''' Subscription tracker. Used internally for setting items state in - internal roster representation. ''' - sender = stanza.getAttr('from') - if not sender is None and not sender.bareMatch( - self._owner.User + '@' + self._owner.Server): - return - query = stanza.getTag('query') - if query: - self.received_from_server = True - self.version = stanza.getTagAttr('query', 'ver') - if self.version is None: - self.version = '' - for item in query.getTags('item'): - jid=item.getAttr('jid') - if item.getAttr('subscription')=='remove': - if self._data.has_key(jid): del self._data[jid] - # Looks like we have a workaround - # raise NodeProcessed # a MUST - log.info('Setting roster item %s...' % jid) - if not self._data.has_key(jid): self._data[jid]={} - self._data[jid]['name']=item.getAttr('name') - self._data[jid]['ask']=item.getAttr('ask') - self._data[jid]['subscription']=item.getAttr('subscription') - self._data[jid]['groups']=[] - if not self._data[jid].has_key('resources'): self._data[jid]['resources']={} - for group in item.getTags('group'): self._data[jid]['groups'].append(group.getData()) - self._data[self._owner.User+'@'+self._owner.Server]={'resources':{},'name':None,'ask':None,'subscription':None,'groups':None,} - self.set=1 - # Looks like we have a workaround - # raise NodeProcessed # a MUST. Otherwise you'll get back an + def RosterIqHandler(self, dis, stanza): + ''' Subscription tracker. Used internally for setting items state in + internal roster representation. ''' + sender = stanza.getAttr('from') + if not sender is None and not sender.bareMatch( + self._owner.User + '@' + self._owner.Server): + return + query = stanza.getTag('query') + if query: + self.received_from_server = True + self.version = stanza.getTagAttr('query', 'ver') + if self.version is None: + self.version = '' + for item in query.getTags('item'): + jid=item.getAttr('jid') + if item.getAttr('subscription')=='remove': + if self._data.has_key(jid): del self._data[jid] + # Looks like we have a workaround + # raise NodeProcessed # a MUST + log.info('Setting roster item %s...' % jid) + if not self._data.has_key(jid): self._data[jid]={} + self._data[jid]['name']=item.getAttr('name') + self._data[jid]['ask']=item.getAttr('ask') + self._data[jid]['subscription']=item.getAttr('subscription') + self._data[jid]['groups']=[] + if not self._data[jid].has_key('resources'): self._data[jid]['resources']={} + for group in item.getTags('group'): self._data[jid]['groups'].append(group.getData()) + self._data[self._owner.User+'@'+self._owner.Server]={'resources': {}, 'name': None, 'ask': None, 'subscription': None, 'groups': None,} + self.set=1 + # Looks like we have a workaround + # raise NodeProcessed # a MUST. Otherwise you'll get back an - def PresenceHandler(self,dis,pres): - ''' Presence tracker. Used internally for setting items' resources state in - internal roster representation. ''' - if pres.getTag('x', namespace=NS_MUC_USER): - return - jid=pres.getFrom() - if not jid: - # If no from attribue, it's from server - jid=self._owner.Server - jid=JID(jid) - if not self._data.has_key(jid.getStripped()): self._data[jid.getStripped()]={'name':None,'ask':None,'subscription':'none','groups':['Not in roster'],'resources':{}} - if type(self._data[jid.getStripped()]['resources'])!=type(dict()): - self._data[jid.getStripped()]['resources']={} - item=self._data[jid.getStripped()] - typ=pres.getType() + def PresenceHandler(self, dis, pres): + ''' Presence tracker. Used internally for setting items' resources state in + internal roster representation. ''' + if pres.getTag('x', namespace=NS_MUC_USER): + return + jid=pres.getFrom() + if not jid: + # If no from attribue, it's from server + jid=self._owner.Server + jid=JID(jid) + if not self._data.has_key(jid.getStripped()): self._data[jid.getStripped()]={'name':None,'ask':None,'subscription':'none','groups':['Not in roster'],'resources':{}} + if type(self._data[jid.getStripped()]['resources'])!=type(dict()): + self._data[jid.getStripped()]['resources']={} + item=self._data[jid.getStripped()] + typ=pres.getType() - if not typ: - log.info('Setting roster item %s for resource %s...'%(jid.getStripped(),jid.getResource())) - item['resources'][jid.getResource()]=res={'show':None,'status':None,'priority':'0','timestamp':None} - if pres.getTag('show'): res['show']=pres.getShow() - if pres.getTag('status'): res['status']=pres.getStatus() - if pres.getTag('priority'): res['priority']=pres.getPriority() - if not pres.getTimestamp(): pres.setTimestamp() - res['timestamp']=pres.getTimestamp() - elif typ=='unavailable' and item['resources'].has_key(jid.getResource()): del item['resources'][jid.getResource()] - # Need to handle type='error' also + if not typ: + log.info('Setting roster item %s for resource %s...'%(jid.getStripped(), jid.getResource())) + item['resources'][jid.getResource()]=res={'show':None,'status':None,'priority':'0','timestamp':None} + if pres.getTag('show'): res['show']=pres.getShow() + if pres.getTag('status'): res['status']=pres.getStatus() + if pres.getTag('priority'): res['priority']=pres.getPriority() + if not pres.getTimestamp(): pres.setTimestamp() + res['timestamp']=pres.getTimestamp() + elif typ=='unavailable' and item['resources'].has_key(jid.getResource()): del item['resources'][jid.getResource()] + # Need to handle type='error' also - def _getItemData(self,jid,dataname): - ''' Return specific jid's representation in internal format. Used internally. ''' - jid=jid[:(jid+'/').find('/')] - return self._data[jid][dataname] - def _getResourceData(self,jid,dataname): - ''' Return specific jid's resource representation in internal format. Used internally. ''' - if jid.find('/')+1: - jid,resource=jid.split('/',1) - if self._data[jid]['resources'].has_key(resource): return self._data[jid]['resources'][resource][dataname] - elif self._data[jid]['resources'].keys(): - lastpri=-129 - for r in self._data[jid]['resources'].keys(): - if int(self._data[jid]['resources'][r]['priority'])>lastpri: resource,lastpri=r,int(self._data[jid]['resources'][r]['priority']) - return self._data[jid]['resources'][resource][dataname] - def delItem(self,jid): - ''' Delete contact 'jid' from roster.''' - self._owner.send(Iq('set',NS_ROSTER,payload=[Node('item',{'jid':jid,'subscription':'remove'})])) - def getAsk(self,jid): - ''' Returns 'ask' value of contact 'jid'.''' - return self._getItemData(jid,'ask') - def getGroups(self,jid): - ''' Returns groups list that contact 'jid' belongs to.''' - return self._getItemData(jid,'groups') - def getName(self,jid): - ''' Returns name of contact 'jid'.''' - return self._getItemData(jid,'name') - def getPriority(self,jid): - ''' Returns priority of contact 'jid'. 'jid' should be a full (not bare) JID.''' - return self._getResourceData(jid,'priority') - def getRawRoster(self): - ''' Returns roster representation in internal format. ''' - return self._data - def getRawItem(self,jid): - ''' Returns roster item 'jid' representation in internal format. ''' - return self._data[jid[:(jid+'/').find('/')]] - def getShow(self, jid): - ''' Returns 'show' value of contact 'jid'. 'jid' should be a full (not bare) JID.''' - return self._getResourceData(jid,'show') - def getStatus(self, jid): - ''' Returns 'status' value of contact 'jid'. 'jid' should be a full (not bare) JID.''' - return self._getResourceData(jid,'status') - def getSubscription(self,jid): - ''' Returns 'subscription' value of contact 'jid'.''' - return self._getItemData(jid,'subscription') - def getResources(self,jid): - ''' Returns list of connected resources of contact 'jid'.''' - return self._data[jid[:(jid+'/').find('/')]]['resources'].keys() - def setItem(self,jid,name=None,groups=[]): - ''' Renames contact 'jid' and sets the groups list that it now belongs to.''' - iq=Iq('set',NS_ROSTER) - query=iq.getTag('query') - attrs={'jid':jid} - if name: attrs['name']=name - item=query.setTag('item',attrs) - for group in groups: item.addChild(node=Node('group',payload=[group])) - self._owner.send(iq) - def setItemMulti(self,items): - ''' Renames multiple contacts and sets their group lists.''' - iq=Iq('set',NS_ROSTER) - query=iq.getTag('query') - for i in items: - attrs={'jid':i['jid']} - if i['name']: attrs['name']=i['name'] - item=query.setTag('item',attrs) - for group in i['groups']: item.addChild(node=Node('group',payload=[group])) - self._owner.send(iq) - def getItems(self): - ''' Return list of all [bare] JIDs that the roster is currently tracks.''' - return self._data.keys() - def keys(self): - ''' Same as getItems. Provided for the sake of dictionary interface.''' - return self._data.keys() - def __getitem__(self,item): - ''' Get the contact in the internal format. Raises KeyError if JID 'item' is not in roster.''' - return self._data[item] - def getItem(self,item): - ''' Get the contact in the internal format (or None if JID 'item' is not in roster).''' - if self._data.has_key(item): return self._data[item] - def Subscribe(self,jid): - ''' Send subscription request to JID 'jid'.''' - self._owner.send(Presence(jid,'subscribe')) - def Unsubscribe(self,jid): - ''' Ask for removing our subscription for JID 'jid'.''' - self._owner.send(Presence(jid,'unsubscribe')) - def Authorize(self,jid): - ''' Authorise JID 'jid'. Works only if these JID requested auth previously. ''' - self._owner.send(Presence(jid,'subscribed')) - def Unauthorize(self,jid): - ''' Unauthorise JID 'jid'. Use for declining authorisation request - or for removing existing authorization. ''' - self._owner.send(Presence(jid,'unsubscribed')) - def getRaw(self): - '''Returns the internal data representation of the roster.''' - return self._data - def setRaw(self, data): - '''Returns the internal data representation of the roster.''' - self._data = data - self._data[self._owner.User+'@'+self._owner.Server]={'resources':{},'name':None,'ask':None,'subscription':None,'groups':None,} - self.set=1 - # copypasted methods for roster.py from constructor to here + def _getItemData(self, jid, dataname): + ''' Return specific jid's representation in internal format. Used internally. ''' + jid=jid[:(jid+'/').find('/')] + return self._data[jid][dataname] + def _getResourceData(self, jid, dataname): + ''' Return specific jid's resource representation in internal format. Used internally. ''' + if jid.find('/')+1: + jid, resource=jid.split('/', 1) + if self._data[jid]['resources'].has_key(resource): return self._data[jid]['resources'][resource][dataname] + elif self._data[jid]['resources'].keys(): + lastpri=-129 + for r in self._data[jid]['resources'].keys(): + if int(self._data[jid]['resources'][r]['priority'])>lastpri: resource, lastpri=r, int(self._data[jid]['resources'][r]['priority']) + return self._data[jid]['resources'][resource][dataname] + def delItem(self, jid): + ''' Delete contact 'jid' from roster.''' + self._owner.send(Iq('set', NS_ROSTER, payload=[Node('item', {'jid':jid,'subscription':'remove'})])) + def getAsk(self, jid): + ''' Returns 'ask' value of contact 'jid'.''' + return self._getItemData(jid, 'ask') + def getGroups(self, jid): + ''' Returns groups list that contact 'jid' belongs to.''' + return self._getItemData(jid, 'groups') + def getName(self, jid): + ''' Returns name of contact 'jid'.''' + return self._getItemData(jid, 'name') + def getPriority(self, jid): + ''' Returns priority of contact 'jid'. 'jid' should be a full (not bare) JID.''' + return self._getResourceData(jid, 'priority') + def getRawRoster(self): + ''' Returns roster representation in internal format. ''' + return self._data + def getRawItem(self, jid): + ''' Returns roster item 'jid' representation in internal format. ''' + return self._data[jid[:(jid+'/').find('/')]] + def getShow(self, jid): + ''' Returns 'show' value of contact 'jid'. 'jid' should be a full (not bare) JID.''' + return self._getResourceData(jid, 'show') + def getStatus(self, jid): + ''' Returns 'status' value of contact 'jid'. 'jid' should be a full (not bare) JID.''' + return self._getResourceData(jid, 'status') + def getSubscription(self, jid): + ''' Returns 'subscription' value of contact 'jid'.''' + return self._getItemData(jid, 'subscription') + def getResources(self, jid): + ''' Returns list of connected resources of contact 'jid'.''' + return self._data[jid[:(jid+'/').find('/')]]['resources'].keys() + def setItem(self,jid,name=None,groups=[]): + ''' Renames contact 'jid' and sets the groups list that it now belongs to.''' + iq=Iq('set', NS_ROSTER) + query=iq.getTag('query') + attrs={'jid':jid} + if name: attrs['name']=name + item=query.setTag('item', attrs) + for group in groups: item.addChild(node=Node('group', payload=[group])) + self._owner.send(iq) + def setItemMulti(self, items): + ''' Renames multiple contacts and sets their group lists.''' + iq=Iq('set', NS_ROSTER) + query=iq.getTag('query') + for i in items: + attrs={'jid':i['jid']} + if i['name']: attrs['name']=i['name'] + item=query.setTag('item', attrs) + for group in i['groups']: item.addChild(node=Node('group', payload=[group])) + self._owner.send(iq) + def getItems(self): + ''' Return list of all [bare] JIDs that the roster is currently tracks.''' + return self._data.keys() + def keys(self): + ''' Same as getItems. Provided for the sake of dictionary interface.''' + return self._data.keys() + def __getitem__(self, item): + ''' Get the contact in the internal format. Raises KeyError if JID 'item' is not in roster.''' + return self._data[item] + def getItem(self, item): + ''' Get the contact in the internal format (or None if JID 'item' is not in roster).''' + if self._data.has_key(item): return self._data[item] + def Subscribe(self, jid): + ''' Send subscription request to JID 'jid'.''' + self._owner.send(Presence(jid, 'subscribe')) + def Unsubscribe(self, jid): + ''' Ask for removing our subscription for JID 'jid'.''' + self._owner.send(Presence(jid, 'unsubscribe')) + def Authorize(self, jid): + ''' Authorise JID 'jid'. Works only if these JID requested auth previously. ''' + self._owner.send(Presence(jid, 'subscribed')) + def Unauthorize(self, jid): + ''' Unauthorise JID 'jid'. Use for declining authorisation request + or for removing existing authorization. ''' + self._owner.send(Presence(jid, 'unsubscribed')) + def getRaw(self): + '''Returns the internal data representation of the roster.''' + return self._data + def setRaw(self, data): + '''Returns the internal data representation of the roster.''' + self._data = data + self._data[self._owner.User+'@'+self._owner.Server]={'resources': {}, 'name': None, 'ask': None, 'subscription': None, 'groups': None,} + self.set=1 + # copypasted methods for roster.py from constructor to here - def plugin(self, owner, request=1): - ''' Register presence and subscription trackers in the owner's dispatcher. - Also request roster from server if the 'request' argument is set. - Used internally.''' - self._owner.RegisterHandler('iq', self.RosterIqHandler, 'result', NS_ROSTER, makefirst = 1) - self._owner.RegisterHandler('iq', self.RosterIqHandler, 'set', NS_ROSTER) - self._owner.RegisterHandler('presence', self.PresenceHandler) - if request: - return self.Request() + def plugin(self, owner, request=1): + ''' Register presence and subscription trackers in the owner's dispatcher. + Also request roster from server if the 'request' argument is set. + Used internally.''' + self._owner.RegisterHandler('iq', self.RosterIqHandler, 'result', NS_ROSTER, makefirst = 1) + self._owner.RegisterHandler('iq', self.RosterIqHandler, 'set', NS_ROSTER) + self._owner.RegisterHandler('presence', self.PresenceHandler) + if request: + return self.Request() - def _on_roster_set(self, data): - if data: - self._owner.Dispatcher.ProcessNonBlocking(data) - if not self.set: - return - self._owner.onreceive(None) - if self.on_ready: - self.on_ready(self) - self.on_ready = None - return True + def _on_roster_set(self, data): + if data: + self._owner.Dispatcher.ProcessNonBlocking(data) + if not self.set: + return + self._owner.onreceive(None) + if self.on_ready: + self.on_ready(self) + self.on_ready = None + return True - def getRoster(self, on_ready=None, force=False): - ''' Requests roster from server if neccessary and returns self. ''' - return_self = True - if not self.set: - self.on_ready = on_ready - self._owner.onreceive(self._on_roster_set) - return_self = False - elif on_ready: - on_ready(self) - return_self = False - if return_self or force: - return self - return None - -# vim: se ts=3: + def getRoster(self, on_ready=None, force=False): + ''' Requests roster from server if neccessary and returns self. ''' + return_self = True + if not self.set: + self.on_ready = on_ready + self._owner.onreceive(self._on_roster_set) + return_self = False + elif on_ready: + on_ready(self) + return_self = False + if return_self or force: + return self + return None diff --git a/src/common/xmpp/simplexml.py b/src/common/xmpp/simplexml.py index f47142c46..04890b3a1 100644 --- a/src/common/xmpp/simplexml.py +++ b/src/common/xmpp/simplexml.py @@ -22,462 +22,460 @@ import logging log = logging.getLogger('gajim.c.x.simplexml') def XMLescape(txt): - '''Returns provided string with symbols & < > " replaced by their respective XML entities.''' - # replace also FORM FEED and ESC, because they are not valid XML chars - return txt.replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """).replace(u'\x0C', "").replace(u'\x1B', "") + '''Returns provided string with symbols & < > " replaced by their respective XML entities.''' + # replace also FORM FEED and ESC, because they are not valid XML chars + return txt.replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """).replace(u'\x0C', "").replace(u'\x1B', "") ENCODING='utf-8' def ustr(what): - '''Converts object "what" to unicode string using it's own __str__ method if accessible or unicode method otherwise.''' - if isinstance(what, unicode): return what - try: r=what.__str__() - except AttributeError: r=str(what) - if not isinstance(r, unicode): return unicode(r,ENCODING) - return r + '''Converts object "what" to unicode string using it's own __str__ method if accessible or unicode method otherwise.''' + if isinstance(what, unicode): return what + try: r=what.__str__() + except AttributeError: r=str(what) + if not isinstance(r, unicode): return unicode(r, ENCODING) + return r class Node(object): - ''' Node class describes syntax of separate XML Node. It have a constructor that permits node creation - from set of "namespace name", attributes and payload of text strings and other nodes. - It does not natively support building node from text string and uses NodeBuilder class for that purpose. - After creation node can be mangled in many ways so it can be completely changed. - Also node can be serialised into string in one of two modes: default (where the textual representation - of node describes it exactly) and "fancy" - with whitespace added to make indentation and thus make - result more readable by human. + ''' Node class describes syntax of separate XML Node. It have a constructor that permits node creation + from set of "namespace name", attributes and payload of text strings and other nodes. + It does not natively support building node from text string and uses NodeBuilder class for that purpose. + After creation node can be mangled in many ways so it can be completely changed. + Also node can be serialised into string in one of two modes: default (where the textual representation + of node describes it exactly) and "fancy" - with whitespace added to make indentation and thus make + result more readable by human. - Node class have attribute FORCE_NODE_RECREATION that is defaults to False thus enabling fast node - replication from the some other node. The drawback of the fast way is that new node shares some - info with the "original" node that is changing the one node may influence the other. Though it is - rarely needed (in xmpppy it is never needed at all since I'm usually never using original node after - replication (and using replication only to move upwards on the classes tree). - ''' - FORCE_NODE_RECREATION=0 - def __init__(self, tag=None, attrs={}, payload=[], parent=None, nsp=None, node_built=False, node=None): - ''' Takes "tag" argument as the name of node (prepended by namespace, if needed and separated from it - by a space), attrs dictionary as the set of arguments, payload list as the set of textual strings - and child nodes that this node carries within itself and "parent" argument that is another node - that this one will be the child of. Also the __init__ can be provided with "node" argument that is - either a text string containing exactly one node or another Node instance to begin with. If both - "node" and other arguments is provided then the node initially created as replica of "node" - provided and then modified to be compliant with other arguments.''' - if node: - if self.FORCE_NODE_RECREATION and isinstance(node, Node): - node=str(node) - if not isinstance(node, Node): - node=NodeBuilder(node,self) - node_built = True - else: - self.name,self.namespace,self.attrs,self.data,self.kids,self.parent,self.nsd = node.name,node.namespace,{},[],[],node.parent,{} - for key in node.attrs.keys(): self.attrs[key]=node.attrs[key] - for data in node.data: self.data.append(data) - for kid in node.kids: self.kids.append(kid) - for k,v in node.nsd.items(): self.nsd[k] = v - else: self.name,self.namespace,self.attrs,self.data,self.kids,self.parent,self.nsd = 'tag','',{},[],[],None,{} - if parent: - self.parent = parent - self.nsp_cache = {} - if nsp: - for k,v in nsp.items(): self.nsp_cache[k] = v - for attr,val in attrs.items(): - if attr == 'xmlns': - self.nsd[u''] = val - elif attr.startswith('xmlns:'): - self.nsd[attr[6:]] = val - self.attrs[attr]=attrs[attr] - if tag: - if node_built: - pfx,self.name = (['']+tag.split(':'))[-2:] - self.namespace = self.lookup_nsp(pfx) - else: - if ' ' in tag: - self.namespace,self.name = tag.split() - else: - self.name = tag - if isinstance(payload, basestring): payload=[payload] - for i in payload: - if isinstance(i, Node): self.addChild(node=i) - else: self.data.append(ustr(i)) + Node class have attribute FORCE_NODE_RECREATION that is defaults to False thus enabling fast node + replication from the some other node. The drawback of the fast way is that new node shares some + info with the "original" node that is changing the one node may influence the other. Though it is + rarely needed (in xmpppy it is never needed at all since I'm usually never using original node after + replication (and using replication only to move upwards on the classes tree). + ''' + FORCE_NODE_RECREATION=0 + def __init__(self, tag=None, attrs={}, payload=[], parent=None, nsp=None, node_built=False, node=None): + ''' Takes "tag" argument as the name of node (prepended by namespace, if needed and separated from it + by a space), attrs dictionary as the set of arguments, payload list as the set of textual strings + and child nodes that this node carries within itself and "parent" argument that is another node + that this one will be the child of. Also the __init__ can be provided with "node" argument that is + either a text string containing exactly one node or another Node instance to begin with. If both + "node" and other arguments is provided then the node initially created as replica of "node" + provided and then modified to be compliant with other arguments.''' + if node: + if self.FORCE_NODE_RECREATION and isinstance(node, Node): + node=str(node) + if not isinstance(node, Node): + node=NodeBuilder(node, self) + node_built = True + else: + self.name, self.namespace, self.attrs, self.data, self.kids, self.parent, self.nsd = node.name, node.namespace, {}, [], [], node.parent, {} + for key in node.attrs.keys(): self.attrs[key]=node.attrs[key] + for data in node.data: self.data.append(data) + for kid in node.kids: self.kids.append(kid) + for k, v in node.nsd.items(): self.nsd[k] = v + else: self.name, self.namespace, self.attrs, self.data, self.kids, self.parent, self.nsd = 'tag', '', {}, [], [], None, {} + if parent: + self.parent = parent + self.nsp_cache = {} + if nsp: + for k, v in nsp.items(): self.nsp_cache[k] = v + for attr, val in attrs.items(): + if attr == 'xmlns': + self.nsd[u''] = val + elif attr.startswith('xmlns:'): + self.nsd[attr[6:]] = val + self.attrs[attr]=attrs[attr] + if tag: + if node_built: + pfx, self.name = (['']+tag.split(':'))[-2:] + self.namespace = self.lookup_nsp(pfx) + else: + if ' ' in tag: + self.namespace, self.name = tag.split() + else: + self.name = tag + if isinstance(payload, basestring): payload=[payload] + for i in payload: + if isinstance(i, Node): self.addChild(node=i) + else: self.data.append(ustr(i)) - def lookup_nsp(self,pfx=''): - ns = self.nsd.get(pfx,None) - if ns is None: - ns = self.nsp_cache.get(pfx,None) - if ns is None: - if self.parent: - ns = self.parent.lookup_nsp(pfx) - self.nsp_cache[pfx] = ns - else: - return 'http://www.gajim.org/xmlns/undeclared' - return ns + def lookup_nsp(self,pfx=''): + ns = self.nsd.get(pfx, None) + if ns is None: + ns = self.nsp_cache.get(pfx, None) + if ns is None: + if self.parent: + ns = self.parent.lookup_nsp(pfx) + self.nsp_cache[pfx] = ns + else: + return 'http://www.gajim.org/xmlns/undeclared' + return ns - def __str__(self,fancy=0): - ''' Method used to dump node into textual representation. - if "fancy" argument is set to True produces indented output for readability.''' - s = (fancy-1) * 2 * ' ' + "<" + self.name - if self.namespace: - if not self.parent or self.parent.namespace!=self.namespace: - if 'xmlns' not in self.attrs: - s = s + ' xmlns="%s"'%self.namespace - for key in self.attrs.keys(): - val = ustr(self.attrs[key]) - s = s + ' %s="%s"' % ( key, XMLescape(val) ) - s = s + ">" - cnt = 0 - if self.kids: - if fancy: s = s + "\n" - for a in self.kids: - if not fancy and (len(self.data)-1)>=cnt: s=s+XMLescape(self.data[cnt]) - elif (len(self.data)-1)>=cnt: s=s+XMLescape(self.data[cnt].strip()) - if isinstance(a, str) or isinstance(a, unicode): - s = s + a.__str__() - else: - s = s + a.__str__(fancy and fancy+1) - cnt=cnt+1 - if not fancy and (len(self.data)-1) >= cnt: s = s + XMLescape(self.data[cnt]) - elif (len(self.data)-1) >= cnt: s = s + XMLescape(self.data[cnt].strip()) - if not self.kids and s.endswith('>'): - s=s[:-1]+' />' - if fancy: s = s + "\n" - else: - if fancy and not self.data: s = s + (fancy-1) * 2 * ' ' - s = s + "" - if fancy: s = s + "\n" - return s - def addChild(self, name=None, attrs={}, payload=[], namespace=None, node=None): - ''' If "node" argument is provided, adds it as child node. Else creates new node from - the other arguments' values and adds it as well.''' - if 'xmlns' in attrs: - raise AttributeError("Use namespace=x instead of attrs={'xmlns':x}") - if node: - newnode=node - node.parent = self - else: newnode=Node(tag=name, parent=self, attrs=attrs, payload=payload) - if namespace: - newnode.setNamespace(namespace) - self.kids.append(newnode) - return newnode - def addData(self, data): - ''' Adds some CDATA to node. ''' - self.data.append(ustr(data)) - def clearData(self): - ''' Removes all CDATA from the node. ''' - self.data=[] - def delAttr(self, key): - ''' Deletes an attribute "key" ''' - del self.attrs[key] - def delChild(self, node, attrs={}): - ''' Deletes the "node" from the node's childs list, if "node" is an instance. - Else deletes the first node that have specified name and (optionally) attributes. ''' - if not isinstance(node, Node): node=self.getTag(node,attrs) - self.kids.remove(node) - return node - def getAttrs(self): - ''' Returns all node's attributes as dictionary. ''' - return self.attrs - def getAttr(self, key): - ''' Returns value of specified attribute. ''' - return self.attrs.get(key) - def getChildren(self): - ''' Returns all node's child nodes as list. ''' - return self.kids - def getData(self): - ''' Returns all node CDATA as string (concatenated). ''' - return ''.join(self.data) - def getName(self): - ''' Returns the name of node ''' - return self.name - def getNamespace(self): - ''' Returns the namespace of node ''' - return self.namespace - def getParent(self): - ''' Returns the parent of node (if present). ''' - return self.parent - def getPayload(self): - ''' Return the payload of node i.e. list of child nodes and CDATA entries. - F.e. for "text1 text2" will be returned list: - ['text1', , , ' text2']. ''' - ret=[] - for i in range(len(self.kids)+len(self.data)+1): - try: - if self.data[i]: ret.append(self.data[i]) - except IndexError: pass - try: ret.append(self.kids[i]) - except IndexError: pass - return ret - def getTag(self, name, attrs={}, namespace=None): - ''' Filters all child nodes using specified arguments as filter. - Returns the first found or None if not found. ''' - return self.getTags(name, attrs, namespace, one=1) - def getTagAttr(self,tag,attr): - ''' Returns attribute value of the child with specified name (or None if no such attribute).''' - try: - return self.getTag(tag).attrs[attr] - except: - return None - def getTagData(self,tag): - ''' Returns cocatenated CDATA of the child with specified name.''' - try: - return self.getTag(tag).getData() - except Exception: - return None - def getTags(self, name, attrs={}, namespace=None, one=0): - ''' Filters all child nodes using specified arguments as filter. - Returns the list of nodes found. ''' - nodes=[] - for node in self.kids: - if namespace and namespace!=node.getNamespace(): continue - if node.getName() == name: - for key in attrs.keys(): - if key not in node.attrs or node.attrs[key]!=attrs[key]: break - else: nodes.append(node) - if one and nodes: return nodes[0] - if not one: return nodes + def __str__(self,fancy=0): + ''' Method used to dump node into textual representation. + if "fancy" argument is set to True produces indented output for readability.''' + s = (fancy-1) * 2 * ' ' + "<" + self.name + if self.namespace: + if not self.parent or self.parent.namespace!=self.namespace: + if 'xmlns' not in self.attrs: + s = s + ' xmlns="%s"'%self.namespace + for key in self.attrs.keys(): + val = ustr(self.attrs[key]) + s = s + ' %s="%s"' % ( key, XMLescape(val) ) + s = s + ">" + cnt = 0 + if self.kids: + if fancy: s = s + "\n" + for a in self.kids: + if not fancy and (len(self.data)-1)>=cnt: s=s+XMLescape(self.data[cnt]) + elif (len(self.data)-1)>=cnt: s=s+XMLescape(self.data[cnt].strip()) + if isinstance(a, str) or isinstance(a, unicode): + s = s + a.__str__() + else: + s = s + a.__str__(fancy and fancy+1) + cnt=cnt+1 + if not fancy and (len(self.data)-1) >= cnt: s = s + XMLescape(self.data[cnt]) + elif (len(self.data)-1) >= cnt: s = s + XMLescape(self.data[cnt].strip()) + if not self.kids and s.endswith('>'): + s=s[:-1]+' />' + if fancy: s = s + "\n" + else: + if fancy and not self.data: s = s + (fancy-1) * 2 * ' ' + s = s + "" + if fancy: s = s + "\n" + return s + def addChild(self, name=None, attrs={}, payload=[], namespace=None, node=None): + ''' If "node" argument is provided, adds it as child node. Else creates new node from + the other arguments' values and adds it as well.''' + if 'xmlns' in attrs: + raise AttributeError("Use namespace=x instead of attrs={'xmlns':x}") + if node: + newnode=node + node.parent = self + else: newnode=Node(tag=name, parent=self, attrs=attrs, payload=payload) + if namespace: + newnode.setNamespace(namespace) + self.kids.append(newnode) + return newnode + def addData(self, data): + ''' Adds some CDATA to node. ''' + self.data.append(ustr(data)) + def clearData(self): + ''' Removes all CDATA from the node. ''' + self.data=[] + def delAttr(self, key): + ''' Deletes an attribute "key" ''' + del self.attrs[key] + def delChild(self, node, attrs={}): + ''' Deletes the "node" from the node's childs list, if "node" is an instance. + Else deletes the first node that have specified name and (optionally) attributes. ''' + if not isinstance(node, Node): node=self.getTag(node, attrs) + self.kids.remove(node) + return node + def getAttrs(self): + ''' Returns all node's attributes as dictionary. ''' + return self.attrs + def getAttr(self, key): + ''' Returns value of specified attribute. ''' + return self.attrs.get(key) + def getChildren(self): + ''' Returns all node's child nodes as list. ''' + return self.kids + def getData(self): + ''' Returns all node CDATA as string (concatenated). ''' + return ''.join(self.data) + def getName(self): + ''' Returns the name of node ''' + return self.name + def getNamespace(self): + ''' Returns the namespace of node ''' + return self.namespace + def getParent(self): + ''' Returns the parent of node (if present). ''' + return self.parent + def getPayload(self): + ''' Return the payload of node i.e. list of child nodes and CDATA entries. + F.e. for "text1 text2" will be returned list: + ['text1', , , ' text2']. ''' + ret=[] + for i in range(len(self.kids)+len(self.data)+1): + try: + if self.data[i]: ret.append(self.data[i]) + except IndexError: pass + try: ret.append(self.kids[i]) + except IndexError: pass + return ret + def getTag(self, name, attrs={}, namespace=None): + ''' Filters all child nodes using specified arguments as filter. + Returns the first found or None if not found. ''' + return self.getTags(name, attrs, namespace, one=1) + def getTagAttr(self, tag, attr): + ''' Returns attribute value of the child with specified name (or None if no such attribute).''' + try: + return self.getTag(tag).attrs[attr] + except: + return None + def getTagData(self, tag): + ''' Returns cocatenated CDATA of the child with specified name.''' + try: + return self.getTag(tag).getData() + except Exception: + return None + def getTags(self, name, attrs={}, namespace=None, one=0): + ''' Filters all child nodes using specified arguments as filter. + Returns the list of nodes found. ''' + nodes=[] + for node in self.kids: + if namespace and namespace!=node.getNamespace(): continue + if node.getName() == name: + for key in attrs.keys(): + if key not in node.attrs or node.attrs[key]!=attrs[key]: break + else: nodes.append(node) + if one and nodes: return nodes[0] + if not one: return nodes - def iterTags(self, name, attrs={}, namespace=None): - ''' Iterate over all children using specified arguments as filter. ''' - for node in self.kids: - if namespace is not None and namespace!=node.getNamespace(): continue - if node.getName() == name: - for key in attrs.keys(): - if key not in node.attrs or \ - node.attrs[key]!=attrs[key]: break - else: - yield node + def iterTags(self, name, attrs={}, namespace=None): + ''' Iterate over all children using specified arguments as filter. ''' + for node in self.kids: + if namespace is not None and namespace!=node.getNamespace(): continue + if node.getName() == name: + for key in attrs.keys(): + if key not in node.attrs or \ + node.attrs[key]!=attrs[key]: break + else: + yield node - def setAttr(self, key, val): - ''' Sets attribute "key" with the value "val". ''' - self.attrs[key]=val - def setData(self, data): - ''' Sets node's CDATA to provided string. Resets all previous CDATA!''' - self.data=[ustr(data)] - def setName(self,val): - ''' Changes the node name. ''' - self.name = val - def setNamespace(self, namespace): - ''' Changes the node namespace. ''' - self.namespace=namespace - def setParent(self, node): - ''' Sets node's parent to "node". WARNING: do not checks if the parent already present - and not removes the node from the list of childs of previous parent. ''' - self.parent = node - def setPayload(self,payload,add=0): - ''' Sets node payload according to the list specified. WARNING: completely replaces all node's - previous content. If you wish just to add child or CDATA - use addData or addChild methods. ''' - if isinstance(payload, basestring): payload=[payload] - if add: self.kids+=payload - else: self.kids=payload - def setTag(self, name, attrs={}, namespace=None): - ''' Same as getTag but if the node with specified namespace/attributes not found, creates such - node and returns it. ''' - node=self.getTags(name, attrs, namespace=namespace, one=1) - if node: return node - else: return self.addChild(name, attrs, namespace=namespace) - def setTagAttr(self,tag,attr,val): - ''' Creates new node (if not already present) with name "tag" - and sets it's attribute "attr" to value "val". ''' - try: - self.getTag(tag).attrs[attr]=val - except Exception: - self.addChild(tag,attrs={attr:val}) - def setTagData(self,tag,val,attrs={}): - ''' Creates new node (if not already present) with name "tag" and (optionally) attributes "attrs" - and sets it's CDATA to string "val". ''' - try: - self.getTag(tag,attrs).setData(ustr(val)) - except Exception: - self.addChild(tag,attrs,payload=[ustr(val)]) - def has_attr(self,key): - ''' Checks if node have attribute "key".''' - return key in self.attrs - def __getitem__(self,item): - ''' Returns node's attribute "item" value. ''' - return self.getAttr(item) - def __setitem__(self,item,val): - ''' Sets node's attribute "item" value. ''' - return self.setAttr(item,val) - def __delitem__(self,item): - ''' Deletes node's attribute "item". ''' - return self.delAttr(item) - def __contains__(self,item): - """ Checks if node has attribute "item" """ - return self.has_attr(item) - def __getattr__(self,attr): - ''' Reduce memory usage caused by T/NT classes - use memory only when needed. ''' - if attr=='T': - self.T=T(self) - return self.T - if attr=='NT': - self.NT=NT(self) - return self.NT - raise AttributeError + def setAttr(self, key, val): + ''' Sets attribute "key" with the value "val". ''' + self.attrs[key]=val + def setData(self, data): + ''' Sets node's CDATA to provided string. Resets all previous CDATA!''' + self.data=[ustr(data)] + def setName(self, val): + ''' Changes the node name. ''' + self.name = val + def setNamespace(self, namespace): + ''' Changes the node namespace. ''' + self.namespace=namespace + def setParent(self, node): + ''' Sets node's parent to "node". WARNING: do not checks if the parent already present + and not removes the node from the list of childs of previous parent. ''' + self.parent = node + def setPayload(self,payload,add=0): + ''' Sets node payload according to the list specified. WARNING: completely replaces all node's + previous content. If you wish just to add child or CDATA - use addData or addChild methods. ''' + if isinstance(payload, basestring): payload=[payload] + if add: self.kids+=payload + else: self.kids=payload + def setTag(self, name, attrs={}, namespace=None): + ''' Same as getTag but if the node with specified namespace/attributes not found, creates such + node and returns it. ''' + node=self.getTags(name, attrs, namespace=namespace, one=1) + if node: return node + else: return self.addChild(name, attrs, namespace=namespace) + def setTagAttr(self, tag, attr, val): + ''' Creates new node (if not already present) with name "tag" + and sets it's attribute "attr" to value "val". ''' + try: + self.getTag(tag).attrs[attr]=val + except Exception: + self.addChild(tag, attrs={attr:val}) + def setTagData(self,tag,val,attrs={}): + ''' Creates new node (if not already present) with name "tag" and (optionally) attributes "attrs" + and sets it's CDATA to string "val". ''' + try: + self.getTag(tag, attrs).setData(ustr(val)) + except Exception: + self.addChild(tag, attrs, payload=[ustr(val)]) + def has_attr(self, key): + ''' Checks if node have attribute "key".''' + return key in self.attrs + def __getitem__(self, item): + ''' Returns node's attribute "item" value. ''' + return self.getAttr(item) + def __setitem__(self, item, val): + ''' Sets node's attribute "item" value. ''' + return self.setAttr(item, val) + def __delitem__(self, item): + ''' Deletes node's attribute "item". ''' + return self.delAttr(item) + def __contains__(self, item): + """ Checks if node has attribute "item" """ + return self.has_attr(item) + def __getattr__(self, attr): + ''' Reduce memory usage caused by T/NT classes - use memory only when needed. ''' + if attr=='T': + self.T=T(self) + return self.T + if attr=='NT': + self.NT=NT(self) + return self.NT + raise AttributeError class T: - ''' Auxiliary class used to quick access to node's child nodes. ''' - def __init__(self,node): self.__dict__['node']=node - def __getattr__(self,attr): return self.node.setTag(attr) - def __setattr__(self,attr,val): - if isinstance(val,Node): Node.__init__(self.node.setTag(attr),node=val) - else: return self.node.setTagData(attr,val) - def __delattr__(self,attr): return self.node.delChild(attr) + ''' Auxiliary class used to quick access to node's child nodes. ''' + def __init__(self, node): self.__dict__['node']=node + def __getattr__(self, attr): return self.node.setTag(attr) + def __setattr__(self, attr, val): + if isinstance(val, Node): Node.__init__(self.node.setTag(attr), node=val) + else: return self.node.setTagData(attr, val) + def __delattr__(self, attr): return self.node.delChild(attr) class NT(T): - ''' Auxiliary class used to quick create node's child nodes. ''' - def __getattr__(self,attr): return self.node.addChild(attr) - def __setattr__(self,attr,val): - if isinstance(val,Node): self.node.addChild(attr,node=val) - else: return self.node.addChild(attr,payload=[val]) + ''' Auxiliary class used to quick create node's child nodes. ''' + def __getattr__(self, attr): return self.node.addChild(attr) + def __setattr__(self, attr, val): + if isinstance(val, Node): self.node.addChild(attr, node=val) + else: return self.node.addChild(attr, payload=[val]) class NodeBuilder: - ''' Builds a Node class minidom from data parsed to it. This class used for two purposes: - 1. Creation an XML Node from a textual representation. F.e. reading a config file. See an XML2Node method. - 2. Handling an incoming XML stream. This is done by mangling - the __dispatch_depth parameter and redefining the dispatch method. - You do not need to use this class directly if you do not designing your own XML handler.''' - def __init__(self,data=None,initial_node=None): - ''' Takes two optional parameters: "data" and "initial_node". - By default class initialised with empty Node class instance. - Though, if "initial_node" is provided it used as "starting point". - You can think about it as of "node upgrade". - "data" (if provided) feeded to parser immidiatedly after instance init. - ''' - log.debug("Preparing to handle incoming XML stream.") - self._parser = xml.parsers.expat.ParserCreate() - self._parser.StartElementHandler = self.starttag - self._parser.EndElementHandler = self.endtag - self._parser.StartNamespaceDeclHandler = self.handle_namespace_start - self._parser.CharacterDataHandler = self.handle_cdata - self._parser.buffer_text = True - self.Parse = self._parser.Parse + ''' Builds a Node class minidom from data parsed to it. This class used for two purposes: + 1. Creation an XML Node from a textual representation. F.e. reading a config file. See an XML2Node method. + 2. Handling an incoming XML stream. This is done by mangling + the __dispatch_depth parameter and redefining the dispatch method. + You do not need to use this class directly if you do not designing your own XML handler.''' + def __init__(self,data=None,initial_node=None): + ''' Takes two optional parameters: "data" and "initial_node". + By default class initialised with empty Node class instance. + Though, if "initial_node" is provided it used as "starting point". + You can think about it as of "node upgrade". + "data" (if provided) feeded to parser immidiatedly after instance init. + ''' + log.debug("Preparing to handle incoming XML stream.") + self._parser = xml.parsers.expat.ParserCreate() + self._parser.StartElementHandler = self.starttag + self._parser.EndElementHandler = self.endtag + self._parser.StartNamespaceDeclHandler = self.handle_namespace_start + self._parser.CharacterDataHandler = self.handle_cdata + self._parser.buffer_text = True + self.Parse = self._parser.Parse - self.__depth = 0 - self.__last_depth = 0 - self.__max_depth = 0 - self._dispatch_depth = 1 - self._document_attrs = None - self._document_nsp = None - self._mini_dom=initial_node - self.last_is_data = 1 - self._ptr=None - self.data_buffer = None - self.streamError = '' - if data: - self._parser.Parse(data,1) + self.__depth = 0 + self.__last_depth = 0 + self.__max_depth = 0 + self._dispatch_depth = 1 + self._document_attrs = None + self._document_nsp = None + self._mini_dom=initial_node + self.last_is_data = 1 + self._ptr=None + self.data_buffer = None + self.streamError = '' + if data: + self._parser.Parse(data, 1) - def check_data_buffer(self): - if self.data_buffer: - self._ptr.data.append(''.join(self.data_buffer)) - del self.data_buffer[:] - self.data_buffer = None + def check_data_buffer(self): + if self.data_buffer: + self._ptr.data.append(''.join(self.data_buffer)) + del self.data_buffer[:] + self.data_buffer = None - def destroy(self): - ''' Method used to allow class instance to be garbage-collected. ''' - self.check_data_buffer() - self._parser.StartElementHandler = None - self._parser.EndElementHandler = None - self._parser.CharacterDataHandler = None - self._parser.StartNamespaceDeclHandler = None + def destroy(self): + ''' Method used to allow class instance to be garbage-collected. ''' + self.check_data_buffer() + self._parser.StartElementHandler = None + self._parser.EndElementHandler = None + self._parser.CharacterDataHandler = None + self._parser.StartNamespaceDeclHandler = None - def starttag(self, tag, attrs): - '''XML Parser callback. Used internally''' - self.check_data_buffer() - self._inc_depth() - log.info("STARTTAG.. DEPTH -> %i , tag -> %s, attrs -> %s" % (self.__depth, tag, `attrs`)) - if self.__depth == self._dispatch_depth: - if not self._mini_dom : - self._mini_dom = Node(tag=tag, attrs=attrs, nsp = self._document_nsp, node_built=True) - else: - Node.__init__(self._mini_dom,tag=tag, attrs=attrs, nsp = self._document_nsp, node_built=True) - self._ptr = self._mini_dom - elif self.__depth > self._dispatch_depth: - self._ptr.kids.append(Node(tag=tag,parent=self._ptr,attrs=attrs, node_built=True)) - self._ptr = self._ptr.kids[-1] - if self.__depth == 1: - self._document_attrs = {} - self._document_nsp = {} - nsp, name = (['']+tag.split(':'))[-2:] - for attr,val in attrs.items(): - if attr == 'xmlns': - self._document_nsp[u''] = val - elif attr.startswith('xmlns:'): - self._document_nsp[attr[6:]] = val - else: - self._document_attrs[attr] = val - ns = self._document_nsp.get(nsp, 'http://www.gajim.org/xmlns/undeclared-root') - try: - self.stream_header_received(ns, name, attrs) - except ValueError, e: - self._document_attrs = None - raise ValueError(str(e)) - if not self.last_is_data and self._ptr.parent: - self._ptr.parent.data.append('') - self.last_is_data = 0 - def endtag(self, tag ): - '''XML Parser callback. Used internally''' - log.info("DEPTH -> %i , tag -> %s" % (self.__depth, tag)) - self.check_data_buffer() - if self.__depth == self._dispatch_depth: - if self._mini_dom.getName() == 'error': - children = self._mini_dom.getChildren() - if children: - self.streamError = children[0].getName() - else: - self.streamError = self._mini_dom.getData() - self.dispatch(self._mini_dom) - elif self.__depth > self._dispatch_depth: - self._ptr = self._ptr.parent - else: - log.info("Got higher than dispatch level. Stream terminated?") - self._dec_depth() - self.last_is_data = 0 - if self.__depth == 0: self.stream_footer_received() + def starttag(self, tag, attrs): + '''XML Parser callback. Used internally''' + self.check_data_buffer() + self._inc_depth() + log.info("STARTTAG.. DEPTH -> %i , tag -> %s, attrs -> %s" % (self.__depth, tag, `attrs`)) + if self.__depth == self._dispatch_depth: + if not self._mini_dom : + self._mini_dom = Node(tag=tag, attrs=attrs, nsp = self._document_nsp, node_built=True) + else: + Node.__init__(self._mini_dom, tag=tag, attrs=attrs, nsp = self._document_nsp, node_built=True) + self._ptr = self._mini_dom + elif self.__depth > self._dispatch_depth: + self._ptr.kids.append(Node(tag=tag, parent=self._ptr, attrs=attrs, node_built=True)) + self._ptr = self._ptr.kids[-1] + if self.__depth == 1: + self._document_attrs = {} + self._document_nsp = {} + nsp, name = (['']+tag.split(':'))[-2:] + for attr, val in attrs.items(): + if attr == 'xmlns': + self._document_nsp[u''] = val + elif attr.startswith('xmlns:'): + self._document_nsp[attr[6:]] = val + else: + self._document_attrs[attr] = val + ns = self._document_nsp.get(nsp, 'http://www.gajim.org/xmlns/undeclared-root') + try: + self.stream_header_received(ns, name, attrs) + except ValueError, e: + self._document_attrs = None + raise ValueError(str(e)) + if not self.last_is_data and self._ptr.parent: + self._ptr.parent.data.append('') + self.last_is_data = 0 + def endtag(self, tag ): + '''XML Parser callback. Used internally''' + log.info("DEPTH -> %i , tag -> %s" % (self.__depth, tag)) + self.check_data_buffer() + if self.__depth == self._dispatch_depth: + if self._mini_dom.getName() == 'error': + children = self._mini_dom.getChildren() + if children: + self.streamError = children[0].getName() + else: + self.streamError = self._mini_dom.getData() + self.dispatch(self._mini_dom) + elif self.__depth > self._dispatch_depth: + self._ptr = self._ptr.parent + else: + log.info("Got higher than dispatch level. Stream terminated?") + self._dec_depth() + self.last_is_data = 0 + if self.__depth == 0: self.stream_footer_received() - def handle_cdata(self, data): - if self.last_is_data: - if self.data_buffer: - self.data_buffer.append(data) - elif self._ptr: - self.data_buffer = [data] - self.last_is_data = 1 + def handle_cdata(self, data): + if self.last_is_data: + if self.data_buffer: + self.data_buffer.append(data) + elif self._ptr: + self.data_buffer = [data] + self.last_is_data = 1 - def handle_namespace_start(self, prefix, uri): - '''XML Parser callback. Used internally''' - self.check_data_buffer() + def handle_namespace_start(self, prefix, uri): + '''XML Parser callback. Used internally''' + self.check_data_buffer() - def getDom(self): - ''' Returns just built Node. ''' - self.check_data_buffer() - return self._mini_dom - def dispatch(self,stanza): - ''' Gets called when the NodeBuilder reaches some level of depth on it's way up with the built - node as argument. Can be redefined to convert incoming XML stanzas to program events. ''' - def stream_header_received(self,ns,tag,attrs): - ''' Method called when stream just opened. ''' - self.check_data_buffer() - def stream_footer_received(self): - ''' Method called when stream just closed. ''' - self.check_data_buffer() + def getDom(self): + ''' Returns just built Node. ''' + self.check_data_buffer() + return self._mini_dom + def dispatch(self, stanza): + ''' Gets called when the NodeBuilder reaches some level of depth on it's way up with the built + node as argument. Can be redefined to convert incoming XML stanzas to program events. ''' + def stream_header_received(self, ns, tag, attrs): + ''' Method called when stream just opened. ''' + self.check_data_buffer() + def stream_footer_received(self): + ''' Method called when stream just closed. ''' + self.check_data_buffer() - def has_received_endtag(self, level=0): - ''' Return True if at least one end tag was seen (at level) ''' - return self.__depth <= level and self.__max_depth > level + def has_received_endtag(self, level=0): + ''' Return True if at least one end tag was seen (at level) ''' + return self.__depth <= level and self.__max_depth > level - def _inc_depth(self): - self.__last_depth = self.__depth - self.__depth += 1 - self.__max_depth = max(self.__depth, self.__max_depth) + def _inc_depth(self): + self.__last_depth = self.__depth + self.__depth += 1 + self.__max_depth = max(self.__depth, self.__max_depth) - def _dec_depth(self): - self.__last_depth = self.__depth - self.__depth -= 1 + def _dec_depth(self): + self.__last_depth = self.__depth + self.__depth -= 1 def XML2Node(xml): - ''' Converts supplied textual string into XML node. Handy f.e. for reading configuration file. - Raises xml.parser.expat.parsererror if provided string is not well-formed XML. ''' - return NodeBuilder(xml).getDom() + ''' Converts supplied textual string into XML node. Handy f.e. for reading configuration file. + Raises xml.parser.expat.parsererror if provided string is not well-formed XML. ''' + return NodeBuilder(xml).getDom() def BadXML2Node(xml): - ''' Converts supplied textual string into XML node. Survives if xml data is cutted half way round. - I.e. "some text
some more text". Will raise xml.parser.expat.parsererror on misplaced - tags though. F.e. "some text
some more text
" will not work.''' - return NodeBuilder(xml).getDom() - -# vim: se ts=3: + ''' Converts supplied textual string into XML node. Survives if xml data is cutted half way round. + I.e. "some text
some more text". Will raise xml.parser.expat.parsererror on misplaced + tags though. F.e. "some text
some more text
" will not work.''' + return NodeBuilder(xml).getDom() diff --git a/src/common/xmpp/stringprepare.py b/src/common/xmpp/stringprepare.py index 47b1a2d1e..89b61da48 100644 --- a/src/common/xmpp/stringprepare.py +++ b/src/common/xmpp/stringprepare.py @@ -26,176 +26,176 @@ import unicodedata from encodings import idna class ILookupTable: - """ Interface for character lookup classes. """ + """ Interface for character lookup classes. """ - def lookup(self, c): - """ Return whether character is in this table. """ + def lookup(self, c): + """ Return whether character is in this table. """ class IMappingTable: - """ Interface for character mapping classes. """ + """ Interface for character mapping classes. """ - def map(self, c): - """ Return mapping for character. """ + def map(self, c): + """ Return mapping for character. """ class LookupTableFromFunction: - __implements__ = ILookupTable + __implements__ = ILookupTable - def __init__(self, in_table_function): - self.lookup = in_table_function + def __init__(self, in_table_function): + self.lookup = in_table_function class LookupTable: - __implements__ = ILookupTable + __implements__ = ILookupTable - def __init__(self, table): - self._table = table + def __init__(self, table): + self._table = table - def lookup(self, c): - return c in self._table + def lookup(self, c): + return c in self._table class MappingTableFromFunction: - __implements__ = IMappingTable + __implements__ = IMappingTable - def __init__(self, map_table_function): - self.map = map_table_function + def __init__(self, map_table_function): + self.map = map_table_function class EmptyMappingTable: - __implements__ = IMappingTable + __implements__ = IMappingTable - def __init__(self, in_table_function): - self._in_table_function = in_table_function + def __init__(self, in_table_function): + self._in_table_function = in_table_function - def map(self, c): - if self._in_table_function(c): - return None - else: - return c + def map(self, c): + if self._in_table_function(c): + return None + else: + return c class Profile: - def __init__(self, mappings=[], normalize=True, prohibiteds=[], - check_unassigneds=True, check_bidi=True): - self.mappings = mappings - self.normalize = normalize - self.prohibiteds = prohibiteds - self.do_check_unassigneds = check_unassigneds - self.do_check_bidi = check_bidi + def __init__(self, mappings=[], normalize=True, prohibiteds=[], + check_unassigneds=True, check_bidi=True): + self.mappings = mappings + self.normalize = normalize + self.prohibiteds = prohibiteds + self.do_check_unassigneds = check_unassigneds + self.do_check_bidi = check_bidi - def prepare(self, string): - result = self.map(string) - if self.normalize: - result = unicodedata.normalize("NFKC", result) - self.check_prohibiteds(result) - if self.do_check_unassigneds: - self.check_unassigneds(result) - if self.do_check_bidi: - self.check_bidirectionals(result) - return result + def prepare(self, string): + result = self.map(string) + if self.normalize: + result = unicodedata.normalize("NFKC", result) + self.check_prohibiteds(result) + if self.do_check_unassigneds: + self.check_unassigneds(result) + if self.do_check_bidi: + self.check_bidirectionals(result) + return result - def map(self, string): - result = [] + def map(self, string): + result = [] - for c in string: - result_c = c + for c in string: + result_c = c - for mapping in self.mappings: - result_c = mapping.map(c) - if result_c != c: - break + for mapping in self.mappings: + result_c = mapping.map(c) + if result_c != c: + break - if result_c is not None: - result.append(result_c) + if result_c is not None: + result.append(result_c) - return u"".join(result) + return u"".join(result) - def check_prohibiteds(self, string): - for c in string: - for table in self.prohibiteds: - if table.lookup(c): - raise UnicodeError, "Invalid character %s" % repr(c) + def check_prohibiteds(self, string): + for c in string: + for table in self.prohibiteds: + if table.lookup(c): + raise UnicodeError, "Invalid character %s" % repr(c) - def check_unassigneds(self, string): - for c in string: - if stringprep.in_table_a1(c): - raise UnicodeError, "Unassigned code point %s" % repr(c) + def check_unassigneds(self, string): + for c in string: + if stringprep.in_table_a1(c): + raise UnicodeError, "Unassigned code point %s" % repr(c) - def check_bidirectionals(self, string): - found_LCat = False - found_RandALCat = False + def check_bidirectionals(self, string): + found_LCat = False + found_RandALCat = False - for c in string: - if stringprep.in_table_d1(c): - found_RandALCat = True - if stringprep.in_table_d2(c): - found_LCat = True + for c in string: + if stringprep.in_table_d1(c): + found_RandALCat = True + if stringprep.in_table_d2(c): + found_LCat = True - if found_LCat and found_RandALCat: - raise UnicodeError, "Violation of BIDI Requirement 2" + if found_LCat and found_RandALCat: + raise UnicodeError, "Violation of BIDI Requirement 2" - if found_RandALCat and not (stringprep.in_table_d1(string[0]) and - stringprep.in_table_d1(string[-1])): - raise UnicodeError, "Violation of BIDI Requirement 3" + if found_RandALCat and not (stringprep.in_table_d1(string[0]) and + stringprep.in_table_d1(string[-1])): + raise UnicodeError, "Violation of BIDI Requirement 3" class NamePrep: - """ Implements preparation of internationalized domain names. + """ Implements preparation of internationalized domain names. - This class implements preparing internationalized domain names using the - rules defined in RFC 3491, section 4 (Conversion operations). + This class implements preparing internationalized domain names using the + rules defined in RFC 3491, section 4 (Conversion operations). - We do not perform step 4 since we deal with unicode representations of - domain names and do not convert from or to ASCII representations using - punycode encoding. When such a conversion is needed, the L{idna} standard - library provides the C{ToUnicode()} and C{ToASCII()} functions. Note that - L{idna} itself assumes UseSTD3ASCIIRules to be false. + We do not perform step 4 since we deal with unicode representations of + domain names and do not convert from or to ASCII representations using + punycode encoding. When such a conversion is needed, the L{idna} standard + library provides the C{ToUnicode()} and C{ToASCII()} functions. Note that + L{idna} itself assumes UseSTD3ASCIIRules to be false. - The following steps are performed by C{prepare()}: + The following steps are performed by C{prepare()}: - * Split the domain name in labels at the dots (RFC 3490, 3.1) - * Apply nameprep proper on each label (RFC 3491) - * Enforce the restrictions on ASCII characters in host names by - assuming STD3ASCIIRules to be true. (STD 3) - * Rejoin the labels using the label separator U+002E (full stop). - """ + * Split the domain name in labels at the dots (RFC 3490, 3.1) + * Apply nameprep proper on each label (RFC 3491) + * Enforce the restrictions on ASCII characters in host names by + assuming STD3ASCIIRules to be true. (STD 3) + * Rejoin the labels using the label separator U+002E (full stop). + """ - # Prohibited characters. - prohibiteds = [unichr(n) for n in range(0x00, 0x2c + 1) + - range(0x2e, 0x2f + 1) + - range(0x3a, 0x40 + 1) + - range(0x5b, 0x60 + 1) + - range(0x7b, 0x7f + 1) ] + # Prohibited characters. + prohibiteds = [unichr(n) for n in range(0x00, 0x2c + 1) + + range(0x2e, 0x2f + 1) + + range(0x3a, 0x40 + 1) + + range(0x5b, 0x60 + 1) + + range(0x7b, 0x7f + 1) ] - def prepare(self, string): - result = [] + def prepare(self, string): + result = [] - labels = idna.dots.split(string) + labels = idna.dots.split(string) - if labels and len(labels[-1]) == 0: - trailing_dot = '.' - del labels[-1] - else: - trailing_dot = '' + if labels and len(labels[-1]) == 0: + trailing_dot = '.' + del labels[-1] + else: + trailing_dot = '' - for label in labels: - result.append(self.nameprep(label)) + for label in labels: + result.append(self.nameprep(label)) - return ".".join(result)+trailing_dot + return ".".join(result)+trailing_dot - def check_prohibiteds(self, string): - for c in string: - if c in self.prohibiteds: - raise UnicodeError, "Invalid character %s" % repr(c) + def check_prohibiteds(self, string): + for c in string: + if c in self.prohibiteds: + raise UnicodeError, "Invalid character %s" % repr(c) - def nameprep(self, label): - label = idna.nameprep(label) - self.check_prohibiteds(label) - if label[0] == '-': - raise UnicodeError, "Invalid leading hyphen-minus" - if label[-1] == '-': - raise UnicodeError, "Invalid trailing hyphen-minus" - return label + def nameprep(self, label): + label = idna.nameprep(label) + self.check_prohibiteds(label) + if label[0] == '-': + raise UnicodeError, "Invalid leading hyphen-minus" + if label[-1] == '-': + raise UnicodeError, "Invalid trailing hyphen-minus" + return label C_11 = LookupTableFromFunction(stringprep.in_table_c11) C_12 = LookupTableFromFunction(stringprep.in_table_c12) @@ -213,15 +213,13 @@ B_1 = EmptyMappingTable(stringprep.in_table_b1) B_2 = MappingTableFromFunction(stringprep.map_table_b2) nodeprep = Profile(mappings=[B_1, B_2], - prohibiteds=[C_11, C_12, C_21, C_22, - C_3, C_4, C_5, C_6, C_7, C_8, C_9, - LookupTable([u'"', u'&', u"'", u'/', - u':', u'<', u'>', u'@'])]) + prohibiteds=[C_11, C_12, C_21, C_22, + C_3, C_4, C_5, C_6, C_7, C_8, C_9, + LookupTable([u'"', u'&', u"'", u'/', + u':', u'<', u'>', u'@'])]) resourceprep = Profile(mappings=[B_1,], - prohibiteds=[C_12, C_21, C_22, - C_3, C_4, C_5, C_6, C_7, C_8, C_9]) + prohibiteds=[C_12, C_21, C_22, + C_3, C_4, C_5, C_6, C_7, C_8, C_9]) nameprep = NamePrep() - -# vim: se ts=3: diff --git a/src/common/xmpp/tls_nb.py b/src/common/xmpp/tls_nb.py index 5ed10728b..a756f7bc8 100644 --- a/src/common/xmpp/tls_nb.py +++ b/src/common/xmpp/tls_nb.py @@ -33,378 +33,376 @@ PYOPENSSL = 'PYOPENSSL' PYSTDLIB = 'PYSTDLIB' try: - #raise ImportError("Manually disabled PyOpenSSL") - import OpenSSL.SSL - import OpenSSL.crypto - USE_PYOPENSSL = True - log.info("PyOpenSSL loaded") + #raise ImportError("Manually disabled PyOpenSSL") + import OpenSSL.SSL + import OpenSSL.crypto + USE_PYOPENSSL = True + log.info("PyOpenSSL loaded") except ImportError: - log.debug("Import of PyOpenSSL failed:", exc_info=True) + log.debug("Import of PyOpenSSL failed:", exc_info=True) - # FIXME: Remove these prints before release, replace with a warning dialog. - print >> sys.stderr, "=" * 79 - print >> sys.stderr, "PyOpenSSL not found, falling back to Python builtin SSL objects (insecure)." - print >> sys.stderr, "=" * 79 + # FIXME: Remove these prints before release, replace with a warning dialog. + print >> sys.stderr, "=" * 79 + print >> sys.stderr, "PyOpenSSL not found, falling back to Python builtin SSL objects (insecure)." + print >> sys.stderr, "=" * 79 def gattr(obj, attr, default=None): - try: - return getattr(obj, attr) - except AttributeError: - return default + try: + return getattr(obj, attr) + except AttributeError: + return default class SSLWrapper: - ''' - Abstract SSLWrapper base class - ''' - class Error(IOError): - ''' Generic SSL Error Wrapper ''' - def __init__(self, sock=None, exc=None, errno=None, strerror=None, - peer=None): - self.parent = IOError + ''' + Abstract SSLWrapper base class + ''' + class Error(IOError): + ''' Generic SSL Error Wrapper ''' + def __init__(self, sock=None, exc=None, errno=None, strerror=None, + peer=None): + self.parent = IOError - errno = errno or gattr(exc, 'errno') or exc[0] - strerror = strerror or gattr(exc, 'strerror') or gattr(exc, 'args') - if not isinstance(strerror, basestring): - strerror = repr(strerror) + errno = errno or gattr(exc, 'errno') or exc[0] + strerror = strerror or gattr(exc, 'strerror') or gattr(exc, 'args') + if not isinstance(strerror, basestring): + strerror = repr(strerror) - self.sock = sock - self.exc = exc - self.peer = peer - self.exc_name = None - self.exc_args = None - self.exc_str = None - self.exc_repr = None + self.sock = sock + self.exc = exc + self.peer = peer + self.exc_name = None + self.exc_args = None + self.exc_str = None + self.exc_repr = None - if self.exc is not None: - self.exc_name = str(self.exc.__class__) - self.exc_args = gattr(self.exc, 'args') - self.exc_str = str(self.exc) - self.exc_repr = repr(self.exc) - if not errno: - try: - if isinstance(exc, OpenSSL.SSL.SysCallError): - if self.exc_args[0] > 0: - errno = self.exc_args[0] - strerror = self.exc_args[1] - except: pass + if self.exc is not None: + self.exc_name = str(self.exc.__class__) + self.exc_args = gattr(self.exc, 'args') + self.exc_str = str(self.exc) + self.exc_repr = repr(self.exc) + if not errno: + try: + if isinstance(exc, OpenSSL.SSL.SysCallError): + if self.exc_args[0] > 0: + errno = self.exc_args[0] + strerror = self.exc_args[1] + except: pass - self.parent.__init__(self, errno, strerror) + self.parent.__init__(self, errno, strerror) - if self.peer is None and sock is not None: - try: - ppeer = self.sock.getpeername() - if len(ppeer) == 2 and isinstance(ppeer[0], basestring) \ - and isinstance(ppeer[1], int): - self.peer = ppeer - except: - pass + if self.peer is None and sock is not None: + try: + ppeer = self.sock.getpeername() + if len(ppeer) == 2 and isinstance(ppeer[0], basestring) \ + and isinstance(ppeer[1], int): + self.peer = ppeer + except: + pass - def __str__(self): - s = str(self.__class__) - if self.peer: - s += " for %s:%d" % self.peer - if self.errno is not None: - s += ": [Errno: %d]" % self.errno - if self.strerror: - s += " (%s)" % self.strerror - if self.exc_name: - s += ", Caused by %s" % self.exc_name - if self.exc_str: - if self.strerror: - s += "(%s)" % self.exc_str - else: s += "(%s)" % str(self.exc_args) - return s + def __str__(self): + s = str(self.__class__) + if self.peer: + s += " for %s:%d" % self.peer + if self.errno is not None: + s += ": [Errno: %d]" % self.errno + if self.strerror: + s += " (%s)" % self.strerror + if self.exc_name: + s += ", Caused by %s" % self.exc_name + if self.exc_str: + if self.strerror: + s += "(%s)" % self.exc_str + else: s += "(%s)" % str(self.exc_args) + return s - def __init__(self, sslobj, sock=None): - self.sslobj = sslobj - self.sock = sock - log.debug("%s.__init__ called with %s", self.__class__, sslobj) + def __init__(self, sslobj, sock=None): + self.sslobj = sslobj + self.sock = sock + log.debug("%s.__init__ called with %s", self.__class__, sslobj) - def recv(self, data, flags=None): - ''' - Receive wrapper for SSL object + def recv(self, data, flags=None): + ''' + Receive wrapper for SSL object - We can return None out of this function to signal that no data is - available right now. Better than an exception, which differs - depending on which SSL lib we're using. Unfortunately returning '' - can indicate that the socket has been closed, so to be sure, we avoid - this by returning None. - ''' - raise NotImplementedError + We can return None out of this function to signal that no data is + available right now. Better than an exception, which differs + depending on which SSL lib we're using. Unfortunately returning '' + can indicate that the socket has been closed, so to be sure, we avoid + this by returning None. + ''' + raise NotImplementedError - def send(self, data, flags=None, now=False): - ''' Send wrapper for SSL object ''' - raise NotImplementedError + def send(self, data, flags=None, now=False): + ''' Send wrapper for SSL object ''' + raise NotImplementedError class PyOpenSSLWrapper(SSLWrapper): - '''Wrapper class for PyOpenSSL's recv() and send() methods''' + '''Wrapper class for PyOpenSSL's recv() and send() methods''' - def __init__(self, *args): - self.parent = SSLWrapper - self.parent.__init__(self, *args) + def __init__(self, *args): + self.parent = SSLWrapper + self.parent.__init__(self, *args) - def is_numtoolarge(self, e): - ''' Magic methods don't need documentation ''' - t = ('asn1 encoding routines', 'a2d_ASN1_OBJECT', 'first num too large') - return (isinstance(e.args, (list, tuple)) and len(e.args) == 1 and - isinstance(e.args[0], (list, tuple)) and len(e.args[0]) == 2 and - e.args[0][0] == e.args[0][1] == t) + def is_numtoolarge(self, e): + ''' Magic methods don't need documentation ''' + t = ('asn1 encoding routines', 'a2d_ASN1_OBJECT', 'first num too large') + return (isinstance(e.args, (list, tuple)) and len(e.args) == 1 and + isinstance(e.args[0], (list, tuple)) and len(e.args[0]) == 2 and + e.args[0][0] == e.args[0][1] == t) - def recv(self, bufsize, flags=None): - retval = None - try: - if flags is None: - retval = self.sslobj.recv(bufsize) - else: - retval = self.sslobj.recv(bufsize, flags) - except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e: - log.debug("Recv: Want-error: " + repr(e)) - except OpenSSL.SSL.SysCallError, e: - log.debug("Recv: Got OpenSSL.SSL.SysCallError: " + repr(e), - exc_info=True) - raise SSLWrapper.Error(self.sock or self.sslobj, e) - except OpenSSL.SSL.ZeroReturnError, e: - # end-of-connection raises ZeroReturnError instead of having the - # connection's .recv() method return a zero-sized result. - raise SSLWrapper.Error(self.sock or self.sslobj, e, -1) - except OpenSSL.SSL.Error, e: - if self.is_numtoolarge(e): - # warn, but ignore this exception - log.warning("Recv: OpenSSL: asn1enc: first num too large (ignored)") - else: - log.debug("Recv: Caught OpenSSL.SSL.Error:", exc_info=True) - raise SSLWrapper.Error(self.sock or self.sslobj, e) - return retval + def recv(self, bufsize, flags=None): + retval = None + try: + if flags is None: + retval = self.sslobj.recv(bufsize) + else: + retval = self.sslobj.recv(bufsize, flags) + except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e: + log.debug("Recv: Want-error: " + repr(e)) + except OpenSSL.SSL.SysCallError, e: + log.debug("Recv: Got OpenSSL.SSL.SysCallError: " + repr(e), + exc_info=True) + raise SSLWrapper.Error(self.sock or self.sslobj, e) + except OpenSSL.SSL.ZeroReturnError, e: + # end-of-connection raises ZeroReturnError instead of having the + # connection's .recv() method return a zero-sized result. + raise SSLWrapper.Error(self.sock or self.sslobj, e, -1) + except OpenSSL.SSL.Error, e: + if self.is_numtoolarge(e): + # warn, but ignore this exception + log.warning("Recv: OpenSSL: asn1enc: first num too large (ignored)") + else: + log.debug("Recv: Caught OpenSSL.SSL.Error:", exc_info=True) + raise SSLWrapper.Error(self.sock or self.sslobj, e) + return retval - def send(self, data, flags=None, now=False): - try: - if flags is None: - return self.sslobj.send(data) - else: - return self.sslobj.send(data, flags) - except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e: - #log.debug("Send: " + repr(e)) - time.sleep(0.1) # prevent 100% CPU usage - except OpenSSL.SSL.SysCallError, e: - log.error("Send: Got OpenSSL.SSL.SysCallError: " + repr(e), - exc_info=True) - raise SSLWrapper.Error(self.sock or self.sslobj, e) - except OpenSSL.SSL.Error, e: - if self.is_numtoolarge(e): - # warn, but ignore this exception - log.warning("Send: OpenSSL: asn1enc: first num too large (ignored)") - else: - log.error("Send: Caught OpenSSL.SSL.Error:", exc_info=True) - raise SSLWrapper.Error(self.sock or self.sslobj, e) - return 0 + def send(self, data, flags=None, now=False): + try: + if flags is None: + return self.sslobj.send(data) + else: + return self.sslobj.send(data, flags) + except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e: + #log.debug("Send: " + repr(e)) + time.sleep(0.1) # prevent 100% CPU usage + except OpenSSL.SSL.SysCallError, e: + log.error("Send: Got OpenSSL.SSL.SysCallError: " + repr(e), + exc_info=True) + raise SSLWrapper.Error(self.sock or self.sslobj, e) + except OpenSSL.SSL.Error, e: + if self.is_numtoolarge(e): + # warn, but ignore this exception + log.warning("Send: OpenSSL: asn1enc: first num too large (ignored)") + else: + log.error("Send: Caught OpenSSL.SSL.Error:", exc_info=True) + raise SSLWrapper.Error(self.sock or self.sslobj, e) + return 0 class StdlibSSLWrapper(SSLWrapper): - '''Wrapper class for Python socket.ssl read() and write() methods''' + '''Wrapper class for Python socket.ssl read() and write() methods''' - def __init__(self, *args): - self.parent = SSLWrapper - self.parent.__init__(self, *args) + def __init__(self, *args): + self.parent = SSLWrapper + self.parent.__init__(self, *args) - def recv(self, bufsize, flags=None): - # we simply ignore flags since ssl object doesn't support it - try: - return self.sslobj.read(bufsize) - except socket.sslerror, e: - log.debug("Recv: Caught socket.sslerror: " + repr(e), exc_info=True) - if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE): - raise SSLWrapper.Error(self.sock or self.sslobj, e) - return None + def recv(self, bufsize, flags=None): + # we simply ignore flags since ssl object doesn't support it + try: + return self.sslobj.read(bufsize) + except socket.sslerror, e: + log.debug("Recv: Caught socket.sslerror: " + repr(e), exc_info=True) + if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE): + raise SSLWrapper.Error(self.sock or self.sslobj, e) + return None - def send(self, data, flags=None, now=False): - # we simply ignore flags since ssl object doesn't support it - try: - return self.sslobj.write(data) - except socket.sslerror, e: - log.debug("Send: Caught socket.sslerror:", exc_info=True) - if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE): - raise SSLWrapper.Error(self.sock or self.sslobj, e) - return 0 + def send(self, data, flags=None, now=False): + # we simply ignore flags since ssl object doesn't support it + try: + return self.sslobj.write(data) + except socket.sslerror, e: + log.debug("Send: Caught socket.sslerror:", exc_info=True) + if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE): + raise SSLWrapper.Error(self.sock or self.sslobj, e) + return 0 class NonBlockingTLS(PlugIn): - ''' - TLS connection used to encrypts already estabilished tcp connection. + ''' + TLS connection used to encrypts already estabilished tcp connection. - Can be plugged into NonBlockingTCP and will make use of StdlibSSLWrapper or - PyOpenSSLWrapper. - ''' + Can be plugged into NonBlockingTCP and will make use of StdlibSSLWrapper or + PyOpenSSLWrapper. + ''' - def __init__(self, cacerts, mycerts): - ''' - :param cacerts: path to pem file with certificates of known XMPP servers - :param mycerts: path to pem file with certificates of user trusted servers - ''' - PlugIn.__init__(self) - self.cacerts = cacerts - self.mycerts = mycerts + def __init__(self, cacerts, mycerts): + ''' + :param cacerts: path to pem file with certificates of known XMPP servers + :param mycerts: path to pem file with certificates of user trusted servers + ''' + PlugIn.__init__(self) + self.cacerts = cacerts + self.mycerts = mycerts - # from ssl.h (partial extract) - ssl_h_bits = { "SSL_ST_CONNECT": 0x1000, "SSL_ST_ACCEPT": 0x2000, - "SSL_CB_LOOP": 0x01, "SSL_CB_EXIT": 0x02, - "SSL_CB_READ": 0x04, "SSL_CB_WRITE": 0x08, - "SSL_CB_ALERT": 0x4000, - "SSL_CB_HANDSHAKE_START": 0x10, "SSL_CB_HANDSHAKE_DONE": 0x20} + # from ssl.h (partial extract) + ssl_h_bits = { "SSL_ST_CONNECT": 0x1000, "SSL_ST_ACCEPT": 0x2000, + "SSL_CB_LOOP": 0x01, "SSL_CB_EXIT": 0x02, + "SSL_CB_READ": 0x04, "SSL_CB_WRITE": 0x08, + "SSL_CB_ALERT": 0x4000, + "SSL_CB_HANDSHAKE_START": 0x10, "SSL_CB_HANDSHAKE_DONE": 0x20} - def plugin(self, owner): - ''' - Use to PlugIn TLS into transport and start establishing immediately - Returns True if TLS/SSL was established correctly, otherwise False. - ''' - log.info('Starting TLS estabilishing') - try: - res = self._startSSL() - except Exception, e: - log.error("PlugIn: while trying _startSSL():", exc_info=True) - return False - return res + def plugin(self, owner): + ''' + Use to PlugIn TLS into transport and start establishing immediately + Returns True if TLS/SSL was established correctly, otherwise False. + ''' + log.info('Starting TLS estabilishing') + try: + res = self._startSSL() + except Exception, e: + log.error("PlugIn: while trying _startSSL():", exc_info=True) + return False + return res - def _dumpX509(self, cert, stream=sys.stderr): - print >> stream, "Digest (SHA-1):", cert.digest("sha1") - print >> stream, "Digest (MD5):", cert.digest("md5") - print >> stream, "Serial #:", cert.get_serial_number() - print >> stream, "Version:", cert.get_version() - print >> stream, "Expired:", ("Yes" if cert.has_expired() else "No") - print >> stream, "Subject:" - self._dumpX509Name(cert.get_subject(), stream) - print >> stream, "Issuer:" - self._dumpX509Name(cert.get_issuer(), stream) - self._dumpPKey(cert.get_pubkey(), stream) + def _dumpX509(self, cert, stream=sys.stderr): + print >> stream, "Digest (SHA-1):", cert.digest("sha1") + print >> stream, "Digest (MD5):", cert.digest("md5") + print >> stream, "Serial #:", cert.get_serial_number() + print >> stream, "Version:", cert.get_version() + print >> stream, "Expired:", ("Yes" if cert.has_expired() else "No") + print >> stream, "Subject:" + self._dumpX509Name(cert.get_subject(), stream) + print >> stream, "Issuer:" + self._dumpX509Name(cert.get_issuer(), stream) + self._dumpPKey(cert.get_pubkey(), stream) - def _dumpX509Name(self, name, stream=sys.stderr): - print >> stream, "X509Name:", str(name) + def _dumpX509Name(self, name, stream=sys.stderr): + print >> stream, "X509Name:", str(name) - def _dumpPKey(self, pkey, stream=sys.stderr): - typedict = {OpenSSL.crypto.TYPE_RSA: "RSA", - OpenSSL.crypto.TYPE_DSA: "DSA"} - print >> stream, "PKey bits:", pkey.bits() - print >> stream, "PKey type: %s (%d)" % (typedict.get(pkey.type(), - "Unknown"), pkey.type()) + def _dumpPKey(self, pkey, stream=sys.stderr): + typedict = {OpenSSL.crypto.TYPE_RSA: "RSA", + OpenSSL.crypto.TYPE_DSA: "DSA"} + print >> stream, "PKey bits:", pkey.bits() + print >> stream, "PKey type: %s (%d)" % (typedict.get(pkey.type(), + "Unknown"), pkey.type()) - def _startSSL(self): - ''' Immediatedly switch socket to TLS mode. Used internally.''' - log.debug("_startSSL called") + def _startSSL(self): + ''' Immediatedly switch socket to TLS mode. Used internally.''' + log.debug("_startSSL called") - if USE_PYOPENSSL: - result = self._startSSL_pyOpenSSL() - else: - result = self._startSSL_stdlib() + if USE_PYOPENSSL: + result = self._startSSL_pyOpenSSL() + else: + result = self._startSSL_stdlib() - if result: - log.debug('Synchronous handshake completed') - return True - else: - return False + if result: + log.debug('Synchronous handshake completed') + return True + else: + return False - def _load_cert_file(self, cert_path, cert_store, logg=True): - if not os.path.isfile(cert_path): - return - try: - f = open(cert_path) - except IOError, e: - log.warning('Unable to open certificate file %s: %s' % \ - (cert_path, str(e))) - return - lines = f.readlines() - i = 0 - begin = -1 - for line in lines: - if 'BEGIN CERTIFICATE' in line: - begin = i - elif 'END CERTIFICATE' in line and begin > -1: - cert = ''.join(lines[begin:i+2]) - try: - x509cert = OpenSSL.crypto.load_certificate( - OpenSSL.crypto.FILETYPE_PEM, cert) - cert_store.add_cert(x509cert) - except OpenSSL.crypto.Error, exception_obj: - if logg: - log.warning('Unable to load a certificate from file %s: %s' %\ - (cert_path, exception_obj.args[0][0][2])) - except: - log.warning('Unknown error while loading certificate from file ' - '%s' % cert_path) - begin = -1 - i += 1 + def _load_cert_file(self, cert_path, cert_store, logg=True): + if not os.path.isfile(cert_path): + return + try: + f = open(cert_path) + except IOError, e: + log.warning('Unable to open certificate file %s: %s' % \ + (cert_path, str(e))) + return + lines = f.readlines() + i = 0 + begin = -1 + for line in lines: + if 'BEGIN CERTIFICATE' in line: + begin = i + elif 'END CERTIFICATE' in line and begin > -1: + cert = ''.join(lines[begin:i+2]) + try: + x509cert = OpenSSL.crypto.load_certificate( + OpenSSL.crypto.FILETYPE_PEM, cert) + cert_store.add_cert(x509cert) + except OpenSSL.crypto.Error, exception_obj: + if logg: + log.warning('Unable to load a certificate from file %s: %s' %\ + (cert_path, exception_obj.args[0][0][2])) + except: + log.warning('Unknown error while loading certificate from file ' + '%s' % cert_path) + begin = -1 + i += 1 - def _startSSL_pyOpenSSL(self): - log.debug("_startSSL_pyOpenSSL called") - tcpsock = self._owner - # See http://docs.python.org/dev/library/ssl.html - tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) - tcpsock.ssl_errnum = 0 - tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER, - self._ssl_verify_callback) - try: - tcpsock._sslContext.load_verify_locations(self.cacerts) - except: - log.warning('Unable to load SSL certificates from file %s' % \ - os.path.abspath(self.cacerts)) - store = tcpsock._sslContext.get_cert_store() - self._load_cert_file(self.mycerts, store) - if os.path.isdir('/etc/ssl/certs'): - for f in os.listdir('/etc/ssl/certs'): - # We don't logg because there is a lot a duplicated certs in this - # folder - self._load_cert_file(os.path.join('/etc/ssl/certs', f), store, - logg=False) + def _startSSL_pyOpenSSL(self): + log.debug("_startSSL_pyOpenSSL called") + tcpsock = self._owner + # See http://docs.python.org/dev/library/ssl.html + tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) + tcpsock.ssl_errnum = 0 + tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER, + self._ssl_verify_callback) + try: + tcpsock._sslContext.load_verify_locations(self.cacerts) + except: + log.warning('Unable to load SSL certificates from file %s' % \ + os.path.abspath(self.cacerts)) + store = tcpsock._sslContext.get_cert_store() + self._load_cert_file(self.mycerts, store) + if os.path.isdir('/etc/ssl/certs'): + for f in os.listdir('/etc/ssl/certs'): + # We don't logg because there is a lot a duplicated certs in this + # folder + self._load_cert_file(os.path.join('/etc/ssl/certs', f), store, + logg=False) - tcpsock._sslObj = OpenSSL.SSL.Connection(tcpsock._sslContext, - tcpsock._sock) - tcpsock._sslObj.set_connect_state() # set to client mode - wrapper = PyOpenSSLWrapper(tcpsock._sslObj) - tcpsock._recv = wrapper.recv - tcpsock._send = wrapper.send + tcpsock._sslObj = OpenSSL.SSL.Connection(tcpsock._sslContext, + tcpsock._sock) + tcpsock._sslObj.set_connect_state() # set to client mode + wrapper = PyOpenSSLWrapper(tcpsock._sslObj) + tcpsock._recv = wrapper.recv + tcpsock._send = wrapper.send - log.debug("Initiating handshake...") - tcpsock._sslObj.setblocking(True) - try: - tcpsock._sslObj.do_handshake() - except: - log.error('Error while TLS handshake: ', exc_info=True) - return False - tcpsock._sslObj.setblocking(False) - self._owner.ssl_lib = PYOPENSSL - return True + log.debug("Initiating handshake...") + tcpsock._sslObj.setblocking(True) + try: + tcpsock._sslObj.do_handshake() + except: + log.error('Error while TLS handshake: ', exc_info=True) + return False + tcpsock._sslObj.setblocking(False) + self._owner.ssl_lib = PYOPENSSL + return True - def _startSSL_stdlib(self): - log.debug("_startSSL_stdlib called") - tcpsock=self._owner - try: - tcpsock._sock.setblocking(True) - tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None) - tcpsock._sock.setblocking(False) - tcpsock._sslIssuer = tcpsock._sslObj.issuer() - tcpsock._sslServer = tcpsock._sslObj.server() - wrapper = StdlibSSLWrapper(tcpsock._sslObj, tcpsock._sock) - tcpsock._recv = wrapper.recv - tcpsock._send = wrapper.send - except: - log.error("Exception caught in _startSSL_stdlib:", exc_info=True) - return False - self._owner.ssl_lib = PYSTDLIB - return True + def _startSSL_stdlib(self): + log.debug("_startSSL_stdlib called") + tcpsock=self._owner + try: + tcpsock._sock.setblocking(True) + tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None) + tcpsock._sock.setblocking(False) + tcpsock._sslIssuer = tcpsock._sslObj.issuer() + tcpsock._sslServer = tcpsock._sslObj.server() + wrapper = StdlibSSLWrapper(tcpsock._sslObj, tcpsock._sock) + tcpsock._recv = wrapper.recv + tcpsock._send = wrapper.send + except: + log.error("Exception caught in _startSSL_stdlib:", exc_info=True) + return False + self._owner.ssl_lib = PYSTDLIB + return True - def _ssl_verify_callback(self, sslconn, cert, errnum, depth, ok): - # Exceptions can't propagate up through this callback, so print them here. - try: - self._owner.ssl_fingerprint_sha1 = cert.digest('sha1') - if errnum == 0: - return True - self._owner.ssl_errnum = errnum - self._owner.ssl_cert_pem = OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_PEM, cert) - return True - except: - log.error("Exception caught in _ssl_info_callback:", exc_info=True) - # Make sure something is printed, even if log is disabled. - traceback.print_exc() - -# vim: se ts=3: + def _ssl_verify_callback(self, sslconn, cert, errnum, depth, ok): + # Exceptions can't propagate up through this callback, so print them here. + try: + self._owner.ssl_fingerprint_sha1 = cert.digest('sha1') + if errnum == 0: + return True + self._owner.ssl_errnum = errnum + self._owner.ssl_cert_pem = OpenSSL.crypto.dump_certificate( + OpenSSL.crypto.FILETYPE_PEM, cert) + return True + except: + log.error("Exception caught in _ssl_info_callback:", exc_info=True) + # Make sure something is printed, even if log is disabled. + traceback.print_exc() diff --git a/src/common/xmpp/transports_nb.py b/src/common/xmpp/transports_nb.py index e6d63bcb3..6adf4e69c 100644 --- a/src/common/xmpp/transports_nb.py +++ b/src/common/xmpp/transports_nb.py @@ -41,40 +41,40 @@ import logging log = logging.getLogger('gajim.c.x.transports_nb') def urisplit(uri): - ''' - Function for splitting URI string to tuple (protocol, host, port, path). - e.g. urisplit('http://httpcm.jabber.org:123/webclient') returns - ('http', 'httpcm.jabber.org', 123, '/webclient') - return 443 as default port if proto is https else 80 - ''' - splitted = urlparse.urlsplit(uri) - proto, host, path = splitted.scheme, splitted.hostname, splitted.path - try: - port = splitted.port - except ValueError: - log.warn('port cannot be extracted from BOSH URL %s, using default port' \ - % uri) - port = '' - if not port: - if proto == 'https': - port = 443 - else: - port = 80 - return proto, host, port, path + ''' + Function for splitting URI string to tuple (protocol, host, port, path). + e.g. urisplit('http://httpcm.jabber.org:123/webclient') returns + ('http', 'httpcm.jabber.org', 123, '/webclient') + return 443 as default port if proto is https else 80 + ''' + splitted = urlparse.urlsplit(uri) + proto, host, path = splitted.scheme, splitted.hostname, splitted.path + try: + port = splitted.port + except ValueError: + log.warn('port cannot be extracted from BOSH URL %s, using default port' \ + % uri) + port = '' + if not port: + if proto == 'https': + port = 443 + else: + port = 80 + return proto, host, port, path def get_proxy_data_from_dict(proxy): - tcp_host, tcp_port, proxy_user, proxy_pass = None, None, None, None - proxy_type = proxy['type'] - if proxy_type == 'bosh' and not proxy['bosh_useproxy']: - # with BOSH not over proxy we have to parse the hostname from BOSH URI - proto, tcp_host, tcp_port, path = urisplit(proxy['bosh_uri']) - else: - # with proxy!=bosh or with bosh over HTTP proxy we're connecting to proxy - # machine - tcp_host, tcp_port = proxy['host'], proxy['port'] - if proxy.get('useauth', False): - proxy_user, proxy_pass = proxy['user'], proxy['pass'] - return tcp_host, tcp_port, proxy_user, proxy_pass + tcp_host, tcp_port, proxy_user, proxy_pass = None, None, None, None + proxy_type = proxy['type'] + if proxy_type == 'bosh' and not proxy['bosh_useproxy']: + # with BOSH not over proxy we have to parse the hostname from BOSH URI + proto, tcp_host, tcp_port, path = urisplit(proxy['bosh_uri']) + else: + # with proxy!=bosh or with bosh over HTTP proxy we're connecting to proxy + # machine + tcp_host, tcp_port = proxy['host'], proxy['port'] + if proxy.get('useauth', False): + proxy_user, proxy_pass = proxy['user'], proxy['pass'] + return tcp_host, tcp_port, proxy_user, proxy_pass #: timeout to connect to the server socket, it doesn't include auth CONNECT_TIMEOUT_SECONDS = 30 @@ -102,666 +102,664 @@ CONNECTED = 'CONNECTED' STATES = (DISCONNECTED, CONNECTING, PROXY_CONNECTING, CONNECTED, DISCONNECTING) class NonBlockingTransport(PlugIn): - ''' - Abstract class representing a transport. + ''' + Abstract class representing a transport. - Subclasses CAN have different constructor signature but connect method SHOULD - be the same. - ''' - def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, - certs): - ''' - Each trasport class can have different constructor but it has to have at - least all the arguments of NonBlockingTransport constructor. + Subclasses CAN have different constructor signature but connect method SHOULD + be the same. + ''' + def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, + certs): + ''' + Each trasport class can have different constructor but it has to have at + least all the arguments of NonBlockingTransport constructor. - :param raise_event: callback for monitoring of sent and received data - :param on_disconnect: callback called on disconnection during runtime - :param idlequeue: processing idlequeue - :param estabilish_tls: boolean whether to estabilish TLS connection after - TCP connection is done - :param certs: tuple of (cacerts, mycerts) see constructor of - tls_nb.NonBlockingTLS for more details - ''' - PlugIn.__init__(self) - self.raise_event = raise_event - self.on_disconnect = on_disconnect - self.on_connect = None - self.on_connect_failure = None - self.idlequeue = idlequeue - self.on_receive = None - self.server = None - self.port = None - self.conn_5tuple = None - self.set_state(DISCONNECTED) - self.estabilish_tls = estabilish_tls - self.certs = certs - # type of used ssl lib (if any) will be assigned to this member var - self.ssl_lib = None - self._exported_methods=[self.onreceive, self.set_send_timeout, - self.set_send_timeout2, self.set_timeout, self.remove_timeout, - self.start_disconnect] + :param raise_event: callback for monitoring of sent and received data + :param on_disconnect: callback called on disconnection during runtime + :param idlequeue: processing idlequeue + :param estabilish_tls: boolean whether to estabilish TLS connection after + TCP connection is done + :param certs: tuple of (cacerts, mycerts) see constructor of + tls_nb.NonBlockingTLS for more details + ''' + PlugIn.__init__(self) + self.raise_event = raise_event + self.on_disconnect = on_disconnect + self.on_connect = None + self.on_connect_failure = None + self.idlequeue = idlequeue + self.on_receive = None + self.server = None + self.port = None + self.conn_5tuple = None + self.set_state(DISCONNECTED) + self.estabilish_tls = estabilish_tls + self.certs = certs + # type of used ssl lib (if any) will be assigned to this member var + self.ssl_lib = None + self._exported_methods=[self.onreceive, self.set_send_timeout, + self.set_send_timeout2, self.set_timeout, self.remove_timeout, + self.start_disconnect] - # time to wait for SOME stanza to come and then send keepalive - self.sendtimeout = 0 + # time to wait for SOME stanza to come and then send keepalive + self.sendtimeout = 0 - # in case we want to something different than sending keepalives - self.on_timeout = None - self.on_timeout2 = None + # in case we want to something different than sending keepalives + self.on_timeout = None + self.on_timeout2 = None - def plugin(self, owner): - owner.Connection = self + def plugin(self, owner): + owner.Connection = self - def plugout(self): - self._owner.Connection = None - self._owner = None - self.disconnect(do_callback=False) + def plugout(self): + self._owner.Connection = None + self._owner = None + self.disconnect(do_callback=False) - def connect(self, conn_5tuple, on_connect, on_connect_failure): - ''' - Creates and connects transport to server and port defined in conn_5tuple - which should be item from list returned from getaddrinfo. + def connect(self, conn_5tuple, on_connect, on_connect_failure): + ''' + Creates and connects transport to server and port defined in conn_5tuple + which should be item from list returned from getaddrinfo. - :param conn_5tuple: 5-tuple returned from getaddrinfo - :param on_connect: callback called on successful connect to the server - :param on_connect_failure: callback called on failure when connecting - ''' - self.on_connect = on_connect - self.on_connect_failure = on_connect_failure - self.server, self.port = conn_5tuple[4][:2] - self.conn_5tuple = conn_5tuple + :param conn_5tuple: 5-tuple returned from getaddrinfo + :param on_connect: callback called on successful connect to the server + :param on_connect_failure: callback called on failure when connecting + ''' + self.on_connect = on_connect + self.on_connect_failure = on_connect_failure + self.server, self.port = conn_5tuple[4][:2] + self.conn_5tuple = conn_5tuple - def set_state(self, newstate): - assert(newstate in STATES) - self.state = newstate + def set_state(self, newstate): + assert(newstate in STATES) + self.state = newstate - def get_state(self): - return self.state + def get_state(self): + return self.state - def _on_connect(self): - ''' preceeds call of on_connect callback ''' - # data is reference to socket wrapper instance. We don't need it in client - # because - self.set_state(CONNECTED) - self.on_connect() + def _on_connect(self): + ''' preceeds call of on_connect callback ''' + # data is reference to socket wrapper instance. We don't need it in client + # because + self.set_state(CONNECTED) + self.on_connect() - def _on_connect_failure(self, err_message): - ''' preceeds call of on_connect_failure callback ''' - # In case of error while connecting we need to disconnect transport - # but we don't want to call DisconnectHandlers from client, - # thus the do_callback=False - self.disconnect(do_callback=False) - self.on_connect_failure(err_message=err_message) + def _on_connect_failure(self, err_message): + ''' preceeds call of on_connect_failure callback ''' + # In case of error while connecting we need to disconnect transport + # but we don't want to call DisconnectHandlers from client, + # thus the do_callback=False + self.disconnect(do_callback=False) + self.on_connect_failure(err_message=err_message) - def send(self, raw_data, now=False): - if self.get_state() == DISCONNECTED: - log.error('Unable to send %s \n because state is %s.' % - (raw_data, self.get_state())) + def send(self, raw_data, now=False): + if self.get_state() == DISCONNECTED: + log.error('Unable to send %s \n because state is %s.' % + (raw_data, self.get_state())) - def disconnect(self, do_callback=True): - self.set_state(DISCONNECTED) - if do_callback: - # invoke callback given in __init__ - self.on_disconnect() + def disconnect(self, do_callback=True): + self.set_state(DISCONNECTED) + if do_callback: + # invoke callback given in __init__ + self.on_disconnect() - def onreceive(self, recv_handler): - ''' - Sets the on_receive callback. + def onreceive(self, recv_handler): + ''' + Sets the on_receive callback. - onreceive(None) sets callback to Dispatcher.ProcessNonBlocking which is - the default one that will decide what to do with received stanza based on - its tag name and namespace. + onreceive(None) sets callback to Dispatcher.ProcessNonBlocking which is + the default one that will decide what to do with received stanza based on + its tag name and namespace. - Do not confuse it with on_receive() method, which is the callback itself. - ''' - if not recv_handler: - if hasattr(self, '_owner') and hasattr(self._owner, 'Dispatcher'): - self.on_receive = self._owner.Dispatcher.ProcessNonBlocking - else: - log.warning('No Dispatcher plugged. Received data will not be processed') - self.on_receive = None - return - self.on_receive = recv_handler + Do not confuse it with on_receive() method, which is the callback itself. + ''' + if not recv_handler: + if hasattr(self, '_owner') and hasattr(self._owner, 'Dispatcher'): + self.on_receive = self._owner.Dispatcher.ProcessNonBlocking + else: + log.warning('No Dispatcher plugged. Received data will not be processed') + self.on_receive = None + return + self.on_receive = recv_handler - def _tcp_connecting_started(self): - self.set_state(CONNECTING) + def _tcp_connecting_started(self): + self.set_state(CONNECTING) - def read_timeout(self): - ''' called when there's no response from server in defined timeout ''' - if self.on_timeout: - self.on_timeout() - self.renew_send_timeout() + def read_timeout(self): + ''' called when there's no response from server in defined timeout ''' + if self.on_timeout: + self.on_timeout() + self.renew_send_timeout() - def read_timeout2(self): - ''' called when there's no response from server in defined timeout ''' - if self.on_timeout2: - self.on_timeout2() - self.renew_send_timeout2() + def read_timeout2(self): + ''' called when there's no response from server in defined timeout ''' + if self.on_timeout2: + self.on_timeout2() + self.renew_send_timeout2() - def renew_send_timeout(self): - if self.on_timeout and self.sendtimeout > 0: - self.set_timeout(self.sendtimeout) + def renew_send_timeout(self): + if self.on_timeout and self.sendtimeout > 0: + self.set_timeout(self.sendtimeout) - def renew_send_timeout2(self): - if self.on_timeout2 and self.sendtimeout2 > 0: - self.set_timeout2(self.sendtimeout2) + def renew_send_timeout2(self): + if self.on_timeout2 and self.sendtimeout2 > 0: + self.set_timeout2(self.sendtimeout2) - def set_timeout(self, timeout): - self.idlequeue.set_read_timeout(self.fd, timeout) + def set_timeout(self, timeout): + self.idlequeue.set_read_timeout(self.fd, timeout) - def set_timeout2(self, timeout2): - self.idlequeue.set_read_timeout(self.fd, timeout2, self.read_timeout2) + def set_timeout2(self, timeout2): + self.idlequeue.set_read_timeout(self.fd, timeout2, self.read_timeout2) - def get_fd(self): - pass + def get_fd(self): + pass - def remove_timeout(self): - self.idlequeue.remove_timeout(self.fd) + def remove_timeout(self): + self.idlequeue.remove_timeout(self.fd) - def set_send_timeout(self, timeout, on_timeout): - self.sendtimeout = timeout - if self.sendtimeout > 0: - self.on_timeout = on_timeout - else: - self.on_timeout = None + def set_send_timeout(self, timeout, on_timeout): + self.sendtimeout = timeout + if self.sendtimeout > 0: + self.on_timeout = on_timeout + else: + self.on_timeout = None - def set_send_timeout2(self, timeout2, on_timeout2): - self.sendtimeout2 = timeout2 - if self.sendtimeout2 > 0: - self.on_timeout2 = on_timeout2 - else: - self.on_timeout2 = None + def set_send_timeout2(self, timeout2, on_timeout2): + self.sendtimeout2 = timeout2 + if self.sendtimeout2 > 0: + self.on_timeout2 = on_timeout2 + else: + self.on_timeout2 = None - # FIXME: where and why does this need to be called - def start_disconnect(self): - self.set_state(DISCONNECTING) + # FIXME: where and why does this need to be called + def start_disconnect(self): + self.set_state(DISCONNECTING) class NonBlockingTCP(NonBlockingTransport, IdleObject): - ''' - Non-blocking TCP socket wrapper. + ''' + Non-blocking TCP socket wrapper. - It is used for simple XMPP connection. Can be connected via proxy and can - estabilish TLS connection. - ''' - def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, - certs, proxy_dict=None): - ''' - :param proxy_dict: dictionary with proxy data as loaded from config file - ''' - NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue, - estabilish_tls, certs) - IdleObject.__init__(self) + It is used for simple XMPP connection. Can be connected via proxy and can + estabilish TLS connection. + ''' + def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, + certs, proxy_dict=None): + ''' + :param proxy_dict: dictionary with proxy data as loaded from config file + ''' + NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue, + estabilish_tls, certs) + IdleObject.__init__(self) - # queue with messages to be send - self.sendqueue = [] + # queue with messages to be send + self.sendqueue = [] - # bytes remained from the last send message - self.sendbuff = '' + # bytes remained from the last send message + self.sendbuff = '' - self.proxy_dict = proxy_dict - self.on_remote_disconnect = self.disconnect + self.proxy_dict = proxy_dict + self.on_remote_disconnect = self.disconnect - # FIXME: transport should not be aware xmpp - def start_disconnect(self): - NonBlockingTransport.start_disconnect(self) - self.send('', now=True) - self.disconnect() + # FIXME: transport should not be aware xmpp + def start_disconnect(self): + NonBlockingTransport.start_disconnect(self) + self.send('', now=True) + self.disconnect() - def connect(self, conn_5tuple, on_connect, on_connect_failure): - NonBlockingTransport.connect(self, conn_5tuple, on_connect, - on_connect_failure) - log.info('NonBlockingTCP Connect :: About to connect to %s:%s' % - (self.server, self.port)) + def connect(self, conn_5tuple, on_connect, on_connect_failure): + NonBlockingTransport.connect(self, conn_5tuple, on_connect, + on_connect_failure) + log.info('NonBlockingTCP Connect :: About to connect to %s:%s' % + (self.server, self.port)) - try: - self._sock = socket.socket(*conn_5tuple[:3]) - except socket.error, (errnum, errstr): - self._on_connect_failure('NonBlockingTCP Connect: Error while creating\ - socket: %s %s' % (errnum, errstr)) - return + try: + self._sock = socket.socket(*conn_5tuple[:3]) + except socket.error, (errnum, errstr): + self._on_connect_failure('NonBlockingTCP Connect: Error while creating\ + socket: %s %s' % (errnum, errstr)) + return - self._send = self._sock.send - self._recv = self._sock.recv - self.fd = self._sock.fileno() + self._send = self._sock.send + self._recv = self._sock.recv + self.fd = self._sock.fileno() - # we want to be notified when send is possible to connected socket because - # it means the TCP connection is estabilished - self._plug_idle(writable=True, readable=False) - self.peerhost = None + # we want to be notified when send is possible to connected socket because + # it means the TCP connection is estabilished + self._plug_idle(writable=True, readable=False) + self.peerhost = None - # variable for errno symbol that will be found from exception raised - # from connect() - errnum = 0 + # variable for errno symbol that will be found from exception raised + # from connect() + errnum = 0 - # set timeout for TCP connecting - if nonblocking connect() fails, pollend - # is called. If if succeeds pollout is called. - self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT_SECONDS) + # set timeout for TCP connecting - if nonblocking connect() fails, pollend + # is called. If if succeeds pollout is called. + self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT_SECONDS) - try: - self._sock.setblocking(False) - self._sock.connect((self.server, self.port)) - except Exception, (errnum, errstr): - pass + try: + self._sock.setblocking(False) + self._sock.connect((self.server, self.port)) + except Exception, (errnum, errstr): + pass - if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): - # connecting in progress - log.info('After NB connect() of %s. "%s" raised => CONNECTING' % - (id(self), errstr)) - self._tcp_connecting_started() - return + if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): + # connecting in progress + log.info('After NB connect() of %s. "%s" raised => CONNECTING' % + (id(self), errstr)) + self._tcp_connecting_started() + return - # if there was some other exception, call failure callback and unplug - # transport which will also remove read_timeouts for descriptor - self._on_connect_failure('Exception while connecting to %s:%s - %s %s' % - (self.server, self.port, errnum, errstr)) + # if there was some other exception, call failure callback and unplug + # transport which will also remove read_timeouts for descriptor + self._on_connect_failure('Exception while connecting to %s:%s - %s %s' % + (self.server, self.port, errnum, errstr)) - def _connect_to_proxy(self): - self.set_state(PROXY_CONNECTING) - if self.proxy_dict['type'] == 'socks5': - proxyclass = proxy_connectors.SOCKS5Connector - elif self.proxy_dict['type'] == 'http' : - proxyclass = proxy_connectors.HTTPCONNECTConnector - proxyclass.get_instance( - send_method=self.send, - onreceive=self.onreceive, - old_on_receive=self.on_receive, - on_success=self._on_connect, - on_failure=self._on_connect_failure, - xmpp_server=self.proxy_dict['xmpp_server'], - proxy_creds=self.proxy_dict['credentials']) + def _connect_to_proxy(self): + self.set_state(PROXY_CONNECTING) + if self.proxy_dict['type'] == 'socks5': + proxyclass = proxy_connectors.SOCKS5Connector + elif self.proxy_dict['type'] == 'http' : + proxyclass = proxy_connectors.HTTPCONNECTConnector + proxyclass.get_instance( + send_method=self.send, + onreceive=self.onreceive, + old_on_receive=self.on_receive, + on_success=self._on_connect, + on_failure=self._on_connect_failure, + xmpp_server=self.proxy_dict['xmpp_server'], + proxy_creds=self.proxy_dict['credentials']) - def _on_connect(self): - ''' - Preceeds invoking of on_connect callback. TCP connection is already - estabilished by this time. - ''' - if self.estabilish_tls: - self.tls_init( - on_succ = lambda: NonBlockingTransport._on_connect(self), - on_fail = lambda: self._on_connect_failure( - 'error while estabilishing TLS')) - else: - NonBlockingTransport._on_connect(self) + def _on_connect(self): + ''' + Preceeds invoking of on_connect callback. TCP connection is already + estabilished by this time. + ''' + if self.estabilish_tls: + self.tls_init( + on_succ = lambda: NonBlockingTransport._on_connect(self), + on_fail = lambda: self._on_connect_failure( + 'error while estabilishing TLS')) + else: + NonBlockingTransport._on_connect(self) - def tls_init(self, on_succ, on_fail): - ''' - Estabilishes TLS/SSL using this TCP connection by plugging a - NonBlockingTLS module - ''' - cacerts, mycerts = self.certs - result = tls_nb.NonBlockingTLS.get_instance(cacerts, mycerts).PlugIn(self) - if result: - on_succ() - else: - on_fail() + def tls_init(self, on_succ, on_fail): + ''' + Estabilishes TLS/SSL using this TCP connection by plugging a + NonBlockingTLS module + ''' + cacerts, mycerts = self.certs + result = tls_nb.NonBlockingTLS.get_instance(cacerts, mycerts).PlugIn(self) + if result: + on_succ() + else: + on_fail() - def pollin(self): - '''called by idlequeu when receive on plugged socket is possible ''' - log.info('pollin called, state == %s' % self.get_state()) - self._do_receive() + def pollin(self): + '''called by idlequeu when receive on plugged socket is possible ''' + log.info('pollin called, state == %s' % self.get_state()) + self._do_receive() - def pollout(self): - '''called by idlequeu when send to plugged socket is possible''' - log.info('pollout called, state == %s' % self.get_state()) + def pollout(self): + '''called by idlequeu when send to plugged socket is possible''' + log.info('pollout called, state == %s' % self.get_state()) - if self.get_state() == CONNECTING: - log.info('%s socket wrapper connected' % id(self)) - self.idlequeue.remove_timeout(self.fd) - self._plug_idle(writable=False, readable=False) - self.peerhost = self._sock.getsockname() - if self.proxy_dict: - self._connect_to_proxy() - else: - self._on_connect() - else: - self._do_send() + if self.get_state() == CONNECTING: + log.info('%s socket wrapper connected' % id(self)) + self.idlequeue.remove_timeout(self.fd) + self._plug_idle(writable=False, readable=False) + self.peerhost = self._sock.getsockname() + if self.proxy_dict: + self._connect_to_proxy() + else: + self._on_connect() + else: + self._do_send() - def pollend(self): - '''called by idlequeue on TCP connection errors''' - log.info('pollend called, state == %s' % self.get_state()) + def pollend(self): + '''called by idlequeue on TCP connection errors''' + log.info('pollend called, state == %s' % self.get_state()) - if self.get_state() == CONNECTING: - self._on_connect_failure('Error during connect to %s:%s' % - (self.server, self.port)) - else: - self.disconnect() + if self.get_state() == CONNECTING: + self._on_connect_failure('Error during connect to %s:%s' % + (self.server, self.port)) + else: + self.disconnect() - def disconnect(self, do_callback=True): - if self.get_state() == DISCONNECTED: - return - self.set_state(DISCONNECTED) - self.idlequeue.unplug_idle(self.fd) - if 'NonBlockingTLS' in self.__dict__: - self.NonBlockingTLS.PlugOut() - try: - self._sock.shutdown(socket.SHUT_RDWR) - self._sock.close() - except socket.error, (errnum, errstr): - log.error('Error while disconnecting socket: %s' % errstr) - self.fd = -1 - NonBlockingTransport.disconnect(self, do_callback) + def disconnect(self, do_callback=True): + if self.get_state() == DISCONNECTED: + return + self.set_state(DISCONNECTED) + self.idlequeue.unplug_idle(self.fd) + if 'NonBlockingTLS' in self.__dict__: + self.NonBlockingTLS.PlugOut() + try: + self._sock.shutdown(socket.SHUT_RDWR) + self._sock.close() + except socket.error, (errnum, errstr): + log.error('Error while disconnecting socket: %s' % errstr) + self.fd = -1 + NonBlockingTransport.disconnect(self, do_callback) - def read_timeout(self): - log.info('read_timeout called, state == %s' % self.get_state()) - if self.get_state() == CONNECTING: - # if read_timeout is called during connecting, connect() didn't end yet - # thus we have to call the tcp failure callback - self._on_connect_failure('Error during connect to %s:%s' % - (self.server, self.port)) - else: - NonBlockingTransport.read_timeout(self) + def read_timeout(self): + log.info('read_timeout called, state == %s' % self.get_state()) + if self.get_state() == CONNECTING: + # if read_timeout is called during connecting, connect() didn't end yet + # thus we have to call the tcp failure callback + self._on_connect_failure('Error during connect to %s:%s' % + (self.server, self.port)) + else: + NonBlockingTransport.read_timeout(self) - def set_timeout(self, timeout): - if self.get_state() != DISCONNECTED and self.fd != -1: - NonBlockingTransport.set_timeout(self, timeout) - else: - log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % - (self.get_state(), self.fd)) + def set_timeout(self, timeout): + if self.get_state() != DISCONNECTED and self.fd != -1: + NonBlockingTransport.set_timeout(self, timeout) + else: + log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % + (self.get_state(), self.fd)) - def remove_timeout(self): - if self.fd: - NonBlockingTransport.remove_timeout(self) - else: - log.warn('remove_timeout: no self.fd state is %s' % self.get_state()) + def remove_timeout(self): + if self.fd: + NonBlockingTransport.remove_timeout(self) + else: + log.warn('remove_timeout: no self.fd state is %s' % self.get_state()) - def send(self, raw_data, now=False): - ''' - Append raw_data to the queue of messages to be send. - If supplied data is unicode string, encode it to utf-8. - ''' - NonBlockingTransport.send(self, raw_data, now) + def send(self, raw_data, now=False): + ''' + Append raw_data to the queue of messages to be send. + If supplied data is unicode string, encode it to utf-8. + ''' + NonBlockingTransport.send(self, raw_data, now) - r = self.encode_stanza(raw_data) + r = self.encode_stanza(raw_data) - if now: - self.sendqueue.insert(0, r) - self._do_send() - else: - self.sendqueue.append(r) + if now: + self.sendqueue.insert(0, r) + self._do_send() + else: + self.sendqueue.append(r) - self._plug_idle(writable=True, readable=True) + self._plug_idle(writable=True, readable=True) - def encode_stanza(self, stanza): - ''' Encode str or unicode to utf-8 ''' - if isinstance(stanza, unicode): - stanza = stanza.encode('utf-8') - elif not isinstance(stanza, str): - stanza = ustr(stanza).encode('utf-8') - return stanza + def encode_stanza(self, stanza): + ''' Encode str or unicode to utf-8 ''' + if isinstance(stanza, unicode): + stanza = stanza.encode('utf-8') + elif not isinstance(stanza, str): + stanza = ustr(stanza).encode('utf-8') + return stanza - def _plug_idle(self, writable, readable): - ''' - Plugs file descriptor of socket to Idlequeue. + def _plug_idle(self, writable, readable): + ''' + Plugs file descriptor of socket to Idlequeue. - Plugged socket will be watched for "send possible" or/and "recv possible" - events. pollin() callback is invoked on "recv possible", pollout() on - "send_possible". + Plugged socket will be watched for "send possible" or/and "recv possible" + events. pollin() callback is invoked on "recv possible", pollout() on + "send_possible". - Plugged socket will always be watched for "error" event - in that case, - pollend() is called. - ''' - log.info('Plugging fd %d, W:%s, R:%s' % (self.fd, writable, readable)) - self.idlequeue.plug_idle(self, writable, readable) + Plugged socket will always be watched for "error" event - in that case, + pollend() is called. + ''' + log.info('Plugging fd %d, W:%s, R:%s' % (self.fd, writable, readable)) + self.idlequeue.plug_idle(self, writable, readable) - def _do_send(self): - ''' - Called when send() to connected socket is possible. First message from - sendqueue will be sent. - ''' - if not self.sendbuff: - if not self.sendqueue: - log.warn('calling send on empty buffer and queue') - self._plug_idle(writable=False, readable=True) - return None - self.sendbuff = self.sendqueue.pop(0) - try: - send_count = self._send(self.sendbuff) - if send_count: - sent_data = self.sendbuff[:send_count] - self.sendbuff = self.sendbuff[send_count:] - self._plug_idle( - writable=((self.sendqueue!=[]) or (self.sendbuff!='')), - readable=True) - self.raise_event(DATA_SENT, sent_data) + def _do_send(self): + ''' + Called when send() to connected socket is possible. First message from + sendqueue will be sent. + ''' + if not self.sendbuff: + if not self.sendqueue: + log.warn('calling send on empty buffer and queue') + self._plug_idle(writable=False, readable=True) + return None + self.sendbuff = self.sendqueue.pop(0) + try: + send_count = self._send(self.sendbuff) + if send_count: + sent_data = self.sendbuff[:send_count] + self.sendbuff = self.sendbuff[send_count:] + self._plug_idle( + writable=((self.sendqueue!=[]) or (self.sendbuff!='')), + readable=True) + self.raise_event(DATA_SENT, sent_data) - except socket.error, e: - log.error('_do_send:', exc_info=True) - traceback.print_exc() - self.disconnect() + except socket.error, e: + log.error('_do_send:', exc_info=True) + traceback.print_exc() + self.disconnect() - def _do_receive(self): - ''' - Reads all pending incoming data. Will call owner's disconnected() method - if appropriate. - ''' - received = None - errnum = 0 - errstr = 'No Error Set' + def _do_receive(self): + ''' + Reads all pending incoming data. Will call owner's disconnected() method + if appropriate. + ''' + received = None + errnum = 0 + errstr = 'No Error Set' - try: - # get as many bites, as possible, but not more than RECV_BUFSIZE - received = self._recv(RECV_BUFSIZE) - except socket.error, (errnum, errstr): - log.info("_do_receive: got %s:" % received , exc_info=True) - except tls_nb.SSLWrapper.Error, e: - log.info("_do_receive, caught SSL error, got %s:" % received, - exc_info=True) - errnum, errstr = e.errno, e.strerror + try: + # get as many bites, as possible, but not more than RECV_BUFSIZE + received = self._recv(RECV_BUFSIZE) + except socket.error, (errnum, errstr): + log.info("_do_receive: got %s:" % received, exc_info=True) + except tls_nb.SSLWrapper.Error, e: + log.info("_do_receive, caught SSL error, got %s:" % received, + exc_info=True) + errnum, errstr = e.errno, e.strerror - if received == '': - errstr = 'zero bytes on recv' + if received == '': + errstr = 'zero bytes on recv' - if (self.ssl_lib is None and received == '') or \ - (self.ssl_lib == tls_nb.PYSTDLIB and errnum == 8 ) or \ - (self.ssl_lib == tls_nb.PYOPENSSL and errnum == -1 ): - # 8 in stdlib: errstr == EOF occured in violation of protocol - # -1 in pyopenssl: errstr == Unexpected EOF - log.info("Disconnected by remote server: #%s, %s" % (errnum, errstr)) - self.on_remote_disconnect() - return + if (self.ssl_lib is None and received == '') or \ + (self.ssl_lib == tls_nb.PYSTDLIB and errnum == 8 ) or \ + (self.ssl_lib == tls_nb.PYOPENSSL and errnum == -1 ): + # 8 in stdlib: errstr == EOF occured in violation of protocol + # -1 in pyopenssl: errstr == Unexpected EOF + log.info("Disconnected by remote server: #%s, %s" % (errnum, errstr)) + self.on_remote_disconnect() + return - if errnum: - log.info("Connection to %s:%s lost: %s %s" % (self.server, self.port, - errnum, errstr), exc_info=True) - self.disconnect() - return + if errnum: + log.info("Connection to %s:%s lost: %s %s" % (self.server, self.port, + errnum, errstr), exc_info=True) + self.disconnect() + return - # this branch is for case of non-fatal SSL errors - None is returned from - # recv() but no errnum is set - if received is None: - return + # this branch is for case of non-fatal SSL errors - None is returned from + # recv() but no errnum is set + if received is None: + return - # we have received some bytes, stop the timeout! - self.remove_timeout() - self.renew_send_timeout() - self.renew_send_timeout2() - # pass received data to owner - if self.on_receive: - self.raise_event(DATA_RECEIVED, received) - self._on_receive(received) - else: - # This should never happen, so we need the debug. - # (If there is no handler on receive specified, data is passed to - # Dispatcher.ProcessNonBlocking) - log.error('SOCKET %s Unhandled data received: %s' % (id(self), - received)) - self.disconnect() + # we have received some bytes, stop the timeout! + self.remove_timeout() + self.renew_send_timeout() + self.renew_send_timeout2() + # pass received data to owner + if self.on_receive: + self.raise_event(DATA_RECEIVED, received) + self._on_receive(received) + else: + # This should never happen, so we need the debug. + # (If there is no handler on receive specified, data is passed to + # Dispatcher.ProcessNonBlocking) + log.error('SOCKET %s Unhandled data received: %s' % (id(self), + received)) + self.disconnect() - def _on_receive(self, data): - ''' preceeds on_receive callback. It peels off and checks HTTP headers in - HTTP classes, in here it just calls the callback.''' - self.on_receive(data) + def _on_receive(self, data): + ''' preceeds on_receive callback. It peels off and checks HTTP headers in + HTTP classes, in here it just calls the callback.''' + self.on_receive(data) class NonBlockingHTTP(NonBlockingTCP): - ''' - Socket wrapper that creates HTTP message out of sent data and peels-off - HTTP headers from incoming messages. - ''' + ''' + Socket wrapper that creates HTTP message out of sent data and peels-off + HTTP headers from incoming messages. + ''' - def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, - certs, on_http_request_possible, on_persistent_fallback, http_dict, - proxy_dict=None): - ''' - :param on_http_request_possible: method to call when HTTP request to - socket owned by transport is possible. - :param on_persistent_fallback: callback called when server ends TCP - connection. It doesn't have to be fatal for HTTP session. - :param http_dict: dictionary with data for HTTP request and headers - ''' - NonBlockingTCP.__init__(self, raise_event, on_disconnect, idlequeue, - estabilish_tls, certs, proxy_dict) + def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, + certs, on_http_request_possible, on_persistent_fallback, http_dict, + proxy_dict=None): + ''' + :param on_http_request_possible: method to call when HTTP request to + socket owned by transport is possible. + :param on_persistent_fallback: callback called when server ends TCP + connection. It doesn't have to be fatal for HTTP session. + :param http_dict: dictionary with data for HTTP request and headers + ''' + NonBlockingTCP.__init__(self, raise_event, on_disconnect, idlequeue, + estabilish_tls, certs, proxy_dict) - self.http_protocol, self.http_host, self.http_port, self.http_path = \ - urisplit(http_dict['http_uri']) - self.http_protocol = self.http_protocol or 'http' - self.http_path = self.http_path or '/' - self.http_version = http_dict['http_version'] - self.http_persistent = http_dict['http_persistent'] - self.add_proxy_headers = http_dict['add_proxy_headers'] + self.http_protocol, self.http_host, self.http_port, self.http_path = \ + urisplit(http_dict['http_uri']) + self.http_protocol = self.http_protocol or 'http' + self.http_path = self.http_path or '/' + self.http_version = http_dict['http_version'] + self.http_persistent = http_dict['http_persistent'] + self.add_proxy_headers = http_dict['add_proxy_headers'] - if 'proxy_user' in http_dict and 'proxy_pass' in http_dict: - self.proxy_user, self.proxy_pass = http_dict['proxy_user'], http_dict[ - 'proxy_pass'] - else: - self.proxy_user, self.proxy_pass = None, None + if 'proxy_user' in http_dict and 'proxy_pass' in http_dict: + self.proxy_user, self.proxy_pass = http_dict['proxy_user'], http_dict[ + 'proxy_pass'] + else: + self.proxy_user, self.proxy_pass = None, None - # buffer for partial responses - self.recvbuff = '' - self.expected_length = 0 - self.pending_requests = 0 - self.on_http_request_possible = on_http_request_possible - self.last_recv_time = 0 - self.close_current_connection = False - self.on_remote_disconnect = lambda: on_persistent_fallback(self) + # buffer for partial responses + self.recvbuff = '' + self.expected_length = 0 + self.pending_requests = 0 + self.on_http_request_possible = on_http_request_possible + self.last_recv_time = 0 + self.close_current_connection = False + self.on_remote_disconnect = lambda: on_persistent_fallback(self) - def http_send(self, raw_data, now=False): - self.send(self.build_http_message(raw_data), now) + def http_send(self, raw_data, now=False): + self.send(self.build_http_message(raw_data), now) - def _on_receive(self, data): - ''' - Preceeds passing received data to owner class. Gets rid of HTTP headers - and checks them. - ''' - if self.get_state() == PROXY_CONNECTING: - NonBlockingTCP._on_receive(self, data) - return + def _on_receive(self, data): + ''' + Preceeds passing received data to owner class. Gets rid of HTTP headers + and checks them. + ''' + if self.get_state() == PROXY_CONNECTING: + NonBlockingTCP._on_receive(self, data) + return - # append currently received data to HTTP msg in buffer - self.recvbuff = '%s%s' % (self.recvbuff or '', data) - statusline, headers, httpbody, buffer_rest = self.parse_http_message( - self.recvbuff) + # append currently received data to HTTP msg in buffer + self.recvbuff = '%s%s' % (self.recvbuff or '', data) + statusline, headers, httpbody, buffer_rest = self.parse_http_message( + self.recvbuff) - if not (statusline and headers and httpbody): - log.debug('Received incomplete HTTP response') - return + if not (statusline and headers and httpbody): + log.debug('Received incomplete HTTP response') + return - if statusline[1] != '200': - log.error('HTTP Error: %s %s' % (statusline[1], statusline[2])) - self.disconnect() - return - self.expected_length = int(headers['Content-Length']) - if 'Connection' in headers and headers['Connection'].strip()=='close': - self.close_current_connection = True + if statusline[1] != '200': + log.error('HTTP Error: %s %s' % (statusline[1], statusline[2])) + self.disconnect() + return + self.expected_length = int(headers['Content-Length']) + if 'Connection' in headers and headers['Connection'].strip()=='close': + self.close_current_connection = True - if self.expected_length > len(httpbody): - # If we haven't received the whole HTTP mess yet, let's end the thread. - # It will be finnished from one of following recvs on plugged socket. - log.info('not enough bytes in HTTP response - %d expected, got %d' % - (self.expected_length, len(httpbody))) - else: - # First part of buffer has been extraced and is going to be handled, - # remove it from buffer - self.recvbuff = buffer_rest + if self.expected_length > len(httpbody): + # If we haven't received the whole HTTP mess yet, let's end the thread. + # It will be finnished from one of following recvs on plugged socket. + log.info('not enough bytes in HTTP response - %d expected, got %d' % + (self.expected_length, len(httpbody))) + else: + # First part of buffer has been extraced and is going to be handled, + # remove it from buffer + self.recvbuff = buffer_rest - # everything was received - self.expected_length = 0 + # everything was received + self.expected_length = 0 - if not self.http_persistent or self.close_current_connection: - # not-persistent connections disconnect after response - self.disconnect(do_callback=False) - self.close_current_connection = False - self.last_recv_time = time.time() - self.on_receive(data=httpbody, socket=self) - self.on_http_request_possible() + if not self.http_persistent or self.close_current_connection: + # not-persistent connections disconnect after response + self.disconnect(do_callback=False) + self.close_current_connection = False + self.last_recv_time = time.time() + self.on_receive(data=httpbody, socket=self) + self.on_http_request_possible() - def build_http_message(self, httpbody, method='POST'): - ''' - Builds http message with given body. - Values for headers and status line fields are taken from class variables. - ''' - absolute_uri = '%s://%s:%s%s' % (self.http_protocol, self.http_host, - self.http_port, self.http_path) - headers = ['%s %s %s' % (method, absolute_uri, self.http_version), - 'Host: %s:%s' % (self.http_host, self.http_port), - 'User-Agent: Gajim', - 'Content-Type: text/xml; charset=utf-8', - 'Content-Length: %s' % len(str(httpbody))] - if self.add_proxy_headers: - headers.append('Proxy-Connection: keep-alive') - headers.append('Pragma: no-cache') - if self.proxy_user and self.proxy_pass: - credentials = '%s:%s' % (self.proxy_user, self.proxy_pass) - credentials = base64.encodestring(credentials).strip() - headers.append('Proxy-Authorization: Basic %s' % credentials) - else: - headers.append('Connection: Keep-Alive') - headers.append('\r\n') - headers = '\r\n'.join(headers) - return('%s%s' % (headers, httpbody)) + def build_http_message(self, httpbody, method='POST'): + ''' + Builds http message with given body. + Values for headers and status line fields are taken from class variables. + ''' + absolute_uri = '%s://%s:%s%s' % (self.http_protocol, self.http_host, + self.http_port, self.http_path) + headers = ['%s %s %s' % (method, absolute_uri, self.http_version), + 'Host: %s:%s' % (self.http_host, self.http_port), + 'User-Agent: Gajim', + 'Content-Type: text/xml; charset=utf-8', + 'Content-Length: %s' % len(str(httpbody))] + if self.add_proxy_headers: + headers.append('Proxy-Connection: keep-alive') + headers.append('Pragma: no-cache') + if self.proxy_user and self.proxy_pass: + credentials = '%s:%s' % (self.proxy_user, self.proxy_pass) + credentials = base64.encodestring(credentials).strip() + headers.append('Proxy-Authorization: Basic %s' % credentials) + else: + headers.append('Connection: Keep-Alive') + headers.append('\r\n') + headers = '\r\n'.join(headers) + return('%s%s' % (headers, httpbody)) - def parse_http_message(self, message): - ''' - splits http message to tuple: - (statusline - list of e.g. ['HTTP/1.1', '200', 'OK'], - headers - dictionary of headers e.g. {'Content-Length': '604', - 'Content-Type': 'text/xml; charset=utf-8'}, - httpbody - string with http body) - http_rest - what is left in the message after a full HTTP header + body - ''' - message = message.replace('\r','') - message = message.lstrip('\n') - splitted = message.split('\n\n') - if len(splitted) < 2: - # no complete http message. Keep filling the buffer until we find one - buffer_rest = message - return ('', '', '', buffer_rest) - else: - (header, httpbody) = splitted[:2] - header = header.split('\n') - statusline = header[0].split(' ', 2) - header = header[1:] - headers = {} - for dummy in header: - row = dummy.split(' ', 1) - headers[row[0][:-1]] = row[1] - body_size = headers['Content-Length'] - rest_splitted = splitted[2:] - while (len(httpbody) < body_size) and rest_splitted: - # Complete httpbody until it has the announced size - httpbody = '\n\n'.join([httpbody, rest_splitted.pop(0)]) - buffer_rest = "\n\n".join(rest_splitted) - return (statusline, headers, httpbody, buffer_rest) + def parse_http_message(self, message): + ''' + splits http message to tuple: + (statusline - list of e.g. ['HTTP/1.1', '200', 'OK'], + headers - dictionary of headers e.g. {'Content-Length': '604', + 'Content-Type': 'text/xml; charset=utf-8'}, + httpbody - string with http body) + http_rest - what is left in the message after a full HTTP header + body + ''' + message = message.replace('\r', '') + message = message.lstrip('\n') + splitted = message.split('\n\n') + if len(splitted) < 2: + # no complete http message. Keep filling the buffer until we find one + buffer_rest = message + return ('', '', '', buffer_rest) + else: + (header, httpbody) = splitted[:2] + header = header.split('\n') + statusline = header[0].split(' ', 2) + header = header[1:] + headers = {} + for dummy in header: + row = dummy.split(' ', 1) + headers[row[0][:-1]] = row[1] + body_size = headers['Content-Length'] + rest_splitted = splitted[2:] + while (len(httpbody) < body_size) and rest_splitted: + # Complete httpbody until it has the announced size + httpbody = '\n\n'.join([httpbody, rest_splitted.pop(0)]) + buffer_rest = "\n\n".join(rest_splitted) + return (statusline, headers, httpbody, buffer_rest) class NonBlockingHTTPBOSH(NonBlockingHTTP): - ''' - Class for BOSH HTTP connections. Slightly redefines HTTP transport by calling - bosh bodytag generating callback before putting data on wire. - ''' + ''' + Class for BOSH HTTP connections. Slightly redefines HTTP transport by calling + bosh bodytag generating callback before putting data on wire. + ''' - def set_stanza_build_cb(self, build_cb): - self.build_cb = build_cb + def set_stanza_build_cb(self, build_cb): + self.build_cb = build_cb - def _do_send(self): - if self.state == PROXY_CONNECTING: - NonBlockingTCP._do_send(self) - return - if not self.sendbuff: - stanza = self.build_cb(socket=self) - stanza = self.encode_stanza(stanza) - stanza = self.build_http_message(httpbody=stanza) - self.sendbuff = stanza - NonBlockingTCP._do_send(self) - -# vim: se ts=3: + def _do_send(self): + if self.state == PROXY_CONNECTING: + NonBlockingTCP._do_send(self) + return + if not self.sendbuff: + stanza = self.build_cb(socket=self) + stanza = self.encode_stanza(stanza) + stanza = self.build_http_message(httpbody=stanza) + self.sendbuff = stanza + NonBlockingTCP._do_send(self) diff --git a/src/common/zeroconf/__init__.py b/src/common/zeroconf/__init__.py index bbd45f43d..e69de29bb 100644 --- a/src/common/zeroconf/__init__.py +++ b/src/common/zeroconf/__init__.py @@ -1,2 +0,0 @@ - -# vim: se ts=3: \ No newline at end of file diff --git a/src/common/zeroconf/client_zeroconf.py b/src/common/zeroconf/client_zeroconf.py index ab863709d..bbe14275b 100644 --- a/src/common/zeroconf/client_zeroconf.py +++ b/src/common/zeroconf/client_zeroconf.py @@ -1,7 +1,7 @@ ## common/zeroconf/client_zeroconf.py ## ## Copyright (C) 2006 Stefan Bethge -## 2006 Dimitur Kirov +## 2006 Dimitur Kirov ## ## This file is part of Gajim. ## @@ -46,735 +46,733 @@ CONNECT_TIMEOUT_SECONDS = 10 ACTIVITY_TIMEOUT_SECONDS = 30 class ZeroconfListener(IdleObject): - def __init__(self, port, conn_holder): - ''' handle all incomming connections on ('0.0.0.0', port)''' - self.port = port - self.queue_idx = -1 - #~ self.queue = None - self.started = False - self._sock = None - self.fd = -1 - self.caller = conn_holder.caller - self.conn_holder = conn_holder + def __init__(self, port, conn_holder): + ''' handle all incomming connections on ('0.0.0.0', port)''' + self.port = port + self.queue_idx = -1 + #~ self.queue = None + self.started = False + self._sock = None + self.fd = -1 + self.caller = conn_holder.caller + self.conn_holder = conn_holder - def bind(self): - flags = socket.AI_PASSIVE - if hasattr(socket, 'AI_ADDRCONFIG'): - flags |= socket.AI_ADDRCONFIG - ai = socket.getaddrinfo(None, self.port, socket.AF_UNSPEC, - socket.SOCK_STREAM, 0, flags)[0] - self._serv = socket.socket(ai[0], ai[1]) - self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) - self._serv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - # will fail when port is busy, or we don't have rights to bind - try: - self._serv.bind((ai[4][0], self.port)) - except Exception: - # unable to bind, show error dialog - return None - self._serv.listen(socket.SOMAXCONN) - self._serv.setblocking(False) - self.fd = self._serv.fileno() - gajim.idlequeue.plug_idle(self, False, True) - self.started = True + def bind(self): + flags = socket.AI_PASSIVE + if hasattr(socket, 'AI_ADDRCONFIG'): + flags |= socket.AI_ADDRCONFIG + ai = socket.getaddrinfo(None, self.port, socket.AF_UNSPEC, + socket.SOCK_STREAM, 0, flags)[0] + self._serv = socket.socket(ai[0], ai[1]) + self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + self._serv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + # will fail when port is busy, or we don't have rights to bind + try: + self._serv.bind((ai[4][0], self.port)) + except Exception: + # unable to bind, show error dialog + return None + self._serv.listen(socket.SOMAXCONN) + self._serv.setblocking(False) + self.fd = self._serv.fileno() + gajim.idlequeue.plug_idle(self, False, True) + self.started = True - def pollend(self): - ''' called when we stop listening on (host, port) ''' - self.disconnect() + def pollend(self): + ''' called when we stop listening on (host, port) ''' + self.disconnect() - def pollin(self): - ''' accept a new incomming connection and notify queue''' - sock = self.accept_conn() - # loop through roster to find who has connected to us - from_jid = None - ipaddr = sock[1][0] - for jid in self.conn_holder.getRoster().keys(): - entry = self.conn_holder.getRoster().getItem(jid) - if (entry['address'] == ipaddr): - from_jid = jid - break - P2PClient(sock[0], ipaddr, sock[1][1], self.conn_holder, [], from_jid) + def pollin(self): + ''' accept a new incomming connection and notify queue''' + sock = self.accept_conn() + # loop through roster to find who has connected to us + from_jid = None + ipaddr = sock[1][0] + for jid in self.conn_holder.getRoster().keys(): + entry = self.conn_holder.getRoster().getItem(jid) + if (entry['address'] == ipaddr): + from_jid = jid + break + P2PClient(sock[0], ipaddr, sock[1][1], self.conn_holder, [], from_jid) - def disconnect(self, message=''): - ''' free all resources, we are not listening anymore ''' - log.info('Disconnecting ZeroconfListener: %s' % message) - gajim.idlequeue.remove_timeout(self.fd) - gajim.idlequeue.unplug_idle(self.fd) - self.fd = -1 - self.started = False - try: - self._serv.close() - except socket.error: - pass - self.conn_holder.kill_all_connections() + def disconnect(self, message=''): + ''' free all resources, we are not listening anymore ''' + log.info('Disconnecting ZeroconfListener: %s' % message) + gajim.idlequeue.remove_timeout(self.fd) + gajim.idlequeue.unplug_idle(self.fd) + self.fd = -1 + self.started = False + try: + self._serv.close() + except socket.error: + pass + self.conn_holder.kill_all_connections() - def accept_conn(self): - ''' accepts a new incoming connection ''' - _sock = self._serv.accept() - _sock[0].setblocking(False) - return _sock + def accept_conn(self): + ''' accepts a new incoming connection ''' + _sock = self._serv.accept() + _sock[0].setblocking(False) + return _sock class P2PClient(IdleObject): - def __init__(self, _sock, host, port, conn_holder, stanzaqueue=[], to=None, - on_ok=None, on_not_ok=None): - self._owner = self - self.Namespace = 'jabber:client' - self.protocol_type = 'XMPP' - self.defaultNamespace = self.Namespace - self._component = 0 - self._registered_name = None - self._caller = conn_holder.caller - self.conn_holder = conn_holder - self.stanzaqueue = stanzaqueue - self.to = to - self.Server = host - self.on_ok = on_ok - self.on_not_ok = on_not_ok - self.Connection = None - self.sock_hash = None - if _sock: - self.sock_type = TYPE_SERVER - else: - self.sock_type = TYPE_CLIENT - self.fd = -1 - conn = P2PConnection('', _sock, host, port, self._caller, self.on_connect, - self) - if not self.conn_holder: - # An error occured, disconnect() has been called - if on_not_ok: - on_not_ok('Connection to host could not be established.') - return - self.sock_hash = conn._sock.__hash__ - self.fd = conn.fd - self.conn_holder.add_connection(self, self.Server, port, self.to) - # count messages in queue - for val in self.stanzaqueue: - stanza, is_message = val - if is_message: - if self.fd == -1: - if on_not_ok: - on_not_ok('Connection to host could not be established.') - return - thread_id = stanza.getThread() - id_ = stanza.getID() - if not id_: - id_ = self.Dispatcher.getAnID() - if self.conn_holder.ids_of_awaiting_messages.has_key(self.fd): - self.conn_holder.ids_of_awaiting_messages[self.fd].append((id_, - thread_id)) - else: - self.conn_holder.ids_of_awaiting_messages[self.fd] = [(id_, - thread_id)] + def __init__(self, _sock, host, port, conn_holder, stanzaqueue=[], to=None, + on_ok=None, on_not_ok=None): + self._owner = self + self.Namespace = 'jabber:client' + self.protocol_type = 'XMPP' + self.defaultNamespace = self.Namespace + self._component = 0 + self._registered_name = None + self._caller = conn_holder.caller + self.conn_holder = conn_holder + self.stanzaqueue = stanzaqueue + self.to = to + self.Server = host + self.on_ok = on_ok + self.on_not_ok = on_not_ok + self.Connection = None + self.sock_hash = None + if _sock: + self.sock_type = TYPE_SERVER + else: + self.sock_type = TYPE_CLIENT + self.fd = -1 + conn = P2PConnection('', _sock, host, port, self._caller, self.on_connect, + self) + if not self.conn_holder: + # An error occured, disconnect() has been called + if on_not_ok: + on_not_ok('Connection to host could not be established.') + return + self.sock_hash = conn._sock.__hash__ + self.fd = conn.fd + self.conn_holder.add_connection(self, self.Server, port, self.to) + # count messages in queue + for val in self.stanzaqueue: + stanza, is_message = val + if is_message: + if self.fd == -1: + if on_not_ok: + on_not_ok('Connection to host could not be established.') + return + thread_id = stanza.getThread() + id_ = stanza.getID() + if not id_: + id_ = self.Dispatcher.getAnID() + if self.conn_holder.ids_of_awaiting_messages.has_key(self.fd): + self.conn_holder.ids_of_awaiting_messages[self.fd].append((id_, + thread_id)) + else: + self.conn_holder.ids_of_awaiting_messages[self.fd] = [(id_, + thread_id)] - self.on_responses = {} + self.on_responses = {} - def add_stanza(self, stanza, is_message=False): - if self.Connection: - if self.Connection.state == -1: - return False - self.send(stanza, is_message) - else: - self.stanzaqueue.append((stanza, is_message)) + def add_stanza(self, stanza, is_message=False): + if self.Connection: + if self.Connection.state == -1: + return False + self.send(stanza, is_message) + else: + self.stanzaqueue.append((stanza, is_message)) - if is_message: - thread_id = stanza.getThread() - id_ = stanza.getID() - if not id_: - id_ = self.Dispatcher.getAnID() - if self.conn_holder.ids_of_awaiting_messages.has_key(self.fd): - self.conn_holder.ids_of_awaiting_messages[self.fd].append((id_, - thread_id)) - else: - self.conn_holder.ids_of_awaiting_messages[self.fd] = [(id_, - thread_id)] + if is_message: + thread_id = stanza.getThread() + id_ = stanza.getID() + if not id_: + id_ = self.Dispatcher.getAnID() + if self.conn_holder.ids_of_awaiting_messages.has_key(self.fd): + self.conn_holder.ids_of_awaiting_messages[self.fd].append((id_, + thread_id)) + else: + self.conn_holder.ids_of_awaiting_messages[self.fd] = [(id_, + thread_id)] - return True + return True - def on_message_sent(self, connection_id): - id_, thread_id = \ - self.conn_holder.ids_of_awaiting_messages[connection_id].pop(0) - if self.on_ok: - self.on_ok(id_) - # use on_ok only on first message. For others it's called in - # ClientZeroconf - self.on_ok = None + def on_message_sent(self, connection_id): + id_, thread_id = \ + self.conn_holder.ids_of_awaiting_messages[connection_id].pop(0) + if self.on_ok: + self.on_ok(id_) + # use on_ok only on first message. For others it's called in + # ClientZeroconf + self.on_ok = None - def on_connect(self, conn): - self.Connection = conn - self.Connection.PlugIn(self) - dispatcher_nb.Dispatcher().PlugIn(self) - self._register_handlers() + def on_connect(self, conn): + self.Connection = conn + self.Connection.PlugIn(self) + dispatcher_nb.Dispatcher().PlugIn(self) + self._register_handlers() - def StreamInit(self): - ''' Send an initial stream header. ''' - self.Dispatcher.Stream = simplexml.NodeBuilder() - self.Dispatcher.Stream._dispatch_depth = 2 - self.Dispatcher.Stream.dispatch = self.Dispatcher.dispatch - self.Dispatcher.Stream.stream_header_received = self._check_stream_start - self.Dispatcher.Stream.features = None - if self.sock_type == TYPE_CLIENT: - self.send_stream_header() + def StreamInit(self): + ''' Send an initial stream header. ''' + self.Dispatcher.Stream = simplexml.NodeBuilder() + self.Dispatcher.Stream._dispatch_depth = 2 + self.Dispatcher.Stream.dispatch = self.Dispatcher.dispatch + self.Dispatcher.Stream.stream_header_received = self._check_stream_start + self.Dispatcher.Stream.features = None + if self.sock_type == TYPE_CLIENT: + self.send_stream_header() - def send_stream_header(self): - self.Dispatcher._metastream = Node('stream:stream') - self.Dispatcher._metastream.setNamespace(self.Namespace) - self.Dispatcher._metastream.setAttr('version', '1.0') - self.Dispatcher._metastream.setAttr('xmlns:stream', NS_STREAMS) - self.Dispatcher._metastream.setAttr('from', self.conn_holder.zeroconf.name) - if self.to: - self.Dispatcher._metastream.setAttr('to', self.to) - self.Dispatcher.send("%s>" % str( - self.Dispatcher._metastream)[:-2]) + def send_stream_header(self): + self.Dispatcher._metastream = Node('stream:stream') + self.Dispatcher._metastream.setNamespace(self.Namespace) + self.Dispatcher._metastream.setAttr('version', '1.0') + self.Dispatcher._metastream.setAttr('xmlns:stream', NS_STREAMS) + self.Dispatcher._metastream.setAttr('from', self.conn_holder.zeroconf.name) + if self.to: + self.Dispatcher._metastream.setAttr('to', self.to) + self.Dispatcher.send("%s>" % str( + self.Dispatcher._metastream)[:-2]) - def _check_stream_start(self, ns, tag, attrs): - if ns != NS_STREAMS or tag != 'stream': - log.error('Incorrect stream start: (%s,%s).Terminating!' % (tag, ns), 'error') - self.Connection.disconnect() - if self.on_not_ok: - self.on_not_ok('Connection to host could not be established: Incorrect answer from server.') - return - if self.sock_type == TYPE_SERVER: - if attrs.has_key('from'): - self.to = attrs['from'] - self.send_stream_header() - if attrs.has_key('version') and attrs['version'] == '1.0': - # other part supports stream features - features = Node('stream:features') - self.Dispatcher.send(features) - while self.stanzaqueue: - stanza, is_message = self.stanzaqueue.pop(0) - self.send(stanza, is_message) - elif self.sock_type == TYPE_CLIENT: - while self.stanzaqueue: - stanza, is_message = self.stanzaqueue.pop(0) - self.send(stanza, is_message) + def _check_stream_start(self, ns, tag, attrs): + if ns != NS_STREAMS or tag != 'stream': + log.error('Incorrect stream start: (%s,%s).Terminating!' % (tag, ns), 'error') + self.Connection.disconnect() + if self.on_not_ok: + self.on_not_ok('Connection to host could not be established: Incorrect answer from server.') + return + if self.sock_type == TYPE_SERVER: + if attrs.has_key('from'): + self.to = attrs['from'] + self.send_stream_header() + if attrs.has_key('version') and attrs['version'] == '1.0': + # other part supports stream features + features = Node('stream:features') + self.Dispatcher.send(features) + while self.stanzaqueue: + stanza, is_message = self.stanzaqueue.pop(0) + self.send(stanza, is_message) + elif self.sock_type == TYPE_CLIENT: + while self.stanzaqueue: + stanza, is_message = self.stanzaqueue.pop(0) + self.send(stanza, is_message) - def on_disconnect(self): - if self.conn_holder: - if self.conn_holder.ids_of_awaiting_messages.has_key(self.fd): - del self.conn_holder.ids_of_awaiting_messages[self.fd] - self.conn_holder.remove_connection(self.sock_hash) - if self.__dict__.has_key('Dispatcher'): - self.Dispatcher.PlugOut() - if self.__dict__.has_key('P2PConnection'): - self.P2PConnection.PlugOut() - self.Connection = None - self._caller = None - self.conn_holder = None + def on_disconnect(self): + if self.conn_holder: + if self.conn_holder.ids_of_awaiting_messages.has_key(self.fd): + del self.conn_holder.ids_of_awaiting_messages[self.fd] + self.conn_holder.remove_connection(self.sock_hash) + if self.__dict__.has_key('Dispatcher'): + self.Dispatcher.PlugOut() + if self.__dict__.has_key('P2PConnection'): + self.P2PConnection.PlugOut() + self.Connection = None + self._caller = None + self.conn_holder = None - def force_disconnect(self): - if self.Connection: - self.disconnect() - else: - self.on_disconnect() + def force_disconnect(self): + if self.Connection: + self.disconnect() + else: + self.on_disconnect() - def _on_receive_document_attrs(self, data): - if data: - self.Dispatcher.ProcessNonBlocking(data) - if not hasattr(self, 'Dispatcher') or \ - self.Dispatcher.Stream._document_attrs is None: - return - self.onreceive(None) - if self.Dispatcher.Stream._document_attrs.has_key('version') and \ - self.Dispatcher.Stream._document_attrs['version'] == '1.0': - #~ self.onreceive(self._on_receive_stream_features) - #XXX continue with TLS - return - self.onreceive(None) - return True + def _on_receive_document_attrs(self, data): + if data: + self.Dispatcher.ProcessNonBlocking(data) + if not hasattr(self, 'Dispatcher') or \ + self.Dispatcher.Stream._document_attrs is None: + return + self.onreceive(None) + if self.Dispatcher.Stream._document_attrs.has_key('version') and \ + self.Dispatcher.Stream._document_attrs['version'] == '1.0': + #~ self.onreceive(self._on_receive_stream_features) + #XXX continue with TLS + return + self.onreceive(None) + return True - def remove_timeout(self): - pass + def remove_timeout(self): + pass - def _register_handlers(self): - self._caller.peerhost = self.Connection._sock.getsockname() - self.RegisterHandler('message', lambda conn, data:self._caller._messageCB( - self.Server, conn, data)) - self.RegisterHandler('iq', self._caller._siSetCB, 'set', - common.xmpp.NS_SI) - self.RegisterHandler('iq', self._caller._siErrorCB, 'error', - common.xmpp.NS_SI) - self.RegisterHandler('iq', self._caller._siResultCB, 'result', - common.xmpp.NS_SI) - self.RegisterHandler('iq', self._caller._bytestreamSetCB, 'set', - common.xmpp.NS_BYTESTREAM) - self.RegisterHandler('iq', self._caller._bytestreamResultCB, 'result', - common.xmpp.NS_BYTESTREAM) - self.RegisterHandler('iq', self._caller._bytestreamErrorCB, 'error', - common.xmpp.NS_BYTESTREAM) - self.RegisterHandler('iq', self._caller._DiscoverItemsGetCB, 'get', - common.xmpp.NS_DISCO_ITEMS) + def _register_handlers(self): + self._caller.peerhost = self.Connection._sock.getsockname() + self.RegisterHandler('message', lambda conn, data:self._caller._messageCB( + self.Server, conn, data)) + self.RegisterHandler('iq', self._caller._siSetCB, 'set', + common.xmpp.NS_SI) + self.RegisterHandler('iq', self._caller._siErrorCB, 'error', + common.xmpp.NS_SI) + self.RegisterHandler('iq', self._caller._siResultCB, 'result', + common.xmpp.NS_SI) + self.RegisterHandler('iq', self._caller._bytestreamSetCB, 'set', + common.xmpp.NS_BYTESTREAM) + self.RegisterHandler('iq', self._caller._bytestreamResultCB, 'result', + common.xmpp.NS_BYTESTREAM) + self.RegisterHandler('iq', self._caller._bytestreamErrorCB, 'error', + common.xmpp.NS_BYTESTREAM) + self.RegisterHandler('iq', self._caller._DiscoverItemsGetCB, 'get', + common.xmpp.NS_DISCO_ITEMS) class P2PConnection(IdleObject, PlugIn): - def __init__(self, sock_hash, _sock, host=None, port=None, caller=None, - on_connect=None, client=None): - IdleObject.__init__(self) - self._owner = client - PlugIn.__init__(self) - self.sendqueue = [] - self.sendbuff = None - self.buff_is_message = False - self._sock = _sock - self.sock_hash = None - self.host, self.port = host, port - self.on_connect = on_connect - self.client = client - self.writable = False - self.readable = False - self._exported_methods = [self.send, self.disconnect, self.onreceive] - self.on_receive = None - if _sock: - self._sock = _sock - self.state = 1 - self._sock.setblocking(False) - self.fd = self._sock.fileno() - self.on_connect(self) - else: - self.state = 0 - try: - self.ais = socket.getaddrinfo(host, port, socket.AF_UNSPEC, - socket.SOCK_STREAM) - except socket.gaierror, e: - log.info('Lookup failure for %s: %s[%s]', host, e[1], repr(e[0]), - exc_info=True) - else: - self.connect_to_next_ip() + def __init__(self, sock_hash, _sock, host=None, port=None, caller=None, + on_connect=None, client=None): + IdleObject.__init__(self) + self._owner = client + PlugIn.__init__(self) + self.sendqueue = [] + self.sendbuff = None + self.buff_is_message = False + self._sock = _sock + self.sock_hash = None + self.host, self.port = host, port + self.on_connect = on_connect + self.client = client + self.writable = False + self.readable = False + self._exported_methods = [self.send, self.disconnect, self.onreceive] + self.on_receive = None + if _sock: + self._sock = _sock + self.state = 1 + self._sock.setblocking(False) + self.fd = self._sock.fileno() + self.on_connect(self) + else: + self.state = 0 + try: + self.ais = socket.getaddrinfo(host, port, socket.AF_UNSPEC, + socket.SOCK_STREAM) + except socket.gaierror, e: + log.info('Lookup failure for %s: %s[%s]', host, e[1], repr(e[0]), + exc_info=True) + else: + self.connect_to_next_ip() - def connect_to_next_ip(self): - if len(self.ais) == 0: - log.error('Connection failure to %s', self.host, exc_info=True) - self.disconnect() - return - ai = self.ais.pop(0) - log.info('Trying to connect to %s through %s:%s', self.host, ai[4][0], - ai[4][1], exc_info=True) - try: - self._sock = socket.socket(*ai[:3]) - self._sock.setblocking(False) - self._server = ai[4] - except socket.error: - if sys.exc_value[0] != errno.EINPROGRESS: - # for all errors, we try other addresses - self.connect_to_next_ip() - return - self.fd = self._sock.fileno() - gajim.idlequeue.plug_idle(self, True, False) - self.set_timeout(CONNECT_TIMEOUT_SECONDS) - self.do_connect() + def connect_to_next_ip(self): + if len(self.ais) == 0: + log.error('Connection failure to %s', self.host, exc_info=True) + self.disconnect() + return + ai = self.ais.pop(0) + log.info('Trying to connect to %s through %s:%s', self.host, ai[4][0], + ai[4][1], exc_info=True) + try: + self._sock = socket.socket(*ai[:3]) + self._sock.setblocking(False) + self._server = ai[4] + except socket.error: + if sys.exc_value[0] != errno.EINPROGRESS: + # for all errors, we try other addresses + self.connect_to_next_ip() + return + self.fd = self._sock.fileno() + gajim.idlequeue.plug_idle(self, True, False) + self.set_timeout(CONNECT_TIMEOUT_SECONDS) + self.do_connect() - def set_timeout(self, timeout): - gajim.idlequeue.remove_timeout(self.fd) - if self.state >= 0: - gajim.idlequeue.set_read_timeout(self.fd, timeout) + def set_timeout(self, timeout): + gajim.idlequeue.remove_timeout(self.fd) + if self.state >= 0: + gajim.idlequeue.set_read_timeout(self.fd, timeout) - def plugin(self, owner): - self.onreceive(owner._on_receive_document_attrs) - self._plug_idle() - return True + def plugin(self, owner): + self.onreceive(owner._on_receive_document_attrs) + self._plug_idle() + return True - def plugout(self): - '''Disconnect from the remote server and unregister self.disconnected method from - the owner's dispatcher.''' - self.disconnect() - self._owner = None + def plugout(self): + '''Disconnect from the remote server and unregister self.disconnected method from + the owner's dispatcher.''' + self.disconnect() + self._owner = None - def onreceive(self, recv_handler): - if not recv_handler: - if hasattr(self._owner, 'Dispatcher'): - self.on_receive = self._owner.Dispatcher.ProcessNonBlocking - else: - self.on_receive = None - return - _tmp = self.on_receive - # make sure this cb is not overriden by recursive calls - if not recv_handler(None) and _tmp == self.on_receive: - self.on_receive = recv_handler + def onreceive(self, recv_handler): + if not recv_handler: + if hasattr(self._owner, 'Dispatcher'): + self.on_receive = self._owner.Dispatcher.ProcessNonBlocking + else: + self.on_receive = None + return + _tmp = self.on_receive + # make sure this cb is not overriden by recursive calls + if not recv_handler(None) and _tmp == self.on_receive: + self.on_receive = recv_handler - def send(self, packet, is_message=False, now=False): - '''Append stanza to the queue of messages to be send if now is - False, else send it instantly. - If supplied data is unicode string, encode it to utf-8. - ''' - print 'ici' - if self.state <= 0: - return + def send(self, packet, is_message=False, now=False): + '''Append stanza to the queue of messages to be send if now is + False, else send it instantly. + If supplied data is unicode string, encode it to utf-8. + ''' + print 'ici' + if self.state <= 0: + return - r = packet + r = packet - if isinstance(r, unicode): - r = r.encode('utf-8') - elif not isinstance(r, str): - r = ustr(r).encode('utf-8') + if isinstance(r, unicode): + r = r.encode('utf-8') + elif not isinstance(r, str): + r = ustr(r).encode('utf-8') - if now: - self.sendqueue.insert(0, (r, is_message)) - self._do_send() - else: - self.sendqueue.append((r, is_message)) - self._plug_idle() + if now: + self.sendqueue.insert(0, (r, is_message)) + self._do_send() + else: + self.sendqueue.append((r, is_message)) + self._plug_idle() - def read_timeout(self): - ids = self.client.conn_holder.ids_of_awaiting_messages - if self.fd in ids and len(ids[self.fd]) > 0: - for (id_, thread_id) in ids[self.fd]: - if hasattr(self._owner, 'Dispatcher'): - self._owner.Dispatcher.Event('', DATA_ERROR, (self.client.to, - thread_id)) - else: - self._owner.on_not_ok('conenction timeout') - ids[self.fd] = [] - self.pollend() + def read_timeout(self): + ids = self.client.conn_holder.ids_of_awaiting_messages + if self.fd in ids and len(ids[self.fd]) > 0: + for (id_, thread_id) in ids[self.fd]: + if hasattr(self._owner, 'Dispatcher'): + self._owner.Dispatcher.Event('', DATA_ERROR, (self.client.to, + thread_id)) + else: + self._owner.on_not_ok('conenction timeout') + ids[self.fd] = [] + self.pollend() - def do_connect(self): - errnum = 0 - try: - self._sock.connect(self._server) - self._sock.setblocking(False) - except Exception, ee: - (errnum, errstr) = ee - if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): - return - # win32 needs this - elif errnum not in (0, 10056, errno.EISCONN) or self.state != 0: - log.error('Could not connect to %s: %s [%s]', self.host, errnum, - errstr) - self.connect_to_next_ip() - return - else: # socket is already connected - self._sock.setblocking(False) - self.state = 1 # connected - # we are connected - self.on_connect(self) + def do_connect(self): + errnum = 0 + try: + self._sock.connect(self._server) + self._sock.setblocking(False) + except Exception, ee: + (errnum, errstr) = ee + if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): + return + # win32 needs this + elif errnum not in (0, 10056, errno.EISCONN) or self.state != 0: + log.error('Could not connect to %s: %s [%s]', self.host, errnum, + errstr) + self.connect_to_next_ip() + return + else: # socket is already connected + self._sock.setblocking(False) + self.state = 1 # connected + # we are connected + self.on_connect(self) - def pollout(self): - if self.state == 0: - self.do_connect() - return - gajim.idlequeue.remove_timeout(self.fd) - self._do_send() + def pollout(self): + if self.state == 0: + self.do_connect() + return + gajim.idlequeue.remove_timeout(self.fd) + self._do_send() - def pollend(self): - self.state = -1 - self.disconnect() + def pollend(self): + self.state = -1 + self.disconnect() - def pollin(self): - ''' Reads all pending incoming data. Calls owner's disconnected() method if appropriate.''' - received = '' - errnum = 0 - try: - # get as many bites, as possible, but not more than RECV_BUFSIZE - received = self._sock.recv(MAX_BUFF_LEN) - except Exception, e: - if len(e.args) > 0 and isinstance(e.args[0], int): - errnum = e[0] - # "received" will be empty anyhow - if errnum == socket.SSL_ERROR_WANT_READ: - pass - elif errnum in [errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN]: - self.pollend() - # don't proccess result, cas it will raise error - return - elif not received : - if errnum != socket.SSL_ERROR_EOF: - # 8 EOF occurred in violation of protocol - self.pollend() - if self.state >= 0: - self.disconnect() - return + def pollin(self): + ''' Reads all pending incoming data. Calls owner's disconnected() method if appropriate.''' + received = '' + errnum = 0 + try: + # get as many bites, as possible, but not more than RECV_BUFSIZE + received = self._sock.recv(MAX_BUFF_LEN) + except Exception, e: + if len(e.args) > 0 and isinstance(e.args[0], int): + errnum = e[0] + # "received" will be empty anyhow + if errnum == socket.SSL_ERROR_WANT_READ: + pass + elif errnum in [errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN]: + self.pollend() + # don't proccess result, cas it will raise error + return + elif not received : + if errnum != socket.SSL_ERROR_EOF: + # 8 EOF occurred in violation of protocol + self.pollend() + if self.state >= 0: + self.disconnect() + return - if self.state < 0: - return - if self.on_receive: - if self._owner.sock_type == TYPE_CLIENT: - self.set_timeout(ACTIVITY_TIMEOUT_SECONDS) - if received.strip(): - log.debug('received: %s', received) - if hasattr(self._owner, 'Dispatcher'): - self._owner.Dispatcher.Event('', DATA_RECEIVED, received) - self.on_receive(received) - else: - # This should never happed, so we need the debug - log.error('Unhandled data received: %s' % received) - self.disconnect() - return True + if self.state < 0: + return + if self.on_receive: + if self._owner.sock_type == TYPE_CLIENT: + self.set_timeout(ACTIVITY_TIMEOUT_SECONDS) + if received.strip(): + log.debug('received: %s', received) + if hasattr(self._owner, 'Dispatcher'): + self._owner.Dispatcher.Event('', DATA_RECEIVED, received) + self.on_receive(received) + else: + # This should never happed, so we need the debug + log.error('Unhandled data received: %s' % received) + self.disconnect() + return True - def disconnect(self, message=''): - ''' Closes the socket. ''' - gajim.idlequeue.remove_timeout(self.fd) - gajim.idlequeue.unplug_idle(self.fd) - try: - self._sock.shutdown(socket.SHUT_RDWR) - self._sock.close() - except socket.error: - # socket is already closed - pass - self.fd = -1 - self.state = -1 - if self._owner: - self._owner.on_disconnect() + def disconnect(self, message=''): + ''' Closes the socket. ''' + gajim.idlequeue.remove_timeout(self.fd) + gajim.idlequeue.unplug_idle(self.fd) + try: + self._sock.shutdown(socket.SHUT_RDWR) + self._sock.close() + except socket.error: + # socket is already closed + pass + self.fd = -1 + self.state = -1 + if self._owner: + self._owner.on_disconnect() - def _do_send(self): - if not self.sendbuff: - if not self.sendqueue: - return None # nothing to send - self.sendbuff, self.buff_is_message = self.sendqueue.pop(0) - self.sent_data = self.sendbuff - try: - send_count = self._sock.send(self.sendbuff) - if send_count: - self.sendbuff = self.sendbuff[send_count:] - if not self.sendbuff and not self.sendqueue: - if self.state < 0: - gajim.idlequeue.unplug_idle(self.fd) - self._on_send() - self.disconnect() - return - # we are not waiting for write - self._plug_idle() - self._on_send() + def _do_send(self): + if not self.sendbuff: + if not self.sendqueue: + return None # nothing to send + self.sendbuff, self.buff_is_message = self.sendqueue.pop(0) + self.sent_data = self.sendbuff + try: + send_count = self._sock.send(self.sendbuff) + if send_count: + self.sendbuff = self.sendbuff[send_count:] + if not self.sendbuff and not self.sendqueue: + if self.state < 0: + gajim.idlequeue.unplug_idle(self.fd) + self._on_send() + self.disconnect() + return + # we are not waiting for write + self._plug_idle() + self._on_send() - except socket.error, e: - if e[0] == socket.SSL_ERROR_WANT_WRITE: - return True - if self.state < 0: - self.disconnect() - return - self._on_send_failure() - return - if self._owner.sock_type == TYPE_CLIENT: - self.set_timeout(ACTIVITY_TIMEOUT_SECONDS) - return True + except socket.error, e: + if e[0] == socket.SSL_ERROR_WANT_WRITE: + return True + if self.state < 0: + self.disconnect() + return + self._on_send_failure() + return + if self._owner.sock_type == TYPE_CLIENT: + self.set_timeout(ACTIVITY_TIMEOUT_SECONDS) + return True - def _plug_idle(self): - readable = self.state != 0 - if self.sendqueue or self.sendbuff: - writable = True - else: - writable = False - if self.writable != writable or self.readable != readable: - gajim.idlequeue.plug_idle(self, writable, readable) + def _plug_idle(self): + readable = self.state != 0 + if self.sendqueue or self.sendbuff: + writable = True + else: + writable = False + if self.writable != writable or self.readable != readable: + gajim.idlequeue.plug_idle(self, writable, readable) - def _on_send(self): - if self.sent_data and self.sent_data.strip(): - log.debug('sent: %s' % self.sent_data) - if hasattr(self._owner, 'Dispatcher'): - self._owner.Dispatcher.Event('', DATA_SENT, self.sent_data) - self.sent_data = None - if self.buff_is_message: - self._owner.on_message_sent(self.fd) - self.buff_is_message = False + def _on_send(self): + if self.sent_data and self.sent_data.strip(): + log.debug('sent: %s' % self.sent_data) + if hasattr(self._owner, 'Dispatcher'): + self._owner.Dispatcher.Event('', DATA_SENT, self.sent_data) + self.sent_data = None + if self.buff_is_message: + self._owner.on_message_sent(self.fd) + self.buff_is_message = False - def _on_send_failure(self): - log.error('Socket error while sending data') - self._owner.on_disconnect() - self.sent_data = None + def _on_send_failure(self): + log.error('Socket error while sending data') + self._owner.on_disconnect() + self.sent_data = None class ClientZeroconf: - def __init__(self, caller): - self.caller = caller - self.zeroconf = None - self.roster = None - self.last_msg = '' - self.connections = {} - self.recipient_to_hash = {} - self.ip_to_hash = {} - self.hash_to_port = {} - self.listener = None - self.ids_of_awaiting_messages = {} - self.disconnect_handlers = [] - self.disconnecting = False + def __init__(self, caller): + self.caller = caller + self.zeroconf = None + self.roster = None + self.last_msg = '' + self.connections = {} + self.recipient_to_hash = {} + self.ip_to_hash = {} + self.hash_to_port = {} + self.listener = None + self.ids_of_awaiting_messages = {} + self.disconnect_handlers = [] + self.disconnecting = False - def connect(self, show, msg): - self.port = self.start_listener(self.caller.port) - if not self.port: - return False - self.zeroconf_init(show, msg) - if not self.zeroconf.connect(): - self.disconnect() - return None - self.roster = roster_zeroconf.Roster(self.zeroconf) - return True + def connect(self, show, msg): + self.port = self.start_listener(self.caller.port) + if not self.port: + return False + self.zeroconf_init(show, msg) + if not self.zeroconf.connect(): + self.disconnect() + return None + self.roster = roster_zeroconf.Roster(self.zeroconf) + return True - def remove_announce(self): - if self.zeroconf: - return self.zeroconf.remove_announce() + def remove_announce(self): + if self.zeroconf: + return self.zeroconf.remove_announce() - def announce(self): - if self.zeroconf: - return self.zeroconf.announce() + def announce(self): + if self.zeroconf: + return self.zeroconf.announce() - def set_show_msg(self, show, msg): - if self.zeroconf: - self.zeroconf.txt['msg'] = msg - self.last_msg = msg - return self.zeroconf.update_txt(show) + def set_show_msg(self, show, msg): + if self.zeroconf: + self.zeroconf.txt['msg'] = msg + self.last_msg = msg + return self.zeroconf.update_txt(show) - def resolve_all(self): - if self.zeroconf: - self.zeroconf.resolve_all() + def resolve_all(self): + if self.zeroconf: + self.zeroconf.resolve_all() - def reannounce(self, txt): - self.remove_announce() - self.zeroconf.txt = txt - self.zeroconf.port = self.port - self.zeroconf.username = self.caller.username - return self.announce() + def reannounce(self, txt): + self.remove_announce() + self.zeroconf.txt = txt + self.zeroconf.port = self.port + self.zeroconf.username = self.caller.username + return self.announce() - def zeroconf_init(self, show, msg): - self.zeroconf = zeroconf.Zeroconf(self.caller._on_new_service, - self.caller._on_remove_service, self.caller._on_name_conflictCB, - self.caller._on_disconnected, self.caller._on_error, - self.caller.username, self.caller.host, self.port) - self.zeroconf.txt['msg'] = msg - self.zeroconf.txt['status'] = show - self.zeroconf.txt['1st'] = self.caller.first - self.zeroconf.txt['last'] = self.caller.last - self.zeroconf.txt['jid'] = self.caller.jabber_id - self.zeroconf.txt['email'] = self.caller.email - self.zeroconf.username = self.caller.username - self.zeroconf.host = self.caller.host - self.zeroconf.port = self.port - self.last_msg = msg + def zeroconf_init(self, show, msg): + self.zeroconf = zeroconf.Zeroconf(self.caller._on_new_service, + self.caller._on_remove_service, self.caller._on_name_conflictCB, + self.caller._on_disconnected, self.caller._on_error, + self.caller.username, self.caller.host, self.port) + self.zeroconf.txt['msg'] = msg + self.zeroconf.txt['status'] = show + self.zeroconf.txt['1st'] = self.caller.first + self.zeroconf.txt['last'] = self.caller.last + self.zeroconf.txt['jid'] = self.caller.jabber_id + self.zeroconf.txt['email'] = self.caller.email + self.zeroconf.username = self.caller.username + self.zeroconf.host = self.caller.host + self.zeroconf.port = self.port + self.last_msg = msg - def disconnect(self): - # to avoid recursive calls - if self.disconnecting: - return - if self.listener: - self.listener.disconnect() - self.listener = None - if self.zeroconf: - self.zeroconf.disconnect() - self.zeroconf = None - if self.roster: - self.roster.zeroconf = None - self.roster._data = None - self.roster = None - self.disconnecting = True - for i in reversed(self.disconnect_handlers): - log.debug('Calling disconnect handler %s' % i) - i() - self.disconnecting = False + def disconnect(self): + # to avoid recursive calls + if self.disconnecting: + return + if self.listener: + self.listener.disconnect() + self.listener = None + if self.zeroconf: + self.zeroconf.disconnect() + self.zeroconf = None + if self.roster: + self.roster.zeroconf = None + self.roster._data = None + self.roster = None + self.disconnecting = True + for i in reversed(self.disconnect_handlers): + log.debug('Calling disconnect handler %s' % i) + i() + self.disconnecting = False - def start_disconnect(self): - self.disconnect() + def start_disconnect(self): + self.disconnect() - def kill_all_connections(self): - for connection in self.connections.values(): - connection.force_disconnect() + def kill_all_connections(self): + for connection in self.connections.values(): + connection.force_disconnect() - def add_connection(self, connection, ip, port, recipient): - sock_hash=connection.sock_hash - if sock_hash not in self.connections: - self.connections[sock_hash] = connection - self.ip_to_hash[ip] = sock_hash - self.hash_to_port[sock_hash] = port - if recipient: - self.recipient_to_hash[recipient] = sock_hash + def add_connection(self, connection, ip, port, recipient): + sock_hash=connection.sock_hash + if sock_hash not in self.connections: + self.connections[sock_hash] = connection + self.ip_to_hash[ip] = sock_hash + self.hash_to_port[sock_hash] = port + if recipient: + self.recipient_to_hash[recipient] = sock_hash - def remove_connection(self, sock_hash): - if sock_hash in self.connections: - del self.connections[sock_hash] - for i in self.recipient_to_hash: - if self.recipient_to_hash[i] == sock_hash: - del self.recipient_to_hash[i] - break - for i in self.ip_to_hash: - if self.ip_to_hash[i] == sock_hash: - del self.ip_to_hash[i] - break - if self.hash_to_port.has_key(sock_hash): - del self.hash_to_port[sock_hash] + def remove_connection(self, sock_hash): + if sock_hash in self.connections: + del self.connections[sock_hash] + for i in self.recipient_to_hash: + if self.recipient_to_hash[i] == sock_hash: + del self.recipient_to_hash[i] + break + for i in self.ip_to_hash: + if self.ip_to_hash[i] == sock_hash: + del self.ip_to_hash[i] + break + if self.hash_to_port.has_key(sock_hash): + del self.hash_to_port[sock_hash] - def start_listener(self, port): - for p in range(port, port + 5): - self.listener = ZeroconfListener(p, self) - self.listener.bind() - if self.listener.started: - return p - self.listener = None - return False + def start_listener(self, port): + for p in range(port, port + 5): + self.listener = ZeroconfListener(p, self) + self.listener.bind() + if self.listener.started: + return p + self.listener = None + return False - def getRoster(self): - if self.roster: - return self.roster.getRoster() - return {} + def getRoster(self): + if self.roster: + return self.roster.getRoster() + return {} - def send(self, stanza, is_message=False, now=False, on_ok=None, - on_not_ok=None): - stanza.setFrom(self.roster.zeroconf.name) - to = stanza.getTo() + def send(self, stanza, is_message=False, now=False, on_ok=None, + on_not_ok=None): + stanza.setFrom(self.roster.zeroconf.name) + to = stanza.getTo() - try: - item = self.roster[to] - except KeyError: - # Contact offline - return -1 + try: + item = self.roster[to] + except KeyError: + # Contact offline + return -1 - # look for hashed connections - if to in self.recipient_to_hash: - conn = self.connections[self.recipient_to_hash[to]] - id_ = stanza.getID() or '' - if conn.add_stanza(stanza, is_message): - if on_ok: - on_ok(id_) - return + # look for hashed connections + if to in self.recipient_to_hash: + conn = self.connections[self.recipient_to_hash[to]] + id_ = stanza.getID() or '' + if conn.add_stanza(stanza, is_message): + if on_ok: + on_ok(id_) + return - if item['address'] in self.ip_to_hash: - hash_ = self.ip_to_hash[item['address']] - if self.hash_to_port[hash_] == item['port']: - conn = self.connections[hash_] - id_ = stanza.getID() or '' - if conn.add_stanza(stanza, is_message): - if on_ok: - on_ok(id_) - return + if item['address'] in self.ip_to_hash: + hash_ = self.ip_to_hash[item['address']] + if self.hash_to_port[hash_] == item['port']: + conn = self.connections[hash_] + id_ = stanza.getID() or '' + if conn.add_stanza(stanza, is_message): + if on_ok: + on_ok(id_) + return - # otherwise open new connection - if not stanza.getID(): - stanza.setID('zero') - P2PClient(None, item['address'], item['port'], self, - [(stanza, is_message)], to, on_ok=on_ok, on_not_ok=on_not_ok) + # otherwise open new connection + if not stanza.getID(): + stanza.setID('zero') + P2PClient(None, item['address'], item['port'], self, + [(stanza, is_message)], to, on_ok=on_ok, on_not_ok=on_not_ok) - def RegisterDisconnectHandler(self, handler): - ''' Register handler that will be called on disconnect.''' - self.disconnect_handlers.append(handler) + def RegisterDisconnectHandler(self, handler): + ''' Register handler that will be called on disconnect.''' + self.disconnect_handlers.append(handler) - def UnregisterDisconnectHandler(self, handler): - ''' Unregister handler that is called on disconnect.''' - self.disconnect_handlers.remove(handler) + def UnregisterDisconnectHandler(self, handler): + ''' Unregister handler that is called on disconnect.''' + self.disconnect_handlers.remove(handler) - def SendAndWaitForResponse(self, stanza, timeout=None, func=None, args=None): - ''' - Send stanza and wait for recipient's response to it. Will call transports - on_timeout callback if response is not retrieved in time. + def SendAndWaitForResponse(self, stanza, timeout=None, func=None, args=None): + ''' + Send stanza and wait for recipient's response to it. Will call transports + on_timeout callback if response is not retrieved in time. - Be aware: Only timeout of latest call of SendAndWait is active. - ''' -# if timeout is None: -# timeout = DEFAULT_TIMEOUT_SECONDS - def on_ok(_waitid): -# if timeout: -# self._owner.set_timeout(timeout) - to = stanza.getTo() - conn = None - if to in self.recipient_to_hash: - conn = self.connections[self.recipient_to_hash[to]] - elif item['address'] in self.ip_to_hash: - hash_ = self.ip_to_hash[item['address']] - if self.hash_to_port[hash_] == item['port']: - conn = self.connections[hash_] - if func: - conn.Dispatcher.on_responses[_waitid] = (func, args) - conn.onreceive(conn.Dispatcher._WaitForData) - conn.Dispatcher._expected[_waitid] = None - self.send(stanza, on_ok=on_ok) + Be aware: Only timeout of latest call of SendAndWait is active. + ''' +# if timeout is None: +# timeout = DEFAULT_TIMEOUT_SECONDS + def on_ok(_waitid): +# if timeout: +# self._owner.set_timeout(timeout) + to = stanza.getTo() + conn = None + if to in self.recipient_to_hash: + conn = self.connections[self.recipient_to_hash[to]] + elif item['address'] in self.ip_to_hash: + hash_ = self.ip_to_hash[item['address']] + if self.hash_to_port[hash_] == item['port']: + conn = self.connections[hash_] + if func: + conn.Dispatcher.on_responses[_waitid] = (func, args) + conn.onreceive(conn.Dispatcher._WaitForData) + conn.Dispatcher._expected[_waitid] = None + self.send(stanza, on_ok=on_ok) - def SendAndCallForResponse(self, stanza, func=None, args=None): - ''' Put stanza on the wire and call back when recipient replies. - Additional callback arguments can be specified in args. ''' - self.SendAndWaitForResponse(stanza, 0, func, args) - -# vim: se ts=3: + def SendAndCallForResponse(self, stanza, func=None, args=None): + ''' Put stanza on the wire and call back when recipient replies. + Additional callback arguments can be specified in args. ''' + self.SendAndWaitForResponse(stanza, 0, func, args) diff --git a/src/common/zeroconf/connection_handlers_zeroconf.py b/src/common/zeroconf/connection_handlers_zeroconf.py index 8c036f3f8..2e743ffc4 100644 --- a/src/common/zeroconf/connection_handlers_zeroconf.py +++ b/src/common/zeroconf/connection_handlers_zeroconf.py @@ -2,10 +2,10 @@ ## Copyright (C) 2006 Gajim Team ## ## Contributors for this file: -## - Yann Leboulanger -## - Nikos Kouremenos -## - Dimitur Kirov -## - Travis Shirk +## - Yann Leboulanger +## - Nikos Kouremenos +## - Dimitur Kirov +## - Travis Shirk ## - Stefan Bethge ## ## This file is part of Gajim. @@ -42,176 +42,174 @@ import logging log = logging.getLogger('gajim.c.z.connection_handlers_zeroconf') STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd', - 'invisible'] + 'invisible'] # kind of events we can wait for an answer VCARD_PUBLISHED = 'vcard_published' VCARD_ARRIVED = 'vcard_arrived' AGENT_REMOVED = 'agent_removed' HAS_IDLE = True try: - import idle + import idle except Exception: - log.debug(_('Unable to load idle module')) - HAS_IDLE = False + log.debug(_('Unable to load idle module')) + HAS_IDLE = False from common import connection_handlers from session import ChatControlSession class ConnectionVcard(connection_handlers.ConnectionVcard): - def add_sha(self, p, send_caps = True): - return p + def add_sha(self, p, send_caps = True): + return p - def add_caps(self, p): - return p + def add_caps(self, p): + return p - def request_vcard(self, jid = None, is_fake_jid = False): - pass + def request_vcard(self, jid = None, is_fake_jid = False): + pass - def send_vcard(self, vcard): - pass + def send_vcard(self, vcard): + pass class ConnectionBytestream(connection_handlers.ConnectionBytestream): - def _ft_get_from(self, iq_obj): - return unicode(iq_obj.getFrom()) + def _ft_get_from(self, iq_obj): + return unicode(iq_obj.getFrom()) - def _ft_get_our_jid(self): - return gajim.get_jid_from_account(self.name) + def _ft_get_our_jid(self): + return gajim.get_jid_from_account(self.name) - def _ft_get_receiver_jid(self, file_props): - return file_props['receiver'].jid + def _ft_get_receiver_jid(self, file_props): + return file_props['receiver'].jid - def _ft_get_streamhost_jid_attr(self, streamhost): - return streamhost.getAttr('jid') + def _ft_get_streamhost_jid_attr(self, streamhost): + return streamhost.getAttr('jid') class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestream, ConnectionCommands, ConnectionPEP, ConnectionArchive, connection_handlers.ConnectionHandlersBase): - def __init__(self): - ConnectionVcard.__init__(self) - ConnectionBytestream.__init__(self) - ConnectionCommands.__init__(self) - ConnectionArchive.__init__(self) - connection_handlers.ConnectionHandlersBase.__init__(self) + def __init__(self): + ConnectionVcard.__init__(self) + ConnectionBytestream.__init__(self) + ConnectionCommands.__init__(self) + ConnectionArchive.__init__(self) + connection_handlers.ConnectionHandlersBase.__init__(self) - try: - idle.init() - except Exception: - global HAS_IDLE - HAS_IDLE = False + try: + idle.init() + except Exception: + global HAS_IDLE + HAS_IDLE = False - def _messageCB(self, ip, con, msg): - '''Called when we receive a message''' + def _messageCB(self, ip, con, msg): + '''Called when we receive a message''' - log.debug('Zeroconf MessageCB') + log.debug('Zeroconf MessageCB') - frm = msg.getFrom() - mtype = msg.getType() - thread_id = msg.getThread() + frm = msg.getFrom() + mtype = msg.getType() + thread_id = msg.getThread() - if not mtype: - mtype = 'normal' + if not mtype: + mtype = 'normal' - if frm is None: - for key in self.connection.zeroconf.contacts: - if ip == self.connection.zeroconf.contacts[key][zeroconf.C_ADDRESS]: - frm = key + if frm is None: + for key in self.connection.zeroconf.contacts: + if ip == self.connection.zeroconf.contacts[key][zeroconf.C_ADDRESS]: + frm = key - frm = unicode(frm) + frm = unicode(frm) - session = self.get_or_create_session(frm, thread_id) + session = self.get_or_create_session(frm, thread_id) - if thread_id and not session.received_thread_id: - session.received_thread_id = True + if thread_id and not session.received_thread_id: + session.received_thread_id = True - if msg.getTag('feature') and msg.getTag('feature').namespace == \ - common.xmpp.NS_FEATURE: - if gajim.HAVE_PYCRYPTO: - self._FeatureNegCB(con, msg, session) - return + if msg.getTag('feature') and msg.getTag('feature').namespace == \ + common.xmpp.NS_FEATURE: + if gajim.HAVE_PYCRYPTO: + self._FeatureNegCB(con, msg, session) + return - if msg.getTag('init') and msg.getTag('init').namespace == \ - common.xmpp.NS_ESESSION_INIT: - self._InitE2ECB(con, msg, session) + if msg.getTag('init') and msg.getTag('init').namespace == \ + common.xmpp.NS_ESESSION_INIT: + self._InitE2ECB(con, msg, session) - encrypted = False - tim = msg.getTimestamp() - tim = helpers.datetime_tuple(tim) - tim = time.localtime(timegm(tim)) + encrypted = False + tim = msg.getTimestamp() + tim = helpers.datetime_tuple(tim) + tim = time.localtime(timegm(tim)) - if msg.getTag('c', namespace = common.xmpp.NS_STANZA_CRYPTO): - encrypted = True + if msg.getTag('c', namespace = common.xmpp.NS_STANZA_CRYPTO): + encrypted = True - try: - msg = session.decrypt_stanza(msg) - except Exception: - self.dispatch('FAILED_DECRYPT', (frm, tim)) + try: + msg = session.decrypt_stanza(msg) + except Exception: + self.dispatch('FAILED_DECRYPT', (frm, tim)) - msgtxt = msg.getBody() - subject = msg.getSubject() # if not there, it's None + msgtxt = msg.getBody() + subject = msg.getSubject() # if not there, it's None - # invitations - invite = None - encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED) + # invitations + invite = None + encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED) - if not encTag: - invite = msg.getTag('x', namespace = common.xmpp.NS_MUC_USER) - if invite and not invite.getTag('invite'): - invite = None + if not encTag: + invite = msg.getTag('x', namespace = common.xmpp.NS_MUC_USER) + if invite and not invite.getTag('invite'): + invite = None - if encTag and self.USE_GPG: - #decrypt - encmsg = encTag.getData() + if encTag and self.USE_GPG: + #decrypt + encmsg = encTag.getData() - keyID = gajim.config.get_per('accounts', self.name, 'keyid') - if keyID: - decmsg = self.gpg.decrypt(encmsg, keyID) - # \x00 chars are not allowed in C (so in GTK) - msgtxt = decmsg.replace('\x00', '') - encrypted = True + keyID = gajim.config.get_per('accounts', self.name, 'keyid') + if keyID: + decmsg = self.gpg.decrypt(encmsg, keyID) + # \x00 chars are not allowed in C (so in GTK) + msgtxt = decmsg.replace('\x00', '') + encrypted = True - if mtype == 'error': - self.dispatch_error_msg(msg, msgtxt, session, frm, tim, subject) - else: - # XXX this shouldn't be hardcoded - if isinstance(session, ChatControlSession): - session.received(frm, msgtxt, tim, encrypted, msg) - else: - session.received(msg) - # END messageCB + if mtype == 'error': + self.dispatch_error_msg(msg, msgtxt, session, frm, tim, subject) + else: + # XXX this shouldn't be hardcoded + if isinstance(session, ChatControlSession): + session.received(frm, msgtxt, tim, encrypted, msg) + else: + session.received(msg) + # END messageCB - def store_metacontacts(self, tags): - ''' fake empty method ''' - # serverside metacontacts are not supported with zeroconf - # (there is no server) - pass + def store_metacontacts(self, tags): + ''' fake empty method ''' + # serverside metacontacts are not supported with zeroconf + # (there is no server) + pass - def remove_transfers_for_contact(self, contact): - ''' stop all active transfer for contact ''' - pass + def remove_transfers_for_contact(self, contact): + ''' stop all active transfer for contact ''' + pass - def remove_all_transfers(self): - ''' stops and removes all active connections from the socks5 pool ''' - pass + def remove_all_transfers(self): + ''' stops and removes all active connections from the socks5 pool ''' + pass - def remove_transfer(self, file_props, remove_from_list = True): - pass + def remove_transfer(self, file_props, remove_from_list = True): + pass - def _DiscoverItemsGetCB(self, con, iq_obj): - log.debug('DiscoverItemsGetCB') + def _DiscoverItemsGetCB(self, con, iq_obj): + log.debug('DiscoverItemsGetCB') - if not self.connection or self.connected < 2: - return + if not self.connection or self.connected < 2: + return - if self.commandItemsQuery(con, iq_obj): - raise common.xmpp.NodeProcessed - node = iq_obj.getTagAttr('query', 'node') - if node is None: - result = iq_obj.buildReply('result') - self.connection.send(result) - raise common.xmpp.NodeProcessed - if node==common.xmpp.NS_COMMANDS: - self.commandListQuery(con, iq_obj) - raise common.xmpp.NodeProcessed - -# vim: se ts=3: + if self.commandItemsQuery(con, iq_obj): + raise common.xmpp.NodeProcessed + node = iq_obj.getTagAttr('query', 'node') + if node is None: + result = iq_obj.buildReply('result') + self.connection.send(result) + raise common.xmpp.NodeProcessed + if node==common.xmpp.NS_COMMANDS: + self.commandListQuery(con, iq_obj) + raise common.xmpp.NodeProcessed diff --git a/src/common/zeroconf/connection_zeroconf.py b/src/common/zeroconf/connection_zeroconf.py index 1b43117c2..d05f69de0 100644 --- a/src/common/zeroconf/connection_zeroconf.py +++ b/src/common/zeroconf/connection_zeroconf.py @@ -1,10 +1,10 @@ -## common/zeroconf/connection_zeroconf.py +## common/zeroconf/connection_zeroconf.py ## ## Contributors for this file: -## - Yann Leboulanger -## - Nikos Kouremenos -## - Dimitur Kirov -## - Travis Shirk +## - Yann Leboulanger +## - Nikos Kouremenos +## - Dimitur Kirov +## - Travis Shirk ## - Stefan Bethge ## ## Copyright (C) 2003-2004 Yann Leboulanger @@ -39,7 +39,7 @@ random.seed() import signal if os.name != 'nt': - signal.signal(signal.SIGPIPE, signal.SIG_DFL) + signal.signal(signal.SIGPIPE, signal.SIG_DFL) import getpass import gobject @@ -51,314 +51,312 @@ from common.zeroconf import zeroconf from connection_handlers_zeroconf import * class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf): - '''Connection class''' - def __init__(self, name): - ConnectionHandlersZeroconf.__init__(self) - # system username - self.username = None - self.server_resource = '' # zeroconf has no resource, fake an empty one - self.is_zeroconf = True - self.call_resolve_timeout = False - # we don't need a password, but must be non-empty - self.password = 'zeroconf' - self.autoconnect = False + '''Connection class''' + def __init__(self, name): + ConnectionHandlersZeroconf.__init__(self) + # system username + self.username = None + self.server_resource = '' # zeroconf has no resource, fake an empty one + self.is_zeroconf = True + self.call_resolve_timeout = False + # we don't need a password, but must be non-empty + self.password = 'zeroconf' + self.autoconnect = False - CommonConnection.__init__(self, name) + CommonConnection.__init__(self, name) - def get_config_values_or_default(self): - ''' get name, host, port from config, or - create zeroconf account with default values''' + def get_config_values_or_default(self): + ''' get name, host, port from config, or + create zeroconf account with default values''' - if not gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name'): - gajim.log.debug('Creating zeroconf account') - gajim.config.add_per('accounts', gajim.ZEROCONF_ACC_NAME) - gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'autoconnect', True) - gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'no_log_for', - '') - gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'password', - 'zeroconf') - gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'sync_with_global_status', True) + if not gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name'): + gajim.log.debug('Creating zeroconf account') + gajim.config.add_per('accounts', gajim.ZEROCONF_ACC_NAME) + gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'autoconnect', True) + gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'no_log_for', + '') + gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'password', + 'zeroconf') + gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'sync_with_global_status', True) - gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'custom_port', 5298) - gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'is_zeroconf', True) - gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'use_ft_proxies', False) - #XXX make sure host is US-ASCII - self.host = unicode(socket.gethostname()) - gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'hostname', - self.host) - self.port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'custom_port') - self.autoconnect = gajim.config.get_per('accounts', - gajim.ZEROCONF_ACC_NAME, 'autoconnect') - self.sync_with_global_status = gajim.config.get_per('accounts', - gajim.ZEROCONF_ACC_NAME, 'sync_with_global_status') - self.first = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'zeroconf_first_name') - self.last = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'zeroconf_last_name') - self.jabber_id = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'zeroconf_jabber_id') - self.email = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'zeroconf_email') + gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'custom_port', 5298) + gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'is_zeroconf', True) + gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'use_ft_proxies', False) + #XXX make sure host is US-ASCII + self.host = unicode(socket.gethostname()) + gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'hostname', + self.host) + self.port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'custom_port') + self.autoconnect = gajim.config.get_per('accounts', + gajim.ZEROCONF_ACC_NAME, 'autoconnect') + self.sync_with_global_status = gajim.config.get_per('accounts', + gajim.ZEROCONF_ACC_NAME, 'sync_with_global_status') + self.first = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'zeroconf_first_name') + self.last = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'zeroconf_last_name') + self.jabber_id = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'zeroconf_jabber_id') + self.email = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'zeroconf_email') - if not self.username: - self.username = unicode(getpass.getuser()) - gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name', - self.username) - else: - self.username = gajim.config.get_per('accounts', - gajim.ZEROCONF_ACC_NAME, 'name') - # END __init__ + if not self.username: + self.username = unicode(getpass.getuser()) + gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name', + self.username) + else: + self.username = gajim.config.get_per('accounts', + gajim.ZEROCONF_ACC_NAME, 'name') + # END __init__ - def check_jid(self, jid): - return jid + def check_jid(self, jid): + return jid - def _reconnect(self): - # Do not try to reco while we are already trying - self.time_to_reconnect = None - gajim.log.debug('reconnect') + def _reconnect(self): + # Do not try to reco while we are already trying + self.time_to_reconnect = None + gajim.log.debug('reconnect') - self.disconnect() - self.change_status(self.old_show, self.status) + self.disconnect() + self.change_status(self.old_show, self.status) - def disable_account(self): - self.disconnect() + def disable_account(self): + self.disconnect() - def _on_resolve_timeout(self): - if self.connected: - self.connection.resolve_all() - diffs = self.roster.getDiffs() - for key in diffs: - self.roster.setItem(key) - self.dispatch('ROSTER_INFO', (key, self.roster.getName(key), - 'both', 'no', self.roster.getGroups(key))) - self.dispatch('NOTIFY', (key, self.roster.getStatus(key), - self.roster.getMessage(key), 'local', 0, None, 0, None)) - #XXX open chat windows don't get refreshed (full name), add that - return self.call_resolve_timeout + def _on_resolve_timeout(self): + if self.connected: + self.connection.resolve_all() + diffs = self.roster.getDiffs() + for key in diffs: + self.roster.setItem(key) + self.dispatch('ROSTER_INFO', (key, self.roster.getName(key), + 'both', 'no', self.roster.getGroups(key))) + self.dispatch('NOTIFY', (key, self.roster.getStatus(key), + self.roster.getMessage(key), 'local', 0, None, 0, None)) + #XXX open chat windows don't get refreshed (full name), add that + return self.call_resolve_timeout - # callbacks called from zeroconf - def _on_new_service(self, jid): - self.roster.setItem(jid) - self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', 'no', - self.roster.getGroups(jid))) - self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid), - self.roster.getMessage(jid), 'local', 0, None, 0, None)) + # callbacks called from zeroconf + def _on_new_service(self, jid): + self.roster.setItem(jid) + self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', 'no', + self.roster.getGroups(jid))) + self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid), + self.roster.getMessage(jid), 'local', 0, None, 0, None)) - def _on_remove_service(self, jid): - self.roster.delItem(jid) - # 'NOTIFY' (account, (jid, status, status message, resource, priority, - # keyID, timestamp, contact_nickname)) - self.dispatch('NOTIFY', (jid, 'offline', '', 'local', 0, None, 0, None)) + def _on_remove_service(self, jid): + self.roster.delItem(jid) + # 'NOTIFY' (account, (jid, status, status message, resource, priority, + # keyID, timestamp, contact_nickname)) + self.dispatch('NOTIFY', (jid, 'offline', '', 'local', 0, None, 0, None)) - def _disconnectedReconnCB(self): - '''Called when we are disconnected. Comes from network manager for example - we don't try to reconnect, network manager will tell us when we can''' - if gajim.account_is_connected(self.name): - # we cannot change our status to offline or connecting - # after we auth to server - self.old_show = STATUS_LIST[self.connected] - self.connected = 0 - self.dispatch('STATUS', 'offline') - # random number to show we wait network manager to send us a reconenct - self.time_to_reconnect = 5 - self.on_purpose = False + def _disconnectedReconnCB(self): + '''Called when we are disconnected. Comes from network manager for example + we don't try to reconnect, network manager will tell us when we can''' + if gajim.account_is_connected(self.name): + # we cannot change our status to offline or connecting + # after we auth to server + self.old_show = STATUS_LIST[self.connected] + self.connected = 0 + self.dispatch('STATUS', 'offline') + # random number to show we wait network manager to send us a reconenct + self.time_to_reconnect = 5 + self.on_purpose = False - def _on_name_conflictCB(self, alt_name): - self.disconnect() - self.dispatch('STATUS', 'offline') - self.dispatch('ZC_NAME_CONFLICT', alt_name) + def _on_name_conflictCB(self, alt_name): + self.disconnect() + self.dispatch('STATUS', 'offline') + self.dispatch('ZC_NAME_CONFLICT', alt_name) - def _on_error(self, message): - self.dispatch('ERROR', (_('Avahi error'), - _('%s\nLink-local messaging might not work properly.') % message)) + def _on_error(self, message): + self.dispatch('ERROR', (_('Avahi error'), + _('%s\nLink-local messaging might not work properly.') % message)) - def connect(self, show='online', msg=''): - self.get_config_values_or_default() - if not self.connection: - self.connection = client_zeroconf.ClientZeroconf(self) - if not zeroconf.test_zeroconf(): - self.dispatch('STATUS', 'offline') - self.status = 'offline' - self.dispatch('CONNECTION_LOST', - (_('Could not connect to "%s"') % self.name, - _('Please check if Avahi or Bonjour is installed.'))) - self.disconnect() - return - result = self.connection.connect(show, msg) - if not result: - self.dispatch('STATUS', 'offline') - self.status = 'offline' - if result is False: - self.dispatch('CONNECTION_LOST', - (_('Could not start local service'), - _('Unable to bind to port %d.' % self.port))) - else: # result is None - self.dispatch('CONNECTION_LOST', - (_('Could not start local service'), - _('Please check if avahi-daemon is running.'))) - self.disconnect() - return - else: - self.connection.announce() - self.roster = self.connection.getRoster() - self.dispatch('ROSTER', self.roster) + def connect(self, show='online', msg=''): + self.get_config_values_or_default() + if not self.connection: + self.connection = client_zeroconf.ClientZeroconf(self) + if not zeroconf.test_zeroconf(): + self.dispatch('STATUS', 'offline') + self.status = 'offline' + self.dispatch('CONNECTION_LOST', + (_('Could not connect to "%s"') % self.name, + _('Please check if Avahi or Bonjour is installed.'))) + self.disconnect() + return + result = self.connection.connect(show, msg) + if not result: + self.dispatch('STATUS', 'offline') + self.status = 'offline' + if result is False: + self.dispatch('CONNECTION_LOST', + (_('Could not start local service'), + _('Unable to bind to port %d.' % self.port))) + else: # result is None + self.dispatch('CONNECTION_LOST', + (_('Could not start local service'), + _('Please check if avahi-daemon is running.'))) + self.disconnect() + return + else: + self.connection.announce() + self.roster = self.connection.getRoster() + self.dispatch('ROSTER', self.roster) - # display contacts already detected and resolved - for jid in self.roster.keys(): - self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', - 'no', self.roster.getGroups(jid))) - self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid), - self.roster.getMessage(jid), 'local', 0, None, 0, None)) + # display contacts already detected and resolved + for jid in self.roster.keys(): + self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', + 'no', self.roster.getGroups(jid))) + self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid), + self.roster.getMessage(jid), 'local', 0, None, 0, None)) - self.connected = STATUS_LIST.index(show) + self.connected = STATUS_LIST.index(show) - # refresh all contacts data every five seconds - self.call_resolve_timeout = True - gobject.timeout_add_seconds(5, self._on_resolve_timeout) - return True + # refresh all contacts data every five seconds + self.call_resolve_timeout = True + gobject.timeout_add_seconds(5, self._on_resolve_timeout) + return True - def disconnect(self, on_purpose=False): - self.connected = 0 - self.time_to_reconnect = None - if self.connection: - self.connection.disconnect() - self.connection = None - # stop calling the timeout - self.call_resolve_timeout = False + def disconnect(self, on_purpose=False): + self.connected = 0 + self.time_to_reconnect = None + if self.connection: + self.connection.disconnect() + self.connection = None + # stop calling the timeout + self.call_resolve_timeout = False - def reannounce(self): - if self.connected: - txt = {} - txt['1st'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'zeroconf_first_name') - txt['last'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'zeroconf_last_name') - txt['jid'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'zeroconf_jabber_id') - txt['email'] = gajim.config.get_per('accounts', - gajim.ZEROCONF_ACC_NAME, 'zeroconf_email') - self.connection.reannounce(txt) + def reannounce(self): + if self.connected: + txt = {} + txt['1st'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'zeroconf_first_name') + txt['last'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'zeroconf_last_name') + txt['jid'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'zeroconf_jabber_id') + txt['email'] = gajim.config.get_per('accounts', + gajim.ZEROCONF_ACC_NAME, 'zeroconf_email') + self.connection.reannounce(txt) - def update_details(self): - if self.connection: - port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'custom_port') - if port != self.port: - self.port = port - last_msg = self.connection.last_msg - self.disconnect() - if not self.connect(self.status, last_msg): - return - if self.status != 'invisible': - self.connection.announce() - else: - self.reannounce() + def update_details(self): + if self.connection: + port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'custom_port') + if port != self.port: + self.port = port + last_msg = self.connection.last_msg + self.disconnect() + if not self.connect(self.status, last_msg): + return + if self.status != 'invisible': + self.connection.announce() + else: + self.reannounce() - def connect_and_init(self, show, msg, sign_msg): - # to check for errors from zeroconf - check = True - if not self.connect(show, msg): - return - if show != 'invisible': - check = self.connection.announce() - else: - self.connected = STATUS_LIST.index(show) - self.dispatch('SIGNED_IN', ()) + def connect_and_init(self, show, msg, sign_msg): + # to check for errors from zeroconf + check = True + if not self.connect(show, msg): + return + if show != 'invisible': + check = self.connection.announce() + else: + self.connected = STATUS_LIST.index(show) + self.dispatch('SIGNED_IN', ()) - # stay offline when zeroconf does something wrong - if check: - self.dispatch('STATUS', show) - else: - # show notification that avahi or system bus is down - self.dispatch('STATUS', 'offline') - self.status = 'offline' - self.dispatch('CONNECTION_LOST', - (_('Could not change status of account "%s"') % self.name, - _('Please check if avahi-daemon is running.'))) + # stay offline when zeroconf does something wrong + if check: + self.dispatch('STATUS', show) + else: + # show notification that avahi or system bus is down + self.dispatch('STATUS', 'offline') + self.status = 'offline' + self.dispatch('CONNECTION_LOST', + (_('Could not change status of account "%s"') % self.name, + _('Please check if avahi-daemon is running.'))) - def _change_to_invisible(self, msg): - if self.connection.remove_announce(): - self.dispatch('STATUS', 'invisible') - else: - # show notification that avahi or system bus is down - self.dispatch('STATUS', 'offline') - self.status = 'offline' - self.dispatch('CONNECTION_LOST', - (_('Could not change status of account "%s"') % self.name, - _('Please check if avahi-daemon is running.'))) + def _change_to_invisible(self, msg): + if self.connection.remove_announce(): + self.dispatch('STATUS', 'invisible') + else: + # show notification that avahi or system bus is down + self.dispatch('STATUS', 'offline') + self.status = 'offline' + self.dispatch('CONNECTION_LOST', + (_('Could not change status of account "%s"') % self.name, + _('Please check if avahi-daemon is running.'))) - def _change_from_invisible(self): - self.connection.announce() + def _change_from_invisible(self): + self.connection.announce() - def _update_status(self, show, msg): - if self.connection.set_show_msg(show, msg): - self.dispatch('STATUS', show) - else: - # show notification that avahi or system bus is down - self.dispatch('STATUS', 'offline') - self.status = 'offline' - self.dispatch('CONNECTION_LOST', - (_('Could not change status of account "%s"') % self.name, - _('Please check if avahi-daemon is running.'))) + def _update_status(self, show, msg): + if self.connection.set_show_msg(show, msg): + self.dispatch('STATUS', show) + else: + # show notification that avahi or system bus is down + self.dispatch('STATUS', 'offline') + self.status = 'offline' + self.dispatch('CONNECTION_LOST', + (_('Could not change status of account "%s"') % self.name, + _('Please check if avahi-daemon is running.'))) - def send_message(self, jid, msg, keyID, type_='chat', subject='', - chatstate=None, msg_id=None, composing_xep=None, resource=None, - user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None, - original_message=None, delayed=None, callback=None, callback_args=[]): + def send_message(self, jid, msg, keyID, type_='chat', subject='', + chatstate=None, msg_id=None, composing_xep=None, resource=None, + user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None, + original_message=None, delayed=None, callback=None, callback_args=[]): - def on_send_ok(msg_id): - self.dispatch('MSGSENT', (jid, msg, keyID)) - if callback: - callback(msg_id, *callback_args) + def on_send_ok(msg_id): + self.dispatch('MSGSENT', (jid, msg, keyID)) + if callback: + callback(msg_id, *callback_args) - self.log_message(jid, msg, forward_from, session, original_message, - subject, type_) + self.log_message(jid, msg, forward_from, session, original_message, + subject, type_) - def on_send_not_ok(reason): - reason += ' ' + _('Your message could not be sent.') - self.dispatch('MSGERROR', [jid, -1, reason, None, None, session]) + def on_send_not_ok(reason): + reason += ' ' + _('Your message could not be sent.') + self.dispatch('MSGERROR', [jid, -1, reason, None, None, session]) - def cb(jid, msg, keyID, forward_from, session, original_message, subject, - type_, msg_iq): - ret = self.connection.send(msg_iq, msg is not None, on_ok=on_send_ok, - on_not_ok=on_send_not_ok) + def cb(jid, msg, keyID, forward_from, session, original_message, subject, + type_, msg_iq): + ret = self.connection.send(msg_iq, msg is not None, on_ok=on_send_ok, + on_not_ok=on_send_not_ok) - if ret == -1: - # Contact Offline - self.dispatch('MSGERROR', [jid, -1, _('Contact is offline. Your ' - 'message could not be sent.'), None, None, session]) + if ret == -1: + # Contact Offline + self.dispatch('MSGERROR', [jid, -1, _('Contact is offline. Your ' + 'message could not be sent.'), None, None, session]) - self._prepare_message(jid, msg, keyID, type_=type_, subject=subject, - chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep, - resource=resource, user_nick=user_nick, xhtml=xhtml, session=session, - forward_from=forward_from, form_node=form_node, - original_message=original_message, delayed=delayed, callback=cb) + self._prepare_message(jid, msg, keyID, type_=type_, subject=subject, + chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep, + resource=resource, user_nick=user_nick, xhtml=xhtml, session=session, + forward_from=forward_from, form_node=form_node, + original_message=original_message, delayed=delayed, callback=cb) - def send_stanza(self, stanza): - # send a stanza untouched - if not self.connection: - return - if not isinstance(stanza, common.xmpp.Node): - stanza = common.xmpp.Protocol(node=stanza) - self.connection.send(stanza) + def send_stanza(self, stanza): + # send a stanza untouched + if not self.connection: + return + if not isinstance(stanza, common.xmpp.Node): + stanza = common.xmpp.Protocol(node=stanza) + self.connection.send(stanza) - def _event_dispatcher(self, realm, event, data): - CommonConnection._event_dispatcher(self, realm, event, data) - if realm == '': - if event == common.xmpp.transports_nb.DATA_ERROR: - thread_id = data[1] - frm = unicode(data[0]) - session = self.get_or_create_session(frm, thread_id) - self.dispatch('MSGERROR', [frm, -1, - _('Connection to host could not be established: Timeout while ' - 'sending data.'), None, None, session]) + def _event_dispatcher(self, realm, event, data): + CommonConnection._event_dispatcher(self, realm, event, data) + if realm == '': + if event == common.xmpp.transports_nb.DATA_ERROR: + thread_id = data[1] + frm = unicode(data[0]) + session = self.get_or_create_session(frm, thread_id) + self.dispatch('MSGERROR', [frm, -1, + _('Connection to host could not be established: Timeout while ' + 'sending data.'), None, None, session]) # END ConnectionZeroconf - -# vim: se ts=3: diff --git a/src/common/zeroconf/roster_zeroconf.py b/src/common/zeroconf/roster_zeroconf.py index 42fe0edd6..cce1cb0e6 100644 --- a/src/common/zeroconf/roster_zeroconf.py +++ b/src/common/zeroconf/roster_zeroconf.py @@ -21,144 +21,142 @@ from common.zeroconf import zeroconf class Roster: - def __init__(self, zeroconf): - self._data = None - self.zeroconf = zeroconf # our zeroconf instance + def __init__(self, zeroconf): + self._data = None + self.zeroconf = zeroconf # our zeroconf instance - def update_roster(self): - for val in self.zeroconf.contacts.values(): - self.setItem(val[zeroconf.C_NAME]) + def update_roster(self): + for val in self.zeroconf.contacts.values(): + self.setItem(val[zeroconf.C_NAME]) - def getRoster(self): - #print 'roster_zeroconf.py: getRoster' - if self._data is None: - self._data = {} - self.update_roster() - return self + def getRoster(self): + #print 'roster_zeroconf.py: getRoster' + if self._data is None: + self._data = {} + self.update_roster() + return self - def getDiffs(self): - ''' update the roster with new data and return dict with - jid -> new status pairs to do notifications and stuff ''' + def getDiffs(self): + ''' update the roster with new data and return dict with + jid -> new status pairs to do notifications and stuff ''' - diffs = {} - old_data = self._data.copy() - self.update_roster() - for key in old_data.keys(): - if key in self._data: - if old_data[key] != self._data[key]: - diffs[key] = self._data[key]['status'] - #print 'roster_zeroconf.py: diffs:' + str(diffs) - return diffs + diffs = {} + old_data = self._data.copy() + self.update_roster() + for key in old_data.keys(): + if key in self._data: + if old_data[key] != self._data[key]: + diffs[key] = self._data[key]['status'] + #print 'roster_zeroconf.py: diffs:' + str(diffs) + return diffs - def setItem(self, jid, name='', groups=''): - #print 'roster_zeroconf.py: setItem %s' % jid - contact = self.zeroconf.get_contact(jid) - if not contact: - return + def setItem(self, jid, name='', groups=''): + #print 'roster_zeroconf.py: setItem %s' % jid + contact = self.zeroconf.get_contact(jid) + if not contact: + return - host, address, port = contact[4:7] - txt = contact[8] + host, address, port = contact[4:7] + txt = contact[8] - self._data[jid]={} - self._data[jid]['ask'] = 'none' - self._data[jid]['subscription'] = 'both' - self._data[jid]['groups'] = [] - self._data[jid]['resources'] = {} - self._data[jid]['address'] = address - self._data[jid]['host'] = host - self._data[jid]['port'] = port - txt_dict = self.zeroconf.txt_array_to_dict(txt) - status = txt_dict.get('status', '') - if not status: - status = 'avail' - nm = txt_dict.get('1st', '') - if 'last' in txt_dict: - if nm != '': - nm += ' ' - nm += txt_dict['last'] - if nm: - self._data[jid]['name'] = nm - else: - self._data[jid]['name'] = jid - if status == 'avail': - status = 'online' - self._data[jid]['txt_dict'] = txt_dict - if 'msg' not in self._data[jid]['txt_dict']: - self._data[jid]['txt_dict']['msg'] = '' - self._data[jid]['status'] = status - self._data[jid]['show'] = status + self._data[jid]={} + self._data[jid]['ask'] = 'none' + self._data[jid]['subscription'] = 'both' + self._data[jid]['groups'] = [] + self._data[jid]['resources'] = {} + self._data[jid]['address'] = address + self._data[jid]['host'] = host + self._data[jid]['port'] = port + txt_dict = self.zeroconf.txt_array_to_dict(txt) + status = txt_dict.get('status', '') + if not status: + status = 'avail' + nm = txt_dict.get('1st', '') + if 'last' in txt_dict: + if nm != '': + nm += ' ' + nm += txt_dict['last'] + if nm: + self._data[jid]['name'] = nm + else: + self._data[jid]['name'] = jid + if status == 'avail': + status = 'online' + self._data[jid]['txt_dict'] = txt_dict + if 'msg' not in self._data[jid]['txt_dict']: + self._data[jid]['txt_dict']['msg'] = '' + self._data[jid]['status'] = status + self._data[jid]['show'] = status - def setItemMulti(self, items): - for i in items: - self.setItem(jid=i['jid'], name=i['name'], groups=i['groups']) + def setItemMulti(self, items): + for i in items: + self.setItem(jid=i['jid'], name=i['name'], groups=i['groups']) - def delItem(self, jid): - #print 'roster_zeroconf.py: delItem %s' % jid - if jid in self._data: - del self._data[jid] + def delItem(self, jid): + #print 'roster_zeroconf.py: delItem %s' % jid + if jid in self._data: + del self._data[jid] - def getItem(self, jid): - #print 'roster_zeroconf.py: getItem: %s' % jid - if jid in self._data: - return self._data[jid] + def getItem(self, jid): + #print 'roster_zeroconf.py: getItem: %s' % jid + if jid in self._data: + return self._data[jid] - def __getitem__(self, jid): - #print 'roster_zeroconf.py: __getitem__' - return self._data[jid] + def __getitem__(self, jid): + #print 'roster_zeroconf.py: __getitem__' + return self._data[jid] - def getItems(self): - #print 'roster_zeroconf.py: getItems' - # Return list of all [bare] JIDs that the roster currently tracks. - return self._data.keys() + def getItems(self): + #print 'roster_zeroconf.py: getItems' + # Return list of all [bare] JIDs that the roster currently tracks. + return self._data.keys() - def keys(self): - #print 'roster_zeroconf.py: keys' - return self._data.keys() + def keys(self): + #print 'roster_zeroconf.py: keys' + return self._data.keys() - def getRaw(self): - #print 'roster_zeroconf.py: getRaw' - return self._data + def getRaw(self): + #print 'roster_zeroconf.py: getRaw' + return self._data - def getResources(self, jid): - #print 'roster_zeroconf.py: getResources(%s)' % jid - return {} + def getResources(self, jid): + #print 'roster_zeroconf.py: getResources(%s)' % jid + return {} - def getGroups(self, jid): - return self._data[jid]['groups'] + def getGroups(self, jid): + return self._data[jid]['groups'] - def getName(self, jid): - if jid in self._data: - return self._data[jid]['name'] + def getName(self, jid): + if jid in self._data: + return self._data[jid]['name'] - def getStatus(self, jid): - if jid in self._data: - return self._data[jid]['status'] + def getStatus(self, jid): + if jid in self._data: + return self._data[jid]['status'] - def getMessage(self, jid): - if jid in self._data: - return self._data[jid]['txt_dict']['msg'] + def getMessage(self, jid): + if jid in self._data: + return self._data[jid]['txt_dict']['msg'] - def getShow(self, jid): - #print 'roster_zeroconf.py: getShow' - return self.getStatus(jid) + def getShow(self, jid): + #print 'roster_zeroconf.py: getShow' + return self.getStatus(jid) - def getPriority(self, jid): - return 5 + def getPriority(self, jid): + return 5 - def getSubscription(self, jid): - #print 'roster_zeroconf.py: getSubscription' - return 'both' + def getSubscription(self, jid): + #print 'roster_zeroconf.py: getSubscription' + return 'both' - def Subscribe(self, jid): - pass + def Subscribe(self, jid): + pass - def Unsubscribe(self, jid): - pass + def Unsubscribe(self, jid): + pass - def Authorize(self, jid): - pass + def Authorize(self, jid): + pass - def Unauthorize(self, jid): - pass - -# vim: se ts=3: + def Unauthorize(self, jid): + pass diff --git a/src/common/zeroconf/zeroconf.py b/src/common/zeroconf/zeroconf.py index a99f0390d..36c2d8ab5 100644 --- a/src/common/zeroconf/zeroconf.py +++ b/src/common/zeroconf/zeroconf.py @@ -21,29 +21,27 @@ C_NAME, C_DOMAIN, C_INTERFACE, C_PROTOCOL, C_HOST, \ C_ADDRESS, C_PORT, C_BARE_NAME, C_TXT = range(9) def test_avahi(): - try: - import avahi - except ImportError: - return False - return True + try: + import avahi + except ImportError: + return False + return True def test_bonjour(): - try: - import pybonjour - except ImportError: - return False - except WindowsError: - return False - return True + try: + import pybonjour + except ImportError: + return False + except WindowsError: + return False + return True def test_zeroconf(): - return test_avahi() or test_bonjour() + return test_avahi() or test_bonjour() if test_avahi(): - from common.zeroconf import zeroconf_avahi - Zeroconf = zeroconf_avahi.Zeroconf + from common.zeroconf import zeroconf_avahi + Zeroconf = zeroconf_avahi.Zeroconf elif test_bonjour(): - from common.zeroconf import zeroconf_bonjour - Zeroconf = zeroconf_bonjour.Zeroconf - -# vim: se ts=3: + from common.zeroconf import zeroconf_bonjour + Zeroconf = zeroconf_bonjour.Zeroconf diff --git a/src/common/zeroconf/zeroconf_avahi.py b/src/common/zeroconf/zeroconf_avahi.py index baa373880..9ea90bd5e 100644 --- a/src/common/zeroconf/zeroconf_avahi.py +++ b/src/common/zeroconf/zeroconf_avahi.py @@ -21,433 +21,431 @@ import logging log = logging.getLogger('gajim.c.z.zeroconf_avahi') try: - import dbus.glib + import dbus.glib except ImportError, e: - pass + pass from common.zeroconf.zeroconf import C_BARE_NAME, C_INTERFACE, C_PROTOCOL, C_DOMAIN class Zeroconf: - def __init__(self, new_serviceCB, remove_serviceCB, name_conflictCB, - disconnected_CB, error_CB, name, host, port): - self.avahi = None - self.domain = None # specific domain to browse - self.stype = '_presence._tcp' - self.port = port # listening port that gets announced - self.username = name - self.host = host - self.txt = {} # service data + def __init__(self, new_serviceCB, remove_serviceCB, name_conflictCB, + disconnected_CB, error_CB, name, host, port): + self.avahi = None + self.domain = None # specific domain to browse + self.stype = '_presence._tcp' + self.port = port # listening port that gets announced + self.username = name + self.host = host + self.txt = {} # service data - #XXX these CBs should be set to None when we destroy the object - # (go offline), because they create a circular reference - self.new_serviceCB = new_serviceCB - self.remove_serviceCB = remove_serviceCB - self.name_conflictCB = name_conflictCB - self.disconnected_CB = disconnected_CB - self.error_CB = error_CB + #XXX these CBs should be set to None when we destroy the object + # (go offline), because they create a circular reference + self.new_serviceCB = new_serviceCB + self.remove_serviceCB = remove_serviceCB + self.name_conflictCB = name_conflictCB + self.disconnected_CB = disconnected_CB + self.error_CB = error_CB - self.service_browser = None - self.domain_browser = None - self.bus = None - self.server = None - self.contacts = {} # all current local contacts with data - self.entrygroup = None - self.connected = False - self.announced = False - self.invalid_self_contact = {} + self.service_browser = None + self.domain_browser = None + self.bus = None + self.server = None + self.contacts = {} # all current local contacts with data + self.entrygroup = None + self.connected = False + self.announced = False + self.invalid_self_contact = {} - ## handlers for dbus callbacks - def entrygroup_commit_error_CB(self, err): - # left blank for possible later usage - pass + ## handlers for dbus callbacks + def entrygroup_commit_error_CB(self, err): + # left blank for possible later usage + pass - def error_callback1(self, err): - log.debug('Error while resolving: ' + str(err)) + def error_callback1(self, err): + log.debug('Error while resolving: ' + str(err)) - def error_callback(self, err): - log.debug(str(err)) - # timeouts are non-critical - if str(err) != 'Timeout reached': - self.disconnect() - self.disconnected_CB() + def error_callback(self, err): + log.debug(str(err)) + # timeouts are non-critical + if str(err) != 'Timeout reached': + self.disconnect() + self.disconnected_CB() - def new_service_callback(self, interface, protocol, name, stype, domain, - flags): - log.debug('Found service %s in domain %s on %i.%i.' % (name, domain, - interface, protocol)) - if not self.connected: - return + def new_service_callback(self, interface, protocol, name, stype, domain, + flags): + log.debug('Found service %s in domain %s on %i.%i.' % (name, domain, + interface, protocol)) + if not self.connected: + return - # synchronous resolving - self.server.ResolveService( int(interface), int(protocol), name, stype, - domain, self.avahi.PROTO_UNSPEC, dbus.UInt32(0), - reply_handler=self.service_resolved_callback, - error_handler=self.error_callback1) + # synchronous resolving + self.server.ResolveService( int(interface), int(protocol), name, stype, + domain, self.avahi.PROTO_UNSPEC, dbus.UInt32(0), + reply_handler=self.service_resolved_callback, + error_handler=self.error_callback1) - def remove_service_callback(self, interface, protocol, name, stype, domain, - flags): - log.debug('Service %s in domain %s on %i.%i disappeared.' % (name, - domain, interface, protocol)) - if not self.connected: - return - if name != self.name: - for key in self.contacts.keys(): - if self.contacts[key][C_BARE_NAME] == name: - del self.contacts[key] - self.remove_serviceCB(key) - return + def remove_service_callback(self, interface, protocol, name, stype, domain, + flags): + log.debug('Service %s in domain %s on %i.%i disappeared.' % (name, + domain, interface, protocol)) + if not self.connected: + return + if name != self.name: + for key in self.contacts.keys(): + if self.contacts[key][C_BARE_NAME] == name: + del self.contacts[key] + self.remove_serviceCB(key) + return - def new_service_type(self, interface, protocol, stype, domain, flags): - # Are we already browsing this domain for this type? - if self.service_browser: - return + def new_service_type(self, interface, protocol, stype, domain, flags): + # Are we already browsing this domain for this type? + if self.service_browser: + return - object_path = self.server.ServiceBrowserNew(interface, protocol, \ - stype, domain, dbus.UInt32(0)) + object_path = self.server.ServiceBrowserNew(interface, protocol, \ + stype, domain, dbus.UInt32(0)) - self.service_browser = dbus.Interface(self.bus.get_object( - self.avahi.DBUS_NAME, object_path), - self.avahi.DBUS_INTERFACE_SERVICE_BROWSER) - self.service_browser.connect_to_signal('ItemNew', - self.new_service_callback) - self.service_browser.connect_to_signal('ItemRemove', - self.remove_service_callback) - self.service_browser.connect_to_signal('Failure', self.error_callback) + self.service_browser = dbus.Interface(self.bus.get_object( + self.avahi.DBUS_NAME, object_path), + self.avahi.DBUS_INTERFACE_SERVICE_BROWSER) + self.service_browser.connect_to_signal('ItemNew', + self.new_service_callback) + self.service_browser.connect_to_signal('ItemRemove', + self.remove_service_callback) + self.service_browser.connect_to_signal('Failure', self.error_callback) - def new_domain_callback(self,interface, protocol, domain, flags): - if domain != 'local': - self.browse_domain(interface, protocol, domain) + def new_domain_callback(self, interface, protocol, domain, flags): + if domain != 'local': + self.browse_domain(interface, protocol, domain) - def txt_array_to_dict(self, txt_array): - txt_dict = {} - for els in txt_array: - key, val = '', None - for c in els: - c = chr(c) - if val is None: - if c == '=': - val = '' - else: - key += c - else: - val += c - if val is None: # missing '=' - val = '' - txt_dict[key] = val.decode('utf-8', 'ignore') - return txt_dict + def txt_array_to_dict(self, txt_array): + txt_dict = {} + for els in txt_array: + key, val = '', None + for c in els: + c = chr(c) + if val is None: + if c == '=': + val = '' + else: + key += c + else: + val += c + if val is None: # missing '=' + val = '' + txt_dict[key] = val.decode('utf-8', 'ignore') + return txt_dict - def service_resolved_callback(self, interface, protocol, name, stype, domain, - host, aprotocol, address, port, txt, flags): - log.debug('Service data for service %s in domain %s on %i.%i:' - % (name, domain, interface, protocol)) - log.debug('Host %s (%s), port %i, TXT data: %s' % (host, address, - port, self.txt_array_to_dict(txt))) - if not self.connected: - return - bare_name = name - if name.find('@') == -1: - name = name + '@' + name + def service_resolved_callback(self, interface, protocol, name, stype, domain, + host, aprotocol, address, port, txt, flags): + log.debug('Service data for service %s in domain %s on %i.%i:' + % (name, domain, interface, protocol)) + log.debug('Host %s (%s), port %i, TXT data: %s' % (host, address, + port, self.txt_array_to_dict(txt))) + if not self.connected: + return + bare_name = name + if name.find('@') == -1: + name = name + '@' + name - # we don't want to see ourselves in the list - if name != self.name: - self.contacts[name] = (name, domain, interface, protocol, host, - address, port, bare_name, txt) - self.new_serviceCB(name) - else: - # remember data - # In case this is not our own record but of another - # gajim instance on the same machine, - # it will be used when we get a new name. - self.invalid_self_contact[name] = (name, domain, interface, protocol, - host, address, port, bare_name, txt) + # we don't want to see ourselves in the list + if name != self.name: + self.contacts[name] = (name, domain, interface, protocol, host, + address, port, bare_name, txt) + self.new_serviceCB(name) + else: + # remember data + # In case this is not our own record but of another + # gajim instance on the same machine, + # it will be used when we get a new name. + self.invalid_self_contact[name] = (name, domain, interface, protocol, + host, address, port, bare_name, txt) - # different handler when resolving all contacts - def service_resolved_all_callback(self, interface, protocol, name, stype, - domain, host, aprotocol, address, port, txt, flags): - if not self.connected: - return - bare_name = name - if name.find('@') == -1: - name = name + '@' + name - self.contacts[name] = (name, domain, interface, protocol, host, address, - port, bare_name, txt) + # different handler when resolving all contacts + def service_resolved_all_callback(self, interface, protocol, name, stype, + domain, host, aprotocol, address, port, txt, flags): + if not self.connected: + return + bare_name = name + if name.find('@') == -1: + name = name + '@' + name + self.contacts[name] = (name, domain, interface, protocol, host, address, + port, bare_name, txt) - def service_added_callback(self): - log.debug('Service successfully added') + def service_added_callback(self): + log.debug('Service successfully added') - def service_committed_callback(self): - log.debug('Service successfully committed') + def service_committed_callback(self): + log.debug('Service successfully committed') - def service_updated_callback(self): - log.debug('Service successfully updated') + def service_updated_callback(self): + log.debug('Service successfully updated') - def service_add_fail_callback(self, err): - log.debug('Error while adding service. %s' % str(err)) - if 'Local name collision' in str(err): - alternative_name = self.server.GetAlternativeServiceName(self.username) - self.name_conflictCB(alternative_name) - return - self.error_CB(_('Error while adding service. %s') % str(err)) - self.disconnect() + def service_add_fail_callback(self, err): + log.debug('Error while adding service. %s' % str(err)) + if 'Local name collision' in str(err): + alternative_name = self.server.GetAlternativeServiceName(self.username) + self.name_conflictCB(alternative_name) + return + self.error_CB(_('Error while adding service. %s') % str(err)) + self.disconnect() - def server_state_changed_callback(self, state, error): - log.debug('server state changed to %s' % state) - if state == self.avahi.SERVER_RUNNING: - self.create_service() - elif state in (self.avahi.SERVER_COLLISION, - self.avahi.SERVER_REGISTERING): - self.disconnect() - self.entrygroup.Reset() - elif state == self.avahi.CLIENT_FAILURE: - # does it ever go here? - log.debug('CLIENT FAILURE') + def server_state_changed_callback(self, state, error): + log.debug('server state changed to %s' % state) + if state == self.avahi.SERVER_RUNNING: + self.create_service() + elif state in (self.avahi.SERVER_COLLISION, + self.avahi.SERVER_REGISTERING): + self.disconnect() + self.entrygroup.Reset() + elif state == self.avahi.CLIENT_FAILURE: + # does it ever go here? + log.debug('CLIENT FAILURE') - def entrygroup_state_changed_callback(self, state, error): - # the name is already present, so recreate - if state == self.avahi.ENTRY_GROUP_COLLISION: - log.debug('zeroconf.py: local name collision') - self.service_add_fail_callback('Local name collision') - elif state == self.avahi.ENTRY_GROUP_FAILURE: - self.disconnect() - self.entrygroup.Reset() - log.debug('zeroconf.py: ENTRY_GROUP_FAILURE reached(that' - ' should not happen)') + def entrygroup_state_changed_callback(self, state, error): + # the name is already present, so recreate + if state == self.avahi.ENTRY_GROUP_COLLISION: + log.debug('zeroconf.py: local name collision') + self.service_add_fail_callback('Local name collision') + elif state == self.avahi.ENTRY_GROUP_FAILURE: + self.disconnect() + self.entrygroup.Reset() + log.debug('zeroconf.py: ENTRY_GROUP_FAILURE reached(that' + ' should not happen)') - # make zeroconf-valid names - def replace_show(self, show): - if show in ['chat', 'online', '']: - return 'avail' - elif show == 'xa': - return 'away' - return show + # make zeroconf-valid names + def replace_show(self, show): + if show in ['chat', 'online', '']: + return 'avail' + elif show == 'xa': + return 'away' + return show - def avahi_txt(self): - utf8_dict = {} - for key in self.txt: - val = self.txt[key] - if isinstance(val, unicode): - utf8_dict[key] = val.encode('utf-8') - else: - utf8_dict[key] = val - return self.avahi.dict_to_txt_array(utf8_dict) + def avahi_txt(self): + utf8_dict = {} + for key in self.txt: + val = self.txt[key] + if isinstance(val, unicode): + utf8_dict[key] = val.encode('utf-8') + else: + utf8_dict[key] = val + return self.avahi.dict_to_txt_array(utf8_dict) - def create_service(self): - try: - if not self.entrygroup: - # create an EntryGroup for publishing - self.entrygroup = dbus.Interface(self.bus.get_object( - self.avahi.DBUS_NAME, self.server.EntryGroupNew()), - self.avahi.DBUS_INTERFACE_ENTRY_GROUP) - self.entrygroup.connect_to_signal('StateChanged', - self.entrygroup_state_changed_callback) + def create_service(self): + try: + if not self.entrygroup: + # create an EntryGroup for publishing + self.entrygroup = dbus.Interface(self.bus.get_object( + self.avahi.DBUS_NAME, self.server.EntryGroupNew()), + self.avahi.DBUS_INTERFACE_ENTRY_GROUP) + self.entrygroup.connect_to_signal('StateChanged', + self.entrygroup_state_changed_callback) - txt = {} + txt = {} - # remove empty keys - for key,val in self.txt.iteritems(): - if val: - txt[key] = val + # remove empty keys + for key, val in self.txt.iteritems(): + if val: + txt[key] = val - txt['port.p2pj'] = self.port - txt['version'] = 1 - txt['txtvers'] = 1 + txt['port.p2pj'] = self.port + txt['version'] = 1 + txt['txtvers'] = 1 - # replace gajim's show messages with compatible ones - if 'status' in self.txt: - txt['status'] = self.replace_show(self.txt['status']) - else: - txt['status'] = 'avail' + # replace gajim's show messages with compatible ones + if 'status' in self.txt: + txt['status'] = self.replace_show(self.txt['status']) + else: + txt['status'] = 'avail' - self.txt = txt - log.debug('Publishing service %s of type %s' % (self.name, - self.stype)) - self.entrygroup.AddService(self.avahi.IF_UNSPEC, - self.avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype, '', - '', dbus.UInt16(self.port), self.avahi_txt(), - reply_handler=self.service_added_callback, - error_handler=self.service_add_fail_callback) + self.txt = txt + log.debug('Publishing service %s of type %s' % (self.name, + self.stype)) + self.entrygroup.AddService(self.avahi.IF_UNSPEC, + self.avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype, '', + '', dbus.UInt16(self.port), self.avahi_txt(), + reply_handler=self.service_added_callback, + error_handler=self.service_add_fail_callback) - self.entrygroup.Commit(reply_handler=self.service_committed_callback, - error_handler=self.entrygroup_commit_error_CB) + self.entrygroup.Commit(reply_handler=self.service_committed_callback, + error_handler=self.entrygroup_commit_error_CB) - return True + return True - except dbus.DBusException, e: - log.debug(str(e)) - return False + except dbus.DBusException, e: + log.debug(str(e)) + return False - def announce(self): - if not self.connected: - return False + def announce(self): + if not self.connected: + return False - state = self.server.GetState() - if state == self.avahi.SERVER_RUNNING: - self.create_service() - self.announced = True - return True + state = self.server.GetState() + if state == self.avahi.SERVER_RUNNING: + self.create_service() + self.announced = True + return True - def remove_announce(self): - if self.announced == False: - return False - try: - if self.entrygroup.GetState() != self.avahi.ENTRY_GROUP_FAILURE: - self.entrygroup.Reset() - self.entrygroup.Free() - # .Free() has mem leaks - self.entrygroup._obj._bus = None - self.entrygroup._obj = None - self.entrygroup = None - self.announced = False + def remove_announce(self): + if self.announced == False: + return False + try: + if self.entrygroup.GetState() != self.avahi.ENTRY_GROUP_FAILURE: + self.entrygroup.Reset() + self.entrygroup.Free() + # .Free() has mem leaks + self.entrygroup._obj._bus = None + self.entrygroup._obj = None + self.entrygroup = None + self.announced = False - return True - else: - return False - except dbus.DBusException: - log.debug("Can't remove service. That should not happen") + return True + else: + return False + except dbus.DBusException: + log.debug("Can't remove service. That should not happen") - def browse_domain(self, interface, protocol, domain): - self.new_service_type(interface, protocol, self.stype, domain, '') + def browse_domain(self, interface, protocol, domain): + self.new_service_type(interface, protocol, self.stype, domain, '') - def avahi_dbus_connect_cb(self, a, connect, disconnect): - if connect != "": - log.debug('Lost connection to avahi-daemon') - self.disconnect() - if self.disconnected_CB: - self.disconnected_CB() - else: - log.debug('We are connected to avahi-daemon') + def avahi_dbus_connect_cb(self, a, connect, disconnect): + if connect != "": + log.debug('Lost connection to avahi-daemon') + self.disconnect() + if self.disconnected_CB: + self.disconnected_CB() + else: + log.debug('We are connected to avahi-daemon') - # connect to dbus - def connect_dbus(self): - try: - import dbus - except ImportError: - log.debug('Error: python-dbus needs to be installed. No ' - 'zeroconf support.') - return False - if self.bus: - return True - try: - self.bus = dbus.SystemBus() - self.bus.add_signal_receiver(self.avahi_dbus_connect_cb, - 'NameOwnerChanged', 'org.freedesktop.DBus', - arg0='org.freedesktop.Avahi') - except Exception, e: - # System bus is not present - self.bus = None - log.debug(str(e)) - return False - else: - return True + # connect to dbus + def connect_dbus(self): + try: + import dbus + except ImportError: + log.debug('Error: python-dbus needs to be installed. No ' + 'zeroconf support.') + return False + if self.bus: + return True + try: + self.bus = dbus.SystemBus() + self.bus.add_signal_receiver(self.avahi_dbus_connect_cb, + 'NameOwnerChanged', 'org.freedesktop.DBus', + arg0='org.freedesktop.Avahi') + except Exception, e: + # System bus is not present + self.bus = None + log.debug(str(e)) + return False + else: + return True - # connect to avahi - def connect_avahi(self): - if not self.connect_dbus(): - return False - try: - import avahi - self.avahi = avahi - except ImportError: - log.debug('Error: python-avahi needs to be installed. No ' - 'zeroconf support.') - return False + # connect to avahi + def connect_avahi(self): + if not self.connect_dbus(): + return False + try: + import avahi + self.avahi = avahi + except ImportError: + log.debug('Error: python-avahi needs to be installed. No ' + 'zeroconf support.') + return False - if self.server: - return True - try: - self.server = dbus.Interface(self.bus.get_object(self.avahi.DBUS_NAME, - self.avahi.DBUS_PATH_SERVER), self.avahi.DBUS_INTERFACE_SERVER) - self.server.connect_to_signal('StateChanged', - self.server_state_changed_callback) - except Exception, e: - # Avahi service is not present - self.server = None - log.debug(str(e)) - return False - else: - return True + if self.server: + return True + try: + self.server = dbus.Interface(self.bus.get_object(self.avahi.DBUS_NAME, + self.avahi.DBUS_PATH_SERVER), self.avahi.DBUS_INTERFACE_SERVER) + self.server.connect_to_signal('StateChanged', + self.server_state_changed_callback) + except Exception, e: + # Avahi service is not present + self.server = None + log.debug(str(e)) + return False + else: + return True - def connect(self): - self.name = self.username + '@' + self.host # service name - if not self.connect_avahi(): - return False + def connect(self): + self.name = self.username + '@' + self.host # service name + if not self.connect_avahi(): + return False - self.connected = True - # start browsing - if self.domain is None: - # Explicitly browse .local - self.browse_domain(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, - 'local') + self.connected = True + # start browsing + if self.domain is None: + # Explicitly browse .local + self.browse_domain(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, + 'local') - # Browse for other browsable domains - self.domain_browser = dbus.Interface(self.bus.get_object( - self.avahi.DBUS_NAME, self.server.DomainBrowserNew( - self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, '', - self.avahi.DOMAIN_BROWSER_BROWSE, dbus.UInt32(0))), - self.avahi.DBUS_INTERFACE_DOMAIN_BROWSER) - self.domain_browser.connect_to_signal('ItemNew', - self.new_domain_callback) - self.domain_browser.connect_to_signal('Failure', self.error_callback) - else: - self.browse_domain(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, - self.domain) + # Browse for other browsable domains + self.domain_browser = dbus.Interface(self.bus.get_object( + self.avahi.DBUS_NAME, self.server.DomainBrowserNew( + self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, '', + self.avahi.DOMAIN_BROWSER_BROWSE, dbus.UInt32(0))), + self.avahi.DBUS_INTERFACE_DOMAIN_BROWSER) + self.domain_browser.connect_to_signal('ItemNew', + self.new_domain_callback) + self.domain_browser.connect_to_signal('Failure', self.error_callback) + else: + self.browse_domain(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, + self.domain) - return True + return True - def disconnect(self): - if self.connected: - self.connected = False - if self.service_browser: - self.service_browser.Free() - self.service_browser._obj._bus = None - self.service_browser._obj = None - if self.domain_browser: - self.domain_browser.Free() - self.domain_browser._obj._bus = None - self.domain_browser._obj = None - self.remove_announce() - self.server._obj._bus = None - self.server._obj = None - self.server = None - self.service_browser = None - self.domain_browser = None + def disconnect(self): + if self.connected: + self.connected = False + if self.service_browser: + self.service_browser.Free() + self.service_browser._obj._bus = None + self.service_browser._obj = None + if self.domain_browser: + self.domain_browser.Free() + self.domain_browser._obj._bus = None + self.domain_browser._obj = None + self.remove_announce() + self.server._obj._bus = None + self.server._obj = None + self.server = None + self.service_browser = None + self.domain_browser = None - # refresh txt data of all contacts manually (no callback available) - def resolve_all(self): - if not self.connected: - return - for val in self.contacts.values(): - self.server.ResolveService(int(val[C_INTERFACE]), int(val[C_PROTOCOL]), - val[C_BARE_NAME], self.stype, val[C_DOMAIN], - self.avahi.PROTO_UNSPEC, dbus.UInt32(0), - reply_handler=self.service_resolved_all_callback, - error_handler=self.error_callback) + # refresh txt data of all contacts manually (no callback available) + def resolve_all(self): + if not self.connected: + return + for val in self.contacts.values(): + self.server.ResolveService(int(val[C_INTERFACE]), int(val[C_PROTOCOL]), + val[C_BARE_NAME], self.stype, val[C_DOMAIN], + self.avahi.PROTO_UNSPEC, dbus.UInt32(0), + reply_handler=self.service_resolved_all_callback, + error_handler=self.error_callback) - def get_contacts(self): - return self.contacts + def get_contacts(self): + return self.contacts - def get_contact(self, jid): - if not jid in self.contacts: - return None - return self.contacts[jid] + def get_contact(self, jid): + if not jid in self.contacts: + return None + return self.contacts[jid] - def update_txt(self, show = None): - if show: - self.txt['status'] = self.replace_show(show) + def update_txt(self, show = None): + if show: + self.txt['status'] = self.replace_show(show) - txt = self.avahi_txt() - if self.connected and self.entrygroup: - self.entrygroup.UpdateServiceTxt(self.avahi.IF_UNSPEC, - self.avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype, '', - txt, reply_handler=self.service_updated_callback, - error_handler=self.error_callback) - return True - else: - return False + txt = self.avahi_txt() + if self.connected and self.entrygroup: + self.entrygroup.UpdateServiceTxt(self.avahi.IF_UNSPEC, + self.avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype, '', + txt, reply_handler=self.service_updated_callback, + error_handler=self.error_callback) + return True + else: + return False # END Zeroconf - -# vim: se ts=3: diff --git a/src/common/zeroconf/zeroconf_bonjour.py b/src/common/zeroconf/zeroconf_bonjour.py index 10568a7a7..cd33f2060 100644 --- a/src/common/zeroconf/zeroconf_bonjour.py +++ b/src/common/zeroconf/zeroconf_bonjour.py @@ -23,313 +23,311 @@ import re from common.zeroconf.zeroconf import C_BARE_NAME, C_DOMAIN try: - import pybonjour + import pybonjour except ImportError, e: - pass + pass resolve_timeout = 1 class Zeroconf: - def __init__(self, new_serviceCB, remove_serviceCB, name_conflictCB, - disconnected_CB, error_CB, name, host, port): - self.domain = None # specific domain to browse - self.stype = '_presence._tcp' - self.port = port # listening port that gets announced - self.username = name - self.host = host - self.txt = pybonjour.TXTRecord() # service data + def __init__(self, new_serviceCB, remove_serviceCB, name_conflictCB, + disconnected_CB, error_CB, name, host, port): + self.domain = None # specific domain to browse + self.stype = '_presence._tcp' + self.port = port # listening port that gets announced + self.username = name + self.host = host + self.txt = pybonjour.TXTRecord() # service data - # XXX these CBs should be set to None when we destroy the object - # (go offline), because they create a circular reference - self.new_serviceCB = new_serviceCB - self.remove_serviceCB = remove_serviceCB - self.name_conflictCB = name_conflictCB - self.disconnected_CB = disconnected_CB - self.error_CB = error_CB + # XXX these CBs should be set to None when we destroy the object + # (go offline), because they create a circular reference + self.new_serviceCB = new_serviceCB + self.remove_serviceCB = remove_serviceCB + self.name_conflictCB = name_conflictCB + self.disconnected_CB = disconnected_CB + self.error_CB = error_CB - self.contacts = {} # all current local contacts with data - self.connected = False - self.announced = False - self.invalid_self_contact = {} - self.resolved = [] + self.contacts = {} # all current local contacts with data + self.connected = False + self.announced = False + self.invalid_self_contact = {} + self.resolved = [] - def browse_callback(self, sdRef, flags, interfaceIndex, errorCode, serviceName, regtype, replyDomain): - gajim.log.debug('Found service %s in domain %s on %i(type: %s).' % (serviceName, replyDomain, interfaceIndex, regtype)) - if not self.connected: - return - if errorCode != pybonjour.kDNSServiceErr_NoError: - return - if not (flags & pybonjour.kDNSServiceFlagsAdd): - self.remove_service_callback(serviceName) - return + def browse_callback(self, sdRef, flags, interfaceIndex, errorCode, serviceName, regtype, replyDomain): + gajim.log.debug('Found service %s in domain %s on %i(type: %s).' % (serviceName, replyDomain, interfaceIndex, regtype)) + if not self.connected: + return + if errorCode != pybonjour.kDNSServiceErr_NoError: + return + if not (flags & pybonjour.kDNSServiceFlagsAdd): + self.remove_service_callback(serviceName) + return - # asynchronous resolving - resolve_sdRef = pybonjour.DNSServiceResolve(0, interfaceIndex, serviceName, regtype, replyDomain, self.service_resolved_callback) + # asynchronous resolving + resolve_sdRef = pybonjour.DNSServiceResolve(0, interfaceIndex, serviceName, regtype, replyDomain, self.service_resolved_callback) - try: - while not self.resolved: - ready = select.select([resolve_sdRef], [], [], resolve_timeout) - if resolve_sdRef not in ready[0]: - gajim.log.debug('Resolve timed out') - break - pybonjour.DNSServiceProcessResult(resolve_sdRef) - else: - self.resolved.pop() - finally: - resolve_sdRef.close() + try: + while not self.resolved: + ready = select.select([resolve_sdRef], [], [], resolve_timeout) + if resolve_sdRef not in ready[0]: + gajim.log.debug('Resolve timed out') + break + pybonjour.DNSServiceProcessResult(resolve_sdRef) + else: + self.resolved.pop() + finally: + resolve_sdRef.close() - def remove_service_callback(self, name): - gajim.log.debug('Service %s disappeared.' % name) - if not self.connected: - return - if name != self.name: - for key in self.contacts.keys(): - if self.contacts[key][C_BARE_NAME] == name: - del self.contacts[key] - self.remove_serviceCB(key) - return + def remove_service_callback(self, name): + gajim.log.debug('Service %s disappeared.' % name) + if not self.connected: + return + if name != self.name: + for key in self.contacts.keys(): + if self.contacts[key][C_BARE_NAME] == name: + del self.contacts[key] + self.remove_serviceCB(key) + return - def new_domain_callback(self,interface, protocol, domain, flags): - if domain != "local": - self.browse_domain(interface, protocol, domain) + def new_domain_callback(self, interface, protocol, domain, flags): + if domain != "local": + self.browse_domain(interface, protocol, domain) - # takes a TXTRecord instance - def txt_array_to_dict(self, txt): - items = pybonjour.TXTRecord.parse(txt)._items - return dict((v[0], v[1]) for v in items.values()) + # takes a TXTRecord instance + def txt_array_to_dict(self, txt): + items = pybonjour.TXTRecord.parse(txt)._items + return dict((v[0], v[1]) for v in items.values()) - def service_resolved_callback(self, sdRef, flags, interfaceIndex, errorCode, fullname, - hosttarget, port, txtRecord): + def service_resolved_callback(self, sdRef, flags, interfaceIndex, errorCode, fullname, + hosttarget, port, txtRecord): - # TODO: do proper decoding... - escaping= { - r'\.': '.', - r'\032': ' ', - r'\064': '@', - } + # TODO: do proper decoding... + escaping= { + r'\.': '.', + r'\032': ' ', + r'\064': '@', + } - # Split on '.' but do not split on '\.' - result = re.split('(? 0: - def login(account, show_before, status_before): - """ - Login with previous status - """ - # first make sure connection is really closed, - # 0.5 may not be enough - gajim.connections[account].disconnect(True) - gajim.interface.roster.send_status(account, show_before, - status_before) - - def relog(account): - self.dialog.destroy() - show_before = gajim.SHOW_LIST[gajim.connections[account].connected] - status_before = gajim.connections[account].status - gajim.interface.roster.send_status(account, 'offline', - _('Be right back.')) - gobject.timeout_add(500, login, account, show_before, status_before) - - def on_yes(checked, account): - relog(account) - def on_no(account): - if self.resend_presence: - self.resend(account) - if self.current_account in gajim.connections: - self.dialog = dialogs.YesNoDialog(_('Relogin now?'), - _('If you want all the changes to apply instantly, ' - 'you must relogin.'), on_response_yes=(on_yes, - self.current_account), on_response_no=(on_no, - self.current_account)) - elif self.resend_presence: - self.resend(self.current_account) - - self.need_relogin = False - self.resend_presence = False - - def on_accounts_treeview_cursor_changed(self, widget): - """ - Activate modify buttons when a row is selected, update accounts info - """ - sel = self.accounts_treeview.get_selection() - (model, iter_) = sel.get_selected() - if iter_: - account = model[iter_][0].decode('utf-8') - else: - account = None - if self.current_account and self.current_account == account: - # We're comming back to our current account, no need to update widgets - return - # Save config for previous account if needed cause focus_out event is - # called after the changed event - if self.current_account and self.window.get_focus(): - focused_widget = self.window.get_focus() - focused_widget_name = focused_widget.get_name() - if focused_widget_name in ('jid_entry1', 'resource_entry1', - 'custom_port_entry'): - if focused_widget_name == 'jid_entry1': - func = self.on_jid_entry1_focus_out_event - elif focused_widget_name == 'resource_entry1': - func = self.on_resource_entry1_focus_out_event - elif focused_widget_name == 'custom_port_entry': - func = self.on_custom_port_entry_focus_out_event - if func(focused_widget, None): - # Error detected in entry, don't change account, re-put cursor on - # previous row - self.select_account(self.current_account) - return True - self.window.set_focus(widget) - - self.check_resend_relog() - - self.remove_button.set_sensitive(True) - self.rename_button.set_sensitive(True) - if iter_: - self.current_account = account - if account == gajim.ZEROCONF_ACC_NAME: - self.remove_button.set_sensitive(False) - self.init_account() - self.update_proxy_list() - - def update_proxy_list(self): - if self.current_account: - our_proxy = gajim.config.get_per('accounts', self.current_account, - 'proxy') - else: - our_proxy = '' - - if not our_proxy: - our_proxy = _('None') - proxy_combobox = self.xml.get_widget('proxies_combobox1') - model = gtk.ListStore(str) - proxy_combobox.set_model(model) - l = gajim.config.get_per('proxies') - l.insert(0, _('None')) - for i in xrange(len(l)): - model.append([l[i]]) - if our_proxy == l[i]: - proxy_combobox.set_active(i) - - def init_account(self): - if not self.current_account: - self.notebook.set_current_page(0) - return - if gajim.config.get_per('accounts', self.current_account, 'is_zeroconf'): - self.ignore_events = True - self.init_zeroconf_account() - self.ignore_events = False - self.notebook.set_current_page(2) - return - self.ignore_events = True - self.init_normal_account() - self.ignore_events = False - self.notebook.set_current_page(1) - - def init_zeroconf_account(self): - active = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'active') - self.xml.get_widget('enable_zeroconf_checkbutton2').set_active(active) - if not gajim.HAVE_ZEROCONF: - self.xml.get_widget('enable_zeroconf_checkbutton2').set_sensitive( - False) - self.xml.get_widget('zeroconf_notebook').set_sensitive(active) - # General tab - st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'autoconnect') - self.xml.get_widget('autoconnect_checkbutton2').set_active(st) - - list_no_log_for = gajim.config.get_per('accounts', - gajim.ZEROCONF_ACC_NAME, 'no_log_for').split() - if gajim.ZEROCONF_ACC_NAME in list_no_log_for: - self.xml.get_widget('log_history_checkbutton2').set_active(0) - else: - self.xml.get_widget('log_history_checkbutton2').set_active(1) - - st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'sync_with_global_status') - self.xml.get_widget('sync_with_global_status_checkbutton2').set_active(st) - - st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'use_custom_host') - self.xml.get_widget('custom_port_checkbutton2').set_active(st) - self.xml.get_widget('custom_port_entry2').set_sensitive(st) - - st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'custom_port') - if not st: - gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'custom_port', '5298') - st = '5298' - self.xml.get_widget('custom_port_entry2').set_text(str(st)) - - # Personal tab - gpg_key_label = self.xml.get_widget('gpg_key_label2') - if gajim.ZEROCONF_ACC_NAME in gajim.connections and \ - gajim.connections[gajim.ZEROCONF_ACC_NAME].gpg: - self.xml.get_widget('gpg_choose_button2').set_sensitive(True) - self.init_account_gpg() - else: - gpg_key_label.set_text(_('OpenPGP is not usable on this computer')) - self.xml.get_widget('gpg_choose_button2').set_sensitive(False) - - for opt in ('first_name', 'last_name', 'jabber_id', 'email'): - st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'zeroconf_' + opt) - self.xml.get_widget(opt + '_entry2').set_text(st) - - def init_account_gpg(self): - account = self.current_account - keyid = gajim.config.get_per('accounts', account, 'keyid') - keyname = gajim.config.get_per('accounts', account, 'keyname') - use_gpg_agent = gajim.config.get('use_gpg_agent') - - if account == gajim.ZEROCONF_ACC_NAME: - widget_name_add = '2' - else: - widget_name_add = '1' - - gpg_key_label = self.xml.get_widget('gpg_key_label' + widget_name_add) - gpg_name_label = self.xml.get_widget('gpg_name_label' + widget_name_add) - use_gpg_agent_checkbutton = self.xml.get_widget( - 'use_gpg_agent_checkbutton' + widget_name_add) - - if not keyid: - use_gpg_agent_checkbutton.set_sensitive(False) - gpg_key_label.set_text(_('No key selected')) - gpg_name_label.set_text('') - return - - gpg_key_label.set_text(keyid) - gpg_name_label.set_text(keyname) - use_gpg_agent_checkbutton.set_sensitive(True) - use_gpg_agent_checkbutton.set_active(use_gpg_agent) - - def draw_normal_jid(self): - account = self.current_account - self.ignore_events = True - active = gajim.config.get_per('accounts', account, 'active') - self.xml.get_widget('enable_checkbutton1').set_active(active) - self.xml.get_widget('normal_notebook1').set_sensitive(active) - if gajim.config.get_per('accounts', account, 'anonymous_auth'): - self.xml.get_widget('anonymous_checkbutton1').set_active(True) - self.xml.get_widget('jid_label1').set_text(_('Server:')) - save_password = self.xml.get_widget('save_password_checkbutton1') - save_password.set_active(False) - save_password.set_sensitive(False) - password_entry = self.xml.get_widget('password_entry1') - password_entry.set_text('') - password_entry.set_sensitive(False) - jid = gajim.config.get_per('accounts', account, 'hostname') - else: - self.xml.get_widget('anonymous_checkbutton1').set_active(False) - self.xml.get_widget('jid_label1').set_text(_('Jabber ID:')) - savepass = gajim.config.get_per('accounts', account, 'savepass') - save_password = self.xml.get_widget('save_password_checkbutton1') - save_password.set_sensitive(True) - save_password.set_active(savepass) - password_entry = self.xml.get_widget('password_entry1') - if savepass: - passstr = passwords.get_password(account) or '' - password_entry.set_sensitive(True) - else: - passstr = '' - password_entry.set_sensitive(False) - password_entry.set_text(passstr) - - jid = gajim.config.get_per('accounts', account, 'name') \ - + '@' + gajim.config.get_per('accounts', account, 'hostname') - self.xml.get_widget('jid_entry1').set_text(jid) - self.ignore_events = False - - def init_normal_account(self): - account = self.current_account - # Account tab - self.draw_normal_jid() - self.xml.get_widget('resource_entry1').set_text(gajim.config.get_per( - 'accounts', account, 'resource')) - self.xml.get_widget('adjust_priority_with_status_checkbutton1').\ - set_active(gajim.config.get_per('accounts', account, - 'adjust_priority_with_status')) - spinbutton = self.xml.get_widget('priority_spinbutton1') - if gajim.config.get('enable_negative_priority'): - spinbutton.set_range(-128, 127) - else: - spinbutton.set_range(0, 127) - spinbutton.set_value(gajim.config.get_per('accounts', account, - 'priority')) - - # Connection tab - use_env_http_proxy = gajim.config.get_per('accounts', account, - 'use_env_http_proxy') - self.xml.get_widget('use_env_http_proxy_checkbutton1').set_active( - use_env_http_proxy) - self.xml.get_widget('proxy_hbox1').set_sensitive(not use_env_http_proxy) - - warn_when_insecure_ssl = gajim.config.get_per('accounts', account, - 'warn_when_insecure_ssl_connection') - self.xml.get_widget('warn_when_insecure_connection_checkbutton1').\ - set_active(warn_when_insecure_ssl) - - self.xml.get_widget('send_keepalive_checkbutton1').set_active( - gajim.config.get_per('accounts', account, 'keep_alives_enabled')) - - use_custom_host = gajim.config.get_per('accounts', account, - 'use_custom_host') - self.xml.get_widget('custom_host_port_checkbutton1').set_active( - use_custom_host) - custom_host = gajim.config.get_per('accounts', account, 'custom_host') - if not custom_host: - custom_host = gajim.config.get_per('accounts', account, 'hostname') - gajim.config.set_per('accounts', account, 'custom_host', custom_host) - self.xml.get_widget('custom_host_entry1').set_text(custom_host) - custom_port = gajim.config.get_per('accounts', account, 'custom_port') - if not custom_port: - custom_port = 5222 - gajim.config.set_per('accounts', account, 'custom_port', custom_port) - self.xml.get_widget('custom_port_entry1').set_text(unicode(custom_port)) - - # Personal tab - gpg_key_label = self.xml.get_widget('gpg_key_label1') - if gajim.HAVE_GPG: - self.xml.get_widget('gpg_choose_button1').set_sensitive(True) - self.init_account_gpg() - else: - gpg_key_label.set_text(_('OpenPGP is not usable on this computer')) - self.xml.get_widget('gpg_choose_button1').set_sensitive(False) - - # General tab - self.xml.get_widget('autoconnect_checkbutton1').set_active(gajim.config.\ - get_per('accounts', account, 'autoconnect')) - self.xml.get_widget('autoreconnect_checkbutton1').set_active(gajim. - config.get_per('accounts', account, 'autoreconnect')) - - list_no_log_for = gajim.config.get_per('accounts', account, - 'no_log_for').split() - if account in list_no_log_for: - self.xml.get_widget('log_history_checkbutton1').set_active(False) - else: - self.xml.get_widget('log_history_checkbutton1').set_active(True) - - self.xml.get_widget('sync_with_global_status_checkbutton1').set_active( - gajim.config.get_per('accounts', account, 'sync_with_global_status')) - self.xml.get_widget('use_ft_proxies_checkbutton1').set_active( - gajim.config.get_per('accounts', account, 'use_ft_proxies')) - - def on_add_button_clicked(self, widget): - """ - When add button is clicked: open an account information window - """ - if 'account_creation_wizard' in gajim.interface.instances: - gajim.interface.instances['account_creation_wizard'].window.present() - else: - gajim.interface.instances['account_creation_wizard'] = \ - AccountCreationWizardWindow() - - def on_remove_button_clicked(self, widget): - """ - When delete button is clicked: Remove an account from the listStore and - from the config file - """ - if not self.current_account: - return - account = self.current_account - if len(gajim.events.get_events(account)): - dialogs.ErrorDialog(_('Unread events'), - _('Read all pending events before removing this account.')) - return - - if gajim.config.get_per('accounts', account, 'is_zeroconf'): - # Should never happen as button is insensitive - return - - win_opened = False - if gajim.interface.msg_win_mgr.get_controls(acct = account): - win_opened = True - else: - for key in gajim.interface.instances[account]: - if gajim.interface.instances[account][key] and key != \ - 'remove_account': - win_opened = True - break - # Detect if we have opened windows for this account - def remove(account): - if 'remove_account' in gajim.interface.instances[account]: - gajim.interface.instances[account]['remove_account'].window.\ - present() - else: - gajim.interface.instances[account]['remove_account'] = \ - RemoveAccountWindow(account) - if win_opened: - dialogs.ConfirmationDialog( - _('You have opened chat in account %s') % account, - _('All chat and groupchat windows will be closed. Do you want to ' - 'continue?'), - on_response_ok = (remove, account)) - else: - remove(account) - - def on_rename_button_clicked(self, widget): - if not self.current_account: - return - active = gajim.config.get_per('accounts', self.current_account, 'active') - if active and gajim.connections[self.current_account].connected != 0: - dialogs.ErrorDialog( - _('You are currently connected to the server'), - _('To change the account name, you must be disconnected.')) - return - if len(gajim.events.get_events(self.current_account)): - dialogs.ErrorDialog(_('Unread events'), - _('To change the account name, you must read all pending ' - 'events.')) - return - # Get the new name - def on_renamed(new_name, old_name): - if new_name in gajim.connections: - dialogs.ErrorDialog(_('Account Name Already Used'), - _('This name is already used by another of your accounts. ' - 'Please choose another name.')) - return - if (new_name == ''): - dialogs.ErrorDialog(_('Invalid account name'), - _('Account name cannot be empty.')) - return - if new_name.find(' ') != -1: - dialogs.ErrorDialog(_('Invalid account name'), - _('Account name cannot contain spaces.')) - return - if active: - # update variables - gajim.interface.instances[new_name] = gajim.interface.instances[ - old_name] - gajim.interface.minimized_controls[new_name] = \ - gajim.interface.minimized_controls[old_name] - gajim.nicks[new_name] = gajim.nicks[old_name] - gajim.block_signed_in_notifications[new_name] = \ - gajim.block_signed_in_notifications[old_name] - gajim.groups[new_name] = gajim.groups[old_name] - gajim.gc_connected[new_name] = gajim.gc_connected[old_name] - gajim.automatic_rooms[new_name] = gajim.automatic_rooms[old_name] - gajim.newly_added[new_name] = gajim.newly_added[old_name] - gajim.to_be_removed[new_name] = gajim.to_be_removed[old_name] - gajim.sleeper_state[new_name] = gajim.sleeper_state[old_name] - gajim.encrypted_chats[new_name] = gajim.encrypted_chats[old_name] - gajim.last_message_time[new_name] = \ - gajim.last_message_time[old_name] - gajim.status_before_autoaway[new_name] = \ - gajim.status_before_autoaway[old_name] - gajim.transport_avatar[new_name] = gajim.transport_avatar[old_name] - gajim.gajim_optional_features[new_name] = \ - gajim.gajim_optional_features[old_name] - gajim.caps_hash[new_name] = gajim.caps_hash[old_name] - - gajim.contacts.change_account_name(old_name, new_name) - gajim.events.change_account_name(old_name, new_name) - - # change account variable for chat / gc controls - gajim.interface.msg_win_mgr.change_account_name(old_name, new_name) - # upgrade account variable in opened windows - for kind in ('infos', 'disco', 'gc_config', 'search', - 'online_dialog'): - for j in gajim.interface.instances[new_name][kind]: - gajim.interface.instances[new_name][kind][j].account = \ - new_name - - # ServiceCache object keep old property account - if hasattr(gajim.connections[old_name], 'services_cache'): - gajim.connections[old_name].services_cache.account = new_name - del gajim.interface.instances[old_name] - del gajim.interface.minimized_controls[old_name] - del gajim.nicks[old_name] - del gajim.block_signed_in_notifications[old_name] - del gajim.groups[old_name] - del gajim.gc_connected[old_name] - del gajim.automatic_rooms[old_name] - del gajim.newly_added[old_name] - del gajim.to_be_removed[old_name] - del gajim.sleeper_state[old_name] - del gajim.encrypted_chats[old_name] - del gajim.last_message_time[old_name] - del gajim.status_before_autoaway[old_name] - del gajim.transport_avatar[old_name] - del gajim.gajim_optional_features[old_name] - del gajim.caps_hash[old_name] - gajim.connections[old_name].name = new_name - gajim.connections[new_name] = gajim.connections[old_name] - del gajim.connections[old_name] - gajim.config.add_per('accounts', new_name) - old_config = gajim.config.get_per('accounts', old_name) - for opt in old_config: - gajim.config.set_per('accounts', new_name, opt, old_config[opt][1]) - gajim.config.del_per('accounts', old_name) - if self.current_account == old_name: - self.current_account = new_name - if old_name == gajim.ZEROCONF_ACC_NAME: - gajim.ZEROCONF_ACC_NAME = new_name - # refresh roster - gajim.interface.roster.setup_and_draw_roster() - self.init_accounts() - self.select_account(new_name) - - title = _('Rename Account') - message = _('Enter a new name for account %s') % self.current_account - old_text = self.current_account - dialogs.InputDialog(title, message, old_text, is_modal=False, - ok_handler=(on_renamed, self.current_account)) - - def option_changed(self, option, value): - return gajim.config.get_per('accounts', self.current_account, option) != \ - value - - def on_jid_entry1_focus_out_event(self, widget, event): - if self.ignore_events: - return - jid = widget.get_text() - # check if jid is conform to RFC and stringprep it - try: - jid = helpers.parse_jid(jid) - except helpers.InvalidFormat, s: - if not widget.is_focus(): - pritext = _('Invalid Jabber ID') - dialogs.ErrorDialog(pritext, str(s)) - gobject.idle_add(lambda: widget.grab_focus()) - return True - - jid_splited = jid.split('@', 1) - if len(jid_splited) != 2 and not gajim.config.get_per('accounts', - self.current_account, 'anonymous_auth'): - if not widget.is_focus(): - pritext = _('Invalid Jabber ID') - sectext = _('A Jabber ID must be in the form "user@servername".') - dialogs.ErrorDialog(pritext, sectext) - gobject.idle_add(lambda: widget.grab_focus()) - return True - - - if gajim.config.get_per('accounts', self.current_account, - 'anonymous_auth'): - gajim.config.set_per('accounts', self.current_account, 'hostname', - jid_splited[0]) - if self.option_changed('hostname', jid_splited[0]): - self.need_relogin = True - else: - if self.option_changed('name', jid_splited[0]) or \ - self.option_changed('hostname', jid_splited[1]): - self.need_relogin = True - - gajim.config.set_per('accounts', self.current_account, 'name', - jid_splited[0]) - gajim.config.set_per('accounts', self.current_account, 'hostname', - jid_splited[1]) - - def on_anonymous_checkbutton1_toggled(self, widget): - if self.ignore_events: - return - active = widget.get_active() - gajim.config.set_per('accounts', self.current_account, 'anonymous_auth', - active) - self.draw_normal_jid() - - def on_password_entry1_changed(self, widget): - if self.ignore_events: - return - passwords.save_password(self.current_account, widget.get_text().decode( - 'utf-8')) - - def on_save_password_checkbutton1_toggled(self, widget): - if self.ignore_events: - return - active = widget.get_active() - password_entry = self.xml.get_widget('password_entry1') - password_entry.set_sensitive(active) - gajim.config.set_per('accounts', self.current_account, 'savepass', active) - if active: - password = password_entry.get_text() - passwords.save_password(self.current_account, password) - else: - passwords.save_password(self.current_account, '') - - def on_resource_entry1_focus_out_event(self, widget, event): - if self.ignore_events: - return - resource = self.xml.get_widget('resource_entry1').get_text().decode( - 'utf-8') - try: - resource = helpers.parse_resource(resource) - except helpers.InvalidFormat, s: - if not widget.is_focus(): - pritext = _('Invalid Jabber ID') - dialogs.ErrorDialog(pritext, str(s)) - gobject.idle_add(lambda: widget.grab_focus()) - return True - - if self.option_changed('resource', resource): - self.need_relogin = True - - gajim.config.set_per('accounts', self.current_account, 'resource', - resource) - - def on_adjust_priority_with_status_checkbutton1_toggled(self, widget): - self.xml.get_widget('priority_spinbutton1').set_sensitive( - not widget.get_active()) - self.on_checkbutton_toggled(widget, 'adjust_priority_with_status', - account = self.current_account) - - def on_priority_spinbutton1_value_changed(self, widget): - prio = widget.get_value_as_int() - - if self.option_changed('priority', prio): - self.resend_presence = True - - gajim.config.set_per('accounts', self.current_account, 'priority', prio) - - def on_synchronise_contacts_button1_clicked(self, widget): - try: - dialogs.SynchroniseSelectAccountDialog(self.current_account) - except GajimGeneralException: - # If we showed ErrorDialog, there will not be dialog instance - return - - def on_change_password_button1_clicked(self, widget): - def on_changed(new_password): - if new_password is not None: - gajim.connections[self.current_account].change_password( - new_password) - if self.xml.get_widget('save_password_checkbutton1').get_active(): - self.xml.get_widget('password_entry1').set_text(new_password) - - try: - dialogs.ChangePasswordDialog(self.current_account, on_changed) - except GajimGeneralException: - # if we showed ErrorDialog, there will not be dialog instance - return - - def on_autoconnect_checkbutton_toggled(self, widget): - if self.ignore_events: - return - self.on_checkbutton_toggled(widget, 'autoconnect', - account=self.current_account) - - def on_autoreconnect_checkbutton_toggled(self, widget): - if self.ignore_events: - return - self.on_checkbutton_toggled(widget, 'autoreconnect', - account=self.current_account) - - def on_log_history_checkbutton_toggled(self, widget): - if self.ignore_events: - return - list_no_log_for = gajim.config.get_per('accounts', self.current_account, - 'no_log_for').split() - if self.current_account in list_no_log_for: - list_no_log_for.remove(self.current_account) - - if not widget.get_active(): - list_no_log_for.append(self.current_account) - gajim.config.set_per('accounts', self.current_account, 'no_log_for', - ' '.join(list_no_log_for)) - - def on_sync_with_global_status_checkbutton_toggled(self, widget): - if self.ignore_events: - return - self.on_checkbutton_toggled(widget, 'sync_with_global_status', - account=self.current_account) - gajim.interface.roster.update_status_combobox() - - def on_use_ft_proxies_checkbutton1_toggled(self, widget): - if self.ignore_events: - return - self.on_checkbutton_toggled(widget, 'use_ft_proxies', - account=self.current_account) - - def on_use_env_http_proxy_checkbutton1_toggled(self, widget): - if self.ignore_events: - return - self.on_checkbutton_toggled(widget, 'use_env_http_proxy', - account=self.current_account) - hbox = self.xml.get_widget('proxy_hbox1') - hbox.set_sensitive(not widget.get_active()) - - def on_proxies_combobox1_changed(self, widget): - active = widget.get_active() - proxy = widget.get_model()[active][0].decode('utf-8') - if proxy == _('None'): - proxy = '' - - if self.option_changed('proxy', proxy): - self.need_relogin = True - - gajim.config.set_per('accounts', self.current_account, 'proxy', proxy) - - def on_manage_proxies_button1_clicked(self, widget): - if 'manage_proxies' in gajim.interface.instances: - gajim.interface.instances['manage_proxies'].window.present() - else: - gajim.interface.instances['manage_proxies'] = ManageProxiesWindow() - - def on_warn_when_insecure_connection_checkbutton1_toggled(self, widget): - if self.ignore_events: - return - - self.on_checkbutton_toggled(widget, 'warn_when_insecure_ssl_connection', - account=self.current_account) - - def on_send_keepalive_checkbutton1_toggled(self, widget): - if self.ignore_events: - return - self.on_checkbutton_toggled(widget, 'keep_alives_enabled', - account=self.current_account) - gajim.config.set_per('accounts', self.current_account, - 'ping_alives_enabled', widget.get_active()) - - def on_custom_host_port_checkbutton1_toggled(self, widget): - if self.option_changed('use_custom_host', widget.get_active()): - self.need_relogin = True - - self.on_checkbutton_toggled(widget, 'use_custom_host', - account=self.current_account) - active = widget.get_active() - self.xml.get_widget('custom_host_port_hbox1').set_sensitive(active) - - def on_custom_host_entry1_changed(self, widget): - if self.ignore_events: - return - host = widget.get_text().decode('utf-8') - if self.option_changed('custom_host', host): - self.need_relogin = True - gajim.config.set_per('accounts', self.current_account, 'custom_host', - host) - - def on_custom_port_entry_focus_out_event(self, widget, event): - if self.ignore_events: - return - custom_port = widget.get_text() - try: - custom_port = int(custom_port) - except Exception: - if not widget.is_focus(): - dialogs.ErrorDialog(_('Invalid entry'), - _('Custom port must be a port number.')) - gobject.idle_add(lambda: widget.grab_focus()) - return True - if self.option_changed('custom_port', custom_port): - self.need_relogin = True - gajim.config.set_per('accounts', self.current_account, 'custom_port', - custom_port) - - def on_gpg_choose_button_clicked(self, widget, data = None): - if self.current_account in gajim.connections and \ - gajim.connections[self.current_account].gpg: - secret_keys = gajim.connections[self.current_account].\ - ask_gpg_secrete_keys() - - # self.current_account is None and/or gajim.connections is {} - else: - if gajim.HAVE_GPG: - secret_keys = GnuPG.GnuPG().get_secret_keys() - else: - secret_keys = [] - if not secret_keys: - dialogs.ErrorDialog(_('Failed to get secret keys'), - _('There is no OpenPGP secret key available.')) - secret_keys[_('None')] = _('None') - - def on_key_selected(keyID): - if keyID is None: - return - if self.current_account == gajim.ZEROCONF_ACC_NAME: - wiget_name_ext = '2' - else: - wiget_name_ext = '1' - gpg_key_label = self.xml.get_widget('gpg_key_label' + wiget_name_ext) - gpg_name_label = self.xml.get_widget('gpg_name_label' + wiget_name_ext) - use_gpg_agent_checkbutton = self.xml.get_widget( - 'use_gpg_agent_checkbutton' + wiget_name_ext) - if keyID[0] == _('None'): - gpg_key_label.set_text(_('No key selected')) - gpg_name_label.set_text('') - use_gpg_agent_checkbutton.set_sensitive(False) - if self.option_changed('keyid', ''): - self.need_relogin = True - gajim.config.set_per('accounts', self.current_account, 'keyname', - '') - gajim.config.set_per('accounts', self.current_account, 'keyid', '') - else: - gpg_key_label.set_text(keyID[0]) - gpg_name_label.set_text(keyID[1]) - use_gpg_agent_checkbutton.set_sensitive(True) - if self.option_changed('keyid', keyID[0]): - self.need_relogin = True - gajim.config.set_per('accounts', self.current_account, 'keyname', - keyID[1]) - gajim.config.set_per('accounts', self.current_account, 'keyid', - keyID[0]) - - dialogs.ChooseGPGKeyDialog(_('OpenPGP Key Selection'), - _('Choose your OpenPGP key'), secret_keys, on_key_selected) - - def on_use_gpg_agent_checkbutton_toggled(self, widget): - self.on_checkbutton_toggled(widget, 'use_gpg_agent') - - def on_edit_details_button1_clicked(self, widget): - if self.current_account not in gajim.interface.instances: - dialogs.ErrorDialog(_('No such account available'), - _('You must create your account before editing your personal ' - 'information.')) - return - - # show error dialog if account is newly created (not in gajim.connections) - if self.current_account not in gajim.connections or \ - gajim.connections[self.current_account].connected < 2: - dialogs.ErrorDialog(_('You are not connected to the server'), - _('Without a connection, you can not edit your personal information.')) - return - - if not gajim.connections[self.current_account].vcard_supported: - dialogs.ErrorDialog(_("Your server doesn't support Vcard"), - _("Your server can't save your personal information.")) - return - - gajim.interface.edit_own_details(self.current_account) - - def on_checkbutton_toggled(self, widget, config_name, - change_sensitivity_widgets = None, account = None): - if account: - gajim.config.set_per('accounts', account, config_name, - widget.get_active()) - else: - gajim.config.set(config_name, widget.get_active()) - if change_sensitivity_widgets: - for w in change_sensitivity_widgets: - w.set_sensitive(widget.get_active()) - gajim.interface.save_config() - - def on_merge_checkbutton_toggled(self, widget): - self.on_checkbutton_toggled(widget, 'mergeaccounts') - if len(gajim.connections) >= 2: # Do not merge accounts if only one active - gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') - else: - gajim.interface.roster.regroup = False - gajim.interface.roster.setup_and_draw_roster() - - def _disable_account(self, account): - gajim.interface.roster.close_all(account) - if account == gajim.ZEROCONF_ACC_NAME: - gajim.connections[account].disable_account() - del gajim.connections[account] - gajim.interface.save_config() - del gajim.interface.instances[account] - del gajim.interface.minimized_controls[account] - del gajim.nicks[account] - del gajim.block_signed_in_notifications[account] - del gajim.groups[account] - gajim.contacts.remove_account(account) - del gajim.gc_connected[account] - del gajim.automatic_rooms[account] - del gajim.to_be_removed[account] - del gajim.newly_added[account] - del gajim.sleeper_state[account] - del gajim.encrypted_chats[account] - del gajim.last_message_time[account] - del gajim.status_before_autoaway[account] - del gajim.transport_avatar[account] - del gajim.gajim_optional_features[account] - del gajim.caps_hash[account] - if len(gajim.connections) >= 2: - # Do not merge accounts if only one exists - gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') - else: - gajim.interface.roster.regroup = False - gajim.interface.roster.setup_and_draw_roster() - gajim.interface.roster.set_actions_menu_needs_rebuild() - - def _enable_account(self, account): - if account == gajim.ZEROCONF_ACC_NAME: - gajim.connections[account] = connection_zeroconf.ConnectionZeroconf( - account) - if gajim.connections[account].gpg: - self.xml.get_widget('gpg_choose_button2').set_sensitive(True) - else: - gajim.connections[account] = common.connection.Connection(account) - if gajim.connections[account].gpg: - self.xml.get_widget('gpg_choose_button1').set_sensitive(True) - self.init_account_gpg() - # update variables - gajim.interface.instances[account] = {'infos': {}, - 'disco': {}, 'gc_config': {}, 'search': {}, 'online_dialog': {}} - gajim.interface.minimized_controls[account] = {} - gajim.connections[account].connected = 0 - gajim.groups[account] = {} - gajim.contacts.add_account(account) - gajim.gc_connected[account] = {} - gajim.automatic_rooms[account] = {} - gajim.newly_added[account] = [] - gajim.to_be_removed[account] = [] - if account == gajim.ZEROCONF_ACC_NAME: - gajim.nicks[account] = gajim.ZEROCONF_ACC_NAME - else: - gajim.nicks[account] = gajim.config.get_per('accounts', account, - 'name') - gajim.block_signed_in_notifications[account] = True - gajim.sleeper_state[account] = 'off' - gajim.encrypted_chats[account] = [] - gajim.last_message_time[account] = {} - gajim.status_before_autoaway[account] = '' - gajim.transport_avatar[account] = {} - gajim.gajim_optional_features[account] = [] - gajim.caps_hash[account] = '' - # refresh roster - if len(gajim.connections) >= 2: - # Do not merge accounts if only one exists - gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') - else: - gajim.interface.roster.regroup = False - gajim.interface.roster.setup_and_draw_roster() - gajim.interface.roster.set_actions_menu_needs_rebuild() - gajim.interface.save_config() - - def on_enable_zeroconf_checkbutton2_toggled(self, widget): - # don't do anything if there is an account with the local name but is a - # normal account - if self.ignore_events: - return - if gajim.account_is_connected(self.current_account): - self.ignore_events = True - self.xml.get_widget('enable_zeroconf_checkbutton2').set_active(True) - self.ignore_events = False - dialogs.ErrorDialog( - _('You are currently connected to the server'), - _('To disable the account, you must be disconnected.')) - return - if gajim.ZEROCONF_ACC_NAME in gajim.connections and not \ - gajim.connections[gajim.ZEROCONF_ACC_NAME].is_zeroconf: - gajim.connections[gajim.ZEROCONF_ACC_NAME].dispatch('ERROR', - (_('Account Local already exists.'), - _('Please rename or remove it before enabling link-local messaging' - '.'))) - return - - if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \ - and not widget.get_active(): - self.xml.get_widget('zeroconf_notebook').set_sensitive(False) - # disable - self._disable_account(gajim.ZEROCONF_ACC_NAME) - - elif not gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'active') and widget.get_active(): - self.xml.get_widget('zeroconf_notebook').set_sensitive(True) - # enable (will create new account if not present) - self._enable_account(gajim.ZEROCONF_ACC_NAME) - - self.on_checkbutton_toggled(widget, 'active', - account=gajim.ZEROCONF_ACC_NAME) - - def on_enable_checkbutton1_toggled(self, widget): - if self.ignore_events: - return - if gajim.account_is_connected(self.current_account): - self.ignore_events = True - self.xml.get_widget('enable_checkbutton1').set_active(True) - self.ignore_events = False - dialogs.ErrorDialog( - _('You are currently connected to the server'), - _('To disable the account, you must be disconnected.')) - return - # add/remove account in roster and all variables - if widget.get_active(): - # enable - self._enable_account(self.current_account) - else: - # disable - self._disable_account(self.current_account) - self.on_checkbutton_toggled(widget, 'active', - account=self.current_account, change_sensitivity_widgets=[ - self.xml.get_widget('normal_notebook1')]) - - def on_custom_port_checkbutton2_toggled(self, widget): - self.xml.get_widget('custom_port_entry2').set_sensitive( - widget.get_active()) - self.on_checkbutton_toggled(widget, 'use_custom_host', - account = self.current_account) - if not widget.get_active(): - self.xml.get_widget('custom_port_entry2').set_text('5298') - - def on_first_name_entry2_changed(self, widget): - if self.ignore_events: - return - name = widget.get_text().decode('utf-8') - if self.option_changed('zeroconf_first_name', name): - self.need_relogin = True - gajim.config.set_per('accounts', self.current_account, - 'zeroconf_first_name', name) - - def on_last_name_entry2_changed(self, widget): - if self.ignore_events: - return - name = widget.get_text().decode('utf-8') - if self.option_changed('zeroconf_last_name', name): - self.need_relogin = True - gajim.config.set_per('accounts', self.current_account, - 'zeroconf_last_name', name) - - def on_jabber_id_entry2_changed(self, widget): - if self.ignore_events: - return - id_ = widget.get_text().decode('utf-8') - if self.option_changed('zeroconf_jabber_id', id_): - self.need_relogin = True - gajim.config.set_per('accounts', self.current_account, - 'zeroconf_jabber_id', id_) - - def on_email_entry2_changed(self, widget): - if self.ignore_events: - return - email = widget.get_text().decode('utf-8') - if self.option_changed('zeroconf_email', email): - self.need_relogin = True - gajim.config.set_per('accounts', self.current_account, - 'zeroconf_email', email) + """ + Class for accounts window: list of accounts + """ + + def on_accounts_window_destroy(self, widget): + del gajim.interface.instances['accounts'] + + def on_close_button_clicked(self, widget): + self.check_resend_relog() + self.window.destroy() + + def __init__(self): + self.xml = gtkgui_helpers.get_glade('accounts_window.glade') + self.window = self.xml.get_widget('accounts_window') + self.window.set_transient_for(gajim.interface.roster.window) + self.accounts_treeview = self.xml.get_widget('accounts_treeview') + self.remove_button = self.xml.get_widget('remove_button') + self.rename_button = self.xml.get_widget('rename_button') + path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps', + 'kbd_input.png') + img = self.xml.get_widget('rename_image') + img.set_from_file(path_to_kbd_input_img) + self.notebook = self.xml.get_widget('notebook') + # Name + model = gtk.ListStore(str) + self.accounts_treeview.set_model(model) + # column + renderer = gtk.CellRendererText() + self.accounts_treeview.insert_column_with_attributes(-1, + _('Name'), renderer, text = 0) + + self.current_account = None + # When we fill info, we don't want to handle the changed signals + self.ignore_events = False + self.need_relogin = False + self.resend_presence = False + + self.update_proxy_list() + self.xml.signal_autoconnect(self) + self.init_accounts() + self.window.show_all() + + # Merge accounts + st = gajim.config.get('mergeaccounts') + checkbutton = self.xml.get_widget('merge_checkbutton') + checkbutton.set_active(st) + # prevent roster redraws by connecting the signal after button state is set + checkbutton.connect('toggled', self.on_merge_checkbutton_toggled) + + self.avahi_available = True + try: + import avahi + except ImportError: + self.avahi_available = False + + def on_accounts_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: + self.check_resend_relog() + self.window.destroy() + + def select_account(self, account): + model = self.accounts_treeview.get_model() + iter_ = model.get_iter_root() + while iter_: + acct = model[iter_][0].decode('utf-8') + if account == acct: + self.accounts_treeview.set_cursor(model.get_path(iter_)) + return + iter_ = model.iter_next(iter_) + + def init_accounts(self): + """ + Initialize listStore with existing accounts + """ + self.remove_button.set_sensitive(False) + self.rename_button.set_sensitive(False) + self.current_account = None + model = self.accounts_treeview.get_model() + model.clear() + for account in gajim.config.get_per('accounts'): + iter_ = model.append() + model.set(iter_, 0, account) + + def resend(self, account): + if not account in gajim.connections: + return + show = gajim.SHOW_LIST[gajim.connections[account].connected] + status = gajim.connections[account].status + gajim.connections[account].change_status(show, status) + + def check_resend_relog(self): + if self.need_relogin and self.current_account == gajim.ZEROCONF_ACC_NAME: + if gajim.ZEROCONF_ACC_NAME in gajim.connections: + gajim.connections[gajim.ZEROCONF_ACC_NAME].update_details() + return + + elif self.need_relogin and self.current_account and \ + gajim.connections[self.current_account].connected > 0: + def login(account, show_before, status_before): + """ + Login with previous status + """ + # first make sure connection is really closed, + # 0.5 may not be enough + gajim.connections[account].disconnect(True) + gajim.interface.roster.send_status(account, show_before, + status_before) + + def relog(account): + self.dialog.destroy() + show_before = gajim.SHOW_LIST[gajim.connections[account].connected] + status_before = gajim.connections[account].status + gajim.interface.roster.send_status(account, 'offline', + _('Be right back.')) + gobject.timeout_add(500, login, account, show_before, status_before) + + def on_yes(checked, account): + relog(account) + def on_no(account): + if self.resend_presence: + self.resend(account) + if self.current_account in gajim.connections: + self.dialog = dialogs.YesNoDialog(_('Relogin now?'), + _('If you want all the changes to apply instantly, ' + 'you must relogin.'), on_response_yes=(on_yes, + self.current_account), on_response_no=(on_no, + self.current_account)) + elif self.resend_presence: + self.resend(self.current_account) + + self.need_relogin = False + self.resend_presence = False + + def on_accounts_treeview_cursor_changed(self, widget): + """ + Activate modify buttons when a row is selected, update accounts info + """ + sel = self.accounts_treeview.get_selection() + (model, iter_) = sel.get_selected() + if iter_: + account = model[iter_][0].decode('utf-8') + else: + account = None + if self.current_account and self.current_account == account: + # We're comming back to our current account, no need to update widgets + return + # Save config for previous account if needed cause focus_out event is + # called after the changed event + if self.current_account and self.window.get_focus(): + focused_widget = self.window.get_focus() + focused_widget_name = focused_widget.get_name() + if focused_widget_name in ('jid_entry1', 'resource_entry1', + 'custom_port_entry'): + if focused_widget_name == 'jid_entry1': + func = self.on_jid_entry1_focus_out_event + elif focused_widget_name == 'resource_entry1': + func = self.on_resource_entry1_focus_out_event + elif focused_widget_name == 'custom_port_entry': + func = self.on_custom_port_entry_focus_out_event + if func(focused_widget, None): + # Error detected in entry, don't change account, re-put cursor on + # previous row + self.select_account(self.current_account) + return True + self.window.set_focus(widget) + + self.check_resend_relog() + + self.remove_button.set_sensitive(True) + self.rename_button.set_sensitive(True) + if iter_: + self.current_account = account + if account == gajim.ZEROCONF_ACC_NAME: + self.remove_button.set_sensitive(False) + self.init_account() + self.update_proxy_list() + + def update_proxy_list(self): + if self.current_account: + our_proxy = gajim.config.get_per('accounts', self.current_account, + 'proxy') + else: + our_proxy = '' + + if not our_proxy: + our_proxy = _('None') + proxy_combobox = self.xml.get_widget('proxies_combobox1') + model = gtk.ListStore(str) + proxy_combobox.set_model(model) + l = gajim.config.get_per('proxies') + l.insert(0, _('None')) + for i in xrange(len(l)): + model.append([l[i]]) + if our_proxy == l[i]: + proxy_combobox.set_active(i) + + def init_account(self): + if not self.current_account: + self.notebook.set_current_page(0) + return + if gajim.config.get_per('accounts', self.current_account, 'is_zeroconf'): + self.ignore_events = True + self.init_zeroconf_account() + self.ignore_events = False + self.notebook.set_current_page(2) + return + self.ignore_events = True + self.init_normal_account() + self.ignore_events = False + self.notebook.set_current_page(1) + + def init_zeroconf_account(self): + active = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'active') + self.xml.get_widget('enable_zeroconf_checkbutton2').set_active(active) + if not gajim.HAVE_ZEROCONF: + self.xml.get_widget('enable_zeroconf_checkbutton2').set_sensitive( + False) + self.xml.get_widget('zeroconf_notebook').set_sensitive(active) + # General tab + st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'autoconnect') + self.xml.get_widget('autoconnect_checkbutton2').set_active(st) + + list_no_log_for = gajim.config.get_per('accounts', + gajim.ZEROCONF_ACC_NAME, 'no_log_for').split() + if gajim.ZEROCONF_ACC_NAME in list_no_log_for: + self.xml.get_widget('log_history_checkbutton2').set_active(0) + else: + self.xml.get_widget('log_history_checkbutton2').set_active(1) + + st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'sync_with_global_status') + self.xml.get_widget('sync_with_global_status_checkbutton2').set_active(st) + + st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'use_custom_host') + self.xml.get_widget('custom_port_checkbutton2').set_active(st) + self.xml.get_widget('custom_port_entry2').set_sensitive(st) + + st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'custom_port') + if not st: + gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'custom_port', '5298') + st = '5298' + self.xml.get_widget('custom_port_entry2').set_text(str(st)) + + # Personal tab + gpg_key_label = self.xml.get_widget('gpg_key_label2') + if gajim.ZEROCONF_ACC_NAME in gajim.connections and \ + gajim.connections[gajim.ZEROCONF_ACC_NAME].gpg: + self.xml.get_widget('gpg_choose_button2').set_sensitive(True) + self.init_account_gpg() + else: + gpg_key_label.set_text(_('OpenPGP is not usable on this computer')) + self.xml.get_widget('gpg_choose_button2').set_sensitive(False) + + for opt in ('first_name', 'last_name', 'jabber_id', 'email'): + st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'zeroconf_' + opt) + self.xml.get_widget(opt + '_entry2').set_text(st) + + def init_account_gpg(self): + account = self.current_account + keyid = gajim.config.get_per('accounts', account, 'keyid') + keyname = gajim.config.get_per('accounts', account, 'keyname') + use_gpg_agent = gajim.config.get('use_gpg_agent') + + if account == gajim.ZEROCONF_ACC_NAME: + widget_name_add = '2' + else: + widget_name_add = '1' + + gpg_key_label = self.xml.get_widget('gpg_key_label' + widget_name_add) + gpg_name_label = self.xml.get_widget('gpg_name_label' + widget_name_add) + use_gpg_agent_checkbutton = self.xml.get_widget( + 'use_gpg_agent_checkbutton' + widget_name_add) + + if not keyid: + use_gpg_agent_checkbutton.set_sensitive(False) + gpg_key_label.set_text(_('No key selected')) + gpg_name_label.set_text('') + return + + gpg_key_label.set_text(keyid) + gpg_name_label.set_text(keyname) + use_gpg_agent_checkbutton.set_sensitive(True) + use_gpg_agent_checkbutton.set_active(use_gpg_agent) + + def draw_normal_jid(self): + account = self.current_account + self.ignore_events = True + active = gajim.config.get_per('accounts', account, 'active') + self.xml.get_widget('enable_checkbutton1').set_active(active) + self.xml.get_widget('normal_notebook1').set_sensitive(active) + if gajim.config.get_per('accounts', account, 'anonymous_auth'): + self.xml.get_widget('anonymous_checkbutton1').set_active(True) + self.xml.get_widget('jid_label1').set_text(_('Server:')) + save_password = self.xml.get_widget('save_password_checkbutton1') + save_password.set_active(False) + save_password.set_sensitive(False) + password_entry = self.xml.get_widget('password_entry1') + password_entry.set_text('') + password_entry.set_sensitive(False) + jid = gajim.config.get_per('accounts', account, 'hostname') + else: + self.xml.get_widget('anonymous_checkbutton1').set_active(False) + self.xml.get_widget('jid_label1').set_text(_('Jabber ID:')) + savepass = gajim.config.get_per('accounts', account, 'savepass') + save_password = self.xml.get_widget('save_password_checkbutton1') + save_password.set_sensitive(True) + save_password.set_active(savepass) + password_entry = self.xml.get_widget('password_entry1') + if savepass: + passstr = passwords.get_password(account) or '' + password_entry.set_sensitive(True) + else: + passstr = '' + password_entry.set_sensitive(False) + password_entry.set_text(passstr) + + jid = gajim.config.get_per('accounts', account, 'name') \ + + '@' + gajim.config.get_per('accounts', account, 'hostname') + self.xml.get_widget('jid_entry1').set_text(jid) + self.ignore_events = False + + def init_normal_account(self): + account = self.current_account + # Account tab + self.draw_normal_jid() + self.xml.get_widget('resource_entry1').set_text(gajim.config.get_per( + 'accounts', account, 'resource')) + self.xml.get_widget('adjust_priority_with_status_checkbutton1').\ + set_active(gajim.config.get_per('accounts', account, + 'adjust_priority_with_status')) + spinbutton = self.xml.get_widget('priority_spinbutton1') + if gajim.config.get('enable_negative_priority'): + spinbutton.set_range(-128, 127) + else: + spinbutton.set_range(0, 127) + spinbutton.set_value(gajim.config.get_per('accounts', account, + 'priority')) + + # Connection tab + use_env_http_proxy = gajim.config.get_per('accounts', account, + 'use_env_http_proxy') + self.xml.get_widget('use_env_http_proxy_checkbutton1').set_active( + use_env_http_proxy) + self.xml.get_widget('proxy_hbox1').set_sensitive(not use_env_http_proxy) + + warn_when_insecure_ssl = gajim.config.get_per('accounts', account, + 'warn_when_insecure_ssl_connection') + self.xml.get_widget('warn_when_insecure_connection_checkbutton1').\ + set_active(warn_when_insecure_ssl) + + self.xml.get_widget('send_keepalive_checkbutton1').set_active( + gajim.config.get_per('accounts', account, 'keep_alives_enabled')) + + use_custom_host = gajim.config.get_per('accounts', account, + 'use_custom_host') + self.xml.get_widget('custom_host_port_checkbutton1').set_active( + use_custom_host) + custom_host = gajim.config.get_per('accounts', account, 'custom_host') + if not custom_host: + custom_host = gajim.config.get_per('accounts', account, 'hostname') + gajim.config.set_per('accounts', account, 'custom_host', custom_host) + self.xml.get_widget('custom_host_entry1').set_text(custom_host) + custom_port = gajim.config.get_per('accounts', account, 'custom_port') + if not custom_port: + custom_port = 5222 + gajim.config.set_per('accounts', account, 'custom_port', custom_port) + self.xml.get_widget('custom_port_entry1').set_text(unicode(custom_port)) + + # Personal tab + gpg_key_label = self.xml.get_widget('gpg_key_label1') + if gajim.HAVE_GPG: + self.xml.get_widget('gpg_choose_button1').set_sensitive(True) + self.init_account_gpg() + else: + gpg_key_label.set_text(_('OpenPGP is not usable on this computer')) + self.xml.get_widget('gpg_choose_button1').set_sensitive(False) + + # General tab + self.xml.get_widget('autoconnect_checkbutton1').set_active(gajim.config.\ + get_per('accounts', account, 'autoconnect')) + self.xml.get_widget('autoreconnect_checkbutton1').set_active(gajim. + config.get_per('accounts', account, 'autoreconnect')) + + list_no_log_for = gajim.config.get_per('accounts', account, + 'no_log_for').split() + if account in list_no_log_for: + self.xml.get_widget('log_history_checkbutton1').set_active(False) + else: + self.xml.get_widget('log_history_checkbutton1').set_active(True) + + self.xml.get_widget('sync_with_global_status_checkbutton1').set_active( + gajim.config.get_per('accounts', account, 'sync_with_global_status')) + self.xml.get_widget('use_ft_proxies_checkbutton1').set_active( + gajim.config.get_per('accounts', account, 'use_ft_proxies')) + + def on_add_button_clicked(self, widget): + """ + When add button is clicked: open an account information window + """ + if 'account_creation_wizard' in gajim.interface.instances: + gajim.interface.instances['account_creation_wizard'].window.present() + else: + gajim.interface.instances['account_creation_wizard'] = \ + AccountCreationWizardWindow() + + def on_remove_button_clicked(self, widget): + """ + When delete button is clicked: Remove an account from the listStore and + from the config file + """ + if not self.current_account: + return + account = self.current_account + if len(gajim.events.get_events(account)): + dialogs.ErrorDialog(_('Unread events'), + _('Read all pending events before removing this account.')) + return + + if gajim.config.get_per('accounts', account, 'is_zeroconf'): + # Should never happen as button is insensitive + return + + win_opened = False + if gajim.interface.msg_win_mgr.get_controls(acct = account): + win_opened = True + else: + for key in gajim.interface.instances[account]: + if gajim.interface.instances[account][key] and key != \ + 'remove_account': + win_opened = True + break + # Detect if we have opened windows for this account + def remove(account): + if 'remove_account' in gajim.interface.instances[account]: + gajim.interface.instances[account]['remove_account'].window.\ + present() + else: + gajim.interface.instances[account]['remove_account'] = \ + RemoveAccountWindow(account) + if win_opened: + dialogs.ConfirmationDialog( + _('You have opened chat in account %s') % account, + _('All chat and groupchat windows will be closed. Do you want to ' + 'continue?'), + on_response_ok = (remove, account)) + else: + remove(account) + + def on_rename_button_clicked(self, widget): + if not self.current_account: + return + active = gajim.config.get_per('accounts', self.current_account, 'active') + if active and gajim.connections[self.current_account].connected != 0: + dialogs.ErrorDialog( + _('You are currently connected to the server'), + _('To change the account name, you must be disconnected.')) + return + if len(gajim.events.get_events(self.current_account)): + dialogs.ErrorDialog(_('Unread events'), + _('To change the account name, you must read all pending ' + 'events.')) + return + # Get the new name + def on_renamed(new_name, old_name): + if new_name in gajim.connections: + dialogs.ErrorDialog(_('Account Name Already Used'), + _('This name is already used by another of your accounts. ' + 'Please choose another name.')) + return + if (new_name == ''): + dialogs.ErrorDialog(_('Invalid account name'), + _('Account name cannot be empty.')) + return + if new_name.find(' ') != -1: + dialogs.ErrorDialog(_('Invalid account name'), + _('Account name cannot contain spaces.')) + return + if active: + # update variables + gajim.interface.instances[new_name] = gajim.interface.instances[ + old_name] + gajim.interface.minimized_controls[new_name] = \ + gajim.interface.minimized_controls[old_name] + gajim.nicks[new_name] = gajim.nicks[old_name] + gajim.block_signed_in_notifications[new_name] = \ + gajim.block_signed_in_notifications[old_name] + gajim.groups[new_name] = gajim.groups[old_name] + gajim.gc_connected[new_name] = gajim.gc_connected[old_name] + gajim.automatic_rooms[new_name] = gajim.automatic_rooms[old_name] + gajim.newly_added[new_name] = gajim.newly_added[old_name] + gajim.to_be_removed[new_name] = gajim.to_be_removed[old_name] + gajim.sleeper_state[new_name] = gajim.sleeper_state[old_name] + gajim.encrypted_chats[new_name] = gajim.encrypted_chats[old_name] + gajim.last_message_time[new_name] = \ + gajim.last_message_time[old_name] + gajim.status_before_autoaway[new_name] = \ + gajim.status_before_autoaway[old_name] + gajim.transport_avatar[new_name] = gajim.transport_avatar[old_name] + gajim.gajim_optional_features[new_name] = \ + gajim.gajim_optional_features[old_name] + gajim.caps_hash[new_name] = gajim.caps_hash[old_name] + + gajim.contacts.change_account_name(old_name, new_name) + gajim.events.change_account_name(old_name, new_name) + + # change account variable for chat / gc controls + gajim.interface.msg_win_mgr.change_account_name(old_name, new_name) + # upgrade account variable in opened windows + for kind in ('infos', 'disco', 'gc_config', 'search', + 'online_dialog'): + for j in gajim.interface.instances[new_name][kind]: + gajim.interface.instances[new_name][kind][j].account = \ + new_name + + # ServiceCache object keep old property account + if hasattr(gajim.connections[old_name], 'services_cache'): + gajim.connections[old_name].services_cache.account = new_name + del gajim.interface.instances[old_name] + del gajim.interface.minimized_controls[old_name] + del gajim.nicks[old_name] + del gajim.block_signed_in_notifications[old_name] + del gajim.groups[old_name] + del gajim.gc_connected[old_name] + del gajim.automatic_rooms[old_name] + del gajim.newly_added[old_name] + del gajim.to_be_removed[old_name] + del gajim.sleeper_state[old_name] + del gajim.encrypted_chats[old_name] + del gajim.last_message_time[old_name] + del gajim.status_before_autoaway[old_name] + del gajim.transport_avatar[old_name] + del gajim.gajim_optional_features[old_name] + del gajim.caps_hash[old_name] + gajim.connections[old_name].name = new_name + gajim.connections[new_name] = gajim.connections[old_name] + del gajim.connections[old_name] + gajim.config.add_per('accounts', new_name) + old_config = gajim.config.get_per('accounts', old_name) + for opt in old_config: + gajim.config.set_per('accounts', new_name, opt, old_config[opt][1]) + gajim.config.del_per('accounts', old_name) + if self.current_account == old_name: + self.current_account = new_name + if old_name == gajim.ZEROCONF_ACC_NAME: + gajim.ZEROCONF_ACC_NAME = new_name + # refresh roster + gajim.interface.roster.setup_and_draw_roster() + self.init_accounts() + self.select_account(new_name) + + title = _('Rename Account') + message = _('Enter a new name for account %s') % self.current_account + old_text = self.current_account + dialogs.InputDialog(title, message, old_text, is_modal=False, + ok_handler=(on_renamed, self.current_account)) + + def option_changed(self, option, value): + return gajim.config.get_per('accounts', self.current_account, option) != \ + value + + def on_jid_entry1_focus_out_event(self, widget, event): + if self.ignore_events: + return + jid = widget.get_text() + # check if jid is conform to RFC and stringprep it + try: + jid = helpers.parse_jid(jid) + except helpers.InvalidFormat, s: + if not widget.is_focus(): + pritext = _('Invalid Jabber ID') + dialogs.ErrorDialog(pritext, str(s)) + gobject.idle_add(lambda: widget.grab_focus()) + return True + + jid_splited = jid.split('@', 1) + if len(jid_splited) != 2 and not gajim.config.get_per('accounts', + self.current_account, 'anonymous_auth'): + if not widget.is_focus(): + pritext = _('Invalid Jabber ID') + sectext = _('A Jabber ID must be in the form "user@servername".') + dialogs.ErrorDialog(pritext, sectext) + gobject.idle_add(lambda: widget.grab_focus()) + return True + + + if gajim.config.get_per('accounts', self.current_account, + 'anonymous_auth'): + gajim.config.set_per('accounts', self.current_account, 'hostname', + jid_splited[0]) + if self.option_changed('hostname', jid_splited[0]): + self.need_relogin = True + else: + if self.option_changed('name', jid_splited[0]) or \ + self.option_changed('hostname', jid_splited[1]): + self.need_relogin = True + + gajim.config.set_per('accounts', self.current_account, 'name', + jid_splited[0]) + gajim.config.set_per('accounts', self.current_account, 'hostname', + jid_splited[1]) + + def on_anonymous_checkbutton1_toggled(self, widget): + if self.ignore_events: + return + active = widget.get_active() + gajim.config.set_per('accounts', self.current_account, 'anonymous_auth', + active) + self.draw_normal_jid() + + def on_password_entry1_changed(self, widget): + if self.ignore_events: + return + passwords.save_password(self.current_account, widget.get_text().decode( + 'utf-8')) + + def on_save_password_checkbutton1_toggled(self, widget): + if self.ignore_events: + return + active = widget.get_active() + password_entry = self.xml.get_widget('password_entry1') + password_entry.set_sensitive(active) + gajim.config.set_per('accounts', self.current_account, 'savepass', active) + if active: + password = password_entry.get_text() + passwords.save_password(self.current_account, password) + else: + passwords.save_password(self.current_account, '') + + def on_resource_entry1_focus_out_event(self, widget, event): + if self.ignore_events: + return + resource = self.xml.get_widget('resource_entry1').get_text().decode( + 'utf-8') + try: + resource = helpers.parse_resource(resource) + except helpers.InvalidFormat, s: + if not widget.is_focus(): + pritext = _('Invalid Jabber ID') + dialogs.ErrorDialog(pritext, str(s)) + gobject.idle_add(lambda: widget.grab_focus()) + return True + + if self.option_changed('resource', resource): + self.need_relogin = True + + gajim.config.set_per('accounts', self.current_account, 'resource', + resource) + + def on_adjust_priority_with_status_checkbutton1_toggled(self, widget): + self.xml.get_widget('priority_spinbutton1').set_sensitive( + not widget.get_active()) + self.on_checkbutton_toggled(widget, 'adjust_priority_with_status', + account = self.current_account) + + def on_priority_spinbutton1_value_changed(self, widget): + prio = widget.get_value_as_int() + + if self.option_changed('priority', prio): + self.resend_presence = True + + gajim.config.set_per('accounts', self.current_account, 'priority', prio) + + def on_synchronise_contacts_button1_clicked(self, widget): + try: + dialogs.SynchroniseSelectAccountDialog(self.current_account) + except GajimGeneralException: + # If we showed ErrorDialog, there will not be dialog instance + return + + def on_change_password_button1_clicked(self, widget): + def on_changed(new_password): + if new_password is not None: + gajim.connections[self.current_account].change_password( + new_password) + if self.xml.get_widget('save_password_checkbutton1').get_active(): + self.xml.get_widget('password_entry1').set_text(new_password) + + try: + dialogs.ChangePasswordDialog(self.current_account, on_changed) + except GajimGeneralException: + # if we showed ErrorDialog, there will not be dialog instance + return + + def on_autoconnect_checkbutton_toggled(self, widget): + if self.ignore_events: + return + self.on_checkbutton_toggled(widget, 'autoconnect', + account=self.current_account) + + def on_autoreconnect_checkbutton_toggled(self, widget): + if self.ignore_events: + return + self.on_checkbutton_toggled(widget, 'autoreconnect', + account=self.current_account) + + def on_log_history_checkbutton_toggled(self, widget): + if self.ignore_events: + return + list_no_log_for = gajim.config.get_per('accounts', self.current_account, + 'no_log_for').split() + if self.current_account in list_no_log_for: + list_no_log_for.remove(self.current_account) + + if not widget.get_active(): + list_no_log_for.append(self.current_account) + gajim.config.set_per('accounts', self.current_account, 'no_log_for', + ' '.join(list_no_log_for)) + + def on_sync_with_global_status_checkbutton_toggled(self, widget): + if self.ignore_events: + return + self.on_checkbutton_toggled(widget, 'sync_with_global_status', + account=self.current_account) + gajim.interface.roster.update_status_combobox() + + def on_use_ft_proxies_checkbutton1_toggled(self, widget): + if self.ignore_events: + return + self.on_checkbutton_toggled(widget, 'use_ft_proxies', + account=self.current_account) + + def on_use_env_http_proxy_checkbutton1_toggled(self, widget): + if self.ignore_events: + return + self.on_checkbutton_toggled(widget, 'use_env_http_proxy', + account=self.current_account) + hbox = self.xml.get_widget('proxy_hbox1') + hbox.set_sensitive(not widget.get_active()) + + def on_proxies_combobox1_changed(self, widget): + active = widget.get_active() + proxy = widget.get_model()[active][0].decode('utf-8') + if proxy == _('None'): + proxy = '' + + if self.option_changed('proxy', proxy): + self.need_relogin = True + + gajim.config.set_per('accounts', self.current_account, 'proxy', proxy) + + def on_manage_proxies_button1_clicked(self, widget): + if 'manage_proxies' in gajim.interface.instances: + gajim.interface.instances['manage_proxies'].window.present() + else: + gajim.interface.instances['manage_proxies'] = ManageProxiesWindow() + + def on_warn_when_insecure_connection_checkbutton1_toggled(self, widget): + if self.ignore_events: + return + + self.on_checkbutton_toggled(widget, 'warn_when_insecure_ssl_connection', + account=self.current_account) + + def on_send_keepalive_checkbutton1_toggled(self, widget): + if self.ignore_events: + return + self.on_checkbutton_toggled(widget, 'keep_alives_enabled', + account=self.current_account) + gajim.config.set_per('accounts', self.current_account, + 'ping_alives_enabled', widget.get_active()) + + def on_custom_host_port_checkbutton1_toggled(self, widget): + if self.option_changed('use_custom_host', widget.get_active()): + self.need_relogin = True + + self.on_checkbutton_toggled(widget, 'use_custom_host', + account=self.current_account) + active = widget.get_active() + self.xml.get_widget('custom_host_port_hbox1').set_sensitive(active) + + def on_custom_host_entry1_changed(self, widget): + if self.ignore_events: + return + host = widget.get_text().decode('utf-8') + if self.option_changed('custom_host', host): + self.need_relogin = True + gajim.config.set_per('accounts', self.current_account, 'custom_host', + host) + + def on_custom_port_entry_focus_out_event(self, widget, event): + if self.ignore_events: + return + custom_port = widget.get_text() + try: + custom_port = int(custom_port) + except Exception: + if not widget.is_focus(): + dialogs.ErrorDialog(_('Invalid entry'), + _('Custom port must be a port number.')) + gobject.idle_add(lambda: widget.grab_focus()) + return True + if self.option_changed('custom_port', custom_port): + self.need_relogin = True + gajim.config.set_per('accounts', self.current_account, 'custom_port', + custom_port) + + def on_gpg_choose_button_clicked(self, widget, data = None): + if self.current_account in gajim.connections and \ + gajim.connections[self.current_account].gpg: + secret_keys = gajim.connections[self.current_account].\ + ask_gpg_secrete_keys() + + # self.current_account is None and/or gajim.connections is {} + else: + if gajim.HAVE_GPG: + secret_keys = GnuPG.GnuPG().get_secret_keys() + else: + secret_keys = [] + if not secret_keys: + dialogs.ErrorDialog(_('Failed to get secret keys'), + _('There is no OpenPGP secret key available.')) + secret_keys[_('None')] = _('None') + + def on_key_selected(keyID): + if keyID is None: + return + if self.current_account == gajim.ZEROCONF_ACC_NAME: + wiget_name_ext = '2' + else: + wiget_name_ext = '1' + gpg_key_label = self.xml.get_widget('gpg_key_label' + wiget_name_ext) + gpg_name_label = self.xml.get_widget('gpg_name_label' + wiget_name_ext) + use_gpg_agent_checkbutton = self.xml.get_widget( + 'use_gpg_agent_checkbutton' + wiget_name_ext) + if keyID[0] == _('None'): + gpg_key_label.set_text(_('No key selected')) + gpg_name_label.set_text('') + use_gpg_agent_checkbutton.set_sensitive(False) + if self.option_changed('keyid', ''): + self.need_relogin = True + gajim.config.set_per('accounts', self.current_account, 'keyname', + '') + gajim.config.set_per('accounts', self.current_account, 'keyid', '') + else: + gpg_key_label.set_text(keyID[0]) + gpg_name_label.set_text(keyID[1]) + use_gpg_agent_checkbutton.set_sensitive(True) + if self.option_changed('keyid', keyID[0]): + self.need_relogin = True + gajim.config.set_per('accounts', self.current_account, 'keyname', + keyID[1]) + gajim.config.set_per('accounts', self.current_account, 'keyid', + keyID[0]) + + dialogs.ChooseGPGKeyDialog(_('OpenPGP Key Selection'), + _('Choose your OpenPGP key'), secret_keys, on_key_selected) + + def on_use_gpg_agent_checkbutton_toggled(self, widget): + self.on_checkbutton_toggled(widget, 'use_gpg_agent') + + def on_edit_details_button1_clicked(self, widget): + if self.current_account not in gajim.interface.instances: + dialogs.ErrorDialog(_('No such account available'), + _('You must create your account before editing your personal ' + 'information.')) + return + + # show error dialog if account is newly created (not in gajim.connections) + if self.current_account not in gajim.connections or \ + gajim.connections[self.current_account].connected < 2: + dialogs.ErrorDialog(_('You are not connected to the server'), + _('Without a connection, you can not edit your personal information.')) + return + + if not gajim.connections[self.current_account].vcard_supported: + dialogs.ErrorDialog(_("Your server doesn't support Vcard"), + _("Your server can't save your personal information.")) + return + + gajim.interface.edit_own_details(self.current_account) + + def on_checkbutton_toggled(self, widget, config_name, + change_sensitivity_widgets = None, account = None): + if account: + gajim.config.set_per('accounts', account, config_name, + widget.get_active()) + else: + gajim.config.set(config_name, widget.get_active()) + if change_sensitivity_widgets: + for w in change_sensitivity_widgets: + w.set_sensitive(widget.get_active()) + gajim.interface.save_config() + + def on_merge_checkbutton_toggled(self, widget): + self.on_checkbutton_toggled(widget, 'mergeaccounts') + if len(gajim.connections) >= 2: # Do not merge accounts if only one active + gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') + else: + gajim.interface.roster.regroup = False + gajim.interface.roster.setup_and_draw_roster() + + def _disable_account(self, account): + gajim.interface.roster.close_all(account) + if account == gajim.ZEROCONF_ACC_NAME: + gajim.connections[account].disable_account() + del gajim.connections[account] + gajim.interface.save_config() + del gajim.interface.instances[account] + del gajim.interface.minimized_controls[account] + del gajim.nicks[account] + del gajim.block_signed_in_notifications[account] + del gajim.groups[account] + gajim.contacts.remove_account(account) + del gajim.gc_connected[account] + del gajim.automatic_rooms[account] + del gajim.to_be_removed[account] + del gajim.newly_added[account] + del gajim.sleeper_state[account] + del gajim.encrypted_chats[account] + del gajim.last_message_time[account] + del gajim.status_before_autoaway[account] + del gajim.transport_avatar[account] + del gajim.gajim_optional_features[account] + del gajim.caps_hash[account] + if len(gajim.connections) >= 2: + # Do not merge accounts if only one exists + gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') + else: + gajim.interface.roster.regroup = False + gajim.interface.roster.setup_and_draw_roster() + gajim.interface.roster.set_actions_menu_needs_rebuild() + + def _enable_account(self, account): + if account == gajim.ZEROCONF_ACC_NAME: + gajim.connections[account] = connection_zeroconf.ConnectionZeroconf( + account) + if gajim.connections[account].gpg: + self.xml.get_widget('gpg_choose_button2').set_sensitive(True) + else: + gajim.connections[account] = common.connection.Connection(account) + if gajim.connections[account].gpg: + self.xml.get_widget('gpg_choose_button1').set_sensitive(True) + self.init_account_gpg() + # update variables + gajim.interface.instances[account] = {'infos': {}, + 'disco': {}, 'gc_config': {}, 'search': {}, 'online_dialog': {}} + gajim.interface.minimized_controls[account] = {} + gajim.connections[account].connected = 0 + gajim.groups[account] = {} + gajim.contacts.add_account(account) + gajim.gc_connected[account] = {} + gajim.automatic_rooms[account] = {} + gajim.newly_added[account] = [] + gajim.to_be_removed[account] = [] + if account == gajim.ZEROCONF_ACC_NAME: + gajim.nicks[account] = gajim.ZEROCONF_ACC_NAME + else: + gajim.nicks[account] = gajim.config.get_per('accounts', account, + 'name') + gajim.block_signed_in_notifications[account] = True + gajim.sleeper_state[account] = 'off' + gajim.encrypted_chats[account] = [] + gajim.last_message_time[account] = {} + gajim.status_before_autoaway[account] = '' + gajim.transport_avatar[account] = {} + gajim.gajim_optional_features[account] = [] + gajim.caps_hash[account] = '' + # refresh roster + if len(gajim.connections) >= 2: + # Do not merge accounts if only one exists + gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') + else: + gajim.interface.roster.regroup = False + gajim.interface.roster.setup_and_draw_roster() + gajim.interface.roster.set_actions_menu_needs_rebuild() + gajim.interface.save_config() + + def on_enable_zeroconf_checkbutton2_toggled(self, widget): + # don't do anything if there is an account with the local name but is a + # normal account + if self.ignore_events: + return + if gajim.account_is_connected(self.current_account): + self.ignore_events = True + self.xml.get_widget('enable_zeroconf_checkbutton2').set_active(True) + self.ignore_events = False + dialogs.ErrorDialog( + _('You are currently connected to the server'), + _('To disable the account, you must be disconnected.')) + return + if gajim.ZEROCONF_ACC_NAME in gajim.connections and not \ + gajim.connections[gajim.ZEROCONF_ACC_NAME].is_zeroconf: + gajim.connections[gajim.ZEROCONF_ACC_NAME].dispatch('ERROR', + (_('Account Local already exists.'), + _('Please rename or remove it before enabling link-local messaging' + '.'))) + return + + if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \ + and not widget.get_active(): + self.xml.get_widget('zeroconf_notebook').set_sensitive(False) + # disable + self._disable_account(gajim.ZEROCONF_ACC_NAME) + + elif not gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'active') and widget.get_active(): + self.xml.get_widget('zeroconf_notebook').set_sensitive(True) + # enable (will create new account if not present) + self._enable_account(gajim.ZEROCONF_ACC_NAME) + + self.on_checkbutton_toggled(widget, 'active', + account=gajim.ZEROCONF_ACC_NAME) + + def on_enable_checkbutton1_toggled(self, widget): + if self.ignore_events: + return + if gajim.account_is_connected(self.current_account): + self.ignore_events = True + self.xml.get_widget('enable_checkbutton1').set_active(True) + self.ignore_events = False + dialogs.ErrorDialog( + _('You are currently connected to the server'), + _('To disable the account, you must be disconnected.')) + return + # add/remove account in roster and all variables + if widget.get_active(): + # enable + self._enable_account(self.current_account) + else: + # disable + self._disable_account(self.current_account) + self.on_checkbutton_toggled(widget, 'active', + account=self.current_account, change_sensitivity_widgets=[ + self.xml.get_widget('normal_notebook1')]) + + def on_custom_port_checkbutton2_toggled(self, widget): + self.xml.get_widget('custom_port_entry2').set_sensitive( + widget.get_active()) + self.on_checkbutton_toggled(widget, 'use_custom_host', + account = self.current_account) + if not widget.get_active(): + self.xml.get_widget('custom_port_entry2').set_text('5298') + + def on_first_name_entry2_changed(self, widget): + if self.ignore_events: + return + name = widget.get_text().decode('utf-8') + if self.option_changed('zeroconf_first_name', name): + self.need_relogin = True + gajim.config.set_per('accounts', self.current_account, + 'zeroconf_first_name', name) + + def on_last_name_entry2_changed(self, widget): + if self.ignore_events: + return + name = widget.get_text().decode('utf-8') + if self.option_changed('zeroconf_last_name', name): + self.need_relogin = True + gajim.config.set_per('accounts', self.current_account, + 'zeroconf_last_name', name) + + def on_jabber_id_entry2_changed(self, widget): + if self.ignore_events: + return + id_ = widget.get_text().decode('utf-8') + if self.option_changed('zeroconf_jabber_id', id_): + self.need_relogin = True + gajim.config.set_per('accounts', self.current_account, + 'zeroconf_jabber_id', id_) + + def on_email_entry2_changed(self, widget): + if self.ignore_events: + return + email = widget.get_text().decode('utf-8') + if self.option_changed('zeroconf_email', email): + self.need_relogin = True + gajim.config.set_per('accounts', self.current_account, + 'zeroconf_email', email) class FakeDataForm(gtk.Table, object): - """ - Class for forms that are in XML format value1 infos in a - table {entry1: value1} - """ + """ + Class for forms that are in XML format value1 infos in a + table {entry1: value1} + """ - def __init__(self, infos): - gtk.Table.__init__(self) - self.infos = infos - self.entries = {} - self._draw_table() + def __init__(self, infos): + gtk.Table.__init__(self) + self.infos = infos + self.entries = {} + self._draw_table() - def _draw_table(self): - """ - Draw the table - """ - nbrow = 0 - if 'instructions' in self.infos: - nbrow = 1 - self.resize(rows = nbrow, columns = 2) - label = gtk.Label(self.infos['instructions']) - self.attach(label, 0, 2, 0, 1, 0, 0, 0, 0) - for name in self.infos.keys(): - if name in ('key', 'instructions', 'x', 'registered'): - continue - if not name: - continue + def _draw_table(self): + """ + Draw the table + """ + nbrow = 0 + if 'instructions' in self.infos: + nbrow = 1 + self.resize(rows = nbrow, columns = 2) + label = gtk.Label(self.infos['instructions']) + self.attach(label, 0, 2, 0, 1, 0, 0, 0, 0) + for name in self.infos.keys(): + if name in ('key', 'instructions', 'x', 'registered'): + continue + if not name: + continue - nbrow = nbrow + 1 - self.resize(rows = nbrow, columns = 2) - label = gtk.Label(name.capitalize() + ':') - self.attach(label, 0, 1, nbrow - 1, nbrow, 0, 0, 0, 0) - entry = gtk.Entry() - entry.set_activates_default(True) - if self.infos[name]: - entry.set_text(self.infos[name]) - if name == 'password': - entry.set_visibility(False) - self.attach(entry, 1, 2, nbrow - 1, nbrow, 0, 0, 0, 0) - self.entries[name] = entry - if nbrow == 1: - entry.grab_focus() + nbrow = nbrow + 1 + self.resize(rows = nbrow, columns = 2) + label = gtk.Label(name.capitalize() + ':') + self.attach(label, 0, 1, nbrow - 1, nbrow, 0, 0, 0, 0) + entry = gtk.Entry() + entry.set_activates_default(True) + if self.infos[name]: + entry.set_text(self.infos[name]) + if name == 'password': + entry.set_visibility(False) + self.attach(entry, 1, 2, nbrow - 1, nbrow, 0, 0, 0, 0) + self.entries[name] = entry + if nbrow == 1: + entry.grab_focus() - def get_infos(self): - for name in self.entries.keys(): - self.infos[name] = self.entries[name].get_text().decode('utf-8') - return self.infos + def get_infos(self): + for name in self.entries.keys(): + self.infos[name] = self.entries[name].get_text().decode('utf-8') + return self.infos class ServiceRegistrationWindow: - """ - Class for Service registration window. Window that appears when we want to - subscribe to a service if is_form we use dataforms_widget else we use - service_registarion_window - """ - def __init__(self, service, infos, account, is_form): - self.service = service - self.account = account - self.is_form = is_form - self.xml = gtkgui_helpers.get_glade('service_registration_window.glade') - self.window = self.xml.get_widget('service_registration_window') - self.window.set_transient_for(gajim.interface.roster.window) - if self.is_form: - dataform = dataforms.ExtendForm(node = infos) - self.data_form_widget = dataforms_widget.DataFormWidget(dataform) - if self.data_form_widget.title: - self.window.set_title('%s - Gajim' % self.data_form_widget.title) - table = self.xml.get_widget('table') - table.attach(self.data_form_widget, 0, 2, 0, 1) - else: - if 'registered' in infos: - self.window.set_title(_('Edit %s') % service) - else: - self.window.set_title(_('Register to %s') % service) - self.data_form_widget = FakeDataForm(infos) - table = self.xml.get_widget('table') - table.attach(self.data_form_widget, 0, 2, 0, 1) + """ + Class for Service registration window. Window that appears when we want to + subscribe to a service if is_form we use dataforms_widget else we use + service_registarion_window + """ + def __init__(self, service, infos, account, is_form): + self.service = service + self.account = account + self.is_form = is_form + self.xml = gtkgui_helpers.get_glade('service_registration_window.glade') + self.window = self.xml.get_widget('service_registration_window') + self.window.set_transient_for(gajim.interface.roster.window) + if self.is_form: + dataform = dataforms.ExtendForm(node = infos) + self.data_form_widget = dataforms_widget.DataFormWidget(dataform) + if self.data_form_widget.title: + self.window.set_title('%s - Gajim' % self.data_form_widget.title) + table = self.xml.get_widget('table') + table.attach(self.data_form_widget, 0, 2, 0, 1) + else: + if 'registered' in infos: + self.window.set_title(_('Edit %s') % service) + else: + self.window.set_title(_('Register to %s') % service) + self.data_form_widget = FakeDataForm(infos) + table = self.xml.get_widget('table') + table.attach(self.data_form_widget, 0, 2, 0, 1) - self.xml.signal_autoconnect(self) - self.window.show_all() + self.xml.signal_autoconnect(self) + self.window.show_all() - def on_cancel_button_clicked(self, widget): - self.window.destroy() + def on_cancel_button_clicked(self, widget): + self.window.destroy() - def on_ok_button_clicked(self, widget): - # send registration info to the core - if self.is_form: - form = self.data_form_widget.data_form - gajim.connections[self.account].register_agent(self.service, - form, True) # True is for is_form - else: - infos = self.data_form_widget.get_infos() - if 'instructions' in infos: - del infos['instructions'] - if 'registered' in infos: - del infos['registered'] - gajim.connections[self.account].register_agent(self.service, infos) + def on_ok_button_clicked(self, widget): + # send registration info to the core + if self.is_form: + form = self.data_form_widget.data_form + gajim.connections[self.account].register_agent(self.service, + form, True) # True is for is_form + else: + infos = self.data_form_widget.get_infos() + if 'instructions' in infos: + del infos['instructions'] + if 'registered' in infos: + del infos['registered'] + gajim.connections[self.account].register_agent(self.service, infos) - self.window.destroy() + self.window.destroy() class GroupchatConfigWindow: - def __init__(self, account, room_jid, form = None): - self.account = account - self.room_jid = room_jid - self.form = form - self.remove_button = {} - self.affiliation_treeview = {} - self.start_users_dict = {} # list at the beginning - self.affiliation_labels = {'outcast': _('Ban List'), - 'member': _('Member List'), - 'owner': _('Owner List'), - 'admin':_('Administrator List')} + def __init__(self, account, room_jid, form = None): + self.account = account + self.room_jid = room_jid + self.form = form + self.remove_button = {} + self.affiliation_treeview = {} + self.start_users_dict = {} # list at the beginning + self.affiliation_labels = {'outcast': _('Ban List'), + 'member': _('Member List'), + 'owner': _('Owner List'), + 'admin':_('Administrator List')} - self.xml = gtkgui_helpers.get_glade('data_form_window.glade', 'data_form_window') - self.window = self.xml.get_widget('data_form_window') - self.window.set_transient_for(gajim.interface.roster.window) + self.xml = gtkgui_helpers.get_glade('data_form_window.glade', 'data_form_window') + self.window = self.xml.get_widget('data_form_window') + self.window.set_transient_for(gajim.interface.roster.window) - if self.form: - config_vbox = self.xml.get_widget('config_vbox') - dataform = dataforms.ExtendForm(node = self.form) - self.data_form_widget = dataforms_widget.DataFormWidget(dataform) - # hide scrollbar of this data_form_widget, we already have in this - # widget - sw = self.data_form_widget.xml.get_widget('single_form_scrolledwindow') - sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER) + if self.form: + config_vbox = self.xml.get_widget('config_vbox') + dataform = dataforms.ExtendForm(node = self.form) + self.data_form_widget = dataforms_widget.DataFormWidget(dataform) + # hide scrollbar of this data_form_widget, we already have in this + # widget + sw = self.data_form_widget.xml.get_widget('single_form_scrolledwindow') + sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER) - self.data_form_widget.show() - config_vbox.pack_start(self.data_form_widget) + self.data_form_widget.show() + config_vbox.pack_start(self.data_form_widget) - # Draw the edit affiliation list things - add_on_vbox = self.xml.get_widget('add_on_vbox') + # Draw the edit affiliation list things + add_on_vbox = self.xml.get_widget('add_on_vbox') - for affiliation in self.affiliation_labels.keys(): - self.start_users_dict[affiliation] = {} - hbox = gtk.HBox(spacing = 5) - add_on_vbox.pack_start(hbox, False) + for affiliation in self.affiliation_labels.keys(): + self.start_users_dict[affiliation] = {} + hbox = gtk.HBox(spacing = 5) + add_on_vbox.pack_start(hbox, False) - label = gtk.Label(self.affiliation_labels[affiliation]) - hbox.pack_start(label, False) + label = gtk.Label(self.affiliation_labels[affiliation]) + hbox.pack_start(label, False) - bb = gtk.HButtonBox() - bb.set_layout(gtk.BUTTONBOX_END) - bb.set_spacing(5) - hbox.pack_start(bb) - add_button = gtk.Button(stock = gtk.STOCK_ADD) - add_button.connect('clicked', self.on_add_button_clicked, affiliation) - bb.pack_start(add_button) - self.remove_button[affiliation] = gtk.Button(stock = gtk.STOCK_REMOVE) - self.remove_button[affiliation].set_sensitive(False) - self.remove_button[affiliation].connect('clicked', - self.on_remove_button_clicked, affiliation) - bb.pack_start(self.remove_button[affiliation]) + bb = gtk.HButtonBox() + bb.set_layout(gtk.BUTTONBOX_END) + bb.set_spacing(5) + hbox.pack_start(bb) + add_button = gtk.Button(stock = gtk.STOCK_ADD) + add_button.connect('clicked', self.on_add_button_clicked, affiliation) + bb.pack_start(add_button) + self.remove_button[affiliation] = gtk.Button(stock = gtk.STOCK_REMOVE) + self.remove_button[affiliation].set_sensitive(False) + self.remove_button[affiliation].connect('clicked', + self.on_remove_button_clicked, affiliation) + bb.pack_start(self.remove_button[affiliation]) - liststore = gtk.ListStore(str, str, str, str) # Jid, reason, nick, role - self.affiliation_treeview[affiliation] = gtk.TreeView(liststore) - self.affiliation_treeview[affiliation].get_selection().set_mode( - gtk.SELECTION_MULTIPLE) - self.affiliation_treeview[affiliation].connect('cursor-changed', - self.on_affiliation_treeview_cursor_changed, affiliation) - renderer = gtk.CellRendererText() - col = gtk.TreeViewColumn(_('JID'), renderer) - col.add_attribute(renderer, 'text', 0) - col.set_resizable(True) - col.set_sort_column_id(0) - self.affiliation_treeview[affiliation].append_column(col) + liststore = gtk.ListStore(str, str, str, str) # Jid, reason, nick, role + self.affiliation_treeview[affiliation] = gtk.TreeView(liststore) + self.affiliation_treeview[affiliation].get_selection().set_mode( + gtk.SELECTION_MULTIPLE) + self.affiliation_treeview[affiliation].connect('cursor-changed', + self.on_affiliation_treeview_cursor_changed, affiliation) + renderer = gtk.CellRendererText() + col = gtk.TreeViewColumn(_('JID'), renderer) + col.add_attribute(renderer, 'text', 0) + col.set_resizable(True) + col.set_sort_column_id(0) + self.affiliation_treeview[affiliation].append_column(col) - if affiliation == 'outcast': - renderer = gtk.CellRendererText() - renderer.set_property('editable', True) - renderer.connect('edited', self.on_cell_edited) - col = gtk.TreeViewColumn(_('Reason'), renderer) - col.add_attribute(renderer, 'text', 1) - col.set_resizable(True) - col.set_sort_column_id(1) - self.affiliation_treeview[affiliation].append_column(col) - elif affiliation == 'member': - renderer = gtk.CellRendererText() - col = gtk.TreeViewColumn(_('Nick'), renderer) - col.add_attribute(renderer, 'text', 2) - col.set_resizable(True) - col.set_sort_column_id(2) - self.affiliation_treeview[affiliation].append_column(col) - renderer = gtk.CellRendererText() - col = gtk.TreeViewColumn(_('Role'), renderer) - col.add_attribute(renderer, 'text', 3) - col.set_resizable(True) - col.set_sort_column_id(3) - self.affiliation_treeview[affiliation].append_column(col) + if affiliation == 'outcast': + renderer = gtk.CellRendererText() + renderer.set_property('editable', True) + renderer.connect('edited', self.on_cell_edited) + col = gtk.TreeViewColumn(_('Reason'), renderer) + col.add_attribute(renderer, 'text', 1) + col.set_resizable(True) + col.set_sort_column_id(1) + self.affiliation_treeview[affiliation].append_column(col) + elif affiliation == 'member': + renderer = gtk.CellRendererText() + col = gtk.TreeViewColumn(_('Nick'), renderer) + col.add_attribute(renderer, 'text', 2) + col.set_resizable(True) + col.set_sort_column_id(2) + self.affiliation_treeview[affiliation].append_column(col) + renderer = gtk.CellRendererText() + col = gtk.TreeViewColumn(_('Role'), renderer) + col.add_attribute(renderer, 'text', 3) + col.set_resizable(True) + col.set_sort_column_id(3) + self.affiliation_treeview[affiliation].append_column(col) - sw = gtk.ScrolledWindow() - sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_NEVER) - sw.add(self.affiliation_treeview[affiliation]) - add_on_vbox.pack_start(sw) - gajim.connections[self.account].get_affiliation_list(self.room_jid, - affiliation) + sw = gtk.ScrolledWindow() + sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_NEVER) + sw.add(self.affiliation_treeview[affiliation]) + add_on_vbox.pack_start(sw) + gajim.connections[self.account].get_affiliation_list(self.room_jid, + affiliation) - self.xml.signal_autoconnect(self) - self.window.show_all() + self.xml.signal_autoconnect(self) + self.window.show_all() - def on_cancel_button_clicked(self, widget): - self.window.destroy() + def on_cancel_button_clicked(self, widget): + self.window.destroy() - def on_cell_edited(self, cell, path, new_text): - model = self.affiliation_treeview['outcast'].get_model() - new_text = new_text.decode('utf-8') - iter_ = model.get_iter(path) - model[iter_][1] = new_text + def on_cell_edited(self, cell, path, new_text): + model = self.affiliation_treeview['outcast'].get_model() + new_text = new_text.decode('utf-8') + iter_ = model.get_iter(path) + model[iter_][1] = new_text - def on_add_button_clicked(self, widget, affiliation): - if affiliation == 'outcast': - title = _('Banning...') - #You can move '\n' before user@domain if that line is TOO BIG - prompt = _('Whom do you want to ban?\n\n') - elif affiliation == 'member': - title = _('Adding Member...') - prompt = _('Whom do you want to make a member?\n\n') - elif affiliation == 'owner': - title = _('Adding Owner...') - prompt = _('Whom do you want to make an owner?\n\n') - else: - title = _('Adding Administrator...') - prompt = _('Whom do you want to make an administrator?\n\n') - prompt += _('Can be one of the following:\n' - '1. user@domain/resource (only that resource matches).\n' - '2. user@domain (any resource matches).\n' - '3. domain/resource (only that resource matches).\n' - '4. domain (the domain itself matches, as does any user@domain,\n' - 'domain/resource, or address containing a subdomain).') + def on_add_button_clicked(self, widget, affiliation): + if affiliation == 'outcast': + title = _('Banning...') + #You can move '\n' before user@domain if that line is TOO BIG + prompt = _('Whom do you want to ban?\n\n') + elif affiliation == 'member': + title = _('Adding Member...') + prompt = _('Whom do you want to make a member?\n\n') + elif affiliation == 'owner': + title = _('Adding Owner...') + prompt = _('Whom do you want to make an owner?\n\n') + else: + title = _('Adding Administrator...') + prompt = _('Whom do you want to make an administrator?\n\n') + prompt += _('Can be one of the following:\n' + '1. user@domain/resource (only that resource matches).\n' + '2. user@domain (any resource matches).\n' + '3. domain/resource (only that resource matches).\n' + '4. domain (the domain itself matches, as does any user@domain,\n' + 'domain/resource, or address containing a subdomain).') - def on_ok(jid): - if not jid: - return - model = self.affiliation_treeview[affiliation].get_model() - model.append((jid,'', '', '')) - dialogs.InputDialog(title, prompt, ok_handler=on_ok) + def on_ok(jid): + if not jid: + return + model = self.affiliation_treeview[affiliation].get_model() + model.append((jid, '', '', '')) + dialogs.InputDialog(title, prompt, ok_handler=on_ok) - def on_remove_button_clicked(self, widget, affiliation): - selection = self.affiliation_treeview[affiliation].get_selection() - model, paths = selection.get_selected_rows() - row_refs = [] - for path in paths: - row_refs.append(gtk.TreeRowReference(model, path)) - for row_ref in row_refs: - path = row_ref.get_path() - iter_ = model.get_iter(path) - jid = model[iter_][0] - model.remove(iter_) - self.remove_button[affiliation].set_sensitive(False) + def on_remove_button_clicked(self, widget, affiliation): + selection = self.affiliation_treeview[affiliation].get_selection() + model, paths = selection.get_selected_rows() + row_refs = [] + for path in paths: + row_refs.append(gtk.TreeRowReference(model, path)) + for row_ref in row_refs: + path = row_ref.get_path() + iter_ = model.get_iter(path) + jid = model[iter_][0] + model.remove(iter_) + self.remove_button[affiliation].set_sensitive(False) - def on_affiliation_treeview_cursor_changed(self, widget, affiliation): - self.remove_button[affiliation].set_sensitive(True) + def on_affiliation_treeview_cursor_changed(self, widget, affiliation): + self.remove_button[affiliation].set_sensitive(True) - def affiliation_list_received(self, users_dict): - """ - Fill the affiliation treeview - """ - for jid in users_dict: - affiliation = users_dict[jid]['affiliation'] - if affiliation not in self.affiliation_labels.keys(): - # Unknown affiliation or 'none' affiliation, do not show it - continue - self.start_users_dict[affiliation][jid] = users_dict[jid] - tv = self.affiliation_treeview[affiliation] - model = tv.get_model() - reason = users_dict[jid].get('reason', '') - nick = users_dict[jid].get('nick', '') - role = users_dict[jid].get('role', '') - model.append((jid, reason, nick, role)) + def affiliation_list_received(self, users_dict): + """ + Fill the affiliation treeview + """ + for jid in users_dict: + affiliation = users_dict[jid]['affiliation'] + if affiliation not in self.affiliation_labels.keys(): + # Unknown affiliation or 'none' affiliation, do not show it + continue + self.start_users_dict[affiliation][jid] = users_dict[jid] + tv = self.affiliation_treeview[affiliation] + model = tv.get_model() + reason = users_dict[jid].get('reason', '') + nick = users_dict[jid].get('nick', '') + role = users_dict[jid].get('role', '') + model.append((jid, reason, nick, role)) - def on_data_form_window_destroy(self, widget): - del gajim.interface.instances[self.account]['gc_config'][self.room_jid] + def on_data_form_window_destroy(self, widget): + del gajim.interface.instances[self.account]['gc_config'][self.room_jid] - def on_ok_button_clicked(self, widget): - if self.form: - form = self.data_form_widget.data_form - gajim.connections[self.account].send_gc_config(self.room_jid, form) - for affiliation in self.affiliation_labels.keys(): - users_dict = {} - actual_jid_list = [] - model = self.affiliation_treeview[affiliation].get_model() - iter_ = model.get_iter_first() - # add new jid - while iter_: - jid = model[iter_][0].decode('utf-8') - actual_jid_list.append(jid) - if jid not in self.start_users_dict[affiliation] or \ - (affiliation == 'outcast' and 'reason' in self.start_users_dict[affiliation]\ - [jid] and self.start_users_dict[affiliation][jid]\ - ['reason'] != model[iter_][1].decode('utf-8')): - users_dict[jid] = {'affiliation': affiliation} - if affiliation == 'outcast': - users_dict[jid]['reason'] = model[iter_][1].decode('utf-8') - iter_ = model.iter_next(iter_) - # remove removed one - for jid in self.start_users_dict[affiliation]: - if jid not in actual_jid_list: - users_dict[jid] = {'affiliation': 'none'} - if users_dict: - gajim.connections[self.account].send_gc_affiliation_list( - self.room_jid, users_dict) - self.window.destroy() + def on_ok_button_clicked(self, widget): + if self.form: + form = self.data_form_widget.data_form + gajim.connections[self.account].send_gc_config(self.room_jid, form) + for affiliation in self.affiliation_labels.keys(): + users_dict = {} + actual_jid_list = [] + model = self.affiliation_treeview[affiliation].get_model() + iter_ = model.get_iter_first() + # add new jid + while iter_: + jid = model[iter_][0].decode('utf-8') + actual_jid_list.append(jid) + if jid not in self.start_users_dict[affiliation] or \ + (affiliation == 'outcast' and 'reason' in self.start_users_dict[affiliation]\ + [jid] and self.start_users_dict[affiliation][jid]\ + ['reason'] != model[iter_][1].decode('utf-8')): + users_dict[jid] = {'affiliation': affiliation} + if affiliation == 'outcast': + users_dict[jid]['reason'] = model[iter_][1].decode('utf-8') + iter_ = model.iter_next(iter_) + # remove removed one + for jid in self.start_users_dict[affiliation]: + if jid not in actual_jid_list: + users_dict[jid] = {'affiliation': 'none'} + if users_dict: + gajim.connections[self.account].send_gc_affiliation_list( + self.room_jid, users_dict) + self.window.destroy() #---------- RemoveAccountWindow class -------------# class RemoveAccountWindow: - """ - Ask for removing from gajim only or from gajim and server too and do - removing of the account given - """ + """ + Ask for removing from gajim only or from gajim and server too and do + removing of the account given + """ - def on_remove_account_window_destroy(self, widget): - if self.account in gajim.interface.instances: - del gajim.interface.instances[self.account]['remove_account'] + def on_remove_account_window_destroy(self, widget): + if self.account in gajim.interface.instances: + del gajim.interface.instances[self.account]['remove_account'] - def on_cancel_button_clicked(self, widget): - self.window.destroy() + def on_cancel_button_clicked(self, widget): + self.window.destroy() - def __init__(self, account): - self.account = account - xml = gtkgui_helpers.get_glade('remove_account_window.glade') - self.window = xml.get_widget('remove_account_window') - self.window.set_transient_for(gajim.interface.roster.window) - self.remove_and_unregister_radiobutton = xml.get_widget( - 'remove_and_unregister_radiobutton') - self.window.set_title(_('Removing %s account') % self.account) - xml.signal_autoconnect(self) - self.window.show_all() + def __init__(self, account): + self.account = account + xml = gtkgui_helpers.get_glade('remove_account_window.glade') + self.window = xml.get_widget('remove_account_window') + self.window.set_transient_for(gajim.interface.roster.window) + self.remove_and_unregister_radiobutton = xml.get_widget( + 'remove_and_unregister_radiobutton') + self.window.set_title(_('Removing %s account') % self.account) + xml.signal_autoconnect(self) + self.window.show_all() - def on_remove_button_clicked(self, widget): - def remove(): - if gajim.connections[self.account].connected and \ - not self.remove_and_unregister_radiobutton.get_active(): - # change status to offline only if we will not remove this JID from - # server - gajim.connections[self.account].change_status('offline', 'offline') - if self.remove_and_unregister_radiobutton.get_active(): - if not gajim.connections[self.account].password: - def on_ok(passphrase, checked): - if passphrase == -1: - # We don't remove account cause we canceled pw window - return - gajim.connections[self.account].password = passphrase - gajim.connections[self.account].unregister_account( - self._on_remove_success) + def on_remove_button_clicked(self, widget): + def remove(): + if gajim.connections[self.account].connected and \ + not self.remove_and_unregister_radiobutton.get_active(): + # change status to offline only if we will not remove this JID from + # server + gajim.connections[self.account].change_status('offline', 'offline') + if self.remove_and_unregister_radiobutton.get_active(): + if not gajim.connections[self.account].password: + def on_ok(passphrase, checked): + if passphrase == -1: + # We don't remove account cause we canceled pw window + return + gajim.connections[self.account].password = passphrase + gajim.connections[self.account].unregister_account( + self._on_remove_success) - dialogs.PassphraseDialog( - _('Password Required'), - _('Enter your password for account %s') % self.account, - _('Save password'), ok_handler=on_ok) - return - gajim.connections[self.account].unregister_account( - self._on_remove_success) - else: - self._on_remove_success(True) + dialogs.PassphraseDialog( + _('Password Required'), + _('Enter your password for account %s') % self.account, + _('Save password'), ok_handler=on_ok) + return + gajim.connections[self.account].unregister_account( + self._on_remove_success) + else: + self._on_remove_success(True) - if gajim.connections[self.account].connected: - dialogs.ConfirmationDialog( - _('Account "%s" is connected to the server') % self.account, - _('If you remove it, the connection will be lost.'), - on_response_ok=remove) - else: - remove() + if gajim.connections[self.account].connected: + dialogs.ConfirmationDialog( + _('Account "%s" is connected to the server') % self.account, + _('If you remove it, the connection will be lost.'), + on_response_ok=remove) + else: + remove() - def _on_remove_success(self, res): - # action of unregistration has failed, we don't remove the account - # Error message is send by connect_and_auth() - if not res: - return - # Close all opened windows - gajim.interface.roster.close_all(self.account, force = True) - gajim.connections[self.account].disconnect(on_purpose = True) - del gajim.connections[self.account] - gajim.logger.remove_roster(gajim.get_jid_from_account(self.account)) - gajim.config.del_per('accounts', self.account) - gajim.interface.save_config() - del gajim.interface.instances[self.account] - del gajim.interface.minimized_controls[self.account] - del gajim.nicks[self.account] - del gajim.block_signed_in_notifications[self.account] - del gajim.groups[self.account] - gajim.contacts.remove_account(self.account) - del gajim.gc_connected[self.account] - del gajim.automatic_rooms[self.account] - del gajim.to_be_removed[self.account] - del gajim.newly_added[self.account] - del gajim.sleeper_state[self.account] - del gajim.encrypted_chats[self.account] - del gajim.last_message_time[self.account] - del gajim.status_before_autoaway[self.account] - del gajim.transport_avatar[self.account] - del gajim.gajim_optional_features[self.account] - del gajim.caps_hash[self.account] - if len(gajim.connections) >= 2: # Do not merge accounts if only one exists - gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') - else: - gajim.interface.roster.regroup = False - gajim.interface.roster.setup_and_draw_roster() - gajim.interface.roster.set_actions_menu_needs_rebuild() - if 'accounts' in gajim.interface.instances: - gajim.interface.instances['accounts'].init_accounts() - gajim.interface.instances['accounts'].init_account() - self.window.destroy() + def _on_remove_success(self, res): + # action of unregistration has failed, we don't remove the account + # Error message is send by connect_and_auth() + if not res: + return + # Close all opened windows + gajim.interface.roster.close_all(self.account, force = True) + gajim.connections[self.account].disconnect(on_purpose = True) + del gajim.connections[self.account] + gajim.logger.remove_roster(gajim.get_jid_from_account(self.account)) + gajim.config.del_per('accounts', self.account) + gajim.interface.save_config() + del gajim.interface.instances[self.account] + del gajim.interface.minimized_controls[self.account] + del gajim.nicks[self.account] + del gajim.block_signed_in_notifications[self.account] + del gajim.groups[self.account] + gajim.contacts.remove_account(self.account) + del gajim.gc_connected[self.account] + del gajim.automatic_rooms[self.account] + del gajim.to_be_removed[self.account] + del gajim.newly_added[self.account] + del gajim.sleeper_state[self.account] + del gajim.encrypted_chats[self.account] + del gajim.last_message_time[self.account] + del gajim.status_before_autoaway[self.account] + del gajim.transport_avatar[self.account] + del gajim.gajim_optional_features[self.account] + del gajim.caps_hash[self.account] + if len(gajim.connections) >= 2: # Do not merge accounts if only one exists + gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') + else: + gajim.interface.roster.regroup = False + gajim.interface.roster.setup_and_draw_roster() + gajim.interface.roster.set_actions_menu_needs_rebuild() + if 'accounts' in gajim.interface.instances: + gajim.interface.instances['accounts'].init_accounts() + gajim.interface.instances['accounts'].init_account() + self.window.destroy() #---------- ManageBookmarksWindow class -------------# class ManageBookmarksWindow: - def __init__(self): - self.xml = gtkgui_helpers.get_glade('manage_bookmarks_window.glade') - self.window = self.xml.get_widget('manage_bookmarks_window') - self.window.set_transient_for(gajim.interface.roster.window) + def __init__(self): + self.xml = gtkgui_helpers.get_glade('manage_bookmarks_window.glade') + self.window = self.xml.get_widget('manage_bookmarks_window') + self.window.set_transient_for(gajim.interface.roster.window) - # Account-JID, RoomName, Room-JID, Autojoin, Minimize, Passowrd, Nick, - # Show_Status - self.treestore = gtk.TreeStore(str, str, str, bool, bool, str, str, str) - self.treestore.set_sort_column_id(1, gtk.SORT_ASCENDING) + # Account-JID, RoomName, Room-JID, Autojoin, Minimize, Passowrd, Nick, + # Show_Status + self.treestore = gtk.TreeStore(str, str, str, bool, bool, str, str, str) + self.treestore.set_sort_column_id(1, gtk.SORT_ASCENDING) - # Store bookmarks in treeview. - for account in gajim.connections: - if gajim.connections[account].connected <= 1: - continue - if gajim.connections[account].is_zeroconf: - continue - if not gajim.connections[account].private_storage_supported: - continue - iter_ = self.treestore.append(None, [None, account, None, None, - None, None, None, None]) + # Store bookmarks in treeview. + for account in gajim.connections: + if gajim.connections[account].connected <= 1: + continue + if gajim.connections[account].is_zeroconf: + continue + if not gajim.connections[account].private_storage_supported: + continue + iter_ = self.treestore.append(None, [None, account, None, None, + None, None, None, None]) - for bookmark in gajim.connections[account].bookmarks: - if bookmark['name'] == '': - # No name was given for this bookmark. - # Use the first part of JID instead... - name = bookmark['jid'].split("@")[0] - bookmark['name'] = name + for bookmark in gajim.connections[account].bookmarks: + if bookmark['name'] == '': + # No name was given for this bookmark. + # Use the first part of JID instead... + name = bookmark['jid'].split("@")[0] + bookmark['name'] = name - # make '1', '0', 'true', 'false' (or other) to True/False - autojoin = helpers.from_xs_boolean_to_python_boolean( - bookmark['autojoin']) + # make '1', '0', 'true', 'false' (or other) to True/False + autojoin = helpers.from_xs_boolean_to_python_boolean( + bookmark['autojoin']) - minimize = helpers.from_xs_boolean_to_python_boolean( - bookmark['minimize']) + minimize = helpers.from_xs_boolean_to_python_boolean( + bookmark['minimize']) - print_status = bookmark.get('print_status', '') - if print_status not in ('', 'all', 'in_and_out', 'none'): - print_status = '' - self.treestore.append(iter_, [ - account, - bookmark['name'], - bookmark['jid'], - autojoin, - minimize, - bookmark['password'], - bookmark['nick'], - print_status ]) + print_status = bookmark.get('print_status', '') + if print_status not in ('', 'all', 'in_and_out', 'none'): + print_status = '' + self.treestore.append(iter_, [ + account, + bookmark['name'], + bookmark['jid'], + autojoin, + minimize, + bookmark['password'], + bookmark['nick'], + print_status ]) - self.print_status_combobox = self.xml.get_widget('print_status_combobox') - model = gtk.ListStore(str, str) + self.print_status_combobox = self.xml.get_widget('print_status_combobox') + model = gtk.ListStore(str, str) - self.option_list = {'': _('Default'), 'all': Q_('?print_status:All'), - 'in_and_out': _('Enter and leave only'), - 'none': Q_('?print_status:None')} - opts = sorted(self.option_list.keys()) - for opt in opts: - model.append([self.option_list[opt], opt]) + self.option_list = {'': _('Default'), 'all': Q_('?print_status:All'), + 'in_and_out': _('Enter and leave only'), + 'none': Q_('?print_status:None')} + opts = sorted(self.option_list.keys()) + for opt in opts: + model.append([self.option_list[opt], opt]) - self.print_status_combobox.set_model(model) - self.print_status_combobox.set_active(1) + self.print_status_combobox.set_model(model) + self.print_status_combobox.set_active(1) - self.view = self.xml.get_widget('bookmarks_treeview') - self.view.set_model(self.treestore) - self.view.expand_all() + self.view = self.xml.get_widget('bookmarks_treeview') + self.view.set_model(self.treestore) + self.view.expand_all() - renderer = gtk.CellRendererText() - column = gtk.TreeViewColumn('Bookmarks', renderer, text=1) - self.view.append_column(column) + renderer = gtk.CellRendererText() + column = gtk.TreeViewColumn('Bookmarks', renderer, text=1) + self.view.append_column(column) - self.selection = self.view.get_selection() - self.selection.connect('changed', self.bookmark_selected) + self.selection = self.view.get_selection() + self.selection.connect('changed', self.bookmark_selected) - #Prepare input fields - self.title_entry = self.xml.get_widget('title_entry') - self.title_entry.connect('changed', self.on_title_entry_changed) - self.nick_entry = self.xml.get_widget('nick_entry') - self.nick_entry.connect('changed', self.on_nick_entry_changed) - self.server_entry = self.xml.get_widget('server_entry') - self.server_entry.connect('changed', self.on_server_entry_changed) - self.room_entry = self.xml.get_widget('room_entry') - self.room_entry.connect('changed', self.on_room_entry_changed) - self.pass_entry = self.xml.get_widget('pass_entry') - self.pass_entry.connect('changed', self.on_pass_entry_changed) - self.autojoin_checkbutton = self.xml.get_widget('autojoin_checkbutton') - self.minimize_checkbutton = self.xml.get_widget('minimize_checkbutton') + #Prepare input fields + self.title_entry = self.xml.get_widget('title_entry') + self.title_entry.connect('changed', self.on_title_entry_changed) + self.nick_entry = self.xml.get_widget('nick_entry') + self.nick_entry.connect('changed', self.on_nick_entry_changed) + self.server_entry = self.xml.get_widget('server_entry') + self.server_entry.connect('changed', self.on_server_entry_changed) + self.room_entry = self.xml.get_widget('room_entry') + self.room_entry.connect('changed', self.on_room_entry_changed) + self.pass_entry = self.xml.get_widget('pass_entry') + self.pass_entry.connect('changed', self.on_pass_entry_changed) + self.autojoin_checkbutton = self.xml.get_widget('autojoin_checkbutton') + self.minimize_checkbutton = self.xml.get_widget('minimize_checkbutton') - self.xml.signal_autoconnect(self) - self.window.show_all() + self.xml.signal_autoconnect(self) + self.window.show_all() - def on_bookmarks_treeview_button_press_event(self, widget, event): - (model, iter_) = self.selection.get_selected() - if not iter_: - # Removed a bookmark before - return + def on_bookmarks_treeview_button_press_event(self, widget, event): + (model, iter_) = self.selection.get_selected() + if not iter_: + # Removed a bookmark before + return - if model.iter_parent(iter_): - # The currently selected node is a bookmark - return not self.check_valid_bookmark() + if model.iter_parent(iter_): + # The currently selected node is a bookmark + return not self.check_valid_bookmark() - def on_manage_bookmarks_window_destroy(self, widget, event): - del gajim.interface.instances['manage_bookmarks'] + def on_manage_bookmarks_window_destroy(self, widget, event): + del gajim.interface.instances['manage_bookmarks'] - def on_add_bookmark_button_clicked(self, widget): - """ - Add a new bookmark - """ - # Get the account that is currently used - # (the parent of the currently selected item) - (model, iter_) = self.selection.get_selected() - if not iter_: # Nothing selected, do nothing - return + def on_add_bookmark_button_clicked(self, widget): + """ + Add a new bookmark + """ + # Get the account that is currently used + # (the parent of the currently selected item) + (model, iter_) = self.selection.get_selected() + if not iter_: # Nothing selected, do nothing + return - parent = model.iter_parent(iter_) + parent = model.iter_parent(iter_) - if parent: - # We got a bookmark selected, so we add_to the parent - add_to = parent - else: - # No parent, so we got an account -> add to this. - add_to = iter_ + if parent: + # We got a bookmark selected, so we add_to the parent + add_to = parent + else: + # No parent, so we got an account -> add to this. + add_to = iter_ - account = model[add_to][1].decode('utf-8') - nick = gajim.nicks[account] - iter_ = self.treestore.append(add_to, [account, _('New Group Chat'), '', - False, False, '', nick, 'in_and_out']) + account = model[add_to][1].decode('utf-8') + nick = gajim.nicks[account] + iter_ = self.treestore.append(add_to, [account, _('New Group Chat'), '', + False, False, '', nick, 'in_and_out']) - self.view.expand_row(model.get_path(add_to), True) - self.view.set_cursor(model.get_path(iter_)) + self.view.expand_row(model.get_path(add_to), True) + self.view.set_cursor(model.get_path(iter_)) - def on_remove_bookmark_button_clicked(self, widget): - """ - Remove selected bookmark - """ - (model, iter_) = self.selection.get_selected() - if not iter_: # Nothing selected - return + def on_remove_bookmark_button_clicked(self, widget): + """ + Remove selected bookmark + """ + (model, iter_) = self.selection.get_selected() + if not iter_: # Nothing selected + return - if not model.iter_parent(iter_): - # Don't remove account iters - return + if not model.iter_parent(iter_): + # Don't remove account iters + return - model.remove(iter_) - self.clear_fields() + model.remove(iter_) + self.clear_fields() - def check_valid_bookmark(self): - """ - Check if all neccessary fields are entered correctly - """ - (model, iter_) = self.selection.get_selected() + def check_valid_bookmark(self): + """ + Check if all neccessary fields are entered correctly + """ + (model, iter_) = self.selection.get_selected() - if not model.iter_parent(iter_): - #Account data can't be changed - return + if not model.iter_parent(iter_): + #Account data can't be changed + return - if self.server_entry.get_text().decode('utf-8') == '' or \ - self.room_entry.get_text().decode('utf-8') == '': - dialogs.ErrorDialog(_('This bookmark has invalid data'), - _('Please be sure to fill out server and room fields or remove this' - ' bookmark.')) - return False + if self.server_entry.get_text().decode('utf-8') == '' or \ + self.room_entry.get_text().decode('utf-8') == '': + dialogs.ErrorDialog(_('This bookmark has invalid data'), + _('Please be sure to fill out server and room fields or remove this' + ' bookmark.')) + return False - return True + return True - def on_ok_button_clicked(self, widget): - """ - Parse the treestore data into our new bookmarks array, then send the new - bookmarks to the server. - """ - (model, iter_) = self.selection.get_selected() - if iter_ and model.iter_parent(iter_): - #bookmark selected, check it - if not self.check_valid_bookmark(): - return + def on_ok_button_clicked(self, widget): + """ + Parse the treestore data into our new bookmarks array, then send the new + bookmarks to the server. + """ + (model, iter_) = self.selection.get_selected() + if iter_ and model.iter_parent(iter_): + #bookmark selected, check it + if not self.check_valid_bookmark(): + return - for account in self.treestore: - account_unicode = account[1].decode('utf-8') - gajim.connections[account_unicode].bookmarks = [] + for account in self.treestore: + account_unicode = account[1].decode('utf-8') + gajim.connections[account_unicode].bookmarks = [] - for bm in account.iterchildren(): - #Convert True/False/None to '1' or '0' - autojoin = unicode(int(bm[3])) - minimize = unicode(int(bm[4])) + for bm in account.iterchildren(): + #Convert True/False/None to '1' or '0' + autojoin = unicode(int(bm[3])) + minimize = unicode(int(bm[4])) - #create the bookmark-dict - bmdict = { 'name': bm[1], 'jid': bm[2], 'autojoin': autojoin, - 'minimize': minimize, 'password': bm[5], 'nick': bm[6], - 'print_status': bm[7]} + #create the bookmark-dict + bmdict = { 'name': bm[1], 'jid': bm[2], 'autojoin': autojoin, + 'minimize': minimize, 'password': bm[5], 'nick': bm[6], + 'print_status': bm[7]} - gajim.connections[account_unicode].bookmarks.append(bmdict) + gajim.connections[account_unicode].bookmarks.append(bmdict) - gajim.connections[account_unicode].store_bookmarks() - gajim.interface.roster.set_actions_menu_needs_rebuild() - self.window.destroy() + gajim.connections[account_unicode].store_bookmarks() + gajim.interface.roster.set_actions_menu_needs_rebuild() + self.window.destroy() - def on_cancel_button_clicked(self, widget): - self.window.destroy() + def on_cancel_button_clicked(self, widget): + self.window.destroy() - def bookmark_selected(self, selection): - """ - Fill in the bookmark's data into the fields. - """ - (model, iter_) = selection.get_selected() + def bookmark_selected(self, selection): + """ + Fill in the bookmark's data into the fields. + """ + (model, iter_) = selection.get_selected() - if not iter_: - # After removing the last bookmark for one account - # this will be None, so we will just: - return + if not iter_: + # After removing the last bookmark for one account + # this will be None, so we will just: + return - widgets = [ self.title_entry, self.nick_entry, self.room_entry, - self.server_entry, self.pass_entry, self.autojoin_checkbutton, - self.minimize_checkbutton, self.print_status_combobox] + widgets = [ self.title_entry, self.nick_entry, self.room_entry, + self.server_entry, self.pass_entry, self.autojoin_checkbutton, + self.minimize_checkbutton, self.print_status_combobox] - if model.iter_parent(iter_): - # make the fields sensitive - for field in widgets: - field.set_sensitive(True) - else: - # Top-level has no data (it's the account fields) - # clear fields & make them insensitive - self.clear_fields() - for field in widgets: - field.set_sensitive(False) - return + if model.iter_parent(iter_): + # make the fields sensitive + for field in widgets: + field.set_sensitive(True) + else: + # Top-level has no data (it's the account fields) + # clear fields & make them insensitive + self.clear_fields() + for field in widgets: + field.set_sensitive(False) + return - # Fill in the data for childs - self.title_entry.set_text(model[iter_][1]) - room_jid = model[iter_][2].decode('utf-8') - try: - (room, server) = room_jid.split('@') - except ValueError: - # We just added this one - room = '' - server = '' - self.room_entry.set_text(room) - self.server_entry.set_text(server) + # Fill in the data for childs + self.title_entry.set_text(model[iter_][1]) + room_jid = model[iter_][2].decode('utf-8') + try: + (room, server) = room_jid.split('@') + except ValueError: + # We just added this one + room = '' + server = '' + self.room_entry.set_text(room) + self.server_entry.set_text(server) - self.autojoin_checkbutton.set_active(model[iter_][3]) - self.minimize_checkbutton.set_active(model[iter_][4]) - # sensitive only if auto join is checked - self.minimize_checkbutton.set_sensitive(model[iter_][3]) + self.autojoin_checkbutton.set_active(model[iter_][3]) + self.minimize_checkbutton.set_active(model[iter_][4]) + # sensitive only if auto join is checked + self.minimize_checkbutton.set_sensitive(model[iter_][3]) - if model[iter_][5] is not None: - password = model[iter_][5].decode('utf-8') - else: - password = None + if model[iter_][5] is not None: + password = model[iter_][5].decode('utf-8') + else: + password = None - if password: - self.pass_entry.set_text(password) - else: - self.pass_entry.set_text('') - nick = model[iter_][6] - if nick: - nick = nick.decode('utf-8') - self.nick_entry.set_text(nick) - else: - self.nick_entry.set_text('') + if password: + self.pass_entry.set_text(password) + else: + self.pass_entry.set_text('') + nick = model[iter_][6] + if nick: + nick = nick.decode('utf-8') + self.nick_entry.set_text(nick) + else: + self.nick_entry.set_text('') - print_status = model[iter_][7] - opts = sorted(self.option_list.keys()) - self.print_status_combobox.set_active(opts.index(print_status)) + print_status = model[iter_][7] + opts = sorted(self.option_list.keys()) + self.print_status_combobox.set_active(opts.index(print_status)) - def on_title_entry_changed(self, widget): - (model, iter_) = self.selection.get_selected() - if iter_: # After removing a bookmark, we got nothing selected - if model.iter_parent(iter_): - # Don't clear the title field for account nodes - model[iter_][1] = self.title_entry.get_text() + def on_title_entry_changed(self, widget): + (model, iter_) = self.selection.get_selected() + if iter_: # After removing a bookmark, we got nothing selected + if model.iter_parent(iter_): + # Don't clear the title field for account nodes + model[iter_][1] = self.title_entry.get_text() - def on_nick_entry_changed(self, widget): - (model, iter_) = self.selection.get_selected() - if iter_: - nick = self.nick_entry.get_text().decode('utf-8') - try: - nick = helpers.parse_resource(nick) - except helpers.InvalidFormat, e: - dialogs.ErrorDialog(_('Invalid nickname'), - _('Character not allowed')) - self.nick_entry.set_text(model[iter_][6]) - return True - model[iter_][6] = nick + def on_nick_entry_changed(self, widget): + (model, iter_) = self.selection.get_selected() + if iter_: + nick = self.nick_entry.get_text().decode('utf-8') + try: + nick = helpers.parse_resource(nick) + except helpers.InvalidFormat, e: + dialogs.ErrorDialog(_('Invalid nickname'), + _('Character not allowed')) + self.nick_entry.set_text(model[iter_][6]) + return True + model[iter_][6] = nick - def on_server_entry_changed(self, widget): - (model, iter_) = self.selection.get_selected() - if iter_: - room_jid = self.room_entry.get_text().decode('utf-8').strip() + '@' + \ - self.server_entry.get_text().decode('utf-8').strip() - try: - room_jid = helpers.parse_resource(room_jid) - except helpers.InvalidFormat, e: - dialogs.ErrorDialog(_('Invalid server'), - _('Character not allowed')) - self.server_entry.set_text(model[iter_][2].split('@')[1]) - return True - model[iter_][2] = room_jid + def on_server_entry_changed(self, widget): + (model, iter_) = self.selection.get_selected() + if iter_: + room_jid = self.room_entry.get_text().decode('utf-8').strip() + '@' + \ + self.server_entry.get_text().decode('utf-8').strip() + try: + room_jid = helpers.parse_resource(room_jid) + except helpers.InvalidFormat, e: + dialogs.ErrorDialog(_('Invalid server'), + _('Character not allowed')) + self.server_entry.set_text(model[iter_][2].split('@')[1]) + return True + model[iter_][2] = room_jid - def on_room_entry_changed(self, widget): - (model, iter_) = self.selection.get_selected() - if iter_: - room_jid = self.room_entry.get_text().decode('utf-8').strip() + '@' + \ - self.server_entry.get_text().decode('utf-8').strip() - try: - room_jid = helpers.parse_resource(room_jid) - except helpers.InvalidFormat, e: - dialogs.ErrorDialog(_('Invalid room'), - _('Character not allowed')) - self.room_entry.set_text(model[iter_][2].split('@')[0]) - return True - model[iter_][2] = room_jid + def on_room_entry_changed(self, widget): + (model, iter_) = self.selection.get_selected() + if iter_: + room_jid = self.room_entry.get_text().decode('utf-8').strip() + '@' + \ + self.server_entry.get_text().decode('utf-8').strip() + try: + room_jid = helpers.parse_resource(room_jid) + except helpers.InvalidFormat, e: + dialogs.ErrorDialog(_('Invalid room'), + _('Character not allowed')) + self.room_entry.set_text(model[iter_][2].split('@')[0]) + return True + model[iter_][2] = room_jid - def on_pass_entry_changed(self, widget): - (model, iter_) = self.selection.get_selected() - if iter_: - model[iter_][5] = self.pass_entry.get_text() + def on_pass_entry_changed(self, widget): + (model, iter_) = self.selection.get_selected() + if iter_: + model[iter_][5] = self.pass_entry.get_text() - def on_autojoin_checkbutton_toggled(self, widget): - (model, iter_) = self.selection.get_selected() - if iter_: - model[iter_][3] = self.autojoin_checkbutton.get_active() - self.minimize_checkbutton.set_sensitive(model[iter_][3]) + def on_autojoin_checkbutton_toggled(self, widget): + (model, iter_) = self.selection.get_selected() + if iter_: + model[iter_][3] = self.autojoin_checkbutton.get_active() + self.minimize_checkbutton.set_sensitive(model[iter_][3]) - def on_minimize_checkbutton_toggled(self, widget): - (model, iter_) = self.selection.get_selected() - if iter_: - model[iter_][4] = self.minimize_checkbutton.get_active() + def on_minimize_checkbutton_toggled(self, widget): + (model, iter_) = self.selection.get_selected() + if iter_: + model[iter_][4] = self.minimize_checkbutton.get_active() - def on_print_status_combobox_changed(self, widget): - active = widget.get_active() - model = widget.get_model() - print_status = model[active][1] - (model2, iter_) = self.selection.get_selected() - if iter_: - model2[iter_][7] = print_status + def on_print_status_combobox_changed(self, widget): + active = widget.get_active() + model = widget.get_model() + print_status = model[active][1] + (model2, iter_) = self.selection.get_selected() + if iter_: + model2[iter_][7] = print_status - def clear_fields(self): - widgets = [ self.title_entry, self.nick_entry, self.room_entry, - self.server_entry, self.pass_entry ] - for field in widgets: - field.set_text('') - self.autojoin_checkbutton.set_active(False) - self.minimize_checkbutton.set_active(False) - self.print_status_combobox.set_active(1) + def clear_fields(self): + widgets = [ self.title_entry, self.nick_entry, self.room_entry, + self.server_entry, self.pass_entry ] + for field in widgets: + field.set_text('') + self.autojoin_checkbutton.set_active(False) + self.minimize_checkbutton.set_active(False) + self.print_status_combobox.set_active(1) class AccountCreationWizardWindow: - def __init__(self): - self.xml = gtkgui_helpers.get_glade( - 'account_creation_wizard_window.glade') - self.window = self.xml.get_widget('account_creation_wizard_window') - self.window.set_transient_for(gajim.interface.roster.window) + def __init__(self): + self.xml = gtkgui_helpers.get_glade( + 'account_creation_wizard_window.glade') + self.window = self.xml.get_widget('account_creation_wizard_window') + self.window.set_transient_for(gajim.interface.roster.window) - completion = gtk.EntryCompletion() - # Connect events from comboboxentry.child - server_comboboxentry = self.xml.get_widget('server_comboboxentry') - entry = server_comboboxentry.child - entry.connect('key_press_event', - self.on_server_comboboxentry_key_press_event, server_comboboxentry) - entry.set_completion(completion) - # Do the same for the other server comboboxentry - server_comboboxentry1 = self.xml.get_widget('server_comboboxentry1') - entry = server_comboboxentry1.child - entry.connect('key_press_event', - self.on_server_comboboxentry_key_press_event, server_comboboxentry1) - entry.set_completion(completion) + completion = gtk.EntryCompletion() + # Connect events from comboboxentry.child + server_comboboxentry = self.xml.get_widget('server_comboboxentry') + entry = server_comboboxentry.child + entry.connect('key_press_event', + self.on_server_comboboxentry_key_press_event, server_comboboxentry) + entry.set_completion(completion) + # Do the same for the other server comboboxentry + server_comboboxentry1 = self.xml.get_widget('server_comboboxentry1') + entry = server_comboboxentry1.child + entry.connect('key_press_event', + self.on_server_comboboxentry_key_press_event, server_comboboxentry1) + entry.set_completion(completion) - self.update_proxy_list() + self.update_proxy_list() - # parse servers.xml - servers_xml = os.path.join(gajim.DATA_DIR, 'other', 'servers.xml') - servers = gtkgui_helpers.parse_server_xml(servers_xml) - servers_model = gtk.ListStore(str, int) - for server in servers: - if not server[2]['hidden']: - servers_model.append((str(server[0]), int(server[1]))) + # parse servers.xml + servers_xml = os.path.join(gajim.DATA_DIR, 'other', 'servers.xml') + servers = gtkgui_helpers.parse_server_xml(servers_xml) + servers_model = gtk.ListStore(str, int) + for server in servers: + if not server[2]['hidden']: + servers_model.append((str(server[0]), int(server[1]))) - completion.set_model(servers_model) - completion.set_text_column(0) + completion.set_model(servers_model) + completion.set_text_column(0) - # Put servers into comboboxentries - server_comboboxentry.set_model(servers_model) - server_comboboxentry.set_text_column(0) - server_comboboxentry1.set_model(servers_model) - server_comboboxentry1.set_text_column(0) + # Put servers into comboboxentries + server_comboboxentry.set_model(servers_model) + server_comboboxentry.set_text_column(0) + server_comboboxentry1.set_model(servers_model) + server_comboboxentry1.set_text_column(0) - # Generic widgets - self.notebook = self.xml.get_widget('notebook') - self.cancel_button = self.xml.get_widget('cancel_button') - self.back_button = self.xml.get_widget('back_button') - self.forward_button = self.xml.get_widget('forward_button') - self.finish_button = self.xml.get_widget('finish_button') - self.advanced_button = self.xml.get_widget('advanced_button') - self.finish_label = self.xml.get_widget('finish_label') - self.go_online_checkbutton = self.xml.get_widget( - 'go_online_checkbutton') - self.show_vcard_checkbutton = self.xml.get_widget( - 'show_vcard_checkbutton') - self.progressbar = self.xml.get_widget('progressbar') + # Generic widgets + self.notebook = self.xml.get_widget('notebook') + self.cancel_button = self.xml.get_widget('cancel_button') + self.back_button = self.xml.get_widget('back_button') + self.forward_button = self.xml.get_widget('forward_button') + self.finish_button = self.xml.get_widget('finish_button') + self.advanced_button = self.xml.get_widget('advanced_button') + self.finish_label = self.xml.get_widget('finish_label') + self.go_online_checkbutton = self.xml.get_widget( + 'go_online_checkbutton') + self.show_vcard_checkbutton = self.xml.get_widget( + 'show_vcard_checkbutton') + self.progressbar = self.xml.get_widget('progressbar') - # some vars - self.update_progressbar_timeout_id = None + # some vars + self.update_progressbar_timeout_id = None - self.notebook.set_current_page(0) - self.xml.signal_autoconnect(self) - self.window.show_all() + self.notebook.set_current_page(0) + self.xml.signal_autoconnect(self) + self.window.show_all() - def on_wizard_window_destroy(self, widget): - page = self.notebook.get_current_page() - if page in (4, 5) and self.account in gajim.connections: - # connection instance is saved in gajim.connections and we canceled the - # addition of the account - del gajim.connections[self.account] - if self.account in gajim.config.get_per('accounts'): - gajim.config.del_per('accounts', self.account) - del gajim.interface.instances['account_creation_wizard'] + def on_wizard_window_destroy(self, widget): + page = self.notebook.get_current_page() + if page in (4, 5) and self.account in gajim.connections: + # connection instance is saved in gajim.connections and we canceled the + # addition of the account + del gajim.connections[self.account] + if self.account in gajim.config.get_per('accounts'): + gajim.config.del_per('accounts', self.account) + del gajim.interface.instances['account_creation_wizard'] - def on_register_server_features_button_clicked(self, widget): - helpers.launch_browser_mailer('url', - 'http://www.jabber.org/network/oldnetwork.shtml') + def on_register_server_features_button_clicked(self, widget): + helpers.launch_browser_mailer('url', + 'http://www.jabber.org/network/oldnetwork.shtml') - def on_save_password_checkbutton_toggled(self, widget): - self.xml.get_widget('password_entry').grab_focus() + def on_save_password_checkbutton_toggled(self, widget): + self.xml.get_widget('password_entry').grab_focus() - def on_cancel_button_clicked(self, widget): - self.window.destroy() + def on_cancel_button_clicked(self, widget): + self.window.destroy() - def on_back_button_clicked(self, widget): - cur_page = self.notebook.get_current_page() - if cur_page in (1, 2): - self.notebook.set_current_page(0) - self.back_button.set_sensitive(False) - elif cur_page == 3: - self.xml.get_widget('form_vbox').remove(self.data_form_widget) - self.notebook.set_current_page(2) # show server page - elif cur_page == 4: - if self.account in gajim.connections: - del gajim.connections[self.account] - self.notebook.set_current_page(2) - self.xml.get_widget('form_vbox').remove(self.data_form_widget) - elif cur_page == 6: # finish page - self.forward_button.show() - if self.modify: - self.notebook.set_current_page(1) # Go to parameters page - else: - self.notebook.set_current_page(2) # Go to server page + def on_back_button_clicked(self, widget): + cur_page = self.notebook.get_current_page() + if cur_page in (1, 2): + self.notebook.set_current_page(0) + self.back_button.set_sensitive(False) + elif cur_page == 3: + self.xml.get_widget('form_vbox').remove(self.data_form_widget) + self.notebook.set_current_page(2) # show server page + elif cur_page == 4: + if self.account in gajim.connections: + del gajim.connections[self.account] + self.notebook.set_current_page(2) + self.xml.get_widget('form_vbox').remove(self.data_form_widget) + elif cur_page == 6: # finish page + self.forward_button.show() + if self.modify: + self.notebook.set_current_page(1) # Go to parameters page + else: + self.notebook.set_current_page(2) # Go to server page - def on_anonymous_checkbutton1_toggled(self, widget): - active = widget.get_active() - self.xml.get_widget('username_entry').set_sensitive(not active) - self.xml.get_widget('password_entry').set_sensitive(not active) - self.xml.get_widget('save_password_checkbutton').set_sensitive(not active) + def on_anonymous_checkbutton1_toggled(self, widget): + active = widget.get_active() + self.xml.get_widget('username_entry').set_sensitive(not active) + self.xml.get_widget('password_entry').set_sensitive(not active) + self.xml.get_widget('save_password_checkbutton').set_sensitive(not active) - def show_finish_page(self): - self.cancel_button.hide() - self.back_button.hide() - self.forward_button.hide() - if self.modify: - finish_text = '%s\n\n%s' % ( - _('Account has been added successfully'), - _('You can set advanced account options by pressing the ' - 'Advanced button, or later by choosing the Accounts menu item ' - 'under the Edit menu from the main window.')) - else: - finish_text = '%s\n\n%s' % ( - _('Your new account has been created successfully'), - _('You can set advanced account options by pressing the Advanced ' - 'button, or later by choosing the Accounts menu item under the Edit' - ' menu from the main window.')) - self.finish_label.set_markup(finish_text) - self.finish_button.show() - self.finish_button.set_property('has-default', True) - self.advanced_button.show() - self.go_online_checkbutton.show() - img = self.xml.get_widget('finish_image') - if self.modify: - img.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_DIALOG) - else: - path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'gajim.png') - img.set_from_file(path_to_file) - self.show_vcard_checkbutton.set_active(not self.modify) - self.notebook.set_current_page(6) # show finish page + def show_finish_page(self): + self.cancel_button.hide() + self.back_button.hide() + self.forward_button.hide() + if self.modify: + finish_text = '%s\n\n%s' % ( + _('Account has been added successfully'), + _('You can set advanced account options by pressing the ' + 'Advanced button, or later by choosing the Accounts menu item ' + 'under the Edit menu from the main window.')) + else: + finish_text = '%s\n\n%s' % ( + _('Your new account has been created successfully'), + _('You can set advanced account options by pressing the Advanced ' + 'button, or later by choosing the Accounts menu item under the Edit' + ' menu from the main window.')) + self.finish_label.set_markup(finish_text) + self.finish_button.show() + self.finish_button.set_property('has-default', True) + self.advanced_button.show() + self.go_online_checkbutton.show() + img = self.xml.get_widget('finish_image') + if self.modify: + img.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_DIALOG) + else: + path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'gajim.png') + img.set_from_file(path_to_file) + self.show_vcard_checkbutton.set_active(not self.modify) + self.notebook.set_current_page(6) # show finish page - def on_forward_button_clicked(self, widget): - cur_page = self.notebook.get_current_page() + def on_forward_button_clicked(self, widget): + cur_page = self.notebook.get_current_page() - if cur_page == 0: - widget = self.xml.get_widget('use_existing_account_radiobutton') - if widget.get_active(): - self.modify = True - self.notebook.set_current_page(1) - else: - self.modify = False - self.notebook.set_current_page(2) - self.back_button.set_sensitive(True) - return + if cur_page == 0: + widget = self.xml.get_widget('use_existing_account_radiobutton') + if widget.get_active(): + self.modify = True + self.notebook.set_current_page(1) + else: + self.modify = False + self.notebook.set_current_page(2) + self.back_button.set_sensitive(True) + return - elif cur_page == 1: - # We are adding an existing account - anonymous = self.xml.get_widget('anonymous_checkbutton1').get_active() - username = self.xml.get_widget('username_entry').get_text().decode( - 'utf-8').strip() - if not username and not anonymous: - pritext = _('Invalid username') - sectext = _( - 'You must provide a username to configure this account.') - dialogs.ErrorDialog(pritext, sectext) - return - server = self.xml.get_widget('server_comboboxentry').child.get_text().\ - decode('utf-8').strip() - savepass = self.xml.get_widget('save_password_checkbutton').\ - get_active() - password = self.xml.get_widget('password_entry').get_text().decode( - 'utf-8') + elif cur_page == 1: + # We are adding an existing account + anonymous = self.xml.get_widget('anonymous_checkbutton1').get_active() + username = self.xml.get_widget('username_entry').get_text().decode( + 'utf-8').strip() + if not username and not anonymous: + pritext = _('Invalid username') + sectext = _( + 'You must provide a username to configure this account.') + dialogs.ErrorDialog(pritext, sectext) + return + server = self.xml.get_widget('server_comboboxentry').child.get_text().\ + decode('utf-8').strip() + savepass = self.xml.get_widget('save_password_checkbutton').\ + get_active() + password = self.xml.get_widget('password_entry').get_text().decode( + 'utf-8') - jid = username + '@' + server - # check if jid is conform to RFC and stringprep it - try: - jid = helpers.parse_jid(jid) - except helpers.InvalidFormat, s: - pritext = _('Invalid Jabber ID') - dialogs.ErrorDialog(pritext, str(s)) - return + jid = username + '@' + server + # check if jid is conform to RFC and stringprep it + try: + jid = helpers.parse_jid(jid) + except helpers.InvalidFormat, s: + pritext = _('Invalid Jabber ID') + dialogs.ErrorDialog(pritext, str(s)) + return - self.account = server - i = 1 - while self.account in gajim.connections: - self.account = server + str(i) - i += 1 + self.account = server + i = 1 + while self.account in gajim.connections: + self.account = server + str(i) + i += 1 - username, server = gajim.get_name_and_server_from_jid(jid) - if self.xml.get_widget('anonymous_checkbutton1').get_active(): - self.save_account('', server, False, '', anonymous=True) - else: - self.save_account(username, server, savepass, password) - self.show_finish_page() - elif cur_page == 2: - # We are creating a new account - server = self.xml.get_widget('server_comboboxentry1').child.get_text()\ - .decode('utf-8') + username, server = gajim.get_name_and_server_from_jid(jid) + if self.xml.get_widget('anonymous_checkbutton1').get_active(): + self.save_account('', server, False, '', anonymous=True) + else: + self.save_account(username, server, savepass, password) + self.show_finish_page() + elif cur_page == 2: + # We are creating a new account + server = self.xml.get_widget('server_comboboxentry1').child.get_text()\ + .decode('utf-8') - if not server: - dialogs.ErrorDialog(_('Invalid server'), - _('Please provide a server on which you want to register.')) - return - self.account = server - i = 1 - while self.account in gajim.connections: - self.account = server + str(i) - i += 1 + if not server: + dialogs.ErrorDialog(_('Invalid server'), + _('Please provide a server on which you want to register.')) + return + self.account = server + i = 1 + while self.account in gajim.connections: + self.account = server + str(i) + i += 1 - config = self.get_config('', server, '', '') - # Get advanced options - proxies_combobox = self.xml.get_widget('proxies_combobox') - active = proxies_combobox.get_active() - proxy = proxies_combobox.get_model()[active][0].decode('utf-8') - if proxy == _('None'): - proxy = '' - config['proxy'] = proxy + config = self.get_config('', server, '', '') + # Get advanced options + proxies_combobox = self.xml.get_widget('proxies_combobox') + active = proxies_combobox.get_active() + proxy = proxies_combobox.get_model()[active][0].decode('utf-8') + if proxy == _('None'): + proxy = '' + config['proxy'] = proxy - config['use_custom_host'] = self.xml.get_widget( - 'custom_host_port_checkbutton').get_active() - custom_port = self.xml.get_widget('custom_port_entry').get_text() - try: - custom_port = int(custom_port) - except Exception: - dialogs.ErrorDialog(_('Invalid entry'), - _('Custom port must be a port number.')) - return - config['custom_port'] = custom_port - config['custom_host'] = self.xml.get_widget( - 'custom_host_entry').get_text().decode('utf-8') + config['use_custom_host'] = self.xml.get_widget( + 'custom_host_port_checkbutton').get_active() + custom_port = self.xml.get_widget('custom_port_entry').get_text() + try: + custom_port = int(custom_port) + except Exception: + dialogs.ErrorDialog(_('Invalid entry'), + _('Custom port must be a port number.')) + return + config['custom_port'] = custom_port + config['custom_host'] = self.xml.get_widget( + 'custom_host_entry').get_text().decode('utf-8') - if self.xml.get_widget('anonymous_checkbutton2').get_active(): - self.modify = True - self.save_account('', server, False, '', anonymous=True) - self.show_finish_page() - else: - self.notebook.set_current_page(5) # show creating page - self.back_button.hide() - self.forward_button.hide() - self.update_progressbar_timeout_id = gobject.timeout_add(100, - self.update_progressbar) - # Get form from serveur - con = connection.Connection(self.account) - gajim.connections[self.account] = con - con.new_account(self.account, config) - elif cur_page == 3: - checked = self.xml.get_widget('ssl_checkbutton').get_active() - if checked: - hostname = gajim.connections[self.account].new_account_info[ - 'hostname'] - # Check if cert is already in file - certs = '' - if os.path.isfile(gajim.MY_CACERTS): - f = open(gajim.MY_CACERTS) - certs = f.read() - f.close() - if self.ssl_cert in certs: - dialogs.ErrorDialog(_('Certificate Already in File'), - _('This certificate is already in file %s, so it\'s not added again.') % gajim.MY_CACERTS) - else: - f = open(gajim.MY_CACERTS, 'a') - f.write(hostname + '\n') - f.write(self.ssl_cert + '\n\n') - f.close() - gajim.connections[self.account].new_account_info[ - 'ssl_fingerprint_sha1'] = self.ssl_fingerprint - self.notebook.set_current_page(4) # show fom page - elif cur_page == 4: - if self.is_form: - form = self.data_form_widget.data_form - else: - form = self.data_form_widget.get_infos() - gajim.connections[self.account].send_new_account_infos(form, - self.is_form) - self.xml.get_widget('form_vbox').remove(self.data_form_widget) - self.xml.get_widget('progressbar_label').set_markup('Account is being created\n\nPlease wait...') - self.notebook.set_current_page(5) # show creating page - self.back_button.hide() - self.forward_button.hide() - self.update_progressbar_timeout_id = gobject.timeout_add(100, - self.update_progressbar) + if self.xml.get_widget('anonymous_checkbutton2').get_active(): + self.modify = True + self.save_account('', server, False, '', anonymous=True) + self.show_finish_page() + else: + self.notebook.set_current_page(5) # show creating page + self.back_button.hide() + self.forward_button.hide() + self.update_progressbar_timeout_id = gobject.timeout_add(100, + self.update_progressbar) + # Get form from serveur + con = connection.Connection(self.account) + gajim.connections[self.account] = con + con.new_account(self.account, config) + elif cur_page == 3: + checked = self.xml.get_widget('ssl_checkbutton').get_active() + if checked: + hostname = gajim.connections[self.account].new_account_info[ + 'hostname'] + # Check if cert is already in file + certs = '' + if os.path.isfile(gajim.MY_CACERTS): + f = open(gajim.MY_CACERTS) + certs = f.read() + f.close() + if self.ssl_cert in certs: + dialogs.ErrorDialog(_('Certificate Already in File'), + _('This certificate is already in file %s, so it\'s not added again.') % gajim.MY_CACERTS) + else: + f = open(gajim.MY_CACERTS, 'a') + f.write(hostname + '\n') + f.write(self.ssl_cert + '\n\n') + f.close() + gajim.connections[self.account].new_account_info[ + 'ssl_fingerprint_sha1'] = self.ssl_fingerprint + self.notebook.set_current_page(4) # show fom page + elif cur_page == 4: + if self.is_form: + form = self.data_form_widget.data_form + else: + form = self.data_form_widget.get_infos() + gajim.connections[self.account].send_new_account_infos(form, + self.is_form) + self.xml.get_widget('form_vbox').remove(self.data_form_widget) + self.xml.get_widget('progressbar_label').set_markup('Account is being created\n\nPlease wait...') + self.notebook.set_current_page(5) # show creating page + self.back_button.hide() + self.forward_button.hide() + self.update_progressbar_timeout_id = gobject.timeout_add(100, + self.update_progressbar) - def update_proxy_list(self): - proxies_combobox = self.xml.get_widget('proxies_combobox') - model = gtk.ListStore(str) - proxies_combobox.set_model(model) - l = gajim.config.get_per('proxies') - l.insert(0, _('None')) - for i in xrange(len(l)): - model.append([l[i]]) - proxies_combobox.set_active(0) + def update_proxy_list(self): + proxies_combobox = self.xml.get_widget('proxies_combobox') + model = gtk.ListStore(str) + proxies_combobox.set_model(model) + l = gajim.config.get_per('proxies') + l.insert(0, _('None')) + for i in xrange(len(l)): + model.append([l[i]]) + proxies_combobox.set_active(0) - def on_manage_proxies_button_clicked(self, widget): - if 'manage_proxies' in gajim.interface.instances: - gajim.interface.instances['manage_proxies'].window.present() - else: - gajim.interface.instances['manage_proxies'] = \ - ManageProxiesWindow() + def on_manage_proxies_button_clicked(self, widget): + if 'manage_proxies' in gajim.interface.instances: + gajim.interface.instances['manage_proxies'].window.present() + else: + gajim.interface.instances['manage_proxies'] = \ + ManageProxiesWindow() - def on_custom_host_port_checkbutton_toggled(self, widget): - self.xml.get_widget('custom_host_hbox').set_sensitive(widget.get_active()) + def on_custom_host_port_checkbutton_toggled(self, widget): + self.xml.get_widget('custom_host_hbox').set_sensitive(widget.get_active()) - def update_progressbar(self): - self.progressbar.pulse() - return True # loop forever + def update_progressbar(self): + self.progressbar.pulse() + return True # loop forever - def new_acc_connected(self, form, is_form, ssl_msg, ssl_err, ssl_cert, - ssl_fingerprint): - """ - Connection to server succeded, present the form to the user - """ - if self.update_progressbar_timeout_id is not None: - gobject.source_remove(self.update_progressbar_timeout_id) - self.back_button.show() - self.forward_button.show() - self.is_form = is_form - if is_form: - dataform = dataforms.ExtendForm(node = form) - self.data_form_widget = dataforms_widget.DataFormWidget(dataform) - else: - self.data_form_widget = FakeDataForm(form) - self.data_form_widget.show_all() - self.xml.get_widget('form_vbox').pack_start(self.data_form_widget) - self.ssl_fingerprint = ssl_fingerprint - self.ssl_cert = ssl_cert - if ssl_msg: - # An SSL warning occured, show it - hostname = gajim.connections[self.account].new_account_info['hostname'] - self.xml.get_widget('ssl_label').set_markup(_('Security Warning' - '\n\nThe authenticity of the %(hostname)s SSL certificate could be ' - 'invalid.\nSSL Error: %(error)s\n' - 'Do you still want to connect to this server?') % { - 'hostname': hostname, 'error': ssl_msg}) - if ssl_err in (18, 27): - text = _('Add this certificate to the list of trusted certificates.\nSHA1 fingerprint of the certificate:\n%s') % ssl_fingerprint - self.xml.get_widget('ssl_checkbutton').set_label(text) - else: - self.xml.get_widget('ssl_checkbutton').set_no_show_all(True) - self.xml.get_widget('ssl_checkbutton').hide() - self.notebook.set_current_page(3) # show SSL page - else: - self.notebook.set_current_page(4) # show form page + def new_acc_connected(self, form, is_form, ssl_msg, ssl_err, ssl_cert, + ssl_fingerprint): + """ + Connection to server succeded, present the form to the user + """ + if self.update_progressbar_timeout_id is not None: + gobject.source_remove(self.update_progressbar_timeout_id) + self.back_button.show() + self.forward_button.show() + self.is_form = is_form + if is_form: + dataform = dataforms.ExtendForm(node = form) + self.data_form_widget = dataforms_widget.DataFormWidget(dataform) + else: + self.data_form_widget = FakeDataForm(form) + self.data_form_widget.show_all() + self.xml.get_widget('form_vbox').pack_start(self.data_form_widget) + self.ssl_fingerprint = ssl_fingerprint + self.ssl_cert = ssl_cert + if ssl_msg: + # An SSL warning occured, show it + hostname = gajim.connections[self.account].new_account_info['hostname'] + self.xml.get_widget('ssl_label').set_markup(_('Security Warning' + '\n\nThe authenticity of the %(hostname)s SSL certificate could be ' + 'invalid.\nSSL Error: %(error)s\n' + 'Do you still want to connect to this server?') % { + 'hostname': hostname, 'error': ssl_msg}) + if ssl_err in (18, 27): + text = _('Add this certificate to the list of trusted certificates.\nSHA1 fingerprint of the certificate:\n%s') % ssl_fingerprint + self.xml.get_widget('ssl_checkbutton').set_label(text) + else: + self.xml.get_widget('ssl_checkbutton').set_no_show_all(True) + self.xml.get_widget('ssl_checkbutton').hide() + self.notebook.set_current_page(3) # show SSL page + else: + self.notebook.set_current_page(4) # show form page - def new_acc_not_connected(self, reason): - """ - Account creation failed: connection to server failed - """ - if self.account not in gajim.connections: - return - if self.update_progressbar_timeout_id is not None: - gobject.source_remove(self.update_progressbar_timeout_id) - del gajim.connections[self.account] - if self.account in gajim.config.get_per('accounts'): - gajim.config.del_per('accounts', self.account) - self.back_button.show() - self.cancel_button.show() - self.go_online_checkbutton.hide() - self.show_vcard_checkbutton.hide() - img = self.xml.get_widget('finish_image') - img.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_DIALOG) - finish_text = '%s\n\n%s' % ( - _('An error occurred during account creation') , reason) - self.finish_label.set_markup(finish_text) - self.notebook.set_current_page(6) # show finish page + def new_acc_not_connected(self, reason): + """ + Account creation failed: connection to server failed + """ + if self.account not in gajim.connections: + return + if self.update_progressbar_timeout_id is not None: + gobject.source_remove(self.update_progressbar_timeout_id) + del gajim.connections[self.account] + if self.account in gajim.config.get_per('accounts'): + gajim.config.del_per('accounts', self.account) + self.back_button.show() + self.cancel_button.show() + self.go_online_checkbutton.hide() + self.show_vcard_checkbutton.hide() + img = self.xml.get_widget('finish_image') + img.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_DIALOG) + finish_text = '%s\n\n%s' % ( + _('An error occurred during account creation'), reason) + self.finish_label.set_markup(finish_text) + self.notebook.set_current_page(6) # show finish page - def acc_is_ok(self, config): - """ - Account creation succeeded - """ - self.create_vars(config) - self.show_finish_page() + def acc_is_ok(self, config): + """ + Account creation succeeded + """ + self.create_vars(config) + self.show_finish_page() - if self.update_progressbar_timeout_id is not None: - gobject.source_remove(self.update_progressbar_timeout_id) + if self.update_progressbar_timeout_id is not None: + gobject.source_remove(self.update_progressbar_timeout_id) - def acc_is_not_ok(self, reason): - """ - Account creation failed - """ - self.back_button.show() - self.cancel_button.show() - self.go_online_checkbutton.hide() - self.show_vcard_checkbutton.hide() - del gajim.connections[self.account] - if self.account in gajim.config.get_per('accounts'): - gajim.config.del_per('accounts', self.account) - img = self.xml.get_widget('finish_image') - img.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_DIALOG) - finish_text = '%s\n\n%s' % (_('An error occurred during ' - 'account creation') , reason) - self.finish_label.set_markup(finish_text) - self.notebook.set_current_page(6) # show finish page + def acc_is_not_ok(self, reason): + """ + Account creation failed + """ + self.back_button.show() + self.cancel_button.show() + self.go_online_checkbutton.hide() + self.show_vcard_checkbutton.hide() + del gajim.connections[self.account] + if self.account in gajim.config.get_per('accounts'): + gajim.config.del_per('accounts', self.account) + img = self.xml.get_widget('finish_image') + img.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_DIALOG) + finish_text = '%s\n\n%s' % (_('An error occurred during ' + 'account creation'), reason) + self.finish_label.set_markup(finish_text) + self.notebook.set_current_page(6) # show finish page - if self.update_progressbar_timeout_id is not None: - gobject.source_remove(self.update_progressbar_timeout_id) + if self.update_progressbar_timeout_id is not None: + gobject.source_remove(self.update_progressbar_timeout_id) - def on_advanced_button_clicked(self, widget): - if 'accounts' in gajim.interface.instances: - gajim.interface.instances['accounts'].window.present() - else: - gajim.interface.instances['accounts'] = AccountsWindow() - gajim.interface.instances['accounts'].select_account( - self.account) - self.window.destroy() + def on_advanced_button_clicked(self, widget): + if 'accounts' in gajim.interface.instances: + gajim.interface.instances['accounts'].window.present() + else: + gajim.interface.instances['accounts'] = AccountsWindow() + gajim.interface.instances['accounts'].select_account( + self.account) + self.window.destroy() - def on_finish_button_clicked(self, widget): - go_online = self.xml.get_widget('go_online_checkbutton').get_active() - show_vcard = self.xml.get_widget('show_vcard_checkbutton').get_active() - self.window.destroy() - if show_vcard: - gajim.interface.show_vcard_when_connect.append(self.account) - if go_online: - gajim.interface.roster.send_status(self.account, 'online', '') + def on_finish_button_clicked(self, widget): + go_online = self.xml.get_widget('go_online_checkbutton').get_active() + show_vcard = self.xml.get_widget('show_vcard_checkbutton').get_active() + self.window.destroy() + if show_vcard: + gajim.interface.show_vcard_when_connect.append(self.account) + if go_online: + gajim.interface.roster.send_status(self.account, 'online', '') - def on_username_entry_key_press_event(self, widget, event): - # Check for pressed @ and jump to combobox if found - if event.keyval == gtk.keysyms.at: - combobox = self.xml.get_widget('server_comboboxentry') - combobox.grab_focus() - combobox.child.set_position(-1) - return True + def on_username_entry_key_press_event(self, widget, event): + # Check for pressed @ and jump to combobox if found + if event.keyval == gtk.keysyms.at: + combobox = self.xml.get_widget('server_comboboxentry') + combobox.grab_focus() + combobox.child.set_position(-1) + return True - def on_server_comboboxentry_key_press_event(self, widget, event, combobox): - # If backspace is pressed in empty field, return to the nick entry field - backspace = event.keyval == gtk.keysyms.BackSpace - empty = len(combobox.get_active_text()) == 0 - if backspace and empty and self.modify: - username_entry = self.xml.get_widget('username_entry') - username_entry.grab_focus() - username_entry.set_position(-1) - return True + def on_server_comboboxentry_key_press_event(self, widget, event, combobox): + # If backspace is pressed in empty field, return to the nick entry field + backspace = event.keyval == gtk.keysyms.BackSpace + empty = len(combobox.get_active_text()) == 0 + if backspace and empty and self.modify: + username_entry = self.xml.get_widget('username_entry') + username_entry.grab_focus() + username_entry.set_position(-1) + return True - def get_config(self, login, server, savepass, password, anonymous=False): - config = {} - config['name'] = login - config['hostname'] = server - config['savepass'] = savepass - config['password'] = password - config['resource'] = 'Gajim' - config['anonymous_auth'] = anonymous - config['priority'] = 5 - config['autoconnect'] = True - config['no_log_for'] = '' - config['sync_with_global_status'] = True - config['proxy'] = '' - config['usessl'] = False - config['use_custom_host'] = False - config['custom_port'] = 0 - config['custom_host'] = '' - config['keyname'] = '' - config['keyid'] = '' - return config + def get_config(self, login, server, savepass, password, anonymous=False): + config = {} + config['name'] = login + config['hostname'] = server + config['savepass'] = savepass + config['password'] = password + config['resource'] = 'Gajim' + config['anonymous_auth'] = anonymous + config['priority'] = 5 + config['autoconnect'] = True + config['no_log_for'] = '' + config['sync_with_global_status'] = True + config['proxy'] = '' + config['usessl'] = False + config['use_custom_host'] = False + config['custom_port'] = 0 + config['custom_host'] = '' + config['keyname'] = '' + config['keyid'] = '' + return config - def save_account(self, login, server, savepass, password, anonymous=False): - if self.account in gajim.connections: - dialogs.ErrorDialog(_('Account name is in use'), - _('You already have an account using this name.')) - return - con = connection.Connection(self.account) - con.password = password + def save_account(self, login, server, savepass, password, anonymous=False): + if self.account in gajim.connections: + dialogs.ErrorDialog(_('Account name is in use'), + _('You already have an account using this name.')) + return + con = connection.Connection(self.account) + con.password = password - config = self.get_config(login, server, savepass, password, anonymous) + config = self.get_config(login, server, savepass, password, anonymous) - if not self.modify: - con.new_account(self.account, config) - return - gajim.connections[self.account] = con - self.create_vars(config) + if not self.modify: + con.new_account(self.account, config) + return + gajim.connections[self.account] = con + self.create_vars(config) - def create_vars(self, config): - gajim.config.add_per('accounts', self.account) + def create_vars(self, config): + gajim.config.add_per('accounts', self.account) - if not config['savepass']: - config['password'] = '' + if not config['savepass']: + config['password'] = '' - for opt in config: - gajim.config.set_per('accounts', self.account, opt, config[opt]) + for opt in config: + gajim.config.set_per('accounts', self.account, opt, config[opt]) - # update variables - gajim.interface.instances[self.account] = {'infos': {}, 'disco': {}, - 'gc_config': {}, 'search': {}, 'online_dialog': {}} - gajim.interface.minimized_controls[self.account] = {} - gajim.connections[self.account].connected = 0 - gajim.connections[self.account].keepalives = gajim.config.get_per( - 'accounts', self.account, 'keep_alive_every_foo_secs') - gajim.groups[self.account] = {} - gajim.contacts.add_account(self.account) - gajim.gc_connected[self.account] = {} - gajim.automatic_rooms[self.account] = {} - gajim.newly_added[self.account] = [] - gajim.to_be_removed[self.account] = [] - gajim.nicks[self.account] = config['name'] - gajim.block_signed_in_notifications[self.account] = True - gajim.sleeper_state[self.account] = 'off' - gajim.encrypted_chats[self.account] = [] - gajim.last_message_time[self.account] = {} - gajim.status_before_autoaway[self.account] = '' - gajim.transport_avatar[self.account] = {} - gajim.gajim_optional_features[self.account] = [] - gajim.caps_hash[self.account] = '' - # refresh accounts window - if 'accounts' in gajim.interface.instances: - gajim.interface.instances['accounts'].init_accounts() - # refresh roster - if len(gajim.connections) >= 2: # Do not merge accounts if only one exists - gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') - else: - gajim.interface.roster.regroup = False - gajim.interface.roster.setup_and_draw_roster() - gajim.interface.roster.set_actions_menu_needs_rebuild() - gajim.interface.save_config() + # update variables + gajim.interface.instances[self.account] = {'infos': {}, 'disco': {}, + 'gc_config': {}, 'search': {}, 'online_dialog': {}} + gajim.interface.minimized_controls[self.account] = {} + gajim.connections[self.account].connected = 0 + gajim.connections[self.account].keepalives = gajim.config.get_per( + 'accounts', self.account, 'keep_alive_every_foo_secs') + gajim.groups[self.account] = {} + gajim.contacts.add_account(self.account) + gajim.gc_connected[self.account] = {} + gajim.automatic_rooms[self.account] = {} + gajim.newly_added[self.account] = [] + gajim.to_be_removed[self.account] = [] + gajim.nicks[self.account] = config['name'] + gajim.block_signed_in_notifications[self.account] = True + gajim.sleeper_state[self.account] = 'off' + gajim.encrypted_chats[self.account] = [] + gajim.last_message_time[self.account] = {} + gajim.status_before_autoaway[self.account] = '' + gajim.transport_avatar[self.account] = {} + gajim.gajim_optional_features[self.account] = [] + gajim.caps_hash[self.account] = '' + # refresh accounts window + if 'accounts' in gajim.interface.instances: + gajim.interface.instances['accounts'].init_accounts() + # refresh roster + if len(gajim.connections) >= 2: # Do not merge accounts if only one exists + gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') + else: + gajim.interface.roster.regroup = False + gajim.interface.roster.setup_and_draw_roster() + gajim.interface.roster.set_actions_menu_needs_rebuild() + gajim.interface.save_config() class ManagePEPServicesWindow: - def __init__(self, account): - self.xml = gtkgui_helpers.get_glade('manage_pep_services_window.glade') - self.window = self.xml.get_widget('manage_pep_services_window') - self.window.set_transient_for(gajim.interface.roster.window) - self.xml.get_widget('configure_button').set_sensitive(False) - self.xml.get_widget('delete_button').set_sensitive(False) - self.xml.signal_autoconnect(self) - self.account = account + def __init__(self, account): + self.xml = gtkgui_helpers.get_glade('manage_pep_services_window.glade') + self.window = self.xml.get_widget('manage_pep_services_window') + self.window.set_transient_for(gajim.interface.roster.window) + self.xml.get_widget('configure_button').set_sensitive(False) + self.xml.get_widget('delete_button').set_sensitive(False) + self.xml.signal_autoconnect(self) + self.account = account - self.init_services() - self.xml.get_widget('services_treeview').get_selection().connect( - 'changed', self.on_services_selection_changed) - self.window.show_all() + self.init_services() + self.xml.get_widget('services_treeview').get_selection().connect( + 'changed', self.on_services_selection_changed) + self.window.show_all() - def on_manage_pep_services_window_destroy(self, widget): - '''close window''' - del gajim.interface.instances[self.account]['pep_services'] + def on_manage_pep_services_window_destroy(self, widget): + '''close window''' + del gajim.interface.instances[self.account]['pep_services'] - def on_close_button_clicked(self, widget): - self.window.destroy() + def on_close_button_clicked(self, widget): + self.window.destroy() - def on_services_selection_changed(self, sel): - self.xml.get_widget('configure_button').set_sensitive(True) - self.xml.get_widget('delete_button').set_sensitive(True) + def on_services_selection_changed(self, sel): + self.xml.get_widget('configure_button').set_sensitive(True) + self.xml.get_widget('delete_button').set_sensitive(True) - def init_services(self): - self.treeview = self.xml.get_widget('services_treeview') - # service, access_model, group - self.treestore = gtk.ListStore(str) - self.treeview.set_model(self.treestore) + def init_services(self): + self.treeview = self.xml.get_widget('services_treeview') + # service, access_model, group + self.treestore = gtk.ListStore(str) + self.treeview.set_model(self.treestore) - col = gtk.TreeViewColumn('Service') - self.treeview.append_column(col) + col = gtk.TreeViewColumn('Service') + self.treeview.append_column(col) - cellrenderer_text = gtk.CellRendererText() - col.pack_start(cellrenderer_text) - col.add_attribute(cellrenderer_text, 'text', 0) + cellrenderer_text = gtk.CellRendererText() + col.pack_start(cellrenderer_text) + col.add_attribute(cellrenderer_text, 'text', 0) - our_jid = gajim.get_jid_from_account(self.account) - gajim.connections[self.account].discoverItems(our_jid) + our_jid = gajim.get_jid_from_account(self.account) + gajim.connections[self.account].discoverItems(our_jid) - def items_received(self, items): - our_jid = gajim.get_jid_from_account(self.account) - for item in items: - if 'jid' in item and item['jid'] == our_jid and 'node' in item: - self.treestore.append([item['node']]) + def items_received(self, items): + our_jid = gajim.get_jid_from_account(self.account) + for item in items: + if 'jid' in item and item['jid'] == our_jid and 'node' in item: + self.treestore.append([item['node']]) - def node_removed(self, node): - model = self.treeview.get_model() - iter_ = model.get_iter_root() - while iter_: - if model[iter_][0] == node: - model.remove(iter_) - break - iter_ = model.get_iter_next(iter_) + def node_removed(self, node): + model = self.treeview.get_model() + iter_ = model.get_iter_root() + while iter_: + if model[iter_][0] == node: + model.remove(iter_) + break + iter_ = model.get_iter_next(iter_) - def on_delete_button_clicked(self, widget): - selection = self.treeview.get_selection() - if not selection: - return - model, iter_ = selection.get_selected() - node = model[iter_][0] - our_jid = gajim.get_jid_from_account(self.account) - gajim.connections[self.account].send_pb_delete(our_jid, node) + def on_delete_button_clicked(self, widget): + selection = self.treeview.get_selection() + if not selection: + return + model, iter_ = selection.get_selected() + node = model[iter_][0] + our_jid = gajim.get_jid_from_account(self.account) + gajim.connections[self.account].send_pb_delete(our_jid, node) - def on_configure_button_clicked(self, widget): - selection = self.treeview.get_selection() - if not selection: - return - model, iter_ = selection.get_selected() - node = model[iter_][0] - our_jid = gajim.get_jid_from_account(self.account) - gajim.connections[self.account].request_pb_configuration(our_jid, node) + def on_configure_button_clicked(self, widget): + selection = self.treeview.get_selection() + if not selection: + return + model, iter_ = selection.get_selected() + node = model[iter_][0] + our_jid = gajim.get_jid_from_account(self.account) + gajim.connections[self.account].request_pb_configuration(our_jid, node) - def config(self, node, form): - def on_ok(form, node): - form.type = 'submit' - our_jid = gajim.get_jid_from_account(self.account) - gajim.connections[self.account].send_pb_configure(our_jid, node, form) - window = dialogs.DataFormWindow(form, (on_ok, node)) - title = "Configure %s" % node - window.set_title(title) - window.show_all() + def config(self, node, form): + def on_ok(form, node): + form.type = 'submit' + our_jid = gajim.get_jid_from_account(self.account) + gajim.connections[self.account].send_pb_configure(our_jid, node, form) + window = dialogs.DataFormWindow(form, (on_ok, node)) + title = "Configure %s" % node + window.set_title(title) + window.show_all() class ManageSoundsWindow: - def __init__(self): - self.xml = gtkgui_helpers.get_glade('manage_sounds_window.glade') - self.window = self.xml.get_widget('manage_sounds_window') + def __init__(self): + self.xml = gtkgui_helpers.get_glade('manage_sounds_window.glade') + self.window = self.xml.get_widget('manage_sounds_window') - # sounds treeview - self.sound_tree = self.xml.get_widget('sounds_treeview') + # sounds treeview + self.sound_tree = self.xml.get_widget('sounds_treeview') - # active, event ui name, path to sound file, event_config_name - model = gtk.ListStore(bool, str, str, str) - self.sound_tree.set_model(model) + # active, event ui name, path to sound file, event_config_name + model = gtk.ListStore(bool, str, str, str) + self.sound_tree.set_model(model) - col = gtk.TreeViewColumn(_('Active')) - self.sound_tree.append_column(col) - renderer = gtk.CellRendererToggle() - renderer.set_property('activatable', True) - renderer.connect('toggled', self.sound_toggled_cb) - col.pack_start(renderer) - col.set_attributes(renderer, active = 0) + col = gtk.TreeViewColumn(_('Active')) + self.sound_tree.append_column(col) + renderer = gtk.CellRendererToggle() + renderer.set_property('activatable', True) + renderer.connect('toggled', self.sound_toggled_cb) + col.pack_start(renderer) + col.set_attributes(renderer, active = 0) - col = gtk.TreeViewColumn(_('Event')) - self.sound_tree.append_column(col) - renderer = gtk.CellRendererText() - col.pack_start(renderer) - col.set_attributes(renderer, text = 1) + col = gtk.TreeViewColumn(_('Event')) + self.sound_tree.append_column(col) + renderer = gtk.CellRendererText() + col.pack_start(renderer) + col.set_attributes(renderer, text = 1) - self.fill_sound_treeview() + self.fill_sound_treeview() - self.xml.signal_autoconnect(self) + self.xml.signal_autoconnect(self) - self.sound_tree.get_model().connect('row-changed', - self.on_sounds_treemodel_row_changed) + self.sound_tree.get_model().connect('row-changed', + self.on_sounds_treemodel_row_changed) - self.window.show_all() + self.window.show_all() - def on_sounds_treemodel_row_changed(self, model, path, iter_): - sound_event = model[iter_][3].decode('utf-8') - gajim.config.set_per('soundevents', sound_event, 'enabled', - bool(model[path][0])) - gajim.config.set_per('soundevents', sound_event, 'path', - model[iter_][2].decode('utf-8')) - gajim.interface.save_config() + def on_sounds_treemodel_row_changed(self, model, path, iter_): + sound_event = model[iter_][3].decode('utf-8') + gajim.config.set_per('soundevents', sound_event, 'enabled', + bool(model[path][0])) + gajim.config.set_per('soundevents', sound_event, 'path', + model[iter_][2].decode('utf-8')) + gajim.interface.save_config() - def sound_toggled_cb(self, cell, path): - model = self.sound_tree.get_model() - model[path][0] = not model[path][0] + def sound_toggled_cb(self, cell, path): + model = self.sound_tree.get_model() + model[path][0] = not model[path][0] - def fill_sound_treeview(self): - model = self.sound_tree.get_model() - model.clear() - model.set_sort_column_id(1, gtk.SORT_ASCENDING) + def fill_sound_treeview(self): + model = self.sound_tree.get_model() + model.clear() + model.set_sort_column_id(1, gtk.SORT_ASCENDING) - # NOTE: sounds_ui_names MUST have all items of - # sounds = gajim.config.get_per('soundevents') as keys - sounds_dict = { - 'first_message_received': _('First Message Received'), - 'next_message_received_focused': _('Next Message Received Focused'), - 'next_message_received_unfocused': - _('Next Message Received Unfocused'), - 'contact_connected': _('Contact Connected'), - 'contact_disconnected': _('Contact Disconnected'), - 'message_sent': _('Message Sent'), - 'muc_message_highlight': _('Group Chat Message Highlight'), - 'muc_message_received': _('Group Chat Message Received'), - 'gmail_received': _('GMail Email Received') - } + # NOTE: sounds_ui_names MUST have all items of + # sounds = gajim.config.get_per('soundevents') as keys + sounds_dict = { + 'first_message_received': _('First Message Received'), + 'next_message_received_focused': _('Next Message Received Focused'), + 'next_message_received_unfocused': + _('Next Message Received Unfocused'), + 'contact_connected': _('Contact Connected'), + 'contact_disconnected': _('Contact Disconnected'), + 'message_sent': _('Message Sent'), + 'muc_message_highlight': _('Group Chat Message Highlight'), + 'muc_message_received': _('Group Chat Message Received'), + 'gmail_received': _('GMail Email Received') + } - for sound_event_config_name, sound_ui_name in sounds_dict.items(): - enabled = gajim.config.get_per('soundevents', - sound_event_config_name, 'enabled') - path = gajim.config.get_per('soundevents', - sound_event_config_name, 'path') - model.append((enabled, sound_ui_name, path, sound_event_config_name)) + for sound_event_config_name, sound_ui_name in sounds_dict.items(): + enabled = gajim.config.get_per('soundevents', + sound_event_config_name, 'enabled') + path = gajim.config.get_per('soundevents', + sound_event_config_name, 'path') + model.append((enabled, sound_ui_name, path, sound_event_config_name)) - def on_treeview_sounds_cursor_changed(self, widget, data = None): - (model, iter_) = self.sound_tree.get_selection().get_selected() - sounds_entry = self.xml.get_widget('sounds_entry') - if not iter_: - sounds_entry.set_text('') - return - path_to_snd_file = model[iter_][2] - sounds_entry.set_text(path_to_snd_file) + def on_treeview_sounds_cursor_changed(self, widget, data = None): + (model, iter_) = self.sound_tree.get_selection().get_selected() + sounds_entry = self.xml.get_widget('sounds_entry') + if not iter_: + sounds_entry.set_text('') + return + path_to_snd_file = model[iter_][2] + sounds_entry.set_text(path_to_snd_file) - def on_browse_for_sounds_button_clicked(self, widget, data = None): - (model, iter_) = self.sound_tree.get_selection().get_selected() - if not iter_: - return - def on_ok(widget, path_to_snd_file): - self.dialog.destroy() - model, iter_ = self.sound_tree.get_selection().get_selected() - if not path_to_snd_file: - model[iter_][2] = '' - self.xml.get_widget('sounds_entry').set_text('') - model[iter_][0] = False - return - directory = os.path.dirname(path_to_snd_file) - gajim.config.set('last_sounds_dir', directory) - path_to_snd_file = helpers.strip_soundfile_path(path_to_snd_file) - self.xml.get_widget('sounds_entry').set_text(path_to_snd_file) + def on_browse_for_sounds_button_clicked(self, widget, data = None): + (model, iter_) = self.sound_tree.get_selection().get_selected() + if not iter_: + return + def on_ok(widget, path_to_snd_file): + self.dialog.destroy() + model, iter_ = self.sound_tree.get_selection().get_selected() + if not path_to_snd_file: + model[iter_][2] = '' + self.xml.get_widget('sounds_entry').set_text('') + model[iter_][0] = False + return + directory = os.path.dirname(path_to_snd_file) + gajim.config.set('last_sounds_dir', directory) + path_to_snd_file = helpers.strip_soundfile_path(path_to_snd_file) + self.xml.get_widget('sounds_entry').set_text(path_to_snd_file) - model[iter_][2] = path_to_snd_file # set new path to sounds_model - model[iter_][0] = True # set the sound to enabled + model[iter_][2] = path_to_snd_file # set new path to sounds_model + model[iter_][0] = True # set the sound to enabled - def on_cancel(widget): - self.dialog.destroy() + def on_cancel(widget): + self.dialog.destroy() - path_to_snd_file = model[iter_][2].decode('utf-8') - self.dialog = dialogs.SoundChooserDialog(path_to_snd_file, on_ok, - on_cancel) + path_to_snd_file = model[iter_][2].decode('utf-8') + self.dialog = dialogs.SoundChooserDialog(path_to_snd_file, on_ok, + on_cancel) - def on_sounds_entry_changed(self, widget): - path_to_snd_file = widget.get_text() - model, iter_ = self.sound_tree.get_selection().get_selected() - model[iter_][2] = path_to_snd_file # set new path to sounds_model + def on_sounds_entry_changed(self, widget): + path_to_snd_file = widget.get_text() + model, iter_ = self.sound_tree.get_selection().get_selected() + model[iter_][2] = path_to_snd_file # set new path to sounds_model - def on_play_button_clicked(self, widget): - model, iter_ = self.sound_tree.get_selection().get_selected() - if not iter_: - return - snd_event_config_name = model[iter_][3] - helpers.play_sound(snd_event_config_name) + def on_play_button_clicked(self, widget): + model, iter_ = self.sound_tree.get_selection().get_selected() + if not iter_: + return + snd_event_config_name = model[iter_][3] + helpers.play_sound(snd_event_config_name) - def on_close_button_clicked(self, widget): - self.window.hide() + def on_close_button_clicked(self, widget): + self.window.hide() - def on_manage_sounds_window_delete_event(self, widget, event): - self.window.hide() - return True # do NOT destroy the window -# vim: se ts=3: + def on_manage_sounds_window_delete_event(self, widget, event): + self.window.hide() diff --git a/src/conversation_textview.py b/src/conversation_textview.py index 7744ef685..da07ee15f 100644 --- a/src/conversation_textview.py +++ b/src/conversation_textview.py @@ -56,1303 +56,1301 @@ ALREADY_RECEIVED = 1 SHOWN = 2 def is_selection_modified(mark): - name = mark.get_name() - if name and name in ('selection_bound', 'insert'): - return True - else: - return False + name = mark.get_name() + if name and name in ('selection_bound', 'insert'): + return True + else: + return False def has_focus(widget): - return widget.flags() & gtk.HAS_FOCUS == gtk.HAS_FOCUS + return widget.flags() & gtk.HAS_FOCUS == gtk.HAS_FOCUS class TextViewImage(gtk.Image): - def __init__(self, anchor, text): - super(TextViewImage, self).__init__() - self.anchor = anchor - self._selected = False - self._disconnect_funcs = [] - self.connect('parent-set', self.on_parent_set) - self.connect('expose-event', self.on_expose) - self.set_tooltip_text(text) - self.anchor.set_data('plaintext', text) + def __init__(self, anchor, text): + super(TextViewImage, self).__init__() + self.anchor = anchor + self._selected = False + self._disconnect_funcs = [] + self.connect('parent-set', self.on_parent_set) + self.connect('expose-event', self.on_expose) + self.set_tooltip_text(text) + self.anchor.set_data('plaintext', text) - def _get_selected(self): - parent = self.get_parent() - if not parent or not self.anchor: return False - buffer_ = parent.get_buffer() - position = buffer_.get_iter_at_child_anchor(self.anchor) - bounds = buffer_.get_selection_bounds() - if bounds and position.in_range(*bounds): - return True - else: - return False + def _get_selected(self): + parent = self.get_parent() + if not parent or not self.anchor: return False + buffer_ = parent.get_buffer() + position = buffer_.get_iter_at_child_anchor(self.anchor) + bounds = buffer_.get_selection_bounds() + if bounds and position.in_range(*bounds): + return True + else: + return False - def get_state(self): - parent = self.get_parent() - if not parent: - return gtk.STATE_NORMAL - if self._selected: - if has_focus(parent): - return gtk.STATE_SELECTED - else: - return gtk.STATE_ACTIVE - else: - return gtk.STATE_NORMAL + def get_state(self): + parent = self.get_parent() + if not parent: + return gtk.STATE_NORMAL + if self._selected: + if has_focus(parent): + return gtk.STATE_SELECTED + else: + return gtk.STATE_ACTIVE + else: + return gtk.STATE_NORMAL - def _update_selected(self): - selected = self._get_selected() - if self._selected != selected: - self._selected = selected - self.queue_draw() + def _update_selected(self): + selected = self._get_selected() + if self._selected != selected: + self._selected = selected + self.queue_draw() - def _do_connect(self, widget, signal, callback): - id_ = widget.connect(signal, callback) - def disconnect(): - widget.disconnect(id_) - self._disconnect_funcs.append(disconnect) + def _do_connect(self, widget, signal, callback): + id_ = widget.connect(signal, callback) + def disconnect(): + widget.disconnect(id_) + self._disconnect_funcs.append(disconnect) - def _disconnect_signals(self): - for func in self._disconnect_funcs: - func() - self._disconnect_funcs = [] + def _disconnect_signals(self): + for func in self._disconnect_funcs: + func() + self._disconnect_funcs = [] - def on_parent_set(self, widget, old_parent): - parent = self.get_parent() - if not parent: - self._disconnect_signals() - return + def on_parent_set(self, widget, old_parent): + parent = self.get_parent() + if not parent: + self._disconnect_signals() + return - self._do_connect(parent, 'style-set', self.do_queue_draw) - self._do_connect(parent, 'focus-in-event', self.do_queue_draw) - self._do_connect(parent, 'focus-out-event', self.do_queue_draw) + self._do_connect(parent, 'style-set', self.do_queue_draw) + self._do_connect(parent, 'focus-in-event', self.do_queue_draw) + self._do_connect(parent, 'focus-out-event', self.do_queue_draw) - textbuf = parent.get_buffer() - self._do_connect(textbuf, 'mark-set', self.on_mark_set) - self._do_connect(textbuf, 'mark-deleted', self.on_mark_deleted) + textbuf = parent.get_buffer() + self._do_connect(textbuf, 'mark-set', self.on_mark_set) + self._do_connect(textbuf, 'mark-deleted', self.on_mark_deleted) - def do_queue_draw(self, *args): - self.queue_draw() - return False + def do_queue_draw(self, *args): + self.queue_draw() + return False - def on_mark_set(self, buf, iterat, mark): - self.on_mark_modified(mark) - return False + def on_mark_set(self, buf, iterat, mark): + self.on_mark_modified(mark) + return False - def on_mark_deleted(self, buf, mark): - self.on_mark_modified(mark) - return False + def on_mark_deleted(self, buf, mark): + self.on_mark_modified(mark) + return False - def on_mark_modified(self, mark): - if is_selection_modified(mark): - self._update_selected() + def on_mark_modified(self, mark): + if is_selection_modified(mark): + self._update_selected() - def on_expose(self, widget, event): - state = self.get_state() - if state != gtk.STATE_NORMAL: - gc = widget.get_style().base_gc[state] - area = widget.allocation - widget.window.draw_rectangle(gc, True, area.x, area.y, - area.width, area.height) - return False + def on_expose(self, widget, event): + state = self.get_state() + if state != gtk.STATE_NORMAL: + gc = widget.get_style().base_gc[state] + area = widget.allocation + widget.window.draw_rectangle(gc, True, area.x, area.y, + area.width, area.height) + return False class ConversationTextview(gobject.GObject): - """ - Class for the conversation textview (where user reads already said messages) - for chat/groupchat windows - """ - __gsignals__ = dict( - quote = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION, - None, # return value - (str, ) # arguments - ) - ) - - FOCUS_OUT_LINE_PIXBUF = gtk.gdk.pixbuf_new_from_file(os.path.join( - gajim.DATA_DIR, 'pixmaps', 'muc_separator.png')) - XEP0184_WARNING_PIXBUF = gtk.gdk.pixbuf_new_from_file(os.path.join( - gajim.DATA_DIR, 'pixmaps', 'receipt_missing.png')) - - # smooth scroll constants - MAX_SCROLL_TIME = 0.4 # seconds - SCROLL_DELAY = 33 # milliseconds - - def __init__(self, account, used_in_history_window = False): - """ - If used_in_history_window is True, then we do not show Clear menuitem in - context menu - """ - gobject.GObject.__init__(self) - self.used_in_history_window = used_in_history_window - - self.fc = FuzzyClock() - - - # no need to inherit TextView, use it as atrribute is safer - self.tv = HtmlTextView() - self.tv.html_hyperlink_handler = self.html_hyperlink_handler - - # set properties - self.tv.set_border_width(1) - self.tv.set_accepts_tab(True) - self.tv.set_editable(False) - self.tv.set_cursor_visible(False) - self.tv.set_wrap_mode(gtk.WRAP_WORD_CHAR) - self.tv.set_left_margin(2) - self.tv.set_right_margin(2) - self.handlers = {} - self.images = [] - self.image_cache = {} - self.xep0184_marks = {} - self.xep0184_shown = {} - - # It's True when we scroll in the code, so we can detect scroll from user - self.auto_scrolling = False - - # connect signals - id_ = self.tv.connect('motion_notify_event', - self.on_textview_motion_notify_event) - self.handlers[id_] = self.tv - id_ = self.tv.connect('populate_popup', self.on_textview_populate_popup) - self.handlers[id_] = self.tv - id_ = self.tv.connect('button_press_event', - self.on_textview_button_press_event) - self.handlers[id_] = self.tv - - id_ = self.tv.connect('expose-event', - self.on_textview_expose_event) - self.handlers[id_] = self.tv - - - self.account = account - self.change_cursor = False - self.last_time_printout = 0 - - font = pango.FontDescription(gajim.config.get('conversation_font')) - self.tv.modify_font(font) - buffer_ = self.tv.get_buffer() - end_iter = buffer_.get_end_iter() - buffer_.create_mark('end', end_iter, False) - - self.tagIn = buffer_.create_tag('incoming') - color = gajim.config.get('inmsgcolor') - font = pango.FontDescription(gajim.config.get('inmsgfont')) - self.tagIn.set_property('foreground', color) - self.tagIn.set_property('font-desc', font) - - self.tagOut = buffer_.create_tag('outgoing') - color = gajim.config.get('outmsgcolor') - font = pango.FontDescription(gajim.config.get('outmsgfont')) - self.tagOut.set_property('foreground', color) - self.tagOut.set_property('font-desc', font) - - self.tagStatus = buffer_.create_tag('status') - color = gajim.config.get('statusmsgcolor') - font = pango.FontDescription(gajim.config.get('satusmsgfont')) - self.tagStatus.set_property('foreground', color) - self.tagStatus.set_property('font-desc', font) - - self.tagInText = buffer_.create_tag('incomingtxt') - color = gajim.config.get('inmsgtxtcolor') - font = pango.FontDescription(gajim.config.get('inmsgtxtfont')) - if color: - self.tagInText.set_property('foreground', color) - self.tagInText.set_property('font-desc', font) - - self.tagOutText = buffer_.create_tag('outgoingtxt') - color = gajim.config.get('outmsgtxtcolor') - if color: - font = pango.FontDescription(gajim.config.get('outmsgtxtfont')) - self.tagOutText.set_property('foreground', color) - self.tagOutText.set_property('font-desc', font) - - colors = gajim.config.get('gc_nicknames_colors') - colors = colors.split(':') - for i,color in enumerate(colors): - tagname = 'gc_nickname_color_' + str(i) - tag = buffer_.create_tag(tagname) - tag.set_property('foreground', color) - - tag = buffer_.create_tag('marked') - color = gajim.config.get('markedmsgcolor') - tag.set_property('foreground', color) - tag.set_property('weight', pango.WEIGHT_BOLD) - - tag = buffer_.create_tag('time_sometimes') - tag.set_property('foreground', 'darkgrey') - tag.set_property('scale', pango.SCALE_SMALL) - tag.set_property('justification', gtk.JUSTIFY_CENTER) - - tag = buffer_.create_tag('small') - tag.set_property('scale', pango.SCALE_SMALL) - - tag = buffer_.create_tag('restored_message') - color = gajim.config.get('restored_messages_color') - tag.set_property('foreground', color) - - self.tagURL = buffer_.create_tag('url') - color = gajim.config.get('urlmsgcolor') - self.tagURL.set_property('foreground', color) - self.tagURL.set_property('underline', pango.UNDERLINE_SINGLE) - id_ = self.tagURL.connect('event', self.hyperlink_handler, 'url') - self.handlers[id_] = self.tagURL - - self.tagMail = buffer_.create_tag('mail') - self.tagMail.set_property('foreground', color) - self.tagMail.set_property('underline', pango.UNDERLINE_SINGLE) - id_ = self.tagMail.connect('event', self.hyperlink_handler, 'mail') - self.handlers[id_] = self.tagMail - - self.tagXMPP = buffer_.create_tag('xmpp') - self.tagXMPP.set_property('foreground', color) - self.tagXMPP.set_property('underline', pango.UNDERLINE_SINGLE) - id_ = self.tagXMPP.connect('event', self.hyperlink_handler, 'xmpp') - self.handlers[id_] = self.tagXMPP - - self.tagSthAtSth = buffer_.create_tag('sth_at_sth') - self.tagSthAtSth.set_property('foreground', color) - self.tagSthAtSth.set_property('underline', pango.UNDERLINE_SINGLE) - id_ = self.tagSthAtSth.connect('event', self.hyperlink_handler, - 'sth_at_sth') - self.handlers[id_] = self.tagSthAtSth - - tag = buffer_.create_tag('bold') - tag.set_property('weight', pango.WEIGHT_BOLD) - - tag = buffer_.create_tag('italic') - tag.set_property('style', pango.STYLE_ITALIC) - - tag = buffer_.create_tag('underline') - tag.set_property('underline', pango.UNDERLINE_SINGLE) - - buffer_.create_tag('focus-out-line', justification = gtk.JUSTIFY_CENTER) - - tag = buffer_.create_tag('xep0184-warning') - - # One mark at the begining then 2 marks between each lines - size = gajim.config.get('max_conversation_lines') - size = 2 * size - 1 - self.marks_queue = Queue.Queue(size) - - self.allow_focus_out_line = True - # holds a mark at the end of --- line - self.focus_out_end_mark = None - - self.xep0184_warning_tooltip = tooltips.BaseTooltip() - - self.line_tooltip = tooltips.BaseTooltip() - # use it for hr too - self.tv.focus_out_line_pixbuf = ConversationTextview.FOCUS_OUT_LINE_PIXBUF - self.smooth_id = None - - def del_handlers(self): - for i in self.handlers.keys(): - if self.handlers[i].handler_is_connected(i): - self.handlers[i].disconnect(i) - del self.handlers - self.tv.destroy() - #FIXME: - # self.line_tooltip.destroy() - - def update_tags(self): - self.tagIn.set_property('foreground', gajim.config.get('inmsgcolor')) - self.tagOut.set_property('foreground', gajim.config.get('outmsgcolor')) - self.tagStatus.set_property('foreground', - gajim.config.get('statusmsgcolor')) - self.tagURL.set_property('foreground', gajim.config.get('urlmsgcolor')) - self.tagMail.set_property('foreground', gajim.config.get('urlmsgcolor')) - - def at_the_end(self): - buffer_ = self.tv.get_buffer() - end_iter = buffer_.get_end_iter() - end_rect = self.tv.get_iter_location(end_iter) - visible_rect = self.tv.get_visible_rect() - if end_rect.y <= (visible_rect.y + visible_rect.height): - return True - return False - - # Smooth scrolling inspired by Pidgin code - def smooth_scroll(self): - parent = self.tv.get_parent() - if not parent: - return False - vadj = parent.get_vadjustment() - max_val = vadj.upper - vadj.page_size + 1 - cur_val = vadj.get_value() - # scroll by 1/3rd of remaining distance - onethird = cur_val + ((max_val - cur_val) / 3.0) - self.auto_scrolling = True - vadj.set_value(onethird) - self.auto_scrolling = False - if max_val - onethird < 0.01: - self.smooth_id = None - self.smooth_scroll_timer.cancel() - return False - return True - - def smooth_scroll_timeout(self): - gobject.idle_add(self.do_smooth_scroll_timeout) - return - - def do_smooth_scroll_timeout(self): - if not self.smooth_id: - # we finished scrolling - return - gobject.source_remove(self.smooth_id) - self.smooth_id = None - parent = self.tv.get_parent() - if parent: - vadj = parent.get_vadjustment() - self.auto_scrolling = True - vadj.set_value(vadj.upper - vadj.page_size + 1) - self.auto_scrolling = False - - def smooth_scroll_to_end(self): - if None != self.smooth_id: # already scrolling - return False - self.smooth_id = gobject.timeout_add(self.SCROLL_DELAY, - self.smooth_scroll) - self.smooth_scroll_timer = Timer(self.MAX_SCROLL_TIME, - self.smooth_scroll_timeout) - self.smooth_scroll_timer.start() - return False - - def scroll_to_end(self): - parent = self.tv.get_parent() - buffer_ = self.tv.get_buffer() - end_mark = buffer_.get_mark('end') - if not end_mark: - return False - self.auto_scrolling = True - self.tv.scroll_to_mark(end_mark, 0, True, 0, 1) - adjustment = parent.get_hadjustment() - adjustment.set_value(0) - self.auto_scrolling = False - return False # when called in an idle_add, just do it once - - def bring_scroll_to_end(self, diff_y = 0, - use_smooth=gajim.config.get('use_smooth_scrolling')): - ''' scrolls to the end of textview if end is not visible ''' - buffer_ = self.tv.get_buffer() - end_iter = buffer_.get_end_iter() - end_rect = self.tv.get_iter_location(end_iter) - visible_rect = self.tv.get_visible_rect() - # scroll only if expected end is not visible - if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y): - if use_smooth: - gobject.idle_add(self.smooth_scroll_to_end) - else: - gobject.idle_add(self.scroll_to_end_iter) - - def scroll_to_end_iter(self): - buffer_ = self.tv.get_buffer() - end_iter = buffer_.get_end_iter() - if not end_iter: - return False - self.tv.scroll_to_iter(end_iter, 0, False, 1, 1) - return False # when called in an idle_add, just do it once - - def stop_scrolling(self): - if self.smooth_id: - gobject.source_remove(self.smooth_id) - self.smooth_id = None - self.smooth_scroll_timer.cancel() - - def show_xep0184_warning(self, id_): - if id_ in self.xep0184_marks: - return - - buffer_ = self.tv.get_buffer() - buffer_.begin_user_action() - - self.xep0184_marks[id_] = buffer_.create_mark(None, - buffer_.get_end_iter(), left_gravity=True) - self.xep0184_shown[id_] = NOT_SHOWN - - def show_it(): - if (not id_ in self.xep0184_shown) or \ - self.xep0184_shown[id_] == ALREADY_RECEIVED: - return False - - end_iter = buffer_.get_iter_at_mark( - self.xep0184_marks[id_]) - buffer_.insert(end_iter, ' ') - buffer_.insert_pixbuf(end_iter, - ConversationTextview.XEP0184_WARNING_PIXBUF) - before_img_iter = buffer_.get_iter_at_mark( - self.xep0184_marks[id_]) - before_img_iter.forward_char() - post_img_iter = before_img_iter.copy() - post_img_iter.forward_char() - buffer_.apply_tag_by_name('xep0184-warning', before_img_iter, - post_img_iter) - - self.xep0184_shown[id_] = SHOWN - return False - gobject.timeout_add_seconds(3, show_it) - - buffer_.end_user_action() - - def hide_xep0184_warning(self, id_): - if id_ not in self.xep0184_marks: - return - - if self.xep0184_shown[id_] == NOT_SHOWN: - self.xep0184_shown[id_] = ALREADY_RECEIVED - return - - buffer_ = self.tv.get_buffer() - buffer_.begin_user_action() - - begin_iter = buffer_.get_iter_at_mark(self.xep0184_marks[id_]) - - end_iter = begin_iter.copy() - # XXX: Is there a nicer way? - end_iter.forward_char() - end_iter.forward_char() - - buffer_.delete(begin_iter, end_iter) - buffer_.delete_mark(self.xep0184_marks[id_]) - - buffer_.end_user_action() - - del self.xep0184_marks[id_] - del self.xep0184_shown[id_] - - def show_focus_out_line(self): - if not self.allow_focus_out_line: - # if room did not receive focus-in from the last time we added - # --- line then do not readd - return - - print_focus_out_line = False - buffer_ = self.tv.get_buffer() - - if self.focus_out_end_mark is None: - # this happens only first time we focus out on this room - print_focus_out_line = True - - else: - focus_out_end_iter = buffer_.get_iter_at_mark(self.focus_out_end_mark) - focus_out_end_iter_offset = focus_out_end_iter.get_offset() - if focus_out_end_iter_offset != buffer_.get_end_iter().get_offset(): - # this means after last-focus something was printed - # (else end_iter's offset is the same as before) - # only then print ---- line (eg. we avoid printing many following - # ---- lines) - print_focus_out_line = True - - if print_focus_out_line and buffer_.get_char_count() > 0: - buffer_.begin_user_action() - - # remove previous focus out line if such focus out line exists - if self.focus_out_end_mark is not None: - end_iter_for_previous_line = buffer_.get_iter_at_mark( - self.focus_out_end_mark) - begin_iter_for_previous_line = end_iter_for_previous_line.copy() - # img_char+1 (the '\n') - begin_iter_for_previous_line.backward_chars(2) - - # remove focus out line - buffer_.delete(begin_iter_for_previous_line, - end_iter_for_previous_line) - buffer_.delete_mark(self.focus_out_end_mark) - - # add the new focus out line - end_iter = buffer_.get_end_iter() - buffer_.insert(end_iter, '\n') - buffer_.insert_pixbuf(end_iter, - ConversationTextview.FOCUS_OUT_LINE_PIXBUF) - - end_iter = buffer_.get_end_iter() - before_img_iter = end_iter.copy() - # one char back (an image also takes one char) - before_img_iter.backward_char() - buffer_.apply_tag_by_name('focus-out-line', before_img_iter, end_iter) - - self.allow_focus_out_line = False - - # update the iter we hold to make comparison the next time - self.focus_out_end_mark = buffer_.create_mark(None, - buffer_.get_end_iter(), left_gravity=True) - - buffer_.end_user_action() - - # scroll to the end (via idle in case the scrollbar has appeared) - gobject.idle_add(self.scroll_to_end) - - def show_xep0184_warning_tooltip(self): - pointer = self.tv.get_pointer() - x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, - pointer[0], pointer[1]) - tags = self.tv.get_iter_at_location(x, y).get_tags() - tag_table = self.tv.get_buffer().get_tag_table() - xep0184_warning = False - for tag in tags: - if tag == tag_table.lookup('xep0184-warning'): - xep0184_warning = True - break - if xep0184_warning and not self.xep0184_warning_tooltip.win: - # check if the current pointer is still over the line - position = self.tv.window.get_origin() - self.xep0184_warning_tooltip.show_tooltip(_('This icon indicates that ' - 'this message has not yet\nbeen received by the remote end. ' - "If this icon stays\nfor a long time, it's likely the message got " - 'lost.'), 8, position[1] + pointer[1]) - - def show_line_tooltip(self): - pointer = self.tv.get_pointer() - x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, - pointer[0], pointer[1]) - tags = self.tv.get_iter_at_location(x, y).get_tags() - tag_table = self.tv.get_buffer().get_tag_table() - over_line = False - for tag in tags: - if tag == tag_table.lookup('focus-out-line'): - over_line = True - break - if over_line and not self.line_tooltip.win: - # check if the current pointer is still over the line - position = self.tv.window.get_origin() - self.line_tooltip.show_tooltip(_('Text below this line is what has ' - 'been said since the\nlast time you paid attention to this group ' - 'chat'), 8, position[1] + pointer[1]) - - def on_textview_expose_event(self, widget, event): - expalloc = event.area - exp_x0 = expalloc.x - exp_y0 = expalloc.y - exp_x1 = exp_x0 + expalloc.width - exp_y1 = exp_y0 + expalloc.height - - try: - tryfirst = [self.image_cache[(exp_x0, exp_y0)]] - except KeyError: - tryfirst = [] - - for image in tryfirst + self.images: - imgalloc = image.allocation - img_x0 = imgalloc.x - img_y0 = imgalloc.y - img_x1 = img_x0 + imgalloc.width - img_y1 = img_y0 + imgalloc.height - - if img_x0 <= exp_x0 and img_y0 <= exp_y0 and \ - exp_x1 <= img_x1 and exp_y1 <= img_y1: - self.image_cache[(img_x0, img_y0)] = image - widget.propagate_expose(image, event) - return True - return False - - def on_textview_motion_notify_event(self, widget, event): - """ - Change the cursor to a hand when we are over a mail or an url - """ - pointer_x, pointer_y = self.tv.window.get_pointer()[0:2] - x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, - pointer_x, pointer_y) - tags = self.tv.get_iter_at_location(x, y).get_tags() - if self.change_cursor: - self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( - gtk.gdk.Cursor(gtk.gdk.XTERM)) - self.change_cursor = False - tag_table = self.tv.get_buffer().get_tag_table() - over_line = False - xep0184_warning = False - for tag in tags: - if tag in (tag_table.lookup('url'), tag_table.lookup('mail'), \ - tag_table.lookup('xmpp'), tag_table.lookup('sth_at_sth')): - self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( - gtk.gdk.Cursor(gtk.gdk.HAND2)) - self.change_cursor = True - elif tag == tag_table.lookup('focus-out-line'): - over_line = True - elif tag == tag_table.lookup('xep0184-warning'): - xep0184_warning = True - - if self.line_tooltip.timeout != 0: - # Check if we should hide the line tooltip - if not over_line: - self.line_tooltip.hide_tooltip() - if self.xep0184_warning_tooltip.timeout != 0: - # Check if we should hide the XEP-184 warning tooltip - if not xep0184_warning: - self.xep0184_warning_tooltip.hide_tooltip() - if over_line and not self.line_tooltip.win: - self.line_tooltip.timeout = gobject.timeout_add(500, - self.show_line_tooltip) - self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( - gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)) - self.change_cursor = True - if xep0184_warning and not self.xep0184_warning_tooltip.win: - self.xep0184_warning_tooltip.timeout = gobject.timeout_add(500, - self.show_xep0184_warning_tooltip) - self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( - gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)) - self.change_cursor = True - - def clear(self, tv = None): - """ - Clear text in the textview - """ - buffer_ = self.tv.get_buffer() - start, end = buffer_.get_bounds() - buffer_.delete(start, end) - size = gajim.config.get('max_conversation_lines') - size = 2 * size - 1 - self.marks_queue = Queue.Queue(size) - self.focus_out_end_mark = None - - def visit_url_from_menuitem(self, widget, link): - """ - Basically it filters out the widget instance - """ - helpers.launch_browser_mailer('url', link) - - def on_textview_populate_popup(self, textview, menu): - """ - Override the default context menu and we prepend Clear (only if - used_in_history_window is False) and if we have sth selected we show a - submenu with actions on the phrase (see - on_conversation_textview_button_press_event) - """ - separator_menuitem_was_added = False - if not self.used_in_history_window: - item = gtk.SeparatorMenuItem() - menu.prepend(item) - separator_menuitem_was_added = True - - item = gtk.ImageMenuItem(gtk.STOCK_CLEAR) - menu.prepend(item) - id_ = item.connect('activate', self.clear) - self.handlers[id_] = item - - if self.selected_phrase: - if not separator_menuitem_was_added: - item = gtk.SeparatorMenuItem() - menu.prepend(item) - - if not self.used_in_history_window: - item = gtk.MenuItem(_('_Quote')) - id_ = item.connect('activate', self.on_quote) - self.handlers[id_] = item - menu.prepend(item) - - _selected_phrase = helpers.reduce_chars_newlines( - self.selected_phrase, 25, 2) - item = gtk.MenuItem(_('_Actions for "%s"') % _selected_phrase) - menu.prepend(item) - submenu = gtk.Menu() - item.set_submenu(submenu) - - always_use_en = gajim.config.get('always_english_wikipedia') - if always_use_en: - link = 'http://en.wikipedia.org/wiki/Special:Search?search=%s'\ - % self.selected_phrase - else: - link = 'http://%s.wikipedia.org/wiki/Special:Search?search=%s'\ - % (gajim.LANG, self.selected_phrase) - item = gtk.MenuItem(_('Read _Wikipedia Article')) - id_ = item.connect('activate', self.visit_url_from_menuitem, link) - self.handlers[id_] = item - submenu.append(item) - - item = gtk.MenuItem(_('Look it up in _Dictionary')) - dict_link = gajim.config.get('dictionary_url') - if dict_link == 'WIKTIONARY': - # special link (yeah undocumented but default) - always_use_en = gajim.config.get('always_english_wiktionary') - if always_use_en: - link = 'http://en.wiktionary.org/wiki/Special:Search?search=%s'\ - % self.selected_phrase - else: - link = 'http://%s.wiktionary.org/wiki/Special:Search?search=%s'\ - % (gajim.LANG, self.selected_phrase) - id_ = item.connect('activate', self.visit_url_from_menuitem, link) - self.handlers[id_] = item - else: - if dict_link.find('%s') == -1: - # we must have %s in the url if not WIKTIONARY - item = gtk.MenuItem(_( - 'Dictionary URL is missing an "%s" and it is not WIKTIONARY')) - item.set_property('sensitive', False) - else: - link = dict_link % self.selected_phrase - id_ = item.connect('activate', self.visit_url_from_menuitem, - link) - self.handlers[id_] = item - submenu.append(item) - - - search_link = gajim.config.get('search_engine') - if search_link.find('%s') == -1: - # we must have %s in the url - item = gtk.MenuItem(_('Web Search URL is missing an "%s"')) - item.set_property('sensitive', False) - else: - item = gtk.MenuItem(_('Web _Search for it')) - link = search_link % self.selected_phrase - id_ = item.connect('activate', self.visit_url_from_menuitem, link) - self.handlers[id_] = item - submenu.append(item) - - item = gtk.MenuItem(_('Open as _Link')) - id_ = item.connect('activate', self.visit_url_from_menuitem, link) - self.handlers[id_] = item - submenu.append(item) - - menu.show_all() - - def on_quote(self, widget): - self.emit('quote', self.selected_phrase) - - def on_textview_button_press_event(self, widget, event): - # If we clicked on a taged text do NOT open the standard popup menu - # if normal text check if we have sth selected - self.selected_phrase = '' # do not move belove event button check! - - if event.button != 3: # if not right click - return False - - x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, - int(event.x), int(event.y)) - iter_ = self.tv.get_iter_at_location(x, y) - tags = iter_.get_tags() - - - if tags: # we clicked on sth special (it can be status message too) - for tag in tags: - tag_name = tag.get_property('name') - if tag_name in ('url', 'mail', 'xmpp', 'sth_at_sth'): - return True # we block normal context menu - - # we check if sth was selected and if it was we assign - # selected_phrase variable - # so on_conversation_textview_populate_popup can use it - buffer_ = self.tv.get_buffer() - return_val = buffer_.get_selection_bounds() - if return_val: # if sth was selected when we right-clicked - # get the selected text - start_sel, finish_sel = return_val[0], return_val[1] - self.selected_phrase = buffer_.get_text(start_sel, finish_sel).decode( - 'utf-8') - elif ord(iter_.get_char()) > 31: - # we clicked on a word, do as if it's selected for context menu - start_sel = iter_.copy() - if not start_sel.starts_word(): - start_sel.backward_word_start() - finish_sel = iter_.copy() - if not finish_sel.ends_word(): - finish_sel.forward_word_end() - self.selected_phrase = buffer_.get_text(start_sel, finish_sel).decode( - 'utf-8') - - def on_open_link_activate(self, widget, kind, text): - helpers.launch_browser_mailer(kind, text) - - def on_copy_link_activate(self, widget, text): - clip = gtk.clipboard_get() - clip.set_text(text) - - def on_start_chat_activate(self, widget, jid): - gajim.interface.new_chat_from_jid(self.account, jid) - - def on_join_group_chat_menuitem_activate(self, widget, room_jid): - if 'join_gc' in gajim.interface.instances[self.account]: - instance = gajim.interface.instances[self.account]['join_gc'] - instance.xml.get_widget('room_jid_entry').set_text(room_jid) - gajim.interface.instances[self.account]['join_gc'].window.present() - else: - try: - dialogs.JoinGroupchatWindow(account=self.account, room_jid=room_jid) - except GajimGeneralException: - pass - - def on_add_to_roster_activate(self, widget, jid): - dialogs.AddNewContactWindow(self.account, jid) - - def make_link_menu(self, event, kind, text): - xml = gtkgui_helpers.get_glade('chat_context_menu.glade') - menu = xml.get_widget('chat_context_menu') - childs = menu.get_children() - if kind == 'url': - id_ = childs[0].connect('activate', self.on_copy_link_activate, text) - self.handlers[id_] = childs[0] - id_ = childs[1].connect('activate', self.on_open_link_activate, kind, - text) - self.handlers[id_] = childs[1] - childs[2].hide() # copy mail address - childs[3].hide() # open mail composer - childs[4].hide() # jid section separator - childs[5].hide() # start chat - childs[6].hide() # join group chat - childs[7].hide() # add to roster - else: # It's a mail or a JID - # load muc icon - join_group_chat_menuitem = xml.get_widget('join_group_chat_menuitem') - muc_icon = gtkgui_helpers.load_icon('muc_active') - if muc_icon: - join_group_chat_menuitem.set_image(muc_icon) - - text = text.lower() - if text.startswith('xmpp:'): - text = text[5:] - id_ = childs[2].connect('activate', self.on_copy_link_activate, text) - self.handlers[id_] = childs[2] - id_ = childs[3].connect('activate', self.on_open_link_activate, kind, - text) - self.handlers[id_] = childs[3] - id_ = childs[5].connect('activate', self.on_start_chat_activate, text) - self.handlers[id_] = childs[5] - id_ = childs[6].connect('activate', - self.on_join_group_chat_menuitem_activate, text) - self.handlers[id_] = childs[6] - - allow_add = False - if self.account: - c = gajim.contacts.get_first_contact_from_jid(self.account, text) - if c and not gajim.contacts.is_pm_from_contact(self.account, c): - if _('Not in Roster') in c.groups: - allow_add = True - else: # he or she's not at all in the account contacts - allow_add = True - - if allow_add: - id_ = childs[7].connect('activate', self.on_add_to_roster_activate, - text) - self.handlers[id_] = childs[7] - childs[7].show() # show add to roster menuitem - else: - childs[7].hide() # hide add to roster menuitem - - if kind == 'xmpp': - childs[2].hide() # copy mail address - childs[3].hide() # open mail composer - childs[4].hide() # jid section separator - elif kind == 'mail': - childs[4].hide() # jid section separator - childs[5].hide() # start chat - childs[6].hide() # join group chat - childs[7].hide() # add to roster - - childs[0].hide() # copy link location - childs[1].hide() # open link in browser - - menu.popup(None, None, None, event.button, event.time) - - def hyperlink_handler(self, texttag, widget, event, iter_, kind): - if event.type == gtk.gdk.BUTTON_PRESS: - begin_iter = iter_.copy() - # we get the begining of the tag - while not begin_iter.begins_tag(texttag): - begin_iter.backward_char() - end_iter = iter_.copy() - # we get the end of the tag - while not end_iter.ends_tag(texttag): - end_iter.forward_char() - word = self.tv.get_buffer().get_text(begin_iter, end_iter).decode( - 'utf-8') - if event.button == 3: # right click - self.make_link_menu(event, kind, word) - else: - # we launch the correct application - if kind == 'xmpp': - word = word[5:] - if '?' in word: - (jid, action) = word.split('?') - if action == 'join': - self.on_join_group_chat_menuitem_activate(None, jid) - else: - self.on_start_chat_activate(None, jid) - else: - self.on_start_chat_activate(None, word) - else: - helpers.launch_browser_mailer(kind, word) - - def html_hyperlink_handler(self, texttag, widget, event, iter_, kind, href): - if event.type == gtk.gdk.BUTTON_PRESS: - if event.button == 3: # right click - self.make_link_menu(event, kind, href) - return True - else: - # we launch the correct application - helpers.launch_browser_mailer(kind, href) - - - def detect_and_print_special_text(self, otext, other_tags, graphics=True): - """ - Detect special text (emots & links & formatting), print normal text - before any special text it founds, then print special text (that happens - many times until last special text is printed) and then return the index - after *last* special text, so we can print it in - print_conversation_line() - """ - buffer_ = self.tv.get_buffer() - - insert_tags_func = buffer_.insert_with_tags_by_name - # detect_and_print_special_text() is also used by - # HtmlHandler.handle_specials() and there tags is gtk.TextTag objects, - # not strings - if other_tags and isinstance(other_tags[0], gtk.TextTag): - insert_tags_func = buffer_.insert_with_tags - - index = 0 - - # Too many special elements (emoticons, LaTeX formulas, etc) - # may cause Gajim to freeze (see #5129). - # We impose an arbitrary limit of 100 specials per message. - specials_limit = 100 - - # basic: links + mail + formatting is always checked (we like that) - if gajim.config.get('emoticons_theme') and graphics: - # search for emoticons & urls - iterator = gajim.interface.emot_and_basic_re.finditer(otext) - else: # search for just urls + mail + formatting - iterator = gajim.interface.basic_pattern_re.finditer(otext) - for match in iterator: - start, end = match.span() - special_text = otext[start:end] - if start > index: - text_before_special_text = otext[index:start] - end_iter = buffer_.get_end_iter() - # we insert normal text - insert_tags_func(end_iter, text_before_special_text, *other_tags) - index = end # update index - - # now print it - self.print_special_text(special_text, other_tags, graphics=graphics) - specials_limit -= 1 - if specials_limit <= 0: - break - - # add the rest of text located in the index and after - end_iter = buffer_.get_end_iter() - insert_tags_func(end_iter, otext[index:], *other_tags) - - return buffer_.get_end_iter() - - def print_special_text(self, special_text, other_tags, graphics=True): - """ - Is called by detect_and_print_special_text and prints special text - (emots, links, formatting) - """ - tags = [] - use_other_tags = True - text_is_valid_uri = False - show_ascii_formatting_chars = \ - gajim.config.get('show_ascii_formatting_chars') - buffer_ = self.tv.get_buffer() - - # Check if we accept this as an uri - schemes = gajim.config.get('uri_schemes').split() - for scheme in schemes: - if special_text.startswith(scheme + ':'): - text_is_valid_uri = True - - possible_emot_ascii_caps = special_text.upper() # emoticons keys are CAPS - if gajim.config.get('emoticons_theme') and \ - possible_emot_ascii_caps in gajim.interface.emoticons.keys() and graphics: - # it's an emoticon - emot_ascii = possible_emot_ascii_caps - end_iter = buffer_.get_end_iter() - anchor = buffer_.create_child_anchor(end_iter) - img = TextViewImage(anchor, special_text) - animations = gajim.interface.emoticons_animations - if not emot_ascii in animations: - animations[emot_ascii] = gtk.gdk.PixbufAnimation( - gajim.interface.emoticons[emot_ascii]) - img.set_from_animation(animations[emot_ascii]) - img.show() - self.images.append(img) - # add with possible animation - self.tv.add_child_at_anchor(img, anchor) - elif special_text.startswith('www.') or \ - special_text.startswith('ftp.') or \ - text_is_valid_uri: - tags.append('url') - use_other_tags = False - elif special_text.startswith('mailto:'): - tags.append('mail') - use_other_tags = False - elif special_text.startswith('xmpp:'): - tags.append('xmpp') - use_other_tags = False - elif gajim.interface.sth_at_sth_dot_sth_re.match(special_text): - # it's a JID or mail - tags.append('sth_at_sth') - use_other_tags = False - elif special_text.startswith('*'): # it's a bold text - tags.append('bold') - if special_text[1] == '/' and special_text[-2] == '/' and\ - len(special_text) > 4: # it's also italic - tags.append('italic') - if not show_ascii_formatting_chars: - special_text = special_text[2:-2] # remove */ /* - elif special_text[1] == '_' and special_text[-2] == '_' and \ - len(special_text) > 4: # it's also underlined - tags.append('underline') - if not show_ascii_formatting_chars: - special_text = special_text[2:-2] # remove *_ _* - else: - if not show_ascii_formatting_chars: - special_text = special_text[1:-1] # remove * * - elif special_text.startswith('/'): # it's an italic text - tags.append('italic') - if special_text[1] == '*' and special_text[-2] == '*' and \ - len(special_text) > 4: # it's also bold - tags.append('bold') - if not show_ascii_formatting_chars: - special_text = special_text[2:-2] # remove /* */ - elif special_text[1] == '_' and special_text[-2] == '_' and \ - len(special_text) > 4: # it's also underlined - tags.append('underline') - if not show_ascii_formatting_chars: - special_text = special_text[2:-2] # remove /_ _/ - else: - if not show_ascii_formatting_chars: - special_text = special_text[1:-1] # remove / / - elif special_text.startswith('_'): # it's an underlined text - tags.append('underline') - if special_text[1] == '*' and special_text[-2] == '*' and \ - len(special_text) > 4: # it's also bold - tags.append('bold') - if not show_ascii_formatting_chars: - special_text = special_text[2:-2] # remove _* *_ - elif special_text[1] == '/' and special_text[-2] == '/' and \ - len(special_text) > 4: # it's also italic - tags.append('italic') - if not show_ascii_formatting_chars: - special_text = special_text[2:-2] # remove _/ /_ - else: - if not show_ascii_formatting_chars: - special_text = special_text[1:-1] # remove _ _ - elif gajim.HAVE_LATEX and special_text.startswith('$$') and \ - special_text.endswith('$$') and graphics: - try: - imagepath = latex.latex_to_image(special_text[2:-2]) - except LatexError, e: - # print the error after the line has been written - gobject.idle_add(self.print_conversation_line, str(e), '', 'info', - '', None) - imagepath = None - end_iter = buffer_.get_end_iter() - if imagepath is not None: - anchor = buffer_.create_child_anchor(end_iter) - img = gtk.Image() - img.set_from_file(imagepath) - img.show() - # add - self.tv.add_child_at_anchor(img, anchor) - # delete old file - try: - os.remove(imagepath) - except Exception: - pass - else: - buffer_.insert(end_iter, special_text) - use_other_tags = False - else: - # It's nothing special - if use_other_tags: - end_iter = buffer_.get_end_iter() - insert_tags_func = buffer_.insert_with_tags_by_name - if other_tags and isinstance(other_tags[0], gtk.TextTag): - insert_tags_func = buffer_.insert_with_tags - - insert_tags_func(end_iter, special_text, *other_tags) - - if tags: - end_iter = buffer_.get_end_iter() - all_tags = tags[:] - if use_other_tags: - all_tags += other_tags - buffer_.insert_with_tags_by_name(end_iter, special_text, *all_tags) - - def print_empty_line(self): - buffer_ = self.tv.get_buffer() - end_iter = buffer_.get_end_iter() - buffer_.insert_with_tags_by_name(end_iter, '\n', 'eol') - - def print_conversation_line(self, text, jid, kind, name, tim, - other_tags_for_name=[], other_tags_for_time=[], - other_tags_for_text=[], subject=None, old_kind=None, xhtml=None, - simple=False, graphics=True): - """ - Print 'chat' type messages - """ - buffer_ = self.tv.get_buffer() - buffer_.begin_user_action() - if self.marks_queue.full(): - # remove oldest line - m1 = self.marks_queue.get() - m2 = self.marks_queue.get() - i1 = buffer_.get_iter_at_mark(m1) - i2 = buffer_.get_iter_at_mark(m2) - buffer_.delete(i1, i2) - buffer_.delete_mark(m1) - end_iter = buffer_.get_end_iter() - end_offset = end_iter.get_offset() - at_the_end = self.at_the_end() - move_selection = False - if buffer_.get_has_selection() and buffer_.get_selection_bounds()[1].\ - get_offset() == end_offset: - move_selection = True - - # Create one mark and add it to queue once if it's the first line - # else twice (one for end bound, one for start bound) - mark = None - if buffer_.get_char_count() > 0: - if not simple: - buffer_.insert_with_tags_by_name(end_iter, '\n', 'eol') - if move_selection: - sel_start, sel_end = buffer_.get_selection_bounds() - sel_end.backward_char() - buffer_.select_range(sel_start, sel_end) - mark = buffer_.create_mark(None, end_iter, left_gravity=True) - self.marks_queue.put(mark) - if not mark: - mark = buffer_.create_mark(None, end_iter, left_gravity=True) - self.marks_queue.put(mark) - if kind == 'incoming_queue': - kind = 'incoming' - if old_kind == 'incoming_queue': - old_kind = 'incoming' - # print the time stamp - if not tim: - # We don't have tim for outgoing messages... - tim = time.localtime() - current_print_time = gajim.config.get('print_time') - if current_print_time == 'always' and kind != 'info' and not simple: - timestamp_str = self.get_time_to_show(tim) - timestamp = time.strftime(timestamp_str, tim) - buffer_.insert_with_tags_by_name(end_iter, timestamp, - *other_tags_for_time) - elif current_print_time == 'sometimes' and kind != 'info' and not simple: - every_foo_seconds = 60 * gajim.config.get( - 'print_ichat_every_foo_minutes') - seconds_passed = time.mktime(tim) - self.last_time_printout - if seconds_passed > every_foo_seconds: - self.last_time_printout = time.mktime(tim) - end_iter = buffer_.get_end_iter() - if gajim.config.get('print_time_fuzzy') > 0: - ft = self.fc.fuzzy_time(gajim.config.get('print_time_fuzzy'), tim) - tim_format = ft.decode(locale.getpreferredencoding()) - else: - tim_format = self.get_time_to_show(tim) - buffer_.insert_with_tags_by_name(end_iter, tim_format + '\n', - 'time_sometimes') - # kind = info, we print things as if it was a status: same color, ... - if kind in ('error', 'info'): - kind = 'status' - other_text_tag = self.detect_other_text_tag(text, kind) - text_tags = other_tags_for_text[:] # create a new list - if other_text_tag: - # note that color of /me may be overwritten in gc_control - text_tags.append(other_text_tag) - else: # not status nor /me - if gajim.config.get('chat_merge_consecutive_nickname'): - if kind != old_kind: - self.print_name(name, kind, other_tags_for_name) - else: - self.print_real_text(gajim.config.get( - 'chat_merge_consecutive_nickname_indent')) - else: - self.print_name(name, kind, other_tags_for_name) - if kind == 'incoming': - text_tags.append('incomingtxt') - elif kind == 'outgoing': - text_tags.append('outgoingtxt') - self.print_subject(subject) - self.print_real_text(text, text_tags, name, xhtml, graphics=graphics) - - # scroll to the end of the textview - if at_the_end or kind == 'outgoing': - # we are at the end or we are sending something - # scroll to the end (via idle in case the scrollbar has appeared) - if gajim.config.get('use_smooth_scrolling'): - gobject.idle_add(self.smooth_scroll_to_end) - else: - gobject.idle_add(self.scroll_to_end) - - buffer_.end_user_action() - - def get_time_to_show(self, tim): - """ - Get the time, with the day before if needed and return it. It DOESN'T - format a fuzzy time - """ - format = '' - # get difference in days since epoch (86400 = 24*3600) - # number of days since epoch for current time (in GMT) - - # number of days since epoch for message (in GMT) - diff_day = int(timegm(time.localtime())) / 86400 -\ - int(timegm(tim)) / 86400 - if diff_day == 0: - day_str = '' - else: - #%i is day in year (1-365) - day_str = i18n.ngettext('Yesterday', '%i days ago', diff_day, - replace_plural=diff_day) - if day_str: - format += day_str + ' ' - timestamp_str = gajim.config.get('time_stamp') - timestamp_str = helpers.from_one_line(timestamp_str) - format += timestamp_str - tim_format = time.strftime(format, tim) - if locale.getpreferredencoding() != 'KOI8-R': - # if tim_format comes as unicode because of day_str. - # we convert it to the encoding that we want (and that is utf-8) - tim_format = helpers.ensure_utf8_string(tim_format) - return tim_format - - def detect_other_text_tag(self, text, kind): - if kind == 'status': - return kind - elif text.startswith('/me ') or text.startswith('/me\n'): - return kind - - def print_name(self, name, kind, other_tags_for_name): - if name: - buffer_ = self.tv.get_buffer() - end_iter = buffer_.get_end_iter() - name_tags = other_tags_for_name[:] # create a new list - name_tags.append(kind) - before_str = gajim.config.get('before_nickname') - before_str = helpers.from_one_line(before_str) - after_str = gajim.config.get('after_nickname') - after_str = helpers.from_one_line(after_str) - format = before_str + name + after_str + ' ' - buffer_.insert_with_tags_by_name(end_iter, format, *name_tags) - - def print_subject(self, subject): - if subject: # if we have subject, show it too! - subject = _('Subject: %s\n') % subject - buffer_ = self.tv.get_buffer() - end_iter = buffer_.get_end_iter() - buffer_.insert(end_iter, subject) - self.print_empty_line() - - def print_real_text(self, text, text_tags=[], name=None, xhtml=None, - graphics=True): - """ - Add normal and special text. call this to add text - """ - if xhtml: - try: - if name and (text.startswith('/me ') or text.startswith('/me\n')): - xhtml = xhtml.replace('/me', '* %s' % (name,), 1) - self.tv.display_html(xhtml.encode('utf-8'), self) - return - except Exception, e: - gajim.log.debug('Error processing xhtml' + str(e)) - gajim.log.debug('with |' + xhtml + '|') - - # /me is replaced by name if name is given - if name and (text.startswith('/me ') or text.startswith('/me\n')): - text = '* ' + name + text[3:] - text_tags.append('italic') - # detect urls formatting and if the user has it on emoticons - self.detect_and_print_special_text(text, text_tags, graphics=graphics) - -# vim: se ts=3: + """ + Class for the conversation textview (where user reads already said messages) + for chat/groupchat windows + """ + __gsignals__ = dict( + quote = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION, + None, # return value + (str, ) # arguments + ) + ) + + FOCUS_OUT_LINE_PIXBUF = gtk.gdk.pixbuf_new_from_file(os.path.join( + gajim.DATA_DIR, 'pixmaps', 'muc_separator.png')) + XEP0184_WARNING_PIXBUF = gtk.gdk.pixbuf_new_from_file(os.path.join( + gajim.DATA_DIR, 'pixmaps', 'receipt_missing.png')) + + # smooth scroll constants + MAX_SCROLL_TIME = 0.4 # seconds + SCROLL_DELAY = 33 # milliseconds + + def __init__(self, account, used_in_history_window = False): + """ + If used_in_history_window is True, then we do not show Clear menuitem in + context menu + """ + gobject.GObject.__init__(self) + self.used_in_history_window = used_in_history_window + + self.fc = FuzzyClock() + + + # no need to inherit TextView, use it as atrribute is safer + self.tv = HtmlTextView() + self.tv.html_hyperlink_handler = self.html_hyperlink_handler + + # set properties + self.tv.set_border_width(1) + self.tv.set_accepts_tab(True) + self.tv.set_editable(False) + self.tv.set_cursor_visible(False) + self.tv.set_wrap_mode(gtk.WRAP_WORD_CHAR) + self.tv.set_left_margin(2) + self.tv.set_right_margin(2) + self.handlers = {} + self.images = [] + self.image_cache = {} + self.xep0184_marks = {} + self.xep0184_shown = {} + + # It's True when we scroll in the code, so we can detect scroll from user + self.auto_scrolling = False + + # connect signals + id_ = self.tv.connect('motion_notify_event', + self.on_textview_motion_notify_event) + self.handlers[id_] = self.tv + id_ = self.tv.connect('populate_popup', self.on_textview_populate_popup) + self.handlers[id_] = self.tv + id_ = self.tv.connect('button_press_event', + self.on_textview_button_press_event) + self.handlers[id_] = self.tv + + id_ = self.tv.connect('expose-event', + self.on_textview_expose_event) + self.handlers[id_] = self.tv + + + self.account = account + self.change_cursor = False + self.last_time_printout = 0 + + font = pango.FontDescription(gajim.config.get('conversation_font')) + self.tv.modify_font(font) + buffer_ = self.tv.get_buffer() + end_iter = buffer_.get_end_iter() + buffer_.create_mark('end', end_iter, False) + + self.tagIn = buffer_.create_tag('incoming') + color = gajim.config.get('inmsgcolor') + font = pango.FontDescription(gajim.config.get('inmsgfont')) + self.tagIn.set_property('foreground', color) + self.tagIn.set_property('font-desc', font) + + self.tagOut = buffer_.create_tag('outgoing') + color = gajim.config.get('outmsgcolor') + font = pango.FontDescription(gajim.config.get('outmsgfont')) + self.tagOut.set_property('foreground', color) + self.tagOut.set_property('font-desc', font) + + self.tagStatus = buffer_.create_tag('status') + color = gajim.config.get('statusmsgcolor') + font = pango.FontDescription(gajim.config.get('satusmsgfont')) + self.tagStatus.set_property('foreground', color) + self.tagStatus.set_property('font-desc', font) + + self.tagInText = buffer_.create_tag('incomingtxt') + color = gajim.config.get('inmsgtxtcolor') + font = pango.FontDescription(gajim.config.get('inmsgtxtfont')) + if color: + self.tagInText.set_property('foreground', color) + self.tagInText.set_property('font-desc', font) + + self.tagOutText = buffer_.create_tag('outgoingtxt') + color = gajim.config.get('outmsgtxtcolor') + if color: + font = pango.FontDescription(gajim.config.get('outmsgtxtfont')) + self.tagOutText.set_property('foreground', color) + self.tagOutText.set_property('font-desc', font) + + colors = gajim.config.get('gc_nicknames_colors') + colors = colors.split(':') + for i, color in enumerate(colors): + tagname = 'gc_nickname_color_' + str(i) + tag = buffer_.create_tag(tagname) + tag.set_property('foreground', color) + + tag = buffer_.create_tag('marked') + color = gajim.config.get('markedmsgcolor') + tag.set_property('foreground', color) + tag.set_property('weight', pango.WEIGHT_BOLD) + + tag = buffer_.create_tag('time_sometimes') + tag.set_property('foreground', 'darkgrey') + tag.set_property('scale', pango.SCALE_SMALL) + tag.set_property('justification', gtk.JUSTIFY_CENTER) + + tag = buffer_.create_tag('small') + tag.set_property('scale', pango.SCALE_SMALL) + + tag = buffer_.create_tag('restored_message') + color = gajim.config.get('restored_messages_color') + tag.set_property('foreground', color) + + self.tagURL = buffer_.create_tag('url') + color = gajim.config.get('urlmsgcolor') + self.tagURL.set_property('foreground', color) + self.tagURL.set_property('underline', pango.UNDERLINE_SINGLE) + id_ = self.tagURL.connect('event', self.hyperlink_handler, 'url') + self.handlers[id_] = self.tagURL + + self.tagMail = buffer_.create_tag('mail') + self.tagMail.set_property('foreground', color) + self.tagMail.set_property('underline', pango.UNDERLINE_SINGLE) + id_ = self.tagMail.connect('event', self.hyperlink_handler, 'mail') + self.handlers[id_] = self.tagMail + + self.tagXMPP = buffer_.create_tag('xmpp') + self.tagXMPP.set_property('foreground', color) + self.tagXMPP.set_property('underline', pango.UNDERLINE_SINGLE) + id_ = self.tagXMPP.connect('event', self.hyperlink_handler, 'xmpp') + self.handlers[id_] = self.tagXMPP + + self.tagSthAtSth = buffer_.create_tag('sth_at_sth') + self.tagSthAtSth.set_property('foreground', color) + self.tagSthAtSth.set_property('underline', pango.UNDERLINE_SINGLE) + id_ = self.tagSthAtSth.connect('event', self.hyperlink_handler, + 'sth_at_sth') + self.handlers[id_] = self.tagSthAtSth + + tag = buffer_.create_tag('bold') + tag.set_property('weight', pango.WEIGHT_BOLD) + + tag = buffer_.create_tag('italic') + tag.set_property('style', pango.STYLE_ITALIC) + + tag = buffer_.create_tag('underline') + tag.set_property('underline', pango.UNDERLINE_SINGLE) + + buffer_.create_tag('focus-out-line', justification = gtk.JUSTIFY_CENTER) + + tag = buffer_.create_tag('xep0184-warning') + + # One mark at the begining then 2 marks between each lines + size = gajim.config.get('max_conversation_lines') + size = 2 * size - 1 + self.marks_queue = Queue.Queue(size) + + self.allow_focus_out_line = True + # holds a mark at the end of --- line + self.focus_out_end_mark = None + + self.xep0184_warning_tooltip = tooltips.BaseTooltip() + + self.line_tooltip = tooltips.BaseTooltip() + # use it for hr too + self.tv.focus_out_line_pixbuf = ConversationTextview.FOCUS_OUT_LINE_PIXBUF + self.smooth_id = None + + def del_handlers(self): + for i in self.handlers.keys(): + if self.handlers[i].handler_is_connected(i): + self.handlers[i].disconnect(i) + del self.handlers + self.tv.destroy() + #FIXME: + # self.line_tooltip.destroy() + + def update_tags(self): + self.tagIn.set_property('foreground', gajim.config.get('inmsgcolor')) + self.tagOut.set_property('foreground', gajim.config.get('outmsgcolor')) + self.tagStatus.set_property('foreground', + gajim.config.get('statusmsgcolor')) + self.tagURL.set_property('foreground', gajim.config.get('urlmsgcolor')) + self.tagMail.set_property('foreground', gajim.config.get('urlmsgcolor')) + + def at_the_end(self): + buffer_ = self.tv.get_buffer() + end_iter = buffer_.get_end_iter() + end_rect = self.tv.get_iter_location(end_iter) + visible_rect = self.tv.get_visible_rect() + if end_rect.y <= (visible_rect.y + visible_rect.height): + return True + return False + + # Smooth scrolling inspired by Pidgin code + def smooth_scroll(self): + parent = self.tv.get_parent() + if not parent: + return False + vadj = parent.get_vadjustment() + max_val = vadj.upper - vadj.page_size + 1 + cur_val = vadj.get_value() + # scroll by 1/3rd of remaining distance + onethird = cur_val + ((max_val - cur_val) / 3.0) + self.auto_scrolling = True + vadj.set_value(onethird) + self.auto_scrolling = False + if max_val - onethird < 0.01: + self.smooth_id = None + self.smooth_scroll_timer.cancel() + return False + return True + + def smooth_scroll_timeout(self): + gobject.idle_add(self.do_smooth_scroll_timeout) + return + + def do_smooth_scroll_timeout(self): + if not self.smooth_id: + # we finished scrolling + return + gobject.source_remove(self.smooth_id) + self.smooth_id = None + parent = self.tv.get_parent() + if parent: + vadj = parent.get_vadjustment() + self.auto_scrolling = True + vadj.set_value(vadj.upper - vadj.page_size + 1) + self.auto_scrolling = False + + def smooth_scroll_to_end(self): + if None != self.smooth_id: # already scrolling + return False + self.smooth_id = gobject.timeout_add(self.SCROLL_DELAY, + self.smooth_scroll) + self.smooth_scroll_timer = Timer(self.MAX_SCROLL_TIME, + self.smooth_scroll_timeout) + self.smooth_scroll_timer.start() + return False + + def scroll_to_end(self): + parent = self.tv.get_parent() + buffer_ = self.tv.get_buffer() + end_mark = buffer_.get_mark('end') + if not end_mark: + return False + self.auto_scrolling = True + self.tv.scroll_to_mark(end_mark, 0, True, 0, 1) + adjustment = parent.get_hadjustment() + adjustment.set_value(0) + self.auto_scrolling = False + return False # when called in an idle_add, just do it once + + def bring_scroll_to_end(self, diff_y = 0, + use_smooth=gajim.config.get('use_smooth_scrolling')): + ''' scrolls to the end of textview if end is not visible ''' + buffer_ = self.tv.get_buffer() + end_iter = buffer_.get_end_iter() + end_rect = self.tv.get_iter_location(end_iter) + visible_rect = self.tv.get_visible_rect() + # scroll only if expected end is not visible + if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y): + if use_smooth: + gobject.idle_add(self.smooth_scroll_to_end) + else: + gobject.idle_add(self.scroll_to_end_iter) + + def scroll_to_end_iter(self): + buffer_ = self.tv.get_buffer() + end_iter = buffer_.get_end_iter() + if not end_iter: + return False + self.tv.scroll_to_iter(end_iter, 0, False, 1, 1) + return False # when called in an idle_add, just do it once + + def stop_scrolling(self): + if self.smooth_id: + gobject.source_remove(self.smooth_id) + self.smooth_id = None + self.smooth_scroll_timer.cancel() + + def show_xep0184_warning(self, id_): + if id_ in self.xep0184_marks: + return + + buffer_ = self.tv.get_buffer() + buffer_.begin_user_action() + + self.xep0184_marks[id_] = buffer_.create_mark(None, + buffer_.get_end_iter(), left_gravity=True) + self.xep0184_shown[id_] = NOT_SHOWN + + def show_it(): + if (not id_ in self.xep0184_shown) or \ + self.xep0184_shown[id_] == ALREADY_RECEIVED: + return False + + end_iter = buffer_.get_iter_at_mark( + self.xep0184_marks[id_]) + buffer_.insert(end_iter, ' ') + buffer_.insert_pixbuf(end_iter, + ConversationTextview.XEP0184_WARNING_PIXBUF) + before_img_iter = buffer_.get_iter_at_mark( + self.xep0184_marks[id_]) + before_img_iter.forward_char() + post_img_iter = before_img_iter.copy() + post_img_iter.forward_char() + buffer_.apply_tag_by_name('xep0184-warning', before_img_iter, + post_img_iter) + + self.xep0184_shown[id_] = SHOWN + return False + gobject.timeout_add_seconds(3, show_it) + + buffer_.end_user_action() + + def hide_xep0184_warning(self, id_): + if id_ not in self.xep0184_marks: + return + + if self.xep0184_shown[id_] == NOT_SHOWN: + self.xep0184_shown[id_] = ALREADY_RECEIVED + return + + buffer_ = self.tv.get_buffer() + buffer_.begin_user_action() + + begin_iter = buffer_.get_iter_at_mark(self.xep0184_marks[id_]) + + end_iter = begin_iter.copy() + # XXX: Is there a nicer way? + end_iter.forward_char() + end_iter.forward_char() + + buffer_.delete(begin_iter, end_iter) + buffer_.delete_mark(self.xep0184_marks[id_]) + + buffer_.end_user_action() + + del self.xep0184_marks[id_] + del self.xep0184_shown[id_] + + def show_focus_out_line(self): + if not self.allow_focus_out_line: + # if room did not receive focus-in from the last time we added + # --- line then do not readd + return + + print_focus_out_line = False + buffer_ = self.tv.get_buffer() + + if self.focus_out_end_mark is None: + # this happens only first time we focus out on this room + print_focus_out_line = True + + else: + focus_out_end_iter = buffer_.get_iter_at_mark(self.focus_out_end_mark) + focus_out_end_iter_offset = focus_out_end_iter.get_offset() + if focus_out_end_iter_offset != buffer_.get_end_iter().get_offset(): + # this means after last-focus something was printed + # (else end_iter's offset is the same as before) + # only then print ---- line (eg. we avoid printing many following + # ---- lines) + print_focus_out_line = True + + if print_focus_out_line and buffer_.get_char_count() > 0: + buffer_.begin_user_action() + + # remove previous focus out line if such focus out line exists + if self.focus_out_end_mark is not None: + end_iter_for_previous_line = buffer_.get_iter_at_mark( + self.focus_out_end_mark) + begin_iter_for_previous_line = end_iter_for_previous_line.copy() + # img_char+1 (the '\n') + begin_iter_for_previous_line.backward_chars(2) + + # remove focus out line + buffer_.delete(begin_iter_for_previous_line, + end_iter_for_previous_line) + buffer_.delete_mark(self.focus_out_end_mark) + + # add the new focus out line + end_iter = buffer_.get_end_iter() + buffer_.insert(end_iter, '\n') + buffer_.insert_pixbuf(end_iter, + ConversationTextview.FOCUS_OUT_LINE_PIXBUF) + + end_iter = buffer_.get_end_iter() + before_img_iter = end_iter.copy() + # one char back (an image also takes one char) + before_img_iter.backward_char() + buffer_.apply_tag_by_name('focus-out-line', before_img_iter, end_iter) + + self.allow_focus_out_line = False + + # update the iter we hold to make comparison the next time + self.focus_out_end_mark = buffer_.create_mark(None, + buffer_.get_end_iter(), left_gravity=True) + + buffer_.end_user_action() + + # scroll to the end (via idle in case the scrollbar has appeared) + gobject.idle_add(self.scroll_to_end) + + def show_xep0184_warning_tooltip(self): + pointer = self.tv.get_pointer() + x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, + pointer[0], pointer[1]) + tags = self.tv.get_iter_at_location(x, y).get_tags() + tag_table = self.tv.get_buffer().get_tag_table() + xep0184_warning = False + for tag in tags: + if tag == tag_table.lookup('xep0184-warning'): + xep0184_warning = True + break + if xep0184_warning and not self.xep0184_warning_tooltip.win: + # check if the current pointer is still over the line + position = self.tv.window.get_origin() + self.xep0184_warning_tooltip.show_tooltip(_('This icon indicates that ' + 'this message has not yet\nbeen received by the remote end. ' + "If this icon stays\nfor a long time, it's likely the message got " + 'lost.'), 8, position[1] + pointer[1]) + + def show_line_tooltip(self): + pointer = self.tv.get_pointer() + x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, + pointer[0], pointer[1]) + tags = self.tv.get_iter_at_location(x, y).get_tags() + tag_table = self.tv.get_buffer().get_tag_table() + over_line = False + for tag in tags: + if tag == tag_table.lookup('focus-out-line'): + over_line = True + break + if over_line and not self.line_tooltip.win: + # check if the current pointer is still over the line + position = self.tv.window.get_origin() + self.line_tooltip.show_tooltip(_('Text below this line is what has ' + 'been said since the\nlast time you paid attention to this group ' + 'chat'), 8, position[1] + pointer[1]) + + def on_textview_expose_event(self, widget, event): + expalloc = event.area + exp_x0 = expalloc.x + exp_y0 = expalloc.y + exp_x1 = exp_x0 + expalloc.width + exp_y1 = exp_y0 + expalloc.height + + try: + tryfirst = [self.image_cache[(exp_x0, exp_y0)]] + except KeyError: + tryfirst = [] + + for image in tryfirst + self.images: + imgalloc = image.allocation + img_x0 = imgalloc.x + img_y0 = imgalloc.y + img_x1 = img_x0 + imgalloc.width + img_y1 = img_y0 + imgalloc.height + + if img_x0 <= exp_x0 and img_y0 <= exp_y0 and \ + exp_x1 <= img_x1 and exp_y1 <= img_y1: + self.image_cache[(img_x0, img_y0)] = image + widget.propagate_expose(image, event) + return True + return False + + def on_textview_motion_notify_event(self, widget, event): + """ + Change the cursor to a hand when we are over a mail or an url + """ + pointer_x, pointer_y = self.tv.window.get_pointer()[0:2] + x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, + pointer_x, pointer_y) + tags = self.tv.get_iter_at_location(x, y).get_tags() + if self.change_cursor: + self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( + gtk.gdk.Cursor(gtk.gdk.XTERM)) + self.change_cursor = False + tag_table = self.tv.get_buffer().get_tag_table() + over_line = False + xep0184_warning = False + for tag in tags: + if tag in (tag_table.lookup('url'), tag_table.lookup('mail'), \ + tag_table.lookup('xmpp'), tag_table.lookup('sth_at_sth')): + self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( + gtk.gdk.Cursor(gtk.gdk.HAND2)) + self.change_cursor = True + elif tag == tag_table.lookup('focus-out-line'): + over_line = True + elif tag == tag_table.lookup('xep0184-warning'): + xep0184_warning = True + + if self.line_tooltip.timeout != 0: + # Check if we should hide the line tooltip + if not over_line: + self.line_tooltip.hide_tooltip() + if self.xep0184_warning_tooltip.timeout != 0: + # Check if we should hide the XEP-184 warning tooltip + if not xep0184_warning: + self.xep0184_warning_tooltip.hide_tooltip() + if over_line and not self.line_tooltip.win: + self.line_tooltip.timeout = gobject.timeout_add(500, + self.show_line_tooltip) + self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( + gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)) + self.change_cursor = True + if xep0184_warning and not self.xep0184_warning_tooltip.win: + self.xep0184_warning_tooltip.timeout = gobject.timeout_add(500, + self.show_xep0184_warning_tooltip) + self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( + gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)) + self.change_cursor = True + + def clear(self, tv = None): + """ + Clear text in the textview + """ + buffer_ = self.tv.get_buffer() + start, end = buffer_.get_bounds() + buffer_.delete(start, end) + size = gajim.config.get('max_conversation_lines') + size = 2 * size - 1 + self.marks_queue = Queue.Queue(size) + self.focus_out_end_mark = None + + def visit_url_from_menuitem(self, widget, link): + """ + Basically it filters out the widget instance + """ + helpers.launch_browser_mailer('url', link) + + def on_textview_populate_popup(self, textview, menu): + """ + Override the default context menu and we prepend Clear (only if + used_in_history_window is False) and if we have sth selected we show a + submenu with actions on the phrase (see + on_conversation_textview_button_press_event) + """ + separator_menuitem_was_added = False + if not self.used_in_history_window: + item = gtk.SeparatorMenuItem() + menu.prepend(item) + separator_menuitem_was_added = True + + item = gtk.ImageMenuItem(gtk.STOCK_CLEAR) + menu.prepend(item) + id_ = item.connect('activate', self.clear) + self.handlers[id_] = item + + if self.selected_phrase: + if not separator_menuitem_was_added: + item = gtk.SeparatorMenuItem() + menu.prepend(item) + + if not self.used_in_history_window: + item = gtk.MenuItem(_('_Quote')) + id_ = item.connect('activate', self.on_quote) + self.handlers[id_] = item + menu.prepend(item) + + _selected_phrase = helpers.reduce_chars_newlines( + self.selected_phrase, 25, 2) + item = gtk.MenuItem(_('_Actions for "%s"') % _selected_phrase) + menu.prepend(item) + submenu = gtk.Menu() + item.set_submenu(submenu) + + always_use_en = gajim.config.get('always_english_wikipedia') + if always_use_en: + link = 'http://en.wikipedia.org/wiki/Special:Search?search=%s'\ + % self.selected_phrase + else: + link = 'http://%s.wikipedia.org/wiki/Special:Search?search=%s'\ + % (gajim.LANG, self.selected_phrase) + item = gtk.MenuItem(_('Read _Wikipedia Article')) + id_ = item.connect('activate', self.visit_url_from_menuitem, link) + self.handlers[id_] = item + submenu.append(item) + + item = gtk.MenuItem(_('Look it up in _Dictionary')) + dict_link = gajim.config.get('dictionary_url') + if dict_link == 'WIKTIONARY': + # special link (yeah undocumented but default) + always_use_en = gajim.config.get('always_english_wiktionary') + if always_use_en: + link = 'http://en.wiktionary.org/wiki/Special:Search?search=%s'\ + % self.selected_phrase + else: + link = 'http://%s.wiktionary.org/wiki/Special:Search?search=%s'\ + % (gajim.LANG, self.selected_phrase) + id_ = item.connect('activate', self.visit_url_from_menuitem, link) + self.handlers[id_] = item + else: + if dict_link.find('%s') == -1: + # we must have %s in the url if not WIKTIONARY + item = gtk.MenuItem(_( + 'Dictionary URL is missing an "%s" and it is not WIKTIONARY')) + item.set_property('sensitive', False) + else: + link = dict_link % self.selected_phrase + id_ = item.connect('activate', self.visit_url_from_menuitem, + link) + self.handlers[id_] = item + submenu.append(item) + + + search_link = gajim.config.get('search_engine') + if search_link.find('%s') == -1: + # we must have %s in the url + item = gtk.MenuItem(_('Web Search URL is missing an "%s"')) + item.set_property('sensitive', False) + else: + item = gtk.MenuItem(_('Web _Search for it')) + link = search_link % self.selected_phrase + id_ = item.connect('activate', self.visit_url_from_menuitem, link) + self.handlers[id_] = item + submenu.append(item) + + item = gtk.MenuItem(_('Open as _Link')) + id_ = item.connect('activate', self.visit_url_from_menuitem, link) + self.handlers[id_] = item + submenu.append(item) + + menu.show_all() + + def on_quote(self, widget): + self.emit('quote', self.selected_phrase) + + def on_textview_button_press_event(self, widget, event): + # If we clicked on a taged text do NOT open the standard popup menu + # if normal text check if we have sth selected + self.selected_phrase = '' # do not move belove event button check! + + if event.button != 3: # if not right click + return False + + x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, + int(event.x), int(event.y)) + iter_ = self.tv.get_iter_at_location(x, y) + tags = iter_.get_tags() + + + if tags: # we clicked on sth special (it can be status message too) + for tag in tags: + tag_name = tag.get_property('name') + if tag_name in ('url', 'mail', 'xmpp', 'sth_at_sth'): + return True # we block normal context menu + + # we check if sth was selected and if it was we assign + # selected_phrase variable + # so on_conversation_textview_populate_popup can use it + buffer_ = self.tv.get_buffer() + return_val = buffer_.get_selection_bounds() + if return_val: # if sth was selected when we right-clicked + # get the selected text + start_sel, finish_sel = return_val[0], return_val[1] + self.selected_phrase = buffer_.get_text(start_sel, finish_sel).decode( + 'utf-8') + elif ord(iter_.get_char()) > 31: + # we clicked on a word, do as if it's selected for context menu + start_sel = iter_.copy() + if not start_sel.starts_word(): + start_sel.backward_word_start() + finish_sel = iter_.copy() + if not finish_sel.ends_word(): + finish_sel.forward_word_end() + self.selected_phrase = buffer_.get_text(start_sel, finish_sel).decode( + 'utf-8') + + def on_open_link_activate(self, widget, kind, text): + helpers.launch_browser_mailer(kind, text) + + def on_copy_link_activate(self, widget, text): + clip = gtk.clipboard_get() + clip.set_text(text) + + def on_start_chat_activate(self, widget, jid): + gajim.interface.new_chat_from_jid(self.account, jid) + + def on_join_group_chat_menuitem_activate(self, widget, room_jid): + if 'join_gc' in gajim.interface.instances[self.account]: + instance = gajim.interface.instances[self.account]['join_gc'] + instance.xml.get_widget('room_jid_entry').set_text(room_jid) + gajim.interface.instances[self.account]['join_gc'].window.present() + else: + try: + dialogs.JoinGroupchatWindow(account=self.account, room_jid=room_jid) + except GajimGeneralException: + pass + + def on_add_to_roster_activate(self, widget, jid): + dialogs.AddNewContactWindow(self.account, jid) + + def make_link_menu(self, event, kind, text): + xml = gtkgui_helpers.get_glade('chat_context_menu.glade') + menu = xml.get_widget('chat_context_menu') + childs = menu.get_children() + if kind == 'url': + id_ = childs[0].connect('activate', self.on_copy_link_activate, text) + self.handlers[id_] = childs[0] + id_ = childs[1].connect('activate', self.on_open_link_activate, kind, + text) + self.handlers[id_] = childs[1] + childs[2].hide() # copy mail address + childs[3].hide() # open mail composer + childs[4].hide() # jid section separator + childs[5].hide() # start chat + childs[6].hide() # join group chat + childs[7].hide() # add to roster + else: # It's a mail or a JID + # load muc icon + join_group_chat_menuitem = xml.get_widget('join_group_chat_menuitem') + muc_icon = gtkgui_helpers.load_icon('muc_active') + if muc_icon: + join_group_chat_menuitem.set_image(muc_icon) + + text = text.lower() + if text.startswith('xmpp:'): + text = text[5:] + id_ = childs[2].connect('activate', self.on_copy_link_activate, text) + self.handlers[id_] = childs[2] + id_ = childs[3].connect('activate', self.on_open_link_activate, kind, + text) + self.handlers[id_] = childs[3] + id_ = childs[5].connect('activate', self.on_start_chat_activate, text) + self.handlers[id_] = childs[5] + id_ = childs[6].connect('activate', + self.on_join_group_chat_menuitem_activate, text) + self.handlers[id_] = childs[6] + + allow_add = False + if self.account: + c = gajim.contacts.get_first_contact_from_jid(self.account, text) + if c and not gajim.contacts.is_pm_from_contact(self.account, c): + if _('Not in Roster') in c.groups: + allow_add = True + else: # he or she's not at all in the account contacts + allow_add = True + + if allow_add: + id_ = childs[7].connect('activate', self.on_add_to_roster_activate, + text) + self.handlers[id_] = childs[7] + childs[7].show() # show add to roster menuitem + else: + childs[7].hide() # hide add to roster menuitem + + if kind == 'xmpp': + childs[2].hide() # copy mail address + childs[3].hide() # open mail composer + childs[4].hide() # jid section separator + elif kind == 'mail': + childs[4].hide() # jid section separator + childs[5].hide() # start chat + childs[6].hide() # join group chat + childs[7].hide() # add to roster + + childs[0].hide() # copy link location + childs[1].hide() # open link in browser + + menu.popup(None, None, None, event.button, event.time) + + def hyperlink_handler(self, texttag, widget, event, iter_, kind): + if event.type == gtk.gdk.BUTTON_PRESS: + begin_iter = iter_.copy() + # we get the begining of the tag + while not begin_iter.begins_tag(texttag): + begin_iter.backward_char() + end_iter = iter_.copy() + # we get the end of the tag + while not end_iter.ends_tag(texttag): + end_iter.forward_char() + word = self.tv.get_buffer().get_text(begin_iter, end_iter).decode( + 'utf-8') + if event.button == 3: # right click + self.make_link_menu(event, kind, word) + else: + # we launch the correct application + if kind == 'xmpp': + word = word[5:] + if '?' in word: + (jid, action) = word.split('?') + if action == 'join': + self.on_join_group_chat_menuitem_activate(None, jid) + else: + self.on_start_chat_activate(None, jid) + else: + self.on_start_chat_activate(None, word) + else: + helpers.launch_browser_mailer(kind, word) + + def html_hyperlink_handler(self, texttag, widget, event, iter_, kind, href): + if event.type == gtk.gdk.BUTTON_PRESS: + if event.button == 3: # right click + self.make_link_menu(event, kind, href) + return True + else: + # we launch the correct application + helpers.launch_browser_mailer(kind, href) + + + def detect_and_print_special_text(self, otext, other_tags, graphics=True): + """ + Detect special text (emots & links & formatting), print normal text + before any special text it founds, then print special text (that happens + many times until last special text is printed) and then return the index + after *last* special text, so we can print it in + print_conversation_line() + """ + buffer_ = self.tv.get_buffer() + + insert_tags_func = buffer_.insert_with_tags_by_name + # detect_and_print_special_text() is also used by + # HtmlHandler.handle_specials() and there tags is gtk.TextTag objects, + # not strings + if other_tags and isinstance(other_tags[0], gtk.TextTag): + insert_tags_func = buffer_.insert_with_tags + + index = 0 + + # Too many special elements (emoticons, LaTeX formulas, etc) + # may cause Gajim to freeze (see #5129). + # We impose an arbitrary limit of 100 specials per message. + specials_limit = 100 + + # basic: links + mail + formatting is always checked (we like that) + if gajim.config.get('emoticons_theme') and graphics: + # search for emoticons & urls + iterator = gajim.interface.emot_and_basic_re.finditer(otext) + else: # search for just urls + mail + formatting + iterator = gajim.interface.basic_pattern_re.finditer(otext) + for match in iterator: + start, end = match.span() + special_text = otext[start:end] + if start > index: + text_before_special_text = otext[index:start] + end_iter = buffer_.get_end_iter() + # we insert normal text + insert_tags_func(end_iter, text_before_special_text, *other_tags) + index = end # update index + + # now print it + self.print_special_text(special_text, other_tags, graphics=graphics) + specials_limit -= 1 + if specials_limit <= 0: + break + + # add the rest of text located in the index and after + end_iter = buffer_.get_end_iter() + insert_tags_func(end_iter, otext[index:], *other_tags) + + return buffer_.get_end_iter() + + def print_special_text(self, special_text, other_tags, graphics=True): + """ + Is called by detect_and_print_special_text and prints special text + (emots, links, formatting) + """ + tags = [] + use_other_tags = True + text_is_valid_uri = False + show_ascii_formatting_chars = \ + gajim.config.get('show_ascii_formatting_chars') + buffer_ = self.tv.get_buffer() + + # Check if we accept this as an uri + schemes = gajim.config.get('uri_schemes').split() + for scheme in schemes: + if special_text.startswith(scheme + ':'): + text_is_valid_uri = True + + possible_emot_ascii_caps = special_text.upper() # emoticons keys are CAPS + if gajim.config.get('emoticons_theme') and \ + possible_emot_ascii_caps in gajim.interface.emoticons.keys() and graphics: + # it's an emoticon + emot_ascii = possible_emot_ascii_caps + end_iter = buffer_.get_end_iter() + anchor = buffer_.create_child_anchor(end_iter) + img = TextViewImage(anchor, special_text) + animations = gajim.interface.emoticons_animations + if not emot_ascii in animations: + animations[emot_ascii] = gtk.gdk.PixbufAnimation( + gajim.interface.emoticons[emot_ascii]) + img.set_from_animation(animations[emot_ascii]) + img.show() + self.images.append(img) + # add with possible animation + self.tv.add_child_at_anchor(img, anchor) + elif special_text.startswith('www.') or \ + special_text.startswith('ftp.') or \ + text_is_valid_uri: + tags.append('url') + use_other_tags = False + elif special_text.startswith('mailto:'): + tags.append('mail') + use_other_tags = False + elif special_text.startswith('xmpp:'): + tags.append('xmpp') + use_other_tags = False + elif gajim.interface.sth_at_sth_dot_sth_re.match(special_text): + # it's a JID or mail + tags.append('sth_at_sth') + use_other_tags = False + elif special_text.startswith('*'): # it's a bold text + tags.append('bold') + if special_text[1] == '/' and special_text[-2] == '/' and\ + len(special_text) > 4: # it's also italic + tags.append('italic') + if not show_ascii_formatting_chars: + special_text = special_text[2:-2] # remove */ /* + elif special_text[1] == '_' and special_text[-2] == '_' and \ + len(special_text) > 4: # it's also underlined + tags.append('underline') + if not show_ascii_formatting_chars: + special_text = special_text[2:-2] # remove *_ _* + else: + if not show_ascii_formatting_chars: + special_text = special_text[1:-1] # remove * * + elif special_text.startswith('/'): # it's an italic text + tags.append('italic') + if special_text[1] == '*' and special_text[-2] == '*' and \ + len(special_text) > 4: # it's also bold + tags.append('bold') + if not show_ascii_formatting_chars: + special_text = special_text[2:-2] # remove /* */ + elif special_text[1] == '_' and special_text[-2] == '_' and \ + len(special_text) > 4: # it's also underlined + tags.append('underline') + if not show_ascii_formatting_chars: + special_text = special_text[2:-2] # remove /_ _/ + else: + if not show_ascii_formatting_chars: + special_text = special_text[1:-1] # remove / / + elif special_text.startswith('_'): # it's an underlined text + tags.append('underline') + if special_text[1] == '*' and special_text[-2] == '*' and \ + len(special_text) > 4: # it's also bold + tags.append('bold') + if not show_ascii_formatting_chars: + special_text = special_text[2:-2] # remove _* *_ + elif special_text[1] == '/' and special_text[-2] == '/' and \ + len(special_text) > 4: # it's also italic + tags.append('italic') + if not show_ascii_formatting_chars: + special_text = special_text[2:-2] # remove _/ /_ + else: + if not show_ascii_formatting_chars: + special_text = special_text[1:-1] # remove _ _ + elif gajim.HAVE_LATEX and special_text.startswith('$$') and \ + special_text.endswith('$$') and graphics: + try: + imagepath = latex.latex_to_image(special_text[2:-2]) + except LatexError, e: + # print the error after the line has been written + gobject.idle_add(self.print_conversation_line, str(e), '', 'info', + '', None) + imagepath = None + end_iter = buffer_.get_end_iter() + if imagepath is not None: + anchor = buffer_.create_child_anchor(end_iter) + img = gtk.Image() + img.set_from_file(imagepath) + img.show() + # add + self.tv.add_child_at_anchor(img, anchor) + # delete old file + try: + os.remove(imagepath) + except Exception: + pass + else: + buffer_.insert(end_iter, special_text) + use_other_tags = False + else: + # It's nothing special + if use_other_tags: + end_iter = buffer_.get_end_iter() + insert_tags_func = buffer_.insert_with_tags_by_name + if other_tags and isinstance(other_tags[0], gtk.TextTag): + insert_tags_func = buffer_.insert_with_tags + + insert_tags_func(end_iter, special_text, *other_tags) + + if tags: + end_iter = buffer_.get_end_iter() + all_tags = tags[:] + if use_other_tags: + all_tags += other_tags + buffer_.insert_with_tags_by_name(end_iter, special_text, *all_tags) + + def print_empty_line(self): + buffer_ = self.tv.get_buffer() + end_iter = buffer_.get_end_iter() + buffer_.insert_with_tags_by_name(end_iter, '\n', 'eol') + + def print_conversation_line(self, text, jid, kind, name, tim, + other_tags_for_name=[], other_tags_for_time=[], + other_tags_for_text=[], subject=None, old_kind=None, xhtml=None, + simple=False, graphics=True): + """ + Print 'chat' type messages + """ + buffer_ = self.tv.get_buffer() + buffer_.begin_user_action() + if self.marks_queue.full(): + # remove oldest line + m1 = self.marks_queue.get() + m2 = self.marks_queue.get() + i1 = buffer_.get_iter_at_mark(m1) + i2 = buffer_.get_iter_at_mark(m2) + buffer_.delete(i1, i2) + buffer_.delete_mark(m1) + end_iter = buffer_.get_end_iter() + end_offset = end_iter.get_offset() + at_the_end = self.at_the_end() + move_selection = False + if buffer_.get_has_selection() and buffer_.get_selection_bounds()[1].\ + get_offset() == end_offset: + move_selection = True + + # Create one mark and add it to queue once if it's the first line + # else twice (one for end bound, one for start bound) + mark = None + if buffer_.get_char_count() > 0: + if not simple: + buffer_.insert_with_tags_by_name(end_iter, '\n', 'eol') + if move_selection: + sel_start, sel_end = buffer_.get_selection_bounds() + sel_end.backward_char() + buffer_.select_range(sel_start, sel_end) + mark = buffer_.create_mark(None, end_iter, left_gravity=True) + self.marks_queue.put(mark) + if not mark: + mark = buffer_.create_mark(None, end_iter, left_gravity=True) + self.marks_queue.put(mark) + if kind == 'incoming_queue': + kind = 'incoming' + if old_kind == 'incoming_queue': + old_kind = 'incoming' + # print the time stamp + if not tim: + # We don't have tim for outgoing messages... + tim = time.localtime() + current_print_time = gajim.config.get('print_time') + if current_print_time == 'always' and kind != 'info' and not simple: + timestamp_str = self.get_time_to_show(tim) + timestamp = time.strftime(timestamp_str, tim) + buffer_.insert_with_tags_by_name(end_iter, timestamp, + *other_tags_for_time) + elif current_print_time == 'sometimes' and kind != 'info' and not simple: + every_foo_seconds = 60 * gajim.config.get( + 'print_ichat_every_foo_minutes') + seconds_passed = time.mktime(tim) - self.last_time_printout + if seconds_passed > every_foo_seconds: + self.last_time_printout = time.mktime(tim) + end_iter = buffer_.get_end_iter() + if gajim.config.get('print_time_fuzzy') > 0: + ft = self.fc.fuzzy_time(gajim.config.get('print_time_fuzzy'), tim) + tim_format = ft.decode(locale.getpreferredencoding()) + else: + tim_format = self.get_time_to_show(tim) + buffer_.insert_with_tags_by_name(end_iter, tim_format + '\n', + 'time_sometimes') + # kind = info, we print things as if it was a status: same color, ... + if kind in ('error', 'info'): + kind = 'status' + other_text_tag = self.detect_other_text_tag(text, kind) + text_tags = other_tags_for_text[:] # create a new list + if other_text_tag: + # note that color of /me may be overwritten in gc_control + text_tags.append(other_text_tag) + else: # not status nor /me + if gajim.config.get('chat_merge_consecutive_nickname'): + if kind != old_kind: + self.print_name(name, kind, other_tags_for_name) + else: + self.print_real_text(gajim.config.get( + 'chat_merge_consecutive_nickname_indent')) + else: + self.print_name(name, kind, other_tags_for_name) + if kind == 'incoming': + text_tags.append('incomingtxt') + elif kind == 'outgoing': + text_tags.append('outgoingtxt') + self.print_subject(subject) + self.print_real_text(text, text_tags, name, xhtml, graphics=graphics) + + # scroll to the end of the textview + if at_the_end or kind == 'outgoing': + # we are at the end or we are sending something + # scroll to the end (via idle in case the scrollbar has appeared) + if gajim.config.get('use_smooth_scrolling'): + gobject.idle_add(self.smooth_scroll_to_end) + else: + gobject.idle_add(self.scroll_to_end) + + buffer_.end_user_action() + + def get_time_to_show(self, tim): + """ + Get the time, with the day before if needed and return it. It DOESN'T + format a fuzzy time + """ + format = '' + # get difference in days since epoch (86400 = 24*3600) + # number of days since epoch for current time (in GMT) - + # number of days since epoch for message (in GMT) + diff_day = int(timegm(time.localtime())) / 86400 -\ + int(timegm(tim)) / 86400 + if diff_day == 0: + day_str = '' + else: + #%i is day in year (1-365) + day_str = i18n.ngettext('Yesterday', '%i days ago', diff_day, + replace_plural=diff_day) + if day_str: + format += day_str + ' ' + timestamp_str = gajim.config.get('time_stamp') + timestamp_str = helpers.from_one_line(timestamp_str) + format += timestamp_str + tim_format = time.strftime(format, tim) + if locale.getpreferredencoding() != 'KOI8-R': + # if tim_format comes as unicode because of day_str. + # we convert it to the encoding that we want (and that is utf-8) + tim_format = helpers.ensure_utf8_string(tim_format) + return tim_format + + def detect_other_text_tag(self, text, kind): + if kind == 'status': + return kind + elif text.startswith('/me ') or text.startswith('/me\n'): + return kind + + def print_name(self, name, kind, other_tags_for_name): + if name: + buffer_ = self.tv.get_buffer() + end_iter = buffer_.get_end_iter() + name_tags = other_tags_for_name[:] # create a new list + name_tags.append(kind) + before_str = gajim.config.get('before_nickname') + before_str = helpers.from_one_line(before_str) + after_str = gajim.config.get('after_nickname') + after_str = helpers.from_one_line(after_str) + format = before_str + name + after_str + ' ' + buffer_.insert_with_tags_by_name(end_iter, format, *name_tags) + + def print_subject(self, subject): + if subject: # if we have subject, show it too! + subject = _('Subject: %s\n') % subject + buffer_ = self.tv.get_buffer() + end_iter = buffer_.get_end_iter() + buffer_.insert(end_iter, subject) + self.print_empty_line() + + def print_real_text(self, text, text_tags=[], name=None, xhtml=None, + graphics=True): + """ + Add normal and special text. call this to add text + """ + if xhtml: + try: + if name and (text.startswith('/me ') or text.startswith('/me\n')): + xhtml = xhtml.replace('/me', '* %s' % (name,), 1) + self.tv.display_html(xhtml.encode('utf-8'), self) + return + except Exception, e: + gajim.log.debug('Error processing xhtml' + str(e)) + gajim.log.debug('with |' + xhtml + '|') + + # /me is replaced by name if name is given + if name and (text.startswith('/me ') or text.startswith('/me\n')): + text = '* ' + name + text[3:] + text_tags.append('italic') + # detect urls formatting and if the user has it on emoticons + self.detect_and_print_special_text(text, text_tags, graphics=graphics) diff --git a/src/dataforms_widget.py b/src/dataforms_widget.py index 4a9681dab..b22a3c3ba 100644 --- a/src/dataforms_widget.py +++ b/src/dataforms_widget.py @@ -38,587 +38,585 @@ import itertools class DataFormWidget(gtk.Alignment, object): # "public" interface - """ - Data Form widget. Use like any other widget - """ + """ + Data Form widget. Use like any other widget + """ - def __init__(self, dataformnode=None): - ''' Create a widget. ''' - gtk.Alignment.__init__(self, xscale=1.0, yscale=1.0) + def __init__(self, dataformnode=None): + ''' Create a widget. ''' + gtk.Alignment.__init__(self, xscale=1.0, yscale=1.0) - self._data_form = None + self._data_form = None - self.xml = gtkgui_helpers.get_glade('data_form_window.glade', - 'data_form_vbox') - self.xml.signal_autoconnect(self) - for name in ('instructions_label', 'instructions_hseparator', - 'single_form_viewport', 'data_form_types_notebook', - 'single_form_scrolledwindow', 'multiple_form_hbox', - 'records_treeview', 'buttons_vbox', 'add_button', 'remove_button', - 'edit_button', 'up_button', 'down_button', 'clear_button'): - self.__dict__[name] = self.xml.get_widget(name) + self.xml = gtkgui_helpers.get_glade('data_form_window.glade', + 'data_form_vbox') + self.xml.signal_autoconnect(self) + for name in ('instructions_label', 'instructions_hseparator', + 'single_form_viewport', 'data_form_types_notebook', + 'single_form_scrolledwindow', 'multiple_form_hbox', + 'records_treeview', 'buttons_vbox', 'add_button', 'remove_button', + 'edit_button', 'up_button', 'down_button', 'clear_button'): + self.__dict__[name] = self.xml.get_widget(name) - self.add(self.xml.get_widget('data_form_vbox')) + self.add(self.xml.get_widget('data_form_vbox')) - if dataformnode is not None: - self.set_data_form(dataformnode) + if dataformnode is not None: + self.set_data_form(dataformnode) - selection = self.records_treeview.get_selection() - selection.connect('changed', self.on_records_selection_changed) - selection.set_mode(gtk.SELECTION_MULTIPLE) + selection = self.records_treeview.get_selection() + selection.connect('changed', self.on_records_selection_changed) + selection.set_mode(gtk.SELECTION_MULTIPLE) - def set_data_form(self, dataform): - """ - Set the data form (xmpp.DataForm) displayed in widget - """ - assert isinstance(dataform, dataforms.DataForm) + def set_data_form(self, dataform): + """ + Set the data form (xmpp.DataForm) displayed in widget + """ + assert isinstance(dataform, dataforms.DataForm) - self.del_data_form() - self._data_form = dataform - if isinstance(dataform, dataforms.SimpleDataForm): - self.build_single_data_form() - else: - self.build_multiple_data_form() + self.del_data_form() + self._data_form = dataform + if isinstance(dataform, dataforms.SimpleDataForm): + self.build_single_data_form() + else: + self.build_multiple_data_form() - # create appropriate description for instructions field if there isn't any - if dataform.instructions == '': - self.instructions_label.set_no_show_all(True) - self.instructions_label.hide() - else: - self.instructions_label.set_text(dataform.instructions) - gtkgui_helpers.label_set_autowrap(self.instructions_label) + # create appropriate description for instructions field if there isn't any + if dataform.instructions == '': + self.instructions_label.set_no_show_all(True) + self.instructions_label.hide() + else: + self.instructions_label.set_text(dataform.instructions) + gtkgui_helpers.label_set_autowrap(self.instructions_label) - def get_data_form(self): - """ - Data form displayed in the widget or None if no form - """ - return self._data_form + def get_data_form(self): + """ + Data form displayed in the widget or None if no form + """ + return self._data_form - def del_data_form(self): - self.clean_data_form() - self._data_form = None + def del_data_form(self): + self.clean_data_form() + self._data_form = None - data_form = property(get_data_form, set_data_form, del_data_form, - 'Data form presented in a widget') + data_form = property(get_data_form, set_data_form, del_data_form, + 'Data form presented in a widget') - def get_title(self): - """ - Get the title of data form, as a unicode object. If no title or no form, - returns u''. Useful for setting window title - """ - if self._data_form is not None: - if self._data_form.title is not None: - return self._data_form.title - return u'' + def get_title(self): + """ + Get the title of data form, as a unicode object. If no title or no form, + returns u''. Useful for setting window title + """ + if self._data_form is not None: + if self._data_form.title is not None: + return self._data_form.title + return u'' - title = property(get_title, None, None, 'Data form title') + title = property(get_title, None, None, 'Data form title') - def show(self): - ''' Treat 'us' as one widget. ''' - self.show_all() + def show(self): + ''' Treat 'us' as one widget. ''' + self.show_all() # "private" methods # we have actually two different kinds of data forms: one is a simple form to fill, # second is a table with several records; - def empty_method(self): - pass + def empty_method(self): + pass - def clean_data_form(self): - """ - Remove data about existing form. This metod is empty, because it is - rewritten by build_*_data_form, according to type of form which is - actually displayed - """ - pass + def clean_data_form(self): + """ + Remove data about existing form. This metod is empty, because it is + rewritten by build_*_data_form, according to type of form which is + actually displayed + """ + pass - def build_single_data_form(self): - '''Invoked when new single form is to be created.''' - assert isinstance(self._data_form, dataforms.SimpleDataForm) + def build_single_data_form(self): + '''Invoked when new single form is to be created.''' + assert isinstance(self._data_form, dataforms.SimpleDataForm) - self.clean_data_form() + self.clean_data_form() - self.singleform = SingleForm(self._data_form) - self.singleform.show() - self.single_form_viewport.add(self.singleform) - self.data_form_types_notebook.set_current_page( - self.data_form_types_notebook.page_num( - self.single_form_scrolledwindow)) + self.singleform = SingleForm(self._data_form) + self.singleform.show() + self.single_form_viewport.add(self.singleform) + self.data_form_types_notebook.set_current_page( + self.data_form_types_notebook.page_num( + self.single_form_scrolledwindow)) - self.clean_data_form = self.clean_single_data_form + self.clean_data_form = self.clean_single_data_form - def clean_single_data_form(self): - """ - Called as clean_data_form, read the docs of clean_data_form(). Remove - form from widget - """ - self.singleform.destroy() - self.clean_data_form = self.empty_method # we won't call it twice - del self.singleform + def clean_single_data_form(self): + """ + Called as clean_data_form, read the docs of clean_data_form(). Remove + form from widget + """ + self.singleform.destroy() + self.clean_data_form = self.empty_method # we won't call it twice + del self.singleform - def build_multiple_data_form(self): - """ - Invoked when new multiple form is to be created - """ - assert isinstance(self._data_form, dataforms.MultipleDataForm) + def build_multiple_data_form(self): + """ + Invoked when new multiple form is to be created + """ + assert isinstance(self._data_form, dataforms.MultipleDataForm) - self.clean_data_form() + self.clean_data_form() - # creating model for form... - fieldtypes = [] - fieldvars = [] - for field in self._data_form.reported.iter_fields(): - # note: we store also text-private and hidden fields, - # we just do not display them. - # TODO: boolean fields - #elif field.type=='boolean': fieldtypes.append(bool) - fieldtypes.append(str) - fieldvars.append(field.var) + # creating model for form... + fieldtypes = [] + fieldvars = [] + for field in self._data_form.reported.iter_fields(): + # note: we store also text-private and hidden fields, + # we just do not display them. + # TODO: boolean fields + #elif field.type=='boolean': fieldtypes.append(bool) + fieldtypes.append(str) + fieldvars.append(field.var) - self.multiplemodel = gtk.ListStore(*fieldtypes) + self.multiplemodel = gtk.ListStore(*fieldtypes) - # moving all data to model - for item in self._data_form.iter_records(): - iter_ = self.multiplemodel.append() - for field in item.iter_fields(): - self.multiplemodel.set_value(iter_, fieldvars.index(field.var), - field.value) + # moving all data to model + for item in self._data_form.iter_records(): + iter_ = self.multiplemodel.append() + for field in item.iter_fields(): + self.multiplemodel.set_value(iter_, fieldvars.index(field.var), + field.value) - # constructing columns... - for field, counter in zip(self._data_form.reported.iter_fields(), - itertools.count()): - self.records_treeview.append_column( - gtk.TreeViewColumn(field.label, gtk.CellRendererText(), - text=counter)) + # constructing columns... + for field, counter in zip(self._data_form.reported.iter_fields(), + itertools.count()): + self.records_treeview.append_column( + gtk.TreeViewColumn(field.label, gtk.CellRendererText(), + text=counter)) - self.records_treeview.set_model(self.multiplemodel) - self.records_treeview.show_all() + self.records_treeview.set_model(self.multiplemodel) + self.records_treeview.show_all() - self.data_form_types_notebook.set_current_page( - self.data_form_types_notebook.page_num( - self.multiple_form_hbox)) + self.data_form_types_notebook.set_current_page( + self.data_form_types_notebook.page_num( + self.multiple_form_hbox)) - self.clean_data_form = self.clean_multiple_data_form + self.clean_data_form = self.clean_multiple_data_form - readwrite = self._data_form.type != 'result' - if not readwrite: - self.buttons_vbox.set_no_show_all(True) - self.buttons_vbox.hide() - else: - self.buttons_vbox.set_no_show_all(False) - # refresh list look - self.refresh_multiple_buttons() + readwrite = self._data_form.type != 'result' + if not readwrite: + self.buttons_vbox.set_no_show_all(True) + self.buttons_vbox.hide() + else: + self.buttons_vbox.set_no_show_all(False) + # refresh list look + self.refresh_multiple_buttons() - def clean_multiple_data_form(self): - """ - Called as clean_data_form, read the docs of clean_data_form(). Remove - form from widget - """ - self.clean_data_form = self.empty_method # we won't call it twice - del self.multiplemodel + def clean_multiple_data_form(self): + """ + Called as clean_data_form, read the docs of clean_data_form(). Remove + form from widget + """ + self.clean_data_form = self.empty_method # we won't call it twice + del self.multiplemodel - def refresh_multiple_buttons(self): - """ - Checks for treeview state and makes control buttons sensitive - """ - selection = self.records_treeview.get_selection() - model = self.records_treeview.get_model() - count = selection.count_selected_rows() - if count == 0: - self.remove_button.set_sensitive(False) - self.edit_button.set_sensitive(False) - self.up_button.set_sensitive(False) - self.down_button.set_sensitive(False) - elif count == 1: - self.remove_button.set_sensitive(True) - self.edit_button.set_sensitive(True) - _, (path,) = selection.get_selected_rows() - iter_ = model.get_iter(path) - if model.iter_next(iter_) is None: - self.up_button.set_sensitive(True) - self.down_button.set_sensitive(False) - elif path == (0, ): - self.up_button.set_sensitive(False) - self.down_button.set_sensitive(True) - else: - self.up_button.set_sensitive(True) - self.down_button.set_sensitive(True) - else: - self.remove_button.set_sensitive(True) - self.edit_button.set_sensitive(True) - self.up_button.set_sensitive(False) - self.down_button.set_sensitive(False) + def refresh_multiple_buttons(self): + """ + Checks for treeview state and makes control buttons sensitive + """ + selection = self.records_treeview.get_selection() + model = self.records_treeview.get_model() + count = selection.count_selected_rows() + if count == 0: + self.remove_button.set_sensitive(False) + self.edit_button.set_sensitive(False) + self.up_button.set_sensitive(False) + self.down_button.set_sensitive(False) + elif count == 1: + self.remove_button.set_sensitive(True) + self.edit_button.set_sensitive(True) + _, (path,) = selection.get_selected_rows() + iter_ = model.get_iter(path) + if model.iter_next(iter_) is None: + self.up_button.set_sensitive(True) + self.down_button.set_sensitive(False) + elif path == (0, ): + self.up_button.set_sensitive(False) + self.down_button.set_sensitive(True) + else: + self.up_button.set_sensitive(True) + self.down_button.set_sensitive(True) + else: + self.remove_button.set_sensitive(True) + self.edit_button.set_sensitive(True) + self.up_button.set_sensitive(False) + self.down_button.set_sensitive(False) - if len(model) == 0: - self.clear_button.set_sensitive(False) - else: - self.clear_button.set_sensitive(True) + if len(model) == 0: + self.clear_button.set_sensitive(False) + else: + self.clear_button.set_sensitive(True) - def on_clear_button_clicked(self, widget): - self.records_treeview.get_model().clear() + def on_clear_button_clicked(self, widget): + self.records_treeview.get_model().clear() - def on_remove_button_clicked(self, widget): - selection = self.records_treeview.get_selection() - model, rowrefs = selection.get_selected_rows() - # rowref is a list of paths - for i in xrange(len(rowrefs)): - rowrefs[i] = gtk.TreeRowReference(model, rowrefs[i]) - # rowref is a list of row references; need to convert because we will - # modify the model, paths would change - for rowref in rowrefs: - del model[rowref.get_path()] + def on_remove_button_clicked(self, widget): + selection = self.records_treeview.get_selection() + model, rowrefs = selection.get_selected_rows() + # rowref is a list of paths + for i in xrange(len(rowrefs)): + rowrefs[i] = gtk.TreeRowReference(model, rowrefs[i]) + # rowref is a list of row references; need to convert because we will + # modify the model, paths would change + for rowref in rowrefs: + del model[rowref.get_path()] - def on_up_button_clicked(self, widget): - selection = self.records_treeview.get_selection() - model, (path,) = selection.get_selected_rows() - iter_ = model.get_iter(path) - # constructing path for previous iter - previter = model.get_iter((path[0]-1,)) - model.swap(iter_, previter) + def on_up_button_clicked(self, widget): + selection = self.records_treeview.get_selection() + model, (path,) = selection.get_selected_rows() + iter_ = model.get_iter(path) + # constructing path for previous iter + previter = model.get_iter((path[0]-1,)) + model.swap(iter_, previter) - self.refresh_multiple_buttons() + self.refresh_multiple_buttons() - def on_down_button_clicked(self, widget): - selection = self.records_treeview.get_selection() - model, (path,) = selection.get_selected_rows() - iter_ = model.get_iter(path) - nextiter = model.iter_next(iter_) - model.swap(iter_, nextiter) + def on_down_button_clicked(self, widget): + selection = self.records_treeview.get_selection() + model, (path,) = selection.get_selected_rows() + iter_ = model.get_iter(path) + nextiter = model.iter_next(iter_) + model.swap(iter_, nextiter) - self.refresh_multiple_buttons() + self.refresh_multiple_buttons() - def on_records_selection_changed(self, widget): - self.refresh_multiple_buttons() + def on_records_selection_changed(self, widget): + self.refresh_multiple_buttons() class SingleForm(gtk.Table, object): - """ - Widget that represent DATAFORM_SINGLE mode form. Because this is used not - only to display single forms, but to form input windows of multiple-type - forms, it is in another class - """ + """ + Widget that represent DATAFORM_SINGLE mode form. Because this is used not + only to display single forms, but to form input windows of multiple-type + forms, it is in another class + """ - def __init__(self, dataform): - assert isinstance(dataform, dataforms.SimpleDataForm) + def __init__(self, dataform): + assert isinstance(dataform, dataforms.SimpleDataForm) - gtk.Table.__init__(self) - self.set_col_spacings(12) - self.set_row_spacings(6) + gtk.Table.__init__(self) + self.set_col_spacings(12) + self.set_row_spacings(6) - def decorate_with_tooltip(widget, field): - """ - Adds a tooltip containing field's description to a widget. Creates - EventBox if widget doesn't have its own gdk window. Returns decorated - widget - """ - if field.description != '': - if widget.flags() & gtk.NO_WINDOW: - evbox = gtk.EventBox() - evbox.add(widget) - widget = evbox - widget.set_tooltip_text(field.description) - return widget + def decorate_with_tooltip(widget, field): + """ + Adds a tooltip containing field's description to a widget. Creates + EventBox if widget doesn't have its own gdk window. Returns decorated + widget + """ + if field.description != '': + if widget.flags() & gtk.NO_WINDOW: + evbox = gtk.EventBox() + evbox.add(widget) + widget = evbox + widget.set_tooltip_text(field.description) + return widget - self._data_form = dataform + self._data_form = dataform - # building widget - linecounter = 0 + # building widget + linecounter = 0 - # is the form changeable? - readwrite = dataform.type != 'result' + # is the form changeable? + readwrite = dataform.type != 'result' - # for each field... - for field in self._data_form.iter_fields(): - if field.type == 'hidden': continue + # for each field... + for field in self._data_form.iter_fields(): + if field.type == 'hidden': continue - commonlabel = True - commonlabelcenter = False - commonwidget = True - widget = None + commonlabel = True + commonlabelcenter = False + commonwidget = True + widget = None - if field.type == 'boolean': - commonlabelcenter = True - widget = gtk.CheckButton() - widget.connect('toggled', self.on_boolean_checkbutton_toggled, - field) - widget.set_active(field.value) + if field.type == 'boolean': + commonlabelcenter = True + widget = gtk.CheckButton() + widget.connect('toggled', self.on_boolean_checkbutton_toggled, + field) + widget.set_active(field.value) - elif field.type == 'fixed': - leftattach = 1 - rightattach = 2 - if field.label is None: - commonlabel = False - leftattach = 0 + elif field.type == 'fixed': + leftattach = 1 + rightattach = 2 + if field.label is None: + commonlabel = False + leftattach = 0 - commonwidget = False - widget = gtk.Label(field.value) - widget.set_line_wrap(True) - self.attach(widget, leftattach, rightattach, linecounter, - linecounter+1, xoptions=gtk.FILL, yoptions=gtk.FILL) + commonwidget = False + widget = gtk.Label(field.value) + widget.set_line_wrap(True) + self.attach(widget, leftattach, rightattach, linecounter, + linecounter+1, xoptions=gtk.FILL, yoptions=gtk.FILL) - elif field.type == 'list-single': - # TODO: What if we have radio buttons and non-required field? - # TODO: We cannot deactivate them all... - if len(field.options) < 6: - # 5 option max: show radiobutton - widget = gtk.VBox() - first_radio = None - for value, label in field.iter_options(): - if not label: - label = value - radio = gtk.RadioButton(first_radio, label=label) - radio.connect('toggled', - self.on_list_single_radiobutton_toggled, field, value) - if first_radio is None: - first_radio = radio - if field.value == '': # TODO: is None when done - field.value = value - if value == field.value: - radio.set_active(True) - widget.pack_start(radio, expand=False) - else: - # more than 5 options: show combobox - def on_list_single_combobox_changed(combobox, f): - iter_ = combobox.get_active_iter() - if iter_: - model = combobox.get_model() - f.value = model[iter_][1] - else: - f.value = '' - widget = gtkgui_helpers.create_combobox(field.options, - field.value) - widget.connect('changed', on_list_single_combobox_changed, field) - widget.set_sensitive(readwrite) + elif field.type == 'list-single': + # TODO: What if we have radio buttons and non-required field? + # TODO: We cannot deactivate them all... + if len(field.options) < 6: + # 5 option max: show radiobutton + widget = gtk.VBox() + first_radio = None + for value, label in field.iter_options(): + if not label: + label = value + radio = gtk.RadioButton(first_radio, label=label) + radio.connect('toggled', + self.on_list_single_radiobutton_toggled, field, value) + if first_radio is None: + first_radio = radio + if field.value == '': # TODO: is None when done + field.value = value + if value == field.value: + radio.set_active(True) + widget.pack_start(radio, expand=False) + else: + # more than 5 options: show combobox + def on_list_single_combobox_changed(combobox, f): + iter_ = combobox.get_active_iter() + if iter_: + model = combobox.get_model() + f.value = model[iter_][1] + else: + f.value = '' + widget = gtkgui_helpers.create_combobox(field.options, + field.value) + widget.connect('changed', on_list_single_combobox_changed, field) + widget.set_sensitive(readwrite) - elif field.type == 'list-multi': - # TODO: When more than few choices, make a list - if len(field.options) < 6: - # 5 option max: show checkbutton - widget = gtk.VBox() - for value, label in field.iter_options(): - check = gtk.CheckButton(label, use_underline=False) - check.set_active(value in field.values) - check.connect('toggled', - self.on_list_multi_checkbutton_toggled, field, value) - widget.pack_start(check, expand=False) - else: - # more than 5 options: show combobox - def on_list_multi_treeview_changed(selection, f): - def for_selected(treemodel, path, iter): - vals.append(treemodel[iter][1]) - vals = [] - selection.selected_foreach(for_selected) - field.values = vals[:] - widget = gtk.ScrolledWindow() - widget.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - tv = gtkgui_helpers.create_list_multi(field.options, - field.values) - widget.add(tv) - widget.set_size_request(-1, 120) - tv.get_selection().connect('changed', - on_list_multi_treeview_changed, field) - widget.set_sensitive(readwrite) + elif field.type == 'list-multi': + # TODO: When more than few choices, make a list + if len(field.options) < 6: + # 5 option max: show checkbutton + widget = gtk.VBox() + for value, label in field.iter_options(): + check = gtk.CheckButton(label, use_underline=False) + check.set_active(value in field.values) + check.connect('toggled', + self.on_list_multi_checkbutton_toggled, field, value) + widget.pack_start(check, expand=False) + else: + # more than 5 options: show combobox + def on_list_multi_treeview_changed(selection, f): + def for_selected(treemodel, path, iter): + vals.append(treemodel[iter][1]) + vals = [] + selection.selected_foreach(for_selected) + field.values = vals[:] + widget = gtk.ScrolledWindow() + widget.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + tv = gtkgui_helpers.create_list_multi(field.options, + field.values) + widget.add(tv) + widget.set_size_request(-1, 120) + tv.get_selection().connect('changed', + on_list_multi_treeview_changed, field) + widget.set_sensitive(readwrite) - elif field.type == 'jid-single': - widget = gtk.Entry() - widget.connect('changed', self.on_text_single_entry_changed, field) - widget.set_text(field.value) + elif field.type == 'jid-single': + widget = gtk.Entry() + widget.connect('changed', self.on_text_single_entry_changed, field) + widget.set_text(field.value) - elif field.type == 'jid-multi': - commonwidget = False + elif field.type == 'jid-multi': + commonwidget = False - xml = gtkgui_helpers.get_glade('data_form_window.glade', - 'item_list_table') - widget = xml.get_widget('item_list_table') - treeview = xml.get_widget('item_treeview') + xml = gtkgui_helpers.get_glade('data_form_window.glade', + 'item_list_table') + widget = xml.get_widget('item_list_table') + treeview = xml.get_widget('item_treeview') - listmodel = gtk.ListStore(str) - for value in field.iter_values(): - # nobody will create several megabytes long stanza - listmodel.insert(999999, (value,)) + listmodel = gtk.ListStore(str) + for value in field.iter_values(): + # nobody will create several megabytes long stanza + listmodel.insert(999999, (value,)) - treeview.set_model(listmodel) + treeview.set_model(listmodel) - renderer = gtk.CellRendererText() - renderer.set_property('editable', True) - renderer.connect('edited', - self.on_jid_multi_cellrenderertext_edited, treeview, listmodel, - field) + renderer = gtk.CellRendererText() + renderer.set_property('editable', True) + renderer.connect('edited', + self.on_jid_multi_cellrenderertext_edited, treeview, listmodel, + field) - treeview.append_column(gtk.TreeViewColumn(None, renderer, - text=0)) + treeview.append_column(gtk.TreeViewColumn(None, renderer, + text=0)) - decorate_with_tooltip(treeview, field) + decorate_with_tooltip(treeview, field) - add_button=xml.get_widget('add_button') - add_button.connect('clicked', - self.on_jid_multi_add_button_clicked, treeview, listmodel, field) - edit_button=xml.get_widget('edit_button') - edit_button.connect('clicked', - self.on_jid_multi_edit_button_clicked, treeview) - remove_button=xml.get_widget('remove_button') - remove_button.connect('clicked', - self.on_jid_multi_remove_button_clicked, treeview, field) - clear_button=xml.get_widget('clear_button') - clear_button.connect('clicked', - self.on_jid_multi_clean_button_clicked, listmodel, field) - if not readwrite: - add_button.set_no_show_all(True) - edit_button.set_no_show_all(True) - remove_button.set_no_show_all(True) - clear_button.set_no_show_all(True) + add_button=xml.get_widget('add_button') + add_button.connect('clicked', + self.on_jid_multi_add_button_clicked, treeview, listmodel, field) + edit_button=xml.get_widget('edit_button') + edit_button.connect('clicked', + self.on_jid_multi_edit_button_clicked, treeview) + remove_button=xml.get_widget('remove_button') + remove_button.connect('clicked', + self.on_jid_multi_remove_button_clicked, treeview, field) + clear_button=xml.get_widget('clear_button') + clear_button.connect('clicked', + self.on_jid_multi_clean_button_clicked, listmodel, field) + if not readwrite: + add_button.set_no_show_all(True) + edit_button.set_no_show_all(True) + remove_button.set_no_show_all(True) + clear_button.set_no_show_all(True) - widget.set_sensitive(readwrite) - self.attach(widget, 1, 2, linecounter, linecounter+1) + widget.set_sensitive(readwrite) + self.attach(widget, 1, 2, linecounter, linecounter+1) - del xml + del xml - elif field.type == 'text-private': - commonlabelcenter = True - widget = gtk.Entry() - widget.connect('changed', self.on_text_single_entry_changed, field) - widget.set_visibility(False) - widget.set_text(field.value) + elif field.type == 'text-private': + commonlabelcenter = True + widget = gtk.Entry() + widget.connect('changed', self.on_text_single_entry_changed, field) + widget.set_visibility(False) + widget.set_text(field.value) - elif field.type == 'text-multi': - # TODO: bigger text view - commonwidget = False + elif field.type == 'text-multi': + # TODO: bigger text view + commonwidget = False - textwidget = gtk.TextView() - textwidget.set_wrap_mode(gtk.WRAP_WORD) - textwidget.get_buffer().connect('changed', - self.on_text_multi_textbuffer_changed, field) - textwidget.get_buffer().set_text(field.value) + textwidget = gtk.TextView() + textwidget.set_wrap_mode(gtk.WRAP_WORD) + textwidget.get_buffer().connect('changed', + self.on_text_multi_textbuffer_changed, field) + textwidget.get_buffer().set_text(field.value) - widget = gtk.ScrolledWindow() - widget.add(textwidget) + widget = gtk.ScrolledWindow() + widget.add(textwidget) - widget.set_sensitive(readwrite) - widget=decorate_with_tooltip(widget, field) - self.attach(widget, 1, 2, linecounter, linecounter+1) + widget.set_sensitive(readwrite) + widget=decorate_with_tooltip(widget, field) + self.attach(widget, 1, 2, linecounter, linecounter+1) - else: - # field.type == 'text-single' or field.type is nonstandard: - # JEP says that if we don't understand some type, we - # should handle it as text-single - commonlabelcenter = True - if readwrite: - widget = gtk.Entry() - widget.connect('changed', self.on_text_single_entry_changed, - field) - widget.set_sensitive(readwrite) - if field.value is None: - field.value = u'' - widget.set_text(field.value) - else: - commonwidget=False - widget = gtk.Label(field.value) - widget.set_sensitive(True) - widget.set_alignment(0.0, 0.5) - widget=decorate_with_tooltip(widget, field) - self.attach(widget, 1, 2, linecounter, linecounter+1, - yoptions=gtk.FILL) + else: + # field.type == 'text-single' or field.type is nonstandard: + # JEP says that if we don't understand some type, we + # should handle it as text-single + commonlabelcenter = True + if readwrite: + widget = gtk.Entry() + widget.connect('changed', self.on_text_single_entry_changed, + field) + widget.set_sensitive(readwrite) + if field.value is None: + field.value = u'' + widget.set_text(field.value) + else: + commonwidget=False + widget = gtk.Label(field.value) + widget.set_sensitive(True) + widget.set_alignment(0.0, 0.5) + widget=decorate_with_tooltip(widget, field) + self.attach(widget, 1, 2, linecounter, linecounter+1, + yoptions=gtk.FILL) - if commonlabel and field.label is not None: - label = gtk.Label(field.label) - if commonlabelcenter: - label.set_alignment(0.0, 0.5) - else: - label.set_alignment(0.0, 0.0) - label = decorate_with_tooltip(label, field) - self.attach(label, 0, 1, linecounter, linecounter+1, - xoptions=gtk.FILL, yoptions=gtk.FILL) + if commonlabel and field.label is not None: + label = gtk.Label(field.label) + if commonlabelcenter: + label.set_alignment(0.0, 0.5) + else: + label.set_alignment(0.0, 0.0) + label = decorate_with_tooltip(label, field) + self.attach(label, 0, 1, linecounter, linecounter+1, + xoptions=gtk.FILL, yoptions=gtk.FILL) - if commonwidget: - assert widget is not None - widget.set_sensitive(readwrite) - widget = decorate_with_tooltip(widget, field) - self.attach(widget, 1, 2, linecounter, linecounter+1, - yoptions=gtk.FILL) - widget.show_all() + if commonwidget: + assert widget is not None + widget.set_sensitive(readwrite) + widget = decorate_with_tooltip(widget, field) + self.attach(widget, 1, 2, linecounter, linecounter+1, + yoptions=gtk.FILL) + widget.show_all() - linecounter+=1 - if self.get_property('visible'): - self.show_all() + linecounter+=1 + if self.get_property('visible'): + self.show_all() - def show(self): - # simulate that we are one widget - self.show_all() + def show(self): + # simulate that we are one widget + self.show_all() - def on_boolean_checkbutton_toggled(self, widget, field): - field.value = widget.get_active() + def on_boolean_checkbutton_toggled(self, widget, field): + field.value = widget.get_active() - def on_list_single_radiobutton_toggled(self, widget, field, value): - field.value = value + def on_list_single_radiobutton_toggled(self, widget, field, value): + field.value = value - def on_list_multi_checkbutton_toggled(self, widget, field, value): - # TODO: make some methods like add_value and remove_value - if widget.get_active() and value not in field.values: - field.values += [value] - elif not widget.get_active() and value in field.values: - field.values = [v for v in field.values if v!=value] + def on_list_multi_checkbutton_toggled(self, widget, field, value): + # TODO: make some methods like add_value and remove_value + if widget.get_active() and value not in field.values: + field.values += [value] + elif not widget.get_active() and value in field.values: + field.values = [v for v in field.values if v!=value] - def on_text_single_entry_changed(self, widget, field): - field.value = widget.get_text() + def on_text_single_entry_changed(self, widget, field): + field.value = widget.get_text() - def on_text_multi_textbuffer_changed(self, widget, field): - field.value = widget.get_text( - widget.get_start_iter(), - widget.get_end_iter()) + def on_text_multi_textbuffer_changed(self, widget, field): + field.value = widget.get_text( + widget.get_start_iter(), + widget.get_end_iter()) - def on_jid_multi_cellrenderertext_edited(self, cell, path, newtext, treeview, - model, field): - old = model[path][0] - if old == newtext: - return - try: - newtext = helpers.parse_jid(newtext) - except helpers.InvalidFormat, s: - dialogs.ErrorDialog(_('Invalid Jabber ID'), str(s)) - return - if newtext in field.values: - dialogs.ErrorDialog( - _('Jabber ID already in list'), - _('The Jabber ID you entered is already in the list. Choose another one.')) - gobject.idle_add(treeview.set_cursor, path) - return - model[path][0]=newtext + def on_jid_multi_cellrenderertext_edited(self, cell, path, newtext, treeview, + model, field): + old = model[path][0] + if old == newtext: + return + try: + newtext = helpers.parse_jid(newtext) + except helpers.InvalidFormat, s: + dialogs.ErrorDialog(_('Invalid Jabber ID'), str(s)) + return + if newtext in field.values: + dialogs.ErrorDialog( + _('Jabber ID already in list'), + _('The Jabber ID you entered is already in the list. Choose another one.')) + gobject.idle_add(treeview.set_cursor, path) + return + model[path][0]=newtext - values = field.values - values[values.index(old)]=newtext - field.values = values + values = field.values + values[values.index(old)]=newtext + field.values = values - def on_jid_multi_add_button_clicked(self, widget, treeview, model, field): - #Default jid - jid = _('new@jabber.id') - if jid in field.values: - i = 1 - while _('new%d@jabber.id') % i in field.values: - i += 1 - jid = _('new%d@jabber.id') % i - iter_ = model.insert(999999, (jid,)) - treeview.set_cursor(model.get_path(iter_), treeview.get_column(0), True) - field.values = field.values + [jid] + def on_jid_multi_add_button_clicked(self, widget, treeview, model, field): + #Default jid + jid = _('new@jabber.id') + if jid in field.values: + i = 1 + while _('new%d@jabber.id') % i in field.values: + i += 1 + jid = _('new%d@jabber.id') % i + iter_ = model.insert(999999, (jid,)) + treeview.set_cursor(model.get_path(iter_), treeview.get_column(0), True) + field.values = field.values + [jid] - def on_jid_multi_edit_button_clicked(self, widget, treeview): - model, iter_ = treeview.get_selection().get_selected() - assert iter_ is not None + def on_jid_multi_edit_button_clicked(self, widget, treeview): + model, iter_ = treeview.get_selection().get_selected() + assert iter_ is not None - treeview.set_cursor(model.get_path(iter_), treeview.get_column(0), True) + treeview.set_cursor(model.get_path(iter_), treeview.get_column(0), True) - def on_jid_multi_remove_button_clicked(self, widget, treeview, field): - selection = treeview.get_selection() - deleted = [] + def on_jid_multi_remove_button_clicked(self, widget, treeview, field): + selection = treeview.get_selection() + deleted = [] - def remove(model, path, iter_, deleted): - deleted+=model[iter_] - model.remove(iter_) + def remove(model, path, iter_, deleted): + deleted+=model[iter_] + model.remove(iter_) - selection.selected_foreach(remove, deleted) - field.values = (v for v in field.values if v not in deleted) + selection.selected_foreach(remove, deleted) + field.values = (v for v in field.values if v not in deleted) - def on_jid_multi_clean_button_clicked(self, widget, model, field): - model.clear() - del field.values - -# vim: se ts=3: + def on_jid_multi_clean_button_clicked(self, widget, model, field): + model.clear() + del field.values diff --git a/src/dialogs.py b/src/dialogs.py index 1060b2dd7..7025ed9c2 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -44,10 +44,10 @@ from random import randrange from common import pep try: - import gtkspell - HAS_GTK_SPELL = True + import gtkspell + HAS_GTK_SPELL = True except ImportError: - HAS_GTK_SPELL = False + HAS_GTK_SPELL = False # those imports are not used in this file, but in files that 'import dialogs' # so they can do dialog.GajimThemesWindow() for example @@ -61,5068 +61,5066 @@ from common import dataforms from common.exceptions import GajimGeneralException class EditGroupsDialog: - """ - Class for the edit group dialog window - """ + """ + Class for the edit group dialog window + """ - def __init__(self, list_): - """ - list_ is a list of (contact, account) tuples - """ - self.xml = gtkgui_helpers.get_glade('edit_groups_dialog.glade') - self.dialog = self.xml.get_widget('edit_groups_dialog') - self.dialog.set_transient_for(gajim.interface.roster.window) - self.list_ = list_ - self.changes_made = False - self.treeview = self.xml.get_widget('groups_treeview') - if len(list_) == 1: - contact = list_[0][0] - self.xml.get_widget('nickname_label').set_markup( - _('Contact name: %s') % contact.get_shown_name()) - self.xml.get_widget('jid_label').set_markup( - _('Jabber ID: %s') % contact.jid) - else: - self.xml.get_widget('nickname_label').set_no_show_all(True) - self.xml.get_widget('nickname_label').hide() - self.xml.get_widget('jid_label').set_no_show_all(True) - self.xml.get_widget('jid_label').hide() + def __init__(self, list_): + """ + list_ is a list of (contact, account) tuples + """ + self.xml = gtkgui_helpers.get_glade('edit_groups_dialog.glade') + self.dialog = self.xml.get_widget('edit_groups_dialog') + self.dialog.set_transient_for(gajim.interface.roster.window) + self.list_ = list_ + self.changes_made = False + self.treeview = self.xml.get_widget('groups_treeview') + if len(list_) == 1: + contact = list_[0][0] + self.xml.get_widget('nickname_label').set_markup( + _('Contact name: %s') % contact.get_shown_name()) + self.xml.get_widget('jid_label').set_markup( + _('Jabber ID: %s') % contact.jid) + else: + self.xml.get_widget('nickname_label').set_no_show_all(True) + self.xml.get_widget('nickname_label').hide() + self.xml.get_widget('jid_label').set_no_show_all(True) + self.xml.get_widget('jid_label').hide() - self.xml.signal_autoconnect(self) - self.init_list() + self.xml.signal_autoconnect(self) + self.init_list() - self.dialog.show_all() - if self.changes_made: - for (contact, account) in self.list_: - gajim.connections[account].update_contact(contact.jid, contact.name, - contact.groups) + self.dialog.show_all() + if self.changes_made: + for (contact, account) in self.list_: + gajim.connections[account].update_contact(contact.jid, contact.name, + contact.groups) - def on_edit_groups_dialog_response(self, widget, response_id): - if response_id == gtk.RESPONSE_CLOSE: - self.dialog.destroy() + def on_edit_groups_dialog_response(self, widget, response_id): + if response_id == gtk.RESPONSE_CLOSE: + self.dialog.destroy() - def remove_group(self, group): - """ - Remove group group from all contacts and all their brothers - """ - for (contact, account) in self.list_: - gajim.interface.roster.remove_contact_from_groups(contact.jid, account, [group]) + def remove_group(self, group): + """ + Remove group group from all contacts and all their brothers + """ + for (contact, account) in self.list_: + gajim.interface.roster.remove_contact_from_groups(contact.jid, account, [group]) - # FIXME: Ugly workaround. - gajim.interface.roster.draw_group(_('General'), account) + # FIXME: Ugly workaround. + gajim.interface.roster.draw_group(_('General'), account) - def add_group(self, group): - """ - Add group group to all contacts and all their brothers - """ - for (contact, account) in self.list_: - gajim.interface.roster.add_contact_to_groups(contact.jid, account, [group]) + def add_group(self, group): + """ + Add group group to all contacts and all their brothers + """ + for (contact, account) in self.list_: + gajim.interface.roster.add_contact_to_groups(contact.jid, account, [group]) - # FIXME: Ugly workaround. Maybe we haven't been in any group (defaults to General) - gajim.interface.roster.draw_group(_('General'), account) + # FIXME: Ugly workaround. Maybe we haven't been in any group (defaults to General) + gajim.interface.roster.draw_group(_('General'), account) - def on_add_button_clicked(self, widget): - group = self.xml.get_widget('group_entry').get_text().decode('utf-8') - if not group: - return - # Do not allow special groups - if group in helpers.special_groups: - return - # check if it already exists - model = self.treeview.get_model() - iter_ = model.get_iter_root() - while iter_: - if model.get_value(iter_, 0).decode('utf-8') == group: - return - iter_ = model.iter_next(iter_) - self.changes_made = True - model.append((group, True, False)) - self.add_group(group) - self.init_list() # Re-draw list to sort new item + def on_add_button_clicked(self, widget): + group = self.xml.get_widget('group_entry').get_text().decode('utf-8') + if not group: + return + # Do not allow special groups + if group in helpers.special_groups: + return + # check if it already exists + model = self.treeview.get_model() + iter_ = model.get_iter_root() + while iter_: + if model.get_value(iter_, 0).decode('utf-8') == group: + return + iter_ = model.iter_next(iter_) + self.changes_made = True + model.append((group, True, False)) + self.add_group(group) + self.init_list() # Re-draw list to sort new item - def group_toggled_cb(self, cell, path): - self.changes_made = True - model = self.treeview.get_model() - if model[path][2]: - model[path][2] = False - model[path][1] = True - else: - model[path][1] = not model[path][1] - group = model[path][0].decode('utf-8') - if model[path][1]: - self.add_group(group) - else: - self.remove_group(group) + def group_toggled_cb(self, cell, path): + self.changes_made = True + model = self.treeview.get_model() + if model[path][2]: + model[path][2] = False + model[path][1] = True + else: + model[path][1] = not model[path][1] + group = model[path][0].decode('utf-8') + if model[path][1]: + self.add_group(group) + else: + self.remove_group(group) - def init_list(self): - store = gtk.ListStore(str, bool, bool) - self.treeview.set_model(store) - for column in self.treeview.get_columns(): - # Clear treeview when re-drawing - self.treeview.remove_column(column) - accounts = [] - # Store groups in a list so we can sort them and the number of contacts in - # it - groups = {} - for (contact, account) in self.list_: - if account not in accounts: - accounts.append(account) - for g in gajim.groups[account].keys(): - if g in groups: - continue - groups[g] = 0 - c_groups = contact.groups - for g in c_groups: - groups[g] += 1 - group_list = [] - # Remove special groups if they are empty - for group in groups: - if group not in helpers.special_groups or groups[group] > 0: - group_list.append(group) - group_list.sort() - for group in group_list: - iter_ = store.append() - store.set(iter_, 0, group) # Group name - if groups[group] == 0: - store.set(iter_, 1, False) - else: - store.set(iter_, 1, True) - if groups[group] == len(self.list_): - # all contacts are in this group - store.set(iter_, 2, False) - else: - store.set(iter_, 2, True) - column = gtk.TreeViewColumn(_('Group')) - column.set_expand(True) - self.treeview.append_column(column) - renderer = gtk.CellRendererText() - column.pack_start(renderer) - column.set_attributes(renderer, text=0) + def init_list(self): + store = gtk.ListStore(str, bool, bool) + self.treeview.set_model(store) + for column in self.treeview.get_columns(): + # Clear treeview when re-drawing + self.treeview.remove_column(column) + accounts = [] + # Store groups in a list so we can sort them and the number of contacts in + # it + groups = {} + for (contact, account) in self.list_: + if account not in accounts: + accounts.append(account) + for g in gajim.groups[account].keys(): + if g in groups: + continue + groups[g] = 0 + c_groups = contact.groups + for g in c_groups: + groups[g] += 1 + group_list = [] + # Remove special groups if they are empty + for group in groups: + if group not in helpers.special_groups or groups[group] > 0: + group_list.append(group) + group_list.sort() + for group in group_list: + iter_ = store.append() + store.set(iter_, 0, group) # Group name + if groups[group] == 0: + store.set(iter_, 1, False) + else: + store.set(iter_, 1, True) + if groups[group] == len(self.list_): + # all contacts are in this group + store.set(iter_, 2, False) + else: + store.set(iter_, 2, True) + column = gtk.TreeViewColumn(_('Group')) + column.set_expand(True) + self.treeview.append_column(column) + renderer = gtk.CellRendererText() + column.pack_start(renderer) + column.set_attributes(renderer, text=0) - column = gtk.TreeViewColumn(_('In the group')) - column.set_expand(False) - self.treeview.append_column(column) - renderer = gtk.CellRendererToggle() - column.pack_start(renderer) - renderer.set_property('activatable', True) - renderer.connect('toggled', self.group_toggled_cb) - column.set_attributes(renderer, active=1, inconsistent=2) + column = gtk.TreeViewColumn(_('In the group')) + column.set_expand(False) + self.treeview.append_column(column) + renderer = gtk.CellRendererToggle() + column.pack_start(renderer) + renderer.set_property('activatable', True) + renderer.connect('toggled', self.group_toggled_cb) + column.set_attributes(renderer, active=1, inconsistent=2) class PassphraseDialog: - """ - Class for Passphrase dialog - """ - def __init__(self, titletext, labeltext, checkbuttontext=None, - ok_handler=None, cancel_handler=None): - self.xml = gtkgui_helpers.get_glade('passphrase_dialog.glade') - self.window = self.xml.get_widget('passphrase_dialog') - self.passphrase_entry = self.xml.get_widget('passphrase_entry') - self.passphrase = -1 - self.window.set_title(titletext) - self.xml.get_widget('message_label').set_text(labeltext) + """ + Class for Passphrase dialog + """ + def __init__(self, titletext, labeltext, checkbuttontext=None, + ok_handler=None, cancel_handler=None): + self.xml = gtkgui_helpers.get_glade('passphrase_dialog.glade') + self.window = self.xml.get_widget('passphrase_dialog') + self.passphrase_entry = self.xml.get_widget('passphrase_entry') + self.passphrase = -1 + self.window.set_title(titletext) + self.xml.get_widget('message_label').set_text(labeltext) - self.ok = False + self.ok = False - self.cancel_handler = cancel_handler - self.ok_handler = ok_handler - okbutton = self.xml.get_widget('ok_button') - okbutton.connect('clicked', self.on_okbutton_clicked) - cancelbutton = self.xml.get_widget('cancel_button') - cancelbutton.connect('clicked', self.on_cancelbutton_clicked) + self.cancel_handler = cancel_handler + self.ok_handler = ok_handler + okbutton = self.xml.get_widget('ok_button') + okbutton.connect('clicked', self.on_okbutton_clicked) + cancelbutton = self.xml.get_widget('cancel_button') + cancelbutton.connect('clicked', self.on_cancelbutton_clicked) - self.xml.signal_autoconnect(self) - self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - self.window.show_all() + self.xml.signal_autoconnect(self) + self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + self.window.show_all() - self.check = bool(checkbuttontext) - checkbutton = self.xml.get_widget('save_passphrase_checkbutton') - if self.check: - checkbutton.set_label(checkbuttontext) - else: - checkbutton.hide() + self.check = bool(checkbuttontext) + checkbutton = self.xml.get_widget('save_passphrase_checkbutton') + if self.check: + checkbutton.set_label(checkbuttontext) + else: + checkbutton.hide() - def on_okbutton_clicked(self, widget): - if not self.ok_handler: - return + def on_okbutton_clicked(self, widget): + if not self.ok_handler: + return - passph = self.passphrase_entry.get_text().decode('utf-8') + passph = self.passphrase_entry.get_text().decode('utf-8') - if self.check: - checked = self.xml.get_widget('save_passphrase_checkbutton').\ - get_active() - else: - checked = False + if self.check: + checked = self.xml.get_widget('save_passphrase_checkbutton').\ + get_active() + else: + checked = False - self.ok = True + self.ok = True - self.window.destroy() + self.window.destroy() - if isinstance(self.ok_handler, tuple): - self.ok_handler[0](passph, checked, *self.ok_handler[1:]) - else: - self.ok_handler(passph, checked) + if isinstance(self.ok_handler, tuple): + self.ok_handler[0](passph, checked, *self.ok_handler[1:]) + else: + self.ok_handler(passph, checked) - def on_cancelbutton_clicked(self, widget): - self.window.destroy() + def on_cancelbutton_clicked(self, widget): + self.window.destroy() - def on_passphrase_dialog_destroy(self, widget): - if self.cancel_handler and not self.ok: - self.cancel_handler() + def on_passphrase_dialog_destroy(self, widget): + if self.cancel_handler and not self.ok: + self.cancel_handler() class ChooseGPGKeyDialog: - """ - Class for GPG key dialog - """ + """ + Class for GPG key dialog + """ - def __init__(self, title_text, prompt_text, secret_keys, on_response, - selected=None): - '''secret_keys : {keyID: userName, ...}''' - self.on_response = on_response - xml = gtkgui_helpers.get_glade('choose_gpg_key_dialog.glade') - self.window = xml.get_widget('choose_gpg_key_dialog') - self.window.set_title(title_text) - self.keys_treeview = xml.get_widget('keys_treeview') - prompt_label = xml.get_widget('prompt_label') - prompt_label.set_text(prompt_text) - model = gtk.ListStore(str, str) - model.set_sort_func(1, self.sort_keys) - model.set_sort_column_id(1, gtk.SORT_ASCENDING) - self.keys_treeview.set_model(model) - #columns - renderer = gtk.CellRendererText() - col = self.keys_treeview.insert_column_with_attributes(-1, _('KeyID'), - renderer, text=0) - col.set_sort_column_id(0) - renderer = gtk.CellRendererText() - col = self.keys_treeview.insert_column_with_attributes(-1, - _('Contact name'), renderer, text=1) - col.set_sort_column_id(1) - self.keys_treeview.set_search_column(1) - self.fill_tree(secret_keys, selected) - self.window.connect('response', self.on_dialog_response) - self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - self.window.show_all() + def __init__(self, title_text, prompt_text, secret_keys, on_response, + selected=None): + '''secret_keys : {keyID: userName, ...}''' + self.on_response = on_response + xml = gtkgui_helpers.get_glade('choose_gpg_key_dialog.glade') + self.window = xml.get_widget('choose_gpg_key_dialog') + self.window.set_title(title_text) + self.keys_treeview = xml.get_widget('keys_treeview') + prompt_label = xml.get_widget('prompt_label') + prompt_label.set_text(prompt_text) + model = gtk.ListStore(str, str) + model.set_sort_func(1, self.sort_keys) + model.set_sort_column_id(1, gtk.SORT_ASCENDING) + self.keys_treeview.set_model(model) + #columns + renderer = gtk.CellRendererText() + col = self.keys_treeview.insert_column_with_attributes(-1, _('KeyID'), + renderer, text=0) + col.set_sort_column_id(0) + renderer = gtk.CellRendererText() + col = self.keys_treeview.insert_column_with_attributes(-1, + _('Contact name'), renderer, text=1) + col.set_sort_column_id(1) + self.keys_treeview.set_search_column(1) + self.fill_tree(secret_keys, selected) + self.window.connect('response', self.on_dialog_response) + self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + self.window.show_all() - def sort_keys(self, model, iter1, iter2): - value1 = model[iter1][1] - value2 = model[iter2][1] - if value1 == _('None'): - return -1 - elif value2 == _('None'): - return 1 - elif value1 < value2: - return -1 - return 1 + def sort_keys(self, model, iter1, iter2): + value1 = model[iter1][1] + value2 = model[iter2][1] + if value1 == _('None'): + return -1 + elif value2 == _('None'): + return 1 + elif value1 < value2: + return -1 + return 1 - def on_dialog_response(self, dialog, response): - selection = self.keys_treeview.get_selection() - (model, iter_) = selection.get_selected() - if iter_ and response == gtk.RESPONSE_OK: - keyID = [ model[iter_][0].decode('utf-8'), - model[iter_][1].decode('utf-8') ] - else: - keyID = None - self.on_response(keyID) - self.window.destroy() + def on_dialog_response(self, dialog, response): + selection = self.keys_treeview.get_selection() + (model, iter_) = selection.get_selected() + if iter_ and response == gtk.RESPONSE_OK: + keyID = [ model[iter_][0].decode('utf-8'), + model[iter_][1].decode('utf-8') ] + else: + keyID = None + self.on_response(keyID) + self.window.destroy() - def fill_tree(self, list_, selected): - model = self.keys_treeview.get_model() - for keyID in list_.keys(): - iter_ = model.append((keyID, list_[keyID])) - if keyID == selected: - path = model.get_path(iter_) - self.keys_treeview.set_cursor(path) + def fill_tree(self, list_, selected): + model = self.keys_treeview.get_model() + for keyID in list_.keys(): + iter_ = model.append((keyID, list_[keyID])) + if keyID == selected: + path = model.get_path(iter_) + self.keys_treeview.set_cursor(path) class ChangeActivityDialog: - PAGELIST = ['doing_chores', 'drinking', 'eating', 'exercising', 'grooming', - 'having_appointment', 'inactive', 'relaxing', 'talking', 'traveling', - 'working'] + PAGELIST = ['doing_chores', 'drinking', 'eating', 'exercising', 'grooming', + 'having_appointment', 'inactive', 'relaxing', 'talking', 'traveling', + 'working'] - def __init__(self, on_response, activity=None, subactivity=None, text=''): - self.on_response = on_response - self.activity = activity - self.subactivity = subactivity - self.text = text - self.xml = gtkgui_helpers.get_glade( - 'change_activity_dialog.glade') - self.window = self.xml.get_widget('change_activity_dialog') - self.window.set_transient_for(gajim.interface.roster.window) + def __init__(self, on_response, activity=None, subactivity=None, text=''): + self.on_response = on_response + self.activity = activity + self.subactivity = subactivity + self.text = text + self.xml = gtkgui_helpers.get_glade( + 'change_activity_dialog.glade') + self.window = self.xml.get_widget('change_activity_dialog') + self.window.set_transient_for(gajim.interface.roster.window) - self.checkbutton = self.xml.get_widget('enable_checkbutton') - self.notebook = self.xml.get_widget('notebook') - self.entry = self.xml.get_widget('description_entry') + self.checkbutton = self.xml.get_widget('enable_checkbutton') + self.notebook = self.xml.get_widget('notebook') + self.entry = self.xml.get_widget('description_entry') - rbtns = {} - group = None + rbtns = {} + group = None - for category in pep.ACTIVITIES: - item = self.xml.get_widget(category + '_image') - item.set_from_pixbuf( - gtkgui_helpers.load_activity_icon(category).get_pixbuf()) - item.set_tooltip_text(pep.ACTIVITIES[category]['category']) + for category in pep.ACTIVITIES: + item = self.xml.get_widget(category + '_image') + item.set_from_pixbuf( + gtkgui_helpers.load_activity_icon(category).get_pixbuf()) + item.set_tooltip_text(pep.ACTIVITIES[category]['category']) - vbox = self.xml.get_widget(category + '_vbox') - vbox.set_border_width(5) + vbox = self.xml.get_widget(category + '_vbox') + vbox.set_border_width(5) - # Other - act = category + '_other' + # Other + act = category + '_other' - if group: - rbtns[act] = gtk.RadioButton(group) - else: - rbtns[act] = group = gtk.RadioButton() + if group: + rbtns[act] = gtk.RadioButton(group) + else: + rbtns[act] = group = gtk.RadioButton() - hbox = gtk.HBox(False, 5) - hbox.pack_start(gtkgui_helpers.load_activity_icon(category), False, - False, 0) - lbl = gtk.Label('' + pep.ACTIVITIES[category]['category'] + '') - lbl.set_use_markup(True) - hbox.pack_start(lbl, False, False, 0) - rbtns[act].add(hbox) - rbtns[act].connect('toggled', self.on_rbtn_toggled, - [category, 'other']) - vbox.pack_start(rbtns[act], False, False, 0) + hbox = gtk.HBox(False, 5) + hbox.pack_start(gtkgui_helpers.load_activity_icon(category), False, + False, 0) + lbl = gtk.Label('' + pep.ACTIVITIES[category]['category'] + '') + lbl.set_use_markup(True) + hbox.pack_start(lbl, False, False, 0) + rbtns[act].add(hbox) + rbtns[act].connect('toggled', self.on_rbtn_toggled, + [category, 'other']) + vbox.pack_start(rbtns[act], False, False, 0) - activities = [] - for activity in pep.ACTIVITIES[category]: - activities.append(activity) - activities.sort() + activities = [] + for activity in pep.ACTIVITIES[category]: + activities.append(activity) + activities.sort() - for activity in activities: - if activity == 'category': - continue + for activity in activities: + if activity == 'category': + continue - act = category + '_' + activity + act = category + '_' + activity - if group: - rbtns[act] = gtk.RadioButton(group) - else: - rbtns[act] = group = gtk.RadioButton() + if group: + rbtns[act] = gtk.RadioButton(group) + else: + rbtns[act] = group = gtk.RadioButton() - hbox = gtk.HBox(False, 5) - hbox.pack_start(gtkgui_helpers.load_activity_icon(category, - activity), False, False, 0) - hbox.pack_start(gtk.Label(pep.ACTIVITIES[category][activity]), - False, False, 0) - rbtns[act].connect('toggled', self.on_rbtn_toggled, - [category, activity]) - rbtns[act].add(hbox) - vbox.pack_start(rbtns[act], False, False, 0) + hbox = gtk.HBox(False, 5) + hbox.pack_start(gtkgui_helpers.load_activity_icon(category, + activity), False, False, 0) + hbox.pack_start(gtk.Label(pep.ACTIVITIES[category][activity]), + False, False, 0) + rbtns[act].connect('toggled', self.on_rbtn_toggled, + [category, activity]) + rbtns[act].add(hbox) + vbox.pack_start(rbtns[act], False, False, 0) - if self.activity in pep.ACTIVITIES: - if not self.subactivity in pep.ACTIVITIES[self.activity]: - self.subactivity = 'other' + if self.activity in pep.ACTIVITIES: + if not self.subactivity in pep.ACTIVITIES[self.activity]: + self.subactivity = 'other' - rbtns[self.activity + '_' + self.subactivity].set_active(True) + rbtns[self.activity + '_' + self.subactivity].set_active(True) - self.checkbutton.set_active(True) - self.notebook.set_sensitive(True) - self.entry.set_sensitive(True) + self.checkbutton.set_active(True) + self.notebook.set_sensitive(True) + self.entry.set_sensitive(True) - self.notebook.set_current_page( - self.PAGELIST.index(self.activity)) + self.notebook.set_current_page( + self.PAGELIST.index(self.activity)) - self.entry.set_text(text) + self.entry.set_text(text) - else: - self.checkbutton.set_active(False) + else: + self.checkbutton.set_active(False) - self.xml.signal_autoconnect(self) - self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - self.window.show_all() + self.xml.signal_autoconnect(self) + self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + self.window.show_all() - def on_enable_checkbutton_toggled(self, widget): - self.notebook.set_sensitive(widget.get_active()) - self.entry.set_sensitive(widget.get_active()) + def on_enable_checkbutton_toggled(self, widget): + self.notebook.set_sensitive(widget.get_active()) + self.entry.set_sensitive(widget.get_active()) - def on_rbtn_toggled(self, widget, data): - if widget.get_active(): - self.activity = data[0] - self.subactivity = data[1] + def on_rbtn_toggled(self, widget, data): + if widget.get_active(): + self.activity = data[0] + self.subactivity = data[1] - def on_ok_button_clicked(self, widget): - """ - Return activity and messsage (None if no activity selected) - """ - if self.checkbutton.get_active(): - self.on_response(self.activity, self.subactivity, - self.entry.get_text().decode('utf-8')) - else: - self.on_response(None, None, '') - self.window.destroy() + def on_ok_button_clicked(self, widget): + """ + Return activity and messsage (None if no activity selected) + """ + if self.checkbutton.get_active(): + self.on_response(self.activity, self.subactivity, + self.entry.get_text().decode('utf-8')) + else: + self.on_response(None, None, '') + self.window.destroy() - def on_cancel_button_clicked(self, widget): - self.window.destroy() + def on_cancel_button_clicked(self, widget): + self.window.destroy() class ChangeMoodDialog: - COLS = 11 + COLS = 11 - def __init__(self, on_response, mood=None, text=''): - self.on_response = on_response - self.mood = mood - self.text = text - self.xml = gtkgui_helpers.get_glade('change_mood_dialog.glade') + def __init__(self, on_response, mood=None, text=''): + self.on_response = on_response + self.mood = mood + self.text = text + self.xml = gtkgui_helpers.get_glade('change_mood_dialog.glade') - self.window = self.xml.get_widget('change_mood_dialog') - self.window.set_transient_for(gajim.interface.roster.window) - self.window.set_title(_('Set Mood')) + self.window = self.xml.get_widget('change_mood_dialog') + self.window.set_transient_for(gajim.interface.roster.window) + self.window.set_title(_('Set Mood')) - table = self.xml.get_widget('mood_icons_table') - self.label = self.xml.get_widget('mood_label') - self.entry = self.xml.get_widget('description_entry') + table = self.xml.get_widget('mood_icons_table') + self.label = self.xml.get_widget('mood_label') + self.entry = self.xml.get_widget('description_entry') - no_mood_button = self.xml.get_widget('no_mood_button') - no_mood_button.set_mode(False) - no_mood_button.connect('clicked', - self.on_mood_button_clicked, None) + no_mood_button = self.xml.get_widget('no_mood_button') + no_mood_button.set_mode(False) + no_mood_button.connect('clicked', + self.on_mood_button_clicked, None) - x = 1 - y = 0 - self.mood_buttons = {} + x = 1 + y = 0 + self.mood_buttons = {} - # Order them first - self.MOODS = [] - for mood in pep.MOODS: - self.MOODS.append(mood) - self.MOODS.sort() + # Order them first + self.MOODS = [] + for mood in pep.MOODS: + self.MOODS.append(mood) + self.MOODS.sort() - for mood in self.MOODS: - self.mood_buttons[mood] = gtk.RadioButton(no_mood_button) - self.mood_buttons[mood].set_mode(False) - self.mood_buttons[mood].add(gtkgui_helpers.load_mood_icon(mood)) - self.mood_buttons[mood].set_relief(gtk.RELIEF_NONE) - self.mood_buttons[mood].set_tooltip_text(pep.MOODS[mood]) - self.mood_buttons[mood].connect('clicked', - self.on_mood_button_clicked, mood) - table.attach(self.mood_buttons[mood], x, x + 1, y, y + 1) + for mood in self.MOODS: + self.mood_buttons[mood] = gtk.RadioButton(no_mood_button) + self.mood_buttons[mood].set_mode(False) + self.mood_buttons[mood].add(gtkgui_helpers.load_mood_icon(mood)) + self.mood_buttons[mood].set_relief(gtk.RELIEF_NONE) + self.mood_buttons[mood].set_tooltip_text(pep.MOODS[mood]) + self.mood_buttons[mood].connect('clicked', + self.on_mood_button_clicked, mood) + table.attach(self.mood_buttons[mood], x, x + 1, y, y + 1) - # Calculate the next position - x += 1 - if x >= self.COLS: - x = 0 - y += 1 + # Calculate the next position + x += 1 + if x >= self.COLS: + x = 0 + y += 1 - if self.mood in pep.MOODS: - self.mood_buttons[self.mood].set_active(True) - self.label.set_text(pep.MOODS[self.mood]) - self.entry.set_sensitive(True) - if self.text: - self.entry.set_text(self.text) - else: - self.label.set_text(_('None')) - self.entry.set_text('') - self.entry.set_sensitive(False) + if self.mood in pep.MOODS: + self.mood_buttons[self.mood].set_active(True) + self.label.set_text(pep.MOODS[self.mood]) + self.entry.set_sensitive(True) + if self.text: + self.entry.set_text(self.text) + else: + self.label.set_text(_('None')) + self.entry.set_text('') + self.entry.set_sensitive(False) - self.xml.signal_autoconnect(self) - self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - self.window.show_all() + self.xml.signal_autoconnect(self) + self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + self.window.show_all() - def on_mood_button_clicked(self, widget, data): - if data: - self.label.set_text(pep.MOODS[data]) - self.entry.set_sensitive(True) - else: - self.label.set_text(_('None')) - self.entry.set_text('') - self.entry.set_sensitive(False) - self.mood = data + def on_mood_button_clicked(self, widget, data): + if data: + self.label.set_text(pep.MOODS[data]) + self.entry.set_sensitive(True) + else: + self.label.set_text(_('None')) + self.entry.set_text('') + self.entry.set_sensitive(False) + self.mood = data - def on_ok_button_clicked(self, widget): - '''Return mood and messsage (None if no mood selected)''' - message = self.entry.get_text().decode('utf-8') - self.on_response(self.mood, message) - self.window.destroy() + def on_ok_button_clicked(self, widget): + '''Return mood and messsage (None if no mood selected)''' + message = self.entry.get_text().decode('utf-8') + self.on_response(self.mood, message) + self.window.destroy() - def on_cancel_button_clicked(self, widget): - self.window.destroy() + def on_cancel_button_clicked(self, widget): + self.window.destroy() class TimeoutDialog: - """ - Class designed to be derivated to create timeout'd dialogs (dialogs that - closes automatically after a timeout) - """ - def __init__(self, timeout, on_timeout): - self.countdown_left = timeout - self.countdown_enabled = True - self.title_text = '' - self.on_timeout = on_timeout + """ + Class designed to be derivated to create timeout'd dialogs (dialogs that + closes automatically after a timeout) + """ + def __init__(self, timeout, on_timeout): + self.countdown_left = timeout + self.countdown_enabled = True + self.title_text = '' + self.on_timeout = on_timeout - def run_timeout(self): - if self.countdown_left > 0: - self.countdown() - gobject.timeout_add_seconds(1, self.countdown) + def run_timeout(self): + if self.countdown_left > 0: + self.countdown() + gobject.timeout_add_seconds(1, self.countdown) - def on_timeout(): - """ - To be implemented in derivated classes - """ - pass + def on_timeout(): + """ + To be implemented in derivated classes + """ + pass - def countdown(self): - if self.countdown_enabled: - if self.countdown_left <= 0: - self.on_timeout() - return False - self.dialog.set_title('%s [%s]' % (self.title_text, - str(self.countdown_left))) - self.countdown_left -= 1 - return True - else: - self.dialog.set_title(self.title_text) - return False + def countdown(self): + if self.countdown_enabled: + if self.countdown_left <= 0: + self.on_timeout() + return False + self.dialog.set_title('%s [%s]' % (self.title_text, + str(self.countdown_left))) + self.countdown_left -= 1 + return True + else: + self.dialog.set_title(self.title_text) + return False class ChangeStatusMessageDialog(TimeoutDialog): - def __init__(self, on_response, show=None, show_pep=True): - countdown_time = gajim.config.get('change_status_window_timeout') - TimeoutDialog.__init__(self, countdown_time, self.on_timeout) - self.show = show - self.pep_dict = {} - self.show_pep = show_pep - self.on_response = on_response - self.xml = gtkgui_helpers.get_glade('change_status_message_dialog.glade') - self.dialog = self.xml.get_widget('change_status_message_dialog') - self.dialog.set_transient_for(gajim.interface.roster.window) - msg = None - if show: - uf_show = helpers.get_uf_show(show) - self.title_text = _('%s Status Message') % uf_show - msg = gajim.config.get_per('statusmsg', '_last_' + self.show, - 'message') - self.pep_dict['activity'] = gajim.config.get_per('statusmsg', - '_last_' + self.show, 'activity') - self.pep_dict['subactivity'] = gajim.config.get_per('statusmsg', - '_last_' + self.show, 'subactivity') - self.pep_dict['activity_text'] = gajim.config.get_per('statusmsg', - '_last_' + self.show, 'activity_text') - self.pep_dict['mood'] = gajim.config.get_per('statusmsg', - '_last_' + self.show, 'mood') - self.pep_dict['mood_text'] = gajim.config.get_per('statusmsg', - '_last_' + self.show, 'mood_text') - else: - self.title_text = _('Status Message') - self.dialog.set_title(self.title_text) + def __init__(self, on_response, show=None, show_pep=True): + countdown_time = gajim.config.get('change_status_window_timeout') + TimeoutDialog.__init__(self, countdown_time, self.on_timeout) + self.show = show + self.pep_dict = {} + self.show_pep = show_pep + self.on_response = on_response + self.xml = gtkgui_helpers.get_glade('change_status_message_dialog.glade') + self.dialog = self.xml.get_widget('change_status_message_dialog') + self.dialog.set_transient_for(gajim.interface.roster.window) + msg = None + if show: + uf_show = helpers.get_uf_show(show) + self.title_text = _('%s Status Message') % uf_show + msg = gajim.config.get_per('statusmsg', '_last_' + self.show, + 'message') + self.pep_dict['activity'] = gajim.config.get_per('statusmsg', + '_last_' + self.show, 'activity') + self.pep_dict['subactivity'] = gajim.config.get_per('statusmsg', + '_last_' + self.show, 'subactivity') + self.pep_dict['activity_text'] = gajim.config.get_per('statusmsg', + '_last_' + self.show, 'activity_text') + self.pep_dict['mood'] = gajim.config.get_per('statusmsg', + '_last_' + self.show, 'mood') + self.pep_dict['mood_text'] = gajim.config.get_per('statusmsg', + '_last_' + self.show, 'mood_text') + else: + self.title_text = _('Status Message') + self.dialog.set_title(self.title_text) - message_textview = self.xml.get_widget('message_textview') - self.message_buffer = message_textview.get_buffer() - self.message_buffer.connect('changed', self.on_message_buffer_changed) - if not msg: - msg = '' - msg = helpers.from_one_line(msg) - self.message_buffer.set_text(msg) + message_textview = self.xml.get_widget('message_textview') + self.message_buffer = message_textview.get_buffer() + self.message_buffer.connect('changed', self.on_message_buffer_changed) + if not msg: + msg = '' + msg = helpers.from_one_line(msg) + self.message_buffer.set_text(msg) - # have an empty string selectable, so user can clear msg - self.preset_messages_dict = {'': ['', '', '', '', '', '']} - for msg_name in gajim.config.get_per('statusmsg'): - if msg_name.startswith('_last_'): - continue - opts = [] - for opt in ['message', 'activity', 'subactivity', 'activity_text', - 'mood', 'mood_text']: - opts.append(gajim.config.get_per('statusmsg', msg_name, opt)) - opts[0] = helpers.from_one_line(opts[0]) - self.preset_messages_dict[msg_name] = opts - sorted_keys_list = helpers.get_sorted_keys(self.preset_messages_dict) + # have an empty string selectable, so user can clear msg + self.preset_messages_dict = {'': ['', '', '', '', '', '']} + for msg_name in gajim.config.get_per('statusmsg'): + if msg_name.startswith('_last_'): + continue + opts = [] + for opt in ['message', 'activity', 'subactivity', 'activity_text', + 'mood', 'mood_text']: + opts.append(gajim.config.get_per('statusmsg', msg_name, opt)) + opts[0] = helpers.from_one_line(opts[0]) + self.preset_messages_dict[msg_name] = opts + sorted_keys_list = helpers.get_sorted_keys(self.preset_messages_dict) - self.message_liststore = gtk.ListStore(str) # msg_name - self.message_combobox = self.xml.get_widget('message_combobox') - self.message_combobox.set_model(self.message_liststore) - cellrenderertext = gtk.CellRendererText() - self.message_combobox.pack_start(cellrenderertext, True) - self.message_combobox.add_attribute(cellrenderertext, 'text', 0) - for msg_name in sorted_keys_list: - self.message_liststore.append((msg_name,)) + self.message_liststore = gtk.ListStore(str) # msg_name + self.message_combobox = self.xml.get_widget('message_combobox') + self.message_combobox.set_model(self.message_liststore) + cellrenderertext = gtk.CellRendererText() + self.message_combobox.pack_start(cellrenderertext, True) + self.message_combobox.add_attribute(cellrenderertext, 'text', 0) + for msg_name in sorted_keys_list: + self.message_liststore.append((msg_name,)) - if show_pep: - self.draw_activity() - self.draw_mood() - else: - # remove acvtivity / mood lines - self.xml.get_widget('activity_label').set_no_show_all(True) - self.xml.get_widget('activity_button').set_no_show_all(True) - self.xml.get_widget('mood_label').set_no_show_all(True) - self.xml.get_widget('mood_button').set_no_show_all(True) - self.xml.get_widget('activity_label').hide() - self.xml.get_widget('activity_button').hide() - self.xml.get_widget('mood_label').hide() - self.xml.get_widget('mood_button').hide() + if show_pep: + self.draw_activity() + self.draw_mood() + else: + # remove acvtivity / mood lines + self.xml.get_widget('activity_label').set_no_show_all(True) + self.xml.get_widget('activity_button').set_no_show_all(True) + self.xml.get_widget('mood_label').set_no_show_all(True) + self.xml.get_widget('mood_button').set_no_show_all(True) + self.xml.get_widget('activity_label').hide() + self.xml.get_widget('activity_button').hide() + self.xml.get_widget('mood_label').hide() + self.xml.get_widget('mood_button').hide() - self.xml.signal_autoconnect(self) - self.run_timeout() - self.dialog.connect('response', self.on_dialog_response) - self.dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - self.dialog.show_all() + self.xml.signal_autoconnect(self) + self.run_timeout() + self.dialog.connect('response', self.on_dialog_response) + self.dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + self.dialog.show_all() - def draw_activity(self): - """ - Set activity button - """ - img = self.xml.get_widget('activity_image') - label = self.xml.get_widget('activity_button_label') - if 'activity' in self.pep_dict and self.pep_dict['activity'] in \ - pep.ACTIVITIES: - if 'subactivity' in self.pep_dict and self.pep_dict['subactivity'] in \ - pep.ACTIVITIES[self.pep_dict['activity']]: - img.set_from_pixbuf(gtkgui_helpers.load_activity_icon( - self.pep_dict['activity'], self.pep_dict['subactivity']).\ - get_pixbuf()) - else: - img.set_from_pixbuf(gtkgui_helpers.load_activity_icon( - self.pep_dict['activity']).get_pixbuf()) -# item.set_tooltip_text(pep.ACTIVITIES[category]['category']) - if self.pep_dict['activity_text']: - label.set_text(self.pep_dict['activity_text']) - else: - label.set_text('') - else: - img.set_from_pixbuf(None) - label.set_text('') + def draw_activity(self): + """ + Set activity button + """ + img = self.xml.get_widget('activity_image') + label = self.xml.get_widget('activity_button_label') + if 'activity' in self.pep_dict and self.pep_dict['activity'] in \ + pep.ACTIVITIES: + if 'subactivity' in self.pep_dict and self.pep_dict['subactivity'] in \ + pep.ACTIVITIES[self.pep_dict['activity']]: + img.set_from_pixbuf(gtkgui_helpers.load_activity_icon( + self.pep_dict['activity'], self.pep_dict['subactivity']).\ + get_pixbuf()) + else: + img.set_from_pixbuf(gtkgui_helpers.load_activity_icon( + self.pep_dict['activity']).get_pixbuf()) +# item.set_tooltip_text(pep.ACTIVITIES[category]['category']) + if self.pep_dict['activity_text']: + label.set_text(self.pep_dict['activity_text']) + else: + label.set_text('') + else: + img.set_from_pixbuf(None) + label.set_text('') - def draw_mood(self): - """ - Set mood button - """ - img = self.xml.get_widget('mood_image') - label = self.xml.get_widget('mood_button_label') - if self.pep_dict['mood'] in pep.MOODS: - img.set_from_pixbuf(gtkgui_helpers.load_mood_icon( - self.pep_dict['mood']).get_pixbuf()) - if self.pep_dict['mood_text']: - label.set_text(self.pep_dict['mood_text']) - else: - label.set_text('') - else: - img.set_from_pixbuf(None) - label.set_text('') + def draw_mood(self): + """ + Set mood button + """ + img = self.xml.get_widget('mood_image') + label = self.xml.get_widget('mood_button_label') + if self.pep_dict['mood'] in pep.MOODS: + img.set_from_pixbuf(gtkgui_helpers.load_mood_icon( + self.pep_dict['mood']).get_pixbuf()) + if self.pep_dict['mood_text']: + label.set_text(self.pep_dict['mood_text']) + else: + label.set_text('') + else: + img.set_from_pixbuf(None) + label.set_text('') - def on_timeout(self): - # Prevent GUI freeze when the combobox menu is opened on close - self.message_combobox.popdown() - self.dialog.response(gtk.RESPONSE_OK) + def on_timeout(self): + # Prevent GUI freeze when the combobox menu is opened on close + self.message_combobox.popdown() + self.dialog.response(gtk.RESPONSE_OK) - def on_dialog_response(self, dialog, response): - if response == gtk.RESPONSE_OK: - beg, end = self.message_buffer.get_bounds() - message = self.message_buffer.get_text(beg, end).decode('utf-8')\ - .strip() - message = helpers.remove_invalid_xml_chars(message) - msg = helpers.to_one_line(message) - if self.show: - gajim.config.set_per('statusmsg', '_last_' + self.show, 'message', - msg) - if self.show_pep: - gajim.config.set_per('statusmsg', '_last_' + self.show, - 'activity', self.pep_dict['activity']) - gajim.config.set_per('statusmsg', '_last_' + self.show, - 'subactivity', self.pep_dict['subactivity']) - gajim.config.set_per('statusmsg', '_last_' + self.show, - 'activity_text', self.pep_dict['activity_text']) - gajim.config.set_per('statusmsg', '_last_' + self.show, 'mood', - self.pep_dict['mood']) - gajim.config.set_per('statusmsg', '_last_' + self.show, - 'mood_text', self.pep_dict['mood_text']) - else: - message = None # user pressed Cancel button or X wm button - self.dialog.destroy() - self.on_response(message, self.pep_dict) + def on_dialog_response(self, dialog, response): + if response == gtk.RESPONSE_OK: + beg, end = self.message_buffer.get_bounds() + message = self.message_buffer.get_text(beg, end).decode('utf-8')\ + .strip() + message = helpers.remove_invalid_xml_chars(message) + msg = helpers.to_one_line(message) + if self.show: + gajim.config.set_per('statusmsg', '_last_' + self.show, 'message', + msg) + if self.show_pep: + gajim.config.set_per('statusmsg', '_last_' + self.show, + 'activity', self.pep_dict['activity']) + gajim.config.set_per('statusmsg', '_last_' + self.show, + 'subactivity', self.pep_dict['subactivity']) + gajim.config.set_per('statusmsg', '_last_' + self.show, + 'activity_text', self.pep_dict['activity_text']) + gajim.config.set_per('statusmsg', '_last_' + self.show, 'mood', + self.pep_dict['mood']) + gajim.config.set_per('statusmsg', '_last_' + self.show, + 'mood_text', self.pep_dict['mood_text']) + else: + message = None # user pressed Cancel button or X wm button + self.dialog.destroy() + self.on_response(message, self.pep_dict) - def on_message_combobox_changed(self, widget): - self.countdown_enabled = False - model = widget.get_model() - active = widget.get_active() - if active < 0: - return None - name = model[active][0].decode('utf-8') - self.message_buffer.set_text(self.preset_messages_dict[name][0]) - self.pep_dict['activity'] = self.preset_messages_dict[name][1] - self.pep_dict['subactivity'] = self.preset_messages_dict[name][2] - self.pep_dict['activity_text'] = self.preset_messages_dict[name][3] - self.pep_dict['mood'] = self.preset_messages_dict[name][4] - self.pep_dict['mood_text'] = self.preset_messages_dict[name][5] - self.draw_activity() - self.draw_mood() + def on_message_combobox_changed(self, widget): + self.countdown_enabled = False + model = widget.get_model() + active = widget.get_active() + if active < 0: + return None + name = model[active][0].decode('utf-8') + self.message_buffer.set_text(self.preset_messages_dict[name][0]) + self.pep_dict['activity'] = self.preset_messages_dict[name][1] + self.pep_dict['subactivity'] = self.preset_messages_dict[name][2] + self.pep_dict['activity_text'] = self.preset_messages_dict[name][3] + self.pep_dict['mood'] = self.preset_messages_dict[name][4] + self.pep_dict['mood_text'] = self.preset_messages_dict[name][5] + self.draw_activity() + self.draw_mood() - def on_change_status_message_dialog_key_press_event(self, widget, event): - self.countdown_enabled = False - if event.keyval == gtk.keysyms.Return or \ - event.keyval == gtk.keysyms.KP_Enter: # catch CTRL+ENTER - if (event.state & gtk.gdk.CONTROL_MASK): - self.dialog.response(gtk.RESPONSE_OK) - # Stop the event - return True + def on_change_status_message_dialog_key_press_event(self, widget, event): + self.countdown_enabled = False + if event.keyval == gtk.keysyms.Return or \ + event.keyval == gtk.keysyms.KP_Enter: # catch CTRL+ENTER + if (event.state & gtk.gdk.CONTROL_MASK): + self.dialog.response(gtk.RESPONSE_OK) + # Stop the event + return True - def on_message_buffer_changed(self, widget): - self.countdown_enabled = False - self.toggle_sensitiviy_of_save_as_preset() + def on_message_buffer_changed(self, widget): + self.countdown_enabled = False + self.toggle_sensitiviy_of_save_as_preset() - def toggle_sensitiviy_of_save_as_preset(self): - btn = self.xml.get_widget('save_as_preset_button') - if self.message_buffer.get_char_count() == 0: - btn.set_sensitive(False) - else: - btn.set_sensitive(True) + def toggle_sensitiviy_of_save_as_preset(self): + btn = self.xml.get_widget('save_as_preset_button') + if self.message_buffer.get_char_count() == 0: + btn.set_sensitive(False) + else: + btn.set_sensitive(True) - def on_save_as_preset_button_clicked(self, widget): - self.countdown_enabled = False - start_iter, finish_iter = self.message_buffer.get_bounds() - status_message_to_save_as_preset = self.message_buffer.get_text( - start_iter, finish_iter) - def on_ok(msg_name): - msg_text = status_message_to_save_as_preset.decode('utf-8') - msg_text_1l = helpers.to_one_line(msg_text) - if not msg_name: # msg_name was '' - msg_name = msg_text_1l.decode('utf-8') + def on_save_as_preset_button_clicked(self, widget): + self.countdown_enabled = False + start_iter, finish_iter = self.message_buffer.get_bounds() + status_message_to_save_as_preset = self.message_buffer.get_text( + start_iter, finish_iter) + def on_ok(msg_name): + msg_text = status_message_to_save_as_preset.decode('utf-8') + msg_text_1l = helpers.to_one_line(msg_text) + if not msg_name: # msg_name was '' + msg_name = msg_text_1l.decode('utf-8') - def on_ok2(): - self.preset_messages_dict[msg_name] = [msg_text, self.pep_dict.get( - 'activity'), self.pep_dict.get('subactivity'), self.pep_dict.get( - 'activity_text'), self.pep_dict.get('mood'), self.pep_dict.get( - 'mood_text')] - gajim.config.set_per('statusmsg', msg_name, 'message', msg_text_1l) - gajim.config.set_per('statusmsg', msg_name, 'activity', - self.pep_dict.get('activity')) - gajim.config.set_per('statusmsg', msg_name, 'subactivity', - self.pep_dict.get('subactivity')) - gajim.config.set_per('statusmsg', msg_name, 'activity_text', - self.pep_dict.get('activity_text')) - gajim.config.set_per('statusmsg', msg_name, 'mood', - self.pep_dict.get('mood')) - gajim.config.set_per('statusmsg', msg_name, 'mood_text', - self.pep_dict.get('mood_text')) - if msg_name in self.preset_messages_dict: - ConfirmationDialog(_('Overwrite Status Message?'), - _('This name is already used. Do you want to overwrite this ' - 'status message?'), on_response_ok=on_ok2) - return - gajim.config.add_per('statusmsg', msg_name) - on_ok2() - iter_ = self.message_liststore.append((msg_name,)) - # select in combobox the one we just saved - self.message_combobox.set_active_iter(iter_) - InputDialog(_('Save as Preset Status Message'), - _('Please type a name for this status message'), is_modal=False, - ok_handler=on_ok) + def on_ok2(): + self.preset_messages_dict[msg_name] = [msg_text, self.pep_dict.get( + 'activity'), self.pep_dict.get('subactivity'), self.pep_dict.get( + 'activity_text'), self.pep_dict.get('mood'), self.pep_dict.get( + 'mood_text')] + gajim.config.set_per('statusmsg', msg_name, 'message', msg_text_1l) + gajim.config.set_per('statusmsg', msg_name, 'activity', + self.pep_dict.get('activity')) + gajim.config.set_per('statusmsg', msg_name, 'subactivity', + self.pep_dict.get('subactivity')) + gajim.config.set_per('statusmsg', msg_name, 'activity_text', + self.pep_dict.get('activity_text')) + gajim.config.set_per('statusmsg', msg_name, 'mood', + self.pep_dict.get('mood')) + gajim.config.set_per('statusmsg', msg_name, 'mood_text', + self.pep_dict.get('mood_text')) + if msg_name in self.preset_messages_dict: + ConfirmationDialog(_('Overwrite Status Message?'), + _('This name is already used. Do you want to overwrite this ' + 'status message?'), on_response_ok=on_ok2) + return + gajim.config.add_per('statusmsg', msg_name) + on_ok2() + iter_ = self.message_liststore.append((msg_name,)) + # select in combobox the one we just saved + self.message_combobox.set_active_iter(iter_) + InputDialog(_('Save as Preset Status Message'), + _('Please type a name for this status message'), is_modal=False, + ok_handler=on_ok) - def on_activity_button_clicked(self, widget): - self.countdown_enabled = False - def on_response(activity, subactivity, text): - self.pep_dict['activity'] = activity or '' - self.pep_dict['subactivity'] = subactivity or '' - self.pep_dict['activity_text'] = text - self.draw_activity() - ChangeActivityDialog(on_response, self.pep_dict['activity'], - self.pep_dict['subactivity'], self.pep_dict['activity_text']) + def on_activity_button_clicked(self, widget): + self.countdown_enabled = False + def on_response(activity, subactivity, text): + self.pep_dict['activity'] = activity or '' + self.pep_dict['subactivity'] = subactivity or '' + self.pep_dict['activity_text'] = text + self.draw_activity() + ChangeActivityDialog(on_response, self.pep_dict['activity'], + self.pep_dict['subactivity'], self.pep_dict['activity_text']) - def on_mood_button_clicked(self, widget): - self.countdown_enabled = False - def on_response(mood, text): - self.pep_dict['mood'] = mood or '' - self.pep_dict['mood_text'] = text - self.draw_mood() - ChangeMoodDialog(on_response, self.pep_dict['mood'], - self.pep_dict['mood_text']) + def on_mood_button_clicked(self, widget): + self.countdown_enabled = False + def on_response(mood, text): + self.pep_dict['mood'] = mood or '' + self.pep_dict['mood_text'] = text + self.draw_mood() + ChangeMoodDialog(on_response, self.pep_dict['mood'], + self.pep_dict['mood_text']) class AddNewContactWindow: - """ - Class for AddNewContactWindow - """ + """ + Class for AddNewContactWindow + """ - uid_labels = {'jabber': _('Jabber ID:'), - 'aim': _('AIM Address:'), - 'gadu-gadu': _('GG Number:'), - 'icq': _('ICQ Number:'), - 'msn': _('MSN Address:'), - 'yahoo': _('Yahoo! Address:')} + uid_labels = {'jabber': _('Jabber ID:'), + 'aim': _('AIM Address:'), + 'gadu-gadu': _('GG Number:'), + 'icq': _('ICQ Number:'), + 'msn': _('MSN Address:'), + 'yahoo': _('Yahoo! Address:')} - def __init__(self, account=None, jid=None, user_nick=None, group=None): - self.account = account - if account is None: - # fill accounts with active accounts - accounts = [] - for account in gajim.connections.keys(): - if gajim.connections[account].connected > 1: - accounts.append(account) - if not accounts: - return - if len(accounts) == 1: - self.account = account - else: - accounts = [self.account] - if self.account: - location = gajim.interface.instances[self.account] - else: - location = gajim.interface.instances - if 'add_contact' in location: - location['add_contact'].window.present() - # An instance is already opened - return - location['add_contact'] = self - self.xml = gtkgui_helpers.get_glade('add_new_contact_window.glade') - self.xml.signal_autoconnect(self) - self.window = self.xml.get_widget('add_new_contact_window') - for w in ('account_combobox', 'account_hbox', 'account_label', - 'uid_label', 'uid_entry', 'protocol_combobox', 'protocol_jid_combobox', - 'protocol_hbox', 'nickname_entry', 'message_scrolledwindow', - 'register_hbox', 'subscription_table', 'add_button', - 'message_textview', 'connected_label', 'group_comboboxentry', - 'auto_authorize_checkbutton'): - self.__dict__[w] = self.xml.get_widget(w) - if account and len(gajim.connections) >= 2: - prompt_text =\ + def __init__(self, account=None, jid=None, user_nick=None, group=None): + self.account = account + if account is None: + # fill accounts with active accounts + accounts = [] + for account in gajim.connections.keys(): + if gajim.connections[account].connected > 1: + accounts.append(account) + if not accounts: + return + if len(accounts) == 1: + self.account = account + else: + accounts = [self.account] + if self.account: + location = gajim.interface.instances[self.account] + else: + location = gajim.interface.instances + if 'add_contact' in location: + location['add_contact'].window.present() + # An instance is already opened + return + location['add_contact'] = self + self.xml = gtkgui_helpers.get_glade('add_new_contact_window.glade') + self.xml.signal_autoconnect(self) + self.window = self.xml.get_widget('add_new_contact_window') + for w in ('account_combobox', 'account_hbox', 'account_label', + 'uid_label', 'uid_entry', 'protocol_combobox', 'protocol_jid_combobox', + 'protocol_hbox', 'nickname_entry', 'message_scrolledwindow', + 'register_hbox', 'subscription_table', 'add_button', + 'message_textview', 'connected_label', 'group_comboboxentry', + 'auto_authorize_checkbutton'): + self.__dict__[w] = self.xml.get_widget(w) + if account and len(gajim.connections) >= 2: + prompt_text =\ _('Please fill in the data of the contact you want to add in account %s') %account - else: - prompt_text = _('Please fill in the data of the contact you want to add') - self.xml.get_widget('prompt_label').set_text(prompt_text) - self.agents = {'jabber': []} - # types to which we are not subscribed but account has an agent for it - self.available_types = [] - for acct in accounts: - for j in gajim.contacts.get_jid_list(acct): - if gajim.jid_is_transport(j): - type_ = gajim.get_transport_name_from_jid(j, False) - if not type_: - continue - if type_ in self.agents: - self.agents[type_].append(j) - else: - self.agents[type_] = [j] - # Now add the one to which we can register - for acct in accounts: - for type_ in gajim.connections[acct].available_transports: - if type_ in self.agents: - continue - self.agents[type_] = [] - for jid_ in gajim.connections[acct].available_transports[type_]: - if not jid_ in self.agents[type_]: - self.agents[type_].append(jid_) - self.available_types.append(type_) - liststore = gtk.ListStore(str) - self.group_comboboxentry.set_model(liststore) - # Combobox with transport/jabber icons - liststore = gtk.ListStore(str, gtk.gdk.Pixbuf, str) - cell = gtk.CellRendererPixbuf() - self.protocol_combobox.pack_start(cell, False) - self.protocol_combobox.add_attribute(cell, 'pixbuf', 1) - cell = gtk.CellRendererText() - cell.set_property('xpad', 5) - self.protocol_combobox.pack_start(cell, True) - self.protocol_combobox.add_attribute(cell, 'text', 0) - self.protocol_combobox.set_model(liststore) - uf_type = {'jabber': 'Jabber', 'aim': 'AIM', 'gadu-gadu': 'Gadu Gadu', - 'icq': 'ICQ', 'msn': 'MSN', 'yahoo': 'Yahoo'} - # Jabber as first - img = gajim.interface.jabber_state_images['16']['online'] - liststore.append(['Jabber', img.get_pixbuf(), 'jabber']) - for type_ in self.agents: - if type_ == 'jabber': - continue - imgs = gajim.interface.roster.transports_state_images - img = None - if type_ in imgs['16'] and 'online' in imgs['16'][type_]: - img = imgs['16'][type_]['online'] - if type_ in uf_type: - liststore.append([uf_type[type_], img.get_pixbuf(), type_]) - else: - liststore.append([type_, img.get_pixbuf(), type_]) - else: - liststore.append([type_, img, type_]) - self.protocol_combobox.set_active(0) - self.auto_authorize_checkbutton.show() - liststore = gtk.ListStore(str) - self.protocol_jid_combobox.set_model(liststore) - if jid: - type_ = gajim.get_transport_name_from_jid(jid) - if not type_: - type_ = 'jabber' - if type_ == 'jabber': - self.uid_entry.set_text(jid) - else: - uid, transport = gajim.get_name_and_server_from_jid(jid) - self.uid_entry.set_text(uid.replace('%', '@', 1)) - #set protocol_combobox - model = self.protocol_combobox.get_model() - iter_ = model.get_iter_first() - i = 0 - while iter_: - if model[iter_][2] == type_: - self.protocol_combobox.set_active(i) - break - iter_ = model.iter_next(iter_) - i += 1 + else: + prompt_text = _('Please fill in the data of the contact you want to add') + self.xml.get_widget('prompt_label').set_text(prompt_text) + self.agents = {'jabber': []} + # types to which we are not subscribed but account has an agent for it + self.available_types = [] + for acct in accounts: + for j in gajim.contacts.get_jid_list(acct): + if gajim.jid_is_transport(j): + type_ = gajim.get_transport_name_from_jid(j, False) + if not type_: + continue + if type_ in self.agents: + self.agents[type_].append(j) + else: + self.agents[type_] = [j] + # Now add the one to which we can register + for acct in accounts: + for type_ in gajim.connections[acct].available_transports: + if type_ in self.agents: + continue + self.agents[type_] = [] + for jid_ in gajim.connections[acct].available_transports[type_]: + if not jid_ in self.agents[type_]: + self.agents[type_].append(jid_) + self.available_types.append(type_) + liststore = gtk.ListStore(str) + self.group_comboboxentry.set_model(liststore) + # Combobox with transport/jabber icons + liststore = gtk.ListStore(str, gtk.gdk.Pixbuf, str) + cell = gtk.CellRendererPixbuf() + self.protocol_combobox.pack_start(cell, False) + self.protocol_combobox.add_attribute(cell, 'pixbuf', 1) + cell = gtk.CellRendererText() + cell.set_property('xpad', 5) + self.protocol_combobox.pack_start(cell, True) + self.protocol_combobox.add_attribute(cell, 'text', 0) + self.protocol_combobox.set_model(liststore) + uf_type = {'jabber': 'Jabber', 'aim': 'AIM', 'gadu-gadu': 'Gadu Gadu', + 'icq': 'ICQ', 'msn': 'MSN', 'yahoo': 'Yahoo'} + # Jabber as first + img = gajim.interface.jabber_state_images['16']['online'] + liststore.append(['Jabber', img.get_pixbuf(), 'jabber']) + for type_ in self.agents: + if type_ == 'jabber': + continue + imgs = gajim.interface.roster.transports_state_images + img = None + if type_ in imgs['16'] and 'online' in imgs['16'][type_]: + img = imgs['16'][type_]['online'] + if type_ in uf_type: + liststore.append([uf_type[type_], img.get_pixbuf(), type_]) + else: + liststore.append([type_, img.get_pixbuf(), type_]) + else: + liststore.append([type_, img, type_]) + self.protocol_combobox.set_active(0) + self.auto_authorize_checkbutton.show() + liststore = gtk.ListStore(str) + self.protocol_jid_combobox.set_model(liststore) + if jid: + type_ = gajim.get_transport_name_from_jid(jid) + if not type_: + type_ = 'jabber' + if type_ == 'jabber': + self.uid_entry.set_text(jid) + else: + uid, transport = gajim.get_name_and_server_from_jid(jid) + self.uid_entry.set_text(uid.replace('%', '@', 1)) + #set protocol_combobox + model = self.protocol_combobox.get_model() + iter_ = model.get_iter_first() + i = 0 + while iter_: + if model[iter_][2] == type_: + self.protocol_combobox.set_active(i) + break + iter_ = model.iter_next(iter_) + i += 1 - # set protocol_jid_combobox - self.protocol_jid_combobox.set_active(0) - model = self.protocol_jid_combobox.get_model() - iter_ = model.get_iter_first() - i = 0 - while iter_: - if model[iter_][0] == transport: - self.protocol_jid_combobox.set_active(i) - break - iter_ = model.iter_next(iter_) - i += 1 - if user_nick: - self.nickname_entry.set_text(user_nick) - self.nickname_entry.grab_focus() - else: - self.uid_entry.grab_focus() - group_names = [] - for acct in accounts: - for g in gajim.groups[acct].keys(): - if g not in helpers.special_groups and g not in group_names: - group_names.append(g) - group_names.sort() - i = 0 - for g in group_names: - self.group_comboboxentry.append_text(g) - if group == g: - self.group_comboboxentry.set_active(i) - i += 1 + # set protocol_jid_combobox + self.protocol_jid_combobox.set_active(0) + model = self.protocol_jid_combobox.get_model() + iter_ = model.get_iter_first() + i = 0 + while iter_: + if model[iter_][0] == transport: + self.protocol_jid_combobox.set_active(i) + break + iter_ = model.iter_next(iter_) + i += 1 + if user_nick: + self.nickname_entry.set_text(user_nick) + self.nickname_entry.grab_focus() + else: + self.uid_entry.grab_focus() + group_names = [] + for acct in accounts: + for g in gajim.groups[acct].keys(): + if g not in helpers.special_groups and g not in group_names: + group_names.append(g) + group_names.sort() + i = 0 + for g in group_names: + self.group_comboboxentry.append_text(g) + if group == g: + self.group_comboboxentry.set_active(i) + i += 1 - self.window.show_all() + self.window.show_all() - if self.account: - self.account_label.hide() - self.account_hbox.hide() - else: - liststore = gtk.ListStore(str, str) - for acct in accounts: - liststore.append([acct, acct]) - self.account_combobox.set_model(liststore) - self.account_combobox.set_active(0) + if self.account: + self.account_label.hide() + self.account_hbox.hide() + else: + liststore = gtk.ListStore(str, str) + for acct in accounts: + liststore.append([acct, acct]) + self.account_combobox.set_model(liststore) + self.account_combobox.set_active(0) - def on_add_new_contact_window_destroy(self, widget): - if self.account: - location = gajim.interface.instances[self.account] - else: - location = gajim.interface.instances - del location['add_contact'] + def on_add_new_contact_window_destroy(self, widget): + if self.account: + location = gajim.interface.instances[self.account] + else: + location = gajim.interface.instances + del location['add_contact'] - def on_register_button_clicked(self, widget): - jid = self.protocol_jid_combobox.get_active_text().decode('utf-8') - gajim.connections[self.account].request_register_agent_info(jid) + def on_register_button_clicked(self, widget): + jid = self.protocol_jid_combobox.get_active_text().decode('utf-8') + gajim.connections[self.account].request_register_agent_info(jid) - def on_add_new_contact_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: # ESCAPE - self.window.destroy() + def on_add_new_contact_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: # ESCAPE + self.window.destroy() - def on_cancel_button_clicked(self, widget): - """ - When Cancel button is clicked - """ - self.window.destroy() + def on_cancel_button_clicked(self, widget): + """ + When Cancel button is clicked + """ + self.window.destroy() - def on_add_button_clicked(self, widget): - """ - When Subscribe button is clicked - """ - jid = self.uid_entry.get_text().decode('utf-8').strip() - if not jid: - return + def on_add_button_clicked(self, widget): + """ + When Subscribe button is clicked + """ + jid = self.uid_entry.get_text().decode('utf-8').strip() + if not jid: + return - model = self.protocol_combobox.get_model() - iter_ = self.protocol_combobox.get_active_iter() - type_ = model[iter_][2] - if type_ != 'jabber': - transport = self.protocol_jid_combobox.get_active_text().decode( - 'utf-8') - jid = jid.replace('@', '%') + '@' + transport + model = self.protocol_combobox.get_model() + iter_ = self.protocol_combobox.get_active_iter() + type_ = model[iter_][2] + if type_ != 'jabber': + transport = self.protocol_jid_combobox.get_active_text().decode( + 'utf-8') + jid = jid.replace('@', '%') + '@' + transport - # check if jid is conform to RFC and stringprep it - try: - jid = helpers.parse_jid(jid) - except helpers.InvalidFormat, s: - pritext = _('Invalid User ID') - ErrorDialog(pritext, str(s)) - return + # check if jid is conform to RFC and stringprep it + try: + jid = helpers.parse_jid(jid) + except helpers.InvalidFormat, s: + pritext = _('Invalid User ID') + ErrorDialog(pritext, str(s)) + return - # No resource in jid - if jid.find('/') >= 0: - pritext = _('Invalid User ID') - ErrorDialog(pritext, _('The user ID must not contain a resource.')) - return + # No resource in jid + if jid.find('/') >= 0: + pritext = _('Invalid User ID') + ErrorDialog(pritext, _('The user ID must not contain a resource.')) + return - if jid == gajim.get_jid_from_account(self.account): - pritext = _('Invalid User ID') - ErrorDialog(pritext, _('You cannot add yourself to your roster.')) - return + if jid == gajim.get_jid_from_account(self.account): + pritext = _('Invalid User ID') + ErrorDialog(pritext, _('You cannot add yourself to your roster.')) + return - nickname = self.nickname_entry.get_text().decode('utf-8') or '' - # get value of account combobox, if account was not specified - if not self.account: - model = self.account_combobox.get_model() - index = self.account_combobox.get_active() - self.account = model[index][1] + nickname = self.nickname_entry.get_text().decode('utf-8') or '' + # get value of account combobox, if account was not specified + if not self.account: + model = self.account_combobox.get_model() + index = self.account_combobox.get_active() + self.account = model[index][1] - # Check if jid is already in roster - if jid in gajim.contacts.get_jid_list(self.account): - c = gajim.contacts.get_first_contact_from_jid(self.account, jid) - if _('Not in Roster') not in c.groups and c.sub in ('both', 'to'): - ErrorDialog(_('Contact already in roster'), - _('This contact is already listed in your roster.')) - return + # Check if jid is already in roster + if jid in gajim.contacts.get_jid_list(self.account): + c = gajim.contacts.get_first_contact_from_jid(self.account, jid) + if _('Not in Roster') not in c.groups and c.sub in ('both', 'to'): + ErrorDialog(_('Contact already in roster'), + _('This contact is already listed in your roster.')) + return - if type_ == 'jabber': - message_buffer = self.message_textview.get_buffer() - start_iter = message_buffer.get_start_iter() - end_iter = message_buffer.get_end_iter() - message = message_buffer.get_text(start_iter, end_iter).decode('utf-8') - else: - message= '' - group = self.group_comboboxentry.child.get_text().decode('utf-8') - groups = [] - if group: - groups = [group] - auto_auth = self.auto_authorize_checkbutton.get_active() - gajim.interface.roster.req_sub(self, jid, message, self.account, - groups = groups, nickname = nickname, auto_auth = auto_auth) - self.window.destroy() + if type_ == 'jabber': + message_buffer = self.message_textview.get_buffer() + start_iter = message_buffer.get_start_iter() + end_iter = message_buffer.get_end_iter() + message = message_buffer.get_text(start_iter, end_iter).decode('utf-8') + else: + message= '' + group = self.group_comboboxentry.child.get_text().decode('utf-8') + groups = [] + if group: + groups = [group] + auto_auth = self.auto_authorize_checkbutton.get_active() + gajim.interface.roster.req_sub(self, jid, message, self.account, + groups = groups, nickname = nickname, auto_auth = auto_auth) + self.window.destroy() - def on_protocol_combobox_changed(self, widget): - model = widget.get_model() - iter_ = widget.get_active_iter() - type_ = model[iter_][2] - model = self.protocol_jid_combobox.get_model() - model.clear() - if len(self.agents[type_]): - for jid_ in self.agents[type_]: - model.append([jid_]) - self.protocol_jid_combobox.set_active(0) - if len(self.agents[type_]) > 1: - self.protocol_jid_combobox.show() - else: - self.protocol_jid_combobox.hide() - if type_ in self.uid_labels: - self.uid_label.set_text(self.uid_labels[type_]) - else: - self.uid_label.set_text(_('User ID:')) - if type_ == 'jabber': - self.message_scrolledwindow.show() - else: - self.message_scrolledwindow.hide() - if type_ in self.available_types: - self.register_hbox.show() - self.auto_authorize_checkbutton.hide() - self.connected_label.hide() - self.subscription_table.hide() - self.add_button.set_sensitive(False) - else: - self.register_hbox.hide() - if type_ != 'jabber': - jid = self.protocol_jid_combobox.get_active_text() - contact = gajim.contacts.get_first_contact_from_jid(self.account, - jid) - if contact.show in ('offline', 'error'): - self.subscription_table.hide() - self.connected_label.show() - self.add_button.set_sensitive(False) - self.auto_authorize_checkbutton.hide() - return - self.subscription_table.show() - self.auto_authorize_checkbutton.show() - self.connected_label.hide() - self.add_button.set_sensitive(True) + def on_protocol_combobox_changed(self, widget): + model = widget.get_model() + iter_ = widget.get_active_iter() + type_ = model[iter_][2] + model = self.protocol_jid_combobox.get_model() + model.clear() + if len(self.agents[type_]): + for jid_ in self.agents[type_]: + model.append([jid_]) + self.protocol_jid_combobox.set_active(0) + if len(self.agents[type_]) > 1: + self.protocol_jid_combobox.show() + else: + self.protocol_jid_combobox.hide() + if type_ in self.uid_labels: + self.uid_label.set_text(self.uid_labels[type_]) + else: + self.uid_label.set_text(_('User ID:')) + if type_ == 'jabber': + self.message_scrolledwindow.show() + else: + self.message_scrolledwindow.hide() + if type_ in self.available_types: + self.register_hbox.show() + self.auto_authorize_checkbutton.hide() + self.connected_label.hide() + self.subscription_table.hide() + self.add_button.set_sensitive(False) + else: + self.register_hbox.hide() + if type_ != 'jabber': + jid = self.protocol_jid_combobox.get_active_text() + contact = gajim.contacts.get_first_contact_from_jid(self.account, + jid) + if contact.show in ('offline', 'error'): + self.subscription_table.hide() + self.connected_label.show() + self.add_button.set_sensitive(False) + self.auto_authorize_checkbutton.hide() + return + self.subscription_table.show() + self.auto_authorize_checkbutton.show() + self.connected_label.hide() + self.add_button.set_sensitive(True) - def transport_signed_in(self, jid): - if self.protocol_jid_combobox.get_active_text() == jid: - self.register_hbox.hide() - self.connected_label.hide() - self.subscription_table.show() - self.auto_authorize_checkbutton.show() - self.add_button.set_sensitive(True) + def transport_signed_in(self, jid): + if self.protocol_jid_combobox.get_active_text() == jid: + self.register_hbox.hide() + self.connected_label.hide() + self.subscription_table.show() + self.auto_authorize_checkbutton.show() + self.add_button.set_sensitive(True) - def transport_signed_out(self, jid): - if self.protocol_jid_combobox.get_active_text() == jid: - self.subscription_table.hide() - self.auto_authorize_checkbutton.hide() - self.connected_label.show() - self.add_button.set_sensitive(False) + def transport_signed_out(self, jid): + if self.protocol_jid_combobox.get_active_text() == jid: + self.subscription_table.hide() + self.auto_authorize_checkbutton.hide() + self.connected_label.show() + self.add_button.set_sensitive(False) class AboutDialog: - """ - Class for about dialog - """ - - def __init__(self): - dlg = gtk.AboutDialog() - dlg.set_transient_for(gajim.interface.roster.window) - dlg.set_name('Gajim') - dlg.set_version(gajim.version) - s = u'Copyright © 2003-2009 Gajim Team' - dlg.set_copyright(s) - copying_file_path = self.get_path('COPYING') - if copying_file_path: - text = open(copying_file_path).read() - dlg.set_license(text) + """ + Class for about dialog + """ - dlg.set_comments('%s\n%s %s\n%s %s' - % (_('A GTK+ jabber client'), \ - _('GTK+ Version:'), self.tuple2str(gtk.gtk_version), \ - _('PyGTK Version:'), self.tuple2str(gtk.pygtk_version))) - dlg.set_website('http://www.gajim.org/') + def __init__(self): + dlg = gtk.AboutDialog() + dlg.set_transient_for(gajim.interface.roster.window) + dlg.set_name('Gajim') + dlg.set_version(gajim.version) + s = u'Copyright © 2003-2009 Gajim Team' + dlg.set_copyright(s) + copying_file_path = self.get_path('COPYING') + if copying_file_path: + text = open(copying_file_path).read() + dlg.set_license(text) - authors_file_path = self.get_path('AUTHORS') - if authors_file_path: - authors = [] - authors_file = open(authors_file_path).read() - authors_file = authors_file.split('\n') - for author in authors_file: - if author == 'CURRENT DEVELOPERS:': - authors.append(_('Current Developers:')) - elif author == 'PAST DEVELOPERS:': - authors.append('\n' + _('Past Developers:')) - elif author != '': # Real author line - authors.append(author) + dlg.set_comments('%s\n%s %s\n%s %s' + % (_('A GTK+ jabber client'), \ + _('GTK+ Version:'), self.tuple2str(gtk.gtk_version), \ + _('PyGTK Version:'), self.tuple2str(gtk.pygtk_version))) + dlg.set_website('http://www.gajim.org/') - thanks_file_path = self.get_path('THANKS') - if thanks_file_path: - authors.append('\n' + _('THANKS:')) + authors_file_path = self.get_path('AUTHORS') + if authors_file_path: + authors = [] + authors_file = open(authors_file_path).read() + authors_file = authors_file.split('\n') + for author in authors_file: + if author == 'CURRENT DEVELOPERS:': + authors.append(_('Current Developers:')) + elif author == 'PAST DEVELOPERS:': + authors.append('\n' + _('Past Developers:')) + elif author != '': # Real author line + authors.append(author) - text = open(thanks_file_path).read() - text_splitted = text.split('\n') - text = '\n'.join(text_splitted[:-2]) # remove one english sentence - # and add it manually as translatable - text += '\n%s\n' % _('Last but not least, we would like to thank all ' - 'the package maintainers.') - authors.append(text) + thanks_file_path = self.get_path('THANKS') + if thanks_file_path: + authors.append('\n' + _('THANKS:')) - dlg.set_authors(authors) + text = open(thanks_file_path).read() + text_splitted = text.split('\n') + text = '\n'.join(text_splitted[:-2]) # remove one english sentence + # and add it manually as translatable + text += '\n%s\n' % _('Last but not least, we would like to thank all ' + 'the package maintainers.') + authors.append(text) - dlg.props.wrap_license = True + dlg.set_authors(authors) - pixbuf = gtk.gdk.pixbuf_new_from_file(os.path.join( - gajim.DATA_DIR, 'pixmaps', 'gajim_about.png')) + dlg.props.wrap_license = True - dlg.set_logo(pixbuf) - #here you write your name in the form Name FamilyName - dlg.set_translator_credits(_('translator-credits')) + pixbuf = gtk.gdk.pixbuf_new_from_file(os.path.join( + gajim.DATA_DIR, 'pixmaps', 'gajim_about.png')) - thanks_artists_file_path = self.get_path('THANKS.artists') - if thanks_artists_file_path: - artists_text = open(thanks_artists_file_path).read() - artists = artists_text.split('\n') - dlg.set_artists(artists) - # connect close button to destroy() function - for button in dlg.action_area.get_children(): - if button.get_property('label') == gtk.STOCK_CLOSE: - button.connect('clicked', lambda x:dlg.destroy()) - dlg.show_all() + dlg.set_logo(pixbuf) + #here you write your name in the form Name FamilyName + dlg.set_translator_credits(_('translator-credits')) - def tuple2str(self, tuple_): - str_ = '' - for num in tuple_: - str_ += str(num) + '.' - return str_[0:-1] # remove latest . + thanks_artists_file_path = self.get_path('THANKS.artists') + if thanks_artists_file_path: + artists_text = open(thanks_artists_file_path).read() + artists = artists_text.split('\n') + dlg.set_artists(artists) + # connect close button to destroy() function + for button in dlg.action_area.get_children(): + if button.get_property('label') == gtk.STOCK_CLOSE: + button.connect('clicked', lambda x:dlg.destroy()) + dlg.show_all() - def get_path(self, filename): - """ - Where can we find this Credits file? - """ - if os.path.isfile(os.path.join(gajim.defs.docdir, filename)): - return os.path.join(gajim.defs.docdir, filename) - elif os.path.isfile('../' + filename): - return ('../' + filename) - else: - return None + def tuple2str(self, tuple_): + str_ = '' + for num in tuple_: + str_ += str(num) + '.' + return str_[0:-1] # remove latest . + + def get_path(self, filename): + """ + Where can we find this Credits file? + """ + if os.path.isfile(os.path.join(gajim.defs.docdir, filename)): + return os.path.join(gajim.defs.docdir, filename) + elif os.path.isfile('../' + filename): + return ('../' + filename) + else: + return None class Dialog(gtk.Dialog): - def __init__(self, parent, title, buttons, default=None, - on_response_ok=None, on_response_cancel=None): - gtk.Dialog.__init__(self, title, parent, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR) + def __init__(self, parent, title, buttons, default=None, + on_response_ok=None, on_response_cancel=None): + gtk.Dialog.__init__(self, title, parent, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR) - self.user_response_ok = on_response_ok - self.user_response_cancel = on_response_cancel - self.set_border_width(6) - self.vbox.set_spacing(12) - self.set_resizable(False) + self.user_response_ok = on_response_ok + self.user_response_cancel = on_response_cancel + self.set_border_width(6) + self.vbox.set_spacing(12) + self.set_resizable(False) - possible_responses = {gtk.STOCK_OK: self.on_response_ok, - gtk.STOCK_CANCEL: self.on_response_cancel} - for stock, response in buttons: - b = self.add_button(stock, response) - for response in possible_responses: - if stock == response: - b.connect('clicked', possible_responses[response]) - break + possible_responses = {gtk.STOCK_OK: self.on_response_ok, + gtk.STOCK_CANCEL: self.on_response_cancel} + for stock, response in buttons: + b = self.add_button(stock, response) + for response in possible_responses: + if stock == response: + b.connect('clicked', possible_responses[response]) + break - if default is not None: - self.set_default_response(default) - else: - self.set_default_response(buttons[-1][1]) + if default is not None: + self.set_default_response(default) + else: + self.set_default_response(buttons[-1][1]) - def on_response_ok(self, widget): - if self.user_response_ok: - if isinstance(self.user_response_ok, tuple): - self.user_response_ok[0](*self.user_response_ok[1:]) - else: - self.user_response_ok() - self.destroy() + def on_response_ok(self, widget): + if self.user_response_ok: + if isinstance(self.user_response_ok, tuple): + self.user_response_ok[0](*self.user_response_ok[1:]) + else: + self.user_response_ok() + self.destroy() - def on_response_cancel(self, widget): - if self.user_response_cancel: - if isinstance(self.user_response_cancel, tuple): - self.user_response_cancel[0](*self.user_response_ok[1:]) - else: - self.user_response_cancel() - self.destroy() + def on_response_cancel(self, widget): + if self.user_response_cancel: + if isinstance(self.user_response_cancel, tuple): + self.user_response_cancel[0](*self.user_response_ok[1:]) + else: + self.user_response_cancel() + self.destroy() - def just_destroy(self, widget): - self.destroy() + def just_destroy(self, widget): + self.destroy() - def get_button(self, index): - buttons = self.action_area.get_children() - return index < len(buttons) and buttons[index] or None + def get_button(self, index): + buttons = self.action_area.get_children() + return index < len(buttons) and buttons[index] or None class HigDialog(gtk.MessageDialog): - def __init__(self, parent, type_, buttons, pritext, sectext, - on_response_ok = None, on_response_cancel = None, on_response_yes = None, - on_response_no = None): - gtk.MessageDialog.__init__(self, parent, - gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, - type_, buttons, message_format = pritext) + def __init__(self, parent, type_, buttons, pritext, sectext, + on_response_ok = None, on_response_cancel = None, on_response_yes = None, + on_response_no = None): + gtk.MessageDialog.__init__(self, parent, + gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, + type_, buttons, message_format = pritext) - self.format_secondary_markup(sectext) + self.format_secondary_markup(sectext) - buttons = self.action_area.get_children() - possible_responses = {gtk.STOCK_OK: on_response_ok, - gtk.STOCK_CANCEL: on_response_cancel, gtk.STOCK_YES: on_response_yes, - gtk.STOCK_NO: on_response_no} - for b in buttons: - for response in possible_responses: - if b.get_label() == response: - if not possible_responses[response]: - b.connect('clicked', self.just_destroy) - elif isinstance(possible_responses[response], tuple): - if len(possible_responses[response]) == 1: - b.connect('clicked', possible_responses[response][0]) - else: - b.connect('clicked', *possible_responses[response]) - else: - b.connect('clicked', possible_responses[response]) - break + buttons = self.action_area.get_children() + possible_responses = {gtk.STOCK_OK: on_response_ok, + gtk.STOCK_CANCEL: on_response_cancel, gtk.STOCK_YES: on_response_yes, + gtk.STOCK_NO: on_response_no} + for b in buttons: + for response in possible_responses: + if b.get_label() == response: + if not possible_responses[response]: + b.connect('clicked', self.just_destroy) + elif isinstance(possible_responses[response], tuple): + if len(possible_responses[response]) == 1: + b.connect('clicked', possible_responses[response][0]) + else: + b.connect('clicked', *possible_responses[response]) + else: + b.connect('clicked', possible_responses[response]) + break - def just_destroy(self, widget): - self.destroy() + def just_destroy(self, widget): + self.destroy() - def popup(self): - """ - Show dialog - """ - vb = self.get_children()[0].get_children()[0] # Give focus to top vbox - vb.set_flags(gtk.CAN_FOCUS) - vb.grab_focus() - self.show_all() + def popup(self): + """ + Show dialog + """ + vb = self.get_children()[0].get_children()[0] # Give focus to top vbox + vb.set_flags(gtk.CAN_FOCUS) + vb.grab_focus() + self.show_all() class FileChooserDialog(gtk.FileChooserDialog): - """ - Non-blocking FileChooser Dialog around gtk.FileChooserDialog - """ - def __init__(self, title_text, action, buttons, default_response, - select_multiple = False, current_folder = None, on_response_ok = None, - on_response_cancel = None): + """ + Non-blocking FileChooser Dialog around gtk.FileChooserDialog + """ + def __init__(self, title_text, action, buttons, default_response, + select_multiple = False, current_folder = None, on_response_ok = None, + on_response_cancel = None): - gtk.FileChooserDialog.__init__(self, title=title_text, action=action, - buttons=buttons) + gtk.FileChooserDialog.__init__(self, title=title_text, action=action, + buttons=buttons) - self.set_default_response(default_response) - self.set_select_multiple(select_multiple) - if current_folder and os.path.isdir(current_folder): - self.set_current_folder(current_folder) - else: - self.set_current_folder(helpers.get_documents_path()) - self.response_ok, self.response_cancel = \ - on_response_ok, on_response_cancel - # in gtk+-2.10 clicked signal on some of the buttons in a dialog - # is emitted twice, so we cannot rely on 'clicked' signal - self.connect('response', self.on_dialog_response) - self.show_all() + self.set_default_response(default_response) + self.set_select_multiple(select_multiple) + if current_folder and os.path.isdir(current_folder): + self.set_current_folder(current_folder) + else: + self.set_current_folder(helpers.get_documents_path()) + self.response_ok, self.response_cancel = \ + on_response_ok, on_response_cancel + # in gtk+-2.10 clicked signal on some of the buttons in a dialog + # is emitted twice, so we cannot rely on 'clicked' signal + self.connect('response', self.on_dialog_response) + self.show_all() - def on_dialog_response(self, dialog, response): - if response in (gtk.RESPONSE_CANCEL, gtk.RESPONSE_CLOSE): - if self.response_cancel: - if isinstance(self.response_cancel, tuple): - self.response_cancel[0](dialog, *self.response_cancel[1:]) - else: - self.response_cancel(dialog) - else: - self.just_destroy(dialog) - elif response == gtk.RESPONSE_OK: - if self.response_ok: - if isinstance(self.response_ok, tuple): - self.response_ok[0](dialog, *self.response_ok[1:]) - else: - self.response_ok(dialog) - else: - self.just_destroy(dialog) + def on_dialog_response(self, dialog, response): + if response in (gtk.RESPONSE_CANCEL, gtk.RESPONSE_CLOSE): + if self.response_cancel: + if isinstance(self.response_cancel, tuple): + self.response_cancel[0](dialog, *self.response_cancel[1:]) + else: + self.response_cancel(dialog) + else: + self.just_destroy(dialog) + elif response == gtk.RESPONSE_OK: + if self.response_ok: + if isinstance(self.response_ok, tuple): + self.response_ok[0](dialog, *self.response_ok[1:]) + else: + self.response_ok(dialog) + else: + self.just_destroy(dialog) - def just_destroy(self, widget): - self.destroy() + def just_destroy(self, widget): + self.destroy() class AspellDictError: - def __init__(self, lang): - ErrorDialog( - _('Dictionary for lang %s not available') % lang, - _('You have to install %s dictionary to use spellchecking, or ' - 'choose another language by setting the speller_language option.' - '\n\nHighlighting misspelled words feature will not be used') % lang) - gajim.config.set('use_speller', False) + def __init__(self, lang): + ErrorDialog( + _('Dictionary for lang %s not available') % lang, + _('You have to install %s dictionary to use spellchecking, or ' + 'choose another language by setting the speller_language option.' + '\n\nHighlighting misspelled words feature will not be used') % lang) + gajim.config.set('use_speller', False) class ConfirmationDialog(HigDialog): - """ - HIG compliant confirmation dialog - """ + """ + HIG compliant confirmation dialog + """ - def __init__(self, pritext, sectext='', on_response_ok=None, - on_response_cancel=None): - self.user_response_ok = on_response_ok - self.user_response_cancel = on_response_cancel - HigDialog.__init__(self, None, - gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, pritext, sectext, - self.on_response_ok, self.on_response_cancel) - self.popup() + def __init__(self, pritext, sectext='', on_response_ok=None, + on_response_cancel=None): + self.user_response_ok = on_response_ok + self.user_response_cancel = on_response_cancel + HigDialog.__init__(self, None, + gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, pritext, sectext, + self.on_response_ok, self.on_response_cancel) + self.popup() - def on_response_ok(self, widget): - if self.user_response_ok: - if isinstance(self.user_response_ok, tuple): - self.user_response_ok[0](*self.user_response_ok[1:]) - else: - self.user_response_ok() - self.destroy() + def on_response_ok(self, widget): + if self.user_response_ok: + if isinstance(self.user_response_ok, tuple): + self.user_response_ok[0](*self.user_response_ok[1:]) + else: + self.user_response_ok() + self.destroy() - def on_response_cancel(self, widget): - if self.user_response_cancel: - if isinstance(self.user_response_cancel, tuple): - self.user_response_cancel[0](*self.user_response_ok[1:]) - else: - self.user_response_cancel() - self.destroy() + def on_response_cancel(self, widget): + if self.user_response_cancel: + if isinstance(self.user_response_cancel, tuple): + self.user_response_cancel[0](*self.user_response_ok[1:]) + else: + self.user_response_cancel() + self.destroy() class NonModalConfirmationDialog(HigDialog): - """ - HIG compliant non modal confirmation dialog - """ + """ + HIG compliant non modal confirmation dialog + """ - def __init__(self, pritext, sectext='', on_response_ok=None, - on_response_cancel=None): - self.user_response_ok = on_response_ok - self.user_response_cancel = on_response_cancel - HigDialog.__init__(self, None, - gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, pritext, sectext, - self.on_response_ok, self.on_response_cancel) - self.set_modal(False) + def __init__(self, pritext, sectext='', on_response_ok=None, + on_response_cancel=None): + self.user_response_ok = on_response_ok + self.user_response_cancel = on_response_cancel + HigDialog.__init__(self, None, + gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, pritext, sectext, + self.on_response_ok, self.on_response_cancel) + self.set_modal(False) - def on_response_ok(self, widget): - if self.user_response_ok: - if isinstance(self.user_response_ok, tuple): - self.user_response_ok[0](*self.user_response_ok[1:]) - else: - self.user_response_ok() - self.destroy() + def on_response_ok(self, widget): + if self.user_response_ok: + if isinstance(self.user_response_ok, tuple): + self.user_response_ok[0](*self.user_response_ok[1:]) + else: + self.user_response_ok() + self.destroy() - def on_response_cancel(self, widget): - if self.user_response_cancel: - if isinstance(self.user_response_cancel, tuple): - self.user_response_cancel[0](*self.user_response_cancel[1:]) - else: - self.user_response_cancel() - self.destroy() + def on_response_cancel(self, widget): + if self.user_response_cancel: + if isinstance(self.user_response_cancel, tuple): + self.user_response_cancel[0](*self.user_response_cancel[1:]) + else: + self.user_response_cancel() + self.destroy() class WarningDialog(HigDialog): - """ - HIG compliant warning dialog - """ + """ + HIG compliant warning dialog + """ - def __init__(self, pritext, sectext=''): - HigDialog.__init__( self, None, - gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, pritext, sectext) - self.set_modal(False) - if hasattr(gajim.interface, 'roster'): - self.set_transient_for(gajim.interface.roster.window) - self.popup() + def __init__(self, pritext, sectext=''): + HigDialog.__init__( self, None, + gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, pritext, sectext) + self.set_modal(False) + if hasattr(gajim.interface, 'roster'): + self.set_transient_for(gajim.interface.roster.window) + self.popup() class InformationDialog(HigDialog): - """ - HIG compliant info dialog - """ + """ + HIG compliant info dialog + """ - def __init__(self, pritext, sectext=''): - HigDialog.__init__(self, None, - gtk.MESSAGE_INFO, gtk.BUTTONS_OK, pritext, sectext) - self.set_modal(False) - self.set_transient_for(gajim.interface.roster.window) - self.popup() + def __init__(self, pritext, sectext=''): + HigDialog.__init__(self, None, + gtk.MESSAGE_INFO, gtk.BUTTONS_OK, pritext, sectext) + self.set_modal(False) + self.set_transient_for(gajim.interface.roster.window) + self.popup() class ErrorDialog(HigDialog): - """ - HIG compliant error dialog - """ + """ + HIG compliant error dialog + """ - def __init__(self, pritext, sectext=''): - HigDialog.__init__( self, None, - gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, pritext, sectext) - self.popup() + def __init__(self, pritext, sectext=''): + HigDialog.__init__( self, None, + gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, pritext, sectext) + self.popup() class YesNoDialog(HigDialog): - """ - HIG compliant YesNo dialog - """ + """ + HIG compliant YesNo dialog + """ - def __init__(self, pritext, sectext='', checktext='', on_response_yes=None, - on_response_no=None): - self.user_response_yes = on_response_yes - self.user_response_no = on_response_no - HigDialog.__init__( self, None, - gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, pritext, sectext, - on_response_yes=self.on_response_yes, - on_response_no=self.on_response_no) + def __init__(self, pritext, sectext='', checktext='', on_response_yes=None, + on_response_no=None): + self.user_response_yes = on_response_yes + self.user_response_no = on_response_no + HigDialog.__init__( self, None, + gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, pritext, sectext, + on_response_yes=self.on_response_yes, + on_response_no=self.on_response_no) - if checktext: - self.checkbutton = gtk.CheckButton(checktext) - self.vbox.pack_start(self.checkbutton, expand=False, fill=True) - else: - self.checkbutton = None - self.set_modal(False) - self.popup() + if checktext: + self.checkbutton = gtk.CheckButton(checktext) + self.vbox.pack_start(self.checkbutton, expand=False, fill=True) + else: + self.checkbutton = None + self.set_modal(False) + self.popup() - def on_response_yes(self, widget): - if self.user_response_yes: - if isinstance(self.user_response_yes, tuple): - self.user_response_yes[0](self.is_checked(), - *self.user_response_yes[1:]) - else: - self.user_response_yes(self.is_checked()) - self.destroy() + def on_response_yes(self, widget): + if self.user_response_yes: + if isinstance(self.user_response_yes, tuple): + self.user_response_yes[0](self.is_checked(), + *self.user_response_yes[1:]) + else: + self.user_response_yes(self.is_checked()) + self.destroy() - def on_response_no(self, widget): - if self.user_response_no: - if isinstance(self.user_response_no, tuple): - self.user_response_no[0](*self.user_response_no[1:]) - else: - self.user_response_no() - self.destroy() + def on_response_no(self, widget): + if self.user_response_no: + if isinstance(self.user_response_no, tuple): + self.user_response_no[0](*self.user_response_no[1:]) + else: + self.user_response_no() + self.destroy() - def is_checked(self): - """ - Get active state of the checkbutton - """ - if not self.checkbutton: - return False - return self.checkbutton.get_active() + def is_checked(self): + """ + Get active state of the checkbutton + """ + if not self.checkbutton: + return False + return self.checkbutton.get_active() class ConfirmationDialogCheck(ConfirmationDialog): - """ - HIG compliant confirmation dialog with checkbutton - """ + """ + HIG compliant confirmation dialog with checkbutton + """ - def __init__(self, pritext, sectext='', checktext='', on_response_ok=None, - on_response_cancel=None, is_modal=True): - self.user_response_ok = on_response_ok - self.user_response_cancel = on_response_cancel + def __init__(self, pritext, sectext='', checktext='', on_response_ok=None, + on_response_cancel=None, is_modal=True): + self.user_response_ok = on_response_ok + self.user_response_cancel = on_response_cancel - HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION, - gtk.BUTTONS_OK_CANCEL, pritext, sectext, self.on_response_ok, - self.on_response_cancel) + HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION, + gtk.BUTTONS_OK_CANCEL, pritext, sectext, self.on_response_ok, + self.on_response_cancel) - self.set_default_response(gtk.RESPONSE_OK) + self.set_default_response(gtk.RESPONSE_OK) - ok_button = self.action_area.get_children()[0] # right to left - ok_button.grab_focus() + ok_button = self.action_area.get_children()[0] # right to left + ok_button.grab_focus() - self.checkbutton = gtk.CheckButton(checktext) - self.vbox.pack_start(self.checkbutton, expand=False, fill=True) - self.set_modal(is_modal) - self.popup() + self.checkbutton = gtk.CheckButton(checktext) + self.vbox.pack_start(self.checkbutton, expand=False, fill=True) + self.set_modal(is_modal) + self.popup() - # XXX should cancel if somebody closes the dialog + # XXX should cancel if somebody closes the dialog - def on_response_ok(self, widget): - if self.user_response_ok: - if isinstance(self.user_response_ok, tuple): - self.user_response_ok[0](self.is_checked(), - *self.user_response_ok[1:]) - else: - self.user_response_ok(self.is_checked()) - self.destroy() + def on_response_ok(self, widget): + if self.user_response_ok: + if isinstance(self.user_response_ok, tuple): + self.user_response_ok[0](self.is_checked(), + *self.user_response_ok[1:]) + else: + self.user_response_ok(self.is_checked()) + self.destroy() - def on_response_cancel(self, widget): - if self.user_response_cancel: - if isinstance(self.user_response_cancel, tuple): - self.user_response_cancel[0](self.is_checked(), - *self.user_response_cancel[1:]) - else: - self.user_response_cancel(self.is_checked()) - self.destroy() + def on_response_cancel(self, widget): + if self.user_response_cancel: + if isinstance(self.user_response_cancel, tuple): + self.user_response_cancel[0](self.is_checked(), + *self.user_response_cancel[1:]) + else: + self.user_response_cancel(self.is_checked()) + self.destroy() - def is_checked(self): - """ - Get active state of the checkbutton - """ - return self.checkbutton.get_active() + def is_checked(self): + """ + Get active state of the checkbutton + """ + return self.checkbutton.get_active() class ConfirmationDialogDubbleCheck(ConfirmationDialog): - """ - HIG compliant confirmation dialog with 2 checkbuttons - """ + """ + HIG compliant confirmation dialog with 2 checkbuttons + """ - def __init__(self, pritext, sectext='', checktext1='', checktext2='', - on_response_ok=None, on_response_cancel=None, is_modal=True): - self.user_response_ok = on_response_ok - self.user_response_cancel = on_response_cancel + def __init__(self, pritext, sectext='', checktext1='', checktext2='', + on_response_ok=None, on_response_cancel=None, is_modal=True): + self.user_response_ok = on_response_ok + self.user_response_cancel = on_response_cancel - HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION, - gtk.BUTTONS_OK_CANCEL, pritext, sectext, self.on_response_ok, - self.on_response_cancel) + HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION, + gtk.BUTTONS_OK_CANCEL, pritext, sectext, self.on_response_ok, + self.on_response_cancel) - self.set_default_response(gtk.RESPONSE_OK) + self.set_default_response(gtk.RESPONSE_OK) - ok_button = self.action_area.get_children()[0] # right to left - ok_button.grab_focus() + ok_button = self.action_area.get_children()[0] # right to left + ok_button.grab_focus() - if checktext1: - self.checkbutton1 = gtk.CheckButton(checktext1) - self.vbox.pack_start(self.checkbutton1, expand=False, fill=True) - else: - self.checkbutton1 = None - if checktext2: - self.checkbutton2 = gtk.CheckButton(checktext2) - self.vbox.pack_start(self.checkbutton2, expand=False, fill=True) - else: - self.checkbutton2 = None + if checktext1: + self.checkbutton1 = gtk.CheckButton(checktext1) + self.vbox.pack_start(self.checkbutton1, expand=False, fill=True) + else: + self.checkbutton1 = None + if checktext2: + self.checkbutton2 = gtk.CheckButton(checktext2) + self.vbox.pack_start(self.checkbutton2, expand=False, fill=True) + else: + self.checkbutton2 = None - self.set_modal(is_modal) - self.popup() + self.set_modal(is_modal) + self.popup() - # XXX should cancel if somebody closes the dialog + # XXX should cancel if somebody closes the dialog - def on_response_ok(self, widget): - if self.user_response_ok: - if isinstance(self.user_response_ok, tuple): - self.user_response_ok[0](self.is_checked(), - *self.user_response_ok[1:]) - else: - self.user_response_ok(self.is_checked()) - self.destroy() + def on_response_ok(self, widget): + if self.user_response_ok: + if isinstance(self.user_response_ok, tuple): + self.user_response_ok[0](self.is_checked(), + *self.user_response_ok[1:]) + else: + self.user_response_ok(self.is_checked()) + self.destroy() - def on_response_cancel(self, widget): - if self.user_response_cancel: - if isinstance(self.user_response_cancel, tuple): - self.user_response_cancel[0](*self.user_response_cancel[1:]) - else: - self.user_response_cancel() - self.destroy() + def on_response_cancel(self, widget): + if self.user_response_cancel: + if isinstance(self.user_response_cancel, tuple): + self.user_response_cancel[0](*self.user_response_cancel[1:]) + else: + self.user_response_cancel() + self.destroy() - def is_checked(self): - ''' Get active state of the checkbutton ''' - if self.checkbutton1: - is_checked_1 = self.checkbutton1.get_active() - else: - is_checked_1 = False - if self.checkbutton2: - is_checked_2 = self.checkbutton2.get_active() - else: - is_checked_2 = False - return [is_checked_1, is_checked_2] + def is_checked(self): + ''' Get active state of the checkbutton ''' + if self.checkbutton1: + is_checked_1 = self.checkbutton1.get_active() + else: + is_checked_1 = False + if self.checkbutton2: + is_checked_2 = self.checkbutton2.get_active() + else: + is_checked_2 = False + return [is_checked_1, is_checked_2] class FTOverwriteConfirmationDialog(ConfirmationDialog): - """ - HIG compliant confirmation dialog to overwrite or resume a file transfert - """ + """ + HIG compliant confirmation dialog to overwrite or resume a file transfert + """ - def __init__(self, pritext, sectext='', propose_resume=True, - on_response=None): - HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION, gtk.BUTTONS_CANCEL, - pritext, sectext) + def __init__(self, pritext, sectext='', propose_resume=True, + on_response=None): + HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION, gtk.BUTTONS_CANCEL, + pritext, sectext) - self.on_response = on_response + self.on_response = on_response - if propose_resume: - b = gtk.Button('', gtk.STOCK_REFRESH) - align = b.get_children()[0] - hbox = align.get_children()[0] - label = hbox.get_children()[1] - label.set_text('_Resume') - label.set_use_underline(True) - self.add_action_widget(b, 100) + if propose_resume: + b = gtk.Button('', gtk.STOCK_REFRESH) + align = b.get_children()[0] + hbox = align.get_children()[0] + label = hbox.get_children()[1] + label.set_text('_Resume') + label.set_use_underline(True) + self.add_action_widget(b, 100) - b = gtk.Button('', gtk.STOCK_SAVE_AS) - align = b.get_children()[0] - hbox = align.get_children()[0] - label = hbox.get_children()[1] - label.set_text('Re_place') - label.set_use_underline(True) - self.add_action_widget(b, 200) + b = gtk.Button('', gtk.STOCK_SAVE_AS) + align = b.get_children()[0] + hbox = align.get_children()[0] + label = hbox.get_children()[1] + label.set_text('Re_place') + label.set_use_underline(True) + self.add_action_widget(b, 200) - self.connect('response', self.on_dialog_response) - self.show_all() + self.connect('response', self.on_dialog_response) + self.show_all() - def on_dialog_response(self, dialog, response): - if self.on_response: - if isinstance(self.on_response, tuple): - self.on_response[0](response, *self.on_response[1:]) - else: - self.on_response(response) - self.destroy() + def on_dialog_response(self, dialog, response): + if self.on_response: + if isinstance(self.on_response, tuple): + self.on_response[0](response, *self.on_response[1:]) + else: + self.on_response(response) + self.destroy() class CommonInputDialog: - """ - Common Class for Input dialogs - """ - - def __init__(self, title, label_str, is_modal, ok_handler, cancel_handler): - self.dialog = self.xml.get_widget('input_dialog') - label = self.xml.get_widget('label') - self.dialog.set_title(title) - label.set_markup(label_str) - self.cancel_handler = cancel_handler - self.vbox = self.xml.get_widget('vbox') + """ + Common Class for Input dialogs + """ - self.ok_handler = ok_handler - okbutton = self.xml.get_widget('okbutton') - okbutton.connect('clicked', self.on_okbutton_clicked) - cancelbutton = self.xml.get_widget('cancelbutton') - cancelbutton.connect('clicked', self.on_cancelbutton_clicked) - self.xml.signal_autoconnect(self) - self.dialog.show_all() + def __init__(self, title, label_str, is_modal, ok_handler, cancel_handler): + self.dialog = self.xml.get_widget('input_dialog') + label = self.xml.get_widget('label') + self.dialog.set_title(title) + label.set_markup(label_str) + self.cancel_handler = cancel_handler + self.vbox = self.xml.get_widget('vbox') - def on_input_dialog_delete_event(self, widget, event): - if self.cancel_handler: - self.cancel_handler() + self.ok_handler = ok_handler + okbutton = self.xml.get_widget('okbutton') + okbutton.connect('clicked', self.on_okbutton_clicked) + cancelbutton = self.xml.get_widget('cancelbutton') + cancelbutton.connect('clicked', self.on_cancelbutton_clicked) + self.xml.signal_autoconnect(self) + self.dialog.show_all() - def on_okbutton_clicked(self, widget): - user_input = self.get_text() - if user_input: - user_input = user_input.decode('utf-8') - self.cancel_handler = None - self.dialog.destroy() - if isinstance(self.ok_handler, tuple): - self.ok_handler[0](user_input, *self.ok_handler[1:]) - else: - self.ok_handler(user_input) + def on_input_dialog_delete_event(self, widget, event): + if self.cancel_handler: + self.cancel_handler() - def on_cancelbutton_clicked(self, widget): - self.dialog.destroy() + def on_okbutton_clicked(self, widget): + user_input = self.get_text() + if user_input: + user_input = user_input.decode('utf-8') + self.cancel_handler = None + self.dialog.destroy() + if isinstance(self.ok_handler, tuple): + self.ok_handler[0](user_input, *self.ok_handler[1:]) + else: + self.ok_handler(user_input) + + def on_cancelbutton_clicked(self, widget): + self.dialog.destroy() class InputDialog(CommonInputDialog): - """ - Class for Input dialog - """ + """ + Class for Input dialog + """ - def __init__(self, title, label_str, input_str=None, is_modal=True, - ok_handler=None, cancel_handler=None): - self.xml = gtkgui_helpers.get_glade('input_dialog.glade') - CommonInputDialog.__init__(self, title, label_str, is_modal, ok_handler, - cancel_handler) - self.input_entry = self.xml.get_widget('input_entry') - if input_str: - self.set_entry(input_str) + def __init__(self, title, label_str, input_str=None, is_modal=True, + ok_handler=None, cancel_handler=None): + self.xml = gtkgui_helpers.get_glade('input_dialog.glade') + CommonInputDialog.__init__(self, title, label_str, is_modal, ok_handler, + cancel_handler) + self.input_entry = self.xml.get_widget('input_entry') + if input_str: + self.set_entry(input_str) - def set_entry(self, value): - self.input_entry.set_text(value) - self.input_entry.select_region(0, -1) # select all + def set_entry(self, value): + self.input_entry.set_text(value) + self.input_entry.select_region(0, -1) # select all - def get_text(self): - return self.input_entry.get_text().decode('utf-8') + def get_text(self): + return self.input_entry.get_text().decode('utf-8') class InputDialogCheck(InputDialog): - """ - Class for Input dialog - """ + """ + Class for Input dialog + """ - def __init__(self, title, label_str, checktext='', input_str=None, - is_modal=True, ok_handler=None, cancel_handler=None): - self.xml = gtkgui_helpers.get_glade('input_dialog.glade') - InputDialog.__init__(self, title, label_str, input_str=input_str, - is_modal=is_modal, ok_handler=ok_handler, - cancel_handler=cancel_handler) - self.input_entry = self.xml.get_widget('input_entry') - if input_str: - self.input_entry.set_text(input_str) - self.input_entry.select_region(0, -1) # select all + def __init__(self, title, label_str, checktext='', input_str=None, + is_modal=True, ok_handler=None, cancel_handler=None): + self.xml = gtkgui_helpers.get_glade('input_dialog.glade') + InputDialog.__init__(self, title, label_str, input_str=input_str, + is_modal=is_modal, ok_handler=ok_handler, + cancel_handler=cancel_handler) + self.input_entry = self.xml.get_widget('input_entry') + if input_str: + self.input_entry.set_text(input_str) + self.input_entry.select_region(0, -1) # select all - if checktext: - self.checkbutton = gtk.CheckButton(checktext) - self.vbox.pack_start(self.checkbutton, expand=False, fill=True) - self.checkbutton.show() + if checktext: + self.checkbutton = gtk.CheckButton(checktext) + self.vbox.pack_start(self.checkbutton, expand=False, fill=True) + self.checkbutton.show() - def on_okbutton_clicked(self, widget): - user_input = self.get_text() - if user_input: - user_input = user_input.decode('utf-8') - self.cancel_handler = None - self.dialog.destroy() - if isinstance(self.ok_handler, tuple): - self.ok_handler[0](user_input, self.is_checked(), *self.ok_handler[1:]) - else: - self.ok_handler(user_input, self.is_checked()) + def on_okbutton_clicked(self, widget): + user_input = self.get_text() + if user_input: + user_input = user_input.decode('utf-8') + self.cancel_handler = None + self.dialog.destroy() + if isinstance(self.ok_handler, tuple): + self.ok_handler[0](user_input, self.is_checked(), *self.ok_handler[1:]) + else: + self.ok_handler(user_input, self.is_checked()) - def get_text(self): - return self.input_entry.get_text().decode('utf-8') + def get_text(self): + return self.input_entry.get_text().decode('utf-8') - def is_checked(self): - """ - Get active state of the checkbutton - """ - try: - return self.checkbutton.get_active() - except Exception: - # There is no checkbutton - return False + def is_checked(self): + """ + Get active state of the checkbutton + """ + try: + return self.checkbutton.get_active() + except Exception: + # There is no checkbutton + return False class ChangeNickDialog(InputDialogCheck): - """ - Class for changing room nickname in case of conflict - """ + """ + Class for changing room nickname in case of conflict + """ - def __init__(self, account, room_jid, title, prompt, check_text=None): - InputDialogCheck.__init__(self, title, '', checktext=check_text, - input_str='', is_modal=True, ok_handler=None, cancel_handler=None) - self.room_queue = [(account, room_jid, prompt)] - self.check_next() + def __init__(self, account, room_jid, title, prompt, check_text=None): + InputDialogCheck.__init__(self, title, '', checktext=check_text, + input_str='', is_modal=True, ok_handler=None, cancel_handler=None) + self.room_queue = [(account, room_jid, prompt)] + self.check_next() - def on_input_dialog_delete_event(self, widget, event): - self.on_cancelbutton_clicked(widget) - return True + def on_input_dialog_delete_event(self, widget, event): + self.on_cancelbutton_clicked(widget) + return True - def setup_dialog(self): - self.gc_control = gajim.interface.msg_win_mgr.get_gc_control( - self.room_jid, self.account) - if not self.gc_control and \ - self.room_jid in gajim.interface.minimized_controls[self.account]: - self.gc_control = \ - gajim.interface.minimized_controls[self.account][self.room_jid] - if not self.gc_control: - self.check_next() - return - label = self.xml.get_widget('label') - label.set_markup(self.prompt) - self.set_entry(self.gc_control.nick + \ - gajim.config.get('gc_proposed_nick_char')) + def setup_dialog(self): + self.gc_control = gajim.interface.msg_win_mgr.get_gc_control( + self.room_jid, self.account) + if not self.gc_control and \ + self.room_jid in gajim.interface.minimized_controls[self.account]: + self.gc_control = \ + gajim.interface.minimized_controls[self.account][self.room_jid] + if not self.gc_control: + self.check_next() + return + label = self.xml.get_widget('label') + label.set_markup(self.prompt) + self.set_entry(self.gc_control.nick + \ + gajim.config.get('gc_proposed_nick_char')) - def check_next(self): - if len(self.room_queue) == 0: - self.cancel_handler = None - self.dialog.destroy() - if 'change_nick_dialog' in gajim.interface.instances: - del gajim.interface.instances['change_nick_dialog'] - return - self.account, self.room_jid, self.prompt = self.room_queue.pop(0) - self.setup_dialog() + def check_next(self): + if len(self.room_queue) == 0: + self.cancel_handler = None + self.dialog.destroy() + if 'change_nick_dialog' in gajim.interface.instances: + del gajim.interface.instances['change_nick_dialog'] + return + self.account, self.room_jid, self.prompt = self.room_queue.pop(0) + self.setup_dialog() - if gajim.new_room_nick is not None and not gajim.gc_connected[ - self.account][self.room_jid] and self.gc_control.nick != \ - gajim.new_room_nick: - self.dialog.hide() - self.on_ok(gajim.new_room_nick, True) - else: - self.dialog.show() + if gajim.new_room_nick is not None and not gajim.gc_connected[ + self.account][self.room_jid] and self.gc_control.nick != \ + gajim.new_room_nick: + self.dialog.hide() + self.on_ok(gajim.new_room_nick, True) + else: + self.dialog.show() - def on_okbutton_clicked(self, widget): - nick = self.get_text() - if nick: - nick = nick.decode('utf-8') - # send presence to room - try: - nick = helpers.parse_resource(nick) - except Exception: - # invalid char - dialogs.ErrorDialog(_('Invalid nickname'), - _('The nickname has not allowed characters.')) - return - self.on_ok(nick, self.is_checked()) + def on_okbutton_clicked(self, widget): + nick = self.get_text() + if nick: + nick = nick.decode('utf-8') + # send presence to room + try: + nick = helpers.parse_resource(nick) + except Exception: + # invalid char + dialogs.ErrorDialog(_('Invalid nickname'), + _('The nickname has not allowed characters.')) + return + self.on_ok(nick, self.is_checked()) - def on_ok(self, nick, is_checked): - if is_checked: - gajim.new_room_nick = nick - gajim.connections[self.account].join_gc(nick, self.room_jid, None, - change_nick=True) - if gajim.gc_connected[self.account][self.room_jid]: - # We are changing nick, we will change self.nick when we receive - # presence that inform that it works - self.gc_control.new_nick = nick - else: - # We are connecting, we will not get a changed nick presence so - # change it NOW. We don't already have a nick so it's harmless - self.gc_control.nick = nick - self.check_next() + def on_ok(self, nick, is_checked): + if is_checked: + gajim.new_room_nick = nick + gajim.connections[self.account].join_gc(nick, self.room_jid, None, + change_nick=True) + if gajim.gc_connected[self.account][self.room_jid]: + # We are changing nick, we will change self.nick when we receive + # presence that inform that it works + self.gc_control.new_nick = nick + else: + # We are connecting, we will not get a changed nick presence so + # change it NOW. We don't already have a nick so it's harmless + self.gc_control.nick = nick + self.check_next() - def on_cancelbutton_clicked(self, widget): - self.gc_control.new_nick = '' - self.check_next() + def on_cancelbutton_clicked(self, widget): + self.gc_control.new_nick = '' + self.check_next() - def add_room(self, account, room_jid, prompt): - if (account, room_jid, prompt) not in self.room_queue: - self.room_queue.append((account, room_jid, prompt)) + def add_room(self, account, room_jid, prompt): + if (account, room_jid, prompt) not in self.room_queue: + self.room_queue.append((account, room_jid, prompt)) class InputTextDialog(CommonInputDialog): - """ - Class for multilines Input dialog (more place than InputDialog) - """ + """ + Class for multilines Input dialog (more place than InputDialog) + """ - def __init__(self, title, label_str, input_str=None, is_modal=True, - ok_handler=None, cancel_handler=None): - self.xml = gtkgui_helpers.get_glade('input_text_dialog.glade') - CommonInputDialog.__init__(self, title, label_str, is_modal, ok_handler, - cancel_handler) - self.input_buffer = self.xml.get_widget('input_textview').get_buffer() - if input_str: - self.input_buffer.set_text(input_str) - start_iter, end_iter = self.input_buffer.get_bounds() - self.input_buffer.select_range(start_iter, end_iter) # select all + def __init__(self, title, label_str, input_str=None, is_modal=True, + ok_handler=None, cancel_handler=None): + self.xml = gtkgui_helpers.get_glade('input_text_dialog.glade') + CommonInputDialog.__init__(self, title, label_str, is_modal, ok_handler, + cancel_handler) + self.input_buffer = self.xml.get_widget('input_textview').get_buffer() + if input_str: + self.input_buffer.set_text(input_str) + start_iter, end_iter = self.input_buffer.get_bounds() + self.input_buffer.select_range(start_iter, end_iter) # select all - def get_text(self): - start_iter, end_iter = self.input_buffer.get_bounds() - return self.input_buffer.get_text(start_iter, end_iter).decode('utf-8') + def get_text(self): + start_iter, end_iter = self.input_buffer.get_bounds() + return self.input_buffer.get_text(start_iter, end_iter).decode('utf-8') class DubbleInputDialog: - """ - Class for Dubble Input dialog - """ + """ + Class for Dubble Input dialog + """ - def __init__(self, title, label_str1, label_str2, input_str1=None, - input_str2=None, is_modal=True, ok_handler=None, cancel_handler=None): - self.xml = gtkgui_helpers.get_glade('dubbleinput_dialog.glade') - self.dialog = self.xml.get_widget('dubbleinput_dialog') - label1 = self.xml.get_widget('label1') - self.input_entry1 = self.xml.get_widget('input_entry1') - label2 = self.xml.get_widget('label2') - self.input_entry2 = self.xml.get_widget('input_entry2') - self.dialog.set_title(title) - label1.set_markup(label_str1) - label2.set_markup(label_str2) - self.cancel_handler = cancel_handler - if input_str1: - self.input_entry1.set_text(input_str1) - self.input_entry1.select_region(0, -1) # select all - if input_str2: - self.input_entry2.set_text(input_str2) - self.input_entry2.select_region(0, -1) # select all + def __init__(self, title, label_str1, label_str2, input_str1=None, + input_str2=None, is_modal=True, ok_handler=None, cancel_handler=None): + self.xml = gtkgui_helpers.get_glade('dubbleinput_dialog.glade') + self.dialog = self.xml.get_widget('dubbleinput_dialog') + label1 = self.xml.get_widget('label1') + self.input_entry1 = self.xml.get_widget('input_entry1') + label2 = self.xml.get_widget('label2') + self.input_entry2 = self.xml.get_widget('input_entry2') + self.dialog.set_title(title) + label1.set_markup(label_str1) + label2.set_markup(label_str2) + self.cancel_handler = cancel_handler + if input_str1: + self.input_entry1.set_text(input_str1) + self.input_entry1.select_region(0, -1) # select all + if input_str2: + self.input_entry2.set_text(input_str2) + self.input_entry2.select_region(0, -1) # select all - self.dialog.set_modal(is_modal) + self.dialog.set_modal(is_modal) - self.ok_handler = ok_handler - okbutton = self.xml.get_widget('okbutton') - okbutton.connect('clicked', self.on_okbutton_clicked) - cancelbutton = self.xml.get_widget('cancelbutton') - cancelbutton.connect('clicked', self.on_cancelbutton_clicked) - self.xml.signal_autoconnect(self) - self.dialog.show_all() + self.ok_handler = ok_handler + okbutton = self.xml.get_widget('okbutton') + okbutton.connect('clicked', self.on_okbutton_clicked) + cancelbutton = self.xml.get_widget('cancelbutton') + cancelbutton.connect('clicked', self.on_cancelbutton_clicked) + self.xml.signal_autoconnect(self) + self.dialog.show_all() - def on_dubbleinput_dialog_destroy(self, widget): - if not self.cancel_handler: - return False - if isinstance(self.cancel_handler, tuple): - self.cancel_handler[0](*self.cancel_handler[1:]) - else: - self.cancel_handler() + def on_dubbleinput_dialog_destroy(self, widget): + if not self.cancel_handler: + return False + if isinstance(self.cancel_handler, tuple): + self.cancel_handler[0](*self.cancel_handler[1:]) + else: + self.cancel_handler() - def on_okbutton_clicked(self, widget): - user_input1 = self.input_entry1.get_text().decode('utf-8') - user_input2 = self.input_entry2.get_text().decode('utf-8') - self.dialog.destroy() - if not self.ok_handler: - return - if isinstance(self.ok_handler, tuple): - self.ok_handler[0](user_input1, user_input2, *self.ok_handler[1:]) - else: - self.ok_handler(user_input1, user_input2) + def on_okbutton_clicked(self, widget): + user_input1 = self.input_entry1.get_text().decode('utf-8') + user_input2 = self.input_entry2.get_text().decode('utf-8') + self.dialog.destroy() + if not self.ok_handler: + return + if isinstance(self.ok_handler, tuple): + self.ok_handler[0](user_input1, user_input2, *self.ok_handler[1:]) + else: + self.ok_handler(user_input1, user_input2) - def on_cancelbutton_clicked(self, widget): - self.dialog.destroy() - if not self.cancel_handler: - return - if isinstance(self.cancel_handler, tuple): - self.cancel_handler[0](*self.cancel_handler[1:]) - else: - self.cancel_handler() + def on_cancelbutton_clicked(self, widget): + self.dialog.destroy() + if not self.cancel_handler: + return + if isinstance(self.cancel_handler, tuple): + self.cancel_handler[0](*self.cancel_handler[1:]) + else: + self.cancel_handler() class SubscriptionRequestWindow: - def __init__(self, jid, text, account, user_nick=None): - xml = gtkgui_helpers.get_glade('subscription_request_window.glade') - self.window = xml.get_widget('subscription_request_window') - self.jid = jid - self.account = account - self.user_nick = user_nick - if len(gajim.connections) >= 2: - prompt_text = \ - _('Subscription request for account %(account)s from %(jid)s')\ - % {'account': account, 'jid': self.jid} - else: - prompt_text = _('Subscription request from %s') % self.jid - xml.get_widget('from_label').set_text(prompt_text) - xml.get_widget('message_textview').get_buffer().set_text(text) - xml.signal_autoconnect(self) - self.window.show_all() + def __init__(self, jid, text, account, user_nick=None): + xml = gtkgui_helpers.get_glade('subscription_request_window.glade') + self.window = xml.get_widget('subscription_request_window') + self.jid = jid + self.account = account + self.user_nick = user_nick + if len(gajim.connections) >= 2: + prompt_text = \ + _('Subscription request for account %(account)s from %(jid)s')\ + % {'account': account, 'jid': self.jid} + else: + prompt_text = _('Subscription request from %s') % self.jid + xml.get_widget('from_label').set_text(prompt_text) + xml.get_widget('message_textview').get_buffer().set_text(text) + xml.signal_autoconnect(self) + self.window.show_all() - def prepare_popup_menu(self): - xml = gtkgui_helpers.get_glade('subscription_request_popup_menu.glade') - menu = xml.get_widget('subscription_request_popup_menu') - xml.signal_autoconnect(self) - return menu + def prepare_popup_menu(self): + xml = gtkgui_helpers.get_glade('subscription_request_popup_menu.glade') + menu = xml.get_widget('subscription_request_popup_menu') + xml.signal_autoconnect(self) + return menu - def on_close_button_clicked(self, widget): - self.window.destroy() + def on_close_button_clicked(self, widget): + self.window.destroy() - def on_authorize_button_clicked(self, widget): - """ - Accept the request - """ - gajim.connections[self.account].send_authorization(self.jid) - self.window.destroy() - contact = gajim.contacts.get_contact(self.account, self.jid) - if not contact or _('Not in Roster') in contact.groups: - AddNewContactWindow(self.account, self.jid, self.user_nick) + def on_authorize_button_clicked(self, widget): + """ + Accept the request + """ + gajim.connections[self.account].send_authorization(self.jid) + self.window.destroy() + contact = gajim.contacts.get_contact(self.account, self.jid) + if not contact or _('Not in Roster') in contact.groups: + AddNewContactWindow(self.account, self.jid, self.user_nick) - def on_contact_info_activate(self, widget): - """ - Ask vcard - """ - if self.jid in gajim.interface.instances[self.account]['infos']: - gajim.interface.instances[self.account]['infos'][self.jid].window.present() - else: - contact = gajim.contacts.create_contact(jid=self.jid, account=self.account) - gajim.interface.instances[self.account]['infos'][self.jid] = \ - vcard.VcardWindow(contact, self.account) - # Remove jabber page - gajim.interface.instances[self.account]['infos'][self.jid].xml.\ - get_widget('information_notebook').remove_page(0) + def on_contact_info_activate(self, widget): + """ + Ask vcard + """ + if self.jid in gajim.interface.instances[self.account]['infos']: + gajim.interface.instances[self.account]['infos'][self.jid].window.present() + else: + contact = gajim.contacts.create_contact(jid=self.jid, account=self.account) + gajim.interface.instances[self.account]['infos'][self.jid] = \ + vcard.VcardWindow(contact, self.account) + # Remove jabber page + gajim.interface.instances[self.account]['infos'][self.jid].xml.\ + get_widget('information_notebook').remove_page(0) - def on_start_chat_activate(self, widget): - """ - Open chat - """ - gajim.interface.new_chat_from_jid(self.account, self.jid) + def on_start_chat_activate(self, widget): + """ + Open chat + """ + gajim.interface.new_chat_from_jid(self.account, self.jid) - def on_deny_button_clicked(self, widget): - """ - Refuse the request - """ - gajim.connections[self.account].refuse_authorization(self.jid) - contact = gajim.contacts.get_contact(self.account, self.jid) - if contact and _('Not in Roster') in contact.get_shown_groups(): - gajim.interface.roster.remove_contact(self.jid, self.account) - self.window.destroy() + def on_deny_button_clicked(self, widget): + """ + Refuse the request + """ + gajim.connections[self.account].refuse_authorization(self.jid) + contact = gajim.contacts.get_contact(self.account, self.jid) + if contact and _('Not in Roster') in contact.get_shown_groups(): + gajim.interface.roster.remove_contact(self.jid, self.account) + self.window.destroy() - def on_actions_button_clicked(self, widget): - """ - Popup action menu - """ - menu = self.prepare_popup_menu() - menu.show_all() - gtkgui_helpers.popup_emoticons_under_button(menu, widget, self.window.window) + def on_actions_button_clicked(self, widget): + """ + Popup action menu + """ + menu = self.prepare_popup_menu() + menu.show_all() + gtkgui_helpers.popup_emoticons_under_button(menu, widget, self.window.window) class JoinGroupchatWindow: - def __init__(self, account=None, room_jid='', nick='', password='', - automatic=False): - """ - Automatic is a dict like {'invities': []}. If automatic is not empty, - this means room must be automaticaly configured and when done, invities - must be automatically invited - """ - if account: - if room_jid != '' and room_jid in gajim.gc_connected[account] and\ - gajim.gc_connected[account][room_jid]: - ErrorDialog(_('You are already in group chat %s') % room_jid) - raise GajimGeneralException, 'You are already in this group chat' - if nick == '': - nick = gajim.nicks[account] - if gajim.connections[account].connected < 2: - ErrorDialog(_('You are not connected to the server'), - _('You can not join a group chat unless you are connected.')) - raise GajimGeneralException, 'You must be connected to join a groupchat' + def __init__(self, account=None, room_jid='', nick='', password='', + automatic=False): + """ + Automatic is a dict like {'invities': []}. If automatic is not empty, + this means room must be automaticaly configured and when done, invities + must be automatically invited + """ + if account: + if room_jid != '' and room_jid in gajim.gc_connected[account] and\ + gajim.gc_connected[account][room_jid]: + ErrorDialog(_('You are already in group chat %s') % room_jid) + raise GajimGeneralException, 'You are already in this group chat' + if nick == '': + nick = gajim.nicks[account] + if gajim.connections[account].connected < 2: + ErrorDialog(_('You are not connected to the server'), + _('You can not join a group chat unless you are connected.')) + raise GajimGeneralException, 'You must be connected to join a groupchat' - self.xml = gtkgui_helpers.get_glade('join_groupchat_window.glade') - - account_label = self.xml.get_widget('account_label') - account_combobox = self.xml.get_widget('account_combobox') - account_label.set_no_show_all(False) - account_combobox.set_no_show_all(False) - liststore = gtk.ListStore(str) - account_combobox.set_model(liststore) - cell = gtk.CellRendererText() - account_combobox.pack_start(cell, True) - account_combobox.add_attribute(cell, 'text', 0) - account_combobox.set_active(-1) - - # Add accounts, set current as active if it matches 'account' - for acct in [a for a in gajim.connections if \ - gajim.account_is_connected(a)]: - account_combobox.append_text(acct) - if account and account == acct: - account_combobox.set_active(liststore.iter_n_children(None)-1) - - self.account = account - self.automatic = automatic - self._empty_required_widgets = [] + self.xml = gtkgui_helpers.get_glade('join_groupchat_window.glade') - self.window = self.xml.get_widget('join_groupchat_window') - self._room_jid_entry = self.xml.get_widget('room_jid_entry') - self._nickname_entry = self.xml.get_widget('nickname_entry') - self._password_entry = self.xml.get_widget('password_entry') + account_label = self.xml.get_widget('account_label') + account_combobox = self.xml.get_widget('account_combobox') + account_label.set_no_show_all(False) + account_combobox.set_no_show_all(False) + liststore = gtk.ListStore(str) + account_combobox.set_model(liststore) + cell = gtk.CellRendererText() + account_combobox.pack_start(cell, True) + account_combobox.add_attribute(cell, 'text', 0) + account_combobox.set_active(-1) - self._room_jid_entry.set_text(room_jid) - self._nickname_entry.set_text(nick) - if password: - self._password_entry.set_text(password) - self.xml.signal_autoconnect(self) - title = None - if account: - # now add us to open windows - gajim.interface.instances[account]['join_gc'] = self - if len(gajim.connections) > 1: - title = _('Join Group Chat with account %s') % account - if title is None: - title = _('Join Group Chat') - self.window.set_title(title) + # Add accounts, set current as active if it matches 'account' + for acct in [a for a in gajim.connections if \ + gajim.account_is_connected(a)]: + account_combobox.append_text(acct) + if account and account == acct: + account_combobox.set_active(liststore.iter_n_children(None)-1) - self.recently_combobox = self.xml.get_widget('recently_combobox') - liststore = gtk.ListStore(str) - self.recently_combobox.set_model(liststore) - cell = gtk.CellRendererText() - self.recently_combobox.pack_start(cell, True) - self.recently_combobox.add_attribute(cell, 'text', 0) - self.recently_groupchat = gajim.config.get('recently_groupchat').split() - for g in self.recently_groupchat: - self.recently_combobox.append_text(g) - if len(self.recently_groupchat) == 0: - self.recently_combobox.set_sensitive(False) - elif room_jid == '': - self.recently_combobox.set_active(0) - self._room_jid_entry.select_region(0, -1) - elif room_jid != '': - self.xml.get_widget('join_button').grab_focus() + self.account = account + self.automatic = automatic + self._empty_required_widgets = [] - if not self._room_jid_entry.get_text(): - self._empty_required_widgets.append(self._room_jid_entry) - if not self._nickname_entry.get_text(): - self._empty_required_widgets.append(self._nickname_entry) - if len(self._empty_required_widgets): - self.xml.get_widget('join_button').set_sensitive(False) + self.window = self.xml.get_widget('join_groupchat_window') + self._room_jid_entry = self.xml.get_widget('room_jid_entry') + self._nickname_entry = self.xml.get_widget('nickname_entry') + self._password_entry = self.xml.get_widget('password_entry') - if account and not gajim.connections[account].private_storage_supported: - self.xml.get_widget('bookmark_checkbutton').set_sensitive(False) + self._room_jid_entry.set_text(room_jid) + self._nickname_entry.set_text(nick) + if password: + self._password_entry.set_text(password) + self.xml.signal_autoconnect(self) + title = None + if account: + # now add us to open windows + gajim.interface.instances[account]['join_gc'] = self + if len(gajim.connections) > 1: + title = _('Join Group Chat with account %s') % account + if title is None: + title = _('Join Group Chat') + self.window.set_title(title) - self.window.show_all() + self.recently_combobox = self.xml.get_widget('recently_combobox') + liststore = gtk.ListStore(str) + self.recently_combobox.set_model(liststore) + cell = gtk.CellRendererText() + self.recently_combobox.pack_start(cell, True) + self.recently_combobox.add_attribute(cell, 'text', 0) + self.recently_groupchat = gajim.config.get('recently_groupchat').split() + for g in self.recently_groupchat: + self.recently_combobox.append_text(g) + if len(self.recently_groupchat) == 0: + self.recently_combobox.set_sensitive(False) + elif room_jid == '': + self.recently_combobox.set_active(0) + self._room_jid_entry.select_region(0, -1) + elif room_jid != '': + self.xml.get_widget('join_button').grab_focus() - def on_join_groupchat_window_destroy(self, widget): - """ - Close window - """ - if self.account and 'join_gc' in gajim.interface.instances[self.account]: - # remove us from open windows - del gajim.interface.instances[self.account]['join_gc'] + if not self._room_jid_entry.get_text(): + self._empty_required_widgets.append(self._room_jid_entry) + if not self._nickname_entry.get_text(): + self._empty_required_widgets.append(self._nickname_entry) + if len(self._empty_required_widgets): + self.xml.get_widget('join_button').set_sensitive(False) - def on_join_groupchat_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: # ESCAPE - widget.destroy() + if account and not gajim.connections[account].private_storage_supported: + self.xml.get_widget('bookmark_checkbutton').set_sensitive(False) - def on_required_entry_changed(self, widget): - if not widget.get_text(): - self._empty_required_widgets.append(widget) - self.xml.get_widget('join_button').set_sensitive(False) - else: - if widget in self._empty_required_widgets: - self._empty_required_widgets.remove(widget) - if len(self._empty_required_widgets) == 0 and self.account: - self.xml.get_widget('join_button').set_sensitive(True) + self.window.show_all() - def on_account_combobox_changed(self, widget): - model = widget.get_model() - iter_ = widget.get_active_iter() - self.account = model[iter_][0].decode('utf-8') - self.on_required_entry_changed(self._nickname_entry) + def on_join_groupchat_window_destroy(self, widget): + """ + Close window + """ + if self.account and 'join_gc' in gajim.interface.instances[self.account]: + # remove us from open windows + del gajim.interface.instances[self.account]['join_gc'] - def on_recently_combobox_changed(self, widget): - model = widget.get_model() - iter_ = widget.get_active_iter() - room_jid = model[iter_][0].decode('utf-8') - self._room_jid_entry.set_text(room_jid) + def on_join_groupchat_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: # ESCAPE + widget.destroy() - def on_cancel_button_clicked(self, widget): - """ - When Cancel button is clicked - """ - self.window.destroy() + def on_required_entry_changed(self, widget): + if not widget.get_text(): + self._empty_required_widgets.append(widget) + self.xml.get_widget('join_button').set_sensitive(False) + else: + if widget in self._empty_required_widgets: + self._empty_required_widgets.remove(widget) + if len(self._empty_required_widgets) == 0 and self.account: + self.xml.get_widget('join_button').set_sensitive(True) - def on_bookmark_checkbutton_toggled(self, widget): - auto_join_checkbutton = self.xml.get_widget('auto_join_checkbutton') - if widget.get_active(): - auto_join_checkbutton.set_sensitive(True) - else: - auto_join_checkbutton.set_sensitive(False) + def on_account_combobox_changed(self, widget): + model = widget.get_model() + iter_ = widget.get_active_iter() + self.account = model[iter_][0].decode('utf-8') + self.on_required_entry_changed(self._nickname_entry) - def on_join_button_clicked(self, widget): - """ - When Join button is clicked - """ - if not self.account: - ErrorDialog(_('Invalid Account'), - _('You have to choose an account from which you want to join the ' - 'groupchat.')) - return - nickname = self._nickname_entry.get_text().decode('utf-8') - room_jid = self._room_jid_entry.get_text().decode('utf-8') - password = self._password_entry.get_text().decode('utf-8') - try: - nickname = helpers.parse_resource(nickname) - except Exception: - ErrorDialog(_('Invalid Nickname'), - _('The nickname has not allowed characters.')) - return - user, server, resource = helpers.decompose_jid(room_jid) - if not user or not server or resource: - ErrorDialog(_('Invalid group chat Jabber ID'), - _('Please enter the group chat Jabber ID as room@server.')) - return - try: - room_jid = helpers.parse_jid(room_jid) - except Exception: - ErrorDialog(_('Invalid group chat Jabber ID'), - _('The group chat Jabber ID has not allowed characters.')) - return + def on_recently_combobox_changed(self, widget): + model = widget.get_model() + iter_ = widget.get_active_iter() + room_jid = model[iter_][0].decode('utf-8') + self._room_jid_entry.set_text(room_jid) - if gajim.interface.msg_win_mgr.has_window(room_jid, self.account): - ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid, - self.account) - if ctrl.type_id != message_control.TYPE_GC: - ErrorDialog(_('This is not a group chat'), - _('%s is not the name of a group chat.') % room_jid) - return - if room_jid in self.recently_groupchat: - self.recently_groupchat.remove(room_jid) - self.recently_groupchat.insert(0, room_jid) - if len(self.recently_groupchat) > 10: - self.recently_groupchat = self.recently_groupchat[0:10] - gajim.config.set('recently_groupchat', - ' '.join(self.recently_groupchat)) + def on_cancel_button_clicked(self, widget): + """ + When Cancel button is clicked + """ + self.window.destroy() - if self.xml.get_widget('bookmark_checkbutton').get_active(): - if self.xml.get_widget('auto_join_checkbutton').get_active(): - autojoin = '1' - else: - autojoin = '0' - # Add as bookmark, with autojoin and not minimized - name = gajim.get_nick_from_jid(room_jid) - gajim.interface.add_gc_bookmark(self.account, name, room_jid, autojoin, - '0', password, nickname) + def on_bookmark_checkbutton_toggled(self, widget): + auto_join_checkbutton = self.xml.get_widget('auto_join_checkbutton') + if widget.get_active(): + auto_join_checkbutton.set_sensitive(True) + else: + auto_join_checkbutton.set_sensitive(False) - if self.automatic: - gajim.automatic_rooms[self.account][room_jid] = self.automatic - gajim.interface.join_gc_room(self.account, room_jid, nickname, password) + def on_join_button_clicked(self, widget): + """ + When Join button is clicked + """ + if not self.account: + ErrorDialog(_('Invalid Account'), + _('You have to choose an account from which you want to join the ' + 'groupchat.')) + return + nickname = self._nickname_entry.get_text().decode('utf-8') + room_jid = self._room_jid_entry.get_text().decode('utf-8') + password = self._password_entry.get_text().decode('utf-8') + try: + nickname = helpers.parse_resource(nickname) + except Exception: + ErrorDialog(_('Invalid Nickname'), + _('The nickname has not allowed characters.')) + return + user, server, resource = helpers.decompose_jid(room_jid) + if not user or not server or resource: + ErrorDialog(_('Invalid group chat Jabber ID'), + _('Please enter the group chat Jabber ID as room@server.')) + return + try: + room_jid = helpers.parse_jid(room_jid) + except Exception: + ErrorDialog(_('Invalid group chat Jabber ID'), + _('The group chat Jabber ID has not allowed characters.')) + return - self.window.destroy() + if gajim.interface.msg_win_mgr.has_window(room_jid, self.account): + ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid, + self.account) + if ctrl.type_id != message_control.TYPE_GC: + ErrorDialog(_('This is not a group chat'), + _('%s is not the name of a group chat.') % room_jid) + return + if room_jid in self.recently_groupchat: + self.recently_groupchat.remove(room_jid) + self.recently_groupchat.insert(0, room_jid) + if len(self.recently_groupchat) > 10: + self.recently_groupchat = self.recently_groupchat[0:10] + gajim.config.set('recently_groupchat', + ' '.join(self.recently_groupchat)) + + if self.xml.get_widget('bookmark_checkbutton').get_active(): + if self.xml.get_widget('auto_join_checkbutton').get_active(): + autojoin = '1' + else: + autojoin = '0' + # Add as bookmark, with autojoin and not minimized + name = gajim.get_nick_from_jid(room_jid) + gajim.interface.add_gc_bookmark(self.account, name, room_jid, autojoin, + '0', password, nickname) + + if self.automatic: + gajim.automatic_rooms[self.account][room_jid] = self.automatic + gajim.interface.join_gc_room(self.account, room_jid, nickname, password) + + self.window.destroy() class SynchroniseSelectAccountDialog: - def __init__(self, account): - # 'account' can be None if we are about to create our first one - if not account or gajim.connections[account].connected < 2: - ErrorDialog(_('You are not connected to the server'), - _('Without a connection, you can not synchronise your contacts.')) - raise GajimGeneralException, 'You are not connected to the server' - self.account = account - self.xml = gtkgui_helpers.get_glade('synchronise_select_account_dialog.glade') - self.dialog = self.xml.get_widget('synchronise_select_account_dialog') - self.accounts_treeview = self.xml.get_widget('accounts_treeview') - model = gtk.ListStore(str, str, bool) - self.accounts_treeview.set_model(model) - # columns - renderer = gtk.CellRendererText() - self.accounts_treeview.insert_column_with_attributes(-1, - _('Name'), renderer, text=0) - renderer = gtk.CellRendererText() - self.accounts_treeview.insert_column_with_attributes(-1, - _('Server'), renderer, text=1) + def __init__(self, account): + # 'account' can be None if we are about to create our first one + if not account or gajim.connections[account].connected < 2: + ErrorDialog(_('You are not connected to the server'), + _('Without a connection, you can not synchronise your contacts.')) + raise GajimGeneralException, 'You are not connected to the server' + self.account = account + self.xml = gtkgui_helpers.get_glade('synchronise_select_account_dialog.glade') + self.dialog = self.xml.get_widget('synchronise_select_account_dialog') + self.accounts_treeview = self.xml.get_widget('accounts_treeview') + model = gtk.ListStore(str, str, bool) + self.accounts_treeview.set_model(model) + # columns + renderer = gtk.CellRendererText() + self.accounts_treeview.insert_column_with_attributes(-1, + _('Name'), renderer, text=0) + renderer = gtk.CellRendererText() + self.accounts_treeview.insert_column_with_attributes(-1, + _('Server'), renderer, text=1) - self.xml.signal_autoconnect(self) - self.init_accounts() - self.dialog.show_all() + self.xml.signal_autoconnect(self) + self.init_accounts() + self.dialog.show_all() - def on_accounts_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: - self.window.destroy() + def on_accounts_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: + self.window.destroy() - def init_accounts(self): - """ - Initialize listStore with existing accounts - """ - model = self.accounts_treeview.get_model() - model.clear() - for remote_account in gajim.connections: - if remote_account == self.account: - # Do not show the account we're sync'ing - continue - iter_ = model.append() - model.set(iter_, 0, remote_account, 1, gajim.get_hostname_from_account( - remote_account)) + def init_accounts(self): + """ + Initialize listStore with existing accounts + """ + model = self.accounts_treeview.get_model() + model.clear() + for remote_account in gajim.connections: + if remote_account == self.account: + # Do not show the account we're sync'ing + continue + iter_ = model.append() + model.set(iter_, 0, remote_account, 1, gajim.get_hostname_from_account( + remote_account)) - def on_cancel_button_clicked(self, widget): - self.dialog.destroy() + def on_cancel_button_clicked(self, widget): + self.dialog.destroy() - def on_ok_button_clicked(self, widget): - sel = self.accounts_treeview.get_selection() - (model, iter_) = sel.get_selected() - if not iter_: - return - remote_account = model.get_value(iter_, 0).decode('utf-8') + def on_ok_button_clicked(self, widget): + sel = self.accounts_treeview.get_selection() + (model, iter_) = sel.get_selected() + if not iter_: + return + remote_account = model.get_value(iter_, 0).decode('utf-8') - if gajim.connections[remote_account].connected < 2: - ErrorDialog(_('This account is not connected to the server'), - _('You cannot synchronize with an account unless it is connected.')) - return - else: - try: - SynchroniseSelectContactsDialog(self.account, remote_account) - except GajimGeneralException: - # if we showed ErrorDialog, there will not be dialog instance - return - self.dialog.destroy() + if gajim.connections[remote_account].connected < 2: + ErrorDialog(_('This account is not connected to the server'), + _('You cannot synchronize with an account unless it is connected.')) + return + else: + try: + SynchroniseSelectContactsDialog(self.account, remote_account) + except GajimGeneralException: + # if we showed ErrorDialog, there will not be dialog instance + return + self.dialog.destroy() class SynchroniseSelectContactsDialog: - def __init__(self, account, remote_account): - self.local_account = account - self.remote_account = remote_account - self.xml = gtkgui_helpers.get_glade('synchronise_select_contacts_dialog.glade') - self.dialog = self.xml.get_widget('synchronise_select_contacts_dialog') - self.contacts_treeview = self.xml.get_widget('contacts_treeview') - model = gtk.ListStore(bool, str) - self.contacts_treeview.set_model(model) - # columns - renderer1 = gtk.CellRendererToggle() - renderer1.set_property('activatable', True) - renderer1.connect('toggled', self.toggled_callback) - self.contacts_treeview.insert_column_with_attributes(-1, - _('Synchronise'), renderer1, active=0) - renderer2 = gtk.CellRendererText() - self.contacts_treeview.insert_column_with_attributes(-1, - _('Name'), renderer2, text=1) + def __init__(self, account, remote_account): + self.local_account = account + self.remote_account = remote_account + self.xml = gtkgui_helpers.get_glade('synchronise_select_contacts_dialog.glade') + self.dialog = self.xml.get_widget('synchronise_select_contacts_dialog') + self.contacts_treeview = self.xml.get_widget('contacts_treeview') + model = gtk.ListStore(bool, str) + self.contacts_treeview.set_model(model) + # columns + renderer1 = gtk.CellRendererToggle() + renderer1.set_property('activatable', True) + renderer1.connect('toggled', self.toggled_callback) + self.contacts_treeview.insert_column_with_attributes(-1, + _('Synchronise'), renderer1, active=0) + renderer2 = gtk.CellRendererText() + self.contacts_treeview.insert_column_with_attributes(-1, + _('Name'), renderer2, text=1) - self.xml.signal_autoconnect(self) - self.init_contacts() - self.dialog.show_all() + self.xml.signal_autoconnect(self) + self.init_contacts() + self.dialog.show_all() - def toggled_callback(self, cell, path): - model = self.contacts_treeview.get_model() - iter_ = model.get_iter(path) - model[iter_][0] = not cell.get_active() + def toggled_callback(self, cell, path): + model = self.contacts_treeview.get_model() + iter_ = model.get_iter(path) + model[iter_][0] = not cell.get_active() - def on_contacts_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: - self.window.destroy() + def on_contacts_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: + self.window.destroy() - def init_contacts(self): - """ - Initialize listStore with existing accounts - """ - model = self.contacts_treeview.get_model() - model.clear() + def init_contacts(self): + """ + Initialize listStore with existing accounts + """ + model = self.contacts_treeview.get_model() + model.clear() - # recover local contacts - local_jid_list = gajim.contacts.get_jid_list(self.local_account) + # recover local contacts + local_jid_list = gajim.contacts.get_jid_list(self.local_account) - remote_jid_list = gajim.contacts.get_jid_list(self.remote_account) - for remote_jid in remote_jid_list: - if remote_jid not in local_jid_list: - iter_ = model.append() - model.set(iter_, 0, True, 1, remote_jid) + remote_jid_list = gajim.contacts.get_jid_list(self.remote_account) + for remote_jid in remote_jid_list: + if remote_jid not in local_jid_list: + iter_ = model.append() + model.set(iter_, 0, True, 1, remote_jid) - def on_cancel_button_clicked(self, widget): - self.dialog.destroy() + def on_cancel_button_clicked(self, widget): + self.dialog.destroy() - def on_ok_button_clicked(self, widget): - model = self.contacts_treeview.get_model() - iter_ = model.get_iter_root() - while iter_: - if model[iter_][0]: - # it is selected - remote_jid = model[iter_][1].decode('utf-8') - message = 'I\'m synchronizing my contacts from my %s account, could you please add this address to your contact list?' % \ - gajim.get_hostname_from_account(self.remote_account) - remote_contact = gajim.contacts.get_first_contact_from_jid( - self.remote_account, remote_jid) - # keep same groups and same nickname - gajim.interface.roster.req_sub(self, remote_jid, message, - self.local_account, groups = remote_contact.groups, - nickname = remote_contact.name, auto_auth = True) - iter_ = model.iter_next(iter_) - self.dialog.destroy() + def on_ok_button_clicked(self, widget): + model = self.contacts_treeview.get_model() + iter_ = model.get_iter_root() + while iter_: + if model[iter_][0]: + # it is selected + remote_jid = model[iter_][1].decode('utf-8') + message = 'I\'m synchronizing my contacts from my %s account, could you please add this address to your contact list?' % \ + gajim.get_hostname_from_account(self.remote_account) + remote_contact = gajim.contacts.get_first_contact_from_jid( + self.remote_account, remote_jid) + # keep same groups and same nickname + gajim.interface.roster.req_sub(self, remote_jid, message, + self.local_account, groups = remote_contact.groups, + nickname = remote_contact.name, auto_auth = True) + iter_ = model.iter_next(iter_) + self.dialog.destroy() class NewChatDialog(InputDialog): - def __init__(self, account): - self.account = account + def __init__(self, account): + self.account = account - if len(gajim.connections) > 1: - title = _('Start Chat with account %s') % account - else: - title = _('Start Chat') - prompt_text = _('Fill in the nickname or the Jabber ID of the contact you would like\nto send a chat message to:') - InputDialog.__init__(self, title, prompt_text, is_modal=False) + if len(gajim.connections) > 1: + title = _('Start Chat with account %s') % account + else: + title = _('Start Chat') + prompt_text = _('Fill in the nickname or the Jabber ID of the contact you would like\nto send a chat message to:') + InputDialog.__init__(self, title, prompt_text, is_modal=False) - self.completion_dict = {} - liststore = gtkgui_helpers.get_completion_liststore(self.input_entry) - self.completion_dict = helpers.get_contact_dict_for_account(account) - # add all contacts to the model - keys = sorted(self.completion_dict.keys()) - for jid in keys: - contact = self.completion_dict[jid] - img = gajim.interface.jabber_state_images['16'][contact.show] - liststore.append((img.get_pixbuf(), jid)) + self.completion_dict = {} + liststore = gtkgui_helpers.get_completion_liststore(self.input_entry) + self.completion_dict = helpers.get_contact_dict_for_account(account) + # add all contacts to the model + keys = sorted(self.completion_dict.keys()) + for jid in keys: + contact = self.completion_dict[jid] + img = gajim.interface.jabber_state_images['16'][contact.show] + liststore.append((img.get_pixbuf(), jid)) - self.ok_handler = self.new_chat_response - okbutton = self.xml.get_widget('okbutton') - okbutton.connect('clicked', self.on_okbutton_clicked) - cancelbutton = self.xml.get_widget('cancelbutton') - cancelbutton.connect('clicked', self.on_cancelbutton_clicked) - self.dialog.show_all() + self.ok_handler = self.new_chat_response + okbutton = self.xml.get_widget('okbutton') + okbutton.connect('clicked', self.on_okbutton_clicked) + cancelbutton = self.xml.get_widget('cancelbutton') + cancelbutton.connect('clicked', self.on_cancelbutton_clicked) + self.dialog.show_all() - def new_chat_response(self, jid): - """ - Called when ok button is clicked - """ - if gajim.connections[self.account].connected <= 1: - #if offline or connecting - ErrorDialog(_('Connection not available'), - _('Please make sure you are connected with "%s".') % self.account) - return + def new_chat_response(self, jid): + """ + Called when ok button is clicked + """ + if gajim.connections[self.account].connected <= 1: + #if offline or connecting + ErrorDialog(_('Connection not available'), + _('Please make sure you are connected with "%s".') % self.account) + return - if jid in self.completion_dict: - jid = self.completion_dict[jid].jid - else: - try: - jid = helpers.parse_jid(jid) - except helpers.InvalidFormat, e: - ErrorDialog(_('Invalid JID'), e[0]) - return - except: - ErrorDialog(_('Invalid JID'), _('Unable to parse "%s".') % jid) - return - gajim.interface.new_chat_from_jid(self.account, jid) + if jid in self.completion_dict: + jid = self.completion_dict[jid].jid + else: + try: + jid = helpers.parse_jid(jid) + except helpers.InvalidFormat, e: + ErrorDialog(_('Invalid JID'), e[0]) + return + except: + ErrorDialog(_('Invalid JID'), _('Unable to parse "%s".') % jid) + return + gajim.interface.new_chat_from_jid(self.account, jid) class ChangePasswordDialog: - def __init__(self, account, on_response): - # 'account' can be None if we are about to create our first one - if not account or gajim.connections[account].connected < 2: - ErrorDialog(_('You are not connected to the server'), - _('Without a connection, you can not change your password.')) - raise GajimGeneralException, 'You are not connected to the server' - self.account = account - self.on_response = on_response - self.xml = gtkgui_helpers.get_glade('change_password_dialog.glade') - self.dialog = self.xml.get_widget('change_password_dialog') - self.password1_entry = self.xml.get_widget('password1_entry') - self.password2_entry = self.xml.get_widget('password2_entry') - self.dialog.connect('response', self.on_dialog_response) + def __init__(self, account, on_response): + # 'account' can be None if we are about to create our first one + if not account or gajim.connections[account].connected < 2: + ErrorDialog(_('You are not connected to the server'), + _('Without a connection, you can not change your password.')) + raise GajimGeneralException, 'You are not connected to the server' + self.account = account + self.on_response = on_response + self.xml = gtkgui_helpers.get_glade('change_password_dialog.glade') + self.dialog = self.xml.get_widget('change_password_dialog') + self.password1_entry = self.xml.get_widget('password1_entry') + self.password2_entry = self.xml.get_widget('password2_entry') + self.dialog.connect('response', self.on_dialog_response) - self.dialog.show_all() + self.dialog.show_all() - def on_dialog_response(self, dialog, response): - if response != gtk.RESPONSE_OK: - dialog.destroy() - self.on_response(None) - return - password1 = self.password1_entry.get_text().decode('utf-8') - if not password1: - ErrorDialog(_('Invalid password'), _('You must enter a password.')) - return - password2 = self.password2_entry.get_text().decode('utf-8') - if password1 != password2: - ErrorDialog(_('Passwords do not match'), - _('The passwords typed in both fields must be identical.')) - return - dialog.destroy() - self.on_response(password1) + def on_dialog_response(self, dialog, response): + if response != gtk.RESPONSE_OK: + dialog.destroy() + self.on_response(None) + return + password1 = self.password1_entry.get_text().decode('utf-8') + if not password1: + ErrorDialog(_('Invalid password'), _('You must enter a password.')) + return + password2 = self.password2_entry.get_text().decode('utf-8') + if password1 != password2: + ErrorDialog(_('Passwords do not match'), + _('The passwords typed in both fields must be identical.')) + return + dialog.destroy() + self.on_response(password1) class PopupNotificationWindow: - def __init__(self, event_type, jid, account, msg_type='', - path_to_image=None, title=None, text=None): - self.account = account - self.jid = jid - self.msg_type = msg_type + def __init__(self, event_type, jid, account, msg_type='', + path_to_image=None, title=None, text=None): + self.account = account + self.jid = jid + self.msg_type = msg_type - xml = gtkgui_helpers.get_glade('popup_notification_window.glade') - self.window = xml.get_widget('popup_notification_window') - self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_TOOLTIP) - close_button = xml.get_widget('close_button') - event_type_label = xml.get_widget('event_type_label') - event_description_label = xml.get_widget('event_description_label') - eventbox = xml.get_widget('eventbox') - image = xml.get_widget('notification_image') + xml = gtkgui_helpers.get_glade('popup_notification_window.glade') + self.window = xml.get_widget('popup_notification_window') + self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_TOOLTIP) + close_button = xml.get_widget('close_button') + event_type_label = xml.get_widget('event_type_label') + event_description_label = xml.get_widget('event_description_label') + eventbox = xml.get_widget('eventbox') + image = xml.get_widget('notification_image') - if not text: - text = gajim.get_name_from_jid(account, jid) # default value of text - if not title: - title = '' + if not text: + text = gajim.get_name_from_jid(account, jid) # default value of text + if not title: + title = '' - event_type_label.set_markup( - '%s' % - gobject.markup_escape_text(title)) + event_type_label.set_markup( + '%s' % + gobject.markup_escape_text(title)) - # set colors [ http://www.pitt.edu/~nisg/cis/web/cgi/rgb.html ] - self.window.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse('black')) + # set colors [ http://www.pitt.edu/~nisg/cis/web/cgi/rgb.html ] + self.window.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse('black')) - # default image - if not path_to_image: - path_to_image = os.path.abspath( - os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'chat_msg_recv.png')) # img to display + # default image + if not path_to_image: + path_to_image = os.path.abspath( + os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', + 'chat_msg_recv.png')) # img to display - if event_type == _('Contact Signed In'): - bg_color = 'limegreen' - elif event_type == _('Contact Signed Out'): - bg_color = 'red' - elif event_type in (_('New Message'), _('New Single Message'), - _('New Private Message'), _('New E-mail')): - bg_color = 'dodgerblue' - elif event_type == _('File Transfer Request'): - bg_color = 'khaki' - elif event_type == _('File Transfer Error'): - bg_color = 'firebrick' - elif event_type in (_('File Transfer Completed'), - _('File Transfer Stopped')): - bg_color = 'yellowgreen' - elif event_type == _('Groupchat Invitation'): - bg_color = 'tan1' - elif event_type == _('Contact Changed Status'): - bg_color = 'thistle2' - else: # Unknown event! Shouldn't happen but deal with it - bg_color = 'white' - popup_bg_color = gtk.gdk.color_parse(bg_color) - close_button.modify_bg(gtk.STATE_NORMAL, popup_bg_color) - eventbox.modify_bg(gtk.STATE_NORMAL, popup_bg_color) - event_description_label.set_markup('%s' % - gobject.markup_escape_text(text)) + if event_type == _('Contact Signed In'): + bg_color = 'limegreen' + elif event_type == _('Contact Signed Out'): + bg_color = 'red' + elif event_type in (_('New Message'), _('New Single Message'), + _('New Private Message'), _('New E-mail')): + bg_color = 'dodgerblue' + elif event_type == _('File Transfer Request'): + bg_color = 'khaki' + elif event_type == _('File Transfer Error'): + bg_color = 'firebrick' + elif event_type in (_('File Transfer Completed'), + _('File Transfer Stopped')): + bg_color = 'yellowgreen' + elif event_type == _('Groupchat Invitation'): + bg_color = 'tan1' + elif event_type == _('Contact Changed Status'): + bg_color = 'thistle2' + else: # Unknown event! Shouldn't happen but deal with it + bg_color = 'white' + popup_bg_color = gtk.gdk.color_parse(bg_color) + close_button.modify_bg(gtk.STATE_NORMAL, popup_bg_color) + eventbox.modify_bg(gtk.STATE_NORMAL, popup_bg_color) + event_description_label.set_markup('%s' % + gobject.markup_escape_text(text)) - # set the image - image.set_from_file(path_to_image) + # set the image + image.set_from_file(path_to_image) - # position the window to bottom-right of screen - window_width, self.window_height = self.window.get_size() - gajim.interface.roster.popups_notification_height += self.window_height - pos_x = gajim.config.get('notification_position_x') - if pos_x < 0: - pos_x = gtk.gdk.screen_width() - window_width + pos_x + 1 - pos_y = gajim.config.get('notification_position_y') - if pos_y < 0: - pos_y = gtk.gdk.screen_height() - \ - gajim.interface.roster.popups_notification_height + pos_y + 1 - self.window.move(pos_x, pos_y) + # position the window to bottom-right of screen + window_width, self.window_height = self.window.get_size() + gajim.interface.roster.popups_notification_height += self.window_height + pos_x = gajim.config.get('notification_position_x') + if pos_x < 0: + pos_x = gtk.gdk.screen_width() - window_width + pos_x + 1 + pos_y = gajim.config.get('notification_position_y') + if pos_y < 0: + pos_y = gtk.gdk.screen_height() - \ + gajim.interface.roster.popups_notification_height + pos_y + 1 + self.window.move(pos_x, pos_y) - xml.signal_autoconnect(self) - self.window.show_all() - timeout = gajim.config.get('notification_timeout') - gobject.timeout_add_seconds(timeout, self.on_timeout) + xml.signal_autoconnect(self) + self.window.show_all() + timeout = gajim.config.get('notification_timeout') + gobject.timeout_add_seconds(timeout, self.on_timeout) - def on_close_button_clicked(self, widget): - self.adjust_height_and_move_popup_notification_windows() + def on_close_button_clicked(self, widget): + self.adjust_height_and_move_popup_notification_windows() - def on_timeout(self): - self.adjust_height_and_move_popup_notification_windows() + def on_timeout(self): + self.adjust_height_and_move_popup_notification_windows() - def adjust_height_and_move_popup_notification_windows(self): - #remove - gajim.interface.roster.popups_notification_height -= self.window_height - self.window.destroy() + def adjust_height_and_move_popup_notification_windows(self): + #remove + gajim.interface.roster.popups_notification_height -= self.window_height + self.window.destroy() - if len(gajim.interface.roster.popup_notification_windows) > 0: - # we want to remove the first window added in the list - gajim.interface.roster.popup_notification_windows.pop(0) + if len(gajim.interface.roster.popup_notification_windows) > 0: + # we want to remove the first window added in the list + gajim.interface.roster.popup_notification_windows.pop(0) - # move the rest of popup windows - gajim.interface.roster.popups_notification_height = 0 - for window_instance in gajim.interface.roster.popup_notification_windows: - window_width, window_height = window_instance.window.get_size() - gajim.interface.roster.popups_notification_height += window_height - window_instance.window.move(gtk.gdk.screen_width() - window_width, - gtk.gdk.screen_height() - \ - gajim.interface.roster.popups_notification_height) + # move the rest of popup windows + gajim.interface.roster.popups_notification_height = 0 + for window_instance in gajim.interface.roster.popup_notification_windows: + window_width, window_height = window_instance.window.get_size() + gajim.interface.roster.popups_notification_height += window_height + window_instance.window.move(gtk.gdk.screen_width() - window_width, + gtk.gdk.screen_height() - \ + gajim.interface.roster.popups_notification_height) - def on_popup_notification_window_button_press_event(self, widget, event): - if event.button != 1: - self.window.destroy() - return - gajim.interface.handle_event(self.account, self.jid, self.msg_type) - self.adjust_height_and_move_popup_notification_windows() + def on_popup_notification_window_button_press_event(self, widget, event): + if event.button != 1: + self.window.destroy() + return + gajim.interface.handle_event(self.account, self.jid, self.msg_type) + self.adjust_height_and_move_popup_notification_windows() class SingleMessageWindow: - """ - SingleMessageWindow can send or show a received singled message depending on - action argument which can be 'send' or 'receive' - """ - # Keep a reference on windows so garbage collector don't restroy them - instances = [] - def __init__(self, account, to='', action='', from_whom='', subject='', - message='', resource='', session=None, form_node=None): - self.instances.append(self) - self.account = account - self.action = action + """ + SingleMessageWindow can send or show a received singled message depending on + action argument which can be 'send' or 'receive' + """ + # Keep a reference on windows so garbage collector don't restroy them + instances = [] + def __init__(self, account, to='', action='', from_whom='', subject='', + message='', resource='', session=None, form_node=None): + self.instances.append(self) + self.account = account + self.action = action - self.subject = subject - self.message = message - self.to = to - self.from_whom = from_whom - self.resource = resource - self.session = session + self.subject = subject + self.message = message + self.to = to + self.from_whom = from_whom + self.resource = resource + self.session = session - self.xml = gtkgui_helpers.get_glade('single_message_window.glade') - self.window = self.xml.get_widget('single_message_window') - self.count_chars_label = self.xml.get_widget('count_chars_label') - self.from_label = self.xml.get_widget('from_label') - self.from_entry = self.xml.get_widget('from_entry') - self.to_label = self.xml.get_widget('to_label') - self.to_entry = self.xml.get_widget('to_entry') - self.subject_entry = self.xml.get_widget('subject_entry') - self.message_scrolledwindow = self.xml.get_widget( - 'message_scrolledwindow') - self.message_textview = self.xml.get_widget('message_textview') - self.message_tv_buffer = self.message_textview.get_buffer() - self.conversation_scrolledwindow = self.xml.get_widget( - 'conversation_scrolledwindow') - self.conversation_textview = conversation_textview.ConversationTextview( - account) - self.conversation_textview.tv.show() - self.conversation_tv_buffer = self.conversation_textview.tv.get_buffer() - self.xml.get_widget('conversation_scrolledwindow').add( - self.conversation_textview.tv) + self.xml = gtkgui_helpers.get_glade('single_message_window.glade') + self.window = self.xml.get_widget('single_message_window') + self.count_chars_label = self.xml.get_widget('count_chars_label') + self.from_label = self.xml.get_widget('from_label') + self.from_entry = self.xml.get_widget('from_entry') + self.to_label = self.xml.get_widget('to_label') + self.to_entry = self.xml.get_widget('to_entry') + self.subject_entry = self.xml.get_widget('subject_entry') + self.message_scrolledwindow = self.xml.get_widget( + 'message_scrolledwindow') + self.message_textview = self.xml.get_widget('message_textview') + self.message_tv_buffer = self.message_textview.get_buffer() + self.conversation_scrolledwindow = self.xml.get_widget( + 'conversation_scrolledwindow') + self.conversation_textview = conversation_textview.ConversationTextview( + account) + self.conversation_textview.tv.show() + self.conversation_tv_buffer = self.conversation_textview.tv.get_buffer() + self.xml.get_widget('conversation_scrolledwindow').add( + self.conversation_textview.tv) - self.form_widget = None - parent_box = self.xml.get_widget('conversation_scrolledwindow').\ - get_parent() - if form_node: - dataform = dataforms.ExtendForm(node=form_node) - self.form_widget = dataforms_widget.DataFormWidget(dataform) - self.form_widget.show_all() - parent_box.add(self.form_widget) - parent_box.child_set_property(self.form_widget, 'position', - parent_box.child_get_property(self.xml.get_widget( - 'conversation_scrolledwindow'), 'position')) - self.action = 'form' + self.form_widget = None + parent_box = self.xml.get_widget('conversation_scrolledwindow').\ + get_parent() + if form_node: + dataform = dataforms.ExtendForm(node=form_node) + self.form_widget = dataforms_widget.DataFormWidget(dataform) + self.form_widget.show_all() + parent_box.add(self.form_widget) + parent_box.child_set_property(self.form_widget, 'position', + parent_box.child_get_property(self.xml.get_widget( + 'conversation_scrolledwindow'), 'position')) + self.action = 'form' - self.send_button = self.xml.get_widget('send_button') - self.reply_button = self.xml.get_widget('reply_button') - self.send_and_close_button = self.xml.get_widget('send_and_close_button') - self.cancel_button = self.xml.get_widget('cancel_button') - self.close_button = self.xml.get_widget('close_button') - self.message_tv_buffer.connect('changed', self.update_char_counter) - if isinstance(to, list): - jid = ', '.join( [i[0].jid + '/' + i[0].resource for i in to]) - self.to_entry.set_text(jid) - self.to_entry.set_sensitive(False) - else: - self.to_entry.set_text(to) + self.send_button = self.xml.get_widget('send_button') + self.reply_button = self.xml.get_widget('reply_button') + self.send_and_close_button = self.xml.get_widget('send_and_close_button') + self.cancel_button = self.xml.get_widget('cancel_button') + self.close_button = self.xml.get_widget('close_button') + self.message_tv_buffer.connect('changed', self.update_char_counter) + if isinstance(to, list): + jid = ', '.join( [i[0].jid + '/' + i[0].resource for i in to]) + self.to_entry.set_text(jid) + self.to_entry.set_sensitive(False) + else: + self.to_entry.set_text(to) - if gajim.config.get('use_speller') and HAS_GTK_SPELL and action == 'send': - try: - lang = gajim.config.get('speller_language') - if not lang: - lang = gajim.LANG - gtkspell.Spell(self.conversation_textview.tv, lang) - gtkspell.Spell(self.message_textview, lang) - except (gobject.GError, TypeError, RuntimeError, OSError): - AspellDictError(lang) + if gajim.config.get('use_speller') and HAS_GTK_SPELL and action == 'send': + try: + lang = gajim.config.get('speller_language') + if not lang: + lang = gajim.LANG + gtkspell.Spell(self.conversation_textview.tv, lang) + gtkspell.Spell(self.message_textview, lang) + except (gobject.GError, TypeError, RuntimeError, OSError): + AspellDictError(lang) - self.prepare_widgets_for(self.action) + self.prepare_widgets_for(self.action) - # set_text(None) raises TypeError exception - if self.subject is None: - self.subject = '' - self.subject_entry.set_text(self.subject) + # set_text(None) raises TypeError exception + if self.subject is None: + self.subject = '' + self.subject_entry.set_text(self.subject) - if to == '': - liststore = gtkgui_helpers.get_completion_liststore(self.to_entry) - self.completion_dict = helpers.get_contact_dict_for_account(account) - keys = sorted(self.completion_dict.keys()) - for jid in keys: - contact = self.completion_dict[jid] - img = gajim.interface.jabber_state_images['16'][contact.show] - liststore.append((img.get_pixbuf(), jid)) - else: - self.completion_dict = {} - self.xml.signal_autoconnect(self) + if to == '': + liststore = gtkgui_helpers.get_completion_liststore(self.to_entry) + self.completion_dict = helpers.get_contact_dict_for_account(account) + keys = sorted(self.completion_dict.keys()) + for jid in keys: + contact = self.completion_dict[jid] + img = gajim.interface.jabber_state_images['16'][contact.show] + liststore.append((img.get_pixbuf(), jid)) + else: + self.completion_dict = {} + self.xml.signal_autoconnect(self) - # get window position and size from config - gtkgui_helpers.resize_window(self.window, - gajim.config.get('single-msg-width'), - gajim.config.get('single-msg-height')) - gtkgui_helpers.move_window(self.window, - gajim.config.get('single-msg-x-position'), - gajim.config.get('single-msg-y-position')) + # get window position and size from config + gtkgui_helpers.resize_window(self.window, + gajim.config.get('single-msg-width'), + gajim.config.get('single-msg-height')) + gtkgui_helpers.move_window(self.window, + gajim.config.get('single-msg-x-position'), + gajim.config.get('single-msg-y-position')) - self.window.show_all() + self.window.show_all() - def on_single_message_window_destroy(self, widget): - self.instances.remove(self) - c = gajim.contacts.get_contact_with_highest_priority(self.account, - self.from_whom) - if not c: - # Groupchat is maybe already destroyed - return - if c.is_groupchat() and not self.from_whom in \ - gajim.interface.minimized_controls[self.account] and self.action == \ - 'receive' and gajim.events.get_nb_roster_events(self.account, - self.from_whom, types=['chat', 'normal']) == 0: - gajim.interface.roster.remove_groupchat(self.from_whom, self.account) + def on_single_message_window_destroy(self, widget): + self.instances.remove(self) + c = gajim.contacts.get_contact_with_highest_priority(self.account, + self.from_whom) + if not c: + # Groupchat is maybe already destroyed + return + if c.is_groupchat() and not self.from_whom in \ + gajim.interface.minimized_controls[self.account] and self.action == \ + 'receive' and gajim.events.get_nb_roster_events(self.account, + self.from_whom, types=['chat', 'normal']) == 0: + gajim.interface.roster.remove_groupchat(self.from_whom, self.account) - def set_cursor_to_end(self): - end_iter = self.message_tv_buffer.get_end_iter() - self.message_tv_buffer.place_cursor(end_iter) + def set_cursor_to_end(self): + end_iter = self.message_tv_buffer.get_end_iter() + self.message_tv_buffer.place_cursor(end_iter) - def save_pos(self): - # save the window size and position - x, y = self.window.get_position() - gajim.config.set('single-msg-x-position', x) - gajim.config.set('single-msg-y-position', y) - width, height = self.window.get_size() - gajim.config.set('single-msg-width', width) - gajim.config.set('single-msg-height', height) - gajim.interface.save_config() + def save_pos(self): + # save the window size and position + x, y = self.window.get_position() + gajim.config.set('single-msg-x-position', x) + gajim.config.set('single-msg-y-position', y) + width, height = self.window.get_size() + gajim.config.set('single-msg-width', width) + gajim.config.set('single-msg-height', height) + gajim.interface.save_config() - def on_single_message_window_delete_event(self, window, ev): - self.save_pos() + def on_single_message_window_delete_event(self, window, ev): + self.save_pos() - def prepare_widgets_for(self, action): - if len(gajim.connections) > 1: - if action == 'send': - title = _('Single Message using account %s') % self.account - else: - title = _('Single Message in account %s') % self.account - else: - title = _('Single Message') + def prepare_widgets_for(self, action): + if len(gajim.connections) > 1: + if action == 'send': + title = _('Single Message using account %s') % self.account + else: + title = _('Single Message in account %s') % self.account + else: + title = _('Single Message') - if action == 'send': # prepare UI for Sending - title = _('Send %s') % title - self.send_button.show() - self.send_and_close_button.show() - self.to_label.show() - self.to_entry.show() - self.reply_button.hide() - self.from_label.hide() - self.from_entry.hide() - self.conversation_scrolledwindow.hide() - self.message_scrolledwindow.show() + if action == 'send': # prepare UI for Sending + title = _('Send %s') % title + self.send_button.show() + self.send_and_close_button.show() + self.to_label.show() + self.to_entry.show() + self.reply_button.hide() + self.from_label.hide() + self.from_entry.hide() + self.conversation_scrolledwindow.hide() + self.message_scrolledwindow.show() - if self.message: # we come from a reply? - self.message_textview.grab_focus() - self.cancel_button.hide() - self.close_button.show() - self.message_tv_buffer.set_text(self.message) - gobject.idle_add(self.set_cursor_to_end) - else: # we write a new message (not from reply) - self.close_button.hide() - if self.to: # do we already have jid? - self.subject_entry.grab_focus() + if self.message: # we come from a reply? + self.message_textview.grab_focus() + self.cancel_button.hide() + self.close_button.show() + self.message_tv_buffer.set_text(self.message) + gobject.idle_add(self.set_cursor_to_end) + else: # we write a new message (not from reply) + self.close_button.hide() + if self.to: # do we already have jid? + self.subject_entry.grab_focus() - elif action == 'receive': # prepare UI for Receiving - title = _('Received %s') % title - self.reply_button.show() - self.from_label.show() - self.from_entry.show() - self.send_button.hide() - self.send_and_close_button.hide() - self.to_label.hide() - self.to_entry.hide() - self.conversation_scrolledwindow.show() - self.message_scrolledwindow.hide() + elif action == 'receive': # prepare UI for Receiving + title = _('Received %s') % title + self.reply_button.show() + self.from_label.show() + self.from_entry.show() + self.send_button.hide() + self.send_and_close_button.hide() + self.to_label.hide() + self.to_entry.hide() + self.conversation_scrolledwindow.show() + self.message_scrolledwindow.hide() - if self.message: - self.conversation_textview.print_real_text(self.message) - fjid = self.from_whom - if self.resource: - fjid += '/' + self.resource # Full jid of sender (with resource) - self.from_entry.set_text(fjid) - self.from_entry.set_property('editable', False) - self.subject_entry.set_property('editable', False) - self.reply_button.grab_focus() - self.cancel_button.hide() - self.close_button.show() - elif action == 'form': # prepare UI for Receiving - title = _('Form %s') % title - self.send_button.show() - self.send_and_close_button.show() - self.to_label.show() - self.to_entry.show() - self.reply_button.hide() - self.from_label.hide() - self.from_entry.hide() - self.conversation_scrolledwindow.hide() - self.message_scrolledwindow.hide() + if self.message: + self.conversation_textview.print_real_text(self.message) + fjid = self.from_whom + if self.resource: + fjid += '/' + self.resource # Full jid of sender (with resource) + self.from_entry.set_text(fjid) + self.from_entry.set_property('editable', False) + self.subject_entry.set_property('editable', False) + self.reply_button.grab_focus() + self.cancel_button.hide() + self.close_button.show() + elif action == 'form': # prepare UI for Receiving + title = _('Form %s') % title + self.send_button.show() + self.send_and_close_button.show() + self.to_label.show() + self.to_entry.show() + self.reply_button.hide() + self.from_label.hide() + self.from_entry.hide() + self.conversation_scrolledwindow.hide() + self.message_scrolledwindow.hide() - self.window.set_title(title) + self.window.set_title(title) - def on_cancel_button_clicked(self, widget): - self.save_pos() - self.window.destroy() + def on_cancel_button_clicked(self, widget): + self.save_pos() + self.window.destroy() - def on_close_button_clicked(self, widget): - self.save_pos() - self.window.destroy() + def on_close_button_clicked(self, widget): + self.save_pos() + self.window.destroy() - def update_char_counter(self, widget): - characters_no = self.message_tv_buffer.get_char_count() - self.count_chars_label.set_text(unicode(characters_no)) + def update_char_counter(self, widget): + characters_no = self.message_tv_buffer.get_char_count() + self.count_chars_label.set_text(unicode(characters_no)) - def send_single_message(self): - if gajim.connections[self.account].connected <= 1: - # if offline or connecting - ErrorDialog(_('Connection not available'), - _('Please make sure you are connected with "%s".') % self.account) - return - if isinstance(self.to, list): - sender_list = [i[0].jid + '/' + i[0].resource for i in self.to] - else: - sender_list = [self.to_entry.get_text().decode('utf-8')] + def send_single_message(self): + if gajim.connections[self.account].connected <= 1: + # if offline or connecting + ErrorDialog(_('Connection not available'), + _('Please make sure you are connected with "%s".') % self.account) + return + if isinstance(self.to, list): + sender_list = [i[0].jid + '/' + i[0].resource for i in self.to] + else: + sender_list = [self.to_entry.get_text().decode('utf-8')] - for to_whom_jid in sender_list: - if to_whom_jid in self.completion_dict: - to_whom_jid = self.completion_dict[to_whom_jid].jid - try: - to_whom_jid = helpers.parse_jid(to_whom_jid) - except helpers.InvalidFormat: - ErrorDialog(_('Invalid Jabber ID'), - _('It is not possible to send a message to %s, this JID is not ' - 'valid.') % to_whom_jid) - return + for to_whom_jid in sender_list: + if to_whom_jid in self.completion_dict: + to_whom_jid = self.completion_dict[to_whom_jid].jid + try: + to_whom_jid = helpers.parse_jid(to_whom_jid) + except helpers.InvalidFormat: + ErrorDialog(_('Invalid Jabber ID'), + _('It is not possible to send a message to %s, this JID is not ' + 'valid.') % to_whom_jid) + return - subject = self.subject_entry.get_text().decode('utf-8') - begin, end = self.message_tv_buffer.get_bounds() - message = self.message_tv_buffer.get_text(begin, end).decode('utf-8') + subject = self.subject_entry.get_text().decode('utf-8') + begin, end = self.message_tv_buffer.get_bounds() + message = self.message_tv_buffer.get_text(begin, end).decode('utf-8') - if '/announce/' in to_whom_jid: - gajim.connections[self.account].send_motd(to_whom_jid, subject, - message) - continue + if '/announce/' in to_whom_jid: + gajim.connections[self.account].send_motd(to_whom_jid, subject, + message) + continue - if self.session: - session = self.session - else: - session = gajim.connections[self.account].make_new_session( - to_whom_jid) + if self.session: + session = self.session + else: + session = gajim.connections[self.account].make_new_session( + to_whom_jid) - if self.form_widget: - form_node = self.form_widget.data_form - else: - form_node = None - # FIXME: allow GPG message some day - gajim.connections[self.account].send_message(to_whom_jid, message, - keyID=None, type_='normal', subject=subject, session=session, - form_node=form_node) + if self.form_widget: + form_node = self.form_widget.data_form + else: + form_node = None + # FIXME: allow GPG message some day + gajim.connections[self.account].send_message(to_whom_jid, message, + keyID=None, type_='normal', subject=subject, session=session, + form_node=form_node) - self.subject_entry.set_text('') # we sent ok, clear the subject - self.message_tv_buffer.set_text('') # we sent ok, clear the textview + self.subject_entry.set_text('') # we sent ok, clear the subject + self.message_tv_buffer.set_text('') # we sent ok, clear the textview - def on_send_button_clicked(self, widget): - self.send_single_message() + def on_send_button_clicked(self, widget): + self.send_single_message() - def on_reply_button_clicked(self, widget): - # we create a new blank window to send and we preset RE: and to jid - self.subject = _('RE: %s') % self.subject - self.message = _('%s wrote:\n') % self.from_whom + self.message - # add > at the begining of each line - self.message = self.message.replace('\n', '\n> ') + '\n\n' - self.window.destroy() - SingleMessageWindow(self.account, to=self.from_whom, action='send', - from_whom=self.from_whom, subject=self.subject, message=self.message, - session=self.session) + def on_reply_button_clicked(self, widget): + # we create a new blank window to send and we preset RE: and to jid + self.subject = _('RE: %s') % self.subject + self.message = _('%s wrote:\n') % self.from_whom + self.message + # add > at the begining of each line + self.message = self.message.replace('\n', '\n> ') + '\n\n' + self.window.destroy() + SingleMessageWindow(self.account, to=self.from_whom, action='send', + from_whom=self.from_whom, subject=self.subject, message=self.message, + session=self.session) - def on_send_and_close_button_clicked(self, widget): - self.send_single_message() - self.save_pos() - self.window.destroy() + def on_send_and_close_button_clicked(self, widget): + self.send_single_message() + self.save_pos() + self.window.destroy() - def on_single_message_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: # ESCAPE - self.save_pos() - self.window.destroy() + def on_single_message_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: # ESCAPE + self.save_pos() + self.window.destroy() class XMLConsoleWindow: - def __init__(self, account): - self.account = account + def __init__(self, account): + self.account = account - self.xml = gtkgui_helpers.get_glade('xml_console_window.glade') - self.window = self.xml.get_widget('xml_console_window') - self.input_textview = self.xml.get_widget('input_textview') - self.stanzas_log_textview = self.xml.get_widget('stanzas_log_textview') - self.input_tv_buffer = self.input_textview.get_buffer() - buffer_ = self.stanzas_log_textview.get_buffer() - end_iter = buffer_.get_end_iter() - buffer_.create_mark('end', end_iter, False) + self.xml = gtkgui_helpers.get_glade('xml_console_window.glade') + self.window = self.xml.get_widget('xml_console_window') + self.input_textview = self.xml.get_widget('input_textview') + self.stanzas_log_textview = self.xml.get_widget('stanzas_log_textview') + self.input_tv_buffer = self.input_textview.get_buffer() + buffer_ = self.stanzas_log_textview.get_buffer() + end_iter = buffer_.get_end_iter() + buffer_.create_mark('end', end_iter, False) - self.tagIn = buffer_.create_tag('incoming') - color = gajim.config.get('inmsgcolor') - self.tagIn.set_property('foreground', color) - self.tagInComment = buffer_.create_tag('in_comment') - self.tagInComment.set_property('foreground', color) + self.tagIn = buffer_.create_tag('incoming') + color = gajim.config.get('inmsgcolor') + self.tagIn.set_property('foreground', color) + self.tagInComment = buffer_.create_tag('in_comment') + self.tagInComment.set_property('foreground', color) - self.tagOut = buffer_.create_tag('outgoing') - color = gajim.config.get('outmsgcolor') - self.tagOut.set_property('foreground', color) - self.tagOutComment = buffer_.create_tag('out_comment') - self.tagOutComment.set_property('foreground', color) + self.tagOut = buffer_.create_tag('outgoing') + color = gajim.config.get('outmsgcolor') + self.tagOut.set_property('foreground', color) + self.tagOutComment = buffer_.create_tag('out_comment') + self.tagOutComment.set_property('foreground', color) - self.enabled = False + self.enabled = False - self.input_textview.modify_text( - gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) + self.input_textview.modify_text( + gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) - if len(gajim.connections) > 1: - title = _('XML Console for %s') % self.account - else: - title = _('XML Console') + if len(gajim.connections) > 1: + title = _('XML Console for %s') % self.account + else: + title = _('XML Console') - self.window.set_title(title) - self.window.show_all() + self.window.set_title(title) + self.window.show_all() - self.xml.signal_autoconnect(self) + self.xml.signal_autoconnect(self) - def on_xml_console_window_delete_event(self, widget, event): - self.window.hide() - return True # do NOT destroy the window + def on_xml_console_window_delete_event(self, widget, event): + self.window.hide() + return True # do NOT destroy the window - def on_clear_button_clicked(self, widget): - buffer_ = self.stanzas_log_textview.get_buffer() - buffer_.set_text('') + def on_clear_button_clicked(self, widget): + buffer_ = self.stanzas_log_textview.get_buffer() + buffer_.set_text('') - def on_enable_checkbutton_toggled(self, widget): - self.enabled = widget.get_active() + def on_enable_checkbutton_toggled(self, widget): + self.enabled = widget.get_active() - def scroll_to_end(self, ): - parent = self.stanzas_log_textview.get_parent() - buffer_ = self.stanzas_log_textview.get_buffer() - end_mark = buffer_.get_mark('end') - if not end_mark: - return False - self.stanzas_log_textview.scroll_to_mark(end_mark, 0, True, 0, 1) - adjustment = parent.get_hadjustment() - adjustment.set_value(0) - return False + def scroll_to_end(self, ): + parent = self.stanzas_log_textview.get_parent() + buffer_ = self.stanzas_log_textview.get_buffer() + end_mark = buffer_.get_mark('end') + if not end_mark: + return False + self.stanzas_log_textview.scroll_to_mark(end_mark, 0, True, 0, 1) + adjustment = parent.get_hadjustment() + adjustment.set_value(0) + return False - def print_stanza(self, stanza, kind): - # kind must be 'incoming' or 'outgoing' - if not self.enabled: - return - if not stanza: - return + def print_stanza(self, stanza, kind): + # kind must be 'incoming' or 'outgoing' + if not self.enabled: + return + if not stanza: + return - buffer = self.stanzas_log_textview.get_buffer() - at_the_end = False - end_iter = buffer.get_end_iter() - end_rect = self.stanzas_log_textview.get_iter_location(end_iter) - visible_rect = self.stanzas_log_textview.get_visible_rect() - if end_rect.y <= (visible_rect.y + visible_rect.height): - at_the_end = True - end_iter = buffer.get_end_iter() - if kind == 'incoming': - buffer.insert_with_tags_by_name(end_iter, '\n', - 'in_comment') - elif kind == 'outgoing': - buffer.insert_with_tags_by_name(end_iter, '\n', - 'out_comment') - end_iter = buffer.get_end_iter() - buffer.insert_with_tags_by_name(end_iter, stanza.replace('><', '>\n<') + \ - '\n\n', kind) - if at_the_end: - gobject.idle_add(self.scroll_to_end) + buffer = self.stanzas_log_textview.get_buffer() + at_the_end = False + end_iter = buffer.get_end_iter() + end_rect = self.stanzas_log_textview.get_iter_location(end_iter) + visible_rect = self.stanzas_log_textview.get_visible_rect() + if end_rect.y <= (visible_rect.y + visible_rect.height): + at_the_end = True + end_iter = buffer.get_end_iter() + if kind == 'incoming': + buffer.insert_with_tags_by_name(end_iter, '\n', + 'in_comment') + elif kind == 'outgoing': + buffer.insert_with_tags_by_name(end_iter, '\n', + 'out_comment') + end_iter = buffer.get_end_iter() + buffer.insert_with_tags_by_name(end_iter, stanza.replace('><', '>\n<') + \ + '\n\n', kind) + if at_the_end: + gobject.idle_add(self.scroll_to_end) - def on_send_button_clicked(self, widget): - if gajim.connections[self.account].connected <= 1: - #if offline or connecting - ErrorDialog(_('Connection not available'), - _('Please make sure you are connected with "%s".') % self.account) - return - begin_iter, end_iter = self.input_tv_buffer.get_bounds() - stanza = self.input_tv_buffer.get_text(begin_iter, end_iter).decode( - 'utf-8') - if stanza: - gajim.connections[self.account].send_stanza(stanza) - self.input_tv_buffer.set_text('') # we sent ok, clear the textview + def on_send_button_clicked(self, widget): + if gajim.connections[self.account].connected <= 1: + #if offline or connecting + ErrorDialog(_('Connection not available'), + _('Please make sure you are connected with "%s".') % self.account) + return + begin_iter, end_iter = self.input_tv_buffer.get_bounds() + stanza = self.input_tv_buffer.get_text(begin_iter, end_iter).decode( + 'utf-8') + if stanza: + gajim.connections[self.account].send_stanza(stanza) + self.input_tv_buffer.set_text('') # we sent ok, clear the textview - def on_presence_button_clicked(self, widget): - self.input_tv_buffer.set_text( - '' - '') + def on_presence_button_clicked(self, widget): + self.input_tv_buffer.set_text( + '' + '') - def on_iq_button_clicked(self, widget): - self.input_tv_buffer.set_text( - '') + def on_iq_button_clicked(self, widget): + self.input_tv_buffer.set_text( + '') - def on_message_button_clicked(self, widget): - self.input_tv_buffer.set_text( - '') + def on_message_button_clicked(self, widget): + self.input_tv_buffer.set_text( + '') - def on_expander_activate(self, widget): - if not widget.get_expanded(): # it's the opposite! - # it's expanded!! - self.input_textview.grab_focus() + def on_expander_activate(self, widget): + if not widget.get_expanded(): # it's the opposite! + # it's expanded!! + self.input_textview.grab_focus() #Action that can be done with an incoming list of contacts TRANSLATED_ACTION = {'add': _('add'), 'modify': _('modify'), - 'remove': _('remove')} + 'remove': _('remove')} class RosterItemExchangeWindow: - """ - Windows used when someone send you a exchange contact suggestion - """ + """ + Windows used when someone send you a exchange contact suggestion + """ - def __init__(self, account, action, exchange_list, jid_from, - message_body=None): - self.account = account - self.action = action - self.exchange_list = exchange_list - self.message_body = message_body - self.jid_from = jid_from + def __init__(self, account, action, exchange_list, jid_from, + message_body=None): + self.account = account + self.action = action + self.exchange_list = exchange_list + self.message_body = message_body + self.jid_from = jid_from - show_dialog = False + show_dialog = False - # Connect to glade - self.xml = gtkgui_helpers.get_glade('roster_item_exchange_window.glade') - self.window = self.xml.get_widget('roster_item_exchange_window') + # Connect to glade + self.xml = gtkgui_helpers.get_glade('roster_item_exchange_window.glade') + self.window = self.xml.get_widget('roster_item_exchange_window') - # Add Widgets. - for widget_to_add in ['accept_button_label', 'type_label', - 'body_scrolledwindow', 'body_textview', 'items_list_treeview']: - self.__dict__[widget_to_add] = self.xml.get_widget(widget_to_add) + # Add Widgets. + for widget_to_add in ['accept_button_label', 'type_label', + 'body_scrolledwindow', 'body_textview', 'items_list_treeview']: + self.__dict__[widget_to_add] = self.xml.get_widget(widget_to_add) - # Set labels - # self.action can be 'add', 'modify' or 'remove' - self.type_label.set_label( - _('%(jid)s would like you to %(action)s some contacts ' - 'in your roster.') % {'jid': jid_from, - 'action': TRANSLATED_ACTION[self.action]}) - if message_body: - buffer_ = self.body_textview.get_buffer() - buffer_.set_text(self.message_body) - else: - self.body_scrolledwindow.hide() - # Treeview - model = gtk.ListStore(bool, str, str, str, str) - self.items_list_treeview.set_model(model) - # columns - renderer1 = gtk.CellRendererToggle() - renderer1.set_property('activatable', True) - renderer1.connect('toggled', self.toggled_callback) - if self.action == 'add': - title = _('Add') - elif self.action == 'modify': - title = _('Modify') - elif self.action == 'delete': - title = _('Delete') - self.items_list_treeview.insert_column_with_attributes(-1, title, - renderer1, active=0) - renderer2 = gtk.CellRendererText() - self.items_list_treeview.insert_column_with_attributes(-1, _('Jabber ID'), - renderer2, text=1) - renderer3 = gtk.CellRendererText() - self.items_list_treeview.insert_column_with_attributes(-1, _('Name'), - renderer3, text=2) - renderer4 = gtk.CellRendererText() - self.items_list_treeview.insert_column_with_attributes(-1, _('Groups'), - renderer4, text=3) + # Set labels + # self.action can be 'add', 'modify' or 'remove' + self.type_label.set_label( + _('%(jid)s would like you to %(action)s some contacts ' + 'in your roster.') % {'jid': jid_from, + 'action': TRANSLATED_ACTION[self.action]}) + if message_body: + buffer_ = self.body_textview.get_buffer() + buffer_.set_text(self.message_body) + else: + self.body_scrolledwindow.hide() + # Treeview + model = gtk.ListStore(bool, str, str, str, str) + self.items_list_treeview.set_model(model) + # columns + renderer1 = gtk.CellRendererToggle() + renderer1.set_property('activatable', True) + renderer1.connect('toggled', self.toggled_callback) + if self.action == 'add': + title = _('Add') + elif self.action == 'modify': + title = _('Modify') + elif self.action == 'delete': + title = _('Delete') + self.items_list_treeview.insert_column_with_attributes(-1, title, + renderer1, active=0) + renderer2 = gtk.CellRendererText() + self.items_list_treeview.insert_column_with_attributes(-1, _('Jabber ID'), + renderer2, text=1) + renderer3 = gtk.CellRendererText() + self.items_list_treeview.insert_column_with_attributes(-1, _('Name'), + renderer3, text=2) + renderer4 = gtk.CellRendererText() + self.items_list_treeview.insert_column_with_attributes(-1, _('Groups'), + renderer4, text=3) - # Init contacts - model = self.items_list_treeview.get_model() - model.clear() + # Init contacts + model = self.items_list_treeview.get_model() + model.clear() - if action == 'add': - for jid in self.exchange_list: - groups = '' - is_in_roster = True - contact = gajim.contacts.get_contact_with_highest_priority( - self.account, jid) - if not contact: - is_in_roster = False - name = self.exchange_list[jid][0] - num_list = len(self.exchange_list[jid][1]) - current = 0 - for group in self.exchange_list[jid][1]: - current += 1 - if contact and not group in contact.groups: - is_in_roster = False - if current == num_list: - groups = groups + group - else: - groups = groups + group + ', ' - if not is_in_roster: - show_dialog = True - iter_ = model.append() - model.set(iter_, 0, True, 1, jid, 2, name, 3, groups) + if action == 'add': + for jid in self.exchange_list: + groups = '' + is_in_roster = True + contact = gajim.contacts.get_contact_with_highest_priority( + self.account, jid) + if not contact: + is_in_roster = False + name = self.exchange_list[jid][0] + num_list = len(self.exchange_list[jid][1]) + current = 0 + for group in self.exchange_list[jid][1]: + current += 1 + if contact and not group in contact.groups: + is_in_roster = False + if current == num_list: + groups = groups + group + else: + groups = groups + group + ', ' + if not is_in_roster: + show_dialog = True + iter_ = model.append() + model.set(iter_, 0, True, 1, jid, 2, name, 3, groups) - # Change label for accept_button to action name instead of 'OK'. - self.accept_button_label.set_label(_('Add')) - elif action == 'modify': - for jid in self.exchange_list: - groups = '' - is_in_roster = True - is_right = True - contact = gajim.contacts.get_contact_with_highest_priority( - self.account, jid) - name = self.exchange_list[jid][0] - if not contact: - is_in_roster = False - is_right = False - else: - if name != contact.name: - is_right = False - num_list = len(self.exchange_list[jid][1]) - current = 0 - for group in self.exchange_list[jid][1]: - current += 1 - if contact and not group in contact.groups: - is_right = False - if current == num_list: - groups = groups + group - else: - groups = groups + group + ', ' - if not is_right and is_in_roster: - show_dialog = True - iter_ = model.append() - model.set(iter_, 0, True, 1, jid, 2, name, 3, groups) + # Change label for accept_button to action name instead of 'OK'. + self.accept_button_label.set_label(_('Add')) + elif action == 'modify': + for jid in self.exchange_list: + groups = '' + is_in_roster = True + is_right = True + contact = gajim.contacts.get_contact_with_highest_priority( + self.account, jid) + name = self.exchange_list[jid][0] + if not contact: + is_in_roster = False + is_right = False + else: + if name != contact.name: + is_right = False + num_list = len(self.exchange_list[jid][1]) + current = 0 + for group in self.exchange_list[jid][1]: + current += 1 + if contact and not group in contact.groups: + is_right = False + if current == num_list: + groups = groups + group + else: + groups = groups + group + ', ' + if not is_right and is_in_roster: + show_dialog = True + iter_ = model.append() + model.set(iter_, 0, True, 1, jid, 2, name, 3, groups) - # Change label for accept_button to action name instead of 'OK'. - self.accept_button_label.set_label(_('Modify')) - elif action == 'delete': - for jid in self.exchange_list: - groups = '' - is_in_roster = True - contact = gajim.contacts.get_contact_with_highest_priority( - self.account, jid) - name = self.exchange_list[jid][0] - if not contact: - is_in_roster = False - num_list = len(self.exchange_list[jid][1]) - current = 0 - for group in self.exchange_list[jid][1]: - current += 1 - if current == num_list: - groups = groups + group - else: - groups = groups + group + ', ' - if is_in_roster: - show_dialog = True - iter_ = model.append() - model.set(iter_, 0, True, 1, jid, 2, name, 3, groups) + # Change label for accept_button to action name instead of 'OK'. + self.accept_button_label.set_label(_('Modify')) + elif action == 'delete': + for jid in self.exchange_list: + groups = '' + is_in_roster = True + contact = gajim.contacts.get_contact_with_highest_priority( + self.account, jid) + name = self.exchange_list[jid][0] + if not contact: + is_in_roster = False + num_list = len(self.exchange_list[jid][1]) + current = 0 + for group in self.exchange_list[jid][1]: + current += 1 + if current == num_list: + groups = groups + group + else: + groups = groups + group + ', ' + if is_in_roster: + show_dialog = True + iter_ = model.append() + model.set(iter_, 0, True, 1, jid, 2, name, 3, groups) - # Change label for accept_button to action name instead of 'OK'. - self.accept_button_label.set_label(_('Delete')) + # Change label for accept_button to action name instead of 'OK'. + self.accept_button_label.set_label(_('Delete')) - if show_dialog: - self.window.show_all() - self.xml.signal_autoconnect(self) + if show_dialog: + self.window.show_all() + self.xml.signal_autoconnect(self) - def toggled_callback(self, cell, path): - model = self.items_list_treeview.get_model() - iter_ = model.get_iter(path) - model[iter_][0] = not cell.get_active() + def toggled_callback(self, cell, path): + model = self.items_list_treeview.get_model() + iter_ = model.get_iter(path) + model[iter_][0] = not cell.get_active() - def on_accept_button_clicked(self, widget): - model = self.items_list_treeview.get_model() - iter_ = model.get_iter_root() - if self.action == 'add': - a = 0 - while iter_: - if model[iter_][0]: - a+=1 - # it is selected - #remote_jid = model[iter_][1].decode('utf-8') - message = _('%s suggested me to add you in my roster.' - % self.jid_from) - # keep same groups and same nickname - groups = model[iter_][3].split(', ') - if groups == ['']: - groups = [] - jid = model[iter_][1].decode('utf-8') - if gajim.jid_is_transport(self.jid_from): - gajim.connections[self.account].automatically_added.append( - jid) - gajim.interface.roster.req_sub(self, jid, message, - self.account, groups=groups, nickname=model[iter_][2], - auto_auth=True) - iter_ = model.iter_next(iter_) - InformationDialog(_('Added %s contacts') % str(a)) - elif self.action == 'modify': - a = 0 - while iter_: - if model[iter_][0]: - a+=1 - # it is selected - jid = model[iter_][1].decode('utf-8') - # keep same groups and same nickname - groups = model[iter_][3].split(', ') - if groups == ['']: - groups = [] - for u in gajim.contacts.get_contact(self.account, jid): - u.name = model[iter_][2] - gajim.connections[self.account].update_contact(jid, - model[iter_][2], groups) - self.draw_contact(jid, account) - # Update opened chat - ctrl = gajim.interface.msg_win_mgr.get_control(jid, self.account) - if ctrl: - ctrl.update_ui() - win = gajim.interface.msg_win_mgr.get_window(jid, - self.account) - win.redraw_tab(ctrl) - win.show_title() - iter_ = model.iter_next(iter_) - elif self.action == 'delete': - a = 0 - while iter_: - if model[iter_][0]: - a+=1 - # it is selected - jid = model[iter_][1].decode('utf-8') - gajim.connections[self.account].unsubscribe(jid) - gajim.interface.roster.remove_contact(jid, self.account) - gajim.contacts.remove_jid(self.account, jid) - iter_ = model.iter_next(iter_) - InformationDialog(_('Removed %s contacts') % str(a)) - self.window.destroy() + def on_accept_button_clicked(self, widget): + model = self.items_list_treeview.get_model() + iter_ = model.get_iter_root() + if self.action == 'add': + a = 0 + while iter_: + if model[iter_][0]: + a+=1 + # it is selected + #remote_jid = model[iter_][1].decode('utf-8') + message = _('%s suggested me to add you in my roster.' + % self.jid_from) + # keep same groups and same nickname + groups = model[iter_][3].split(', ') + if groups == ['']: + groups = [] + jid = model[iter_][1].decode('utf-8') + if gajim.jid_is_transport(self.jid_from): + gajim.connections[self.account].automatically_added.append( + jid) + gajim.interface.roster.req_sub(self, jid, message, + self.account, groups=groups, nickname=model[iter_][2], + auto_auth=True) + iter_ = model.iter_next(iter_) + InformationDialog(_('Added %s contacts') % str(a)) + elif self.action == 'modify': + a = 0 + while iter_: + if model[iter_][0]: + a+=1 + # it is selected + jid = model[iter_][1].decode('utf-8') + # keep same groups and same nickname + groups = model[iter_][3].split(', ') + if groups == ['']: + groups = [] + for u in gajim.contacts.get_contact(self.account, jid): + u.name = model[iter_][2] + gajim.connections[self.account].update_contact(jid, + model[iter_][2], groups) + self.draw_contact(jid, account) + # Update opened chat + ctrl = gajim.interface.msg_win_mgr.get_control(jid, self.account) + if ctrl: + ctrl.update_ui() + win = gajim.interface.msg_win_mgr.get_window(jid, + self.account) + win.redraw_tab(ctrl) + win.show_title() + iter_ = model.iter_next(iter_) + elif self.action == 'delete': + a = 0 + while iter_: + if model[iter_][0]: + a+=1 + # it is selected + jid = model[iter_][1].decode('utf-8') + gajim.connections[self.account].unsubscribe(jid) + gajim.interface.roster.remove_contact(jid, self.account) + gajim.contacts.remove_jid(self.account, jid) + iter_ = model.iter_next(iter_) + InformationDialog(_('Removed %s contacts') % str(a)) + self.window.destroy() - def on_cancel_button_clicked(self, widget): - self.window.destroy() + def on_cancel_button_clicked(self, widget): + self.window.destroy() class ItemArchivingPreferencesWindow: - otr_name = ('approve', 'concede', 'forbid', 'oppose', 'prefer', 'require') - otr_index = dict([(j, i) for i, j in enumerate(otr_name)]) - save_name = ('body', 'false', 'message', 'stream') - save_index = dict([(j, i) for i, j in enumerate(save_name)]) + otr_name = ('approve', 'concede', 'forbid', 'oppose', 'prefer', 'require') + otr_index = dict([(j, i) for i, j in enumerate(otr_name)]) + save_name = ('body', 'false', 'message', 'stream') + save_index = dict([(j, i) for i, j in enumerate(save_name)]) - def __init__(self, account, item): - self.account = account - self.item = item - if self.item and self.item != 'Default': - self.item_config = gajim.connections[self.account].items[self.item] - else: - self.item_config = gajim.connections[self.account].default - print self.item, self.item_config - self.waiting = None + def __init__(self, account, item): + self.account = account + self.item = item + if self.item and self.item != 'Default': + self.item_config = gajim.connections[self.account].items[self.item] + else: + self.item_config = gajim.connections[self.account].default + print self.item, self.item_config + self.waiting = None - # Connect to glade - self.xml = gtkgui_helpers.get_glade( - 'item_archiving_preferences_window.glade') - self.window = self.xml.get_widget('item_archiving_preferences_window') + # Connect to glade + self.xml = gtkgui_helpers.get_glade( + 'item_archiving_preferences_window.glade') + self.window = self.xml.get_widget('item_archiving_preferences_window') - # Add Widgets - for widget_to_add in ('jid_entry', 'expire_entry', 'otr_combobox', - 'save_combobox', 'cancel_button', 'ok_button', 'progressbar'): - self.__dict__[widget_to_add] = self.xml.get_widget(widget_to_add) + # Add Widgets + for widget_to_add in ('jid_entry', 'expire_entry', 'otr_combobox', + 'save_combobox', 'cancel_button', 'ok_button', 'progressbar'): + self.__dict__[widget_to_add] = self.xml.get_widget(widget_to_add) - if self.item: - self.jid_entry.set_text(self.item) - expire_value = self.item_config['expire'] or '' - self.otr_combobox.set_active(self.otr_index[self.item_config['otr']]) - self.save_combobox.set_active( - self.save_index[self.item_config['save']]) - self.expire_entry.set_text(expire_value) + if self.item: + self.jid_entry.set_text(self.item) + expire_value = self.item_config['expire'] or '' + self.otr_combobox.set_active(self.otr_index[self.item_config['otr']]) + self.save_combobox.set_active( + self.save_index[self.item_config['save']]) + self.expire_entry.set_text(expire_value) - self.window.set_title(_('Archiving Preferences for %s') % self.account) + self.window.set_title(_('Archiving Preferences for %s') % self.account) - self.window.show_all() - self.progressbar.hide() - self.xml.signal_autoconnect(self) + self.window.show_all() + self.progressbar.hide() + self.xml.signal_autoconnect(self) - def update_progressbar(self): - if self.waiting: - self.progressbar.pulse() - return True - return False + def update_progressbar(self): + if self.waiting: + self.progressbar.pulse() + return True + return False - def on_otr_combobox_changed(self, widget): - otr = self.otr_name[self.otr_combobox.get_active()] - if otr == 'require': - self.save_combobox.set_active(self.save_index['false']) + def on_otr_combobox_changed(self, widget): + otr = self.otr_name[self.otr_combobox.get_active()] + if otr == 'require': + self.save_combobox.set_active(self.save_index['false']) - def on_ok_button_clicked(self, widget): - # Return directly if operation in progress - if self.waiting: - return + def on_ok_button_clicked(self, widget): + # Return directly if operation in progress + if self.waiting: + return - item = self.jid_entry.get_text() - otr = self.otr_name[self.otr_combobox.get_active()] - save = self.save_name[self.save_combobox.get_active()] - expire = self.expire_entry.get_text() + item = self.jid_entry.get_text() + otr = self.otr_name[self.otr_combobox.get_active()] + save = self.save_name[self.save_combobox.get_active()] + expire = self.expire_entry.get_text() - if self.item != 'Default': - try: - item = helpers.parse_jid(item) - except helpers.InvalidFormat, s: - pritext = _('Invalid User ID') - ErrorDialog(pritext, str(s)) - return + if self.item != 'Default': + try: + item = helpers.parse_jid(item) + except helpers.InvalidFormat, s: + pritext = _('Invalid User ID') + ErrorDialog(pritext, str(s)) + return - if expire: - try: - if int(expire) < 0 or str(int(expire)) != expire: - raise ValueError - except ValueError: - pritext = _('Invalid expire value') - sectext = _('Expire must be a valid positive integer.') - ErrorDialog(pritext, sectext) - return + if expire: + try: + if int(expire) < 0 or str(int(expire)) != expire: + raise ValueError + except ValueError: + pritext = _('Invalid expire value') + sectext = _('Expire must be a valid positive integer.') + ErrorDialog(pritext, sectext) + return - if not (item == self.item and expire == self.item_config['expire'] and - otr == self.item_config['otr'] and save == self.item_config['save']): - if not self.item or self.item == item: - if self.item == 'Default': - self.waiting = 'default' - gajim.connections[self.account].set_default( - otr, save, expire) - else: - self.waiting = 'item' - gajim.connections[self.account].append_or_update_item( - item, otr, save, expire) - else: - self.waiting = 'item' - gajim.connections[self.account].append_or_update_item( - item, otr, save, expire) - gajim.connections[self.account].remove_item(self.item) - self.launch_progressbar() - #self.window.destroy() + if not (item == self.item and expire == self.item_config['expire'] and + otr == self.item_config['otr'] and save == self.item_config['save']): + if not self.item or self.item == item: + if self.item == 'Default': + self.waiting = 'default' + gajim.connections[self.account].set_default( + otr, save, expire) + else: + self.waiting = 'item' + gajim.connections[self.account].append_or_update_item( + item, otr, save, expire) + else: + self.waiting = 'item' + gajim.connections[self.account].append_or_update_item( + item, otr, save, expire) + gajim.connections[self.account].remove_item(self.item) + self.launch_progressbar() + #self.window.destroy() - def on_cancel_button_clicked(self, widget): - self.window.destroy() + def on_cancel_button_clicked(self, widget): + self.window.destroy() - def on_item_archiving_preferences_window_destroy(self, widget): - if self.item: - key_name = 'edit_item_archiving_preferences_%s' % self.item - else: - key_name = 'new_item_archiving_preferences' - if key_name in gajim.interface.instances[self.account]: - del gajim.interface.instances[self.account][key_name] + def on_item_archiving_preferences_window_destroy(self, widget): + if self.item: + key_name = 'edit_item_archiving_preferences_%s' % self.item + else: + key_name = 'new_item_archiving_preferences' + if key_name in gajim.interface.instances[self.account]: + del gajim.interface.instances[self.account][key_name] - def launch_progressbar(self): - self.progressbar.show() - self.update_progressbar_timeout_id = gobject.timeout_add( - 100, self.update_progressbar) + def launch_progressbar(self): + self.progressbar.show() + self.update_progressbar_timeout_id = gobject.timeout_add( + 100, self.update_progressbar) - def response_arrived(self, data): - if self.waiting: - self.window.destroy() + def response_arrived(self, data): + if self.waiting: + self.window.destroy() - def error_arrived(self, error): - if self.waiting: - self.waiting = None - self.progressbar.hide() - pritext = _('There is an error with the form') - sectext = error - ErrorDialog(pritext, sectext) + def error_arrived(self, error): + if self.waiting: + self.waiting = None + self.progressbar.hide() + pritext = _('There is an error with the form') + sectext = error + ErrorDialog(pritext, sectext) class ArchivingPreferencesWindow: - auto_name = ('false', 'true') - auto_index = dict([(j, i) for i, j in enumerate(auto_name)]) - method_foo_name = ('prefer', 'concede', 'forbid') - method_foo_index = dict([(j, i) for i, j in enumerate(method_foo_name)]) + auto_name = ('false', 'true') + auto_index = dict([(j, i) for i, j in enumerate(auto_name)]) + method_foo_name = ('prefer', 'concede', 'forbid') + method_foo_index = dict([(j, i) for i, j in enumerate(method_foo_name)]) - def __init__(self, account): - self.account = account - self.waiting = [] + def __init__(self, account): + self.account = account + self.waiting = [] - # Connect to glade - self.xml = gtkgui_helpers.get_glade('archiving_preferences_window.glade') - self.window = self.xml.get_widget('archiving_preferences_window') + # Connect to glade + self.xml = gtkgui_helpers.get_glade('archiving_preferences_window.glade') + self.window = self.xml.get_widget('archiving_preferences_window') - # Add Widgets - for widget_to_add in ('auto_combobox', 'method_auto_combobox', - 'method_local_combobox', 'method_manual_combobox', 'close_button', - 'item_treeview', 'item_notebook', 'otr_combobox', 'save_combobox', - 'expire_entry', 'remove_button', 'edit_button'): - self.__dict__[widget_to_add] = self.xml.get_widget(widget_to_add) + # Add Widgets + for widget_to_add in ('auto_combobox', 'method_auto_combobox', + 'method_local_combobox', 'method_manual_combobox', 'close_button', + 'item_treeview', 'item_notebook', 'otr_combobox', 'save_combobox', + 'expire_entry', 'remove_button', 'edit_button'): + self.__dict__[widget_to_add] = self.xml.get_widget(widget_to_add) - self.auto_combobox.set_active( - self.auto_index[gajim.connections[self.account].auto]) - self.method_auto_combobox.set_active( - self.method_foo_index[gajim.connections[self.account].method_auto]) - self.method_local_combobox.set_active( - self.method_foo_index[gajim.connections[self.account].method_local]) - self.method_manual_combobox.set_active( - self.method_foo_index[gajim.connections[self.account].method_manual]) + self.auto_combobox.set_active( + self.auto_index[gajim.connections[self.account].auto]) + self.method_auto_combobox.set_active( + self.method_foo_index[gajim.connections[self.account].method_auto]) + self.method_local_combobox.set_active( + self.method_foo_index[gajim.connections[self.account].method_local]) + self.method_manual_combobox.set_active( + self.method_foo_index[gajim.connections[self.account].method_manual]) - model = gtk.ListStore(str, str, str, str) - self.item_treeview.set_model(model) - col = gtk.TreeViewColumn('jid') - self.item_treeview.append_column(col) - renderer = gtk.CellRendererText() - col.pack_start(renderer, True) - col.set_attributes(renderer, text=0) + model = gtk.ListStore(str, str, str, str) + self.item_treeview.set_model(model) + col = gtk.TreeViewColumn('jid') + self.item_treeview.append_column(col) + renderer = gtk.CellRendererText() + col.pack_start(renderer, True) + col.set_attributes(renderer, text=0) - col = gtk.TreeViewColumn('expire') - col.pack_start(renderer, True) - col.set_attributes(renderer, text=1) - self.item_treeview.append_column(col) + col = gtk.TreeViewColumn('expire') + col.pack_start(renderer, True) + col.set_attributes(renderer, text=1) + self.item_treeview.append_column(col) - col = gtk.TreeViewColumn('otr') - col.pack_start(renderer, True) - col.set_attributes(renderer, text=2) - self.item_treeview.append_column(col) + col = gtk.TreeViewColumn('otr') + col.pack_start(renderer, True) + col.set_attributes(renderer, text=2) + self.item_treeview.append_column(col) - col = gtk.TreeViewColumn('save') - col.pack_start(renderer, True) - col.set_attributes(renderer, text=3) - self.item_treeview.append_column(col) + col = gtk.TreeViewColumn('save') + col.pack_start(renderer, True) + col.set_attributes(renderer, text=3) + self.item_treeview.append_column(col) - self.fill_items() + self.fill_items() - self.current_item = None + self.current_item = None - def sort_items(model, iter1, iter2): - item1 = model.get_value(iter1, 0) - item2 = model.get_value(iter2, 0) - if item1 == 'Default': - return -1 - if item2 == 'Default': - return 1 - if '@' in item1: - if '@' not in item2: - return 1 - elif '@' in item2: - return -1 - if item1 < item2: - return -1 - if item1 > item2: - return 1 - # item1 == item2 ? WTF? - return 0 + def sort_items(model, iter1, iter2): + item1 = model.get_value(iter1, 0) + item2 = model.get_value(iter2, 0) + if item1 == 'Default': + return -1 + if item2 == 'Default': + return 1 + if '@' in item1: + if '@' not in item2: + return 1 + elif '@' in item2: + return -1 + if item1 < item2: + return -1 + if item1 > item2: + return 1 + # item1 == item2 ? WTF? + return 0 - model.set_sort_column_id(0, gtk.SORT_ASCENDING) - model.set_sort_func(0, sort_items) + model.set_sort_column_id(0, gtk.SORT_ASCENDING) + model.set_sort_func(0, sort_items) - self.remove_button.set_sensitive(False) - self.edit_button.set_sensitive(False) + self.remove_button.set_sensitive(False) + self.edit_button.set_sensitive(False) - self.window.set_title(_('Archiving Preferences for %s') % self.account) + self.window.set_title(_('Archiving Preferences for %s') % self.account) - self.window.show_all() + self.window.show_all() - self.xml.signal_autoconnect(self) + self.xml.signal_autoconnect(self) - def on_add_item_button_clicked(self, widget): - key_name = 'new_item_archiving_preferences' - if key_name in gajim.interface.instances[self.account]: - gajim.interface.instances[self.account][key_name].window.present() - else: - gajim.interface.instances[self.account][key_name] = \ - ItemArchivingPreferencesWindow(self.account, '') + def on_add_item_button_clicked(self, widget): + key_name = 'new_item_archiving_preferences' + if key_name in gajim.interface.instances[self.account]: + gajim.interface.instances[self.account][key_name].window.present() + else: + gajim.interface.instances[self.account][key_name] = \ + ItemArchivingPreferencesWindow(self.account, '') - def on_remove_item_button_clicked(self, widget): - if not self.current_item: - return + def on_remove_item_button_clicked(self, widget): + if not self.current_item: + return - self.waiting.append('itemremove') - sel = self.item_treeview.get_selection() - (model, iter_) = sel.get_selected() - gajim.connections[self.account].remove_item(model[iter_][0]) - model.remove(iter_) - self.remove_button.set_sensitive(False) - self.edit_button.set_sensitive(False) + self.waiting.append('itemremove') + sel = self.item_treeview.get_selection() + (model, iter_) = sel.get_selected() + gajim.connections[self.account].remove_item(model[iter_][0]) + model.remove(iter_) + self.remove_button.set_sensitive(False) + self.edit_button.set_sensitive(False) - def on_edit_item_button_clicked(self, widget): - if not self.current_item: - print 'there is no current item' - return + def on_edit_item_button_clicked(self, widget): + if not self.current_item: + print 'there is no current item' + return - key_name = 'edit_item_archiving_preferences_%s' % self.current_item - if key_name in gajim.interface.instances[self.account]: - gajim.interface.instances[self.account][key_name].window.present() - else: - gajim.interface.instances[self.account][key_name] = \ - ItemArchivingPreferencesWindow(self.account, self.current_item) + key_name = 'edit_item_archiving_preferences_%s' % self.current_item + if key_name in gajim.interface.instances[self.account]: + gajim.interface.instances[self.account][key_name].window.present() + else: + gajim.interface.instances[self.account][key_name] = \ + ItemArchivingPreferencesWindow(self.account, self.current_item) - def on_item_treeview_cursor_changed(self, widget): - sel = self.item_treeview.get_selection() - (model, iter_) = sel.get_selected() - item = None - if iter_: - item = model[iter_][0] - if self.current_item and self.current_item == item: - return + def on_item_treeview_cursor_changed(self, widget): + sel = self.item_treeview.get_selection() + (model, iter_) = sel.get_selected() + item = None + if iter_: + item = model[iter_][0] + if self.current_item and self.current_item == item: + return - self.current_item = item - if self.current_item == 'Default': - self.remove_button.set_sensitive(False) - self.edit_button.set_sensitive(True) - elif self.current_item: - self.remove_button.set_sensitive(True) - self.edit_button.set_sensitive(True) - else: - self.remove_button.set_sensitive(False) - self.edit_button.set_sensitive(False) + self.current_item = item + if self.current_item == 'Default': + self.remove_button.set_sensitive(False) + self.edit_button.set_sensitive(True) + elif self.current_item: + self.remove_button.set_sensitive(True) + self.edit_button.set_sensitive(True) + else: + self.remove_button.set_sensitive(False) + self.edit_button.set_sensitive(False) - def on_auto_combobox_changed(self, widget): - save = self.auto_name[widget.get_active()] - gajim.connections[self.account].set_auto(save) + def on_auto_combobox_changed(self, widget): + save = self.auto_name[widget.get_active()] + gajim.connections[self.account].set_auto(save) - def on_method_foo_combobox_changed(self, widget): - # We retrieve method type from widget name - # ('foo' in 'method_foo_combobox') - method_type = widget.name.split('_')[1] - use = self.method_foo_name[widget.get_active()] - self.waiting.append('method_%s' % method_type) - gajim.connections[self.account].set_method(method_type, use) + def on_method_foo_combobox_changed(self, widget): + # We retrieve method type from widget name + # ('foo' in 'method_foo_combobox') + method_type = widget.name.split('_')[1] + use = self.method_foo_name[widget.get_active()] + self.waiting.append('method_%s' % method_type) + gajim.connections[self.account].set_method(method_type, use) - def get_child_window(self): - edit_key_name = 'edit_item_archiving_preferences_%s' % \ - self.current_item - new_key_name = 'new_item_archiving_preferences' + def get_child_window(self): + edit_key_name = 'edit_item_archiving_preferences_%s' % \ + self.current_item + new_key_name = 'new_item_archiving_preferences' - if edit_key_name in gajim.interface.instances[self.account]: - return gajim.interface.instances[self.account][edit_key_name] + if edit_key_name in gajim.interface.instances[self.account]: + return gajim.interface.instances[self.account][edit_key_name] - if new_key_name in gajim.interface.instances[self.account]: - return gajim.interface.instances[self.account][new_key_name] + if new_key_name in gajim.interface.instances[self.account]: + return gajim.interface.instances[self.account][new_key_name] - def archiving_changed(self, data): - if data[0] in ('auto', 'method_auto', 'method_local', 'method_manual'): - if data[0] in self.waiting: - self.waiting.remove(data[0]) - elif data[0] == 'default': - key_name = 'edit_item_archiving_preferences_%s' % \ - self.current_item - if key_name in gajim.interface.instances[self.account]: - gajim.interface.instances[self.account][key_name].\ - response_arrived(data[1:]) - self.fill_items(True) - elif data[0] == 'item': - child = self.get_child_window() - if child: - is_new = not child.item - child.response_arrived(data[1:]) - if is_new: - model = self.item_treeview.get_model() - model.append((data[1], data[2]['expire'], data[2]['otr'], - data[2]['save'])) - return - self.fill_items(True) - elif data[0] == 'itemremove' == self.waiting: - if data[0] in self.waiting: - self.waiting.remove(data[0]) - self.fill_items(True) + def archiving_changed(self, data): + if data[0] in ('auto', 'method_auto', 'method_local', 'method_manual'): + if data[0] in self.waiting: + self.waiting.remove(data[0]) + elif data[0] == 'default': + key_name = 'edit_item_archiving_preferences_%s' % \ + self.current_item + if key_name in gajim.interface.instances[self.account]: + gajim.interface.instances[self.account][key_name].\ + response_arrived(data[1:]) + self.fill_items(True) + elif data[0] == 'item': + child = self.get_child_window() + if child: + is_new = not child.item + child.response_arrived(data[1:]) + if is_new: + model = self.item_treeview.get_model() + model.append((data[1], data[2]['expire'], data[2]['otr'], + data[2]['save'])) + return + self.fill_items(True) + elif data[0] == 'itemremove' == self.waiting: + if data[0] in self.waiting: + self.waiting.remove(data[0]) + self.fill_items(True) - def fill_items(self, clear=False): - model = self.item_treeview.get_model() - if clear: - model.clear() - default_config = gajim.connections[self.account].default - expire_value = default_config['expire'] or '' - model.append(('Default', expire_value, - default_config['otr'], default_config['save'])) - for item, item_config in \ - gajim.connections[self.account].items.items(): - expire_value = item_config['expire'] or '' - model.append((item, expire_value, item_config['otr'], - item_config['save'])) + def fill_items(self, clear=False): + model = self.item_treeview.get_model() + if clear: + model.clear() + default_config = gajim.connections[self.account].default + expire_value = default_config['expire'] or '' + model.append(('Default', expire_value, + default_config['otr'], default_config['save'])) + for item, item_config in \ + gajim.connections[self.account].items.items(): + expire_value = item_config['expire'] or '' + model.append((item, expire_value, item_config['otr'], + item_config['save'])) - def archiving_error(self, error): - if self.waiting: - pritext = _('There is an error') - sectext = error - ErrorDialog(pritext, sectext) - self.waiting.pop() - else: - child = self.get_child_window() - if child: - child.error_arrived(error) - print error + def archiving_error(self, error): + if self.waiting: + pritext = _('There is an error') + sectext = error + ErrorDialog(pritext, sectext) + self.waiting.pop() + else: + child = self.get_child_window() + if child: + child.error_arrived(error) + print error - def on_close_button_clicked(self, widget): - if not self.waiting: - self.window.destroy() + def on_close_button_clicked(self, widget): + if not self.waiting: + self.window.destroy() - def on_archiving_preferences_window_destroy(self, widget): - if 'archiving_preferences' in gajim.interface.instances[self.account]: - del gajim.interface.instances[self.account]['archiving_preferences'] + def on_archiving_preferences_window_destroy(self, widget): + if 'archiving_preferences' in gajim.interface.instances[self.account]: + del gajim.interface.instances[self.account]['archiving_preferences'] class PrivacyListWindow: - """ - Window that is used for creating NEW or EDITING already there privacy lists - """ + """ + Window that is used for creating NEW or EDITING already there privacy lists + """ - def __init__(self, account, privacy_list_name, action): - '''action is 'EDIT' or 'NEW' depending on if we create a new priv list - or edit an already existing one''' - self.account = account - self.privacy_list_name = privacy_list_name + def __init__(self, account, privacy_list_name, action): + '''action is 'EDIT' or 'NEW' depending on if we create a new priv list + or edit an already existing one''' + self.account = account + self.privacy_list_name = privacy_list_name - # Dicts and Default Values - self.active_rule = '' - self.global_rules = {} - self.list_of_groups = {} + # Dicts and Default Values + self.active_rule = '' + self.global_rules = {} + self.list_of_groups = {} - self.max_order = 0 + self.max_order = 0 - # Default Edit Values - self.edit_rule_type = 'jid' - self.allow_deny = 'allow' + # Default Edit Values + self.edit_rule_type = 'jid' + self.allow_deny = 'allow' - # Connect to glade - self.xml = gtkgui_helpers.get_glade('privacy_list_window.glade') - self.window = self.xml.get_widget('privacy_list_edit_window') + # Connect to glade + self.xml = gtkgui_helpers.get_glade('privacy_list_window.glade') + self.window = self.xml.get_widget('privacy_list_edit_window') - # Add Widgets + # Add Widgets - for widget_to_add in ('title_hbox', 'privacy_lists_title_label', - 'list_of_rules_label', 'add_edit_rule_label', 'delete_open_buttons_hbox', - 'privacy_list_active_checkbutton', 'privacy_list_default_checkbutton', - 'list_of_rules_combobox', 'delete_open_buttons_hbox', - 'delete_rule_button', 'open_rule_button', 'edit_allow_radiobutton', - 'edit_deny_radiobutton', 'edit_type_jabberid_radiobutton', - 'edit_type_jabberid_entry', 'edit_type_group_radiobutton', - 'edit_type_group_combobox', 'edit_type_subscription_radiobutton', - 'edit_type_subscription_combobox', 'edit_type_select_all_radiobutton', - 'edit_queries_send_checkbutton', 'edit_send_messages_checkbutton', - 'edit_view_status_checkbutton', 'edit_all_checkbutton', - 'edit_order_spinbutton', 'new_rule_button', 'save_rule_button', - 'privacy_list_refresh_button', 'privacy_list_close_button', - 'edit_send_status_checkbutton', 'add_edit_vbox', - 'privacy_list_active_checkbutton', 'privacy_list_default_checkbutton'): - self.__dict__[widget_to_add] = self.xml.get_widget(widget_to_add) + for widget_to_add in ('title_hbox', 'privacy_lists_title_label', + 'list_of_rules_label', 'add_edit_rule_label', 'delete_open_buttons_hbox', + 'privacy_list_active_checkbutton', 'privacy_list_default_checkbutton', + 'list_of_rules_combobox', 'delete_open_buttons_hbox', + 'delete_rule_button', 'open_rule_button', 'edit_allow_radiobutton', + 'edit_deny_radiobutton', 'edit_type_jabberid_radiobutton', + 'edit_type_jabberid_entry', 'edit_type_group_radiobutton', + 'edit_type_group_combobox', 'edit_type_subscription_radiobutton', + 'edit_type_subscription_combobox', 'edit_type_select_all_radiobutton', + 'edit_queries_send_checkbutton', 'edit_send_messages_checkbutton', + 'edit_view_status_checkbutton', 'edit_all_checkbutton', + 'edit_order_spinbutton', 'new_rule_button', 'save_rule_button', + 'privacy_list_refresh_button', 'privacy_list_close_button', + 'edit_send_status_checkbutton', 'add_edit_vbox', + 'privacy_list_active_checkbutton', 'privacy_list_default_checkbutton'): + self.__dict__[widget_to_add] = self.xml.get_widget(widget_to_add) - self.privacy_lists_title_label.set_label( - _('Privacy List %s') % \ - gobject.markup_escape_text(self.privacy_list_name)) + self.privacy_lists_title_label.set_label( + _('Privacy List %s') % \ + gobject.markup_escape_text(self.privacy_list_name)) - if len(gajim.connections) > 1: - title = _('Privacy List for %s') % self.account - else: - title = _('Privacy List') + if len(gajim.connections) > 1: + title = _('Privacy List for %s') % self.account + else: + title = _('Privacy List') - self.delete_rule_button.set_sensitive(False) - self.open_rule_button.set_sensitive(False) - self.privacy_list_active_checkbutton.set_sensitive(False) - self.privacy_list_default_checkbutton.set_sensitive(False) - self.list_of_rules_combobox.set_sensitive(False) + self.delete_rule_button.set_sensitive(False) + self.open_rule_button.set_sensitive(False) + self.privacy_list_active_checkbutton.set_sensitive(False) + self.privacy_list_default_checkbutton.set_sensitive(False) + self.list_of_rules_combobox.set_sensitive(False) - # set jabber id completion - jids_list_store = gtk.ListStore(gobject.TYPE_STRING) - for jid in gajim.contacts.get_jid_list(self.account): - jids_list_store.append([jid]) - jid_entry_completion = gtk.EntryCompletion() - jid_entry_completion.set_text_column(0) - jid_entry_completion.set_model(jids_list_store) - jid_entry_completion.set_popup_completion(True) - self.edit_type_jabberid_entry.set_completion(jid_entry_completion) - if action == 'EDIT': - self.refresh_rules() + # set jabber id completion + jids_list_store = gtk.ListStore(gobject.TYPE_STRING) + for jid in gajim.contacts.get_jid_list(self.account): + jids_list_store.append([jid]) + jid_entry_completion = gtk.EntryCompletion() + jid_entry_completion.set_text_column(0) + jid_entry_completion.set_model(jids_list_store) + jid_entry_completion.set_popup_completion(True) + self.edit_type_jabberid_entry.set_completion(jid_entry_completion) + if action == 'EDIT': + self.refresh_rules() - count = 0 - for group in gajim.groups[self.account]: - self.list_of_groups[group] = count - count += 1 - self.edit_type_group_combobox.append_text(group) - self.edit_type_group_combobox.set_active(0) + count = 0 + for group in gajim.groups[self.account]: + self.list_of_groups[group] = count + count += 1 + self.edit_type_group_combobox.append_text(group) + self.edit_type_group_combobox.set_active(0) - self.window.set_title(title) + self.window.set_title(title) - self.window.show_all() - self.add_edit_vbox.hide() + self.window.show_all() + self.add_edit_vbox.hide() - self.xml.signal_autoconnect(self) + self.xml.signal_autoconnect(self) - def on_privacy_list_edit_window_destroy(self, widget): - key_name = 'privacy_list_%s' % self.privacy_list_name - if key_name in gajim.interface.instances[self.account]: - del gajim.interface.instances[self.account][key_name] + def on_privacy_list_edit_window_destroy(self, widget): + key_name = 'privacy_list_%s' % self.privacy_list_name + if key_name in gajim.interface.instances[self.account]: + del gajim.interface.instances[self.account][key_name] - def check_active_default(self, a_d_dict): - if a_d_dict['active'] == self.privacy_list_name: - self.privacy_list_active_checkbutton.set_active(True) - else: - self.privacy_list_active_checkbutton.set_active(False) - if a_d_dict['default'] == self.privacy_list_name: - self.privacy_list_default_checkbutton.set_active(True) - else: - self.privacy_list_default_checkbutton.set_active(False) + def check_active_default(self, a_d_dict): + if a_d_dict['active'] == self.privacy_list_name: + self.privacy_list_active_checkbutton.set_active(True) + else: + self.privacy_list_active_checkbutton.set_active(False) + if a_d_dict['default'] == self.privacy_list_name: + self.privacy_list_default_checkbutton.set_active(True) + else: + self.privacy_list_default_checkbutton.set_active(False) - def privacy_list_received(self, rules): - self.list_of_rules_combobox.get_model().clear() - self.global_rules = {} - for rule in rules: - if 'type' in rule: - text_item = _('Order: %(order)s, action: %(action)s, type: %(type)s' - ', value: %(value)s') % {'order': rule['order'], - 'action': rule['action'], 'type': rule['type'], - 'value': rule['value']} - else: - text_item = _('Order: %(order)s, action: %(action)s') % \ - {'order': rule['order'], 'action': rule['action']} - if int(rule['order']) > self.max_order: - self.max_order = int(rule['order']) - self.global_rules[text_item] = rule - self.list_of_rules_combobox.append_text(text_item) - if len(rules) == 0: - self.title_hbox.set_sensitive(False) - self.list_of_rules_combobox.set_sensitive(False) - self.delete_rule_button.set_sensitive(False) - self.open_rule_button.set_sensitive(False) - self.privacy_list_active_checkbutton.set_sensitive(False) - self.privacy_list_default_checkbutton.set_sensitive(False) - else: - self.list_of_rules_combobox.set_active(0) - self.title_hbox.set_sensitive(True) - self.list_of_rules_combobox.set_sensitive(True) - self.delete_rule_button.set_sensitive(True) - self.open_rule_button.set_sensitive(True) - self.privacy_list_active_checkbutton.set_sensitive(True) - self.privacy_list_default_checkbutton.set_sensitive(True) - self.reset_fields() - gajim.connections[self.account].get_active_default_lists() + def privacy_list_received(self, rules): + self.list_of_rules_combobox.get_model().clear() + self.global_rules = {} + for rule in rules: + if 'type' in rule: + text_item = _('Order: %(order)s, action: %(action)s, type: %(type)s' + ', value: %(value)s') % {'order': rule['order'], + 'action': rule['action'], 'type': rule['type'], + 'value': rule['value']} + else: + text_item = _('Order: %(order)s, action: %(action)s') % \ + {'order': rule['order'], 'action': rule['action']} + if int(rule['order']) > self.max_order: + self.max_order = int(rule['order']) + self.global_rules[text_item] = rule + self.list_of_rules_combobox.append_text(text_item) + if len(rules) == 0: + self.title_hbox.set_sensitive(False) + self.list_of_rules_combobox.set_sensitive(False) + self.delete_rule_button.set_sensitive(False) + self.open_rule_button.set_sensitive(False) + self.privacy_list_active_checkbutton.set_sensitive(False) + self.privacy_list_default_checkbutton.set_sensitive(False) + else: + self.list_of_rules_combobox.set_active(0) + self.title_hbox.set_sensitive(True) + self.list_of_rules_combobox.set_sensitive(True) + self.delete_rule_button.set_sensitive(True) + self.open_rule_button.set_sensitive(True) + self.privacy_list_active_checkbutton.set_sensitive(True) + self.privacy_list_default_checkbutton.set_sensitive(True) + self.reset_fields() + gajim.connections[self.account].get_active_default_lists() - def refresh_rules(self): - gajim.connections[self.account].get_privacy_list(self.privacy_list_name) + def refresh_rules(self): + gajim.connections[self.account].get_privacy_list(self.privacy_list_name) - def on_delete_rule_button_clicked(self, widget): - tags = [] - for rule in self.global_rules: - if rule != self.list_of_rules_combobox.get_active_text(): - tags.append(self.global_rules[rule]) - gajim.connections[self.account].set_privacy_list( - self.privacy_list_name, tags) - self.privacy_list_received(tags) - self.add_edit_vbox.hide() - if not tags: # we removed latest rule - if 'privacy_lists' in gajim.interface.instances[self.account]: - win = gajim.interface.instances[self.account]['privacy_lists'] - win.remove_privacy_list_from_combobox(self.privacy_list_name) - win.draw_widgets() + def on_delete_rule_button_clicked(self, widget): + tags = [] + for rule in self.global_rules: + if rule != self.list_of_rules_combobox.get_active_text(): + tags.append(self.global_rules[rule]) + gajim.connections[self.account].set_privacy_list( + self.privacy_list_name, tags) + self.privacy_list_received(tags) + self.add_edit_vbox.hide() + if not tags: # we removed latest rule + if 'privacy_lists' in gajim.interface.instances[self.account]: + win = gajim.interface.instances[self.account]['privacy_lists'] + win.remove_privacy_list_from_combobox(self.privacy_list_name) + win.draw_widgets() - def on_open_rule_button_clicked(self, widget): - self.add_edit_rule_label.set_label( - _('Edit a rule')) - active_num = self.list_of_rules_combobox.get_active() - if active_num == -1: - self.active_rule = '' - else: - self.active_rule = \ - self.list_of_rules_combobox.get_active_text().decode('utf-8') - if self.active_rule != '': - rule_info = self.global_rules[self.active_rule] - self.edit_order_spinbutton.set_value(int(rule_info['order'])) - if 'type' in rule_info: - if rule_info['type'] == 'jid': - self.edit_type_jabberid_radiobutton.set_active(True) - self.edit_type_jabberid_entry.set_text(rule_info['value']) - elif rule_info['type'] == 'group': - self.edit_type_group_radiobutton.set_active(True) - if rule_info['value'] in self.list_of_groups: - self.edit_type_group_combobox.set_active( - self.list_of_groups[rule_info['value']]) - else: - self.edit_type_group_combobox.set_active(0) - elif rule_info['type'] == 'subscription': - self.edit_type_subscription_radiobutton.set_active(True) - sub_value = rule_info['value'] - if sub_value == 'none': - self.edit_type_subscription_combobox.set_active(0) - elif sub_value == 'both': - self.edit_type_subscription_combobox.set_active(1) - elif sub_value == 'from': - self.edit_type_subscription_combobox.set_active(2) - elif sub_value == 'to': - self.edit_type_subscription_combobox.set_active(3) - else: - self.edit_type_select_all_radiobutton.set_active(True) - else: - self.edit_type_select_all_radiobutton.set_active(True) - self.edit_send_messages_checkbutton.set_active(False) - self.edit_queries_send_checkbutton.set_active(False) - self.edit_view_status_checkbutton.set_active(False) - self.edit_send_status_checkbutton.set_active(False) - self.edit_all_checkbutton.set_active(False) - if not rule_info['child']: - self.edit_all_checkbutton.set_active(True) - else: - if 'presence-out' in rule_info['child']: - self.edit_send_status_checkbutton.set_active(True) - if 'presence-in' in rule_info['child']: - self.edit_view_status_checkbutton.set_active(True) - if 'iq' in rule_info['child']: - self.edit_queries_send_checkbutton.set_active(True) - if 'message' in rule_info['child']: - self.edit_send_messages_checkbutton.set_active(True) + def on_open_rule_button_clicked(self, widget): + self.add_edit_rule_label.set_label( + _('Edit a rule')) + active_num = self.list_of_rules_combobox.get_active() + if active_num == -1: + self.active_rule = '' + else: + self.active_rule = \ + self.list_of_rules_combobox.get_active_text().decode('utf-8') + if self.active_rule != '': + rule_info = self.global_rules[self.active_rule] + self.edit_order_spinbutton.set_value(int(rule_info['order'])) + if 'type' in rule_info: + if rule_info['type'] == 'jid': + self.edit_type_jabberid_radiobutton.set_active(True) + self.edit_type_jabberid_entry.set_text(rule_info['value']) + elif rule_info['type'] == 'group': + self.edit_type_group_radiobutton.set_active(True) + if rule_info['value'] in self.list_of_groups: + self.edit_type_group_combobox.set_active( + self.list_of_groups[rule_info['value']]) + else: + self.edit_type_group_combobox.set_active(0) + elif rule_info['type'] == 'subscription': + self.edit_type_subscription_radiobutton.set_active(True) + sub_value = rule_info['value'] + if sub_value == 'none': + self.edit_type_subscription_combobox.set_active(0) + elif sub_value == 'both': + self.edit_type_subscription_combobox.set_active(1) + elif sub_value == 'from': + self.edit_type_subscription_combobox.set_active(2) + elif sub_value == 'to': + self.edit_type_subscription_combobox.set_active(3) + else: + self.edit_type_select_all_radiobutton.set_active(True) + else: + self.edit_type_select_all_radiobutton.set_active(True) + self.edit_send_messages_checkbutton.set_active(False) + self.edit_queries_send_checkbutton.set_active(False) + self.edit_view_status_checkbutton.set_active(False) + self.edit_send_status_checkbutton.set_active(False) + self.edit_all_checkbutton.set_active(False) + if not rule_info['child']: + self.edit_all_checkbutton.set_active(True) + else: + if 'presence-out' in rule_info['child']: + self.edit_send_status_checkbutton.set_active(True) + if 'presence-in' in rule_info['child']: + self.edit_view_status_checkbutton.set_active(True) + if 'iq' in rule_info['child']: + self.edit_queries_send_checkbutton.set_active(True) + if 'message' in rule_info['child']: + self.edit_send_messages_checkbutton.set_active(True) - if rule_info['action'] == 'allow': - self.edit_allow_radiobutton.set_active(True) - else: - self.edit_deny_radiobutton.set_active(True) - self.add_edit_vbox.show() + if rule_info['action'] == 'allow': + self.edit_allow_radiobutton.set_active(True) + else: + self.edit_deny_radiobutton.set_active(True) + self.add_edit_vbox.show() - def on_edit_all_checkbutton_toggled(self, widget): - if widget.get_active(): - self.edit_send_messages_checkbutton.set_active(True) - self.edit_queries_send_checkbutton.set_active(True) - self.edit_view_status_checkbutton.set_active(True) - self.edit_send_status_checkbutton.set_active(True) - self.edit_send_messages_checkbutton.set_sensitive(False) - self.edit_queries_send_checkbutton.set_sensitive(False) - self.edit_view_status_checkbutton.set_sensitive(False) - self.edit_send_status_checkbutton.set_sensitive(False) - else: - self.edit_send_messages_checkbutton.set_active(False) - self.edit_queries_send_checkbutton.set_active(False) - self.edit_view_status_checkbutton.set_active(False) - self.edit_send_status_checkbutton.set_active(False) - self.edit_send_messages_checkbutton.set_sensitive(True) - self.edit_queries_send_checkbutton.set_sensitive(True) - self.edit_view_status_checkbutton.set_sensitive(True) - self.edit_send_status_checkbutton.set_sensitive(True) + def on_edit_all_checkbutton_toggled(self, widget): + if widget.get_active(): + self.edit_send_messages_checkbutton.set_active(True) + self.edit_queries_send_checkbutton.set_active(True) + self.edit_view_status_checkbutton.set_active(True) + self.edit_send_status_checkbutton.set_active(True) + self.edit_send_messages_checkbutton.set_sensitive(False) + self.edit_queries_send_checkbutton.set_sensitive(False) + self.edit_view_status_checkbutton.set_sensitive(False) + self.edit_send_status_checkbutton.set_sensitive(False) + else: + self.edit_send_messages_checkbutton.set_active(False) + self.edit_queries_send_checkbutton.set_active(False) + self.edit_view_status_checkbutton.set_active(False) + self.edit_send_status_checkbutton.set_active(False) + self.edit_send_messages_checkbutton.set_sensitive(True) + self.edit_queries_send_checkbutton.set_sensitive(True) + self.edit_view_status_checkbutton.set_sensitive(True) + self.edit_send_status_checkbutton.set_sensitive(True) - def on_privacy_list_active_checkbutton_toggled(self, widget): - if widget.get_active(): - gajim.connections[self.account].set_active_list( - self.privacy_list_name) - else: - gajim.connections[self.account].set_active_list(None) + def on_privacy_list_active_checkbutton_toggled(self, widget): + if widget.get_active(): + gajim.connections[self.account].set_active_list( + self.privacy_list_name) + else: + gajim.connections[self.account].set_active_list(None) - def on_privacy_list_default_checkbutton_toggled(self, widget): - if widget.get_active(): - gajim.connections[self.account].set_default_list( - self.privacy_list_name) - else: - gajim.connections[self.account].set_default_list(None) + def on_privacy_list_default_checkbutton_toggled(self, widget): + if widget.get_active(): + gajim.connections[self.account].set_default_list( + self.privacy_list_name) + else: + gajim.connections[self.account].set_default_list(None) - def on_new_rule_button_clicked(self, widget): - self.reset_fields() - self.add_edit_vbox.show() + def on_new_rule_button_clicked(self, widget): + self.reset_fields() + self.add_edit_vbox.show() - def reset_fields(self): - self.edit_type_jabberid_entry.set_text('') - self.edit_allow_radiobutton.set_active(True) - self.edit_type_jabberid_radiobutton.set_active(True) - self.active_rule = '' - self.edit_send_messages_checkbutton.set_active(False) - self.edit_queries_send_checkbutton.set_active(False) - self.edit_view_status_checkbutton.set_active(False) - self.edit_send_status_checkbutton.set_active(False) - self.edit_all_checkbutton.set_active(False) - self.edit_order_spinbutton.set_value(self.max_order + 1) - self.edit_type_group_combobox.set_active(0) - self.edit_type_subscription_combobox.set_active(0) - self.add_edit_rule_label.set_label( - _('Add a rule')) + def reset_fields(self): + self.edit_type_jabberid_entry.set_text('') + self.edit_allow_radiobutton.set_active(True) + self.edit_type_jabberid_radiobutton.set_active(True) + self.active_rule = '' + self.edit_send_messages_checkbutton.set_active(False) + self.edit_queries_send_checkbutton.set_active(False) + self.edit_view_status_checkbutton.set_active(False) + self.edit_send_status_checkbutton.set_active(False) + self.edit_all_checkbutton.set_active(False) + self.edit_order_spinbutton.set_value(self.max_order + 1) + self.edit_type_group_combobox.set_active(0) + self.edit_type_subscription_combobox.set_active(0) + self.add_edit_rule_label.set_label( + _('Add a rule')) - def get_current_tags(self): - if self.edit_type_jabberid_radiobutton.get_active(): - edit_type = 'jid' - edit_value = self.edit_type_jabberid_entry.get_text() - elif self.edit_type_group_radiobutton.get_active(): - edit_type = 'group' - edit_value = self.edit_type_group_combobox.get_active_text() - elif self.edit_type_subscription_radiobutton.get_active(): - edit_type = 'subscription' - subs = ['none', 'both', 'from', 'to'] - edit_value = subs[self.edit_type_subscription_combobox.get_active()] - elif self.edit_type_select_all_radiobutton.get_active(): - edit_type = '' - edit_value = '' - edit_order = str(self.edit_order_spinbutton.get_value_as_int()) - if self.edit_allow_radiobutton.get_active(): - edit_deny = 'allow' - else: - edit_deny = 'deny' - child = [] - if not self.edit_all_checkbutton.get_active(): - if self.edit_send_messages_checkbutton.get_active(): - child.append('message') - if self.edit_queries_send_checkbutton.get_active(): - child.append('iq') - if self.edit_send_status_checkbutton.get_active(): - child.append('presence-out') - if self.edit_view_status_checkbutton.get_active(): - child.append('presence-in') - if edit_type != '': - return {'order': edit_order, 'action': edit_deny, - 'type': edit_type, 'value': edit_value, 'child': child} - return {'order': edit_order, 'action': edit_deny, 'child': child} + def get_current_tags(self): + if self.edit_type_jabberid_radiobutton.get_active(): + edit_type = 'jid' + edit_value = self.edit_type_jabberid_entry.get_text() + elif self.edit_type_group_radiobutton.get_active(): + edit_type = 'group' + edit_value = self.edit_type_group_combobox.get_active_text() + elif self.edit_type_subscription_radiobutton.get_active(): + edit_type = 'subscription' + subs = ['none', 'both', 'from', 'to'] + edit_value = subs[self.edit_type_subscription_combobox.get_active()] + elif self.edit_type_select_all_radiobutton.get_active(): + edit_type = '' + edit_value = '' + edit_order = str(self.edit_order_spinbutton.get_value_as_int()) + if self.edit_allow_radiobutton.get_active(): + edit_deny = 'allow' + else: + edit_deny = 'deny' + child = [] + if not self.edit_all_checkbutton.get_active(): + if self.edit_send_messages_checkbutton.get_active(): + child.append('message') + if self.edit_queries_send_checkbutton.get_active(): + child.append('iq') + if self.edit_send_status_checkbutton.get_active(): + child.append('presence-out') + if self.edit_view_status_checkbutton.get_active(): + child.append('presence-in') + if edit_type != '': + return {'order': edit_order, 'action': edit_deny, + 'type': edit_type, 'value': edit_value, 'child': child} + return {'order': edit_order, 'action': edit_deny, 'child': child} - def on_save_rule_button_clicked(self, widget): - tags=[] - current_tags = self.get_current_tags() - if int(current_tags['order']) > self.max_order: - self.max_order = int(current_tags['order']) - if self.active_rule == '': - tags.append(current_tags) + def on_save_rule_button_clicked(self, widget): + tags=[] + current_tags = self.get_current_tags() + if int(current_tags['order']) > self.max_order: + self.max_order = int(current_tags['order']) + if self.active_rule == '': + tags.append(current_tags) - for rule in self.global_rules: - if rule != self.active_rule: - tags.append(self.global_rules[rule]) - else: - tags.append(current_tags) + for rule in self.global_rules: + if rule != self.active_rule: + tags.append(self.global_rules[rule]) + else: + tags.append(current_tags) - gajim.connections[self.account].set_privacy_list( - self.privacy_list_name, tags) - self.refresh_rules() - self.add_edit_vbox.hide() - if 'privacy_lists' in gajim.interface.instances[self.account]: - win = gajim.interface.instances[self.account]['privacy_lists'] - win.add_privacy_list_to_combobox(self.privacy_list_name) - win.draw_widgets() + gajim.connections[self.account].set_privacy_list( + self.privacy_list_name, tags) + self.refresh_rules() + self.add_edit_vbox.hide() + if 'privacy_lists' in gajim.interface.instances[self.account]: + win = gajim.interface.instances[self.account]['privacy_lists'] + win.add_privacy_list_to_combobox(self.privacy_list_name) + win.draw_widgets() - def on_list_of_rules_combobox_changed(self, widget): - self.add_edit_vbox.hide() + def on_list_of_rules_combobox_changed(self, widget): + self.add_edit_vbox.hide() - def on_edit_type_radiobutton_changed(self, widget, radiobutton): - active_bool = widget.get_active() - if active_bool: - self.edit_rule_type = radiobutton + def on_edit_type_radiobutton_changed(self, widget, radiobutton): + active_bool = widget.get_active() + if active_bool: + self.edit_rule_type = radiobutton - def on_edit_allow_radiobutton_changed(self, widget, radiobutton): - active_bool = widget.get_active() - if active_bool: - self.allow_deny = radiobutton + def on_edit_allow_radiobutton_changed(self, widget, radiobutton): + active_bool = widget.get_active() + if active_bool: + self.allow_deny = radiobutton - def on_close_button_clicked(self, widget): - self.window.destroy() + def on_close_button_clicked(self, widget): + self.window.destroy() class PrivacyListsWindow: - """ - Window that is the main window for Privacy Lists; we can list there the - privacy lists and ask to create a new one or edit an already there one - """ - def __init__(self, account): - self.account = account - self.privacy_lists_save = [] + """ + Window that is the main window for Privacy Lists; we can list there the + privacy lists and ask to create a new one or edit an already there one + """ + def __init__(self, account): + self.account = account + self.privacy_lists_save = [] - self.xml = gtkgui_helpers.get_glade('privacy_lists_window.glade') + self.xml = gtkgui_helpers.get_glade('privacy_lists_window.glade') - self.window = self.xml.get_widget('privacy_lists_first_window') - for widget_to_add in ('list_of_privacy_lists_combobox', - 'delete_privacy_list_button', 'open_privacy_list_button', - 'new_privacy_list_button', 'new_privacy_list_entry', - 'privacy_lists_refresh_button', 'close_privacy_lists_window_button'): - self.__dict__[widget_to_add] = self.xml.get_widget( - widget_to_add) + self.window = self.xml.get_widget('privacy_lists_first_window') + for widget_to_add in ('list_of_privacy_lists_combobox', + 'delete_privacy_list_button', 'open_privacy_list_button', + 'new_privacy_list_button', 'new_privacy_list_entry', + 'privacy_lists_refresh_button', 'close_privacy_lists_window_button'): + self.__dict__[widget_to_add] = self.xml.get_widget( + widget_to_add) - self.draw_privacy_lists_in_combobox([]) - self.privacy_lists_refresh() + self.draw_privacy_lists_in_combobox([]) + self.privacy_lists_refresh() - self.enabled = True + self.enabled = True - if len(gajim.connections) > 1: - title = _('Privacy Lists for %s') % self.account - else: - title = _('Privacy Lists') + if len(gajim.connections) > 1: + title = _('Privacy Lists for %s') % self.account + else: + title = _('Privacy Lists') - self.window.set_title(title) + self.window.set_title(title) - self.window.show_all() + self.window.show_all() - self.xml.signal_autoconnect(self) + self.xml.signal_autoconnect(self) - def on_privacy_lists_first_window_destroy(self, widget): - if 'privacy_lists' in gajim.interface.instances[self.account]: - del gajim.interface.instances[self.account]['privacy_lists'] + def on_privacy_lists_first_window_destroy(self, widget): + if 'privacy_lists' in gajim.interface.instances[self.account]: + del gajim.interface.instances[self.account]['privacy_lists'] - def remove_privacy_list_from_combobox(self, privacy_list): - if privacy_list not in self.privacy_lists_save: - return - privacy_list_index = self.privacy_lists_save.index(privacy_list) - self.list_of_privacy_lists_combobox.remove_text(privacy_list_index) - self.privacy_lists_save.remove(privacy_list) + def remove_privacy_list_from_combobox(self, privacy_list): + if privacy_list not in self.privacy_lists_save: + return + privacy_list_index = self.privacy_lists_save.index(privacy_list) + self.list_of_privacy_lists_combobox.remove_text(privacy_list_index) + self.privacy_lists_save.remove(privacy_list) - def add_privacy_list_to_combobox(self, privacy_list): - if privacy_list in self.privacy_lists_save: - return - self.list_of_privacy_lists_combobox.append_text(privacy_list) - self.privacy_lists_save.append(privacy_list) + def add_privacy_list_to_combobox(self, privacy_list): + if privacy_list in self.privacy_lists_save: + return + self.list_of_privacy_lists_combobox.append_text(privacy_list) + self.privacy_lists_save.append(privacy_list) - def draw_privacy_lists_in_combobox(self, privacy_lists): - self.list_of_privacy_lists_combobox.set_active(-1) - self.list_of_privacy_lists_combobox.get_model().clear() - self.privacy_lists_save = [] - for add_item in privacy_lists: - self.add_privacy_list_to_combobox(add_item) - self.draw_widgets() + def draw_privacy_lists_in_combobox(self, privacy_lists): + self.list_of_privacy_lists_combobox.set_active(-1) + self.list_of_privacy_lists_combobox.get_model().clear() + self.privacy_lists_save = [] + for add_item in privacy_lists: + self.add_privacy_list_to_combobox(add_item) + self.draw_widgets() - def draw_widgets(self): - if len(self.privacy_lists_save) == 0: - self.list_of_privacy_lists_combobox.set_sensitive(False) - self.open_privacy_list_button.set_sensitive(False) - self.delete_privacy_list_button.set_sensitive(False) - else: - self.list_of_privacy_lists_combobox.set_sensitive(True) - self.list_of_privacy_lists_combobox.set_active(0) - self.open_privacy_list_button.set_sensitive(True) - self.delete_privacy_list_button.set_sensitive(True) + def draw_widgets(self): + if len(self.privacy_lists_save) == 0: + self.list_of_privacy_lists_combobox.set_sensitive(False) + self.open_privacy_list_button.set_sensitive(False) + self.delete_privacy_list_button.set_sensitive(False) + else: + self.list_of_privacy_lists_combobox.set_sensitive(True) + self.list_of_privacy_lists_combobox.set_active(0) + self.open_privacy_list_button.set_sensitive(True) + self.delete_privacy_list_button.set_sensitive(True) - def on_close_button_clicked(self, widget): - self.window.destroy() + def on_close_button_clicked(self, widget): + self.window.destroy() - def on_delete_privacy_list_button_clicked(self, widget): - active_list = self.privacy_lists_save[ - self.list_of_privacy_lists_combobox.get_active()] - gajim.connections[self.account].del_privacy_list(active_list) + def on_delete_privacy_list_button_clicked(self, widget): + active_list = self.privacy_lists_save[ + self.list_of_privacy_lists_combobox.get_active()] + gajim.connections[self.account].del_privacy_list(active_list) - def privacy_list_removed(self, active_list): - self.privacy_lists_save.remove(active_list) - self.privacy_lists_received({'lists': self.privacy_lists_save}) + def privacy_list_removed(self, active_list): + self.privacy_lists_save.remove(active_list) + self.privacy_lists_received({'lists': self.privacy_lists_save}) - def privacy_lists_received(self, lists): - if not lists: - return - privacy_lists = [] - for privacy_list in lists['lists']: - privacy_lists.append(privacy_list) - self.draw_privacy_lists_in_combobox(privacy_lists) + def privacy_lists_received(self, lists): + if not lists: + return + privacy_lists = [] + for privacy_list in lists['lists']: + privacy_lists.append(privacy_list) + self.draw_privacy_lists_in_combobox(privacy_lists) - def privacy_lists_refresh(self): - gajim.connections[self.account].get_privacy_lists() + def privacy_lists_refresh(self): + gajim.connections[self.account].get_privacy_lists() - def on_new_privacy_list_button_clicked(self, widget): - name = self.new_privacy_list_entry.get_text() - if not name: - ErrorDialog(_('Invalid List Name'), - _('You must enter a name to create a privacy list.')) - return - key_name = 'privacy_list_%s' % name - if key_name in gajim.interface.instances[self.account]: - gajim.interface.instances[self.account][key_name].window.present() - else: - gajim.interface.instances[self.account][key_name] = \ - PrivacyListWindow(self.account, name, 'NEW') - self.new_privacy_list_entry.set_text('') + def on_new_privacy_list_button_clicked(self, widget): + name = self.new_privacy_list_entry.get_text() + if not name: + ErrorDialog(_('Invalid List Name'), + _('You must enter a name to create a privacy list.')) + return + key_name = 'privacy_list_%s' % name + if key_name in gajim.interface.instances[self.account]: + gajim.interface.instances[self.account][key_name].window.present() + else: + gajim.interface.instances[self.account][key_name] = \ + PrivacyListWindow(self.account, name, 'NEW') + self.new_privacy_list_entry.set_text('') - def on_privacy_lists_refresh_button_clicked(self, widget): - self.privacy_lists_refresh() + def on_privacy_lists_refresh_button_clicked(self, widget): + self.privacy_lists_refresh() - def on_open_privacy_list_button_clicked(self, widget): - name = self.privacy_lists_save[ - self.list_of_privacy_lists_combobox.get_active()] - key_name = 'privacy_list_%s' % name - if key_name in gajim.interface.instances[self.account]: - gajim.interface.instances[self.account][key_name].window.present() - else: - gajim.interface.instances[self.account][key_name] = \ - PrivacyListWindow(self.account, name, 'EDIT') + def on_open_privacy_list_button_clicked(self, widget): + name = self.privacy_lists_save[ + self.list_of_privacy_lists_combobox.get_active()] + key_name = 'privacy_list_%s' % name + if key_name in gajim.interface.instances[self.account]: + gajim.interface.instances[self.account][key_name].window.present() + else: + gajim.interface.instances[self.account][key_name] = \ + PrivacyListWindow(self.account, name, 'EDIT') class InvitationReceivedDialog: - def __init__(self, account, room_jid, contact_jid, password=None, - comment=None, is_continued=False): + def __init__(self, account, room_jid, contact_jid, password=None, + comment=None, is_continued=False): - self.room_jid = room_jid - self.account = account - self.password = password - self.is_continued = is_continued + self.room_jid = room_jid + self.account = account + self.password = password + self.is_continued = is_continued - pritext = _('''You are invited to a groupchat''') - #Don't translate $Contact - if is_continued: - sectext = _('$Contact has invited you to join a discussion') - else: - sectext = _('$Contact has invited you to group chat %(room_jid)s')\ - % {'room_jid': room_jid} - contact = gajim.contacts.get_first_contact_from_jid(account, contact_jid) - contact_text = contact and contact.name or contact_jid - sectext = sectext.replace('$Contact', contact_text) + pritext = _('''You are invited to a groupchat''') + #Don't translate $Contact + if is_continued: + sectext = _('$Contact has invited you to join a discussion') + else: + sectext = _('$Contact has invited you to group chat %(room_jid)s')\ + % {'room_jid': room_jid} + contact = gajim.contacts.get_first_contact_from_jid(account, contact_jid) + contact_text = contact and contact.name or contact_jid + sectext = sectext.replace('$Contact', contact_text) - if comment: # only if not None and not '' - comment = gobject.markup_escape_text(comment) - comment = _('Comment: %s') % comment - sectext += '\n\n%s' % comment - sectext += '\n\n' + _('Do you want to accept the invitation?') + if comment: # only if not None and not '' + comment = gobject.markup_escape_text(comment) + comment = _('Comment: %s') % comment + sectext += '\n\n%s' % comment + sectext += '\n\n' + _('Do you want to accept the invitation?') - def on_yes(checked): - try: - if self.is_continued: - gajim.interface.join_gc_room(self.account, self.room_jid, - gajim.nicks[self.account], None, is_continued=True) - else: - JoinGroupchatWindow(self.account, self.room_jid) - except GajimGeneralException: - pass + def on_yes(checked): + try: + if self.is_continued: + gajim.interface.join_gc_room(self.account, self.room_jid, + gajim.nicks[self.account], None, is_continued=True) + else: + JoinGroupchatWindow(self.account, self.room_jid) + except GajimGeneralException: + pass - YesNoDialog(pritext, sectext, on_response_yes=on_yes) + YesNoDialog(pritext, sectext, on_response_yes=on_yes) class ProgressDialog: - def __init__(self, title_text, during_text, messages_queue): - """ - During text is what to show during the procedure, messages_queue has the - message to show in the textview - """ - self.xml = gtkgui_helpers.get_glade('progress_dialog.glade') - self.dialog = self.xml.get_widget('progress_dialog') - self.label = self.xml.get_widget('label') - self.label.set_markup('' + during_text + '') - self.progressbar = self.xml.get_widget('progressbar') - self.dialog.set_title(title_text) - self.dialog.set_default_size(450, 250) - self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - self.dialog.show_all() - self.xml.signal_autoconnect(self) + def __init__(self, title_text, during_text, messages_queue): + """ + During text is what to show during the procedure, messages_queue has the + message to show in the textview + """ + self.xml = gtkgui_helpers.get_glade('progress_dialog.glade') + self.dialog = self.xml.get_widget('progress_dialog') + self.label = self.xml.get_widget('label') + self.label.set_markup('' + during_text + '') + self.progressbar = self.xml.get_widget('progressbar') + self.dialog.set_title(title_text) + self.dialog.set_default_size(450, 250) + self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + self.dialog.show_all() + self.xml.signal_autoconnect(self) - self.update_progressbar_timeout_id = gobject.timeout_add(100, - self.update_progressbar) + self.update_progressbar_timeout_id = gobject.timeout_add(100, + self.update_progressbar) - def update_progressbar(self): - if self.dialog: - self.progressbar.pulse() - return True # loop forever - return False + def update_progressbar(self): + if self.dialog: + self.progressbar.pulse() + return True # loop forever + return False - def on_progress_dialog_delete_event(self, widget, event): - return True # WM's X button or Escape key should not destroy the window + def on_progress_dialog_delete_event(self, widget, event): + return True # WM's X button or Escape key should not destroy the window class SoundChooserDialog(FileChooserDialog): - def __init__(self, path_to_snd_file='', on_response_ok=None, - on_response_cancel=None): - """ - Optionally accepts path_to_snd_file so it has that as selected - """ - def on_ok(widget, callback): - """ - Check if file exists and call callback - """ - path_to_snd_file = self.get_filename() - path_to_snd_file = gtkgui_helpers.decode_filechooser_file_paths( - (path_to_snd_file,))[0] - if os.path.exists(path_to_snd_file): - callback(widget, path_to_snd_file) + def __init__(self, path_to_snd_file='', on_response_ok=None, + on_response_cancel=None): + """ + Optionally accepts path_to_snd_file so it has that as selected + """ + def on_ok(widget, callback): + """ + Check if file exists and call callback + """ + path_to_snd_file = self.get_filename() + path_to_snd_file = gtkgui_helpers.decode_filechooser_file_paths( + (path_to_snd_file,))[0] + if os.path.exists(path_to_snd_file): + callback(widget, path_to_snd_file) - FileChooserDialog.__init__(self, - title_text = _('Choose Sound'), - action = gtk.FILE_CHOOSER_ACTION_OPEN, - buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_OPEN, gtk.RESPONSE_OK), - default_response = gtk.RESPONSE_OK, - current_folder = gajim.config.get('last_sounds_dir'), - on_response_ok = (on_ok, on_response_ok), - on_response_cancel = on_response_cancel) + FileChooserDialog.__init__(self, + title_text = _('Choose Sound'), + action = gtk.FILE_CHOOSER_ACTION_OPEN, + buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_OPEN, gtk.RESPONSE_OK), + default_response = gtk.RESPONSE_OK, + current_folder = gajim.config.get('last_sounds_dir'), + on_response_ok = (on_ok, on_response_ok), + on_response_cancel = on_response_cancel) - filter_ = gtk.FileFilter() - filter_.set_name(_('All files')) - filter_.add_pattern('*') - self.add_filter(filter_) + filter_ = gtk.FileFilter() + filter_.set_name(_('All files')) + filter_.add_pattern('*') + self.add_filter(filter_) - filter_ = gtk.FileFilter() - filter_.set_name(_('Wav Sounds')) - filter_.add_pattern('*.wav') - self.add_filter(filter_) - self.set_filter(filter_) + filter_ = gtk.FileFilter() + filter_.set_name(_('Wav Sounds')) + filter_.add_pattern('*.wav') + self.add_filter(filter_) + self.set_filter(filter_) - path_to_snd_file = helpers.check_soundfile_path(path_to_snd_file) - if path_to_snd_file: - # set_filename accept only absolute path - path_to_snd_file = os.path.abspath(path_to_snd_file) - self.set_filename(path_to_snd_file) + path_to_snd_file = helpers.check_soundfile_path(path_to_snd_file) + if path_to_snd_file: + # set_filename accept only absolute path + path_to_snd_file = os.path.abspath(path_to_snd_file) + self.set_filename(path_to_snd_file) class ImageChooserDialog(FileChooserDialog): - def __init__(self, path_to_file='', on_response_ok=None, - on_response_cancel=None): - """ - Optionally accepts path_to_snd_file so it has that as selected - """ - def on_ok(widget, callback): - '''check if file exists and call callback''' - path_to_file = self.get_filename() - if not path_to_file: - return - path_to_file = gtkgui_helpers.decode_filechooser_file_paths( - (path_to_file,))[0] - if os.path.exists(path_to_file): - if isinstance(callback, tuple): - callback[0](widget, path_to_file, *callback[1:]) - else: - callback(widget, path_to_file) + def __init__(self, path_to_file='', on_response_ok=None, + on_response_cancel=None): + """ + Optionally accepts path_to_snd_file so it has that as selected + """ + def on_ok(widget, callback): + '''check if file exists and call callback''' + path_to_file = self.get_filename() + if not path_to_file: + return + path_to_file = gtkgui_helpers.decode_filechooser_file_paths( + (path_to_file,))[0] + if os.path.exists(path_to_file): + if isinstance(callback, tuple): + callback[0](widget, path_to_file, *callback[1:]) + else: + callback(widget, path_to_file) - try: - if os.name == 'nt': - path = helpers.get_my_pictures_path() - else: - path = os.environ['HOME'] - except Exception: - path = '' - FileChooserDialog.__init__(self, - title_text = _('Choose Image'), - action = gtk.FILE_CHOOSER_ACTION_OPEN, - buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_OPEN, gtk.RESPONSE_OK), - default_response = gtk.RESPONSE_OK, - current_folder = path, - on_response_ok = (on_ok, on_response_ok), - on_response_cancel = on_response_cancel) + try: + if os.name == 'nt': + path = helpers.get_my_pictures_path() + else: + path = os.environ['HOME'] + except Exception: + path = '' + FileChooserDialog.__init__(self, + title_text = _('Choose Image'), + action = gtk.FILE_CHOOSER_ACTION_OPEN, + buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_OPEN, gtk.RESPONSE_OK), + default_response = gtk.RESPONSE_OK, + current_folder = path, + on_response_ok = (on_ok, on_response_ok), + on_response_cancel = on_response_cancel) - if on_response_cancel: - self.connect('destroy', on_response_cancel) + if on_response_cancel: + self.connect('destroy', on_response_cancel) - filter_ = gtk.FileFilter() - filter_.set_name(_('All files')) - filter_.add_pattern('*') - self.add_filter(filter_) + filter_ = gtk.FileFilter() + filter_.set_name(_('All files')) + filter_.add_pattern('*') + self.add_filter(filter_) - filter_ = gtk.FileFilter() - filter_.set_name(_('Images')) - filter_.add_mime_type('image/png') - filter_.add_mime_type('image/jpeg') - filter_.add_mime_type('image/gif') - filter_.add_mime_type('image/tiff') - filter_.add_mime_type('image/svg+xml') - filter_.add_mime_type('image/x-xpixmap') # xpm - self.add_filter(filter_) - self.set_filter(filter_) + filter_ = gtk.FileFilter() + filter_.set_name(_('Images')) + filter_.add_mime_type('image/png') + filter_.add_mime_type('image/jpeg') + filter_.add_mime_type('image/gif') + filter_.add_mime_type('image/tiff') + filter_.add_mime_type('image/svg+xml') + filter_.add_mime_type('image/x-xpixmap') # xpm + self.add_filter(filter_) + self.set_filter(filter_) - if path_to_file: - self.set_filename(path_to_file) + if path_to_file: + self.set_filename(path_to_file) - self.set_use_preview_label(False) - self.set_preview_widget(gtk.Image()) - self.connect('selection-changed', self.update_preview) + self.set_use_preview_label(False) + self.set_preview_widget(gtk.Image()) + self.connect('selection-changed', self.update_preview) - def update_preview(self, widget): - path_to_file = widget.get_preview_filename() - if path_to_file is None or os.path.isdir(path_to_file): - # nothing to preview or directory - # make sure you clean image do show nothing - widget.get_preview_widget().set_from_file(None) - return - try: - pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(path_to_file, 100, 100) - except gobject.GError: - return - widget.get_preview_widget().set_from_pixbuf(pixbuf) + def update_preview(self, widget): + path_to_file = widget.get_preview_filename() + if path_to_file is None or os.path.isdir(path_to_file): + # nothing to preview or directory + # make sure you clean image do show nothing + widget.get_preview_widget().set_from_file(None) + return + try: + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(path_to_file, 100, 100) + except gobject.GError: + return + widget.get_preview_widget().set_from_pixbuf(pixbuf) class AvatarChooserDialog(ImageChooserDialog): - def __init__(self, path_to_file='', on_response_ok=None, - on_response_cancel=None, on_response_clear=None): - ImageChooserDialog.__init__(self, path_to_file, on_response_ok, - on_response_cancel) - button = gtk.Button(None, gtk.STOCK_CLEAR) - self.response_clear = on_response_clear - if on_response_clear: - button.connect('clicked', self.on_clear) - button.show_all() - self.action_area.pack_start(button) - self.action_area.reorder_child(button, 0) + def __init__(self, path_to_file='', on_response_ok=None, + on_response_cancel=None, on_response_clear=None): + ImageChooserDialog.__init__(self, path_to_file, on_response_ok, + on_response_cancel) + button = gtk.Button(None, gtk.STOCK_CLEAR) + self.response_clear = on_response_clear + if on_response_clear: + button.connect('clicked', self.on_clear) + button.show_all() + self.action_area.pack_start(button) + self.action_area.reorder_child(button, 0) - def on_clear(self, widget): - if isinstance(self.response_clear, tuple): - self.response_clear[0](widget, *self.response_clear[1:]) - else: - self.response_clear(widget) + def on_clear(self, widget): + if isinstance(self.response_clear, tuple): + self.response_clear[0](widget, *self.response_clear[1:]) + else: + self.response_clear(widget) class AddSpecialNotificationDialog: - def __init__(self, jid): - """ - jid is the jid for which we want to add special notification (sound and - notification popups) - """ - self.xml = gtkgui_helpers.get_glade('add_special_notification_window.glade') - self.window = self.xml.get_widget('add_special_notification_window') - self.condition_combobox = self.xml.get_widget('condition_combobox') - self.condition_combobox.set_active(0) - self.notification_popup_yes_no_combobox = self.xml.get_widget( - 'notification_popup_yes_no_combobox') - self.notification_popup_yes_no_combobox.set_active(0) - self.listen_sound_combobox = self.xml.get_widget('listen_sound_combobox') - self.listen_sound_combobox.set_active(0) + def __init__(self, jid): + """ + jid is the jid for which we want to add special notification (sound and + notification popups) + """ + self.xml = gtkgui_helpers.get_glade('add_special_notification_window.glade') + self.window = self.xml.get_widget('add_special_notification_window') + self.condition_combobox = self.xml.get_widget('condition_combobox') + self.condition_combobox.set_active(0) + self.notification_popup_yes_no_combobox = self.xml.get_widget( + 'notification_popup_yes_no_combobox') + self.notification_popup_yes_no_combobox.set_active(0) + self.listen_sound_combobox = self.xml.get_widget('listen_sound_combobox') + self.listen_sound_combobox.set_active(0) - self.jid = jid - self.xml.get_widget('when_foo_becomes_label').set_text( - _('When %s becomes:') % self.jid) + self.jid = jid + self.xml.get_widget('when_foo_becomes_label').set_text( + _('When %s becomes:') % self.jid) - self.window.set_title(_('Adding Special Notification for %s') % jid) - self.window.show_all() - self.xml.signal_autoconnect(self) + self.window.set_title(_('Adding Special Notification for %s') % jid) + self.window.show_all() + self.xml.signal_autoconnect(self) - def on_cancel_button_clicked(self, widget): - self.window.destroy() + def on_cancel_button_clicked(self, widget): + self.window.destroy() - def on_add_special_notification_window_delete_event(self, widget, event): - self.window.destroy() + def on_add_special_notification_window_delete_event(self, widget, event): + self.window.destroy() - def on_listen_sound_combobox_changed(self, widget): - active = widget.get_active() - if active == 1: # user selected 'choose sound' - def on_ok(widget, path_to_snd_file): - pass - #print path_to_snd_file + def on_listen_sound_combobox_changed(self, widget): + active = widget.get_active() + if active == 1: # user selected 'choose sound' + def on_ok(widget, path_to_snd_file): + pass + #print path_to_snd_file - def on_cancel(widget): - widget.set_active(0) # go back to No Sound + def on_cancel(widget): + widget.set_active(0) # go back to No Sound - self.dialog = SoundChooserDialog(on_response_ok=on_ok, - on_response_cancel=on_cancel) + self.dialog = SoundChooserDialog(on_response_ok=on_ok, + on_response_cancel=on_cancel) - def on_ok_button_clicked(self, widget): - conditions = ('online', 'chat', 'online_and_chat', - 'away', 'xa', 'away_and_xa', 'dnd', 'xa_and_dnd', 'offline') - active = self.condition_combobox.get_active() + def on_ok_button_clicked(self, widget): + conditions = ('online', 'chat', 'online_and_chat', + 'away', 'xa', 'away_and_xa', 'dnd', 'xa_and_dnd', 'offline') + active = self.condition_combobox.get_active() - active_iter = self.listen_sound_combobox.get_active_iter() - listen_sound_model = self.listen_sound_combobox.get_model() + active_iter = self.listen_sound_combobox.get_active_iter() + listen_sound_model = self.listen_sound_combobox.get_model() class AdvancedNotificationsWindow: - events_list = ['message_received', 'contact_connected', - 'contact_disconnected', 'contact_change_status', 'gc_msg_highlight', - 'gc_msg', 'ft_request', 'ft_started', 'ft_finished'] - recipient_types_list = ['contact', 'group', 'all'] - config_options = ['event', 'recipient_type', 'recipients', 'status', - 'tab_opened', 'sound', 'sound_file', 'popup', 'auto_open', - 'run_command', 'command', 'systray', 'roster', 'urgency_hint'] - def __init__(self): - self.xml = gtkgui_helpers.get_glade('advanced_notifications_window.glade') - self.window = self.xml.get_widget('advanced_notifications_window') - for w in ('conditions_treeview', 'config_vbox', 'event_combobox', - 'recipient_type_combobox', 'recipient_list_entry', 'delete_button', - 'status_hbox', 'use_sound_cb', 'disable_sound_cb', 'use_popup_cb', - 'disable_popup_cb', 'use_auto_open_cb', 'disable_auto_open_cb', - 'use_systray_cb', 'disable_systray_cb', 'use_roster_cb', - 'disable_roster_cb', 'tab_opened_cb', 'not_tab_opened_cb', - 'sound_entry', 'sound_file_hbox', 'up_button', 'down_button', - 'run_command_cb', 'command_entry', 'urgency_hint_cb'): - self.__dict__[w] = self.xml.get_widget(w) + events_list = ['message_received', 'contact_connected', + 'contact_disconnected', 'contact_change_status', 'gc_msg_highlight', + 'gc_msg', 'ft_request', 'ft_started', 'ft_finished'] + recipient_types_list = ['contact', 'group', 'all'] + config_options = ['event', 'recipient_type', 'recipients', 'status', + 'tab_opened', 'sound', 'sound_file', 'popup', 'auto_open', + 'run_command', 'command', 'systray', 'roster', 'urgency_hint'] + def __init__(self): + self.xml = gtkgui_helpers.get_glade('advanced_notifications_window.glade') + self.window = self.xml.get_widget('advanced_notifications_window') + for w in ('conditions_treeview', 'config_vbox', 'event_combobox', + 'recipient_type_combobox', 'recipient_list_entry', 'delete_button', + 'status_hbox', 'use_sound_cb', 'disable_sound_cb', 'use_popup_cb', + 'disable_popup_cb', 'use_auto_open_cb', 'disable_auto_open_cb', + 'use_systray_cb', 'disable_systray_cb', 'use_roster_cb', + 'disable_roster_cb', 'tab_opened_cb', 'not_tab_opened_cb', + 'sound_entry', 'sound_file_hbox', 'up_button', 'down_button', + 'run_command_cb', 'command_entry', 'urgency_hint_cb'): + self.__dict__[w] = self.xml.get_widget(w) - # Contains status checkboxes - childs = self.status_hbox.get_children() + # Contains status checkboxes + childs = self.status_hbox.get_children() - self.all_status_rb = childs[0] - self.special_status_rb = childs[1] - self.online_cb = childs[2] - self.away_cb = childs[3] - self.xa_cb = childs[4] - self.dnd_cb = childs[5] - self.invisible_cb = childs[6] + self.all_status_rb = childs[0] + self.special_status_rb = childs[1] + self.online_cb = childs[2] + self.away_cb = childs[3] + self.xa_cb = childs[4] + self.dnd_cb = childs[5] + self.invisible_cb = childs[6] - model = gtk.ListStore(int, str) - model.set_sort_column_id(0, gtk.SORT_ASCENDING) - model.clear() - self.conditions_treeview.set_model(model) + model = gtk.ListStore(int, str) + model.set_sort_column_id(0, gtk.SORT_ASCENDING) + model.clear() + self.conditions_treeview.set_model(model) - ## means number - col = gtk.TreeViewColumn(_('#')) - self.conditions_treeview.append_column(col) - renderer = gtk.CellRendererText() - col.pack_start(renderer, expand=False) - col.set_attributes(renderer, text=0) + ## means number + col = gtk.TreeViewColumn(_('#')) + self.conditions_treeview.append_column(col) + renderer = gtk.CellRendererText() + col.pack_start(renderer, expand=False) + col.set_attributes(renderer, text=0) - col = gtk.TreeViewColumn(_('Condition')) - self.conditions_treeview.append_column(col) - renderer = gtk.CellRendererText() - col.pack_start(renderer, expand=True) - col.set_attributes(renderer, text=1) + col = gtk.TreeViewColumn(_('Condition')) + self.conditions_treeview.append_column(col) + renderer = gtk.CellRendererText() + col.pack_start(renderer, expand=True) + col.set_attributes(renderer, text=1) - self.xml.signal_autoconnect(self) + self.xml.signal_autoconnect(self) - # Fill conditions_treeview - num = 0 - while gajim.config.get_per('notifications', str(num)): - iter_ = model.append((num, '')) - path = model.get_path(iter_) - self.conditions_treeview.set_cursor(path) - self.active_num = num - self.initiate_rule_state() - self.set_treeview_string() - num += 1 + # Fill conditions_treeview + num = 0 + while gajim.config.get_per('notifications', str(num)): + iter_ = model.append((num, '')) + path = model.get_path(iter_) + self.conditions_treeview.set_cursor(path) + self.active_num = num + self.initiate_rule_state() + self.set_treeview_string() + num += 1 - # No rule selected at init time - self.conditions_treeview.get_selection().unselect_all() - self.active_num = -1 - self.config_vbox.set_sensitive(False) - self.delete_button.set_sensitive(False) - self.down_button.set_sensitive(False) - self.up_button.set_sensitive(False) + # No rule selected at init time + self.conditions_treeview.get_selection().unselect_all() + self.active_num = -1 + self.config_vbox.set_sensitive(False) + self.delete_button.set_sensitive(False) + self.down_button.set_sensitive(False) + self.up_button.set_sensitive(False) - self.window.show_all() + self.window.show_all() - def initiate_rule_state(self): - """ - Set values for all widgets - """ - if self.active_num < 0: - return - # event - value = gajim.config.get_per('notifications', str(self.active_num), - 'event') - if value: - self.event_combobox.set_active(self.events_list.index(value)) - else: - self.event_combobox.set_active(-1) - # recipient_type - value = gajim.config.get_per('notifications', str(self.active_num), - 'recipient_type') - if value: - self.recipient_type_combobox.set_active( - self.recipient_types_list.index(value)) - else: - self.recipient_type_combobox.set_active(-1) - # recipient - value = gajim.config.get_per('notifications', str(self.active_num), - 'recipients') - if not value: - value = '' - self.recipient_list_entry.set_text(value) - # status - value = gajim.config.get_per('notifications', str(self.active_num), - 'status') - if value == 'all': - self.all_status_rb.set_active(True) - else: - self.special_status_rb.set_active(True) - values = value.split() - for v in ('online', 'away', 'xa', 'dnd', 'invisible'): - if v in values: - self.__dict__[v + '_cb'].set_active(True) - else: - self.__dict__[v + '_cb'].set_active(False) - self.on_status_radiobutton_toggled(self.all_status_rb) - # tab_opened - value = gajim.config.get_per('notifications', str(self.active_num), - 'tab_opened') - self.tab_opened_cb.set_active(True) - self.not_tab_opened_cb.set_active(True) - if value == 'no': - self.tab_opened_cb.set_active(False) - elif value == 'yes': - self.not_tab_opened_cb.set_active(False) - # sound_file - value = gajim.config.get_per('notifications', str(self.active_num), - 'sound_file') - self.sound_entry.set_text(value) - # sound, popup, auto_open, systray, roster - for option in ('sound', 'popup', 'auto_open', 'systray', 'roster'): - value = gajim.config.get_per('notifications', str(self.active_num), - option) - if value == 'yes': - self.__dict__['use_' + option + '_cb'].set_active(True) - else: - self.__dict__['use_' + option + '_cb'].set_active(False) - if value == 'no': - self.__dict__['disable_' + option + '_cb'].set_active(True) - else: - self.__dict__['disable_' + option + '_cb'].set_active(False) - # run_command - value = gajim.config.get_per('notifications', str(self.active_num), - 'run_command') - self.run_command_cb.set_active(value) - # command - value = gajim.config.get_per('notifications', str(self.active_num), - 'command') - self.command_entry.set_text(value) - # urgency_hint - value = gajim.config.get_per('notifications', str(self.active_num), - 'urgency_hint') - self.urgency_hint_cb.set_active(value) + def initiate_rule_state(self): + """ + Set values for all widgets + """ + if self.active_num < 0: + return + # event + value = gajim.config.get_per('notifications', str(self.active_num), + 'event') + if value: + self.event_combobox.set_active(self.events_list.index(value)) + else: + self.event_combobox.set_active(-1) + # recipient_type + value = gajim.config.get_per('notifications', str(self.active_num), + 'recipient_type') + if value: + self.recipient_type_combobox.set_active( + self.recipient_types_list.index(value)) + else: + self.recipient_type_combobox.set_active(-1) + # recipient + value = gajim.config.get_per('notifications', str(self.active_num), + 'recipients') + if not value: + value = '' + self.recipient_list_entry.set_text(value) + # status + value = gajim.config.get_per('notifications', str(self.active_num), + 'status') + if value == 'all': + self.all_status_rb.set_active(True) + else: + self.special_status_rb.set_active(True) + values = value.split() + for v in ('online', 'away', 'xa', 'dnd', 'invisible'): + if v in values: + self.__dict__[v + '_cb'].set_active(True) + else: + self.__dict__[v + '_cb'].set_active(False) + self.on_status_radiobutton_toggled(self.all_status_rb) + # tab_opened + value = gajim.config.get_per('notifications', str(self.active_num), + 'tab_opened') + self.tab_opened_cb.set_active(True) + self.not_tab_opened_cb.set_active(True) + if value == 'no': + self.tab_opened_cb.set_active(False) + elif value == 'yes': + self.not_tab_opened_cb.set_active(False) + # sound_file + value = gajim.config.get_per('notifications', str(self.active_num), + 'sound_file') + self.sound_entry.set_text(value) + # sound, popup, auto_open, systray, roster + for option in ('sound', 'popup', 'auto_open', 'systray', 'roster'): + value = gajim.config.get_per('notifications', str(self.active_num), + option) + if value == 'yes': + self.__dict__['use_' + option + '_cb'].set_active(True) + else: + self.__dict__['use_' + option + '_cb'].set_active(False) + if value == 'no': + self.__dict__['disable_' + option + '_cb'].set_active(True) + else: + self.__dict__['disable_' + option + '_cb'].set_active(False) + # run_command + value = gajim.config.get_per('notifications', str(self.active_num), + 'run_command') + self.run_command_cb.set_active(value) + # command + value = gajim.config.get_per('notifications', str(self.active_num), + 'command') + self.command_entry.set_text(value) + # urgency_hint + value = gajim.config.get_per('notifications', str(self.active_num), + 'urgency_hint') + self.urgency_hint_cb.set_active(value) - def set_treeview_string(self): - (model, iter_) = self.conditions_treeview.get_selection().get_selected() - if not iter_: - return - event = self.event_combobox.get_active_text() - recipient_type = self.recipient_type_combobox.get_active_text() - recipient = '' - if recipient_type != 'everybody': - recipient = self.recipient_list_entry.get_text() - if self.all_status_rb.get_active(): - status = '' - else: - status = _('when I am ') - for st in ('online', 'away', 'xa', 'dnd', 'invisible'): - if self.__dict__[st + '_cb'].get_active(): - status += helpers.get_uf_show(st) + ' ' - model[iter_][1] = "When %s for %s %s %s" % (event, recipient_type, - recipient, status) + def set_treeview_string(self): + (model, iter_) = self.conditions_treeview.get_selection().get_selected() + if not iter_: + return + event = self.event_combobox.get_active_text() + recipient_type = self.recipient_type_combobox.get_active_text() + recipient = '' + if recipient_type != 'everybody': + recipient = self.recipient_list_entry.get_text() + if self.all_status_rb.get_active(): + status = '' + else: + status = _('when I am ') + for st in ('online', 'away', 'xa', 'dnd', 'invisible'): + if self.__dict__[st + '_cb'].get_active(): + status += helpers.get_uf_show(st) + ' ' + model[iter_][1] = "When %s for %s %s %s" % (event, recipient_type, + recipient, status) - def on_conditions_treeview_cursor_changed(self, widget): - (model, iter_) = widget.get_selection().get_selected() - if not iter_: - self.active_num = -1 - return - self.active_num = model[iter_][0] - if self.active_num == 0: - self.up_button.set_sensitive(False) - else: - self.up_button.set_sensitive(True) - max = self.conditions_treeview.get_model().iter_n_children(None) - if self.active_num == max - 1: - self.down_button.set_sensitive(False) - else: - self.down_button.set_sensitive(True) - self.initiate_rule_state() - self.config_vbox.set_sensitive(True) - self.delete_button.set_sensitive(True) + def on_conditions_treeview_cursor_changed(self, widget): + (model, iter_) = widget.get_selection().get_selected() + if not iter_: + self.active_num = -1 + return + self.active_num = model[iter_][0] + if self.active_num == 0: + self.up_button.set_sensitive(False) + else: + self.up_button.set_sensitive(True) + max = self.conditions_treeview.get_model().iter_n_children(None) + if self.active_num == max - 1: + self.down_button.set_sensitive(False) + else: + self.down_button.set_sensitive(True) + self.initiate_rule_state() + self.config_vbox.set_sensitive(True) + self.delete_button.set_sensitive(True) - def on_new_button_clicked(self, widget): - model = self.conditions_treeview.get_model() - num = self.conditions_treeview.get_model().iter_n_children(None) - gajim.config.add_per('notifications', str(num)) - iter_ = model.append((num, '')) - path = model.get_path(iter_) - self.conditions_treeview.set_cursor(path) - self.active_num = num - self.set_treeview_string() - self.config_vbox.set_sensitive(True) + def on_new_button_clicked(self, widget): + model = self.conditions_treeview.get_model() + num = self.conditions_treeview.get_model().iter_n_children(None) + gajim.config.add_per('notifications', str(num)) + iter_ = model.append((num, '')) + path = model.get_path(iter_) + self.conditions_treeview.set_cursor(path) + self.active_num = num + self.set_treeview_string() + self.config_vbox.set_sensitive(True) - def on_delete_button_clicked(self, widget): - (model, iter_) = self.conditions_treeview.get_selection().get_selected() - if not iter_: - return - # up all others - iter2 = model.iter_next(iter_) - num = self.active_num - while iter2: - num = model[iter2][0] - model[iter2][0] = num - 1 - for opt in self.config_options: - val = gajim.config.get_per('notifications', str(num), opt) - gajim.config.set_per('notifications', str(num - 1), opt, val) - iter2 = model.iter_next(iter2) - model.remove(iter_) - gajim.config.del_per('notifications', str(num)) # delete latest - self.active_num = -1 - self.config_vbox.set_sensitive(False) - self.delete_button.set_sensitive(False) - self.up_button.set_sensitive(False) - self.down_button.set_sensitive(False) + def on_delete_button_clicked(self, widget): + (model, iter_) = self.conditions_treeview.get_selection().get_selected() + if not iter_: + return + # up all others + iter2 = model.iter_next(iter_) + num = self.active_num + while iter2: + num = model[iter2][0] + model[iter2][0] = num - 1 + for opt in self.config_options: + val = gajim.config.get_per('notifications', str(num), opt) + gajim.config.set_per('notifications', str(num - 1), opt, val) + iter2 = model.iter_next(iter2) + model.remove(iter_) + gajim.config.del_per('notifications', str(num)) # delete latest + self.active_num = -1 + self.config_vbox.set_sensitive(False) + self.delete_button.set_sensitive(False) + self.up_button.set_sensitive(False) + self.down_button.set_sensitive(False) - def on_up_button_clicked(self, widget): - (model, iter_) = self.conditions_treeview.get_selection().\ - get_selected() - if not iter_: - return - for opt in self.config_options: - val = gajim.config.get_per('notifications', str(self.active_num), opt) - val2 = gajim.config.get_per('notifications', str(self.active_num - 1), - opt) - gajim.config.set_per('notifications', str(self.active_num), opt, val2) - gajim.config.set_per('notifications', str(self.active_num - 1), opt, - val) + def on_up_button_clicked(self, widget): + (model, iter_) = self.conditions_treeview.get_selection().\ + get_selected() + if not iter_: + return + for opt in self.config_options: + val = gajim.config.get_per('notifications', str(self.active_num), opt) + val2 = gajim.config.get_per('notifications', str(self.active_num - 1), + opt) + gajim.config.set_per('notifications', str(self.active_num), opt, val2) + gajim.config.set_per('notifications', str(self.active_num - 1), opt, + val) - model[iter_][0] = self.active_num - 1 - # get previous iter - path = model.get_path(iter_) - iter_ = model.get_iter((path[0] - 1,)) - model[iter_][0] = self.active_num - self.on_conditions_treeview_cursor_changed(self.conditions_treeview) + model[iter_][0] = self.active_num - 1 + # get previous iter + path = model.get_path(iter_) + iter_ = model.get_iter((path[0] - 1,)) + model[iter_][0] = self.active_num + self.on_conditions_treeview_cursor_changed(self.conditions_treeview) - def on_down_button_clicked(self, widget): - (model, iter_) = self.conditions_treeview.get_selection().get_selected() - if not iter_: - return - for opt in self.config_options: - val = gajim.config.get_per('notifications', str(self.active_num), opt) - val2 = gajim.config.get_per('notifications', str(self.active_num + 1), - opt) - gajim.config.set_per('notifications', str(self.active_num), opt, val2) - gajim.config.set_per('notifications', str(self.active_num + 1), opt, - val) + def on_down_button_clicked(self, widget): + (model, iter_) = self.conditions_treeview.get_selection().get_selected() + if not iter_: + return + for opt in self.config_options: + val = gajim.config.get_per('notifications', str(self.active_num), opt) + val2 = gajim.config.get_per('notifications', str(self.active_num + 1), + opt) + gajim.config.set_per('notifications', str(self.active_num), opt, val2) + gajim.config.set_per('notifications', str(self.active_num + 1), opt, + val) - model[iter_][0] = self.active_num + 1 - iter_ = model.iter_next(iter_) - model[iter_][0] = self.active_num - self.on_conditions_treeview_cursor_changed(self.conditions_treeview) + model[iter_][0] = self.active_num + 1 + iter_ = model.iter_next(iter_) + model[iter_][0] = self.active_num + self.on_conditions_treeview_cursor_changed(self.conditions_treeview) - def on_event_combobox_changed(self, widget): - if self.active_num < 0: - return - active = self.event_combobox.get_active() - if active == -1: - event = '' - else: - event = self.events_list[active] - gajim.config.set_per('notifications', str(self.active_num), 'event', - event) - self.set_treeview_string() + def on_event_combobox_changed(self, widget): + if self.active_num < 0: + return + active = self.event_combobox.get_active() + if active == -1: + event = '' + else: + event = self.events_list[active] + gajim.config.set_per('notifications', str(self.active_num), 'event', + event) + self.set_treeview_string() - def on_recipient_type_combobox_changed(self, widget): - if self.active_num < 0: - return - recipient_type = self.recipient_types_list[self.recipient_type_combobox.\ - get_active()] - gajim.config.set_per('notifications', str(self.active_num), - 'recipient_type', recipient_type) - if recipient_type == 'all': - self.recipient_list_entry.hide() - else: - self.recipient_list_entry.show() - self.set_treeview_string() + def on_recipient_type_combobox_changed(self, widget): + if self.active_num < 0: + return + recipient_type = self.recipient_types_list[self.recipient_type_combobox.\ + get_active()] + gajim.config.set_per('notifications', str(self.active_num), + 'recipient_type', recipient_type) + if recipient_type == 'all': + self.recipient_list_entry.hide() + else: + self.recipient_list_entry.show() + self.set_treeview_string() - def on_recipient_list_entry_changed(self, widget): - if self.active_num < 0: - return - recipients = widget.get_text().decode('utf-8') - #TODO: do some check - gajim.config.set_per('notifications', str(self.active_num), - 'recipients', recipients) - self.set_treeview_string() + def on_recipient_list_entry_changed(self, widget): + if self.active_num < 0: + return + recipients = widget.get_text().decode('utf-8') + #TODO: do some check + gajim.config.set_per('notifications', str(self.active_num), + 'recipients', recipients) + self.set_treeview_string() - def set_status_config(self): - if self.active_num < 0: - return - status = '' - for st in ('online', 'away', 'xa', 'dnd', 'invisible'): - if self.__dict__[st + '_cb'].get_active(): - status += st + ' ' - if status: - status = status[:-1] - gajim.config.set_per('notifications', str(self.active_num), 'status', - status) - self.set_treeview_string() + def set_status_config(self): + if self.active_num < 0: + return + status = '' + for st in ('online', 'away', 'xa', 'dnd', 'invisible'): + if self.__dict__[st + '_cb'].get_active(): + status += st + ' ' + if status: + status = status[:-1] + gajim.config.set_per('notifications', str(self.active_num), 'status', + status) + self.set_treeview_string() - def on_status_radiobutton_toggled(self, widget): - if self.active_num < 0: - return - if self.all_status_rb.get_active(): - gajim.config.set_per('notifications', str(self.active_num), 'status', - 'all') - # 'All status' clicked - for st in ('online', 'away', 'xa', 'dnd', 'invisible'): - self.__dict__[st + '_cb'].hide() + def on_status_radiobutton_toggled(self, widget): + if self.active_num < 0: + return + if self.all_status_rb.get_active(): + gajim.config.set_per('notifications', str(self.active_num), 'status', + 'all') + # 'All status' clicked + for st in ('online', 'away', 'xa', 'dnd', 'invisible'): + self.__dict__[st + '_cb'].hide() - self.special_status_rb.show() - else: - self.set_status_config() - # 'special status' clicked - for st in ('online', 'away', 'xa', 'dnd', 'invisible'): - self.__dict__[st + '_cb'].show() + self.special_status_rb.show() + else: + self.set_status_config() + # 'special status' clicked + for st in ('online', 'away', 'xa', 'dnd', 'invisible'): + self.__dict__[st + '_cb'].show() - self.special_status_rb.hide() - self.set_treeview_string() + self.special_status_rb.hide() + self.set_treeview_string() - def on_status_cb_toggled(self, widget): - if self.active_num < 0: - return - self.set_status_config() + def on_status_cb_toggled(self, widget): + if self.active_num < 0: + return + self.set_status_config() - # tab_opened OR (not xor) not_tab_opened must be active - def on_tab_opened_cb_toggled(self, widget): - if self.active_num < 0: - return - if self.tab_opened_cb.get_active(): - if self.not_tab_opened_cb.get_active(): - gajim.config.set_per('notifications', str(self.active_num), - 'tab_opened', 'both') - else: - gajim.config.set_per('notifications', str(self.active_num), - 'tab_opened', 'yes') - elif not self.not_tab_opened_cb.get_active(): - self.not_tab_opened_cb.set_active(True) - gajim.config.set_per('notifications', str(self.active_num), - 'tab_opened', 'no') + # tab_opened OR (not xor) not_tab_opened must be active + def on_tab_opened_cb_toggled(self, widget): + if self.active_num < 0: + return + if self.tab_opened_cb.get_active(): + if self.not_tab_opened_cb.get_active(): + gajim.config.set_per('notifications', str(self.active_num), + 'tab_opened', 'both') + else: + gajim.config.set_per('notifications', str(self.active_num), + 'tab_opened', 'yes') + elif not self.not_tab_opened_cb.get_active(): + self.not_tab_opened_cb.set_active(True) + gajim.config.set_per('notifications', str(self.active_num), + 'tab_opened', 'no') - def on_not_tab_opened_cb_toggled(self, widget): - if self.active_num < 0: - return - if self.not_tab_opened_cb.get_active(): - if self.tab_opened_cb.get_active(): - gajim.config.set_per('notifications', str(self.active_num), - 'tab_opened', 'both') - else: - gajim.config.set_per('notifications', str(self.active_num), - 'tab_opened', 'no') - elif not self.tab_opened_cb.get_active(): - self.tab_opened_cb.set_active(True) - gajim.config.set_per('notifications', str(self.active_num), - 'tab_opened', 'yes') + def on_not_tab_opened_cb_toggled(self, widget): + if self.active_num < 0: + return + if self.not_tab_opened_cb.get_active(): + if self.tab_opened_cb.get_active(): + gajim.config.set_per('notifications', str(self.active_num), + 'tab_opened', 'both') + else: + gajim.config.set_per('notifications', str(self.active_num), + 'tab_opened', 'no') + elif not self.tab_opened_cb.get_active(): + self.tab_opened_cb.set_active(True) + gajim.config.set_per('notifications', str(self.active_num), + 'tab_opened', 'yes') - def on_use_it_toggled(self, widget, oposite_widget, option): - if widget.get_active(): - if oposite_widget.get_active(): - oposite_widget.set_active(False) - gajim.config.set_per('notifications', str(self.active_num), option, - 'yes') - elif oposite_widget.get_active(): - gajim.config.set_per('notifications', str(self.active_num), option, - 'no') - else: - gajim.config.set_per('notifications', str(self.active_num), option, '') + def on_use_it_toggled(self, widget, oposite_widget, option): + if widget.get_active(): + if oposite_widget.get_active(): + oposite_widget.set_active(False) + gajim.config.set_per('notifications', str(self.active_num), option, + 'yes') + elif oposite_widget.get_active(): + gajim.config.set_per('notifications', str(self.active_num), option, + 'no') + else: + gajim.config.set_per('notifications', str(self.active_num), option, '') - def on_disable_it_toggled(self, widget, oposite_widget, option): - if widget.get_active(): - if oposite_widget.get_active(): - oposite_widget.set_active(False) - gajim.config.set_per('notifications', str(self.active_num), option, - 'no') - elif oposite_widget.get_active(): - gajim.config.set_per('notifications', str(self.active_num), option, - 'yes') - else: - gajim.config.set_per('notifications', str(self.active_num), option, '') + def on_disable_it_toggled(self, widget, oposite_widget, option): + if widget.get_active(): + if oposite_widget.get_active(): + oposite_widget.set_active(False) + gajim.config.set_per('notifications', str(self.active_num), option, + 'no') + elif oposite_widget.get_active(): + gajim.config.set_per('notifications', str(self.active_num), option, + 'yes') + else: + gajim.config.set_per('notifications', str(self.active_num), option, '') - def on_use_sound_cb_toggled(self, widget): - self.on_use_it_toggled(widget, self.disable_sound_cb, 'sound') - if widget.get_active(): - self.sound_file_hbox.set_sensitive(True) - else: - self.sound_file_hbox.set_sensitive(False) + def on_use_sound_cb_toggled(self, widget): + self.on_use_it_toggled(widget, self.disable_sound_cb, 'sound') + if widget.get_active(): + self.sound_file_hbox.set_sensitive(True) + else: + self.sound_file_hbox.set_sensitive(False) - def on_browse_for_sounds_button_clicked(self, widget, data=None): - if self.active_num < 0: - return + def on_browse_for_sounds_button_clicked(self, widget, data=None): + if self.active_num < 0: + return - def on_ok(widget, path_to_snd_file): - dialog.destroy() - if not path_to_snd_file: - path_to_snd_file = '' - gajim.config.set_per('notifications', str(self.active_num), - 'sound_file', path_to_snd_file) - self.sound_entry.set_text(path_to_snd_file) + def on_ok(widget, path_to_snd_file): + dialog.destroy() + if not path_to_snd_file: + path_to_snd_file = '' + gajim.config.set_per('notifications', str(self.active_num), + 'sound_file', path_to_snd_file) + self.sound_entry.set_text(path_to_snd_file) - path_to_snd_file = self.sound_entry.get_text().decode('utf-8') - path_to_snd_file = os.path.join(os.getcwd(), path_to_snd_file) - dialog = SoundChooserDialog(path_to_snd_file, on_ok) + path_to_snd_file = self.sound_entry.get_text().decode('utf-8') + path_to_snd_file = os.path.join(os.getcwd(), path_to_snd_file) + dialog = SoundChooserDialog(path_to_snd_file, on_ok) - def on_play_button_clicked(self, widget): - helpers.play_sound_file(self.sound_entry.get_text().decode('utf-8')) + def on_play_button_clicked(self, widget): + helpers.play_sound_file(self.sound_entry.get_text().decode('utf-8')) - def on_disable_sound_cb_toggled(self, widget): - self.on_disable_it_toggled(widget, self.use_sound_cb, 'sound') + def on_disable_sound_cb_toggled(self, widget): + self.on_disable_it_toggled(widget, self.use_sound_cb, 'sound') - def on_sound_entry_changed(self, widget): - gajim.config.set_per('notifications', str(self.active_num), - 'sound_file', widget.get_text().decode('utf-8')) + def on_sound_entry_changed(self, widget): + gajim.config.set_per('notifications', str(self.active_num), + 'sound_file', widget.get_text().decode('utf-8')) - def on_use_popup_cb_toggled(self, widget): - self.on_use_it_toggled(widget, self.disable_popup_cb, 'popup') + def on_use_popup_cb_toggled(self, widget): + self.on_use_it_toggled(widget, self.disable_popup_cb, 'popup') - def on_disable_popup_cb_toggled(self, widget): - self.on_disable_it_toggled(widget, self.use_popup_cb, 'popup') + def on_disable_popup_cb_toggled(self, widget): + self.on_disable_it_toggled(widget, self.use_popup_cb, 'popup') - def on_use_auto_open_cb_toggled(self, widget): - self.on_use_it_toggled(widget, self.disable_auto_open_cb, 'auto_open') + def on_use_auto_open_cb_toggled(self, widget): + self.on_use_it_toggled(widget, self.disable_auto_open_cb, 'auto_open') - def on_disable_auto_open_cb_toggled(self, widget): - self.on_disable_it_toggled(widget, self.use_auto_open_cb, 'auto_open') + def on_disable_auto_open_cb_toggled(self, widget): + self.on_disable_it_toggled(widget, self.use_auto_open_cb, 'auto_open') - def on_run_command_cb_toggled(self, widget): - gajim.config.set_per('notifications', str(self.active_num), 'run_command', - widget.get_active()) - if widget.get_active(): - self.command_entry.set_sensitive(True) - else: - self.command_entry.set_sensitive(False) + def on_run_command_cb_toggled(self, widget): + gajim.config.set_per('notifications', str(self.active_num), 'run_command', + widget.get_active()) + if widget.get_active(): + self.command_entry.set_sensitive(True) + else: + self.command_entry.set_sensitive(False) - def on_command_entry_changed(self, widget): - gajim.config.set_per('notifications', str(self.active_num), 'command', - widget.get_text().decode('utf-8')) + def on_command_entry_changed(self, widget): + gajim.config.set_per('notifications', str(self.active_num), 'command', + widget.get_text().decode('utf-8')) - def on_use_systray_cb_toggled(self, widget): - self.on_use_it_toggled(widget, self.disable_systray_cb, 'systray') + def on_use_systray_cb_toggled(self, widget): + self.on_use_it_toggled(widget, self.disable_systray_cb, 'systray') - def on_disable_systray_cb_toggled(self, widget): - self.on_disable_it_toggled(widget, self.use_systray_cb, 'systray') + def on_disable_systray_cb_toggled(self, widget): + self.on_disable_it_toggled(widget, self.use_systray_cb, 'systray') - def on_use_roster_cb_toggled(self, widget): - self.on_use_it_toggled(widget, self.disable_roster_cb, 'roster') + def on_use_roster_cb_toggled(self, widget): + self.on_use_it_toggled(widget, self.disable_roster_cb, 'roster') - def on_disable_roster_cb_toggled(self, widget): - self.on_disable_it_toggled(widget, self.use_roster_cb, 'roster') + def on_disable_roster_cb_toggled(self, widget): + self.on_disable_it_toggled(widget, self.use_roster_cb, 'roster') - def on_urgency_hint_cb_toggled(self, widget): - gajim.config.set_per('notifications', str(self.active_num), - 'uregency_hint', widget.get_active()) + def on_urgency_hint_cb_toggled(self, widget): + gajim.config.set_per('notifications', str(self.active_num), + 'uregency_hint', widget.get_active()) - def on_close_window(self, widget): - self.window.destroy() + def on_close_window(self, widget): + self.window.destroy() class TransformChatToMUC: - # Keep a reference on windows so garbage collector don't restroy them - instances = [] - def __init__(self, account, jids, preselected=None): - """ - This window is used to trasform a one-to-one chat to a MUC. We do 2 - things: first select the server and then make a guests list - """ + # Keep a reference on windows so garbage collector don't restroy them + instances = [] + def __init__(self, account, jids, preselected=None): + """ + This window is used to trasform a one-to-one chat to a MUC. We do 2 + things: first select the server and then make a guests list + """ - self.instances.append(self) - self.account = account - self.auto_jids = jids - self.preselected_jids = preselected + self.instances.append(self) + self.account = account + self.auto_jids = jids + self.preselected_jids = preselected - self.xml = gtkgui_helpers.get_glade('chat_to_muc_window.glade') - self.window = self.xml.get_widget('chat_to_muc_window') + self.xml = gtkgui_helpers.get_glade('chat_to_muc_window.glade') + self.window = self.xml.get_widget('chat_to_muc_window') - for widget_to_add in ('invite_button', 'cancel_button', - 'server_list_comboboxentry', 'guests_treeview', - 'server_and_guests_hseparator', 'server_select_label'): - self.__dict__[widget_to_add] = self.xml.get_widget(widget_to_add) + for widget_to_add in ('invite_button', 'cancel_button', + 'server_list_comboboxentry', 'guests_treeview', + 'server_and_guests_hseparator', 'server_select_label'): + self.__dict__[widget_to_add] = self.xml.get_widget(widget_to_add) - server_list = [] - self.servers = gtk.ListStore(str) - self.server_list_comboboxentry.set_model(self.servers) + server_list = [] + self.servers = gtk.ListStore(str) + self.server_list_comboboxentry.set_model(self.servers) - self.server_list_comboboxentry.set_text_column(0) + self.server_list_comboboxentry.set_text_column(0) - # get the muc server of our server - if 'jabber' in gajim.connections[account].muc_jid: - server_list.append(gajim.connections[account].muc_jid['jabber']) - # add servers or recently joined groupchats - recently_groupchat = gajim.config.get('recently_groupchat').split() - for g in recently_groupchat: - server = gajim.get_server_from_jid(g) - if server not in server_list and not server.startswith('irc'): - server_list.append(server) - # add a default server - if not server_list: - server_list.append('conference.jabber.org') + # get the muc server of our server + if 'jabber' in gajim.connections[account].muc_jid: + server_list.append(gajim.connections[account].muc_jid['jabber']) + # add servers or recently joined groupchats + recently_groupchat = gajim.config.get('recently_groupchat').split() + for g in recently_groupchat: + server = gajim.get_server_from_jid(g) + if server not in server_list and not server.startswith('irc'): + server_list.append(server) + # add a default server + if not server_list: + server_list.append('conference.jabber.org') - for s in server_list: - self.servers.append([s]) + for s in server_list: + self.servers.append([s]) - self.server_list_comboboxentry.set_active(0) + self.server_list_comboboxentry.set_active(0) - # set treeview - # name, jid - self.store = gtk.ListStore(gtk.gdk.Pixbuf, str, str) - self.store.set_sort_column_id(1, gtk.SORT_ASCENDING) - self.guests_treeview.set_model(self.store) + # set treeview + # name, jid + self.store = gtk.ListStore(gtk.gdk.Pixbuf, str, str) + self.store.set_sort_column_id(1, gtk.SORT_ASCENDING) + self.guests_treeview.set_model(self.store) - renderer1 = gtk.CellRendererText() - renderer2 = gtk.CellRendererPixbuf() - column = gtk.TreeViewColumn('Status', renderer2, pixbuf=0) - self.guests_treeview.append_column(column) - column = gtk.TreeViewColumn('Name', renderer1, text=1) - self.guests_treeview.append_column(column) + renderer1 = gtk.CellRendererText() + renderer2 = gtk.CellRendererPixbuf() + column = gtk.TreeViewColumn('Status', renderer2, pixbuf=0) + self.guests_treeview.append_column(column) + column = gtk.TreeViewColumn('Name', renderer1, text=1) + self.guests_treeview.append_column(column) - self.guests_treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) + self.guests_treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) - # All contacts beside the following can be invited: - # transports, zeroconf contacts, minimized groupchats - def invitable(contact, contact_transport=None): - return (contact.jid not in self.auto_jids and - contact.jid != gajim.get_jid_from_account(self.account) and - contact.jid not in gajim.interface.minimized_controls[account] and - not contact.is_transport() and - not contact_transport) + # All contacts beside the following can be invited: + # transports, zeroconf contacts, minimized groupchats + def invitable(contact, contact_transport=None): + return (contact.jid not in self.auto_jids and + contact.jid != gajim.get_jid_from_account(self.account) and + contact.jid not in gajim.interface.minimized_controls[account] and + not contact.is_transport() and + not contact_transport) - # set jabber id and pseudos - for account in gajim.contacts.get_accounts(): - if gajim.connections[account].is_zeroconf: - continue - for jid in gajim.contacts.get_jid_list(account): - contact = \ - gajim.contacts.get_contact_with_highest_priority(account, jid) - contact_transport = gajim.get_transport_name_from_jid(jid) - # Add contact if it can be invited - if invitable(contact, contact_transport) and \ - contact.show not in ('offline', 'error'): - img = gajim.interface.jabber_state_images['16'][contact.show] - name = contact.name - if name == '': - name = jid.split('@')[0] - iter_ = self.store.append([img.get_pixbuf(), name, jid]) - # preselect treeview rows - if self.preselected_jids and jid in self.preselected_jids: - path = self.store.get_path(iter_) - self.guests_treeview.get_selection().\ - select_path(path) + # set jabber id and pseudos + for account in gajim.contacts.get_accounts(): + if gajim.connections[account].is_zeroconf: + continue + for jid in gajim.contacts.get_jid_list(account): + contact = \ + gajim.contacts.get_contact_with_highest_priority(account, jid) + contact_transport = gajim.get_transport_name_from_jid(jid) + # Add contact if it can be invited + if invitable(contact, contact_transport) and \ + contact.show not in ('offline', 'error'): + img = gajim.interface.jabber_state_images['16'][contact.show] + name = contact.name + if name == '': + name = jid.split('@')[0] + iter_ = self.store.append([img.get_pixbuf(), name, jid]) + # preselect treeview rows + if self.preselected_jids and jid in self.preselected_jids: + path = self.store.get_path(iter_) + self.guests_treeview.get_selection().\ + select_path(path) - # show all - self.window.show_all() + # show all + self.window.show_all() - self.xml.signal_autoconnect(self) + self.xml.signal_autoconnect(self) - def on_chat_to_muc_window_destroy(self, widget): - self.instances.remove(self) + def on_chat_to_muc_window_destroy(self, widget): + self.instances.remove(self) - def on_chat_to_muc_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: # ESCAPE - self.window.destroy() + def on_chat_to_muc_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: # ESCAPE + self.window.destroy() - def on_invite_button_clicked(self, widget): - server = self.server_list_comboboxentry.get_active_text() - if server == '': - return - gajim.connections[self.account].check_unique_room_id_support(server, self) + def on_invite_button_clicked(self, widget): + server = self.server_list_comboboxentry.get_active_text() + if server == '': + return + gajim.connections[self.account].check_unique_room_id_support(server, self) - def unique_room_id_supported(self, server, room_id): - guest_list = [] - guests = self.guests_treeview.get_selection().get_selected_rows() - for guest in guests[1]: - iter_ = self.store.get_iter(guest) - guest_list.append(self.store[iter_][2].decode('utf-8')) - for guest in self.auto_jids: - guest_list.append(guest) - room_jid = room_id + '@' + server - gajim.automatic_rooms[self.account][room_jid] = {} - gajim.automatic_rooms[self.account][room_jid]['invities'] = guest_list - gajim.automatic_rooms[self.account][room_jid]['continue_tag'] = True - gajim.interface.join_gc_room(self.account, room_jid, - gajim.nicks[self.account], None, is_continued=True) - self.window.destroy() + def unique_room_id_supported(self, server, room_id): + guest_list = [] + guests = self.guests_treeview.get_selection().get_selected_rows() + for guest in guests[1]: + iter_ = self.store.get_iter(guest) + guest_list.append(self.store[iter_][2].decode('utf-8')) + for guest in self.auto_jids: + guest_list.append(guest) + room_jid = room_id + '@' + server + gajim.automatic_rooms[self.account][room_jid] = {} + gajim.automatic_rooms[self.account][room_jid]['invities'] = guest_list + gajim.automatic_rooms[self.account][room_jid]['continue_tag'] = True + gajim.interface.join_gc_room(self.account, room_jid, + gajim.nicks[self.account], None, is_continued=True) + self.window.destroy() - def on_cancel_button_clicked(self, widget): - self.window.destroy() + def on_cancel_button_clicked(self, widget): + self.window.destroy() - def unique_room_id_error(self, server): - self.unique_room_id_supported(server, - gajim.nicks[self.account].lower().replace(' ','') + str(randrange( - 9999999))) + def unique_room_id_error(self, server): + self.unique_room_id_supported(server, + gajim.nicks[self.account].lower().replace(' ', '') + str(randrange( + 9999999))) class DataFormWindow(Dialog): - def __init__(self, form, on_response_ok): - self.df_response_ok = on_response_ok - Dialog.__init__(self, None, 'test', [(gtk.STOCK_CANCEL, - gtk.RESPONSE_REJECT), (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)], - on_response_ok=self.on_ok) - self.set_resizable(True) - gtkgui_helpers.resize_window(self, 600, 400) - self.dataform_widget = dataforms_widget.DataFormWidget() - self.dataform = dataforms.ExtendForm(node=form) - self.dataform_widget.set_sensitive(True) - self.dataform_widget.data_form = self.dataform - self.dataform_widget.show_all() - self.vbox.pack_start(self.dataform_widget) + def __init__(self, form, on_response_ok): + self.df_response_ok = on_response_ok + Dialog.__init__(self, None, 'test', [(gtk.STOCK_CANCEL, + gtk.RESPONSE_REJECT), (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)], + on_response_ok=self.on_ok) + self.set_resizable(True) + gtkgui_helpers.resize_window(self, 600, 400) + self.dataform_widget = dataforms_widget.DataFormWidget() + self.dataform = dataforms.ExtendForm(node=form) + self.dataform_widget.set_sensitive(True) + self.dataform_widget.data_form = self.dataform + self.dataform_widget.show_all() + self.vbox.pack_start(self.dataform_widget) - def on_ok(self): - form = self.dataform_widget.data_form - if isinstance(self.df_response_ok, tuple): - self.df_response_ok[0](form, *self.df_response_ok[1:]) - else: - self.df_response_ok(form) - self.destroy() + def on_ok(self): + form = self.dataform_widget.data_form + if isinstance(self.df_response_ok, tuple): + self.df_response_ok[0](form, *self.df_response_ok[1:]) + else: + self.df_response_ok(form) + self.destroy() class ESessionInfoWindow: - """ - Class for displaying information about a XEP-0116 encrypted session - """ - def __init__(self, session): - self.session = session + """ + Class for displaying information about a XEP-0116 encrypted session + """ + def __init__(self, session): + self.session = session - self.xml = gtkgui_helpers.get_glade('esession_info_window.glade') - self.xml.signal_autoconnect(self) + self.xml = gtkgui_helpers.get_glade('esession_info_window.glade') + self.xml.signal_autoconnect(self) - self.security_image = self.xml.get_widget('security_image') - self.verify_now_button = self.xml.get_widget('verify_now_button') - self.button_label = self.xml.get_widget('button_label') - self.window = self.xml.get_widget('esession_info_window') - self.update_info() + self.security_image = self.xml.get_widget('security_image') + self.verify_now_button = self.xml.get_widget('verify_now_button') + self.button_label = self.xml.get_widget('button_label') + self.window = self.xml.get_widget('esession_info_window') + self.update_info() - self.window.show_all() + self.window.show_all() - def update_info(self): - labeltext = _('''Your chat session with %(jid)s is encrypted.\n\nThis session's Short Authentication String is %(sas)s.''') % {'jid': self.session.jid, 'sas': self.session.sas} - dir_ = os.path.join(gajim.DATA_DIR, 'pixmaps') + def update_info(self): + labeltext = _('''Your chat session with %(jid)s is encrypted.\n\nThis session's Short Authentication String is %(sas)s.''') % {'jid': self.session.jid, 'sas': self.session.sas} + dir_ = os.path.join(gajim.DATA_DIR, 'pixmaps') - if self.session.verified_identity: - labeltext += '\n\n' + _('''You have already verified this contact's identity.''') - security_image = 'security-high-big.png' - if self.session.control: - self.session.control._show_lock_image(True, 'E2E', True, - self.session.is_loggable(), True) + if self.session.verified_identity: + labeltext += '\n\n' + _('''You have already verified this contact's identity.''') + security_image = 'security-high-big.png' + if self.session.control: + self.session.control._show_lock_image(True, 'E2E', True, + self.session.is_loggable(), True) - verification_status = _('''Contact's identity verified''') - self.window.set_title(verification_status) - self.xml.get_widget('verification_status_label').set_markup( - '' + - verification_status + - '') + verification_status = _('''Contact's identity verified''') + self.window.set_title(verification_status) + self.xml.get_widget('verification_status_label').set_markup( + '' + + verification_status + + '') - self.xml.get_widget('dialog-action_area1').set_no_show_all(True) - self.button_label.set_text(_('Verify again...')) - else: - if self.session.control: - self.session.control._show_lock_image(True, 'E2E', True, - self.session.is_loggable(), False) - labeltext += '\n\n' + _('''To be certain that only the expected person can read your messages or send you messages, you need to verify their identity by clicking the button below.''') - security_image = 'security-low-big.png' + self.xml.get_widget('dialog-action_area1').set_no_show_all(True) + self.button_label.set_text(_('Verify again...')) + else: + if self.session.control: + self.session.control._show_lock_image(True, 'E2E', True, + self.session.is_loggable(), False) + labeltext += '\n\n' + _('''To be certain that only the expected person can read your messages or send you messages, you need to verify their identity by clicking the button below.''') + security_image = 'security-low-big.png' - verification_status = _('''Contact's identity NOT verified''') - self.window.set_title(verification_status) - self.xml.get_widget('verification_status_label').set_markup( - '' + - verification_status + - '') + verification_status = _('''Contact's identity NOT verified''') + self.window.set_title(verification_status) + self.xml.get_widget('verification_status_label').set_markup( + '' + + verification_status + + '') - self.button_label.set_text(_('Verify...')) + self.button_label.set_text(_('Verify...')) - path = os.path.join(dir_, security_image) - filename = os.path.abspath(path) - self.security_image.set_from_file(filename) + path = os.path.join(dir_, security_image) + filename = os.path.abspath(path) + self.security_image.set_from_file(filename) - self.xml.get_widget('info_display').set_markup(labeltext) + self.xml.get_widget('info_display').set_markup(labeltext) - def on_close_button_clicked(self, widget): - self.window.destroy() + def on_close_button_clicked(self, widget): + self.window.destroy() - def on_verify_now_button_clicked(self, widget): - pritext = _('''Have you verified the contact's identity?''') - sectext = _('''To prevent talking to an unknown person, you should speak to %(jid)s directly (in person or on the phone) and verify that they see the same Short Authentication String (SAS) as you.\n\nThis session's Short Authentication String is %(sas)s.''') % {'jid': self.session.jid, 'sas': self.session.sas} - sectext += '\n\n' + _('Did you talk to the remote contact and verify the SAS?') + def on_verify_now_button_clicked(self, widget): + pritext = _('''Have you verified the contact's identity?''') + sectext = _('''To prevent talking to an unknown person, you should speak to %(jid)s directly (in person or on the phone) and verify that they see the same Short Authentication String (SAS) as you.\n\nThis session's Short Authentication String is %(sas)s.''') % {'jid': self.session.jid, 'sas': self.session.sas} + sectext += '\n\n' + _('Did you talk to the remote contact and verify the SAS?') - def on_yes(checked): - self.session._verified_srs_cb() - self.session.verified_identity = True - self.update_info() + def on_yes(checked): + self.session._verified_srs_cb() + self.session.verified_identity = True + self.update_info() - def on_no(): - self.session._unverified_srs_cb() - self.session.verified_identity = False - self.update_info() + def on_no(): + self.session._unverified_srs_cb() + self.session.verified_identity = False + self.update_info() - YesNoDialog(pritext, sectext, on_response_yes=on_yes, on_response_no=on_no) + YesNoDialog(pritext, sectext, on_response_yes=on_yes, on_response_no=on_no) class GPGInfoWindow: - """ - Class for displaying information about a XEP-0116 encrypted session - """ - def __init__(self, control): - xml = gtkgui_helpers.get_glade('esession_info_window.glade') - security_image = xml.get_widget('security_image') - status_label = xml.get_widget('verification_status_label') - info_label = xml.get_widget('info_display') - verify_now_button = xml.get_widget('verify_now_button') - self.window = xml.get_widget('esession_info_window') - account = control.account - keyID = control.contact.keyID - error = None + """ + Class for displaying information about a XEP-0116 encrypted session + """ + def __init__(self, control): + xml = gtkgui_helpers.get_glade('esession_info_window.glade') + security_image = xml.get_widget('security_image') + status_label = xml.get_widget('verification_status_label') + info_label = xml.get_widget('info_display') + verify_now_button = xml.get_widget('verify_now_button') + self.window = xml.get_widget('esession_info_window') + account = control.account + keyID = control.contact.keyID + error = None - verify_now_button.set_no_show_all(True) - verify_now_button.hide() + verify_now_button.set_no_show_all(True) + verify_now_button.hide() - if keyID.endswith('MISMATCH'): - verification_status = _('''Contact's identity NOT verified''') - info = _('The contact\'s key (%s) does not match the key ' - 'assigned in Gajim.') % keyID[:8] - image = 'security-low-big.png' - elif not keyID: - # No key assigned nor a key is used by remote contact - verification_status = _('No GPG key assigned') - info = _('No GPG key is assigned to this contact. So you cannot ' - 'encrypt messages.') - image = 'security-low-big.png' - else: - error = gajim.connections[account].gpg.encrypt('test', [keyID])[1] - if error: - verification_status = _('''Contact's identity NOT verified''') - info = _('GPG key is assigned to this contact, but you do not ' - 'trust his key, so message cannot be encrypted. Use ' - 'your GPG client to trust this key.') - image = 'security-low-big.png' - else: - verification_status = _('''Contact's identity verified''') - info = _('GPG Key is assigned to this contact, and you trust his ' - 'key, so messages will be encrypted.') - image = 'security-high-big.png' + if keyID.endswith('MISMATCH'): + verification_status = _('''Contact's identity NOT verified''') + info = _('The contact\'s key (%s) does not match the key ' + 'assigned in Gajim.') % keyID[:8] + image = 'security-low-big.png' + elif not keyID: + # No key assigned nor a key is used by remote contact + verification_status = _('No GPG key assigned') + info = _('No GPG key is assigned to this contact. So you cannot ' + 'encrypt messages.') + image = 'security-low-big.png' + else: + error = gajim.connections[account].gpg.encrypt('test', [keyID])[1] + if error: + verification_status = _('''Contact's identity NOT verified''') + info = _('GPG key is assigned to this contact, but you do not ' + 'trust his key, so message cannot be encrypted. Use ' + 'your GPG client to trust this key.') + image = 'security-low-big.png' + else: + verification_status = _('''Contact's identity verified''') + info = _('GPG Key is assigned to this contact, and you trust his ' + 'key, so messages will be encrypted.') + image = 'security-high-big.png' - status_label.set_markup('%s' % \ - verification_status) - info_label.set_markup(info) + status_label.set_markup('%s' % \ + verification_status) + info_label.set_markup(info) - dir_ = os.path.join(gajim.DATA_DIR, 'pixmaps') - path = os.path.join(dir_, image) - filename = os.path.abspath(path) - security_image.set_from_file(filename) + dir_ = os.path.join(gajim.DATA_DIR, 'pixmaps') + path = os.path.join(dir_, image) + filename = os.path.abspath(path) + security_image.set_from_file(filename) - xml.signal_autoconnect(self) - self.window.show_all() + xml.signal_autoconnect(self) + self.window.show_all() - def on_close_button_clicked(self, widget): - self.window.destroy() + def on_close_button_clicked(self, widget): + self.window.destroy() class ResourceConflictDialog(TimeoutDialog, InputDialog): - def __init__(self, title, text, resource, ok_handler): - TimeoutDialog.__init__(self, 15, self.on_timeout) - InputDialog.__init__(self, title, text, input_str=resource, - is_modal=False, ok_handler=ok_handler) - self.title_text = title - self.run_timeout() + def __init__(self, title, text, resource, ok_handler): + TimeoutDialog.__init__(self, 15, self.on_timeout) + InputDialog.__init__(self, title, text, input_str=resource, + is_modal=False, ok_handler=ok_handler) + self.title_text = title + self.run_timeout() - def on_timeout(self): - self.on_okbutton_clicked(None) + def on_timeout(self): + self.on_okbutton_clicked(None) class VoIPCallReceivedDialog(object): - instances = {} - def __init__(self, account, contact_jid, sid, content_types): - self.instances[(contact_jid, sid)] = self - self.account = account - self.fjid = contact_jid - self.sid = sid - self.content_types = content_types + instances = {} + def __init__(self, account, contact_jid, sid, content_types): + self.instances[(contact_jid, sid)] = self + self.account = account + self.fjid = contact_jid + self.sid = sid + self.content_types = content_types - xml = gtkgui_helpers.get_glade('voip_call_received_dialog.glade') - xml.signal_autoconnect(self) + xml = gtkgui_helpers.get_glade('voip_call_received_dialog.glade') + xml.signal_autoconnect(self) - jid = gajim.get_jid_without_resource(self.fjid) - contact = gajim.contacts.get_first_contact_from_jid(account, jid) - if contact and contact.name: - self.contact_text = '%s (%s)' % (contact.name, jid) - else: - self.contact_text = contact_jid + jid = gajim.get_jid_without_resource(self.fjid) + contact = gajim.contacts.get_first_contact_from_jid(account, jid) + if contact and contact.name: + self.contact_text = '%s (%s)' % (contact.name, jid) + else: + self.contact_text = contact_jid - self.dialog = xml.get_widget('voip_call_received_messagedialog') - self.set_secondary_text() + self.dialog = xml.get_widget('voip_call_received_messagedialog') + self.set_secondary_text() - self.dialog.show_all() + self.dialog.show_all() - @classmethod - def get_dialog(cls, jid, sid): - if (jid, sid) in cls.instances: - return cls.instances[(jid, sid)] - else: - return None + @classmethod + def get_dialog(cls, jid, sid): + if (jid, sid) in cls.instances: + return cls.instances[(jid, sid)] + else: + return None - def set_secondary_text(self): - if 'audio' in self.content_types and 'video' in self.content_types: - types_text = _('an audio and video') - elif 'audio' in self.content_types: - types_text = _('an audio') - elif 'video' in self.content_types: - types_text = _('a video') + def set_secondary_text(self): + if 'audio' in self.content_types and 'video' in self.content_types: + types_text = _('an audio and video') + elif 'audio' in self.content_types: + types_text = _('an audio') + elif 'video' in self.content_types: + types_text = _('a video') - # do the substitution - self.dialog.set_property('secondary-text', - _('%(contact)s wants to start %(type)s session with you. Do you want ' - 'to answer the call?') % {'contact': self.contact_text, 'type': types_text}) + # do the substitution + self.dialog.set_property('secondary-text', + _('%(contact)s wants to start %(type)s session with you. Do you want ' + 'to answer the call?') % {'contact': self.contact_text, 'type': types_text}) - def add_contents(self, content_types): - for type_ in content_types: - if type_ not in self.content_types: - self.content_types.add(type_) - self.set_secondary_text() + def add_contents(self, content_types): + for type_ in content_types: + if type_ not in self.content_types: + self.content_types.add(type_) + self.set_secondary_text() - def on_voip_call_received_messagedialog_destroy(self, dialog): - if (self.fjid, self.sid) in self.instances: - del self.instances[(self.fjid, self.sid)] + def on_voip_call_received_messagedialog_destroy(self, dialog): + if (self.fjid, self.sid) in self.instances: + del self.instances[(self.fjid, self.sid)] - def on_voip_call_received_messagedialog_close(self, dialog): - return self.on_voip_call_received_messagedialog_response(dialog, - gtk.RESPONSE_NO) + def on_voip_call_received_messagedialog_close(self, dialog): + return self.on_voip_call_received_messagedialog_response(dialog, + gtk.RESPONSE_NO) - def on_voip_call_received_messagedialog_response(self, dialog, response): - # we've got response from user, either stop connecting or accept the call - session = gajim.connections[self.account].get_jingle_session(self.fjid, - self.sid) - if not session: - return - if response == gtk.RESPONSE_YES: - #TODO: Ensure that ctrl.contact.resource == resource - jid = gajim.get_jid_without_resource(self.fjid) - resource = gajim.get_resource_from_jid(self.fjid) - ctrl = gajim.interface.msg_win_mgr.get_control(self.fjid, self.account) - if not ctrl: - ctrl = gajim.interface.msg_win_mgr.get_control(jid, self.account) - if not ctrl: - # open chat control - contact = gajim.contacts.get_contact(self.account, jid, resource) - if not contact: - contact = gajim.contacts.get_contact(self.account, jid) - if not contact: - return - ctrl = gajim.interface.new_chat(contact, self.account) - # Chat control opened, update content's status - if session.get_content('audio'): - ctrl.set_audio_state('connecting', self.sid) - if session.get_content('video'): - ctrl.set_video_state('connecting', self.sid) - # Now, accept the content/sessions. - # This should be done after the chat control is running - if not session.accepted: - session.approve_session() - for content in self.content_types: - session.approve_content(content) - else: # response==gtk.RESPONSE_NO - if not session.accepted: - session.decline_session() - else: - for content in self.content_types: - session.reject_content(content) + def on_voip_call_received_messagedialog_response(self, dialog, response): + # we've got response from user, either stop connecting or accept the call + session = gajim.connections[self.account].get_jingle_session(self.fjid, + self.sid) + if not session: + return + if response == gtk.RESPONSE_YES: + #TODO: Ensure that ctrl.contact.resource == resource + jid = gajim.get_jid_without_resource(self.fjid) + resource = gajim.get_resource_from_jid(self.fjid) + ctrl = gajim.interface.msg_win_mgr.get_control(self.fjid, self.account) + if not ctrl: + ctrl = gajim.interface.msg_win_mgr.get_control(jid, self.account) + if not ctrl: + # open chat control + contact = gajim.contacts.get_contact(self.account, jid, resource) + if not contact: + contact = gajim.contacts.get_contact(self.account, jid) + if not contact: + return + ctrl = gajim.interface.new_chat(contact, self.account) + # Chat control opened, update content's status + if session.get_content('audio'): + ctrl.set_audio_state('connecting', self.sid) + if session.get_content('video'): + ctrl.set_video_state('connecting', self.sid) + # Now, accept the content/sessions. + # This should be done after the chat control is running + if not session.accepted: + session.approve_session() + for content in self.content_types: + session.approve_content(content) + else: # response==gtk.RESPONSE_NO + if not session.accepted: + session.decline_session() + else: + for content in self.content_types: + session.reject_content(content) - dialog.destroy() + dialog.destroy() - -# vim: se ts=3: diff --git a/src/disco.py b/src/disco.py index c38c7ae7f..9d9360d9f 100644 --- a/src/disco.py +++ b/src/disco.py @@ -68,2156 +68,2154 @@ from common import helpers # For the browser class, None means that the service will only be browsable # when it advertises disco as it's feature, False means it's never browsable. def _gen_agent_type_info(): - return { - # Defaults - (0, 0): (None, None), + return { + # Defaults + (0, 0): (None, None), - # Jabber server - ('server', 'im'): (ToplevelAgentBrowser, 'jabber.png'), - ('services', 'jabber'): (ToplevelAgentBrowser, 'jabber.png'), - ('hierarchy', 'branch'): (AgentBrowser, 'jabber.png'), + # Jabber server + ('server', 'im'): (ToplevelAgentBrowser, 'jabber.png'), + ('services', 'jabber'): (ToplevelAgentBrowser, 'jabber.png'), + ('hierarchy', 'branch'): (AgentBrowser, 'jabber.png'), - # Services - ('conference', 'text'): (MucBrowser, 'conference.png'), - ('headline', 'rss'): (AgentBrowser, 'rss.png'), - ('headline', 'weather'): (False, 'weather.png'), - ('gateway', 'weather'): (False, 'weather.png'), - ('_jid', 'weather'): (False, 'weather.png'), - ('gateway', 'sip'): (False, 'sip.png'), - ('directory', 'user'): (None, 'jud.png'), - ('pubsub', 'generic'): (PubSubBrowser, 'pubsub.png'), - ('pubsub', 'service'): (PubSubBrowser, 'pubsub.png'), - ('proxy', 'bytestreams'): (None, 'bytestreams.png'), # Socks5 FT proxy - ('headline', 'newmail'): (ToplevelAgentBrowser, 'mail.png'), + # Services + ('conference', 'text'): (MucBrowser, 'conference.png'), + ('headline', 'rss'): (AgentBrowser, 'rss.png'), + ('headline', 'weather'): (False, 'weather.png'), + ('gateway', 'weather'): (False, 'weather.png'), + ('_jid', 'weather'): (False, 'weather.png'), + ('gateway', 'sip'): (False, 'sip.png'), + ('directory', 'user'): (None, 'jud.png'), + ('pubsub', 'generic'): (PubSubBrowser, 'pubsub.png'), + ('pubsub', 'service'): (PubSubBrowser, 'pubsub.png'), + ('proxy', 'bytestreams'): (None, 'bytestreams.png'), # Socks5 FT proxy + ('headline', 'newmail'): (ToplevelAgentBrowser, 'mail.png'), - # Transports - ('conference', 'irc'): (ToplevelAgentBrowser, 'irc.png'), - ('_jid', 'irc'): (False, 'irc.png'), - ('gateway', 'aim'): (False, 'aim.png'), - ('_jid', 'aim'): (False, 'aim.png'), - ('gateway', 'gadu-gadu'): (False, 'gadu-gadu.png'), - ('_jid', 'gadugadu'): (False, 'gadu-gadu.png'), - ('gateway', 'http-ws'): (False, 'http-ws.png'), - ('gateway', 'icq'): (False, 'icq.png'), - ('_jid', 'icq'): (False, 'icq.png'), - ('gateway', 'msn'): (False, 'msn.png'), - ('_jid', 'msn'): (False, 'msn.png'), - ('gateway', 'sms'): (False, 'sms.png'), - ('_jid', 'sms'): (False, 'sms.png'), - ('gateway', 'smtp'): (False, 'mail.png'), - ('gateway', 'yahoo'): (False, 'yahoo.png'), - ('_jid', 'yahoo'): (False, 'yahoo.png'), - ('gateway', 'mrim'): (False, 'mrim.png'), - ('_jid', 'mrim'): (False, 'mrim.png'), - ('gateway', 'facebook'): (False, 'facebook.png'), - ('_jid', 'facebook'): (False, 'facebook.png'), - } + # Transports + ('conference', 'irc'): (ToplevelAgentBrowser, 'irc.png'), + ('_jid', 'irc'): (False, 'irc.png'), + ('gateway', 'aim'): (False, 'aim.png'), + ('_jid', 'aim'): (False, 'aim.png'), + ('gateway', 'gadu-gadu'): (False, 'gadu-gadu.png'), + ('_jid', 'gadugadu'): (False, 'gadu-gadu.png'), + ('gateway', 'http-ws'): (False, 'http-ws.png'), + ('gateway', 'icq'): (False, 'icq.png'), + ('_jid', 'icq'): (False, 'icq.png'), + ('gateway', 'msn'): (False, 'msn.png'), + ('_jid', 'msn'): (False, 'msn.png'), + ('gateway', 'sms'): (False, 'sms.png'), + ('_jid', 'sms'): (False, 'sms.png'), + ('gateway', 'smtp'): (False, 'mail.png'), + ('gateway', 'yahoo'): (False, 'yahoo.png'), + ('_jid', 'yahoo'): (False, 'yahoo.png'), + ('gateway', 'mrim'): (False, 'mrim.png'), + ('_jid', 'mrim'): (False, 'mrim.png'), + ('gateway', 'facebook'): (False, 'facebook.png'), + ('_jid', 'facebook'): (False, 'facebook.png'), + } # Category type to "human-readable" description string, and sort priority _cat_to_descr = { - 'other': (_('Others'), 2), - 'gateway': (_('Transports'), 0), - '_jid': (_('Transports'), 0), - #conference is a category for listing mostly groupchats in service discovery - 'conference': (_('Conference'), 1), + 'other': (_('Others'), 2), + 'gateway': (_('Transports'), 0), + '_jid': (_('Transports'), 0), + #conference is a category for listing mostly groupchats in service discovery + 'conference': (_('Conference'), 1), } class CacheDictionary: - """ - A dictionary that keeps items around for only a specific time. Lifetime is - in minutes. Getrefresh specifies whether to refresh when an item is merely - accessed instead of set aswell - """ + """ + A dictionary that keeps items around for only a specific time. Lifetime is + in minutes. Getrefresh specifies whether to refresh when an item is merely + accessed instead of set aswell + """ - def __init__(self, lifetime, getrefresh = True): - self.lifetime = lifetime * 1000 * 60 - self.getrefresh = getrefresh - self.cache = {} + def __init__(self, lifetime, getrefresh = True): + self.lifetime = lifetime * 1000 * 60 + self.getrefresh = getrefresh + self.cache = {} - class CacheItem: - """ - An object to store cache items and their timeouts - """ - def __init__(self, value): - self.value = value - self.source = None + class CacheItem: + """ + An object to store cache items and their timeouts + """ + def __init__(self, value): + self.value = value + self.source = None - def __call__(self): - return self.value + def __call__(self): + return self.value - def cleanup(self): - for key in self.cache.keys(): - item = self.cache[key] - if item.source: - gobject.source_remove(item.source) - del self.cache[key] + def cleanup(self): + for key in self.cache.keys(): + item = self.cache[key] + if item.source: + gobject.source_remove(item.source) + del self.cache[key] - def _expire_timeout(self, key): - """ - The timeout has expired, remove the object - """ - if key in self.cache: - del self.cache[key] - return False + def _expire_timeout(self, key): + """ + The timeout has expired, remove the object + """ + if key in self.cache: + del self.cache[key] + return False - def _refresh_timeout(self, key): - """ - The object was accessed, refresh the timeout - """ - item = self.cache[key] - if item.source: - gobject.source_remove(item.source) - if self.lifetime: - source = gobject.timeout_add_seconds(self.lifetime/1000, self._expire_timeout, key) - item.source = source + def _refresh_timeout(self, key): + """ + The object was accessed, refresh the timeout + """ + item = self.cache[key] + if item.source: + gobject.source_remove(item.source) + if self.lifetime: + source = gobject.timeout_add_seconds(self.lifetime/1000, self._expire_timeout, key) + item.source = source - def __getitem__(self, key): - item = self.cache[key] - if self.getrefresh: - self._refresh_timeout(key) - return item() + def __getitem__(self, key): + item = self.cache[key] + if self.getrefresh: + self._refresh_timeout(key) + return item() - def __setitem__(self, key, value): - item = self.CacheItem(value) - self.cache[key] = item - self._refresh_timeout(key) + def __setitem__(self, key, value): + item = self.CacheItem(value) + self.cache[key] = item + self._refresh_timeout(key) - def __delitem__(self, key): - item = self.cache[key] - if item.source: - gobject.source_remove(item.source) - del self.cache[key] + def __delitem__(self, key): + item = self.cache[key] + if item.source: + gobject.source_remove(item.source) + del self.cache[key] - def __contains__(self, key): - return key in self.cache - has_key = __contains__ + def __contains__(self, key): + return key in self.cache + has_key = __contains__ _icon_cache = CacheDictionary(15) def get_agent_address(jid, node = None): - """ - Get an agent's address for displaying in the GUI - """ - if node: - return '%s@%s' % (node, str(jid)) - else: - return str(jid) + """ + Get an agent's address for displaying in the GUI + """ + if node: + return '%s@%s' % (node, str(jid)) + else: + return str(jid) class Closure(object): - """ - A weak reference to a callback with arguments as an object + """ + A weak reference to a callback with arguments as an object - Weak references to methods immediatly die, even if the object is still - alive. Besides a handy way to store a callback, this provides a workaround - that keeps a reference to the object instead. + Weak references to methods immediatly die, even if the object is still + alive. Besides a handy way to store a callback, this provides a workaround + that keeps a reference to the object instead. - Userargs and removeargs must be tuples. - """ + Userargs and removeargs must be tuples. + """ - def __init__(self, cb, userargs = (), remove = None, removeargs = ()): - self.userargs = userargs - self.remove = remove - self.removeargs = removeargs - if isinstance(cb, types.MethodType): - self.meth_self = weakref.ref(cb.im_self, self._remove) - self.meth_name = cb.func_name - elif callable(cb): - self.meth_self = None - self.cb = weakref.ref(cb, self._remove) - else: - raise TypeError('Object is not callable') + def __init__(self, cb, userargs = (), remove = None, removeargs = ()): + self.userargs = userargs + self.remove = remove + self.removeargs = removeargs + if isinstance(cb, types.MethodType): + self.meth_self = weakref.ref(cb.im_self, self._remove) + self.meth_name = cb.func_name + elif callable(cb): + self.meth_self = None + self.cb = weakref.ref(cb, self._remove) + else: + raise TypeError('Object is not callable') - def _remove(self, ref): - if self.remove: - self.remove(self, *self.removeargs) + def _remove(self, ref): + if self.remove: + self.remove(self, *self.removeargs) - def __call__(self, *args, **kwargs): - if self.meth_self: - obj = self.meth_self() - cb = getattr(obj, self.meth_name) - else: - cb = self.cb() - args = args + self.userargs - return cb(*args, **kwargs) + def __call__(self, *args, **kwargs): + if self.meth_self: + obj = self.meth_self() + cb = getattr(obj, self.meth_name) + else: + cb = self.cb() + args = args + self.userargs + return cb(*args, **kwargs) class ServicesCache: - """ - Class that caches our query results. Each connection will have it's own - ServiceCache instance - """ + """ + Class that caches our query results. Each connection will have it's own + ServiceCache instance + """ - def __init__(self, account): - self.account = account - self._items = CacheDictionary(0, getrefresh = False) - self._info = CacheDictionary(0, getrefresh = False) - self._subscriptions = CacheDictionary(5, getrefresh=False) - self._cbs = {} + def __init__(self, account): + self.account = account + self._items = CacheDictionary(0, getrefresh = False) + self._info = CacheDictionary(0, getrefresh = False) + self._subscriptions = CacheDictionary(5, getrefresh=False) + self._cbs = {} - def cleanup(self): - self._items.cleanup() - self._info.cleanup() + def cleanup(self): + self._items.cleanup() + self._info.cleanup() - def _clean_closure(self, cb, type_, addr): - # A closure died, clean up - cbkey = (type_, addr) - try: - self._cbs[cbkey].remove(cb) - except KeyError: - return - except ValueError: - return - # Clean an empty list - if not self._cbs[cbkey]: - del self._cbs[cbkey] + def _clean_closure(self, cb, type_, addr): + # A closure died, clean up + cbkey = (type_, addr) + try: + self._cbs[cbkey].remove(cb) + except KeyError: + return + except ValueError: + return + # Clean an empty list + if not self._cbs[cbkey]: + del self._cbs[cbkey] - def get_icon(self, identities = []): - """ - Return the icon for an agent - """ - # Grab the first identity with an icon - for identity in identities: - try: - cat, type_ = identity['category'], identity['type'] - info = _agent_type_info[(cat, type_)] - except KeyError: - continue - filename = info[1] - if filename: - break - else: - # Loop fell through, default to unknown - info = _agent_type_info[(0, 0)] - filename = info[1] - if not filename: # we don't have an image to show for this type - filename = 'jabber.png' - # Use the cache if possible - if filename in _icon_cache: - return _icon_cache[filename] - # Or load it - filepath = os.path.join(gajim.DATA_DIR, 'pixmaps', 'agents', filename) - pix = gtk.gdk.pixbuf_new_from_file(filepath) - # Store in cache - _icon_cache[filename] = pix - return pix + def get_icon(self, identities = []): + """ + Return the icon for an agent + """ + # Grab the first identity with an icon + for identity in identities: + try: + cat, type_ = identity['category'], identity['type'] + info = _agent_type_info[(cat, type_)] + except KeyError: + continue + filename = info[1] + if filename: + break + else: + # Loop fell through, default to unknown + info = _agent_type_info[(0, 0)] + filename = info[1] + if not filename: # we don't have an image to show for this type + filename = 'jabber.png' + # Use the cache if possible + if filename in _icon_cache: + return _icon_cache[filename] + # Or load it + filepath = os.path.join(gajim.DATA_DIR, 'pixmaps', 'agents', filename) + pix = gtk.gdk.pixbuf_new_from_file(filepath) + # Store in cache + _icon_cache[filename] = pix + return pix - def get_browser(self, identities=[], features=[]): - """ - Return the browser class for an agent - """ - # First pass, we try to find a ToplevelAgentBrowser - for identity in identities: - try: - cat, type_ = identity['category'], identity['type'] - info = _agent_type_info[(cat, type_)] - except KeyError: - continue - browser = info[0] - if browser and browser == ToplevelAgentBrowser: - return browser + def get_browser(self, identities=[], features=[]): + """ + Return the browser class for an agent + """ + # First pass, we try to find a ToplevelAgentBrowser + for identity in identities: + try: + cat, type_ = identity['category'], identity['type'] + info = _agent_type_info[(cat, type_)] + except KeyError: + continue + browser = info[0] + if browser and browser == ToplevelAgentBrowser: + return browser - # second pass, we haven't found a ToplevelAgentBrowser - for identity in identities: - try: - cat, type_ = identity['category'], identity['type'] - info = _agent_type_info[(cat, type_)] - except KeyError: - continue - browser = info[0] - if browser: - return browser - # NS_BROWSE is deprecated, but we check for it anyways. - # Some services list it in features and respond to - # NS_DISCO_ITEMS anyways. - # Allow browsing for unknown types aswell. - if (not features and not identities) or \ - xmpp.NS_DISCO_ITEMS in features or xmpp.NS_BROWSE in features: - return ToplevelAgentBrowser - return None + # second pass, we haven't found a ToplevelAgentBrowser + for identity in identities: + try: + cat, type_ = identity['category'], identity['type'] + info = _agent_type_info[(cat, type_)] + except KeyError: + continue + browser = info[0] + if browser: + return browser + # NS_BROWSE is deprecated, but we check for it anyways. + # Some services list it in features and respond to + # NS_DISCO_ITEMS anyways. + # Allow browsing for unknown types aswell. + if (not features and not identities) or \ + xmpp.NS_DISCO_ITEMS in features or xmpp.NS_BROWSE in features: + return ToplevelAgentBrowser + return None - def get_info(self, jid, node, cb, force = False, nofetch = False, args = ()): - """ - Get info for an agent - """ - addr = get_agent_address(jid, node) - # Check the cache - if addr in self._info: - args = self._info[addr] + args - cb(jid, node, *args) - return - if nofetch: - return + def get_info(self, jid, node, cb, force = False, nofetch = False, args = ()): + """ + Get info for an agent + """ + addr = get_agent_address(jid, node) + # Check the cache + if addr in self._info: + args = self._info[addr] + args + cb(jid, node, *args) + return + if nofetch: + return - # Create a closure object - cbkey = ('info', addr) - cb = Closure(cb, userargs = args, remove = self._clean_closure, - removeargs = cbkey) - # Are we already fetching this? - if cbkey in self._cbs: - self._cbs[cbkey].append(cb) - else: - self._cbs[cbkey] = [cb] - gajim.connections[self.account].discoverInfo(jid, node) + # Create a closure object + cbkey = ('info', addr) + cb = Closure(cb, userargs = args, remove = self._clean_closure, + removeargs = cbkey) + # Are we already fetching this? + if cbkey in self._cbs: + self._cbs[cbkey].append(cb) + else: + self._cbs[cbkey] = [cb] + gajim.connections[self.account].discoverInfo(jid, node) - def get_items(self, jid, node, cb, force = False, nofetch = False, args = ()): - """ - Get a list of items in an agent - """ - addr = get_agent_address(jid, node) - # Check the cache - if addr in self._items: - args = (self._items[addr],) + args - cb(jid, node, *args) - return - if nofetch: - return + def get_items(self, jid, node, cb, force = False, nofetch = False, args = ()): + """ + Get a list of items in an agent + """ + addr = get_agent_address(jid, node) + # Check the cache + if addr in self._items: + args = (self._items[addr],) + args + cb(jid, node, *args) + return + if nofetch: + return - # Create a closure object - cbkey = ('items', addr) - cb = Closure(cb, userargs = args, remove = self._clean_closure, - removeargs = cbkey) - # Are we already fetching this? - if cbkey in self._cbs: - self._cbs[cbkey].append(cb) - else: - self._cbs[cbkey] = [cb] - gajim.connections[self.account].discoverItems(jid, node) + # Create a closure object + cbkey = ('items', addr) + cb = Closure(cb, userargs = args, remove = self._clean_closure, + removeargs = cbkey) + # Are we already fetching this? + if cbkey in self._cbs: + self._cbs[cbkey].append(cb) + else: + self._cbs[cbkey] = [cb] + gajim.connections[self.account].discoverItems(jid, node) - def agent_info(self, jid, node, identities, features, data): - """ - Callback for when we receive an agent's info - """ - addr = get_agent_address(jid, node) + def agent_info(self, jid, node, identities, features, data): + """ + Callback for when we receive an agent's info + """ + addr = get_agent_address(jid, node) - # Store in cache - self._info[addr] = (identities, features, data) + # Store in cache + self._info[addr] = (identities, features, data) - # Call callbacks - cbkey = ('info', addr) - if cbkey in self._cbs: - for cb in self._cbs[cbkey]: - cb(jid, node, identities, features, data) - # clean_closure may have beaten us to it - if cbkey in self._cbs: - del self._cbs[cbkey] + # Call callbacks + cbkey = ('info', addr) + if cbkey in self._cbs: + for cb in self._cbs[cbkey]: + cb(jid, node, identities, features, data) + # clean_closure may have beaten us to it + if cbkey in self._cbs: + del self._cbs[cbkey] - def agent_items(self, jid, node, items): - """ - Callback for when we receive an agent's items - """ - addr = get_agent_address(jid, node) + def agent_items(self, jid, node, items): + """ + Callback for when we receive an agent's items + """ + addr = get_agent_address(jid, node) - # Store in cache - self._items[addr] = items + # Store in cache + self._items[addr] = items - # Call callbacks - cbkey = ('items', addr) - if cbkey in self._cbs: - for cb in self._cbs[cbkey]: - cb(jid, node, items) - # clean_closure may have beaten us to it - if cbkey in self._cbs: - del self._cbs[cbkey] + # Call callbacks + cbkey = ('items', addr) + if cbkey in self._cbs: + for cb in self._cbs[cbkey]: + cb(jid, node, items) + # clean_closure may have beaten us to it + if cbkey in self._cbs: + del self._cbs[cbkey] - def agent_info_error(self, jid): - """ - Callback for when a query fails. Even after the browse and agents - namespaces - """ - addr = get_agent_address(jid) + def agent_info_error(self, jid): + """ + Callback for when a query fails. Even after the browse and agents + namespaces + """ + addr = get_agent_address(jid) - # Call callbacks - cbkey = ('info', addr) - if cbkey in self._cbs: - for cb in self._cbs[cbkey]: - cb(jid, '', 0, 0, 0) - # clean_closure may have beaten us to it - if cbkey in self._cbs: - del self._cbs[cbkey] + # Call callbacks + cbkey = ('info', addr) + if cbkey in self._cbs: + for cb in self._cbs[cbkey]: + cb(jid, '', 0, 0, 0) + # clean_closure may have beaten us to it + if cbkey in self._cbs: + del self._cbs[cbkey] - def agent_items_error(self, jid): - """ - Callback for when a query fails. Even after the browse and agents - namespaces - """ - addr = get_agent_address(jid) + def agent_items_error(self, jid): + """ + Callback for when a query fails. Even after the browse and agents + namespaces + """ + addr = get_agent_address(jid) - # Call callbacks - cbkey = ('items', addr) - if cbkey in self._cbs: - for cb in self._cbs[cbkey]: - cb(jid, '', 0) - # clean_closure may have beaten us to it - if cbkey in self._cbs: - del self._cbs[cbkey] + # Call callbacks + cbkey = ('items', addr) + if cbkey in self._cbs: + for cb in self._cbs[cbkey]: + cb(jid, '', 0) + # clean_closure may have beaten us to it + if cbkey in self._cbs: + del self._cbs[cbkey] # object is needed so that @property works class ServiceDiscoveryWindow(object): - """ - Class that represents the Services Discovery window - """ + """ + Class that represents the Services Discovery window + """ - def __init__(self, account, jid = '', node = '', - address_entry = False, parent = None): - self.account = account - self.parent = parent - if not jid: - jid = gajim.config.get_per('accounts', account, 'hostname') - node = '' + def __init__(self, account, jid = '', node = '', + address_entry = False, parent = None): + self.account = account + self.parent = parent + if not jid: + jid = gajim.config.get_per('accounts', account, 'hostname') + node = '' - self.jid = None - self.browser = None - self.children = [] - self.dying = False - self.node = None + self.jid = None + self.browser = None + self.children = [] + self.dying = False + self.node = None - # Check connection - if gajim.connections[account].connected < 2: - dialogs.ErrorDialog(_('You are not connected to the server'), + # Check connection + if gajim.connections[account].connected < 2: + dialogs.ErrorDialog(_('You are not connected to the server'), _('Without a connection, you can not browse available services')) - raise RuntimeError, 'You must be connected to browse services' + raise RuntimeError, 'You must be connected to browse services' - # Get a ServicesCache object. - try: - self.cache = gajim.connections[account].services_cache - except AttributeError: - self.cache = ServicesCache(account) - gajim.connections[account].services_cache = self.cache + # Get a ServicesCache object. + try: + self.cache = gajim.connections[account].services_cache + except AttributeError: + self.cache = ServicesCache(account) + gajim.connections[account].services_cache = self.cache - self.xml = gtkgui_helpers.get_glade('service_discovery_window.glade') - self.window = self.xml.get_widget('service_discovery_window') - self.services_treeview = self.xml.get_widget('services_treeview') - self.model = None - # This is more reliable than the cursor-changed signal. - selection = self.services_treeview.get_selection() - selection.connect_after('changed', - self.on_services_treeview_selection_changed) - self.services_scrollwin = self.xml.get_widget('services_scrollwin') - self.progressbar = self.xml.get_widget('services_progressbar') - self.banner = self.xml.get_widget('banner_agent_label') - self.banner_icon = self.xml.get_widget('banner_agent_icon') - self.banner_eventbox = self.xml.get_widget('banner_agent_eventbox') - self.style_event_id = 0 - self.banner.realize() - self.paint_banner() - self.action_buttonbox = self.xml.get_widget('action_buttonbox') + self.xml = gtkgui_helpers.get_glade('service_discovery_window.glade') + self.window = self.xml.get_widget('service_discovery_window') + self.services_treeview = self.xml.get_widget('services_treeview') + self.model = None + # This is more reliable than the cursor-changed signal. + selection = self.services_treeview.get_selection() + selection.connect_after('changed', + self.on_services_treeview_selection_changed) + self.services_scrollwin = self.xml.get_widget('services_scrollwin') + self.progressbar = self.xml.get_widget('services_progressbar') + self.banner = self.xml.get_widget('banner_agent_label') + self.banner_icon = self.xml.get_widget('banner_agent_icon') + self.banner_eventbox = self.xml.get_widget('banner_agent_eventbox') + self.style_event_id = 0 + self.banner.realize() + self.paint_banner() + self.action_buttonbox = self.xml.get_widget('action_buttonbox') - # Address combobox - self.address_comboboxentry = None - address_table = self.xml.get_widget('address_table') - if address_entry: - self.address_comboboxentry = self.xml.get_widget( - 'address_comboboxentry') - self.address_comboboxentry_entry = self.address_comboboxentry.child - self.address_comboboxentry_entry.set_activates_default(True) + # Address combobox + self.address_comboboxentry = None + address_table = self.xml.get_widget('address_table') + if address_entry: + self.address_comboboxentry = self.xml.get_widget( + 'address_comboboxentry') + self.address_comboboxentry_entry = self.address_comboboxentry.child + self.address_comboboxentry_entry.set_activates_default(True) - liststore = gtk.ListStore(str) - self.address_comboboxentry.set_model(liststore) - self.latest_addresses = gajim.config.get( - 'latest_disco_addresses').split() - if jid in self.latest_addresses: - self.latest_addresses.remove(jid) - self.latest_addresses.insert(0, jid) - if len(self.latest_addresses) > 10: - self.latest_addresses = self.latest_addresses[0:10] - for j in self.latest_addresses: - self.address_comboboxentry.append_text(j) - self.address_comboboxentry.child.set_text(jid) - else: - # Don't show it at all if we didn't ask for it - address_table.set_no_show_all(True) - address_table.hide() + liststore = gtk.ListStore(str) + self.address_comboboxentry.set_model(liststore) + self.latest_addresses = gajim.config.get( + 'latest_disco_addresses').split() + if jid in self.latest_addresses: + self.latest_addresses.remove(jid) + self.latest_addresses.insert(0, jid) + if len(self.latest_addresses) > 10: + self.latest_addresses = self.latest_addresses[0:10] + for j in self.latest_addresses: + self.address_comboboxentry.append_text(j) + self.address_comboboxentry.child.set_text(jid) + else: + # Don't show it at all if we didn't ask for it + address_table.set_no_show_all(True) + address_table.hide() - self._initial_state() - self.xml.signal_autoconnect(self) - self.travel(jid, node) - self.window.show_all() + self._initial_state() + self.xml.signal_autoconnect(self) + self.travel(jid, node) + self.window.show_all() - @property - def _get_account(self): - return self.account + @property + def _get_account(self): + return self.account - @property - def _set_account(self, value): - self.account = value - self.cache.account = value - if self.browser: - self.browser.account = value + @property + def _set_account(self, value): + self.account = value + self.cache.account = value + if self.browser: + self.browser.account = value - def _initial_state(self): - """ - Set some initial state on the window. Separated in a method because it's - handy to use within browser's cleanup method - """ - self.progressbar.hide() - title_text = _('Service Discovery using account %s') % self.account - self.window.set_title(title_text) - self._set_window_banner_text(_('Service Discovery')) - self.banner_icon.clear() - self.banner_icon.hide() # Just clearing it doesn't work + def _initial_state(self): + """ + Set some initial state on the window. Separated in a method because it's + handy to use within browser's cleanup method + """ + self.progressbar.hide() + title_text = _('Service Discovery using account %s') % self.account + self.window.set_title(title_text) + self._set_window_banner_text(_('Service Discovery')) + self.banner_icon.clear() + self.banner_icon.hide() # Just clearing it doesn't work - def _set_window_banner_text(self, text, text_after = None): - theme = gajim.config.get('roster_theme') - bannerfont = gajim.config.get_per('themes', theme, 'bannerfont') - bannerfontattrs = gajim.config.get_per('themes', theme, - 'bannerfontattrs') + def _set_window_banner_text(self, text, text_after = None): + theme = gajim.config.get('roster_theme') + bannerfont = gajim.config.get_per('themes', theme, 'bannerfont') + bannerfontattrs = gajim.config.get_per('themes', theme, + 'bannerfontattrs') - if bannerfont: - font = pango.FontDescription(bannerfont) - else: - font = pango.FontDescription('Normal') - if bannerfontattrs: - # B is attribute set by default - if 'B' in bannerfontattrs: - font.set_weight(pango.WEIGHT_HEAVY) - if 'I' in bannerfontattrs: - font.set_style(pango.STYLE_ITALIC) + if bannerfont: + font = pango.FontDescription(bannerfont) + else: + font = pango.FontDescription('Normal') + if bannerfontattrs: + # B is attribute set by default + if 'B' in bannerfontattrs: + font.set_weight(pango.WEIGHT_HEAVY) + if 'I' in bannerfontattrs: + font.set_style(pango.STYLE_ITALIC) - font_attrs = 'font_desc="%s"' % font.to_string() - font_size = font.get_size() + font_attrs = 'font_desc="%s"' % font.to_string() + font_size = font.get_size() - # in case there is no font specified we use x-large font size - if font_size == 0: - font_attrs = '%s size="large"' % font_attrs - markup = '%s' % (font_attrs, text) - if text_after: - font.set_weight(pango.WEIGHT_NORMAL) - markup = '%s\n%s' % \ - (markup, font.to_string(), text_after) - self.banner.set_markup(markup) + # in case there is no font specified we use x-large font size + if font_size == 0: + font_attrs = '%s size="large"' % font_attrs + markup = '%s' % (font_attrs, text) + if text_after: + font.set_weight(pango.WEIGHT_NORMAL) + markup = '%s\n%s' % \ + (markup, font.to_string(), text_after) + self.banner.set_markup(markup) - def paint_banner(self): - """ - Repaint the banner with theme color - """ - theme = gajim.config.get('roster_theme') - bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor') - textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor') - self.disconnect_style_event() - if bgcolor: - color = gtk.gdk.color_parse(bgcolor) - self.banner_eventbox.modify_bg(gtk.STATE_NORMAL, color) - default_bg = False - else: - default_bg = True + def paint_banner(self): + """ + Repaint the banner with theme color + """ + theme = gajim.config.get('roster_theme') + bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor') + textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor') + self.disconnect_style_event() + if bgcolor: + color = gtk.gdk.color_parse(bgcolor) + self.banner_eventbox.modify_bg(gtk.STATE_NORMAL, color) + default_bg = False + else: + default_bg = True - if textcolor: - color = gtk.gdk.color_parse(textcolor) - self.banner.modify_fg(gtk.STATE_NORMAL, color) - default_fg = False - else: - default_fg = True - if default_fg or default_bg: - self._on_style_set_event(self.banner, None, default_fg, default_bg) - if self.browser: - self.browser.update_theme() + if textcolor: + color = gtk.gdk.color_parse(textcolor) + self.banner.modify_fg(gtk.STATE_NORMAL, color) + default_fg = False + else: + default_fg = True + if default_fg or default_bg: + self._on_style_set_event(self.banner, None, default_fg, default_bg) + if self.browser: + self.browser.update_theme() - def disconnect_style_event(self): - if self.style_event_id: - self.banner.disconnect(self.style_event_id) - self.style_event_id = 0 + def disconnect_style_event(self): + if self.style_event_id: + self.banner.disconnect(self.style_event_id) + self.style_event_id = 0 - def connect_style_event(self, set_fg = False, set_bg = False): - self.disconnect_style_event() - self.style_event_id = self.banner.connect('style-set', - self._on_style_set_event, set_fg, set_bg) + def connect_style_event(self, set_fg = False, set_bg = False): + self.disconnect_style_event() + self.style_event_id = self.banner.connect('style-set', + self._on_style_set_event, set_fg, set_bg) - def _on_style_set_event(self, widget, style, *opts): - """ - Set style of widget from style class *.Frame.Eventbox - opts[0] == True -> set fg color - opts[1] == True -> set bg color - """ - self.disconnect_style_event() - if opts[1]: - bg_color = widget.style.bg[gtk.STATE_SELECTED] - self.banner_eventbox.modify_bg(gtk.STATE_NORMAL, bg_color) - if opts[0]: - fg_color = widget.style.fg[gtk.STATE_SELECTED] - self.banner.modify_fg(gtk.STATE_NORMAL, fg_color) - self.banner.ensure_style() - self.connect_style_event(opts[0], opts[1]) + def _on_style_set_event(self, widget, style, *opts): + """ + Set style of widget from style class *.Frame.Eventbox + opts[0] == True -> set fg color + opts[1] == True -> set bg color + """ + self.disconnect_style_event() + if opts[1]: + bg_color = widget.style.bg[gtk.STATE_SELECTED] + self.banner_eventbox.modify_bg(gtk.STATE_NORMAL, bg_color) + if opts[0]: + fg_color = widget.style.fg[gtk.STATE_SELECTED] + self.banner.modify_fg(gtk.STATE_NORMAL, fg_color) + self.banner.ensure_style() + self.connect_style_event(opts[0], opts[1]) - def destroy(self, chain = False): - """ - Close the browser. This can optionally close its children and propagate - to the parent. This should happen on actions like register, or join to - kill off the entire browser chain - """ - if self.dying: - return - self.dying = True + def destroy(self, chain = False): + """ + Close the browser. This can optionally close its children and propagate + to the parent. This should happen on actions like register, or join to + kill off the entire browser chain + """ + if self.dying: + return + self.dying = True - # self.browser._get_agent_address() would break when no browser. - addr = get_agent_address(self.jid, self.node) - if addr in gajim.interface.instances[self.account]['disco']: - del gajim.interface.instances[self.account]['disco'][addr] + # self.browser._get_agent_address() would break when no browser. + addr = get_agent_address(self.jid, self.node) + if addr in gajim.interface.instances[self.account]['disco']: + del gajim.interface.instances[self.account]['disco'][addr] - if self.browser: - self.window.hide() - self.browser.cleanup() - self.browser = None - self.window.destroy() + if self.browser: + self.window.hide() + self.browser.cleanup() + self.browser = None + self.window.destroy() - for child in self.children[:]: - child.parent = None - if chain: - child.destroy(chain = chain) - self.children.remove(child) - if self.parent: - if self in self.parent.children: - self.parent.children.remove(self) - if chain and not self.parent.children: - self.parent.destroy(chain = chain) - self.parent = None - else: - self.cache.cleanup() + for child in self.children[:]: + child.parent = None + if chain: + child.destroy(chain = chain) + self.children.remove(child) + if self.parent: + if self in self.parent.children: + self.parent.children.remove(self) + if chain and not self.parent.children: + self.parent.destroy(chain = chain) + self.parent = None + else: + self.cache.cleanup() - def travel(self, jid, node): - """ - Travel to an agent within the current services window - """ - if self.browser: - self.browser.cleanup() - self.browser = None - # Update the window list - if self.jid: - old_addr = get_agent_address(self.jid, self.node) - if old_addr in gajim.interface.instances[self.account]['disco']: - del gajim.interface.instances[self.account]['disco'][old_addr] - addr = get_agent_address(jid, node) - gajim.interface.instances[self.account]['disco'][addr] = self - # We need to store these, self.browser is not always available. - self.jid = jid - self.node = node - self.cache.get_info(jid, node, self._travel) + def travel(self, jid, node): + """ + Travel to an agent within the current services window + """ + if self.browser: + self.browser.cleanup() + self.browser = None + # Update the window list + if self.jid: + old_addr = get_agent_address(self.jid, self.node) + if old_addr in gajim.interface.instances[self.account]['disco']: + del gajim.interface.instances[self.account]['disco'][old_addr] + addr = get_agent_address(jid, node) + gajim.interface.instances[self.account]['disco'][addr] = self + # We need to store these, self.browser is not always available. + self.jid = jid + self.node = node + self.cache.get_info(jid, node, self._travel) - def _travel(self, jid, node, identities, features, data): - """ - Continuation of travel - """ - if self.dying or jid != self.jid or node != self.node: - return - if not identities: - if not self.address_comboboxentry: - # We can't travel anywhere else. - self.destroy() - dialogs.ErrorDialog(_('The service could not be found'), + def _travel(self, jid, node, identities, features, data): + """ + Continuation of travel + """ + if self.dying or jid != self.jid or node != self.node: + return + if not identities: + if not self.address_comboboxentry: + # We can't travel anywhere else. + self.destroy() + dialogs.ErrorDialog(_('The service could not be found'), _('There is no service at the address you entered, or it is not responding. Check the address and try again.')) - return - klass = self.cache.get_browser(identities, features) - if not klass: - dialogs.ErrorDialog(_('The service is not browsable'), + return + klass = self.cache.get_browser(identities, features) + if not klass: + dialogs.ErrorDialog(_('The service is not browsable'), _('This type of service does not contain any items to browse.')) - return - elif klass is None: - klass = AgentBrowser - self.browser = klass(self.account, jid, node) - self.browser.prepare_window(self) - self.browser.browse() + return + elif klass is None: + klass = AgentBrowser + self.browser = klass(self.account, jid, node) + self.browser.prepare_window(self) + self.browser.browse() - def open(self, jid, node): - """ - Open an agent. By default, this happens in a new window - """ - try: - win = gajim.interface.instances[self.account]['disco']\ - [get_agent_address(jid, node)] - win.window.present() - return - except KeyError: - pass - try: - win = ServiceDiscoveryWindow(self.account, jid, node, parent=self) - except RuntimeError: - # Disconnected, perhaps - return - self.children.append(win) + def open(self, jid, node): + """ + Open an agent. By default, this happens in a new window + """ + try: + win = gajim.interface.instances[self.account]['disco']\ + [get_agent_address(jid, node)] + win.window.present() + return + except KeyError: + pass + try: + win = ServiceDiscoveryWindow(self.account, jid, node, parent=self) + except RuntimeError: + # Disconnected, perhaps + return + self.children.append(win) - def on_service_discovery_window_destroy(self, widget): - self.destroy() + def on_service_discovery_window_destroy(self, widget): + self.destroy() - def on_close_button_clicked(self, widget): - self.destroy() + def on_close_button_clicked(self, widget): + self.destroy() - def on_address_comboboxentry_changed(self, widget): - if self.address_comboboxentry.get_active() != -1: - # user selected one of the entries so do auto-visit - jid = self.address_comboboxentry.child.get_text().decode('utf-8') - try: - jid = helpers.parse_jid(jid) - except helpers.InvalidFormat, s: - pritext = _('Invalid Server Name') - dialogs.ErrorDialog(pritext, str(s)) - return - self.travel(jid, '') + def on_address_comboboxentry_changed(self, widget): + if self.address_comboboxentry.get_active() != -1: + # user selected one of the entries so do auto-visit + jid = self.address_comboboxentry.child.get_text().decode('utf-8') + try: + jid = helpers.parse_jid(jid) + except helpers.InvalidFormat, s: + pritext = _('Invalid Server Name') + dialogs.ErrorDialog(pritext, str(s)) + return + self.travel(jid, '') - def on_go_button_clicked(self, widget): - jid = self.address_comboboxentry.child.get_text().decode('utf-8') - try: - jid = helpers.parse_jid(jid) - except helpers.InvalidFormat, s: - pritext = _('Invalid Server Name') - dialogs.ErrorDialog(pritext, str(s)) - return - if jid == self.jid: # jid has not changed - return - if jid in self.latest_addresses: - self.latest_addresses.remove(jid) - self.latest_addresses.insert(0, jid) - if len(self.latest_addresses) > 10: - self.latest_addresses = self.latest_addresses[0:10] - self.address_comboboxentry.get_model().clear() - for j in self.latest_addresses: - self.address_comboboxentry.append_text(j) - gajim.config.set('latest_disco_addresses', - ' '.join(self.latest_addresses)) - gajim.interface.save_config() - self.travel(jid, '') + def on_go_button_clicked(self, widget): + jid = self.address_comboboxentry.child.get_text().decode('utf-8') + try: + jid = helpers.parse_jid(jid) + except helpers.InvalidFormat, s: + pritext = _('Invalid Server Name') + dialogs.ErrorDialog(pritext, str(s)) + return + if jid == self.jid: # jid has not changed + return + if jid in self.latest_addresses: + self.latest_addresses.remove(jid) + self.latest_addresses.insert(0, jid) + if len(self.latest_addresses) > 10: + self.latest_addresses = self.latest_addresses[0:10] + self.address_comboboxentry.get_model().clear() + for j in self.latest_addresses: + self.address_comboboxentry.append_text(j) + gajim.config.set('latest_disco_addresses', + ' '.join(self.latest_addresses)) + gajim.interface.save_config() + self.travel(jid, '') - def on_services_treeview_row_activated(self, widget, path, col = 0): - if self.browser: - self.browser.default_action() + def on_services_treeview_row_activated(self, widget, path, col = 0): + if self.browser: + self.browser.default_action() - def on_services_treeview_selection_changed(self, widget): - if self.browser: - self.browser.update_actions() + def on_services_treeview_selection_changed(self, widget): + if self.browser: + self.browser.update_actions() class AgentBrowser: - """ - Class that deals with browsing agents and appearance of the browser window. - This class and subclasses should basically be treated as "part" of the - ServiceDiscoveryWindow class, but had to be separated because this part is - dynamic - """ + """ + Class that deals with browsing agents and appearance of the browser window. + This class and subclasses should basically be treated as "part" of the + ServiceDiscoveryWindow class, but had to be separated because this part is + dynamic + """ - def __init__(self, account, jid, node): - self.account = account - self.jid = jid - self.node = node - self._total_items = 0 - self.browse_button = None - # This is for some timeout callbacks - self.active = False + def __init__(self, account, jid, node): + self.account = account + self.jid = jid + self.node = node + self._total_items = 0 + self.browse_button = None + # This is for some timeout callbacks + self.active = False - def _get_agent_address(self): - """ - Get the agent's address for displaying in the GUI - """ - return get_agent_address(self.jid, self.node) + def _get_agent_address(self): + """ + Get the agent's address for displaying in the GUI + """ + return get_agent_address(self.jid, self.node) - def _set_initial_title(self): - """ - Set the initial window title based on agent address - """ - self.window.window.set_title(_('Browsing %(address)s using account ' - '%(account)s') % {'address': self._get_agent_address(), - 'account': self.account}) - self.window._set_window_banner_text(self._get_agent_address()) + def _set_initial_title(self): + """ + Set the initial window title based on agent address + """ + self.window.window.set_title(_('Browsing %(address)s using account ' + '%(account)s') % {'address': self._get_agent_address(), + 'account': self.account}) + self.window._set_window_banner_text(self._get_agent_address()) - def _create_treemodel(self): - """ - Create the treemodel for the services treeview. When subclassing, note - that the first two columns should ALWAYS be of type string and contain - the JID and node of the item respectively - """ - # JID, node, name, address - self.model = gtk.ListStore(str, str, str, str) - self.model.set_sort_column_id(3, gtk.SORT_ASCENDING) - self.window.services_treeview.set_model(self.model) - # Name column - col = gtk.TreeViewColumn(_('Name')) - renderer = gtk.CellRendererText() - col.pack_start(renderer) - col.set_attributes(renderer, text = 2) - self.window.services_treeview.insert_column(col, -1) - col.set_resizable(True) - # Address column - col = gtk.TreeViewColumn(_('JID')) - renderer = gtk.CellRendererText() - col.pack_start(renderer) - col.set_attributes(renderer, text = 3) - self.window.services_treeview.insert_column(col, -1) - col.set_resizable(True) - self.window.services_treeview.set_headers_visible(True) + def _create_treemodel(self): + """ + Create the treemodel for the services treeview. When subclassing, note + that the first two columns should ALWAYS be of type string and contain + the JID and node of the item respectively + """ + # JID, node, name, address + self.model = gtk.ListStore(str, str, str, str) + self.model.set_sort_column_id(3, gtk.SORT_ASCENDING) + self.window.services_treeview.set_model(self.model) + # Name column + col = gtk.TreeViewColumn(_('Name')) + renderer = gtk.CellRendererText() + col.pack_start(renderer) + col.set_attributes(renderer, text = 2) + self.window.services_treeview.insert_column(col, -1) + col.set_resizable(True) + # Address column + col = gtk.TreeViewColumn(_('JID')) + renderer = gtk.CellRendererText() + col.pack_start(renderer) + col.set_attributes(renderer, text = 3) + self.window.services_treeview.insert_column(col, -1) + col.set_resizable(True) + self.window.services_treeview.set_headers_visible(True) - def _clean_treemodel(self): - self.model.clear() - for col in self.window.services_treeview.get_columns(): - self.window.services_treeview.remove_column(col) - self.window.services_treeview.set_headers_visible(False) + def _clean_treemodel(self): + self.model.clear() + for col in self.window.services_treeview.get_columns(): + self.window.services_treeview.remove_column(col) + self.window.services_treeview.set_headers_visible(False) - def _add_actions(self): - """ - Add the action buttons to the buttonbox for actions the browser can - perform - """ - self.browse_button = gtk.Button() - image = gtk.image_new_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_BUTTON) - label = gtk.Label(_('_Browse')) - label.set_use_underline(True) - hbox = gtk.HBox() - hbox.pack_start(image, False, True, 6) - hbox.pack_end(label, True, True) - self.browse_button.add(hbox) - self.browse_button.connect('clicked', self.on_browse_button_clicked) - self.window.action_buttonbox.add(self.browse_button) - self.browse_button.show_all() + def _add_actions(self): + """ + Add the action buttons to the buttonbox for actions the browser can + perform + """ + self.browse_button = gtk.Button() + image = gtk.image_new_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_BUTTON) + label = gtk.Label(_('_Browse')) + label.set_use_underline(True) + hbox = gtk.HBox() + hbox.pack_start(image, False, True, 6) + hbox.pack_end(label, True, True) + self.browse_button.add(hbox) + self.browse_button.connect('clicked', self.on_browse_button_clicked) + self.window.action_buttonbox.add(self.browse_button) + self.browse_button.show_all() - def _clean_actions(self): - """ - Remove the action buttons specific to this browser - """ - if self.browse_button: - self.browse_button.destroy() - self.browse_button = None + def _clean_actions(self): + """ + Remove the action buttons specific to this browser + """ + if self.browse_button: + self.browse_button.destroy() + self.browse_button = None - def _set_title(self, jid, node, identities, features, data): - """ - Set the window title based on agent info - """ - # Set the banner and window title - if 'name' in identities[0]: - name = identities[0]['name'] - self.window._set_window_banner_text(self._get_agent_address(), name) + def _set_title(self, jid, node, identities, features, data): + """ + Set the window title based on agent info + """ + # Set the banner and window title + if 'name' in identities[0]: + name = identities[0]['name'] + self.window._set_window_banner_text(self._get_agent_address(), name) - # Add an icon to the banner. - pix = self.cache.get_icon(identities) - self.window.banner_icon.set_from_pixbuf(pix) - self.window.banner_icon.show() + # Add an icon to the banner. + pix = self.cache.get_icon(identities) + self.window.banner_icon.set_from_pixbuf(pix) + self.window.banner_icon.show() - def _clean_title(self): - # Everything done here is done in window._initial_state - # This is for subclasses. - pass + def _clean_title(self): + # Everything done here is done in window._initial_state + # This is for subclasses. + pass - def prepare_window(self, window): - """ - Prepare the service discovery window. Called when a browser is hooked up - with a ServiceDiscoveryWindow instance - """ - self.window = window - self.cache = window.cache + def prepare_window(self, window): + """ + Prepare the service discovery window. Called when a browser is hooked up + with a ServiceDiscoveryWindow instance + """ + self.window = window + self.cache = window.cache - self._set_initial_title() - self._create_treemodel() - self._add_actions() + self._set_initial_title() + self._create_treemodel() + self._add_actions() - # This is a hack. The buttonbox apparently doesn't care about pack_start - # or pack_end, so we repack the close button here to make sure it's last - close_button = self.window.xml.get_widget('close_button') - self.window.action_buttonbox.remove(close_button) - self.window.action_buttonbox.pack_end(close_button) - close_button.show_all() + # This is a hack. The buttonbox apparently doesn't care about pack_start + # or pack_end, so we repack the close button here to make sure it's last + close_button = self.window.xml.get_widget('close_button') + self.window.action_buttonbox.remove(close_button) + self.window.action_buttonbox.pack_end(close_button) + close_button.show_all() - self.update_actions() + self.update_actions() - self.active = True - self.cache.get_info(self.jid, self.node, self._set_title) + self.active = True + self.cache.get_info(self.jid, self.node, self._set_title) - def cleanup(self): - """ - Cleanup when the window intends to switch browsers - """ - self.active = False + def cleanup(self): + """ + Cleanup when the window intends to switch browsers + """ + self.active = False - self._clean_actions() - self._clean_treemodel() - self._clean_title() + self._clean_actions() + self._clean_treemodel() + self._clean_title() - self.window._initial_state() + self.window._initial_state() - def update_theme(self): - """ - Called when the default theme is changed - """ - pass + def update_theme(self): + """ + Called when the default theme is changed + """ + pass - def on_browse_button_clicked(self, widget = None): - """ - When we want to browse an agent: open a new services window with a - browser for the agent type - """ - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if not iter_: - return - jid = model[iter_][0].decode('utf-8') - if jid: - node = model[iter_][1].decode('utf-8') - self.window.open(jid, node) + def on_browse_button_clicked(self, widget = None): + """ + When we want to browse an agent: open a new services window with a + browser for the agent type + """ + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if not iter_: + return + jid = model[iter_][0].decode('utf-8') + if jid: + node = model[iter_][1].decode('utf-8') + self.window.open(jid, node) - def update_actions(self): - """ - When we select a row: activate action buttons based on the agent's info - """ - if self.browse_button: - self.browse_button.set_sensitive(False) - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if not iter_: - return - jid = model[iter_][0].decode('utf-8') - node = model[iter_][1].decode('utf-8') - if jid: - self.cache.get_info(jid, node, self._update_actions, nofetch = True) + def update_actions(self): + """ + When we select a row: activate action buttons based on the agent's info + """ + if self.browse_button: + self.browse_button.set_sensitive(False) + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if not iter_: + return + jid = model[iter_][0].decode('utf-8') + node = model[iter_][1].decode('utf-8') + if jid: + self.cache.get_info(jid, node, self._update_actions, nofetch = True) - def _update_actions(self, jid, node, identities, features, data): - """ - Continuation of update_actions - """ - if not identities or not self.browse_button: - return - klass = self.cache.get_browser(identities, features) - if klass: - self.browse_button.set_sensitive(True) + def _update_actions(self, jid, node, identities, features, data): + """ + Continuation of update_actions + """ + if not identities or not self.browse_button: + return + klass = self.cache.get_browser(identities, features) + if klass: + self.browse_button.set_sensitive(True) - def default_action(self): - """ - When we double-click a row: perform the default action on the selected - item - """ - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if not iter_: - return - jid = model[iter_][0].decode('utf-8') - node = model[iter_][1].decode('utf-8') - if jid: - self.cache.get_info(jid, node, self._default_action, nofetch = True) + def default_action(self): + """ + When we double-click a row: perform the default action on the selected + item + """ + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if not iter_: + return + jid = model[iter_][0].decode('utf-8') + node = model[iter_][1].decode('utf-8') + if jid: + self.cache.get_info(jid, node, self._default_action, nofetch = True) - def _default_action(self, jid, node, identities, features, data): - """ - Continuation of default_action - """ - if self.cache.get_browser(identities, features): - # Browse if we can - self.on_browse_button_clicked() - return True - return False + def _default_action(self, jid, node, identities, features, data): + """ + Continuation of default_action + """ + if self.cache.get_browser(identities, features): + # Browse if we can + self.on_browse_button_clicked() + return True + return False - def browse(self, force = False): - """ - Fill the treeview with agents, fetching the info if necessary - """ - self.model.clear() - self._total_items = self._progress = 0 - self.window.progressbar.show() - self._pulse_timeout = gobject.timeout_add(250, self._pulse_timeout_cb) - self.cache.get_items(self.jid, self.node, self._agent_items, - force = force, args = (force,)) + def browse(self, force = False): + """ + Fill the treeview with agents, fetching the info if necessary + """ + self.model.clear() + self._total_items = self._progress = 0 + self.window.progressbar.show() + self._pulse_timeout = gobject.timeout_add(250, self._pulse_timeout_cb) + self.cache.get_items(self.jid, self.node, self._agent_items, + force = force, args = (force,)) - def _pulse_timeout_cb(self, *args): - """ - Simple callback to keep the progressbar pulsing - """ - if not self.active: - return False - self.window.progressbar.pulse() - return True + def _pulse_timeout_cb(self, *args): + """ + Simple callback to keep the progressbar pulsing + """ + if not self.active: + return False + self.window.progressbar.pulse() + return True - def _find_item(self, jid, node): - """ - Check if an item is already in the treeview. Return an iter to it if so, - None otherwise - """ - iter_ = self.model.get_iter_root() - while iter_: - cjid = self.model.get_value(iter_, 0).decode('utf-8') - cnode = self.model.get_value(iter_, 1).decode('utf-8') - if jid == cjid and node == cnode: - break - iter_ = self.model.iter_next(iter_) - if iter_: - return iter_ - return None + def _find_item(self, jid, node): + """ + Check if an item is already in the treeview. Return an iter to it if so, + None otherwise + """ + iter_ = self.model.get_iter_root() + while iter_: + cjid = self.model.get_value(iter_, 0).decode('utf-8') + cnode = self.model.get_value(iter_, 1).decode('utf-8') + if jid == cjid and node == cnode: + break + iter_ = self.model.iter_next(iter_) + if iter_: + return iter_ + return None - def _agent_items(self, jid, node, items, force): - """ - Callback for when we receive a list of agent items - """ - self.model.clear() - self._total_items = 0 - gobject.source_remove(self._pulse_timeout) - self.window.progressbar.hide() - # The server returned an error - if items == 0: - if not self.window.address_comboboxentry: - # We can't travel anywhere else. - self.window.destroy() - dialogs.ErrorDialog(_('The service is not browsable'), + def _agent_items(self, jid, node, items, force): + """ + Callback for when we receive a list of agent items + """ + self.model.clear() + self._total_items = 0 + gobject.source_remove(self._pulse_timeout) + self.window.progressbar.hide() + # The server returned an error + if items == 0: + if not self.window.address_comboboxentry: + # We can't travel anywhere else. + self.window.destroy() + dialogs.ErrorDialog(_('The service is not browsable'), _('This service does not contain any items to browse.')) - return - # We got a list of items - self.window.services_treeview.set_model(None) - for item in items: - jid_ = item['jid'] - node_ = item.get('node', '') - # If such an item is already here: don't add it - if self._find_item(jid_, node_): - continue - self._total_items += 1 - self._add_item(jid_, node_, node, item, force) - self.window.services_treeview.set_model(self.model) + return + # We got a list of items + self.window.services_treeview.set_model(None) + for item in items: + jid_ = item['jid'] + node_ = item.get('node', '') + # If such an item is already here: don't add it + if self._find_item(jid_, node_): + continue + self._total_items += 1 + self._add_item(jid_, node_, node, item, force) + self.window.services_treeview.set_model(self.model) - def _agent_info(self, jid, node, identities, features, data): - """ - Callback for when we receive info about an agent's item - """ - iter_ = self._find_item(jid, node) - if not iter_: - # Not in the treeview, stop - return - if identities == 0: - # The server returned an error - self._update_error(iter_, jid, node) - else: - # We got our info - self._update_info(iter_, jid, node, identities, features, data) - self.update_actions() + def _agent_info(self, jid, node, identities, features, data): + """ + Callback for when we receive info about an agent's item + """ + iter_ = self._find_item(jid, node) + if not iter_: + # Not in the treeview, stop + return + if identities == 0: + # The server returned an error + self._update_error(iter_, jid, node) + else: + # We got our info + self._update_info(iter_, jid, node, identities, features, data) + self.update_actions() - def _add_item(self, jid, node, parent_node, item, force): - """ - Called when an item should be added to the model. The result of a - disco#items query - """ - self.model.append((jid, node, item.get('name', ''), - get_agent_address(jid, node))) - self.cache.get_info(jid, node, self._agent_info, force = force) + def _add_item(self, jid, node, parent_node, item, force): + """ + Called when an item should be added to the model. The result of a + disco#items query + """ + self.model.append((jid, node, item.get('name', ''), + get_agent_address(jid, node))) + self.cache.get_info(jid, node, self._agent_info, force = force) - def _update_item(self, iter_, jid, node, item): - """ - Called when an item should be updated in the model. The result of a - disco#items query - """ - if 'name' in item: - self.model[iter_][2] = item['name'] + def _update_item(self, iter_, jid, node, item): + """ + Called when an item should be updated in the model. The result of a + disco#items query + """ + if 'name' in item: + self.model[iter_][2] = item['name'] - def _update_info(self, iter_, jid, node, identities, features, data): - """ - Called when an item should be updated in the model with further info. - The result of a disco#info query - """ - name = identities[0].get('name', '') - if name: - self.model[iter_][2] = name + def _update_info(self, iter_, jid, node, identities, features, data): + """ + Called when an item should be updated in the model with further info. + The result of a disco#info query + """ + name = identities[0].get('name', '') + if name: + self.model[iter_][2] = name - def _update_error(self, iter_, jid, node): - '''Called when a disco#info query failed for an item.''' - pass + def _update_error(self, iter_, jid, node): + '''Called when a disco#info query failed for an item.''' + pass class ToplevelAgentBrowser(AgentBrowser): - """ - This browser is used at the top level of a jabber server to browse services - such as transports, conference servers, etc - """ + """ + This browser is used at the top level of a jabber server to browse services + such as transports, conference servers, etc + """ - def __init__(self, *args): - AgentBrowser.__init__(self, *args) - self._progressbar_sourceid = None - self._renderer = None - self._progress = 0 - self.tooltip = tooltips.ServiceDiscoveryTooltip() - self.register_button = None - self.join_button = None - self.execute_button = None - self.search_button = None - # Keep track of our treeview signals - self._view_signals = [] - self._scroll_signal = None + def __init__(self, *args): + AgentBrowser.__init__(self, *args) + self._progressbar_sourceid = None + self._renderer = None + self._progress = 0 + self.tooltip = tooltips.ServiceDiscoveryTooltip() + self.register_button = None + self.join_button = None + self.execute_button = None + self.search_button = None + # Keep track of our treeview signals + self._view_signals = [] + self._scroll_signal = None - def _pixbuf_renderer_data_func(self, col, cell, model, iter_): - """ - Callback for setting the pixbuf renderer's properties - """ - jid = model.get_value(iter_, 0) - if jid: - pix = model.get_value(iter_, 2) - cell.set_property('visible', True) - cell.set_property('pixbuf', pix) - else: - cell.set_property('visible', False) + def _pixbuf_renderer_data_func(self, col, cell, model, iter_): + """ + Callback for setting the pixbuf renderer's properties + """ + jid = model.get_value(iter_, 0) + if jid: + pix = model.get_value(iter_, 2) + cell.set_property('visible', True) + cell.set_property('pixbuf', pix) + else: + cell.set_property('visible', False) - def _text_renderer_data_func(self, col, cell, model, iter_): - """ - Callback for setting the text renderer's properties - """ - jid = model.get_value(iter_, 0) - markup = model.get_value(iter_, 3) - state = model.get_value(iter_, 4) - cell.set_property('markup', markup) - if jid: - cell.set_property('cell_background_set', False) - if state > 0: - # 1 = fetching, 2 = error - cell.set_property('foreground_set', True) - else: - # Normal/succes - cell.set_property('foreground_set', False) - else: - theme = gajim.config.get('roster_theme') - bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor') - if bgcolor: - cell.set_property('cell_background_set', True) - cell.set_property('foreground_set', False) + def _text_renderer_data_func(self, col, cell, model, iter_): + """ + Callback for setting the text renderer's properties + """ + jid = model.get_value(iter_, 0) + markup = model.get_value(iter_, 3) + state = model.get_value(iter_, 4) + cell.set_property('markup', markup) + if jid: + cell.set_property('cell_background_set', False) + if state > 0: + # 1 = fetching, 2 = error + cell.set_property('foreground_set', True) + else: + # Normal/succes + cell.set_property('foreground_set', False) + else: + theme = gajim.config.get('roster_theme') + bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor') + if bgcolor: + cell.set_property('cell_background_set', True) + cell.set_property('foreground_set', False) - def _treemodel_sort_func(self, model, iter1, iter2): - """ - Sort function for our treemode - """ - # Compare state - statecmp = cmp(model.get_value(iter1, 4), model.get_value(iter2, 4)) - if statecmp == 0: - # These can be None, apparently - descr1 = model.get_value(iter1, 3) - if descr1: - descr1 = descr1.decode('utf-8') - descr2 = model.get_value(iter2, 3) - if descr2: - descr2 = descr2.decode('utf-8') - # Compare strings - return cmp(descr1, descr2) - return statecmp + def _treemodel_sort_func(self, model, iter1, iter2): + """ + Sort function for our treemode + """ + # Compare state + statecmp = cmp(model.get_value(iter1, 4), model.get_value(iter2, 4)) + if statecmp == 0: + # These can be None, apparently + descr1 = model.get_value(iter1, 3) + if descr1: + descr1 = descr1.decode('utf-8') + descr2 = model.get_value(iter2, 3) + if descr2: + descr2 = descr2.decode('utf-8') + # Compare strings + return cmp(descr1, descr2) + return statecmp - def _show_tooltip(self, state): - view = self.window.services_treeview - pointer = view.get_pointer() - props = view.get_path_at_pos(pointer[0], pointer[1]) - # check if the current pointer is at the same path - # as it was before setting the timeout - if props and self.tooltip.id == props[0]: - # bounding rectangle of coordinates for the cell within the treeview - rect = view.get_cell_area(props[0], props[1]) - # position of the treeview on the screen - position = view.window.get_origin() - self.tooltip.show_tooltip(state, rect.height, position[1] + rect.y) - else: - self.tooltip.hide_tooltip() + def _show_tooltip(self, state): + view = self.window.services_treeview + pointer = view.get_pointer() + props = view.get_path_at_pos(pointer[0], pointer[1]) + # check if the current pointer is at the same path + # as it was before setting the timeout + if props and self.tooltip.id == props[0]: + # bounding rectangle of coordinates for the cell within the treeview + rect = view.get_cell_area(props[0], props[1]) + # position of the treeview on the screen + position = view.window.get_origin() + self.tooltip.show_tooltip(state, rect.height, position[1] + rect.y) + else: + self.tooltip.hide_tooltip() - # These are all callbacks to make tooltips work - def on_treeview_leave_notify_event(self, widget, event): - props = widget.get_path_at_pos(int(event.x), int(event.y)) - if self.tooltip.timeout > 0: - if not props or self.tooltip.id == props[0]: - self.tooltip.hide_tooltip() + # These are all callbacks to make tooltips work + def on_treeview_leave_notify_event(self, widget, event): + props = widget.get_path_at_pos(int(event.x), int(event.y)) + if self.tooltip.timeout > 0: + if not props or self.tooltip.id == props[0]: + self.tooltip.hide_tooltip() - def on_treeview_motion_notify_event(self, widget, event): - props = widget.get_path_at_pos(int(event.x), int(event.y)) - if self.tooltip.timeout > 0: - if not props or self.tooltip.id != props[0]: - self.tooltip.hide_tooltip() - if props: - row = props[0] - iter_ = None - try: - iter_ = self.model.get_iter(row) - except Exception: - self.tooltip.hide_tooltip() - return - jid = self.model[iter_][0] - state = self.model[iter_][4] - # Not a category, and we have something to say about state - if jid and state > 0 and \ - (self.tooltip.timeout == 0 or self.tooltip.id != props[0]): - self.tooltip.id = row - self.tooltip.timeout = gobject.timeout_add(500, - self._show_tooltip, state) + def on_treeview_motion_notify_event(self, widget, event): + props = widget.get_path_at_pos(int(event.x), int(event.y)) + if self.tooltip.timeout > 0: + if not props or self.tooltip.id != props[0]: + self.tooltip.hide_tooltip() + if props: + row = props[0] + iter_ = None + try: + iter_ = self.model.get_iter(row) + except Exception: + self.tooltip.hide_tooltip() + return + jid = self.model[iter_][0] + state = self.model[iter_][4] + # Not a category, and we have something to say about state + if jid and state > 0 and \ + (self.tooltip.timeout == 0 or self.tooltip.id != props[0]): + self.tooltip.id = row + self.tooltip.timeout = gobject.timeout_add(500, + self._show_tooltip, state) - def on_treeview_event_hide_tooltip(self, widget, event): - """ - This happens on scroll_event, key_press_event and button_press_event - """ - self.tooltip.hide_tooltip() + def on_treeview_event_hide_tooltip(self, widget, event): + """ + This happens on scroll_event, key_press_event and button_press_event + """ + self.tooltip.hide_tooltip() - def _create_treemodel(self): - # JID, node, icon, description, state - # State means 2 when error, 1 when fetching, 0 when succes. - view = self.window.services_treeview - self.model = gtk.TreeStore(str, str, gtk.gdk.Pixbuf, str, int) - self.model.set_sort_func(4, self._treemodel_sort_func) - self.model.set_sort_column_id(4, gtk.SORT_ASCENDING) - view.set_model(self.model) + def _create_treemodel(self): + # JID, node, icon, description, state + # State means 2 when error, 1 when fetching, 0 when succes. + view = self.window.services_treeview + self.model = gtk.TreeStore(str, str, gtk.gdk.Pixbuf, str, int) + self.model.set_sort_func(4, self._treemodel_sort_func) + self.model.set_sort_column_id(4, gtk.SORT_ASCENDING) + view.set_model(self.model) - col = gtk.TreeViewColumn() - # Icon Renderer - renderer = gtk.CellRendererPixbuf() - renderer.set_property('xpad', 6) - col.pack_start(renderer, expand=False) - col.set_cell_data_func(renderer, self._pixbuf_renderer_data_func) - # Text Renderer - renderer = gtk.CellRendererText() - col.pack_start(renderer, expand=True) - col.set_cell_data_func(renderer, self._text_renderer_data_func) - renderer.set_property('foreground', 'dark gray') - # Save this so we can go along with theme changes - self._renderer = renderer - self.update_theme() + col = gtk.TreeViewColumn() + # Icon Renderer + renderer = gtk.CellRendererPixbuf() + renderer.set_property('xpad', 6) + col.pack_start(renderer, expand=False) + col.set_cell_data_func(renderer, self._pixbuf_renderer_data_func) + # Text Renderer + renderer = gtk.CellRendererText() + col.pack_start(renderer, expand=True) + col.set_cell_data_func(renderer, self._text_renderer_data_func) + renderer.set_property('foreground', 'dark gray') + # Save this so we can go along with theme changes + self._renderer = renderer + self.update_theme() - view.insert_column(col, -1) - col.set_resizable(True) + view.insert_column(col, -1) + col.set_resizable(True) - # Connect signals - scrollwin = self.window.services_scrollwin - self._view_signals.append(view.connect('leave-notify-event', - self.on_treeview_leave_notify_event)) - self._view_signals.append(view.connect('motion-notify-event', - self.on_treeview_motion_notify_event)) - self._view_signals.append(view.connect('key-press-event', - self.on_treeview_event_hide_tooltip)) - self._view_signals.append(view.connect('button-press-event', - self.on_treeview_event_hide_tooltip)) - self._scroll_signal = scrollwin.connect('scroll-event', - self.on_treeview_event_hide_tooltip) + # Connect signals + scrollwin = self.window.services_scrollwin + self._view_signals.append(view.connect('leave-notify-event', + self.on_treeview_leave_notify_event)) + self._view_signals.append(view.connect('motion-notify-event', + self.on_treeview_motion_notify_event)) + self._view_signals.append(view.connect('key-press-event', + self.on_treeview_event_hide_tooltip)) + self._view_signals.append(view.connect('button-press-event', + self.on_treeview_event_hide_tooltip)) + self._scroll_signal = scrollwin.connect('scroll-event', + self.on_treeview_event_hide_tooltip) - def _clean_treemodel(self): - # Disconnect signals - view = self.window.services_treeview - for sig in self._view_signals: - view.disconnect(sig) - self._view_signals = [] - if self._scroll_signal: - scrollwin = self.window.services_scrollwin - scrollwin.disconnect(self._scroll_signal) - self._scroll_signal = None - AgentBrowser._clean_treemodel(self) + def _clean_treemodel(self): + # Disconnect signals + view = self.window.services_treeview + for sig in self._view_signals: + view.disconnect(sig) + self._view_signals = [] + if self._scroll_signal: + scrollwin = self.window.services_scrollwin + scrollwin.disconnect(self._scroll_signal) + self._scroll_signal = None + AgentBrowser._clean_treemodel(self) - def _add_actions(self): - AgentBrowser._add_actions(self) - self.execute_button = gtk.Button() - image = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_BUTTON) - label = gtk.Label(_('_Execute Command')) - label.set_use_underline(True) - hbox = gtk.HBox() - hbox.pack_start(image, False, True, 6) - hbox.pack_end(label, True, True) - self.execute_button.add(hbox) - self.execute_button.connect('clicked', self.on_execute_button_clicked) - self.window.action_buttonbox.add(self.execute_button) - self.execute_button.show_all() + def _add_actions(self): + AgentBrowser._add_actions(self) + self.execute_button = gtk.Button() + image = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_BUTTON) + label = gtk.Label(_('_Execute Command')) + label.set_use_underline(True) + hbox = gtk.HBox() + hbox.pack_start(image, False, True, 6) + hbox.pack_end(label, True, True) + self.execute_button.add(hbox) + self.execute_button.connect('clicked', self.on_execute_button_clicked) + self.window.action_buttonbox.add(self.execute_button) + self.execute_button.show_all() - self.register_button = gtk.Button(label=_("Re_gister"), - use_underline=True) - self.register_button.connect('clicked', self.on_register_button_clicked) - self.window.action_buttonbox.add(self.register_button) - self.register_button.show_all() + self.register_button = gtk.Button(label=_("Re_gister"), + use_underline=True) + self.register_button.connect('clicked', self.on_register_button_clicked) + self.window.action_buttonbox.add(self.register_button) + self.register_button.show_all() - self.join_button = gtk.Button() - image = gtk.image_new_from_stock(gtk.STOCK_CONNECT, gtk.ICON_SIZE_BUTTON) - label = gtk.Label(_('_Join')) - label.set_use_underline(True) - hbox = gtk.HBox() - hbox.pack_start(image, False, True, 6) - hbox.pack_end(label, True, True) - self.join_button.add(hbox) - self.join_button.connect('clicked', self.on_join_button_clicked) - self.window.action_buttonbox.add(self.join_button) - self.join_button.show_all() + self.join_button = gtk.Button() + image = gtk.image_new_from_stock(gtk.STOCK_CONNECT, gtk.ICON_SIZE_BUTTON) + label = gtk.Label(_('_Join')) + label.set_use_underline(True) + hbox = gtk.HBox() + hbox.pack_start(image, False, True, 6) + hbox.pack_end(label, True, True) + self.join_button.add(hbox) + self.join_button.connect('clicked', self.on_join_button_clicked) + self.window.action_buttonbox.add(self.join_button) + self.join_button.show_all() - self.search_button = gtk.Button() - image = gtk.image_new_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_BUTTON) - label = gtk.Label(_('_Search')) - label.set_use_underline(True) - hbox = gtk.HBox() - hbox.pack_start(image, False, True, 6) - hbox.pack_end(label, True, True) - self.search_button.add(hbox) - self.search_button.connect('clicked', self.on_search_button_clicked) - self.window.action_buttonbox.add(self.search_button) - self.search_button.show_all() + self.search_button = gtk.Button() + image = gtk.image_new_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_BUTTON) + label = gtk.Label(_('_Search')) + label.set_use_underline(True) + hbox = gtk.HBox() + hbox.pack_start(image, False, True, 6) + hbox.pack_end(label, True, True) + self.search_button.add(hbox) + self.search_button.connect('clicked', self.on_search_button_clicked) + self.window.action_buttonbox.add(self.search_button) + self.search_button.show_all() - def _clean_actions(self): - if self.execute_button: - self.execute_button.destroy() - self.execute_button = None - if self.register_button: - self.register_button.destroy() - self.register_button = None - if self.join_button: - self.join_button.destroy() - self.join_button = None - if self.search_button: - self.search_button.destroy() - self.search_button = None - AgentBrowser._clean_actions(self) + def _clean_actions(self): + if self.execute_button: + self.execute_button.destroy() + self.execute_button = None + if self.register_button: + self.register_button.destroy() + self.register_button = None + if self.join_button: + self.join_button.destroy() + self.join_button = None + if self.search_button: + self.search_button.destroy() + self.search_button = None + AgentBrowser._clean_actions(self) - def on_search_button_clicked(self, widget = None): - """ - When we want to search something: open search window - """ - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if not iter_: - return - service = model[iter_][0].decode('utf-8') - if service in gajim.interface.instances[self.account]['search']: - gajim.interface.instances[self.account]['search'][service].window.\ - present() - else: - gajim.interface.instances[self.account]['search'][service] = \ - search_window.SearchWindow(self.account, service) + def on_search_button_clicked(self, widget = None): + """ + When we want to search something: open search window + """ + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if not iter_: + return + service = model[iter_][0].decode('utf-8') + if service in gajim.interface.instances[self.account]['search']: + gajim.interface.instances[self.account]['search'][service].window.\ + present() + else: + gajim.interface.instances[self.account]['search'][service] = \ + search_window.SearchWindow(self.account, service) - def cleanup(self): - self.tooltip.hide_tooltip() - AgentBrowser.cleanup(self) + def cleanup(self): + self.tooltip.hide_tooltip() + AgentBrowser.cleanup(self) - def update_theme(self): - theme = gajim.config.get('roster_theme') - bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor') - if bgcolor: - self._renderer.set_property('cell-background', bgcolor) - self.window.services_treeview.queue_draw() + def update_theme(self): + theme = gajim.config.get('roster_theme') + bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor') + if bgcolor: + self._renderer.set_property('cell-background', bgcolor) + self.window.services_treeview.queue_draw() - def on_execute_button_clicked(self, widget=None): - """ - When we want to execute a command: open adhoc command window - """ - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if not iter_: - return - service = model[iter_][0].decode('utf-8') - node = model[iter_][1].decode('utf-8') - adhoc_commands.CommandWindow(self.account, service, commandnode=node) + def on_execute_button_clicked(self, widget=None): + """ + When we want to execute a command: open adhoc command window + """ + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if not iter_: + return + service = model[iter_][0].decode('utf-8') + node = model[iter_][1].decode('utf-8') + adhoc_commands.CommandWindow(self.account, service, commandnode=node) - def on_register_button_clicked(self, widget = None): - """ - When we want to register an agent: request information about registering - with the agent and close the window - """ - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if not iter_: - return - jid = model[iter_][0].decode('utf-8') - if jid: - gajim.connections[self.account].request_register_agent_info(jid) - self.window.destroy(chain = True) + def on_register_button_clicked(self, widget = None): + """ + When we want to register an agent: request information about registering + with the agent and close the window + """ + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if not iter_: + return + jid = model[iter_][0].decode('utf-8') + if jid: + gajim.connections[self.account].request_register_agent_info(jid) + self.window.destroy(chain = True) - def on_join_button_clicked(self, widget): - """ - When we want to join an IRC room or create a new MUC room: Opens the - join_groupchat_window - """ - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if not iter_: - return - service = model[iter_][0].decode('utf-8') - if 'join_gc' not in gajim.interface.instances[self.account]: - try: - dialogs.JoinGroupchatWindow(self.account, service) - except GajimGeneralException: - pass - else: - gajim.interface.instances[self.account]['join_gc'].window.present() - self.window.destroy(chain = True) + def on_join_button_clicked(self, widget): + """ + When we want to join an IRC room or create a new MUC room: Opens the + join_groupchat_window + """ + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if not iter_: + return + service = model[iter_][0].decode('utf-8') + if 'join_gc' not in gajim.interface.instances[self.account]: + try: + dialogs.JoinGroupchatWindow(self.account, service) + except GajimGeneralException: + pass + else: + gajim.interface.instances[self.account]['join_gc'].window.present() + self.window.destroy(chain = True) - def update_actions(self): - if self.execute_button: - self.execute_button.set_sensitive(False) - if self.register_button: - self.register_button.set_sensitive(False) - if self.browse_button: - self.browse_button.set_sensitive(False) - if self.join_button: - self.join_button.set_sensitive(False) - if self.search_button: - self.search_button.set_sensitive(False) - model, iter_ = self.window.services_treeview.get_selection().get_selected() - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if not iter_: - return - if not model[iter_][0]: - # We're on a category row - return - if model[iter_][4] != 0: - # We don't have the info (yet) - # It's either unknown or a transport, register button should be active - if self.register_button: - self.register_button.set_sensitive(True) - # Guess what kind of service we're dealing with - if self.browse_button: - jid = model[iter_][0].decode('utf-8') - type_ = gajim.get_transport_name_from_jid(jid, - use_config_setting = False) - if type_: - identity = {'category': '_jid', 'type': type_} - klass = self.cache.get_browser([identity]) - if klass: - self.browse_button.set_sensitive(True) - else: - # We couldn't guess - self.browse_button.set_sensitive(True) - else: - # Normal case, we have info - AgentBrowser.update_actions(self) + def update_actions(self): + if self.execute_button: + self.execute_button.set_sensitive(False) + if self.register_button: + self.register_button.set_sensitive(False) + if self.browse_button: + self.browse_button.set_sensitive(False) + if self.join_button: + self.join_button.set_sensitive(False) + if self.search_button: + self.search_button.set_sensitive(False) + model, iter_ = self.window.services_treeview.get_selection().get_selected() + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if not iter_: + return + if not model[iter_][0]: + # We're on a category row + return + if model[iter_][4] != 0: + # We don't have the info (yet) + # It's either unknown or a transport, register button should be active + if self.register_button: + self.register_button.set_sensitive(True) + # Guess what kind of service we're dealing with + if self.browse_button: + jid = model[iter_][0].decode('utf-8') + type_ = gajim.get_transport_name_from_jid(jid, + use_config_setting = False) + if type_: + identity = {'category': '_jid', 'type': type_} + klass = self.cache.get_browser([identity]) + if klass: + self.browse_button.set_sensitive(True) + else: + # We couldn't guess + self.browse_button.set_sensitive(True) + else: + # Normal case, we have info + AgentBrowser.update_actions(self) - def _update_actions(self, jid, node, identities, features, data): - AgentBrowser._update_actions(self, jid, node, identities, features, data) - if self.execute_button and xmpp.NS_COMMANDS in features: - self.execute_button.set_sensitive(True) - if self.search_button and xmpp.NS_SEARCH in features: - self.search_button.set_sensitive(True) - if self.register_button and xmpp.NS_REGISTER in features: - # We can register this agent - registered_transports = [] - jid_list = gajim.contacts.get_jid_list(self.account) - for jid in jid_list: - contact = gajim.contacts.get_first_contact_from_jid( - self.account, jid) - if _('Transports') in contact.groups: - registered_transports.append(jid) - if jid in registered_transports: - self.register_button.set_label(_('_Edit')) - else: - self.register_button.set_label(_('Re_gister')) - self.register_button.set_sensitive(True) - if self.join_button and xmpp.NS_MUC in features: - self.join_button.set_sensitive(True) + def _update_actions(self, jid, node, identities, features, data): + AgentBrowser._update_actions(self, jid, node, identities, features, data) + if self.execute_button and xmpp.NS_COMMANDS in features: + self.execute_button.set_sensitive(True) + if self.search_button and xmpp.NS_SEARCH in features: + self.search_button.set_sensitive(True) + if self.register_button and xmpp.NS_REGISTER in features: + # We can register this agent + registered_transports = [] + jid_list = gajim.contacts.get_jid_list(self.account) + for jid in jid_list: + contact = gajim.contacts.get_first_contact_from_jid( + self.account, jid) + if _('Transports') in contact.groups: + registered_transports.append(jid) + if jid in registered_transports: + self.register_button.set_label(_('_Edit')) + else: + self.register_button.set_label(_('Re_gister')) + self.register_button.set_sensitive(True) + if self.join_button and xmpp.NS_MUC in features: + self.join_button.set_sensitive(True) - def _default_action(self, jid, node, identities, features, data): - if AgentBrowser._default_action(self, jid, node, identities, features, data): - return True - if xmpp.NS_REGISTER in features: - # Register if we can't browse - self.on_register_button_clicked() - return True - return False + def _default_action(self, jid, node, identities, features, data): + if AgentBrowser._default_action(self, jid, node, identities, features, data): + return True + if xmpp.NS_REGISTER in features: + # Register if we can't browse + self.on_register_button_clicked() + return True + return False - def browse(self, force = False): - self._progress = 0 - AgentBrowser.browse(self, force = force) + def browse(self, force = False): + self._progress = 0 + AgentBrowser.browse(self, force = force) - def _expand_all(self): - """ - Expand all items in the treeview - """ - # GTK apparently screws up here occasionally. :/ - #def expand_all(*args): - # self.window.services_treeview.expand_all() - # self.expanding = False - # return False - #self.expanding = True - #gobject.idle_add(expand_all) - self.window.services_treeview.expand_all() + def _expand_all(self): + """ + Expand all items in the treeview + """ + # GTK apparently screws up here occasionally. :/ + #def expand_all(*args): + # self.window.services_treeview.expand_all() + # self.expanding = False + # return False + #self.expanding = True + #gobject.idle_add(expand_all) + self.window.services_treeview.expand_all() - def _update_progressbar(self): - """ - Update the progressbar - """ - # Refresh this every update - if self._progressbar_sourceid: - gobject.source_remove(self._progressbar_sourceid) + def _update_progressbar(self): + """ + Update the progressbar + """ + # Refresh this every update + if self._progressbar_sourceid: + gobject.source_remove(self._progressbar_sourceid) - fraction = 0 - if self._total_items: - self.window.progressbar.set_text(_("Scanning %(current)d / %(total)d.." - ) % {'current': self._progress, 'total': self._total_items}) - fraction = float(self._progress) / float(self._total_items) - if self._progress >= self._total_items: - # We show the progressbar for just a bit before hiding it. - id_ = gobject.timeout_add_seconds(2, self._hide_progressbar_cb) - self._progressbar_sourceid = id_ - else: - self.window.progressbar.show() - # Hide the progressbar if we're timing out anyways. (20 secs) - id_ = gobject.timeout_add_seconds(20, self._hide_progressbar_cb) - self._progressbar_sourceid = id_ - self.window.progressbar.set_fraction(fraction) + fraction = 0 + if self._total_items: + self.window.progressbar.set_text(_("Scanning %(current)d / %(total)d.." + ) % {'current': self._progress, 'total': self._total_items}) + fraction = float(self._progress) / float(self._total_items) + if self._progress >= self._total_items: + # We show the progressbar for just a bit before hiding it. + id_ = gobject.timeout_add_seconds(2, self._hide_progressbar_cb) + self._progressbar_sourceid = id_ + else: + self.window.progressbar.show() + # Hide the progressbar if we're timing out anyways. (20 secs) + id_ = gobject.timeout_add_seconds(20, self._hide_progressbar_cb) + self._progressbar_sourceid = id_ + self.window.progressbar.set_fraction(fraction) - def _hide_progressbar_cb(self, *args): - """ - Simple callback to hide the progressbar a second after we finish - """ - if self.active: - self.window.progressbar.hide() - return False + def _hide_progressbar_cb(self, *args): + """ + Simple callback to hide the progressbar a second after we finish + """ + if self.active: + self.window.progressbar.hide() + return False - def _friendly_category(self, category, type_=None): - """ - Get the friendly category name and priority - """ - cat = None - if type_: - # Try type-specific override - try: - cat, prio = _cat_to_descr[(category, type_)] - except KeyError: - pass - if not cat: - try: - cat, prio = _cat_to_descr[category] - except KeyError: - cat, prio = _cat_to_descr['other'] - return cat, prio + def _friendly_category(self, category, type_=None): + """ + Get the friendly category name and priority + """ + cat = None + if type_: + # Try type-specific override + try: + cat, prio = _cat_to_descr[(category, type_)] + except KeyError: + pass + if not cat: + try: + cat, prio = _cat_to_descr[category] + except KeyError: + cat, prio = _cat_to_descr['other'] + return cat, prio - def _create_category(self, cat, type_=None): - """ - Creates a category row - """ - cat, prio = self._friendly_category(cat, type_) - return self.model.append(None, ('', '', None, cat, prio)) + def _create_category(self, cat, type_=None): + """ + Creates a category row + """ + cat, prio = self._friendly_category(cat, type_) + return self.model.append(None, ('', '', None, cat, prio)) - def _find_category(self, cat, type_=None): - """ - Looks up a category row and returns the iterator to it, or None - """ - cat = self._friendly_category(cat, type_)[0] - iter_ = self.model.get_iter_root() - while iter_: - if self.model.get_value(iter_, 3).decode('utf-8') == cat: - break - iter_ = self.model.iter_next(iter_) - if iter_: - return iter_ - return None + def _find_category(self, cat, type_=None): + """ + Looks up a category row and returns the iterator to it, or None + """ + cat = self._friendly_category(cat, type_)[0] + iter_ = self.model.get_iter_root() + while iter_: + if self.model.get_value(iter_, 3).decode('utf-8') == cat: + break + iter_ = self.model.iter_next(iter_) + if iter_: + return iter_ + return None - def _find_item(self, jid, node): - iter_ = None - cat_iter = self.model.get_iter_root() - while cat_iter and not iter_: - iter_ = self.model.iter_children(cat_iter) - while iter_: - cjid = self.model.get_value(iter_, 0).decode('utf-8') - cnode = self.model.get_value(iter_, 1).decode('utf-8') - if jid == cjid and node == cnode: - break - iter_ = self.model.iter_next(iter_) - cat_iter = self.model.iter_next(cat_iter) - if iter_: - return iter_ - return None + def _find_item(self, jid, node): + iter_ = None + cat_iter = self.model.get_iter_root() + while cat_iter and not iter_: + iter_ = self.model.iter_children(cat_iter) + while iter_: + cjid = self.model.get_value(iter_, 0).decode('utf-8') + cnode = self.model.get_value(iter_, 1).decode('utf-8') + if jid == cjid and node == cnode: + break + iter_ = self.model.iter_next(iter_) + cat_iter = self.model.iter_next(cat_iter) + if iter_: + return iter_ + return None - def _add_item(self, jid, node, parent_node, item, force): - # Row text - addr = get_agent_address(jid, node) - if 'name' in item: - descr = "%s\n%s" % (item['name'], addr) - else: - descr = "%s" % addr - # Guess which kind of service this is - identities = [] - type_ = gajim.get_transport_name_from_jid(jid, - use_config_setting = False) - if type_: - identity = {'category': '_jid', 'type': type_} - identities.append(identity) - cat_args = ('_jid', type_) - else: - # Put it in the 'other' category for now - cat_args = ('other',) - # Set the pixmap for the row - pix = self.cache.get_icon(identities) - # Put it in the right category - cat = self._find_category(*cat_args) - if not cat: - cat = self._create_category(*cat_args) - self.model.append(cat, (jid, node, pix, descr, 1)) - gobject.idle_add(self._expand_all) - # Grab info on the service - self.cache.get_info(jid, node, self._agent_info, force=force) - self._update_progressbar() + def _add_item(self, jid, node, parent_node, item, force): + # Row text + addr = get_agent_address(jid, node) + if 'name' in item: + descr = "%s\n%s" % (item['name'], addr) + else: + descr = "%s" % addr + # Guess which kind of service this is + identities = [] + type_ = gajim.get_transport_name_from_jid(jid, + use_config_setting = False) + if type_: + identity = {'category': '_jid', 'type': type_} + identities.append(identity) + cat_args = ('_jid', type_) + else: + # Put it in the 'other' category for now + cat_args = ('other',) + # Set the pixmap for the row + pix = self.cache.get_icon(identities) + # Put it in the right category + cat = self._find_category(*cat_args) + if not cat: + cat = self._create_category(*cat_args) + self.model.append(cat, (jid, node, pix, descr, 1)) + gobject.idle_add(self._expand_all) + # Grab info on the service + self.cache.get_info(jid, node, self._agent_info, force=force) + self._update_progressbar() - def _update_item(self, iter_, jid, node, item): - addr = get_agent_address(jid, node) - if 'name' in item: - descr = "%s\n%s" % (item['name'], addr) - else: - descr = "%s" % addr - self.model[iter_][3] = descr + def _update_item(self, iter_, jid, node, item): + addr = get_agent_address(jid, node) + if 'name' in item: + descr = "%s\n%s" % (item['name'], addr) + else: + descr = "%s" % addr + self.model[iter_][3] = descr - def _update_info(self, iter_, jid, node, identities, features, data): - addr = get_agent_address(jid, node) - name = identities[0].get('name', '') - if name: - descr = "%s\n%s" % (name, addr) - else: - descr = "%s" % addr + def _update_info(self, iter_, jid, node, identities, features, data): + addr = get_agent_address(jid, node) + name = identities[0].get('name', '') + if name: + descr = "%s\n%s" % (name, addr) + else: + descr = "%s" % addr - # Update progress - self._progress += 1 - self._update_progressbar() + # Update progress + self._progress += 1 + self._update_progressbar() - # Search for an icon and category we can display - pix = self.cache.get_icon(identities) - for identity in identities: - try: - cat, type_ = identity['category'], identity['type'] - except KeyError: - continue - break + # Search for an icon and category we can display + pix = self.cache.get_icon(identities) + for identity in identities: + try: + cat, type_ = identity['category'], identity['type'] + except KeyError: + continue + break - # Check if we have to move categories - old_cat_iter = self.model.iter_parent(iter_) - old_cat = self.model.get_value(old_cat_iter, 3).decode('utf-8') - if self.model.get_value(old_cat_iter, 3) == cat: - # Already in the right category, just update - self.model[iter_][2] = pix - self.model[iter_][3] = descr - self.model[iter_][4] = 0 - return - # Not in the right category, move it. - self.model.remove(iter_) + # Check if we have to move categories + old_cat_iter = self.model.iter_parent(iter_) + old_cat = self.model.get_value(old_cat_iter, 3).decode('utf-8') + if self.model.get_value(old_cat_iter, 3) == cat: + # Already in the right category, just update + self.model[iter_][2] = pix + self.model[iter_][3] = descr + self.model[iter_][4] = 0 + return + # Not in the right category, move it. + self.model.remove(iter_) - # Check if the old category is empty - if not self.model.iter_is_valid(old_cat_iter): - old_cat_iter = self._find_category(old_cat) - if not self.model.iter_children(old_cat_iter): - self.model.remove(old_cat_iter) + # Check if the old category is empty + if not self.model.iter_is_valid(old_cat_iter): + old_cat_iter = self._find_category(old_cat) + if not self.model.iter_children(old_cat_iter): + self.model.remove(old_cat_iter) - cat_iter = self._find_category(cat, type_) - if not cat_iter: - cat_iter = self._create_category(cat, type_) - self.model.append(cat_iter, (jid, node, pix, descr, 0)) - self._expand_all() + cat_iter = self._find_category(cat, type_) + if not cat_iter: + cat_iter = self._create_category(cat, type_) + self.model.append(cat_iter, (jid, node, pix, descr, 0)) + self._expand_all() - def _update_error(self, iter_, jid, node): - self.model[iter_][4] = 2 - self._progress += 1 - self._update_progressbar() + def _update_error(self, iter_, jid, node): + self.model[iter_][4] = 2 + self._progress += 1 + self._update_progressbar() class MucBrowser(AgentBrowser): - def __init__(self, *args, **kwargs): - AgentBrowser.__init__(self, *args, **kwargs) - self.join_button = None - self.bookmark_button = None + def __init__(self, *args, **kwargs): + AgentBrowser.__init__(self, *args, **kwargs) + self.join_button = None + self.bookmark_button = None - def _create_treemodel(self): - # JID, node, name, users_int, users_str, description, fetched - # This is rather long, I'd rather not use a data_func here though. - # Users is a string, because want to be able to leave it empty. - self.model = gtk.ListStore(str, str, str, int, str, str, bool) - self.model.set_sort_column_id(2, gtk.SORT_ASCENDING) - self.window.services_treeview.set_model(self.model) - # Name column - col = gtk.TreeViewColumn(_('Name')) - col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) - col.set_fixed_width(100) - renderer = gtk.CellRendererText() - col.pack_start(renderer) - col.set_attributes(renderer, text = 2) - col.set_sort_column_id(2) - self.window.services_treeview.insert_column(col, -1) - col.set_resizable(True) - # Users column - col = gtk.TreeViewColumn(_('Users')) - renderer = gtk.CellRendererText() - col.pack_start(renderer) - col.set_attributes(renderer, text = 4) - col.set_sort_column_id(3) - self.window.services_treeview.insert_column(col, -1) - col.set_resizable(True) - # Description column - col = gtk.TreeViewColumn(_('Description')) - renderer = gtk.CellRendererText() - col.pack_start(renderer) - col.set_attributes(renderer, text = 5) - col.set_sort_column_id(4) - self.window.services_treeview.insert_column(col, -1) - col.set_resizable(True) - # Id column - col = gtk.TreeViewColumn(_('Id')) - renderer = gtk.CellRendererText() - col.pack_start(renderer) - col.set_attributes(renderer, text = 0) - col.set_sort_column_id(0) - self.window.services_treeview.insert_column(col, -1) - col.set_resizable(True) - self.window.services_treeview.set_headers_visible(True) - self.window.services_treeview.set_headers_clickable(True) - # Source id for idle callback used to start disco#info queries. - self._fetch_source = None - # Query failure counter - self._broken = 0 - # Connect to scrollwindow scrolling - self.vadj = self.window.services_scrollwin.get_property('vadjustment') - self.vadj_cbid = self.vadj.connect('value-changed', self.on_scroll) - # And to size changes - self.size_cbid = self.window.services_scrollwin.connect( - 'size-allocate', self.on_scroll) + def _create_treemodel(self): + # JID, node, name, users_int, users_str, description, fetched + # This is rather long, I'd rather not use a data_func here though. + # Users is a string, because want to be able to leave it empty. + self.model = gtk.ListStore(str, str, str, int, str, str, bool) + self.model.set_sort_column_id(2, gtk.SORT_ASCENDING) + self.window.services_treeview.set_model(self.model) + # Name column + col = gtk.TreeViewColumn(_('Name')) + col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) + col.set_fixed_width(100) + renderer = gtk.CellRendererText() + col.pack_start(renderer) + col.set_attributes(renderer, text = 2) + col.set_sort_column_id(2) + self.window.services_treeview.insert_column(col, -1) + col.set_resizable(True) + # Users column + col = gtk.TreeViewColumn(_('Users')) + renderer = gtk.CellRendererText() + col.pack_start(renderer) + col.set_attributes(renderer, text = 4) + col.set_sort_column_id(3) + self.window.services_treeview.insert_column(col, -1) + col.set_resizable(True) + # Description column + col = gtk.TreeViewColumn(_('Description')) + renderer = gtk.CellRendererText() + col.pack_start(renderer) + col.set_attributes(renderer, text = 5) + col.set_sort_column_id(4) + self.window.services_treeview.insert_column(col, -1) + col.set_resizable(True) + # Id column + col = gtk.TreeViewColumn(_('Id')) + renderer = gtk.CellRendererText() + col.pack_start(renderer) + col.set_attributes(renderer, text = 0) + col.set_sort_column_id(0) + self.window.services_treeview.insert_column(col, -1) + col.set_resizable(True) + self.window.services_treeview.set_headers_visible(True) + self.window.services_treeview.set_headers_clickable(True) + # Source id for idle callback used to start disco#info queries. + self._fetch_source = None + # Query failure counter + self._broken = 0 + # Connect to scrollwindow scrolling + self.vadj = self.window.services_scrollwin.get_property('vadjustment') + self.vadj_cbid = self.vadj.connect('value-changed', self.on_scroll) + # And to size changes + self.size_cbid = self.window.services_scrollwin.connect( + 'size-allocate', self.on_scroll) - def _clean_treemodel(self): - if self.size_cbid: - self.window.services_scrollwin.disconnect(self.size_cbid) - self.size_cbid = None - if self.vadj_cbid: - self.vadj.disconnect(self.vadj_cbid) - self.vadj_cbid = None - AgentBrowser._clean_treemodel(self) + def _clean_treemodel(self): + if self.size_cbid: + self.window.services_scrollwin.disconnect(self.size_cbid) + self.size_cbid = None + if self.vadj_cbid: + self.vadj.disconnect(self.vadj_cbid) + self.vadj_cbid = None + AgentBrowser._clean_treemodel(self) - def _add_actions(self): - self.bookmark_button = gtk.Button(label=_('_Bookmark'), use_underline=True) - self.bookmark_button.connect('clicked', self.on_bookmark_button_clicked) - self.window.action_buttonbox.add(self.bookmark_button) - self.bookmark_button.show_all() - self.join_button = gtk.Button(label=_('_Join'), use_underline=True) - self.join_button.connect('clicked', self.on_join_button_clicked) - self.window.action_buttonbox.add(self.join_button) - self.join_button.show_all() + def _add_actions(self): + self.bookmark_button = gtk.Button(label=_('_Bookmark'), use_underline=True) + self.bookmark_button.connect('clicked', self.on_bookmark_button_clicked) + self.window.action_buttonbox.add(self.bookmark_button) + self.bookmark_button.show_all() + self.join_button = gtk.Button(label=_('_Join'), use_underline=True) + self.join_button.connect('clicked', self.on_join_button_clicked) + self.window.action_buttonbox.add(self.join_button) + self.join_button.show_all() - def _clean_actions(self): - if self.bookmark_button: - self.bookmark_button.destroy() - self.bookmark_button = None - if self.join_button: - self.join_button.destroy() - self.join_button = None + def _clean_actions(self): + if self.bookmark_button: + self.bookmark_button.destroy() + self.bookmark_button = None + if self.join_button: + self.join_button.destroy() + self.join_button = None - def on_bookmark_button_clicked(self, *args): - model, iter = self.window.services_treeview.get_selection().get_selected() - if not iter: - return - name = gajim.config.get_per('accounts', self.account, 'name') - room_jid = model[iter][0].decode('utf-8') - bm = { - 'name': room_jid.split('@')[0], - 'jid': room_jid, - 'autojoin': '0', - 'minimize': '0', - 'password': '', - 'nick': name - } + def on_bookmark_button_clicked(self, *args): + model, iter = self.window.services_treeview.get_selection().get_selected() + if not iter: + return + name = gajim.config.get_per('accounts', self.account, 'name') + room_jid = model[iter][0].decode('utf-8') + bm = { + 'name': room_jid.split('@')[0], + 'jid': room_jid, + 'autojoin': '0', + 'minimize': '0', + 'password': '', + 'nick': name + } - for bookmark in gajim.connections[self.account].bookmarks: - if bookmark['jid'] == bm['jid']: - dialogs.ErrorDialog( - _('Bookmark already set'), - _('Group Chat "%s" is already in your bookmarks.') % bm['jid']) - return + for bookmark in gajim.connections[self.account].bookmarks: + if bookmark['jid'] == bm['jid']: + dialogs.ErrorDialog( + _('Bookmark already set'), + _('Group Chat "%s" is already in your bookmarks.') % bm['jid']) + return - gajim.connections[self.account].bookmarks.append(bm) - gajim.connections[self.account].store_bookmarks() + gajim.connections[self.account].bookmarks.append(bm) + gajim.connections[self.account].store_bookmarks() - gajim.interface.roster.set_actions_menu_needs_rebuild() + gajim.interface.roster.set_actions_menu_needs_rebuild() - dialogs.InformationDialog( - _('Bookmark has been added successfully'), - _('You can manage your bookmarks via Actions menu in your roster.')) + dialogs.InformationDialog( + _('Bookmark has been added successfully'), + _('You can manage your bookmarks via Actions menu in your roster.')) - def on_join_button_clicked(self, *args): - """ - When we want to join a conference: ask specific informations about the - selected agent and close the window - """ - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if not iter_: - return - service = model[iter_][0].decode('utf-8') - room = model[iter_][1].decode('utf-8') - if 'join_gc' not in gajim.interface.instances[self.account]: - try: - dialogs.JoinGroupchatWindow(self.account, service) - except GajimGeneralException: - pass - else: - gajim.interface.instances[self.account]['join_gc'].window.present() - self.window.destroy(chain = True) + def on_join_button_clicked(self, *args): + """ + When we want to join a conference: ask specific informations about the + selected agent and close the window + """ + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if not iter_: + return + service = model[iter_][0].decode('utf-8') + room = model[iter_][1].decode('utf-8') + if 'join_gc' not in gajim.interface.instances[self.account]: + try: + dialogs.JoinGroupchatWindow(self.account, service) + except GajimGeneralException: + pass + else: + gajim.interface.instances[self.account]['join_gc'].window.present() + self.window.destroy(chain = True) - def update_actions(self): - sens = self.window.services_treeview.get_selection().count_selected_rows() - if self.bookmark_button: - self.bookmark_button.set_sensitive(sens > 0) - if self.join_button: - self.join_button.set_sensitive(sens > 0) + def update_actions(self): + sens = self.window.services_treeview.get_selection().count_selected_rows() + if self.bookmark_button: + self.bookmark_button.set_sensitive(sens > 0) + if self.join_button: + self.join_button.set_sensitive(sens > 0) - def default_action(self): - self.on_join_button_clicked() + def default_action(self): + self.on_join_button_clicked() - def _start_info_query(self): - """ - Idle callback to start checking for visible rows - """ - self._fetch_source = None - self._query_visible() - return False + def _start_info_query(self): + """ + Idle callback to start checking for visible rows + """ + self._fetch_source = None + self._query_visible() + return False - def on_scroll(self, *args): - """ - Scrollwindow callback to trigger new queries on scolling - """ - # This apparently happens when inactive sometimes - self._query_visible() + def on_scroll(self, *args): + """ + Scrollwindow callback to trigger new queries on scolling + """ + # This apparently happens when inactive sometimes + self._query_visible() - def _query_visible(self): - """ - Query the next visible row for info - """ - if self._fetch_source: - # We're already fetching - return - view = self.window.services_treeview - if not view.flags() & gtk.REALIZED: - # Prevent a silly warning, try again in a bit. - self._fetch_source = gobject.timeout_add(100, self._start_info_query) - return - # We have to do this in a pygtk <2.8 compatible way :/ - #start, end = self.window.services_treeview.get_visible_range() - rect = view.get_visible_rect() - iter_ = end = None - # Top row - try: - sx, sy = view.tree_to_widget_coords(rect.x, rect.y) - spath = view.get_path_at_pos(sx, sy)[0] - iter_ = self.model.get_iter(spath) - except TypeError: - self._fetch_source = None - return - # Bottom row - # Iter compare is broke, use the path instead - try: - ex, ey = view.tree_to_widget_coords(rect.x + rect.height, - rect.y + rect.height) - end = view.get_path_at_pos(ex, ey)[0] - # end is the last visible, we want to query that aswell - end = (end[0] + 1,) - except TypeError: - # We're at the end of the model, we can leave end=None though. - pass - while iter_ and self.model.get_path(iter_) != end: - if not self.model.get_value(iter_, 6): - jid = self.model.get_value(iter_, 0).decode('utf-8') - node = self.model.get_value(iter_, 1).decode('utf-8') - self.cache.get_info(jid, node, self._agent_info) - self._fetch_source = True - return - iter_ = self.model.iter_next(iter_) - self._fetch_source = None + def _query_visible(self): + """ + Query the next visible row for info + """ + if self._fetch_source: + # We're already fetching + return + view = self.window.services_treeview + if not view.flags() & gtk.REALIZED: + # Prevent a silly warning, try again in a bit. + self._fetch_source = gobject.timeout_add(100, self._start_info_query) + return + # We have to do this in a pygtk <2.8 compatible way :/ + #start, end = self.window.services_treeview.get_visible_range() + rect = view.get_visible_rect() + iter_ = end = None + # Top row + try: + sx, sy = view.tree_to_widget_coords(rect.x, rect.y) + spath = view.get_path_at_pos(sx, sy)[0] + iter_ = self.model.get_iter(spath) + except TypeError: + self._fetch_source = None + return + # Bottom row + # Iter compare is broke, use the path instead + try: + ex, ey = view.tree_to_widget_coords(rect.x + rect.height, + rect.y + rect.height) + end = view.get_path_at_pos(ex, ey)[0] + # end is the last visible, we want to query that aswell + end = (end[0] + 1,) + except TypeError: + # We're at the end of the model, we can leave end=None though. + pass + while iter_ and self.model.get_path(iter_) != end: + if not self.model.get_value(iter_, 6): + jid = self.model.get_value(iter_, 0).decode('utf-8') + node = self.model.get_value(iter_, 1).decode('utf-8') + self.cache.get_info(jid, node, self._agent_info) + self._fetch_source = True + return + iter_ = self.model.iter_next(iter_) + self._fetch_source = None - def _channel_altinfo(self, jid, node, items, name = None): - """ - Callback for the alternate disco#items query. We try to atleast get the - amount of users in the room if the service does not support MUC dataforms - """ - if items == 0: - # The server returned an error - self._broken += 1 - if self._broken >= 3: - # Disable queries completely after 3 failures - if self.size_cbid: - self.window.services_scrollwin.disconnect(self.size_cbid) - self.size_cbid = None - if self.vadj_cbid: - self.vadj.disconnect(self.vadj_cbid) - self.vadj_cbid = None - self._fetch_source = None - return - else: - iter_ = self._find_item(jid, node) - if iter_: - if name: - self.model[iter_][2] = name - self.model[iter_][3] = len(items) # The number of users - self.model[iter_][4] = str(len(items)) # The number of users - self.model[iter_][6] = True - self._fetch_source = None - self._query_visible() + def _channel_altinfo(self, jid, node, items, name = None): + """ + Callback for the alternate disco#items query. We try to atleast get the + amount of users in the room if the service does not support MUC dataforms + """ + if items == 0: + # The server returned an error + self._broken += 1 + if self._broken >= 3: + # Disable queries completely after 3 failures + if self.size_cbid: + self.window.services_scrollwin.disconnect(self.size_cbid) + self.size_cbid = None + if self.vadj_cbid: + self.vadj.disconnect(self.vadj_cbid) + self.vadj_cbid = None + self._fetch_source = None + return + else: + iter_ = self._find_item(jid, node) + if iter_: + if name: + self.model[iter_][2] = name + self.model[iter_][3] = len(items) # The number of users + self.model[iter_][4] = str(len(items)) # The number of users + self.model[iter_][6] = True + self._fetch_source = None + self._query_visible() - def _add_item(self, jid, node, parent_node, item, force): - self.model.append((jid, node, item.get('name', ''), -1, '', '', False)) - if not self._fetch_source: - self._fetch_source = gobject.idle_add(self._start_info_query) + def _add_item(self, jid, node, parent_node, item, force): + self.model.append((jid, node, item.get('name', ''), -1, '', '', False)) + if not self._fetch_source: + self._fetch_source = gobject.idle_add(self._start_info_query) - def _update_info(self, iter_, jid, node, identities, features, data): - name = identities[0].get('name', '') - for form in data: - typefield = form.getField('FORM_TYPE') - if typefield and typefield.getValue() == \ - 'http://jabber.org/protocol/muc#roominfo': - # Fill model row from the form's fields - users = form.getField('muc#roominfo_occupants') - descr = form.getField('muc#roominfo_description') - if users: - self.model[iter_][3] = int(users.getValue()) - self.model[iter_][4] = users.getValue() - if descr: - self.model[iter_][5] = descr.getValue() - # Only set these when we find a form with additional info - # Some servers don't support forms and put extra info in - # the name attribute, so we preserve it in that case. - self.model[iter_][2] = name - self.model[iter_][6] = True - break - else: - # We didn't find a form, switch to alternate query mode - self.cache.get_items(jid, node, self._channel_altinfo, args = (name,)) - return - # Continue with the next - self._fetch_source = None - self._query_visible() + def _update_info(self, iter_, jid, node, identities, features, data): + name = identities[0].get('name', '') + for form in data: + typefield = form.getField('FORM_TYPE') + if typefield and typefield.getValue() == \ + 'http://jabber.org/protocol/muc#roominfo': + # Fill model row from the form's fields + users = form.getField('muc#roominfo_occupants') + descr = form.getField('muc#roominfo_description') + if users: + self.model[iter_][3] = int(users.getValue()) + self.model[iter_][4] = users.getValue() + if descr: + self.model[iter_][5] = descr.getValue() + # Only set these when we find a form with additional info + # Some servers don't support forms and put extra info in + # the name attribute, so we preserve it in that case. + self.model[iter_][2] = name + self.model[iter_][6] = True + break + else: + # We didn't find a form, switch to alternate query mode + self.cache.get_items(jid, node, self._channel_altinfo, args = (name,)) + return + # Continue with the next + self._fetch_source = None + self._query_visible() - def _update_error(self, iter_, jid, node): - # switch to alternate query mode - self.cache.get_items(jid, node, self._channel_altinfo) + def _update_error(self, iter_, jid, node): + # switch to alternate query mode + self.cache.get_items(jid, node, self._channel_altinfo) def PubSubBrowser(account, jid, node): - """ - Return an AgentBrowser subclass that will display service discovery for - particular pubsub service. Different pubsub services may need to present - different data during browsing - """ - # for now, only discussion groups are supported... - # TODO: check if it has appropriate features to be such kind of service - return DiscussionGroupsBrowser(account, jid, node) + """ + Return an AgentBrowser subclass that will display service discovery for + particular pubsub service. Different pubsub services may need to present + different data during browsing + """ + # for now, only discussion groups are supported... + # TODO: check if it has appropriate features to be such kind of service + return DiscussionGroupsBrowser(account, jid, node) class DiscussionGroupsBrowser(AgentBrowser): - """ - For browsing pubsub-based discussion groups service - """ + """ + For browsing pubsub-based discussion groups service + """ - def __init__(self, account, jid, node): - AgentBrowser.__init__(self, account, jid, node) + def __init__(self, account, jid, node): + AgentBrowser.__init__(self, account, jid, node) - # this will become set object when we get subscriptions; None means - # we don't know yet which groups are subscribed - self.subscriptions = None + # this will become set object when we get subscriptions; None means + # we don't know yet which groups are subscribed + self.subscriptions = None - # this will become our action widgets when we create them; None means - # we don't have them yet (needed for check in callback) - self.subscribe_button = None - self.unsubscribe_button = None + # this will become our action widgets when we create them; None means + # we don't have them yet (needed for check in callback) + self.subscribe_button = None + self.unsubscribe_button = None - gajim.connections[account].send_pb_subscription_query(jid, self._subscriptionsCB) + gajim.connections[account].send_pb_subscription_query(jid, self._subscriptionsCB) - def _create_treemodel(self): - """ - Create treemodel for the window - """ - # JID, node, name (with description) - pango markup, dont have info?, subscribed? - self.model = gtk.TreeStore(str, str, str, bool, bool) - # sort by name - self.model.set_sort_column_id(2, gtk.SORT_ASCENDING) - self.window.services_treeview.set_model(self.model) + def _create_treemodel(self): + """ + Create treemodel for the window + """ + # JID, node, name (with description) - pango markup, dont have info?, subscribed? + self.model = gtk.TreeStore(str, str, str, bool, bool) + # sort by name + self.model.set_sort_column_id(2, gtk.SORT_ASCENDING) + self.window.services_treeview.set_model(self.model) - # Name column - # Pango markup for name and description, description printed with - # font - renderer = gtk.CellRendererText() - col = gtk.TreeViewColumn(_('Name')) - col.pack_start(renderer) - col.set_attributes(renderer, markup=2) - col.set_resizable(True) - self.window.services_treeview.insert_column(col, -1) - self.window.services_treeview.set_headers_visible(True) + # Name column + # Pango markup for name and description, description printed with + # font + renderer = gtk.CellRendererText() + col = gtk.TreeViewColumn(_('Name')) + col.pack_start(renderer) + col.set_attributes(renderer, markup=2) + col.set_resizable(True) + self.window.services_treeview.insert_column(col, -1) + self.window.services_treeview.set_headers_visible(True) - # Subscription state - renderer = gtk.CellRendererToggle() - col = gtk.TreeViewColumn(_('Subscribed')) - col.pack_start(renderer) - col.set_attributes(renderer, inconsistent=3, active=4) - col.set_resizable(False) - self.window.services_treeview.insert_column(col, -1) + # Subscription state + renderer = gtk.CellRendererToggle() + col = gtk.TreeViewColumn(_('Subscribed')) + col.pack_start(renderer) + col.set_attributes(renderer, inconsistent=3, active=4) + col.set_resizable(False) + self.window.services_treeview.insert_column(col, -1) - # Node Column - renderer = gtk.CellRendererText() - col = gtk.TreeViewColumn(_('Node')) - col.pack_start(renderer) - col.set_attributes(renderer, markup=1) - col.set_resizable(True) - self.window.services_treeview.insert_column(col, -1) + # Node Column + renderer = gtk.CellRendererText() + col = gtk.TreeViewColumn(_('Node')) + col.pack_start(renderer) + col.set_attributes(renderer, markup=1) + col.set_resizable(True) + self.window.services_treeview.insert_column(col, -1) - def _add_items(self, jid, node, items, force): - for item in items: - jid_ = item['jid'] - node_ = item.get('node', '') - self._total_items += 1 - self._add_item(jid_, node_, node, item, force) + def _add_items(self, jid, node, items, force): + for item in items: + jid_ = item['jid'] + node_ = item.get('node', '') + self._total_items += 1 + self._add_item(jid_, node_, node, item, force) - def _in_list_foreach(self, model, path, iter_, node): - if model[path][1] == node: - self.in_list = True + def _in_list_foreach(self, model, path, iter_, node): + if model[path][1] == node: + self.in_list = True - def _in_list(self, node): - self.in_list = False - self.model.foreach(self._in_list_foreach, node) - return self.in_list + def _in_list(self, node): + self.in_list = False + self.model.foreach(self._in_list_foreach, node) + return self.in_list - def _add_item(self, jid, node, parent_node, item, force): - """ - Called when we got basic information about new node from query. Show the - item - """ - name = item.get('name', '') + def _add_item(self, jid, node, parent_node, item, force): + """ + Called when we got basic information about new node from query. Show the + item + """ + name = item.get('name', '') - if self.subscriptions is not None: - dunno = False - subscribed = node in self.subscriptions - else: - dunno = True - subscribed = False + if self.subscriptions is not None: + dunno = False + subscribed = node in self.subscriptions + else: + dunno = True + subscribed = False - name = gobject.markup_escape_text(name) - name = '%s' % name + name = gobject.markup_escape_text(name) + name = '%s' % name - parent_iter = self._get_iter(parent_node) - if not self._in_list(node): - self.model.append(parent_iter, (jid, node, name, dunno, subscribed)) - self.cache.get_items(jid, node, self._add_items, force = force, - args = (force,)) + parent_iter = self._get_iter(parent_node) + if not self._in_list(node): + self.model.append(parent_iter, (jid, node, name, dunno, subscribed)) + self.cache.get_items(jid, node, self._add_items, force = force, + args = (force,)) - def _get_child_iter(self, parent_iter, node): - child_iter = self.model.iter_children(parent_iter) - while child_iter: - if self.model[child_iter][1] == node: - return child_iter - child_iter = self.model.iter_next(child_iter) - return None + def _get_child_iter(self, parent_iter, node): + child_iter = self.model.iter_children(parent_iter) + while child_iter: + if self.model[child_iter][1] == node: + return child_iter + child_iter = self.model.iter_next(child_iter) + return None - def _get_iter(self, node): - ''' Look for an iter with the given node ''' - self.found_iter = None - def is_node(model, path, iter, node): - if model[iter][1] == node: - self.found_iter = iter - return True - self.model.foreach(is_node, node) - return self.found_iter + def _get_iter(self, node): + ''' Look for an iter with the given node ''' + self.found_iter = None + def is_node(model, path, iter, node): + if model[iter][1] == node: + self.found_iter = iter + return True + self.model.foreach(is_node, node) + return self.found_iter - def _add_actions(self): - self.post_button = gtk.Button(label=_('New post'), use_underline=True) - self.post_button.set_sensitive(False) - self.post_button.connect('clicked', self.on_post_button_clicked) - self.window.action_buttonbox.add(self.post_button) - self.post_button.show_all() + def _add_actions(self): + self.post_button = gtk.Button(label=_('New post'), use_underline=True) + self.post_button.set_sensitive(False) + self.post_button.connect('clicked', self.on_post_button_clicked) + self.window.action_buttonbox.add(self.post_button) + self.post_button.show_all() - self.subscribe_button = gtk.Button(label=_('_Subscribe'), use_underline=True) - self.subscribe_button.set_sensitive(False) - self.subscribe_button.connect('clicked', self.on_subscribe_button_clicked) - self.window.action_buttonbox.add(self.subscribe_button) - self.subscribe_button.show_all() + self.subscribe_button = gtk.Button(label=_('_Subscribe'), use_underline=True) + self.subscribe_button.set_sensitive(False) + self.subscribe_button.connect('clicked', self.on_subscribe_button_clicked) + self.window.action_buttonbox.add(self.subscribe_button) + self.subscribe_button.show_all() - self.unsubscribe_button = gtk.Button(label=_('_Unsubscribe'), use_underline=True) - self.unsubscribe_button.set_sensitive(False) - self.unsubscribe_button.connect('clicked', self.on_unsubscribe_button_clicked) - self.window.action_buttonbox.add(self.unsubscribe_button) - self.unsubscribe_button.show_all() + self.unsubscribe_button = gtk.Button(label=_('_Unsubscribe'), use_underline=True) + self.unsubscribe_button.set_sensitive(False) + self.unsubscribe_button.connect('clicked', self.on_unsubscribe_button_clicked) + self.window.action_buttonbox.add(self.unsubscribe_button) + self.unsubscribe_button.show_all() - def _clean_actions(self): - if self.post_button is not None: - self.post_button.destroy() - self.post_button = None + def _clean_actions(self): + if self.post_button is not None: + self.post_button.destroy() + self.post_button = None - if self.subscribe_button is not None: - self.subscribe_button.destroy() - self.subscribe_button = None + if self.subscribe_button is not None: + self.subscribe_button.destroy() + self.subscribe_button = None - if self.unsubscribe_button is not None: - self.unsubscribe_button.destroy() - self.unsubscribe_button = None + if self.unsubscribe_button is not None: + self.unsubscribe_button.destroy() + self.unsubscribe_button = None - def update_actions(self): - """ - Called when user selected a row. Make subscribe/unsubscribe buttons - sensitive appropriatelly - """ - # we have nothing to do if we don't have buttons... - if self.subscribe_button is None: return + def update_actions(self): + """ + Called when user selected a row. Make subscribe/unsubscribe buttons + sensitive appropriatelly + """ + # we have nothing to do if we don't have buttons... + if self.subscribe_button is None: return - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if not iter_ or self.subscriptions is None: - # no item selected or no subscriptions info, all buttons are insensitive - self.post_button.set_sensitive(False) - self.subscribe_button.set_sensitive(False) - self.unsubscribe_button.set_sensitive(False) - else: - subscribed = model.get_value(iter_, 4) # 4 = subscribed? - self.post_button.set_sensitive(subscribed) - self.subscribe_button.set_sensitive(not subscribed) - self.unsubscribe_button.set_sensitive(subscribed) + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if not iter_ or self.subscriptions is None: + # no item selected or no subscriptions info, all buttons are insensitive + self.post_button.set_sensitive(False) + self.subscribe_button.set_sensitive(False) + self.unsubscribe_button.set_sensitive(False) + else: + subscribed = model.get_value(iter_, 4) # 4 = subscribed? + self.post_button.set_sensitive(subscribed) + self.subscribe_button.set_sensitive(not subscribed) + self.unsubscribe_button.set_sensitive(subscribed) - def on_post_button_clicked(self, widget): - """ - Called when 'post' button is pressed. Open window to create post - """ - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if iter_ is None: return + def on_post_button_clicked(self, widget): + """ + Called when 'post' button is pressed. Open window to create post + """ + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if iter_ is None: return - groupnode = model.get_value(iter_, 1) # 1 = groupnode + groupnode = model.get_value(iter_, 1) # 1 = groupnode - groups.GroupsPostWindow(self.account, self.jid, groupnode) + groups.GroupsPostWindow(self.account, self.jid, groupnode) - def on_subscribe_button_clicked(self, widget): - """ - Called when 'subscribe' button is pressed. Send subscribtion request - """ - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if iter_ is None: return + def on_subscribe_button_clicked(self, widget): + """ + Called when 'subscribe' button is pressed. Send subscribtion request + """ + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if iter_ is None: return - groupnode = model.get_value(iter_, 1) # 1 = groupnode + groupnode = model.get_value(iter_, 1) # 1 = groupnode - gajim.connections[self.account].send_pb_subscribe(self.jid, groupnode, self._subscribeCB, groupnode) + gajim.connections[self.account].send_pb_subscribe(self.jid, groupnode, self._subscribeCB, groupnode) - def on_unsubscribe_button_clicked(self, widget): - """ - Called when 'unsubscribe' button is pressed. Send unsubscription request - """ - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if iter_ is None: return + def on_unsubscribe_button_clicked(self, widget): + """ + Called when 'unsubscribe' button is pressed. Send unsubscription request + """ + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if iter_ is None: return - groupnode = model.get_value(iter_, 1) # 1 = groupnode + groupnode = model.get_value(iter_, 1) # 1 = groupnode - gajim.connections[self.account].send_pb_unsubscribe(self.jid, groupnode, self._unsubscribeCB, groupnode) + gajim.connections[self.account].send_pb_unsubscribe(self.jid, groupnode, self._unsubscribeCB, groupnode) - def _subscriptionsCB(self, conn, request): - """ - We got the subscribed groups list stanza. Now, if we already have items - on the list, we should actualize them - """ - try: - subscriptions = request.getTag('pubsub').getTag('subscriptions') - except Exception: - return + def _subscriptionsCB(self, conn, request): + """ + We got the subscribed groups list stanza. Now, if we already have items + on the list, we should actualize them + """ + try: + subscriptions = request.getTag('pubsub').getTag('subscriptions') + except Exception: + return - groups = set() - for child in subscriptions.getTags('subscription'): - groups.add(child['node']) + groups = set() + for child in subscriptions.getTags('subscription'): + groups.add(child['node']) - self.subscriptions = groups + self.subscriptions = groups - # try to setup existing items in model - model = self.window.services_treeview.get_model() - for row in model: - # 1 = group node - # 3 = insensitive checkbox for subscribed - # 4 = subscribed? - groupnode = row[1] - row[3]=False - row[4]=groupnode in groups + # try to setup existing items in model + model = self.window.services_treeview.get_model() + for row in model: + # 1 = group node + # 3 = insensitive checkbox for subscribed + # 4 = subscribed? + groupnode = row[1] + row[3]=False + row[4]=groupnode in groups - # we now know subscriptions, update button states - self.update_actions() + # we now know subscriptions, update button states + self.update_actions() - raise xmpp.NodeProcessed + raise xmpp.NodeProcessed - def _subscribeCB(self, conn, request, groupnode): - """ - We have just subscribed to a node. Update UI - """ - self.subscriptions.add(groupnode) + def _subscribeCB(self, conn, request, groupnode): + """ + We have just subscribed to a node. Update UI + """ + self.subscriptions.add(groupnode) - model = self.window.services_treeview.get_model() - for row in model: - if row[1] == groupnode: # 1 = groupnode - row[4]=True - break + model = self.window.services_treeview.get_model() + for row in model: + if row[1] == groupnode: # 1 = groupnode + row[4]=True + break - self.update_actions() + self.update_actions() - raise xmpp.NodeProcessed + raise xmpp.NodeProcessed - def _unsubscribeCB(self, conn, request, groupnode): - """ - We have just unsubscribed from a node. Update UI - """ - self.subscriptions.remove(groupnode) + def _unsubscribeCB(self, conn, request, groupnode): + """ + We have just unsubscribed from a node. Update UI + """ + self.subscriptions.remove(groupnode) - model = self.window.services_treeview.get_model() - for row in model: - if row[1] == groupnode: # 1 = groupnode - row[4]=False - break + model = self.window.services_treeview.get_model() + for row in model: + if row[1] == groupnode: # 1 = groupnode + row[4]=False + break - self.update_actions() + self.update_actions() - raise xmpp.NodeProcessed + raise xmpp.NodeProcessed # Fill the global agent type info dictionary _agent_type_info = _gen_agent_type_info() - -# vim: se ts=3: diff --git a/src/features_window.py b/src/features_window.py index d784fc50a..cd4b1980d 100644 --- a/src/features_window.py +++ b/src/features_window.py @@ -33,232 +33,230 @@ from common import helpers from common import kwalletbinding class FeaturesWindow: - """ - Class for features window - """ + """ + Class for features window + """ - def __init__(self): - self.xml = gtkgui_helpers.get_glade('features_window.glade') - self.window = self.xml.get_widget('features_window') - treeview = self.xml.get_widget('features_treeview') - self.desc_label = self.xml.get_widget('feature_desc_label') + def __init__(self): + self.xml = gtkgui_helpers.get_glade('features_window.glade') + self.window = self.xml.get_widget('features_window') + treeview = self.xml.get_widget('features_treeview') + self.desc_label = self.xml.get_widget('feature_desc_label') - # {name: (available_function, unix_text, windows_text)} - self.features = { - _('SSL certificat validation'): (self.pyopenssl_available, - _('A library used to validate server certificates to ensure a secure connection.'), - _('Requires python-pyopenssl.'), - _('Requires python-pyopenssl.')), - _('Bonjour / Zeroconf'): (self.zeroconf_available, - _('Serverless chatting with autodetected clients in a local network.'), - _('Requires python-avahi.'), - _('Requires pybonjour (http://o2s.csail.mit.edu/o2s-wiki/pybonjour).')), - _('Command line'): (self.dbus_available, - _('A script to control Gajim via commandline.'), - _('Requires python-dbus.'), - _('Feature not available under Windows.')), - _('OpenGPG message encryption'): (self.gpg_available, - _('Encrypting chat messages with gpg keys.'), - _('Requires gpg and python-GnuPGInterface.'), - _('Feature not available under Windows.')), - _('Network-manager'): (self.network_manager_available, - _('Autodetection of network status.'), - _('Requires gnome-network-manager and python-dbus.'), - _('Feature not available under Windows.')), - _('Session Management'): (self.session_management_available, - _('Gajim session is stored on logout and restored on login.'), - _('Requires python-gnome2.'), - _('Feature not available under Windows.')), - _('Password encryption'): (self.some_keyring_available, - _('Passwords can be stored securely and not just in plaintext.'), - _('Requires gnome-keyring and python-gnome2-desktop, or kwalletcli.'), - _('Feature not available under Windows.')), - _('SRV'): (self.srv_available, - _('Ability to connect to servers which are using SRV records.'), - _('Requires dnsutils.'), - _('Requires nslookup to use SRV records.')), - _('Spell Checker'): (self.speller_available, - _('Spellchecking of composed messages.'), - _('Requires libgtkspell.'), - _('Feature not available under Windows.')), - _('Notification'): (self.notification_available, - _('Passive popups notifying for new events.'), - _('Requires python-notify or instead python-dbus in conjunction with notification-daemon.'), - _('Feature not available under Windows.')), - _('Automatic status'): (self.idle_available, - _('Ability to measure idle time, in order to set auto status.'), - _('Requires libxss library.'), - _('Requires python2.5.')), - _('LaTeX'): (self.latex_available, - _('Transform LaTeX expressions between $$ $$.'), - _('Requires texlive-latex-base and dvipng. You have to set \'use_latex\' to True in the Advanced Configuration Editor.'), - _('Requires texlive-latex-base and dvipng (All is in MikTeX). You have to set \'use_latex\' to True in the Advanced Configuration Editor.')), - _('End to End message encryption'): (self.pycrypto_available, - _('Encrypting chat messages.'), - _('Requires python-crypto.'), - _('Requires python-crypto.')), - _('RST Generator'): (self.docutils_available, - _('Generate XHTML output from RST code (see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html).'), - _('Requires python-docutils.'), - _('Requires python-docutils.')), - _('Banners and clickable links'): (self.pysexy_available, - _('Ability to have clickable URLs in chat and groupchat window banners.'), - _('Requires python-sexy.'), - _('Requires python-sexy.')), - _('Audio / Video'): (self.farsight_available, - _('Ability to start audio and video chat.'), - _('Requires python-farsight.'), - _('Feature not available under Windows.')), - } + # {name: (available_function, unix_text, windows_text)} + self.features = { + _('SSL certificat validation'): (self.pyopenssl_available, + _('A library used to validate server certificates to ensure a secure connection.'), + _('Requires python-pyopenssl.'), + _('Requires python-pyopenssl.')), + _('Bonjour / Zeroconf'): (self.zeroconf_available, + _('Serverless chatting with autodetected clients in a local network.'), + _('Requires python-avahi.'), + _('Requires pybonjour (http://o2s.csail.mit.edu/o2s-wiki/pybonjour).')), + _('Command line'): (self.dbus_available, + _('A script to control Gajim via commandline.'), + _('Requires python-dbus.'), + _('Feature not available under Windows.')), + _('OpenGPG message encryption'): (self.gpg_available, + _('Encrypting chat messages with gpg keys.'), + _('Requires gpg and python-GnuPGInterface.'), + _('Feature not available under Windows.')), + _('Network-manager'): (self.network_manager_available, + _('Autodetection of network status.'), + _('Requires gnome-network-manager and python-dbus.'), + _('Feature not available under Windows.')), + _('Session Management'): (self.session_management_available, + _('Gajim session is stored on logout and restored on login.'), + _('Requires python-gnome2.'), + _('Feature not available under Windows.')), + _('Password encryption'): (self.some_keyring_available, + _('Passwords can be stored securely and not just in plaintext.'), + _('Requires gnome-keyring and python-gnome2-desktop, or kwalletcli.'), + _('Feature not available under Windows.')), + _('SRV'): (self.srv_available, + _('Ability to connect to servers which are using SRV records.'), + _('Requires dnsutils.'), + _('Requires nslookup to use SRV records.')), + _('Spell Checker'): (self.speller_available, + _('Spellchecking of composed messages.'), + _('Requires libgtkspell.'), + _('Feature not available under Windows.')), + _('Notification'): (self.notification_available, + _('Passive popups notifying for new events.'), + _('Requires python-notify or instead python-dbus in conjunction with notification-daemon.'), + _('Feature not available under Windows.')), + _('Automatic status'): (self.idle_available, + _('Ability to measure idle time, in order to set auto status.'), + _('Requires libxss library.'), + _('Requires python2.5.')), + _('LaTeX'): (self.latex_available, + _('Transform LaTeX expressions between $$ $$.'), + _('Requires texlive-latex-base and dvipng. You have to set \'use_latex\' to True in the Advanced Configuration Editor.'), + _('Requires texlive-latex-base and dvipng (All is in MikTeX). You have to set \'use_latex\' to True in the Advanced Configuration Editor.')), + _('End to End message encryption'): (self.pycrypto_available, + _('Encrypting chat messages.'), + _('Requires python-crypto.'), + _('Requires python-crypto.')), + _('RST Generator'): (self.docutils_available, + _('Generate XHTML output from RST code (see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html).'), + _('Requires python-docutils.'), + _('Requires python-docutils.')), + _('Banners and clickable links'): (self.pysexy_available, + _('Ability to have clickable URLs in chat and groupchat window banners.'), + _('Requires python-sexy.'), + _('Requires python-sexy.')), + _('Audio / Video'): (self.farsight_available, + _('Ability to start audio and video chat.'), + _('Requires python-farsight.'), + _('Feature not available under Windows.')), + } - # name, supported - self.model = gtk.ListStore(str, bool) - treeview.set_model(self.model) + # name, supported + self.model = gtk.ListStore(str, bool) + treeview.set_model(self.model) - col = gtk.TreeViewColumn(_('Available')) - treeview.append_column(col) - cell = gtk.CellRendererToggle() - cell.set_property('radio', True) - col.pack_start(cell) - col.set_attributes(cell, active = 1) + col = gtk.TreeViewColumn(_('Available')) + treeview.append_column(col) + cell = gtk.CellRendererToggle() + cell.set_property('radio', True) + col.pack_start(cell) + col.set_attributes(cell, active = 1) - col = gtk.TreeViewColumn(_('Feature')) - treeview.append_column(col) - cell = gtk.CellRendererText() - col.pack_start(cell, expand = True) - col.add_attribute(cell, 'text', 0) + col = gtk.TreeViewColumn(_('Feature')) + treeview.append_column(col) + cell = gtk.CellRendererText() + col.pack_start(cell, expand = True) + col.add_attribute(cell, 'text', 0) - # Fill model - for feature in self.features: - func = self.features[feature][0] - rep = func() - self.model.append([feature, rep]) + # Fill model + for feature in self.features: + func = self.features[feature][0] + rep = func() + self.model.append([feature, rep]) - self.model.set_sort_column_id(0, gtk.SORT_ASCENDING) + self.model.set_sort_column_id(0, gtk.SORT_ASCENDING) - self.xml.signal_autoconnect(self) - self.window.show_all() - self.xml.get_widget('close_button').grab_focus() + self.xml.signal_autoconnect(self) + self.window.show_all() + self.xml.get_widget('close_button').grab_focus() - def on_close_button_clicked(self, widget): - self.window.destroy() + def on_close_button_clicked(self, widget): + self.window.destroy() - def on_features_treeview_cursor_changed(self, widget): - selection = widget.get_selection() - if not selection: - return - rows = selection.get_selected_rows()[1] - if not rows: - return - path = rows[0] - feature = self.model[path][0].decode('utf-8') - text = self.features[feature][1] + '\n' - if os.name == 'nt': - text = text + self.features[feature][3] - else: - text = text + self.features[feature][2] - self.desc_label.set_text(text) + def on_features_treeview_cursor_changed(self, widget): + selection = widget.get_selection() + if not selection: + return + rows = selection.get_selected_rows()[1] + if not rows: + return + path = rows[0] + feature = self.model[path][0].decode('utf-8') + text = self.features[feature][1] + '\n' + if os.name == 'nt': + text = text + self.features[feature][3] + else: + text = text + self.features[feature][2] + self.desc_label.set_text(text) - def pyopenssl_available(self): - try: - import OpenSSL.SSL - import OpenSSL.crypto - except Exception: - return False - return True + def pyopenssl_available(self): + try: + import OpenSSL.SSL + import OpenSSL.crypto + except Exception: + return False + return True - def zeroconf_available(self): - try: - import avahi - except Exception: - try: - import pybonjour - except Exception: - return False - return True + def zeroconf_available(self): + try: + import avahi + except Exception: + try: + import pybonjour + except Exception: + return False + return True - def dbus_available(self): - if os.name == 'nt': - return False - from common import dbus_support - return dbus_support.supported + def dbus_available(self): + if os.name == 'nt': + return False + from common import dbus_support + return dbus_support.supported - def gpg_available(self): - if os.name == 'nt': - return False - return gajim.HAVE_GPG + def gpg_available(self): + if os.name == 'nt': + return False + return gajim.HAVE_GPG - def network_manager_available(self): - if os.name == 'nt': - return False - import network_manager_listener - return network_manager_listener.supported + def network_manager_available(self): + if os.name == 'nt': + return False + import network_manager_listener + return network_manager_listener.supported - def session_management_available(self): - if os.name == 'nt': - return False - try: - import gnome.ui - except Exception: - return False - return True + def session_management_available(self): + if os.name == 'nt': + return False + try: + import gnome.ui + except Exception: + return False + return True - def some_keyring_available(self): - if os.name == 'nt': - return False - if kwalletbinding.kwallet_available(): - return True - try: - import gnomekeyring - except Exception: - return False - return True + def some_keyring_available(self): + if os.name == 'nt': + return False + if kwalletbinding.kwallet_available(): + return True + try: + import gnomekeyring + except Exception: + return False + return True - def srv_available(self): - return helpers.is_in_path('nslookup') + def srv_available(self): + return helpers.is_in_path('nslookup') - def speller_available(self): - if os.name == 'nt': - return False - try: - import gtkspell - except ImportError: - return False - return True + def speller_available(self): + if os.name == 'nt': + return False + try: + import gtkspell + except ImportError: + return False + return True - def notification_available(self): - if os.name == 'nt': - return False - from common import dbus_support - if self.dbus_available() and dbus_support.get_notifications_interface(): - return True - try: - import pynotify - except Exception: - return False - return True + def notification_available(self): + if os.name == 'nt': + return False + from common import dbus_support + if self.dbus_available() and dbus_support.get_notifications_interface(): + return True + try: + import pynotify + except Exception: + return False + return True - def idle_available(self): - from common import sleepy - return sleepy.SUPPORTED + def idle_available(self): + from common import sleepy + return sleepy.SUPPORTED - def latex_available(self): - return gajim.HAVE_LATEX + def latex_available(self): + return gajim.HAVE_LATEX - def pycrypto_available(self): - return gajim.HAVE_PYCRYPTO + def pycrypto_available(self): + return gajim.HAVE_PYCRYPTO - def docutils_available(self): - try: - import docutils - except Exception: - return False - return True + def docutils_available(self): + try: + import docutils + except Exception: + return False + return True - def pysexy_available(self): - return gajim.HAVE_PYSEXY + def pysexy_available(self): + return gajim.HAVE_PYSEXY - def farsight_available(self): - return gajim.HAVE_FARSIGHT - -# vim: se ts=3: + def farsight_available(self): + return gajim.HAVE_FARSIGHT diff --git a/src/filetransfers_window.py b/src/filetransfers_window.py index d3d23a156..6ae056e0f 100644 --- a/src/filetransfers_window.py +++ b/src/filetransfers_window.py @@ -44,953 +44,951 @@ C_SID = 6 class FileTransfersWindow: - def __init__(self): - self.files_props = {'r' : {}, 's': {}} - self.height_diff = 0 - self.xml = gtkgui_helpers.get_glade('filetransfers.glade') - self.window = self.xml.get_widget('file_transfers_window') - self.tree = self.xml.get_widget('transfers_list') - self.cancel_button = self.xml.get_widget('cancel_button') - self.pause_button = self.xml.get_widget('pause_restore_button') - self.cleanup_button = self.xml.get_widget('cleanup_button') - self.notify_ft_checkbox = self.xml.get_widget( - 'notify_ft_complete_checkbox') - notify = gajim.config.get('notify_on_file_complete') - if notify: - self.notify_ft_checkbox.set_active(True) - else: - self.notify_ft_checkbox.set_active(False) - self.model = gtk.ListStore(gtk.gdk.Pixbuf, str, str, str, str, int, str) - self.tree.set_model(self.model) - col = gtk.TreeViewColumn() + def __init__(self): + self.files_props = {'r' : {}, 's': {}} + self.height_diff = 0 + self.xml = gtkgui_helpers.get_glade('filetransfers.glade') + self.window = self.xml.get_widget('file_transfers_window') + self.tree = self.xml.get_widget('transfers_list') + self.cancel_button = self.xml.get_widget('cancel_button') + self.pause_button = self.xml.get_widget('pause_restore_button') + self.cleanup_button = self.xml.get_widget('cleanup_button') + self.notify_ft_checkbox = self.xml.get_widget( + 'notify_ft_complete_checkbox') + notify = gajim.config.get('notify_on_file_complete') + if notify: + self.notify_ft_checkbox.set_active(True) + else: + self.notify_ft_checkbox.set_active(False) + self.model = gtk.ListStore(gtk.gdk.Pixbuf, str, str, str, str, int, str) + self.tree.set_model(self.model) + col = gtk.TreeViewColumn() - render_pixbuf = gtk.CellRendererPixbuf() + render_pixbuf = gtk.CellRendererPixbuf() - col.pack_start(render_pixbuf, expand = True) - render_pixbuf.set_property('xpad', 3) - render_pixbuf.set_property('ypad', 3) - render_pixbuf.set_property('yalign', .0) - col.add_attribute(render_pixbuf, 'pixbuf', 0) - self.tree.append_column(col) + col.pack_start(render_pixbuf, expand = True) + render_pixbuf.set_property('xpad', 3) + render_pixbuf.set_property('ypad', 3) + render_pixbuf.set_property('yalign', .0) + col.add_attribute(render_pixbuf, 'pixbuf', 0) + self.tree.append_column(col) - col = gtk.TreeViewColumn(_('File')) - renderer = gtk.CellRendererText() - col.pack_start(renderer, expand=False) - col.add_attribute(renderer, 'markup' , C_LABELS) - renderer.set_property('yalign', 0.) - renderer = gtk.CellRendererText() - col.pack_start(renderer, expand=True) - col.add_attribute(renderer, 'markup' , C_FILE) - renderer.set_property('xalign', 0.) - renderer.set_property('yalign', 0.) - renderer.set_property('ellipsize', pango.ELLIPSIZE_END) - col.set_resizable(True) - col.set_expand(True) - self.tree.append_column(col) + col = gtk.TreeViewColumn(_('File')) + renderer = gtk.CellRendererText() + col.pack_start(renderer, expand=False) + col.add_attribute(renderer, 'markup', C_LABELS) + renderer.set_property('yalign', 0.) + renderer = gtk.CellRendererText() + col.pack_start(renderer, expand=True) + col.add_attribute(renderer, 'markup', C_FILE) + renderer.set_property('xalign', 0.) + renderer.set_property('yalign', 0.) + renderer.set_property('ellipsize', pango.ELLIPSIZE_END) + col.set_resizable(True) + col.set_expand(True) + self.tree.append_column(col) - col = gtk.TreeViewColumn(_('Time')) - renderer = gtk.CellRendererText() - col.pack_start(renderer, expand=False) - col.add_attribute(renderer, 'markup' , C_TIME) - renderer.set_property('yalign', 0.5) - renderer.set_property('xalign', 0.5) - renderer = gtk.CellRendererText() - renderer.set_property('ellipsize', pango.ELLIPSIZE_END) - col.set_resizable(True) - col.set_expand(False) - self.tree.append_column(col) + col = gtk.TreeViewColumn(_('Time')) + renderer = gtk.CellRendererText() + col.pack_start(renderer, expand=False) + col.add_attribute(renderer, 'markup', C_TIME) + renderer.set_property('yalign', 0.5) + renderer.set_property('xalign', 0.5) + renderer = gtk.CellRendererText() + renderer.set_property('ellipsize', pango.ELLIPSIZE_END) + col.set_resizable(True) + col.set_expand(False) + self.tree.append_column(col) - col = gtk.TreeViewColumn(_('Progress')) - renderer = gtk.CellRendererProgress() - renderer.set_property('yalign', 0.5) - renderer.set_property('xalign', 0.5) - col.pack_start(renderer, expand = False) - col.add_attribute(renderer, 'text' , C_PROGRESS) - col.add_attribute(renderer, 'value' , C_PERCENT) - col.set_resizable(True) - col.set_expand(False) - self.tree.append_column(col) + col = gtk.TreeViewColumn(_('Progress')) + renderer = gtk.CellRendererProgress() + renderer.set_property('yalign', 0.5) + renderer.set_property('xalign', 0.5) + col.pack_start(renderer, expand = False) + col.add_attribute(renderer, 'text', C_PROGRESS) + col.add_attribute(renderer, 'value', C_PERCENT) + col.set_resizable(True) + col.set_expand(False) + self.tree.append_column(col) - self.images = {} - self.icons = { - 'upload': gtk.STOCK_GO_UP, - 'download': gtk.STOCK_GO_DOWN, - 'stop': gtk.STOCK_STOP, - 'waiting': gtk.STOCK_REFRESH, - 'pause': gtk.STOCK_MEDIA_PAUSE, - 'continue': gtk.STOCK_MEDIA_PLAY, - 'ok': gtk.STOCK_APPLY, - } + self.images = {} + self.icons = { + 'upload': gtk.STOCK_GO_UP, + 'download': gtk.STOCK_GO_DOWN, + 'stop': gtk.STOCK_STOP, + 'waiting': gtk.STOCK_REFRESH, + 'pause': gtk.STOCK_MEDIA_PAUSE, + 'continue': gtk.STOCK_MEDIA_PLAY, + 'ok': gtk.STOCK_APPLY, + } - self.tree.get_selection().set_mode(gtk.SELECTION_SINGLE) - self.tree.get_selection().connect('changed', self.selection_changed) - self.tooltip = tooltips.FileTransfersTooltip() - self.file_transfers_menu = self.xml.get_widget('file_transfers_menu') - self.open_folder_menuitem = self.xml.get_widget('open_folder_menuitem') - self.cancel_menuitem = self.xml.get_widget('cancel_menuitem') - self.pause_menuitem = self.xml.get_widget('pause_menuitem') - self.continue_menuitem = self.xml.get_widget('continue_menuitem') - self.remove_menuitem = self.xml.get_widget('remove_menuitem') - self.xml.signal_autoconnect(self) + self.tree.get_selection().set_mode(gtk.SELECTION_SINGLE) + self.tree.get_selection().connect('changed', self.selection_changed) + self.tooltip = tooltips.FileTransfersTooltip() + self.file_transfers_menu = self.xml.get_widget('file_transfers_menu') + self.open_folder_menuitem = self.xml.get_widget('open_folder_menuitem') + self.cancel_menuitem = self.xml.get_widget('cancel_menuitem') + self.pause_menuitem = self.xml.get_widget('pause_menuitem') + self.continue_menuitem = self.xml.get_widget('continue_menuitem') + self.remove_menuitem = self.xml.get_widget('remove_menuitem') + self.xml.signal_autoconnect(self) - def find_transfer_by_jid(self, account, jid): - """ - Find all transfers with peer 'jid' that belong to 'account' - """ - active_transfers = [[],[]] # ['senders', 'receivers'] + def find_transfer_by_jid(self, account, jid): + """ + Find all transfers with peer 'jid' that belong to 'account' + """ + active_transfers = [[], []] # ['senders', 'receivers'] - # 'account' is the sender - for file_props in self.files_props['s'].values(): - if file_props['tt_account'] == account: - receiver_jid = unicode(file_props['receiver']).split('/')[0] - if jid == receiver_jid: - if not self.is_transfer_stopped(file_props): - active_transfers[0].append(file_props) + # 'account' is the sender + for file_props in self.files_props['s'].values(): + if file_props['tt_account'] == account: + receiver_jid = unicode(file_props['receiver']).split('/')[0] + if jid == receiver_jid: + if not self.is_transfer_stopped(file_props): + active_transfers[0].append(file_props) - # 'account' is the recipient - for file_props in self.files_props['r'].values(): - if file_props['tt_account'] == account: - sender_jid = unicode(file_props['sender']).split('/')[0] - if jid == sender_jid: - if not self.is_transfer_stopped(file_props): - active_transfers[1].append(file_props) - return active_transfers + # 'account' is the recipient + for file_props in self.files_props['r'].values(): + if file_props['tt_account'] == account: + sender_jid = unicode(file_props['sender']).split('/')[0] + if jid == sender_jid: + if not self.is_transfer_stopped(file_props): + active_transfers[1].append(file_props) + return active_transfers - def show_completed(self, jid, file_props): - """ - Show a dialog saying that file (file_props) has been transferred - """ - def on_open(widget, file_props): - dialog.destroy() - if 'file-name' not in file_props: - return - path = os.path.split(file_props['file-name'])[0] - if os.path.exists(path) and os.path.isdir(path): - helpers.launch_file_manager(path) - self.tree.get_selection().unselect_all() + def show_completed(self, jid, file_props): + """ + Show a dialog saying that file (file_props) has been transferred + """ + def on_open(widget, file_props): + dialog.destroy() + if 'file-name' not in file_props: + return + path = os.path.split(file_props['file-name'])[0] + if os.path.exists(path) and os.path.isdir(path): + helpers.launch_file_manager(path) + self.tree.get_selection().unselect_all() - if file_props['type'] == 'r': - # file path is used below in 'Save in' - (file_path, file_name) = os.path.split(file_props['file-name']) - else: - file_name = file_props['name'] - sectext = '\t' + _('Filename: %s') % file_name - sectext += '\n\t' + _('Size: %s') % \ - helpers.convert_bytes(file_props['size']) - if file_props['type'] == 'r': - jid = unicode(file_props['sender']).split('/')[0] - sender_name = gajim.contacts.get_first_contact_from_jid( - file_props['tt_account'], jid).get_shown_name() - sender = sender_name - else: - #You is a reply of who sent a file - sender = _('You') - sectext += '\n\t' +_('Sender: %s') % sender - sectext += '\n\t' +_('Recipient: ') - if file_props['type'] == 's': - jid = unicode(file_props['receiver']).split('/')[0] - receiver_name = gajim.contacts.get_first_contact_from_jid( - file_props['tt_account'], jid).get_shown_name() - recipient = receiver_name - else: - #You is a reply of who received a file - recipient = _('You') - sectext += recipient - if file_props['type'] == 'r': - sectext += '\n\t' +_('Saved in: %s') % file_path - dialog = dialogs.HigDialog(None, gtk.MESSAGE_INFO, gtk.BUTTONS_NONE, - _('File transfer completed'), sectext) - if file_props['type'] == 'r': - button = gtk.Button(_('_Open Containing Folder')) - button.connect('clicked', on_open, file_props) - dialog.action_area.pack_start(button) - ok_button = dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) - def on_ok(widget): - dialog.destroy() - ok_button.connect('clicked', on_ok) - dialog.show_all() + if file_props['type'] == 'r': + # file path is used below in 'Save in' + (file_path, file_name) = os.path.split(file_props['file-name']) + else: + file_name = file_props['name'] + sectext = '\t' + _('Filename: %s') % file_name + sectext += '\n\t' + _('Size: %s') % \ + helpers.convert_bytes(file_props['size']) + if file_props['type'] == 'r': + jid = unicode(file_props['sender']).split('/')[0] + sender_name = gajim.contacts.get_first_contact_from_jid( + file_props['tt_account'], jid).get_shown_name() + sender = sender_name + else: + #You is a reply of who sent a file + sender = _('You') + sectext += '\n\t' +_('Sender: %s') % sender + sectext += '\n\t' +_('Recipient: ') + if file_props['type'] == 's': + jid = unicode(file_props['receiver']).split('/')[0] + receiver_name = gajim.contacts.get_first_contact_from_jid( + file_props['tt_account'], jid).get_shown_name() + recipient = receiver_name + else: + #You is a reply of who received a file + recipient = _('You') + sectext += recipient + if file_props['type'] == 'r': + sectext += '\n\t' +_('Saved in: %s') % file_path + dialog = dialogs.HigDialog(None, gtk.MESSAGE_INFO, gtk.BUTTONS_NONE, + _('File transfer completed'), sectext) + if file_props['type'] == 'r': + button = gtk.Button(_('_Open Containing Folder')) + button.connect('clicked', on_open, file_props) + dialog.action_area.pack_start(button) + ok_button = dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) + def on_ok(widget): + dialog.destroy() + ok_button.connect('clicked', on_ok) + dialog.show_all() - def show_request_error(self, file_props): - """ - Show error dialog to the recipient saying that transfer has been canceled - """ - dialogs.InformationDialog(_('File transfer cancelled'), _('Connection with peer cannot be established.')) - self.tree.get_selection().unselect_all() + def show_request_error(self, file_props): + """ + Show error dialog to the recipient saying that transfer has been canceled + """ + dialogs.InformationDialog(_('File transfer cancelled'), _('Connection with peer cannot be established.')) + self.tree.get_selection().unselect_all() - def show_send_error(self, file_props): - """ - Show error dialog to the sender saying that transfer has been canceled - """ - dialogs.InformationDialog(_('File transfer cancelled'), + def show_send_error(self, file_props): + """ + Show error dialog to the sender saying that transfer has been canceled + """ + dialogs.InformationDialog(_('File transfer cancelled'), _('Connection with peer cannot be established.')) - self.tree.get_selection().unselect_all() - - def show_stopped(self, jid, file_props, error_msg = ''): - if file_props['type'] == 'r': - file_name = os.path.basename(file_props['file-name']) - else: - file_name = file_props['name'] - sectext = '\t' + _('Filename: %s') % file_name - sectext += '\n\t' + _('Recipient: %s') % jid - if error_msg: - sectext += '\n\t' + _('Error message: %s') % error_msg - dialogs.ErrorDialog(_('File transfer stopped'), sectext) - self.tree.get_selection().unselect_all() - - def show_file_send_request(self, account, contact): - - desc_entry = gtk.Entry() - - def on_ok(widget): - file_dir = None - files_path_list = dialog.get_filenames() - files_path_list = gtkgui_helpers.decode_filechooser_file_paths( - files_path_list) - desc = desc_entry.get_text() - for file_path in files_path_list: - if self.send_file(account, contact, file_path, desc) and file_dir is None: - file_dir = os.path.dirname(file_path) - if file_dir: - gajim.config.set('last_send_dir', file_dir) - dialog.destroy() - - dialog = dialogs.FileChooserDialog(_('Choose File to Send...'), - gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), - gtk.RESPONSE_OK, - True, # select multiple true as we can select many files to send - gajim.config.get('last_send_dir'), - on_response_ok = on_ok, - on_response_cancel = lambda e:dialog.destroy() - ) - - btn = gtk.Button(_('_Send')) - btn.set_property('can-default', True) - # FIXME: add send icon to this button (JUMP_TO) - dialog.add_action_widget(btn, gtk.RESPONSE_OK) - dialog.set_default_response(gtk.RESPONSE_OK) - - desc_hbox = gtk.HBox(False, 5) - desc_hbox.pack_start(gtk.Label(_('Description: ')),False,False,0) - desc_hbox.pack_start(desc_entry,True,True,0) - - dialog.vbox.pack_start(desc_hbox, False, False, 0) - - btn.show() - desc_hbox.show_all() - - def send_file(self, account, contact, file_path, file_desc=''): - """ - Start the real transfer(upload) of the file - """ - if gtkgui_helpers.file_is_locked(file_path): - pritext = _('Gajim cannot access this file') - sextext = _('This file is being used by another process.') - dialogs.ErrorDialog(pritext, sextext) - return - - if isinstance(contact, str): - if contact.find('/') == -1: - return - (jid, resource) = contact.split('/', 1) - contact = gajim.contacts.create_contact(jid=jid, account=account, resource=resource) - file_name = os.path.split(file_path)[1] - file_props = self.get_send_file_props(account, contact, - file_path, file_name, file_desc) - if file_props is None: - return False - self.add_transfer(account, contact, file_props) - gajim.connections[account].send_file_request(file_props) - return True - - def _start_receive(self, file_path, account, contact, file_props): - file_dir = os.path.dirname(file_path) - if file_dir: - gajim.config.set('last_save_dir', file_dir) - file_props['file-name'] = file_path - self.add_transfer(account, contact, file_props) - gajim.connections[account].send_file_approval(file_props) - - def show_file_request(self, account, contact, file_props): - """ - Show dialog asking for comfirmation and store location of new file - requested by a contact - """ - if file_props is None or 'name' not in file_props: - return - sec_text = '\t' + _('File: %s') % gobject.markup_escape_text( - file_props['name']) - if 'size' in file_props: - sec_text += '\n\t' + _('Size: %s') % \ - helpers.convert_bytes(file_props['size']) - if 'mime-type' in file_props: - sec_text += '\n\t' + _('Type: %s') % file_props['mime-type'] - if 'desc' in file_props: - sec_text += '\n\t' + _('Description: %s') % file_props['desc'] - prim_text = _('%s wants to send you a file:') % contact.jid - dialog = None - - def on_response_ok(account, contact, file_props): - - def on_ok(widget, account, contact, file_props): - file_path = dialog2.get_filename() - file_path = gtkgui_helpers.decode_filechooser_file_paths( - (file_path,))[0] - if os.path.exists(file_path): - # check if we have write permissions - if not os.access(file_path, os.W_OK): - file_name = os.path.basename(file_path) - dialogs.ErrorDialog(_('Cannot overwrite existing file "%s"' % file_name), - _('A file with this name already exists and you do not have permission to overwrite it.')) - return - stat = os.stat(file_path) - dl_size = stat.st_size - file_size = file_props['size'] - dl_finished = dl_size >= file_size - - def on_response(response): - if response < 0: - return - elif response == 100: - file_props['offset'] = dl_size - dialog2.destroy() - self._start_receive(file_path, account, contact, file_props) - - dialog = dialogs.FTOverwriteConfirmationDialog( - _('This file already exists'), _('What do you want to do?'), - propose_resume=not dl_finished, on_response=on_response) - dialog.set_transient_for(dialog2) - dialog.set_destroy_with_parent(True) - return - else: - dirname = os.path.dirname(file_path) - if not os.access(dirname, os.W_OK) and os.name != 'nt': - # read-only bit is used to mark special folder under windows, - # not to mark that a folder is read-only. See ticket #3587 - dialogs.ErrorDialog(_('Directory "%s" is not writable') % dirname, _('You do not have permission to create files in this directory.')) - return - dialog2.destroy() - self._start_receive(file_path, account, contact, file_props) - - def on_cancel(widget, account, contact, file_props): - dialog2.destroy() - gajim.connections[account].send_file_rejection(file_props) - - dialog2 = dialogs.FileChooserDialog( - title_text = _('Save File as...'), - action = gtk.FILE_CHOOSER_ACTION_SAVE, - buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_SAVE, gtk.RESPONSE_OK), - default_response = gtk.RESPONSE_OK, - current_folder = gajim.config.get('last_save_dir'), - on_response_ok = (on_ok, account, contact, file_props), - on_response_cancel = (on_cancel, account, contact, file_props)) - - dialog2.set_current_name(file_props['name']) - dialog2.connect('delete-event', lambda widget, event: - on_cancel(widget, account, contact, file_props)) - - def on_response_cancel(account, file_props): - gajim.connections[account].send_file_rejection(file_props) - - dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text, - on_response_ok = (on_response_ok, account, contact, file_props), - on_response_cancel = (on_response_cancel, account, file_props)) - dialog.connect('delete-event', lambda widget, event: - on_response_cancel(widget, account, file_props)) - dialog.popup() - - def get_icon(self, ident): - return self.images.setdefault(ident, - self.window.render_icon(self.icons[ident], gtk.ICON_SIZE_MENU)) - - def set_status(self, typ, sid, status): - """ - Change the status of a transfer to state 'status' - """ - iter_ = self.get_iter_by_sid(typ, sid) - if iter_ is None: - return - sid = self.model[iter_][C_SID].decode('utf-8') - file_props = self.files_props[sid[0]][sid[1:]] - if status == 'stop': - file_props['stopped'] = True - elif status == 'ok': - file_props['completed'] = True - self.model.set(iter_, C_IMAGE, self.get_icon(status)) - path = self.model.get_path(iter_) - self.select_func(path) - - def _format_percent(self, percent): - """ - Add extra spaces from both sides of the percent, so that progress string - has always a fixed size - """ - _str = ' ' - if percent != 100.: - _str += ' ' - if percent < 10: - _str += ' ' - _str += unicode(percent) + '% \n' - return _str - - def _format_time(self, _time): - times = { 'hours': 0, 'minutes': 0, 'seconds': 0 } - _time = int(_time) - times['seconds'] = _time % 60 - if _time >= 60: - _time /= 60 - times['minutes'] = _time % 60 - if _time >= 60: - times['hours'] = _time / 60 - - #Print remaining time in format 00:00:00 - #You can change the places of (hours), (minutes), (seconds) - - #they are not translatable. - return _('%(hours)02.d:%(minutes)02.d:%(seconds)02.d') % times - - def _get_eta_and_speed(self, full_size, transfered_size, file_props): - if len(file_props['transfered_size']) == 0: - return 0., 0. - elif len(file_props['transfered_size']) == 1: - speed = round(float(transfered_size) / file_props['elapsed-time']) - else: - # first and last are (time, transfered_size) - first = file_props['transfered_size'][0] - last = file_props['transfered_size'][-1] - transfered = last[1] - first[1] - tim = last[0] - first[0] - if tim == 0: - return 0., 0. - speed = round(float(transfered) / tim) - if speed == 0.: - return 0., 0. - remaining_size = full_size - transfered_size - eta = remaining_size / speed - return eta, speed - - def _remove_transfer(self, iter_, sid, file_props): - self.model.remove(iter_) - if 'tt_account' in file_props: - # file transfer is set - account = file_props['tt_account'] - if account in gajim.connections: - # there is a connection to the account - gajim.connections[account].remove_transfer(file_props) - if file_props['type'] == 'r': # we receive a file - other = file_props['sender'] - else: # we send a file - other = file_props['receiver'] - if isinstance(other, unicode): - jid = gajim.get_jid_without_resource(other) - else: # It's a Contact instance - jid = other.jid - for ev_type in ('file-error', 'file-completed', 'file-request-error', - 'file-send-error', 'file-stopped'): - for event in gajim.events.get_events(account, jid, [ev_type]): - if event.parameters['sid'] == file_props['sid']: - gajim.events.remove_events(account, jid, event) - gajim.interface.roster.draw_contact(jid, account) - gajim.interface.roster.show_title() - del(self.files_props[sid[0]][sid[1:]]) - del(file_props) - - def set_progress(self, typ, sid, transfered_size, iter_ = None): - """ - Change the progress of a transfer with new transfered size - """ - if sid not in self.files_props[typ]: - return - file_props = self.files_props[typ][sid] - full_size = int(file_props['size']) - if full_size == 0: - percent = 0 - else: - percent = round(float(transfered_size) / full_size * 100, 1) - if iter_ is None: - iter_ = self.get_iter_by_sid(typ, sid) - if iter_ is not None: - just_began = False - if self.model[iter_][C_PERCENT] == 0 and int(percent > 0): - just_began = True - text = self._format_percent(percent) - if transfered_size == 0: - text += '0' - else: - text += helpers.convert_bytes(transfered_size) - text += '/' + helpers.convert_bytes(full_size) - # Kb/s - - # remaining time - if 'offset' in file_props and file_props['offset']: - transfered_size -= file_props['offset'] - full_size -= file_props['offset'] - - if file_props['elapsed-time'] > 0: - file_props['transfered_size'].append((file_props['last-time'], transfered_size)) - if len(file_props['transfered_size']) > 6: - file_props['transfered_size'].pop(0) - eta, speed = self._get_eta_and_speed(full_size, transfered_size, - file_props) - - self.model.set(iter_, C_PROGRESS, text) - self.model.set(iter_, C_PERCENT, int(percent)) - text = self._format_time(eta) - text += '\n' - #This should make the string Kb/s, - #where 'Kb' part is taken from %s. - #Only the 's' after / (which means second) should be translated. - text += _('(%(filesize_unit)s/s)') % {'filesize_unit': - helpers.convert_bytes(speed)} - self.model.set(iter_, C_TIME, text) - - # try to guess what should be the status image - if file_props['type'] == 'r': - status = 'download' - else: - status = 'upload' - if 'paused' in file_props and file_props['paused'] == True: - status = 'pause' - elif 'stalled' in file_props and file_props['stalled'] == True: - status = 'waiting' - if 'connected' in file_props and file_props['connected'] == False: - status = 'stop' - self.model.set(iter_, 0, self.get_icon(status)) - if transfered_size == full_size: - self.set_status(typ, sid, 'ok') - elif just_began: - path = self.model.get_path(iter_) - self.select_func(path) - - def get_iter_by_sid(self, typ, sid): - """ - Return iter to the row, which holds file transfer, identified by the - session id - """ - iter_ = self.model.get_iter_root() - while iter_: - if typ + sid == self.model[iter_][C_SID].decode('utf-8'): - return iter_ - iter_ = self.model.iter_next(iter_) - - def get_send_file_props(self, account, contact, file_path, file_name, - file_desc=''): - """ - Create new file_props dict and set initial file transfer properties in it - """ - file_props = {'file-name' : file_path, 'name' : file_name, - 'type' : 's', 'desc' : file_desc} - if os.path.isfile(file_path): - stat = os.stat(file_path) - else: - dialogs.ErrorDialog(_('Invalid File'), _('File: ') + file_path) - return None - if stat[6] == 0: - dialogs.ErrorDialog(_('Invalid File'), - _('It is not possible to send empty files')) - return None - file_props['elapsed-time'] = 0 - file_props['size'] = unicode(stat[6]) - file_props['sid'] = helpers.get_random_string_16() - file_props['completed'] = False - file_props['started'] = False - file_props['sender'] = account - file_props['receiver'] = contact - file_props['tt_account'] = account - # keep the last time: transfered_size to compute transfer speed - file_props['transfered_size'] = [] - return file_props - - def add_transfer(self, account, contact, file_props): - """ - Add new transfer to FT window and show the FT window - """ - self.on_transfers_list_leave_notify_event(None) - if file_props is None: - return - file_props['elapsed-time'] = 0 - self.files_props[file_props['type']][file_props['sid']] = file_props - iter_ = self.model.prepend() - text_labels = '' + _('Name: ') + '\n' - if file_props['type'] == 'r': - text_labels += '' + _('Sender: ') + '' - else: - text_labels += '' + _('Recipient: ') + '' - - if file_props['type'] == 'r': - file_name = os.path.split(file_props['file-name'])[1] - else: - file_name = file_props['name'] - text_props = gobject.markup_escape_text(file_name) + '\n' - text_props += contact.get_shown_name() - self.model.set(iter_, 1, text_labels, 2, text_props, C_SID, - file_props['type'] + file_props['sid']) - self.set_progress(file_props['type'], file_props['sid'], 0, iter_) - if 'started' in file_props and file_props['started'] is False: - status = 'waiting' - elif file_props['type'] == 'r': - status = 'download' - else: - status = 'upload' - file_props['tt_account'] = account - self.set_status(file_props['type'], file_props['sid'], status) - self.set_cleanup_sensitivity() - self.window.show_all() - - def on_transfers_list_motion_notify_event(self, widget, event): - pointer = self.tree.get_pointer() - props = widget.get_path_at_pos(int(event.x), int(event.y)) - self.height_diff = pointer[1] - int(event.y) - if self.tooltip.timeout > 0: - if not props or self.tooltip.id != props[0]: - self.tooltip.hide_tooltip() - if props: - row = props[0] - iter_ = None - try: - iter_ = self.model.get_iter(row) - except Exception: - self.tooltip.hide_tooltip() - return - sid = self.model[iter_][C_SID].decode('utf-8') - file_props = self.files_props[sid[0]][sid[1:]] - if file_props is not None: - if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: - self.tooltip.id = row - self.tooltip.timeout = gobject.timeout_add(500, - self.show_tooltip, widget) - - def on_transfers_list_leave_notify_event(self, widget = None, event = None): - if event is not None: - self.height_diff = int(event.y) - elif self.height_diff is 0: - return - pointer = self.tree.get_pointer() - props = self.tree.get_path_at_pos(pointer[0], - pointer[1] - self.height_diff) - if self.tooltip.timeout > 0: - if not props or self.tooltip.id == props[0]: - self.tooltip.hide_tooltip() - - def on_transfers_list_row_activated(self, widget, path, col): - # try to open the containing folder - self.on_open_folder_menuitem_activate(widget) - - def is_transfer_paused(self, file_props): - if 'stopped' in file_props and file_props['stopped']: - return False - if 'completed' in file_props and file_props['completed']: - return False - if 'disconnect_cb' not in file_props: - return False - return file_props['paused'] - - def is_transfer_active(self, file_props): - if 'stopped' in file_props and file_props['stopped']: - return False - if 'completed' in file_props and file_props['completed']: - return False - if 'started' not in file_props or not file_props['started']: - return False - if 'paused' not in file_props: - return True - return not file_props['paused'] - - def is_transfer_stopped(self, file_props): - if 'error' in file_props and file_props['error'] != 0: - return True - if 'completed' in file_props and file_props['completed']: - return True - if 'connected' in file_props and file_props['connected'] == False: - return True - if 'stopped' not in file_props or not file_props['stopped']: - return False - return True - - def set_cleanup_sensitivity(self): - """ - Check if there are transfer rows and set cleanup_button sensitive, or - insensitive if model is empty - """ - if len(self.model) == 0: - self.cleanup_button.set_sensitive(False) - else: - self.cleanup_button.set_sensitive(True) - - def set_all_insensitive(self): - """ - Make all buttons/menuitems insensitive - """ - self.pause_button.set_sensitive(False) - self.pause_menuitem.set_sensitive(False) - self.continue_menuitem.set_sensitive(False) - self.remove_menuitem.set_sensitive(False) - self.cancel_button.set_sensitive(False) - self.cancel_menuitem.set_sensitive(False) - self.open_folder_menuitem.set_sensitive(False) - self.set_cleanup_sensitivity() - - def set_buttons_sensitive(self, path, is_row_selected): - """ - Make buttons/menuitems sensitive as appropriate to the state of file - transfer located at path 'path' - """ - if path is None: - self.set_all_insensitive() - return - current_iter = self.model.get_iter(path) - sid = self.model[current_iter][C_SID].decode('utf-8') - file_props = self.files_props[sid[0]][sid[1:]] - self.remove_menuitem.set_sensitive(is_row_selected) - self.open_folder_menuitem.set_sensitive(is_row_selected) - is_stopped = False - if self.is_transfer_stopped(file_props): - is_stopped = True - self.cancel_button.set_sensitive(not is_stopped) - self.cancel_menuitem.set_sensitive(not is_stopped) - if not is_row_selected: - # no selection, disable the buttons - self.set_all_insensitive() - elif not is_stopped: - if self.is_transfer_active(file_props): - # file transfer is active - self.toggle_pause_continue(True) - self.pause_button.set_sensitive(True) - elif self.is_transfer_paused(file_props): - # file transfer is paused - self.toggle_pause_continue(False) - self.pause_button.set_sensitive(True) - else: - self.pause_button.set_sensitive(False) - self.pause_menuitem.set_sensitive(False) - self.continue_menuitem.set_sensitive(False) - else: - self.pause_button.set_sensitive(False) - self.pause_menuitem.set_sensitive(False) - self.continue_menuitem.set_sensitive(False) - return True - - def selection_changed(self, args): - """ - Selection has changed - change the sensitivity of the buttons/menuitems - """ - selection = args - selected = selection.get_selected_rows() - if selected[1] != []: - selected_path = selected[1][0] - self.select_func(selected_path) - else: - self.set_all_insensitive() - - def select_func(self, path): - is_selected = False - selected = self.tree.get_selection().get_selected_rows() - if selected[1] != []: - selected_path = selected[1][0] - if selected_path == path: - is_selected = True - self.set_buttons_sensitive(path, is_selected) - self.set_cleanup_sensitivity() - return True - - def on_cleanup_button_clicked(self, widget): - i = len(self.model) - 1 - while i >= 0: - iter_ = self.model.get_iter((i)) - sid = self.model[iter_][C_SID].decode('utf-8') - file_props = self.files_props[sid[0]][sid[1:]] - if self.is_transfer_stopped(file_props): - self._remove_transfer(iter_, sid, file_props) - i -= 1 - self.tree.get_selection().unselect_all() - self.set_all_insensitive() - - def toggle_pause_continue(self, status): - if status: - label = _('Pause') - self.pause_button.set_label(label) - self.pause_button.set_image(gtk.image_new_from_stock( - gtk.STOCK_MEDIA_PAUSE, gtk.ICON_SIZE_MENU)) - - self.pause_menuitem.set_sensitive(True) - self.pause_menuitem.set_no_show_all(False) - self.continue_menuitem.hide() - self.continue_menuitem.set_no_show_all(True) - - else: - label = _('_Continue') - self.pause_button.set_label(label) - self.pause_button.set_image(gtk.image_new_from_stock( - gtk.STOCK_MEDIA_PLAY, gtk.ICON_SIZE_MENU)) - self.pause_menuitem.hide() - self.pause_menuitem.set_no_show_all(True) - self.continue_menuitem.set_sensitive(True) - self.continue_menuitem.set_no_show_all(False) - - def on_pause_restore_button_clicked(self, widget): - selected = self.tree.get_selection().get_selected() - if selected is None or selected[1] is None: - return - s_iter = selected[1] - sid = self.model[s_iter][C_SID].decode('utf-8') - file_props = self.files_props[sid[0]][sid[1:]] - if self.is_transfer_paused(file_props): - file_props['last-time'] = time.time() - file_props['paused'] = False - types = {'r' : 'download', 's' : 'upload'} - self.set_status(file_props['type'], file_props['sid'], types[sid[0]]) - self.toggle_pause_continue(True) - file_props['continue_cb']() - elif self.is_transfer_active(file_props): - file_props['paused'] = True - self.set_status(file_props['type'], file_props['sid'], 'pause') - # reset that to compute speed only when we resume - file_props['transfered_size'] = [] - self.toggle_pause_continue(False) - - def on_cancel_button_clicked(self, widget): - selected = self.tree.get_selection().get_selected() - if selected is None or selected[1] is None: - return - s_iter = selected[1] - sid = self.model[s_iter][C_SID].decode('utf-8') - file_props = self.files_props[sid[0]][sid[1:]] - if 'tt_account' not in file_props: - return - account = file_props['tt_account'] - if account not in gajim.connections: - return - gajim.connections[account].disconnect_transfer(file_props) - self.set_status(file_props['type'], file_props['sid'], 'stop') - - def show_tooltip(self, widget): - if self.height_diff == 0: - self.tooltip.hide_tooltip() - return - pointer = self.tree.get_pointer() - props = self.tree.get_path_at_pos(pointer[0], - pointer[1] - self.height_diff) - # check if the current pointer is at the same path - # as it was before setting the timeout - if props and self.tooltip.id == props[0]: - iter_ = self.model.get_iter(props[0]) - sid = self.model[iter_][C_SID].decode('utf-8') - file_props = self.files_props[sid[0]][sid[1:]] - # bounding rectangle of coordinates for the cell within the treeview - rect = self.tree.get_cell_area(props[0],props[1]) - # position of the treeview on the screen - position = widget.window.get_origin() - self.tooltip.show_tooltip(file_props , rect.height, - position[1] + rect.y + self.height_diff) - else: - self.tooltip.hide_tooltip() - - def on_notify_ft_complete_checkbox_toggled(self, widget): - gajim.config.set('notify_on_file_complete', - widget.get_active()) - - def on_file_transfers_dialog_delete_event(self, widget, event): - self.on_transfers_list_leave_notify_event(widget, None) - self.window.hide() - return True # do NOT destory window - - def on_close_button_clicked(self, widget): - self.window.hide() - - def show_context_menu(self, event, iter_): - # change the sensitive propery of the buttons and menuitems - path = None - if iter_ is not None: - path = self.model.get_path(iter_) - self.set_buttons_sensitive(path, True) - - event_button = gtkgui_helpers.get_possible_button_event(event) - self.file_transfers_menu.show_all() - self.file_transfers_menu.popup(None, self.tree, None, - event_button, event.time) - - def on_transfers_list_key_press_event(self, widget, event): - """ - When a key is pressed in the treeviews - """ - self.tooltip.hide_tooltip() - iter_ = None - try: - iter_ = self.tree.get_selection().get_selected()[1] - except TypeError: - self.tree.get_selection().unselect_all() - - if iter_ is not None: - path = self.model.get_path(iter_) - self.tree.get_selection().select_path(path) - - if event.keyval == gtk.keysyms.Menu: - self.show_context_menu(event, iter_) - return True - - - def on_transfers_list_button_release_event(self, widget, event): - # hide tooltip, no matter the button is pressed - self.tooltip.hide_tooltip() - path = None - try: - path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0] - except TypeError: - self.tree.get_selection().unselect_all() - if path is None: - self.set_all_insensitive() - else: - self.select_func(path) - - def on_transfers_list_button_press_event(self, widget, event): - # hide tooltip, no matter the button is pressed - self.tooltip.hide_tooltip() - path, iter_ = None, None - try: - path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0] - except TypeError: - self.tree.get_selection().unselect_all() - if event.button == 3: # Right click - if path is not None: - self.tree.get_selection().select_path(path) - iter_ = self.model.get_iter(path) - self.show_context_menu(event, iter_) - if path is not None: - return True - - def on_open_folder_menuitem_activate(self, widget): - selected = self.tree.get_selection().get_selected() - if selected is None or selected[1] is None: - return - s_iter = selected[1] - sid = self.model[s_iter][C_SID].decode('utf-8') - file_props = self.files_props[sid[0]][sid[1:]] - if 'file-name' not in file_props: - return - path = os.path.split(file_props['file-name'])[0] - if os.path.exists(path) and os.path.isdir(path): - helpers.launch_file_manager(path) - - def on_cancel_menuitem_activate(self, widget): - self.on_cancel_button_clicked(widget) - - def on_continue_menuitem_activate(self, widget): - self.on_pause_restore_button_clicked(widget) - - def on_pause_menuitem_activate(self, widget): - self.on_pause_restore_button_clicked(widget) - - def on_remove_menuitem_activate(self, widget): - selected = self.tree.get_selection().get_selected() - if selected is None or selected[1] is None: - return - s_iter = selected[1] - sid = self.model[s_iter][C_SID].decode('utf-8') - file_props = self.files_props[sid[0]][sid[1:]] - self._remove_transfer(s_iter, sid, file_props) - self.set_all_insensitive() - - def on_file_transfers_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: # ESCAPE - self.window.hide() - -# vim: se ts=3: + self.tree.get_selection().unselect_all() + + def show_stopped(self, jid, file_props, error_msg = ''): + if file_props['type'] == 'r': + file_name = os.path.basename(file_props['file-name']) + else: + file_name = file_props['name'] + sectext = '\t' + _('Filename: %s') % file_name + sectext += '\n\t' + _('Recipient: %s') % jid + if error_msg: + sectext += '\n\t' + _('Error message: %s') % error_msg + dialogs.ErrorDialog(_('File transfer stopped'), sectext) + self.tree.get_selection().unselect_all() + + def show_file_send_request(self, account, contact): + + desc_entry = gtk.Entry() + + def on_ok(widget): + file_dir = None + files_path_list = dialog.get_filenames() + files_path_list = gtkgui_helpers.decode_filechooser_file_paths( + files_path_list) + desc = desc_entry.get_text() + for file_path in files_path_list: + if self.send_file(account, contact, file_path, desc) and file_dir is None: + file_dir = os.path.dirname(file_path) + if file_dir: + gajim.config.set('last_send_dir', file_dir) + dialog.destroy() + + dialog = dialogs.FileChooserDialog(_('Choose File to Send...'), + gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), + gtk.RESPONSE_OK, + True, # select multiple true as we can select many files to send + gajim.config.get('last_send_dir'), + on_response_ok = on_ok, + on_response_cancel = lambda e:dialog.destroy() + ) + + btn = gtk.Button(_('_Send')) + btn.set_property('can-default', True) + # FIXME: add send icon to this button (JUMP_TO) + dialog.add_action_widget(btn, gtk.RESPONSE_OK) + dialog.set_default_response(gtk.RESPONSE_OK) + + desc_hbox = gtk.HBox(False, 5) + desc_hbox.pack_start(gtk.Label(_('Description: ')), False, False, 0) + desc_hbox.pack_start(desc_entry, True, True, 0) + + dialog.vbox.pack_start(desc_hbox, False, False, 0) + + btn.show() + desc_hbox.show_all() + + def send_file(self, account, contact, file_path, file_desc=''): + """ + Start the real transfer(upload) of the file + """ + if gtkgui_helpers.file_is_locked(file_path): + pritext = _('Gajim cannot access this file') + sextext = _('This file is being used by another process.') + dialogs.ErrorDialog(pritext, sextext) + return + + if isinstance(contact, str): + if contact.find('/') == -1: + return + (jid, resource) = contact.split('/', 1) + contact = gajim.contacts.create_contact(jid=jid, account=account, resource=resource) + file_name = os.path.split(file_path)[1] + file_props = self.get_send_file_props(account, contact, + file_path, file_name, file_desc) + if file_props is None: + return False + self.add_transfer(account, contact, file_props) + gajim.connections[account].send_file_request(file_props) + return True + + def _start_receive(self, file_path, account, contact, file_props): + file_dir = os.path.dirname(file_path) + if file_dir: + gajim.config.set('last_save_dir', file_dir) + file_props['file-name'] = file_path + self.add_transfer(account, contact, file_props) + gajim.connections[account].send_file_approval(file_props) + + def show_file_request(self, account, contact, file_props): + """ + Show dialog asking for comfirmation and store location of new file + requested by a contact + """ + if file_props is None or 'name' not in file_props: + return + sec_text = '\t' + _('File: %s') % gobject.markup_escape_text( + file_props['name']) + if 'size' in file_props: + sec_text += '\n\t' + _('Size: %s') % \ + helpers.convert_bytes(file_props['size']) + if 'mime-type' in file_props: + sec_text += '\n\t' + _('Type: %s') % file_props['mime-type'] + if 'desc' in file_props: + sec_text += '\n\t' + _('Description: %s') % file_props['desc'] + prim_text = _('%s wants to send you a file:') % contact.jid + dialog = None + + def on_response_ok(account, contact, file_props): + + def on_ok(widget, account, contact, file_props): + file_path = dialog2.get_filename() + file_path = gtkgui_helpers.decode_filechooser_file_paths( + (file_path,))[0] + if os.path.exists(file_path): + # check if we have write permissions + if not os.access(file_path, os.W_OK): + file_name = os.path.basename(file_path) + dialogs.ErrorDialog(_('Cannot overwrite existing file "%s"' % file_name), + _('A file with this name already exists and you do not have permission to overwrite it.')) + return + stat = os.stat(file_path) + dl_size = stat.st_size + file_size = file_props['size'] + dl_finished = dl_size >= file_size + + def on_response(response): + if response < 0: + return + elif response == 100: + file_props['offset'] = dl_size + dialog2.destroy() + self._start_receive(file_path, account, contact, file_props) + + dialog = dialogs.FTOverwriteConfirmationDialog( + _('This file already exists'), _('What do you want to do?'), + propose_resume=not dl_finished, on_response=on_response) + dialog.set_transient_for(dialog2) + dialog.set_destroy_with_parent(True) + return + else: + dirname = os.path.dirname(file_path) + if not os.access(dirname, os.W_OK) and os.name != 'nt': + # read-only bit is used to mark special folder under windows, + # not to mark that a folder is read-only. See ticket #3587 + dialogs.ErrorDialog(_('Directory "%s" is not writable') % dirname, _('You do not have permission to create files in this directory.')) + return + dialog2.destroy() + self._start_receive(file_path, account, contact, file_props) + + def on_cancel(widget, account, contact, file_props): + dialog2.destroy() + gajim.connections[account].send_file_rejection(file_props) + + dialog2 = dialogs.FileChooserDialog( + title_text = _('Save File as...'), + action = gtk.FILE_CHOOSER_ACTION_SAVE, + buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_SAVE, gtk.RESPONSE_OK), + default_response = gtk.RESPONSE_OK, + current_folder = gajim.config.get('last_save_dir'), + on_response_ok = (on_ok, account, contact, file_props), + on_response_cancel = (on_cancel, account, contact, file_props)) + + dialog2.set_current_name(file_props['name']) + dialog2.connect('delete-event', lambda widget, event: + on_cancel(widget, account, contact, file_props)) + + def on_response_cancel(account, file_props): + gajim.connections[account].send_file_rejection(file_props) + + dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text, + on_response_ok = (on_response_ok, account, contact, file_props), + on_response_cancel = (on_response_cancel, account, file_props)) + dialog.connect('delete-event', lambda widget, event: + on_response_cancel(widget, account, file_props)) + dialog.popup() + + def get_icon(self, ident): + return self.images.setdefault(ident, + self.window.render_icon(self.icons[ident], gtk.ICON_SIZE_MENU)) + + def set_status(self, typ, sid, status): + """ + Change the status of a transfer to state 'status' + """ + iter_ = self.get_iter_by_sid(typ, sid) + if iter_ is None: + return + sid = self.model[iter_][C_SID].decode('utf-8') + file_props = self.files_props[sid[0]][sid[1:]] + if status == 'stop': + file_props['stopped'] = True + elif status == 'ok': + file_props['completed'] = True + self.model.set(iter_, C_IMAGE, self.get_icon(status)) + path = self.model.get_path(iter_) + self.select_func(path) + + def _format_percent(self, percent): + """ + Add extra spaces from both sides of the percent, so that progress string + has always a fixed size + """ + _str = ' ' + if percent != 100.: + _str += ' ' + if percent < 10: + _str += ' ' + _str += unicode(percent) + '% \n' + return _str + + def _format_time(self, _time): + times = { 'hours': 0, 'minutes': 0, 'seconds': 0 } + _time = int(_time) + times['seconds'] = _time % 60 + if _time >= 60: + _time /= 60 + times['minutes'] = _time % 60 + if _time >= 60: + times['hours'] = _time / 60 + + #Print remaining time in format 00:00:00 + #You can change the places of (hours), (minutes), (seconds) - + #they are not translatable. + return _('%(hours)02.d:%(minutes)02.d:%(seconds)02.d') % times + + def _get_eta_and_speed(self, full_size, transfered_size, file_props): + if len(file_props['transfered_size']) == 0: + return 0., 0. + elif len(file_props['transfered_size']) == 1: + speed = round(float(transfered_size) / file_props['elapsed-time']) + else: + # first and last are (time, transfered_size) + first = file_props['transfered_size'][0] + last = file_props['transfered_size'][-1] + transfered = last[1] - first[1] + tim = last[0] - first[0] + if tim == 0: + return 0., 0. + speed = round(float(transfered) / tim) + if speed == 0.: + return 0., 0. + remaining_size = full_size - transfered_size + eta = remaining_size / speed + return eta, speed + + def _remove_transfer(self, iter_, sid, file_props): + self.model.remove(iter_) + if 'tt_account' in file_props: + # file transfer is set + account = file_props['tt_account'] + if account in gajim.connections: + # there is a connection to the account + gajim.connections[account].remove_transfer(file_props) + if file_props['type'] == 'r': # we receive a file + other = file_props['sender'] + else: # we send a file + other = file_props['receiver'] + if isinstance(other, unicode): + jid = gajim.get_jid_without_resource(other) + else: # It's a Contact instance + jid = other.jid + for ev_type in ('file-error', 'file-completed', 'file-request-error', + 'file-send-error', 'file-stopped'): + for event in gajim.events.get_events(account, jid, [ev_type]): + if event.parameters['sid'] == file_props['sid']: + gajim.events.remove_events(account, jid, event) + gajim.interface.roster.draw_contact(jid, account) + gajim.interface.roster.show_title() + del(self.files_props[sid[0]][sid[1:]]) + del(file_props) + + def set_progress(self, typ, sid, transfered_size, iter_ = None): + """ + Change the progress of a transfer with new transfered size + """ + if sid not in self.files_props[typ]: + return + file_props = self.files_props[typ][sid] + full_size = int(file_props['size']) + if full_size == 0: + percent = 0 + else: + percent = round(float(transfered_size) / full_size * 100, 1) + if iter_ is None: + iter_ = self.get_iter_by_sid(typ, sid) + if iter_ is not None: + just_began = False + if self.model[iter_][C_PERCENT] == 0 and int(percent > 0): + just_began = True + text = self._format_percent(percent) + if transfered_size == 0: + text += '0' + else: + text += helpers.convert_bytes(transfered_size) + text += '/' + helpers.convert_bytes(full_size) + # Kb/s + + # remaining time + if 'offset' in file_props and file_props['offset']: + transfered_size -= file_props['offset'] + full_size -= file_props['offset'] + + if file_props['elapsed-time'] > 0: + file_props['transfered_size'].append((file_props['last-time'], transfered_size)) + if len(file_props['transfered_size']) > 6: + file_props['transfered_size'].pop(0) + eta, speed = self._get_eta_and_speed(full_size, transfered_size, + file_props) + + self.model.set(iter_, C_PROGRESS, text) + self.model.set(iter_, C_PERCENT, int(percent)) + text = self._format_time(eta) + text += '\n' + #This should make the string Kb/s, + #where 'Kb' part is taken from %s. + #Only the 's' after / (which means second) should be translated. + text += _('(%(filesize_unit)s/s)') % {'filesize_unit': + helpers.convert_bytes(speed)} + self.model.set(iter_, C_TIME, text) + + # try to guess what should be the status image + if file_props['type'] == 'r': + status = 'download' + else: + status = 'upload' + if 'paused' in file_props and file_props['paused'] == True: + status = 'pause' + elif 'stalled' in file_props and file_props['stalled'] == True: + status = 'waiting' + if 'connected' in file_props and file_props['connected'] == False: + status = 'stop' + self.model.set(iter_, 0, self.get_icon(status)) + if transfered_size == full_size: + self.set_status(typ, sid, 'ok') + elif just_began: + path = self.model.get_path(iter_) + self.select_func(path) + + def get_iter_by_sid(self, typ, sid): + """ + Return iter to the row, which holds file transfer, identified by the + session id + """ + iter_ = self.model.get_iter_root() + while iter_: + if typ + sid == self.model[iter_][C_SID].decode('utf-8'): + return iter_ + iter_ = self.model.iter_next(iter_) + + def get_send_file_props(self, account, contact, file_path, file_name, + file_desc=''): + """ + Create new file_props dict and set initial file transfer properties in it + """ + file_props = {'file-name' : file_path, 'name' : file_name, + 'type' : 's', 'desc' : file_desc} + if os.path.isfile(file_path): + stat = os.stat(file_path) + else: + dialogs.ErrorDialog(_('Invalid File'), _('File: ') + file_path) + return None + if stat[6] == 0: + dialogs.ErrorDialog(_('Invalid File'), + _('It is not possible to send empty files')) + return None + file_props['elapsed-time'] = 0 + file_props['size'] = unicode(stat[6]) + file_props['sid'] = helpers.get_random_string_16() + file_props['completed'] = False + file_props['started'] = False + file_props['sender'] = account + file_props['receiver'] = contact + file_props['tt_account'] = account + # keep the last time: transfered_size to compute transfer speed + file_props['transfered_size'] = [] + return file_props + + def add_transfer(self, account, contact, file_props): + """ + Add new transfer to FT window and show the FT window + """ + self.on_transfers_list_leave_notify_event(None) + if file_props is None: + return + file_props['elapsed-time'] = 0 + self.files_props[file_props['type']][file_props['sid']] = file_props + iter_ = self.model.prepend() + text_labels = '' + _('Name: ') + '\n' + if file_props['type'] == 'r': + text_labels += '' + _('Sender: ') + '' + else: + text_labels += '' + _('Recipient: ') + '' + + if file_props['type'] == 'r': + file_name = os.path.split(file_props['file-name'])[1] + else: + file_name = file_props['name'] + text_props = gobject.markup_escape_text(file_name) + '\n' + text_props += contact.get_shown_name() + self.model.set(iter_, 1, text_labels, 2, text_props, C_SID, + file_props['type'] + file_props['sid']) + self.set_progress(file_props['type'], file_props['sid'], 0, iter_) + if 'started' in file_props and file_props['started'] is False: + status = 'waiting' + elif file_props['type'] == 'r': + status = 'download' + else: + status = 'upload' + file_props['tt_account'] = account + self.set_status(file_props['type'], file_props['sid'], status) + self.set_cleanup_sensitivity() + self.window.show_all() + + def on_transfers_list_motion_notify_event(self, widget, event): + pointer = self.tree.get_pointer() + props = widget.get_path_at_pos(int(event.x), int(event.y)) + self.height_diff = pointer[1] - int(event.y) + if self.tooltip.timeout > 0: + if not props or self.tooltip.id != props[0]: + self.tooltip.hide_tooltip() + if props: + row = props[0] + iter_ = None + try: + iter_ = self.model.get_iter(row) + except Exception: + self.tooltip.hide_tooltip() + return + sid = self.model[iter_][C_SID].decode('utf-8') + file_props = self.files_props[sid[0]][sid[1:]] + if file_props is not None: + if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: + self.tooltip.id = row + self.tooltip.timeout = gobject.timeout_add(500, + self.show_tooltip, widget) + + def on_transfers_list_leave_notify_event(self, widget = None, event = None): + if event is not None: + self.height_diff = int(event.y) + elif self.height_diff is 0: + return + pointer = self.tree.get_pointer() + props = self.tree.get_path_at_pos(pointer[0], + pointer[1] - self.height_diff) + if self.tooltip.timeout > 0: + if not props or self.tooltip.id == props[0]: + self.tooltip.hide_tooltip() + + def on_transfers_list_row_activated(self, widget, path, col): + # try to open the containing folder + self.on_open_folder_menuitem_activate(widget) + + def is_transfer_paused(self, file_props): + if 'stopped' in file_props and file_props['stopped']: + return False + if 'completed' in file_props and file_props['completed']: + return False + if 'disconnect_cb' not in file_props: + return False + return file_props['paused'] + + def is_transfer_active(self, file_props): + if 'stopped' in file_props and file_props['stopped']: + return False + if 'completed' in file_props and file_props['completed']: + return False + if 'started' not in file_props or not file_props['started']: + return False + if 'paused' not in file_props: + return True + return not file_props['paused'] + + def is_transfer_stopped(self, file_props): + if 'error' in file_props and file_props['error'] != 0: + return True + if 'completed' in file_props and file_props['completed']: + return True + if 'connected' in file_props and file_props['connected'] == False: + return True + if 'stopped' not in file_props or not file_props['stopped']: + return False + return True + + def set_cleanup_sensitivity(self): + """ + Check if there are transfer rows and set cleanup_button sensitive, or + insensitive if model is empty + """ + if len(self.model) == 0: + self.cleanup_button.set_sensitive(False) + else: + self.cleanup_button.set_sensitive(True) + + def set_all_insensitive(self): + """ + Make all buttons/menuitems insensitive + """ + self.pause_button.set_sensitive(False) + self.pause_menuitem.set_sensitive(False) + self.continue_menuitem.set_sensitive(False) + self.remove_menuitem.set_sensitive(False) + self.cancel_button.set_sensitive(False) + self.cancel_menuitem.set_sensitive(False) + self.open_folder_menuitem.set_sensitive(False) + self.set_cleanup_sensitivity() + + def set_buttons_sensitive(self, path, is_row_selected): + """ + Make buttons/menuitems sensitive as appropriate to the state of file + transfer located at path 'path' + """ + if path is None: + self.set_all_insensitive() + return + current_iter = self.model.get_iter(path) + sid = self.model[current_iter][C_SID].decode('utf-8') + file_props = self.files_props[sid[0]][sid[1:]] + self.remove_menuitem.set_sensitive(is_row_selected) + self.open_folder_menuitem.set_sensitive(is_row_selected) + is_stopped = False + if self.is_transfer_stopped(file_props): + is_stopped = True + self.cancel_button.set_sensitive(not is_stopped) + self.cancel_menuitem.set_sensitive(not is_stopped) + if not is_row_selected: + # no selection, disable the buttons + self.set_all_insensitive() + elif not is_stopped: + if self.is_transfer_active(file_props): + # file transfer is active + self.toggle_pause_continue(True) + self.pause_button.set_sensitive(True) + elif self.is_transfer_paused(file_props): + # file transfer is paused + self.toggle_pause_continue(False) + self.pause_button.set_sensitive(True) + else: + self.pause_button.set_sensitive(False) + self.pause_menuitem.set_sensitive(False) + self.continue_menuitem.set_sensitive(False) + else: + self.pause_button.set_sensitive(False) + self.pause_menuitem.set_sensitive(False) + self.continue_menuitem.set_sensitive(False) + return True + + def selection_changed(self, args): + """ + Selection has changed - change the sensitivity of the buttons/menuitems + """ + selection = args + selected = selection.get_selected_rows() + if selected[1] != []: + selected_path = selected[1][0] + self.select_func(selected_path) + else: + self.set_all_insensitive() + + def select_func(self, path): + is_selected = False + selected = self.tree.get_selection().get_selected_rows() + if selected[1] != []: + selected_path = selected[1][0] + if selected_path == path: + is_selected = True + self.set_buttons_sensitive(path, is_selected) + self.set_cleanup_sensitivity() + return True + + def on_cleanup_button_clicked(self, widget): + i = len(self.model) - 1 + while i >= 0: + iter_ = self.model.get_iter((i)) + sid = self.model[iter_][C_SID].decode('utf-8') + file_props = self.files_props[sid[0]][sid[1:]] + if self.is_transfer_stopped(file_props): + self._remove_transfer(iter_, sid, file_props) + i -= 1 + self.tree.get_selection().unselect_all() + self.set_all_insensitive() + + def toggle_pause_continue(self, status): + if status: + label = _('Pause') + self.pause_button.set_label(label) + self.pause_button.set_image(gtk.image_new_from_stock( + gtk.STOCK_MEDIA_PAUSE, gtk.ICON_SIZE_MENU)) + + self.pause_menuitem.set_sensitive(True) + self.pause_menuitem.set_no_show_all(False) + self.continue_menuitem.hide() + self.continue_menuitem.set_no_show_all(True) + + else: + label = _('_Continue') + self.pause_button.set_label(label) + self.pause_button.set_image(gtk.image_new_from_stock( + gtk.STOCK_MEDIA_PLAY, gtk.ICON_SIZE_MENU)) + self.pause_menuitem.hide() + self.pause_menuitem.set_no_show_all(True) + self.continue_menuitem.set_sensitive(True) + self.continue_menuitem.set_no_show_all(False) + + def on_pause_restore_button_clicked(self, widget): + selected = self.tree.get_selection().get_selected() + if selected is None or selected[1] is None: + return + s_iter = selected[1] + sid = self.model[s_iter][C_SID].decode('utf-8') + file_props = self.files_props[sid[0]][sid[1:]] + if self.is_transfer_paused(file_props): + file_props['last-time'] = time.time() + file_props['paused'] = False + types = {'r' : 'download', 's' : 'upload'} + self.set_status(file_props['type'], file_props['sid'], types[sid[0]]) + self.toggle_pause_continue(True) + file_props['continue_cb']() + elif self.is_transfer_active(file_props): + file_props['paused'] = True + self.set_status(file_props['type'], file_props['sid'], 'pause') + # reset that to compute speed only when we resume + file_props['transfered_size'] = [] + self.toggle_pause_continue(False) + + def on_cancel_button_clicked(self, widget): + selected = self.tree.get_selection().get_selected() + if selected is None or selected[1] is None: + return + s_iter = selected[1] + sid = self.model[s_iter][C_SID].decode('utf-8') + file_props = self.files_props[sid[0]][sid[1:]] + if 'tt_account' not in file_props: + return + account = file_props['tt_account'] + if account not in gajim.connections: + return + gajim.connections[account].disconnect_transfer(file_props) + self.set_status(file_props['type'], file_props['sid'], 'stop') + + def show_tooltip(self, widget): + if self.height_diff == 0: + self.tooltip.hide_tooltip() + return + pointer = self.tree.get_pointer() + props = self.tree.get_path_at_pos(pointer[0], + pointer[1] - self.height_diff) + # check if the current pointer is at the same path + # as it was before setting the timeout + if props and self.tooltip.id == props[0]: + iter_ = self.model.get_iter(props[0]) + sid = self.model[iter_][C_SID].decode('utf-8') + file_props = self.files_props[sid[0]][sid[1:]] + # bounding rectangle of coordinates for the cell within the treeview + rect = self.tree.get_cell_area(props[0], props[1]) + # position of the treeview on the screen + position = widget.window.get_origin() + self.tooltip.show_tooltip(file_props, rect.height, + position[1] + rect.y + self.height_diff) + else: + self.tooltip.hide_tooltip() + + def on_notify_ft_complete_checkbox_toggled(self, widget): + gajim.config.set('notify_on_file_complete', + widget.get_active()) + + def on_file_transfers_dialog_delete_event(self, widget, event): + self.on_transfers_list_leave_notify_event(widget, None) + self.window.hide() + return True # do NOT destory window + + def on_close_button_clicked(self, widget): + self.window.hide() + + def show_context_menu(self, event, iter_): + # change the sensitive propery of the buttons and menuitems + path = None + if iter_ is not None: + path = self.model.get_path(iter_) + self.set_buttons_sensitive(path, True) + + event_button = gtkgui_helpers.get_possible_button_event(event) + self.file_transfers_menu.show_all() + self.file_transfers_menu.popup(None, self.tree, None, + event_button, event.time) + + def on_transfers_list_key_press_event(self, widget, event): + """ + When a key is pressed in the treeviews + """ + self.tooltip.hide_tooltip() + iter_ = None + try: + iter_ = self.tree.get_selection().get_selected()[1] + except TypeError: + self.tree.get_selection().unselect_all() + + if iter_ is not None: + path = self.model.get_path(iter_) + self.tree.get_selection().select_path(path) + + if event.keyval == gtk.keysyms.Menu: + self.show_context_menu(event, iter_) + return True + + + def on_transfers_list_button_release_event(self, widget, event): + # hide tooltip, no matter the button is pressed + self.tooltip.hide_tooltip() + path = None + try: + path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0] + except TypeError: + self.tree.get_selection().unselect_all() + if path is None: + self.set_all_insensitive() + else: + self.select_func(path) + + def on_transfers_list_button_press_event(self, widget, event): + # hide tooltip, no matter the button is pressed + self.tooltip.hide_tooltip() + path, iter_ = None, None + try: + path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0] + except TypeError: + self.tree.get_selection().unselect_all() + if event.button == 3: # Right click + if path is not None: + self.tree.get_selection().select_path(path) + iter_ = self.model.get_iter(path) + self.show_context_menu(event, iter_) + if path is not None: + return True + + def on_open_folder_menuitem_activate(self, widget): + selected = self.tree.get_selection().get_selected() + if selected is None or selected[1] is None: + return + s_iter = selected[1] + sid = self.model[s_iter][C_SID].decode('utf-8') + file_props = self.files_props[sid[0]][sid[1:]] + if 'file-name' not in file_props: + return + path = os.path.split(file_props['file-name'])[0] + if os.path.exists(path) and os.path.isdir(path): + helpers.launch_file_manager(path) + + def on_cancel_menuitem_activate(self, widget): + self.on_cancel_button_clicked(widget) + + def on_continue_menuitem_activate(self, widget): + self.on_pause_restore_button_clicked(widget) + + def on_pause_menuitem_activate(self, widget): + self.on_pause_restore_button_clicked(widget) + + def on_remove_menuitem_activate(self, widget): + selected = self.tree.get_selection().get_selected() + if selected is None or selected[1] is None: + return + s_iter = selected[1] + sid = self.model[s_iter][C_SID].decode('utf-8') + file_props = self.files_props[sid[0]][sid[1:]] + self._remove_transfer(s_iter, sid, file_props) + self.set_all_insensitive() + + def on_file_transfers_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: # ESCAPE + self.window.hide() diff --git a/src/gajim-remote.py b/src/gajim-remote.py index 09db604d0..43f606481 100644 --- a/src/gajim-remote.py +++ b/src/gajim-remote.py @@ -36,24 +36,24 @@ from common import exceptions from common import i18n # This installs _() function try: - PREFERRED_ENCODING = locale.getpreferredencoding() + PREFERRED_ENCODING = locale.getpreferredencoding() except Exception: - PREFERRED_ENCODING = 'UTF-8' + PREFERRED_ENCODING = 'UTF-8' def send_error(error_message): - '''Writes error message to stderr and exits''' - print >> sys.stderr, error_message.encode(PREFERRED_ENCODING) - sys.exit(1) + '''Writes error message to stderr and exits''' + print >> sys.stderr, error_message.encode(PREFERRED_ENCODING) + sys.exit(1) try: - import dbus - import dbus.service - import dbus.glib - # test if dbus-x11 is installed - bus = dbus.SessionBus() + import dbus + import dbus.service + import dbus.glib + # test if dbus-x11 is installed + bus = dbus.SessionBus() except Exception: - print str(exceptions.DbusNotSupported()) - sys.exit(1) + print str(exceptions.DbusNotSupported()) + sys.exit(1) OBJ_PATH = '/org/gajim/dbus/RemoteObject' INTERFACE = 'org.gajim.dbus.RemoteInterface' @@ -62,528 +62,526 @@ BASENAME = 'gajim-remote' class GajimRemote: - def __init__(self): - self.argv_len = len(sys.argv) - # define commands dict. Prototype : - # { - # 'command': [comment, [list of arguments] ] - # } - # - # each argument is defined as a tuple: - # (argument name, help on argument, is mandatory) - # - self.commands = { - 'help':[ - _('Shows a help on specific command'), - [ - #User gets help for the command, specified by this parameter - (_('command'), - _('show help on command'), False) - ] - ], - 'toggle_roster_appearance' : [ - _('Shows or hides the roster window'), - [] - ], - 'show_next_pending_event': [ - _('Pops up a window with the next pending event'), - [] - ], - 'list_contacts': [ - _('Prints a list of all contacts in the roster. Each contact ' - 'appears on a separate line'), - [ - (_('account'), _('show only contacts of the given account'), - False) - ] + def __init__(self): + self.argv_len = len(sys.argv) + # define commands dict. Prototype : + # { + # 'command': [comment, [list of arguments] ] + # } + # + # each argument is defined as a tuple: + # (argument name, help on argument, is mandatory) + # + self.commands = { + 'help': [ + _('Shows a help on specific command'), + [ + #User gets help for the command, specified by this parameter + (_('command'), + _('show help on command'), False) + ] + ], + 'toggle_roster_appearance': [ + _('Shows or hides the roster window'), + [] + ], + 'show_next_pending_event': [ + _('Pops up a window with the next pending event'), + [] + ], + 'list_contacts': [ + _('Prints a list of all contacts in the roster. Each contact ' + 'appears on a separate line'), + [ + (_('account'), _('show only contacts of the given account'), + False) + ] - ], - 'list_accounts': [ - _('Prints a list of registered accounts'), - [] - ], - 'change_status': [ - _('Changes the status of account or accounts'), - [ + ], + 'list_accounts': [ + _('Prints a list of registered accounts'), + [] + ], + 'change_status': [ + _('Changes the status of account or accounts'), + [ #offline, online, chat, away, xa, dnd, invisible should not be translated - (_('status'), _('one of: offline, online, chat, away, xa, dnd, invisible '), True), - (_('message'), _('status message'), False), - (_('account'), _('change status of account "account". ' - 'If not specified, try to change status of all accounts that have ' - '"sync with global status" option set'), False) - ] - ], - 'set_priority': [ - _('Changes the priority of account or accounts'), - [ - (_('priority'), _('priority you want to give to the account'), - True), - (_('account'), _('change the priority of the given account. ' - 'If not specified, change status of all accounts that have' - ' "sync with global status" option set'), False) - ] - ], - 'open_chat': [ - _('Shows the chat dialog so that you can send messages to a contact'), - [ - ('jid', _('JID of the contact that you want to chat with'), - True), - (_('account'), _('if specified, contact is taken from the ' - 'contact list of this account'), False), - (_('message'), - _('message content. The account must be specified or ""'), - False) - ] - ], - 'send_chat_message':[ - _('Sends new chat message to a contact in the roster. Both OpenPGP key ' - 'and account are optional. If you want to set only \'account\', ' - 'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'), - [ - ('jid', _('JID of the contact that will receive the message'), True), - (_('message'), _('message contents'), True), - (_('pgp key'), _('if specified, the message will be encrypted ' - 'using this public key'), False), - (_('account'), _('if specified, the message will be sent ' - 'using this account'), False), - ] - ], - 'send_single_message':[ - _('Sends new single message to a contact in the roster. Both OpenPGP key ' - 'and account are optional. If you want to set only \'account\', ' - 'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'), - [ - ('jid', _('JID of the contact that will receive the message'), True), - (_('subject'), _('message subject'), True), - (_('message'), _('message contents'), True), - (_('pgp key'), _('if specified, the message will be encrypted ' - 'using this public key'), False), - (_('account'), _('if specified, the message will be sent ' - 'using this account'), False), - ] - ], - 'send_groupchat_message':[ - _('Sends new message to a groupchat you\'ve joined.'), - [ - ('room_jid', _('JID of the room that will receive the message'), True), - (_('message'), _('message contents'), True), - (_('account'), _('if specified, the message will be sent ' - 'using this account'), False), - ] - ], - 'contact_info': [ - _('Gets detailed info on a contact'), - [ - ('jid', _('JID of the contact'), True) - ] - ], - 'account_info': [ - _('Gets detailed info on a account'), - [ - ('account', _('Name of the account'), True) - ] - ], - 'send_file': [ - _('Sends file to a contact'), - [ - (_('file'), _('File path'), True), - ('jid', _('JID of the contact'), True), - (_('account'), _('if specified, file will be sent using this ' - 'account'), False) - ] - ], - 'prefs_list': [ - _('Lists all preferences and their values'), - [ ] - ], - 'prefs_put': [ - _('Sets value of \'key\' to \'value\'.'), - [ - (_('key=value'), _('\'key\' is the name of the preference, ' - '\'value\' is the value to set it to'), True) - ] - ], - 'prefs_del': [ - _('Deletes a preference item'), - [ - (_('key'), _('name of the preference to be deleted'), True) - ] - ], - 'prefs_store': [ - _('Writes the current state of Gajim preferences to the .config ' - 'file'), - [ ] - ], - 'remove_contact': [ - _('Removes contact from roster'), - [ - ('jid', _('JID of the contact'), True), - (_('account'), _('if specified, contact is taken from the ' - 'contact list of this account'), False) + (_('status'), _('one of: offline, online, chat, away, xa, dnd, invisible '), True), + (_('message'), _('status message'), False), + (_('account'), _('change status of account "account". ' + 'If not specified, try to change status of all accounts that have ' + '"sync with global status" option set'), False) + ] + ], + 'set_priority': [ + _('Changes the priority of account or accounts'), + [ + (_('priority'), _('priority you want to give to the account'), + True), + (_('account'), _('change the priority of the given account. ' + 'If not specified, change status of all accounts that have' + ' "sync with global status" option set'), False) + ] + ], + 'open_chat': [ + _('Shows the chat dialog so that you can send messages to a contact'), + [ + ('jid', _('JID of the contact that you want to chat with'), + True), + (_('account'), _('if specified, contact is taken from the ' + 'contact list of this account'), False), + (_('message'), + _('message content. The account must be specified or ""'), + False) + ] + ], + 'send_chat_message': [ + _('Sends new chat message to a contact in the roster. Both OpenPGP key ' + 'and account are optional. If you want to set only \'account\', ' + 'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'), + [ + ('jid', _('JID of the contact that will receive the message'), True), + (_('message'), _('message contents'), True), + (_('pgp key'), _('if specified, the message will be encrypted ' + 'using this public key'), False), + (_('account'), _('if specified, the message will be sent ' + 'using this account'), False), + ] + ], + 'send_single_message': [ + _('Sends new single message to a contact in the roster. Both OpenPGP key ' + 'and account are optional. If you want to set only \'account\', ' + 'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'), + [ + ('jid', _('JID of the contact that will receive the message'), True), + (_('subject'), _('message subject'), True), + (_('message'), _('message contents'), True), + (_('pgp key'), _('if specified, the message will be encrypted ' + 'using this public key'), False), + (_('account'), _('if specified, the message will be sent ' + 'using this account'), False), + ] + ], + 'send_groupchat_message': [ + _('Sends new message to a groupchat you\'ve joined.'), + [ + ('room_jid', _('JID of the room that will receive the message'), True), + (_('message'), _('message contents'), True), + (_('account'), _('if specified, the message will be sent ' + 'using this account'), False), + ] + ], + 'contact_info': [ + _('Gets detailed info on a contact'), + [ + ('jid', _('JID of the contact'), True) + ] + ], + 'account_info': [ + _('Gets detailed info on a account'), + [ + ('account', _('Name of the account'), True) + ] + ], + 'send_file': [ + _('Sends file to a contact'), + [ + (_('file'), _('File path'), True), + ('jid', _('JID of the contact'), True), + (_('account'), _('if specified, file will be sent using this ' + 'account'), False) + ] + ], + 'prefs_list': [ + _('Lists all preferences and their values'), + [ ] + ], + 'prefs_put': [ + _('Sets value of \'key\' to \'value\'.'), + [ + (_('key=value'), _('\'key\' is the name of the preference, ' + '\'value\' is the value to set it to'), True) + ] + ], + 'prefs_del': [ + _('Deletes a preference item'), + [ + (_('key'), _('name of the preference to be deleted'), True) + ] + ], + 'prefs_store': [ + _('Writes the current state of Gajim preferences to the .config ' + 'file'), + [ ] + ], + 'remove_contact': [ + _('Removes contact from roster'), + [ + ('jid', _('JID of the contact'), True), + (_('account'), _('if specified, contact is taken from the ' + 'contact list of this account'), False) - ] - ], - 'add_contact': [ - _('Adds contact to roster'), - [ - (_('jid'), _('JID of the contact'), True), - (_('account'), _('Adds new contact to this account'), False) - ] - ], + ] + ], + 'add_contact': [ + _('Adds contact to roster'), + [ + (_('jid'), _('JID of the contact'), True), + (_('account'), _('Adds new contact to this account'), False) + ] + ], - 'get_status': [ - _('Returns current status (the global one unless account is specified)'), - [ - (_('account'), '', False) - ] - ], + 'get_status': [ + _('Returns current status (the global one unless account is specified)'), + [ + (_('account'), '', False) + ] + ], - 'get_status_message': [ - _('Returns current status message (the global one unless account is specified)'), - [ - (_('account'), '', False) - ] - ], + 'get_status_message': [ + _('Returns current status message (the global one unless account is specified)'), + [ + (_('account'), '', False) + ] + ], - 'get_unread_msgs_number': [ - _('Returns number of unread messages'), - [ ] - ], - 'start_chat': [ - _('Opens \'Start Chat\' dialog'), - [ - (_('account'), _('Starts chat, using this account'), True) - ] - ], - 'send_xml': [ - _('Sends custom XML'), - [ - ('xml', _('XML to send'), True), - ('account', _('Account in which the xml will be sent; ' - 'if not specified, xml will be sent to all accounts'), - False) - ] - ], - 'handle_uri': [ - _('Handle a xmpp:/ uri'), - [ - (_('uri'), _('URI to handle'), True), - (_('account'), _('Account in which you want to handle it'), - False), - (_('message'), _('Message content'), False) - ] - ], - 'join_room': [ - _('Join a MUC room'), - [ - (_('room'), _('Room JID'), True), - (_('nick'), _('Nickname to use'), False), - (_('password'), _('Password to enter the room'), False), - (_('account'), _('Account from which you want to enter the ' - 'room'), False) - ] - ], - 'check_gajim_running':[ - _('Check if Gajim is running'), - [] - ], - 'toggle_ipython' : [ - _('Shows or hides the ipython window'), - [] - ], + 'get_unread_msgs_number': [ + _('Returns number of unread messages'), + [ ] + ], + 'start_chat': [ + _('Opens \'Start Chat\' dialog'), + [ + (_('account'), _('Starts chat, using this account'), True) + ] + ], + 'send_xml': [ + _('Sends custom XML'), + [ + ('xml', _('XML to send'), True), + ('account', _('Account in which the xml will be sent; ' + 'if not specified, xml will be sent to all accounts'), + False) + ] + ], + 'handle_uri': [ + _('Handle a xmpp:/ uri'), + [ + (_('uri'), _('URI to handle'), True), + (_('account'), _('Account in which you want to handle it'), + False), + (_('message'), _('Message content'), False) + ] + ], + 'join_room': [ + _('Join a MUC room'), + [ + (_('room'), _('Room JID'), True), + (_('nick'), _('Nickname to use'), False), + (_('password'), _('Password to enter the room'), False), + (_('account'), _('Account from which you want to enter the ' + 'room'), False) + ] + ], + 'check_gajim_running': [ + _('Check if Gajim is running'), + [] + ], + 'toggle_ipython': [ + _('Shows or hides the ipython window'), + [] + ], - } + } - self.sbus = None - if self.argv_len < 2 or sys.argv[1] not in self.commands.keys(): - # no args or bad args - send_error(self.compose_help()) - self.command = sys.argv[1] - if self.command == 'help': - if self.argv_len == 3: - print self.help_on_command(sys.argv[2]).encode(PREFERRED_ENCODING) - else: - print self.compose_help().encode(PREFERRED_ENCODING) - sys.exit(0) - if self.command == 'handle_uri': - self.handle_uri() - if self.command == 'check_gajim_running': - print self.check_gajim_running() - sys.exit(0) - self.init_connection() - self.check_arguments() + self.sbus = None + if self.argv_len < 2 or sys.argv[1] not in self.commands.keys(): + # no args or bad args + send_error(self.compose_help()) + self.command = sys.argv[1] + if self.command == 'help': + if self.argv_len == 3: + print self.help_on_command(sys.argv[2]).encode(PREFERRED_ENCODING) + else: + print self.compose_help().encode(PREFERRED_ENCODING) + sys.exit(0) + if self.command == 'handle_uri': + self.handle_uri() + if self.command == 'check_gajim_running': + print self.check_gajim_running() + sys.exit(0) + self.init_connection() + self.check_arguments() - if self.command == 'contact_info': - if self.argv_len < 3: - send_error(_('Missing argument "contact_jid"')) + if self.command == 'contact_info': + if self.argv_len < 3: + send_error(_('Missing argument "contact_jid"')) - try: - res = self.call_remote_method() - except exceptions.ServiceNotAvailable: - # At this point an error message has already been displayed - sys.exit(1) - else: - self.print_result(res) + try: + res = self.call_remote_method() + except exceptions.ServiceNotAvailable: + # At this point an error message has already been displayed + sys.exit(1) + else: + self.print_result(res) - def print_result(self, res): - """ - Print retrieved result to the output - """ - if res is not None: - if self.command in ('open_chat', 'send_chat_message', 'send_single_message', 'start_chat'): - if self.command in ('send_message', 'send_single_message'): - self.argv_len -= 2 + def print_result(self, res): + """ + Print retrieved result to the output + """ + if res is not None: + if self.command in ('open_chat', 'send_chat_message', 'send_single_message', 'start_chat'): + if self.command in ('send_message', 'send_single_message'): + self.argv_len -= 2 - if res is False: - if self.argv_len < 4: - send_error(_('\'%s\' is not in your roster.\n' - 'Please specify account for sending the message.') % sys.argv[2]) - else: - send_error(_('You have no active account')) - elif self.command == 'list_accounts': - if isinstance(res, list): - for account in res: - if isinstance(account, unicode): - print account.encode(PREFERRED_ENCODING) - else: - print account - elif self.command == 'account_info': - if res: - print self.print_info(0, res, True) - elif self.command == 'list_contacts': - for account_dict in res: - print self.print_info(0, account_dict, True) - elif self.command == 'prefs_list': - pref_keys = sorted(res.keys()) - for pref_key in pref_keys: - result = '%s = %s' % (pref_key, res[pref_key]) - if isinstance(result, unicode): - print result.encode(PREFERRED_ENCODING) - else: - print result - elif self.command == 'contact_info': - print self.print_info(0, res, True) - elif res: - print unicode(res).encode(PREFERRED_ENCODING) + if res is False: + if self.argv_len < 4: + send_error(_('\'%s\' is not in your roster.\n' + 'Please specify account for sending the message.') % sys.argv[2]) + else: + send_error(_('You have no active account')) + elif self.command == 'list_accounts': + if isinstance(res, list): + for account in res: + if isinstance(account, unicode): + print account.encode(PREFERRED_ENCODING) + else: + print account + elif self.command == 'account_info': + if res: + print self.print_info(0, res, True) + elif self.command == 'list_contacts': + for account_dict in res: + print self.print_info(0, account_dict, True) + elif self.command == 'prefs_list': + pref_keys = sorted(res.keys()) + for pref_key in pref_keys: + result = '%s = %s' % (pref_key, res[pref_key]) + if isinstance(result, unicode): + print result.encode(PREFERRED_ENCODING) + else: + print result + elif self.command == 'contact_info': + print self.print_info(0, res, True) + elif res: + print unicode(res).encode(PREFERRED_ENCODING) - def check_gajim_running(self): - if not self.sbus: - try: - self.sbus = dbus.SessionBus() - except Exception: - raise exceptions.SessionBusNotPresent + def check_gajim_running(self): + if not self.sbus: + try: + self.sbus = dbus.SessionBus() + except Exception: + raise exceptions.SessionBusNotPresent - test = False - if hasattr(self.sbus, 'name_has_owner'): - if self.sbus.name_has_owner(SERVICE): - test = True - elif dbus.dbus_bindings.bus_name_has_owner(self.sbus.get_connection(), - SERVICE): - test = True - return test + test = False + if hasattr(self.sbus, 'name_has_owner'): + if self.sbus.name_has_owner(SERVICE): + test = True + elif dbus.dbus_bindings.bus_name_has_owner(self.sbus.get_connection(), + SERVICE): + test = True + return test - def init_connection(self): - """ - Create the onnection to the session dbus, or exit if it is not possible - """ - try: - self.sbus = dbus.SessionBus() - except Exception: - raise exceptions.SessionBusNotPresent + def init_connection(self): + """ + Create the onnection to the session dbus, or exit if it is not possible + """ + try: + self.sbus = dbus.SessionBus() + except Exception: + raise exceptions.SessionBusNotPresent - if not self.check_gajim_running(): - send_error(_('It seems Gajim is not running. So you can\'t use gajim-remote.')) - obj = self.sbus.get_object(SERVICE, OBJ_PATH) - interface = dbus.Interface(obj, INTERFACE) + if not self.check_gajim_running(): + send_error(_('It seems Gajim is not running. So you can\'t use gajim-remote.')) + obj = self.sbus.get_object(SERVICE, OBJ_PATH) + interface = dbus.Interface(obj, INTERFACE) - # get the function asked - self.method = interface.__getattr__(self.command) + # get the function asked + self.method = interface.__getattr__(self.command) - def make_arguments_row(self, args): - """ - Return arguments list. Mandatory arguments are enclosed with: - '<', '>', optional arguments - with '[', ']' - """ - s = '' - for arg in args: - if arg[2]: - s += ' <' + arg[0] + '>' - else: - s += ' [' + arg[0] + ']' - return s + def make_arguments_row(self, args): + """ + Return arguments list. Mandatory arguments are enclosed with: + '<', '>', optional arguments - with '[', ']' + """ + s = '' + for arg in args: + if arg[2]: + s += ' <' + arg[0] + '>' + else: + s += ' [' + arg[0] + ']' + return s - def help_on_command(self, command): - """ - Return help message for a given command - """ - if command in self.commands: - command_props = self.commands[command] - arguments_str = self.make_arguments_row(command_props[1]) - str_ = _('Usage: %(basename)s %(command)s %(arguments)s \n\t %(help)s')\ - % {'basename': BASENAME, 'command': command, - 'arguments': arguments_str, 'help': command_props[0]} - if len(command_props[1]) > 0: - str_ += '\n\n' + _('Arguments:') + '\n' - for argument in command_props[1]: - str_ += ' ' + argument[0] + ' - ' + argument[1] + '\n' - return str_ - send_error(_('%s not found') % command) + def help_on_command(self, command): + """ + Return help message for a given command + """ + if command in self.commands: + command_props = self.commands[command] + arguments_str = self.make_arguments_row(command_props[1]) + str_ = _('Usage: %(basename)s %(command)s %(arguments)s \n\t %(help)s')\ + % {'basename': BASENAME, 'command': command, + 'arguments': arguments_str, 'help': command_props[0]} + if len(command_props[1]) > 0: + str_ += '\n\n' + _('Arguments:') + '\n' + for argument in command_props[1]: + str_ += ' ' + argument[0] + ' - ' + argument[1] + '\n' + return str_ + send_error(_('%s not found') % command) - def compose_help(self): - """ - Print usage, and list available commands - """ - s = _('Usage: %s command [arguments]\nCommand is one of:\n' ) % BASENAME - for command in sorted(self.commands): - s += ' ' + command - for arg in self.commands[command][1]: - if arg[2]: - s += ' <' + arg[0] + '>' - else: - s += ' [' + arg[0] + ']' - s += '\n' - return s + def compose_help(self): + """ + Print usage, and list available commands + """ + s = _('Usage: %s command [arguments]\nCommand is one of:\n' ) % BASENAME + for command in sorted(self.commands): + s += ' ' + command + for arg in self.commands[command][1]: + if arg[2]: + s += ' <' + arg[0] + '>' + else: + s += ' [' + arg[0] + ']' + s += '\n' + return s - def print_info(self, level, prop_dict, encode_return = False): - """ - Return formated string from data structure - """ - if prop_dict is None or not isinstance(prop_dict, (dict, list, tuple)): - return '' - ret_str = '' - if isinstance(prop_dict, (list, tuple)): - ret_str = '' - spacing = ' ' * level * 4 - for val in prop_dict: - if val is None: - ret_str +='\t' - elif isinstance(val, int): - ret_str +='\t' + str(val) - elif isinstance(val, (str, unicode)): - ret_str +='\t' + val - elif isinstance(val, (list, tuple)): - res = '' - for items in val: - res += self.print_info(level+1, items) - if res != '': - ret_str += '\t' + res - elif isinstance(val, dict): - ret_str += self.print_info(level+1, val) - ret_str = '%s(%s)\n' % (spacing, ret_str[1:]) - elif isinstance(prop_dict, dict): - for key in prop_dict.keys(): - val = prop_dict[key] - spacing = ' ' * level * 4 - if isinstance(val, (unicode, int, str)): - if val is not None: - val = val.strip() - ret_str += '%s%-10s: %s\n' % (spacing, key, val) - elif isinstance(val, (list, tuple)): - res = '' - for items in val: - res += self.print_info(level+1, items) - if res != '': - ret_str += '%s%s: \n%s' % (spacing, key, res) - elif isinstance(val, dict): - res = self.print_info(level+1, val) - if res != '': - ret_str += '%s%s: \n%s' % (spacing, key, res) - if (encode_return): - try: - ret_str = ret_str.encode(PREFERRED_ENCODING) - except Exception: - pass - return ret_str + def print_info(self, level, prop_dict, encode_return = False): + """ + Return formated string from data structure + """ + if prop_dict is None or not isinstance(prop_dict, (dict, list, tuple)): + return '' + ret_str = '' + if isinstance(prop_dict, (list, tuple)): + ret_str = '' + spacing = ' ' * level * 4 + for val in prop_dict: + if val is None: + ret_str +='\t' + elif isinstance(val, int): + ret_str +='\t' + str(val) + elif isinstance(val, (str, unicode)): + ret_str +='\t' + val + elif isinstance(val, (list, tuple)): + res = '' + for items in val: + res += self.print_info(level+1, items) + if res != '': + ret_str += '\t' + res + elif isinstance(val, dict): + ret_str += self.print_info(level+1, val) + ret_str = '%s(%s)\n' % (spacing, ret_str[1:]) + elif isinstance(prop_dict, dict): + for key in prop_dict.keys(): + val = prop_dict[key] + spacing = ' ' * level * 4 + if isinstance(val, (unicode, int, str)): + if val is not None: + val = val.strip() + ret_str += '%s%-10s: %s\n' % (spacing, key, val) + elif isinstance(val, (list, tuple)): + res = '' + for items in val: + res += self.print_info(level+1, items) + if res != '': + ret_str += '%s%s: \n%s' % (spacing, key, res) + elif isinstance(val, dict): + res = self.print_info(level+1, val) + if res != '': + ret_str += '%s%s: \n%s' % (spacing, key, res) + if (encode_return): + try: + ret_str = ret_str.encode(PREFERRED_ENCODING) + except Exception: + pass + return ret_str - def check_arguments(self): - """ - Make check if all necessary arguments are given - """ - argv_len = self.argv_len - 2 - args = self.commands[self.command][1] - if len(args) < argv_len: - send_error(_('Too many arguments. \n' - 'Type "%(basename)s help %(command)s" for more info') % { - 'basename': BASENAME, 'command': self.command}) - if len(args) > argv_len: - if args[argv_len][2]: - send_error(_('Argument "%(arg)s" is not specified. \n' - 'Type "%(basename)s help %(command)s" for more info') % - {'arg': args[argv_len][0], 'basename': BASENAME, - 'command': self.command}) - self.arguments = [] - i = 0 - for arg in sys.argv[2:]: - i += 1 - if i < len(args): - self.arguments.append(arg) - else: - # it's latest argument with spaces - self.arguments.append(' '.join(sys.argv[i+1:])) - break - # add empty string for missing args - self.arguments += ['']*(len(args)-i) + def check_arguments(self): + """ + Make check if all necessary arguments are given + """ + argv_len = self.argv_len - 2 + args = self.commands[self.command][1] + if len(args) < argv_len: + send_error(_('Too many arguments. \n' + 'Type "%(basename)s help %(command)s" for more info') % { + 'basename': BASENAME, 'command': self.command}) + if len(args) > argv_len: + if args[argv_len][2]: + send_error(_('Argument "%(arg)s" is not specified. \n' + 'Type "%(basename)s help %(command)s" for more info') % + {'arg': args[argv_len][0], 'basename': BASENAME, + 'command': self.command}) + self.arguments = [] + i = 0 + for arg in sys.argv[2:]: + i += 1 + if i < len(args): + self.arguments.append(arg) + else: + # it's latest argument with spaces + self.arguments.append(' '.join(sys.argv[i+1:])) + break + # add empty string for missing args + self.arguments += ['']*(len(args)-i) - def handle_uri(self): - if not sys.argv[2].startswith('xmpp:'): - send_error(_('Wrong uri')) - sys.argv[2] = sys.argv[2][5:] - uri = sys.argv[2] - if not '?' in uri: - self.command = sys.argv[1] = 'open_chat' - return - if 'body=' in uri: - # Open chat window and paste the text in the input message dialog - self.command = sys.argv[1] = 'open_chat' - message = uri.split('body=') - message = message[1].split(';')[0] - try: - message = urllib.unquote(message) - except UnicodeDecodeError: - pass - sys.argv[2] = uri.split('?')[0] - if len(sys.argv) == 4: - # jid in the sys.argv - sys.argv.append(message) - else: - sys.argv.append('') - sys.argv.append(message) - sys.argv[3] = '' - sys.argv[4] = message - return - (jid, action) = uri.split('?', 1) - try: - jid = urllib.unquote(jid) - except UnicodeDecodeError: - pass - sys.argv[2] = jid - if action == 'join': - self.command = sys.argv[1] = 'join_room' - # Move account parameter from position 3 to 5 - sys.argv.append('') - sys.argv.append(sys.argv[3]) - sys.argv[3] = '' - return - if action.startswith('roster'): - # Add contact to roster - self.command = sys.argv[1] = 'add_contact' - return - sys.exit(0) + def handle_uri(self): + if not sys.argv[2].startswith('xmpp:'): + send_error(_('Wrong uri')) + sys.argv[2] = sys.argv[2][5:] + uri = sys.argv[2] + if not '?' in uri: + self.command = sys.argv[1] = 'open_chat' + return + if 'body=' in uri: + # Open chat window and paste the text in the input message dialog + self.command = sys.argv[1] = 'open_chat' + message = uri.split('body=') + message = message[1].split(';')[0] + try: + message = urllib.unquote(message) + except UnicodeDecodeError: + pass + sys.argv[2] = uri.split('?')[0] + if len(sys.argv) == 4: + # jid in the sys.argv + sys.argv.append(message) + else: + sys.argv.append('') + sys.argv.append(message) + sys.argv[3] = '' + sys.argv[4] = message + return + (jid, action) = uri.split('?', 1) + try: + jid = urllib.unquote(jid) + except UnicodeDecodeError: + pass + sys.argv[2] = jid + if action == 'join': + self.command = sys.argv[1] = 'join_room' + # Move account parameter from position 3 to 5 + sys.argv.append('') + sys.argv.append(sys.argv[3]) + sys.argv[3] = '' + return + if action.startswith('roster'): + # Add contact to roster + self.command = sys.argv[1] = 'add_contact' + return + sys.exit(0) - def call_remote_method(self): - """ - Calls self.method with arguments from sys.argv[2:] - """ - args = [i.decode(PREFERRED_ENCODING) for i in self.arguments] - args = [dbus.String(i) for i in args] - try: - res = self.method(*args) - return res - except Exception: - raise exceptions.ServiceNotAvailable - return None + def call_remote_method(self): + """ + Calls self.method with arguments from sys.argv[2:] + """ + args = [i.decode(PREFERRED_ENCODING) for i in self.arguments] + args = [dbus.String(i) for i in args] + try: + res = self.method(*args) + return res + except Exception: + raise exceptions.ServiceNotAvailable + return None if __name__ == '__main__': - GajimRemote() - -# vim: se ts=3: + GajimRemote() diff --git a/src/gajim.py b/src/gajim.py index a2e58f4e7..c1b5b578e 100644 --- a/src/gajim.py +++ b/src/gajim.py @@ -40,24 +40,24 @@ import sys import warnings if os.name == 'nt': - warnings.filterwarnings(action='ignore') + warnings.filterwarnings(action='ignore') - if os.path.isdir('gtk'): - # Used to create windows installer with GTK included - paths = os.environ['PATH'] - list_ = paths.split(';') - new_list = [] - for p in list_: - if p.find('gtk') < 0 and p.find('GTK') < 0: - new_list.append(p) - new_list.insert(0, 'gtk/lib') - new_list.insert(0, 'gtk/bin') - os.environ['PATH'] = ';'.join(new_list) - os.environ['GTK_BASEPATH'] = 'gtk' + if os.path.isdir('gtk'): + # Used to create windows installer with GTK included + paths = os.environ['PATH'] + list_ = paths.split(';') + new_list = [] + for p in list_: + if p.find('gtk') < 0 and p.find('GTK') < 0: + new_list.append(p) + new_list.insert(0, 'gtk/lib') + new_list.insert(0, 'gtk/bin') + os.environ['PATH'] = ';'.join(new_list) + os.environ['GTK_BASEPATH'] = 'gtk' if os.name == 'nt': - # needed for docutils - sys.path.append('.') + # needed for docutils + sys.path.append('.') from common import logging_helpers logging_helpers.init('TERM' in os.environ) @@ -70,32 +70,32 @@ import getopt from common import i18n def parseOpts(): - profile = '' - config_path = None + profile = '' + config_path = None - try: - shortargs = 'hqvl:p:c:' - longargs = 'help quiet verbose loglevel= profile= config_path=' - opts = getopt.getopt(sys.argv[1:], shortargs, longargs.split())[0] - except getopt.error, msg: - print msg - print 'for help use --help' - sys.exit(2) - for o, a in opts: - if o in ('-h', '--help'): - print 'gajim [--help] [--quiet] [--verbose] [--loglevel subsystem=level[,subsystem=level[...]]] [--profile name] [--config-path]' - sys.exit() - elif o in ('-q', '--quiet'): - logging_helpers.set_quiet() - elif o in ('-v', '--verbose'): - logging_helpers.set_verbose() - elif o in ('-p', '--profile'): # gajim --profile name - profile = a - elif o in ('-l', '--loglevel'): - logging_helpers.set_loglevels(a) - elif o in ('-c', '--config-path'): - config_path = a - return profile, config_path + try: + shortargs = 'hqvl:p:c:' + longargs = 'help quiet verbose loglevel= profile= config_path=' + opts = getopt.getopt(sys.argv[1:], shortargs, longargs.split())[0] + except getopt.error, msg: + print msg + print 'for help use --help' + sys.exit(2) + for o, a in opts: + if o in ('-h', '--help'): + print 'gajim [--help] [--quiet] [--verbose] [--loglevel subsystem=level[,subsystem=level[...]]] [--profile name] [--config-path]' + sys.exit() + elif o in ('-q', '--quiet'): + logging_helpers.set_quiet() + elif o in ('-v', '--verbose'): + logging_helpers.set_verbose() + elif o in ('-p', '--profile'): # gajim --profile name + profile = a + elif o in ('-l', '--loglevel'): + logging_helpers.set_loglevels(a) + elif o in ('-c', '--config-path'): + config_path = a + return profile, config_path profile, config_path = parseOpts() del parseOpts @@ -110,111 +110,111 @@ common.configpaths.gajimpaths.init_profile(profile) del profile if os.name == 'nt': - class MyStderr(object): - _file = None - _error = None - def write(self, text): - fname=os.path.join(common.configpaths.gajimpaths.root, - os.path.split(sys.executable)[1]+'.log') - if self._file is None and self._error is None: - try: - self._file = open(fname, 'a') - except Exception, details: - self._error = details - if self._file is not None: - self._file.write(text) - self._file.flush() - def flush(self): - if self._file is not None: - self._file.flush() + class MyStderr(object): + _file = None + _error = None + def write(self, text): + fname=os.path.join(common.configpaths.gajimpaths.root, + os.path.split(sys.executable)[1]+'.log') + if self._file is None and self._error is None: + try: + self._file = open(fname, 'a') + except Exception, details: + self._error = details + if self._file is not None: + self._file.write(text) + self._file.flush() + def flush(self): + if self._file is not None: + self._file.flush() - sys.stderr = MyStderr() + sys.stderr = MyStderr() # PyGTK2.10+ only throws a warning warnings.filterwarnings('error', module='gtk') try: - import gtk + import gtk except Warning, msg: - if str(msg) == 'could not open display': - print >> sys.stderr, _('Gajim needs X server to run. Quiting...') - else: - print >> sys.stderr, _('importing PyGTK failed: %s') % str(msg) - sys.exit() + if str(msg) == 'could not open display': + print >> sys.stderr, _('Gajim needs X server to run. Quiting...') + else: + print >> sys.stderr, _('importing PyGTK failed: %s') % str(msg) + sys.exit() warnings.resetwarnings() if os.name == 'nt': - warnings.filterwarnings(action='ignore') + warnings.filterwarnings(action='ignore') pritext = '' from common import exceptions try: - from common import gajim + from common import gajim except exceptions.DatabaseMalformed: - pritext = _('Database Error') - sectext = _('The database file (%s) cannot be read. Try to repair it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove it (all history will be lost).') % common.logger.LOG_DB_PATH + pritext = _('Database Error') + sectext = _('The database file (%s) cannot be read. Try to repair it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove it (all history will be lost).') % common.logger.LOG_DB_PATH else: - from common import dbus_support - if dbus_support.supported: - from music_track_listener import MusicTrackListener - import dbus + from common import dbus_support + if dbus_support.supported: + from music_track_listener import MusicTrackListener + import dbus - from ctypes import CDLL - from ctypes.util import find_library - import platform + from ctypes import CDLL + from ctypes.util import find_library + import platform - sysname = platform.system() - if sysname in ('Linux', 'FreeBSD', 'OpenBSD', 'NetBSD'): - libc = CDLL(find_library('c')) + sysname = platform.system() + if sysname in ('Linux', 'FreeBSD', 'OpenBSD', 'NetBSD'): + libc = CDLL(find_library('c')) - # The constant defined in which is used to set the name of - # the process. - PR_SET_NAME = 15 + # The constant defined in which is used to set the name of + # the process. + PR_SET_NAME = 15 - if sysname == 'Linux': - libc.prctl(PR_SET_NAME, 'gajim') - elif sysname in ('FreeBSD', 'OpenBSD', 'NetBSD'): - libc.setproctitle('gajim') + if sysname == 'Linux': + libc.prctl(PR_SET_NAME, 'gajim') + elif sysname in ('FreeBSD', 'OpenBSD', 'NetBSD'): + libc.setproctitle('gajim') - if gtk.pygtk_version < (2, 12, 0): - pritext = _('Gajim needs PyGTK 2.12 or above') - sectext = _('Gajim needs PyGTK 2.12 or above to run. Quiting...') - elif gtk.gtk_version < (2, 12, 0): - pritext = _('Gajim needs GTK 2.12 or above') - sectext = _('Gajim needs GTK 2.12 or above to run. Quiting...') + if gtk.pygtk_version < (2, 12, 0): + pritext = _('Gajim needs PyGTK 2.12 or above') + sectext = _('Gajim needs PyGTK 2.12 or above to run. Quiting...') + elif gtk.gtk_version < (2, 12, 0): + pritext = _('Gajim needs GTK 2.12 or above') + sectext = _('Gajim needs GTK 2.12 or above to run. Quiting...') - try: - import gtk.glade # check if user has libglade (in pygtk and in gtk) - except ImportError: - pritext = _('GTK+ runtime is missing libglade support') - if os.name == 'nt': - sectext = _('Please remove your current GTK+ runtime and install the latest stable version from %s') % 'http://gladewin32.sourceforge.net' - else: - sectext = _('Please make sure that GTK+ and PyGTK have libglade support in your system.') + try: + import gtk.glade # check if user has libglade (in pygtk and in gtk) + except ImportError: + pritext = _('GTK+ runtime is missing libglade support') + if os.name == 'nt': + sectext = _('Please remove your current GTK+ runtime and install the latest stable version from %s') % 'http://gladewin32.sourceforge.net' + else: + sectext = _('Please make sure that GTK+ and PyGTK have libglade support in your system.') - try: - from common import check_paths - except exceptions.PysqliteNotAvailable, e: - pritext = _('Gajim needs PySQLite2 to run') - sectext = str(e) + try: + from common import check_paths + except exceptions.PysqliteNotAvailable, e: + pritext = _('Gajim needs PySQLite2 to run') + sectext = str(e) - if os.name == 'nt': - try: - import winsound # windows-only built-in module for playing wav - import win32api # do NOT remove. we req this module - except Exception: - pritext = _('Gajim needs pywin32 to run') - sectext = _('Please make sure that Pywin32 is installed on your system. You can get it at %s') % 'http://sourceforge.net/project/showfiles.php?group_id=78018' + if os.name == 'nt': + try: + import winsound # windows-only built-in module for playing wav + import win32api # do NOT remove. we req this module + except Exception: + pritext = _('Gajim needs pywin32 to run') + sectext = _('Please make sure that Pywin32 is installed on your system. You can get it at %s') % 'http://sourceforge.net/project/showfiles.php?group_id=78018' if pritext: - dlg = gtk.MessageDialog(None, - gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, - gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message_format = pritext) + dlg = gtk.MessageDialog(None, + gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, + gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message_format = pritext) - dlg.format_secondary_text(sectext) - dlg.run() - dlg.destroy() - sys.exit() + dlg.format_secondary_text(sectext) + dlg.run() + dlg.destroy() + sys.exit() del pritext @@ -222,9 +222,9 @@ import gtkexcepthook import gobject if not hasattr(gobject, 'timeout_add_seconds'): - def timeout_add_seconds_fake(time_sec, *args): - return gobject.timeout_add(time_sec * 1000, *args) - gobject.timeout_add_seconds = timeout_add_seconds_fake + def timeout_add_seconds_fake(time_sec, *args): + return gobject.timeout_add(time_sec * 1000, *args) + gobject.timeout_add_seconds = timeout_add_seconds_fake import signal @@ -240,183 +240,181 @@ import errno import dialogs def pid_alive(): - try: - pf = open(pid_filename) - except IOError: - # probably file not found - return False + try: + pf = open(pid_filename) + except IOError: + # probably file not found + return False - try: - pid = int(pf.read().strip()) - pf.close() - except Exception: - traceback.print_exc() - # PID file exists, but something happened trying to read PID - # Could be 0.10 style empty PID file, so assume Gajim is running - return True + try: + pid = int(pf.read().strip()) + pf.close() + except Exception: + traceback.print_exc() + # PID file exists, but something happened trying to read PID + # Could be 0.10 style empty PID file, so assume Gajim is running + return True - if os.name == 'nt': - try: - from ctypes import (windll, c_ulong, c_int, Structure, c_char, POINTER, pointer, ) - except Exception: - return True + if os.name == 'nt': + try: + from ctypes import (windll, c_ulong, c_int, Structure, c_char, POINTER, pointer, ) + except Exception: + return True - class PROCESSENTRY32(Structure): - _fields_ = [ - ('dwSize', c_ulong, ), - ('cntUsage', c_ulong, ), - ('th32ProcessID', c_ulong, ), - ('th32DefaultHeapID', c_ulong, ), - ('th32ModuleID', c_ulong, ), - ('cntThreads', c_ulong, ), - ('th32ParentProcessID', c_ulong, ), - ('pcPriClassBase', c_ulong, ), - ('dwFlags', c_ulong, ), - ('szExeFile', c_char*512, ), - ] - def __init__(self): - Structure.__init__(self, 512+9*4) + class PROCESSENTRY32(Structure): + _fields_ = [ + ('dwSize', c_ulong, ), + ('cntUsage', c_ulong, ), + ('th32ProcessID', c_ulong, ), + ('th32DefaultHeapID', c_ulong, ), + ('th32ModuleID', c_ulong, ), + ('cntThreads', c_ulong, ), + ('th32ParentProcessID', c_ulong, ), + ('pcPriClassBase', c_ulong, ), + ('dwFlags', c_ulong, ), + ('szExeFile', c_char*512, ), + ] + def __init__(self): + Structure.__init__(self, 512+9*4) - k = windll.kernel32 - k.CreateToolhelp32Snapshot.argtypes = c_ulong, c_ulong, - k.CreateToolhelp32Snapshot.restype = c_int - k.Process32First.argtypes = c_int, POINTER(PROCESSENTRY32), - k.Process32First.restype = c_int - k.Process32Next.argtypes = c_int, POINTER(PROCESSENTRY32), - k.Process32Next.restype = c_int + k = windll.kernel32 + k.CreateToolhelp32Snapshot.argtypes = c_ulong, c_ulong, + k.CreateToolhelp32Snapshot.restype = c_int + k.Process32First.argtypes = c_int, POINTER(PROCESSENTRY32), + k.Process32First.restype = c_int + k.Process32Next.argtypes = c_int, POINTER(PROCESSENTRY32), + k.Process32Next.restype = c_int - def get_p(p): - h = k.CreateToolhelp32Snapshot(2, 0) # TH32CS_SNAPPROCESS - assert h > 0, 'CreateToolhelp32Snapshot failed' - b = pointer(PROCESSENTRY32()) - f = k.Process32First(h, b) - while f: - if b.contents.th32ProcessID == p: - return b.contents.szExeFile - f = k.Process32Next(h, b) + def get_p(p): + h = k.CreateToolhelp32Snapshot(2, 0) # TH32CS_SNAPPROCESS + assert h > 0, 'CreateToolhelp32Snapshot failed' + b = pointer(PROCESSENTRY32()) + f = k.Process32First(h, b) + while f: + if b.contents.th32ProcessID == p: + return b.contents.szExeFile + f = k.Process32Next(h, b) - if get_p(pid) in ('python.exe', 'gajim.exe'): - return True - return False - try: - if not os.path.exists('/proc'): - return True # no /proc, assume Gajim is running + if get_p(pid) in ('python.exe', 'gajim.exe'): + return True + return False + try: + if not os.path.exists('/proc'): + return True # no /proc, assume Gajim is running - try: - f = open('/proc/%d/cmdline'% pid) - except IOError, e: - if e.errno == errno.ENOENT: - return False # file/pid does not exist - raise + try: + f = open('/proc/%d/cmdline'% pid) + except IOError, e: + if e.errno == errno.ENOENT: + return False # file/pid does not exist + raise - n = f.read().lower() - f.close() - if n.find('gajim') < 0: - return False - return True # Running Gajim found at pid - except Exception: - traceback.print_exc() + n = f.read().lower() + f.close() + if n.find('gajim') < 0: + return False + return True # Running Gajim found at pid + except Exception: + traceback.print_exc() - # If we are here, pidfile exists, but some unexpected error occured. - # Assume Gajim is running. - return True + # If we are here, pidfile exists, but some unexpected error occured. + # Assume Gajim is running. + return True if pid_alive(): - path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps/gajim.png') - pix = gtk.gdk.pixbuf_new_from_file(path_to_file) - gtk.window_set_default_icon(pix) # set the icon to all newly opened wind - pritext = _('Gajim is already running') - sectext = _('Another instance of Gajim seems to be running\nRun anyway?') - dialog = dialogs.YesNoDialog(pritext, sectext) - dialog.popup() - if dialog.run() != gtk.RESPONSE_YES: - sys.exit(3) - dialog.destroy() - # run anyway, delete pid and useless global vars - if os.path.exists(pid_filename): - os.remove(pid_filename) - del path_to_file - del pix - del pritext - del sectext - dialog.destroy() + path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps/gajim.png') + pix = gtk.gdk.pixbuf_new_from_file(path_to_file) + gtk.window_set_default_icon(pix) # set the icon to all newly opened wind + pritext = _('Gajim is already running') + sectext = _('Another instance of Gajim seems to be running\nRun anyway?') + dialog = dialogs.YesNoDialog(pritext, sectext) + dialog.popup() + if dialog.run() != gtk.RESPONSE_YES: + sys.exit(3) + dialog.destroy() + # run anyway, delete pid and useless global vars + if os.path.exists(pid_filename): + os.remove(pid_filename) + del path_to_file + del pix + del pritext + del sectext + dialog.destroy() # Create .gajim dir pid_dir = os.path.dirname(pid_filename) if not os.path.exists(pid_dir): - check_paths.create_path(pid_dir) + check_paths.create_path(pid_dir) # Create pid file try: - f = open(pid_filename, 'w') - f.write(str(os.getpid())) - f.close() + f = open(pid_filename, 'w') + f.write(str(os.getpid())) + f.close() except IOError, e: - dlg = dialogs.ErrorDialog(_('Disk Write Error'), str(e)) - dlg.run() - dlg.destroy() - sys.exit() + dlg = dialogs.ErrorDialog(_('Disk Write Error'), str(e)) + dlg.run() + dlg.destroy() + sys.exit() del pid_dir del f def on_exit(): - # delete pid file on normal exit - if os.path.exists(pid_filename): - os.remove(pid_filename) - # Shutdown GUI and save config - gajim.interface.roster.prepare_quit() + # delete pid file on normal exit + if os.path.exists(pid_filename): + os.remove(pid_filename) + # Shutdown GUI and save config + gajim.interface.roster.prepare_quit() import atexit atexit.register(on_exit) - + from gui_interface import Interface if __name__ == '__main__': - def sigint_cb(num, stack): - sys.exit(5) - # ^C exits the application normally to delete pid file - signal.signal(signal.SIGINT, sigint_cb) + def sigint_cb(num, stack): + sys.exit(5) + # ^C exits the application normally to delete pid file + signal.signal(signal.SIGINT, sigint_cb) - log.info("Encodings: d:%s, fs:%s, p:%s", sys.getdefaultencoding(), \ - sys.getfilesystemencoding(), locale.getpreferredencoding()) + log.info("Encodings: d:%s, fs:%s, p:%s", sys.getdefaultencoding(), \ + sys.getfilesystemencoding(), locale.getpreferredencoding()) - if os.name != 'nt': - # Session Management support - try: - import gnome.ui - except ImportError: - pass - else: - def die_cb(cli): - gajim.interface.roster.quit_gtkgui_interface() - gnome.program_init('gajim', gajim.version) - cli = gnome.ui.master_client() - cli.connect('die', die_cb) + if os.name != 'nt': + # Session Management support + try: + import gnome.ui + except ImportError: + pass + else: + def die_cb(cli): + gajim.interface.roster.quit_gtkgui_interface() + gnome.program_init('gajim', gajim.version) + cli = gnome.ui.master_client() + cli.connect('die', die_cb) - path_to_gajim_script = gtkgui_helpers.get_abspath_for_script( - 'gajim') + path_to_gajim_script = gtkgui_helpers.get_abspath_for_script( + 'gajim') - if path_to_gajim_script: - argv = [path_to_gajim_script] - # FIXME: remove this typeerror catch when gnome python is old and - # not bad patched by distro men [2.12.0 + should not need all that - # NORMALLY] - try: - cli.set_restart_command(argv) - except AttributeError: - cli.set_restart_command(len(argv), argv) + if path_to_gajim_script: + argv = [path_to_gajim_script] + # FIXME: remove this typeerror catch when gnome python is old and + # not bad patched by distro men [2.12.0 + should not need all that + # NORMALLY] + try: + cli.set_restart_command(argv) + except AttributeError: + cli.set_restart_command(len(argv), argv) - check_paths.check_and_possibly_create_paths() + check_paths.check_and_possibly_create_paths() - interface = Interface() - interface.run() + interface = Interface() + interface.run() - try: - if os.name != 'nt': - # This makes Gajim unusable under windows, and threads are used only - # for GPG, so not under windows - gtk.gdk.threads_init() - gtk.main() - except KeyboardInterrupt: - print >> sys.stderr, 'KeyboardInterrupt' - -# vim: se ts=3: + try: + if os.name != 'nt': + # This makes Gajim unusable under windows, and threads are used only + # for GPG, so not under windows + gtk.gdk.threads_init() + gtk.main() + except KeyboardInterrupt: + print >> sys.stderr, 'KeyboardInterrupt' diff --git a/src/gajim_themes_window.py b/src/gajim_themes_window.py index e6c23929a..e8b4068f5 100644 --- a/src/gajim_themes_window.py +++ b/src/gajim_themes_window.py @@ -31,378 +31,376 @@ from common import gajim class GajimThemesWindow: - def __init__(self): - self.xml = gtkgui_helpers.get_glade('gajim_themes_window.glade') - self.window = self.xml.get_widget('gajim_themes_window') - self.window.set_transient_for(gajim.interface.roster.window) + def __init__(self): + self.xml = gtkgui_helpers.get_glade('gajim_themes_window.glade') + self.window = self.xml.get_widget('gajim_themes_window') + self.window.set_transient_for(gajim.interface.roster.window) - self.options = ['account', 'group', 'contact', 'banner'] - self.options_combobox = self.xml.get_widget('options_combobox') - self.textcolor_checkbutton = self.xml.get_widget('textcolor_checkbutton') - self.background_checkbutton = self.xml.get_widget('background_checkbutton') - self.textfont_checkbutton = self.xml.get_widget('textfont_checkbutton') - self.text_colorbutton = self.xml.get_widget('text_colorbutton') - self.background_colorbutton = self.xml.get_widget('background_colorbutton') - self.text_fontbutton = self.xml.get_widget('text_fontbutton') - self.bold_togglebutton = self.xml.get_widget('bold_togglebutton') - self.italic_togglebutton = self.xml.get_widget('italic_togglebutton') - self.themes_tree = self.xml.get_widget('themes_treeview') - self.theme_options_vbox = self.xml.get_widget('theme_options_vbox') - self.theme_options_table = self.xml.get_widget('theme_options_table') - self.colorbuttons = {} - for chatstate in ('inactive', 'composing', 'paused', 'gone', - 'muc_msg', 'muc_directed_msg'): - self.colorbuttons[chatstate] = self.xml.get_widget(chatstate + \ - '_colorbutton') - model = gtk.ListStore(str) - self.themes_tree.set_model(model) - col = gtk.TreeViewColumn(_('Theme')) - self.themes_tree.append_column(col) - renderer = gtk.CellRendererText() - col.pack_start(renderer, True) - col.set_attributes(renderer, text = 0) - renderer.connect('edited', self.on_theme_cell_edited) - renderer.set_property('editable', True) - self.current_theme = gajim.config.get('roster_theme') - self.no_update = False - self.fill_themes_treeview() - self.select_active_theme() - self.current_option = self.options[0] - self.set_theme_options(self.current_theme, self.current_option) + self.options = ['account', 'group', 'contact', 'banner'] + self.options_combobox = self.xml.get_widget('options_combobox') + self.textcolor_checkbutton = self.xml.get_widget('textcolor_checkbutton') + self.background_checkbutton = self.xml.get_widget('background_checkbutton') + self.textfont_checkbutton = self.xml.get_widget('textfont_checkbutton') + self.text_colorbutton = self.xml.get_widget('text_colorbutton') + self.background_colorbutton = self.xml.get_widget('background_colorbutton') + self.text_fontbutton = self.xml.get_widget('text_fontbutton') + self.bold_togglebutton = self.xml.get_widget('bold_togglebutton') + self.italic_togglebutton = self.xml.get_widget('italic_togglebutton') + self.themes_tree = self.xml.get_widget('themes_treeview') + self.theme_options_vbox = self.xml.get_widget('theme_options_vbox') + self.theme_options_table = self.xml.get_widget('theme_options_table') + self.colorbuttons = {} + for chatstate in ('inactive', 'composing', 'paused', 'gone', + 'muc_msg', 'muc_directed_msg'): + self.colorbuttons[chatstate] = self.xml.get_widget(chatstate + \ + '_colorbutton') + model = gtk.ListStore(str) + self.themes_tree.set_model(model) + col = gtk.TreeViewColumn(_('Theme')) + self.themes_tree.append_column(col) + renderer = gtk.CellRendererText() + col.pack_start(renderer, True) + col.set_attributes(renderer, text = 0) + renderer.connect('edited', self.on_theme_cell_edited) + renderer.set_property('editable', True) + self.current_theme = gajim.config.get('roster_theme') + self.no_update = False + self.fill_themes_treeview() + self.select_active_theme() + self.current_option = self.options[0] + self.set_theme_options(self.current_theme, self.current_option) - self.xml.signal_autoconnect(self) - self.window.connect('delete-event', self.on_themese_window_delete_event) - self.themes_tree.get_selection().connect('changed', - self.selection_changed) - self.window.show_all() + self.xml.signal_autoconnect(self) + self.window.connect('delete-event', self.on_themese_window_delete_event) + self.themes_tree.get_selection().connect('changed', + self.selection_changed) + self.window.show_all() - def on_themese_window_delete_event(self, widget, event): - self.window.hide() - return True # do NOT destroy the window + def on_themese_window_delete_event(self, widget, event): + self.window.hide() + return True # do NOT destroy the window - def on_close_button_clicked(self, widget): - if 'preferences' in gajim.interface.instances: - gajim.interface.instances['preferences'].update_theme_list() - self.window.hide() + def on_close_button_clicked(self, widget): + if 'preferences' in gajim.interface.instances: + gajim.interface.instances['preferences'].update_theme_list() + self.window.hide() - def on_theme_cell_edited(self, cell, row, new_name): - model = self.themes_tree.get_model() - iter_ = model.get_iter_from_string(row) - old_name = model.get_value(iter_, 0).decode('utf-8') - new_name = new_name.decode('utf-8') - if old_name == new_name: - return - if old_name == 'default': - dialogs.ErrorDialog( - _('You cannot make changes to the default theme'), - _('Please create a clean new theme with your desired name.')) - return - new_config_name = new_name.replace(' ', '_') - if new_config_name in gajim.config.get_per('themes'): - return - gajim.config.add_per('themes', new_config_name) - # Copy old theme values - old_config_name = old_name.replace(' ', '_') - properties = ['textcolor', 'bgcolor', 'font', 'fontattrs'] - gajim.config.add_per('themes', new_config_name) - for option in self.options: - for property_ in properties: - option_name = option + property_ - gajim.config.set_per('themes', new_config_name, option_name, - gajim.config.get_per('themes', old_config_name, option_name)) - gajim.config.del_per('themes', old_config_name) - if old_config_name == gajim.config.get('roster_theme'): - gajim.config.set('roster_theme', new_config_name) - model.set_value(iter_, 0, new_name) - self.current_theme = new_name + def on_theme_cell_edited(self, cell, row, new_name): + model = self.themes_tree.get_model() + iter_ = model.get_iter_from_string(row) + old_name = model.get_value(iter_, 0).decode('utf-8') + new_name = new_name.decode('utf-8') + if old_name == new_name: + return + if old_name == 'default': + dialogs.ErrorDialog( + _('You cannot make changes to the default theme'), + _('Please create a clean new theme with your desired name.')) + return + new_config_name = new_name.replace(' ', '_') + if new_config_name in gajim.config.get_per('themes'): + return + gajim.config.add_per('themes', new_config_name) + # Copy old theme values + old_config_name = old_name.replace(' ', '_') + properties = ['textcolor', 'bgcolor', 'font', 'fontattrs'] + gajim.config.add_per('themes', new_config_name) + for option in self.options: + for property_ in properties: + option_name = option + property_ + gajim.config.set_per('themes', new_config_name, option_name, + gajim.config.get_per('themes', old_config_name, option_name)) + gajim.config.del_per('themes', old_config_name) + if old_config_name == gajim.config.get('roster_theme'): + gajim.config.set('roster_theme', new_config_name) + model.set_value(iter_, 0, new_name) + self.current_theme = new_name - def fill_themes_treeview(self): - model = self.themes_tree.get_model() - model.clear() - for config_theme in gajim.config.get_per('themes'): - theme = config_theme.replace('_', ' ') - model.append([theme]) + def fill_themes_treeview(self): + model = self.themes_tree.get_model() + model.clear() + for config_theme in gajim.config.get_per('themes'): + theme = config_theme.replace('_', ' ') + model.append([theme]) - def select_active_theme(self): - model = self.themes_tree.get_model() - iter_ = model.get_iter_root() - active_theme = gajim.config.get('roster_theme').replace('_', ' ') - while iter_: - theme = model[iter_][0] - if theme == active_theme: - self.themes_tree.get_selection().select_iter(iter_) - if active_theme == 'default': - self.xml.get_widget('remove_button').set_sensitive(False) - self.theme_options_vbox.set_sensitive(False) - self.theme_options_table.set_sensitive(False) - else: - self.xml.get_widget('remove_button').set_sensitive(True) - self.theme_options_vbox.set_sensitive(True) - self.theme_options_table.set_sensitive(True) - break - iter_ = model.iter_next(iter_) + def select_active_theme(self): + model = self.themes_tree.get_model() + iter_ = model.get_iter_root() + active_theme = gajim.config.get('roster_theme').replace('_', ' ') + while iter_: + theme = model[iter_][0] + if theme == active_theme: + self.themes_tree.get_selection().select_iter(iter_) + if active_theme == 'default': + self.xml.get_widget('remove_button').set_sensitive(False) + self.theme_options_vbox.set_sensitive(False) + self.theme_options_table.set_sensitive(False) + else: + self.xml.get_widget('remove_button').set_sensitive(True) + self.theme_options_vbox.set_sensitive(True) + self.theme_options_table.set_sensitive(True) + break + iter_ = model.iter_next(iter_) - def selection_changed(self, widget = None): - (model, iter_) = self.themes_tree.get_selection().get_selected() - selected = self.themes_tree.get_selection().get_selected_rows() - if not iter_ or selected[1] == []: - self.theme_options_vbox.set_sensitive(False) - self.theme_options_table.set_sensitive(False) - return - self.current_theme = model.get_value(iter_, 0).decode('utf-8') - self.current_theme = self.current_theme.replace(' ', '_') - self.set_theme_options(self.current_theme) - if self.current_theme == 'default': - self.xml.get_widget('remove_button').set_sensitive(False) - self.theme_options_vbox.set_sensitive(False) - self.theme_options_table.set_sensitive(False) - else: - self.xml.get_widget('remove_button').set_sensitive(True) - self.theme_options_vbox.set_sensitive(True) - self.theme_options_table.set_sensitive(True) + def selection_changed(self, widget = None): + (model, iter_) = self.themes_tree.get_selection().get_selected() + selected = self.themes_tree.get_selection().get_selected_rows() + if not iter_ or selected[1] == []: + self.theme_options_vbox.set_sensitive(False) + self.theme_options_table.set_sensitive(False) + return + self.current_theme = model.get_value(iter_, 0).decode('utf-8') + self.current_theme = self.current_theme.replace(' ', '_') + self.set_theme_options(self.current_theme) + if self.current_theme == 'default': + self.xml.get_widget('remove_button').set_sensitive(False) + self.theme_options_vbox.set_sensitive(False) + self.theme_options_table.set_sensitive(False) + else: + self.xml.get_widget('remove_button').set_sensitive(True) + self.theme_options_vbox.set_sensitive(True) + self.theme_options_table.set_sensitive(True) - def on_add_button_clicked(self, widget): - model = self.themes_tree.get_model() - iter_ = model.append() - i = 0 - # don't confuse translators - theme_name = _('theme name') - theme_name_ns = theme_name.replace(' ', '_') - while theme_name_ns + unicode(i) in gajim.config.get_per('themes'): - i += 1 - model.set_value(iter_, 0, theme_name + unicode(i)) - gajim.config.add_per('themes', theme_name_ns + unicode(i)) - self.themes_tree.get_selection().select_iter(iter_) - col = self.themes_tree.get_column(0) - path = model.get_path(iter_) - self.themes_tree.set_cursor(path, col, True) + def on_add_button_clicked(self, widget): + model = self.themes_tree.get_model() + iter_ = model.append() + i = 0 + # don't confuse translators + theme_name = _('theme name') + theme_name_ns = theme_name.replace(' ', '_') + while theme_name_ns + unicode(i) in gajim.config.get_per('themes'): + i += 1 + model.set_value(iter_, 0, theme_name + unicode(i)) + gajim.config.add_per('themes', theme_name_ns + unicode(i)) + self.themes_tree.get_selection().select_iter(iter_) + col = self.themes_tree.get_column(0) + path = model.get_path(iter_) + self.themes_tree.set_cursor(path, col, True) - def on_remove_button_clicked(self, widget): - (model, iter_) = self.themes_tree.get_selection().get_selected() - if not iter_: - return - if self.current_theme == gajim.config.get('roster_theme'): - dialogs.ErrorDialog( - _('You cannot delete your current theme'), - _('Please first choose another for your current theme.')) - return - self.theme_options_vbox.set_sensitive(False) - self.theme_options_table.set_sensitive(False) - self.xml.get_widget('remove_button').set_sensitive(False) - gajim.config.del_per('themes', self.current_theme) - model.remove(iter_) + def on_remove_button_clicked(self, widget): + (model, iter_) = self.themes_tree.get_selection().get_selected() + if not iter_: + return + if self.current_theme == gajim.config.get('roster_theme'): + dialogs.ErrorDialog( + _('You cannot delete your current theme'), + _('Please first choose another for your current theme.')) + return + self.theme_options_vbox.set_sensitive(False) + self.theme_options_table.set_sensitive(False) + self.xml.get_widget('remove_button').set_sensitive(False) + gajim.config.del_per('themes', self.current_theme) + model.remove(iter_) - def set_theme_options(self, theme, option = 'account'): - self.no_update = True - self.options_combobox.set_active(self.options.index(option)) - textcolor = gajim.config.get_per('themes', theme, option + 'textcolor') - if textcolor: - state = True - self.text_colorbutton.set_color(gtk.gdk.color_parse(textcolor)) - else: - state = False - self.textcolor_checkbutton.set_active(state) - self.text_colorbutton.set_sensitive(state) - bgcolor = gajim.config.get_per('themes', theme, option + 'bgcolor') - if bgcolor: - state = True - self.background_colorbutton.set_color(gtk.gdk.color_parse( - bgcolor)) - else: - state = False - self.background_checkbutton.set_active(state) - self.background_colorbutton.set_sensitive(state) + def set_theme_options(self, theme, option = 'account'): + self.no_update = True + self.options_combobox.set_active(self.options.index(option)) + textcolor = gajim.config.get_per('themes', theme, option + 'textcolor') + if textcolor: + state = True + self.text_colorbutton.set_color(gtk.gdk.color_parse(textcolor)) + else: + state = False + self.textcolor_checkbutton.set_active(state) + self.text_colorbutton.set_sensitive(state) + bgcolor = gajim.config.get_per('themes', theme, option + 'bgcolor') + if bgcolor: + state = True + self.background_colorbutton.set_color(gtk.gdk.color_parse( + bgcolor)) + else: + state = False + self.background_checkbutton.set_active(state) + self.background_colorbutton.set_sensitive(state) - # get the font name before we set widgets and it will not be overriden - font_name = gajim.config.get_per('themes', theme, option + 'font') - font_attrs = gajim.config.get_per('themes', theme, option + 'fontattrs') - self._set_font_widgets(font_attrs) - if font_name: - state = True - self.text_fontbutton.set_font_name(font_name) - else: - state = False - self.textfont_checkbutton.set_active(state) - self.text_fontbutton.set_sensitive(state) - self.no_update = False - gajim.interface.roster.change_roster_style(None) + # get the font name before we set widgets and it will not be overriden + font_name = gajim.config.get_per('themes', theme, option + 'font') + font_attrs = gajim.config.get_per('themes', theme, option + 'fontattrs') + self._set_font_widgets(font_attrs) + if font_name: + state = True + self.text_fontbutton.set_font_name(font_name) + else: + state = False + self.textfont_checkbutton.set_active(state) + self.text_fontbutton.set_sensitive(state) + self.no_update = False + gajim.interface.roster.change_roster_style(None) - for chatstate in ('inactive', 'composing', 'paused', 'gone', - 'muc_msg', 'muc_directed_msg'): - color = gajim.config.get_per('themes', theme, 'state_' + chatstate + \ - '_color') - self.colorbuttons[chatstate].set_color(gtk.gdk.color_parse(color)) + for chatstate in ('inactive', 'composing', 'paused', 'gone', + 'muc_msg', 'muc_directed_msg'): + color = gajim.config.get_per('themes', theme, 'state_' + chatstate + \ + '_color') + self.colorbuttons[chatstate].set_color(gtk.gdk.color_parse(color)) - def on_textcolor_checkbutton_toggled(self, widget): - state = widget.get_active() - self.text_colorbutton.set_sensitive(state) - self._set_color(state, self.text_colorbutton, - 'textcolor') + def on_textcolor_checkbutton_toggled(self, widget): + state = widget.get_active() + self.text_colorbutton.set_sensitive(state) + self._set_color(state, self.text_colorbutton, + 'textcolor') - def on_background_checkbutton_toggled(self, widget): - state = widget.get_active() - self.background_colorbutton.set_sensitive(state) - self._set_color(state, self.background_colorbutton, - 'bgcolor') + def on_background_checkbutton_toggled(self, widget): + state = widget.get_active() + self.background_colorbutton.set_sensitive(state) + self._set_color(state, self.background_colorbutton, + 'bgcolor') - def on_textfont_checkbutton_toggled(self, widget): - self.text_fontbutton.set_sensitive(widget.get_active()) - self._set_font() + def on_textfont_checkbutton_toggled(self, widget): + self.text_fontbutton.set_sensitive(widget.get_active()) + self._set_font() - def on_text_colorbutton_color_set(self, widget): - self._set_color(True, widget, 'textcolor') + def on_text_colorbutton_color_set(self, widget): + self._set_color(True, widget, 'textcolor') - def on_background_colorbutton_color_set(self, widget): - self._set_color(True, widget, 'bgcolor') + def on_background_colorbutton_color_set(self, widget): + self._set_color(True, widget, 'bgcolor') - def on_text_fontbutton_font_set(self, widget): - self._set_font() + def on_text_fontbutton_font_set(self, widget): + self._set_font() - def on_options_combobox_changed(self, widget): - index = self.options_combobox.get_active() - if index == -1: - return - self.current_option = self.options[index] - self.set_theme_options(self.current_theme, - self.current_option) + def on_options_combobox_changed(self, widget): + index = self.options_combobox.get_active() + if index == -1: + return + self.current_option = self.options[index] + self.set_theme_options(self.current_theme, + self.current_option) - def on_bold_togglebutton_toggled(self, widget): - if not self.no_update: - self._set_font() + def on_bold_togglebutton_toggled(self, widget): + if not self.no_update: + self._set_font() - def on_italic_togglebutton_toggled(self, widget): - if not self.no_update: - self._set_font() + def on_italic_togglebutton_toggled(self, widget): + if not self.no_update: + self._set_font() - def _set_color(self, state, widget, option): - """ - Set color value in prefs and update the UI - """ - if state: - color = widget.get_color() - color_string = gtkgui_helpers.make_color_string(color) - else: - color_string = '' - begin_option = '' - if not option.startswith('state'): - begin_option = self.current_option - gajim.config.set_per('themes', self.current_theme, - begin_option + option, color_string) - # use faster functions for this - if self.current_option == 'banner': - gajim.interface.roster.repaint_themed_widgets() - gajim.interface.save_config() - return - if self.no_update: - return - gajim.interface.roster.change_roster_style(self.current_option) - gajim.interface.save_config() + def _set_color(self, state, widget, option): + """ + Set color value in prefs and update the UI + """ + if state: + color = widget.get_color() + color_string = gtkgui_helpers.make_color_string(color) + else: + color_string = '' + begin_option = '' + if not option.startswith('state'): + begin_option = self.current_option + gajim.config.set_per('themes', self.current_theme, + begin_option + option, color_string) + # use faster functions for this + if self.current_option == 'banner': + gajim.interface.roster.repaint_themed_widgets() + gajim.interface.save_config() + return + if self.no_update: + return + gajim.interface.roster.change_roster_style(self.current_option) + gajim.interface.save_config() - def _set_font(self): - """ - Set font value in prefs and update the UI - """ - state = self.textfont_checkbutton.get_active() - if state: - font_string = self.text_fontbutton.get_font_name() - else: - font_string = '' - gajim.config.set_per('themes', self.current_theme, - self.current_option + 'font', font_string) - font_attrs = self._get_font_attrs() - gajim.config.set_per('themes', self.current_theme, - self.current_option + 'fontattrs', font_attrs) - # use faster functions for this - if self.current_option == 'banner': - gajim.interface.roster.repaint_themed_widgets() - if self.no_update: - return - gajim.interface.roster.change_roster_style(self.current_option) - gajim.interface.save_config() + def _set_font(self): + """ + Set font value in prefs and update the UI + """ + state = self.textfont_checkbutton.get_active() + if state: + font_string = self.text_fontbutton.get_font_name() + else: + font_string = '' + gajim.config.set_per('themes', self.current_theme, + self.current_option + 'font', font_string) + font_attrs = self._get_font_attrs() + gajim.config.set_per('themes', self.current_theme, + self.current_option + 'fontattrs', font_attrs) + # use faster functions for this + if self.current_option == 'banner': + gajim.interface.roster.repaint_themed_widgets() + if self.no_update: + return + gajim.interface.roster.change_roster_style(self.current_option) + gajim.interface.save_config() - def _toggle_font_widgets(self, font_props): - """ - Toggle font buttons with the bool values of font_props tuple - """ - self.bold_togglebutton.set_active(font_props[0]) - self.italic_togglebutton.set_active(font_props[1]) + def _toggle_font_widgets(self, font_props): + """ + Toggle font buttons with the bool values of font_props tuple + """ + self.bold_togglebutton.set_active(font_props[0]) + self.italic_togglebutton.set_active(font_props[1]) - def _get_font_description(self): - """ - Return a FontDescription from togglebuttons states - """ - fd = pango.FontDescription() - if self.bold_togglebutton.get_active(): - fd.set_weight(pango.WEIGHT_BOLD) - if self.italic_togglebutton.get_active(): - fd.set_style(pango.STYLE_ITALIC) - return fd + def _get_font_description(self): + """ + Return a FontDescription from togglebuttons states + """ + fd = pango.FontDescription() + if self.bold_togglebutton.get_active(): + fd.set_weight(pango.WEIGHT_BOLD) + if self.italic_togglebutton.get_active(): + fd.set_style(pango.STYLE_ITALIC) + return fd - def _set_font_widgets(self, font_attrs): - """ - Set the correct toggle state of font style buttons by a font string of - type 'BI' - """ - font_props = [False, False, False] - if font_attrs: - if font_attrs.find('B') != -1: - font_props[0] = True - if font_attrs.find('I') != -1: - font_props[1] = True - self._toggle_font_widgets(font_props) + def _set_font_widgets(self, font_attrs): + """ + Set the correct toggle state of font style buttons by a font string of + type 'BI' + """ + font_props = [False, False, False] + if font_attrs: + if font_attrs.find('B') != -1: + font_props[0] = True + if font_attrs.find('I') != -1: + font_props[1] = True + self._toggle_font_widgets(font_props) - def _get_font_attrs(self): - """ - Get a string with letters of font attribures: 'BI' - """ - attrs = '' - if self.bold_togglebutton.get_active(): - attrs += 'B' - if self.italic_togglebutton.get_active(): - attrs += 'I' - return attrs + def _get_font_attrs(self): + """ + Get a string with letters of font attribures: 'BI' + """ + attrs = '' + if self.bold_togglebutton.get_active(): + attrs += 'B' + if self.italic_togglebutton.get_active(): + attrs += 'I' + return attrs - def _get_font_props(self, font_name): - """ - Get tuple of font properties: weight, style - """ - font_props = [False, False, False] - font_description = pango.FontDescription(font_name) - if font_description.get_weight() != pango.WEIGHT_NORMAL: - font_props[0] = True - if font_description.get_style() != pango.STYLE_ITALIC: - font_props[1] = True - return font_props + def _get_font_props(self, font_name): + """ + Get tuple of font properties: weight, style + """ + font_props = [False, False, False] + font_description = pango.FontDescription(font_name) + if font_description.get_weight() != pango.WEIGHT_NORMAL: + font_props[0] = True + if font_description.get_style() != pango.STYLE_ITALIC: + font_props[1] = True + return font_props - def on_inactive_colorbutton_color_set(self, widget): - self.no_update = True - self._set_color(True, widget, 'state_inactive_color') - self.no_update = False + def on_inactive_colorbutton_color_set(self, widget): + self.no_update = True + self._set_color(True, widget, 'state_inactive_color') + self.no_update = False - def on_composing_colorbutton_color_set(self, widget): - self.no_update = True - self._set_color(True, widget, 'state_composing_color') - self.no_update = False + def on_composing_colorbutton_color_set(self, widget): + self.no_update = True + self._set_color(True, widget, 'state_composing_color') + self.no_update = False - def on_paused_colorbutton_color_set(self, widget): - self.no_update = True - self._set_color(True, widget, 'state_paused_color') - self.no_update = False + def on_paused_colorbutton_color_set(self, widget): + self.no_update = True + self._set_color(True, widget, 'state_paused_color') + self.no_update = False - def on_gone_colorbutton_color_set(self, widget): - self.no_update = True - self._set_color(True, widget, 'state_gone_color') - self.no_update = False + def on_gone_colorbutton_color_set(self, widget): + self.no_update = True + self._set_color(True, widget, 'state_gone_color') + self.no_update = False - def on_muc_msg_colorbutton_color_set(self, widget): - self.no_update = True - self._set_color(True, widget, 'state_muc_msg_color') - self.no_update = False + def on_muc_msg_colorbutton_color_set(self, widget): + self.no_update = True + self._set_color(True, widget, 'state_muc_msg_color') + self.no_update = False - def on_muc_directed_msg_colorbutton_color_set(self, widget): - self.no_update = True - self._set_color(True, widget, 'state_muc_directed_msg_color') - self.no_update = False - -# vim: se ts=3: + def on_muc_directed_msg_colorbutton_color_set(self, widget): + self.no_update = True + self._set_color(True, widget, 'state_muc_directed_msg_color') + self.no_update = False diff --git a/src/groupchat_control.py b/src/groupchat_control.py index 6db3398f5..914438e28 100644 --- a/src/groupchat_control.py +++ b/src/groupchat_control.py @@ -64,2362 +64,2360 @@ C_AVATAR, # avatar of the contact ) = range(5) def set_renderer_color(treeview, renderer, set_background=True): - """ - Set style for group row, using PRELIGHT system color - """ - if set_background: - bgcolor = treeview.style.bg[gtk.STATE_PRELIGHT] - renderer.set_property('cell-background-gdk', bgcolor) - else: - fgcolor = treeview.style.fg[gtk.STATE_PRELIGHT] - renderer.set_property('foreground-gdk', fgcolor) + """ + Set style for group row, using PRELIGHT system color + """ + if set_background: + bgcolor = treeview.style.bg[gtk.STATE_PRELIGHT] + renderer.set_property('cell-background-gdk', bgcolor) + else: + fgcolor = treeview.style.fg[gtk.STATE_PRELIGHT] + renderer.set_property('foreground-gdk', fgcolor) def tree_cell_data_func(column, renderer, model, iter_, tv=None): - # cell data func is global, because we don't want it to keep - # reference to GroupchatControl instance (self) - theme = gajim.config.get('roster_theme') - # allocate space for avatar only if needed - parent_iter = model.iter_parent(iter_) - if isinstance(renderer, gtk.CellRendererPixbuf): - avatar_position = gajim.config.get('avatar_position_in_roster') - if avatar_position == 'right': - renderer.set_property('xalign', 1) # align pixbuf to the right - else: - renderer.set_property('xalign', 0.5) - if parent_iter and (model[iter_][C_AVATAR] or avatar_position == 'left'): - renderer.set_property('visible', True) - renderer.set_property('width', gajim.config.get('roster_avatar_width')) - else: - renderer.set_property('visible', False) - if parent_iter: - bgcolor = gajim.config.get_per('themes', theme, 'contactbgcolor') - if bgcolor: - renderer.set_property('cell-background', bgcolor) - else: - renderer.set_property('cell-background', None) - if isinstance(renderer, gtk.CellRendererText): - # foreground property is only with CellRendererText - color = gajim.config.get_per('themes', theme, 'contacttextcolor') - if color: - renderer.set_property('foreground', color) - else: - renderer.set_property('foreground', None) - renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont')) - else: # it is root (eg. group) - bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor') - if bgcolor: - renderer.set_property('cell-background', bgcolor) - else: - set_renderer_color(tv, renderer) - if isinstance(renderer, gtk.CellRendererText): - # foreground property is only with CellRendererText - color = gajim.config.get_per('themes', theme, 'grouptextcolor') - if color: - renderer.set_property('foreground', color) - else: - set_renderer_color(tv, renderer, False) - renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont')) + # cell data func is global, because we don't want it to keep + # reference to GroupchatControl instance (self) + theme = gajim.config.get('roster_theme') + # allocate space for avatar only if needed + parent_iter = model.iter_parent(iter_) + if isinstance(renderer, gtk.CellRendererPixbuf): + avatar_position = gajim.config.get('avatar_position_in_roster') + if avatar_position == 'right': + renderer.set_property('xalign', 1) # align pixbuf to the right + else: + renderer.set_property('xalign', 0.5) + if parent_iter and (model[iter_][C_AVATAR] or avatar_position == 'left'): + renderer.set_property('visible', True) + renderer.set_property('width', gajim.config.get('roster_avatar_width')) + else: + renderer.set_property('visible', False) + if parent_iter: + bgcolor = gajim.config.get_per('themes', theme, 'contactbgcolor') + if bgcolor: + renderer.set_property('cell-background', bgcolor) + else: + renderer.set_property('cell-background', None) + if isinstance(renderer, gtk.CellRendererText): + # foreground property is only with CellRendererText + color = gajim.config.get_per('themes', theme, 'contacttextcolor') + if color: + renderer.set_property('foreground', color) + else: + renderer.set_property('foreground', None) + renderer.set_property('font', + gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont')) + else: # it is root (eg. group) + bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor') + if bgcolor: + renderer.set_property('cell-background', bgcolor) + else: + set_renderer_color(tv, renderer) + if isinstance(renderer, gtk.CellRendererText): + # foreground property is only with CellRendererText + color = gajim.config.get_per('themes', theme, 'grouptextcolor') + if color: + renderer.set_property('foreground', color) + else: + set_renderer_color(tv, renderer, False) + renderer.set_property('font', + gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont')) class PrivateChatControl(ChatControl): - TYPE_ID = message_control.TYPE_PM + TYPE_ID = message_control.TYPE_PM # Set a command host to bound to. Every command given through a private chat # will be processed with this command host. - COMMAND_HOST = PrivateChatCommands + COMMAND_HOST = PrivateChatCommands - def __init__(self, parent_win, gc_contact, contact, account, session): - room_jid = contact.jid.split('/')[0] - room_ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid, account) - if room_jid in gajim.interface.minimized_controls[account]: - room_ctrl = gajim.interface.minimized_controls[account][room_jid] - if room_ctrl: - self.room_name = room_ctrl.name - else: - self.room_name = room_jid - self.gc_contact = gc_contact - ChatControl.__init__(self, parent_win, contact, account, session) - self.TYPE_ID = 'pm' + def __init__(self, parent_win, gc_contact, contact, account, session): + room_jid = contact.jid.split('/')[0] + room_ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid, account) + if room_jid in gajim.interface.minimized_controls[account]: + room_ctrl = gajim.interface.minimized_controls[account][room_jid] + if room_ctrl: + self.room_name = room_ctrl.name + else: + self.room_name = room_jid + self.gc_contact = gc_contact + ChatControl.__init__(self, parent_win, contact, account, session) + self.TYPE_ID = 'pm' - def send_message(self, message, xhtml=None, process_commands=True): - """ - Call this method to send the message - """ - if not message: - return + def send_message(self, message, xhtml=None, process_commands=True): + """ + Call this method to send the message + """ + if not message: + return - message = helpers.remove_invalid_xml_chars(message) + message = helpers.remove_invalid_xml_chars(message) - if not message: - return + if not message: + return - # We need to make sure that we can still send through the room and that - # the recipient did not go away - contact = gajim.contacts.get_first_contact_from_jid(self.account, - self.contact.jid) - if contact is None: - # contact was from pm in MUC - room, nick = gajim.get_room_and_nick_from_fjid(self.contact.jid) - gc_contact = gajim.contacts.get_gc_contact(self.account, room, nick) - if not gc_contact: - dialogs.ErrorDialog( - _('Sending private message failed'), - #in second %s code replaces with nickname - _('You are no longer in group chat "%(room)s" or "%(nick)s" has ' - 'left.') % {'room': room, 'nick': nick}) - return + # We need to make sure that we can still send through the room and that + # the recipient did not go away + contact = gajim.contacts.get_first_contact_from_jid(self.account, + self.contact.jid) + if contact is None: + # contact was from pm in MUC + room, nick = gajim.get_room_and_nick_from_fjid(self.contact.jid) + gc_contact = gajim.contacts.get_gc_contact(self.account, room, nick) + if not gc_contact: + dialogs.ErrorDialog( + _('Sending private message failed'), + #in second %s code replaces with nickname + _('You are no longer in group chat "%(room)s" or "%(nick)s" has ' + 'left.') % {'room': room, 'nick': nick}) + return - ChatControl.send_message(self, message, xhtml=xhtml, - process_commands=process_commands) + ChatControl.send_message(self, message, xhtml=xhtml, + process_commands=process_commands) - def update_ui(self): - if self.contact.show == 'offline': - self.got_disconnected() - else: - self.got_connected() - ChatControl.update_ui(self) + def update_ui(self): + if self.contact.show == 'offline': + self.got_disconnected() + else: + self.got_connected() + ChatControl.update_ui(self) - def update_contact(self): - self.contact = self.gc_contact.as_contact() + def update_contact(self): + self.contact = self.gc_contact.as_contact() - def begin_e2e_negotiation(self): - self.no_autonegotiation = True + def begin_e2e_negotiation(self): + self.no_autonegotiation = True - if not self.session: - fjid = self.gc_contact.get_full_jid() - new_sess = gajim.connections[self.account].make_new_session(fjid, type_=self.type_id) - self.set_session(new_sess) + if not self.session: + fjid = self.gc_contact.get_full_jid() + new_sess = gajim.connections[self.account].make_new_session(fjid, type_=self.type_id) + self.set_session(new_sess) - self.session.negotiate_e2e(False) + self.session.negotiate_e2e(False) class GroupchatControl(ChatControlBase): - TYPE_ID = message_control.TYPE_GC + TYPE_ID = message_control.TYPE_GC # Set a command host to bound to. Every command given through a group chat # will be processed with this command host. - COMMAND_HOST = GroupChatCommands - - def __init__(self, parent_win, contact, acct, is_continued=False): - ChatControlBase.__init__(self, self.TYPE_ID, parent_win, - 'muc_child_vbox', contact, acct) - - self.is_continued=is_continued - self.is_anonymous = True - - # Controls the state of autorejoin. - # None - autorejoin is neutral. - # False - autorejoin is to be prevented (gets reset to initial state in - # got_connected()). - # int - autorejoin is being active and working (gets reset to initial - # state in got_connected()). - self.autorejoin = None - - self.actions_button = self.xml.get_widget('muc_window_actions_button') - id_ = self.actions_button.connect('clicked', - self.on_actions_button_clicked) - self.handlers[id_] = self.actions_button - - widget = self.xml.get_widget('change_nick_button') - id_ = widget.connect('clicked', self._on_change_nick_menuitem_activate) - self.handlers[id_] = widget - - widget = self.xml.get_widget('change_subject_button') - id_ = widget.connect('clicked', self._on_change_subject_menuitem_activate) - self.handlers[id_] = widget - - widget = self.xml.get_widget('bookmark_button') - for bm in gajim.connections[self.account].bookmarks: - if bm['jid'] == self.contact.jid: - widget.hide() - break - else: - id_ = widget.connect('clicked', - self._on_bookmark_room_menuitem_activate) - self.handlers[id_] = widget - widget.show() - - widget = self.xml.get_widget('list_treeview') - id_ = widget.connect('row_expanded', self.on_list_treeview_row_expanded) - self.handlers[id_] = widget - - id_ = widget.connect('row_collapsed', self.on_list_treeview_row_collapsed) - self.handlers[id_] = widget - - id_ = widget.connect('row_activated', - self.on_list_treeview_row_activated) - self.handlers[id_] = widget - - id_ = widget.connect('button_press_event', - self.on_list_treeview_button_press_event) - self.handlers[id_] = widget - - id_ = widget.connect('key_press_event', - self.on_list_treeview_key_press_event) - self.handlers[id_] = widget - - id_ = widget.connect('motion_notify_event', - self.on_list_treeview_motion_notify_event) - self.handlers[id_] = widget - - id_ = widget.connect('leave_notify_event', - self.on_list_treeview_leave_notify_event) - self.handlers[id_] = widget - - self.room_jid = self.contact.jid - self.nick = contact.name.decode('utf-8') - self.new_nick = '' - self.name = '' - for bm in gajim.connections[self.account].bookmarks: - if bm['jid'] == self.room_jid: - self.name = bm['name'] - break - if not self.name: - self.name = self.room_jid.split('@')[0] - - compact_view = gajim.config.get('compact_view') - self.chat_buttons_set_visible(compact_view) - self.widget_set_visible(self.xml.get_widget('banner_eventbox'), - gajim.config.get('hide_groupchat_banner')) - self.widget_set_visible(self.xml.get_widget('list_scrolledwindow'), - gajim.config.get('hide_groupchat_occupants_list')) - - self._last_selected_contact = None # None or holds jid, account tuple - - # muc attention flag (when we are mentioned in a muc) - # if True, the room has mentioned us - self.attention_flag = False - - # sorted list of nicks who mentioned us (last at the end) - self.attention_list = [] - self.room_creation = int(time.time()) # Use int to reduce mem usage - self.nick_hits = [] - self.last_key_tabs = False - - self.subject = '' - - self.tooltip = tooltips.GCTooltip() - - # nickname coloring - self.gc_count_nicknames_colors = 0 - self.gc_custom_colors = {} - self.number_of_colors = len(gajim.config.get('gc_nicknames_colors').\ - split(':')) - - self.name_label = self.xml.get_widget('banner_name_label') - self.event_box = self.xml.get_widget('banner_eventbox') - - # set the position of the current hpaned - hpaned_position = gajim.config.get('gc-hpaned-position') - self.hpaned = self.xml.get_widget('hpaned') - self.hpaned.set_position(hpaned_position) - - self.list_treeview = self.xml.get_widget('list_treeview') - selection = self.list_treeview.get_selection() - id_ = selection.connect('changed', - self.on_list_treeview_selection_changed) - self.handlers[id_] = selection - id_ = self.list_treeview.connect('style-set', - self.on_list_treeview_style_set) - self.handlers[id_] = self.list_treeview - self.resize_from_another_muc = False - # we want to know when the the widget resizes, because that is - # an indication that the hpaned has moved... - # FIXME: Find a better indicator that the hpaned has moved. - id_ = self.list_treeview.connect('size-allocate', - self.on_treeview_size_allocate) - self.handlers[id_] = self.list_treeview - #status_image, shown_nick, type, nickname, avatar - store = gtk.TreeStore(gtk.Image, str, str, str, gtk.gdk.Pixbuf) - store.set_sort_func(C_NICK, self.tree_compare_iters) - store.set_sort_column_id(C_NICK, gtk.SORT_ASCENDING) - self.list_treeview.set_model(store) - - # columns - - # this col has 3 cells: - # first one img, second one text, third is sec pixbuf - column = gtk.TreeViewColumn() - - def add_avatar_renderer(): - renderer_pixbuf = gtk.CellRendererPixbuf() # avatar image - column.pack_start(renderer_pixbuf, expand=False) - column.add_attribute(renderer_pixbuf, 'pixbuf', C_AVATAR) - column.set_cell_data_func(renderer_pixbuf, tree_cell_data_func, - self.list_treeview) - - if gajim.config.get('avatar_position_in_roster') == 'left': - add_avatar_renderer() - - renderer_image = cell_renderer_image.CellRendererImage(0, 0) # status img - renderer_image.set_property('width', 26) - column.pack_start(renderer_image, expand=False) - column.add_attribute(renderer_image, 'image', C_IMG) - column.set_cell_data_func(renderer_image, tree_cell_data_func, - self.list_treeview) - - renderer_text = gtk.CellRendererText() # nickname - column.pack_start(renderer_text, expand=True) - column.add_attribute(renderer_text, 'markup', C_TEXT) - renderer_text.set_property("ellipsize", pango.ELLIPSIZE_END) - column.set_cell_data_func(renderer_text, tree_cell_data_func, - self.list_treeview) - - if gajim.config.get('avatar_position_in_roster') == 'right': - add_avatar_renderer() - - self.list_treeview.append_column(column) - - # workaround to avoid gtk arrows to be shown - column = gtk.TreeViewColumn() # 2nd COLUMN - renderer = gtk.CellRendererPixbuf() - column.pack_start(renderer, expand=False) - self.list_treeview.append_column(column) - column.set_visible(False) - self.list_treeview.set_expander_column(column) - - gajim.gc_connected[self.account][self.room_jid] = False - # disable win, we are not connected yet - ChatControlBase.got_disconnected(self) - - self.update_ui() - self.conv_textview.tv.grab_focus() - self.widget.show_all() - - def tree_compare_iters(self, model, iter1, iter2): - """ - Compare two iters to sort them - """ - type1 = model[iter1][C_TYPE] - type2 = model[iter2][C_TYPE] - if not type1 or not type2: - return 0 - nick1 = model[iter1][C_NICK] - nick2 = model[iter2][C_NICK] - if not nick1 or not nick2: - return 0 - nick1 = nick1.decode('utf-8') - nick2 = nick2.decode('utf-8') - if type1 == 'role': - return locale.strcoll(nick1, nick2) - if type1 == 'contact': - gc_contact1 = gajim.contacts.get_gc_contact(self.account, - self.room_jid, nick1) - if not gc_contact1: - return 0 - if type2 == 'contact': - gc_contact2 = gajim.contacts.get_gc_contact(self.account, - self.room_jid, nick2) - if not gc_contact2: - return 0 - if type1 == 'contact' and type2 == 'contact' and \ - gajim.config.get('sort_by_show_in_muc'): - cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4, - 'invisible': 5, 'offline': 6, 'error': 7} - show1 = cshow[gc_contact1.show] - show2 = cshow[gc_contact2.show] - if show1 < show2: - return -1 - elif show1 > show2: - return 1 - # We compare names - name1 = gc_contact1.get_shown_name() - name2 = gc_contact2.get_shown_name() - return locale.strcoll(name1.lower(), name2.lower()) - - def on_msg_textview_populate_popup(self, textview, menu): - """ - Override the default context menu and we prepend Clear - and the ability to insert a nick - """ - ChatControlBase.on_msg_textview_populate_popup(self, textview, menu) - item = gtk.SeparatorMenuItem() - menu.prepend(item) - - item = gtk.MenuItem(_('Insert Nickname')) - menu.prepend(item) - submenu = gtk.Menu() - item.set_submenu(submenu) - - for nick in sorted(gajim.contacts.get_nick_list(self.account, - self.room_jid)): - item = gtk.MenuItem(nick, use_underline=False) - submenu.append(item) - id_ = item.connect('activate', self.append_nick_in_msg_textview, nick) - self.handlers[id_] = item - - menu.show_all() - - def resize_occupant_treeview(self, position): - self.resize_from_another_muc = True - self.hpaned.set_position(position) - def reset_flag(): - self.resize_from_another_muc = False - # Reset the flag when everything will be redrawn, and in particular when - # on_treeview_size_allocate will have been called. - gobject.idle_add(reset_flag) - - def on_treeview_size_allocate(self, widget, allocation): - """ - The MUC treeview has resized. Move the hpaned in all tabs to match - """ - if self.resize_from_another_muc: - # Don't send the event to other MUC - return - hpaned_position = self.hpaned.get_position() - for account in gajim.gc_connected: - for room_jid in [i for i in gajim.gc_connected[account] if \ - gajim.gc_connected[account][i] and i != self.room_jid]: - ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid, account) - if not ctrl: - ctrl = gajim.interface.minimized_controls[account][room_jid] - if ctrl: - ctrl.resize_occupant_treeview(hpaned_position) - - def iter_contact_rows(self): - """ - Iterate over all contact rows in the tree model - """ - model = self.list_treeview.get_model() - role_iter = model.get_iter_root() - while role_iter: - contact_iter = model.iter_children(role_iter) - while contact_iter: - yield model[contact_iter] - contact_iter = model.iter_next(contact_iter) - role_iter = model.iter_next(role_iter) - - def on_list_treeview_style_set(self, treeview, style): - """ - When style (theme) changes, redraw all contacts - """ - # Get the room_jid from treeview - for contact in self.iter_contact_rows(): - nick = contact[C_NICK].decode('utf-8') - self.draw_contact(nick) - - def on_list_treeview_selection_changed(self, selection): - model, selected_iter = selection.get_selected() - self.draw_contact(self.nick) - if self._last_selected_contact is not None: - self.draw_contact(self._last_selected_contact) - if selected_iter is None: - self._last_selected_contact = None - return - contact = model[selected_iter] - nick = contact[C_NICK].decode('utf-8') - self._last_selected_contact = nick - if contact[C_TYPE] != 'contact': - return - self.draw_contact(nick, selected=True, focus=True) - - def get_tab_label(self, chatstate): - """ - Markup the label if necessary. Returns a tuple such as: (new_label_str, - color) either of which can be None if chatstate is given that means we - have HE SENT US a chatstate - """ - - has_focus = self.parent_win.window.get_property('has-toplevel-focus') - current_tab = self.parent_win.get_active_control() == self - color_name = None - color = None - theme = gajim.config.get('roster_theme') - if chatstate == 'attention' and (not has_focus or not current_tab): - self.attention_flag = True - color_name = gajim.config.get_per('themes', theme, - 'state_muc_directed_msg_color') - elif chatstate: - if chatstate == 'active' or (current_tab and has_focus): - self.attention_flag = False - # get active color from gtk - color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE] - elif chatstate == 'newmsg' and (not has_focus or not current_tab) and\ - not self.attention_flag: - color_name = gajim.config.get_per('themes', theme, - 'state_muc_msg_color') - if color_name: - color = gtk.gdk.colormap_get_system().alloc_color(color_name) - - if self.is_continued: - # if this is a continued conversation - label_str = self.get_continued_conversation_name() - else: - label_str = self.name - - # count waiting highlighted messages - unread = '' - num_unread = self.get_nb_unread() - if num_unread == 1: - unread = '*' - elif num_unread > 1: - unread = '[' + unicode(num_unread) + ']' - label_str = unread + label_str - return (label_str, color) - - def get_tab_image(self, count_unread=True): - # Set tab image (always 16x16) - tab_image = None - if gajim.gc_connected[self.account][self.room_jid]: - tab_image = gtkgui_helpers.load_icon('muc_active') - else: - tab_image = gtkgui_helpers.load_icon('muc_inactive') - return tab_image - - def update_ui(self): - ChatControlBase.update_ui(self) - for nick in gajim.contacts.get_nick_list(self.account, self.room_jid): - self.draw_contact(nick) - - def _change_style(self, model, path, iter_): - model[iter_][C_NICK] = model[iter_][C_NICK] - - def change_roster_style(self): - model = self.list_treeview.get_model() - model.foreach(self._change_style) - - def repaint_themed_widgets(self): - ChatControlBase.repaint_themed_widgets(self) - self.change_roster_style() - - def _update_banner_state_image(self): - banner_status_img = self.xml.get_widget('gc_banner_status_image') - images = gajim.interface.jabber_state_images - if self.room_jid in gajim.gc_connected[self.account] and \ - gajim.gc_connected[self.account][self.room_jid]: - image = 'muc_active' - else: - image = 'muc_inactive' - if '32' in images and image in images['32']: - muc_icon = images['32'][image] - if muc_icon.get_storage_type() != gtk.IMAGE_EMPTY: - pix = muc_icon.get_pixbuf() - banner_status_img.set_from_pixbuf(pix) - return - # we need to scale 16x16 to 32x32 - muc_icon = images['16'][image] - pix = muc_icon.get_pixbuf() - scaled_pix = pix.scale_simple(32, 32, gtk.gdk.INTERP_BILINEAR) - banner_status_img.set_from_pixbuf(scaled_pix) - - def get_continued_conversation_name(self): - """ - Get the name of a continued conversation. Will return Continued - Conversation if there isn't any other contact in the room - """ - nicks = [] - for nick in gajim.contacts.get_nick_list(self.account, - self.room_jid): - if nick != self.nick: - nicks.append(nick) - if nicks != []: - title = ', ' - title = _('Conversation with ') + title.join(nicks) - else: - title = _('Continued conversation') - return title - - def draw_banner_text(self): - """ - Draw the text in the fat line at the top of the window that houses the - room jid, subject - """ - self.name_label.set_ellipsize(pango.ELLIPSIZE_END) - self.banner_status_label.set_ellipsize(pango.ELLIPSIZE_END) - font_attrs, font_attrs_small = self.get_font_attrs() - if self.is_continued: - name = self.get_continued_conversation_name() - else: - name = self.room_jid - text = '%s' % (font_attrs, name) - self.name_label.set_markup(text) - - if self.subject: - subject = helpers.reduce_chars_newlines(self.subject, max_lines=2) - subject = gobject.markup_escape_text(subject) - if gajim.HAVE_PYSEXY: - subject_text = self.urlfinder.sub(self.make_href, subject) - subject_text = '%s' % (font_attrs_small, - subject_text) - else: - subject_text = '%s' % (font_attrs_small, subject) - - # tooltip must always hold ALL the subject - self.event_box.set_tooltip_text(self.subject) - self.banner_status_label.show() - self.banner_status_label.set_no_show_all(False) - else: - subject_text = '' - self.event_box.set_has_tooltip(False) - self.banner_status_label.hide() - self.banner_status_label.set_no_show_all(True) - - self.banner_status_label.set_markup(subject_text) - - def prepare_context_menu(self, hide_buttonbar_items=False): - """ - Set sensitivity state for configure_room - """ - xml = gtkgui_helpers.get_glade('gc_control_popup_menu.glade') - menu = xml.get_widget('gc_control_popup_menu') - - bookmark_room_menuitem = xml.get_widget('bookmark_room_menuitem') - change_nick_menuitem = xml.get_widget('change_nick_menuitem') - configure_room_menuitem = xml.get_widget('configure_room_menuitem') - destroy_room_menuitem = xml.get_widget('destroy_room_menuitem') - change_subject_menuitem = xml.get_widget('change_subject_menuitem') - history_menuitem = xml.get_widget('history_menuitem') - minimize_menuitem = xml.get_widget('minimize_menuitem') - bookmark_separator = xml.get_widget('bookmark_separator') - separatormenuitem2 = xml.get_widget('separatormenuitem2') - - if hide_buttonbar_items: - change_nick_menuitem.hide() - change_subject_menuitem.hide() - bookmark_room_menuitem.hide() - history_menuitem.hide() - bookmark_separator.hide() - separatormenuitem2.hide() - else: - change_nick_menuitem.show() - change_subject_menuitem.show() - bookmark_room_menuitem.show() - history_menuitem.show() - bookmark_separator.show() - separatormenuitem2.show() - for bm in gajim.connections[self.account].bookmarks: - if bm['jid'] == self.room_jid: - bookmark_room_menuitem.hide() - bookmark_separator.hide() - break - - ag = gtk.accel_groups_from_object(self.parent_win.window)[0] - change_nick_menuitem.add_accelerator('activate', ag, gtk.keysyms.n, - gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) - change_subject_menuitem.add_accelerator('activate', ag, - gtk.keysyms.t, gtk.gdk.MOD1_MASK, gtk.ACCEL_VISIBLE) - bookmark_room_menuitem.add_accelerator('activate', ag, gtk.keysyms.b, - gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) - history_menuitem.add_accelerator('activate', ag, gtk.keysyms.h, - gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) - - if self.contact.jid in gajim.config.get_per('accounts', self.account, - 'minimized_gc').split(' '): - minimize_menuitem.set_active(True) - if not gajim.connections[self.account].private_storage_supported: - bookmark_room_menuitem.set_sensitive(False) - if gajim.gc_connected[self.account][self.room_jid]: - c = gajim.contacts.get_gc_contact(self.account, self.room_jid, - self.nick) - if c.affiliation not in ('owner', 'admin'): - configure_room_menuitem.set_sensitive(False) - else: - configure_room_menuitem.set_sensitive(True) - if c.affiliation != 'owner': - destroy_room_menuitem.set_sensitive(False) - else: - destroy_room_menuitem.set_sensitive(True) - change_subject_menuitem.set_sensitive(True) - change_nick_menuitem.set_sensitive(True) - else: - # We are not connected to this groupchat, disable unusable menuitems - configure_room_menuitem.set_sensitive(False) - destroy_room_menuitem.set_sensitive(False) - change_subject_menuitem.set_sensitive(False) - change_nick_menuitem.set_sensitive(False) - - # connect the menuitems to their respective functions - id_ = bookmark_room_menuitem.connect('activate', - self._on_bookmark_room_menuitem_activate) - self.handlers[id_] = bookmark_room_menuitem - - id_ = change_nick_menuitem.connect('activate', - self._on_change_nick_menuitem_activate) - self.handlers[id_] = change_nick_menuitem - - id_ = configure_room_menuitem.connect('activate', - self._on_configure_room_menuitem_activate) - self.handlers[id_] = configure_room_menuitem - - id_ = destroy_room_menuitem.connect('activate', - self._on_destroy_room_menuitem_activate) - self.handlers[id_] = destroy_room_menuitem - - id_ = change_subject_menuitem.connect('activate', - self._on_change_subject_menuitem_activate) - self.handlers[id_] = change_subject_menuitem - - id_ = history_menuitem.connect('activate', - self._on_history_menuitem_activate) - self.handlers[id_] = history_menuitem - - id_ = minimize_menuitem.connect('toggled', - self.on_minimize_menuitem_toggled) - self.handlers[id_] = minimize_menuitem - - menu.connect('selection-done', self.destroy_menu, - change_nick_menuitem, change_subject_menuitem, - bookmark_room_menuitem, history_menuitem) - return menu - - def destroy_menu(self, menu, change_nick_menuitem, change_subject_menuitem, - bookmark_room_menuitem, history_menuitem): - # destroy accelerators - ag = gtk.accel_groups_from_object(self.parent_win.window)[0] - change_nick_menuitem.remove_accelerator(ag, gtk.keysyms.n, - gtk.gdk.CONTROL_MASK) - change_subject_menuitem.remove_accelerator(ag, gtk.keysyms.t, - gtk.gdk.MOD1_MASK) - bookmark_room_menuitem.remove_accelerator(ag, gtk.keysyms.b, - gtk.gdk.CONTROL_MASK) - history_menuitem.remove_accelerator(ag, gtk.keysyms.h, - gtk.gdk.CONTROL_MASK) - # destroy menu - menu.destroy() - - def on_message(self, nick, msg, tim, has_timestamp=False, xhtml=None, - status_code=[]): - if '100' in status_code: - # Room is not anonymous - self.is_anonymous = False - if not nick: - # message from server - self.print_conversation(msg, tim=tim, xhtml=xhtml) - else: - # message from someone - if has_timestamp: - # don't print xhtml if it's an old message. - # Like that xhtml messages are grayed too. - self.print_old_conversation(msg, nick, tim, None) - else: - self.print_conversation(msg, nick, tim, xhtml) - - def on_private_message(self, nick, msg, tim, xhtml, session, msg_id=None, - encrypted=False): - # Do we have a queue? - fjid = self.room_jid + '/' + nick - no_queue = len(gajim.events.get_events(self.account, fjid)) == 0 - - event = gajim.events.create_event('pm', (msg, '', 'incoming', tim, - encrypted, '', msg_id, xhtml, session)) - gajim.events.add_event(self.account, fjid, event) - - autopopup = gajim.config.get('autopopup') - autopopupaway = gajim.config.get('autopopupaway') - iter_ = self.get_contact_iter(nick) - path = self.list_treeview.get_model().get_path(iter_) - if not autopopup or (not autopopupaway and \ - gajim.connections[self.account].connected > 2): - if no_queue: # We didn't have a queue: we change icons - model = self.list_treeview.get_model() - state_images =\ - gajim.interface.roster.get_appropriate_state_images( - self.room_jid, icon_name='event') - image = state_images['event'] - model[iter_][C_IMG] = image - if self.parent_win: - self.parent_win.show_title() - self.parent_win.redraw_tab(self) - else: - self._start_private_message(nick) - # Scroll to line - self.list_treeview.expand_row(path[0:1], False) - self.list_treeview.scroll_to_cell(path) - self.list_treeview.set_cursor(path) - contact = gajim.contacts.get_contact_with_highest_priority(self.account, \ - self.room_jid) - if contact: - gajim.interface.roster.draw_contact(self.room_jid, self.account) - - def get_contact_iter(self, nick): - model = self.list_treeview.get_model() - fin = False - role_iter = model.get_iter_root() - if not role_iter: - return None - while not fin: - fin2 = False - user_iter = model.iter_children(role_iter) - if not user_iter: - fin2 = True - while not fin2: - if nick == model[user_iter][C_NICK].decode('utf-8'): - return user_iter - user_iter = model.iter_next(user_iter) - if not user_iter: - fin2 = True - role_iter = model.iter_next(role_iter) - if not role_iter: - fin = True - return None - - def print_old_conversation(self, text, contact='', tim=None, xhtml = None): - if isinstance(text, str): - text = unicode(text, 'utf-8') - if contact: - if contact == self.nick: # it's us - kind = 'outgoing' - else: - kind = 'incoming' - else: - kind = 'status' - if gajim.config.get('restored_messages_small'): - small_attr = ['small'] - else: - small_attr = [] - ChatControlBase.print_conversation_line(self, text, kind, contact, tim, - small_attr, small_attr + ['restored_message'], - small_attr + ['restored_message'], count_as_new=False, xhtml=xhtml) - - def print_conversation(self, text, contact='', tim=None, xhtml=None, - graphics=True): - """ - Print a line in the conversation - - If contact is set: it's a message from someone or an info message - (contact = 'info' in such a case). - If contact is not set: it's a message from the server or help. - """ - if isinstance(text, str): - text = unicode(text, 'utf-8') - other_tags_for_name = [] - other_tags_for_text = [] - if contact: - if contact == self.nick: # it's us - kind = 'outgoing' - elif contact == 'info': - kind = 'info' - contact = None - else: - kind = 'incoming' - # muc-specific chatstate - if self.parent_win: - self.parent_win.redraw_tab(self, 'newmsg') - else: - kind = 'status' - - if kind == 'incoming': # it's a message NOT from us - # highlighting and sounds - (highlight, sound) = self.highlighting_for_message(text, tim) - if contact in self.gc_custom_colors: - other_tags_for_name.append('gc_nickname_color_' + \ - str(self.gc_custom_colors[contact])) - else: - self.gc_count_nicknames_colors += 1 - if self.gc_count_nicknames_colors == self.number_of_colors: - self.gc_count_nicknames_colors = 0 - self.gc_custom_colors[contact] = \ - self.gc_count_nicknames_colors - other_tags_for_name.append('gc_nickname_color_' + \ - str(self.gc_count_nicknames_colors)) - if highlight: - # muc-specific chatstate - if self.parent_win: - self.parent_win.redraw_tab(self, 'attention') - else: - self.attention_flag = True - other_tags_for_name.append('bold') - other_tags_for_text.append('marked') - - if contact in self.attention_list: - self.attention_list.remove(contact) - elif len(self.attention_list) > 6: - self.attention_list.pop(0) # remove older - self.attention_list.append(contact) - - if sound == 'received': - helpers.play_sound('muc_message_received') - elif sound == 'highlight': - helpers.play_sound('muc_message_highlight') - if text.startswith('/me ') or text.startswith('/me\n'): - other_tags_for_text.append('gc_nickname_color_' + \ - str(self.gc_custom_colors[contact])) - - self.check_and_possibly_add_focus_out_line() - - ChatControlBase.print_conversation_line(self, text, kind, contact, tim, - other_tags_for_name, [], other_tags_for_text, xhtml=xhtml, - graphics=graphics) - - def get_nb_unread(self): - type_events = ['printed_marked_gc_msg'] - if gajim.config.get('notify_on_all_muc_messages'): - type_events.append('printed_gc_msg') - nb = len(gajim.events.get_events(self.account, self.room_jid, - type_events)) - nb += self.get_nb_unread_pm() - return nb - - def get_nb_unread_pm(self): - nb = 0 - for nick in gajim.contacts.get_nick_list(self.account, self.room_jid): - nb += len(gajim.events.get_events(self.account, self.room_jid + '/' + \ - nick, ['pm'])) - return nb - - def highlighting_for_message(self, text, tim): - """ - Returns a 2-Tuple. The first says whether or not to highlight the text, - the second, what sound to play - """ - highlight, sound = (None, None) - - # Are any of the defined highlighting words in the text? - if self.needs_visual_notification(text): - highlight = True - if gajim.config.get_per('soundevents', 'muc_message_highlight', - 'enabled'): - sound = 'highlight' - - # Do we play a sound on every muc message? - elif gajim.config.get_per('soundevents', 'muc_message_received', \ - 'enabled'): - sound = 'received' - - # Is it a history message? Don't want sound-floods when we join. - if tim != time.localtime(): - sound = None - - return (highlight, sound) - - def check_and_possibly_add_focus_out_line(self): - """ - Check and possibly add focus out line for room_jid if it needs it and - does not already have it as last event. If it goes to add this line - - remove previous line first - """ - win = gajim.interface.msg_win_mgr.get_window(self.room_jid, self.account) - if win and self.room_jid == win.get_active_jid() and\ - win.window.get_property('has-toplevel-focus') and\ - self.parent_win.get_active_control() == self: - # it's the current room and it's the focused window. - # we have full focus (we are reading it!) - return - - self.conv_textview.show_focus_out_line() - - def needs_visual_notification(self, text): - """ - Check text to see whether any of the words in (muc_highlight_words and - nick) appear - """ - special_words = gajim.config.get('muc_highlight_words').split(';') - special_words.append(self.nick) - # Strip empties: ''.split(';') == [''] and would highlight everything. - # Also lowercase everything for case insensitive compare. - special_words = [word.lower() for word in special_words if word] - text = text.lower() - - for special_word in special_words: - found_here = text.find(special_word) - while(found_here > -1): - end_here = found_here + len(special_word) - if (found_here == 0 or not text[found_here - 1].isalpha()) and \ - (end_here == len(text) or not text[end_here].isalpha()): - # It is beginning of text or char before is not alpha AND - # it is end of text or char after is not alpha - return True - # continue searching - start = found_here + 1 - found_here = text.find(special_word, start) - return False - - def set_subject(self, subject): - self.subject = subject - self.draw_banner_text() - - def got_connected(self): - # Make autorejoin stop. - if self.autorejoin: - gobject.source_remove(self.autorejoin) - self.autorejoin = None - - gajim.gc_connected[self.account][self.room_jid] = True - ChatControlBase.got_connected(self) - # We don't redraw the whole banner here, because only icon change - self._update_banner_state_image() - if self.parent_win: - self.parent_win.redraw_tab(self) - - def got_disconnected(self): - self.list_treeview.get_model().clear() - nick_list = gajim.contacts.get_nick_list(self.account, self.room_jid) - for nick in nick_list: - # Update pm chat window - fjid = self.room_jid + '/' + nick - gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, - nick) - - ctrl = gajim.interface.msg_win_mgr.get_control(fjid, self.account) - if ctrl: - gc_contact.show = 'offline' - gc_contact.status = '' - ctrl.update_ui() - if ctrl.parent_win: - ctrl.parent_win.redraw_tab(ctrl) - - gajim.contacts.remove_gc_contact(self.account, gc_contact) - gajim.gc_connected[self.account][self.room_jid] = False - ChatControlBase.got_disconnected(self) - # Tell connection to note the date we disconnect to avoid duplicate logs - gajim.connections[self.account].gc_got_disconnected(self.room_jid) - # We don't redraw the whole banner here, because only icon change - self._update_banner_state_image() - if self.parent_win: - self.parent_win.redraw_tab(self) - - # Autorejoin stuff goes here. - # Notice that we don't need to activate autorejoin if connection is lost - # or in progress. - if self.autorejoin is None and gajim.account_is_connected(self.account): - ar_to = gajim.config.get('muc_autorejoin_timeout') - if ar_to: - self.autorejoin = gobject.timeout_add_seconds(ar_to, self.rejoin) - - def rejoin(self): - if not self.autorejoin: - return False - password = gajim.gc_passwords.get(self.room_jid, '') - gajim.connections[self.account].join_gc(self.nick, self.room_jid, - password) - return True - - def draw_roster(self): - self.list_treeview.get_model().clear() - for nick in gajim.contacts.get_nick_list(self.account, self.room_jid): - gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, - nick) - self.add_contact_to_roster(nick, gc_contact.show, gc_contact.role, - gc_contact.affiliation, gc_contact.status, gc_contact.jid) - self.draw_all_roles() - # Recalculate column width for ellipsizin - self.list_treeview.columns_autosize() - - def on_send_pm(self, widget=None, model=None, iter_=None, nick=None, - msg=None): - """ - Open a chat window and if msg is not None - send private message to a - contact in a room - """ - if nick is None: - nick = model[iter_][C_NICK].decode('utf-8') - - ctrl = self._start_private_message(nick) - if ctrl and msg: - ctrl.send_message(msg) - - def on_send_file(self, widget, gc_contact): - """ - Send a file to a contact in the room - """ - self._on_send_file(gc_contact) - - def draw_contact(self, nick, selected=False, focus=False): - iter_ = self.get_contact_iter(nick) - if not iter_: - return - model = self.list_treeview.get_model() - gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, - nick) - state_images = gajim.interface.jabber_state_images['16'] - if len(gajim.events.get_events(self.account, self.room_jid + '/' + nick)): - image = state_images['event'] - else: - image = state_images[gc_contact.show] - - name = gobject.markup_escape_text(gc_contact.name) - - # Strike name if blocked - fjid = self.room_jid + '/' + nick - if helpers.jid_is_blocked(self.account, fjid): - name = '%s' % name - - status = gc_contact.status - # add status msg, if not empty, under contact name in the treeview - if status and gajim.config.get('show_status_msgs_in_roster'): - status = status.strip() - if status != '': - status = helpers.reduce_chars_newlines(status, max_lines=1) - # escape markup entities and make them small italic and fg color - color = gtkgui_helpers._get_fade_color(self.list_treeview, - selected, focus) - colorstring = "#%04x%04x%04x" % (color.red, color.green, color.blue) - name += ('\n' - '%s') % (colorstring, gobject.markup_escape_text(status)) - - if image.get_storage_type() == gtk.IMAGE_PIXBUF and \ - gc_contact.affiliation != 'none': - pixbuf1 = image.get_pixbuf().copy() - pixbuf2 = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, 4, 4) - if gc_contact.affiliation == 'owner': - pixbuf2.fill(0xff0000ff) # Red - elif gc_contact.affiliation == 'admin': - pixbuf2.fill(0xffb200ff) # Oragne - elif gc_contact.affiliation == 'member': - pixbuf2.fill(0x00ff00ff) # Green - pixbuf2.composite(pixbuf1, 12, 12, pixbuf2.get_property('width'), - pixbuf2.get_property('height'), 0, 0, 1.0, 1.0, - gtk.gdk.INTERP_HYPER, 127) - image = gtk.image_new_from_pixbuf(pixbuf1) - model[iter_][C_IMG] = image - model[iter_][C_TEXT] = name - - def draw_avatar(self, nick): - if not gajim.config.get('show_avatars_in_roster'): - return - model = self.list_treeview.get_model() - iter_ = self.get_contact_iter(nick) - if not iter_: - return - pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(self.room_jid + \ - '/' + nick, True) - if pixbuf in ('ask', None): - scaled_pixbuf = None - else: - scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'roster') - model[iter_][C_AVATAR] = scaled_pixbuf - - def draw_role(self, role): - role_iter = self.get_role_iter(role) - if not role_iter: - return - model = self.list_treeview.get_model() - role_name = helpers.get_uf_role(role, plural=True) - if gajim.config.get('show_contacts_number'): - nbr_role, nbr_total = gajim.contacts.get_nb_role_total_gc_contacts( - self.account, self.room_jid, role) - role_name += ' (%s/%s)' % (repr(nbr_role), repr(nbr_total)) - model[role_iter][C_TEXT] = role_name - - def draw_all_roles(self): - for role in ('visitor', 'participant', 'moderator'): - self.draw_role(role) - - def chg_contact_status(self, nick, show, status, role, affiliation, jid, - reason, actor, statusCode, new_nick, avatar_sha, tim=None): - """ - When an occupant changes his or her status - """ - if show == 'invisible': - return - - if not role: - role = 'visitor' - if not affiliation: - affiliation = 'none' - fake_jid = self.room_jid + '/' + nick - newly_created = False - nick_jid = nick - - # Set to true if role or affiliation have changed - right_changed = False - - if jid: - # delete ressource - simple_jid = gajim.get_jid_without_resource(jid) - nick_jid += ' (%s)' % simple_jid - - # statusCode - # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes-init - if statusCode: - if '100' in statusCode: - # Can be a message (see handle_event_gc_config_change in gajim.py) - self.print_conversation(\ - _('Any occupant is allowed to see your full JID')) - if '170' in statusCode: - # Can be a message (see handle_event_gc_config_change in gajim.py) - self.print_conversation(_('Room logging is enabled')) - if '201' in statusCode: - self.print_conversation(_('A new room has been created')) - if '210' in statusCode: - self.print_conversation(\ - _('The server has assigned or modified your roomnick')) - - if show in ('offline', 'error'): - if statusCode: - if '307' in statusCode: - if actor is None: # do not print 'kicked by None' - s = _('%(nick)s has been kicked: %(reason)s') % { - 'nick': nick, - 'reason': reason } - else: - s = _('%(nick)s has been kicked by %(who)s: %(reason)s') % { - 'nick': nick, - 'who': actor, - 'reason': reason } - self.print_conversation(s, 'info', tim=tim, graphics=False) - if nick == self.nick and not gajim.config.get( - 'muc_autorejoin_on_kick'): - self.autorejoin = False - elif '301' in statusCode: - if actor is None: # do not print 'banned by None' - s = _('%(nick)s has been banned: %(reason)s') % { - 'nick': nick, - 'reason': reason } - else: - s = _('%(nick)s has been banned by %(who)s: %(reason)s') % { - 'nick': nick, - 'who': actor, - 'reason': reason } - self.print_conversation(s, 'info', tim=tim, graphics=False) - if nick == self.nick: - self.autorejoin = False - elif '303' in statusCode: # Someone changed his or her nick - if new_nick == self.new_nick or nick == self.nick: - # We changed our nick - self.nick = new_nick - self.new_nick = '' - s = _('You are now known as %s') % new_nick - # Stop all E2E sessions - nick_list = gajim.contacts.get_nick_list(self.account, - self.room_jid) - for nick_ in nick_list: - fjid_ = self.room_jid + '/' + nick_ - ctrl = gajim.interface.msg_win_mgr.get_control(fjid_, - self.account) - if ctrl and ctrl.session and \ - ctrl.session.enable_encryption: - thread_id = ctrl.session.thread_id - ctrl.session.terminate_e2e() - gajim.connections[self.account].delete_session(fjid_, - thread_id) - ctrl.no_autonegotiation = False - else: - s = _('%(nick)s is now known as %(new_nick)s') % { - 'nick': nick, 'new_nick': new_nick} - # We add new nick to muc roster here, so we don't see - # that "new_nick has joined the room" when he just changed nick. - # add_contact_to_roster will be called a second time - # after that, but that doesn't hurt - self.add_contact_to_roster(new_nick, show, role, affiliation, - status, jid) - if nick in self.attention_list: - self.attention_list.remove(nick) - # keep nickname color - if nick in self.gc_custom_colors: - self.gc_custom_colors[new_nick] = \ - self.gc_custom_colors[nick] - # rename vcard / avatar - puny_jid = helpers.sanitize_filename(self.room_jid) - puny_nick = helpers.sanitize_filename(nick) - puny_new_nick = helpers.sanitize_filename(new_nick) - old_path = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) - new_path = os.path.join(gajim.VCARD_PATH, puny_jid, - puny_new_nick) - files = {old_path: new_path} - path = os.path.join(gajim.AVATAR_PATH, puny_jid) - # possible extensions - for ext in ('.png', '.jpeg', '_notif_size_bw.png', - '_notif_size_colored.png'): - files[os.path.join(path, puny_nick + ext)] = \ - os.path.join(path, puny_new_nick + ext) - for old_file in files: - if os.path.exists(old_file) and old_file != files[old_file]: - if os.path.exists(files[old_file]) and helpers.windowsify( - old_file) != helpers.windowsify(files[old_file]): - # Windows require this, but os.remove('test') will also - # remove 'TEST' - os.remove(files[old_file]) - os.rename(old_file, files[old_file]) - self.print_conversation(s, 'info', tim=tim, graphics=False) - elif '321' in statusCode: - s = _('%(nick)s has been removed from the room (%(reason)s)') % { - 'nick': nick, 'reason': _('affiliation changed') } - self.print_conversation(s, 'info', tim=tim, graphics=False) - elif '322' in statusCode: - s = _('%(nick)s has been removed from the room (%(reason)s)') % { - 'nick': nick, - 'reason': _('room configuration changed to members-only') } - self.print_conversation(s, 'info', tim=tim, graphics=False) - elif '332' in statusCode: - s = _('%(nick)s has been removed from the room (%(reason)s)') % { - 'nick': nick, - 'reason': _('system shutdown') } - self.print_conversation(s, 'info', tim=tim, graphics=False) - elif 'destroyed' in statusCode: # Room has been destroyed - self.print_conversation(reason, 'info', tim, graphics=False) - - if len(gajim.events.get_events(self.account, jid=fake_jid, - types=['pm'])) == 0: - self.remove_contact(nick) - self.draw_all_roles() - else: - c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick) - c.show = show - c.status = status - if nick == self.nick and (not statusCode or \ - '303' not in statusCode): # We became offline - self.got_disconnected() - contact = gajim.contacts.\ - get_contact_with_highest_priority(self.account, self.room_jid) - if contact: - gajim.interface.roster.draw_contact(self.room_jid, self.account) - if self.parent_win: - self.parent_win.redraw_tab(self) - else: - iter_ = self.get_contact_iter(nick) - if not iter_: - if '210' in statusCode: - # Server changed our nick - self.nick = nick - s = _('You are now known as %s') % nick - self.print_conversation(s, 'info', tim=tim, graphics=False) - iter_ = self.add_contact_to_roster(nick, show, role, affiliation, - status, jid) - newly_created = True - self.draw_all_roles() - if statusCode and '201' in statusCode: # We just created the room - gajim.connections[self.account].request_gc_config(self.room_jid) - else: - gc_c = gajim.contacts.get_gc_contact(self.account, self.room_jid, - nick) - if not gc_c: - log.error('%s has an iter, but no gc_contact instance') - return - # Re-get vcard if avatar has changed - # We do that here because we may request it to the real JID if we - # knows it. connections.py doesn't know it. - con = gajim.connections[self.account] - if gc_c and gc_c.jid: - real_jid = gc_c.jid - if gc_c.resource: - real_jid += '/' + gc_c.resource - else: - real_jid = fake_jid - if fake_jid in con.vcard_shas: - if avatar_sha != con.vcard_shas[fake_jid]: - server = gajim.get_server_from_jid(self.room_jid) - if not server.startswith('irc'): - con.request_vcard(real_jid, fake_jid) - else: - cached_vcard = con.get_cached_vcard(fake_jid, True) - if cached_vcard and 'PHOTO' in cached_vcard and \ - 'SHA' in cached_vcard['PHOTO']: - cached_sha = cached_vcard['PHOTO']['SHA'] - else: - cached_sha = '' - if cached_sha != avatar_sha: - # avatar has been updated - # sha in mem will be updated later - server = gajim.get_server_from_jid(self.room_jid) - if not server.startswith('irc'): - con.request_vcard(real_jid, fake_jid) - else: - # save sha in mem NOW - con.vcard_shas[fake_jid] = avatar_sha - - actual_affiliation = gc_c.affiliation - if affiliation != actual_affiliation: - if actor: - st = _('** Affiliation of %(nick)s has been set to ' - '%(affiliation)s by %(actor)s') % {'nick': nick_jid, - 'affiliation': affiliation, 'actor': actor} - else: - st = _('** Affiliation of %(nick)s has been set to ' - '%(affiliation)s') % {'nick': nick_jid, - 'affiliation': affiliation} - if reason: - st += ' (%s)' % reason - self.print_conversation(st, tim=tim, graphics=False) - right_changed = True - actual_role = self.get_role(nick) - if role != actual_role: - self.remove_contact(nick) - self.add_contact_to_roster(nick, show, role, - affiliation, status, jid) - self.draw_role(actual_role) - self.draw_role(role) - if actor: - st = _('** Role of %(nick)s has been set to %(role)s by ' - '%(actor)s') % {'nick': nick_jid, 'role': role, - 'actor': actor} - else: - st = _('** Role of %(nick)s has been set to %(role)s') % { - 'nick': nick_jid, 'role': role} - if reason: - st += ' (%s)' % reason - self.print_conversation(st, tim=tim, graphics=False) - right_changed = True - else: - if gc_c.show == show and gc_c.status == status and \ - gc_c.affiliation == affiliation: # no change - return - gc_c.show = show - gc_c.affiliation = affiliation - gc_c.status = status - self.draw_contact(nick) - if (time.time() - self.room_creation) > 30 and nick != self.nick and \ - (not statusCode or '303' not in statusCode) and not right_changed: - st = '' - print_status = None - for bookmark in gajim.connections[self.account].bookmarks: - if bookmark['jid'] == self.room_jid: - print_status = bookmark.get('print_status', None) - break - if not print_status: - print_status = gajim.config.get('print_status_in_muc') - if show == 'offline': - if nick in self.attention_list: - self.attention_list.remove(nick) - if show == 'offline' and print_status in ('all', 'in_and_out') and \ - (not statusCode or '307' not in statusCode): - st = _('%s has left') % nick_jid - if reason: - st += ' [%s]' % reason - else: - if newly_created and print_status in ('all', 'in_and_out'): - st = _('%s has joined the group chat') % nick_jid - elif print_status == 'all': - st = _('%(nick)s is now %(status)s') % {'nick': nick_jid, - 'status': helpers.get_uf_show(show)} - if st: - if status: - st += ' (' + status + ')' - self.print_conversation(st, tim=tim, graphics=False) - - def add_contact_to_roster(self, nick, show, role, affiliation, status, - jid=''): - model = self.list_treeview.get_model() - role_name = helpers.get_uf_role(role, plural=True) - - resource = '' - if jid: - jids = jid.split('/', 1) - j = jids[0] - if len(jids) > 1: - resource = jids[1] - else: - j = '' - - name = nick - - role_iter = self.get_role_iter(role) - if not role_iter: - role_iter = model.append(None, - (gajim.interface.jabber_state_images['16']['closed'], role, - 'role', role_name, None)) - self.draw_all_roles() - iter_ = model.append(role_iter, (None, nick, 'contact', name, None)) - if not nick in gajim.contacts.get_nick_list(self.account, self.room_jid): - gc_contact = gajim.contacts.create_gc_contact(room_jid=self.room_jid, account=self.account, - name=nick, show=show, status=status, role=role, - affiliation=affiliation, jid=j, resource=resource) - gajim.contacts.add_gc_contact(self.account, gc_contact) - self.draw_contact(nick) - self.draw_avatar(nick) - # Do not ask avatar to irc rooms as irc transports reply with messages - server = gajim.get_server_from_jid(self.room_jid) - if gajim.config.get('ask_avatars_on_startup') and \ - not server.startswith('irc'): - fake_jid = self.room_jid + '/' + nick - pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(fake_jid, True) - if pixbuf == 'ask': - if j: - fjid = j - if resource: - fjid += '/' + resource - gajim.connections[self.account].request_vcard(fjid, fake_jid) - else: - gajim.connections[self.account].request_vcard(fake_jid, fake_jid) - if nick == self.nick: # we became online - self.got_connected() - self.list_treeview.expand_row((model.get_path(role_iter)), False) - if self.is_continued: - self.draw_banner_text() - return iter_ - - def get_role_iter(self, role): - model = self.list_treeview.get_model() - fin = False - iter_ = model.get_iter_root() - if not iter_: - return None - while not fin: - role_name = model[iter_][C_NICK].decode('utf-8') - if role == role_name: - return iter_ - iter_ = model.iter_next(iter_) - if not iter_: - fin = True - return None - - def remove_contact(self, nick): - """ - Remove a user from the contacts_list - """ - model = self.list_treeview.get_model() - iter_ = self.get_contact_iter(nick) - if not iter_: - return - gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, - nick) - if gc_contact: - gajim.contacts.remove_gc_contact(self.account, gc_contact) - parent_iter = model.iter_parent(iter_) - model.remove(iter_) - if model.iter_n_children(parent_iter) == 0: - model.remove(parent_iter) - - def send_message(self, message, xhtml=None, process_commands=True): - """ - Call this function to send our message - """ - if not message: - return - - if process_commands and self.process_as_command(message): - return - - message = helpers.remove_invalid_xml_chars(message) - - if not message: - return - - if message != '' or message != '\n': - self.save_sent_message(message) - - # Send the message - gajim.connections[self.account].send_gc_message(self.room_jid, - message, xhtml=xhtml) - self.msg_textview.get_buffer().set_text('') - self.msg_textview.grab_focus() - - def get_role(self, nick): - gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, - nick) - if gc_contact: - return gc_contact.role - else: - return 'visitor' - - def minimizable(self): - if self.contact.jid in gajim.config.get_per('accounts', self.account, - 'minimized_gc').split(' '): - return True - return False - - def minimize(self, status='offline'): - # Minimize it - win = gajim.interface.msg_win_mgr.get_window(self.contact.jid, - self.account) - ctrl = win.get_control(self.contact.jid, self.account) - - ctrl_page = win.notebook.page_num(ctrl.widget) - control = win.notebook.get_nth_page(ctrl_page) - - win.notebook.remove_page(ctrl_page) - control.unparent() - ctrl.parent_win = None - - gajim.interface.roster.add_groupchat(self.contact.jid, self.account, - status = self.subject) - - del win._controls[self.account][self.contact.jid] - - def shutdown(self, status='offline'): - # Preventing autorejoin from being activated - self.autorejoin = False - - if self.room_jid in gajim.gc_connected[self.account] and \ - gajim.gc_connected[self.account][self.room_jid]: - # Tell connection to note the date we disconnect to avoid duplicate - # logs. We do it only when connected because if connection was lost - # there may be new messages since disconnection. - gajim.connections[self.account].gc_got_disconnected(self.room_jid) - gajim.connections[self.account].send_gc_status(self.nick, self.room_jid, - show='offline', status=status) - nick_list = gajim.contacts.get_nick_list(self.account, self.room_jid) - for nick in nick_list: - # Update pm chat window - fjid = self.room_jid + '/' + nick - ctrl = gajim.interface.msg_win_mgr.get_gc_control(fjid, self.account) - if ctrl: - contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, - nick) - contact.show = 'offline' - contact.status = '' - ctrl.update_ui() - ctrl.parent_win.redraw_tab(ctrl) - for sess in gajim.connections[self.account].get_sessions(fjid): - if sess.control: - sess.control.no_autonegotiation = False - if sess.enable_encryption: - sess.terminate_e2e() - gajim.connections[self.account].delete_session(fjid, - sess.thread_id) - # They can already be removed by the destroy function - if self.room_jid in gajim.contacts.get_gc_list(self.account): - gajim.contacts.remove_room(self.account, self.room_jid) - del gajim.gc_connected[self.account][self.room_jid] - # Save hpaned position - gajim.config.set('gc-hpaned-position', self.hpaned.get_position()) - # remove all register handlers on wigets, created by self.xml - # to prevent circular references among objects - for i in self.handlers.keys(): - if self.handlers[i].handler_is_connected(i): - self.handlers[i].disconnect(i) - del self.handlers[i] - # Remove unread events from systray - gajim.events.remove_events(self.account, self.room_jid) - - def safe_shutdown(self): - if self.minimizable(): - return True - includes = gajim.config.get('confirm_close_muc_rooms').split(' ') - excludes = gajim.config.get('noconfirm_close_muc_rooms').split(' ') - # whether to ask for comfirmation before closing muc - if (gajim.config.get('confirm_close_muc') or self.room_jid in includes) \ - and gajim.gc_connected[self.account][self.room_jid] and self.room_jid not\ - in excludes: - return False - return True - - def allow_shutdown(self, method, on_yes, on_no, on_minimize): - if self.minimizable(): - on_minimize(self) - return - if method == self.parent_win.CLOSE_ESC: - iter_ = self.list_treeview.get_selection().get_selected()[1] - if iter_: - self.list_treeview.get_selection().unselect_all() - on_no(self) - return - includes = gajim.config.get('confirm_close_muc_rooms').split(' ') - excludes = gajim.config.get('noconfirm_close_muc_rooms').split(' ') - # whether to ask for comfirmation before closing muc - if (gajim.config.get('confirm_close_muc') or self.room_jid in includes) \ - and gajim.gc_connected[self.account][self.room_jid] and self.room_jid not\ - in excludes: - - def on_ok(clicked): - if clicked: - # user does not want to be asked again - gajim.config.set('confirm_close_muc', False) - on_yes(self) - - def on_cancel(clicked): - if clicked: - # user does not want to be asked again - gajim.config.set('confirm_close_muc', False) - on_no(self) - - pritext = _('Are you sure you want to leave group chat "%s"?')\ - % self.name - sectext = _('If you close this window, you will be disconnected ' - 'from this group chat.') - - dialogs.ConfirmationDialogCheck(pritext, sectext, - _('Do _not ask me again'), on_response_ok=on_ok, - on_response_cancel=on_cancel) - return - - on_yes(self) - - def set_control_active(self, state): - self.conv_textview.allow_focus_out_line = True - self.attention_flag = False - ChatControlBase.set_control_active(self, state) - if not state: - # add the focus-out line to the tab we are leaving - self.check_and_possibly_add_focus_out_line() - # Sending active to undo unread state - self.parent_win.redraw_tab(self, 'active') - - def get_specific_unread(self): - # returns the number of the number of unread msgs - # for room_jid & number of unread private msgs with each contact - # that we have - nb = 0 - for nick in gajim.contacts.get_nick_list(self.account, self.room_jid): - fjid = self.room_jid + '/' + nick - nb += len(gajim.events.get_events(self.account, fjid)) - # gc can only have messages as event - return nb - - def _on_change_subject_menuitem_activate(self, widget): - def on_ok(subject): - # Note, we don't update self.subject since we don't know whether it - # will work yet - gajim.connections[self.account].send_gc_subject(self.room_jid, subject) - - dialogs.InputTextDialog(_('Changing Subject'), - _('Please specify the new subject:'), input_str=self.subject, - ok_handler=on_ok) - - def _on_change_nick_menuitem_activate(self, widget): - if 'change_nick_dialog' in gajim.interface.instances: - gajim.interface.instances['change_nick_dialog'].present() - else: - title = _('Changing Nickname') - prompt = _('Please specify the new nickname you want to use:') - gajim.interface.instances['change_nick_dialog'] = \ - dialogs.ChangeNickDialog(self.account, self.room_jid, title, - prompt) - - def _on_configure_room_menuitem_activate(self, widget): - c = gajim.contacts.get_gc_contact(self.account, self.room_jid, self.nick) - if c.affiliation == 'owner': - gajim.connections[self.account].request_gc_config(self.room_jid) - elif c.affiliation == 'admin': - if self.room_jid not in gajim.interface.instances[self.account][ - 'gc_config']: - gajim.interface.instances[self.account]['gc_config'][self.room_jid]\ - = config.GroupchatConfigWindow(self.account, self.room_jid) - - def _on_destroy_room_menuitem_activate(self, widget): - def on_ok(reason, jid): - if jid: - # Test jid - try: - jid = helpers.parse_jid(jid) - except Exception: - dialogs.ErrorDialog(_('Invalid group chat Jabber ID'), - _('The group chat Jabber ID has not allowed characters.')) - return - gajim.connections[self.account].destroy_gc_room(self.room_jid, reason, - jid) - - # Ask for a reason - dialogs.DubbleInputDialog(_('Destroying %s') % self.room_jid, - _('You are going to definitively destroy this room.\n' - 'You may specify a reason below:'), - _('You may also enter an alternate venue:'), ok_handler=on_ok) - - def _on_bookmark_room_menuitem_activate(self, widget): - """ - Bookmark the room, without autojoin and not minimized - """ - password = gajim.gc_passwords.get(self.room_jid, '') - gajim.interface.add_gc_bookmark(self.account, self.name, self.room_jid, \ - '0', '0', password, self.nick) - - def _on_drag_data_received(self, widget, context, x, y, selection, - target_type, timestamp): - # Invite contact to groupchat - treeview = gajim.interface.roster.tree - model = treeview.get_model() - if not selection.data or target_type == 80: - # target_type = 80 means a file is dropped - return - data = selection.data - path = treeview.get_selection().get_selected_rows()[1][0] - iter_ = model.get_iter(path) - type_ = model[iter_][2] - if type_ != 'contact': # source is not a contact - return - contact_jid = data.decode('utf-8') - gajim.connections[self.account].send_invite(self.room_jid, contact_jid) - - def handle_message_textview_mykey_press(self, widget, event_keyval, - event_keymod): - # NOTE: handles mykeypress which is custom signal connected to this - # CB in new_room(). for this singal see message_textview.py - - # construct event instance from binding - event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here - event.keyval = event_keyval - event.state = event_keymod - event.time = 0 # assign current time - - message_buffer = widget.get_buffer() - start_iter, end_iter = message_buffer.get_bounds() - - if event.keyval == gtk.keysyms.Tab: # TAB - cursor_position = message_buffer.get_insert() - end_iter = message_buffer.get_iter_at_mark(cursor_position) - text = message_buffer.get_text(start_iter, end_iter, False).decode( - 'utf-8') - - splitted_text = text.split() - - # HACK: Not the best soltution. - if (text.startswith(self.COMMAND_PREFIX) and not - text.startswith(self.COMMAND_PREFIX * 2) and len(splitted_text) == 1): - return super(GroupchatControl, - self).handle_message_textview_mykey_press(widget, event_keyval, - event_keymod) - - # nick completion - # check if tab is pressed with empty message - if len(splitted_text): # if there are any words - begin = splitted_text[-1] # last word we typed - else: - begin = '' - - gc_refer_to_nick_char = gajim.config.get('gc_refer_to_nick_char') - with_refer_to_nick_char = False - - # first part of this if : works fine even if refer_to_nick_char - if gc_refer_to_nick_char and begin.endswith(gc_refer_to_nick_char): - with_refer_to_nick_char = True - if len(self.nick_hits) and self.last_key_tabs and \ - text[:-len(gc_refer_to_nick_char + ' ')].endswith(self.nick_hits[0]): - # we should cycle - # Previous nick in list may had a space inside, so we check text and - # not splitted_text and store it into 'begin' var - self.nick_hits.append(self.nick_hits[0]) - begin = self.nick_hits.pop(0) - else: - self.nick_hits = [] # clear the hit list - list_nick = gajim.contacts.get_nick_list(self.account, - self.room_jid) - list_nick.sort(key=unicode.lower) # case-insensitive sort - if begin == '': - # empty message, show lasts nicks that highlighted us first - for nick in self.attention_list: - if nick in list_nick: - list_nick.remove(nick) - list_nick.insert(0, nick) - - list_nick.remove(self.nick) # Skip self - for nick in list_nick: - if nick.lower().startswith(begin.lower()): - # the word is the begining of a nick - self.nick_hits.append(nick) - if len(self.nick_hits): - if len(splitted_text) < 2 or with_refer_to_nick_char: - # This is the 1st word of the line or no word or we are cycling - # at the beginning, possibly with a space in one nick - add = gc_refer_to_nick_char + ' ' - else: - add = ' ' - start_iter = end_iter.copy() - if self.last_key_tabs and with_refer_to_nick_char or (text and \ - text[-1] == ' '): - # have to accomodate for the added space from last - # completion - # gc_refer_to_nick_char may be more than one char! - start_iter.backward_chars(len(begin) + len(add)) - elif self.last_key_tabs and not gajim.config.get( - 'shell_like_completion'): - # have to accomodate for the added space from last - # completion - start_iter.backward_chars(len(begin) + \ - len(gc_refer_to_nick_char)) - else: - start_iter.backward_chars(len(begin)) - - message_buffer.delete(start_iter, end_iter) - # get a shell-like completion - # if there's more than one nick for this completion, complete only - # the part that all these nicks have in common - if gajim.config.get('shell_like_completion') and \ - len(self.nick_hits) > 1: - end = False - completion = '' - add = "" # if nick is not complete, don't add anything - while not end and len(completion) < len(self.nick_hits[0]): - completion = self.nick_hits[0][:len(completion)+1] - for nick in self.nick_hits: - if completion.lower() not in nick.lower(): - end = True - completion = completion[:-1] - break - # if the current nick matches a COMPLETE existing nick, - # and if the user tab TWICE, complete that nick (with the "add") - if self.last_key_tabs: - for nick in self.nick_hits: - if nick == completion: - # The user seems to want this nick, so - # complete it as if it were the only nick - # available - add = gc_refer_to_nick_char + ' ' - else: - completion = self.nick_hits[0] - message_buffer.insert_at_cursor(completion + add) - self.last_key_tabs = True - return True - self.last_key_tabs = False - - def on_list_treeview_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: - selection = widget.get_selection() - iter_ = selection.get_selected()[1] - if iter_: - widget.get_selection().unselect_all() - return True - - def on_list_treeview_row_expanded(self, widget, iter_, path): - """ - When a row is expanded: change the icon of the arrow - """ - model = widget.get_model() - image = gajim.interface.jabber_state_images['16']['opened'] - model[iter_][C_IMG] = image - - def on_list_treeview_row_collapsed(self, widget, iter_, path): - """ - When a row is collapsed: change the icon of the arrow - """ - model = widget.get_model() - image = gajim.interface.jabber_state_images['16']['closed'] - model[iter_][C_IMG] = image - - def kick(self, widget, nick): - """ - Kick a user - """ - def on_ok(reason): - gajim.connections[self.account].gc_set_role(self.room_jid, nick, - 'none', reason) - - # ask for reason - dialogs.InputDialog(_('Kicking %s') % nick, - _('You may specify a reason below:'), ok_handler=on_ok) - - def mk_menu(self, event, iter_): - """ - Make contact's popup menu - """ - model = self.list_treeview.get_model() - nick = model[iter_][C_NICK].decode('utf-8') - c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick) - fjid = self.room_jid + '/' + nick - jid = c.jid - target_affiliation = c.affiliation - target_role = c.role - - # looking for user's affiliation and role - user_nick = self.nick - user_affiliation = gajim.contacts.get_gc_contact(self.account, - self.room_jid, user_nick).affiliation - user_role = self.get_role(user_nick) - - # making menu from glade - xml = gtkgui_helpers.get_glade('gc_occupants_menu.glade') - - # these conditions were taken from JEP 0045 - item = xml.get_widget('kick_menuitem') - if user_role != 'moderator' or \ - (user_affiliation == 'admin' and target_affiliation == 'owner') or \ - (user_affiliation == 'member' and target_affiliation in ('admin', - 'owner')) or (user_affiliation == 'none' and target_affiliation != \ - 'none'): - item.set_sensitive(False) - id_ = item.connect('activate', self.kick, nick) - self.handlers[id_] = item - - item = xml.get_widget('voice_checkmenuitem') - item.set_active(target_role != 'visitor') - if user_role != 'moderator' or \ - user_affiliation == 'none' or \ - (user_affiliation=='member' and target_affiliation!='none') or \ - target_affiliation in ('admin', 'owner'): - item.set_sensitive(False) - id_ = item.connect('activate', self.on_voice_checkmenuitem_activate, - nick) - self.handlers[id_] = item - - item = xml.get_widget('moderator_checkmenuitem') - item.set_active(target_role == 'moderator') - if not user_affiliation in ('admin', 'owner') or \ - target_affiliation in ('admin', 'owner'): - item.set_sensitive(False) - id_ = item.connect('activate', self.on_moderator_checkmenuitem_activate, - nick) - self.handlers[id_] = item - - item = xml.get_widget('ban_menuitem') - if not user_affiliation in ('admin', 'owner') or \ - (target_affiliation in ('admin', 'owner') and\ - user_affiliation != 'owner'): - item.set_sensitive(False) - id_ = item.connect('activate', self.ban, jid) - self.handlers[id_] = item - - item = xml.get_widget('member_checkmenuitem') - item.set_active(target_affiliation != 'none') - if not user_affiliation in ('admin', 'owner') or \ - (user_affiliation != 'owner' and target_affiliation in ('admin','owner')): - item.set_sensitive(False) - id_ = item.connect('activate', self.on_member_checkmenuitem_activate, jid) - self.handlers[id_] = item - - item = xml.get_widget('admin_checkmenuitem') - item.set_active(target_affiliation in ('admin', 'owner')) - if not user_affiliation == 'owner': - item.set_sensitive(False) - id_ = item.connect('activate', self.on_admin_checkmenuitem_activate, jid) - self.handlers[id_] = item - - item = xml.get_widget('owner_checkmenuitem') - item.set_active(target_affiliation == 'owner') - if not user_affiliation == 'owner': - item.set_sensitive(False) - id_ = item.connect('activate', self.on_owner_checkmenuitem_activate, jid) - self.handlers[id_] = item - - item = xml.get_widget('information_menuitem') - id_ = item.connect('activate', self.on_info, nick) - self.handlers[id_] = item - - item = xml.get_widget('history_menuitem') - id_ = item.connect('activate', self.on_history, nick) - self.handlers[id_] = item - - item = xml.get_widget('add_to_roster_menuitem') - our_jid = gajim.get_jid_from_account(self.account) - if not jid or jid == our_jid: - item.set_sensitive(False) - else: - id_ = item.connect('activate', self.on_add_to_roster, jid) - self.handlers[id_] = item - - item = xml.get_widget('block_menuitem') - item2 = xml.get_widget('unblock_menuitem') - if helpers.jid_is_blocked(self.account, fjid): - item.set_no_show_all(True) - item.hide() - id_ = item2.connect('activate', self.on_unblock, nick) - self.handlers[id_] = item2 - else: - id_ = item.connect('activate', self.on_block, nick) - self.handlers[id_] = item - item2.set_no_show_all(True) - item2.hide() - - item = xml.get_widget('send_private_message_menuitem') - id_ = item.connect('activate', self.on_send_pm, model, iter_) - self.handlers[id_] = item - - item = xml.get_widget('send_file_menuitem') - # add a special img for send file menuitem - path_to_upload_img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'upload.png') - img = gtk.Image() - img.set_from_file(path_to_upload_img) - item.set_image(img) - - if not c.resource: - item.set_sensitive(False) - else: - id_ = item.connect('activate', self.on_send_file, c) - self.handlers[id_] = item - - # show the popup now! - menu = xml.get_widget('gc_occupants_menu') - menu.show_all() - menu.popup(None, None, None, event.button, event.time) - - def _start_private_message(self, nick): - gc_c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick) - nick_jid = gc_c.get_full_jid() - - ctrl = gajim.interface.msg_win_mgr.get_control(nick_jid, self.account) - if not ctrl: - ctrl = gajim.interface.new_private_chat(gc_c, self.account) - - if ctrl: - ctrl.parent_win.set_active_tab(ctrl) - - return ctrl - - def on_row_activated(self, widget, path): - """ - When an iter is activated (dubblick or single click if gnome is set this - way - """ - model = widget.get_model() - if len(path) == 1: # It's a group - if (widget.row_expanded(path)): - widget.collapse_row(path) - else: - widget.expand_row(path, False) - else: # We want to send a private message - nick = model[path][C_NICK].decode('utf-8') - self._start_private_message(nick) - - def on_list_treeview_row_activated(self, widget, path, col=0): - """ - When an iter is double clicked: open the chat window - """ - if not gajim.single_click: - self.on_row_activated(widget, path) - - def on_list_treeview_button_press_event(self, widget, event): - """ - Popup user's group's or agent menu - """ - # hide tooltip, no matter the button is pressed - self.tooltip.hide_tooltip() - try: - pos = widget.get_path_at_pos(int(event.x), int(event.y)) - path, x = pos[0], pos[2] - except TypeError: - widget.get_selection().unselect_all() - return - if event.button == 3: # right click - widget.get_selection().select_path(path) - model = widget.get_model() - iter_ = model.get_iter(path) - if len(path) == 2: - self.mk_menu(event, iter_) - return True - - elif event.button == 2: # middle click - widget.get_selection().select_path(path) - model = widget.get_model() - iter_ = model.get_iter(path) - if len(path) == 2: - nick = model[iter_][C_NICK].decode('utf-8') - self._start_private_message(nick) - return True - - elif event.button == 1: # left click - if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK: - self.on_row_activated(widget, path) - return True - else: - model = widget.get_model() - iter_ = model.get_iter(path) - nick = model[iter_][C_NICK].decode('utf-8') - if not nick in gajim.contacts.get_nick_list(self.account, - self.room_jid): - # it's a group - if x < 27: - if (widget.row_expanded(path)): - widget.collapse_row(path) - else: - widget.expand_row(path, False) - elif event.state & gtk.gdk.SHIFT_MASK: - self.append_nick_in_msg_textview(self.msg_textview, nick) - self.msg_textview.grab_focus() - return True - - def append_nick_in_msg_textview(self, widget, nick): - message_buffer = self.msg_textview.get_buffer() - start_iter, end_iter = message_buffer.get_bounds() - cursor_position = message_buffer.get_insert() - end_iter = message_buffer.get_iter_at_mark(cursor_position) - text = message_buffer.get_text(start_iter, end_iter, False) - start = '' - if text: # Cursor is not at first position - if not text[-1] in (' ', '\n', '\t'): - start = ' ' - add = ' ' - else: - gc_refer_to_nick_char = gajim.config.get('gc_refer_to_nick_char') - add = gc_refer_to_nick_char + ' ' - message_buffer.insert_at_cursor(start + nick + add) - - def on_list_treeview_motion_notify_event(self, widget, event): - model = widget.get_model() - props = widget.get_path_at_pos(int(event.x), int(event.y)) - if self.tooltip.timeout > 0: - if not props or self.tooltip.id != props[0]: - self.tooltip.hide_tooltip() - if props: - [row, col, x, y] = props - iter_ = None - try: - iter_ = model.get_iter(row) - except Exception: - self.tooltip.hide_tooltip() - return - typ = model[iter_][C_TYPE].decode('utf-8') - if typ == 'contact': - account = self.account - - if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: - self.tooltip.id = row - nick = model[iter_][C_NICK].decode('utf-8') - self.tooltip.timeout = gobject.timeout_add(500, - self.show_tooltip, gajim.contacts.get_gc_contact(account, - self.room_jid, nick)) - - def on_list_treeview_leave_notify_event(self, widget, event): - props = widget.get_path_at_pos(int(event.x), int(event.y)) - if self.tooltip.timeout > 0: - if not props or self.tooltip.id == props[0]: - self.tooltip.hide_tooltip() - - def show_tooltip(self, contact): - if not self.list_treeview.window: - # control has been destroyed since tooltip was requested - return - pointer = self.list_treeview.get_pointer() - props = self.list_treeview.get_path_at_pos(pointer[0], pointer[1]) - # check if the current pointer is at the same path - # as it was before setting the timeout - if props and self.tooltip.id == props[0]: - rect = self.list_treeview.get_cell_area(props[0],props[1]) - position = self.list_treeview.window.get_origin() - self.tooltip.show_tooltip(contact, rect.height, - position[1] + rect.y) - else: - self.tooltip.hide_tooltip() - - def grant_voice(self, widget, nick): - """ - Grant voice privilege to a user - """ - gajim.connections[self.account].gc_set_role(self.room_jid, nick, - 'participant') - - def revoke_voice(self, widget, nick): - """ - Revoke voice privilege to a user - """ - gajim.connections[self.account].gc_set_role(self.room_jid, nick, - 'visitor') - - def grant_moderator(self, widget, nick): - """ - Grant moderator privilege to a user - """ - gajim.connections[self.account].gc_set_role(self.room_jid, nick, - 'moderator') - - def revoke_moderator(self, widget, nick): - """ - Revoke moderator privilege to a user - """ - gajim.connections[self.account].gc_set_role(self.room_jid, nick, - 'participant') - - def ban(self, widget, jid): - """ - Ban a user - """ - def on_ok(reason): - gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, - 'outcast', reason) - - # to ban we know the real jid. so jid is not fakejid - nick = gajim.get_nick_from_jid(jid) - # ask for reason - dialogs.InputDialog(_('Banning %s') % nick, - _('You may specify a reason below:'), ok_handler=on_ok) - - def grant_membership(self, widget, jid): - """ - Grant membership privilege to a user - """ - gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, - 'member') - - def revoke_membership(self, widget, jid): - """ - Revoke membership privilege to a user - """ - gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, - 'none') - - def grant_admin(self, widget, jid): - """ - Grant administrative privilege to a user - """ - gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, - 'admin') - - def revoke_admin(self, widget, jid): - """ - Revoke administrative privilege to a user - """ - gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, - 'member') - - def grant_owner(self, widget, jid): - """ - Grant owner privilege to a user - """ - gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, - 'owner') - - def revoke_owner(self, widget, jid): - """ - Revoke owner privilege to a user - """ - gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, - 'admin') - - def on_info(self, widget, nick): - '''Call vcard_information_window class to display user's information''' - gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick) - contact = gc_contact.as_contact() - if contact.jid in gajim.interface.instances[self.account]['infos']: - gajim.interface.instances[self.account]['infos'][contact.jid].window.\ - present() - else: - gajim.interface.instances[self.account]['infos'][contact.jid] = \ - vcard.VcardWindow(contact, self.account, gc_contact) - - def on_history(self, widget, nick): - jid = gajim.construct_fjid(self.room_jid, nick) - self._on_history_menuitem_activate(widget=widget, jid=jid) - - def on_add_to_roster(self, widget, jid): - dialogs.AddNewContactWindow(self.account, jid) - - def on_block(self, widget, nick): - fjid = self.room_jid + '/' + nick - connection = gajim.connections[self.account] - if fjid in connection.blocked_contacts: - return - new_rule = {'order': u'1', 'type': u'jid', 'action': u'deny', - 'value' : fjid, 'child': [u'message', u'iq', u'presence-out']} - connection.blocked_list.append(new_rule) - connection.blocked_contacts.append(fjid) - self.draw_contact(nick) - connection.set_privacy_list('block', connection.blocked_list) - if len(connection.blocked_list) == 1: - connection.set_active_list('block') - connection.set_default_list('block') - connection.get_privacy_list('block') - - def on_unblock(self, widget, nick): - fjid = self.room_jid + '/' + nick - connection = gajim.connections[self.account] - connection.new_blocked_list = [] - # needed for draw_contact: - if fjid in connection.blocked_contacts: - connection.blocked_contacts.remove(fjid) - self.draw_contact(nick) - for rule in connection.blocked_list: - if rule['action'] != 'deny' or rule['type'] != 'jid' \ - or rule['value'] != fjid: - connection.new_blocked_list.append(rule) - - connection.set_privacy_list('block', connection.new_blocked_list) - connection.get_privacy_list('block') - if len(connection.new_blocked_list) == 0: - connection.blocked_list = [] - connection.blocked_contacts = [] - connection.blocked_groups = [] - connection.set_default_list('') - connection.set_active_list('') - connection.del_privacy_list('block') - if 'blocked_contacts' in gajim.interface.instances[self.account]: - gajim.interface.instances[self.account]['blocked_contacts'].\ - privacy_list_received([]) - - def on_voice_checkmenuitem_activate(self, widget, nick): - if widget.get_active(): - self.grant_voice(widget, nick) - else: - self.revoke_voice(widget, nick) - - def on_moderator_checkmenuitem_activate(self, widget, nick): - if widget.get_active(): - self.grant_moderator(widget, nick) - else: - self.revoke_moderator(widget, nick) - - def on_member_checkmenuitem_activate(self, widget, jid): - if widget.get_active(): - self.grant_membership(widget, jid) - else: - self.revoke_membership(widget, jid) - - def on_admin_checkmenuitem_activate(self, widget, jid): - if widget.get_active(): - self.grant_admin(widget, jid) - else: - self.revoke_admin(widget, jid) - - def on_owner_checkmenuitem_activate(self, widget, jid): - if widget.get_active(): - self.grant_owner(widget, jid) - else: - self.revoke_owner(widget, jid) - -# vim: se ts=3: + COMMAND_HOST = GroupChatCommands + + def __init__(self, parent_win, contact, acct, is_continued=False): + ChatControlBase.__init__(self, self.TYPE_ID, parent_win, + 'muc_child_vbox', contact, acct) + + self.is_continued=is_continued + self.is_anonymous = True + + # Controls the state of autorejoin. + # None - autorejoin is neutral. + # False - autorejoin is to be prevented (gets reset to initial state in + # got_connected()). + # int - autorejoin is being active and working (gets reset to initial + # state in got_connected()). + self.autorejoin = None + + self.actions_button = self.xml.get_widget('muc_window_actions_button') + id_ = self.actions_button.connect('clicked', + self.on_actions_button_clicked) + self.handlers[id_] = self.actions_button + + widget = self.xml.get_widget('change_nick_button') + id_ = widget.connect('clicked', self._on_change_nick_menuitem_activate) + self.handlers[id_] = widget + + widget = self.xml.get_widget('change_subject_button') + id_ = widget.connect('clicked', self._on_change_subject_menuitem_activate) + self.handlers[id_] = widget + + widget = self.xml.get_widget('bookmark_button') + for bm in gajim.connections[self.account].bookmarks: + if bm['jid'] == self.contact.jid: + widget.hide() + break + else: + id_ = widget.connect('clicked', + self._on_bookmark_room_menuitem_activate) + self.handlers[id_] = widget + widget.show() + + widget = self.xml.get_widget('list_treeview') + id_ = widget.connect('row_expanded', self.on_list_treeview_row_expanded) + self.handlers[id_] = widget + + id_ = widget.connect('row_collapsed', self.on_list_treeview_row_collapsed) + self.handlers[id_] = widget + + id_ = widget.connect('row_activated', + self.on_list_treeview_row_activated) + self.handlers[id_] = widget + + id_ = widget.connect('button_press_event', + self.on_list_treeview_button_press_event) + self.handlers[id_] = widget + + id_ = widget.connect('key_press_event', + self.on_list_treeview_key_press_event) + self.handlers[id_] = widget + + id_ = widget.connect('motion_notify_event', + self.on_list_treeview_motion_notify_event) + self.handlers[id_] = widget + + id_ = widget.connect('leave_notify_event', + self.on_list_treeview_leave_notify_event) + self.handlers[id_] = widget + + self.room_jid = self.contact.jid + self.nick = contact.name.decode('utf-8') + self.new_nick = '' + self.name = '' + for bm in gajim.connections[self.account].bookmarks: + if bm['jid'] == self.room_jid: + self.name = bm['name'] + break + if not self.name: + self.name = self.room_jid.split('@')[0] + + compact_view = gajim.config.get('compact_view') + self.chat_buttons_set_visible(compact_view) + self.widget_set_visible(self.xml.get_widget('banner_eventbox'), + gajim.config.get('hide_groupchat_banner')) + self.widget_set_visible(self.xml.get_widget('list_scrolledwindow'), + gajim.config.get('hide_groupchat_occupants_list')) + + self._last_selected_contact = None # None or holds jid, account tuple + + # muc attention flag (when we are mentioned in a muc) + # if True, the room has mentioned us + self.attention_flag = False + + # sorted list of nicks who mentioned us (last at the end) + self.attention_list = [] + self.room_creation = int(time.time()) # Use int to reduce mem usage + self.nick_hits = [] + self.last_key_tabs = False + + self.subject = '' + + self.tooltip = tooltips.GCTooltip() + + # nickname coloring + self.gc_count_nicknames_colors = 0 + self.gc_custom_colors = {} + self.number_of_colors = len(gajim.config.get('gc_nicknames_colors').\ + split(':')) + + self.name_label = self.xml.get_widget('banner_name_label') + self.event_box = self.xml.get_widget('banner_eventbox') + + # set the position of the current hpaned + hpaned_position = gajim.config.get('gc-hpaned-position') + self.hpaned = self.xml.get_widget('hpaned') + self.hpaned.set_position(hpaned_position) + + self.list_treeview = self.xml.get_widget('list_treeview') + selection = self.list_treeview.get_selection() + id_ = selection.connect('changed', + self.on_list_treeview_selection_changed) + self.handlers[id_] = selection + id_ = self.list_treeview.connect('style-set', + self.on_list_treeview_style_set) + self.handlers[id_] = self.list_treeview + self.resize_from_another_muc = False + # we want to know when the the widget resizes, because that is + # an indication that the hpaned has moved... + # FIXME: Find a better indicator that the hpaned has moved. + id_ = self.list_treeview.connect('size-allocate', + self.on_treeview_size_allocate) + self.handlers[id_] = self.list_treeview + #status_image, shown_nick, type, nickname, avatar + store = gtk.TreeStore(gtk.Image, str, str, str, gtk.gdk.Pixbuf) + store.set_sort_func(C_NICK, self.tree_compare_iters) + store.set_sort_column_id(C_NICK, gtk.SORT_ASCENDING) + self.list_treeview.set_model(store) + + # columns + + # this col has 3 cells: + # first one img, second one text, third is sec pixbuf + column = gtk.TreeViewColumn() + + def add_avatar_renderer(): + renderer_pixbuf = gtk.CellRendererPixbuf() # avatar image + column.pack_start(renderer_pixbuf, expand=False) + column.add_attribute(renderer_pixbuf, 'pixbuf', C_AVATAR) + column.set_cell_data_func(renderer_pixbuf, tree_cell_data_func, + self.list_treeview) + + if gajim.config.get('avatar_position_in_roster') == 'left': + add_avatar_renderer() + + renderer_image = cell_renderer_image.CellRendererImage(0, 0) # status img + renderer_image.set_property('width', 26) + column.pack_start(renderer_image, expand=False) + column.add_attribute(renderer_image, 'image', C_IMG) + column.set_cell_data_func(renderer_image, tree_cell_data_func, + self.list_treeview) + + renderer_text = gtk.CellRendererText() # nickname + column.pack_start(renderer_text, expand=True) + column.add_attribute(renderer_text, 'markup', C_TEXT) + renderer_text.set_property("ellipsize", pango.ELLIPSIZE_END) + column.set_cell_data_func(renderer_text, tree_cell_data_func, + self.list_treeview) + + if gajim.config.get('avatar_position_in_roster') == 'right': + add_avatar_renderer() + + self.list_treeview.append_column(column) + + # workaround to avoid gtk arrows to be shown + column = gtk.TreeViewColumn() # 2nd COLUMN + renderer = gtk.CellRendererPixbuf() + column.pack_start(renderer, expand=False) + self.list_treeview.append_column(column) + column.set_visible(False) + self.list_treeview.set_expander_column(column) + + gajim.gc_connected[self.account][self.room_jid] = False + # disable win, we are not connected yet + ChatControlBase.got_disconnected(self) + + self.update_ui() + self.conv_textview.tv.grab_focus() + self.widget.show_all() + + def tree_compare_iters(self, model, iter1, iter2): + """ + Compare two iters to sort them + """ + type1 = model[iter1][C_TYPE] + type2 = model[iter2][C_TYPE] + if not type1 or not type2: + return 0 + nick1 = model[iter1][C_NICK] + nick2 = model[iter2][C_NICK] + if not nick1 or not nick2: + return 0 + nick1 = nick1.decode('utf-8') + nick2 = nick2.decode('utf-8') + if type1 == 'role': + return locale.strcoll(nick1, nick2) + if type1 == 'contact': + gc_contact1 = gajim.contacts.get_gc_contact(self.account, + self.room_jid, nick1) + if not gc_contact1: + return 0 + if type2 == 'contact': + gc_contact2 = gajim.contacts.get_gc_contact(self.account, + self.room_jid, nick2) + if not gc_contact2: + return 0 + if type1 == 'contact' and type2 == 'contact' and \ + gajim.config.get('sort_by_show_in_muc'): + cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4, + 'invisible': 5, 'offline': 6, 'error': 7} + show1 = cshow[gc_contact1.show] + show2 = cshow[gc_contact2.show] + if show1 < show2: + return -1 + elif show1 > show2: + return 1 + # We compare names + name1 = gc_contact1.get_shown_name() + name2 = gc_contact2.get_shown_name() + return locale.strcoll(name1.lower(), name2.lower()) + + def on_msg_textview_populate_popup(self, textview, menu): + """ + Override the default context menu and we prepend Clear + and the ability to insert a nick + """ + ChatControlBase.on_msg_textview_populate_popup(self, textview, menu) + item = gtk.SeparatorMenuItem() + menu.prepend(item) + + item = gtk.MenuItem(_('Insert Nickname')) + menu.prepend(item) + submenu = gtk.Menu() + item.set_submenu(submenu) + + for nick in sorted(gajim.contacts.get_nick_list(self.account, + self.room_jid)): + item = gtk.MenuItem(nick, use_underline=False) + submenu.append(item) + id_ = item.connect('activate', self.append_nick_in_msg_textview, nick) + self.handlers[id_] = item + + menu.show_all() + + def resize_occupant_treeview(self, position): + self.resize_from_another_muc = True + self.hpaned.set_position(position) + def reset_flag(): + self.resize_from_another_muc = False + # Reset the flag when everything will be redrawn, and in particular when + # on_treeview_size_allocate will have been called. + gobject.idle_add(reset_flag) + + def on_treeview_size_allocate(self, widget, allocation): + """ + The MUC treeview has resized. Move the hpaned in all tabs to match + """ + if self.resize_from_another_muc: + # Don't send the event to other MUC + return + hpaned_position = self.hpaned.get_position() + for account in gajim.gc_connected: + for room_jid in [i for i in gajim.gc_connected[account] if \ + gajim.gc_connected[account][i] and i != self.room_jid]: + ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid, account) + if not ctrl: + ctrl = gajim.interface.minimized_controls[account][room_jid] + if ctrl: + ctrl.resize_occupant_treeview(hpaned_position) + + def iter_contact_rows(self): + """ + Iterate over all contact rows in the tree model + """ + model = self.list_treeview.get_model() + role_iter = model.get_iter_root() + while role_iter: + contact_iter = model.iter_children(role_iter) + while contact_iter: + yield model[contact_iter] + contact_iter = model.iter_next(contact_iter) + role_iter = model.iter_next(role_iter) + + def on_list_treeview_style_set(self, treeview, style): + """ + When style (theme) changes, redraw all contacts + """ + # Get the room_jid from treeview + for contact in self.iter_contact_rows(): + nick = contact[C_NICK].decode('utf-8') + self.draw_contact(nick) + + def on_list_treeview_selection_changed(self, selection): + model, selected_iter = selection.get_selected() + self.draw_contact(self.nick) + if self._last_selected_contact is not None: + self.draw_contact(self._last_selected_contact) + if selected_iter is None: + self._last_selected_contact = None + return + contact = model[selected_iter] + nick = contact[C_NICK].decode('utf-8') + self._last_selected_contact = nick + if contact[C_TYPE] != 'contact': + return + self.draw_contact(nick, selected=True, focus=True) + + def get_tab_label(self, chatstate): + """ + Markup the label if necessary. Returns a tuple such as: (new_label_str, + color) either of which can be None if chatstate is given that means we + have HE SENT US a chatstate + """ + + has_focus = self.parent_win.window.get_property('has-toplevel-focus') + current_tab = self.parent_win.get_active_control() == self + color_name = None + color = None + theme = gajim.config.get('roster_theme') + if chatstate == 'attention' and (not has_focus or not current_tab): + self.attention_flag = True + color_name = gajim.config.get_per('themes', theme, + 'state_muc_directed_msg_color') + elif chatstate: + if chatstate == 'active' or (current_tab and has_focus): + self.attention_flag = False + # get active color from gtk + color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE] + elif chatstate == 'newmsg' and (not has_focus or not current_tab) and\ + not self.attention_flag: + color_name = gajim.config.get_per('themes', theme, + 'state_muc_msg_color') + if color_name: + color = gtk.gdk.colormap_get_system().alloc_color(color_name) + + if self.is_continued: + # if this is a continued conversation + label_str = self.get_continued_conversation_name() + else: + label_str = self.name + + # count waiting highlighted messages + unread = '' + num_unread = self.get_nb_unread() + if num_unread == 1: + unread = '*' + elif num_unread > 1: + unread = '[' + unicode(num_unread) + ']' + label_str = unread + label_str + return (label_str, color) + + def get_tab_image(self, count_unread=True): + # Set tab image (always 16x16) + tab_image = None + if gajim.gc_connected[self.account][self.room_jid]: + tab_image = gtkgui_helpers.load_icon('muc_active') + else: + tab_image = gtkgui_helpers.load_icon('muc_inactive') + return tab_image + + def update_ui(self): + ChatControlBase.update_ui(self) + for nick in gajim.contacts.get_nick_list(self.account, self.room_jid): + self.draw_contact(nick) + + def _change_style(self, model, path, iter_): + model[iter_][C_NICK] = model[iter_][C_NICK] + + def change_roster_style(self): + model = self.list_treeview.get_model() + model.foreach(self._change_style) + + def repaint_themed_widgets(self): + ChatControlBase.repaint_themed_widgets(self) + self.change_roster_style() + + def _update_banner_state_image(self): + banner_status_img = self.xml.get_widget('gc_banner_status_image') + images = gajim.interface.jabber_state_images + if self.room_jid in gajim.gc_connected[self.account] and \ + gajim.gc_connected[self.account][self.room_jid]: + image = 'muc_active' + else: + image = 'muc_inactive' + if '32' in images and image in images['32']: + muc_icon = images['32'][image] + if muc_icon.get_storage_type() != gtk.IMAGE_EMPTY: + pix = muc_icon.get_pixbuf() + banner_status_img.set_from_pixbuf(pix) + return + # we need to scale 16x16 to 32x32 + muc_icon = images['16'][image] + pix = muc_icon.get_pixbuf() + scaled_pix = pix.scale_simple(32, 32, gtk.gdk.INTERP_BILINEAR) + banner_status_img.set_from_pixbuf(scaled_pix) + + def get_continued_conversation_name(self): + """ + Get the name of a continued conversation. Will return Continued + Conversation if there isn't any other contact in the room + """ + nicks = [] + for nick in gajim.contacts.get_nick_list(self.account, + self.room_jid): + if nick != self.nick: + nicks.append(nick) + if nicks != []: + title = ', ' + title = _('Conversation with ') + title.join(nicks) + else: + title = _('Continued conversation') + return title + + def draw_banner_text(self): + """ + Draw the text in the fat line at the top of the window that houses the + room jid, subject + """ + self.name_label.set_ellipsize(pango.ELLIPSIZE_END) + self.banner_status_label.set_ellipsize(pango.ELLIPSIZE_END) + font_attrs, font_attrs_small = self.get_font_attrs() + if self.is_continued: + name = self.get_continued_conversation_name() + else: + name = self.room_jid + text = '%s' % (font_attrs, name) + self.name_label.set_markup(text) + + if self.subject: + subject = helpers.reduce_chars_newlines(self.subject, max_lines=2) + subject = gobject.markup_escape_text(subject) + if gajim.HAVE_PYSEXY: + subject_text = self.urlfinder.sub(self.make_href, subject) + subject_text = '%s' % (font_attrs_small, + subject_text) + else: + subject_text = '%s' % (font_attrs_small, subject) + + # tooltip must always hold ALL the subject + self.event_box.set_tooltip_text(self.subject) + self.banner_status_label.show() + self.banner_status_label.set_no_show_all(False) + else: + subject_text = '' + self.event_box.set_has_tooltip(False) + self.banner_status_label.hide() + self.banner_status_label.set_no_show_all(True) + + self.banner_status_label.set_markup(subject_text) + + def prepare_context_menu(self, hide_buttonbar_items=False): + """ + Set sensitivity state for configure_room + """ + xml = gtkgui_helpers.get_glade('gc_control_popup_menu.glade') + menu = xml.get_widget('gc_control_popup_menu') + + bookmark_room_menuitem = xml.get_widget('bookmark_room_menuitem') + change_nick_menuitem = xml.get_widget('change_nick_menuitem') + configure_room_menuitem = xml.get_widget('configure_room_menuitem') + destroy_room_menuitem = xml.get_widget('destroy_room_menuitem') + change_subject_menuitem = xml.get_widget('change_subject_menuitem') + history_menuitem = xml.get_widget('history_menuitem') + minimize_menuitem = xml.get_widget('minimize_menuitem') + bookmark_separator = xml.get_widget('bookmark_separator') + separatormenuitem2 = xml.get_widget('separatormenuitem2') + + if hide_buttonbar_items: + change_nick_menuitem.hide() + change_subject_menuitem.hide() + bookmark_room_menuitem.hide() + history_menuitem.hide() + bookmark_separator.hide() + separatormenuitem2.hide() + else: + change_nick_menuitem.show() + change_subject_menuitem.show() + bookmark_room_menuitem.show() + history_menuitem.show() + bookmark_separator.show() + separatormenuitem2.show() + for bm in gajim.connections[self.account].bookmarks: + if bm['jid'] == self.room_jid: + bookmark_room_menuitem.hide() + bookmark_separator.hide() + break + + ag = gtk.accel_groups_from_object(self.parent_win.window)[0] + change_nick_menuitem.add_accelerator('activate', ag, gtk.keysyms.n, + gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) + change_subject_menuitem.add_accelerator('activate', ag, + gtk.keysyms.t, gtk.gdk.MOD1_MASK, gtk.ACCEL_VISIBLE) + bookmark_room_menuitem.add_accelerator('activate', ag, gtk.keysyms.b, + gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) + history_menuitem.add_accelerator('activate', ag, gtk.keysyms.h, + gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) + + if self.contact.jid in gajim.config.get_per('accounts', self.account, + 'minimized_gc').split(' '): + minimize_menuitem.set_active(True) + if not gajim.connections[self.account].private_storage_supported: + bookmark_room_menuitem.set_sensitive(False) + if gajim.gc_connected[self.account][self.room_jid]: + c = gajim.contacts.get_gc_contact(self.account, self.room_jid, + self.nick) + if c.affiliation not in ('owner', 'admin'): + configure_room_menuitem.set_sensitive(False) + else: + configure_room_menuitem.set_sensitive(True) + if c.affiliation != 'owner': + destroy_room_menuitem.set_sensitive(False) + else: + destroy_room_menuitem.set_sensitive(True) + change_subject_menuitem.set_sensitive(True) + change_nick_menuitem.set_sensitive(True) + else: + # We are not connected to this groupchat, disable unusable menuitems + configure_room_menuitem.set_sensitive(False) + destroy_room_menuitem.set_sensitive(False) + change_subject_menuitem.set_sensitive(False) + change_nick_menuitem.set_sensitive(False) + + # connect the menuitems to their respective functions + id_ = bookmark_room_menuitem.connect('activate', + self._on_bookmark_room_menuitem_activate) + self.handlers[id_] = bookmark_room_menuitem + + id_ = change_nick_menuitem.connect('activate', + self._on_change_nick_menuitem_activate) + self.handlers[id_] = change_nick_menuitem + + id_ = configure_room_menuitem.connect('activate', + self._on_configure_room_menuitem_activate) + self.handlers[id_] = configure_room_menuitem + + id_ = destroy_room_menuitem.connect('activate', + self._on_destroy_room_menuitem_activate) + self.handlers[id_] = destroy_room_menuitem + + id_ = change_subject_menuitem.connect('activate', + self._on_change_subject_menuitem_activate) + self.handlers[id_] = change_subject_menuitem + + id_ = history_menuitem.connect('activate', + self._on_history_menuitem_activate) + self.handlers[id_] = history_menuitem + + id_ = minimize_menuitem.connect('toggled', + self.on_minimize_menuitem_toggled) + self.handlers[id_] = minimize_menuitem + + menu.connect('selection-done', self.destroy_menu, + change_nick_menuitem, change_subject_menuitem, + bookmark_room_menuitem, history_menuitem) + return menu + + def destroy_menu(self, menu, change_nick_menuitem, change_subject_menuitem, + bookmark_room_menuitem, history_menuitem): + # destroy accelerators + ag = gtk.accel_groups_from_object(self.parent_win.window)[0] + change_nick_menuitem.remove_accelerator(ag, gtk.keysyms.n, + gtk.gdk.CONTROL_MASK) + change_subject_menuitem.remove_accelerator(ag, gtk.keysyms.t, + gtk.gdk.MOD1_MASK) + bookmark_room_menuitem.remove_accelerator(ag, gtk.keysyms.b, + gtk.gdk.CONTROL_MASK) + history_menuitem.remove_accelerator(ag, gtk.keysyms.h, + gtk.gdk.CONTROL_MASK) + # destroy menu + menu.destroy() + + def on_message(self, nick, msg, tim, has_timestamp=False, xhtml=None, + status_code=[]): + if '100' in status_code: + # Room is not anonymous + self.is_anonymous = False + if not nick: + # message from server + self.print_conversation(msg, tim=tim, xhtml=xhtml) + else: + # message from someone + if has_timestamp: + # don't print xhtml if it's an old message. + # Like that xhtml messages are grayed too. + self.print_old_conversation(msg, nick, tim, None) + else: + self.print_conversation(msg, nick, tim, xhtml) + + def on_private_message(self, nick, msg, tim, xhtml, session, msg_id=None, + encrypted=False): + # Do we have a queue? + fjid = self.room_jid + '/' + nick + no_queue = len(gajim.events.get_events(self.account, fjid)) == 0 + + event = gajim.events.create_event('pm', (msg, '', 'incoming', tim, + encrypted, '', msg_id, xhtml, session)) + gajim.events.add_event(self.account, fjid, event) + + autopopup = gajim.config.get('autopopup') + autopopupaway = gajim.config.get('autopopupaway') + iter_ = self.get_contact_iter(nick) + path = self.list_treeview.get_model().get_path(iter_) + if not autopopup or (not autopopupaway and \ + gajim.connections[self.account].connected > 2): + if no_queue: # We didn't have a queue: we change icons + model = self.list_treeview.get_model() + state_images =\ + gajim.interface.roster.get_appropriate_state_images( + self.room_jid, icon_name='event') + image = state_images['event'] + model[iter_][C_IMG] = image + if self.parent_win: + self.parent_win.show_title() + self.parent_win.redraw_tab(self) + else: + self._start_private_message(nick) + # Scroll to line + self.list_treeview.expand_row(path[0:1], False) + self.list_treeview.scroll_to_cell(path) + self.list_treeview.set_cursor(path) + contact = gajim.contacts.get_contact_with_highest_priority(self.account, \ + self.room_jid) + if contact: + gajim.interface.roster.draw_contact(self.room_jid, self.account) + + def get_contact_iter(self, nick): + model = self.list_treeview.get_model() + fin = False + role_iter = model.get_iter_root() + if not role_iter: + return None + while not fin: + fin2 = False + user_iter = model.iter_children(role_iter) + if not user_iter: + fin2 = True + while not fin2: + if nick == model[user_iter][C_NICK].decode('utf-8'): + return user_iter + user_iter = model.iter_next(user_iter) + if not user_iter: + fin2 = True + role_iter = model.iter_next(role_iter) + if not role_iter: + fin = True + return None + + def print_old_conversation(self, text, contact='', tim=None, xhtml = None): + if isinstance(text, str): + text = unicode(text, 'utf-8') + if contact: + if contact == self.nick: # it's us + kind = 'outgoing' + else: + kind = 'incoming' + else: + kind = 'status' + if gajim.config.get('restored_messages_small'): + small_attr = ['small'] + else: + small_attr = [] + ChatControlBase.print_conversation_line(self, text, kind, contact, tim, + small_attr, small_attr + ['restored_message'], + small_attr + ['restored_message'], count_as_new=False, xhtml=xhtml) + + def print_conversation(self, text, contact='', tim=None, xhtml=None, + graphics=True): + """ + Print a line in the conversation + + If contact is set: it's a message from someone or an info message + (contact = 'info' in such a case). + If contact is not set: it's a message from the server or help. + """ + if isinstance(text, str): + text = unicode(text, 'utf-8') + other_tags_for_name = [] + other_tags_for_text = [] + if contact: + if contact == self.nick: # it's us + kind = 'outgoing' + elif contact == 'info': + kind = 'info' + contact = None + else: + kind = 'incoming' + # muc-specific chatstate + if self.parent_win: + self.parent_win.redraw_tab(self, 'newmsg') + else: + kind = 'status' + + if kind == 'incoming': # it's a message NOT from us + # highlighting and sounds + (highlight, sound) = self.highlighting_for_message(text, tim) + if contact in self.gc_custom_colors: + other_tags_for_name.append('gc_nickname_color_' + \ + str(self.gc_custom_colors[contact])) + else: + self.gc_count_nicknames_colors += 1 + if self.gc_count_nicknames_colors == self.number_of_colors: + self.gc_count_nicknames_colors = 0 + self.gc_custom_colors[contact] = \ + self.gc_count_nicknames_colors + other_tags_for_name.append('gc_nickname_color_' + \ + str(self.gc_count_nicknames_colors)) + if highlight: + # muc-specific chatstate + if self.parent_win: + self.parent_win.redraw_tab(self, 'attention') + else: + self.attention_flag = True + other_tags_for_name.append('bold') + other_tags_for_text.append('marked') + + if contact in self.attention_list: + self.attention_list.remove(contact) + elif len(self.attention_list) > 6: + self.attention_list.pop(0) # remove older + self.attention_list.append(contact) + + if sound == 'received': + helpers.play_sound('muc_message_received') + elif sound == 'highlight': + helpers.play_sound('muc_message_highlight') + if text.startswith('/me ') or text.startswith('/me\n'): + other_tags_for_text.append('gc_nickname_color_' + \ + str(self.gc_custom_colors[contact])) + + self.check_and_possibly_add_focus_out_line() + + ChatControlBase.print_conversation_line(self, text, kind, contact, tim, + other_tags_for_name, [], other_tags_for_text, xhtml=xhtml, + graphics=graphics) + + def get_nb_unread(self): + type_events = ['printed_marked_gc_msg'] + if gajim.config.get('notify_on_all_muc_messages'): + type_events.append('printed_gc_msg') + nb = len(gajim.events.get_events(self.account, self.room_jid, + type_events)) + nb += self.get_nb_unread_pm() + return nb + + def get_nb_unread_pm(self): + nb = 0 + for nick in gajim.contacts.get_nick_list(self.account, self.room_jid): + nb += len(gajim.events.get_events(self.account, self.room_jid + '/' + \ + nick, ['pm'])) + return nb + + def highlighting_for_message(self, text, tim): + """ + Returns a 2-Tuple. The first says whether or not to highlight the text, + the second, what sound to play + """ + highlight, sound = (None, None) + + # Are any of the defined highlighting words in the text? + if self.needs_visual_notification(text): + highlight = True + if gajim.config.get_per('soundevents', 'muc_message_highlight', + 'enabled'): + sound = 'highlight' + + # Do we play a sound on every muc message? + elif gajim.config.get_per('soundevents', 'muc_message_received', \ + 'enabled'): + sound = 'received' + + # Is it a history message? Don't want sound-floods when we join. + if tim != time.localtime(): + sound = None + + return (highlight, sound) + + def check_and_possibly_add_focus_out_line(self): + """ + Check and possibly add focus out line for room_jid if it needs it and + does not already have it as last event. If it goes to add this line + - remove previous line first + """ + win = gajim.interface.msg_win_mgr.get_window(self.room_jid, self.account) + if win and self.room_jid == win.get_active_jid() and\ + win.window.get_property('has-toplevel-focus') and\ + self.parent_win.get_active_control() == self: + # it's the current room and it's the focused window. + # we have full focus (we are reading it!) + return + + self.conv_textview.show_focus_out_line() + + def needs_visual_notification(self, text): + """ + Check text to see whether any of the words in (muc_highlight_words and + nick) appear + """ + special_words = gajim.config.get('muc_highlight_words').split(';') + special_words.append(self.nick) + # Strip empties: ''.split(';') == [''] and would highlight everything. + # Also lowercase everything for case insensitive compare. + special_words = [word.lower() for word in special_words if word] + text = text.lower() + + for special_word in special_words: + found_here = text.find(special_word) + while(found_here > -1): + end_here = found_here + len(special_word) + if (found_here == 0 or not text[found_here - 1].isalpha()) and \ + (end_here == len(text) or not text[end_here].isalpha()): + # It is beginning of text or char before is not alpha AND + # it is end of text or char after is not alpha + return True + # continue searching + start = found_here + 1 + found_here = text.find(special_word, start) + return False + + def set_subject(self, subject): + self.subject = subject + self.draw_banner_text() + + def got_connected(self): + # Make autorejoin stop. + if self.autorejoin: + gobject.source_remove(self.autorejoin) + self.autorejoin = None + + gajim.gc_connected[self.account][self.room_jid] = True + ChatControlBase.got_connected(self) + # We don't redraw the whole banner here, because only icon change + self._update_banner_state_image() + if self.parent_win: + self.parent_win.redraw_tab(self) + + def got_disconnected(self): + self.list_treeview.get_model().clear() + nick_list = gajim.contacts.get_nick_list(self.account, self.room_jid) + for nick in nick_list: + # Update pm chat window + fjid = self.room_jid + '/' + nick + gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, + nick) + + ctrl = gajim.interface.msg_win_mgr.get_control(fjid, self.account) + if ctrl: + gc_contact.show = 'offline' + gc_contact.status = '' + ctrl.update_ui() + if ctrl.parent_win: + ctrl.parent_win.redraw_tab(ctrl) + + gajim.contacts.remove_gc_contact(self.account, gc_contact) + gajim.gc_connected[self.account][self.room_jid] = False + ChatControlBase.got_disconnected(self) + # Tell connection to note the date we disconnect to avoid duplicate logs + gajim.connections[self.account].gc_got_disconnected(self.room_jid) + # We don't redraw the whole banner here, because only icon change + self._update_banner_state_image() + if self.parent_win: + self.parent_win.redraw_tab(self) + + # Autorejoin stuff goes here. + # Notice that we don't need to activate autorejoin if connection is lost + # or in progress. + if self.autorejoin is None and gajim.account_is_connected(self.account): + ar_to = gajim.config.get('muc_autorejoin_timeout') + if ar_to: + self.autorejoin = gobject.timeout_add_seconds(ar_to, self.rejoin) + + def rejoin(self): + if not self.autorejoin: + return False + password = gajim.gc_passwords.get(self.room_jid, '') + gajim.connections[self.account].join_gc(self.nick, self.room_jid, + password) + return True + + def draw_roster(self): + self.list_treeview.get_model().clear() + for nick in gajim.contacts.get_nick_list(self.account, self.room_jid): + gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, + nick) + self.add_contact_to_roster(nick, gc_contact.show, gc_contact.role, + gc_contact.affiliation, gc_contact.status, gc_contact.jid) + self.draw_all_roles() + # Recalculate column width for ellipsizin + self.list_treeview.columns_autosize() + + def on_send_pm(self, widget=None, model=None, iter_=None, nick=None, + msg=None): + """ + Open a chat window and if msg is not None - send private message to a + contact in a room + """ + if nick is None: + nick = model[iter_][C_NICK].decode('utf-8') + + ctrl = self._start_private_message(nick) + if ctrl and msg: + ctrl.send_message(msg) + + def on_send_file(self, widget, gc_contact): + """ + Send a file to a contact in the room + """ + self._on_send_file(gc_contact) + + def draw_contact(self, nick, selected=False, focus=False): + iter_ = self.get_contact_iter(nick) + if not iter_: + return + model = self.list_treeview.get_model() + gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, + nick) + state_images = gajim.interface.jabber_state_images['16'] + if len(gajim.events.get_events(self.account, self.room_jid + '/' + nick)): + image = state_images['event'] + else: + image = state_images[gc_contact.show] + + name = gobject.markup_escape_text(gc_contact.name) + + # Strike name if blocked + fjid = self.room_jid + '/' + nick + if helpers.jid_is_blocked(self.account, fjid): + name = '%s' % name + + status = gc_contact.status + # add status msg, if not empty, under contact name in the treeview + if status and gajim.config.get('show_status_msgs_in_roster'): + status = status.strip() + if status != '': + status = helpers.reduce_chars_newlines(status, max_lines=1) + # escape markup entities and make them small italic and fg color + color = gtkgui_helpers._get_fade_color(self.list_treeview, + selected, focus) + colorstring = "#%04x%04x%04x" % (color.red, color.green, color.blue) + name += ('\n' + '%s') % (colorstring, gobject.markup_escape_text(status)) + + if image.get_storage_type() == gtk.IMAGE_PIXBUF and \ + gc_contact.affiliation != 'none': + pixbuf1 = image.get_pixbuf().copy() + pixbuf2 = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, 4, 4) + if gc_contact.affiliation == 'owner': + pixbuf2.fill(0xff0000ff) # Red + elif gc_contact.affiliation == 'admin': + pixbuf2.fill(0xffb200ff) # Oragne + elif gc_contact.affiliation == 'member': + pixbuf2.fill(0x00ff00ff) # Green + pixbuf2.composite(pixbuf1, 12, 12, pixbuf2.get_property('width'), + pixbuf2.get_property('height'), 0, 0, 1.0, 1.0, + gtk.gdk.INTERP_HYPER, 127) + image = gtk.image_new_from_pixbuf(pixbuf1) + model[iter_][C_IMG] = image + model[iter_][C_TEXT] = name + + def draw_avatar(self, nick): + if not gajim.config.get('show_avatars_in_roster'): + return + model = self.list_treeview.get_model() + iter_ = self.get_contact_iter(nick) + if not iter_: + return + pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(self.room_jid + \ + '/' + nick, True) + if pixbuf in ('ask', None): + scaled_pixbuf = None + else: + scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'roster') + model[iter_][C_AVATAR] = scaled_pixbuf + + def draw_role(self, role): + role_iter = self.get_role_iter(role) + if not role_iter: + return + model = self.list_treeview.get_model() + role_name = helpers.get_uf_role(role, plural=True) + if gajim.config.get('show_contacts_number'): + nbr_role, nbr_total = gajim.contacts.get_nb_role_total_gc_contacts( + self.account, self.room_jid, role) + role_name += ' (%s/%s)' % (repr(nbr_role), repr(nbr_total)) + model[role_iter][C_TEXT] = role_name + + def draw_all_roles(self): + for role in ('visitor', 'participant', 'moderator'): + self.draw_role(role) + + def chg_contact_status(self, nick, show, status, role, affiliation, jid, + reason, actor, statusCode, new_nick, avatar_sha, tim=None): + """ + When an occupant changes his or her status + """ + if show == 'invisible': + return + + if not role: + role = 'visitor' + if not affiliation: + affiliation = 'none' + fake_jid = self.room_jid + '/' + nick + newly_created = False + nick_jid = nick + + # Set to true if role or affiliation have changed + right_changed = False + + if jid: + # delete ressource + simple_jid = gajim.get_jid_without_resource(jid) + nick_jid += ' (%s)' % simple_jid + + # statusCode + # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes-init + if statusCode: + if '100' in statusCode: + # Can be a message (see handle_event_gc_config_change in gajim.py) + self.print_conversation(\ + _('Any occupant is allowed to see your full JID')) + if '170' in statusCode: + # Can be a message (see handle_event_gc_config_change in gajim.py) + self.print_conversation(_('Room logging is enabled')) + if '201' in statusCode: + self.print_conversation(_('A new room has been created')) + if '210' in statusCode: + self.print_conversation(\ + _('The server has assigned or modified your roomnick')) + + if show in ('offline', 'error'): + if statusCode: + if '307' in statusCode: + if actor is None: # do not print 'kicked by None' + s = _('%(nick)s has been kicked: %(reason)s') % { + 'nick': nick, + 'reason': reason } + else: + s = _('%(nick)s has been kicked by %(who)s: %(reason)s') % { + 'nick': nick, + 'who': actor, + 'reason': reason } + self.print_conversation(s, 'info', tim=tim, graphics=False) + if nick == self.nick and not gajim.config.get( + 'muc_autorejoin_on_kick'): + self.autorejoin = False + elif '301' in statusCode: + if actor is None: # do not print 'banned by None' + s = _('%(nick)s has been banned: %(reason)s') % { + 'nick': nick, + 'reason': reason } + else: + s = _('%(nick)s has been banned by %(who)s: %(reason)s') % { + 'nick': nick, + 'who': actor, + 'reason': reason } + self.print_conversation(s, 'info', tim=tim, graphics=False) + if nick == self.nick: + self.autorejoin = False + elif '303' in statusCode: # Someone changed his or her nick + if new_nick == self.new_nick or nick == self.nick: + # We changed our nick + self.nick = new_nick + self.new_nick = '' + s = _('You are now known as %s') % new_nick + # Stop all E2E sessions + nick_list = gajim.contacts.get_nick_list(self.account, + self.room_jid) + for nick_ in nick_list: + fjid_ = self.room_jid + '/' + nick_ + ctrl = gajim.interface.msg_win_mgr.get_control(fjid_, + self.account) + if ctrl and ctrl.session and \ + ctrl.session.enable_encryption: + thread_id = ctrl.session.thread_id + ctrl.session.terminate_e2e() + gajim.connections[self.account].delete_session(fjid_, + thread_id) + ctrl.no_autonegotiation = False + else: + s = _('%(nick)s is now known as %(new_nick)s') % { + 'nick': nick, 'new_nick': new_nick} + # We add new nick to muc roster here, so we don't see + # that "new_nick has joined the room" when he just changed nick. + # add_contact_to_roster will be called a second time + # after that, but that doesn't hurt + self.add_contact_to_roster(new_nick, show, role, affiliation, + status, jid) + if nick in self.attention_list: + self.attention_list.remove(nick) + # keep nickname color + if nick in self.gc_custom_colors: + self.gc_custom_colors[new_nick] = \ + self.gc_custom_colors[nick] + # rename vcard / avatar + puny_jid = helpers.sanitize_filename(self.room_jid) + puny_nick = helpers.sanitize_filename(nick) + puny_new_nick = helpers.sanitize_filename(new_nick) + old_path = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) + new_path = os.path.join(gajim.VCARD_PATH, puny_jid, + puny_new_nick) + files = {old_path: new_path} + path = os.path.join(gajim.AVATAR_PATH, puny_jid) + # possible extensions + for ext in ('.png', '.jpeg', '_notif_size_bw.png', + '_notif_size_colored.png'): + files[os.path.join(path, puny_nick + ext)] = \ + os.path.join(path, puny_new_nick + ext) + for old_file in files: + if os.path.exists(old_file) and old_file != files[old_file]: + if os.path.exists(files[old_file]) and helpers.windowsify( + old_file) != helpers.windowsify(files[old_file]): + # Windows require this, but os.remove('test') will also + # remove 'TEST' + os.remove(files[old_file]) + os.rename(old_file, files[old_file]) + self.print_conversation(s, 'info', tim=tim, graphics=False) + elif '321' in statusCode: + s = _('%(nick)s has been removed from the room (%(reason)s)') % { + 'nick': nick, 'reason': _('affiliation changed') } + self.print_conversation(s, 'info', tim=tim, graphics=False) + elif '322' in statusCode: + s = _('%(nick)s has been removed from the room (%(reason)s)') % { + 'nick': nick, + 'reason': _('room configuration changed to members-only') } + self.print_conversation(s, 'info', tim=tim, graphics=False) + elif '332' in statusCode: + s = _('%(nick)s has been removed from the room (%(reason)s)') % { + 'nick': nick, + 'reason': _('system shutdown') } + self.print_conversation(s, 'info', tim=tim, graphics=False) + elif 'destroyed' in statusCode: # Room has been destroyed + self.print_conversation(reason, 'info', tim, graphics=False) + + if len(gajim.events.get_events(self.account, jid=fake_jid, + types=['pm'])) == 0: + self.remove_contact(nick) + self.draw_all_roles() + else: + c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick) + c.show = show + c.status = status + if nick == self.nick and (not statusCode or \ + '303' not in statusCode): # We became offline + self.got_disconnected() + contact = gajim.contacts.\ + get_contact_with_highest_priority(self.account, self.room_jid) + if contact: + gajim.interface.roster.draw_contact(self.room_jid, self.account) + if self.parent_win: + self.parent_win.redraw_tab(self) + else: + iter_ = self.get_contact_iter(nick) + if not iter_: + if '210' in statusCode: + # Server changed our nick + self.nick = nick + s = _('You are now known as %s') % nick + self.print_conversation(s, 'info', tim=tim, graphics=False) + iter_ = self.add_contact_to_roster(nick, show, role, affiliation, + status, jid) + newly_created = True + self.draw_all_roles() + if statusCode and '201' in statusCode: # We just created the room + gajim.connections[self.account].request_gc_config(self.room_jid) + else: + gc_c = gajim.contacts.get_gc_contact(self.account, self.room_jid, + nick) + if not gc_c: + log.error('%s has an iter, but no gc_contact instance') + return + # Re-get vcard if avatar has changed + # We do that here because we may request it to the real JID if we + # knows it. connections.py doesn't know it. + con = gajim.connections[self.account] + if gc_c and gc_c.jid: + real_jid = gc_c.jid + if gc_c.resource: + real_jid += '/' + gc_c.resource + else: + real_jid = fake_jid + if fake_jid in con.vcard_shas: + if avatar_sha != con.vcard_shas[fake_jid]: + server = gajim.get_server_from_jid(self.room_jid) + if not server.startswith('irc'): + con.request_vcard(real_jid, fake_jid) + else: + cached_vcard = con.get_cached_vcard(fake_jid, True) + if cached_vcard and 'PHOTO' in cached_vcard and \ + 'SHA' in cached_vcard['PHOTO']: + cached_sha = cached_vcard['PHOTO']['SHA'] + else: + cached_sha = '' + if cached_sha != avatar_sha: + # avatar has been updated + # sha in mem will be updated later + server = gajim.get_server_from_jid(self.room_jid) + if not server.startswith('irc'): + con.request_vcard(real_jid, fake_jid) + else: + # save sha in mem NOW + con.vcard_shas[fake_jid] = avatar_sha + + actual_affiliation = gc_c.affiliation + if affiliation != actual_affiliation: + if actor: + st = _('** Affiliation of %(nick)s has been set to ' + '%(affiliation)s by %(actor)s') % {'nick': nick_jid, + 'affiliation': affiliation, 'actor': actor} + else: + st = _('** Affiliation of %(nick)s has been set to ' + '%(affiliation)s') % {'nick': nick_jid, + 'affiliation': affiliation} + if reason: + st += ' (%s)' % reason + self.print_conversation(st, tim=tim, graphics=False) + right_changed = True + actual_role = self.get_role(nick) + if role != actual_role: + self.remove_contact(nick) + self.add_contact_to_roster(nick, show, role, + affiliation, status, jid) + self.draw_role(actual_role) + self.draw_role(role) + if actor: + st = _('** Role of %(nick)s has been set to %(role)s by ' + '%(actor)s') % {'nick': nick_jid, 'role': role, + 'actor': actor} + else: + st = _('** Role of %(nick)s has been set to %(role)s') % { + 'nick': nick_jid, 'role': role} + if reason: + st += ' (%s)' % reason + self.print_conversation(st, tim=tim, graphics=False) + right_changed = True + else: + if gc_c.show == show and gc_c.status == status and \ + gc_c.affiliation == affiliation: # no change + return + gc_c.show = show + gc_c.affiliation = affiliation + gc_c.status = status + self.draw_contact(nick) + if (time.time() - self.room_creation) > 30 and nick != self.nick and \ + (not statusCode or '303' not in statusCode) and not right_changed: + st = '' + print_status = None + for bookmark in gajim.connections[self.account].bookmarks: + if bookmark['jid'] == self.room_jid: + print_status = bookmark.get('print_status', None) + break + if not print_status: + print_status = gajim.config.get('print_status_in_muc') + if show == 'offline': + if nick in self.attention_list: + self.attention_list.remove(nick) + if show == 'offline' and print_status in ('all', 'in_and_out') and \ + (not statusCode or '307' not in statusCode): + st = _('%s has left') % nick_jid + if reason: + st += ' [%s]' % reason + else: + if newly_created and print_status in ('all', 'in_and_out'): + st = _('%s has joined the group chat') % nick_jid + elif print_status == 'all': + st = _('%(nick)s is now %(status)s') % {'nick': nick_jid, + 'status': helpers.get_uf_show(show)} + if st: + if status: + st += ' (' + status + ')' + self.print_conversation(st, tim=tim, graphics=False) + + def add_contact_to_roster(self, nick, show, role, affiliation, status, + jid=''): + model = self.list_treeview.get_model() + role_name = helpers.get_uf_role(role, plural=True) + + resource = '' + if jid: + jids = jid.split('/', 1) + j = jids[0] + if len(jids) > 1: + resource = jids[1] + else: + j = '' + + name = nick + + role_iter = self.get_role_iter(role) + if not role_iter: + role_iter = model.append(None, + (gajim.interface.jabber_state_images['16']['closed'], role, + 'role', role_name, None)) + self.draw_all_roles() + iter_ = model.append(role_iter, (None, nick, 'contact', name, None)) + if not nick in gajim.contacts.get_nick_list(self.account, self.room_jid): + gc_contact = gajim.contacts.create_gc_contact(room_jid=self.room_jid, account=self.account, + name=nick, show=show, status=status, role=role, + affiliation=affiliation, jid=j, resource=resource) + gajim.contacts.add_gc_contact(self.account, gc_contact) + self.draw_contact(nick) + self.draw_avatar(nick) + # Do not ask avatar to irc rooms as irc transports reply with messages + server = gajim.get_server_from_jid(self.room_jid) + if gajim.config.get('ask_avatars_on_startup') and \ + not server.startswith('irc'): + fake_jid = self.room_jid + '/' + nick + pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(fake_jid, True) + if pixbuf == 'ask': + if j: + fjid = j + if resource: + fjid += '/' + resource + gajim.connections[self.account].request_vcard(fjid, fake_jid) + else: + gajim.connections[self.account].request_vcard(fake_jid, fake_jid) + if nick == self.nick: # we became online + self.got_connected() + self.list_treeview.expand_row((model.get_path(role_iter)), False) + if self.is_continued: + self.draw_banner_text() + return iter_ + + def get_role_iter(self, role): + model = self.list_treeview.get_model() + fin = False + iter_ = model.get_iter_root() + if not iter_: + return None + while not fin: + role_name = model[iter_][C_NICK].decode('utf-8') + if role == role_name: + return iter_ + iter_ = model.iter_next(iter_) + if not iter_: + fin = True + return None + + def remove_contact(self, nick): + """ + Remove a user from the contacts_list + """ + model = self.list_treeview.get_model() + iter_ = self.get_contact_iter(nick) + if not iter_: + return + gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, + nick) + if gc_contact: + gajim.contacts.remove_gc_contact(self.account, gc_contact) + parent_iter = model.iter_parent(iter_) + model.remove(iter_) + if model.iter_n_children(parent_iter) == 0: + model.remove(parent_iter) + + def send_message(self, message, xhtml=None, process_commands=True): + """ + Call this function to send our message + """ + if not message: + return + + if process_commands and self.process_as_command(message): + return + + message = helpers.remove_invalid_xml_chars(message) + + if not message: + return + + if message != '' or message != '\n': + self.save_sent_message(message) + + # Send the message + gajim.connections[self.account].send_gc_message(self.room_jid, + message, xhtml=xhtml) + self.msg_textview.get_buffer().set_text('') + self.msg_textview.grab_focus() + + def get_role(self, nick): + gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, + nick) + if gc_contact: + return gc_contact.role + else: + return 'visitor' + + def minimizable(self): + if self.contact.jid in gajim.config.get_per('accounts', self.account, + 'minimized_gc').split(' '): + return True + return False + + def minimize(self, status='offline'): + # Minimize it + win = gajim.interface.msg_win_mgr.get_window(self.contact.jid, + self.account) + ctrl = win.get_control(self.contact.jid, self.account) + + ctrl_page = win.notebook.page_num(ctrl.widget) + control = win.notebook.get_nth_page(ctrl_page) + + win.notebook.remove_page(ctrl_page) + control.unparent() + ctrl.parent_win = None + + gajim.interface.roster.add_groupchat(self.contact.jid, self.account, + status = self.subject) + + del win._controls[self.account][self.contact.jid] + + def shutdown(self, status='offline'): + # Preventing autorejoin from being activated + self.autorejoin = False + + if self.room_jid in gajim.gc_connected[self.account] and \ + gajim.gc_connected[self.account][self.room_jid]: + # Tell connection to note the date we disconnect to avoid duplicate + # logs. We do it only when connected because if connection was lost + # there may be new messages since disconnection. + gajim.connections[self.account].gc_got_disconnected(self.room_jid) + gajim.connections[self.account].send_gc_status(self.nick, self.room_jid, + show='offline', status=status) + nick_list = gajim.contacts.get_nick_list(self.account, self.room_jid) + for nick in nick_list: + # Update pm chat window + fjid = self.room_jid + '/' + nick + ctrl = gajim.interface.msg_win_mgr.get_gc_control(fjid, self.account) + if ctrl: + contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, + nick) + contact.show = 'offline' + contact.status = '' + ctrl.update_ui() + ctrl.parent_win.redraw_tab(ctrl) + for sess in gajim.connections[self.account].get_sessions(fjid): + if sess.control: + sess.control.no_autonegotiation = False + if sess.enable_encryption: + sess.terminate_e2e() + gajim.connections[self.account].delete_session(fjid, + sess.thread_id) + # They can already be removed by the destroy function + if self.room_jid in gajim.contacts.get_gc_list(self.account): + gajim.contacts.remove_room(self.account, self.room_jid) + del gajim.gc_connected[self.account][self.room_jid] + # Save hpaned position + gajim.config.set('gc-hpaned-position', self.hpaned.get_position()) + # remove all register handlers on wigets, created by self.xml + # to prevent circular references among objects + for i in self.handlers.keys(): + if self.handlers[i].handler_is_connected(i): + self.handlers[i].disconnect(i) + del self.handlers[i] + # Remove unread events from systray + gajim.events.remove_events(self.account, self.room_jid) + + def safe_shutdown(self): + if self.minimizable(): + return True + includes = gajim.config.get('confirm_close_muc_rooms').split(' ') + excludes = gajim.config.get('noconfirm_close_muc_rooms').split(' ') + # whether to ask for comfirmation before closing muc + if (gajim.config.get('confirm_close_muc') or self.room_jid in includes) \ + and gajim.gc_connected[self.account][self.room_jid] and self.room_jid not\ + in excludes: + return False + return True + + def allow_shutdown(self, method, on_yes, on_no, on_minimize): + if self.minimizable(): + on_minimize(self) + return + if method == self.parent_win.CLOSE_ESC: + iter_ = self.list_treeview.get_selection().get_selected()[1] + if iter_: + self.list_treeview.get_selection().unselect_all() + on_no(self) + return + includes = gajim.config.get('confirm_close_muc_rooms').split(' ') + excludes = gajim.config.get('noconfirm_close_muc_rooms').split(' ') + # whether to ask for comfirmation before closing muc + if (gajim.config.get('confirm_close_muc') or self.room_jid in includes) \ + and gajim.gc_connected[self.account][self.room_jid] and self.room_jid not\ + in excludes: + + def on_ok(clicked): + if clicked: + # user does not want to be asked again + gajim.config.set('confirm_close_muc', False) + on_yes(self) + + def on_cancel(clicked): + if clicked: + # user does not want to be asked again + gajim.config.set('confirm_close_muc', False) + on_no(self) + + pritext = _('Are you sure you want to leave group chat "%s"?')\ + % self.name + sectext = _('If you close this window, you will be disconnected ' + 'from this group chat.') + + dialogs.ConfirmationDialogCheck(pritext, sectext, + _('Do _not ask me again'), on_response_ok=on_ok, + on_response_cancel=on_cancel) + return + + on_yes(self) + + def set_control_active(self, state): + self.conv_textview.allow_focus_out_line = True + self.attention_flag = False + ChatControlBase.set_control_active(self, state) + if not state: + # add the focus-out line to the tab we are leaving + self.check_and_possibly_add_focus_out_line() + # Sending active to undo unread state + self.parent_win.redraw_tab(self, 'active') + + def get_specific_unread(self): + # returns the number of the number of unread msgs + # for room_jid & number of unread private msgs with each contact + # that we have + nb = 0 + for nick in gajim.contacts.get_nick_list(self.account, self.room_jid): + fjid = self.room_jid + '/' + nick + nb += len(gajim.events.get_events(self.account, fjid)) + # gc can only have messages as event + return nb + + def _on_change_subject_menuitem_activate(self, widget): + def on_ok(subject): + # Note, we don't update self.subject since we don't know whether it + # will work yet + gajim.connections[self.account].send_gc_subject(self.room_jid, subject) + + dialogs.InputTextDialog(_('Changing Subject'), + _('Please specify the new subject:'), input_str=self.subject, + ok_handler=on_ok) + + def _on_change_nick_menuitem_activate(self, widget): + if 'change_nick_dialog' in gajim.interface.instances: + gajim.interface.instances['change_nick_dialog'].present() + else: + title = _('Changing Nickname') + prompt = _('Please specify the new nickname you want to use:') + gajim.interface.instances['change_nick_dialog'] = \ + dialogs.ChangeNickDialog(self.account, self.room_jid, title, + prompt) + + def _on_configure_room_menuitem_activate(self, widget): + c = gajim.contacts.get_gc_contact(self.account, self.room_jid, self.nick) + if c.affiliation == 'owner': + gajim.connections[self.account].request_gc_config(self.room_jid) + elif c.affiliation == 'admin': + if self.room_jid not in gajim.interface.instances[self.account][ + 'gc_config']: + gajim.interface.instances[self.account]['gc_config'][self.room_jid]\ + = config.GroupchatConfigWindow(self.account, self.room_jid) + + def _on_destroy_room_menuitem_activate(self, widget): + def on_ok(reason, jid): + if jid: + # Test jid + try: + jid = helpers.parse_jid(jid) + except Exception: + dialogs.ErrorDialog(_('Invalid group chat Jabber ID'), + _('The group chat Jabber ID has not allowed characters.')) + return + gajim.connections[self.account].destroy_gc_room(self.room_jid, reason, + jid) + + # Ask for a reason + dialogs.DubbleInputDialog(_('Destroying %s') % self.room_jid, + _('You are going to definitively destroy this room.\n' + 'You may specify a reason below:'), + _('You may also enter an alternate venue:'), ok_handler=on_ok) + + def _on_bookmark_room_menuitem_activate(self, widget): + """ + Bookmark the room, without autojoin and not minimized + """ + password = gajim.gc_passwords.get(self.room_jid, '') + gajim.interface.add_gc_bookmark(self.account, self.name, self.room_jid, \ + '0', '0', password, self.nick) + + def _on_drag_data_received(self, widget, context, x, y, selection, + target_type, timestamp): + # Invite contact to groupchat + treeview = gajim.interface.roster.tree + model = treeview.get_model() + if not selection.data or target_type == 80: + # target_type = 80 means a file is dropped + return + data = selection.data + path = treeview.get_selection().get_selected_rows()[1][0] + iter_ = model.get_iter(path) + type_ = model[iter_][2] + if type_ != 'contact': # source is not a contact + return + contact_jid = data.decode('utf-8') + gajim.connections[self.account].send_invite(self.room_jid, contact_jid) + + def handle_message_textview_mykey_press(self, widget, event_keyval, + event_keymod): + # NOTE: handles mykeypress which is custom signal connected to this + # CB in new_room(). for this singal see message_textview.py + + # construct event instance from binding + event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here + event.keyval = event_keyval + event.state = event_keymod + event.time = 0 # assign current time + + message_buffer = widget.get_buffer() + start_iter, end_iter = message_buffer.get_bounds() + + if event.keyval == gtk.keysyms.Tab: # TAB + cursor_position = message_buffer.get_insert() + end_iter = message_buffer.get_iter_at_mark(cursor_position) + text = message_buffer.get_text(start_iter, end_iter, False).decode( + 'utf-8') + + splitted_text = text.split() + + # HACK: Not the best soltution. + if (text.startswith(self.COMMAND_PREFIX) and not + text.startswith(self.COMMAND_PREFIX * 2) and len(splitted_text) == 1): + return super(GroupchatControl, + self).handle_message_textview_mykey_press(widget, event_keyval, + event_keymod) + + # nick completion + # check if tab is pressed with empty message + if len(splitted_text): # if there are any words + begin = splitted_text[-1] # last word we typed + else: + begin = '' + + gc_refer_to_nick_char = gajim.config.get('gc_refer_to_nick_char') + with_refer_to_nick_char = False + + # first part of this if : works fine even if refer_to_nick_char + if gc_refer_to_nick_char and begin.endswith(gc_refer_to_nick_char): + with_refer_to_nick_char = True + if len(self.nick_hits) and self.last_key_tabs and \ + text[:-len(gc_refer_to_nick_char + ' ')].endswith(self.nick_hits[0]): + # we should cycle + # Previous nick in list may had a space inside, so we check text and + # not splitted_text and store it into 'begin' var + self.nick_hits.append(self.nick_hits[0]) + begin = self.nick_hits.pop(0) + else: + self.nick_hits = [] # clear the hit list + list_nick = gajim.contacts.get_nick_list(self.account, + self.room_jid) + list_nick.sort(key=unicode.lower) # case-insensitive sort + if begin == '': + # empty message, show lasts nicks that highlighted us first + for nick in self.attention_list: + if nick in list_nick: + list_nick.remove(nick) + list_nick.insert(0, nick) + + list_nick.remove(self.nick) # Skip self + for nick in list_nick: + if nick.lower().startswith(begin.lower()): + # the word is the begining of a nick + self.nick_hits.append(nick) + if len(self.nick_hits): + if len(splitted_text) < 2 or with_refer_to_nick_char: + # This is the 1st word of the line or no word or we are cycling + # at the beginning, possibly with a space in one nick + add = gc_refer_to_nick_char + ' ' + else: + add = ' ' + start_iter = end_iter.copy() + if self.last_key_tabs and with_refer_to_nick_char or (text and \ + text[-1] == ' '): + # have to accomodate for the added space from last + # completion + # gc_refer_to_nick_char may be more than one char! + start_iter.backward_chars(len(begin) + len(add)) + elif self.last_key_tabs and not gajim.config.get( + 'shell_like_completion'): + # have to accomodate for the added space from last + # completion + start_iter.backward_chars(len(begin) + \ + len(gc_refer_to_nick_char)) + else: + start_iter.backward_chars(len(begin)) + + message_buffer.delete(start_iter, end_iter) + # get a shell-like completion + # if there's more than one nick for this completion, complete only + # the part that all these nicks have in common + if gajim.config.get('shell_like_completion') and \ + len(self.nick_hits) > 1: + end = False + completion = '' + add = "" # if nick is not complete, don't add anything + while not end and len(completion) < len(self.nick_hits[0]): + completion = self.nick_hits[0][:len(completion)+1] + for nick in self.nick_hits: + if completion.lower() not in nick.lower(): + end = True + completion = completion[:-1] + break + # if the current nick matches a COMPLETE existing nick, + # and if the user tab TWICE, complete that nick (with the "add") + if self.last_key_tabs: + for nick in self.nick_hits: + if nick == completion: + # The user seems to want this nick, so + # complete it as if it were the only nick + # available + add = gc_refer_to_nick_char + ' ' + else: + completion = self.nick_hits[0] + message_buffer.insert_at_cursor(completion + add) + self.last_key_tabs = True + return True + self.last_key_tabs = False + + def on_list_treeview_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: + selection = widget.get_selection() + iter_ = selection.get_selected()[1] + if iter_: + widget.get_selection().unselect_all() + return True + + def on_list_treeview_row_expanded(self, widget, iter_, path): + """ + When a row is expanded: change the icon of the arrow + """ + model = widget.get_model() + image = gajim.interface.jabber_state_images['16']['opened'] + model[iter_][C_IMG] = image + + def on_list_treeview_row_collapsed(self, widget, iter_, path): + """ + When a row is collapsed: change the icon of the arrow + """ + model = widget.get_model() + image = gajim.interface.jabber_state_images['16']['closed'] + model[iter_][C_IMG] = image + + def kick(self, widget, nick): + """ + Kick a user + """ + def on_ok(reason): + gajim.connections[self.account].gc_set_role(self.room_jid, nick, + 'none', reason) + + # ask for reason + dialogs.InputDialog(_('Kicking %s') % nick, + _('You may specify a reason below:'), ok_handler=on_ok) + + def mk_menu(self, event, iter_): + """ + Make contact's popup menu + """ + model = self.list_treeview.get_model() + nick = model[iter_][C_NICK].decode('utf-8') + c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick) + fjid = self.room_jid + '/' + nick + jid = c.jid + target_affiliation = c.affiliation + target_role = c.role + + # looking for user's affiliation and role + user_nick = self.nick + user_affiliation = gajim.contacts.get_gc_contact(self.account, + self.room_jid, user_nick).affiliation + user_role = self.get_role(user_nick) + + # making menu from glade + xml = gtkgui_helpers.get_glade('gc_occupants_menu.glade') + + # these conditions were taken from JEP 0045 + item = xml.get_widget('kick_menuitem') + if user_role != 'moderator' or \ + (user_affiliation == 'admin' and target_affiliation == 'owner') or \ + (user_affiliation == 'member' and target_affiliation in ('admin', + 'owner')) or (user_affiliation == 'none' and target_affiliation != \ + 'none'): + item.set_sensitive(False) + id_ = item.connect('activate', self.kick, nick) + self.handlers[id_] = item + + item = xml.get_widget('voice_checkmenuitem') + item.set_active(target_role != 'visitor') + if user_role != 'moderator' or \ + user_affiliation == 'none' or \ + (user_affiliation=='member' and target_affiliation!='none') or \ + target_affiliation in ('admin', 'owner'): + item.set_sensitive(False) + id_ = item.connect('activate', self.on_voice_checkmenuitem_activate, + nick) + self.handlers[id_] = item + + item = xml.get_widget('moderator_checkmenuitem') + item.set_active(target_role == 'moderator') + if not user_affiliation in ('admin', 'owner') or \ + target_affiliation in ('admin', 'owner'): + item.set_sensitive(False) + id_ = item.connect('activate', self.on_moderator_checkmenuitem_activate, + nick) + self.handlers[id_] = item + + item = xml.get_widget('ban_menuitem') + if not user_affiliation in ('admin', 'owner') or \ + (target_affiliation in ('admin', 'owner') and\ + user_affiliation != 'owner'): + item.set_sensitive(False) + id_ = item.connect('activate', self.ban, jid) + self.handlers[id_] = item + + item = xml.get_widget('member_checkmenuitem') + item.set_active(target_affiliation != 'none') + if not user_affiliation in ('admin', 'owner') or \ + (user_affiliation != 'owner' and target_affiliation in ('admin', 'owner')): + item.set_sensitive(False) + id_ = item.connect('activate', self.on_member_checkmenuitem_activate, jid) + self.handlers[id_] = item + + item = xml.get_widget('admin_checkmenuitem') + item.set_active(target_affiliation in ('admin', 'owner')) + if not user_affiliation == 'owner': + item.set_sensitive(False) + id_ = item.connect('activate', self.on_admin_checkmenuitem_activate, jid) + self.handlers[id_] = item + + item = xml.get_widget('owner_checkmenuitem') + item.set_active(target_affiliation == 'owner') + if not user_affiliation == 'owner': + item.set_sensitive(False) + id_ = item.connect('activate', self.on_owner_checkmenuitem_activate, jid) + self.handlers[id_] = item + + item = xml.get_widget('information_menuitem') + id_ = item.connect('activate', self.on_info, nick) + self.handlers[id_] = item + + item = xml.get_widget('history_menuitem') + id_ = item.connect('activate', self.on_history, nick) + self.handlers[id_] = item + + item = xml.get_widget('add_to_roster_menuitem') + our_jid = gajim.get_jid_from_account(self.account) + if not jid or jid == our_jid: + item.set_sensitive(False) + else: + id_ = item.connect('activate', self.on_add_to_roster, jid) + self.handlers[id_] = item + + item = xml.get_widget('block_menuitem') + item2 = xml.get_widget('unblock_menuitem') + if helpers.jid_is_blocked(self.account, fjid): + item.set_no_show_all(True) + item.hide() + id_ = item2.connect('activate', self.on_unblock, nick) + self.handlers[id_] = item2 + else: + id_ = item.connect('activate', self.on_block, nick) + self.handlers[id_] = item + item2.set_no_show_all(True) + item2.hide() + + item = xml.get_widget('send_private_message_menuitem') + id_ = item.connect('activate', self.on_send_pm, model, iter_) + self.handlers[id_] = item + + item = xml.get_widget('send_file_menuitem') + # add a special img for send file menuitem + path_to_upload_img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'upload.png') + img = gtk.Image() + img.set_from_file(path_to_upload_img) + item.set_image(img) + + if not c.resource: + item.set_sensitive(False) + else: + id_ = item.connect('activate', self.on_send_file, c) + self.handlers[id_] = item + + # show the popup now! + menu = xml.get_widget('gc_occupants_menu') + menu.show_all() + menu.popup(None, None, None, event.button, event.time) + + def _start_private_message(self, nick): + gc_c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick) + nick_jid = gc_c.get_full_jid() + + ctrl = gajim.interface.msg_win_mgr.get_control(nick_jid, self.account) + if not ctrl: + ctrl = gajim.interface.new_private_chat(gc_c, self.account) + + if ctrl: + ctrl.parent_win.set_active_tab(ctrl) + + return ctrl + + def on_row_activated(self, widget, path): + """ + When an iter is activated (dubblick or single click if gnome is set this + way + """ + model = widget.get_model() + if len(path) == 1: # It's a group + if (widget.row_expanded(path)): + widget.collapse_row(path) + else: + widget.expand_row(path, False) + else: # We want to send a private message + nick = model[path][C_NICK].decode('utf-8') + self._start_private_message(nick) + + def on_list_treeview_row_activated(self, widget, path, col=0): + """ + When an iter is double clicked: open the chat window + """ + if not gajim.single_click: + self.on_row_activated(widget, path) + + def on_list_treeview_button_press_event(self, widget, event): + """ + Popup user's group's or agent menu + """ + # hide tooltip, no matter the button is pressed + self.tooltip.hide_tooltip() + try: + pos = widget.get_path_at_pos(int(event.x), int(event.y)) + path, x = pos[0], pos[2] + except TypeError: + widget.get_selection().unselect_all() + return + if event.button == 3: # right click + widget.get_selection().select_path(path) + model = widget.get_model() + iter_ = model.get_iter(path) + if len(path) == 2: + self.mk_menu(event, iter_) + return True + + elif event.button == 2: # middle click + widget.get_selection().select_path(path) + model = widget.get_model() + iter_ = model.get_iter(path) + if len(path) == 2: + nick = model[iter_][C_NICK].decode('utf-8') + self._start_private_message(nick) + return True + + elif event.button == 1: # left click + if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK: + self.on_row_activated(widget, path) + return True + else: + model = widget.get_model() + iter_ = model.get_iter(path) + nick = model[iter_][C_NICK].decode('utf-8') + if not nick in gajim.contacts.get_nick_list(self.account, + self.room_jid): + # it's a group + if x < 27: + if (widget.row_expanded(path)): + widget.collapse_row(path) + else: + widget.expand_row(path, False) + elif event.state & gtk.gdk.SHIFT_MASK: + self.append_nick_in_msg_textview(self.msg_textview, nick) + self.msg_textview.grab_focus() + return True + + def append_nick_in_msg_textview(self, widget, nick): + message_buffer = self.msg_textview.get_buffer() + start_iter, end_iter = message_buffer.get_bounds() + cursor_position = message_buffer.get_insert() + end_iter = message_buffer.get_iter_at_mark(cursor_position) + text = message_buffer.get_text(start_iter, end_iter, False) + start = '' + if text: # Cursor is not at first position + if not text[-1] in (' ', '\n', '\t'): + start = ' ' + add = ' ' + else: + gc_refer_to_nick_char = gajim.config.get('gc_refer_to_nick_char') + add = gc_refer_to_nick_char + ' ' + message_buffer.insert_at_cursor(start + nick + add) + + def on_list_treeview_motion_notify_event(self, widget, event): + model = widget.get_model() + props = widget.get_path_at_pos(int(event.x), int(event.y)) + if self.tooltip.timeout > 0: + if not props or self.tooltip.id != props[0]: + self.tooltip.hide_tooltip() + if props: + [row, col, x, y] = props + iter_ = None + try: + iter_ = model.get_iter(row) + except Exception: + self.tooltip.hide_tooltip() + return + typ = model[iter_][C_TYPE].decode('utf-8') + if typ == 'contact': + account = self.account + + if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: + self.tooltip.id = row + nick = model[iter_][C_NICK].decode('utf-8') + self.tooltip.timeout = gobject.timeout_add(500, + self.show_tooltip, gajim.contacts.get_gc_contact(account, + self.room_jid, nick)) + + def on_list_treeview_leave_notify_event(self, widget, event): + props = widget.get_path_at_pos(int(event.x), int(event.y)) + if self.tooltip.timeout > 0: + if not props or self.tooltip.id == props[0]: + self.tooltip.hide_tooltip() + + def show_tooltip(self, contact): + if not self.list_treeview.window: + # control has been destroyed since tooltip was requested + return + pointer = self.list_treeview.get_pointer() + props = self.list_treeview.get_path_at_pos(pointer[0], pointer[1]) + # check if the current pointer is at the same path + # as it was before setting the timeout + if props and self.tooltip.id == props[0]: + rect = self.list_treeview.get_cell_area(props[0], props[1]) + position = self.list_treeview.window.get_origin() + self.tooltip.show_tooltip(contact, rect.height, + position[1] + rect.y) + else: + self.tooltip.hide_tooltip() + + def grant_voice(self, widget, nick): + """ + Grant voice privilege to a user + """ + gajim.connections[self.account].gc_set_role(self.room_jid, nick, + 'participant') + + def revoke_voice(self, widget, nick): + """ + Revoke voice privilege to a user + """ + gajim.connections[self.account].gc_set_role(self.room_jid, nick, + 'visitor') + + def grant_moderator(self, widget, nick): + """ + Grant moderator privilege to a user + """ + gajim.connections[self.account].gc_set_role(self.room_jid, nick, + 'moderator') + + def revoke_moderator(self, widget, nick): + """ + Revoke moderator privilege to a user + """ + gajim.connections[self.account].gc_set_role(self.room_jid, nick, + 'participant') + + def ban(self, widget, jid): + """ + Ban a user + """ + def on_ok(reason): + gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, + 'outcast', reason) + + # to ban we know the real jid. so jid is not fakejid + nick = gajim.get_nick_from_jid(jid) + # ask for reason + dialogs.InputDialog(_('Banning %s') % nick, + _('You may specify a reason below:'), ok_handler=on_ok) + + def grant_membership(self, widget, jid): + """ + Grant membership privilege to a user + """ + gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, + 'member') + + def revoke_membership(self, widget, jid): + """ + Revoke membership privilege to a user + """ + gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, + 'none') + + def grant_admin(self, widget, jid): + """ + Grant administrative privilege to a user + """ + gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, + 'admin') + + def revoke_admin(self, widget, jid): + """ + Revoke administrative privilege to a user + """ + gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, + 'member') + + def grant_owner(self, widget, jid): + """ + Grant owner privilege to a user + """ + gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, + 'owner') + + def revoke_owner(self, widget, jid): + """ + Revoke owner privilege to a user + """ + gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, + 'admin') + + def on_info(self, widget, nick): + '''Call vcard_information_window class to display user's information''' + gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick) + contact = gc_contact.as_contact() + if contact.jid in gajim.interface.instances[self.account]['infos']: + gajim.interface.instances[self.account]['infos'][contact.jid].window.\ + present() + else: + gajim.interface.instances[self.account]['infos'][contact.jid] = \ + vcard.VcardWindow(contact, self.account, gc_contact) + + def on_history(self, widget, nick): + jid = gajim.construct_fjid(self.room_jid, nick) + self._on_history_menuitem_activate(widget=widget, jid=jid) + + def on_add_to_roster(self, widget, jid): + dialogs.AddNewContactWindow(self.account, jid) + + def on_block(self, widget, nick): + fjid = self.room_jid + '/' + nick + connection = gajim.connections[self.account] + if fjid in connection.blocked_contacts: + return + new_rule = {'order': u'1', 'type': u'jid', 'action': u'deny', + 'value' : fjid, 'child': [u'message', u'iq', u'presence-out']} + connection.blocked_list.append(new_rule) + connection.blocked_contacts.append(fjid) + self.draw_contact(nick) + connection.set_privacy_list('block', connection.blocked_list) + if len(connection.blocked_list) == 1: + connection.set_active_list('block') + connection.set_default_list('block') + connection.get_privacy_list('block') + + def on_unblock(self, widget, nick): + fjid = self.room_jid + '/' + nick + connection = gajim.connections[self.account] + connection.new_blocked_list = [] + # needed for draw_contact: + if fjid in connection.blocked_contacts: + connection.blocked_contacts.remove(fjid) + self.draw_contact(nick) + for rule in connection.blocked_list: + if rule['action'] != 'deny' or rule['type'] != 'jid' \ + or rule['value'] != fjid: + connection.new_blocked_list.append(rule) + + connection.set_privacy_list('block', connection.new_blocked_list) + connection.get_privacy_list('block') + if len(connection.new_blocked_list) == 0: + connection.blocked_list = [] + connection.blocked_contacts = [] + connection.blocked_groups = [] + connection.set_default_list('') + connection.set_active_list('') + connection.del_privacy_list('block') + if 'blocked_contacts' in gajim.interface.instances[self.account]: + gajim.interface.instances[self.account]['blocked_contacts'].\ + privacy_list_received([]) + + def on_voice_checkmenuitem_activate(self, widget, nick): + if widget.get_active(): + self.grant_voice(widget, nick) + else: + self.revoke_voice(widget, nick) + + def on_moderator_checkmenuitem_activate(self, widget, nick): + if widget.get_active(): + self.grant_moderator(widget, nick) + else: + self.revoke_moderator(widget, nick) + + def on_member_checkmenuitem_activate(self, widget, jid): + if widget.get_active(): + self.grant_membership(widget, jid) + else: + self.revoke_membership(widget, jid) + + def on_admin_checkmenuitem_activate(self, widget, jid): + if widget.get_active(): + self.grant_admin(widget, jid) + else: + self.revoke_admin(widget, jid) + + def on_owner_checkmenuitem_activate(self, widget, jid): + if widget.get_active(): + self.grant_owner(widget, jid) + else: + self.revoke_owner(widget, jid) diff --git a/src/groups.py b/src/groups.py index 2588b11a4..1103692f3 100644 --- a/src/groups.py +++ b/src/groups.py @@ -25,51 +25,49 @@ from common import gajim, xmpp import gtkgui_helpers class GroupsPostWindow: - def __init__(self, account, servicejid, groupid): - """ - Open new 'create post' window to create message for groupid on servicejid - service - """ - assert isinstance(servicejid, basestring) - assert isinstance(groupid, basestring) + def __init__(self, account, servicejid, groupid): + """ + Open new 'create post' window to create message for groupid on servicejid + service + """ + assert isinstance(servicejid, basestring) + assert isinstance(groupid, basestring) - self.account = account - self.servicejid = servicejid - self.groupid = groupid + self.account = account + self.servicejid = servicejid + self.groupid = groupid - self.xml = gtkgui_helpers.get_glade('groups_post_window.glade') - self.window = self.xml.get_widget('groups_post_window') - for name in ('from_entry', 'subject_entry', 'contents_textview'): - self.__dict__[name] = self.xml.get_widget(name) + self.xml = gtkgui_helpers.get_glade('groups_post_window.glade') + self.window = self.xml.get_widget('groups_post_window') + for name in ('from_entry', 'subject_entry', 'contents_textview'): + self.__dict__[name] = self.xml.get_widget(name) - self.xml.signal_autoconnect(self) - self.window.show_all() + self.xml.signal_autoconnect(self) + self.window.show_all() - def on_cancel_button_clicked(self, w): - """ - Close window - """ - self.window.destroy() + def on_cancel_button_clicked(self, w): + """ + Close window + """ + self.window.destroy() - def on_send_button_clicked(self, w): - """ - Gather info from widgets and send it as a message - """ - # constructing item to publish... that's atom:entry element - item = xmpp.Node('entry', {'xmlns':'http://www.w3.org/2005/Atom'}) - author = item.addChild('author') - author.addChild('name', {}, [self.from_entry.get_text()]) - item.addChild('generator', {}, ['Gajim']) - item.addChild('title', {}, [self.subject_entry.get_text()]) - item.addChild('id', {}, ['0']) + def on_send_button_clicked(self, w): + """ + Gather info from widgets and send it as a message + """ + # constructing item to publish... that's atom:entry element + item = xmpp.Node('entry', {'xmlns':'http://www.w3.org/2005/Atom'}) + author = item.addChild('author') + author.addChild('name', {}, [self.from_entry.get_text()]) + item.addChild('generator', {}, ['Gajim']) + item.addChild('title', {}, [self.subject_entry.get_text()]) + item.addChild('id', {}, ['0']) - buf = self.contents_textview.get_buffer() - item.addChild('content', {}, [buf.get_text(buf.get_start_iter(), buf.get_end_iter())]) + buf = self.contents_textview.get_buffer() + item.addChild('content', {}, [buf.get_text(buf.get_start_iter(), buf.get_end_iter())]) - # publish it to node - gajim.connections[self.account].send_pb_publish(self.servicejid, self.groupid, item, '0') + # publish it to node + gajim.connections[self.account].send_pb_publish(self.servicejid, self.groupid, item, '0') - # close the window - self.window.destroy() - -# vim: se ts=3: + # close the window + self.window.destroy() diff --git a/src/gtkexcepthook.py b/src/gtkexcepthook.py index bbc93f373..f71526b8c 100644 --- a/src/gtkexcepthook.py +++ b/src/gtkexcepthook.py @@ -36,73 +36,71 @@ from common import helpers _exception_in_progress = threading.Lock() def _info(type_, value, tb): - if not _exception_in_progress.acquire(False): - # Exceptions have piled up, so we use the default exception - # handler for such exceptions - _excepthook_save(type_, value, tb) - return + if not _exception_in_progress.acquire(False): + # Exceptions have piled up, so we use the default exception + # handler for such exceptions + _excepthook_save(type_, value, tb) + return - dialog = dialogs.HigDialog(None, gtk.MESSAGE_WARNING, gtk.BUTTONS_NONE, - _('A programming error has been detected'), - _('It probably is not fatal, but should be reported ' - 'to the developers nonetheless.')) + dialog = dialogs.HigDialog(None, gtk.MESSAGE_WARNING, gtk.BUTTONS_NONE, + _('A programming error has been detected'), + _('It probably is not fatal, but should be reported ' + 'to the developers nonetheless.')) - dialog.set_modal(False) - #FIXME: add icon to this button - RESPONSE_REPORT_BUG = 42 - dialog.add_buttons(gtk.STOCK_CLOSE, gtk.BUTTONS_CLOSE, - _('_Report Bug'), RESPONSE_REPORT_BUG) - dialog.set_default_response(RESPONSE_REPORT_BUG) - report_button = dialog.action_area.get_children()[0] # right to left - report_button.grab_focus() + dialog.set_modal(False) + #FIXME: add icon to this button + RESPONSE_REPORT_BUG = 42 + dialog.add_buttons(gtk.STOCK_CLOSE, gtk.BUTTONS_CLOSE, + _('_Report Bug'), RESPONSE_REPORT_BUG) + dialog.set_default_response(RESPONSE_REPORT_BUG) + report_button = dialog.action_area.get_children()[0] # right to left + report_button.grab_focus() - # Details - textview = gtk.TextView() - textview.set_editable(False) - textview.modify_font(pango.FontDescription('Monospace')) - sw = gtk.ScrolledWindow() - sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - sw.add(textview) - frame = gtk.Frame() - frame.set_shadow_type(gtk.SHADOW_IN) - frame.add(sw) - frame.set_border_width(6) - textbuffer = textview.get_buffer() - trace = StringIO() - traceback.print_exception(type_, value, tb, None, trace) - textbuffer.set_text(trace.getvalue()) - textview.set_size_request( - gtk.gdk.screen_width() / 3, - gtk.gdk.screen_height() / 4) - expander = gtk.Expander(_('Details')) - expander.add(frame) - dialog.vbox.add(expander) + # Details + textview = gtk.TextView() + textview.set_editable(False) + textview.modify_font(pango.FontDescription('Monospace')) + sw = gtk.ScrolledWindow() + sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + sw.add(textview) + frame = gtk.Frame() + frame.set_shadow_type(gtk.SHADOW_IN) + frame.add(sw) + frame.set_border_width(6) + textbuffer = textview.get_buffer() + trace = StringIO() + traceback.print_exception(type_, value, tb, None, trace) + textbuffer.set_text(trace.getvalue()) + textview.set_size_request( + gtk.gdk.screen_width() / 3, + gtk.gdk.screen_height() / 4) + expander = gtk.Expander(_('Details')) + expander.add(frame) + dialog.vbox.add(expander) - dialog.set_resizable(True) - # on expand the details the dialog remains centered on screen - dialog.set_position(gtk.WIN_POS_CENTER_ALWAYS) + dialog.set_resizable(True) + # on expand the details the dialog remains centered on screen + dialog.set_position(gtk.WIN_POS_CENTER_ALWAYS) - def on_dialog_response(dialog, response): - if response == RESPONSE_REPORT_BUG: - url = 'http://trac.gajim.org/wiki/HowToCreateATicket' - helpers.launch_browser_mailer('url', url) - else: - dialog.destroy() - dialog.connect('response', on_dialog_response) - dialog.show_all() + def on_dialog_response(dialog, response): + if response == RESPONSE_REPORT_BUG: + url = 'http://trac.gajim.org/wiki/HowToCreateATicket' + helpers.launch_browser_mailer('url', url) + else: + dialog.destroy() + dialog.connect('response', on_dialog_response) + dialog.show_all() - _exception_in_progress.release() + _exception_in_progress.release() # gdb/kdm etc if we use startx this is not True if os.name == 'nt' or not sys.stderr.isatty(): - #FIXME: maybe always show dialog? - _excepthook_save = sys.excepthook - sys.excepthook = _info + #FIXME: maybe always show dialog? + _excepthook_save = sys.excepthook + sys.excepthook = _info # this is just to assist testing (python gtkexcepthook.py) if __name__ == '__main__': - _excepthook_save = sys.excepthook - sys.excepthook = _info - raise Exception() - -# vim: se ts=3: + _excepthook_save = sys.excepthook + sys.excepthook = _info + raise Exception() diff --git a/src/gtkgui_helpers.py b/src/gtkgui_helpers.py index 273dc65b7..4b04dad6f 100644 --- a/src/gtkgui_helpers.py +++ b/src/gtkgui_helpers.py @@ -44,12 +44,12 @@ log = logging.getLogger('gajim.gtkgui_helpers') HAS_PYWIN32 = True if os.name == 'nt': - try: - import win32file - import win32con - import pywintypes - except ImportError: - HAS_PYWIN32 = False + try: + import win32file + import win32con + import pywintypes + except ImportError: + HAS_PYWIN32 = False from common import i18n from common import gajim @@ -63,669 +63,669 @@ screen_h = gtk.gdk.screen_height() GLADE_DIR = os.path.join(gajim.DATA_DIR, 'glade') def get_glade(file_name, root = None): - file_path = os.path.join(GLADE_DIR, file_name) - return gtk.glade.XML(file_path, root=root, domain=i18n.APP) + file_path = os.path.join(GLADE_DIR, file_name) + return gtk.glade.XML(file_path, root=root, domain=i18n.APP) def get_completion_liststore(entry): - """ - Create a completion model for entry widget completion list consists of - (Pixbuf, Text) rows - """ - completion = gtk.EntryCompletion() - liststore = gtk.ListStore(gtk.gdk.Pixbuf, str) + """ + Create a completion model for entry widget completion list consists of + (Pixbuf, Text) rows + """ + completion = gtk.EntryCompletion() + liststore = gtk.ListStore(gtk.gdk.Pixbuf, str) - render_pixbuf = gtk.CellRendererPixbuf() - completion.pack_start(render_pixbuf, expand = False) - completion.add_attribute(render_pixbuf, 'pixbuf', 0) + render_pixbuf = gtk.CellRendererPixbuf() + completion.pack_start(render_pixbuf, expand = False) + completion.add_attribute(render_pixbuf, 'pixbuf', 0) - render_text = gtk.CellRendererText() - completion.pack_start(render_text, expand = True) - completion.add_attribute(render_text, 'text', 1) - completion.set_property('text_column', 1) - completion.set_model(liststore) - entry.set_completion(completion) - return liststore + render_text = gtk.CellRendererText() + completion.pack_start(render_text, expand = True) + completion.add_attribute(render_text, 'text', 1) + completion.set_property('text_column', 1) + completion.set_model(liststore) + entry.set_completion(completion) + return liststore def popup_emoticons_under_button(menu, button, parent_win): - """ - Popup the emoticons menu under button, which is in parent_win - """ - window_x1, window_y1 = parent_win.get_origin() - def position_menu_under_button(menu): - # inline function, which will not keep refs, when used as CB - button_x, button_y = button.allocation.x, button.allocation.y + """ + Popup the emoticons menu under button, which is in parent_win + """ + window_x1, window_y1 = parent_win.get_origin() + def position_menu_under_button(menu): + # inline function, which will not keep refs, when used as CB + button_x, button_y = button.allocation.x, button.allocation.y - # now convert them to X11-relative - window_x, window_y = window_x1, window_y1 - x = window_x + button_x - y = window_y + button_y + # now convert them to X11-relative + window_x, window_y = window_x1, window_y1 + x = window_x + button_x + y = window_y + button_y - menu_height = menu.size_request()[1] + menu_height = menu.size_request()[1] - ## should we pop down or up? - if (y + button.allocation.height + menu_height - < gtk.gdk.screen_height()): - # now move the menu below the button - y += button.allocation.height - else: - # now move the menu above the button - y -= menu_height + ## should we pop down or up? + if (y + button.allocation.height + menu_height + < gtk.gdk.screen_height()): + # now move the menu below the button + y += button.allocation.height + else: + # now move the menu above the button + y -= menu_height - # push_in is True so all the menuitems are always inside screen - push_in = True - return (x, y, push_in) + # push_in is True so all the menuitems are always inside screen + push_in = True + return (x, y, push_in) - menu.popup(None, None, position_menu_under_button, 1, 0) + menu.popup(None, None, position_menu_under_button, 1, 0) def get_theme_font_for_option(theme, option): - """ - Return string description of the font, stored in theme preferences - """ - font_name = gajim.config.get_per('themes', theme, option) - font_desc = pango.FontDescription() - font_prop_str = gajim.config.get_per('themes', theme, option + 'attrs') - if font_prop_str: - if font_prop_str.find('B') != -1: - font_desc.set_weight(pango.WEIGHT_BOLD) - if font_prop_str.find('I') != -1: - font_desc.set_style(pango.STYLE_ITALIC) - fd = pango.FontDescription(font_name) - fd.merge(font_desc, True) - return fd.to_string() + """ + Return string description of the font, stored in theme preferences + """ + font_name = gajim.config.get_per('themes', theme, option) + font_desc = pango.FontDescription() + font_prop_str = gajim.config.get_per('themes', theme, option + 'attrs') + if font_prop_str: + if font_prop_str.find('B') != -1: + font_desc.set_weight(pango.WEIGHT_BOLD) + if font_prop_str.find('I') != -1: + font_desc.set_style(pango.STYLE_ITALIC) + fd = pango.FontDescription(font_name) + fd.merge(font_desc, True) + return fd.to_string() def get_default_font(): - """ - Get the desktop setting for application font first check for GNOME, then - Xfce and last KDE it returns None on failure or else a string 'Font Size' - """ - try: - import gconf - # in try because daemon may not be there - client = gconf.client_get_default() + """ + Get the desktop setting for application font first check for GNOME, then + Xfce and last KDE it returns None on failure or else a string 'Font Size' + """ + try: + import gconf + # in try because daemon may not be there + client = gconf.client_get_default() - return client.get_string('/desktop/gnome/interface/font_name' - ).decode('utf-8') - except Exception: - pass + return client.get_string('/desktop/gnome/interface/font_name' + ).decode('utf-8') + except Exception: + pass - # try to get xfce default font - # Xfce 4.2 adopts freedesktop.org's Base Directory Specification - # see http://www.xfce.org/~benny/xfce/file-locations.html - # and http://freedesktop.org/Standards/basedir-spec - xdg_config_home = os.environ.get('XDG_CONFIG_HOME', '') - if xdg_config_home == '': - xdg_config_home = os.path.expanduser('~/.config') # default - xfce_config_file = os.path.join(xdg_config_home, 'xfce4/mcs_settings/gtk.xml') + # try to get xfce default font + # Xfce 4.2 adopts freedesktop.org's Base Directory Specification + # see http://www.xfce.org/~benny/xfce/file-locations.html + # and http://freedesktop.org/Standards/basedir-spec + xdg_config_home = os.environ.get('XDG_CONFIG_HOME', '') + if xdg_config_home == '': + xdg_config_home = os.path.expanduser('~/.config') # default + xfce_config_file = os.path.join(xdg_config_home, 'xfce4/mcs_settings/gtk.xml') - kde_config_file = os.path.expanduser('~/.kde/share/config/kdeglobals') + kde_config_file = os.path.expanduser('~/.kde/share/config/kdeglobals') - if os.path.exists(xfce_config_file): - try: - for line in open(xfce_config_file): - if line.find('name="Gtk/FontName"') != -1: - start = line.find('value="') + 7 - return line[start:line.find('"', start)].decode('utf-8') - except Exception: - #we talk about file - print >> sys.stderr, _('Error: cannot open %s for reading') % xfce_config_file + if os.path.exists(xfce_config_file): + try: + for line in open(xfce_config_file): + if line.find('name="Gtk/FontName"') != -1: + start = line.find('value="') + 7 + return line[start:line.find('"', start)].decode('utf-8') + except Exception: + #we talk about file + print >> sys.stderr, _('Error: cannot open %s for reading') % xfce_config_file - elif os.path.exists(kde_config_file): - try: - for line in open(kde_config_file): - if line.find('font=') == 0: # font=Verdana,9,other_numbers - start = 5 # 5 is len('font=') - line = line[start:] - values = line.split(',') - font_name = values[0] - font_size = values[1] - font_string = '%s %s' % (font_name, font_size) # Verdana 9 - return font_string.decode('utf-8') - except Exception: - #we talk about file - print >> sys.stderr, _('Error: cannot open %s for reading') % kde_config_file + elif os.path.exists(kde_config_file): + try: + for line in open(kde_config_file): + if line.find('font=') == 0: # font=Verdana,9,other_numbers + start = 5 # 5 is len('font=') + line = line[start:] + values = line.split(',') + font_name = values[0] + font_size = values[1] + font_string = '%s %s' % (font_name, font_size) # Verdana 9 + return font_string.decode('utf-8') + except Exception: + #we talk about file + print >> sys.stderr, _('Error: cannot open %s for reading') % kde_config_file - return None + return None def autodetect_browser_mailer(): - # recognize the environment and set appropriate browser/mailer - if user_runs_gnome(): - gajim.config.set('openwith', 'gnome-open') - elif user_runs_kde(): - gajim.config.set('openwith', 'kfmclient exec') - elif user_runs_xfce(): - gajim.config.set('openwith', 'exo-open') - else: - gajim.config.set('openwith', 'custom') + # recognize the environment and set appropriate browser/mailer + if user_runs_gnome(): + gajim.config.set('openwith', 'gnome-open') + elif user_runs_kde(): + gajim.config.set('openwith', 'kfmclient exec') + elif user_runs_xfce(): + gajim.config.set('openwith', 'exo-open') + else: + gajim.config.set('openwith', 'custom') def user_runs_gnome(): - return 'gnome-session' in get_running_processes() + return 'gnome-session' in get_running_processes() def user_runs_kde(): - return 'startkde' in get_running_processes() + return 'startkde' in get_running_processes() def user_runs_xfce(): - procs = get_running_processes() - if 'startxfce4' in procs or 'xfce4-session' in procs: - return True - return False + procs = get_running_processes() + if 'startxfce4' in procs or 'xfce4-session' in procs: + return True + return False def get_running_processes(): - """ - Return running processes or None (if /proc does not exist) - """ - if os.path.isdir('/proc'): - # under Linux: checking if 'gnome-session' or - # 'startkde' programs were run before gajim, by - # checking /proc (if it exists) - # - # if something is unclear, read `man proc`; - # if /proc exists, directories that have only numbers - # in their names contain data about processes. - # /proc/[xxx]/exe is a symlink to executable started - # as process number [xxx]. - # filter out everything that we are not interested in: - files = os.listdir('/proc') + """ + Return running processes or None (if /proc does not exist) + """ + if os.path.isdir('/proc'): + # under Linux: checking if 'gnome-session' or + # 'startkde' programs were run before gajim, by + # checking /proc (if it exists) + # + # if something is unclear, read `man proc`; + # if /proc exists, directories that have only numbers + # in their names contain data about processes. + # /proc/[xxx]/exe is a symlink to executable started + # as process number [xxx]. + # filter out everything that we are not interested in: + files = os.listdir('/proc') - # files that doesn't have only digits in names... - files = filter(str.isdigit, files) + # files that doesn't have only digits in names... + files = filter(str.isdigit, files) - # files that aren't directories... - files = [f for f in files if os.path.isdir('/proc/' + f)] + # files that aren't directories... + files = [f for f in files if os.path.isdir('/proc/' + f)] - # processes owned by somebody not running gajim... - # (we check if we have access to that file) - files = [f for f in files if os.access('/proc/' + f +'/exe', os.F_OK)] + # processes owned by somebody not running gajim... + # (we check if we have access to that file) + files = [f for f in files if os.access('/proc/' + f +'/exe', os.F_OK)] - # be sure that /proc/[number]/exe is really a symlink - # to avoid TBs in incorrectly configured systems - files = [f for f in files if os.path.islink('/proc/' + f + '/exe')] + # be sure that /proc/[number]/exe is really a symlink + # to avoid TBs in incorrectly configured systems + files = [f for f in files if os.path.islink('/proc/' + f + '/exe')] - # list of processes - processes = [os.path.basename(os.readlink('/proc/' + f +'/exe')) for f in files] + # list of processes + processes = [os.path.basename(os.readlink('/proc/' + f +'/exe')) for f in files] - return processes - return [] + return processes + return [] def move_window(window, x, y): - """ - Move the window, but also check if out of screen - """ - if x < 0: - x = 0 - if y < 0: - y = 0 - w, h = window.get_size() - if x + w > screen_w: - x = screen_w - w - if y + h > screen_h: - y = screen_h - h - window.move(x, y) + """ + Move the window, but also check if out of screen + """ + if x < 0: + x = 0 + if y < 0: + y = 0 + w, h = window.get_size() + if x + w > screen_w: + x = screen_w - w + if y + h > screen_h: + y = screen_h - h + window.move(x, y) def resize_window(window, w, h): - """ - Resize window, but also checks if huge window or negative values - """ - if not w or not h: - return - if w > screen_w: - w = screen_w - if h > screen_h: - h = screen_h - window.resize(abs(w), abs(h)) + """ + Resize window, but also checks if huge window or negative values + """ + if not w or not h: + return + if w > screen_w: + w = screen_w + if h > screen_h: + h = screen_h + window.resize(abs(w), abs(h)) class HashDigest: - def __init__(self, algo, digest): - self.algo = self.cleanID(algo) - self.digest = self.cleanID(digest) + def __init__(self, algo, digest): + self.algo = self.cleanID(algo) + self.digest = self.cleanID(digest) - def cleanID(self, id_): - id_ = id_.strip().lower() - for strip in (' :.-_'): id_ = id_.replace(strip, '') - return id_ + def cleanID(self, id_): + id_ = id_.strip().lower() + for strip in (' :.-_'): id_ = id_.replace(strip, '') + return id_ - def __eq__(self, other): - sa, sd = self.algo, self.digest - if isinstance(other, self.__class__): - oa, od = other.algo, other.digest - elif isinstance(other, basestring): - sa, oa, od = None, None, self.cleanID(other) - elif isinstance(other, tuple) and len(other) == 2: - oa, od = self.cleanID(other[0]), self.cleanID(other[1]) - else: - return False + def __eq__(self, other): + sa, sd = self.algo, self.digest + if isinstance(other, self.__class__): + oa, od = other.algo, other.digest + elif isinstance(other, basestring): + sa, oa, od = None, None, self.cleanID(other) + elif isinstance(other, tuple) and len(other) == 2: + oa, od = self.cleanID(other[0]), self.cleanID(other[1]) + else: + return False - return sa == oa and sd == od + return sa == oa and sd == od - def __ne__(self, other): - return not self == other + def __ne__(self, other): + return not self == other - def __hash__(self): - return self.algo ^ self.digest + def __hash__(self): + return self.algo ^ self.digest - def __str__(self): - prettydigest = '' - for i in xrange(0, len(self.digest), 2): - prettydigest += self.digest[i:i + 2] + ':' - return prettydigest[:-1] + def __str__(self): + prettydigest = '' + for i in xrange(0, len(self.digest), 2): + prettydigest += self.digest[i:i + 2] + ':' + return prettydigest[:-1] - def __repr__(self): - return "%s(%s, %s)" % (self.__class__, repr(self.algo), repr(str(self))) + def __repr__(self): + return "%s(%s, %s)" % (self.__class__, repr(self.algo), repr(str(self))) class ServersXMLHandler(xml.sax.ContentHandler): - def __init__(self): - xml.sax.ContentHandler.__init__(self) - self.servers = [] + def __init__(self): + xml.sax.ContentHandler.__init__(self) + self.servers = [] - def startElement(self, name, attributes): - if name == 'item': - # we will get the port next time so we just set it 0 here - sitem = [None, 0, {}] - sitem[2]['digest'] = {} - sitem[2]['hidden'] = False - for attribute in attributes.getNames(): - if attribute == 'jid': - jid = attributes.getValue(attribute) - sitem[0] = jid - elif attribute == 'hidden': - hidden = attributes.getValue(attribute) - if hidden.lower() in ('1', 'y', 'yes', 't', 'true', 'on'): - sitem[2]['hidden'] = True - self.servers.append(sitem) - elif name == 'active': - for attribute in attributes.getNames(): - if attribute == 'port': - port = attributes.getValue(attribute) - # we received the jid last time, so we now assign the port - # number to the last jid in the list - self.servers[-1][1] = port - elif name == 'digest': - algo, digest = None, None - for attribute in attributes.getNames(): - if attribute == 'algo': - algo = attributes.getValue(attribute) - elif attribute == 'value': - digest = attributes.getValue(attribute) - hd = HashDigest(algo, digest) - self.servers[-1][2]['digest'][hd.algo] = hd + def startElement(self, name, attributes): + if name == 'item': + # we will get the port next time so we just set it 0 here + sitem = [None, 0, {}] + sitem[2]['digest'] = {} + sitem[2]['hidden'] = False + for attribute in attributes.getNames(): + if attribute == 'jid': + jid = attributes.getValue(attribute) + sitem[0] = jid + elif attribute == 'hidden': + hidden = attributes.getValue(attribute) + if hidden.lower() in ('1', 'y', 'yes', 't', 'true', 'on'): + sitem[2]['hidden'] = True + self.servers.append(sitem) + elif name == 'active': + for attribute in attributes.getNames(): + if attribute == 'port': + port = attributes.getValue(attribute) + # we received the jid last time, so we now assign the port + # number to the last jid in the list + self.servers[-1][1] = port + elif name == 'digest': + algo, digest = None, None + for attribute in attributes.getNames(): + if attribute == 'algo': + algo = attributes.getValue(attribute) + elif attribute == 'value': + digest = attributes.getValue(attribute) + hd = HashDigest(algo, digest) + self.servers[-1][2]['digest'][hd.algo] = hd - def endElement(self, name): - pass + def endElement(self, name): + pass def parse_server_xml(path_to_file): - try: - handler = ServersXMLHandler() - xml.sax.parse(path_to_file, handler) - return handler.servers - # handle exception if unable to open file - except IOError, message: - print >> sys.stderr, _('Error reading file:'), message - # handle exception parsing file - except xml.sax.SAXParseException, message: - print >> sys.stderr, _('Error parsing file:'), message + try: + handler = ServersXMLHandler() + xml.sax.parse(path_to_file, handler) + return handler.servers + # handle exception if unable to open file + except IOError, message: + print >> sys.stderr, _('Error reading file:'), message + # handle exception parsing file + except xml.sax.SAXParseException, message: + print >> sys.stderr, _('Error parsing file:'), message def set_unset_urgency_hint(window, unread_messages_no): - """ - Sets/unset urgency hint in window argument depending if we have unread - messages or not - """ - if gajim.config.get('use_urgency_hint'): - if unread_messages_no > 0: - window.props.urgency_hint = True - else: - window.props.urgency_hint = False + """ + Sets/unset urgency hint in window argument depending if we have unread + messages or not + """ + if gajim.config.get('use_urgency_hint'): + if unread_messages_no > 0: + window.props.urgency_hint = True + else: + window.props.urgency_hint = False def get_abspath_for_script(scriptname, want_type = False): - """ - Check if we are svn or normal user and return abspath to asked script if - want_type is True we return 'svn' or 'install' - """ - if os.path.isdir('.svn'): # we are svn user - type_ = 'svn' - cwd = os.getcwd() # it's always ending with src + """ + Check if we are svn or normal user and return abspath to asked script if + want_type is True we return 'svn' or 'install' + """ + if os.path.isdir('.svn'): # we are svn user + type_ = 'svn' + cwd = os.getcwd() # it's always ending with src - if scriptname == 'gajim-remote': - path_to_script = cwd + '/gajim-remote.py' + if scriptname == 'gajim-remote': + path_to_script = cwd + '/gajim-remote.py' - elif scriptname == 'gajim': - script = '#!/bin/sh\n' # the script we may create - script += 'cd %s' % cwd - path_to_script = cwd + '/../scripts/gajim_sm_script' + elif scriptname == 'gajim': + script = '#!/bin/sh\n' # the script we may create + script += 'cd %s' % cwd + path_to_script = cwd + '/../scripts/gajim_sm_script' - try: - if os.path.exists(path_to_script): - os.remove(path_to_script) + try: + if os.path.exists(path_to_script): + os.remove(path_to_script) - f = open(path_to_script, 'w') - script += '\nexec python -OOt gajim.py $0 $@\n' - f.write(script) - f.close() - os.chmod(path_to_script, 0700) - except OSError: # do not traceback (could be a permission problem) - #we talk about a file here - s = _('Could not write to %s. Session Management support will not work') % path_to_script - print >> sys.stderr, s + f = open(path_to_script, 'w') + script += '\nexec python -OOt gajim.py $0 $@\n' + f.write(script) + f.close() + os.chmod(path_to_script, 0700) + except OSError: # do not traceback (could be a permission problem) + #we talk about a file here + s = _('Could not write to %s. Session Management support will not work') % path_to_script + print >> sys.stderr, s - else: # normal user (not svn user) - type_ = 'install' - # always make it like '/usr/local/bin/gajim' - path_to_script = helpers.is_in_path(scriptname, True) + else: # normal user (not svn user) + type_ = 'install' + # always make it like '/usr/local/bin/gajim' + path_to_script = helpers.is_in_path(scriptname, True) - if want_type: - return path_to_script, type_ - else: - return path_to_script + if want_type: + return path_to_script, type_ + else: + return path_to_script def get_pixbuf_from_data(file_data, want_type = False): - """ - Get image data and returns gtk.gdk.Pixbuf if want_type is True it also - returns 'jpeg', 'png' etc - """ - pixbufloader = gtk.gdk.PixbufLoader() - try: - pixbufloader.write(file_data) - pixbufloader.close() - pixbuf = pixbufloader.get_pixbuf() - except gobject.GError: # 'unknown image format' - pixbufloader.close() - pixbuf = None - if want_type: - return None, None - else: - return None + """ + Get image data and returns gtk.gdk.Pixbuf if want_type is True it also + returns 'jpeg', 'png' etc + """ + pixbufloader = gtk.gdk.PixbufLoader() + try: + pixbufloader.write(file_data) + pixbufloader.close() + pixbuf = pixbufloader.get_pixbuf() + except gobject.GError: # 'unknown image format' + pixbufloader.close() + pixbuf = None + if want_type: + return None, None + else: + return None - if want_type: - typ = pixbufloader.get_format()['name'] - return pixbuf, typ - else: - return pixbuf + if want_type: + typ = pixbufloader.get_format()['name'] + return pixbuf, typ + else: + return pixbuf def get_invisible_cursor(): - pixmap = gtk.gdk.Pixmap(None, 1, 1, 1) - color = gtk.gdk.Color() - cursor = gtk.gdk.Cursor(pixmap, pixmap, color, color, 0, 0) - return cursor + pixmap = gtk.gdk.Pixmap(None, 1, 1, 1) + color = gtk.gdk.Color() + cursor = gtk.gdk.Cursor(pixmap, pixmap, color, color, 0, 0) + return cursor def get_current_desktop(window): - """ - Return the current virtual desktop for given window + """ + Return the current virtual desktop for given window - NOTE: Window is a GDK window. - """ - prop = window.property_get('_NET_CURRENT_DESKTOP') - if prop is None: # it means it's normal window (not root window) - # so we look for it's current virtual desktop in another property - prop = window.property_get('_NET_WM_DESKTOP') + NOTE: Window is a GDK window. + """ + prop = window.property_get('_NET_CURRENT_DESKTOP') + if prop is None: # it means it's normal window (not root window) + # so we look for it's current virtual desktop in another property + prop = window.property_get('_NET_WM_DESKTOP') - if prop is not None: - # f.e. prop is ('CARDINAL', 32, [0]) we want 0 or 1.. from [0] - current_virtual_desktop_no = prop[2][0] - return current_virtual_desktop_no + if prop is not None: + # f.e. prop is ('CARDINAL', 32, [0]) we want 0 or 1.. from [0] + current_virtual_desktop_no = prop[2][0] + return current_virtual_desktop_no def possibly_move_window_in_current_desktop(window): - """ - Moves GTK window to current virtual desktop if it is not in the current - virtual desktop + """ + Moves GTK window to current virtual desktop if it is not in the current + virtual desktop - NOTE: Window is a GDK window. - """ - if os.name == 'nt': - return False + NOTE: Window is a GDK window. + """ + if os.name == 'nt': + return False - root_window = gtk.gdk.screen_get_default().get_root_window() - # current user's vd - current_virtual_desktop_no = get_current_desktop(root_window) + root_window = gtk.gdk.screen_get_default().get_root_window() + # current user's vd + current_virtual_desktop_no = get_current_desktop(root_window) - # vd roster window is in - window_virtual_desktop = get_current_desktop(window.window) + # vd roster window is in + window_virtual_desktop = get_current_desktop(window.window) - # if one of those is None, something went wrong and we cannot know - # VD info, just hide it (default action) and not show it afterwards - if None not in (window_virtual_desktop, current_virtual_desktop_no): - if current_virtual_desktop_no != window_virtual_desktop: - # we are in another VD that the window was - # so show it in current VD - window.present() - return True - return False + # if one of those is None, something went wrong and we cannot know + # VD info, just hide it (default action) and not show it afterwards + if None not in (window_virtual_desktop, current_virtual_desktop_no): + if current_virtual_desktop_no != window_virtual_desktop: + # we are in another VD that the window was + # so show it in current VD + window.present() + return True + return False def file_is_locked(path_to_file): - """ - Return True if file is locked + """ + Return True if file is locked - NOTE: Windows only. - """ - if os.name != 'nt': # just in case - return + NOTE: Windows only. + """ + if os.name != 'nt': # just in case + return - if not HAS_PYWIN32: - return + if not HAS_PYWIN32: + return - secur_att = pywintypes.SECURITY_ATTRIBUTES() - secur_att.Initialize() + secur_att = pywintypes.SECURITY_ATTRIBUTES() + secur_att.Initialize() - try: - # try make a handle for READING the file - hfile = win32file.CreateFile( - path_to_file, # path to file - win32con.GENERIC_READ, # open for reading - 0, # do not share with other proc - secur_att, - win32con.OPEN_EXISTING, # existing file only - win32con.FILE_ATTRIBUTE_NORMAL, # normal file - 0 # no attr. template - ) - except pywintypes.error: - return True - else: # in case all went ok, close file handle (go to hell WinAPI) - hfile.Close() - return False + try: + # try make a handle for READING the file + hfile = win32file.CreateFile( + path_to_file, # path to file + win32con.GENERIC_READ, # open for reading + 0, # do not share with other proc + secur_att, + win32con.OPEN_EXISTING, # existing file only + win32con.FILE_ATTRIBUTE_NORMAL, # normal file + 0 # no attr. template + ) + except pywintypes.error: + return True + else: # in case all went ok, close file handle (go to hell WinAPI) + hfile.Close() + return False def _get_fade_color(treeview, selected, focused): - """ - Get a gdk color that is between foreground and background in 0.3 - 0.7 respectively colors of the cell for the given treeview - """ - style = treeview.style - if selected: - if focused: # is the window focused? - state = gtk.STATE_SELECTED - else: # is it not? NOTE: many gtk themes change bg on this - state = gtk.STATE_ACTIVE - else: - state = gtk.STATE_NORMAL - bg = style.base[state] - fg = style.text[state] + """ + Get a gdk color that is between foreground and background in 0.3 + 0.7 respectively colors of the cell for the given treeview + """ + style = treeview.style + if selected: + if focused: # is the window focused? + state = gtk.STATE_SELECTED + else: # is it not? NOTE: many gtk themes change bg on this + state = gtk.STATE_ACTIVE + else: + state = gtk.STATE_NORMAL + bg = style.base[state] + fg = style.text[state] - p = 0.3 # background - q = 0.7 # foreground # p + q should do 1.0 - return gtk.gdk.Color(int(bg.red*p + fg.red*q), - int(bg.green*p + fg.green*q), - int(bg.blue*p + fg.blue*q)) + p = 0.3 # background + q = 0.7 # foreground # p + q should do 1.0 + return gtk.gdk.Color(int(bg.red*p + fg.red*q), + int(bg.green*p + fg.green*q), + int(bg.blue*p + fg.blue*q)) def get_scaled_pixbuf(pixbuf, kind): - """ - Return scaled pixbuf, keeping ratio etc or None kind is either "chat", - "roster", "notification", "tooltip", "vcard" - """ - # resize to a width / height for the avatar not to have distortion - # (keep aspect ratio) - width = gajim.config.get(kind + '_avatar_width') - height = gajim.config.get(kind + '_avatar_height') - if width < 1 or height < 1: - return None + """ + Return scaled pixbuf, keeping ratio etc or None kind is either "chat", + "roster", "notification", "tooltip", "vcard" + """ + # resize to a width / height for the avatar not to have distortion + # (keep aspect ratio) + width = gajim.config.get(kind + '_avatar_width') + height = gajim.config.get(kind + '_avatar_height') + if width < 1 or height < 1: + return None - # Pixbuf size - pix_width = pixbuf.get_width() - pix_height = pixbuf.get_height() - # don't make avatars bigger than they are - if pix_width < width and pix_height < height: - return pixbuf # we don't want to make avatar bigger + # Pixbuf size + pix_width = pixbuf.get_width() + pix_height = pixbuf.get_height() + # don't make avatars bigger than they are + if pix_width < width and pix_height < height: + return pixbuf # we don't want to make avatar bigger - ratio = float(pix_width) / float(pix_height) - if ratio > 1: - w = width - h = int(w / ratio) - else: - h = height - w = int(h * ratio) - scaled_buf = pixbuf.scale_simple(w, h, gtk.gdk.INTERP_HYPER) - return scaled_buf + ratio = float(pix_width) / float(pix_height) + if ratio > 1: + w = width + h = int(w / ratio) + else: + h = height + w = int(h * ratio) + scaled_buf = pixbuf.scale_simple(w, h, gtk.gdk.INTERP_HYPER) + return scaled_buf def get_avatar_pixbuf_from_cache(fjid, is_fake_jid = False, use_local = True): - """ - Check if jid has cached avatar and if that avatar is valid image (can be - shown) + """ + Check if jid has cached avatar and if that avatar is valid image (can be + shown) - Returns None if there is no image in vcard/ - Returns 'ask' if cached vcard should not be used (user changed his vcard, so - we have new sha) or if we don't have the vcard - """ - jid, nick = gajim.get_room_and_nick_from_fjid(fjid) - if gajim.config.get('hide_avatar_of_transport') and\ - gajim.jid_is_transport(jid): - # don't show avatar for the transport itself - return None + Returns None if there is no image in vcard/ + Returns 'ask' if cached vcard should not be used (user changed his vcard, so + we have new sha) or if we don't have the vcard + """ + jid, nick = gajim.get_room_and_nick_from_fjid(fjid) + if gajim.config.get('hide_avatar_of_transport') and\ + gajim.jid_is_transport(jid): + # don't show avatar for the transport itself + return None - puny_jid = helpers.sanitize_filename(jid) - if is_fake_jid: - puny_nick = helpers.sanitize_filename(nick) - path = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) - local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid, - puny_nick) + '_local' - else: - path = os.path.join(gajim.VCARD_PATH, puny_jid) - local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid) + \ - '_local' - if use_local: - for extension in ('.png', '.jpeg'): - local_avatar_path = local_avatar_basepath + extension - if os.path.isfile(local_avatar_path): - avatar_file = open(local_avatar_path, 'rb') - avatar_data = avatar_file.read() - avatar_file.close() - return get_pixbuf_from_data(avatar_data) + puny_jid = helpers.sanitize_filename(jid) + if is_fake_jid: + puny_nick = helpers.sanitize_filename(nick) + path = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) + local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid, + puny_nick) + '_local' + else: + path = os.path.join(gajim.VCARD_PATH, puny_jid) + local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid) + \ + '_local' + if use_local: + for extension in ('.png', '.jpeg'): + local_avatar_path = local_avatar_basepath + extension + if os.path.isfile(local_avatar_path): + avatar_file = open(local_avatar_path, 'rb') + avatar_data = avatar_file.read() + avatar_file.close() + return get_pixbuf_from_data(avatar_data) - if not os.path.isfile(path): - return 'ask' + if not os.path.isfile(path): + return 'ask' - vcard_dict = gajim.connections.values()[0].get_cached_vcard(fjid, - is_fake_jid) - if not vcard_dict: # This can happen if cached vcard is too old - return 'ask' - if 'PHOTO' not in vcard_dict: - return None - pixbuf = vcard.get_avatar_pixbuf_encoded_mime(vcard_dict['PHOTO'])[0] - return pixbuf + vcard_dict = gajim.connections.values()[0].get_cached_vcard(fjid, + is_fake_jid) + if not vcard_dict: # This can happen if cached vcard is too old + return 'ask' + if 'PHOTO' not in vcard_dict: + return None + pixbuf = vcard.get_avatar_pixbuf_encoded_mime(vcard_dict['PHOTO'])[0] + return pixbuf def make_gtk_month_python_month(month): - """ - GTK starts counting months from 0, so January is 0 but Python's time start - from 1, so align to Python + """ + GTK starts counting months from 0, so January is 0 but Python's time start + from 1, so align to Python - NOTE: Month MUST be an integer. - """ - return month + 1 + NOTE: Month MUST be an integer. + """ + return month + 1 def make_python_month_gtk_month(month): - return month - 1 + return month - 1 def make_color_string(color): - """ - Create #aabbcc color string from gtk color - """ - col = '#' - for i in ('red', 'green', 'blue'): - h = hex(getattr(color, i) / (16*16)).split('x')[1] - if len(h) == 1: - h = '0' + h - col += h - return col + """ + Create #aabbcc color string from gtk color + """ + col = '#' + for i in ('red', 'green', 'blue'): + h = hex(getattr(color, i) / (16*16)).split('x')[1] + if len(h) == 1: + h = '0' + h + col += h + return col def make_pixbuf_grayscale(pixbuf): - pixbuf2 = pixbuf.copy() - pixbuf.saturate_and_pixelate(pixbuf2, 0.0, False) - return pixbuf2 + pixbuf2 = pixbuf.copy() + pixbuf.saturate_and_pixelate(pixbuf2, 0.0, False) + return pixbuf2 def get_path_to_generic_or_avatar(generic, jid = None, suffix = None): - """ - Choose between avatar image and default image + """ + Choose between avatar image and default image - Returns full path to the avatar image if it exists, otherwise returns full - path to the image. generic must be with extension and suffix without - """ - if jid: - # we want an avatar - puny_jid = helpers.sanitize_filename(jid) - path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) + suffix - path_to_local_file = path_to_file + '_local' - for extension in ('.png', '.jpeg'): - path_to_local_file_full = path_to_local_file + extension - if os.path.exists(path_to_local_file_full): - return path_to_local_file_full - for extension in ('.png', '.jpeg'): - path_to_file_full = path_to_file + extension - if os.path.exists(path_to_file_full): - return path_to_file_full - return os.path.abspath(generic) + Returns full path to the avatar image if it exists, otherwise returns full + path to the image. generic must be with extension and suffix without + """ + if jid: + # we want an avatar + puny_jid = helpers.sanitize_filename(jid) + path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) + suffix + path_to_local_file = path_to_file + '_local' + for extension in ('.png', '.jpeg'): + path_to_local_file_full = path_to_local_file + extension + if os.path.exists(path_to_local_file_full): + return path_to_local_file_full + for extension in ('.png', '.jpeg'): + path_to_file_full = path_to_file + extension + if os.path.exists(path_to_file_full): + return path_to_file_full + return os.path.abspath(generic) def decode_filechooser_file_paths(file_paths): - """ - Decode as UTF-8 under Windows and ask sys.getfilesystemencoding() in POSIX - file_paths MUST be LIST - """ - file_paths_list = list() + """ + Decode as UTF-8 under Windows and ask sys.getfilesystemencoding() in POSIX + file_paths MUST be LIST + """ + file_paths_list = list() - if os.name == 'nt': # decode as UTF-8 under Windows - for file_path in file_paths: - file_path = file_path.decode('utf8') - file_paths_list.append(file_path) - else: - for file_path in file_paths: - try: - file_path = file_path.decode(sys.getfilesystemencoding()) - except Exception: - try: - file_path = file_path.decode('utf-8') - except Exception: - pass - file_paths_list.append(file_path) + if os.name == 'nt': # decode as UTF-8 under Windows + for file_path in file_paths: + file_path = file_path.decode('utf8') + file_paths_list.append(file_path) + else: + for file_path in file_paths: + try: + file_path = file_path.decode(sys.getfilesystemencoding()) + except Exception: + try: + file_path = file_path.decode('utf-8') + except Exception: + pass + file_paths_list.append(file_path) - return file_paths_list + return file_paths_list def possibly_set_gajim_as_xmpp_handler(): - """ - Register (by default only the first time) 'xmmp:' to Gajim - """ - path_to_dot_kde = os.path.expanduser('~/.kde') - if os.path.exists(path_to_dot_kde): - path_to_kde_file = os.path.join(path_to_dot_kde, - 'share/services/xmpp.protocol') - else: - path_to_kde_file = None + """ + Register (by default only the first time) 'xmmp:' to Gajim + """ + path_to_dot_kde = os.path.expanduser('~/.kde') + if os.path.exists(path_to_dot_kde): + path_to_kde_file = os.path.join(path_to_dot_kde, + 'share/services/xmpp.protocol') + else: + path_to_kde_file = None - def set_gajim_as_xmpp_handler(is_checked=None): - if is_checked is not None: - # come from confirmation dialog - gajim.config.set('check_if_gajim_is_default', is_checked) - path_to_gajim_script, typ = get_abspath_for_script('gajim-remote', True) - if path_to_gajim_script: - if typ == 'svn': - command = path_to_gajim_script + ' handle_uri %s' - else: # 'installed' - command = 'gajim-remote handle_uri %s' + def set_gajim_as_xmpp_handler(is_checked=None): + if is_checked is not None: + # come from confirmation dialog + gajim.config.set('check_if_gajim_is_default', is_checked) + path_to_gajim_script, typ = get_abspath_for_script('gajim-remote', True) + if path_to_gajim_script: + if typ == 'svn': + command = path_to_gajim_script + ' handle_uri %s' + else: # 'installed' + command = 'gajim-remote handle_uri %s' - # setting for GNOME/Gconf - client.set_bool('/desktop/gnome/url-handlers/xmpp/enabled', True) - client.set_string('/desktop/gnome/url-handlers/xmpp/command', command) - client.set_bool('/desktop/gnome/url-handlers/xmpp/needs_terminal', False) + # setting for GNOME/Gconf + client.set_bool('/desktop/gnome/url-handlers/xmpp/enabled', True) + client.set_string('/desktop/gnome/url-handlers/xmpp/command', command) + client.set_bool('/desktop/gnome/url-handlers/xmpp/needs_terminal', False) - # setting for KDE - if path_to_kde_file is not None: # user has run kde at least once - try: - f = open(path_to_kde_file, 'a') - f.write('''\ + # setting for KDE + if path_to_kde_file is not None: # user has run kde at least once + try: + f = open(path_to_kde_file, 'a') + f.write('''\ [Protocol] exec=%s "%%u" protocol=xmpp @@ -740,360 +740,358 @@ deleting=false icon=gajim Description=xmpp ''' % command) - f.close() - except IOError: - log.debug("I/O Error writing settings to %s", repr(path_to_kde_file), exc_info=True) - else: # no gajim remote, stop ask user everytime - gajim.config.set('check_if_gajim_is_default', False) + f.close() + except IOError: + log.debug("I/O Error writing settings to %s", repr(path_to_kde_file), exc_info=True) + else: # no gajim remote, stop ask user everytime + gajim.config.set('check_if_gajim_is_default', False) - try: - import gconf - # in try because daemon may not be there - client = gconf.client_get_default() - except Exception: - return + try: + import gconf + # in try because daemon may not be there + client = gconf.client_get_default() + except Exception: + return - old_command = client.get_string('/desktop/gnome/url-handlers/xmpp/command') - if not old_command or old_command.endswith(' open_chat %s'): - # first time (GNOME/GCONF) or old Gajim version - we_set = True - elif path_to_kde_file is not None and not os.path.exists(path_to_kde_file): - # only the first time (KDE) - we_set = True - else: - we_set = False + old_command = client.get_string('/desktop/gnome/url-handlers/xmpp/command') + if not old_command or old_command.endswith(' open_chat %s'): + # first time (GNOME/GCONF) or old Gajim version + we_set = True + elif path_to_kde_file is not None and not os.path.exists(path_to_kde_file): + # only the first time (KDE) + we_set = True + else: + we_set = False - if we_set: - set_gajim_as_xmpp_handler() - elif old_command and not old_command.endswith(' handle_uri %s'): - # xmpp: is currently handled by another program, so ask the user - pritext = _('Gajim is not the default Jabber client') - sectext = _('Would you like to make Gajim the default Jabber client?') - checktext = _('Always check to see if Gajim is the default Jabber client ' - 'on startup') - def on_cancel(checked): - gajim.config.set('check_if_gajim_is_default', checked) - dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, checktext, - set_gajim_as_xmpp_handler, on_cancel) - if gajim.config.get('check_if_gajim_is_default'): - dlg.checkbutton.set_active(True) + if we_set: + set_gajim_as_xmpp_handler() + elif old_command and not old_command.endswith(' handle_uri %s'): + # xmpp: is currently handled by another program, so ask the user + pritext = _('Gajim is not the default Jabber client') + sectext = _('Would you like to make Gajim the default Jabber client?') + checktext = _('Always check to see if Gajim is the default Jabber client ' + 'on startup') + def on_cancel(checked): + gajim.config.set('check_if_gajim_is_default', checked) + dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, checktext, + set_gajim_as_xmpp_handler, on_cancel) + if gajim.config.get('check_if_gajim_is_default'): + dlg.checkbutton.set_active(True) def escape_underscore(s): - """ - Escape underlines to prevent them from being interpreted as keyboard - accelerators - """ - return s.replace('_', '__') + """ + Escape underlines to prevent them from being interpreted as keyboard + accelerators + """ + return s.replace('_', '__') def get_state_image_from_file_path_show(file_path, show): - state_file = show.replace(' ', '_') - files = [] - files.append(os.path.join(file_path, state_file + '.png')) - files.append(os.path.join(file_path, state_file + '.gif')) - image = gtk.Image() - image.set_from_pixbuf(None) - for file_ in files: - if os.path.exists(file_): - image.set_from_file(file_) - break + state_file = show.replace(' ', '_') + files = [] + files.append(os.path.join(file_path, state_file + '.png')) + files.append(os.path.join(file_path, state_file + '.gif')) + image = gtk.Image() + image.set_from_pixbuf(None) + for file_ in files: + if os.path.exists(file_): + image.set_from_file(file_) + break - return image + return image def get_possible_button_event(event): - """ - Mouse or keyboard caused the event? - """ - if event.type == gtk.gdk.KEY_PRESS: - return 0 # no event.button so pass 0 - # BUTTON_PRESS event, so pass event.button - return event.button + """ + Mouse or keyboard caused the event? + """ + if event.type == gtk.gdk.KEY_PRESS: + return 0 # no event.button so pass 0 + # BUTTON_PRESS event, so pass event.button + return event.button def destroy_widget(widget): - widget.destroy() + widget.destroy() def on_avatar_save_as_menuitem_activate(widget, jid, account, default_name = ''): - def on_continue(response, file_path): - if response < 0: - return - # Get pixbuf - pixbuf = None - is_fake = False - if account and gajim.contacts.is_pm_from_jid(account, jid): - is_fake = True - pixbuf = get_avatar_pixbuf_from_cache(jid, is_fake, False) - ext = file_path.split('.')[-1] - type_ = '' - if not ext: - # Silently save as Jpeg image - file_path += '.jpeg' - type_ = 'jpeg' - elif ext == 'jpg': - type_ = 'jpeg' - else: - type_ = ext + def on_continue(response, file_path): + if response < 0: + return + # Get pixbuf + pixbuf = None + is_fake = False + if account and gajim.contacts.is_pm_from_jid(account, jid): + is_fake = True + pixbuf = get_avatar_pixbuf_from_cache(jid, is_fake, False) + ext = file_path.split('.')[-1] + type_ = '' + if not ext: + # Silently save as Jpeg image + file_path += '.jpeg' + type_ = 'jpeg' + elif ext == 'jpg': + type_ = 'jpeg' + else: + type_ = ext - # Save image - try: - pixbuf.save(file_path, type_) - except Exception: - if os.path.exists(file_path): - os.remove(file_path) - new_file_path = '.'.join(file_path.split('.')[:-1]) + '.jpeg' - def on_ok(file_path, pixbuf): - pixbuf.save(file_path, 'jpeg') - dialogs.ConfirmationDialog(_('Extension not supported'), - _('Image cannot be saved in %(type)s format. Save as %(new_filename)s?') % {'type': type_, 'new_filename': new_file_path}, - on_response_ok = (on_ok, new_file_path, pixbuf)) - else: - dialog.destroy() + # Save image + try: + pixbuf.save(file_path, type_) + except Exception: + if os.path.exists(file_path): + os.remove(file_path) + new_file_path = '.'.join(file_path.split('.')[:-1]) + '.jpeg' + def on_ok(file_path, pixbuf): + pixbuf.save(file_path, 'jpeg') + dialogs.ConfirmationDialog(_('Extension not supported'), + _('Image cannot be saved in %(type)s format. Save as %(new_filename)s?') % {'type': type_, 'new_filename': new_file_path}, + on_response_ok = (on_ok, new_file_path, pixbuf)) + else: + dialog.destroy() - def on_ok(widget): - file_path = dialog.get_filename() - file_path = decode_filechooser_file_paths((file_path,))[0] - if os.path.exists(file_path): - # check if we have write permissions - if not os.access(file_path, os.W_OK): - file_name = os.path.basename(file_path) - dialogs.ErrorDialog(_('Cannot overwrite existing file "%s"' % - file_name), - _('A file with this name already exists and you do not have ' - 'permission to overwrite it.')) - return - dialog2 = dialogs.FTOverwriteConfirmationDialog( - _('This file already exists'), _('What do you want to do?'), - propose_resume=False, on_response=(on_continue, file_path)) - dialog2.set_transient_for(dialog) - dialog2.set_destroy_with_parent(True) - else: - dirname = os.path.dirname(file_path) - if not os.access(dirname, os.W_OK): - dialogs.ErrorDialog(_('Directory "%s" is not writable') % \ - dirname, _('You do not have permission to create files in this' - ' directory.')) - return + def on_ok(widget): + file_path = dialog.get_filename() + file_path = decode_filechooser_file_paths((file_path,))[0] + if os.path.exists(file_path): + # check if we have write permissions + if not os.access(file_path, os.W_OK): + file_name = os.path.basename(file_path) + dialogs.ErrorDialog(_('Cannot overwrite existing file "%s"' % + file_name), + _('A file with this name already exists and you do not have ' + 'permission to overwrite it.')) + return + dialog2 = dialogs.FTOverwriteConfirmationDialog( + _('This file already exists'), _('What do you want to do?'), + propose_resume=False, on_response=(on_continue, file_path)) + dialog2.set_transient_for(dialog) + dialog2.set_destroy_with_parent(True) + else: + dirname = os.path.dirname(file_path) + if not os.access(dirname, os.W_OK): + dialogs.ErrorDialog(_('Directory "%s" is not writable') % \ + dirname, _('You do not have permission to create files in this' + ' directory.')) + return - on_continue(0, file_path) + on_continue(0, file_path) - def on_cancel(widget): - dialog.destroy() + def on_cancel(widget): + dialog.destroy() - dialog = dialogs.FileChooserDialog(title_text=_('Save Image as...'), - action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL, - gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK), - default_response=gtk.RESPONSE_OK, - current_folder=gajim.config.get('last_save_dir'), on_response_ok=on_ok, - on_response_cancel=on_cancel) + dialog = dialogs.FileChooserDialog(title_text=_('Save Image as...'), + action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL, + gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK), + default_response=gtk.RESPONSE_OK, + current_folder=gajim.config.get('last_save_dir'), on_response_ok=on_ok, + on_response_cancel=on_cancel) - dialog.set_current_name(default_name) - dialog.connect('delete-event', lambda widget, event: - on_cancel(widget)) + dialog.set_current_name(default_name) + dialog.connect('delete-event', lambda widget, event: + on_cancel(widget)) def on_bm_header_changed_state(widget, event): - widget.set_state(gtk.STATE_NORMAL) #do not allow selected_state + widget.set_state(gtk.STATE_NORMAL) #do not allow selected_state def create_combobox(value_list, selected_value = None): - """ - Value_list is [(label1, value1)] - """ - liststore = gtk.ListStore(str, str) - combobox = gtk.ComboBox(liststore) - cell = gtk.CellRendererText() - combobox.pack_start(cell, True) - combobox.add_attribute(cell, 'text', 0) - i = -1 - for value in value_list: - liststore.append(value) - if selected_value == value[1]: - i = value_list.index(value) - if i > -1: - combobox.set_active(i) - combobox.show_all() - return combobox + """ + Value_list is [(label1, value1)] + """ + liststore = gtk.ListStore(str, str) + combobox = gtk.ComboBox(liststore) + cell = gtk.CellRendererText() + combobox.pack_start(cell, True) + combobox.add_attribute(cell, 'text', 0) + i = -1 + for value in value_list: + liststore.append(value) + if selected_value == value[1]: + i = value_list.index(value) + if i > -1: + combobox.set_active(i) + combobox.show_all() + return combobox def create_list_multi(value_list, selected_values=None): - """ - Value_list is [(label1, value1)] - """ - liststore = gtk.ListStore(str, str) - treeview = gtk.TreeView(liststore) - treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) - treeview.set_headers_visible(False) - col = gtk.TreeViewColumn() - treeview.append_column(col) - cell = gtk.CellRendererText() - col.pack_start(cell, True) - col.set_attributes(cell, text=0) - for value in value_list: - iter = liststore.append(value) - if value[1] in selected_values: - treeview.get_selection().select_iter(iter) - treeview.show_all() - return treeview + """ + Value_list is [(label1, value1)] + """ + liststore = gtk.ListStore(str, str) + treeview = gtk.TreeView(liststore) + treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) + treeview.set_headers_visible(False) + col = gtk.TreeViewColumn() + treeview.append_column(col) + cell = gtk.CellRendererText() + col.pack_start(cell, True) + col.set_attributes(cell, text=0) + for value in value_list: + iter = liststore.append(value) + if value[1] in selected_values: + treeview.get_selection().select_iter(iter) + treeview.show_all() + return treeview def load_iconset(path, pixbuf2=None, transport=False): - """ - Load full iconset from the given path, and add pixbuf2 on top left of each - static images - """ - path += '/' - if transport: - list_ = ('online', 'chat', 'away', 'xa', 'dnd', 'offline', - 'not in roster') - else: - list_ = ('connecting', 'online', 'chat', 'away', 'xa', 'dnd', - 'invisible', 'offline', 'error', 'requested', 'event', 'opened', - 'closed', 'not in roster', 'muc_active', 'muc_inactive') - if pixbuf2: - list_ = ('connecting', 'online', 'chat', 'away', 'xa', 'dnd', - 'offline', 'error', 'requested', 'event', 'not in roster') - return _load_icon_list(list_, path, pixbuf2) + """ + Load full iconset from the given path, and add pixbuf2 on top left of each + static images + """ + path += '/' + if transport: + list_ = ('online', 'chat', 'away', 'xa', 'dnd', 'offline', + 'not in roster') + else: + list_ = ('connecting', 'online', 'chat', 'away', 'xa', 'dnd', + 'invisible', 'offline', 'error', 'requested', 'event', 'opened', + 'closed', 'not in roster', 'muc_active', 'muc_inactive') + if pixbuf2: + list_ = ('connecting', 'online', 'chat', 'away', 'xa', 'dnd', + 'offline', 'error', 'requested', 'event', 'not in roster') + return _load_icon_list(list_, path, pixbuf2) def load_icon(icon_name): - """ - Load an icon from the iconset in 16x16 - """ - iconset = gajim.config.get('iconset') - path = os.path.join(helpers.get_iconset_path(iconset), '16x16', '') - icon_list = _load_icon_list([icon_name], path) - return icon_list[icon_name] + """ + Load an icon from the iconset in 16x16 + """ + iconset = gajim.config.get('iconset') + path = os.path.join(helpers.get_iconset_path(iconset), '16x16', '') + icon_list = _load_icon_list([icon_name], path) + return icon_list[icon_name] def load_mood_icon(icon_name): - """ - Load an icon from the mood iconset in 16x16 - """ - iconset = gajim.config.get('mood_iconset') - path = os.path.join(helpers.get_mood_iconset_path(iconset), '') - icon_list = _load_icon_list([icon_name], path) - return icon_list[icon_name] + """ + Load an icon from the mood iconset in 16x16 + """ + iconset = gajim.config.get('mood_iconset') + path = os.path.join(helpers.get_mood_iconset_path(iconset), '') + icon_list = _load_icon_list([icon_name], path) + return icon_list[icon_name] def load_activity_icon(category, activity = None): - """ - Load an icon from the activity iconset in 16x16 - """ - iconset = gajim.config.get('activity_iconset') - path = os.path.join(helpers.get_activity_iconset_path(iconset), - category, '') - if activity is None: - activity = 'category' - icon_list = _load_icon_list([activity], path) - return icon_list[activity] + """ + Load an icon from the activity iconset in 16x16 + """ + iconset = gajim.config.get('activity_iconset') + path = os.path.join(helpers.get_activity_iconset_path(iconset), + category, '') + if activity is None: + activity = 'category' + icon_list = _load_icon_list([activity], path) + return icon_list[activity] def load_icons_meta(): - """ - Load and return - AND + small icons to put on top left of an icon for meta - contacts - """ - iconset = gajim.config.get('iconset') - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - # try to find opened_meta.png file, else opened.png else nopixbuf merge - path_opened = os.path.join(path, 'opened_meta.png') - if not os.path.isfile(path_opened): - path_opened = os.path.join(path, 'opened.png') - if os.path.isfile(path_opened): - pixo = gtk.gdk.pixbuf_new_from_file(path_opened) - else: - pixo = None - # Same thing for closed - path_closed = os.path.join(path, 'opened_meta.png') - if not os.path.isfile(path_closed): - path_closed = os.path.join(path, 'closed.png') - if os.path.isfile(path_closed): - pixc = gtk.gdk.pixbuf_new_from_file(path_closed) - else: - pixc = None - return pixo, pixc + """ + Load and return - AND + small icons to put on top left of an icon for meta + contacts + """ + iconset = gajim.config.get('iconset') + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + # try to find opened_meta.png file, else opened.png else nopixbuf merge + path_opened = os.path.join(path, 'opened_meta.png') + if not os.path.isfile(path_opened): + path_opened = os.path.join(path, 'opened.png') + if os.path.isfile(path_opened): + pixo = gtk.gdk.pixbuf_new_from_file(path_opened) + else: + pixo = None + # Same thing for closed + path_closed = os.path.join(path, 'opened_meta.png') + if not os.path.isfile(path_closed): + path_closed = os.path.join(path, 'closed.png') + if os.path.isfile(path_closed): + pixc = gtk.gdk.pixbuf_new_from_file(path_closed) + else: + pixc = None + return pixo, pixc def _load_icon_list(icons_list, path, pixbuf2 = None): - """ - Load icons in icons_list from the given path, and add pixbuf2 on top left of - each static images - """ - imgs = {} - for icon in icons_list: - # try to open a pixfile with the correct method - icon_file = icon.replace(' ', '_') - files = [] - files.append(path + icon_file + '.gif') - files.append(path + icon_file + '.png') - image = gtk.Image() - image.show() - imgs[icon] = image - for file_ in files: # loop seeking for either gif or png - if os.path.exists(file_): - image.set_from_file(file_) - if pixbuf2 and image.get_storage_type() == gtk.IMAGE_PIXBUF: - # add pixbuf2 on top-left corner of image - pixbuf1 = image.get_pixbuf() - pixbuf2.composite(pixbuf1, 0, 0, - pixbuf2.get_property('width'), - pixbuf2.get_property('height'), 0, 0, 1.0, 1.0, - gtk.gdk.INTERP_NEAREST, 255) - image.set_from_pixbuf(pixbuf1) - break - return imgs + """ + Load icons in icons_list from the given path, and add pixbuf2 on top left of + each static images + """ + imgs = {} + for icon in icons_list: + # try to open a pixfile with the correct method + icon_file = icon.replace(' ', '_') + files = [] + files.append(path + icon_file + '.gif') + files.append(path + icon_file + '.png') + image = gtk.Image() + image.show() + imgs[icon] = image + for file_ in files: # loop seeking for either gif or png + if os.path.exists(file_): + image.set_from_file(file_) + if pixbuf2 and image.get_storage_type() == gtk.IMAGE_PIXBUF: + # add pixbuf2 on top-left corner of image + pixbuf1 = image.get_pixbuf() + pixbuf2.composite(pixbuf1, 0, 0, + pixbuf2.get_property('width'), + pixbuf2.get_property('height'), 0, 0, 1.0, 1.0, + gtk.gdk.INTERP_NEAREST, 255) + image.set_from_pixbuf(pixbuf1) + break + return imgs def make_jabber_state_images(): - """ - Initialize jabber_state_images dictionary - """ - iconset = gajim.config.get('iconset') - if iconset: - if helpers.get_iconset_path(iconset): - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - if not os.path.exists(path): - iconset = gajim.config.DEFAULT_ICONSET - gajim.config.set('iconset', iconset) - else: - iconset = gajim.config.DEFAULT_ICONSET - gajim.config.set('iconset', iconset) - else: - iconset = gajim.config.DEFAULT_ICONSET - gajim.config.set('iconset', iconset) + """ + Initialize jabber_state_images dictionary + """ + iconset = gajim.config.get('iconset') + if iconset: + if helpers.get_iconset_path(iconset): + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + if not os.path.exists(path): + iconset = gajim.config.DEFAULT_ICONSET + gajim.config.set('iconset', iconset) + else: + iconset = gajim.config.DEFAULT_ICONSET + gajim.config.set('iconset', iconset) + else: + iconset = gajim.config.DEFAULT_ICONSET + gajim.config.set('iconset', iconset) - path = os.path.join(helpers.get_iconset_path(iconset), '32x32') - gajim.interface.jabber_state_images['32'] = load_iconset(path) + path = os.path.join(helpers.get_iconset_path(iconset), '32x32') + gajim.interface.jabber_state_images['32'] = load_iconset(path) - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - gajim.interface.jabber_state_images['16'] = load_iconset(path) + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + gajim.interface.jabber_state_images['16'] = load_iconset(path) - pixo, pixc = load_icons_meta() - gajim.interface.jabber_state_images['opened'] = load_iconset(path, pixo) - gajim.interface.jabber_state_images['closed'] = load_iconset(path, pixc) + pixo, pixc = load_icons_meta() + gajim.interface.jabber_state_images['opened'] = load_iconset(path, pixo) + gajim.interface.jabber_state_images['closed'] = load_iconset(path, pixc) def reload_jabber_state_images(): - make_jabber_state_images() - gajim.interface.roster.update_jabber_state_images() + make_jabber_state_images() + gajim.interface.roster.update_jabber_state_images() def label_set_autowrap(widget): - """ - Make labels automatically re-wrap if their containers are resized. - Accepts label or container widgets - """ - if isinstance (widget, gtk.Container): - children = widget.get_children() - for i in xrange (len (children)): - label_set_autowrap(children[i]) - elif isinstance(widget, gtk.Label): - widget.set_line_wrap(True) - widget.connect_after('size-allocate', __label_size_allocate) + """ + Make labels automatically re-wrap if their containers are resized. + Accepts label or container widgets + """ + if isinstance (widget, gtk.Container): + children = widget.get_children() + for i in xrange (len (children)): + label_set_autowrap(children[i]) + elif isinstance(widget, gtk.Label): + widget.set_line_wrap(True) + widget.connect_after('size-allocate', __label_size_allocate) def __label_size_allocate(widget, allocation): - """ - Callback which re-allocates the size of a label - """ - layout = widget.get_layout() + """ + Callback which re-allocates the size of a label + """ + layout = widget.get_layout() - lw_old, lh_old = layout.get_size() - # fixed width labels - if lw_old/pango.SCALE == allocation.width: - return + lw_old, lh_old = layout.get_size() + # fixed width labels + if lw_old/pango.SCALE == allocation.width: + return - # set wrap width to the pango.Layout of the labels ### - layout.set_width (allocation.width * pango.SCALE) - lw, lh = layout.get_size () + # set wrap width to the pango.Layout of the labels ### + layout.set_width (allocation.width * pango.SCALE) + lw, lh = layout.get_size () - if lh_old != lh: - widget.set_size_request (-1, lh / pango.SCALE) - -# vim: se ts=3: + if lh_old != lh: + widget.set_size_request (-1, lh / pango.SCALE) diff --git a/src/gtkspell.py b/src/gtkspell.py index c5dc62af8..aa1adaec7 100644 --- a/src/gtkspell.py +++ b/src/gtkspell.py @@ -97,4 +97,3 @@ class Spell(object): def get_from_text_view(textview): return Spell(textview, create=False) - diff --git a/src/gui_interface.py b/src/gui_interface.py index e4dc996d1..a9129a113 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -49,8 +49,8 @@ from common import gajim from common import dbus_support if dbus_support.supported: - from music_track_listener import MusicTrackListener - import dbus + from music_track_listener import MusicTrackListener + import dbus import gtkgui_helpers @@ -100,3427 +100,3427 @@ class Interface: ### Methods handling events from connection ################################################################################ - def handle_event_roster(self, account, data): - #('ROSTER', account, array) - # FIXME: Those methods depend to highly on each other - # and the order in which they are called - self.roster.fill_contacts_and_groups_dicts(data, account) - self.roster.add_account_contacts(account) - self.roster.fire_up_unread_messages_events(account) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('Roster', (account, data)) - - def handle_event_warning(self, unused, data): - #('WARNING', account, (title_text, section_text)) - dialogs.WarningDialog(data[0], data[1]) - - def handle_event_error(self, unused, data): - #('ERROR', account, (title_text, section_text)) - dialogs.ErrorDialog(data[0], data[1]) - - def handle_event_information(self, unused, data): - #('INFORMATION', account, (title_text, section_text)) - dialogs.InformationDialog(data[0], data[1]) - - def handle_event_ask_new_nick(self, account, data): - #('ASK_NEW_NICK', account, (room_jid,)) - room_jid = data[0] - title = _('Unable to join group chat') - prompt = _('Your desired nickname in group chat %s is in use or ' - 'registered by another occupant.\nPlease specify another nickname ' - 'below:') % room_jid - check_text = _('Always use this nickname when there is a conflict') - if 'change_nick_dialog' in self.instances: - self.instances['change_nick_dialog'].add_room(account, room_jid, - prompt) - else: - self.instances['change_nick_dialog'] = dialogs.ChangeNickDialog( - account, room_jid, title, prompt) - - def handle_event_http_auth(self, account, data): - #('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg)) - def response(account, iq_obj, answer): - self.dialog.destroy() - gajim.connections[account].build_http_auth_answer(iq_obj, answer) - - def on_yes(is_checked, account, iq_obj): - response(account, iq_obj, 'yes') - - sec_msg = _('Do you accept this request?') - if gajim.get_number_of_connected_accounts() > 1: - sec_msg = _('Do you accept this request on account %s?') % account - if data[4]: - sec_msg = data[4] + '\n' + sec_msg - self.dialog = dialogs.YesNoDialog(_('HTTP (%(method)s) Authorization for ' - '%(url)s (id: %(id)s)') % {'method': data[0], 'url': data[1], - 'id': data[2]}, sec_msg, on_response_yes=(on_yes, account, data[3]), - on_response_no=(response, account, data[3], 'no')) - - def handle_event_error_answer(self, account, array): - #('ERROR_ANSWER', account, (id, jid_from, errmsg, errcode)) - id_, jid_from, errmsg, errcode = array - if unicode(errcode) in ('400', '403', '406') and id_: - # show the error dialog - ft = self.instances['file_transfers'] - sid = id_ - if len(id_) > 3 and id_[2] == '_': - sid = id_[3:] - if sid in ft.files_props['s']: - file_props = ft.files_props['s'][sid] - if unicode(errcode) == '400': - file_props['error'] = -3 - else: - file_props['error'] = -4 - self.handle_event_file_request_error(account, - (jid_from, file_props, errmsg)) - conn = gajim.connections[account] - conn.disconnect_transfer(file_props) - return - elif unicode(errcode) == '404': - conn = gajim.connections[account] - sid = id_ - if len(id_) > 3 and id_[2] == '_': - sid = id_[3:] - if sid in conn.files_props: - file_props = conn.files_props[sid] - self.handle_event_file_send_error(account, - (jid_from, file_props)) - conn.disconnect_transfer(file_props) - return - - ctrl = self.msg_win_mgr.get_control(jid_from, account) - if ctrl and ctrl.type_id == message_control.TYPE_GC: - ctrl.print_conversation('Error %s: %s' % (array[2], array[1])) - - def handle_event_con_type(self, account, con_type): - # ('CON_TYPE', account, con_type) which can be 'ssl', 'tls', 'plain' - gajim.con_types[account] = con_type - self.roster.draw_account(account) - - def handle_event_connection_lost(self, account, array): - # ('CONNECTION_LOST', account, [title, text]) - path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'connection_lost.png') - path = gtkgui_helpers.get_path_to_generic_or_avatar(path) - notify.popup(_('Connection Failed'), account, account, - 'connection_failed', path, array[0], array[1]) - - def unblock_signed_in_notifications(self, account): - gajim.block_signed_in_notifications[account] = False - - def handle_event_status(self, account, show): # OUR status - #('STATUS', account, show) - model = self.roster.status_combobox.get_model() - if show in ('offline', 'error'): - for name in self.instances[account]['online_dialog'].keys(): - # .keys() is needed to not have a dictionary length changed during - # iteration error - self.instances[account]['online_dialog'][name].destroy() - del self.instances[account]['online_dialog'][name] - for request in self.gpg_passphrase.values(): - if request: - request.interrupt() - # .keys() is needed because dict changes during loop - for account in self.pass_dialog.keys(): - self.pass_dialog[account].window.destroy() - if show == 'offline': - # sensitivity for this menuitem - if gajim.get_number_of_connected_accounts() == 0: - model[self.roster.status_message_menuitem_iter][3] = False - gajim.block_signed_in_notifications[account] = True - else: - # 30 seconds after we change our status to sth else than offline - # we stop blocking notifications of any kind - # this prevents from getting the roster items as 'just signed in' - # contacts. 30 seconds should be enough time - gobject.timeout_add_seconds(30, self.unblock_signed_in_notifications, account) - # sensitivity for this menuitem - model[self.roster.status_message_menuitem_iter][3] = True - - # Inform all controls for this account of the connection state change - ctrls = self.msg_win_mgr.get_controls() - if account in self.minimized_controls: - # Can not be the case when we remove account - ctrls += self.minimized_controls[account].values() - for ctrl in ctrls: - if ctrl.account == account: - if show == 'offline' or (show == 'invisible' and \ - gajim.connections[account].is_zeroconf): - ctrl.got_disconnected() - else: - # Other code rejoins all GCs, so we don't do it here - if not ctrl.type_id == message_control.TYPE_GC: - ctrl.got_connected() - if ctrl.parent_win: - ctrl.parent_win.redraw_tab(ctrl) - - self.roster.on_status_changed(account, show) - if account in self.show_vcard_when_connect and show not in ('offline', - 'error'): - self.edit_own_details(account) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('AccountPresence', (show, account)) - - def handle_event_new_jid(self, account, data): - #('NEW_JID', account, (old_jid, new_jid)) - """ - This event is raised when our JID changed (most probably because we use - anonymous account. We update contact and roster entry in this case - """ - self.roster.rename_self_contact(data[0], data[1], account) - - def edit_own_details(self, account): - jid = gajim.get_jid_from_account(account) - if 'profile' not in self.instances[account]: - self.instances[account]['profile'] = \ - profile_window.ProfileWindow(account) - gajim.connections[account].request_vcard(jid) - - def handle_event_notify(self, account, array): - # 'NOTIFY' (account, (jid, status, status message, resource, - # priority, # keyID, timestamp, contact_nickname)) - # - # Contact changed show - - # FIXME: Drop and rewrite... - - statuss = ['offline', 'error', 'online', 'chat', 'away', 'xa', 'dnd', - 'invisible'] - # Ignore invalid show - if array[1] not in statuss: - return - old_show = 0 - new_show = statuss.index(array[1]) - status_message = array[2] - jid = array[0].split('/')[0] - keyID = array[5] - contact_nickname = array[7] - - # Get the proper keyID - keyID = helpers.prepare_and_validate_gpg_keyID(account, jid, keyID) - - resource = array[3] - if not resource: - resource = '' - priority = array[4] - if gajim.jid_is_transport(jid): - # It must be an agent - ji = jid.replace('@', '') - else: - ji = jid - - highest = gajim.contacts. \ - get_contact_with_highest_priority(account, jid) - was_highest = (highest and highest.resource == resource) - - conn = gajim.connections[account] - - # Update contact - jid_list = gajim.contacts.get_jid_list(account) - if ji in jid_list or jid == gajim.get_jid_from_account(account): - lcontact = gajim.contacts.get_contacts(account, ji) - contact1 = None - resources = [] - for c in lcontact: - resources.append(c.resource) - if c.resource == resource: - contact1 = c - break - - if contact1: - if contact1.show in statuss: - old_show = statuss.index(contact1.show) - # nick changed - if contact_nickname is not None and \ - contact1.contact_name != contact_nickname: - contact1.contact_name = contact_nickname - self.roster.draw_contact(jid, account) - - if old_show == new_show and contact1.status == status_message and \ - contact1.priority == priority: # no change - return - else: - contact1 = gajim.contacts.get_first_contact_from_jid(account, ji) - if not contact1: - # Presence of another resource of our - # jid - # Create self contact and add to roster - if resource == conn.server_resource: - return - # Ignore offline presence of unknown self resource - if new_show < 2: - return - contact1 = gajim.contacts.create_self_contact(jid=ji, - account=account, show=array[1], status=status_message, - priority=priority, keyID=keyID, resource=resource) - old_show = 0 - gajim.contacts.add_contact(account, contact1) - lcontact.append(contact1) - elif contact1.show in statuss: - old_show = statuss.index(contact1.show) - if (resources != [''] and (len(lcontact) != 1 or \ - lcontact[0].show != 'offline')) and jid.find('@') > 0: - # Another resource of an existing contact connected - old_show = 0 - contact1 = gajim.contacts.copy_contact(contact1) - lcontact.append(contact1) - contact1.resource = resource - - self.roster.add_contact(contact1.jid, account) - - if contact1.jid.find('@') > 0 and len(lcontact) == 1: - # It's not an agent - if old_show == 0 and new_show > 1: - if not contact1.jid in gajim.newly_added[account]: - gajim.newly_added[account].append(contact1.jid) - if contact1.jid in gajim.to_be_removed[account]: - gajim.to_be_removed[account].remove(contact1.jid) - gobject.timeout_add_seconds(5, self.roster.remove_newly_added, - contact1.jid, account) - elif old_show > 1 and new_show == 0 and conn.connected > 1: - if not contact1.jid in gajim.to_be_removed[account]: - gajim.to_be_removed[account].append(contact1.jid) - if contact1.jid in gajim.newly_added[account]: - gajim.newly_added[account].remove(contact1.jid) - self.roster.draw_contact(contact1.jid, account) - gobject.timeout_add_seconds(5, self.roster.remove_to_be_removed, - contact1.jid, account) - - # unset custom status - if (old_show == 0 and new_show > 1) or (old_show > 1 and new_show == 0\ - and conn.connected > 1): - if account in self.status_sent_to_users and \ - jid in self.status_sent_to_users[account]: - del self.status_sent_to_users[account][jid] - - contact1.show = array[1] - contact1.status = status_message - contact1.priority = priority - contact1.keyID = keyID - timestamp = array[6] - if timestamp: - contact1.last_status_time = timestamp - elif not gajim.block_signed_in_notifications[account]: - # We're connected since more that 30 seconds - contact1.last_status_time = time.localtime() - contact1.contact_nickname = contact_nickname - - if gajim.jid_is_transport(jid): - # It must be an agent - if ji in jid_list: - # Update existing iter and group counting - self.roster.draw_contact(ji, account) - self.roster.draw_group(_('Transports'), account) - if new_show > 1 and ji in gajim.transport_avatar[account]: - # transport just signed in. - # request avatars - for jid_ in gajim.transport_avatar[account][ji]: - conn.request_vcard(jid_) - # transport just signed in/out, don't show - # popup notifications for 30s - account_ji = account + '/' + ji - gajim.block_signed_in_notifications[account_ji] = True - gobject.timeout_add_seconds(30, - self.unblock_signed_in_notifications, account_ji) - locations = (self.instances, self.instances[account]) - for location in locations: - if 'add_contact' in location: - if old_show == 0 and new_show > 1: - location['add_contact'].transport_signed_in(jid) - break - elif old_show > 1 and new_show == 0: - location['add_contact'].transport_signed_out(jid) - break - elif ji in jid_list: - # It isn't an agent - # reset chatstate if needed: - # (when contact signs out or has errors) - if array[1] in ('offline', 'error'): - contact1.our_chatstate = contact1.chatstate = \ - contact1.composing_xep = None - - # TODO: This causes problems when another - # resource signs off! - conn.remove_transfers_for_contact(contact1) - - # disable encryption, since if any messages are - # lost they'll be not decryptable (note that - # this contradicts XEP-0201 - trying to get that - # in the XEP, though) - - # there won't be any sessions here if the contact terminated - # their sessions before going offline (which we do) - for sess in conn.get_sessions(ji): - if (ji+'/'+resource) != str(sess.jid): - continue - if sess.control: - sess.control.no_autonegotiation = False - if sess.enable_encryption: - sess.terminate_e2e() - conn.delete_session(jid, sess.thread_id) - - self.roster.chg_contact_status(contact1, array[1], status_message, - account) - # Notifications - if old_show < 2 and new_show > 1: - notify.notify('contact_connected', jid, account, status_message) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('ContactPresence', (account, - array)) - - elif old_show > 1 and new_show < 2: - notify.notify('contact_disconnected', jid, account, status_message) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('ContactAbsence', (account, array)) - # FIXME: stop non active file transfers - # Status change (not connected/disconnected or - # error (<1)) - elif new_show > 1: - notify.notify('status_change', jid, account, [new_show, - status_message]) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('ContactStatus', (account, array)) - else: - # FIXME: MSN transport (CMSN1.2.1 and PyMSN) don't - # follow the XEP, still the case in 2008. - # It's maybe a GC_NOTIFY (specialy for MSN gc) - self.handle_event_gc_notify(account, (jid, array[1], status_message, - array[3], None, None, None, None, None, [], None, None)) - - highest = gajim.contacts.get_contact_with_highest_priority(account, jid) - is_highest = (highest and highest.resource == resource) - - # disconnect the session from the ctrl if the highest resource has changed - if (was_highest and not is_highest) or (not was_highest and is_highest): - ctrl = self.msg_win_mgr.get_control(jid, account) - - if ctrl: - ctrl.no_autonegotiation = False - ctrl.set_session(None) - ctrl.contact = highest - - def handle_event_msgerror(self, account, array): - #'MSGERROR' (account, (jid, error_code, error_msg, msg, time[, session])) - full_jid_with_resource = array[0] - jids = full_jid_with_resource.split('/', 1) - jid = jids[0] - - if array[1] == '503': - # If we get server-not-found error, stop sending chatstates - for contact in gajim.contacts.get_contacts(account, jid): - contact.composing_xep = False - - session = None - if len(array) > 5: - session = array[5] - - gc_control = self.msg_win_mgr.get_gc_control(jid, account) - if not gc_control and \ - jid in self.minimized_controls[account]: - gc_control = self.minimized_controls[account][jid] - if gc_control and gc_control.type_id != message_control.TYPE_GC: - gc_control = None - if gc_control: - if len(jids) > 1: # it's a pm - nick = jids[1] - - if session: - ctrl = session.control - else: - ctrl = self.msg_win_mgr.get_control(full_jid_with_resource, account) - - if not ctrl: - tv = gc_control.list_treeview - model = tv.get_model() - iter_ = gc_control.get_contact_iter(nick) - if iter_: - show = model[iter_][3] - else: - show = 'offline' - gc_c = gajim.contacts.create_gc_contact(room_jid=jid, account=account, - name=nick, show=show) - ctrl = self.new_private_chat(gc_c, account, session) - - ctrl.print_conversation(_('Error %(code)s: %(msg)s') % { - 'code': array[1], 'msg': array[2]}, 'status') - return - - gc_control.print_conversation(_('Error %(code)s: %(msg)s') % { - 'code': array[1], 'msg': array[2]}, 'status') - if gc_control.parent_win and gc_control.parent_win.get_active_jid() == jid: - gc_control.set_subject(gc_control.subject) - return - - if gajim.jid_is_transport(jid): - jid = jid.replace('@', '') - msg = array[2] - if array[3]: - msg = _('error while sending %(message)s ( %(error)s )') % { - 'message': array[3], 'error': msg} - if session: - session.roster_message(jid, msg, array[4], msg_type='error') - - def handle_event_msgsent(self, account, array): - #('MSGSENT', account, (jid, msg, keyID)) - msg = array[1] - # do not play sound when standalone chatstate message (eg no msg) - if msg and gajim.config.get_per('soundevents', 'message_sent', 'enabled'): - helpers.play_sound('message_sent') - - def handle_event_msgnotsent(self, account, array): - #('MSGNOTSENT', account, (jid, ierror_msg, msg, time, session)) - msg = _('error while sending %(message)s ( %(error)s )') % { - 'message': array[2], 'error': array[1]} - if not array[4]: - # No session. This can happen when sending a message from gajim-remote - log.warn(msg) - return - array[4].roster_message(array[0], msg, array[3], account, - msg_type='error') - - def handle_event_subscribe(self, account, array): - #('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172 - if self.remote_ctrl: - self.remote_ctrl.raise_signal('Subscribe', (account, array)) - - jid = array[0] - text = array[1] - nick = array[2] - if helpers.allow_popup_window(account) or not self.systray_enabled: - dialogs.SubscriptionRequestWindow(jid, text, account, nick) - return - - self.add_event(account, jid, 'subscription_request', (text, nick)) - - if helpers.allow_showing_notification(account): - path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'subscription_request.png') - path = gtkgui_helpers.get_path_to_generic_or_avatar(path) - event_type = _('Subscription request') - notify.popup(event_type, jid, account, 'subscription_request', path, - event_type, jid) - - def handle_event_subscribed(self, account, array): - #('SUBSCRIBED', account, (jid, resource)) - jid = array[0] - if jid in gajim.contacts.get_jid_list(account): - c = gajim.contacts.get_first_contact_from_jid(account, jid) - c.resource = array[1] - self.roster.remove_contact_from_groups(c.jid, account, - [_('Not in Roster'), _('Observers')], update=False) - else: - keyID = '' - attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() - if jid in attached_keys: - keyID = attached_keys[attached_keys.index(jid) + 1] - name = jid.split('@', 1)[0] - name = name.split('%', 1)[0] - contact1 = gajim.contacts.create_contact(jid=jid, account=account, - name=name, groups=[], show='online', status='online', - ask='to', resource=array[1], keyID=keyID) - gajim.contacts.add_contact(account, contact1) - self.roster.add_contact(jid, account) - dialogs.InformationDialog(_('Authorization accepted'), - _('The contact "%s" has authorized you to see his or her status.') - % jid) - if not gajim.config.get_per('accounts', account, 'dont_ack_subscription'): - gajim.connections[account].ack_subscribed(jid) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('Subscribed', (account, array)) - - def show_unsubscribed_dialog(self, account, contact): - def on_yes(is_checked, list_): - self.roster.on_req_usub(None, list_) - list_ = [(contact, account)] - dialogs.YesNoDialog( - _('Contact "%s" removed subscription from you') % contact.jid, - _('You will always see him or her as offline.\nDo you want to ' - 'remove him or her from your contact list?'), - on_response_yes=(on_yes, list_)) - # FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does - # not show deny - - def handle_event_unsubscribed(self, account, jid): - #('UNSUBSCRIBED', account, jid) - gajim.connections[account].ack_unsubscribed(jid) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('Unsubscribed', (account, jid)) - - contact = gajim.contacts.get_first_contact_from_jid(account, jid) - if not contact: - return - - if helpers.allow_popup_window(account) or not self.systray_enabled: - self.show_unsubscribed_dialog(account, contact) - - self.add_event(account, jid, 'unsubscribed', contact) - - if helpers.allow_showing_notification(account): - path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'unsubscribed.png') - path = gtkgui_helpers.get_path_to_generic_or_avatar(path) - event_type = _('Unsubscribed') - notify.popup(event_type, jid, account, 'unsubscribed', path, - event_type, jid) - - def handle_event_agent_info_error(self, account, agent): - #('AGENT_ERROR_INFO', account, (agent)) - try: - gajim.connections[account].services_cache.agent_info_error(agent) - except AttributeError: - return - - def handle_event_agent_items_error(self, account, agent): - #('AGENT_ERROR_INFO', account, (agent)) - try: - gajim.connections[account].services_cache.agent_items_error(agent) - except AttributeError: - return - - def handle_event_agent_removed(self, account, agent): - # remove transport's contacts from treeview - jid_list = gajim.contacts.get_jid_list(account) - for jid in jid_list: - if jid.endswith('@' + agent): - c = gajim.contacts.get_first_contact_from_jid(account, jid) - gajim.log.debug( - 'Removing contact %s due to unregistered transport %s'\ - % (jid, agent)) - gajim.connections[account].unsubscribe(c.jid) - # Transport contacts can't have 2 resources - if c.jid in gajim.to_be_removed[account]: - # This way we'll really remove it - gajim.to_be_removed[account].remove(c.jid) - self.roster.remove_contact(c.jid, account, backend=True) - - def handle_event_register_agent_info(self, account, array): - # ('REGISTER_AGENT_INFO', account, (agent, infos, is_form)) - # info in a dataform if is_form is True - if array[2] or 'instructions' in array[1]: - config.ServiceRegistrationWindow(array[0], array[1], account, - array[2]) - else: - dialogs.ErrorDialog(_('Contact with "%s" cannot be established') \ - % array[0], _('Check your connection or try again later.')) - - def handle_event_agent_info_items(self, account, array): - #('AGENT_INFO_ITEMS', account, (agent, node, items)) - our_jid = gajim.get_jid_from_account(account) - if 'pep_services' in gajim.interface.instances[account] and \ - array[0] == our_jid: - gajim.interface.instances[account]['pep_services'].items_received( - array[2]) - try: - gajim.connections[account].services_cache.agent_items(array[0], - array[1], array[2]) - except AttributeError: - return - - def handle_event_agent_info_info(self, account, array): - #('AGENT_INFO_INFO', account, (agent, node, identities, features, data)) - try: - gajim.connections[account].services_cache.agent_info(array[0], - array[1], array[2], array[3], array[4]) - except AttributeError: - return - - def handle_event_new_acc_connected(self, account, array): - #('NEW_ACC_CONNECTED', account, (infos, is_form, ssl_msg, ssl_err, - # ssl_cert, ssl_fingerprint)) - if 'account_creation_wizard' in self.instances: - self.instances['account_creation_wizard'].new_acc_connected(array[0], - array[1], array[2], array[3], array[4], array[5]) - - def handle_event_new_acc_not_connected(self, account, array): - #('NEW_ACC_NOT_CONNECTED', account, (reason)) - if 'account_creation_wizard' in self.instances: - self.instances['account_creation_wizard'].new_acc_not_connected(array) - - def handle_event_acc_ok(self, account, array): - #('ACC_OK', account, (config)) - if 'account_creation_wizard' in self.instances: - self.instances['account_creation_wizard'].acc_is_ok(array) - - if self.remote_ctrl: - self.remote_ctrl.raise_signal('NewAccount', (account, array)) - - def handle_event_acc_not_ok(self, account, array): - #('ACC_NOT_OK', account, (reason)) - if 'account_creation_wizard' in self.instances: - self.instances['account_creation_wizard'].acc_is_not_ok(array) - - def handle_event_quit(self, p1, p2): - self.roster.quit_gtkgui_interface() - - def handle_event_myvcard(self, account, array): - nick = '' - if 'NICKNAME' in array and array['NICKNAME']: - gajim.nicks[account] = array['NICKNAME'] - elif 'FN' in array and array['FN']: - gajim.nicks[account] = array['FN'] - if 'profile' in self.instances[account]: - win = self.instances[account]['profile'] - win.set_values(array) - if account in self.show_vcard_when_connect: - self.show_vcard_when_connect.remove(account) - jid = array['jid'] - if jid in self.instances[account]['infos']: - self.instances[account]['infos'][jid].set_values(array) - - def handle_event_vcard(self, account, vcard): - # ('VCARD', account, data) - '''vcard holds the vcard data''' - jid = vcard['jid'] - resource = vcard.get('resource', '') - fjid = jid + '/' + str(resource) - - # vcard window - win = None - if jid in self.instances[account]['infos']: - win = self.instances[account]['infos'][jid] - elif resource and fjid in self.instances[account]['infos']: - win = self.instances[account]['infos'][fjid] - if win: - win.set_values(vcard) - - # show avatar in chat - ctrl = None - if resource and self.msg_win_mgr.has_window(fjid, account): - win = self.msg_win_mgr.get_window(fjid, account) - ctrl = win.get_control(fjid, account) - elif self.msg_win_mgr.has_window(jid, account): - win = self.msg_win_mgr.get_window(jid, account) - ctrl = win.get_control(jid, account) - - if ctrl and ctrl.type_id != message_control.TYPE_GC: - ctrl.show_avatar() - - # Show avatar in roster or gc_roster - gc_ctrl = self.msg_win_mgr.get_gc_control(jid, account) - if not gc_ctrl and \ - jid in self.minimized_controls[account]: - gc_ctrl = self.minimized_controls[account][jid] - if gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC: - gc_ctrl.draw_avatar(resource) - else: - self.roster.draw_avatar(jid, account) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('VcardInfo', (account, vcard)) - - def handle_event_last_status_time(self, account, array): - # ('LAST_STATUS_TIME', account, (jid, resource, seconds, status)) - tim = array[2] - if tim < 0: - # Ann error occured - return - win = None - if array[0] in self.instances[account]['infos']: - win = self.instances[account]['infos'][array[0]] - elif array[0] + '/' + array[1] in self.instances[account]['infos']: - win = self.instances[account]['infos'][array[0] + '/' + array[1]] - c = gajim.contacts.get_contact(account, array[0], array[1]) - if c: # c can be none if it's a gc contact - c.last_status_time = time.localtime(time.time() - tim) - if array[3]: - c.status = array[3] - self.roster.draw_contact(c.jid, account) # draw offline status - if win: - win.set_last_status_time() - if self.remote_ctrl: - self.remote_ctrl.raise_signal('LastStatusTime', (account, array)) - - def handle_event_os_info(self, account, array): - #'OS_INFO' (account, (jid, resource, client_info, os_info)) - win = None - if array[0] in self.instances[account]['infos']: - win = self.instances[account]['infos'][array[0]] - elif array[0] + '/' + array[1] in self.instances[account]['infos']: - win = self.instances[account]['infos'][array[0] + '/' + array[1]] - if win: - win.set_os_info(array[1], array[2], array[3]) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('OsInfo', (account, array)) - - def handle_event_entity_time(self, account, array): - #'ENTITY_TIME' (account, (jid, resource, time_info)) - win = None - if array[0] in self.instances[account]['infos']: - win = self.instances[account]['infos'][array[0]] - elif array[0] + '/' + array[1] in self.instances[account]['infos']: - win = self.instances[account]['infos'][array[0] + '/' + array[1]] - if win: - win.set_entity_time(array[1], array[2]) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('EntityTime', (account, array)) - - def handle_event_gc_notify(self, account, array): - #'GC_NOTIFY' (account, (room_jid, show, status, nick, - # role, affiliation, jid, reason, actor, statusCode, newNick, avatar_sha)) - nick = array[3] - if not nick: - return - room_jid = array[0] - fjid = room_jid + '/' + nick - show = array[1] - status = array[2] - conn = gajim.connections[account] - - # Get the window and control for the updated status, this may be a - # PrivateChatControl - control = self.msg_win_mgr.get_gc_control(room_jid, account) - - if not control and \ - room_jid in self.minimized_controls[account]: - control = self.minimized_controls[account][room_jid] - - if not control or (control and control.type_id != message_control.TYPE_GC): - return - - control.chg_contact_status(nick, show, status, array[4], array[5], - array[6], array[7], array[8], array[9], array[10], array[11]) - - contact = gajim.contacts.\ - get_contact_with_highest_priority(account, room_jid) - if contact: - self.roster.draw_contact(room_jid, account) - - # print status in chat window and update status/GPG image - ctrl = self.msg_win_mgr.get_control(fjid, account) - if ctrl: - statusCode = array[9] - if '303' in statusCode: - new_nick = array[10] - ctrl.print_conversation(_('%(nick)s is now known as %(new_nick)s') \ - % {'nick': nick, 'new_nick': new_nick}, 'status') - gc_c = gajim.contacts.get_gc_contact(account, room_jid, new_nick) - c = gc_c.as_contact() - ctrl.gc_contact = gc_c - ctrl.contact = c - if ctrl.session: - # stop e2e - if ctrl.session.enable_encryption: - thread_id = ctrl.session.thread_id - ctrl.session.terminate_e2e() - conn.delete_session(fjid, thread_id) - ctrl.no_autonegotiation = False - ctrl.draw_banner() - old_jid = room_jid + '/' + nick - new_jid = room_jid + '/' + new_nick - self.msg_win_mgr.change_key(old_jid, new_jid, account) - else: - contact = ctrl.contact - contact.show = show - contact.status = status - gc_contact = ctrl.gc_contact - gc_contact.show = show - gc_contact.status = status - uf_show = helpers.get_uf_show(show) - ctrl.print_conversation(_('%(nick)s is now %(status)s') % { - 'nick': nick, 'status': uf_show}, 'status') - if status: - ctrl.print_conversation(' (', 'status', simple=True) - ctrl.print_conversation('%s' % (status), 'status', simple=True) - ctrl.print_conversation(')', 'status', simple=True) - ctrl.parent_win.redraw_tab(ctrl) - ctrl.update_ui() - if self.remote_ctrl: - self.remote_ctrl.raise_signal('GCPresence', (account, array)) - - def handle_event_gc_msg(self, account, array): - # ('GC_MSG', account, (jid, msg, time, has_timestamp, htmlmsg, - # [status_codes])) - jids = array[0].split('/', 1) - room_jid = jids[0] - - msg = array[1] - - gc_control = self.msg_win_mgr.get_gc_control(room_jid, account) - if not gc_control and \ - room_jid in self.minimized_controls[account]: - gc_control = self.minimized_controls[account][room_jid] - - if not gc_control: - return - xhtml = array[4] - - if gajim.config.get('ignore_incoming_xhtml'): - xhtml = None - if len(jids) == 1: - # message from server - nick = '' - else: - # message from someone - nick = jids[1] - - gc_control.on_message(nick, msg, array[2], array[3], xhtml, array[5]) - - if self.remote_ctrl: - highlight = gc_control.needs_visual_notification(msg) - array += (highlight,) - self.remote_ctrl.raise_signal('GCMessage', (account, array)) - - def handle_event_gc_subject(self, account, array): - #('GC_SUBJECT', account, (jid, subject, body, has_timestamp)) - jids = array[0].split('/', 1) - jid = jids[0] - - gc_control = self.msg_win_mgr.get_gc_control(jid, account) - - if not gc_control and \ - jid in self.minimized_controls[account]: - gc_control = self.minimized_controls[account][jid] - - contact = gajim.contacts.\ - get_contact_with_highest_priority(account, jid) - if contact: - contact.status = array[1] - self.roster.draw_contact(jid, account) - - if not gc_control: - return - gc_control.set_subject(array[1]) - # Standard way, the message comes from the occupant who set the subject - text = None - if len(jids) > 1: - text = _('%(jid)s has set the subject to %(subject)s') % { - 'jid': jids[1], 'subject': array[1]} - # Workaround for psi bug http://flyspray.psi-im.org/task/595 , to be - # deleted one day. We can receive a subject with a body that contains - # "X has set the subject to Y" ... - elif array[2]: - text = array[2] - if text is not None: - if array[3]: - gc_control.print_old_conversation(text) - else: - gc_control.print_conversation(text) - - def handle_event_gc_config(self, account, array): - #('GC_CONFIG', account, (jid, form)) config is a dict - room_jid = array[0].split('/')[0] - if room_jid in gajim.automatic_rooms[account]: - if 'continue_tag' in gajim.automatic_rooms[account][room_jid]: - # We're converting chat to muc. allow participants to invite - form = dataforms.ExtendForm(node = array[1]) - for f in form.iter_fields(): - if f.var == 'muc#roomconfig_allowinvites': - f.value = True - elif f.var == 'muc#roomconfig_publicroom': - f.value = False - elif f.var == 'muc#roomconfig_membersonly': - f.value = True - elif f.var == 'public_list': - f.value = False - gajim.connections[account].send_gc_config(room_jid, form) - else: - # use default configuration - gajim.connections[account].send_gc_config(room_jid, array[1]) - # invite contacts - # check if it is necessary to add - continue_tag = False - if 'continue_tag' in gajim.automatic_rooms[account][room_jid]: - continue_tag = True - if 'invities' in gajim.automatic_rooms[account][room_jid]: - for jid in gajim.automatic_rooms[account][room_jid]['invities']: - gajim.connections[account].send_invite(room_jid, jid, - continue_tag=continue_tag) - del gajim.automatic_rooms[account][room_jid] - elif room_jid not in self.instances[account]['gc_config']: - self.instances[account]['gc_config'][room_jid] = \ - config.GroupchatConfigWindow(account, room_jid, array[1]) - - def handle_event_gc_config_change(self, account, array): - #('GC_CONFIG_CHANGE', account, (jid, statusCode)) statuscode is a list - # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify - # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes-init - jid = array[0] - statusCode = array[1] - - gc_control = self.msg_win_mgr.get_gc_control(jid, account) - if not gc_control and \ - jid in self.minimized_controls[account]: - gc_control = self.minimized_controls[account][jid] - if not gc_control: - return - - changes = [] - if '100' in statusCode: - # Can be a presence (see chg_contact_status in groupchat_control.py) - changes.append(_('Any occupant is allowed to see your full JID')) - gc_control.is_anonymous = False - if '102' in statusCode: - changes.append(_('Room now shows unavailable member')) - if '103' in statusCode: - changes.append(_('room now does not show unavailable members')) - if '104' in statusCode: - changes.append( - _('A non-privacy-related room configuration change has occurred')) - if '170' in statusCode: - # Can be a presence (see chg_contact_status in groupchat_control.py) - changes.append(_('Room logging is now enabled')) - if '171' in statusCode: - changes.append(_('Room logging is now disabled')) - if '172' in statusCode: - changes.append(_('Room is now non-anonymous')) - gc_control.is_anonymous = False - if '173' in statusCode: - changes.append(_('Room is now semi-anonymous')) - gc_control.is_anonymous = True - if '174' in statusCode: - changes.append(_('Room is now fully-anonymous')) - gc_control.is_anonymous = True - - for change in changes: - gc_control.print_conversation(change) - - def handle_event_gc_affiliation(self, account, array): - #('GC_AFFILIATION', account, (room_jid, users_dict)) - room_jid = array[0] - if room_jid in self.instances[account]['gc_config']: - self.instances[account]['gc_config'][room_jid].\ - affiliation_list_received(array[1]) - - def handle_event_gc_password_required(self, account, array): - #('GC_PASSWORD_REQUIRED', account, (room_jid, nick)) - room_jid = array[0] - nick = array[1] - - def on_ok(text): - gajim.connections[account].join_gc(nick, room_jid, text) - gajim.gc_passwords[room_jid] = text - - def on_cancel(): - # get and destroy window - if room_jid in gajim.interface.minimized_controls[account]: - self.roster.on_disconnect(None, room_jid, account) - else: - win = self.msg_win_mgr.get_window(room_jid, account) - ctrl = self.msg_win_mgr.get_gc_control(room_jid, account) - win.remove_tab(ctrl, 3) - - dlg = dialogs.InputDialog(_('Password Required'), - _('A Password is required to join the room %s. Please type it.') % \ - room_jid, is_modal=False, ok_handler=on_ok, cancel_handler=on_cancel) - dlg.input_entry.set_visibility(False) - - def handle_event_gc_invitation(self, account, array): - #('GC_INVITATION', (room_jid, jid_from, reason, password, is_continued)) - jid = gajim.get_jid_without_resource(array[1]) - room_jid = array[0] - if helpers.allow_popup_window(account) or not self.systray_enabled: - dialogs.InvitationReceivedDialog(account, room_jid, jid, array[3], - array[2], is_continued=array[4]) - return - - self.add_event(account, jid, 'gc-invitation', (room_jid, array[2], - array[3], array[4])) - - if helpers.allow_showing_notification(account): - path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'gc_invitation.png') - path = gtkgui_helpers.get_path_to_generic_or_avatar(path) - event_type = _('Groupchat Invitation') - notify.popup(event_type, jid, account, 'gc-invitation', path, - event_type, room_jid) - - def forget_gpg_passphrase(self, keyid): - if keyid in self.gpg_passphrase: - del self.gpg_passphrase[keyid] - return False - - def handle_event_bad_passphrase(self, account, array): - #('BAD_PASSPHRASE', account, ()) - use_gpg_agent = gajim.config.get('use_gpg_agent') - sectext = '' - if use_gpg_agent: - sectext = _('You configured Gajim to use GPG agent, but there is no ' - 'GPG agent running or it returned a wrong passphrase.\n') - sectext += _('You are currently connected without your OpenPGP key.') - dialogs.WarningDialog(_('Your passphrase is incorrect'), sectext) - else: - path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'warning.png') - notify.popup('warning', account, account, 'warning', path, - _('OpenGPG Passphrase Incorrect'), - _('You are currently connected without your OpenPGP key.')) - keyID = gajim.config.get_per('accounts', account, 'keyid') - self.forget_gpg_passphrase(keyID) - - def handle_event_gpg_password_required(self, account, array): - #('GPG_PASSWORD_REQUIRED', account, (callback,)) - callback = array[0] - keyid = gajim.config.get_per('accounts', account, 'keyid') - if keyid in self.gpg_passphrase: - request = self.gpg_passphrase[keyid] - else: - request = PassphraseRequest(keyid) - self.gpg_passphrase[keyid] = request - request.add_callback(account, callback) - - def handle_event_gpg_always_trust(self, account, callback): - #('GPG_ALWAYS_TRUST', account, callback) - def on_yes(checked): - if checked: - gajim.connections[account].gpg.always_trust = True - callback(True) - - def on_no(): - callback(False) - - dialogs.YesNoDialog(_('GPG key not trusted'), _('The GPG key used to ' - 'encrypt this chat is not trusted. Do you really want to encrypt this ' - 'message?'), checktext=_('Do _not ask me again'), - on_response_yes=on_yes, on_response_no=on_no) - - def handle_event_password_required(self, account, array): - #('PASSWORD_REQUIRED', account, None) - if account in self.pass_dialog: - return - text = _('Enter your password for account %s') % account - if passwords.USER_HAS_GNOMEKEYRING and \ - not passwords.USER_USES_GNOMEKEYRING: - text += '\n' + _('Gnome Keyring is installed but not \ - correctly started (environment variable probably not \ - correctly set)') - - def on_ok(passphrase, save): - if save: - gajim.config.set_per('accounts', account, 'savepass', True) - passwords.save_password(account, passphrase) - gajim.connections[account].set_password(passphrase) - del self.pass_dialog[account] - - def on_cancel(): - self.roster.set_state(account, 'offline') - self.roster.update_status_combobox() - del self.pass_dialog[account] - - self.pass_dialog[account] = dialogs.PassphraseDialog( - _('Password Required'), text, _('Save password'), ok_handler=on_ok, - cancel_handler=on_cancel) - - def handle_event_roster_info(self, account, array): - #('ROSTER_INFO', account, (jid, name, sub, ask, groups)) - jid = array[0] - name = array[1] - sub = array[2] - ask = array[3] - groups = array[4] - contacts = gajim.contacts.get_contacts(account, jid) - if (not sub or sub == 'none') and (not ask or ask == 'none') and \ - not name and not groups: - # contact removed us. - if contacts: - self.roster.remove_contact(jid, account, backend=True) - return - elif not contacts: - if sub == 'remove': - return - # Add new contact to roster - contact = gajim.contacts.create_contact(jid=jid, account=account, - name=name, groups=groups, show='offline', sub=sub, ask=ask) - gajim.contacts.add_contact(account, contact) - self.roster.add_contact(jid, account) - else: - # it is an existing contact that might has changed - re_place = False - # If contact has changed (sub, ask or group) update roster - # Mind about observer status changes: - # According to xep 0162, a contact is not an observer anymore when - # we asked for auth, so also remove him if ask changed - old_groups = contacts[0].groups - if contacts[0].sub != sub or contacts[0].ask != ask\ - or old_groups != groups: - re_place = True - # c.get_shown_groups() has changed. Reflect that in roster_winodow - self.roster.remove_contact(jid, account, force=True) - for contact in contacts: - contact.name = name or '' - contact.sub = sub - contact.ask = ask - contact.groups = groups or [] - if re_place: - self.roster.add_contact(jid, account) - # Refilter and update old groups - for group in old_groups: - self.roster.draw_group(group, account) - else: - self.roster.draw_contact(jid, account) - - if self.remote_ctrl: - self.remote_ctrl.raise_signal('RosterInfo', (account, array)) - - def handle_event_bookmarks(self, account, bms): - # ('BOOKMARKS', account, [{name,jid,autojoin,password,nick}, {}]) - # We received a bookmark item from the server (JEP48) - # Auto join GC windows if neccessary - - self.roster.set_actions_menu_needs_rebuild() - invisible_show = gajim.SHOW_LIST.index('invisible') - # do not autojoin if we are invisible - if gajim.connections[account].connected == invisible_show: - return - - self.auto_join_bookmarks(account) - - def handle_event_file_send_error(self, account, array): - jid = array[0] - file_props = array[1] - ft = self.instances['file_transfers'] - ft.set_status(file_props['type'], file_props['sid'], 'stop') - - if helpers.allow_popup_window(account): - ft.show_send_error(file_props) - return - - self.add_event(account, jid, 'file-send-error', file_props) - - if helpers.allow_showing_notification(account): - img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', 'ft_error.png') - path = gtkgui_helpers.get_path_to_generic_or_avatar(img) - event_type = _('File Transfer Error') - notify.popup(event_type, jid, account, 'file-send-error', path, - event_type, file_props['name']) - - def handle_event_gmail_notify(self, account, array): - jid = array[0] - gmail_new_messages = int(array[1]) - gmail_messages_list = array[2] - if gajim.config.get('notify_on_new_gmail_email'): - img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'new_email_recv.png') - title = _('New mail on %(gmail_mail_address)s') % \ - {'gmail_mail_address': jid} - text = i18n.ngettext('You have %d new mail conversation', - 'You have %d new mail conversations', gmail_new_messages, - gmail_new_messages, gmail_new_messages) - - if gajim.config.get('notify_on_new_gmail_email_extra'): - cnt = 0 - for gmessage in gmail_messages_list: - #FIXME: emulate Gtalk client popups. find out what they parse and - # how they decide what to show each message has a 'From', - # 'Subject' and 'Snippet' field - if cnt >=5: - break - senders = ',\n '.join(reversed(gmessage['From'])) - text += _('\n\nFrom: %(from_address)s\nSubject: %(subject)s\n%(snippet)s') % \ - {'from_address': senders, 'subject': gmessage['Subject'], - 'snippet': gmessage['Snippet']} - cnt += 1 - - if gajim.config.get_per('soundevents', 'gmail_received', 'enabled'): - helpers.play_sound('gmail_received') - path = gtkgui_helpers.get_path_to_generic_or_avatar(img) - notify.popup(_('New E-mail'), jid, account, 'gmail', - path_to_image=path, title=title, - text=text) - - if self.remote_ctrl: - self.remote_ctrl.raise_signal('NewGmail', (account, array)) - - def handle_event_file_request_error(self, account, array): - # ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg)) - jid, file_props, errmsg = array - jid = gajim.get_jid_without_resource(jid) - ft = self.instances['file_transfers'] - ft.set_status(file_props['type'], file_props['sid'], 'stop') - errno = file_props['error'] - - if helpers.allow_popup_window(account): - if errno in (-4, -5): - ft.show_stopped(jid, file_props, errmsg) - else: - ft.show_request_error(file_props) - return - - if errno in (-4, -5): - msg_type = 'file-error' - else: - msg_type = 'file-request-error' - - self.add_event(account, jid, msg_type, file_props) - - if helpers.allow_showing_notification(account): - # check if we should be notified - img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', 'ft_error.png') - - path = gtkgui_helpers.get_path_to_generic_or_avatar(img) - event_type = _('File Transfer Error') - notify.popup(event_type, jid, account, msg_type, path, - title = event_type, text = file_props['name']) - - def handle_event_file_request(self, account, array): - jid = array[0] - jid = gajim.get_jid_without_resource(jid) - if jid not in gajim.contacts.get_jid_list(account): - keyID = '' - attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() - if jid in attached_keys: - keyID = attached_keys[attached_keys.index(jid) + 1] - contact = gajim.contacts.create_not_in_roster_contact(jid=jid, - account=account, keyID=keyID) - gajim.contacts.add_contact(account, contact) - self.roster.add_contact(contact.jid, account) - file_props = array[1] - contact = gajim.contacts.get_first_contact_from_jid(account, jid) - - if helpers.allow_popup_window(account): - self.instances['file_transfers'].show_file_request(account, contact, - file_props) - return - - self.add_event(account, jid, 'file-request', file_props) - - if helpers.allow_showing_notification(account): - img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'ft_request.png') - txt = _('%s wants to send you a file.') % gajim.get_name_from_jid( - account, jid) - path = gtkgui_helpers.get_path_to_generic_or_avatar(img) - event_type = _('File Transfer Request') - notify.popup(event_type, jid, account, 'file-request', - path_to_image = path, title = event_type, text = txt) - - def handle_event_file_error(self, title, message): - dialogs.ErrorDialog(title, message) - - def handle_event_file_progress(self, account, file_props): - if time.time() - self.last_ftwindow_update > 0.5: - # update ft window every 500ms - self.last_ftwindow_update = time.time() - self.instances['file_transfers'].set_progress(file_props['type'], - file_props['sid'], file_props['received-len']) - - def handle_event_file_rcv_completed(self, account, file_props): - ft = self.instances['file_transfers'] - if file_props['error'] == 0: - ft.set_progress(file_props['type'], file_props['sid'], - file_props['received-len']) - else: - ft.set_status(file_props['type'], file_props['sid'], 'stop') - if 'stalled' in file_props and file_props['stalled'] or \ - 'paused' in file_props and file_props['paused']: - return - if file_props['type'] == 'r': # we receive a file - jid = unicode(file_props['sender']) - else: # we send a file - jid = unicode(file_props['receiver']) - - if helpers.allow_popup_window(account): - if file_props['error'] == 0: - if gajim.config.get('notify_on_file_complete'): - ft.show_completed(jid, file_props) - elif file_props['error'] == -1: - ft.show_stopped(jid, file_props, - error_msg=_('Remote contact stopped transfer')) - elif file_props['error'] == -6: - ft.show_stopped(jid, file_props, error_msg=_('Error opening file')) - return - - msg_type = '' - event_type = '' - if file_props['error'] == 0 and gajim.config.get( - 'notify_on_file_complete'): - msg_type = 'file-completed' - event_type = _('File Transfer Completed') - elif file_props['error'] in (-1, -6): - msg_type = 'file-stopped' - event_type = _('File Transfer Stopped') - - if event_type == '': - # FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs) - # this should never happen but it does. see process_result() in socks5.py - # who calls this func (sth is really wrong unless this func is also registered - # as progress_cb - return - - if msg_type: - self.add_event(account, jid, msg_type, file_props) - - if file_props is not None: - if file_props['type'] == 'r': - # get the name of the sender, as it is in the roster - sender = unicode(file_props['sender']).split('/')[0] - name = gajim.contacts.get_first_contact_from_jid(account, - sender).get_shown_name() - filename = os.path.basename(file_props['file-name']) - if event_type == _('File Transfer Completed'): - txt = _('You successfully received %(filename)s from %(name)s.')\ - % {'filename': filename, 'name': name} - img = 'ft_done.png' - else: # ft stopped - txt = _('File transfer of %(filename)s from %(name)s stopped.')\ - % {'filename': filename, 'name': name} - img = 'ft_stopped.png' - else: - receiver = file_props['receiver'] - if hasattr(receiver, 'jid'): - receiver = receiver.jid - receiver = receiver.split('/')[0] - # get the name of the contact, as it is in the roster - name = gajim.contacts.get_first_contact_from_jid(account, - receiver).get_shown_name() - filename = os.path.basename(file_props['file-name']) - if event_type == _('File Transfer Completed'): - txt = _('You successfully sent %(filename)s to %(name)s.')\ - % {'filename': filename, 'name': name} - img = 'ft_done.png' - else: # ft stopped - txt = _('File transfer of %(filename)s to %(name)s stopped.')\ - % {'filename': filename, 'name': name} - img = 'ft_stopped.png' - img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', img) - path = gtkgui_helpers.get_path_to_generic_or_avatar(img) - else: - txt = '' - - if gajim.config.get('notify_on_file_complete') and \ - (gajim.config.get('autopopupaway') or \ - gajim.connections[account].connected in (2, 3)): - # we want to be notified and we are online/chat or we don't mind - # bugged when away/na/busy - notify.popup(event_type, jid, account, msg_type, path_to_image = path, - title = event_type, text = txt) - - def handle_event_stanza_arrived(self, account, stanza): - if account not in self.instances: - return - if 'xml_console' in self.instances[account]: - self.instances[account]['xml_console'].print_stanza(stanza, 'incoming') - - def handle_event_stanza_sent(self, account, stanza): - if account not in self.instances: - return - if 'xml_console' in self.instances[account]: - self.instances[account]['xml_console'].print_stanza(stanza, 'outgoing') - - def handle_event_vcard_published(self, account, array): - if 'profile' in self.instances[account]: - win = self.instances[account]['profile'] - win.vcard_published() - for gc_control in self.msg_win_mgr.get_controls(message_control.TYPE_GC) + \ - self.minimized_controls[account].values(): - if gc_control.account == account: - show = gajim.SHOW_LIST[gajim.connections[account].connected] - status = gajim.connections[account].status - gajim.connections[account].send_gc_status(gc_control.nick, - gc_control.room_jid, show, status) - - def handle_event_vcard_not_published(self, account, array): - if 'profile' in self.instances[account]: - win = self.instances[account]['profile'] - win.vcard_not_published() - - def ask_offline_status(self, account): - for contact in gajim.contacts.iter_contacts(account): - gajim.connections[account].request_last_status_time(contact.jid, - contact.resource) - - def handle_event_signed_in(self, account, empty): - """ - SIGNED_IN event is emitted when we sign in, so handle it - """ - # ('SIGNED_IN', account, ()) - # block signed in notifications for 30 seconds - gajim.block_signed_in_notifications[account] = True - self.roster.set_actions_menu_needs_rebuild() - self.roster.draw_account(account) - state = self.sleeper.getState() - connected = gajim.connections[account].connected - if gajim.config.get('ask_offline_status_on_connection'): - # Ask offline status in 1 minute so w'are sure we got all online - # presences - gobject.timeout_add_seconds(60, self.ask_offline_status, account) - if state != common.sleepy.STATE_UNKNOWN and connected in (2, 3): - # we go online or free for chat, so we activate auto status - gajim.sleeper_state[account] = 'online' - elif not ((state == common.sleepy.STATE_AWAY and connected == 4) or \ - (state == common.sleepy.STATE_XA and connected == 5)): - # If we are autoaway/xa and come back after a disconnection, do nothing - # Else disable autoaway - gajim.sleeper_state[account] = 'off' - invisible_show = gajim.SHOW_LIST.index('invisible') - # We cannot join rooms if we are invisible - if gajim.connections[account].connected == invisible_show: - return - # join already open groupchats - for gc_control in self.msg_win_mgr.get_controls(message_control.TYPE_GC) \ - + self.minimized_controls[account].values(): - if account != gc_control.account: - continue - room_jid = gc_control.room_jid - if room_jid in gajim.gc_connected[account] and \ - gajim.gc_connected[account][room_jid]: - continue - nick = gc_control.nick - password = gajim.gc_passwords.get(room_jid, '') - gajim.connections[account].join_gc(nick, room_jid, password) - # send currently played music - if gajim.connections[account].pep_supported and dbus_support.supported \ - and gajim.config.get_per('accounts', account, 'publish_tune'): - self.enable_music_listener() - # Start merging logs from server - gajim.connections[account].request_modifications_page( - gajim.config.get_per('accounts', account, 'last_archiving_time')) - gajim.config.set_per('accounts', account, 'last_archiving_time', - time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())) - - def handle_event_metacontacts(self, account, tags_list): - gajim.contacts.define_metacontacts(account, tags_list) - self.roster.redraw_metacontacts(account) - - def handle_atom_entry(self, account, data): - atom_entry, = data - AtomWindow.newAtomEntry(atom_entry) - - def handle_event_failed_decrypt(self, account, data): - jid, tim, session = data - - details = _('Unable to decrypt message from ' - '%s\nIt may have been tampered with.') % jid - - ctrl = session.control - if ctrl: - ctrl.print_conversation_line(details, 'status', '', tim) - else: - dialogs.WarningDialog(_('Unable to decrypt message'), - details) - - # terminate the session - session.terminate_e2e() - session.conn.delete_session(jid, session.thread_id) - - # restart the session - if ctrl: - ctrl.begin_e2e_negotiation() - - def handle_event_privacy_lists_received(self, account, data): - # ('PRIVACY_LISTS_RECEIVED', account, list) - if account not in self.instances: - return - if 'privacy_lists' in self.instances[account]: - self.instances[account]['privacy_lists'].privacy_lists_received(data) - - def handle_event_privacy_list_received(self, account, data): - # ('PRIVACY_LIST_RECEIVED', account, (name, rules)) - if account not in self.instances: - return - name = data[0] - rules = data[1] - if 'privacy_list_%s' % name in self.instances[account]: - self.instances[account]['privacy_list_%s' % name].\ - privacy_list_received(rules) - if name == 'block': - gajim.connections[account].blocked_contacts = [] - gajim.connections[account].blocked_groups = [] - gajim.connections[account].blocked_list = [] - gajim.connections[account].blocked_all = False - for rule in rules: - if not 'type' in rule: - gajim.connections[account].blocked_all = True - elif rule['type'] == 'jid' and rule['action'] == 'deny': - gajim.connections[account].blocked_contacts.append(rule['value']) - elif rule['type'] == 'group' and rule['action'] == 'deny': - gajim.connections[account].blocked_groups.append(rule['value']) - gajim.connections[account].blocked_list.append(rule) - #elif rule['type'] == "group" and action == "deny": - # text_item = _('%s group "%s"') % _(rule['action']), rule['value'] - # self.store.append([text_item]) - # self.global_rules.append(rule) - #else: - # self.global_rules_to_append.append(rule) - if 'blocked_contacts' in self.instances[account]: - self.instances[account]['blocked_contacts'].\ - privacy_list_received(rules) - - def handle_event_privacy_lists_active_default(self, account, data): - if not data: - return - # Send to all privacy_list_* windows as we can't know which one asked - for win in self.instances[account]: - if win.startswith('privacy_list_'): - self.instances[account][win].check_active_default(data) - - def handle_event_privacy_list_removed(self, account, name): - # ('PRIVACY_LISTS_REMOVED', account, name) - if account not in self.instances: - return - if 'privacy_lists' in self.instances[account]: - self.instances[account]['privacy_lists'].privacy_list_removed(name) - - def handle_event_zc_name_conflict(self, account, data): - def on_ok(new_name): - gajim.config.set_per('accounts', account, 'name', new_name) - status = gajim.connections[account].status - gajim.connections[account].username = new_name - gajim.connections[account].change_status(status, '') - def on_cancel(): - gajim.connections[account].change_status('offline','') - - dlg = dialogs.InputDialog(_('Username Conflict'), - _('Please type a new username for your local account'), input_str=data, - is_modal=True, ok_handler=on_ok, cancel_handler=on_cancel) - - def handle_event_ping_sent(self, account, contact): - if contact.jid == contact.get_full_jid(): - # If contact is a groupchat user - jids = [contact.jid] - else: - jids = [contact.jid, contact.get_full_jid()] - for jid in jids: - ctrl = self.msg_win_mgr.get_control(jid, account) - if ctrl: - ctrl.print_conversation(_('Ping?'), 'status') - - def handle_event_ping_reply(self, account, data): - contact = data[0] - seconds = data[1] - if contact.jid == contact.get_full_jid(): - # If contact is a groupchat user - jids = [contact.jid] - else: - jids = [contact.jid, contact.get_full_jid()] - for jid in jids: - ctrl = self.msg_win_mgr.get_control(jid, account) - if ctrl: - ctrl.print_conversation(_('Pong! (%s s.)') % seconds, 'status') - - def handle_event_ping_error(self, account, contact): - if contact.jid == contact.get_full_jid(): - # If contact is a groupchat user - jids = [contact.jid] - else: - jids = [contact.jid, contact.get_full_jid()] - for jid in jids: - ctrl = self.msg_win_mgr.get_control(jid, account) - if ctrl: - ctrl.print_conversation(_('Error.'), 'status') - - def handle_event_search_form(self, account, data): - # ('SEARCH_FORM', account, (jid, dataform, is_dataform)) - if data[0] not in self.instances[account]['search']: - return - self.instances[account]['search'][data[0]].on_form_arrived(data[1], - data[2]) - - def handle_event_search_result(self, account, data): - # ('SEARCH_RESULT', account, (jid, dataform, is_dataform)) - if data[0] not in self.instances[account]['search']: - return - self.instances[account]['search'][data[0]].on_result_arrived(data[1], - data[2]) - - def handle_event_resource_conflict(self, account, data): - # ('RESOURCE_CONFLICT', account, ()) - # First we go offline, but we don't overwrite status message - self.roster.send_status(account, 'offline', - gajim.connections[account].status) - def on_ok(new_resource): - gajim.config.set_per('accounts', account, 'resource', new_resource) - self.roster.send_status(account, gajim.connections[account].old_show, - gajim.connections[account].status) - proposed_resource = gajim.connections[account].server_resource - proposed_resource += gajim.config.get('gc_proposed_nick_char') - dlg = dialogs.ResourceConflictDialog(_('Resource Conflict'), - _('You are already connected to this account with the same resource. ' - 'Please type a new one'), resource=proposed_resource, ok_handler=on_ok) - - def handle_event_jingle_incoming(self, account, data): - # ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type, - # data...)) - # TODO: conditional blocking if peer is not in roster - - # unpack data - peerjid, sid, contents = data - content_types = set(c[0] for c in contents) - - # check type of jingle session - if 'audio' in content_types or 'video' in content_types: - # a voip session... - # we now handle only voip, so the only thing we will do here is - # not to return from function - pass - else: - # unknown session type... it should be declined in common/jingle.py - return - - jid = gajim.get_jid_without_resource(peerjid) - resource = gajim.get_resource_from_jid(peerjid) - ctrl = self.msg_win_mgr.get_control(peerjid, account) - if not ctrl: - ctrl = self.msg_win_mgr.get_control(jid, account) - if ctrl: - if 'audio' in content_types: - ctrl.set_audio_state('connection_received', sid) - if 'video' in content_types: - ctrl.set_video_state('connection_received', sid) - - dlg = dialogs.VoIPCallReceivedDialog.get_dialog(peerjid, sid) - if dlg: - dlg.add_contents(content_types) - return - - if helpers.allow_popup_window(account): - dialogs.VoIPCallReceivedDialog(account, peerjid, sid, content_types) - return - - self.add_event(account, peerjid, 'jingle-incoming', (peerjid, sid, - content_types)) - - if helpers.allow_showing_notification(account): - # TODO: we should use another pixmap ;-) - img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'ft_request.png') - txt = _('%s wants to start a voice chat.') % gajim.get_name_from_jid( - account, peerjid) - path = gtkgui_helpers.get_path_to_generic_or_avatar(img) - event_type = _('Voice Chat Request') - notify.popup(event_type, peerjid, account, 'jingle-incoming', - path_to_image = path, title = event_type, text = txt) - - def handle_event_jingle_connected(self, account, data): - # ('JINGLE_CONNECTED', account, (peerjid, sid, media)) - peerjid, sid, media = data - if media in ('audio', 'video'): - jid = gajim.get_jid_without_resource(peerjid) - resource = gajim.get_resource_from_jid(peerjid) - ctrl = self.msg_win_mgr.get_control(peerjid, account) - if not ctrl: - ctrl = self.msg_win_mgr.get_control(jid, account) - if ctrl: - if media == 'audio': - ctrl.set_audio_state('connected', sid) - else: - ctrl.set_video_state('connected', sid) - - def handle_event_jingle_disconnected(self, account, data): - # ('JINGLE_DISCONNECTED', account, (peerjid, sid, reason)) - peerjid, sid, media, reason = data - jid = gajim.get_jid_without_resource(peerjid) - resource = gajim.get_resource_from_jid(peerjid) - ctrl = self.msg_win_mgr.get_control(peerjid, account) - if not ctrl: - ctrl = self.msg_win_mgr.get_control(jid, account) - if ctrl: - if media in ('audio', None): - ctrl.set_audio_state('stop', sid=sid, reason=reason) - if media in ('video', None): - ctrl.set_video_state('stop', sid=sid, reason=reason) - dialog = dialogs.VoIPCallReceivedDialog.get_dialog(peerjid, sid) - if dialog: - dialog.dialog.destroy() - - def handle_event_jingle_error(self, account, data): - # ('JINGLE_ERROR', account, (peerjid, sid, reason)) - peerjid, sid, reason = data - jid = gajim.get_jid_without_resource(peerjid) - resource = gajim.get_resource_from_jid(peerjid) - ctrl = self.msg_win_mgr.get_control(peerjid, account) - if not ctrl: - ctrl = self.msg_win_mgr.get_control(jid, account) - if ctrl: - ctrl.set_audio_state('error', reason=reason) - - def handle_event_pep_config(self, account, data): - # ('PEP_CONFIG', account, (node, form)) - if 'pep_services' in self.instances[account]: - self.instances[account]['pep_services'].config(data[0], data[1]) - - def handle_event_roster_item_exchange(self, account, data): - # data = (action in [add, delete, modify], exchange_list, jid_from) - dialogs.RosterItemExchangeWindow(account, data[0], data[1], data[2]) - - def handle_event_unique_room_id_supported(self, account, data): - """ - Receive confirmation that unique_room_id are supported - """ - # ('UNIQUE_ROOM_ID_SUPPORTED', server, instance, room_id) - instance = data[1] - instance.unique_room_id_supported(data[0], data[2]) - - def handle_event_unique_room_id_unsupported(self, account, data): - # ('UNIQUE_ROOM_ID_UNSUPPORTED', server, instance) - instance = data[1] - instance.unique_room_id_error(data[0]) - - def handle_event_ssl_error(self, account, data): - # ('SSL_ERROR', account, (text, errnum, cert, sha1_fingerprint)) - server = gajim.config.get_per('accounts', account, 'hostname') - - def on_ok(is_checked): - del self.instances[account]['online_dialog']['ssl_error'] - if is_checked[0]: - # Check if cert is already in file - certs = '' - if os.path.isfile(gajim.MY_CACERTS): - f = open(gajim.MY_CACERTS) - certs = f.read() - f.close() - if data[2] in certs: - dialogs.ErrorDialog(_('Certificate Already in File'), - _('This certificate is already in file %s, so it\'s not added again.') % gajim.MY_CACERTS) - else: - f = open(gajim.MY_CACERTS, 'a') - f.write(server + '\n') - f.write(data[2] + '\n\n') - f.close() - gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1', - data[3]) - if is_checked[1]: - ignore_ssl_errors = gajim.config.get_per('accounts', account, - 'ignore_ssl_errors').split() - ignore_ssl_errors.append(str(data[1])) - gajim.config.set_per('accounts', account, 'ignore_ssl_errors', - ' '.join(ignore_ssl_errors)) - gajim.connections[account].ssl_certificate_accepted() - - def on_cancel(): - del self.instances[account]['online_dialog']['ssl_error'] - gajim.connections[account].disconnect(on_purpose=True) - self.handle_event_status(account, 'offline') - - pritext = _('Error verifying SSL certificate') - 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]} - if data[1] in (18, 27): - checktext1 = _('Add this certificate to the list of trusted certificates.\nSHA1 fingerprint of the certificate:\n%s') % data[3] - else: - checktext1 = '' - checktext2 = _('Ignore this error for this certificate.') - if 'ssl_error' in self.instances[account]['online_dialog']: - self.instances[account]['online_dialog']['ssl_error'].destroy() - self.instances[account]['online_dialog']['ssl_error'] = \ - dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, checktext1, - checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel) - - def handle_event_fingerprint_error(self, account, data): - # ('FINGERPRINT_ERROR', account, (new_fingerprint,)) - def on_yes(is_checked): - del self.instances[account]['online_dialog']['fingerprint_error'] - gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1', - data[0]) - # Reset the ignored ssl errors - gajim.config.set_per('accounts', account, 'ignore_ssl_errors', '') - gajim.connections[account].ssl_certificate_accepted() - def on_no(): - del self.instances[account]['online_dialog']['fingerprint_error'] - gajim.connections[account].disconnect(on_purpose=True) - self.handle_event_status(account, 'offline') - pritext = _('SSL certificate error') - sectext = _('It seems the SSL certificate of account %(account)s has ' - 'changed or your connection is being hacked.\nOld fingerprint: %(old)s' - '\nNew fingerprint: %(new)s\n\nDo you still want to connect and update' - ' the fingerprint of the certificate?') % {'account': account, - 'old': gajim.config.get_per('accounts', account, - 'ssl_fingerprint_sha1'), 'new': data[0]} - if 'fingerprint_error' in self.instances[account]['online_dialog']: - self.instances[account]['online_dialog']['fingerprint_error'].destroy() - self.instances[account]['online_dialog']['fingerprint_error'] = \ - dialogs.YesNoDialog(pritext, sectext, on_response_yes=on_yes, - on_response_no=on_no) - - def handle_event_plain_connection(self, account, data): - # ('PLAIN_CONNECTION', account, (connection)) - server = gajim.config.get_per('accounts', account, 'hostname') - def on_ok(is_checked): - if not is_checked[0]: - on_cancel() - return - # On cancel call del self.instances, so don't call it another time - # before - del self.instances[account]['online_dialog']['plain_connection'] - if is_checked[1]: - gajim.config.set_per('accounts', account, - 'warn_when_plaintext_connection', False) - gajim.connections[account].connection_accepted(data[0], 'plain') - def on_cancel(): - del self.instances[account]['online_dialog']['plain_connection'] - gajim.connections[account].disconnect(on_purpose=True) - self.handle_event_status(account, 'offline') - pritext = _('Insecure connection') - sectext = _('You are about to send your password on an unencrypted ' - 'connection. Are you sure you want to do that?') - checktext1 = _('Yes, I really want to connect insecurely') - checktext2 = _('Do _not ask me again') - if 'plain_connection' in self.instances[account]['online_dialog']: - self.instances[account]['online_dialog']['plain_connection'].destroy() - self.instances[account]['online_dialog']['plain_connection'] = \ - dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, - checktext1, checktext2, on_response_ok=on_ok, - on_response_cancel=on_cancel, is_modal=False) - - def handle_event_insecure_ssl_connection(self, account, data): - # ('INSECURE_SSL_CONNECTION', account, (connection, connection_type)) - server = gajim.config.get_per('accounts', account, 'hostname') - def on_ok(is_checked): - if not is_checked[0]: - on_cancel() - return - del self.instances[account]['online_dialog']['insecure_ssl'] - if is_checked[1]: - gajim.config.set_per('accounts', account, - 'warn_when_insecure_ssl_connection', False) - if gajim.connections[account].connected == 0: - # We have been disconnecting (too long time since window is opened) - # re-connect with auto-accept - gajim.connections[account].connection_auto_accepted = True - show, msg = gajim.connections[account].continue_connect_info[:2] - self.roster.send_status(account, show, msg) - return - gajim.connections[account].connection_accepted(data[0], data[1]) - def on_cancel(): - del self.instances[account]['online_dialog']['insecure_ssl'] - gajim.connections[account].disconnect(on_purpose=True) - self.handle_event_status(account, 'offline') - pritext = _('Insecure connection') - sectext = _('You are about to send your password on an insecure ' - 'connection. You should install PyOpenSSL to prevent that. Are you sure you want to do that?') - checktext1 = _('Yes, I really want to connect insecurely') - checktext2 = _('Do _not ask me again') - if 'insecure_ssl' in self.instances[account]['online_dialog']: - self.instances[account]['online_dialog']['insecure_ssl'].destroy() - self.instances[account]['online_dialog']['insecure_ssl'] = \ - dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, - checktext1, checktext2, on_response_ok=on_ok, - on_response_cancel=on_cancel, is_modal=False) - - def handle_event_pubsub_node_removed(self, account, data): - # ('PUBSUB_NODE_REMOVED', account, (jid, node)) - if 'pep_services' in self.instances[account]: - if data[0] == gajim.get_jid_from_account(account): - self.instances[account]['pep_services'].node_removed(data[1]) - - def handle_event_pubsub_node_not_removed(self, account, data): - # ('PUBSUB_NODE_NOT_REMOVED', account, (jid, node, msg)) - if data[0] == gajim.get_jid_from_account(account): - dialogs.WarningDialog(_('PEP node was not removed'), - _('PEP node %(node)s was not removed: %(message)s') % { - 'node': data[1], 'message': data[2]}) - - def handle_event_pep_received(self, account, data): - # ('PEP_RECEIVED', account, (jid, pep_type)) - jid = data[0] - pep_type = data[1] - ctrl = common.gajim.interface.msg_win_mgr.get_control(jid, account) - - if jid == common.gajim.get_jid_from_account(account): - self.roster.draw_account(account) - - if pep_type == 'nickname': - self.roster.draw_contact(jid, account) - if ctrl: - ctrl.update_ui() - win = ctrl.parent_win - win.redraw_tab(ctrl) - win.show_title() - else: - self.roster.draw_pep(jid, account, pep_type) - if ctrl: - ctrl.update_pep(pep_type) - - def handle_event_archiving_changed(self, account, data): - if 'archiving_preferences' in self.instances[account]: - self.instances[account]['archiving_preferences'].archiving_changed( - data) - - def handle_event_archiving_error(self, account, data): - if 'archiving_preferences' in self.instances[account]: - self.instances[account]['archiving_preferences'].archiving_error( - data) - - def register_handler(self, event, handler): - if event not in self.handlers: - self.handlers[event] = [] - - if handler not in self.handlers[event]: - self.handlers[event].append(handler) - - def unregister_handler(self, event, handler): - self.handlers[event].remove(handler) - - def register_handlers(self): - self.handlers = { - 'ROSTER': [self.handle_event_roster], - 'WARNING': [self.handle_event_warning], - 'ERROR': [self.handle_event_error], - 'INFORMATION': [self.handle_event_information], - 'ERROR_ANSWER': [self.handle_event_error_answer], - 'STATUS': [self.handle_event_status], - 'NEW_JID': [self.handle_event_new_jid], - 'NOTIFY': [self.handle_event_notify], - 'MSGERROR': [self.handle_event_msgerror], - 'MSGSENT': [self.handle_event_msgsent], - 'MSGNOTSENT': [self.handle_event_msgnotsent], - 'SUBSCRIBED': [self.handle_event_subscribed], - 'UNSUBSCRIBED': [self.handle_event_unsubscribed], - 'SUBSCRIBE': [self.handle_event_subscribe], - 'AGENT_ERROR_INFO': [self.handle_event_agent_info_error], - 'AGENT_ERROR_ITEMS': [self.handle_event_agent_items_error], - 'AGENT_REMOVED': [self.handle_event_agent_removed], - 'REGISTER_AGENT_INFO': [self.handle_event_register_agent_info], - 'AGENT_INFO_ITEMS': [self.handle_event_agent_info_items], - 'AGENT_INFO_INFO': [self.handle_event_agent_info_info], - 'QUIT': [self.handle_event_quit], - 'NEW_ACC_CONNECTED': [self.handle_event_new_acc_connected], - 'NEW_ACC_NOT_CONNECTED': [self.handle_event_new_acc_not_connected], - 'ACC_OK': [self.handle_event_acc_ok], - 'ACC_NOT_OK': [self.handle_event_acc_not_ok], - 'MYVCARD': [self.handle_event_myvcard], - 'VCARD': [self.handle_event_vcard], - 'LAST_STATUS_TIME': [self.handle_event_last_status_time], - 'OS_INFO': [self.handle_event_os_info], - 'ENTITY_TIME': [self.handle_event_entity_time], - 'GC_NOTIFY': [self.handle_event_gc_notify], - 'GC_MSG': [self.handle_event_gc_msg], - 'GC_SUBJECT': [self.handle_event_gc_subject], - 'GC_CONFIG': [self.handle_event_gc_config], - 'GC_CONFIG_CHANGE': [self.handle_event_gc_config_change], - 'GC_INVITATION': [self.handle_event_gc_invitation], - 'GC_AFFILIATION': [self.handle_event_gc_affiliation], - 'GC_PASSWORD_REQUIRED': [self.handle_event_gc_password_required], - 'BAD_PASSPHRASE': [self.handle_event_bad_passphrase], - 'ROSTER_INFO': [self.handle_event_roster_info], - 'BOOKMARKS': [self.handle_event_bookmarks], - 'CON_TYPE': [self.handle_event_con_type], - 'CONNECTION_LOST': [self.handle_event_connection_lost], - 'FILE_REQUEST': [self.handle_event_file_request], - 'GMAIL_NOTIFY': [self.handle_event_gmail_notify], - 'FILE_REQUEST_ERROR': [self.handle_event_file_request_error], - 'FILE_SEND_ERROR': [self.handle_event_file_send_error], - 'STANZA_ARRIVED': [self.handle_event_stanza_arrived], - 'STANZA_SENT': [self.handle_event_stanza_sent], - 'HTTP_AUTH': [self.handle_event_http_auth], - 'VCARD_PUBLISHED': [self.handle_event_vcard_published], - 'VCARD_NOT_PUBLISHED': [self.handle_event_vcard_not_published], - 'ASK_NEW_NICK': [self.handle_event_ask_new_nick], - 'SIGNED_IN': [self.handle_event_signed_in], - 'METACONTACTS': [self.handle_event_metacontacts], - 'ATOM_ENTRY': [self.handle_atom_entry], - 'FAILED_DECRYPT': [self.handle_event_failed_decrypt], - 'PRIVACY_LISTS_RECEIVED': [self.handle_event_privacy_lists_received], - 'PRIVACY_LIST_RECEIVED': [self.handle_event_privacy_list_received], - 'PRIVACY_LISTS_ACTIVE_DEFAULT': \ - [self.handle_event_privacy_lists_active_default], - 'PRIVACY_LIST_REMOVED': [self.handle_event_privacy_list_removed], - 'ZC_NAME_CONFLICT': [self.handle_event_zc_name_conflict], - 'PING_SENT': [self.handle_event_ping_sent], - 'PING_REPLY': [self.handle_event_ping_reply], - 'PING_ERROR': [self.handle_event_ping_error], - 'SEARCH_FORM': [self.handle_event_search_form], - 'SEARCH_RESULT': [self.handle_event_search_result], - 'RESOURCE_CONFLICT': [self.handle_event_resource_conflict], - 'ROSTERX': [self.handle_event_roster_item_exchange], - 'PEP_CONFIG': [self.handle_event_pep_config], - 'UNIQUE_ROOM_ID_UNSUPPORTED': \ - [self.handle_event_unique_room_id_unsupported], - 'UNIQUE_ROOM_ID_SUPPORTED': [self.handle_event_unique_room_id_supported], - 'GPG_PASSWORD_REQUIRED': [self.handle_event_gpg_password_required], - 'GPG_ALWAYS_TRUST': [self.handle_event_gpg_always_trust], - 'PASSWORD_REQUIRED': [self.handle_event_password_required], - 'SSL_ERROR': [self.handle_event_ssl_error], - 'FINGERPRINT_ERROR': [self.handle_event_fingerprint_error], - 'PLAIN_CONNECTION': [self.handle_event_plain_connection], - 'INSECURE_SSL_CONNECTION': [self.handle_event_insecure_ssl_connection], - 'PUBSUB_NODE_REMOVED': [self.handle_event_pubsub_node_removed], - 'PUBSUB_NODE_NOT_REMOVED': [self.handle_event_pubsub_node_not_removed], - 'JINGLE_INCOMING': [self.handle_event_jingle_incoming], - 'JINGLE_CONNECTED': [self.handle_event_jingle_connected], - 'JINGLE_DISCONNECTED': [self.handle_event_jingle_disconnected], - 'JINGLE_ERROR': [self.handle_event_jingle_error], - 'PEP_RECEIVED': [self.handle_event_pep_received], - 'ARCHIVING_CHANGED': [self.handle_event_archiving_changed], - 'ARCHIVING_ERROR': [self.handle_event_archiving_error], - } - - def dispatch(self, event, account, data): - """ - Dispatch an network event to the event handlers of this class. Return - true if it could be dispatched to alteast one handler - """ - if event not in self.handlers: - log.warning('Unknown event %s dispatched to GUI: %s' % (event, data)) - return False - else: - log.debug('Event %s distpached to GUI: %s' % (event, data)) - for handler in self.handlers[event]: - handler(account, data) - return len(self.handlers[event]) - + def handle_event_roster(self, account, data): + #('ROSTER', account, array) + # FIXME: Those methods depend to highly on each other + # and the order in which they are called + self.roster.fill_contacts_and_groups_dicts(data, account) + self.roster.add_account_contacts(account) + self.roster.fire_up_unread_messages_events(account) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('Roster', (account, data)) + + def handle_event_warning(self, unused, data): + #('WARNING', account, (title_text, section_text)) + dialogs.WarningDialog(data[0], data[1]) + + def handle_event_error(self, unused, data): + #('ERROR', account, (title_text, section_text)) + dialogs.ErrorDialog(data[0], data[1]) + + def handle_event_information(self, unused, data): + #('INFORMATION', account, (title_text, section_text)) + dialogs.InformationDialog(data[0], data[1]) + + def handle_event_ask_new_nick(self, account, data): + #('ASK_NEW_NICK', account, (room_jid,)) + room_jid = data[0] + title = _('Unable to join group chat') + prompt = _('Your desired nickname in group chat %s is in use or ' + 'registered by another occupant.\nPlease specify another nickname ' + 'below:') % room_jid + check_text = _('Always use this nickname when there is a conflict') + if 'change_nick_dialog' in self.instances: + self.instances['change_nick_dialog'].add_room(account, room_jid, + prompt) + else: + self.instances['change_nick_dialog'] = dialogs.ChangeNickDialog( + account, room_jid, title, prompt) + + def handle_event_http_auth(self, account, data): + #('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg)) + def response(account, iq_obj, answer): + self.dialog.destroy() + gajim.connections[account].build_http_auth_answer(iq_obj, answer) + + def on_yes(is_checked, account, iq_obj): + response(account, iq_obj, 'yes') + + sec_msg = _('Do you accept this request?') + if gajim.get_number_of_connected_accounts() > 1: + sec_msg = _('Do you accept this request on account %s?') % account + if data[4]: + sec_msg = data[4] + '\n' + sec_msg + self.dialog = dialogs.YesNoDialog(_('HTTP (%(method)s) Authorization for ' + '%(url)s (id: %(id)s)') % {'method': data[0], 'url': data[1], + 'id': data[2]}, sec_msg, on_response_yes=(on_yes, account, data[3]), + on_response_no=(response, account, data[3], 'no')) + + def handle_event_error_answer(self, account, array): + #('ERROR_ANSWER', account, (id, jid_from, errmsg, errcode)) + id_, jid_from, errmsg, errcode = array + if unicode(errcode) in ('400', '403', '406') and id_: + # show the error dialog + ft = self.instances['file_transfers'] + sid = id_ + if len(id_) > 3 and id_[2] == '_': + sid = id_[3:] + if sid in ft.files_props['s']: + file_props = ft.files_props['s'][sid] + if unicode(errcode) == '400': + file_props['error'] = -3 + else: + file_props['error'] = -4 + self.handle_event_file_request_error(account, + (jid_from, file_props, errmsg)) + conn = gajim.connections[account] + conn.disconnect_transfer(file_props) + return + elif unicode(errcode) == '404': + conn = gajim.connections[account] + sid = id_ + if len(id_) > 3 and id_[2] == '_': + sid = id_[3:] + if sid in conn.files_props: + file_props = conn.files_props[sid] + self.handle_event_file_send_error(account, + (jid_from, file_props)) + conn.disconnect_transfer(file_props) + return + + ctrl = self.msg_win_mgr.get_control(jid_from, account) + if ctrl and ctrl.type_id == message_control.TYPE_GC: + ctrl.print_conversation('Error %s: %s' % (array[2], array[1])) + + def handle_event_con_type(self, account, con_type): + # ('CON_TYPE', account, con_type) which can be 'ssl', 'tls', 'plain' + gajim.con_types[account] = con_type + self.roster.draw_account(account) + + def handle_event_connection_lost(self, account, array): + # ('CONNECTION_LOST', account, [title, text]) + path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', + 'connection_lost.png') + path = gtkgui_helpers.get_path_to_generic_or_avatar(path) + notify.popup(_('Connection Failed'), account, account, + 'connection_failed', path, array[0], array[1]) + + def unblock_signed_in_notifications(self, account): + gajim.block_signed_in_notifications[account] = False + + def handle_event_status(self, account, show): # OUR status + #('STATUS', account, show) + model = self.roster.status_combobox.get_model() + if show in ('offline', 'error'): + for name in self.instances[account]['online_dialog'].keys(): + # .keys() is needed to not have a dictionary length changed during + # iteration error + self.instances[account]['online_dialog'][name].destroy() + del self.instances[account]['online_dialog'][name] + for request in self.gpg_passphrase.values(): + if request: + request.interrupt() + # .keys() is needed because dict changes during loop + for account in self.pass_dialog.keys(): + self.pass_dialog[account].window.destroy() + if show == 'offline': + # sensitivity for this menuitem + if gajim.get_number_of_connected_accounts() == 0: + model[self.roster.status_message_menuitem_iter][3] = False + gajim.block_signed_in_notifications[account] = True + else: + # 30 seconds after we change our status to sth else than offline + # we stop blocking notifications of any kind + # this prevents from getting the roster items as 'just signed in' + # contacts. 30 seconds should be enough time + gobject.timeout_add_seconds(30, self.unblock_signed_in_notifications, account) + # sensitivity for this menuitem + model[self.roster.status_message_menuitem_iter][3] = True + + # Inform all controls for this account of the connection state change + ctrls = self.msg_win_mgr.get_controls() + if account in self.minimized_controls: + # Can not be the case when we remove account + ctrls += self.minimized_controls[account].values() + for ctrl in ctrls: + if ctrl.account == account: + if show == 'offline' or (show == 'invisible' and \ + gajim.connections[account].is_zeroconf): + ctrl.got_disconnected() + else: + # Other code rejoins all GCs, so we don't do it here + if not ctrl.type_id == message_control.TYPE_GC: + ctrl.got_connected() + if ctrl.parent_win: + ctrl.parent_win.redraw_tab(ctrl) + + self.roster.on_status_changed(account, show) + if account in self.show_vcard_when_connect and show not in ('offline', + 'error'): + self.edit_own_details(account) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('AccountPresence', (show, account)) + + def handle_event_new_jid(self, account, data): + #('NEW_JID', account, (old_jid, new_jid)) + """ + This event is raised when our JID changed (most probably because we use + anonymous account. We update contact and roster entry in this case + """ + self.roster.rename_self_contact(data[0], data[1], account) + + def edit_own_details(self, account): + jid = gajim.get_jid_from_account(account) + if 'profile' not in self.instances[account]: + self.instances[account]['profile'] = \ + profile_window.ProfileWindow(account) + gajim.connections[account].request_vcard(jid) + + def handle_event_notify(self, account, array): + # 'NOTIFY' (account, (jid, status, status message, resource, + # priority, # keyID, timestamp, contact_nickname)) + # + # Contact changed show + + # FIXME: Drop and rewrite... + + statuss = ['offline', 'error', 'online', 'chat', 'away', 'xa', 'dnd', + 'invisible'] + # Ignore invalid show + if array[1] not in statuss: + return + old_show = 0 + new_show = statuss.index(array[1]) + status_message = array[2] + jid = array[0].split('/')[0] + keyID = array[5] + contact_nickname = array[7] + + # Get the proper keyID + keyID = helpers.prepare_and_validate_gpg_keyID(account, jid, keyID) + + resource = array[3] + if not resource: + resource = '' + priority = array[4] + if gajim.jid_is_transport(jid): + # It must be an agent + ji = jid.replace('@', '') + else: + ji = jid + + highest = gajim.contacts. \ + get_contact_with_highest_priority(account, jid) + was_highest = (highest and highest.resource == resource) + + conn = gajim.connections[account] + + # Update contact + jid_list = gajim.contacts.get_jid_list(account) + if ji in jid_list or jid == gajim.get_jid_from_account(account): + lcontact = gajim.contacts.get_contacts(account, ji) + contact1 = None + resources = [] + for c in lcontact: + resources.append(c.resource) + if c.resource == resource: + contact1 = c + break + + if contact1: + if contact1.show in statuss: + old_show = statuss.index(contact1.show) + # nick changed + if contact_nickname is not None and \ + contact1.contact_name != contact_nickname: + contact1.contact_name = contact_nickname + self.roster.draw_contact(jid, account) + + if old_show == new_show and contact1.status == status_message and \ + contact1.priority == priority: # no change + return + else: + contact1 = gajim.contacts.get_first_contact_from_jid(account, ji) + if not contact1: + # Presence of another resource of our + # jid + # Create self contact and add to roster + if resource == conn.server_resource: + return + # Ignore offline presence of unknown self resource + if new_show < 2: + return + contact1 = gajim.contacts.create_self_contact(jid=ji, + account=account, show=array[1], status=status_message, + priority=priority, keyID=keyID, resource=resource) + old_show = 0 + gajim.contacts.add_contact(account, contact1) + lcontact.append(contact1) + elif contact1.show in statuss: + old_show = statuss.index(contact1.show) + if (resources != [''] and (len(lcontact) != 1 or \ + lcontact[0].show != 'offline')) and jid.find('@') > 0: + # Another resource of an existing contact connected + old_show = 0 + contact1 = gajim.contacts.copy_contact(contact1) + lcontact.append(contact1) + contact1.resource = resource + + self.roster.add_contact(contact1.jid, account) + + if contact1.jid.find('@') > 0 and len(lcontact) == 1: + # It's not an agent + if old_show == 0 and new_show > 1: + if not contact1.jid in gajim.newly_added[account]: + gajim.newly_added[account].append(contact1.jid) + if contact1.jid in gajim.to_be_removed[account]: + gajim.to_be_removed[account].remove(contact1.jid) + gobject.timeout_add_seconds(5, self.roster.remove_newly_added, + contact1.jid, account) + elif old_show > 1 and new_show == 0 and conn.connected > 1: + if not contact1.jid in gajim.to_be_removed[account]: + gajim.to_be_removed[account].append(contact1.jid) + if contact1.jid in gajim.newly_added[account]: + gajim.newly_added[account].remove(contact1.jid) + self.roster.draw_contact(contact1.jid, account) + gobject.timeout_add_seconds(5, self.roster.remove_to_be_removed, + contact1.jid, account) + + # unset custom status + if (old_show == 0 and new_show > 1) or (old_show > 1 and new_show == 0\ + and conn.connected > 1): + if account in self.status_sent_to_users and \ + jid in self.status_sent_to_users[account]: + del self.status_sent_to_users[account][jid] + + contact1.show = array[1] + contact1.status = status_message + contact1.priority = priority + contact1.keyID = keyID + timestamp = array[6] + if timestamp: + contact1.last_status_time = timestamp + elif not gajim.block_signed_in_notifications[account]: + # We're connected since more that 30 seconds + contact1.last_status_time = time.localtime() + contact1.contact_nickname = contact_nickname + + if gajim.jid_is_transport(jid): + # It must be an agent + if ji in jid_list: + # Update existing iter and group counting + self.roster.draw_contact(ji, account) + self.roster.draw_group(_('Transports'), account) + if new_show > 1 and ji in gajim.transport_avatar[account]: + # transport just signed in. + # request avatars + for jid_ in gajim.transport_avatar[account][ji]: + conn.request_vcard(jid_) + # transport just signed in/out, don't show + # popup notifications for 30s + account_ji = account + '/' + ji + gajim.block_signed_in_notifications[account_ji] = True + gobject.timeout_add_seconds(30, + self.unblock_signed_in_notifications, account_ji) + locations = (self.instances, self.instances[account]) + for location in locations: + if 'add_contact' in location: + if old_show == 0 and new_show > 1: + location['add_contact'].transport_signed_in(jid) + break + elif old_show > 1 and new_show == 0: + location['add_contact'].transport_signed_out(jid) + break + elif ji in jid_list: + # It isn't an agent + # reset chatstate if needed: + # (when contact signs out or has errors) + if array[1] in ('offline', 'error'): + contact1.our_chatstate = contact1.chatstate = \ + contact1.composing_xep = None + + # TODO: This causes problems when another + # resource signs off! + conn.remove_transfers_for_contact(contact1) + + # disable encryption, since if any messages are + # lost they'll be not decryptable (note that + # this contradicts XEP-0201 - trying to get that + # in the XEP, though) + + # there won't be any sessions here if the contact terminated + # their sessions before going offline (which we do) + for sess in conn.get_sessions(ji): + if (ji+'/'+resource) != str(sess.jid): + continue + if sess.control: + sess.control.no_autonegotiation = False + if sess.enable_encryption: + sess.terminate_e2e() + conn.delete_session(jid, sess.thread_id) + + self.roster.chg_contact_status(contact1, array[1], status_message, + account) + # Notifications + if old_show < 2 and new_show > 1: + notify.notify('contact_connected', jid, account, status_message) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('ContactPresence', (account, + array)) + + elif old_show > 1 and new_show < 2: + notify.notify('contact_disconnected', jid, account, status_message) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('ContactAbsence', (account, array)) + # FIXME: stop non active file transfers + # Status change (not connected/disconnected or + # error (<1)) + elif new_show > 1: + notify.notify('status_change', jid, account, [new_show, + status_message]) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('ContactStatus', (account, array)) + else: + # FIXME: MSN transport (CMSN1.2.1 and PyMSN) don't + # follow the XEP, still the case in 2008. + # It's maybe a GC_NOTIFY (specialy for MSN gc) + self.handle_event_gc_notify(account, (jid, array[1], status_message, + array[3], None, None, None, None, None, [], None, None)) + + highest = gajim.contacts.get_contact_with_highest_priority(account, jid) + is_highest = (highest and highest.resource == resource) + + # disconnect the session from the ctrl if the highest resource has changed + if (was_highest and not is_highest) or (not was_highest and is_highest): + ctrl = self.msg_win_mgr.get_control(jid, account) + + if ctrl: + ctrl.no_autonegotiation = False + ctrl.set_session(None) + ctrl.contact = highest + + def handle_event_msgerror(self, account, array): + #'MSGERROR' (account, (jid, error_code, error_msg, msg, time[, session])) + full_jid_with_resource = array[0] + jids = full_jid_with_resource.split('/', 1) + jid = jids[0] + + if array[1] == '503': + # If we get server-not-found error, stop sending chatstates + for contact in gajim.contacts.get_contacts(account, jid): + contact.composing_xep = False + + session = None + if len(array) > 5: + session = array[5] + + gc_control = self.msg_win_mgr.get_gc_control(jid, account) + if not gc_control and \ + jid in self.minimized_controls[account]: + gc_control = self.minimized_controls[account][jid] + if gc_control and gc_control.type_id != message_control.TYPE_GC: + gc_control = None + if gc_control: + if len(jids) > 1: # it's a pm + nick = jids[1] + + if session: + ctrl = session.control + else: + ctrl = self.msg_win_mgr.get_control(full_jid_with_resource, account) + + if not ctrl: + tv = gc_control.list_treeview + model = tv.get_model() + iter_ = gc_control.get_contact_iter(nick) + if iter_: + show = model[iter_][3] + else: + show = 'offline' + gc_c = gajim.contacts.create_gc_contact(room_jid=jid, account=account, + name=nick, show=show) + ctrl = self.new_private_chat(gc_c, account, session) + + ctrl.print_conversation(_('Error %(code)s: %(msg)s') % { + 'code': array[1], 'msg': array[2]}, 'status') + return + + gc_control.print_conversation(_('Error %(code)s: %(msg)s') % { + 'code': array[1], 'msg': array[2]}, 'status') + if gc_control.parent_win and gc_control.parent_win.get_active_jid() == jid: + gc_control.set_subject(gc_control.subject) + return + + if gajim.jid_is_transport(jid): + jid = jid.replace('@', '') + msg = array[2] + if array[3]: + msg = _('error while sending %(message)s ( %(error)s )') % { + 'message': array[3], 'error': msg} + if session: + session.roster_message(jid, msg, array[4], msg_type='error') + + def handle_event_msgsent(self, account, array): + #('MSGSENT', account, (jid, msg, keyID)) + msg = array[1] + # do not play sound when standalone chatstate message (eg no msg) + if msg and gajim.config.get_per('soundevents', 'message_sent', 'enabled'): + helpers.play_sound('message_sent') + + def handle_event_msgnotsent(self, account, array): + #('MSGNOTSENT', account, (jid, ierror_msg, msg, time, session)) + msg = _('error while sending %(message)s ( %(error)s )') % { + 'message': array[2], 'error': array[1]} + if not array[4]: + # No session. This can happen when sending a message from gajim-remote + log.warn(msg) + return + array[4].roster_message(array[0], msg, array[3], account, + msg_type='error') + + def handle_event_subscribe(self, account, array): + #('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172 + if self.remote_ctrl: + self.remote_ctrl.raise_signal('Subscribe', (account, array)) + + jid = array[0] + text = array[1] + nick = array[2] + if helpers.allow_popup_window(account) or not self.systray_enabled: + dialogs.SubscriptionRequestWindow(jid, text, account, nick) + return + + self.add_event(account, jid, 'subscription_request', (text, nick)) + + if helpers.allow_showing_notification(account): + path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', + 'subscription_request.png') + path = gtkgui_helpers.get_path_to_generic_or_avatar(path) + event_type = _('Subscription request') + notify.popup(event_type, jid, account, 'subscription_request', path, + event_type, jid) + + def handle_event_subscribed(self, account, array): + #('SUBSCRIBED', account, (jid, resource)) + jid = array[0] + if jid in gajim.contacts.get_jid_list(account): + c = gajim.contacts.get_first_contact_from_jid(account, jid) + c.resource = array[1] + self.roster.remove_contact_from_groups(c.jid, account, + [_('Not in Roster'), _('Observers')], update=False) + else: + keyID = '' + attached_keys = gajim.config.get_per('accounts', account, + 'attached_gpg_keys').split() + if jid in attached_keys: + keyID = attached_keys[attached_keys.index(jid) + 1] + name = jid.split('@', 1)[0] + name = name.split('%', 1)[0] + contact1 = gajim.contacts.create_contact(jid=jid, account=account, + name=name, groups=[], show='online', status='online', + ask='to', resource=array[1], keyID=keyID) + gajim.contacts.add_contact(account, contact1) + self.roster.add_contact(jid, account) + dialogs.InformationDialog(_('Authorization accepted'), + _('The contact "%s" has authorized you to see his or her status.') + % jid) + if not gajim.config.get_per('accounts', account, 'dont_ack_subscription'): + gajim.connections[account].ack_subscribed(jid) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('Subscribed', (account, array)) + + def show_unsubscribed_dialog(self, account, contact): + def on_yes(is_checked, list_): + self.roster.on_req_usub(None, list_) + list_ = [(contact, account)] + dialogs.YesNoDialog( + _('Contact "%s" removed subscription from you') % contact.jid, + _('You will always see him or her as offline.\nDo you want to ' + 'remove him or her from your contact list?'), + on_response_yes=(on_yes, list_)) + # FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does + # not show deny + + def handle_event_unsubscribed(self, account, jid): + #('UNSUBSCRIBED', account, jid) + gajim.connections[account].ack_unsubscribed(jid) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('Unsubscribed', (account, jid)) + + contact = gajim.contacts.get_first_contact_from_jid(account, jid) + if not contact: + return + + if helpers.allow_popup_window(account) or not self.systray_enabled: + self.show_unsubscribed_dialog(account, contact) + + self.add_event(account, jid, 'unsubscribed', contact) + + if helpers.allow_showing_notification(account): + path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', + 'unsubscribed.png') + path = gtkgui_helpers.get_path_to_generic_or_avatar(path) + event_type = _('Unsubscribed') + notify.popup(event_type, jid, account, 'unsubscribed', path, + event_type, jid) + + def handle_event_agent_info_error(self, account, agent): + #('AGENT_ERROR_INFO', account, (agent)) + try: + gajim.connections[account].services_cache.agent_info_error(agent) + except AttributeError: + return + + def handle_event_agent_items_error(self, account, agent): + #('AGENT_ERROR_INFO', account, (agent)) + try: + gajim.connections[account].services_cache.agent_items_error(agent) + except AttributeError: + return + + def handle_event_agent_removed(self, account, agent): + # remove transport's contacts from treeview + jid_list = gajim.contacts.get_jid_list(account) + for jid in jid_list: + if jid.endswith('@' + agent): + c = gajim.contacts.get_first_contact_from_jid(account, jid) + gajim.log.debug( + 'Removing contact %s due to unregistered transport %s'\ + % (jid, agent)) + gajim.connections[account].unsubscribe(c.jid) + # Transport contacts can't have 2 resources + if c.jid in gajim.to_be_removed[account]: + # This way we'll really remove it + gajim.to_be_removed[account].remove(c.jid) + self.roster.remove_contact(c.jid, account, backend=True) + + def handle_event_register_agent_info(self, account, array): + # ('REGISTER_AGENT_INFO', account, (agent, infos, is_form)) + # info in a dataform if is_form is True + if array[2] or 'instructions' in array[1]: + config.ServiceRegistrationWindow(array[0], array[1], account, + array[2]) + else: + dialogs.ErrorDialog(_('Contact with "%s" cannot be established') \ + % array[0], _('Check your connection or try again later.')) + + def handle_event_agent_info_items(self, account, array): + #('AGENT_INFO_ITEMS', account, (agent, node, items)) + our_jid = gajim.get_jid_from_account(account) + if 'pep_services' in gajim.interface.instances[account] and \ + array[0] == our_jid: + gajim.interface.instances[account]['pep_services'].items_received( + array[2]) + try: + gajim.connections[account].services_cache.agent_items(array[0], + array[1], array[2]) + except AttributeError: + return + + def handle_event_agent_info_info(self, account, array): + #('AGENT_INFO_INFO', account, (agent, node, identities, features, data)) + try: + gajim.connections[account].services_cache.agent_info(array[0], + array[1], array[2], array[3], array[4]) + except AttributeError: + return + + def handle_event_new_acc_connected(self, account, array): + #('NEW_ACC_CONNECTED', account, (infos, is_form, ssl_msg, ssl_err, + # ssl_cert, ssl_fingerprint)) + if 'account_creation_wizard' in self.instances: + self.instances['account_creation_wizard'].new_acc_connected(array[0], + array[1], array[2], array[3], array[4], array[5]) + + def handle_event_new_acc_not_connected(self, account, array): + #('NEW_ACC_NOT_CONNECTED', account, (reason)) + if 'account_creation_wizard' in self.instances: + self.instances['account_creation_wizard'].new_acc_not_connected(array) + + def handle_event_acc_ok(self, account, array): + #('ACC_OK', account, (config)) + if 'account_creation_wizard' in self.instances: + self.instances['account_creation_wizard'].acc_is_ok(array) + + if self.remote_ctrl: + self.remote_ctrl.raise_signal('NewAccount', (account, array)) + + def handle_event_acc_not_ok(self, account, array): + #('ACC_NOT_OK', account, (reason)) + if 'account_creation_wizard' in self.instances: + self.instances['account_creation_wizard'].acc_is_not_ok(array) + + def handle_event_quit(self, p1, p2): + self.roster.quit_gtkgui_interface() + + def handle_event_myvcard(self, account, array): + nick = '' + if 'NICKNAME' in array and array['NICKNAME']: + gajim.nicks[account] = array['NICKNAME'] + elif 'FN' in array and array['FN']: + gajim.nicks[account] = array['FN'] + if 'profile' in self.instances[account]: + win = self.instances[account]['profile'] + win.set_values(array) + if account in self.show_vcard_when_connect: + self.show_vcard_when_connect.remove(account) + jid = array['jid'] + if jid in self.instances[account]['infos']: + self.instances[account]['infos'][jid].set_values(array) + + def handle_event_vcard(self, account, vcard): + # ('VCARD', account, data) + '''vcard holds the vcard data''' + jid = vcard['jid'] + resource = vcard.get('resource', '') + fjid = jid + '/' + str(resource) + + # vcard window + win = None + if jid in self.instances[account]['infos']: + win = self.instances[account]['infos'][jid] + elif resource and fjid in self.instances[account]['infos']: + win = self.instances[account]['infos'][fjid] + if win: + win.set_values(vcard) + + # show avatar in chat + ctrl = None + if resource and self.msg_win_mgr.has_window(fjid, account): + win = self.msg_win_mgr.get_window(fjid, account) + ctrl = win.get_control(fjid, account) + elif self.msg_win_mgr.has_window(jid, account): + win = self.msg_win_mgr.get_window(jid, account) + ctrl = win.get_control(jid, account) + + if ctrl and ctrl.type_id != message_control.TYPE_GC: + ctrl.show_avatar() + + # Show avatar in roster or gc_roster + gc_ctrl = self.msg_win_mgr.get_gc_control(jid, account) + if not gc_ctrl and \ + jid in self.minimized_controls[account]: + gc_ctrl = self.minimized_controls[account][jid] + if gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC: + gc_ctrl.draw_avatar(resource) + else: + self.roster.draw_avatar(jid, account) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('VcardInfo', (account, vcard)) + + def handle_event_last_status_time(self, account, array): + # ('LAST_STATUS_TIME', account, (jid, resource, seconds, status)) + tim = array[2] + if tim < 0: + # Ann error occured + return + win = None + if array[0] in self.instances[account]['infos']: + win = self.instances[account]['infos'][array[0]] + elif array[0] + '/' + array[1] in self.instances[account]['infos']: + win = self.instances[account]['infos'][array[0] + '/' + array[1]] + c = gajim.contacts.get_contact(account, array[0], array[1]) + if c: # c can be none if it's a gc contact + c.last_status_time = time.localtime(time.time() - tim) + if array[3]: + c.status = array[3] + self.roster.draw_contact(c.jid, account) # draw offline status + if win: + win.set_last_status_time() + if self.remote_ctrl: + self.remote_ctrl.raise_signal('LastStatusTime', (account, array)) + + def handle_event_os_info(self, account, array): + #'OS_INFO' (account, (jid, resource, client_info, os_info)) + win = None + if array[0] in self.instances[account]['infos']: + win = self.instances[account]['infos'][array[0]] + elif array[0] + '/' + array[1] in self.instances[account]['infos']: + win = self.instances[account]['infos'][array[0] + '/' + array[1]] + if win: + win.set_os_info(array[1], array[2], array[3]) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('OsInfo', (account, array)) + + def handle_event_entity_time(self, account, array): + #'ENTITY_TIME' (account, (jid, resource, time_info)) + win = None + if array[0] in self.instances[account]['infos']: + win = self.instances[account]['infos'][array[0]] + elif array[0] + '/' + array[1] in self.instances[account]['infos']: + win = self.instances[account]['infos'][array[0] + '/' + array[1]] + if win: + win.set_entity_time(array[1], array[2]) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('EntityTime', (account, array)) + + def handle_event_gc_notify(self, account, array): + #'GC_NOTIFY' (account, (room_jid, show, status, nick, + # role, affiliation, jid, reason, actor, statusCode, newNick, avatar_sha)) + nick = array[3] + if not nick: + return + room_jid = array[0] + fjid = room_jid + '/' + nick + show = array[1] + status = array[2] + conn = gajim.connections[account] + + # Get the window and control for the updated status, this may be a + # PrivateChatControl + control = self.msg_win_mgr.get_gc_control(room_jid, account) + + if not control and \ + room_jid in self.minimized_controls[account]: + control = self.minimized_controls[account][room_jid] + + if not control or (control and control.type_id != message_control.TYPE_GC): + return + + control.chg_contact_status(nick, show, status, array[4], array[5], + array[6], array[7], array[8], array[9], array[10], array[11]) + + contact = gajim.contacts.\ + get_contact_with_highest_priority(account, room_jid) + if contact: + self.roster.draw_contact(room_jid, account) + + # print status in chat window and update status/GPG image + ctrl = self.msg_win_mgr.get_control(fjid, account) + if ctrl: + statusCode = array[9] + if '303' in statusCode: + new_nick = array[10] + ctrl.print_conversation(_('%(nick)s is now known as %(new_nick)s') \ + % {'nick': nick, 'new_nick': new_nick}, 'status') + gc_c = gajim.contacts.get_gc_contact(account, room_jid, new_nick) + c = gc_c.as_contact() + ctrl.gc_contact = gc_c + ctrl.contact = c + if ctrl.session: + # stop e2e + if ctrl.session.enable_encryption: + thread_id = ctrl.session.thread_id + ctrl.session.terminate_e2e() + conn.delete_session(fjid, thread_id) + ctrl.no_autonegotiation = False + ctrl.draw_banner() + old_jid = room_jid + '/' + nick + new_jid = room_jid + '/' + new_nick + self.msg_win_mgr.change_key(old_jid, new_jid, account) + else: + contact = ctrl.contact + contact.show = show + contact.status = status + gc_contact = ctrl.gc_contact + gc_contact.show = show + gc_contact.status = status + uf_show = helpers.get_uf_show(show) + ctrl.print_conversation(_('%(nick)s is now %(status)s') % { + 'nick': nick, 'status': uf_show}, 'status') + if status: + ctrl.print_conversation(' (', 'status', simple=True) + ctrl.print_conversation('%s' % (status), 'status', simple=True) + ctrl.print_conversation(')', 'status', simple=True) + ctrl.parent_win.redraw_tab(ctrl) + ctrl.update_ui() + if self.remote_ctrl: + self.remote_ctrl.raise_signal('GCPresence', (account, array)) + + def handle_event_gc_msg(self, account, array): + # ('GC_MSG', account, (jid, msg, time, has_timestamp, htmlmsg, + # [status_codes])) + jids = array[0].split('/', 1) + room_jid = jids[0] + + msg = array[1] + + gc_control = self.msg_win_mgr.get_gc_control(room_jid, account) + if not gc_control and \ + room_jid in self.minimized_controls[account]: + gc_control = self.minimized_controls[account][room_jid] + + if not gc_control: + return + xhtml = array[4] + + if gajim.config.get('ignore_incoming_xhtml'): + xhtml = None + if len(jids) == 1: + # message from server + nick = '' + else: + # message from someone + nick = jids[1] + + gc_control.on_message(nick, msg, array[2], array[3], xhtml, array[5]) + + if self.remote_ctrl: + highlight = gc_control.needs_visual_notification(msg) + array += (highlight,) + self.remote_ctrl.raise_signal('GCMessage', (account, array)) + + def handle_event_gc_subject(self, account, array): + #('GC_SUBJECT', account, (jid, subject, body, has_timestamp)) + jids = array[0].split('/', 1) + jid = jids[0] + + gc_control = self.msg_win_mgr.get_gc_control(jid, account) + + if not gc_control and \ + jid in self.minimized_controls[account]: + gc_control = self.minimized_controls[account][jid] + + contact = gajim.contacts.\ + get_contact_with_highest_priority(account, jid) + if contact: + contact.status = array[1] + self.roster.draw_contact(jid, account) + + if not gc_control: + return + gc_control.set_subject(array[1]) + # Standard way, the message comes from the occupant who set the subject + text = None + if len(jids) > 1: + text = _('%(jid)s has set the subject to %(subject)s') % { + 'jid': jids[1], 'subject': array[1]} + # Workaround for psi bug http://flyspray.psi-im.org/task/595 , to be + # deleted one day. We can receive a subject with a body that contains + # "X has set the subject to Y" ... + elif array[2]: + text = array[2] + if text is not None: + if array[3]: + gc_control.print_old_conversation(text) + else: + gc_control.print_conversation(text) + + def handle_event_gc_config(self, account, array): + #('GC_CONFIG', account, (jid, form)) config is a dict + room_jid = array[0].split('/')[0] + if room_jid in gajim.automatic_rooms[account]: + if 'continue_tag' in gajim.automatic_rooms[account][room_jid]: + # We're converting chat to muc. allow participants to invite + form = dataforms.ExtendForm(node = array[1]) + for f in form.iter_fields(): + if f.var == 'muc#roomconfig_allowinvites': + f.value = True + elif f.var == 'muc#roomconfig_publicroom': + f.value = False + elif f.var == 'muc#roomconfig_membersonly': + f.value = True + elif f.var == 'public_list': + f.value = False + gajim.connections[account].send_gc_config(room_jid, form) + else: + # use default configuration + gajim.connections[account].send_gc_config(room_jid, array[1]) + # invite contacts + # check if it is necessary to add + continue_tag = False + if 'continue_tag' in gajim.automatic_rooms[account][room_jid]: + continue_tag = True + if 'invities' in gajim.automatic_rooms[account][room_jid]: + for jid in gajim.automatic_rooms[account][room_jid]['invities']: + gajim.connections[account].send_invite(room_jid, jid, + continue_tag=continue_tag) + del gajim.automatic_rooms[account][room_jid] + elif room_jid not in self.instances[account]['gc_config']: + self.instances[account]['gc_config'][room_jid] = \ + config.GroupchatConfigWindow(account, room_jid, array[1]) + + def handle_event_gc_config_change(self, account, array): + #('GC_CONFIG_CHANGE', account, (jid, statusCode)) statuscode is a list + # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify + # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes-init + jid = array[0] + statusCode = array[1] + + gc_control = self.msg_win_mgr.get_gc_control(jid, account) + if not gc_control and \ + jid in self.minimized_controls[account]: + gc_control = self.minimized_controls[account][jid] + if not gc_control: + return + + changes = [] + if '100' in statusCode: + # Can be a presence (see chg_contact_status in groupchat_control.py) + changes.append(_('Any occupant is allowed to see your full JID')) + gc_control.is_anonymous = False + if '102' in statusCode: + changes.append(_('Room now shows unavailable member')) + if '103' in statusCode: + changes.append(_('room now does not show unavailable members')) + if '104' in statusCode: + changes.append( + _('A non-privacy-related room configuration change has occurred')) + if '170' in statusCode: + # Can be a presence (see chg_contact_status in groupchat_control.py) + changes.append(_('Room logging is now enabled')) + if '171' in statusCode: + changes.append(_('Room logging is now disabled')) + if '172' in statusCode: + changes.append(_('Room is now non-anonymous')) + gc_control.is_anonymous = False + if '173' in statusCode: + changes.append(_('Room is now semi-anonymous')) + gc_control.is_anonymous = True + if '174' in statusCode: + changes.append(_('Room is now fully-anonymous')) + gc_control.is_anonymous = True + + for change in changes: + gc_control.print_conversation(change) + + def handle_event_gc_affiliation(self, account, array): + #('GC_AFFILIATION', account, (room_jid, users_dict)) + room_jid = array[0] + if room_jid in self.instances[account]['gc_config']: + self.instances[account]['gc_config'][room_jid].\ + affiliation_list_received(array[1]) + + def handle_event_gc_password_required(self, account, array): + #('GC_PASSWORD_REQUIRED', account, (room_jid, nick)) + room_jid = array[0] + nick = array[1] + + def on_ok(text): + gajim.connections[account].join_gc(nick, room_jid, text) + gajim.gc_passwords[room_jid] = text + + def on_cancel(): + # get and destroy window + if room_jid in gajim.interface.minimized_controls[account]: + self.roster.on_disconnect(None, room_jid, account) + else: + win = self.msg_win_mgr.get_window(room_jid, account) + ctrl = self.msg_win_mgr.get_gc_control(room_jid, account) + win.remove_tab(ctrl, 3) + + dlg = dialogs.InputDialog(_('Password Required'), + _('A Password is required to join the room %s. Please type it.') % \ + room_jid, is_modal=False, ok_handler=on_ok, cancel_handler=on_cancel) + dlg.input_entry.set_visibility(False) + + def handle_event_gc_invitation(self, account, array): + #('GC_INVITATION', (room_jid, jid_from, reason, password, is_continued)) + jid = gajim.get_jid_without_resource(array[1]) + room_jid = array[0] + if helpers.allow_popup_window(account) or not self.systray_enabled: + dialogs.InvitationReceivedDialog(account, room_jid, jid, array[3], + array[2], is_continued=array[4]) + return + + self.add_event(account, jid, 'gc-invitation', (room_jid, array[2], + array[3], array[4])) + + if helpers.allow_showing_notification(account): + path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', + 'gc_invitation.png') + path = gtkgui_helpers.get_path_to_generic_or_avatar(path) + event_type = _('Groupchat Invitation') + notify.popup(event_type, jid, account, 'gc-invitation', path, + event_type, room_jid) + + def forget_gpg_passphrase(self, keyid): + if keyid in self.gpg_passphrase: + del self.gpg_passphrase[keyid] + return False + + def handle_event_bad_passphrase(self, account, array): + #('BAD_PASSPHRASE', account, ()) + use_gpg_agent = gajim.config.get('use_gpg_agent') + sectext = '' + if use_gpg_agent: + sectext = _('You configured Gajim to use GPG agent, but there is no ' + 'GPG agent running or it returned a wrong passphrase.\n') + sectext += _('You are currently connected without your OpenPGP key.') + dialogs.WarningDialog(_('Your passphrase is incorrect'), sectext) + else: + path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'warning.png') + notify.popup('warning', account, account, 'warning', path, + _('OpenGPG Passphrase Incorrect'), + _('You are currently connected without your OpenPGP key.')) + keyID = gajim.config.get_per('accounts', account, 'keyid') + self.forget_gpg_passphrase(keyID) + + def handle_event_gpg_password_required(self, account, array): + #('GPG_PASSWORD_REQUIRED', account, (callback,)) + callback = array[0] + keyid = gajim.config.get_per('accounts', account, 'keyid') + if keyid in self.gpg_passphrase: + request = self.gpg_passphrase[keyid] + else: + request = PassphraseRequest(keyid) + self.gpg_passphrase[keyid] = request + request.add_callback(account, callback) + + def handle_event_gpg_always_trust(self, account, callback): + #('GPG_ALWAYS_TRUST', account, callback) + def on_yes(checked): + if checked: + gajim.connections[account].gpg.always_trust = True + callback(True) + + def on_no(): + callback(False) + + dialogs.YesNoDialog(_('GPG key not trusted'), _('The GPG key used to ' + 'encrypt this chat is not trusted. Do you really want to encrypt this ' + 'message?'), checktext=_('Do _not ask me again'), + on_response_yes=on_yes, on_response_no=on_no) + + def handle_event_password_required(self, account, array): + #('PASSWORD_REQUIRED', account, None) + if account in self.pass_dialog: + return + text = _('Enter your password for account %s') % account + if passwords.USER_HAS_GNOMEKEYRING and \ + not passwords.USER_USES_GNOMEKEYRING: + text += '\n' + _('Gnome Keyring is installed but not \ + correctly started (environment variable probably not \ + correctly set)') + + def on_ok(passphrase, save): + if save: + gajim.config.set_per('accounts', account, 'savepass', True) + passwords.save_password(account, passphrase) + gajim.connections[account].set_password(passphrase) + del self.pass_dialog[account] + + def on_cancel(): + self.roster.set_state(account, 'offline') + self.roster.update_status_combobox() + del self.pass_dialog[account] + + self.pass_dialog[account] = dialogs.PassphraseDialog( + _('Password Required'), text, _('Save password'), ok_handler=on_ok, + cancel_handler=on_cancel) + + def handle_event_roster_info(self, account, array): + #('ROSTER_INFO', account, (jid, name, sub, ask, groups)) + jid = array[0] + name = array[1] + sub = array[2] + ask = array[3] + groups = array[4] + contacts = gajim.contacts.get_contacts(account, jid) + if (not sub or sub == 'none') and (not ask or ask == 'none') and \ + not name and not groups: + # contact removed us. + if contacts: + self.roster.remove_contact(jid, account, backend=True) + return + elif not contacts: + if sub == 'remove': + return + # Add new contact to roster + contact = gajim.contacts.create_contact(jid=jid, account=account, + name=name, groups=groups, show='offline', sub=sub, ask=ask) + gajim.contacts.add_contact(account, contact) + self.roster.add_contact(jid, account) + else: + # it is an existing contact that might has changed + re_place = False + # If contact has changed (sub, ask or group) update roster + # Mind about observer status changes: + # According to xep 0162, a contact is not an observer anymore when + # we asked for auth, so also remove him if ask changed + old_groups = contacts[0].groups + if contacts[0].sub != sub or contacts[0].ask != ask\ + or old_groups != groups: + re_place = True + # c.get_shown_groups() has changed. Reflect that in roster_winodow + self.roster.remove_contact(jid, account, force=True) + for contact in contacts: + contact.name = name or '' + contact.sub = sub + contact.ask = ask + contact.groups = groups or [] + if re_place: + self.roster.add_contact(jid, account) + # Refilter and update old groups + for group in old_groups: + self.roster.draw_group(group, account) + else: + self.roster.draw_contact(jid, account) + + if self.remote_ctrl: + self.remote_ctrl.raise_signal('RosterInfo', (account, array)) + + def handle_event_bookmarks(self, account, bms): + # ('BOOKMARKS', account, [{name,jid,autojoin,password,nick}, {}]) + # We received a bookmark item from the server (JEP48) + # Auto join GC windows if neccessary + + self.roster.set_actions_menu_needs_rebuild() + invisible_show = gajim.SHOW_LIST.index('invisible') + # do not autojoin if we are invisible + if gajim.connections[account].connected == invisible_show: + return + + self.auto_join_bookmarks(account) + + def handle_event_file_send_error(self, account, array): + jid = array[0] + file_props = array[1] + ft = self.instances['file_transfers'] + ft.set_status(file_props['type'], file_props['sid'], 'stop') + + if helpers.allow_popup_window(account): + ft.show_send_error(file_props) + return + + self.add_event(account, jid, 'file-send-error', file_props) + + if helpers.allow_showing_notification(account): + img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', 'ft_error.png') + path = gtkgui_helpers.get_path_to_generic_or_avatar(img) + event_type = _('File Transfer Error') + notify.popup(event_type, jid, account, 'file-send-error', path, + event_type, file_props['name']) + + def handle_event_gmail_notify(self, account, array): + jid = array[0] + gmail_new_messages = int(array[1]) + gmail_messages_list = array[2] + if gajim.config.get('notify_on_new_gmail_email'): + img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', + 'new_email_recv.png') + title = _('New mail on %(gmail_mail_address)s') % \ + {'gmail_mail_address': jid} + text = i18n.ngettext('You have %d new mail conversation', + 'You have %d new mail conversations', gmail_new_messages, + gmail_new_messages, gmail_new_messages) + + if gajim.config.get('notify_on_new_gmail_email_extra'): + cnt = 0 + for gmessage in gmail_messages_list: + #FIXME: emulate Gtalk client popups. find out what they parse and + # how they decide what to show each message has a 'From', + # 'Subject' and 'Snippet' field + if cnt >=5: + break + senders = ',\n '.join(reversed(gmessage['From'])) + text += _('\n\nFrom: %(from_address)s\nSubject: %(subject)s\n%(snippet)s') % \ + {'from_address': senders, 'subject': gmessage['Subject'], + 'snippet': gmessage['Snippet']} + cnt += 1 + + if gajim.config.get_per('soundevents', 'gmail_received', 'enabled'): + helpers.play_sound('gmail_received') + path = gtkgui_helpers.get_path_to_generic_or_avatar(img) + notify.popup(_('New E-mail'), jid, account, 'gmail', + path_to_image=path, title=title, + text=text) + + if self.remote_ctrl: + self.remote_ctrl.raise_signal('NewGmail', (account, array)) + + def handle_event_file_request_error(self, account, array): + # ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg)) + jid, file_props, errmsg = array + jid = gajim.get_jid_without_resource(jid) + ft = self.instances['file_transfers'] + ft.set_status(file_props['type'], file_props['sid'], 'stop') + errno = file_props['error'] + + if helpers.allow_popup_window(account): + if errno in (-4, -5): + ft.show_stopped(jid, file_props, errmsg) + else: + ft.show_request_error(file_props) + return + + if errno in (-4, -5): + msg_type = 'file-error' + else: + msg_type = 'file-request-error' + + self.add_event(account, jid, msg_type, file_props) + + if helpers.allow_showing_notification(account): + # check if we should be notified + img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', 'ft_error.png') + + path = gtkgui_helpers.get_path_to_generic_or_avatar(img) + event_type = _('File Transfer Error') + notify.popup(event_type, jid, account, msg_type, path, + title = event_type, text = file_props['name']) + + def handle_event_file_request(self, account, array): + jid = array[0] + jid = gajim.get_jid_without_resource(jid) + if jid not in gajim.contacts.get_jid_list(account): + keyID = '' + attached_keys = gajim.config.get_per('accounts', account, + 'attached_gpg_keys').split() + if jid in attached_keys: + keyID = attached_keys[attached_keys.index(jid) + 1] + contact = gajim.contacts.create_not_in_roster_contact(jid=jid, + account=account, keyID=keyID) + gajim.contacts.add_contact(account, contact) + self.roster.add_contact(contact.jid, account) + file_props = array[1] + contact = gajim.contacts.get_first_contact_from_jid(account, jid) + + if helpers.allow_popup_window(account): + self.instances['file_transfers'].show_file_request(account, contact, + file_props) + return + + self.add_event(account, jid, 'file-request', file_props) + + if helpers.allow_showing_notification(account): + img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', + 'ft_request.png') + txt = _('%s wants to send you a file.') % gajim.get_name_from_jid( + account, jid) + path = gtkgui_helpers.get_path_to_generic_or_avatar(img) + event_type = _('File Transfer Request') + notify.popup(event_type, jid, account, 'file-request', + path_to_image = path, title = event_type, text = txt) + + def handle_event_file_error(self, title, message): + dialogs.ErrorDialog(title, message) + + def handle_event_file_progress(self, account, file_props): + if time.time() - self.last_ftwindow_update > 0.5: + # update ft window every 500ms + self.last_ftwindow_update = time.time() + self.instances['file_transfers'].set_progress(file_props['type'], + file_props['sid'], file_props['received-len']) + + def handle_event_file_rcv_completed(self, account, file_props): + ft = self.instances['file_transfers'] + if file_props['error'] == 0: + ft.set_progress(file_props['type'], file_props['sid'], + file_props['received-len']) + else: + ft.set_status(file_props['type'], file_props['sid'], 'stop') + if 'stalled' in file_props and file_props['stalled'] or \ + 'paused' in file_props and file_props['paused']: + return + if file_props['type'] == 'r': # we receive a file + jid = unicode(file_props['sender']) + else: # we send a file + jid = unicode(file_props['receiver']) + + if helpers.allow_popup_window(account): + if file_props['error'] == 0: + if gajim.config.get('notify_on_file_complete'): + ft.show_completed(jid, file_props) + elif file_props['error'] == -1: + ft.show_stopped(jid, file_props, + error_msg=_('Remote contact stopped transfer')) + elif file_props['error'] == -6: + ft.show_stopped(jid, file_props, error_msg=_('Error opening file')) + return + + msg_type = '' + event_type = '' + if file_props['error'] == 0 and gajim.config.get( + 'notify_on_file_complete'): + msg_type = 'file-completed' + event_type = _('File Transfer Completed') + elif file_props['error'] in (-1, -6): + msg_type = 'file-stopped' + event_type = _('File Transfer Stopped') + + if event_type == '': + # FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs) + # this should never happen but it does. see process_result() in socks5.py + # who calls this func (sth is really wrong unless this func is also registered + # as progress_cb + return + + if msg_type: + self.add_event(account, jid, msg_type, file_props) + + if file_props is not None: + if file_props['type'] == 'r': + # get the name of the sender, as it is in the roster + sender = unicode(file_props['sender']).split('/')[0] + name = gajim.contacts.get_first_contact_from_jid(account, + sender).get_shown_name() + filename = os.path.basename(file_props['file-name']) + if event_type == _('File Transfer Completed'): + txt = _('You successfully received %(filename)s from %(name)s.')\ + % {'filename': filename, 'name': name} + img = 'ft_done.png' + else: # ft stopped + txt = _('File transfer of %(filename)s from %(name)s stopped.')\ + % {'filename': filename, 'name': name} + img = 'ft_stopped.png' + else: + receiver = file_props['receiver'] + if hasattr(receiver, 'jid'): + receiver = receiver.jid + receiver = receiver.split('/')[0] + # get the name of the contact, as it is in the roster + name = gajim.contacts.get_first_contact_from_jid(account, + receiver).get_shown_name() + filename = os.path.basename(file_props['file-name']) + if event_type == _('File Transfer Completed'): + txt = _('You successfully sent %(filename)s to %(name)s.')\ + % {'filename': filename, 'name': name} + img = 'ft_done.png' + else: # ft stopped + txt = _('File transfer of %(filename)s to %(name)s stopped.')\ + % {'filename': filename, 'name': name} + img = 'ft_stopped.png' + img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', img) + path = gtkgui_helpers.get_path_to_generic_or_avatar(img) + else: + txt = '' + + if gajim.config.get('notify_on_file_complete') and \ + (gajim.config.get('autopopupaway') or \ + gajim.connections[account].connected in (2, 3)): + # we want to be notified and we are online/chat or we don't mind + # bugged when away/na/busy + notify.popup(event_type, jid, account, msg_type, path_to_image = path, + title = event_type, text = txt) + + def handle_event_stanza_arrived(self, account, stanza): + if account not in self.instances: + return + if 'xml_console' in self.instances[account]: + self.instances[account]['xml_console'].print_stanza(stanza, 'incoming') + + def handle_event_stanza_sent(self, account, stanza): + if account not in self.instances: + return + if 'xml_console' in self.instances[account]: + self.instances[account]['xml_console'].print_stanza(stanza, 'outgoing') + + def handle_event_vcard_published(self, account, array): + if 'profile' in self.instances[account]: + win = self.instances[account]['profile'] + win.vcard_published() + for gc_control in self.msg_win_mgr.get_controls(message_control.TYPE_GC) + \ + self.minimized_controls[account].values(): + if gc_control.account == account: + show = gajim.SHOW_LIST[gajim.connections[account].connected] + status = gajim.connections[account].status + gajim.connections[account].send_gc_status(gc_control.nick, + gc_control.room_jid, show, status) + + def handle_event_vcard_not_published(self, account, array): + if 'profile' in self.instances[account]: + win = self.instances[account]['profile'] + win.vcard_not_published() + + def ask_offline_status(self, account): + for contact in gajim.contacts.iter_contacts(account): + gajim.connections[account].request_last_status_time(contact.jid, + contact.resource) + + def handle_event_signed_in(self, account, empty): + """ + SIGNED_IN event is emitted when we sign in, so handle it + """ + # ('SIGNED_IN', account, ()) + # block signed in notifications for 30 seconds + gajim.block_signed_in_notifications[account] = True + self.roster.set_actions_menu_needs_rebuild() + self.roster.draw_account(account) + state = self.sleeper.getState() + connected = gajim.connections[account].connected + if gajim.config.get('ask_offline_status_on_connection'): + # Ask offline status in 1 minute so w'are sure we got all online + # presences + gobject.timeout_add_seconds(60, self.ask_offline_status, account) + if state != common.sleepy.STATE_UNKNOWN and connected in (2, 3): + # we go online or free for chat, so we activate auto status + gajim.sleeper_state[account] = 'online' + elif not ((state == common.sleepy.STATE_AWAY and connected == 4) or \ + (state == common.sleepy.STATE_XA and connected == 5)): + # If we are autoaway/xa and come back after a disconnection, do nothing + # Else disable autoaway + gajim.sleeper_state[account] = 'off' + invisible_show = gajim.SHOW_LIST.index('invisible') + # We cannot join rooms if we are invisible + if gajim.connections[account].connected == invisible_show: + return + # join already open groupchats + for gc_control in self.msg_win_mgr.get_controls(message_control.TYPE_GC) \ + + self.minimized_controls[account].values(): + if account != gc_control.account: + continue + room_jid = gc_control.room_jid + if room_jid in gajim.gc_connected[account] and \ + gajim.gc_connected[account][room_jid]: + continue + nick = gc_control.nick + password = gajim.gc_passwords.get(room_jid, '') + gajim.connections[account].join_gc(nick, room_jid, password) + # send currently played music + if gajim.connections[account].pep_supported and dbus_support.supported \ + and gajim.config.get_per('accounts', account, 'publish_tune'): + self.enable_music_listener() + # Start merging logs from server + gajim.connections[account].request_modifications_page( + gajim.config.get_per('accounts', account, 'last_archiving_time')) + gajim.config.set_per('accounts', account, 'last_archiving_time', + time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())) + + def handle_event_metacontacts(self, account, tags_list): + gajim.contacts.define_metacontacts(account, tags_list) + self.roster.redraw_metacontacts(account) + + def handle_atom_entry(self, account, data): + atom_entry, = data + AtomWindow.newAtomEntry(atom_entry) + + def handle_event_failed_decrypt(self, account, data): + jid, tim, session = data + + details = _('Unable to decrypt message from ' + '%s\nIt may have been tampered with.') % jid + + ctrl = session.control + if ctrl: + ctrl.print_conversation_line(details, 'status', '', tim) + else: + dialogs.WarningDialog(_('Unable to decrypt message'), + details) + + # terminate the session + session.terminate_e2e() + session.conn.delete_session(jid, session.thread_id) + + # restart the session + if ctrl: + ctrl.begin_e2e_negotiation() + + def handle_event_privacy_lists_received(self, account, data): + # ('PRIVACY_LISTS_RECEIVED', account, list) + if account not in self.instances: + return + if 'privacy_lists' in self.instances[account]: + self.instances[account]['privacy_lists'].privacy_lists_received(data) + + def handle_event_privacy_list_received(self, account, data): + # ('PRIVACY_LIST_RECEIVED', account, (name, rules)) + if account not in self.instances: + return + name = data[0] + rules = data[1] + if 'privacy_list_%s' % name in self.instances[account]: + self.instances[account]['privacy_list_%s' % name].\ + privacy_list_received(rules) + if name == 'block': + gajim.connections[account].blocked_contacts = [] + gajim.connections[account].blocked_groups = [] + gajim.connections[account].blocked_list = [] + gajim.connections[account].blocked_all = False + for rule in rules: + if not 'type' in rule: + gajim.connections[account].blocked_all = True + elif rule['type'] == 'jid' and rule['action'] == 'deny': + gajim.connections[account].blocked_contacts.append(rule['value']) + elif rule['type'] == 'group' and rule['action'] == 'deny': + gajim.connections[account].blocked_groups.append(rule['value']) + gajim.connections[account].blocked_list.append(rule) + #elif rule['type'] == "group" and action == "deny": + # text_item = _('%s group "%s"') % _(rule['action']), rule['value'] + # self.store.append([text_item]) + # self.global_rules.append(rule) + #else: + # self.global_rules_to_append.append(rule) + if 'blocked_contacts' in self.instances[account]: + self.instances[account]['blocked_contacts'].\ + privacy_list_received(rules) + + def handle_event_privacy_lists_active_default(self, account, data): + if not data: + return + # Send to all privacy_list_* windows as we can't know which one asked + for win in self.instances[account]: + if win.startswith('privacy_list_'): + self.instances[account][win].check_active_default(data) + + def handle_event_privacy_list_removed(self, account, name): + # ('PRIVACY_LISTS_REMOVED', account, name) + if account not in self.instances: + return + if 'privacy_lists' in self.instances[account]: + self.instances[account]['privacy_lists'].privacy_list_removed(name) + + def handle_event_zc_name_conflict(self, account, data): + def on_ok(new_name): + gajim.config.set_per('accounts', account, 'name', new_name) + status = gajim.connections[account].status + gajim.connections[account].username = new_name + gajim.connections[account].change_status(status, '') + def on_cancel(): + gajim.connections[account].change_status('offline', '') + + dlg = dialogs.InputDialog(_('Username Conflict'), + _('Please type a new username for your local account'), input_str=data, + is_modal=True, ok_handler=on_ok, cancel_handler=on_cancel) + + def handle_event_ping_sent(self, account, contact): + if contact.jid == contact.get_full_jid(): + # If contact is a groupchat user + jids = [contact.jid] + else: + jids = [contact.jid, contact.get_full_jid()] + for jid in jids: + ctrl = self.msg_win_mgr.get_control(jid, account) + if ctrl: + ctrl.print_conversation(_('Ping?'), 'status') + + def handle_event_ping_reply(self, account, data): + contact = data[0] + seconds = data[1] + if contact.jid == contact.get_full_jid(): + # If contact is a groupchat user + jids = [contact.jid] + else: + jids = [contact.jid, contact.get_full_jid()] + for jid in jids: + ctrl = self.msg_win_mgr.get_control(jid, account) + if ctrl: + ctrl.print_conversation(_('Pong! (%s s.)') % seconds, 'status') + + def handle_event_ping_error(self, account, contact): + if contact.jid == contact.get_full_jid(): + # If contact is a groupchat user + jids = [contact.jid] + else: + jids = [contact.jid, contact.get_full_jid()] + for jid in jids: + ctrl = self.msg_win_mgr.get_control(jid, account) + if ctrl: + ctrl.print_conversation(_('Error.'), 'status') + + def handle_event_search_form(self, account, data): + # ('SEARCH_FORM', account, (jid, dataform, is_dataform)) + if data[0] not in self.instances[account]['search']: + return + self.instances[account]['search'][data[0]].on_form_arrived(data[1], + data[2]) + + def handle_event_search_result(self, account, data): + # ('SEARCH_RESULT', account, (jid, dataform, is_dataform)) + if data[0] not in self.instances[account]['search']: + return + self.instances[account]['search'][data[0]].on_result_arrived(data[1], + data[2]) + + def handle_event_resource_conflict(self, account, data): + # ('RESOURCE_CONFLICT', account, ()) + # First we go offline, but we don't overwrite status message + self.roster.send_status(account, 'offline', + gajim.connections[account].status) + def on_ok(new_resource): + gajim.config.set_per('accounts', account, 'resource', new_resource) + self.roster.send_status(account, gajim.connections[account].old_show, + gajim.connections[account].status) + proposed_resource = gajim.connections[account].server_resource + proposed_resource += gajim.config.get('gc_proposed_nick_char') + dlg = dialogs.ResourceConflictDialog(_('Resource Conflict'), + _('You are already connected to this account with the same resource. ' + 'Please type a new one'), resource=proposed_resource, ok_handler=on_ok) + + def handle_event_jingle_incoming(self, account, data): + # ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type, + # data...)) + # TODO: conditional blocking if peer is not in roster + + # unpack data + peerjid, sid, contents = data + content_types = set(c[0] for c in contents) + + # check type of jingle session + if 'audio' in content_types or 'video' in content_types: + # a voip session... + # we now handle only voip, so the only thing we will do here is + # not to return from function + pass + else: + # unknown session type... it should be declined in common/jingle.py + return + + jid = gajim.get_jid_without_resource(peerjid) + resource = gajim.get_resource_from_jid(peerjid) + ctrl = self.msg_win_mgr.get_control(peerjid, account) + if not ctrl: + ctrl = self.msg_win_mgr.get_control(jid, account) + if ctrl: + if 'audio' in content_types: + ctrl.set_audio_state('connection_received', sid) + if 'video' in content_types: + ctrl.set_video_state('connection_received', sid) + + dlg = dialogs.VoIPCallReceivedDialog.get_dialog(peerjid, sid) + if dlg: + dlg.add_contents(content_types) + return + + if helpers.allow_popup_window(account): + dialogs.VoIPCallReceivedDialog(account, peerjid, sid, content_types) + return + + self.add_event(account, peerjid, 'jingle-incoming', (peerjid, sid, + content_types)) + + if helpers.allow_showing_notification(account): + # TODO: we should use another pixmap ;-) + img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', + 'ft_request.png') + txt = _('%s wants to start a voice chat.') % gajim.get_name_from_jid( + account, peerjid) + path = gtkgui_helpers.get_path_to_generic_or_avatar(img) + event_type = _('Voice Chat Request') + notify.popup(event_type, peerjid, account, 'jingle-incoming', + path_to_image = path, title = event_type, text = txt) + + def handle_event_jingle_connected(self, account, data): + # ('JINGLE_CONNECTED', account, (peerjid, sid, media)) + peerjid, sid, media = data + if media in ('audio', 'video'): + jid = gajim.get_jid_without_resource(peerjid) + resource = gajim.get_resource_from_jid(peerjid) + ctrl = self.msg_win_mgr.get_control(peerjid, account) + if not ctrl: + ctrl = self.msg_win_mgr.get_control(jid, account) + if ctrl: + if media == 'audio': + ctrl.set_audio_state('connected', sid) + else: + ctrl.set_video_state('connected', sid) + + def handle_event_jingle_disconnected(self, account, data): + # ('JINGLE_DISCONNECTED', account, (peerjid, sid, reason)) + peerjid, sid, media, reason = data + jid = gajim.get_jid_without_resource(peerjid) + resource = gajim.get_resource_from_jid(peerjid) + ctrl = self.msg_win_mgr.get_control(peerjid, account) + if not ctrl: + ctrl = self.msg_win_mgr.get_control(jid, account) + if ctrl: + if media in ('audio', None): + ctrl.set_audio_state('stop', sid=sid, reason=reason) + if media in ('video', None): + ctrl.set_video_state('stop', sid=sid, reason=reason) + dialog = dialogs.VoIPCallReceivedDialog.get_dialog(peerjid, sid) + if dialog: + dialog.dialog.destroy() + + def handle_event_jingle_error(self, account, data): + # ('JINGLE_ERROR', account, (peerjid, sid, reason)) + peerjid, sid, reason = data + jid = gajim.get_jid_without_resource(peerjid) + resource = gajim.get_resource_from_jid(peerjid) + ctrl = self.msg_win_mgr.get_control(peerjid, account) + if not ctrl: + ctrl = self.msg_win_mgr.get_control(jid, account) + if ctrl: + ctrl.set_audio_state('error', reason=reason) + + def handle_event_pep_config(self, account, data): + # ('PEP_CONFIG', account, (node, form)) + if 'pep_services' in self.instances[account]: + self.instances[account]['pep_services'].config(data[0], data[1]) + + def handle_event_roster_item_exchange(self, account, data): + # data = (action in [add, delete, modify], exchange_list, jid_from) + dialogs.RosterItemExchangeWindow(account, data[0], data[1], data[2]) + + def handle_event_unique_room_id_supported(self, account, data): + """ + Receive confirmation that unique_room_id are supported + """ + # ('UNIQUE_ROOM_ID_SUPPORTED', server, instance, room_id) + instance = data[1] + instance.unique_room_id_supported(data[0], data[2]) + + def handle_event_unique_room_id_unsupported(self, account, data): + # ('UNIQUE_ROOM_ID_UNSUPPORTED', server, instance) + instance = data[1] + instance.unique_room_id_error(data[0]) + + def handle_event_ssl_error(self, account, data): + # ('SSL_ERROR', account, (text, errnum, cert, sha1_fingerprint)) + server = gajim.config.get_per('accounts', account, 'hostname') + + def on_ok(is_checked): + del self.instances[account]['online_dialog']['ssl_error'] + if is_checked[0]: + # Check if cert is already in file + certs = '' + if os.path.isfile(gajim.MY_CACERTS): + f = open(gajim.MY_CACERTS) + certs = f.read() + f.close() + if data[2] in certs: + dialogs.ErrorDialog(_('Certificate Already in File'), + _('This certificate is already in file %s, so it\'s not added again.') % gajim.MY_CACERTS) + else: + f = open(gajim.MY_CACERTS, 'a') + f.write(server + '\n') + f.write(data[2] + '\n\n') + f.close() + gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1', + data[3]) + if is_checked[1]: + ignore_ssl_errors = gajim.config.get_per('accounts', account, + 'ignore_ssl_errors').split() + ignore_ssl_errors.append(str(data[1])) + gajim.config.set_per('accounts', account, 'ignore_ssl_errors', + ' '.join(ignore_ssl_errors)) + gajim.connections[account].ssl_certificate_accepted() + + def on_cancel(): + del self.instances[account]['online_dialog']['ssl_error'] + gajim.connections[account].disconnect(on_purpose=True) + self.handle_event_status(account, 'offline') + + pritext = _('Error verifying SSL certificate') + 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]} + if data[1] in (18, 27): + checktext1 = _('Add this certificate to the list of trusted certificates.\nSHA1 fingerprint of the certificate:\n%s') % data[3] + else: + checktext1 = '' + checktext2 = _('Ignore this error for this certificate.') + if 'ssl_error' in self.instances[account]['online_dialog']: + self.instances[account]['online_dialog']['ssl_error'].destroy() + self.instances[account]['online_dialog']['ssl_error'] = \ + dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, checktext1, + checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel) + + def handle_event_fingerprint_error(self, account, data): + # ('FINGERPRINT_ERROR', account, (new_fingerprint,)) + def on_yes(is_checked): + del self.instances[account]['online_dialog']['fingerprint_error'] + gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1', + data[0]) + # Reset the ignored ssl errors + gajim.config.set_per('accounts', account, 'ignore_ssl_errors', '') + gajim.connections[account].ssl_certificate_accepted() + def on_no(): + del self.instances[account]['online_dialog']['fingerprint_error'] + gajim.connections[account].disconnect(on_purpose=True) + self.handle_event_status(account, 'offline') + pritext = _('SSL certificate error') + sectext = _('It seems the SSL certificate of account %(account)s has ' + 'changed or your connection is being hacked.\nOld fingerprint: %(old)s' + '\nNew fingerprint: %(new)s\n\nDo you still want to connect and update' + ' the fingerprint of the certificate?') % {'account': account, + 'old': gajim.config.get_per('accounts', account, + 'ssl_fingerprint_sha1'), 'new': data[0]} + if 'fingerprint_error' in self.instances[account]['online_dialog']: + self.instances[account]['online_dialog']['fingerprint_error'].destroy() + self.instances[account]['online_dialog']['fingerprint_error'] = \ + dialogs.YesNoDialog(pritext, sectext, on_response_yes=on_yes, + on_response_no=on_no) + + def handle_event_plain_connection(self, account, data): + # ('PLAIN_CONNECTION', account, (connection)) + server = gajim.config.get_per('accounts', account, 'hostname') + def on_ok(is_checked): + if not is_checked[0]: + on_cancel() + return + # On cancel call del self.instances, so don't call it another time + # before + del self.instances[account]['online_dialog']['plain_connection'] + if is_checked[1]: + gajim.config.set_per('accounts', account, + 'warn_when_plaintext_connection', False) + gajim.connections[account].connection_accepted(data[0], 'plain') + def on_cancel(): + del self.instances[account]['online_dialog']['plain_connection'] + gajim.connections[account].disconnect(on_purpose=True) + self.handle_event_status(account, 'offline') + pritext = _('Insecure connection') + sectext = _('You are about to send your password on an unencrypted ' + 'connection. Are you sure you want to do that?') + checktext1 = _('Yes, I really want to connect insecurely') + checktext2 = _('Do _not ask me again') + if 'plain_connection' in self.instances[account]['online_dialog']: + self.instances[account]['online_dialog']['plain_connection'].destroy() + self.instances[account]['online_dialog']['plain_connection'] = \ + dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, + checktext1, checktext2, on_response_ok=on_ok, + on_response_cancel=on_cancel, is_modal=False) + + def handle_event_insecure_ssl_connection(self, account, data): + # ('INSECURE_SSL_CONNECTION', account, (connection, connection_type)) + server = gajim.config.get_per('accounts', account, 'hostname') + def on_ok(is_checked): + if not is_checked[0]: + on_cancel() + return + del self.instances[account]['online_dialog']['insecure_ssl'] + if is_checked[1]: + gajim.config.set_per('accounts', account, + 'warn_when_insecure_ssl_connection', False) + if gajim.connections[account].connected == 0: + # We have been disconnecting (too long time since window is opened) + # re-connect with auto-accept + gajim.connections[account].connection_auto_accepted = True + show, msg = gajim.connections[account].continue_connect_info[:2] + self.roster.send_status(account, show, msg) + return + gajim.connections[account].connection_accepted(data[0], data[1]) + def on_cancel(): + del self.instances[account]['online_dialog']['insecure_ssl'] + gajim.connections[account].disconnect(on_purpose=True) + self.handle_event_status(account, 'offline') + pritext = _('Insecure connection') + sectext = _('You are about to send your password on an insecure ' + 'connection. You should install PyOpenSSL to prevent that. Are you sure you want to do that?') + checktext1 = _('Yes, I really want to connect insecurely') + checktext2 = _('Do _not ask me again') + if 'insecure_ssl' in self.instances[account]['online_dialog']: + self.instances[account]['online_dialog']['insecure_ssl'].destroy() + self.instances[account]['online_dialog']['insecure_ssl'] = \ + dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, + checktext1, checktext2, on_response_ok=on_ok, + on_response_cancel=on_cancel, is_modal=False) + + def handle_event_pubsub_node_removed(self, account, data): + # ('PUBSUB_NODE_REMOVED', account, (jid, node)) + if 'pep_services' in self.instances[account]: + if data[0] == gajim.get_jid_from_account(account): + self.instances[account]['pep_services'].node_removed(data[1]) + + def handle_event_pubsub_node_not_removed(self, account, data): + # ('PUBSUB_NODE_NOT_REMOVED', account, (jid, node, msg)) + if data[0] == gajim.get_jid_from_account(account): + dialogs.WarningDialog(_('PEP node was not removed'), + _('PEP node %(node)s was not removed: %(message)s') % { + 'node': data[1], 'message': data[2]}) + + def handle_event_pep_received(self, account, data): + # ('PEP_RECEIVED', account, (jid, pep_type)) + jid = data[0] + pep_type = data[1] + ctrl = common.gajim.interface.msg_win_mgr.get_control(jid, account) + + if jid == common.gajim.get_jid_from_account(account): + self.roster.draw_account(account) + + if pep_type == 'nickname': + self.roster.draw_contact(jid, account) + if ctrl: + ctrl.update_ui() + win = ctrl.parent_win + win.redraw_tab(ctrl) + win.show_title() + else: + self.roster.draw_pep(jid, account, pep_type) + if ctrl: + ctrl.update_pep(pep_type) + + def handle_event_archiving_changed(self, account, data): + if 'archiving_preferences' in self.instances[account]: + self.instances[account]['archiving_preferences'].archiving_changed( + data) + + def handle_event_archiving_error(self, account, data): + if 'archiving_preferences' in self.instances[account]: + self.instances[account]['archiving_preferences'].archiving_error( + data) + + def register_handler(self, event, handler): + if event not in self.handlers: + self.handlers[event] = [] + + if handler not in self.handlers[event]: + self.handlers[event].append(handler) + + def unregister_handler(self, event, handler): + self.handlers[event].remove(handler) + + def register_handlers(self): + self.handlers = { + 'ROSTER': [self.handle_event_roster], + 'WARNING': [self.handle_event_warning], + 'ERROR': [self.handle_event_error], + 'INFORMATION': [self.handle_event_information], + 'ERROR_ANSWER': [self.handle_event_error_answer], + 'STATUS': [self.handle_event_status], + 'NEW_JID': [self.handle_event_new_jid], + 'NOTIFY': [self.handle_event_notify], + 'MSGERROR': [self.handle_event_msgerror], + 'MSGSENT': [self.handle_event_msgsent], + 'MSGNOTSENT': [self.handle_event_msgnotsent], + 'SUBSCRIBED': [self.handle_event_subscribed], + 'UNSUBSCRIBED': [self.handle_event_unsubscribed], + 'SUBSCRIBE': [self.handle_event_subscribe], + 'AGENT_ERROR_INFO': [self.handle_event_agent_info_error], + 'AGENT_ERROR_ITEMS': [self.handle_event_agent_items_error], + 'AGENT_REMOVED': [self.handle_event_agent_removed], + 'REGISTER_AGENT_INFO': [self.handle_event_register_agent_info], + 'AGENT_INFO_ITEMS': [self.handle_event_agent_info_items], + 'AGENT_INFO_INFO': [self.handle_event_agent_info_info], + 'QUIT': [self.handle_event_quit], + 'NEW_ACC_CONNECTED': [self.handle_event_new_acc_connected], + 'NEW_ACC_NOT_CONNECTED': [self.handle_event_new_acc_not_connected], + 'ACC_OK': [self.handle_event_acc_ok], + 'ACC_NOT_OK': [self.handle_event_acc_not_ok], + 'MYVCARD': [self.handle_event_myvcard], + 'VCARD': [self.handle_event_vcard], + 'LAST_STATUS_TIME': [self.handle_event_last_status_time], + 'OS_INFO': [self.handle_event_os_info], + 'ENTITY_TIME': [self.handle_event_entity_time], + 'GC_NOTIFY': [self.handle_event_gc_notify], + 'GC_MSG': [self.handle_event_gc_msg], + 'GC_SUBJECT': [self.handle_event_gc_subject], + 'GC_CONFIG': [self.handle_event_gc_config], + 'GC_CONFIG_CHANGE': [self.handle_event_gc_config_change], + 'GC_INVITATION': [self.handle_event_gc_invitation], + 'GC_AFFILIATION': [self.handle_event_gc_affiliation], + 'GC_PASSWORD_REQUIRED': [self.handle_event_gc_password_required], + 'BAD_PASSPHRASE': [self.handle_event_bad_passphrase], + 'ROSTER_INFO': [self.handle_event_roster_info], + 'BOOKMARKS': [self.handle_event_bookmarks], + 'CON_TYPE': [self.handle_event_con_type], + 'CONNECTION_LOST': [self.handle_event_connection_lost], + 'FILE_REQUEST': [self.handle_event_file_request], + 'GMAIL_NOTIFY': [self.handle_event_gmail_notify], + 'FILE_REQUEST_ERROR': [self.handle_event_file_request_error], + 'FILE_SEND_ERROR': [self.handle_event_file_send_error], + 'STANZA_ARRIVED': [self.handle_event_stanza_arrived], + 'STANZA_SENT': [self.handle_event_stanza_sent], + 'HTTP_AUTH': [self.handle_event_http_auth], + 'VCARD_PUBLISHED': [self.handle_event_vcard_published], + 'VCARD_NOT_PUBLISHED': [self.handle_event_vcard_not_published], + 'ASK_NEW_NICK': [self.handle_event_ask_new_nick], + 'SIGNED_IN': [self.handle_event_signed_in], + 'METACONTACTS': [self.handle_event_metacontacts], + 'ATOM_ENTRY': [self.handle_atom_entry], + 'FAILED_DECRYPT': [self.handle_event_failed_decrypt], + 'PRIVACY_LISTS_RECEIVED': [self.handle_event_privacy_lists_received], + 'PRIVACY_LIST_RECEIVED': [self.handle_event_privacy_list_received], + 'PRIVACY_LISTS_ACTIVE_DEFAULT': \ + [self.handle_event_privacy_lists_active_default], + 'PRIVACY_LIST_REMOVED': [self.handle_event_privacy_list_removed], + 'ZC_NAME_CONFLICT': [self.handle_event_zc_name_conflict], + 'PING_SENT': [self.handle_event_ping_sent], + 'PING_REPLY': [self.handle_event_ping_reply], + 'PING_ERROR': [self.handle_event_ping_error], + 'SEARCH_FORM': [self.handle_event_search_form], + 'SEARCH_RESULT': [self.handle_event_search_result], + 'RESOURCE_CONFLICT': [self.handle_event_resource_conflict], + 'ROSTERX': [self.handle_event_roster_item_exchange], + 'PEP_CONFIG': [self.handle_event_pep_config], + 'UNIQUE_ROOM_ID_UNSUPPORTED': \ + [self.handle_event_unique_room_id_unsupported], + 'UNIQUE_ROOM_ID_SUPPORTED': [self.handle_event_unique_room_id_supported], + 'GPG_PASSWORD_REQUIRED': [self.handle_event_gpg_password_required], + 'GPG_ALWAYS_TRUST': [self.handle_event_gpg_always_trust], + 'PASSWORD_REQUIRED': [self.handle_event_password_required], + 'SSL_ERROR': [self.handle_event_ssl_error], + 'FINGERPRINT_ERROR': [self.handle_event_fingerprint_error], + 'PLAIN_CONNECTION': [self.handle_event_plain_connection], + 'INSECURE_SSL_CONNECTION': [self.handle_event_insecure_ssl_connection], + 'PUBSUB_NODE_REMOVED': [self.handle_event_pubsub_node_removed], + 'PUBSUB_NODE_NOT_REMOVED': [self.handle_event_pubsub_node_not_removed], + 'JINGLE_INCOMING': [self.handle_event_jingle_incoming], + 'JINGLE_CONNECTED': [self.handle_event_jingle_connected], + 'JINGLE_DISCONNECTED': [self.handle_event_jingle_disconnected], + 'JINGLE_ERROR': [self.handle_event_jingle_error], + 'PEP_RECEIVED': [self.handle_event_pep_received], + 'ARCHIVING_CHANGED': [self.handle_event_archiving_changed], + 'ARCHIVING_ERROR': [self.handle_event_archiving_error], + } + + def dispatch(self, event, account, data): + """ + Dispatch an network event to the event handlers of this class. Return + true if it could be dispatched to alteast one handler + """ + if event not in self.handlers: + log.warning('Unknown event %s dispatched to GUI: %s' % (event, data)) + return False + else: + log.debug('Event %s distpached to GUI: %s' % (event, data)) + for handler in self.handlers[event]: + handler(account, data) + return len(self.handlers[event]) + ################################################################################ ### Methods dealing with gajim.events ################################################################################ - def add_event(self, account, jid, type_, event_args): - """ - Add an event to the gajim.events var - """ - # We add it to the gajim.events queue - # Do we have a queue? - jid = gajim.get_jid_without_resource(jid) - no_queue = len(gajim.events.get_events(account, jid)) == 0 - # type_ can be gc-invitation file-send-error file-error file-request-error - # file-request file-completed file-stopped jingle-incoming - # event_type can be in advancedNotificationWindow.events_list - event_types = {'file-request': 'ft_request', - 'file-completed': 'ft_finished'} - event_type = event_types.get(type_) - show_in_roster = notify.get_show_in_roster(event_type, account, jid) - show_in_systray = notify.get_show_in_systray(event_type, account, jid) - event = gajim.events.create_event(type_, event_args, - show_in_roster=show_in_roster, - show_in_systray=show_in_systray) - gajim.events.add_event(account, jid, event) + def add_event(self, account, jid, type_, event_args): + """ + Add an event to the gajim.events var + """ + # We add it to the gajim.events queue + # Do we have a queue? + jid = gajim.get_jid_without_resource(jid) + no_queue = len(gajim.events.get_events(account, jid)) == 0 + # type_ can be gc-invitation file-send-error file-error file-request-error + # file-request file-completed file-stopped jingle-incoming + # event_type can be in advancedNotificationWindow.events_list + event_types = {'file-request': 'ft_request', + 'file-completed': 'ft_finished'} + event_type = event_types.get(type_) + show_in_roster = notify.get_show_in_roster(event_type, account, jid) + show_in_systray = notify.get_show_in_systray(event_type, account, jid) + event = gajim.events.create_event(type_, event_args, + show_in_roster=show_in_roster, + show_in_systray=show_in_systray) + gajim.events.add_event(account, jid, event) - self.roster.show_title() - if no_queue: # We didn't have a queue: we change icons - if not gajim.contacts.get_contact_with_highest_priority(account, jid): - if type_ == 'gc-invitation': - self.roster.add_groupchat(jid, account, status='offline') - else: - # add contact to roster ("Not In The Roster") if he is not - self.roster.add_to_not_in_the_roster(account, jid) - else: - self.roster.draw_contact(jid, account) + self.roster.show_title() + if no_queue: # We didn't have a queue: we change icons + if not gajim.contacts.get_contact_with_highest_priority(account, jid): + if type_ == 'gc-invitation': + self.roster.add_groupchat(jid, account, status='offline') + else: + # add contact to roster ("Not In The Roster") if he is not + self.roster.add_to_not_in_the_roster(account, jid) + else: + self.roster.draw_contact(jid, account) - # Select the big brother contact in roster, it's visible because it has - # events. - family = gajim.contacts.get_metacontacts_family(account, jid) - if family: - nearby_family, bb_jid, bb_account = \ - gajim.contacts.get_nearby_family_and_big_brother(family, account) - else: - bb_jid, bb_account = jid, account - self.roster.select_contact(bb_jid, bb_account) + # Select the big brother contact in roster, it's visible because it has + # events. + family = gajim.contacts.get_metacontacts_family(account, jid) + if family: + nearby_family, bb_jid, bb_account = \ + gajim.contacts.get_nearby_family_and_big_brother(family, account) + else: + bb_jid, bb_account = jid, account + self.roster.select_contact(bb_jid, bb_account) - def handle_event(self, account, fjid, type_): - w = None - ctrl = None - session = None + def handle_event(self, account, fjid, type_): + w = None + ctrl = None + session = None - resource = gajim.get_resource_from_jid(fjid) - jid = gajim.get_jid_without_resource(fjid) + resource = gajim.get_resource_from_jid(fjid) + jid = gajim.get_jid_without_resource(fjid) - if type_ in ('printed_gc_msg', 'printed_marked_gc_msg', 'gc_msg'): - w = self.msg_win_mgr.get_window(jid, account) - if jid in self.minimized_controls[account]: - self.roster.on_groupchat_maximized(None, jid, account) - return - else: - ctrl = self.msg_win_mgr.get_gc_control(jid, account) + if type_ in ('printed_gc_msg', 'printed_marked_gc_msg', 'gc_msg'): + w = self.msg_win_mgr.get_window(jid, account) + if jid in self.minimized_controls[account]: + self.roster.on_groupchat_maximized(None, jid, account) + return + else: + ctrl = self.msg_win_mgr.get_gc_control(jid, account) - elif type_ in ('printed_chat', 'chat', ''): - # '' is for log in/out notifications + elif type_ in ('printed_chat', 'chat', ''): + # '' is for log in/out notifications - if type_ != '': - event = gajim.events.get_first_event(account, fjid, type_) - if not event: - event = gajim.events.get_first_event(account, jid, type_) - if not event: - return + if type_ != '': + event = gajim.events.get_first_event(account, fjid, type_) + if not event: + event = gajim.events.get_first_event(account, jid, type_) + if not event: + return - if type_ == 'printed_chat': - ctrl = event.parameters[0] - elif type_ == 'chat': - session = event.parameters[8] - ctrl = session.control - elif type_ == '': - ctrl = self.msg_win_mgr.get_control(fjid, account) + if type_ == 'printed_chat': + ctrl = event.parameters[0] + elif type_ == 'chat': + session = event.parameters[8] + ctrl = session.control + elif type_ == '': + ctrl = self.msg_win_mgr.get_control(fjid, account) - if not ctrl: - highest_contact = gajim.contacts.get_contact_with_highest_priority( - account, jid) - # jid can have a window if this resource was lower when he sent - # message and is now higher because the other one is offline - if resource and highest_contact.resource == resource and \ - not self.msg_win_mgr.has_window(jid, account): - # remove resource of events too - gajim.events.change_jid(account, fjid, jid) - resource = None - fjid = jid - contact = None - if resource: - contact = gajim.contacts.get_contact(account, jid, resource) - if not contact: - contact = highest_contact + if not ctrl: + highest_contact = gajim.contacts.get_contact_with_highest_priority( + account, jid) + # jid can have a window if this resource was lower when he sent + # message and is now higher because the other one is offline + if resource and highest_contact.resource == resource and \ + not self.msg_win_mgr.has_window(jid, account): + # remove resource of events too + gajim.events.change_jid(account, fjid, jid) + resource = None + fjid = jid + contact = None + if resource: + contact = gajim.contacts.get_contact(account, jid, resource) + if not contact: + contact = highest_contact - ctrl = self.new_chat(contact, account, resource = resource, session = session) + ctrl = self.new_chat(contact, account, resource = resource, session = session) - gajim.last_message_time[account][jid] = 0 # long time ago + gajim.last_message_time[account][jid] = 0 # long time ago - w = ctrl.parent_win - elif type_ in ('printed_pm', 'pm'): - # assume that the most recently updated control we have for this party - # is the one that this event was in - event = gajim.events.get_first_event(account, fjid, type_) - if not event: - event = gajim.events.get_first_event(account, jid, type_) + w = ctrl.parent_win + elif type_ in ('printed_pm', 'pm'): + # assume that the most recently updated control we have for this party + # is the one that this event was in + event = gajim.events.get_first_event(account, fjid, type_) + if not event: + event = gajim.events.get_first_event(account, jid, type_) - if type_ == 'printed_pm': - ctrl = event.parameters[0] - elif type_ == 'pm': - session = event.parameters[8] + if type_ == 'printed_pm': + ctrl = event.parameters[0] + elif type_ == 'pm': + session = event.parameters[8] - if session and session.control: - ctrl = session.control - elif not ctrl: - room_jid = jid - nick = resource - gc_contact = gajim.contacts.get_gc_contact(account, room_jid, - nick) - if gc_contact: - show = gc_contact.show - else: - show = 'offline' - gc_contact = gajim.contacts.create_gc_contact( - room_jid=room_jid, account=account, name=nick, show=show) + if session and session.control: + ctrl = session.control + elif not ctrl: + room_jid = jid + nick = resource + gc_contact = gajim.contacts.get_gc_contact(account, room_jid, + nick) + if gc_contact: + show = gc_contact.show + else: + show = 'offline' + gc_contact = gajim.contacts.create_gc_contact( + room_jid=room_jid, account=account, name=nick, show=show) - if not session: - session = gajim.connections[account].make_new_session( - fjid, None, type_='pm') + if not session: + session = gajim.connections[account].make_new_session( + fjid, None, type_='pm') - self.new_private_chat(gc_contact, account, session=session) - ctrl = session.control + self.new_private_chat(gc_contact, account, session=session) + ctrl = session.control - w = ctrl.parent_win - elif type_ in ('normal', 'file-request', 'file-request-error', - 'file-send-error', 'file-error', 'file-stopped', 'file-completed'): - # Get the first single message event - event = gajim.events.get_first_event(account, fjid, type_) - if not event: - # default to jid without resource - event = gajim.events.get_first_event(account, jid, type_) - if not event: - return - # Open the window - self.roster.open_event(account, jid, event) - else: - # Open the window - self.roster.open_event(account, fjid, event) - elif type_ == 'gmail': - url=gajim.connections[account].gmail_url - if url: - helpers.launch_browser_mailer('url', url) - elif type_ == 'gc-invitation': - event = gajim.events.get_first_event(account, jid, type_) - data = event.parameters - dialogs.InvitationReceivedDialog(account, data[0], jid, data[2], - data[1], data[3]) - gajim.events.remove_events(account, jid, event) - self.roster.draw_contact(jid, account) - elif type_ == 'subscription_request': - event = gajim.events.get_first_event(account, jid, type_) - data = event.parameters - dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1]) - gajim.events.remove_events(account, jid, event) - self.roster.draw_contact(jid, account) - elif type_ == 'unsubscribed': - event = gajim.events.get_first_event(account, jid, type_) - contact = event.parameters - self.show_unsubscribed_dialog(account, contact) - gajim.events.remove_events(account, jid, event) - self.roster.draw_contact(jid, account) - elif type_ == 'jingle-incoming': - event = gajim.events.get_first_event(account, jid, type_) - peerjid, sid, content_types = event.parameters - dialogs.VoIPCallReceivedDialog(account, peerjid, sid, content_types) - gajim.events.remove_events(account, jid, event) - if w: - w.set_active_tab(ctrl) - w.window.window.focus(gtk.get_current_event_time()) - # Using isinstance here because we want to catch all derived types - if isinstance(ctrl, ChatControlBase): - tv = ctrl.conv_textview - tv.scroll_to_end() + w = ctrl.parent_win + elif type_ in ('normal', 'file-request', 'file-request-error', + 'file-send-error', 'file-error', 'file-stopped', 'file-completed'): + # Get the first single message event + event = gajim.events.get_first_event(account, fjid, type_) + if not event: + # default to jid without resource + event = gajim.events.get_first_event(account, jid, type_) + if not event: + return + # Open the window + self.roster.open_event(account, jid, event) + else: + # Open the window + self.roster.open_event(account, fjid, event) + elif type_ == 'gmail': + url=gajim.connections[account].gmail_url + if url: + helpers.launch_browser_mailer('url', url) + elif type_ == 'gc-invitation': + event = gajim.events.get_first_event(account, jid, type_) + data = event.parameters + dialogs.InvitationReceivedDialog(account, data[0], jid, data[2], + data[1], data[3]) + gajim.events.remove_events(account, jid, event) + self.roster.draw_contact(jid, account) + elif type_ == 'subscription_request': + event = gajim.events.get_first_event(account, jid, type_) + data = event.parameters + dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1]) + gajim.events.remove_events(account, jid, event) + self.roster.draw_contact(jid, account) + elif type_ == 'unsubscribed': + event = gajim.events.get_first_event(account, jid, type_) + contact = event.parameters + self.show_unsubscribed_dialog(account, contact) + gajim.events.remove_events(account, jid, event) + self.roster.draw_contact(jid, account) + elif type_ == 'jingle-incoming': + event = gajim.events.get_first_event(account, jid, type_) + peerjid, sid, content_types = event.parameters + dialogs.VoIPCallReceivedDialog(account, peerjid, sid, content_types) + gajim.events.remove_events(account, jid, event) + if w: + w.set_active_tab(ctrl) + w.window.window.focus(gtk.get_current_event_time()) + # Using isinstance here because we want to catch all derived types + if isinstance(ctrl, ChatControlBase): + tv = ctrl.conv_textview + tv.scroll_to_end() ################################################################################ ### Methods dealing with emoticons ################################################################################ - def image_is_ok(self, image): - if not os.path.exists(image): - return False - img = gtk.Image() - try: - img.set_from_file(image) - except Exception: - return False - t = img.get_storage_type() - if t != gtk.IMAGE_PIXBUF and t != gtk.IMAGE_ANIMATION: - return False - return True + def image_is_ok(self, image): + if not os.path.exists(image): + return False + img = gtk.Image() + try: + img.set_from_file(image) + except Exception: + return False + t = img.get_storage_type() + if t != gtk.IMAGE_PIXBUF and t != gtk.IMAGE_ANIMATION: + return False + return True - @property - def basic_pattern_re(self): - try: - return self._basic_pattern_re - except AttributeError: - self._basic_pattern_re = re.compile(self.basic_pattern, re.IGNORECASE) - return self._basic_pattern_re + @property + def basic_pattern_re(self): + try: + return self._basic_pattern_re + except AttributeError: + self._basic_pattern_re = re.compile(self.basic_pattern, re.IGNORECASE) + return self._basic_pattern_re - @property - def emot_and_basic_re(self): - try: - return self._emot_and_basic_re - except AttributeError: - self._emot_and_basic_re = re.compile(self.emot_and_basic, - re.IGNORECASE + re.UNICODE) - return self._emot_and_basic_re + @property + def emot_and_basic_re(self): + try: + return self._emot_and_basic_re + except AttributeError: + self._emot_and_basic_re = re.compile(self.emot_and_basic, + re.IGNORECASE + re.UNICODE) + return self._emot_and_basic_re - @property - def sth_at_sth_dot_sth_re(self): - try: - return self._sth_at_sth_dot_sth_re - except AttributeError: - self._sth_at_sth_dot_sth_re = re.compile(self.sth_at_sth_dot_sth) - return self._sth_at_sth_dot_sth_re + @property + def sth_at_sth_dot_sth_re(self): + try: + return self._sth_at_sth_dot_sth_re + except AttributeError: + self._sth_at_sth_dot_sth_re = re.compile(self.sth_at_sth_dot_sth) + return self._sth_at_sth_dot_sth_re - @property - def invalid_XML_chars_re(self): - try: - return self._invalid_XML_chars_re - except AttributeError: - self._invalid_XML_chars_re = re.compile(self.invalid_XML_chars) - return self._invalid_XML_chars_re + @property + def invalid_XML_chars_re(self): + try: + return self._invalid_XML_chars_re + except AttributeError: + self._invalid_XML_chars_re = re.compile(self.invalid_XML_chars) + return self._invalid_XML_chars_re - def make_regexps(self): - # regexp meta characters are: . ^ $ * + ? { } [ ] \ | ( ) - # one escapes the metachars with \ - # \S matches anything but ' ' '\t' '\n' '\r' '\f' and '\v' - # \s matches any whitespace character - # \w any alphanumeric character - # \W any non-alphanumeric character - # \b means word boundary. This is a zero-width assertion that - # matches only at the beginning or end of a word. - # ^ matches at the beginning of lines - # - # * means 0 or more times - # + means 1 or more times - # ? means 0 or 1 time - # | means or - # [^*] anything but '*' (inside [] you don't have to escape metachars) - # [^\s*] anything but whitespaces and '*' - # (? in the matching string don't match ? or ) etc.. if at the end - # so http://be) will match http://be and http://be)be) will match http://be)be + def make_regexps(self): + # regexp meta characters are: . ^ $ * + ? { } [ ] \ | ( ) + # one escapes the metachars with \ + # \S matches anything but ' ' '\t' '\n' '\r' '\f' and '\v' + # \s matches any whitespace character + # \w any alphanumeric character + # \W any non-alphanumeric character + # \b means word boundary. This is a zero-width assertion that + # matches only at the beginning or end of a word. + # ^ matches at the beginning of lines + # + # * means 0 or more times + # + means 1 or more times + # ? means 0 or 1 time + # | means or + # [^*] anything but '*' (inside [] you don't have to escape metachars) + # [^\s*] anything but whitespaces and '*' + # (? in the matching string don't match ? or ) etc.. if at the end + # so http://be) will match http://be and http://be)be) will match http://be)be - legacy_prefixes = r"((?<=\()(www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+(?=\)))"\ - r"|((www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\ - r"\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)" - # NOTE: it's ok to catch www.gr such stuff exist! + legacy_prefixes = r"((?<=\()(www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+(?=\)))"\ + r"|((www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\ + r"\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)" + # NOTE: it's ok to catch www.gr such stuff exist! - #FIXME: recognize xmpp: and treat it specially - links = r"((?<=\()[A-Za-z][A-Za-z0-9\+\.\-]*:"\ - r"([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\ - r"(?=\)))|([A-Za-z][A-Za-z0-9\+\.\-]*:([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)" + #FIXME: recognize xmpp: and treat it specially + links = r"((?<=\()[A-Za-z][A-Za-z0-9\+\.\-]*:"\ + r"([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\ + r"(?=\)))|([A-Za-z][A-Za-z0-9\+\.\-]*:([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)" - #2nd one: at_least_one_char@at_least_one_char.at_least_one_char - mail = r'\bmailto:\S*[^\s\W]|' r'\b\S+@\S+\.\S*[^\s\W]' + #2nd one: at_least_one_char@at_least_one_char.at_least_one_char + mail = r'\bmailto:\S*[^\s\W]|' r'\b\S+@\S+\.\S*[^\s\W]' - #detects eg. *b* *bold* *bold bold* test *bold* *bold*! (*bold*) - #doesn't detect (it's a feature :P) * bold* *bold * * bold * test*bold* - formatting = r'|(?> sys.stderr, err_str - # it is good to notify the user - # in case he or she cannot see the output of the console - dialogs.ErrorDialog(_('Could not save your settings and preferences'), - err_str) - sys.exit() + def on_launch_browser_mailer(self, widget, url, kind): + helpers.launch_browser_mailer(kind, url) - def save_avatar_files(self, jid, photo, puny_nick = None, local = False): - """ - Save 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 - """ - puny_jid = helpers.sanitize_filename(jid) - path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) - if puny_nick: - path_to_file = os.path.join(path_to_file, puny_nick) - # remove old avatars - for typ in ('jpeg', 'png'): - if local: - path_to_original_file = path_to_file + '_local'+ '.' + typ - else: - path_to_original_file = path_to_file + '.' + typ - if os.path.isfile(path_to_original_file): - os.remove(path_to_original_file) - if local and photo: - pixbuf = photo - typ = 'png' - extension = '_local.png' # save local avatars as png file - else: - pixbuf, typ = gtkgui_helpers.get_pixbuf_from_data(photo, want_type = True) - if pixbuf is None: - return - extension = '.' + typ - if typ not in ('jpeg', 'png'): - gajim.log.debug('gtkpixbuf cannot save other than jpeg and png formats. saving %s\'avatar as png file (originaly %s)' % (jid, typ)) - typ = 'png' - extension = '.png' - path_to_original_file = path_to_file + extension - try: - pixbuf.save(path_to_original_file, typ) - except Exception, e: - log.error('Error writing avatar file %s: %s' % (path_to_original_file, - str(e))) - # Generate and save the resized, color avatar - pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'notification') - if pixbuf: - path_to_normal_file = path_to_file + '_notif_size_colored' + extension - try: - pixbuf.save(path_to_normal_file, 'png') - except Exception, e: - log.error('Error writing avatar file %s: %s' % \ - (path_to_original_file, str(e))) - # Generate and save the resized, black and white avatar - bwbuf = gtkgui_helpers.get_scaled_pixbuf( - gtkgui_helpers.make_pixbuf_grayscale(pixbuf), 'notification') - if bwbuf: - path_to_bw_file = path_to_file + '_notif_size_bw' + extension - try: - bwbuf.save(path_to_bw_file, 'png') - except Exception, e: - log.error('Error writing avatar file %s: %s' % \ - (path_to_original_file, str(e))) + def process_connections(self): + """ + Called each foo (200) miliseconds. Check for idlequeue timeouts + """ + try: + gajim.idlequeue.process() + except Exception: + # Otherwise, an exception will stop our loop + timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT + if in_seconds: + gobject.timeout_add_seconds(timeout, self.process_connections) + else: + gobject.timeout_add(timeout, self.process_connections) + raise + return True # renew timeout (loop for ever) - def remove_avatar_files(self, jid, puny_nick = None, local = False): - """ - Remove avatar files of a jid - """ - puny_jid = helpers.sanitize_filename(jid) - path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) - if puny_nick: - path_to_file = os.path.join(path_to_file, puny_nick) - for ext in ('.jpeg', '.png'): - if local: - ext = '_local' + ext - path_to_original_file = path_to_file + ext - if os.path.isfile(path_to_file + ext): - os.remove(path_to_file + ext) - if os.path.isfile(path_to_file + '_notif_size_colored' + ext): - os.remove(path_to_file + '_notif_size_colored' + ext) - if os.path.isfile(path_to_file + '_notif_size_bw' + ext): - os.remove(path_to_file + '_notif_size_bw' + ext) + def save_config(self): + err_str = parser.write() + if err_str is not None: + print >> sys.stderr, err_str + # it is good to notify the user + # in case he or she cannot see the output of the console + dialogs.ErrorDialog(_('Could not save your settings and preferences'), + err_str) + sys.exit() - def auto_join_bookmarks(self, account): - """ - Autojoin bookmarked GCs that have 'auto join' on for this account - """ - for bm in gajim.connections[account].bookmarks: - if bm['autojoin'] in ('1', 'true'): - jid = bm['jid'] - # Only join non-opened groupchats. Opened one are already - # auto-joined on re-connection - if not jid in gajim.gc_connected[account]: - # we are not already connected - minimize = bm['minimize'] in ('1', 'true') - gajim.interface.join_gc_room(account, jid, bm['nick'], - bm['password'], minimize = minimize) - elif jid in self.minimized_controls[account]: - # more or less a hack: - # On disconnect the minimized gc contact instances - # were set to offline. Reconnect them to show up in the roster. - self.roster.add_groupchat(jid, account) + def save_avatar_files(self, jid, photo, puny_nick = None, local = False): + """ + Save 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 + """ + puny_jid = helpers.sanitize_filename(jid) + path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) + if puny_nick: + path_to_file = os.path.join(path_to_file, puny_nick) + # remove old avatars + for typ in ('jpeg', 'png'): + if local: + path_to_original_file = path_to_file + '_local'+ '.' + typ + else: + path_to_original_file = path_to_file + '.' + typ + if os.path.isfile(path_to_original_file): + os.remove(path_to_original_file) + if local and photo: + pixbuf = photo + typ = 'png' + extension = '_local.png' # save local avatars as png file + else: + pixbuf, typ = gtkgui_helpers.get_pixbuf_from_data(photo, want_type = True) + if pixbuf is None: + return + extension = '.' + typ + if typ not in ('jpeg', 'png'): + gajim.log.debug('gtkpixbuf cannot save other than jpeg and png formats. saving %s\'avatar as png file (originaly %s)' % (jid, typ)) + typ = 'png' + extension = '.png' + path_to_original_file = path_to_file + extension + try: + pixbuf.save(path_to_original_file, typ) + except Exception, e: + log.error('Error writing avatar file %s: %s' % (path_to_original_file, + str(e))) + # Generate and save the resized, color avatar + pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'notification') + if pixbuf: + path_to_normal_file = path_to_file + '_notif_size_colored' + extension + try: + pixbuf.save(path_to_normal_file, 'png') + except Exception, e: + log.error('Error writing avatar file %s: %s' % \ + (path_to_original_file, str(e))) + # Generate and save the resized, black and white avatar + bwbuf = gtkgui_helpers.get_scaled_pixbuf( + gtkgui_helpers.make_pixbuf_grayscale(pixbuf), 'notification') + if bwbuf: + path_to_bw_file = path_to_file + '_notif_size_bw' + extension + try: + bwbuf.save(path_to_bw_file, 'png') + except Exception, e: + log.error('Error writing avatar file %s: %s' % \ + (path_to_original_file, str(e))) - def add_gc_bookmark(self, account, name, jid, autojoin, minimize, password, - nick): - """ - Add a bookmark for this account, sorted in bookmark list - """ - bm = { - 'name': name, - 'jid': jid, - 'autojoin': autojoin, - 'minimize': minimize, - 'password': password, - 'nick': nick - } - place_found = False - index = 0 - # check for duplicate entry and respect alpha order - for bookmark in gajim.connections[account].bookmarks: - if bookmark['jid'] == bm['jid']: - dialogs.ErrorDialog( - _('Bookmark already set'), - _('Group Chat "%s" is already in your bookmarks.') % bm['jid']) - return - if bookmark['name'] > bm['name']: - place_found = True - break - index += 1 - if place_found: - gajim.connections[account].bookmarks.insert(index, bm) - else: - gajim.connections[account].bookmarks.append(bm) - gajim.connections[account].store_bookmarks() - self.roster.set_actions_menu_needs_rebuild() - dialogs.InformationDialog( - _('Bookmark has been added successfully'), - _('You can manage your bookmarks via Actions menu in your roster.')) + def remove_avatar_files(self, jid, puny_nick = None, local = False): + """ + Remove avatar files of a jid + """ + puny_jid = helpers.sanitize_filename(jid) + path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) + if puny_nick: + path_to_file = os.path.join(path_to_file, puny_nick) + for ext in ('.jpeg', '.png'): + if local: + ext = '_local' + ext + path_to_original_file = path_to_file + ext + if os.path.isfile(path_to_file + ext): + os.remove(path_to_file + ext) + if os.path.isfile(path_to_file + '_notif_size_colored' + ext): + os.remove(path_to_file + '_notif_size_colored' + ext) + if os.path.isfile(path_to_file + '_notif_size_bw' + ext): + os.remove(path_to_file + '_notif_size_bw' + ext) + + def auto_join_bookmarks(self, account): + """ + Autojoin bookmarked GCs that have 'auto join' on for this account + """ + for bm in gajim.connections[account].bookmarks: + if bm['autojoin'] in ('1', 'true'): + jid = bm['jid'] + # Only join non-opened groupchats. Opened one are already + # auto-joined on re-connection + if not jid in gajim.gc_connected[account]: + # we are not already connected + minimize = bm['minimize'] in ('1', 'true') + gajim.interface.join_gc_room(account, jid, bm['nick'], + bm['password'], minimize = minimize) + elif jid in self.minimized_controls[account]: + # more or less a hack: + # On disconnect the minimized gc contact instances + # were set to offline. Reconnect them to show up in the roster. + self.roster.add_groupchat(jid, account) + + def add_gc_bookmark(self, account, name, jid, autojoin, minimize, password, + nick): + """ + Add a bookmark for this account, sorted in bookmark list + """ + bm = { + 'name': name, + 'jid': jid, + 'autojoin': autojoin, + 'minimize': minimize, + 'password': password, + 'nick': nick + } + place_found = False + index = 0 + # check for duplicate entry and respect alpha order + for bookmark in gajim.connections[account].bookmarks: + if bookmark['jid'] == bm['jid']: + dialogs.ErrorDialog( + _('Bookmark already set'), + _('Group Chat "%s" is already in your bookmarks.') % bm['jid']) + return + if bookmark['name'] > bm['name']: + place_found = True + break + index += 1 + if place_found: + gajim.connections[account].bookmarks.insert(index, bm) + else: + gajim.connections[account].bookmarks.append(bm) + gajim.connections[account].store_bookmarks() + self.roster.set_actions_menu_needs_rebuild() + dialogs.InformationDialog( + _('Bookmark has been added successfully'), + _('You can manage your bookmarks via Actions menu in your roster.')) - # does JID exist only within a groupchat? - def is_pm_contact(self, fjid, account): - bare_jid = gajim.get_jid_without_resource(fjid) + # does JID exist only within a groupchat? + def is_pm_contact(self, fjid, account): + bare_jid = gajim.get_jid_without_resource(fjid) - gc_ctrl = self.msg_win_mgr.get_gc_control(bare_jid, account) + gc_ctrl = self.msg_win_mgr.get_gc_control(bare_jid, account) - if not gc_ctrl and \ - bare_jid in self.minimized_controls[account]: - gc_ctrl = self.minimized_controls[account][bare_jid] + if not gc_ctrl and \ + bare_jid in self.minimized_controls[account]: + gc_ctrl = self.minimized_controls[account][bare_jid] - return gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC + return gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC - def create_ipython_window(self): - try: - from ipython_view import IPythonView - except ImportError: - print 'ipython_view not found' - return - import pango + def create_ipython_window(self): + try: + from ipython_view import IPythonView + except ImportError: + print 'ipython_view not found' + return + import pango - if os.name == 'nt': - font = 'Lucida Console 9' - else: - font = 'Luxi Mono 10' + if os.name == 'nt': + font = 'Lucida Console 9' + else: + font = 'Luxi Mono 10' - window = gtk.Window() - window.set_size_request(750,550) - window.set_resizable(True) - sw = gtk.ScrolledWindow() - sw.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC) - view = IPythonView() - view.modify_font(pango.FontDescription(font)) - view.set_wrap_mode(gtk.WRAP_CHAR) - sw.add(view) - window.add(sw) - window.show_all() - def on_delete(win, event): - win.hide() - return True - window.connect('delete_event',on_delete) - view.updateNamespace({'gajim': gajim}) - gajim.ipython_window = window + window = gtk.Window() + window.set_size_request(750, 550) + window.set_resizable(True) + sw = gtk.ScrolledWindow() + sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + view = IPythonView() + view.modify_font(pango.FontDescription(font)) + view.set_wrap_mode(gtk.WRAP_CHAR) + sw.add(view) + window.add(sw) + window.show_all() + def on_delete(win, event): + win.hide() + return True + window.connect('delete_event', on_delete) + view.updateNamespace({'gajim': gajim}) + gajim.ipython_window = window - def run(self): - if gajim.config.get('trayicon') != 'never': - self.show_systray() + def run(self): + if gajim.config.get('trayicon') != 'never': + self.show_systray() - self.roster = roster_window.RosterWindow() - for account in gajim.connections: - gajim.connections[account].load_roster_from_db() + self.roster = roster_window.RosterWindow() + for account in gajim.connections: + gajim.connections[account].load_roster_from_db() - # get instances for windows/dialogs that will show_all()/hide() - self.instances['file_transfers'] = dialogs.FileTransfersWindow() + # get instances for windows/dialogs that will show_all()/hide() + self.instances['file_transfers'] = dialogs.FileTransfersWindow() - gobject.timeout_add(100, self.autoconnect) - timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT - if in_seconds: - gobject.timeout_add_seconds(timeout, self.process_connections) - else: - gobject.timeout_add(timeout, self.process_connections) - gobject.timeout_add_seconds(gajim.config.get( - 'check_idle_every_foo_seconds'), self.read_sleepy) + gobject.timeout_add(100, self.autoconnect) + timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT + if in_seconds: + gobject.timeout_add_seconds(timeout, self.process_connections) + else: + gobject.timeout_add(timeout, self.process_connections) + gobject.timeout_add_seconds(gajim.config.get( + 'check_idle_every_foo_seconds'), self.read_sleepy) - # when using libasyncns we need to process resolver in regular intervals - if resolver.USE_LIBASYNCNS: - gobject.timeout_add(200, gajim.resolver.process) + # when using libasyncns we need to process resolver in regular intervals + if resolver.USE_LIBASYNCNS: + gobject.timeout_add(200, gajim.resolver.process) - # setup the indicator - if gajim.HAVE_INDICATOR: - notify.setup_indicator_server() + # setup the indicator + if gajim.HAVE_INDICATOR: + notify.setup_indicator_server() - def remote_init(): - if gajim.config.get('remote_control'): - try: - import remote_control - self.remote_ctrl = remote_control.Remote() - except Exception: - pass - gobject.timeout_add_seconds(5, remote_init) - - - def __init__(self): - gajim.interface = self - gajim.thread_interface = ThreadInterface - # This is the manager and factory of message windows set by the module - self.msg_win_mgr = None - self.jabber_state_images = {'16': {}, '32': {}, 'opened': {}, - 'closed': {}} - self.emoticons_menu = None - # handler when an emoticon is clicked in emoticons_menu - self.emoticon_menuitem_clicked = None - self.minimized_controls = {} - self.status_sent_to_users = {} - self.status_sent_to_groups = {} - self.gpg_passphrase = {} - self.pass_dialog = {} - self.default_colors = { - 'inmsgcolor': gajim.config.get('inmsgcolor'), - 'outmsgcolor': gajim.config.get('outmsgcolor'), - 'inmsgtxtcolor': gajim.config.get('inmsgtxtcolor'), - 'outmsgtxtcolor': gajim.config.get('outmsgtxtcolor'), - 'statusmsgcolor': gajim.config.get('statusmsgcolor'), - 'urlmsgcolor': gajim.config.get('urlmsgcolor'), - } + def remote_init(): + if gajim.config.get('remote_control'): + try: + import remote_control + self.remote_ctrl = remote_control.Remote() + except Exception: + pass + gobject.timeout_add_seconds(5, remote_init) - cfg_was_read = parser.read() - gajim.logger.reset_shown_unread_messages() - # override logging settings from config (don't take care of '-q' option) - if gajim.config.get('verbose'): - logging_helpers.set_verbose() - # Is Gajim default app? - if os.name != 'nt' and gajim.config.get('check_if_gajim_is_default'): - gtkgui_helpers.possibly_set_gajim_as_xmpp_handler() + def __init__(self): + gajim.interface = self + gajim.thread_interface = ThreadInterface + # This is the manager and factory of message windows set by the module + self.msg_win_mgr = None + self.jabber_state_images = {'16': {}, '32': {}, 'opened': {}, + 'closed': {}} + self.emoticons_menu = None + # handler when an emoticon is clicked in emoticons_menu + self.emoticon_menuitem_clicked = None + self.minimized_controls = {} + self.status_sent_to_users = {} + self.status_sent_to_groups = {} + self.gpg_passphrase = {} + self.pass_dialog = {} + self.default_colors = { + 'inmsgcolor': gajim.config.get('inmsgcolor'), + 'outmsgcolor': gajim.config.get('outmsgcolor'), + 'inmsgtxtcolor': gajim.config.get('inmsgtxtcolor'), + 'outmsgtxtcolor': gajim.config.get('outmsgtxtcolor'), + 'statusmsgcolor': gajim.config.get('statusmsgcolor'), + 'urlmsgcolor': gajim.config.get('urlmsgcolor'), + } - for account in gajim.config.get_per('accounts'): - if gajim.config.get_per('accounts', account, 'is_zeroconf'): - gajim.ZEROCONF_ACC_NAME = account - break - # Is gnome configured to activate row on single click ? - try: - import gconf - client = gconf.client_get_default() - click_policy = client.get_string( - '/apps/nautilus/preferences/click_policy') - if click_policy == 'single': - gajim.single_click = True - except Exception: - pass - # add default status messages if there is not in the config file - if len(gajim.config.get_per('statusmsg')) == 0: - default = gajim.config.statusmsg_default - for msg in default: - gajim.config.add_per('statusmsg', msg) - gajim.config.set_per('statusmsg', msg, 'message', default[msg][0]) - gajim.config.set_per('statusmsg', msg, 'activity', default[msg][1]) - gajim.config.set_per('statusmsg', msg, 'subactivity', - default[msg][2]) - gajim.config.set_per('statusmsg', msg, 'activity_text', - default[msg][3]) - gajim.config.set_per('statusmsg', msg, 'mood', default[msg][4]) - gajim.config.set_per('statusmsg', msg, 'mood_text', default[msg][5]) - #add default themes if there is not in the config file - theme = gajim.config.get('roster_theme') - if not theme in gajim.config.get_per('themes'): - gajim.config.set('roster_theme', _('default')) - if len(gajim.config.get_per('themes')) == 0: - d = ['accounttextcolor', 'accountbgcolor', 'accountfont', - 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont', - 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', - 'contactfont', 'contactfontattrs', 'bannertextcolor', - 'bannerbgcolor'] + cfg_was_read = parser.read() + gajim.logger.reset_shown_unread_messages() + # override logging settings from config (don't take care of '-q' option) + if gajim.config.get('verbose'): + logging_helpers.set_verbose() - default = gajim.config.themes_default - for theme_name in default: - gajim.config.add_per('themes', theme_name) - theme = default[theme_name] - for o in d: - gajim.config.set_per('themes', theme_name, o, - theme[d.index(o)]) + # Is Gajim default app? + if os.name != 'nt' and gajim.config.get('check_if_gajim_is_default'): + gtkgui_helpers.possibly_set_gajim_as_xmpp_handler() - if gajim.config.get('autodetect_browser_mailer') or not cfg_was_read: - gtkgui_helpers.autodetect_browser_mailer() + for account in gajim.config.get_per('accounts'): + if gajim.config.get_per('accounts', account, 'is_zeroconf'): + gajim.ZEROCONF_ACC_NAME = account + break + # Is gnome configured to activate row on single click ? + try: + import gconf + client = gconf.client_get_default() + click_policy = client.get_string( + '/apps/nautilus/preferences/click_policy') + if click_policy == 'single': + gajim.single_click = True + except Exception: + pass + # add default status messages if there is not in the config file + if len(gajim.config.get_per('statusmsg')) == 0: + default = gajim.config.statusmsg_default + for msg in default: + gajim.config.add_per('statusmsg', msg) + gajim.config.set_per('statusmsg', msg, 'message', default[msg][0]) + gajim.config.set_per('statusmsg', msg, 'activity', default[msg][1]) + gajim.config.set_per('statusmsg', msg, 'subactivity', + default[msg][2]) + gajim.config.set_per('statusmsg', msg, 'activity_text', + default[msg][3]) + gajim.config.set_per('statusmsg', msg, 'mood', default[msg][4]) + gajim.config.set_per('statusmsg', msg, 'mood_text', default[msg][5]) + #add default themes if there is not in the config file + theme = gajim.config.get('roster_theme') + if not theme in gajim.config.get_per('themes'): + gajim.config.set('roster_theme', _('default')) + if len(gajim.config.get_per('themes')) == 0: + d = ['accounttextcolor', 'accountbgcolor', 'accountfont', + 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont', + 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', + 'contactfont', 'contactfontattrs', 'bannertextcolor', + 'bannerbgcolor'] - gajim.idlequeue = idlequeue.get_idlequeue() - # resolve and keep current record of resolved hosts - gajim.resolver = resolver.get_resolver(gajim.idlequeue) - gajim.socks5queue = socks5.SocksQueue(gajim.idlequeue, - self.handle_event_file_rcv_completed, - self.handle_event_file_progress, - self.handle_event_file_error) - gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue) - gajim.default_session_type = ChatControlSession - self.register_handlers() - if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \ - and gajim.HAVE_ZEROCONF: - gajim.connections[gajim.ZEROCONF_ACC_NAME] = \ - connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME) - for account in gajim.config.get_per('accounts'): - if not gajim.config.get_per('accounts', account, 'is_zeroconf') and \ - gajim.config.get_per('accounts', account, 'active'): - gajim.connections[account] = common.connection.Connection(account) + default = gajim.config.themes_default + for theme_name in default: + gajim.config.add_per('themes', theme_name) + theme = default[theme_name] + for o in d: + gajim.config.set_per('themes', theme_name, o, + theme[d.index(o)]) - # gtk hooks - gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail') - gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url') - gtk.link_button_set_uri_hook(self.on_launch_browser_mailer, 'url') + if gajim.config.get('autodetect_browser_mailer') or not cfg_was_read: + gtkgui_helpers.autodetect_browser_mailer() - self.instances = {} + gajim.idlequeue = idlequeue.get_idlequeue() + # resolve and keep current record of resolved hosts + gajim.resolver = resolver.get_resolver(gajim.idlequeue) + gajim.socks5queue = socks5.SocksQueue(gajim.idlequeue, + self.handle_event_file_rcv_completed, + self.handle_event_file_progress, + self.handle_event_file_error) + gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue) + gajim.default_session_type = ChatControlSession + self.register_handlers() + if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \ + and gajim.HAVE_ZEROCONF: + gajim.connections[gajim.ZEROCONF_ACC_NAME] = \ + connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME) + for account in gajim.config.get_per('accounts'): + if not gajim.config.get_per('accounts', account, 'is_zeroconf') and \ + gajim.config.get_per('accounts', account, 'active'): + gajim.connections[account] = common.connection.Connection(account) - for a in gajim.connections: - self.instances[a] = {'infos': {}, 'disco': {}, 'gc_config': {}, - 'search': {}, 'online_dialog': {}} - # online_dialog contains all dialogs that have a meaning only when we - # are not disconnected - self.minimized_controls[a] = {} - gajim.contacts.add_account(a) - gajim.groups[a] = {} - gajim.gc_connected[a] = {} - gajim.automatic_rooms[a] = {} - gajim.newly_added[a] = [] - gajim.to_be_removed[a] = [] - gajim.nicks[a] = gajim.config.get_per('accounts', a, 'name') - gajim.block_signed_in_notifications[a] = True - gajim.sleeper_state[a] = 0 - gajim.encrypted_chats[a] = [] - gajim.last_message_time[a] = {} - gajim.status_before_autoaway[a] = '' - gajim.transport_avatar[a] = {} - gajim.gajim_optional_features[a] = [] - gajim.caps_hash[a] = '' + # gtk hooks + gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail') + gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url') + gtk.link_button_set_uri_hook(self.on_launch_browser_mailer, 'url') - helpers.update_optional_features() - # prepopulate data which we are sure of; note: we do not log these info - for account in gajim.connections: - gajimcaps = caps.capscache[('sha-1', gajim.caps_hash[account])] - gajimcaps.identities = [gajim.gajim_identity] - gajimcaps.features = gajim.gajim_common_features + \ - gajim.gajim_optional_features[account] + self.instances = {} - self.remote_ctrl = None + for a in gajim.connections: + self.instances[a] = {'infos': {}, 'disco': {}, 'gc_config': {}, + 'search': {}, 'online_dialog': {}} + # online_dialog contains all dialogs that have a meaning only when we + # are not disconnected + self.minimized_controls[a] = {} + gajim.contacts.add_account(a) + gajim.groups[a] = {} + gajim.gc_connected[a] = {} + gajim.automatic_rooms[a] = {} + gajim.newly_added[a] = [] + gajim.to_be_removed[a] = [] + gajim.nicks[a] = gajim.config.get_per('accounts', a, 'name') + gajim.block_signed_in_notifications[a] = True + gajim.sleeper_state[a] = 0 + gajim.encrypted_chats[a] = [] + gajim.last_message_time[a] = {} + gajim.status_before_autoaway[a] = '' + gajim.transport_avatar[a] = {} + gajim.gajim_optional_features[a] = [] + gajim.caps_hash[a] = '' - if gajim.config.get('networkmanager_support') and dbus_support.supported: - import network_manager_listener + helpers.update_optional_features() + # prepopulate data which we are sure of; note: we do not log these info + for account in gajim.connections: + gajimcaps = caps.capscache[('sha-1', gajim.caps_hash[account])] + gajimcaps.identities = [gajim.gajim_identity] + gajimcaps.features = gajim.gajim_common_features + \ + gajim.gajim_optional_features[account] - # Handle gnome screensaver - if dbus_support.supported: - def gnome_screensaver_ActiveChanged_cb(active): - if not active: - for account in gajim.connections: - if gajim.sleeper_state[account] == 'autoaway-forced': - # We came back online ofter gnome-screensaver autoaway - self.roster.send_status(account, 'online', - gajim.status_before_autoaway[account]) - gajim.status_before_autoaway[account] = '' - gajim.sleeper_state[account] = 'online' - return - if not gajim.config.get('autoaway'): - # Don't go auto away if user disabled the option - return - for account in gajim.connections: - if account not in gajim.sleeper_state or \ - not gajim.sleeper_state[account]: - continue - if gajim.sleeper_state[account] == 'online': - # we save out online status - gajim.status_before_autoaway[account] = \ - gajim.connections[account].status - # we go away (no auto status) [we pass True to auto param] - auto_message = gajim.config.get('autoaway_message') - if not auto_message: - auto_message = gajim.connections[account].status - else: - auto_message = auto_message.replace('$S','%(status)s') - auto_message = auto_message.replace('$T','%(time)s') - auto_message = auto_message % { - 'status': gajim.status_before_autoaway[account], - 'time': gajim.config.get('autoxatime') - } - self.roster.send_status(account, 'away', auto_message, - auto=True) - gajim.sleeper_state[account] = 'autoaway-forced' + self.remote_ctrl = None - try: - bus = dbus.SessionBus() - bus.add_signal_receiver(gnome_screensaver_ActiveChanged_cb, - 'ActiveChanged', 'org.gnome.ScreenSaver') - except Exception: - pass + if gajim.config.get('networkmanager_support') and dbus_support.supported: + import network_manager_listener - self.show_vcard_when_connect = [] + # Handle gnome screensaver + if dbus_support.supported: + def gnome_screensaver_ActiveChanged_cb(active): + if not active: + for account in gajim.connections: + if gajim.sleeper_state[account] == 'autoaway-forced': + # We came back online ofter gnome-screensaver autoaway + self.roster.send_status(account, 'online', + gajim.status_before_autoaway[account]) + gajim.status_before_autoaway[account] = '' + gajim.sleeper_state[account] = 'online' + return + if not gajim.config.get('autoaway'): + # Don't go auto away if user disabled the option + return + for account in gajim.connections: + if account not in gajim.sleeper_state or \ + not gajim.sleeper_state[account]: + continue + if gajim.sleeper_state[account] == 'online': + # we save out online status + gajim.status_before_autoaway[account] = \ + gajim.connections[account].status + # we go away (no auto status) [we pass True to auto param] + auto_message = gajim.config.get('autoaway_message') + if not auto_message: + auto_message = gajim.connections[account].status + else: + auto_message = auto_message.replace('$S', '%(status)s') + auto_message = auto_message.replace('$T', '%(time)s') + auto_message = auto_message % { + 'status': gajim.status_before_autoaway[account], + 'time': gajim.config.get('autoxatime') + } + self.roster.send_status(account, 'away', auto_message, + auto=True) + gajim.sleeper_state[account] = 'autoaway-forced' - self.sleeper = common.sleepy.Sleepy( - gajim.config.get('autoawaytime') * 60, # make minutes to seconds - gajim.config.get('autoxatime') * 60) + try: + bus = dbus.SessionBus() + bus.add_signal_receiver(gnome_screensaver_ActiveChanged_cb, + 'ActiveChanged', 'org.gnome.ScreenSaver') + except Exception: + pass - gtkgui_helpers.make_jabber_state_images() + self.show_vcard_when_connect = [] - self.systray_enabled = False + self.sleeper = common.sleepy.Sleepy( + gajim.config.get('autoawaytime') * 60, # make minutes to seconds + gajim.config.get('autoxatime') * 60) - import statusicon - self.systray = statusicon.StatusIcon() + gtkgui_helpers.make_jabber_state_images() - path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'gajim.png') - pix = gtk.gdk.pixbuf_new_from_file(path_to_file) - # set the icon to all windows - gtk.window_set_default_icon(pix) + self.systray_enabled = False - self.init_emoticons() - self.make_regexps() + import statusicon + self.systray = statusicon.StatusIcon() - # get transports type from DB - gajim.transport_type = gajim.logger.get_transports_type() + path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'gajim.png') + pix = gtk.gdk.pixbuf_new_from_file(path_to_file) + # set the icon to all windows + gtk.window_set_default_icon(pix) - # test is dictionnary is present for speller - if gajim.config.get('use_speller'): - lang = gajim.config.get('speller_language') - if not lang: - lang = gajim.LANG - tv = gtk.TextView() - try: - import gtkspell - spell = gtkspell.Spell(tv, lang) - except (ImportError, TypeError, RuntimeError, OSError): - dialogs.AspellDictError(lang) + self.init_emoticons() + self.make_regexps() - if gajim.config.get('soundplayer') == '': - # only on first time Gajim starts - commands = ('aplay', 'play', 'esdplay', 'artsplay', 'ossplay') - for command in commands: - if helpers.is_in_path(command): - if command == 'aplay': - command += ' -q' - gajim.config.set('soundplayer', command) - break + # get transports type from DB + gajim.transport_type = gajim.logger.get_transports_type() + + # test is dictionnary is present for speller + if gajim.config.get('use_speller'): + lang = gajim.config.get('speller_language') + if not lang: + lang = gajim.LANG + tv = gtk.TextView() + try: + import gtkspell + spell = gtkspell.Spell(tv, lang) + except (ImportError, TypeError, RuntimeError, OSError): + dialogs.AspellDictError(lang) + + if gajim.config.get('soundplayer') == '': + # only on first time Gajim starts + commands = ('aplay', 'play', 'esdplay', 'artsplay', 'ossplay') + for command in commands: + if helpers.is_in_path(command): + if command == 'aplay': + command += ' -q' + gajim.config.set('soundplayer', command) + break + + self.last_ftwindow_update = 0 + + self.music_track_changed_signal = None - self.last_ftwindow_update = 0 - self.music_track_changed_signal = None - - class PassphraseRequest: - def __init__(self, keyid): - self.keyid = keyid - self.callbacks = [] - self.dialog_created = False - self.dialog = None - self.completed = False + def __init__(self, keyid): + self.keyid = keyid + self.callbacks = [] + self.dialog_created = False + self.dialog = None + self.completed = False - def interrupt(self): - self.dialog.window.destroy() - self.callbacks = [] + def interrupt(self): + self.dialog.window.destroy() + self.callbacks = [] - def run_callback(self, account, callback): - gajim.connections[account].gpg_passphrase(self.passphrase) - callback() + def run_callback(self, account, callback): + gajim.connections[account].gpg_passphrase(self.passphrase) + callback() - def add_callback(self, account, cb): - if self.completed: - self.run_callback(account, cb) - else: - self.callbacks.append((account, cb)) - if not self.dialog_created: - self.create_dialog(account) + def add_callback(self, account, cb): + if self.completed: + self.run_callback(account, cb) + else: + self.callbacks.append((account, cb)) + if not self.dialog_created: + self.create_dialog(account) - def complete(self, passphrase): - self.passphrase = passphrase - self.completed = True - if passphrase is not None: - gobject.timeout_add_seconds(30, gajim.interface.forget_gpg_passphrase, - self.keyid) - for (account, cb) in self.callbacks: - self.run_callback(account, cb) - del self.callbacks + def complete(self, passphrase): + self.passphrase = passphrase + self.completed = True + if passphrase is not None: + gobject.timeout_add_seconds(30, gajim.interface.forget_gpg_passphrase, + self.keyid) + for (account, cb) in self.callbacks: + self.run_callback(account, cb) + del self.callbacks - def create_dialog(self, account): - title = _('Passphrase Required') - second = _('Enter GPG key passphrase for key %(keyid)s (account ' - '%(account)s).') % {'keyid': self.keyid, 'account': account} + def create_dialog(self, account): + title = _('Passphrase Required') + second = _('Enter GPG key passphrase for key %(keyid)s (account ' + '%(account)s).') % {'keyid': self.keyid, 'account': account} - def _cancel(): - # user cancelled, continue without GPG - self.complete(None) + def _cancel(): + # user cancelled, continue without GPG + self.complete(None) - def _ok(passphrase, checked, count): - result = gajim.connections[account].test_gpg_passphrase(passphrase) - if result == 'ok': - # passphrase is good - self.complete(passphrase) - return - elif result == 'expired': - dialogs.ErrorDialog(_('GPG key expired'), - _('Your GPG key has expired, you will be connected to %s without' - ' OpenPGP.') % account) - # Don't try to connect with GPG - gajim.connections[account].continue_connect_info[2] = False - self.complete(None) - return + def _ok(passphrase, checked, count): + result = gajim.connections[account].test_gpg_passphrase(passphrase) + if result == 'ok': + # passphrase is good + self.complete(passphrase) + return + elif result == 'expired': + dialogs.ErrorDialog(_('GPG key expired'), + _('Your GPG key has expired, you will be connected to %s without' + ' OpenPGP.') % account) + # Don't try to connect with GPG + gajim.connections[account].continue_connect_info[2] = False + self.complete(None) + return - if count < 3: - # ask again - dialogs.PassphraseDialog(_('Wrong Passphrase'), - _('Please retype your GPG passphrase or press Cancel.'), - ok_handler=(_ok, count + 1), cancel_handler=_cancel) - else: - # user failed 3 times, continue without GPG - self.complete(None) + if count < 3: + # ask again + dialogs.PassphraseDialog(_('Wrong Passphrase'), + _('Please retype your GPG passphrase or press Cancel.'), + ok_handler=(_ok, count + 1), cancel_handler=_cancel) + else: + # user failed 3 times, continue without GPG + self.complete(None) - self.dialog = dialogs.PassphraseDialog(title, second, ok_handler=(_ok, 1), - cancel_handler=_cancel) - self.dialog_created = True + self.dialog = dialogs.PassphraseDialog(title, second, ok_handler=(_ok, 1), + cancel_handler=_cancel) + self.dialog_created = True class ThreadInterface: - def __init__(self, func, func_args, callback, callback_args): - """ - Call a function in a thread - """ - def thread_function(func, func_args, callback, callback_args): - output = func(*func_args) - gobject.idle_add(callback, output, *callback_args) - - Thread(target=thread_function, args=(func, func_args, callback, - callback_args)).start() + def __init__(self, func, func_args, callback, callback_args): + """ + Call a function in a thread + """ + def thread_function(func, func_args, callback, callback_args): + output = func(*func_args) + gobject.idle_add(callback, output, *callback_args) + + Thread(target=thread_function, args=(func, func_args, callback, + callback_args)).start() diff --git a/src/gui_menu_builder.py b/src/gui_menu_builder.py index 453677aa2..a352edab0 100644 --- a/src/gui_menu_builder.py +++ b/src/gui_menu_builder.py @@ -28,452 +28,450 @@ from common import helpers from common.xmpp.protocol import NS_COMMANDS, NS_FILE, NS_MUC, NS_ESESSION def build_resources_submenu(contacts, account, action, room_jid=None, - room_account=None, cap=None): - """ - Build a submenu with contact's resources. room_jid and room_account are for - action self.on_invite_to_room - """ - roster = gajim.interface.roster - sub_menu = gtk.Menu() + room_account=None, cap=None): + """ + Build a submenu with contact's resources. room_jid and room_account are for + action self.on_invite_to_room + """ + roster = gajim.interface.roster + sub_menu = gtk.Menu() - iconset = gajim.config.get('iconset') - if not iconset: - iconset = gajim.config.DEFAULT_ICONSET - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - for c in contacts: - # icon MUST be different instance for every item - state_images = gtkgui_helpers.load_iconset(path) - item = gtk.ImageMenuItem('%s (%s)' % (c.resource, str(c.priority))) - icon_name = helpers.get_icon_name_to_show(c, account) - icon = state_images[icon_name] - item.set_image(icon) - sub_menu.append(item) - - if action == roster.on_invite_to_room: - item.connect('activate', action, [(c, account)], room_jid, - room_account, c.resource) - elif action == roster.on_invite_to_new_room: - item.connect('activate', action, [(c, account)], c.resource) - else: # start_chat, execute_command, send_file - item.connect('activate', action, c, account, c.resource) + iconset = gajim.config.get('iconset') + if not iconset: + iconset = gajim.config.DEFAULT_ICONSET + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + for c in contacts: + # icon MUST be different instance for every item + state_images = gtkgui_helpers.load_iconset(path) + item = gtk.ImageMenuItem('%s (%s)' % (c.resource, str(c.priority))) + icon_name = helpers.get_icon_name_to_show(c, account) + icon = state_images[icon_name] + item.set_image(icon) + sub_menu.append(item) - if cap and not c.supports(cap): - item.set_sensitive(False) + if action == roster.on_invite_to_room: + item.connect('activate', action, [(c, account)], room_jid, + room_account, c.resource) + elif action == roster.on_invite_to_new_room: + item.connect('activate', action, [(c, account)], c.resource) + else: # start_chat, execute_command, send_file + item.connect('activate', action, c, account, c.resource) - return sub_menu + if cap and not c.supports(cap): + item.set_sensitive(False) + + return sub_menu def build_invite_submenu(invite_menuitem, list_): - """ - list_ in a list of (contact, account) - """ - roster = gajim.interface.roster - # used if we invite only one contact with several resources - contact_list = [] - if len(list_) == 1: - contact, account = list_[0] - contact_list = gajim.contacts.get_contacts(account, contact.jid) - contacts_transport = -1 - connected_accounts = [] - # -1 is at start, False when not from the same, None when jabber - for (contact, account) in list_: - if not account in connected_accounts: - connected_accounts.append(account) - transport = gajim.get_transport_name_from_jid(contact.jid) - if contacts_transport == -1: - contacts_transport = transport - elif contacts_transport != transport: - contacts_transport = False + """ + list_ in a list of (contact, account) + """ + roster = gajim.interface.roster + # used if we invite only one contact with several resources + contact_list = [] + if len(list_) == 1: + contact, account = list_[0] + contact_list = gajim.contacts.get_contacts(account, contact.jid) + contacts_transport = -1 + connected_accounts = [] + # -1 is at start, False when not from the same, None when jabber + for (contact, account) in list_: + if not account in connected_accounts: + connected_accounts.append(account) + transport = gajim.get_transport_name_from_jid(contact.jid) + if contacts_transport == -1: + contacts_transport = transport + elif contacts_transport != transport: + contacts_transport = False - if contacts_transport == False: - # they are not all from the same transport - invite_menuitem.set_sensitive(False) - return - invite_to_submenu = gtk.Menu() - invite_menuitem.set_submenu(invite_to_submenu) - invite_to_new_room_menuitem = gtk.ImageMenuItem(_('_New Group Chat')) - icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) - invite_to_new_room_menuitem.set_image(icon) - if len(contact_list) > 1: # several resources - invite_to_new_room_menuitem.set_submenu(build_resources_submenu( - contact_list, account, roster.on_invite_to_new_room, cap=NS_MUC)) - elif len(list_) == 1 and contact.supports(NS_MUC): - invite_menuitem.set_sensitive(True) - # use resource if it's self contact - if contact.jid == gajim.get_jid_from_account(account): - resource = contact.resource - else: - resource = None - invite_to_new_room_menuitem.connect('activate', - roster.on_invite_to_new_room, list_, resource) - else: - invite_menuitem.set_sensitive(False) - # transform None in 'jabber' - c_t = contacts_transport or 'jabber' - muc_jid = {} - for account in connected_accounts: - for t in gajim.connections[account].muc_jid: - muc_jid[t] = gajim.connections[account].muc_jid[t] - if c_t not in muc_jid: - invite_to_new_room_menuitem.set_sensitive(False) - rooms = [] # a list of (room_jid, account) tuple - invite_to_submenu.append(invite_to_new_room_menuitem) - rooms = [] # a list of (room_jid, account) tuple - minimized_controls = [] - for account in connected_accounts: - minimized_controls += gajim.interface.minimized_controls[account].values() - for gc_control in gajim.interface.msg_win_mgr.get_controls( - message_control.TYPE_GC) + minimized_controls: - acct = gc_control.account - room_jid = gc_control.room_jid - if room_jid in gajim.gc_connected[acct] and \ - gajim.gc_connected[acct][room_jid] and \ - contacts_transport == gajim.get_transport_name_from_jid(room_jid): - rooms.append((room_jid, acct)) - if len(rooms): - item = gtk.SeparatorMenuItem() # separator - invite_to_submenu.append(item) - for (room_jid, account) in rooms: - menuitem = gtk.MenuItem(room_jid.split('@')[0]) - if len(contact_list) > 1: # several resources - menuitem.set_submenu(build_resources_submenu( - contact_list, account, roster.on_invite_to_room, room_jid, - account)) - else: - # use resource if it's self contact - if contact.jid == gajim.get_jid_from_account(account): - resource = contact.resource - else: - resource = None - menuitem.connect('activate', roster.on_invite_to_room, list_, - room_jid, account, resource) - invite_to_submenu.append(menuitem) + if contacts_transport == False: + # they are not all from the same transport + invite_menuitem.set_sensitive(False) + return + invite_to_submenu = gtk.Menu() + invite_menuitem.set_submenu(invite_to_submenu) + invite_to_new_room_menuitem = gtk.ImageMenuItem(_('_New Group Chat')) + icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) + invite_to_new_room_menuitem.set_image(icon) + if len(contact_list) > 1: # several resources + invite_to_new_room_menuitem.set_submenu(build_resources_submenu( + contact_list, account, roster.on_invite_to_new_room, cap=NS_MUC)) + elif len(list_) == 1 and contact.supports(NS_MUC): + invite_menuitem.set_sensitive(True) + # use resource if it's self contact + if contact.jid == gajim.get_jid_from_account(account): + resource = contact.resource + else: + resource = None + invite_to_new_room_menuitem.connect('activate', + roster.on_invite_to_new_room, list_, resource) + else: + invite_menuitem.set_sensitive(False) + # transform None in 'jabber' + c_t = contacts_transport or 'jabber' + muc_jid = {} + for account in connected_accounts: + for t in gajim.connections[account].muc_jid: + muc_jid[t] = gajim.connections[account].muc_jid[t] + if c_t not in muc_jid: + invite_to_new_room_menuitem.set_sensitive(False) + rooms = [] # a list of (room_jid, account) tuple + invite_to_submenu.append(invite_to_new_room_menuitem) + rooms = [] # a list of (room_jid, account) tuple + minimized_controls = [] + for account in connected_accounts: + minimized_controls += gajim.interface.minimized_controls[account].values() + for gc_control in gajim.interface.msg_win_mgr.get_controls( + message_control.TYPE_GC) + minimized_controls: + acct = gc_control.account + room_jid = gc_control.room_jid + if room_jid in gajim.gc_connected[acct] and \ + gajim.gc_connected[acct][room_jid] and \ + contacts_transport == gajim.get_transport_name_from_jid(room_jid): + rooms.append((room_jid, acct)) + if len(rooms): + item = gtk.SeparatorMenuItem() # separator + invite_to_submenu.append(item) + for (room_jid, account) in rooms: + menuitem = gtk.MenuItem(room_jid.split('@')[0]) + if len(contact_list) > 1: # several resources + menuitem.set_submenu(build_resources_submenu( + contact_list, account, roster.on_invite_to_room, room_jid, + account)) + else: + # use resource if it's self contact + if contact.jid == gajim.get_jid_from_account(account): + resource = contact.resource + else: + resource = None + menuitem.connect('activate', roster.on_invite_to_room, list_, + room_jid, account, resource) + invite_to_submenu.append(menuitem) def get_contact_menu(contact, account, use_multiple_contacts=True, - show_start_chat=True, show_encryption=False, show_buttonbar_items=True, - control=None): - """ - Build contact popup menu for roster and chat window. If control is not set, - we hide invite_contacts_menuitem - """ - if not contact: - return + show_start_chat=True, show_encryption=False, show_buttonbar_items=True, + control=None): + """ + Build contact popup menu for roster and chat window. If control is not set, + we hide invite_contacts_menuitem + """ + if not contact: + return - jid = contact.jid - our_jid = jid == gajim.get_jid_from_account(account) - roster = gajim.interface.roster + jid = contact.jid + our_jid = jid == gajim.get_jid_from_account(account) + roster = gajim.interface.roster - xml = gtkgui_helpers.get_glade('contact_context_menu.glade') - contact_context_menu = xml.get_widget('contact_context_menu') + xml = gtkgui_helpers.get_glade('contact_context_menu.glade') + contact_context_menu = xml.get_widget('contact_context_menu') - start_chat_menuitem = xml.get_widget('start_chat_menuitem') - execute_command_menuitem = xml.get_widget('execute_command_menuitem') - rename_menuitem = xml.get_widget('rename_menuitem') - edit_groups_menuitem = xml.get_widget('edit_groups_menuitem') - send_file_menuitem = xml.get_widget('send_file_menuitem') - assign_openpgp_key_menuitem = xml.get_widget('assign_openpgp_key_menuitem') - add_special_notification_menuitem = xml.get_widget( - 'add_special_notification_menuitem') - information_menuitem = xml.get_widget('information_menuitem') - history_menuitem = xml.get_widget('history_menuitem') - send_custom_status_menuitem = xml.get_widget('send_custom_status_menuitem') - send_single_message_menuitem = xml.get_widget('send_single_message_menuitem') - invite_menuitem = xml.get_widget('invite_menuitem') - block_menuitem = xml.get_widget('block_menuitem') - unblock_menuitem = xml.get_widget('unblock_menuitem') - ignore_menuitem = xml.get_widget('ignore_menuitem') - unignore_menuitem = xml.get_widget('unignore_menuitem') - set_custom_avatar_menuitem = xml.get_widget('set_custom_avatar_menuitem') - # Subscription submenu - subscription_menuitem = xml.get_widget('subscription_menuitem') - send_auth_menuitem, ask_auth_menuitem, revoke_auth_menuitem = \ - subscription_menuitem.get_submenu().get_children() - add_to_roster_menuitem = xml.get_widget('add_to_roster_menuitem') - remove_from_roster_menuitem = xml.get_widget( - 'remove_from_roster_menuitem') - manage_contact_menuitem = xml.get_widget('manage_contact') - convert_to_gc_menuitem = xml.get_widget('convert_to_groupchat_menuitem') - encryption_separator = xml.get_widget('encryption_separator') - toggle_gpg_menuitem = xml.get_widget('toggle_gpg_menuitem') - toggle_e2e_menuitem = xml.get_widget('toggle_e2e_menuitem') - last_separator = xml.get_widget('last_separator') + start_chat_menuitem = xml.get_widget('start_chat_menuitem') + execute_command_menuitem = xml.get_widget('execute_command_menuitem') + rename_menuitem = xml.get_widget('rename_menuitem') + edit_groups_menuitem = xml.get_widget('edit_groups_menuitem') + send_file_menuitem = xml.get_widget('send_file_menuitem') + assign_openpgp_key_menuitem = xml.get_widget('assign_openpgp_key_menuitem') + add_special_notification_menuitem = xml.get_widget( + 'add_special_notification_menuitem') + information_menuitem = xml.get_widget('information_menuitem') + history_menuitem = xml.get_widget('history_menuitem') + send_custom_status_menuitem = xml.get_widget('send_custom_status_menuitem') + send_single_message_menuitem = xml.get_widget('send_single_message_menuitem') + invite_menuitem = xml.get_widget('invite_menuitem') + block_menuitem = xml.get_widget('block_menuitem') + unblock_menuitem = xml.get_widget('unblock_menuitem') + ignore_menuitem = xml.get_widget('ignore_menuitem') + unignore_menuitem = xml.get_widget('unignore_menuitem') + set_custom_avatar_menuitem = xml.get_widget('set_custom_avatar_menuitem') + # Subscription submenu + subscription_menuitem = xml.get_widget('subscription_menuitem') + send_auth_menuitem, ask_auth_menuitem, revoke_auth_menuitem = \ + subscription_menuitem.get_submenu().get_children() + add_to_roster_menuitem = xml.get_widget('add_to_roster_menuitem') + remove_from_roster_menuitem = xml.get_widget( + 'remove_from_roster_menuitem') + manage_contact_menuitem = xml.get_widget('manage_contact') + convert_to_gc_menuitem = xml.get_widget('convert_to_groupchat_menuitem') + encryption_separator = xml.get_widget('encryption_separator') + toggle_gpg_menuitem = xml.get_widget('toggle_gpg_menuitem') + toggle_e2e_menuitem = xml.get_widget('toggle_e2e_menuitem') + last_separator = xml.get_widget('last_separator') - items_to_hide = [] + items_to_hide = [] - # add a special img for send file menuitem - path_to_upload_img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'upload.png') - img = gtk.Image() - img.set_from_file(path_to_upload_img) - send_file_menuitem.set_image(img) + # add a special img for send file menuitem + path_to_upload_img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'upload.png') + img = gtk.Image() + img.set_from_file(path_to_upload_img) + send_file_menuitem.set_image(img) - if not our_jid: - # add a special img for rename menuitem - path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps', - 'kbd_input.png') - img = gtk.Image() - img.set_from_file(path_to_kbd_input_img) - rename_menuitem.set_image(img) + if not our_jid: + # add a special img for rename menuitem + path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps', + 'kbd_input.png') + img = gtk.Image() + img.set_from_file(path_to_kbd_input_img) + rename_menuitem.set_image(img) - muc_icon = gtkgui_helpers.load_icon('muc_active') - if muc_icon: - convert_to_gc_menuitem.set_image(muc_icon) + muc_icon = gtkgui_helpers.load_icon('muc_active') + if muc_icon: + convert_to_gc_menuitem.set_image(muc_icon) - contacts = gajim.contacts.get_contacts(account, jid) - if len(contacts) > 1 and use_multiple_contacts: # several resources - start_chat_menuitem.set_submenu(build_resources_submenu(contacts, - account, gajim.interface.on_open_chat_window)) - send_file_menuitem.set_submenu(build_resources_submenu(contacts, - account, roster.on_send_file_menuitem_activate, cap=NS_FILE)) - execute_command_menuitem.set_submenu(build_resources_submenu( - contacts, account, roster.on_execute_command, cap=NS_COMMANDS)) - else: - start_chat_menuitem.connect('activate', - gajim.interface.on_open_chat_window, contact, account) - if contact.supports(NS_FILE): - send_file_menuitem.set_sensitive(True) - send_file_menuitem.connect('activate', - roster.on_send_file_menuitem_activate, contact, account) - else: - send_file_menuitem.set_sensitive(False) + contacts = gajim.contacts.get_contacts(account, jid) + if len(contacts) > 1 and use_multiple_contacts: # several resources + start_chat_menuitem.set_submenu(build_resources_submenu(contacts, + account, gajim.interface.on_open_chat_window)) + send_file_menuitem.set_submenu(build_resources_submenu(contacts, + account, roster.on_send_file_menuitem_activate, cap=NS_FILE)) + execute_command_menuitem.set_submenu(build_resources_submenu( + contacts, account, roster.on_execute_command, cap=NS_COMMANDS)) + else: + start_chat_menuitem.connect('activate', + gajim.interface.on_open_chat_window, contact, account) + if contact.supports(NS_FILE): + send_file_menuitem.set_sensitive(True) + send_file_menuitem.connect('activate', + roster.on_send_file_menuitem_activate, contact, account) + else: + send_file_menuitem.set_sensitive(False) - if contact.supports(NS_COMMANDS): - execute_command_menuitem.set_sensitive(True) - execute_command_menuitem.connect('activate', roster.on_execute_command, - contact, account, contact.resource) - else: - execute_command_menuitem.set_sensitive(False) + if contact.supports(NS_COMMANDS): + execute_command_menuitem.set_sensitive(True) + execute_command_menuitem.connect('activate', roster.on_execute_command, + contact, account, contact.resource) + else: + execute_command_menuitem.set_sensitive(False) - rename_menuitem.connect('activate', roster.on_rename, 'contact', jid, - account) - history_menuitem.connect('activate', roster.on_history, contact, account) + rename_menuitem.connect('activate', roster.on_rename, 'contact', jid, + account) + history_menuitem.connect('activate', roster.on_history, contact, account) - if control: - convert_to_gc_menuitem.connect('activate', - control._on_convert_to_gc_menuitem_activate) - else: - items_to_hide.append(convert_to_gc_menuitem) + if control: + convert_to_gc_menuitem.connect('activate', + control._on_convert_to_gc_menuitem_activate) + else: + items_to_hide.append(convert_to_gc_menuitem) - if _('Not in Roster') not in contact.get_shown_groups(): - # contact is in normal group - edit_groups_menuitem.connect('activate', roster.on_edit_groups, [(contact, - account)]) + if _('Not in Roster') not in contact.get_shown_groups(): + # contact is in normal group + edit_groups_menuitem.connect('activate', roster.on_edit_groups, [(contact, + account)]) - if gajim.connections[account].gpg: - assign_openpgp_key_menuitem.connect('activate', - roster.on_assign_pgp_key, contact, account) - else: - assign_openpgp_key_menuitem.set_sensitive(False) - else: - # contact is in group 'Not in Roster' - edit_groups_menuitem.set_sensitive(False) - assign_openpgp_key_menuitem.set_sensitive(False) + if gajim.connections[account].gpg: + assign_openpgp_key_menuitem.connect('activate', + roster.on_assign_pgp_key, contact, account) + else: + assign_openpgp_key_menuitem.set_sensitive(False) + else: + # contact is in group 'Not in Roster' + edit_groups_menuitem.set_sensitive(False) + assign_openpgp_key_menuitem.set_sensitive(False) - # Hide items when it's self contact row - if our_jid: - items_to_hide += [rename_menuitem, edit_groups_menuitem] + # Hide items when it's self contact row + if our_jid: + items_to_hide += [rename_menuitem, edit_groups_menuitem] - # Unsensitive many items when account is offline - if gajim.account_is_disconnected(account): - for widget in (start_chat_menuitem, rename_menuitem, - edit_groups_menuitem, send_file_menuitem, convert_to_gc_menuitem): - widget.set_sensitive(False) + # Unsensitive many items when account is offline + if gajim.account_is_disconnected(account): + for widget in (start_chat_menuitem, rename_menuitem, + edit_groups_menuitem, send_file_menuitem, convert_to_gc_menuitem): + widget.set_sensitive(False) - if not show_start_chat: - items_to_hide.append(start_chat_menuitem) + if not show_start_chat: + items_to_hide.append(start_chat_menuitem) - if not show_encryption or not control: - items_to_hide += [encryption_separator, toggle_gpg_menuitem, - toggle_e2e_menuitem] - else: - e2e_is_active = control.session is not None and \ - control.session.enable_encryption + if not show_encryption or not control: + items_to_hide += [encryption_separator, toggle_gpg_menuitem, + toggle_e2e_menuitem] + else: + e2e_is_active = control.session is not None and \ + control.session.enable_encryption - # check if we support and use gpg - if not gajim.config.get_per('accounts', account, 'keyid') or \ - not gajim.connections[account].USE_GPG or gajim.jid_is_transport( - contact.jid): - toggle_gpg_menuitem.set_sensitive(False) - else: - toggle_gpg_menuitem.set_sensitive(control.gpg_is_active or \ - not e2e_is_active) - toggle_gpg_menuitem.set_active(control.gpg_is_active) - toggle_gpg_menuitem.connect('activate', - control._on_toggle_gpg_menuitem_activate) + # check if we support and use gpg + if not gajim.config.get_per('accounts', account, 'keyid') or \ + not gajim.connections[account].USE_GPG or gajim.jid_is_transport( + contact.jid): + toggle_gpg_menuitem.set_sensitive(False) + else: + toggle_gpg_menuitem.set_sensitive(control.gpg_is_active or \ + not e2e_is_active) + toggle_gpg_menuitem.set_active(control.gpg_is_active) + toggle_gpg_menuitem.connect('activate', + control._on_toggle_gpg_menuitem_activate) - # disable esessions if we or the other client don't support them - if not gajim.HAVE_PYCRYPTO or not contact.supports(NS_ESESSION) or \ - not gajim.config.get_per('accounts', account, 'enable_esessions'): - toggle_e2e_menuitem.set_sensitive(False) - else: - toggle_e2e_menuitem.set_active(e2e_is_active) - toggle_e2e_menuitem.set_sensitive(e2e_is_active or \ - not control.gpg_is_active) - toggle_e2e_menuitem.connect('activate', - control._on_toggle_e2e_menuitem_activate) + # disable esessions if we or the other client don't support them + if not gajim.HAVE_PYCRYPTO or not contact.supports(NS_ESESSION) or \ + not gajim.config.get_per('accounts', account, 'enable_esessions'): + toggle_e2e_menuitem.set_sensitive(False) + else: + toggle_e2e_menuitem.set_active(e2e_is_active) + toggle_e2e_menuitem.set_sensitive(e2e_is_active or \ + not control.gpg_is_active) + toggle_e2e_menuitem.connect('activate', + control._on_toggle_e2e_menuitem_activate) - if not show_buttonbar_items: - items_to_hide += [history_menuitem, send_file_menuitem, - information_menuitem, convert_to_gc_menuitem, last_separator] - - if not control: - items_to_hide.append(convert_to_gc_menuitem) + if not show_buttonbar_items: + items_to_hide += [history_menuitem, send_file_menuitem, + information_menuitem, convert_to_gc_menuitem, last_separator] - for item in items_to_hide: - item.set_no_show_all(True) - item.hide() + if not control: + items_to_hide.append(convert_to_gc_menuitem) - # Zeroconf Account - if gajim.config.get_per('accounts', account, 'is_zeroconf'): - for item in (send_custom_status_menuitem, send_single_message_menuitem, - invite_menuitem, block_menuitem, unblock_menuitem, ignore_menuitem, - unignore_menuitem, set_custom_avatar_menuitem, subscription_menuitem, - manage_contact_menuitem, convert_to_gc_menuitem): - item.set_no_show_all(True) - item.hide() + for item in items_to_hide: + item.set_no_show_all(True) + item.hide() - if contact.show in ('offline', 'error'): - information_menuitem.set_sensitive(False) - send_file_menuitem.set_sensitive(False) - else: - information_menuitem.connect('activate', roster.on_info_zeroconf, - contact, account) + # Zeroconf Account + if gajim.config.get_per('accounts', account, 'is_zeroconf'): + for item in (send_custom_status_menuitem, send_single_message_menuitem, + invite_menuitem, block_menuitem, unblock_menuitem, ignore_menuitem, + unignore_menuitem, set_custom_avatar_menuitem, subscription_menuitem, + manage_contact_menuitem, convert_to_gc_menuitem): + item.set_no_show_all(True) + item.hide() - contact_context_menu.connect('selection-done', - gtkgui_helpers.destroy_widget) - contact_context_menu.show_all() - return contact_context_menu + if contact.show in ('offline', 'error'): + information_menuitem.set_sensitive(False) + send_file_menuitem.set_sensitive(False) + else: + information_menuitem.connect('activate', roster.on_info_zeroconf, + contact, account) - # normal account + contact_context_menu.connect('selection-done', + gtkgui_helpers.destroy_widget) + contact_context_menu.show_all() + return contact_context_menu - # send custom status icon - blocked = False - if helpers.jid_is_blocked(account, jid): - blocked = True - else: - for group in contact.get_shown_groups(): - if helpers.group_is_blocked(account, group): - blocked = True - break - if gajim.get_transport_name_from_jid(jid, use_config_setting=False): - # Transport contact, send custom status unavailable - send_custom_status_menuitem.set_sensitive(False) - elif blocked: - send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon('offline')) - send_custom_status_menuitem.set_sensitive(False) - elif account in gajim.interface.status_sent_to_users and \ - jid in gajim.interface.status_sent_to_users[account]: - send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( - gajim.interface.status_sent_to_users[account][jid])) - else: - icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_MENU) - send_custom_status_menuitem.set_image(icon) + # normal account - muc_icon = gtkgui_helpers.load_icon('muc_active') - if muc_icon: - invite_menuitem.set_image(muc_icon) + # send custom status icon + blocked = False + if helpers.jid_is_blocked(account, jid): + blocked = True + else: + for group in contact.get_shown_groups(): + if helpers.group_is_blocked(account, group): + blocked = True + break + if gajim.get_transport_name_from_jid(jid, use_config_setting=False): + # Transport contact, send custom status unavailable + send_custom_status_menuitem.set_sensitive(False) + elif blocked: + send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon('offline')) + send_custom_status_menuitem.set_sensitive(False) + elif account in gajim.interface.status_sent_to_users and \ + jid in gajim.interface.status_sent_to_users[account]: + send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( + gajim.interface.status_sent_to_users[account][jid])) + else: + icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_MENU) + send_custom_status_menuitem.set_image(icon) - build_invite_submenu(invite_menuitem, [(contact, account)]) + muc_icon = gtkgui_helpers.load_icon('muc_active') + if muc_icon: + invite_menuitem.set_image(muc_icon) - # One or several resource, we do the same for send_custom_status - status_menuitems = gtk.Menu() - send_custom_status_menuitem.set_submenu(status_menuitems) - iconset = gajim.config.get('iconset') - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'): - # icon MUST be different instance for every item - state_images = gtkgui_helpers.load_iconset(path) - status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s)) - status_menuitem.connect('activate', roster.on_send_custom_status, - [(contact, account)], s) - icon = state_images[s] - status_menuitem.set_image(icon) - status_menuitems.append(status_menuitem) + build_invite_submenu(invite_menuitem, [(contact, account)]) - send_single_message_menuitem.connect('activate', - roster.on_send_single_message_menuitem_activate, account, contact) + # One or several resource, we do the same for send_custom_status + status_menuitems = gtk.Menu() + send_custom_status_menuitem.set_submenu(status_menuitems) + iconset = gajim.config.get('iconset') + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'): + # icon MUST be different instance for every item + state_images = gtkgui_helpers.load_iconset(path) + status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s)) + status_menuitem.connect('activate', roster.on_send_custom_status, + [(contact, account)], s) + icon = state_images[s] + status_menuitem.set_image(icon) + status_menuitems.append(status_menuitem) - remove_from_roster_menuitem.connect('activate', roster.on_req_usub, - [(contact, account)]) - information_menuitem.connect('activate', roster.on_info, contact, account) + send_single_message_menuitem.connect('activate', + roster.on_send_single_message_menuitem_activate, account, contact) - if _('Not in Roster') not in contact.get_shown_groups(): - # contact is in normal group - add_to_roster_menuitem.hide() - add_to_roster_menuitem.set_no_show_all(True) + remove_from_roster_menuitem.connect('activate', roster.on_req_usub, + [(contact, account)]) + information_menuitem.connect('activate', roster.on_info, contact, account) - if contact.sub in ('from', 'both'): - send_auth_menuitem.set_sensitive(False) - else: - send_auth_menuitem.connect('activate', roster.authorize, jid, account) - if contact.sub in ('to', 'both'): - ask_auth_menuitem.set_sensitive(False) - add_special_notification_menuitem.connect('activate', - roster.on_add_special_notification_menuitem_activate, jid) - else: - ask_auth_menuitem.connect('activate', roster.req_sub, jid, - _('I would like to add you to my roster'), account, - contact.groups, contact.name) - if contact.sub in ('to', 'none') or gajim.get_transport_name_from_jid( - jid, use_config_setting=False): - revoke_auth_menuitem.set_sensitive(False) - else: - revoke_auth_menuitem.connect('activate', roster.revoke_auth, jid, - account) + if _('Not in Roster') not in contact.get_shown_groups(): + # contact is in normal group + add_to_roster_menuitem.hide() + add_to_roster_menuitem.set_no_show_all(True) - else: - # contact is in group 'Not in Roster' - add_to_roster_menuitem.set_no_show_all(False) - subscription_menuitem.set_sensitive(False) + if contact.sub in ('from', 'both'): + send_auth_menuitem.set_sensitive(False) + else: + send_auth_menuitem.connect('activate', roster.authorize, jid, account) + if contact.sub in ('to', 'both'): + ask_auth_menuitem.set_sensitive(False) + add_special_notification_menuitem.connect('activate', + roster.on_add_special_notification_menuitem_activate, jid) + else: + ask_auth_menuitem.connect('activate', roster.req_sub, jid, + _('I would like to add you to my roster'), account, + contact.groups, contact.name) + if contact.sub in ('to', 'none') or gajim.get_transport_name_from_jid( + jid, use_config_setting=False): + revoke_auth_menuitem.set_sensitive(False) + else: + revoke_auth_menuitem.connect('activate', roster.revoke_auth, jid, + account) - add_to_roster_menuitem.connect('activate', roster.on_add_to_roster, - contact, account) + else: + # contact is in group 'Not in Roster' + add_to_roster_menuitem.set_no_show_all(False) + subscription_menuitem.set_sensitive(False) - set_custom_avatar_menuitem.connect('activate', - roster.on_set_custom_avatar_activate, contact, account) + add_to_roster_menuitem.connect('activate', roster.on_add_to_roster, + contact, account) - # Hide items when it's self contact row - if our_jid: - manage_contact_menuitem.set_sensitive(False) + set_custom_avatar_menuitem.connect('activate', + roster.on_set_custom_avatar_activate, contact, account) - # Unsensitive items when account is offline - if gajim.account_is_disconnected(account): - for widget in (send_single_message_menuitem, subscription_menuitem, - add_to_roster_menuitem, remove_from_roster_menuitem, - execute_command_menuitem, send_custom_status_menuitem): - widget.set_sensitive(False) + # Hide items when it's self contact row + if our_jid: + manage_contact_menuitem.set_sensitive(False) - if gajim.connections[account] and gajim.connections[account].\ - privacy_rules_supported: - if helpers.jid_is_blocked(account, jid): - block_menuitem.set_no_show_all(True) - block_menuitem.hide() - if gajim.get_transport_name_from_jid(jid, use_config_setting=False): - unblock_menuitem.set_no_show_all(True) - unblock_menuitem.hide() - unignore_menuitem.set_no_show_all(False) - unignore_menuitem.connect('activate', roster.on_unblock, [(contact, - account)]) - else: - unblock_menuitem.connect('activate', roster.on_unblock, [(contact, - account)]) - else: - unblock_menuitem.set_no_show_all(True) - unblock_menuitem.hide() - if gajim.get_transport_name_from_jid(jid, use_config_setting=False): - block_menuitem.set_no_show_all(True) - block_menuitem.hide() - ignore_menuitem.set_no_show_all(False) - ignore_menuitem.connect('activate', roster.on_block, [(contact, - account)]) - else: - block_menuitem.connect('activate', roster.on_block, [(contact, - account)]) - else: - unblock_menuitem.set_no_show_all(True) - block_menuitem.set_sensitive(False) - unblock_menuitem.hide() + # Unsensitive items when account is offline + if gajim.account_is_disconnected(account): + for widget in (send_single_message_menuitem, subscription_menuitem, + add_to_roster_menuitem, remove_from_roster_menuitem, + execute_command_menuitem, send_custom_status_menuitem): + widget.set_sensitive(False) - contact_context_menu.connect('selection-done', gtkgui_helpers.destroy_widget) - contact_context_menu.show_all() - return contact_context_menu + if gajim.connections[account] and gajim.connections[account].\ + privacy_rules_supported: + if helpers.jid_is_blocked(account, jid): + block_menuitem.set_no_show_all(True) + block_menuitem.hide() + if gajim.get_transport_name_from_jid(jid, use_config_setting=False): + unblock_menuitem.set_no_show_all(True) + unblock_menuitem.hide() + unignore_menuitem.set_no_show_all(False) + unignore_menuitem.connect('activate', roster.on_unblock, [(contact, + account)]) + else: + unblock_menuitem.connect('activate', roster.on_unblock, [(contact, + account)]) + else: + unblock_menuitem.set_no_show_all(True) + unblock_menuitem.hide() + if gajim.get_transport_name_from_jid(jid, use_config_setting=False): + block_menuitem.set_no_show_all(True) + block_menuitem.hide() + ignore_menuitem.set_no_show_all(False) + ignore_menuitem.connect('activate', roster.on_block, [(contact, + account)]) + else: + block_menuitem.connect('activate', roster.on_block, [(contact, + account)]) + else: + unblock_menuitem.set_no_show_all(True) + block_menuitem.set_sensitive(False) + unblock_menuitem.hide() -# vim: se ts=3: + contact_context_menu.connect('selection-done', gtkgui_helpers.destroy_widget) + contact_context_menu.show_all() + return contact_context_menu diff --git a/src/history_manager.py b/src/history_manager.py index 5d171b75c..ad1673b87 100644 --- a/src/history_manager.py +++ b/src/history_manager.py @@ -30,21 +30,21 @@ import os if os.name == 'nt': - import warnings - warnings.filterwarnings(action='ignore') + import warnings + warnings.filterwarnings(action='ignore') - if os.path.isdir('gtk'): - # Used to create windows installer with GTK included - paths = os.environ['PATH'] - list_ = paths.split(';') - new_list = [] - for p in list_: - if p.find('gtk') < 0 and p.find('GTK') < 0: - new_list.append(p) - new_list.insert(0, 'gtk/lib') - new_list.insert(0, 'gtk/bin') - os.environ['PATH'] = ';'.join(new_list) - os.environ['GTK_BASEPATH'] = 'gtk' + if os.path.isdir('gtk'): + # Used to create windows installer with GTK included + paths = os.environ['PATH'] + list_ = paths.split(';') + new_list = [] + for p in list_: + if p.find('gtk') < 0 and p.find('GTK') < 0: + new_list.append(p) + new_list.insert(0, 'gtk/lib') + new_list.insert(0, 'gtk/bin') + os.environ['PATH'] = ';'.join(new_list) + os.environ['GTK_BASEPATH'] = 'gtk' import sys import signal @@ -57,23 +57,23 @@ import getopt from common import i18n def parseOpts(): - config_path = None + config_path = None - try: - shortargs = 'hc:' - longargs = 'help config_path=' - opts = getopt.getopt(sys.argv[1:], shortargs, longargs.split())[0] - except getopt.error, msg: - print str(msg) - print 'for help use --help' - sys.exit(2) - for o, a in opts: - if o in ('-h', '--help'): - print 'history_manager [--help] [--config-path]' - sys.exit() - elif o in ('-c', '--config-path'): - config_path = a - return config_path + try: + shortargs = 'hc:' + longargs = 'help config_path=' + opts = getopt.getopt(sys.argv[1:], shortargs, longargs.split())[0] + except getopt.error, msg: + print str(msg) + print 'for help use --help' + sys.exit(2) + for o, a in opts: + if o in ('-h', '--help'): + print 'history_manager [--help] [--config-path]' + sys.exit() + elif o in ('-c', '--config-path'): + config_path = a + return config_path config_path = parseOpts() del parseOpts @@ -89,7 +89,7 @@ from common.logger import LOG_DB_PATH, constants #FIXME: constants should implement 2 way mappings status = dict((constants.__dict__[i], i[5:].lower()) for i in \ - constants.__dict__.keys() if i.startswith('SHOW_')) + constants.__dict__.keys() if i.startswith('SHOW_')) from common import helpers import dialogs @@ -103,567 +103,565 @@ C_NICKNAME try: - import sqlite3 as sqlite # python 2.5 + import sqlite3 as sqlite # python 2.5 except ImportError: - try: - from pysqlite2 import dbapi2 as sqlite - except ImportError: - raise exceptions.PysqliteNotAvailable + try: + from pysqlite2 import dbapi2 as sqlite + except ImportError: + raise exceptions.PysqliteNotAvailable class HistoryManager: - def __init__(self): - path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps/gajim.png') - pix = gtk.gdk.pixbuf_new_from_file(path_to_file) - gtk.window_set_default_icon(pix) # set the icon to all newly opened windows - - if not os.path.exists(LOG_DB_PATH): - dialogs.ErrorDialog(_('Cannot find history logs database'), - '%s does not exist.' % LOG_DB_PATH) - sys.exit() - - xml = gtkgui_helpers.get_glade('history_manager.glade') - self.window = xml.get_widget('history_manager_window') - self.jids_listview = xml.get_widget('jids_listview') - self.logs_listview = xml.get_widget('logs_listview') - self.search_results_listview = xml.get_widget('search_results_listview') - self.search_entry = xml.get_widget('search_entry') - self.logs_scrolledwindow = xml.get_widget('logs_scrolledwindow') - self.search_results_scrolledwindow = xml.get_widget( - 'search_results_scrolledwindow') - self.welcome_vbox = xml.get_widget('welcome_vbox') - - self.jids_already_in = [] # holds jids that we already have in DB - self.AT_LEAST_ONE_DELETION_DONE = False - - self.con = sqlite.connect(LOG_DB_PATH, timeout = 20.0, - isolation_level = 'IMMEDIATE') - self.cur = self.con.cursor() - - self._init_jids_listview() - self._init_logs_listview() - self._init_search_results_listview() - - self._fill_jids_listview() - - self.search_entry.grab_focus() - - self.window.show_all() - - xml.signal_autoconnect(self) - - def _init_jids_listview(self): - self.jids_liststore = gtk.ListStore(str, str) # jid, jid_id - self.jids_listview.set_model(self.jids_liststore) - self.jids_listview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) - - renderer_text = gtk.CellRendererText() # holds jid - col = gtk.TreeViewColumn(_('Contacts'), renderer_text, text = 0) - self.jids_listview.append_column(col) - - self.jids_listview.get_selection().connect('changed', - self.on_jids_listview_selection_changed) - - def _init_logs_listview(self): - # log_line_id (HIDDEN), jid_id (HIDDEN), time, message, subject, nickname - self.logs_liststore = gtk.ListStore(str, str, str, str, str, str) - self.logs_listview.set_model(self.logs_liststore) - self.logs_listview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) - - renderer_text = gtk.CellRendererText() # holds time - col = gtk.TreeViewColumn(_('Date'), renderer_text, text = C_UNIXTIME) - col.set_sort_column_id(C_UNIXTIME) # user can click this header and sort - col.set_resizable(True) - self.logs_listview.append_column(col) - - renderer_text = gtk.CellRendererText() # holds nickname - col = gtk.TreeViewColumn(_('Nickname'), renderer_text, text = C_NICKNAME) - col.set_sort_column_id(C_NICKNAME) # user can click this header and sort - col.set_resizable(True) - col.set_visible(False) - self.nickname_col_for_logs = col - self.logs_listview.append_column(col) - - renderer_text = gtk.CellRendererText() # holds message - col = gtk.TreeViewColumn(_('Message'), renderer_text, markup = C_MESSAGE) - col.set_sort_column_id(C_MESSAGE) # user can click this header and sort - col.set_resizable(True) - self.message_col_for_logs = col - self.logs_listview.append_column(col) - - renderer_text = gtk.CellRendererText() # holds subject - col = gtk.TreeViewColumn(_('Subject'), renderer_text, text = C_SUBJECT) - col.set_sort_column_id(C_SUBJECT) # user can click this header and sort - col.set_resizable(True) - col.set_visible(False) - self.subject_col_for_logs = col - self.logs_listview.append_column(col) - - def _init_search_results_listview(self): - # log_line_id (HIDDEN), jid, time, message, subject, nickname - self.search_results_liststore = gtk.ListStore(str, str, str, str, str, str) - self.search_results_listview.set_model(self.search_results_liststore) - - renderer_text = gtk.CellRendererText() # holds JID (who said this) - col = gtk.TreeViewColumn(_('JID'), renderer_text, text = 1) - col.set_sort_column_id(1) # user can click this header and sort - col.set_resizable(True) - self.search_results_listview.append_column(col) - - renderer_text = gtk.CellRendererText() # holds time - col = gtk.TreeViewColumn(_('Date'), renderer_text, text = C_UNIXTIME) - col.set_sort_column_id(C_UNIXTIME) # user can click this header and sort - col.set_resizable(True) - self.search_results_listview.append_column(col) - - renderer_text = gtk.CellRendererText() # holds message - col = gtk.TreeViewColumn(_('Message'), renderer_text, text = C_MESSAGE) - col.set_sort_column_id(C_MESSAGE) # user can click this header and sort - col.set_resizable(True) - self.search_results_listview.append_column(col) - - renderer_text = gtk.CellRendererText() # holds subject - col = gtk.TreeViewColumn(_('Subject'), renderer_text, text = C_SUBJECT) - col.set_sort_column_id(C_SUBJECT) # user can click this header and sort - col.set_resizable(True) - self.search_results_listview.append_column(col) - - renderer_text = gtk.CellRendererText() # holds nickname - col = gtk.TreeViewColumn(_('Nickname'), renderer_text, text = C_NICKNAME) - col.set_sort_column_id(C_NICKNAME) # user can click this header and sort - col.set_resizable(True) - self.search_results_listview.append_column(col) - - def on_history_manager_window_delete_event(self, widget, event): - if self.AT_LEAST_ONE_DELETION_DONE: - def on_yes(clicked): - self.cur.execute('VACUUM') - self.con.commit() - gtk.main_quit() - - def on_no(): - gtk.main_quit() - - dialogs.YesNoDialog( - _('Do you want to clean up the database? ' - '(STRONGLY NOT RECOMMENDED IF GAJIM IS RUNNING)'), - _('Normally allocated database size will not be freed, ' - 'it will just become reusable. If you really want to reduce ' - 'database filesize, click YES, else click NO.' - '\n\nIn case you click YES, please wait...'), - on_response_yes=on_yes, on_response_no=on_no) - return - - gtk.main_quit() - - def _fill_jids_listview(self): - # get those jids that have at least one entry in logs - self.cur.execute('SELECT jid, jid_id FROM jids WHERE jid_id IN (SELECT ' - 'distinct logs.jid_id FROM logs) ORDER BY jid') - rows = self.cur.fetchall() # list of tupples: [(u'aaa@bbb',), (u'cc@dd',)] - for row in rows: - self.jids_already_in.append(row[0]) # jid - self.jids_liststore.append(row) # jid, jid_id - - def on_jids_listview_selection_changed(self, widget, data = None): - liststore, list_of_paths = self.jids_listview.get_selection()\ - .get_selected_rows() - paths_len = len(list_of_paths) - if paths_len == 0: # nothing is selected - return - - self.logs_liststore.clear() # clear the store - - self.welcome_vbox.hide() - self.search_results_scrolledwindow.hide() - self.logs_scrolledwindow.show() - - list_of_rowrefs = [] - for path in list_of_paths: # make them treerowrefs (it's needed) - list_of_rowrefs.append(gtk.TreeRowReference(liststore, path)) - - for rowref in list_of_rowrefs: # FILL THE STORE, for all rows selected - path = rowref.get_path() - if path is None: - continue - jid = liststore[path][0] # jid - self._fill_logs_listview(jid) - - def _get_jid_id(self, jid): - """ - jids table has jid and jid_id - logs table has log_id, jid_id, contact_name, time, kind, show, message - - So to ask logs we need jid_id that matches our jid in jids table this - method wants jid and returns the jid_id for later sql-ing on logs - """ - if jid.find('/') != -1: # if it has a / - jid_is_from_pm = self._jid_is_from_pm(jid) - if not jid_is_from_pm: # it's normal jid with resource - jid = jid.split('/', 1)[0] # remove the resource - self.cur.execute('SELECT jid_id FROM jids WHERE jid = ?', (jid,)) - jid_id = self.cur.fetchone()[0] - return str(jid_id) - - def _get_jid_from_jid_id(self, jid_id): - """ - jids table has jid and jid_id - - This method accepts jid_id and returns the jid for later sql-ing on logs - """ - self.cur.execute('SELECT jid FROM jids WHERE jid_id = ?', (jid_id,)) - jid = self.cur.fetchone()[0] - return jid - - def _jid_is_from_pm(self, jid): - """ - If jid is gajim@conf/nkour it's likely a pm one, how we know gajim@conf - is not a normal guy and nkour is not his resource? We ask if gajim@conf - is already in jids (with type room jid). This fails if user disables - logging for room and only enables for pm (so higly unlikely) and if we - fail we do not go chaos (user will see the first pm as if it was message - in room's public chat) and after that everything is ok - """ - possible_room_jid = jid.split('/', 1)[0] - - self.cur.execute('SELECT jid_id FROM jids WHERE jid = ? AND type = ?', - (possible_room_jid, constants.JID_ROOM_TYPE)) - row = self.cur.fetchone() - if row is None: - return False - else: - return True - - def _jid_is_room_type(self, jid): - """ - Return True/False if given id is room type or not eg. if it is room - """ - self.cur.execute('SELECT type FROM jids WHERE jid = ?', (jid,)) - row = self.cur.fetchone() - if row is None: - raise - elif row[0] == constants.JID_ROOM_TYPE: - return True - else: # normal type - return False - - def _fill_logs_listview(self, jid): - """ - Fill the listview with all messages that user sent to or received from - JID - """ - # no need to lower jid in this context as jid is already lowered - # as we use those jids from db - jid_id = self._get_jid_id(jid) - self.cur.execute(''' - SELECT log_line_id, jid_id, time, kind, message, subject, contact_name, show - FROM logs - WHERE jid_id = ? - ORDER BY time - ''', (jid_id,)) - - results = self.cur.fetchall() - - if self._jid_is_room_type(jid): # is it room? - self.nickname_col_for_logs.set_visible(True) - self.subject_col_for_logs.set_visible(False) - else: - self.nickname_col_for_logs.set_visible(False) - self.subject_col_for_logs.set_visible(True) - - for row in results: - # exposed in UI (TreeViewColumns) are only - # time, message, subject, nickname - # but store in liststore - # log_line_id, jid_id, time, message, subject, nickname - log_line_id, jid_id, time_, kind, message, subject, nickname, show = row - try: - time_ = time.strftime('%x', time.localtime(float(time_))).decode( - locale.getpreferredencoding()) - except ValueError: - pass - else: - color = None - if kind in (constants.KIND_SINGLE_MSG_RECV, - constants.KIND_CHAT_MSG_RECV, constants.KIND_GC_MSG): - # it is the other side - color = gajim.config.get('inmsgcolor') # so incoming color - elif kind in (constants.KIND_SINGLE_MSG_SENT, - constants.KIND_CHAT_MSG_SENT): # it is us - color = gajim.config.get('outmsgcolor') # so outgoing color - elif kind in (constants.KIND_STATUS, - constants.KIND_GCSTATUS): # is is statuses - color = gajim.config.get('statusmsgcolor') # so status color - # include status into (status) message - if message is None: - message = '' - else: - message = ' : ' + message - message = helpers.get_uf_show(gajim.SHOW_LIST[show]) + message - - message_ = ' every_foo_seconds: - self.last_time_printout = tim - tim = time.strftime('%X ', time.localtime(float(tim))) - buf.insert_with_tags_by_name(end_iter, tim + '\n', - 'time_sometimes') - - tag_name = '' - tag_msg = '' - - show = self._get_string_show_from_constant_int(show) - - if kind == constants.KIND_GC_MSG: - tag_name = 'incoming' - elif kind in (constants.KIND_SINGLE_MSG_RECV, - constants.KIND_CHAT_MSG_RECV): - contact_name = self.completion_dict[self.jid][C_INFO_NAME] - tag_name = 'incoming' - tag_msg = 'incomingtxt' - elif kind in (constants.KIND_SINGLE_MSG_SENT, - constants.KIND_CHAT_MSG_SENT): - if self.account: - contact_name = gajim.nicks[self.account] - else: - # we don't have roster, we don't know our own nick, use first - # account one (urk!) - account = gajim.contacts.get_accounts()[0] - contact_name = gajim.nicks[account] - tag_name = 'outgoing' - tag_msg = 'outgoingtxt' - elif kind == constants.KIND_GCSTATUS: - # message here (if not None) is status message - if message: - message = _('%(nick)s is now %(status)s: %(status_msg)s') %\ - {'nick': contact_name, 'status': helpers.get_uf_show(show), - 'status_msg': message } - else: - message = _('%(nick)s is now %(status)s') % {'nick': contact_name, - 'status': helpers.get_uf_show(show) } - tag_msg = 'status' - else: # 'status' - # message here (if not None) is status message - if show is None: # it means error - if message: - message = _('Error: %s') % message - else: - message = _('Error') - elif message: - message = _('Status is now: %(status)s: %(status_msg)s') % \ - {'status': helpers.get_uf_show(show), 'status_msg': message} - else: - message = _('Status is now: %(status)s') % { 'status': - helpers.get_uf_show(show) } - tag_msg = 'status' - - if message.startswith('/me ') or message.startswith('/me\n'): - tag_msg = tag_name - else: - # do not do this if gcstats, avoid dupping contact_name - # eg. nkour: nkour is now Offline - if contact_name and kind != constants.KIND_GCSTATUS: - # add stuff before and after contact name - before_str = gajim.config.get('before_nickname') - before_str = helpers.from_one_line(before_str) - after_str = gajim.config.get('after_nickname') - after_str = helpers.from_one_line(after_str) - format = before_str + contact_name + after_str + ' ' - buf.insert_with_tags_by_name(end_iter, format, tag_name) - - if subject: - message = _('Subject: %s\n') % subject + message - message += '\n' - if tag_msg: - self.history_textview.print_real_text(message, [tag_msg], - name=contact_name) - else: - self.history_textview.print_real_text(message, name=contact_name) - - def on_query_entry_activate(self, widget): - text = self.query_entry.get_text() - model = self.results_treeview.get_model() - model.clear() - if text == '': - self.results_window.set_property('visible', False) - return - else: - self.results_window.set_property('visible', True) - - # perform search in preselected jids - # jids are preselected with the query_combobox (all, single jid...) - for jid in self.jids_to_search: - account = self.completion_dict[jid][C_INFO_ACCOUNT] - if account is None: - # We do not know an account. This can only happen if the contact is offine, - # or if we browse a groupchat history. The account is not needed, a dummy can - # be set. - # This may leed to wrong self nick in the displayed history (Uggh!) - account = gajim.contacts.get_accounts()[0] - - # contact_name, time, kind, show, message, subject - results = gajim.logger.get_search_results_for_query( - jid, text, account) - #FIXME: - # add "subject: | message: " in message column if kind is single - # also do we need show at all? (we do not search on subject) - for row in results: - contact_name = row[0] - if not contact_name: - kind = row[2] - if kind == constants.KIND_CHAT_MSG_SENT: # it's us! :) - contact_name = gajim.nicks[account] - else: - contact_name = self.completion_dict[jid][C_INFO_NAME] - tim = row[1] - message = row[4] - local_time = time.localtime(tim) - date = time.strftime('%Y-%m-%d', local_time) - - # jid (to which log is assigned to), name, date, message, - # time (full unix time) - model.append((jid, contact_name, date, message, tim)) - - def on_query_combobox_changed(self, widget): - if self.query_combobox.get_active() < 0: - return # custom entry - self.account = None - self.jid = None - self.jids_to_search = [] - self._load_history(None) # clear textview - - if self.query_combobox.get_active() == 0: - # JID or Contact name - self.query_entry.set_sensitive(False) - self.jid_entry.grab_focus() - if self.query_combobox.get_active() == 1: - # Groupchat Histories - self.query_entry.set_sensitive(True) - self.query_entry.grab_focus() - self.jids_to_search = (jid for jid in gajim.logger.get_jids_in_db() - if gajim.logger.jid_is_room_jid(jid)) - if self.query_combobox.get_active() == 2: - # All Chat Histories - self.query_entry.set_sensitive(True) - self.query_entry.grab_focus() - self.jids_to_search = gajim.logger.get_jids_in_db() - - def on_results_treeview_row_activated(self, widget, path, column): - '''a row was double clicked, get date from row, and select it in calendar - which results to showing conversation logs for that date''' - # get currently selected date - cur_year, cur_month = self.calendar.get_date()[0:2] - cur_month = gtkgui_helpers.make_gtk_month_python_month(cur_month) - model = widget.get_model() - # make it a tupple (Y, M, D, 0, 0, 0...) - tim = time.strptime(model[path][C_UNIXTIME], '%Y-%m-%d') - year = tim[0] - gtk_month = tim[1] - month = gtkgui_helpers.make_python_month_gtk_month(gtk_month) - day = tim[2] - - # switch to belonging logfile if necessary - log_jid = model[path][C_LOG_JID] - if log_jid != self.jid: - self._load_history(log_jid, None) - - # avoid reruning mark days algo if same month and year! - if year != cur_year or gtk_month != cur_month: - self.calendar.select_month(month, year) - - self.calendar.select_day(day) - unix_time = model[path][C_TIME] - self._scroll_to_result(unix_time) - #FIXME: one day do not search just for unix_time but the whole and user - # specific format of the textbuffer line [time] nick: message - # and highlight all that - - def _scroll_to_result(self, unix_time): - '''scrolls to the result using unix_time and highlight line''' - start_iter = self.history_buffer.get_start_iter() - local_time = time.localtime(float(unix_time)) - tim = time.strftime('%X', local_time) - result = start_iter.forward_search(tim, gtk.TEXT_SEARCH_VISIBLE_ONLY, - None) - if result is not None: - match_start_iter, match_end_iter = result - match_start_iter.backward_char() # include '[' or other character before time - match_end_iter.forward_line() # highlight all message not just time - self.history_buffer.apply_tag_by_name('highlight', match_start_iter, - match_end_iter) - - match_start_mark = self.history_buffer.create_mark('match_start', - match_start_iter, True) - self.history_textview.tv.scroll_to_mark(match_start_mark, 0, True) - - def on_log_history_checkbutton_toggled(self, widget): - # log conversation history? - oldlog = True - no_log_for = gajim.config.get_per('accounts', self.account, - 'no_log_for').split() - if self.jid in no_log_for: - oldlog = False - log = widget.get_active() - if not log and not self.jid in no_log_for: - no_log_for.append(self.jid) - if log and self.jid in no_log_for: - no_log_for.remove(self.jid) - if oldlog != log: - gajim.config.set_per('accounts', self.account, 'no_log_for', - ' '.join(no_log_for)) - - def open_history(self, jid, account): - '''Load chat history of the specified jid''' - self.jid_entry.set_text(jid) - if account and account not in self.accounts_seen_online: - # Update dict to not only show bare jid - gobject.idle_add(self._fill_completion_dict().next) - else: - # Only in that case because it's called by self._fill_completion_dict() - # otherwise - self._load_history(jid, account) - self.results_window.set_property('visible', False) - - def save_state(self): - x,y = self.window.window.get_root_origin() - width, height = self.window.get_size() - - gajim.config.set('history_window_x-position', x) - gajim.config.set('history_window_y-position', y) - gajim.config.set('history_window_width', width); - gajim.config.set('history_window_height', height); - - gajim.interface.save_config() - -# vim: se ts=3: + '''Class for browsing logs of conversations with contacts''' + + def __init__(self, jid = None, account = None): + xml = gtkgui_helpers.get_glade('history_window.glade') + self.window = xml.get_widget('history_window') + self.jid_entry = xml.get_widget('jid_entry') + self.calendar = xml.get_widget('calendar') + scrolledwindow = xml.get_widget('scrolledwindow') + self.history_textview = conversation_textview.ConversationTextview( + account, used_in_history_window = True) + scrolledwindow.add(self.history_textview.tv) + self.history_buffer = self.history_textview.tv.get_buffer() + self.history_buffer.create_tag('highlight', background = 'yellow') + self.checkbutton = xml.get_widget('log_history_checkbutton') + self.checkbutton.connect('toggled', + self.on_log_history_checkbutton_toggled) + self.query_entry = xml.get_widget('query_entry') + self.query_combobox = xml.get_widget('query_combobox') + self.query_combobox.set_active(0) + self.results_treeview = xml.get_widget('results_treeview') + self.results_window = xml.get_widget('results_scrolledwindow') + + # contact_name, date, message, time + model = gtk.ListStore(str, str, str, str, str) + self.results_treeview.set_model(model) + col = gtk.TreeViewColumn(_('Name')) + self.results_treeview.append_column(col) + renderer = gtk.CellRendererText() + col.pack_start(renderer) + col.set_attributes(renderer, text = C_CONTACT_NAME) + col.set_sort_column_id(C_CONTACT_NAME) # user can click this header and sort + col.set_resizable(True) + + col = gtk.TreeViewColumn(_('Date')) + self.results_treeview.append_column(col) + renderer = gtk.CellRendererText() + col.pack_start(renderer) + col.set_attributes(renderer, text = C_UNIXTIME) + col.set_sort_column_id(C_UNIXTIME) # user can click this header and sort + col.set_resizable(True) + + col = gtk.TreeViewColumn(_('Message')) + self.results_treeview.append_column(col) + renderer = gtk.CellRendererText() + col.pack_start(renderer) + col.set_attributes(renderer, text = C_MESSAGE) + col.set_resizable(True) + + self.jid = None # The history we are currently viewing + self.account = None + self.completion_dict = {} + self.accounts_seen_online = [] # Update dict when new accounts connect + self.jids_to_search = [] + + # This will load history too + gobject.idle_add(self._fill_completion_dict().next) + + if jid: + self.jid_entry.set_text(jid) + else: + self._load_history(None) + + gtkgui_helpers.resize_window(self.window, + gajim.config.get('history_window_width'), + gajim.config.get('history_window_height')) + gtkgui_helpers.move_window(self.window, + gajim.config.get('history_window_x-position'), + gajim.config.get('history_window_y-position')) + + xml.signal_autoconnect(self) + self.window.show_all() + + def _fill_completion_dict(self): + '''Fill completion_dict for key auto completion. Then load history for + current jid (by calling another function). + + Key will be either jid or full_completion_name + (contact name or long description like "pm-contact from groupchat....") + + {key : (jid, account, nick_name, full_completion_name} + this is a generator and does pseudo-threading via idle_add() + ''' + liststore = gtkgui_helpers.get_completion_liststore(self.jid_entry) + + # Add all jids in logs.db: + db_jids = gajim.logger.get_jids_in_db() + self.completion_dict = dict.fromkeys(db_jids) + + self.accounts_seen_online = gajim.contacts.get_accounts()[:] + + # Enhance contacts of online accounts with contact. Needed for mapping below + for account in self.accounts_seen_online: + self.completion_dict.update( + helpers.get_contact_dict_for_account(account)) + + muc_active_img = gtkgui_helpers.load_icon('muc_active') + contact_img = gajim.interface.jabber_state_images['16']['online'] + muc_active_pix = muc_active_img.get_pixbuf() + contact_pix = contact_img.get_pixbuf() + + keys = self.completion_dict.keys() + # Move the actual jid at first so we load history faster + actual_jid = self.jid_entry.get_text().decode('utf-8') + if actual_jid in keys: + keys.remove(actual_jid) + keys.insert(0, actual_jid) + if None in keys: + keys.remove(None) + # Map jid to info tuple + # Warning : This for is time critical with big DB + for key in keys: + completed = key + contact = self.completion_dict[completed] + if contact: + info_name = contact.get_shown_name() + info_completion = info_name + info_jid = contact.jid + else: + # Corrensponding account is offline, we know nothing + info_name = completed.split('@')[0] + info_completion = completed + info_jid = completed + + info_acc = self._get_account_for_jid(info_jid) + + if gajim.logger.jid_is_room_jid(completed) or\ + gajim.logger.jid_is_from_pm(completed): + pix = muc_active_pix + if gajim.logger.jid_is_from_pm(completed): + # It's PM. Make it easier to find + room, nick = gajim.get_room_and_nick_from_fjid(completed) + info_completion = '%s from %s' % (nick, room) + completed = info_completion + info_name = nick + else: + pix = contact_pix + + liststore.append((pix, completed)) + self.completion_dict[key] = (info_jid, info_acc, info_name, + info_completion) + self.completion_dict[completed] = (info_jid, info_acc, + info_name, info_completion) + if key == actual_jid: + self._load_history(info_jid, info_acc) + yield True + keys.sort() + yield False + + def _get_account_for_jid(self, jid): + '''Return the corresponding account of the jid. + May be None if an account could not be found''' + accounts = gajim.contacts.get_accounts() + account = None + for acc in accounts: + jid_list = gajim.contacts.get_jid_list(acc) + gc_list = gajim.contacts.get_gc_list(acc) + if jid in jid_list or jid in gc_list: + account = acc + break + return account + + def on_history_window_destroy(self, widget): + self.history_textview.del_handlers() + del gajim.interface.instances['logs'] + + def on_history_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: + self.save_state() + self.window.destroy() + + def on_close_button_clicked(self, widget): + self.save_state() + self.window.destroy() + + def on_jid_entry_activate(self, widget): + if not self.query_combobox.get_active() < 0: + # Don't disable querybox when we have changed the combobox + # to GC or All and hit enter + return + jid = self.jid_entry.get_text().decode('utf-8') + account = None # we don't know the account, could be any. Search for it! + self._load_history(jid, account) + self.results_window.set_property('visible', False) + + def on_jid_entry_focus(self, widget, event): + widget.select_region(0, -1) # select text + + def _load_history(self, jid_or_name, account = None): + '''Load history for the given jid/name and show it''' + if jid_or_name and jid_or_name in self.completion_dict: + # a full qualified jid or a contact name was entered + info_jid, info_account, info_name, info_completion = self.completion_dict[jid_or_name] + self.jids_to_search = [info_jid] + self.jid = info_jid + + if account: + self.account = account + else: + self.account = info_account + if self.account is None: + # We don't know account. Probably a gc not opened or an + # account not connected. + # Disable possibility to say if we want to log or not + self.checkbutton.set_sensitive(False) + else: + # Are log disabled for account ? + if self.account in gajim.config.get_per('accounts', self.account, + 'no_log_for').split(' '): + self.checkbutton.set_active(False) + self.checkbutton.set_sensitive(False) + else: + # Are log disabled for jid ? + log = True + if self.jid in gajim.config.get_per('accounts', self.account, + 'no_log_for').split(' '): + log = False + self.checkbutton.set_active(log) + self.checkbutton.set_sensitive(True) + + self.jids_to_search = [info_jid] + + # select logs for last date we have logs with contact + self.calendar.set_sensitive(True) + last_log = \ + gajim.logger.get_last_date_that_has_logs(self.jid, self.account) + + date = time.localtime(last_log) + + y, m, d = date[0], date[1], date[2] + gtk_month = gtkgui_helpers.make_python_month_gtk_month(m) + self.calendar.select_month(gtk_month, y) + self.calendar.select_day(d) + + self.query_entry.set_sensitive(True) + self.query_entry.grab_focus() + + title = _('Conversation History with %s') % info_name + self.window.set_title(title) + self.jid_entry.set_text(info_completion) + + else: # neither a valid jid, nor an existing contact name was entered + # we have got nothing to show or to search in + self.jid = None + self.account = None + + self.history_buffer.set_text('') # clear the buffer + self.query_entry.set_sensitive(False) + + self.checkbutton.set_sensitive(False) + self.calendar.set_sensitive(False) + self.calendar.clear_marks() + + self.results_window.set_property('visible', False) + + title = _('Conversation History') + self.window.set_title(title) + + def on_calendar_day_selected(self, widget): + if not self.jid: + return + year, month, day = widget.get_date() # integers + month = gtkgui_helpers.make_gtk_month_python_month(month) + self._add_lines_for_date(year, month, day) + + def on_calendar_month_changed(self, widget): + '''asks for days in this month if they have logs it bolds them (marks + them) + ''' + if not self.jid: + return + year, month, day = widget.get_date() # integers + # in gtk January is 1, in python January is 0, + # I want the second + # first day of month is 1 not 0 + widget.clear_marks() + month = gtkgui_helpers.make_gtk_month_python_month(month) + days_in_this_month = calendar.monthrange(year, month)[1] + try: + log_days = gajim.logger.get_days_with_logs(self.jid, year, month, + days_in_this_month, self.account) + except exceptions.PysqliteOperationalError, e: + dialogs.ErrorDialog(_('Disk Error'), str(e)) + return + for day in log_days: + widget.mark_day(day) + + def _get_string_show_from_constant_int(self, show): + if show == constants.SHOW_ONLINE: + show = 'online' + elif show == constants.SHOW_CHAT: + show = 'chat' + elif show == constants.SHOW_AWAY: + show = 'away' + elif show == constants.SHOW_XA: + show = 'xa' + elif show == constants.SHOW_DND: + show = 'dnd' + elif show == constants.SHOW_OFFLINE: + show = 'offline' + + return show + + def _add_lines_for_date(self, year, month, day): + '''adds all the lines for given date in textbuffer''' + self.history_buffer.set_text('') # clear the buffer first + self.last_time_printout = 0 + + lines = gajim.logger.get_conversation_for_date(self.jid, year, month, day, self.account) + # lines holds list with tupples that have: + # contact_name, time, kind, show, message + for line in lines: + # line[0] is contact_name, line[1] is time of message + # line[2] is kind, line[3] is show, line[4] is message + self._add_new_line(line[0], line[1], line[2], line[3], line[4], + line[5]) + + def _add_new_line(self, contact_name, tim, kind, show, message, subject): + '''add a new line in textbuffer''' + if not message and kind not in (constants.KIND_STATUS, + constants.KIND_GCSTATUS): + return + buf = self.history_buffer + end_iter = buf.get_end_iter() + + if gajim.config.get('print_time') == 'always': + timestamp_str = gajim.config.get('time_stamp') + timestamp_str = helpers.from_one_line(timestamp_str) + tim = time.strftime(timestamp_str, time.localtime(float(tim))) + buf.insert(end_iter, tim) # add time + elif gajim.config.get('print_time') == 'sometimes': + every_foo_seconds = 60 * gajim.config.get( + 'print_ichat_every_foo_minutes') + seconds_passed = tim - self.last_time_printout + if seconds_passed > every_foo_seconds: + self.last_time_printout = tim + tim = time.strftime('%X ', time.localtime(float(tim))) + buf.insert_with_tags_by_name(end_iter, tim + '\n', + 'time_sometimes') + + tag_name = '' + tag_msg = '' + + show = self._get_string_show_from_constant_int(show) + + if kind == constants.KIND_GC_MSG: + tag_name = 'incoming' + elif kind in (constants.KIND_SINGLE_MSG_RECV, + constants.KIND_CHAT_MSG_RECV): + contact_name = self.completion_dict[self.jid][C_INFO_NAME] + tag_name = 'incoming' + tag_msg = 'incomingtxt' + elif kind in (constants.KIND_SINGLE_MSG_SENT, + constants.KIND_CHAT_MSG_SENT): + if self.account: + contact_name = gajim.nicks[self.account] + else: + # we don't have roster, we don't know our own nick, use first + # account one (urk!) + account = gajim.contacts.get_accounts()[0] + contact_name = gajim.nicks[account] + tag_name = 'outgoing' + tag_msg = 'outgoingtxt' + elif kind == constants.KIND_GCSTATUS: + # message here (if not None) is status message + if message: + message = _('%(nick)s is now %(status)s: %(status_msg)s') %\ + {'nick': contact_name, 'status': helpers.get_uf_show(show), + 'status_msg': message } + else: + message = _('%(nick)s is now %(status)s') % {'nick': contact_name, + 'status': helpers.get_uf_show(show) } + tag_msg = 'status' + else: # 'status' + # message here (if not None) is status message + if show is None: # it means error + if message: + message = _('Error: %s') % message + else: + message = _('Error') + elif message: + message = _('Status is now: %(status)s: %(status_msg)s') % \ + {'status': helpers.get_uf_show(show), 'status_msg': message} + else: + message = _('Status is now: %(status)s') % { 'status': + helpers.get_uf_show(show) } + tag_msg = 'status' + + if message.startswith('/me ') or message.startswith('/me\n'): + tag_msg = tag_name + else: + # do not do this if gcstats, avoid dupping contact_name + # eg. nkour: nkour is now Offline + if contact_name and kind != constants.KIND_GCSTATUS: + # add stuff before and after contact name + before_str = gajim.config.get('before_nickname') + before_str = helpers.from_one_line(before_str) + after_str = gajim.config.get('after_nickname') + after_str = helpers.from_one_line(after_str) + format = before_str + contact_name + after_str + ' ' + buf.insert_with_tags_by_name(end_iter, format, tag_name) + + if subject: + message = _('Subject: %s\n') % subject + message + message += '\n' + if tag_msg: + self.history_textview.print_real_text(message, [tag_msg], + name=contact_name) + else: + self.history_textview.print_real_text(message, name=contact_name) + + def on_query_entry_activate(self, widget): + text = self.query_entry.get_text() + model = self.results_treeview.get_model() + model.clear() + if text == '': + self.results_window.set_property('visible', False) + return + else: + self.results_window.set_property('visible', True) + + # perform search in preselected jids + # jids are preselected with the query_combobox (all, single jid...) + for jid in self.jids_to_search: + account = self.completion_dict[jid][C_INFO_ACCOUNT] + if account is None: + # We do not know an account. This can only happen if the contact is offine, + # or if we browse a groupchat history. The account is not needed, a dummy can + # be set. + # This may leed to wrong self nick in the displayed history (Uggh!) + account = gajim.contacts.get_accounts()[0] + + # contact_name, time, kind, show, message, subject + results = gajim.logger.get_search_results_for_query( + jid, text, account) + #FIXME: + # add "subject: | message: " in message column if kind is single + # also do we need show at all? (we do not search on subject) + for row in results: + contact_name = row[0] + if not contact_name: + kind = row[2] + if kind == constants.KIND_CHAT_MSG_SENT: # it's us! :) + contact_name = gajim.nicks[account] + else: + contact_name = self.completion_dict[jid][C_INFO_NAME] + tim = row[1] + message = row[4] + local_time = time.localtime(tim) + date = time.strftime('%Y-%m-%d', local_time) + + # jid (to which log is assigned to), name, date, message, + # time (full unix time) + model.append((jid, contact_name, date, message, tim)) + + def on_query_combobox_changed(self, widget): + if self.query_combobox.get_active() < 0: + return # custom entry + self.account = None + self.jid = None + self.jids_to_search = [] + self._load_history(None) # clear textview + + if self.query_combobox.get_active() == 0: + # JID or Contact name + self.query_entry.set_sensitive(False) + self.jid_entry.grab_focus() + if self.query_combobox.get_active() == 1: + # Groupchat Histories + self.query_entry.set_sensitive(True) + self.query_entry.grab_focus() + self.jids_to_search = (jid for jid in gajim.logger.get_jids_in_db() + if gajim.logger.jid_is_room_jid(jid)) + if self.query_combobox.get_active() == 2: + # All Chat Histories + self.query_entry.set_sensitive(True) + self.query_entry.grab_focus() + self.jids_to_search = gajim.logger.get_jids_in_db() + + def on_results_treeview_row_activated(self, widget, path, column): + '''a row was double clicked, get date from row, and select it in calendar + which results to showing conversation logs for that date''' + # get currently selected date + cur_year, cur_month = self.calendar.get_date()[0:2] + cur_month = gtkgui_helpers.make_gtk_month_python_month(cur_month) + model = widget.get_model() + # make it a tupple (Y, M, D, 0, 0, 0...) + tim = time.strptime(model[path][C_UNIXTIME], '%Y-%m-%d') + year = tim[0] + gtk_month = tim[1] + month = gtkgui_helpers.make_python_month_gtk_month(gtk_month) + day = tim[2] + + # switch to belonging logfile if necessary + log_jid = model[path][C_LOG_JID] + if log_jid != self.jid: + self._load_history(log_jid, None) + + # avoid reruning mark days algo if same month and year! + if year != cur_year or gtk_month != cur_month: + self.calendar.select_month(month, year) + + self.calendar.select_day(day) + unix_time = model[path][C_TIME] + self._scroll_to_result(unix_time) + #FIXME: one day do not search just for unix_time but the whole and user + # specific format of the textbuffer line [time] nick: message + # and highlight all that + + def _scroll_to_result(self, unix_time): + '''scrolls to the result using unix_time and highlight line''' + start_iter = self.history_buffer.get_start_iter() + local_time = time.localtime(float(unix_time)) + tim = time.strftime('%X', local_time) + result = start_iter.forward_search(tim, gtk.TEXT_SEARCH_VISIBLE_ONLY, + None) + if result is not None: + match_start_iter, match_end_iter = result + match_start_iter.backward_char() # include '[' or other character before time + match_end_iter.forward_line() # highlight all message not just time + self.history_buffer.apply_tag_by_name('highlight', match_start_iter, + match_end_iter) + + match_start_mark = self.history_buffer.create_mark('match_start', + match_start_iter, True) + self.history_textview.tv.scroll_to_mark(match_start_mark, 0, True) + + def on_log_history_checkbutton_toggled(self, widget): + # log conversation history? + oldlog = True + no_log_for = gajim.config.get_per('accounts', self.account, + 'no_log_for').split() + if self.jid in no_log_for: + oldlog = False + log = widget.get_active() + if not log and not self.jid in no_log_for: + no_log_for.append(self.jid) + if log and self.jid in no_log_for: + no_log_for.remove(self.jid) + if oldlog != log: + gajim.config.set_per('accounts', self.account, 'no_log_for', + ' '.join(no_log_for)) + + def open_history(self, jid, account): + '''Load chat history of the specified jid''' + self.jid_entry.set_text(jid) + if account and account not in self.accounts_seen_online: + # Update dict to not only show bare jid + gobject.idle_add(self._fill_completion_dict().next) + else: + # Only in that case because it's called by self._fill_completion_dict() + # otherwise + self._load_history(jid, account) + self.results_window.set_property('visible', False) + + def save_state(self): + x, y = self.window.window.get_root_origin() + width, height = self.window.get_size() + + gajim.config.set('history_window_x-position', x) + gajim.config.set('history_window_y-position', y) + gajim.config.set('history_window_width', width); + gajim.config.set('history_window_height', height); + + gajim.interface.save_config() diff --git a/src/htmltextview.py b/src/htmltextview.py index 5c12a049c..80d4ac13b 100644 --- a/src/htmltextview.py +++ b/src/htmltextview.py @@ -49,9 +49,9 @@ import urllib2 import operator if __name__ == '__main__': - from common import i18n - import common.configpaths - common.configpaths.gajimpaths.init(None) + from common import i18n + import common.configpaths + common.configpaths.gajimpaths.init(None) from common import gajim import tooltips @@ -64,26 +64,26 @@ allwhitespace_rx = re.compile('^\\s*$') # pixels = points * display_resolution display_resolution = 0.3514598*(gtk.gdk.screen_height() / - float(gtk.gdk.screen_height_mm())) + float(gtk.gdk.screen_height_mm())) # embryo of CSS classes classes = { - #'system-message':';display: none', - 'problematic':';color: red', + #'system-message':';display: none', + 'problematic': ';color: red', } # styles for elements element_styles = { - 'u' : ';text-decoration: underline', - 'em' : ';font-style: oblique', - 'cite' : '; background-color:rgb(170,190,250); font-style: oblique', - 'li' : '; margin-left: 1em; margin-right: 10%', - 'strong' : ';font-weight: bold', - 'pre' : '; background-color:rgb(190,190,190); font-family: monospace; white-space: pre; margin-left: 1em; margin-right: 10%', - 'kbd' : ';background-color:rgb(210,210,210);font-family: monospace', - 'blockquote': '; background-color:rgb(170,190,250); margin-left: 2em; margin-right: 10%', - 'dt' : ';font-weight: bold; font-style: oblique', - 'dd' : ';margin-left: 2em; font-style: oblique' + 'u' : ';text-decoration: underline', + 'em' : ';font-style: oblique', + 'cite' : '; background-color:rgb(170,190,250); font-style: oblique', + 'li' : '; margin-left: 1em; margin-right: 10%', + 'strong' : ';font-weight: bold', + 'pre' : '; background-color:rgb(190,190,190); font-family: monospace; white-space: pre; margin-left: 1em; margin-right: 10%', + 'kbd' : ';background-color:rgb(210,210,210);font-family: monospace', + 'blockquote': '; background-color:rgb(170,190,250); margin-left: 2em; margin-right: 10%', + 'dt' : ';font-weight: bold; font-style: oblique', + 'dd' : ';margin-left: 2em; font-style: oblique' } # no difference for the moment element_styles['dfn'] = element_styles['em'] @@ -135,18 +135,18 @@ element_styles['b'] = element_styles['strong'] # Inline.mix # Character-level elements # InlineNoAnchor.class -# Anchor element +# Anchor element # InlinePre.mix # Pre element # # XHTML-IM also uses the following Attribute Groups: # # Core.extra.attrib -# TBD +# TBD # I18n.extra.attrib -# TBD +# TBD # Common.extra -# style +# style # # # ... @@ -179,898 +179,896 @@ INLINE = INLINE_PHRASAL.union(INLINE_PRES).union(INLINE_STRUCT) LIST_ELEMS = set( 'dl, ol, ul'.split(', ')) for name in BLOCK_HEAD: - num = eval(name[1]) - size = (num-1) // 2 - weigth = (num - 1) % 2 - element_styles[name] = '; font-size: %s; %s' % ( ('large', 'medium', 'small')[size], - ('font-weight: bold', 'font-style: oblique')[weigth], - ) + num = eval(name[1]) + size = (num-1) // 2 + weigth = (num - 1) % 2 + element_styles[name] = '; font-size: %s; %s' % ( ('large', 'medium', 'small')[size], + ('font-weight: bold', 'font-style: oblique')[weigth], + ) def _parse_css_color(color): - '''_parse_css_color(css_color) -> gtk.gdk.Color''' - if color.startswith('rgb(') and color.endswith(')'): - r, g, b = [int(c)*257 for c in color[4:-1].split(',')] - return gtk.gdk.Color(r, g, b) - else: - return gtk.gdk.color_parse(color) + '''_parse_css_color(css_color) -> gtk.gdk.Color''' + if color.startswith('rgb(') and color.endswith(')'): + r, g, b = [int(c)*257 for c in color[4:-1].split(',')] + return gtk.gdk.Color(r, g, b) + else: + return gtk.gdk.color_parse(color) def style_iter(style): - return ([x.strip() for x in item.split(':', 1)] for item in style.split(';')\ - if len(item.strip())) + return ([x.strip() for x in item.split(':', 1)] for item in style.split(';')\ + if len(item.strip())) class HtmlHandler(xml.sax.handler.ContentHandler): - """A handler to display html to a gtk textview. + """A handler to display html to a gtk textview. - It keeps a stack of "style spans" (start/end element pairs) - and a stack of list counters, for nested lists. - """ - def __init__(self, conv_textview, startiter): - xml.sax.handler.ContentHandler.__init__(self) - self.textbuf = conv_textview.tv.get_buffer() - self.textview = conv_textview.tv - self.iter = startiter - self.conv_textview = conv_textview - self.text = '' - self.starting=True - self.preserve = False - self.styles = [] # a gtk.TextTag or None, for each span level - self.list_counters = [] # stack (top at head) of list - # counters, or None for unordered list + It keeps a stack of "style spans" (start/end element pairs) + and a stack of list counters, for nested lists. + """ + def __init__(self, conv_textview, startiter): + xml.sax.handler.ContentHandler.__init__(self) + self.textbuf = conv_textview.tv.get_buffer() + self.textview = conv_textview.tv + self.iter = startiter + self.conv_textview = conv_textview + self.text = '' + self.starting=True + self.preserve = False + self.styles = [] # a gtk.TextTag or None, for each span level + self.list_counters = [] # stack (top at head) of list + # counters, or None for unordered list - def _parse_style_color(self, tag, value): - color = _parse_css_color(value) - tag.set_property('foreground-gdk', color) + def _parse_style_color(self, tag, value): + color = _parse_css_color(value) + tag.set_property('foreground-gdk', color) - def _parse_style_background_color(self, tag, value): - color = _parse_css_color(value) - tag.set_property('background-gdk', color) - tag.set_property('paragraph-background-gdk', color) + def _parse_style_background_color(self, tag, value): + color = _parse_css_color(value) + tag.set_property('background-gdk', color) + tag.set_property('paragraph-background-gdk', color) - def _get_current_attributes(self): - attrs = self.textview.get_default_attributes() - self.iter.backward_char() - self.iter.get_attributes(attrs) - self.iter.forward_char() - return attrs + def _get_current_attributes(self): + attrs = self.textview.get_default_attributes() + self.iter.backward_char() + self.iter.get_attributes(attrs) + self.iter.forward_char() + return attrs - def __parse_length_frac_size_allocate(self, textview, allocation, - frac, callback, args): - callback(allocation.width*frac, *args) + def __parse_length_frac_size_allocate(self, textview, allocation, + frac, callback, args): + callback(allocation.width*frac, *args) - def _parse_length(self, value, font_relative, block_relative, minl, maxl, callback, *args): - '''Parse/calc length, converting to pixels, calls callback(length, *args) - when the length is first computed or changes''' - if value.endswith('%'): - val = float(value[:-1]) - sign = cmp(val,0) - # limits: 1% to 500% - val = sign*max(1,min(abs(val),500)) - frac = val/100 - if font_relative: - attrs = self._get_current_attributes() - font_size = attrs.font.get_size() / pango.SCALE - callback(frac*display_resolution*font_size, *args) - elif block_relative: - # CSS says 'Percentage values: refer to width of the closest - # block-level ancestor' - # This is difficult/impossible to implement, so we use - # textview width instead; a reasonable approximation.. - alloc = self.textview.get_allocation() - self.__parse_length_frac_size_allocate(self.textview, alloc, - frac, callback, args) - self.textview.connect('size-allocate', - self.__parse_length_frac_size_allocate, - frac, callback, args) - else: - callback(frac, *args) - return + def _parse_length(self, value, font_relative, block_relative, minl, maxl, callback, *args): + '''Parse/calc length, converting to pixels, calls callback(length, *args) + when the length is first computed or changes''' + if value.endswith('%'): + val = float(value[:-1]) + sign = cmp(val, 0) + # limits: 1% to 500% + val = sign*max(1, min(abs(val), 500)) + frac = val/100 + if font_relative: + attrs = self._get_current_attributes() + font_size = attrs.font.get_size() / pango.SCALE + callback(frac*display_resolution*font_size, *args) + elif block_relative: + # CSS says 'Percentage values: refer to width of the closest + # block-level ancestor' + # This is difficult/impossible to implement, so we use + # textview width instead; a reasonable approximation.. + alloc = self.textview.get_allocation() + self.__parse_length_frac_size_allocate(self.textview, alloc, + frac, callback, args) + self.textview.connect('size-allocate', + self.__parse_length_frac_size_allocate, + frac, callback, args) + else: + callback(frac, *args) + return - def get_val(): - val = float(value[:-2]) - sign = cmp(val,0) - # validate length - return sign*max(minl,min(abs(val*display_resolution),maxl)) - if value.endswith('pt'): # points - callback(get_val()*display_resolution, *args) + def get_val(): + val = float(value[:-2]) + sign = cmp(val, 0) + # validate length + return sign*max(minl, min(abs(val*display_resolution), maxl)) + if value.endswith('pt'): # points + callback(get_val()*display_resolution, *args) - elif value.endswith('em'): # ems, the width of the element's font - attrs = self._get_current_attributes() - font_size = attrs.font.get_size() / pango.SCALE - callback(get_val()*display_resolution*font_size, *args) + elif value.endswith('em'): # ems, the width of the element's font + attrs = self._get_current_attributes() + font_size = attrs.font.get_size() / pango.SCALE + callback(get_val()*display_resolution*font_size, *args) - elif value.endswith('ex'): # x-height, ~ the height of the letter 'x' - # FIXME: figure out how to calculate this correctly - # for now 'em' size is used as approximation - attrs = self._get_current_attributes() - font_size = attrs.font.get_size() / pango.SCALE - callback(get_val()*display_resolution*font_size, *args) + elif value.endswith('ex'): # x-height, ~ the height of the letter 'x' + # FIXME: figure out how to calculate this correctly + # for now 'em' size is used as approximation + attrs = self._get_current_attributes() + font_size = attrs.font.get_size() / pango.SCALE + callback(get_val()*display_resolution*font_size, *args) - elif value.endswith('px'): # pixels - callback(get_val(), *args) + elif value.endswith('px'): # pixels + callback(get_val(), *args) - else: - try: - # TODO: isn't "no units" interpreted as pixels? - val = int(value) - sign = cmp(val,0) - # validate length - val = sign*max(minl,min(abs(val),maxl)) - callback(val, *args) - except Exception: - warnings.warn('Unable to parse length value "%s"' % value) + else: + try: + # TODO: isn't "no units" interpreted as pixels? + val = int(value) + sign = cmp(val, 0) + # validate length + val = sign*max(minl, min(abs(val), maxl)) + callback(val, *args) + except Exception: + warnings.warn('Unable to parse length value "%s"' % value) - def __parse_font_size_cb(length, tag): - tag.set_property('size-points', length/display_resolution) - __parse_font_size_cb = staticmethod(__parse_font_size_cb) + def __parse_font_size_cb(length, tag): + tag.set_property('size-points', length/display_resolution) + __parse_font_size_cb = staticmethod(__parse_font_size_cb) - def _parse_style_display(self, tag, value): - if value == 'none': - tag.set_property('invisible','true') - # FIXME: display: block, inline + def _parse_style_display(self, tag, value): + if value == 'none': + tag.set_property('invisible', 'true') + # FIXME: display: block, inline - def _parse_style_font_size(self, tag, value): - try: - scale = { - 'xx-small': pango.SCALE_XX_SMALL, - 'x-small': pango.SCALE_X_SMALL, - 'small': pango.SCALE_SMALL, - 'medium': pango.SCALE_MEDIUM, - 'large': pango.SCALE_LARGE, - 'x-large': pango.SCALE_X_LARGE, - 'xx-large': pango.SCALE_XX_LARGE, - } [value] - except KeyError: - pass - else: - attrs = self._get_current_attributes() - tag.set_property('scale', scale / attrs.font_scale) - return - if value == 'smaller': - tag.set_property('scale', pango.SCALE_SMALL) - return - if value == 'larger': - tag.set_property('scale', pango.SCALE_LARGE) - return - # font relative (5 ~ 4pt, 110 ~ 72pt) - self._parse_length(value, True, False, 5, 110, self.__parse_font_size_cb, tag) + def _parse_style_font_size(self, tag, value): + try: + scale = { + 'xx-small': pango.SCALE_XX_SMALL, + 'x-small': pango.SCALE_X_SMALL, + 'small': pango.SCALE_SMALL, + 'medium': pango.SCALE_MEDIUM, + 'large': pango.SCALE_LARGE, + 'x-large': pango.SCALE_X_LARGE, + 'xx-large': pango.SCALE_XX_LARGE, + } [value] + except KeyError: + pass + else: + attrs = self._get_current_attributes() + tag.set_property('scale', scale / attrs.font_scale) + return + if value == 'smaller': + tag.set_property('scale', pango.SCALE_SMALL) + return + if value == 'larger': + tag.set_property('scale', pango.SCALE_LARGE) + return + # font relative (5 ~ 4pt, 110 ~ 72pt) + self._parse_length(value, True, False, 5, 110, self.__parse_font_size_cb, tag) - def _parse_style_font_style(self, tag, value): - try: - style = { - 'normal': pango.STYLE_NORMAL, - 'italic': pango.STYLE_ITALIC, - 'oblique': pango.STYLE_OBLIQUE, - } [value] - except KeyError: - warnings.warn('unknown font-style %s' % value) - else: - tag.set_property('style', style) + def _parse_style_font_style(self, tag, value): + try: + style = { + 'normal': pango.STYLE_NORMAL, + 'italic': pango.STYLE_ITALIC, + 'oblique': pango.STYLE_OBLIQUE, + } [value] + except KeyError: + warnings.warn('unknown font-style %s' % value) + else: + tag.set_property('style', style) - def __frac_length_tag_cb(self,length, tag, propname): - styles = self._get_style_tags() - if styles: - length += styles[-1].get_property(propname) - tag.set_property(propname, length) - #__frac_length_tag_cb = staticmethod(__frac_length_tag_cb) + def __frac_length_tag_cb(self, length, tag, propname): + styles = self._get_style_tags() + if styles: + length += styles[-1].get_property(propname) + tag.set_property(propname, length) + #__frac_length_tag_cb = staticmethod(__frac_length_tag_cb) - def _parse_style_margin_left(self, tag, value): - # block relative - self._parse_length(value, False, True, 1, 1000, self.__frac_length_tag_cb, - tag, 'left-margin') + def _parse_style_margin_left(self, tag, value): + # block relative + self._parse_length(value, False, True, 1, 1000, self.__frac_length_tag_cb, + tag, 'left-margin') - def _parse_style_margin_right(self, tag, value): - # block relative - self._parse_length(value, False, True, 1, 1000, self.__frac_length_tag_cb, - tag, 'right-margin') + def _parse_style_margin_right(self, tag, value): + # block relative + self._parse_length(value, False, True, 1, 1000, self.__frac_length_tag_cb, + tag, 'right-margin') - def _parse_style_font_weight(self, tag, value): - # TODO: missing 'bolder' and 'lighter' - try: - weight = { - '100': pango.WEIGHT_ULTRALIGHT, - '200': pango.WEIGHT_ULTRALIGHT, - '300': pango.WEIGHT_LIGHT, - '400': pango.WEIGHT_NORMAL, - '500': pango.WEIGHT_NORMAL, - '600': pango.WEIGHT_BOLD, - '700': pango.WEIGHT_BOLD, - '800': pango.WEIGHT_ULTRABOLD, - '900': pango.WEIGHT_HEAVY, - 'normal': pango.WEIGHT_NORMAL, - 'bold': pango.WEIGHT_BOLD, - } [value] - except KeyError: - warnings.warn('unknown font-style %s' % value) - else: - tag.set_property('weight', weight) + def _parse_style_font_weight(self, tag, value): + # TODO: missing 'bolder' and 'lighter' + try: + weight = { + '100': pango.WEIGHT_ULTRALIGHT, + '200': pango.WEIGHT_ULTRALIGHT, + '300': pango.WEIGHT_LIGHT, + '400': pango.WEIGHT_NORMAL, + '500': pango.WEIGHT_NORMAL, + '600': pango.WEIGHT_BOLD, + '700': pango.WEIGHT_BOLD, + '800': pango.WEIGHT_ULTRABOLD, + '900': pango.WEIGHT_HEAVY, + 'normal': pango.WEIGHT_NORMAL, + 'bold': pango.WEIGHT_BOLD, + } [value] + except KeyError: + warnings.warn('unknown font-style %s' % value) + else: + tag.set_property('weight', weight) - def _parse_style_font_family(self, tag, value): - tag.set_property('family', value) + def _parse_style_font_family(self, tag, value): + tag.set_property('family', value) - def _parse_style_text_align(self, tag, value): - try: - align = { - 'left': gtk.JUSTIFY_LEFT, - 'right': gtk.JUSTIFY_RIGHT, - 'center': gtk.JUSTIFY_CENTER, - 'justify': gtk.JUSTIFY_FILL, - } [value] - except KeyError: - warnings.warn('Invalid text-align:%s requested' % value) - else: - tag.set_property('justification', align) + def _parse_style_text_align(self, tag, value): + try: + align = { + 'left': gtk.JUSTIFY_LEFT, + 'right': gtk.JUSTIFY_RIGHT, + 'center': gtk.JUSTIFY_CENTER, + 'justify': gtk.JUSTIFY_FILL, + } [value] + except KeyError: + warnings.warn('Invalid text-align:%s requested' % value) + else: + tag.set_property('justification', align) - def _parse_style_text_decoration(self, tag, value): - values = value.split(' ') - if 'none' in values: - tag.set_property('underline', pango.UNDERLINE_NONE) - tag.set_property('strikethrough', False) - if 'underline' in values: - tag.set_property('underline', pango.UNDERLINE_SINGLE) - else: - tag.set_property('underline', pango.UNDERLINE_NONE) - if 'line-through' in values: - tag.set_property('strikethrough', True) - else: - tag.set_property('strikethrough', False) - if 'blink' in values: - warnings.warn('text-decoration:blink not implemented') - if 'overline' in values: - warnings.warn('text-decoration:overline not implemented') + def _parse_style_text_decoration(self, tag, value): + values = value.split(' ') + if 'none' in values: + tag.set_property('underline', pango.UNDERLINE_NONE) + tag.set_property('strikethrough', False) + if 'underline' in values: + tag.set_property('underline', pango.UNDERLINE_SINGLE) + else: + tag.set_property('underline', pango.UNDERLINE_NONE) + if 'line-through' in values: + tag.set_property('strikethrough', True) + else: + tag.set_property('strikethrough', False) + if 'blink' in values: + warnings.warn('text-decoration:blink not implemented') + if 'overline' in values: + warnings.warn('text-decoration:overline not implemented') - def _parse_style_white_space(self, tag, value): - if value == 'pre': - tag.set_property('wrap_mode', gtk.WRAP_NONE) - elif value == 'normal': - tag.set_property('wrap_mode', gtk.WRAP_WORD) - elif value == 'nowrap': - tag.set_property('wrap_mode', gtk.WRAP_NONE) + def _parse_style_white_space(self, tag, value): + if value == 'pre': + tag.set_property('wrap_mode', gtk.WRAP_NONE) + elif value == 'normal': + tag.set_property('wrap_mode', gtk.WRAP_WORD) + elif value == 'nowrap': + tag.set_property('wrap_mode', gtk.WRAP_NONE) - def __length_tag_cb(self, value, tag, propname): - try: - tag.set_property(propname, value) - except Exception: - gajim.log.warn( "Error with prop: " + propname + " for tag: " + str(tag)) + def __length_tag_cb(self, value, tag, propname): + try: + tag.set_property(propname, value) + except Exception: + gajim.log.warn( "Error with prop: " + propname + " for tag: " + str(tag)) - def _parse_style_width(self, tag, value): - if value == 'auto': - return - self._parse_length(value, False, False, 1, 1000, self.__length_tag_cb, - tag, "width") - def _parse_style_height(self, tag, value): - if value == 'auto': - return - self._parse_length(value, False, False, 1, 1000, self.__length_tag_cb, - tag, "height") + def _parse_style_width(self, tag, value): + if value == 'auto': + return + self._parse_length(value, False, False, 1, 1000, self.__length_tag_cb, + tag, "width") + def _parse_style_height(self, tag, value): + if value == 'auto': + return + self._parse_length(value, False, False, 1, 1000, self.__length_tag_cb, + tag, "height") - # build a dictionary mapping styles to methods, for greater speed - __style_methods = dict() - for style in ('background-color', 'color', 'font-family', 'font-size', - 'font-style', 'font-weight', 'margin-left', 'margin-right', - 'text-align', 'text-decoration', 'white-space', 'display', - 'width', 'height' ): - try: - method = locals()['_parse_style_%s' % style.replace('-', '_')] - except KeyError: - warnings.warn('Style attribute "%s" not yet implemented' % style) - else: - __style_methods[style] = method - del style - # -- + # build a dictionary mapping styles to methods, for greater speed + __style_methods = dict() + for style in ('background-color', 'color', 'font-family', 'font-size', + 'font-style', 'font-weight', 'margin-left', 'margin-right', + 'text-align', 'text-decoration', 'white-space', 'display', + 'width', 'height' ): + try: + method = locals()['_parse_style_%s' % style.replace('-', '_')] + except KeyError: + warnings.warn('Style attribute "%s" not yet implemented' % style) + else: + __style_methods[style] = method + del style + # -- - def _get_style_tags(self): - return [tag for tag in self.styles if tag is not None] + def _get_style_tags(self): + return [tag for tag in self.styles if tag is not None] - def _create_url(self, href, title, type_, id_): - '''Process a url tag. - ''' - tag = self.textbuf.create_tag(id_) - if href and href[0] != '#': - tag.href = href - tag.type_ = type_ # to be used by the URL handler - tag.connect('event', self.textview.html_hyperlink_handler, 'url', href) - tag.set_property('foreground', gajim.config.get('urlmsgcolor')) - tag.set_property('underline', pango.UNDERLINE_SINGLE) - tag.is_anchor = True - if title: - tag.title = title - return tag + def _create_url(self, href, title, type_, id_): + '''Process a url tag. + ''' + tag = self.textbuf.create_tag(id_) + if href and href[0] != '#': + tag.href = href + tag.type_ = type_ # to be used by the URL handler + tag.connect('event', self.textview.html_hyperlink_handler, 'url', href) + tag.set_property('foreground', gajim.config.get('urlmsgcolor')) + tag.set_property('underline', pango.UNDERLINE_SINGLE) + tag.is_anchor = True + if title: + tag.title = title + return tag - def _process_img(self, attrs): - '''Process a img tag. - ''' - mem = '' - try: - # Wait maximum 1s for connection - socket.setdefaulttimeout(1) - try: - f = urllib2.urlopen(attrs['src']) - except Exception, ex: - gajim.log.debug('Error loading image %s ' % attrs['src'] + str(ex)) - pixbuf = None - alt = attrs.get('alt', 'Broken image') - else: - # Wait 0.1s between each byte - try: - f.fp._sock.fp._sock.settimeout(0.5) - except Exception: - pass - # Max image size = 2 MB (to try to prevent DoS) - deadline = time.time() + 3 - while True: - if time.time() > deadline: - gajim.log.debug(str('Timeout loading image %s ' % \ - attrs['src'] + ex)) - mem = '' - alt = attrs.get('alt', '') - if alt: - alt += '\n' - alt += _('Timeout loading image') - break - try: - temp = f.read(100) - except socket.timeout, ex: - gajim.log.debug('Timeout loading image %s ' % attrs['src'] + \ - str(ex)) - alt = attrs.get('alt', '') - if alt: - alt += '\n' - alt += _('Timeout loading image') - break - if temp: - mem += temp - else: - break - if len(mem) > 2*1024*1024: - alt = attrs.get('alt', '') - if alt: - alt += '\n' - alt += _('Image is too big') - break - pixbuf = None - if mem: - # Caveat: GdkPixbuf is known not to be safe to load - # images from network... this program is now potentially - # hackable ;) - loader = gtk.gdk.PixbufLoader() - dims = [0,0] - def height_cb(length): - dims[1] = length - def width_cb(length): - dims[0] = length - # process width and height attributes - w = attrs.get('width') - h = attrs.get('height') - # override with width and height styles - for attr, val in style_iter(attrs.get('style', '')): - if attr == 'width': - w = val - elif attr == 'height': - h = val - if w: - self._parse_length(w, False, False, 1, 1000, width_cb) - if h: - self._parse_length(h, False, False, 1, 1000, height_cb) - def set_size(pixbuf, w, h, dims): - '''FIXME: floats should be relative to the whole - textview, and resize with it. This needs new - pifbufs for every resize, gtk.gdk.Pixbuf.scale_simple - or similar. - ''' - if isinstance(dims[0], float): - dims[0] = int(dims[0]*w) - elif not dims[0]: - dims[0] = w - if isinstance(dims[1], float): - dims[1] = int(dims[1]*h) - if not dims[1]: - dims[1] = h - loader.set_size(*dims) - if w or h: - loader.connect('size-prepared', set_size, dims) - loader.write(mem) - loader.close() - pixbuf = loader.get_pixbuf() - alt = attrs.get('alt', '') - if pixbuf is not None: - tags = self._get_style_tags() - if tags: - tmpmark = self.textbuf.create_mark(None, self.iter, True) - self.textbuf.insert_pixbuf(self.iter, pixbuf) - self.starting = False - if tags: - start = self.textbuf.get_iter_at_mark(tmpmark) - for tag in tags: - self.textbuf.apply_tag(tag, start, self.iter) - self.textbuf.delete_mark(tmpmark) - else: - self._insert_text('[IMG: %s]' % alt) - except Exception, ex: - gajim.log.error('Error loading image ' + str(ex)) - pixbuf = None - alt = attrs.get('alt', 'Broken image') - try: - loader.close() - except Exception: - pass - return pixbuf + def _process_img(self, attrs): + '''Process a img tag. + ''' + mem = '' + try: + # Wait maximum 1s for connection + socket.setdefaulttimeout(1) + try: + f = urllib2.urlopen(attrs['src']) + except Exception, ex: + gajim.log.debug('Error loading image %s ' % attrs['src'] + str(ex)) + pixbuf = None + alt = attrs.get('alt', 'Broken image') + else: + # Wait 0.1s between each byte + try: + f.fp._sock.fp._sock.settimeout(0.5) + except Exception: + pass + # Max image size = 2 MB (to try to prevent DoS) + deadline = time.time() + 3 + while True: + if time.time() > deadline: + gajim.log.debug(str('Timeout loading image %s ' % \ + attrs['src'] + ex)) + mem = '' + alt = attrs.get('alt', '') + if alt: + alt += '\n' + alt += _('Timeout loading image') + break + try: + temp = f.read(100) + except socket.timeout, ex: + gajim.log.debug('Timeout loading image %s ' % attrs['src'] + \ + str(ex)) + alt = attrs.get('alt', '') + if alt: + alt += '\n' + alt += _('Timeout loading image') + break + if temp: + mem += temp + else: + break + if len(mem) > 2*1024*1024: + alt = attrs.get('alt', '') + if alt: + alt += '\n' + alt += _('Image is too big') + break + pixbuf = None + if mem: + # Caveat: GdkPixbuf is known not to be safe to load + # images from network... this program is now potentially + # hackable ;) + loader = gtk.gdk.PixbufLoader() + dims = [0, 0] + def height_cb(length): + dims[1] = length + def width_cb(length): + dims[0] = length + # process width and height attributes + w = attrs.get('width') + h = attrs.get('height') + # override with width and height styles + for attr, val in style_iter(attrs.get('style', '')): + if attr == 'width': + w = val + elif attr == 'height': + h = val + if w: + self._parse_length(w, False, False, 1, 1000, width_cb) + if h: + self._parse_length(h, False, False, 1, 1000, height_cb) + def set_size(pixbuf, w, h, dims): + '''FIXME: floats should be relative to the whole + textview, and resize with it. This needs new + pifbufs for every resize, gtk.gdk.Pixbuf.scale_simple + or similar. + ''' + if isinstance(dims[0], float): + dims[0] = int(dims[0]*w) + elif not dims[0]: + dims[0] = w + if isinstance(dims[1], float): + dims[1] = int(dims[1]*h) + if not dims[1]: + dims[1] = h + loader.set_size(*dims) + if w or h: + loader.connect('size-prepared', set_size, dims) + loader.write(mem) + loader.close() + pixbuf = loader.get_pixbuf() + alt = attrs.get('alt', '') + if pixbuf is not None: + tags = self._get_style_tags() + if tags: + tmpmark = self.textbuf.create_mark(None, self.iter, True) + self.textbuf.insert_pixbuf(self.iter, pixbuf) + self.starting = False + if tags: + start = self.textbuf.get_iter_at_mark(tmpmark) + for tag in tags: + self.textbuf.apply_tag(tag, start, self.iter) + self.textbuf.delete_mark(tmpmark) + else: + self._insert_text('[IMG: %s]' % alt) + except Exception, ex: + gajim.log.error('Error loading image ' + str(ex)) + pixbuf = None + alt = attrs.get('alt', 'Broken image') + try: + loader.close() + except Exception: + pass + return pixbuf - def _begin_span(self, style, tag=None, id_=None): - if style is None: - self.styles.append(tag) - return None - if tag is None: - if id_: - tag = self.textbuf.create_tag(id_) - else: - tag = self.textbuf.create_tag() # we create anonymous tag - for attr, val in style_iter(style): - attr = attr.lower() - val = val - try: - method = self.__style_methods[attr] - except KeyError: - warnings.warn('Style attribute "%s" requested ' - 'but not yet implemented' % attr) - else: - method(self, tag, val) - self.styles.append(tag) + def _begin_span(self, style, tag=None, id_=None): + if style is None: + self.styles.append(tag) + return None + if tag is None: + if id_: + tag = self.textbuf.create_tag(id_) + else: + tag = self.textbuf.create_tag() # we create anonymous tag + for attr, val in style_iter(style): + attr = attr.lower() + val = val + try: + method = self.__style_methods[attr] + except KeyError: + warnings.warn('Style attribute "%s" requested ' + 'but not yet implemented' % attr) + else: + method(self, tag, val) + self.styles.append(tag) - def _end_span(self): - self.styles.pop() + def _end_span(self): + self.styles.pop() - def _jump_line(self): - self.textbuf.insert_with_tags_by_name(self.iter, '\n', 'eol') - self.starting = True + def _jump_line(self): + self.textbuf.insert_with_tags_by_name(self.iter, '\n', 'eol') + self.starting = True - def _insert_text(self, text): - if self.starting and text != '\n': - self.starting = (text[-1] == '\n') - tags = self._get_style_tags() - if tags: - self.textbuf.insert_with_tags(self.iter, text, *tags) - else: - self.textbuf.insert(self.iter, text) + def _insert_text(self, text): + if self.starting and text != '\n': + self.starting = (text[-1] == '\n') + tags = self._get_style_tags() + if tags: + self.textbuf.insert_with_tags(self.iter, text, *tags) + else: + self.textbuf.insert(self.iter, text) - def _starts_line(self): - return self.starting or self.iter.starts_line() + def _starts_line(self): + return self.starting or self.iter.starts_line() - def _flush_text(self): - if not self.text: return - text, self.text = self.text, '' - if not self.preserve: - text = text.replace('\n', ' ') - self.handle_specials(whitespace_rx.sub(' ', text)) - else: - self._insert_text(text.strip('\n')) + def _flush_text(self): + if not self.text: return + text, self.text = self.text, '' + if not self.preserve: + text = text.replace('\n', ' ') + self.handle_specials(whitespace_rx.sub(' ', text)) + else: + self._insert_text(text.strip('\n')) - def _anchor_event(self, tag, textview, event, iter_, href, type_): - if event.type == gtk.gdk.BUTTON_PRESS: - self.textview.emit('url-clicked', href, type_) - return True - return False + def _anchor_event(self, tag, textview, event, iter_, href, type_): + if event.type == gtk.gdk.BUTTON_PRESS: + self.textview.emit('url-clicked', href, type_) + return True + return False - def handle_specials(self, text): - self.iter = self.conv_textview.detect_and_print_special_text(text, self._get_style_tags()) + def handle_specials(self, text): + self.iter = self.conv_textview.detect_and_print_special_text(text, self._get_style_tags()) - def characters(self, content): - if self.preserve: - self.text += content - return - if allwhitespace_rx.match(content) is not None and self._starts_line(): - self.text += ' ' - return - self.text += content - self.starting = False + def characters(self, content): + if self.preserve: + self.text += content + return + if allwhitespace_rx.match(content) is not None and self._starts_line(): + self.text += ' ' + return + self.text += content + self.starting = False - def startElement(self, name, attrs): - self._flush_text() - klass = [i for i in attrs.get('class',' ').split(' ') if i] - style = '' - #Add styles defined for classes - for k in klass: - if k in classes: - style += classes[k] + def startElement(self, name, attrs): + self._flush_text() + klass = [i for i in attrs.get('class', ' ').split(' ') if i] + style = '' + #Add styles defined for classes + for k in klass: + if k in classes: + style += classes[k] - tag = None - #FIXME: if we want to use id, it needs to be unique across - # the whole textview, so we need to add something like the - # message-id to it. - #id_ = attrs.get('id',None) - id_ = None - if name == 'a': - #TODO: accesskey, charset, hreflang, rel, rev, tabindex, type - href = attrs.get('href', None) - if not href: - href = attrs.get('HREF', None) - # Gaim sends HREF instead of href - title = attrs.get('title', attrs.get('rel',href)) - type_ = attrs.get('type', None) - tag = self._create_url(href, title, type_, id_) - elif name == 'blockquote': - cite = attrs.get('cite', None) - if cite: - tag = self.textbuf.create_tag(id_) - tag.title = title - tag.is_anchor = True - elif name in LIST_ELEMS: - style += ';margin-left: 2em' - elif name == 'img': - tag = self._process_img(attrs) - if name in element_styles: - style += element_styles[name] - # so that explicit styles override implicit ones, - # we add the attribute last - style += ";"+attrs.get('style','') - if style == '': - style = None - self._begin_span(style, tag, id_) + tag = None + #FIXME: if we want to use id, it needs to be unique across + # the whole textview, so we need to add something like the + # message-id to it. + #id_ = attrs.get('id',None) + id_ = None + if name == 'a': + #TODO: accesskey, charset, hreflang, rel, rev, tabindex, type + href = attrs.get('href', None) + if not href: + href = attrs.get('HREF', None) + # Gaim sends HREF instead of href + title = attrs.get('title', attrs.get('rel', href)) + type_ = attrs.get('type', None) + tag = self._create_url(href, title, type_, id_) + elif name == 'blockquote': + cite = attrs.get('cite', None) + if cite: + tag = self.textbuf.create_tag(id_) + tag.title = title + tag.is_anchor = True + elif name in LIST_ELEMS: + style += ';margin-left: 2em' + elif name == 'img': + tag = self._process_img(attrs) + if name in element_styles: + style += element_styles[name] + # so that explicit styles override implicit ones, + # we add the attribute last + style += ";"+attrs.get('style', '') + if style == '': + style = None + self._begin_span(style, tag, id_) - if name == 'br': - pass # handled in endElement - elif name == 'hr': - pass # handled in endElement - elif name in BLOCK: - if not self._starts_line(): - self._jump_line() - if name == 'pre': - self.preserve = True - elif name == 'span': - pass - elif name in ('dl', 'ul'): - if not self._starts_line(): - self._jump_line() - self.list_counters.append(None) - elif name == 'ol': - if not self._starts_line(): - self._jump_line() - self.list_counters.append(0) - elif name == 'li': - if self.list_counters[-1] is None: - li_head = unichr(0x2022) - else: - self.list_counters[-1] += 1 - li_head = '%i.' % self.list_counters[-1] - self.text = ' '*len(self.list_counters)*4 + li_head + ' ' - self._flush_text() - self.starting = True - elif name == 'dd': - self._jump_line() - elif name == 'dt': - if not self.starting: - self._jump_line() - elif name in ('a', 'img', 'body', 'html'): - pass - elif name in INLINE: - pass - else: - warnings.warn('Unhandled element "%s"' % name) + if name == 'br': + pass # handled in endElement + elif name == 'hr': + pass # handled in endElement + elif name in BLOCK: + if not self._starts_line(): + self._jump_line() + if name == 'pre': + self.preserve = True + elif name == 'span': + pass + elif name in ('dl', 'ul'): + if not self._starts_line(): + self._jump_line() + self.list_counters.append(None) + elif name == 'ol': + if not self._starts_line(): + self._jump_line() + self.list_counters.append(0) + elif name == 'li': + if self.list_counters[-1] is None: + li_head = unichr(0x2022) + else: + self.list_counters[-1] += 1 + li_head = '%i.' % self.list_counters[-1] + self.text = ' '*len(self.list_counters)*4 + li_head + ' ' + self._flush_text() + self.starting = True + elif name == 'dd': + self._jump_line() + elif name == 'dt': + if not self.starting: + self._jump_line() + elif name in ('a', 'img', 'body', 'html'): + pass + elif name in INLINE: + pass + else: + warnings.warn('Unhandled element "%s"' % name) - def endElement(self, name): - endPreserving = False - newLine = False - if name == 'br': - newLine = True - elif name == 'hr': - #FIXME: plenty of unused attributes (width, height,...) :) - self._jump_line() - try: - self.textbuf.insert_pixbuf(self.iter, self.textview.focus_out_line_pixbuf) - #self._insert_text(u'\u2550'*40) - self._jump_line() - except Exception, e: - gajim.log.debug(str('Error in hr'+e)) - elif name in LIST_ELEMS: - self.list_counters.pop() - elif name == 'li': - newLine = True - elif name == 'img': - pass - elif name == 'body' or name == 'html': - pass - elif name == 'a': - pass - elif name in INLINE: - pass - elif name in ('dd', 'dt', ): - pass - elif name in BLOCK: - if name == 'pre': - endPreserving = True - else: - warnings.warn("Unhandled element '%s'" % name) - self._flush_text() - if endPreserving: - self.preserve = False - if newLine: - self._jump_line() - self._end_span() - #if not self._starts_line(): - # self.text = ' ' + def endElement(self, name): + endPreserving = False + newLine = False + if name == 'br': + newLine = True + elif name == 'hr': + #FIXME: plenty of unused attributes (width, height,...) :) + self._jump_line() + try: + self.textbuf.insert_pixbuf(self.iter, self.textview.focus_out_line_pixbuf) + #self._insert_text(u'\u2550'*40) + self._jump_line() + except Exception, e: + gajim.log.debug(str('Error in hr'+e)) + elif name in LIST_ELEMS: + self.list_counters.pop() + elif name == 'li': + newLine = True + elif name == 'img': + pass + elif name == 'body' or name == 'html': + pass + elif name == 'a': + pass + elif name in INLINE: + pass + elif name in ('dd', 'dt', ): + pass + elif name in BLOCK: + if name == 'pre': + endPreserving = True + else: + warnings.warn("Unhandled element '%s'" % name) + self._flush_text() + if endPreserving: + self.preserve = False + if newLine: + self._jump_line() + self._end_span() + #if not self._starts_line(): + # self.text = ' ' class HtmlTextView(gtk.TextView): - def __init__(self): - gobject.GObject.__init__(self) - self.set_wrap_mode(gtk.WRAP_CHAR) - self.set_editable(False) - self._changed_cursor = False - self.connect('destroy', self.__destroy_event) - self.connect('motion-notify-event', self.__motion_notify_event) - self.connect('leave-notify-event', self.__leave_event) - self.connect('enter-notify-event', self.__motion_notify_event) - self.connect('realize', self.on_html_text_view_realized) - self.connect('unrealize', self.on_html_text_view_unrealized) - self.connect('copy-clipboard', self.on_html_text_view_copy_clipboard) - self.get_buffer().connect_after('mark-set', self.on_text_buffer_mark_set) - self.get_buffer().create_tag('eol', scale = pango.SCALE_XX_SMALL) - self.tooltip = tooltips.BaseTooltip() - self.config = gajim.config - self.interface = gajim.interface - # end big hack + def __init__(self): + gobject.GObject.__init__(self) + self.set_wrap_mode(gtk.WRAP_CHAR) + self.set_editable(False) + self._changed_cursor = False + self.connect('destroy', self.__destroy_event) + self.connect('motion-notify-event', self.__motion_notify_event) + self.connect('leave-notify-event', self.__leave_event) + self.connect('enter-notify-event', self.__motion_notify_event) + self.connect('realize', self.on_html_text_view_realized) + self.connect('unrealize', self.on_html_text_view_unrealized) + self.connect('copy-clipboard', self.on_html_text_view_copy_clipboard) + self.get_buffer().connect_after('mark-set', self.on_text_buffer_mark_set) + self.get_buffer().create_tag('eol', scale = pango.SCALE_XX_SMALL) + self.tooltip = tooltips.BaseTooltip() + self.config = gajim.config + self.interface = gajim.interface + # end big hack - def __destroy_event(self, widget): - if self.tooltip.timeout != 0: - self.tooltip.hide_tooltip() + def __destroy_event(self, widget): + if self.tooltip.timeout != 0: + self.tooltip.hide_tooltip() - def __leave_event(self, widget, event): - if self._changed_cursor: - window = widget.get_window(gtk.TEXT_WINDOW_TEXT) - window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM)) - self._changed_cursor = False + def __leave_event(self, widget, event): + if self._changed_cursor: + window = widget.get_window(gtk.TEXT_WINDOW_TEXT) + window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM)) + self._changed_cursor = False - def show_tooltip(self, tag): - if not self.tooltip.win: - # check if the current pointer is still over the line - x, y, _ = self.window.get_pointer() - x, y = self.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y) - tags = self.get_iter_at_location(x, y).get_tags() - is_over_anchor = False - for tag_ in tags: - if getattr(tag_, 'is_anchor', False): - is_over_anchor = True - break - if not is_over_anchor: - return - text = getattr(tag, 'title', False) - if text: - pointer = self.get_pointer() - position = self.window.get_origin() - self.tooltip.show_tooltip(text, 8, position[1] + pointer[1]) + def show_tooltip(self, tag): + if not self.tooltip.win: + # check if the current pointer is still over the line + x, y, _ = self.window.get_pointer() + x, y = self.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y) + tags = self.get_iter_at_location(x, y).get_tags() + is_over_anchor = False + for tag_ in tags: + if getattr(tag_, 'is_anchor', False): + is_over_anchor = True + break + if not is_over_anchor: + return + text = getattr(tag, 'title', False) + if text: + pointer = self.get_pointer() + position = self.window.get_origin() + self.tooltip.show_tooltip(text, 8, position[1] + pointer[1]) - def __motion_notify_event(self, widget, event): - x, y, _ = widget.window.get_pointer() - x, y = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y) - tags = widget.get_iter_at_location(x, y).get_tags() - anchor_tags = [tag for tag in tags if getattr(tag, 'is_anchor', False)] - if self.tooltip.timeout != 0: - # Check if we should hide the line tooltip - if not anchor_tags: - self.tooltip.hide_tooltip() - if not self._changed_cursor and anchor_tags: - window = widget.get_window(gtk.TEXT_WINDOW_TEXT) - window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2)) - self._changed_cursor = True - self.tooltip.timeout = gobject.timeout_add(500, self.show_tooltip, anchor_tags[0]) - elif self._changed_cursor and not anchor_tags: - window = widget.get_window(gtk.TEXT_WINDOW_TEXT) - window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM)) - self._changed_cursor = False - return False + def __motion_notify_event(self, widget, event): + x, y, _ = widget.window.get_pointer() + x, y = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y) + tags = widget.get_iter_at_location(x, y).get_tags() + anchor_tags = [tag for tag in tags if getattr(tag, 'is_anchor', False)] + if self.tooltip.timeout != 0: + # Check if we should hide the line tooltip + if not anchor_tags: + self.tooltip.hide_tooltip() + if not self._changed_cursor and anchor_tags: + window = widget.get_window(gtk.TEXT_WINDOW_TEXT) + window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2)) + self._changed_cursor = True + self.tooltip.timeout = gobject.timeout_add(500, self.show_tooltip, anchor_tags[0]) + elif self._changed_cursor and not anchor_tags: + window = widget.get_window(gtk.TEXT_WINDOW_TEXT) + window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM)) + self._changed_cursor = False + return False - def display_html(self, html, conv_textview): - buffer_ = self.get_buffer() - eob = buffer_.get_end_iter() - ## this works too if libxml2 is not available - # parser = xml.sax.make_parser(['drv_libxml2']) - # parser.setFeature(xml.sax.handler.feature_validation, True) - parser = xml.sax.make_parser() - parser.setContentHandler(HtmlHandler(conv_textview, eob)) - parser.parse(StringIO(html)) + def display_html(self, html, conv_textview): + buffer_ = self.get_buffer() + eob = buffer_.get_end_iter() + ## this works too if libxml2 is not available + # parser = xml.sax.make_parser(['drv_libxml2']) + # parser.setFeature(xml.sax.handler.feature_validation, True) + parser = xml.sax.make_parser() + parser.setContentHandler(HtmlHandler(conv_textview, eob)) + parser.parse(StringIO(html)) - # too much space after :) - #if not eob.starts_line(): - # buffer_.insert(eob, '\n') + # too much space after :) + #if not eob.starts_line(): + # buffer_.insert(eob, '\n') - def on_html_text_view_copy_clipboard(self, unused_data): - clipboard = self.get_clipboard(gtk.gdk.SELECTION_CLIPBOARD) - clipboard.set_text(self.get_selected_text()) - self.emit_stop_by_name('copy-clipboard') + def on_html_text_view_copy_clipboard(self, unused_data): + clipboard = self.get_clipboard(gtk.gdk.SELECTION_CLIPBOARD) + clipboard.set_text(self.get_selected_text()) + self.emit_stop_by_name('copy-clipboard') - def on_html_text_view_realized(self, unused_data): - self.get_buffer().remove_selection_clipboard(self.get_clipboard(gtk.gdk.SELECTION_PRIMARY)) + def on_html_text_view_realized(self, unused_data): + self.get_buffer().remove_selection_clipboard(self.get_clipboard(gtk.gdk.SELECTION_PRIMARY)) - def on_html_text_view_unrealized(self, unused_data): - self.get_buffer().add_selection_clipboard(self.get_clipboard(gtk.gdk.SELECTION_PRIMARY)) + def on_html_text_view_unrealized(self, unused_data): + self.get_buffer().add_selection_clipboard(self.get_clipboard(gtk.gdk.SELECTION_PRIMARY)) - def on_text_buffer_mark_set(self, location, mark, unused_data): - bounds = self.get_buffer().get_selection_bounds() - if bounds: - clipboard = self.get_clipboard(gtk.gdk.SELECTION_PRIMARY) - clipboard.set_text(self.get_selected_text()) + def on_text_buffer_mark_set(self, location, mark, unused_data): + bounds = self.get_buffer().get_selection_bounds() + if bounds: + clipboard = self.get_clipboard(gtk.gdk.SELECTION_PRIMARY) + clipboard.set_text(self.get_selected_text()) - def get_selected_text(self): - bounds = self.get_buffer().get_selection_bounds() - selection = '' - if bounds: - (search_iter, end) = bounds + def get_selected_text(self): + bounds = self.get_buffer().get_selection_bounds() + selection = '' + if bounds: + (search_iter, end) = bounds - while (search_iter.compare(end)): - character = search_iter.get_char() - if character == u'\ufffc': - anchor = search_iter.get_child_anchor() - if anchor: - text = anchor.get_data('plaintext') - if text: - selection+=text - else: - selection+=character - else: - selection+=character - search_iter.forward_char() - return selection + while (search_iter.compare(end)): + character = search_iter.get_char() + if character == u'\ufffc': + anchor = search_iter.get_child_anchor() + if anchor: + text = anchor.get_data('plaintext') + if text: + selection+=text + else: + selection+=character + else: + selection+=character + search_iter.forward_char() + return selection change_cursor = None if __name__ == '__main__': - import os + import os - from conversation_textview import ConversationTextview - import gajim as gaj + from conversation_textview import ConversationTextview + import gajim as gaj - class log(object): + class log(object): - def debug(self, text): - print "debug:", text - def warn(self, text): - print "warn;", text - def error(self,text): - print "error;", text + def debug(self, text): + print "debug:", text + def warn(self, text): + print "warn;", text + def error(self, text): + print "error;", text - gajim.log=log() + gajim.log=log() - gaj.Interface() + gaj.Interface() - htmlview = ConversationTextview(None) + htmlview = ConversationTextview(None) - path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'muc_separator.png') - # use this for hr - htmlview.tv.focus_out_line_pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file) + path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'muc_separator.png') + # use this for hr + htmlview.tv.focus_out_line_pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file) - tooltip = tooltips.BaseTooltip() - def on_textview_motion_notify_event(widget, event): - '''change the cursor to a hand when we are over a mail or an url''' - global change_cursor - pointer_x, pointer_y = htmlview.tv.window.get_pointer()[0:2] - x, y = htmlview.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer_x, - pointer_y) - tags = htmlview.tv.get_iter_at_location(x, y).get_tags() - if change_cursor: - htmlview.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( - gtk.gdk.Cursor(gtk.gdk.XTERM)) - change_cursor = None - tag_table = htmlview.tv.get_buffer().get_tag_table() - for tag in tags: - try: - if tag.is_anchor: - htmlview.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( - gtk.gdk.Cursor(gtk.gdk.HAND2)) - change_cursor = tag - elif tag == tag_table.lookup('focus-out-line'): - over_line = True - except Exception: - pass + tooltip = tooltips.BaseTooltip() + def on_textview_motion_notify_event(widget, event): + '''change the cursor to a hand when we are over a mail or an url''' + global change_cursor + pointer_x, pointer_y = htmlview.tv.window.get_pointer()[0:2] + x, y = htmlview.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer_x, + pointer_y) + tags = htmlview.tv.get_iter_at_location(x, y).get_tags() + if change_cursor: + htmlview.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( + gtk.gdk.Cursor(gtk.gdk.XTERM)) + change_cursor = None + tag_table = htmlview.tv.get_buffer().get_tag_table() + for tag in tags: + try: + if tag.is_anchor: + htmlview.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( + gtk.gdk.Cursor(gtk.gdk.HAND2)) + change_cursor = tag + elif tag == tag_table.lookup('focus-out-line'): + over_line = True + except Exception: + pass - #if line_tooltip.timeout != 0: - # Check if we should hide the line tooltip - # if not over_line: - # line_tooltip.hide_tooltip() - #if over_line and not line_tooltip.win: - # line_tooltip.timeout = gobject.timeout_add(500, - # show_line_tooltip) - # htmlview.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( - # gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)) - # change_cursor = tag + #if line_tooltip.timeout != 0: + # Check if we should hide the line tooltip + # if not over_line: + # line_tooltip.hide_tooltip() + #if over_line and not line_tooltip.win: + # line_tooltip.timeout = gobject.timeout_add(500, + # show_line_tooltip) + # htmlview.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( + # gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)) + # change_cursor = tag - htmlview.tv.connect('motion_notify_event', on_textview_motion_notify_event) + htmlview.tv.connect('motion_notify_event', on_textview_motion_notify_event) - def handler(texttag, widget, event, iter_, kind, href): - if event.type == gtk.gdk.BUTTON_PRESS: - print href + def handler(texttag, widget, event, iter_, kind, href): + if event.type == gtk.gdk.BUTTON_PRESS: + print href - htmlview.tv.html_hyperlink_handler = handler + htmlview.tv.html_hyperlink_handler = handler - htmlview.print_real_text(None, xhtml='
Hello
\n' - '
\n' - ' World\n' - '
\n') - htmlview.print_real_text(None, xhtml='
') - htmlview.print_real_text(None, xhtml='''

a:bGoogle


''') - htmlview.print_real_text(None, xhtml=''' - -

- OMG, - I'm green - with envy! -

- - ''') - htmlview.print_real_text(None, xhtml='
') - htmlview.print_real_text(None, xhtml=''' - - http://test.com/ testing links autolinkifying - - ''') - htmlview.print_real_text(None, xhtml='
') - htmlview.print_real_text(None, xhtml=''' - -

As Emerson said in his essay Self-Reliance:

-

- "A foolish consistency is the hobgoblin of little minds." -

- - ''') - htmlview.print_real_text(None, xhtml='
') - htmlview.print_real_text(None, xhtml=''' - -

Hey, are you licensed to Jabber?

-

A License to Jabber

- - ''') - htmlview.print_real_text(None, xhtml='
') - htmlview.print_real_text(None, xhtml=''' - -
    -
  • One
  • -
  • Two
  • -
  • Three
  • -

def fac(n):
-  def faciter(n,acc):
-	if n==0: return acc
-	return faciter(n-1, acc*n)
-  if n<0: raise ValueError('Must be non-negative')
-  return faciter(n,1)
- - ''') - htmlview.print_real_text(None, xhtml='
') - htmlview.print_real_text(None, xhtml=''' - -
    -
  1. One
  2. -
  3. Two is nested:
      -
    • One
    • -
    • Two
    • -
    • Three
    • -
    • Four
    • -
  4. -
  5. Three
- - ''') - htmlview.tv.show() - sw = gtk.ScrolledWindow() - sw.set_property('hscrollbar-policy', gtk.POLICY_AUTOMATIC) - sw.set_property('vscrollbar-policy', gtk.POLICY_AUTOMATIC) - sw.set_property('border-width', 0) - sw.add(htmlview.tv) - sw.show() - frame = gtk.Frame() - frame.set_shadow_type(gtk.SHADOW_IN) - frame.show() - frame.add(sw) - w = gtk.Window() - w.add(frame) - w.set_default_size(400, 300) - w.show_all() - w.connect('destroy', lambda w: gtk.main_quit()) - gtk.main() - -# vim: se ts=3: + htmlview.print_real_text(None, xhtml='
Hello
\n' + '
\n' + ' World\n' + '
\n') + htmlview.print_real_text(None, xhtml='
') + htmlview.print_real_text(None, xhtml='''

a:bGoogle


''') + htmlview.print_real_text(None, xhtml=''' + +

+ OMG, + I'm green + with envy! +

+ + ''') + htmlview.print_real_text(None, xhtml='
') + htmlview.print_real_text(None, xhtml=''' + + http://test.com/ testing links autolinkifying + + ''') + htmlview.print_real_text(None, xhtml='
') + htmlview.print_real_text(None, xhtml=''' + +

As Emerson said in his essay Self-Reliance:

+

+ "A foolish consistency is the hobgoblin of little minds." +

+ + ''') + htmlview.print_real_text(None, xhtml='
') + htmlview.print_real_text(None, xhtml=''' + +

Hey, are you licensed to Jabber?

+

A License to Jabber

+ + ''') + htmlview.print_real_text(None, xhtml='
') + htmlview.print_real_text(None, xhtml=''' + +
    +
  • One
  • +
  • Two
  • +
  • Three
  • +

def fac(n):
+def faciter(n,acc):
+    if n==0: return acc
+    return faciter(n-1, acc*n)
+if n<0: raise ValueError('Must be non-negative')
+return faciter(n,1)
+ + ''') + htmlview.print_real_text(None, xhtml='
') + htmlview.print_real_text(None, xhtml=''' + +
    +
  1. One
  2. +
  3. Two is nested:
      +
    • One
    • +
    • Two
    • +
    • Three
    • +
    • Four
    • +
  4. +
  5. Three
+ + ''') + htmlview.tv.show() + sw = gtk.ScrolledWindow() + sw.set_property('hscrollbar-policy', gtk.POLICY_AUTOMATIC) + sw.set_property('vscrollbar-policy', gtk.POLICY_AUTOMATIC) + sw.set_property('border-width', 0) + sw.add(htmlview.tv) + sw.show() + frame = gtk.Frame() + frame.set_shadow_type(gtk.SHADOW_IN) + frame.show() + frame.add(sw) + w = gtk.Window() + w.add(frame) + w.set_default_size(400, 300) + w.show_all() + w.connect('destroy', lambda w: gtk.main_quit()) + gtk.main() diff --git a/src/ipython_view.py b/src/ipython_view.py index 4bc8a9564..2d140d0f6 100644 --- a/src/ipython_view.py +++ b/src/ipython_view.py @@ -50,489 +50,487 @@ import pango from StringIO import StringIO try: - import IPython + import IPython except ImportError: - IPython = None + IPython = None class IterableIPShell: - ''' - Create an IPython instance. Does not start a blocking event loop, - instead allow single iterations. This allows embedding in GTK+ - without blockage. - - @ivar IP: IPython instance. - @type IP: IPython.iplib.InteractiveShell - @ivar iter_more: Indicates if the line executed was a complete command, - or we should wait for more. - @type iter_more: integer - @ivar history_level: The place in history where we currently are - when pressing up/down. - @type history_level: integer - @ivar complete_sep: Seperation delimeters for completion function. - @type complete_sep: _sre.SRE_Pattern - ''' - def __init__(self,argv=[],user_ns=None,user_global_ns=None, - cin=None, cout=None,cerr=None, input_func=None): ''' + Create an IPython instance. Does not start a blocking event loop, + instead allow single iterations. This allows embedding in GTK+ + without blockage. - - @param argv: Command line options for IPython - @type argv: list - @param user_ns: User namespace. - @type user_ns: dictionary - @param user_global_ns: User global namespace. - @type user_global_ns: dictionary. - @param cin: Console standard input. - @type cin: IO stream - @param cout: Console standard output. - @type cout: IO stream - @param cerr: Console standard error. - @type cerr: IO stream - @param input_func: Replacement for builtin raw_input() - @type input_func: function + @ivar IP: IPython instance. + @type IP: IPython.iplib.InteractiveShell + @ivar iter_more: Indicates if the line executed was a complete command, + or we should wait for more. + @type iter_more: integer + @ivar history_level: The place in history where we currently are + when pressing up/down. + @type history_level: integer + @ivar complete_sep: Seperation delimeters for completion function. + @type complete_sep: _sre.SRE_Pattern ''' - if input_func: - IPython.iplib.raw_input_original = input_func - if cin: - IPython.Shell.Term.cin = cin - if cout: - IPython.Shell.Term.cout = cout - if cerr: - IPython.Shell.Term.cerr = cerr - - # This is to get rid of the blockage that accurs during - # IPython.Shell.InteractiveShell.user_setup() - IPython.iplib.raw_input = lambda x: None - - self.term = IPython.genutils.IOTerm(cin=cin, cout=cout, cerr=cerr) - os.environ['TERM'] = 'dumb' - excepthook = sys.excepthook - self.IP = IPython.Shell.make_IPython( - argv,user_ns=user_ns, - user_global_ns=user_global_ns, - embedded=True, - shell_class=IPython.Shell.InteractiveShell) - self.IP.system = lambda cmd: self.shell(self.IP.var_expand(cmd), - header='IPython system call: ', - verbose=self.IP.rc.system_verbose) - sys.excepthook = excepthook - self.iter_more = 0 - self.history_level = 0 - self.complete_sep = re.compile('[\s\{\}\[\]\(\)]') - - def execute(self): - ''' - Executes the current line provided by the shell object. - ''' - self.history_level = 0 - orig_stdout = sys.stdout - sys.stdout = IPython.Shell.Term.cout - try: - line = self.IP.raw_input(None, self.iter_more) - if self.IP.autoindent: - self.IP.readline_startup_hook(None) - except KeyboardInterrupt: - self.IP.write('\nKeyboardInterrupt\n') - self.IP.resetbuffer() - # keep cache in sync with the prompt counter: - self.IP.outputcache.prompt_count -= 1 - - if self.IP.autoindent: - self.IP.indent_current_nsp = 0 - self.iter_more = 0 - except: - self.IP.showtraceback() - else: - self.iter_more = self.IP.push(line) - if (self.IP.SyntaxTB.last_syntax_error and - self.IP.rc.autoedit_syntax): - self.IP.edit_syntax_error() - if self.iter_more: - self.prompt = str(self.IP.outputcache.prompt2).strip() - if self.IP.autoindent: - self.IP.readline_startup_hook(self.IP.pre_readline) - else: - self.prompt = str(self.IP.outputcache.prompt1).strip() - sys.stdout = orig_stdout - - def historyBack(self): - ''' - Provides one history command back. - - @return: The command string. - @rtype: string - ''' - self.history_level -= 1 - return self._getHistory() - - def historyForward(self): - ''' - Provides one history command forward. - - @return: The command string. - @rtype: string - ''' - self.history_level += 1 - return self._getHistory() - - def _getHistory(self): - ''' - Get's the command string of the current history level. - - @return: Historic command string. - @rtype: string - ''' - try: - rv = self.IP.user_ns['In'][self.history_level].strip('\n') - except IndexError: - self.history_level = 0 - rv = '' - return rv - - def updateNamespace(self, ns_dict): - ''' - Add the current dictionary to the shell namespace. - - @param ns_dict: A dictionary of symbol-values. - @type ns_dict: dictionary - ''' - self.IP.user_ns.update(ns_dict) - - def complete(self, line): - ''' - Returns an auto completed line and/or posibilities for completion. - - @param line: Given line so far. - @type line: string - - @return: Line completed as for as possible, - and possible further completions. - @rtype: tuple - ''' - split_line = self.complete_sep.split(line) - possibilities = self.IP.complete(split_line[-1]) - - try: - __builtins__.all() - except AttributeError: - def all(iterable): - for element in iterable: - if not element: - return False - return True - - def common_prefix(seq): - """Returns the common prefix of a sequence of strings""" - return "".join(c for i, c in enumerate(seq[0]) - if all(s.startswith(c, i) for s in seq)) - if possibilities: - completed = line[:-len(split_line[-1])]+common_prefix(possibilities) - else: - completed = line - return completed, possibilities + def __init__(self,argv=[],user_ns=None,user_global_ns=None, + cin=None, cout=None,cerr=None, input_func=None): + ''' - def shell(self, cmd,verbose=0,debug=0,header=''): - ''' - Replacement method to allow shell commands without them blocking. + @param argv: Command line options for IPython + @type argv: list + @param user_ns: User namespace. + @type user_ns: dictionary + @param user_global_ns: User global namespace. + @type user_global_ns: dictionary. + @param cin: Console standard input. + @type cin: IO stream + @param cout: Console standard output. + @type cout: IO stream + @param cerr: Console standard error. + @type cerr: IO stream + @param input_func: Replacement for builtin raw_input() + @type input_func: function + ''' + if input_func: + IPython.iplib.raw_input_original = input_func + if cin: + IPython.Shell.Term.cin = cin + if cout: + IPython.Shell.Term.cout = cout + if cerr: + IPython.Shell.Term.cerr = cerr - @param cmd: Shell command to execute. - @type cmd: string - @param verbose: Verbosity - @type verbose: integer - @param debug: Debug level - @type debug: integer - @param header: Header to be printed before output - @type header: string - ''' - if verbose or debug: print header+cmd - # flush stdout so we don't mangle python's buffering - if not debug: - input_, output = os.popen4(cmd) - print output.read() - output.close() - input_.close() + # This is to get rid of the blockage that accurs during + # IPython.Shell.InteractiveShell.user_setup() + IPython.iplib.raw_input = lambda x: None + + self.term = IPython.genutils.IOTerm(cin=cin, cout=cout, cerr=cerr) + os.environ['TERM'] = 'dumb' + excepthook = sys.excepthook + self.IP = IPython.Shell.make_IPython( + argv, user_ns=user_ns, + user_global_ns=user_global_ns, + embedded=True, + shell_class=IPython.Shell.InteractiveShell) + self.IP.system = lambda cmd: self.shell(self.IP.var_expand(cmd), + header='IPython system call: ', + verbose=self.IP.rc.system_verbose) + sys.excepthook = excepthook + self.iter_more = 0 + self.history_level = 0 + self.complete_sep = re.compile('[\s\{\}\[\]\(\)]') + + def execute(self): + ''' + Executes the current line provided by the shell object. + ''' + self.history_level = 0 + orig_stdout = sys.stdout + sys.stdout = IPython.Shell.Term.cout + try: + line = self.IP.raw_input(None, self.iter_more) + if self.IP.autoindent: + self.IP.readline_startup_hook(None) + except KeyboardInterrupt: + self.IP.write('\nKeyboardInterrupt\n') + self.IP.resetbuffer() + # keep cache in sync with the prompt counter: + self.IP.outputcache.prompt_count -= 1 + + if self.IP.autoindent: + self.IP.indent_current_nsp = 0 + self.iter_more = 0 + except: + self.IP.showtraceback() + else: + self.iter_more = self.IP.push(line) + if (self.IP.SyntaxTB.last_syntax_error and + self.IP.rc.autoedit_syntax): + self.IP.edit_syntax_error() + if self.iter_more: + self.prompt = str(self.IP.outputcache.prompt2).strip() + if self.IP.autoindent: + self.IP.readline_startup_hook(self.IP.pre_readline) + else: + self.prompt = str(self.IP.outputcache.prompt1).strip() + sys.stdout = orig_stdout + + def historyBack(self): + ''' + Provides one history command back. + + @return: The command string. + @rtype: string + ''' + self.history_level -= 1 + return self._getHistory() + + def historyForward(self): + ''' + Provides one history command forward. + + @return: The command string. + @rtype: string + ''' + self.history_level += 1 + return self._getHistory() + + def _getHistory(self): + ''' + Get's the command string of the current history level. + + @return: Historic command string. + @rtype: string + ''' + try: + rv = self.IP.user_ns['In'][self.history_level].strip('\n') + except IndexError: + self.history_level = 0 + rv = '' + return rv + + def updateNamespace(self, ns_dict): + ''' + Add the current dictionary to the shell namespace. + + @param ns_dict: A dictionary of symbol-values. + @type ns_dict: dictionary + ''' + self.IP.user_ns.update(ns_dict) + + def complete(self, line): + ''' + Returns an auto completed line and/or posibilities for completion. + + @param line: Given line so far. + @type line: string + + @return: Line completed as for as possible, + and possible further completions. + @rtype: tuple + ''' + split_line = self.complete_sep.split(line) + possibilities = self.IP.complete(split_line[-1]) + + try: + __builtins__.all() + except AttributeError: + def all(iterable): + for element in iterable: + if not element: + return False + return True + + def common_prefix(seq): + """Returns the common prefix of a sequence of strings""" + return "".join(c for i, c in enumerate(seq[0]) + if all(s.startswith(c, i) for s in seq)) + if possibilities: + completed = line[:-len(split_line[-1])]+common_prefix(possibilities) + else: + completed = line + return completed, possibilities + + + def shell(self, cmd,verbose=0,debug=0,header=''): + ''' + Replacement method to allow shell commands without them blocking. + + @param cmd: Shell command to execute. + @type cmd: string + @param verbose: Verbosity + @type verbose: integer + @param debug: Debug level + @type debug: integer + @param header: Header to be printed before output + @type header: string + ''' + if verbose or debug: print header+cmd + # flush stdout so we don't mangle python's buffering + if not debug: + input_, output = os.popen4(cmd) + print output.read() + output.close() + input_.close() class ConsoleView(gtk.TextView): - ''' - Specialized text view for console-like workflow. - - @cvar ANSI_COLORS: Mapping of terminal colors to X11 names. - @type ANSI_COLORS: dictionary - - @ivar text_buffer: Widget's text buffer. - @type text_buffer: gtk.TextBuffer - @ivar color_pat: Regex of terminal color pattern - @type color_pat: _sre.SRE_Pattern - @ivar mark: Scroll mark for automatic scrolling on input. - @type mark: gtk.TextMark - @ivar line_start: Start of command line mark. - @type line_start: gtk.TextMark - ''' - ANSI_COLORS = {'0;30': 'Black', '0;31': 'Red', - '0;32': 'Green', '0;33': 'Brown', - '0;34': 'Blue', '0;35': 'Purple', - '0;36': 'Cyan', '0;37': 'LightGray', - '1;30': 'DarkGray', '1;31': 'DarkRed', - '1;32': 'SeaGreen', '1;33': 'Yellow', - '1;34': 'LightBlue', '1;35': 'MediumPurple', - '1;36': 'LightCyan', '1;37': 'White'} - - def __init__(self): ''' - Initialize console view. + Specialized text view for console-like workflow. + + @cvar ANSI_COLORS: Mapping of terminal colors to X11 names. + @type ANSI_COLORS: dictionary + + @ivar text_buffer: Widget's text buffer. + @type text_buffer: gtk.TextBuffer + @ivar color_pat: Regex of terminal color pattern + @type color_pat: _sre.SRE_Pattern + @ivar mark: Scroll mark for automatic scrolling on input. + @type mark: gtk.TextMark + @ivar line_start: Start of command line mark. + @type line_start: gtk.TextMark ''' - gtk.TextView.__init__(self) - self.modify_font(pango.FontDescription('Mono')) - self.set_cursor_visible(True) - self.text_buffer = self.get_buffer() - self.mark = self.text_buffer.create_mark('scroll_mark', - self.text_buffer.get_end_iter(), - False) - for code in self.ANSI_COLORS: - self.text_buffer.create_tag(code, - foreground=self.ANSI_COLORS[code], - weight=700) - self.text_buffer.create_tag('0') - self.text_buffer.create_tag('notouch', editable=False) - self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?') - self.line_start = \ - self.text_buffer.create_mark('line_start', - self.text_buffer.get_end_iter(), True) - self.connect('key-press-event', self.onKeyPress) + ANSI_COLORS = {'0;30': 'Black', '0;31': 'Red', + '0;32': 'Green', '0;33': 'Brown', + '0;34': 'Blue', '0;35': 'Purple', + '0;36': 'Cyan', '0;37': 'LightGray', + '1;30': 'DarkGray', '1;31': 'DarkRed', + '1;32': 'SeaGreen', '1;33': 'Yellow', + '1;34': 'LightBlue', '1;35': 'MediumPurple', + '1;36': 'LightCyan', '1;37': 'White'} - def write(self, text, editable=False): - gobject.idle_add(self._write, text, editable) + def __init__(self): + ''' + Initialize console view. + ''' + gtk.TextView.__init__(self) + self.modify_font(pango.FontDescription('Mono')) + self.set_cursor_visible(True) + self.text_buffer = self.get_buffer() + self.mark = self.text_buffer.create_mark('scroll_mark', + self.text_buffer.get_end_iter(), + False) + for code in self.ANSI_COLORS: + self.text_buffer.create_tag(code, + foreground=self.ANSI_COLORS[code], + weight=700) + self.text_buffer.create_tag('0') + self.text_buffer.create_tag('notouch', editable=False) + self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?') + self.line_start = \ + self.text_buffer.create_mark('line_start', + self.text_buffer.get_end_iter(), True) + self.connect('key-press-event', self.onKeyPress) - def _write(self, text, editable=False): - ''' - Write given text to buffer. + def write(self, text, editable=False): + gobject.idle_add(self._write, text, editable) - @param text: Text to append. - @type text: string - @param editable: If true, added text is editable. - @type editable: boolean - ''' - segments = self.color_pat.split(text) - segment = segments.pop(0) - start_mark = self.text_buffer.create_mark(None, - self.text_buffer.get_end_iter(), - True) - self.text_buffer.insert(self.text_buffer.get_end_iter(), segment) + def _write(self, text, editable=False): + ''' + Write given text to buffer. - if segments: - ansi_tags = self.color_pat.findall(text) - for tag in ansi_tags: - i = segments.index(tag) - self.text_buffer.insert_with_tags_by_name(self.text_buffer.get_end_iter(), - segments[i+1], tag) - segments.pop(i) - if not editable: - self.text_buffer.apply_tag_by_name('notouch', - self.text_buffer.get_iter_at_mark(start_mark), - self.text_buffer.get_end_iter()) - self.text_buffer.delete_mark(start_mark) - self.scroll_mark_onscreen(self.mark) + @param text: Text to append. + @type text: string + @param editable: If true, added text is editable. + @type editable: boolean + ''' + segments = self.color_pat.split(text) + segment = segments.pop(0) + start_mark = self.text_buffer.create_mark(None, + self.text_buffer.get_end_iter(), + True) + self.text_buffer.insert(self.text_buffer.get_end_iter(), segment) + + if segments: + ansi_tags = self.color_pat.findall(text) + for tag in ansi_tags: + i = segments.index(tag) + self.text_buffer.insert_with_tags_by_name(self.text_buffer.get_end_iter(), + segments[i+1], tag) + segments.pop(i) + if not editable: + self.text_buffer.apply_tag_by_name('notouch', + self.text_buffer.get_iter_at_mark(start_mark), + self.text_buffer.get_end_iter()) + self.text_buffer.delete_mark(start_mark) + self.scroll_mark_onscreen(self.mark) - def showPrompt(self, prompt): - gobject.idle_add(self._showPrompt, prompt) + def showPrompt(self, prompt): + gobject.idle_add(self._showPrompt, prompt) - def _showPrompt(self, prompt): - ''' - Prints prompt at start of line. + def _showPrompt(self, prompt): + ''' + Prints prompt at start of line. - @param prompt: Prompt to print. - @type prompt: string - ''' - self._write(prompt) - self.text_buffer.move_mark(self.line_start, - self.text_buffer.get_end_iter()) + @param prompt: Prompt to print. + @type prompt: string + ''' + self._write(prompt) + self.text_buffer.move_mark(self.line_start, + self.text_buffer.get_end_iter()) - def changeLine(self, text): - gobject.idle_add(self._changeLine, text) + def changeLine(self, text): + gobject.idle_add(self._changeLine, text) - def _changeLine(self, text): - ''' - Replace currently entered command line with given text. + def _changeLine(self, text): + ''' + Replace currently entered command line with given text. - @param text: Text to use as replacement. - @type text: string - ''' - iter_ = self.text_buffer.get_iter_at_mark(self.line_start) - iter_.forward_to_line_end() - self.text_buffer.delete(self.text_buffer.get_iter_at_mark(self.line_start), iter_) - self._write(text, True) + @param text: Text to use as replacement. + @type text: string + ''' + iter_ = self.text_buffer.get_iter_at_mark(self.line_start) + iter_.forward_to_line_end() + self.text_buffer.delete(self.text_buffer.get_iter_at_mark(self.line_start), iter_) + self._write(text, True) - def getCurrentLine(self): - ''' - Get text in current command line. + def getCurrentLine(self): + ''' + Get text in current command line. - @return: Text of current command line. - @rtype: string - ''' - rv = self.text_buffer.get_slice( - self.text_buffer.get_iter_at_mark(self.line_start), - self.text_buffer.get_end_iter(), False) - return rv + @return: Text of current command line. + @rtype: string + ''' + rv = self.text_buffer.get_slice( + self.text_buffer.get_iter_at_mark(self.line_start), + self.text_buffer.get_end_iter(), False) + return rv - def showReturned(self, text): - gobject.idle_add(self._showReturned, text) + def showReturned(self, text): + gobject.idle_add(self._showReturned, text) - def _showReturned(self, text): - ''' - Show returned text from last command and print new prompt. + def _showReturned(self, text): + ''' + Show returned text from last command and print new prompt. - @param text: Text to show. - @type text: string - ''' - iter_ = self.text_buffer.get_iter_at_mark(self.line_start) - iter_.forward_to_line_end() - self.text_buffer.apply_tag_by_name( - 'notouch', - self.text_buffer.get_iter_at_mark(self.line_start), - iter_) - self._write('\n'+text) - if text: - self._write('\n') - self._showPrompt(self.prompt) - self.text_buffer.move_mark(self.line_start,self.text_buffer.get_end_iter()) - self.text_buffer.place_cursor(self.text_buffer.get_end_iter()) + @param text: Text to show. + @type text: string + ''' + iter_ = self.text_buffer.get_iter_at_mark(self.line_start) + iter_.forward_to_line_end() + self.text_buffer.apply_tag_by_name( + 'notouch', + self.text_buffer.get_iter_at_mark(self.line_start), + iter_) + self._write('\n'+text) + if text: + self._write('\n') + self._showPrompt(self.prompt) + self.text_buffer.move_mark(self.line_start, self.text_buffer.get_end_iter()) + self.text_buffer.place_cursor(self.text_buffer.get_end_iter()) - def onKeyPress(self, widget, event): - ''' - Key press callback used for correcting behavior for console-like - interfaces. For example 'home' should go to prompt, not to begining of - line. + def onKeyPress(self, widget, event): + ''' + Key press callback used for correcting behavior for console-like + interfaces. For example 'home' should go to prompt, not to begining of + line. - @param widget: Widget that key press accored in. - @type widget: gtk.Widget - @param event: Event object - @type event: gtk.gdk.Event + @param widget: Widget that key press accored in. + @type widget: gtk.Widget + @param event: Event object + @type event: gtk.gdk.Event - @return: Return True if event should not trickle. - @rtype: boolean - ''' - insert_mark = self.text_buffer.get_insert() - insert_iter = self.text_buffer.get_iter_at_mark(insert_mark) - selection_mark = self.text_buffer.get_selection_bound() - selection_iter = self.text_buffer.get_iter_at_mark(selection_mark) - start_iter = self.text_buffer.get_iter_at_mark(self.line_start) - if event.keyval == gtk.keysyms.Home: - if event.state == 0: - self.text_buffer.place_cursor(start_iter) - return True - elif event.state == gtk.gdk.SHIFT_MASK: - self.text_buffer.move_mark(insert_mark, start_iter) - return True - elif event.keyval == gtk.keysyms.Left: - insert_iter.backward_cursor_position() - if not insert_iter.editable(True): - return True - elif not event.string: - pass - elif start_iter.compare(insert_iter) <= 0 and \ - start_iter.compare(selection_iter) <= 0: - pass - elif start_iter.compare(insert_iter) > 0 and \ - start_iter.compare(selection_iter) > 0: - self.text_buffer.place_cursor(start_iter) - elif insert_iter.compare(selection_iter) < 0: - self.text_buffer.move_mark(insert_mark, start_iter) - elif insert_iter.compare(selection_iter) > 0: - self.text_buffer.move_mark(selection_mark, start_iter) + @return: Return True if event should not trickle. + @rtype: boolean + ''' + insert_mark = self.text_buffer.get_insert() + insert_iter = self.text_buffer.get_iter_at_mark(insert_mark) + selection_mark = self.text_buffer.get_selection_bound() + selection_iter = self.text_buffer.get_iter_at_mark(selection_mark) + start_iter = self.text_buffer.get_iter_at_mark(self.line_start) + if event.keyval == gtk.keysyms.Home: + if event.state == 0: + self.text_buffer.place_cursor(start_iter) + return True + elif event.state == gtk.gdk.SHIFT_MASK: + self.text_buffer.move_mark(insert_mark, start_iter) + return True + elif event.keyval == gtk.keysyms.Left: + insert_iter.backward_cursor_position() + if not insert_iter.editable(True): + return True + elif not event.string: + pass + elif start_iter.compare(insert_iter) <= 0 and \ + start_iter.compare(selection_iter) <= 0: + pass + elif start_iter.compare(insert_iter) > 0 and \ + start_iter.compare(selection_iter) > 0: + self.text_buffer.place_cursor(start_iter) + elif insert_iter.compare(selection_iter) < 0: + self.text_buffer.move_mark(insert_mark, start_iter) + elif insert_iter.compare(selection_iter) > 0: + self.text_buffer.move_mark(selection_mark, start_iter) - return self.onKeyPressExtend(event) + return self.onKeyPressExtend(event) - def onKeyPressExtend(self, event): - ''' - For some reason we can't extend onKeyPress directly (bug #500900). - ''' - pass + def onKeyPressExtend(self, event): + ''' + For some reason we can't extend onKeyPress directly (bug #500900). + ''' + pass class IPythonView(ConsoleView, IterableIPShell): - ''' - Sub-class of both modified IPython shell and L{ConsoleView} this makes - a GTK+ IPython console. - ''' - def __init__(self): ''' - Initialize. Redirect I/O to console. + Sub-class of both modified IPython shell and L{ConsoleView} this makes + a GTK+ IPython console. ''' - ConsoleView.__init__(self) - self.cout = StringIO() - IterableIPShell.__init__(self, cout=self.cout,cerr=self.cout, - input_func=self.raw_input) + def __init__(self): + ''' + Initialize. Redirect I/O to console. + ''' + ConsoleView.__init__(self) + self.cout = StringIO() + IterableIPShell.__init__(self, cout=self.cout, cerr=self.cout, + input_func=self.raw_input) # self.connect('key_press_event', self.keyPress) - self.execute() - self.cout.truncate(0) - self.showPrompt(self.prompt) - self.interrupt = False - - def raw_input(self, prompt=''): - ''' - Custom raw_input() replacement. Get's current line from console buffer. - - @param prompt: Prompt to print. Here for compatability as replacement. - @type prompt: string - - @return: The current command line text. - @rtype: string - ''' - if self.interrupt: - self.interrupt = False - raise KeyboardInterrupt - return self.getCurrentLine() - - def onKeyPressExtend(self, event): - ''' - Key press callback with plenty of shell goodness, like history, - autocompletions, etc. - - @param widget: Widget that key press occured in. - @type widget: gtk.Widget - @param event: Event object. - @type event: gtk.gdk.Event - - @return: True if event should not trickle. - @rtype: boolean - ''' - if event.state & gtk.gdk.CONTROL_MASK and event.keyval == 99: - self.interrupt = True - self._processLine() - return True - elif event.keyval == gtk.keysyms.Return: - self._processLine() - return True - elif event.keyval == gtk.keysyms.Up: - self.changeLine(self.historyBack()) - return True - elif event.keyval == gtk.keysyms.Down: - self.changeLine(self.historyForward()) - return True - elif event.keyval == gtk.keysyms.Tab: - if not self.getCurrentLine().strip(): - return False - completed, possibilities = self.complete(self.getCurrentLine()) - if len(possibilities) > 1: - slice_ = self.getCurrentLine() - self.write('\n') - for symbol in possibilities: - self.write(symbol+'\n') + self.execute() + self.cout.truncate(0) self.showPrompt(self.prompt) - self.changeLine(completed or slice_) - return True + self.interrupt = False - def _processLine(self): - ''' - Process current command line. - ''' - self.history_pos = 0 - self.execute() - rv = self.cout.getvalue() - if rv: rv = rv.strip('\n') - self.showReturned(rv) - self.cout.truncate(0) + def raw_input(self, prompt=''): + ''' + Custom raw_input() replacement. Get's current line from console buffer. + @param prompt: Prompt to print. Here for compatability as replacement. + @type prompt: string + + @return: The current command line text. + @rtype: string + ''' + if self.interrupt: + self.interrupt = False + raise KeyboardInterrupt + return self.getCurrentLine() + + def onKeyPressExtend(self, event): + ''' + Key press callback with plenty of shell goodness, like history, + autocompletions, etc. + + @param widget: Widget that key press occured in. + @type widget: gtk.Widget + @param event: Event object. + @type event: gtk.gdk.Event + + @return: True if event should not trickle. + @rtype: boolean + ''' + if event.state & gtk.gdk.CONTROL_MASK and event.keyval == 99: + self.interrupt = True + self._processLine() + return True + elif event.keyval == gtk.keysyms.Return: + self._processLine() + return True + elif event.keyval == gtk.keysyms.Up: + self.changeLine(self.historyBack()) + return True + elif event.keyval == gtk.keysyms.Down: + self.changeLine(self.historyForward()) + return True + elif event.keyval == gtk.keysyms.Tab: + if not self.getCurrentLine().strip(): + return False + completed, possibilities = self.complete(self.getCurrentLine()) + if len(possibilities) > 1: + slice_ = self.getCurrentLine() + self.write('\n') + for symbol in possibilities: + self.write(symbol+'\n') + self.showPrompt(self.prompt) + self.changeLine(completed or slice_) + return True + + def _processLine(self): + ''' + Process current command line. + ''' + self.history_pos = 0 + self.execute() + rv = self.cout.getvalue() + if rv: rv = rv.strip('\n') + self.showReturned(rv) + self.cout.truncate(0) -# vim: se ts=3: diff --git a/src/message_control.py b/src/message_control.py index ef6bc2868..419ce47e9 100644 --- a/src/message_control.py +++ b/src/message_control.py @@ -40,179 +40,177 @@ TYPE_PM = 'pm' #################### class MessageControl: - '''An abstract base widget that can embed in the gtk.Notebook of a MessageWindow''' + '''An abstract base widget that can embed in the gtk.Notebook of a MessageWindow''' - def __init__(self, type_id, parent_win, widget_name, contact, account, resource = None): - # dict { cb id : widget} - # keep all registered callbacks of widgets, created by self.xml - self.handlers = {} - self.type_id = type_id - self.parent_win = parent_win - self.widget_name = widget_name - self.contact = contact - self.account = account - self.hide_chat_buttons = False - self.resource = resource + def __init__(self, type_id, parent_win, widget_name, contact, account, resource = None): + # dict { cb id : widget} + # keep all registered callbacks of widgets, created by self.xml + self.handlers = {} + self.type_id = type_id + self.parent_win = parent_win + self.widget_name = widget_name + self.contact = contact + self.account = account + self.hide_chat_buttons = False + self.resource = resource - self.session = None + self.session = None - gajim.last_message_time[self.account][self.get_full_jid()] = 0 + gajim.last_message_time[self.account][self.get_full_jid()] = 0 - self.xml = gtkgui_helpers.get_glade('message_window.glade', widget_name) - self.widget = self.xml.get_widget(widget_name) + self.xml = gtkgui_helpers.get_glade('message_window.glade', widget_name) + self.widget = self.xml.get_widget(widget_name) - def get_full_jid(self): - fjid = self.contact.jid - if self.resource: - fjid += '/' + self.resource - return fjid + def get_full_jid(self): + fjid = self.contact.jid + if self.resource: + fjid += '/' + self.resource + return fjid - def set_control_active(self, state): - '''Called when the control becomes active (state is True) - or inactive (state is False)''' - pass # Derived classes MUST implement this method + def set_control_active(self, state): + '''Called when the control becomes active (state is True) + or inactive (state is False)''' + pass # Derived classes MUST implement this method - def minimizable(self): - '''Called to check if control can be minimized''' - # NOTE: Derived classes MAY implement this - return False + def minimizable(self): + '''Called to check if control can be minimized''' + # NOTE: Derived classes MAY implement this + return False - def safe_shutdown(self): - '''Called to check if control can be closed without loosing data. - returns True if control can be closed safely else False''' - # NOTE: Derived classes MAY implement this - return True + def safe_shutdown(self): + '''Called to check if control can be closed without loosing data. + returns True if control can be closed safely else False''' + # NOTE: Derived classes MAY implement this + return True - def allow_shutdown(self, method, on_response_yes, on_response_no, - on_response_minimize): - '''Called to check is a control is allowed to shutdown. - If a control is not in a suitable shutdown state this method - should call on_response_no, else on_response_yes or - on_response_minimize ''' - # NOTE: Derived classes MAY implement this - on_response_yes(self) + def allow_shutdown(self, method, on_response_yes, on_response_no, + on_response_minimize): + '''Called to check is a control is allowed to shutdown. + If a control is not in a suitable shutdown state this method + should call on_response_no, else on_response_yes or + on_response_minimize ''' + # NOTE: Derived classes MAY implement this + on_response_yes(self) - def shutdown(self): - # NOTE: Derived classes MUST implement this - pass + def shutdown(self): + # NOTE: Derived classes MUST implement this + pass - def repaint_themed_widgets(self): - pass # NOTE: Derived classes SHOULD implement this + def repaint_themed_widgets(self): + pass # NOTE: Derived classes SHOULD implement this - def update_ui(self): - pass # NOTE: Derived classes SHOULD implement this + def update_ui(self): + pass # NOTE: Derived classes SHOULD implement this - def toggle_emoticons(self): - pass # NOTE: Derived classes MAY implement this + def toggle_emoticons(self): + pass # NOTE: Derived classes MAY implement this - def update_font(self): - pass # NOTE: Derived classes SHOULD implement this + def update_font(self): + pass # NOTE: Derived classes SHOULD implement this - def update_tags(self): - pass # NOTE: Derived classes SHOULD implement this + def update_tags(self): + pass # NOTE: Derived classes SHOULD implement this - def get_tab_label(self, chatstate): - '''Return a suitable tab label string. Returns a tuple such as: - (label_str, color) either of which can be None - if chatstate is given that means we have HE SENT US a chatstate and - we want it displayed''' - # NOTE: Derived classes MUST implement this - # Return a markup'd label and optional gtk.Color in a tupple like: - #return (label_str, None) - pass + def get_tab_label(self, chatstate): + '''Return a suitable tab label string. Returns a tuple such as: + (label_str, color) either of which can be None + if chatstate is given that means we have HE SENT US a chatstate and + we want it displayed''' + # NOTE: Derived classes MUST implement this + # Return a markup'd label and optional gtk.Color in a tupple like: + #return (label_str, None) + pass - def get_tab_image(self, count_unread=True): - # Return a suitable tab image for display. - # None clears any current label. - return None + def get_tab_image(self, count_unread=True): + # Return a suitable tab image for display. + # None clears any current label. + return None - def prepare_context_menu(self): - # NOTE: Derived classes SHOULD implement this - return None + def prepare_context_menu(self): + # NOTE: Derived classes SHOULD implement this + return None - def chat_buttons_set_visible(self, state): - # NOTE: Derived classes MAY implement this - self.hide_chat_buttons = state + def chat_buttons_set_visible(self, state): + # NOTE: Derived classes MAY implement this + self.hide_chat_buttons = state - def got_connected(self): - pass + def got_connected(self): + pass - def got_disconnected(self): - pass + def got_disconnected(self): + pass - def get_specific_unread(self): - return len(gajim.events.get_events(self.account, - self.contact.jid)) + def get_specific_unread(self): + return len(gajim.events.get_events(self.account, + self.contact.jid)) - def set_session(self, session): - oldsession = None - if hasattr(self, 'session'): - oldsession = self.session + def set_session(self, session): + oldsession = None + if hasattr(self, 'session'): + oldsession = self.session - if oldsession and session == oldsession: - return + if oldsession and session == oldsession: + return - self.session = session + self.session = session - if session: - session.control = self + if session: + session.control = self - if oldsession: - oldsession.control = None + if oldsession: + oldsession.control = None - jid = self.contact.jid - if self.resource: - jid += '/' + self.resource + jid = self.contact.jid + if self.resource: + jid += '/' + self.resource - crypto_changed = bool(session and isinstance(session, - EncryptedStanzaSession) and session.enable_encryption) != \ - bool(oldsession and isinstance(oldsession, EncryptedStanzaSession) and\ - oldsession.enable_encryption) + crypto_changed = bool(session and isinstance(session, + EncryptedStanzaSession) and session.enable_encryption) != \ + bool(oldsession and isinstance(oldsession, EncryptedStanzaSession) and\ + oldsession.enable_encryption) - archiving_changed = bool(session and isinstance(session, - ArchivingStanzaSession) and session.archiving) != \ - bool(oldsession and isinstance(oldsession, ArchivingStanzaSession) and\ - oldsession.archiving) + archiving_changed = bool(session and isinstance(session, + ArchivingStanzaSession) and session.archiving) != \ + bool(oldsession and isinstance(oldsession, ArchivingStanzaSession) and\ + oldsession.archiving) - if crypto_changed or archiving_changed: - self.print_session_details() + if crypto_changed or archiving_changed: + self.print_session_details() - def send_message(self, message, keyID='', type_='chat', chatstate=None, - msg_id=None, composing_xep=None, resource=None, user_nick=None, xhtml=None, - callback=None, callback_args=[]): - # Send the given message to the active tab. - # Doesn't return None if error - jid = self.contact.jid + def send_message(self, message, keyID='', type_='chat', chatstate=None, + msg_id=None, composing_xep=None, resource=None, user_nick=None, xhtml=None, + callback=None, callback_args=[]): + # Send the given message to the active tab. + # Doesn't return None if error + jid = self.contact.jid - message = helpers.remove_invalid_xml_chars(message) + message = helpers.remove_invalid_xml_chars(message) - original_message = message - conn = gajim.connections[self.account] + original_message = message + conn = gajim.connections[self.account] - if not self.session: - if not resource: - if self.resource: - resource = self.resource - else: - resource = self.contact.resource - sess = conn.find_controlless_session(jid, resource=resource) + if not self.session: + if not resource: + if self.resource: + resource = self.resource + else: + resource = self.contact.resource + sess = conn.find_controlless_session(jid, resource=resource) - if self.resource: - jid += '/' + self.resource + if self.resource: + jid += '/' + self.resource - if not sess: - if self.type_id == TYPE_PM: - sess = conn.make_new_session(jid, type_='pm') - else: - sess = conn.make_new_session(jid) + if not sess: + if self.type_id == TYPE_PM: + sess = conn.make_new_session(jid, type_='pm') + else: + sess = conn.make_new_session(jid) - self.set_session(sess) + self.set_session(sess) - # Send and update history - conn.send_message(jid, message, keyID, type_=type_, chatstate=chatstate, - msg_id=msg_id, composing_xep=composing_xep, resource=self.resource, - user_nick=user_nick, session=self.session, - original_message=original_message, xhtml=xhtml, callback=callback, - callback_args=callback_args) - -# vim: se ts=3: + # Send and update history + conn.send_message(jid, message, keyID, type_=type_, chatstate=chatstate, + msg_id=msg_id, composing_xep=composing_xep, resource=self.resource, + user_nick=user_nick, session=self.session, + original_message=original_message, xhtml=xhtml, callback=callback, + callback_args=callback_args) diff --git a/src/message_textview.py b/src/message_textview.py index 236ad1cb0..2ee712047 100644 --- a/src/message_textview.py +++ b/src/message_textview.py @@ -31,262 +31,262 @@ import gtkgui_helpers from common import gajim class MessageTextView(gtk.TextView): - """ - Class for the message textview (where user writes new messages) for - chat/groupchat windows - """ - __gsignals__ = dict( - mykeypress = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION, - None, # return value - (int, gtk.gdk.ModifierType ) # arguments - ) - ) + """ + Class for the message textview (where user writes new messages) for + chat/groupchat windows + """ + __gsignals__ = dict( + mykeypress = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION, + None, # return value + (int, gtk.gdk.ModifierType ) # arguments + ) + ) - def __init__(self): - gtk.TextView.__init__(self) + def __init__(self): + gtk.TextView.__init__(self) - # set properties - self.set_border_width(1) - self.set_accepts_tab(True) - self.set_editable(True) - self.set_cursor_visible(True) - self.set_wrap_mode(gtk.WRAP_WORD_CHAR) - self.set_left_margin(2) - self.set_right_margin(2) - self.set_pixels_above_lines(2) - self.set_pixels_below_lines(2) + # set properties + self.set_border_width(1) + self.set_accepts_tab(True) + self.set_editable(True) + self.set_cursor_visible(True) + self.set_wrap_mode(gtk.WRAP_WORD_CHAR) + self.set_left_margin(2) + self.set_right_margin(2) + self.set_pixels_above_lines(2) + self.set_pixels_below_lines(2) - self.lang = None # Lang used for spell checking - buffer = self.get_buffer() - self.begin_tags = {} - self.end_tags = {} - self.color_tags = [] - self.fonts_tags = [] - self.other_tags = {} - self.other_tags['bold'] = buffer.create_tag('bold') - self.other_tags['bold'].set_property('weight', pango.WEIGHT_BOLD) - self.begin_tags['bold'] = '' - self.end_tags['bold'] = '' - self.other_tags['italic'] = buffer.create_tag('italic') - self.other_tags['italic'].set_property('style', pango.STYLE_ITALIC) - self.begin_tags['italic'] = '' - self.end_tags['italic'] = '' - self.other_tags['underline'] = buffer.create_tag('underline') - self.other_tags['underline'].set_property('underline', pango.UNDERLINE_SINGLE) - self.begin_tags['underline'] = '' - self.end_tags['underline'] = '' - self.other_tags['strike'] = buffer.create_tag('strike') - self.other_tags['strike'].set_property('strikethrough', True) - self.begin_tags['strike'] = '' - self.end_tags['strike'] = '' + self.lang = None # Lang used for spell checking + buffer = self.get_buffer() + self.begin_tags = {} + self.end_tags = {} + self.color_tags = [] + self.fonts_tags = [] + self.other_tags = {} + self.other_tags['bold'] = buffer.create_tag('bold') + self.other_tags['bold'].set_property('weight', pango.WEIGHT_BOLD) + self.begin_tags['bold'] = '' + self.end_tags['bold'] = '' + self.other_tags['italic'] = buffer.create_tag('italic') + self.other_tags['italic'].set_property('style', pango.STYLE_ITALIC) + self.begin_tags['italic'] = '' + self.end_tags['italic'] = '' + self.other_tags['underline'] = buffer.create_tag('underline') + self.other_tags['underline'].set_property('underline', pango.UNDERLINE_SINGLE) + self.begin_tags['underline'] = '' + self.end_tags['underline'] = '' + self.other_tags['strike'] = buffer.create_tag('strike') + self.other_tags['strike'].set_property('strikethrough', True) + self.begin_tags['strike'] = '' + self.end_tags['strike'] = '' - def make_clickable_urls(self, text): - buffer = self.get_buffer() + def make_clickable_urls(self, text): + buffer = self.get_buffer() - start = 0 - end = 0 - index = 0 + start = 0 + end = 0 + index = 0 - new_text = '' - iterator = gajim.interface.link_pattern_re.finditer(text) - for match in iterator: - start, end = match.span() - url = text[start:end] - if start != 0: - text_before_special_text = text[index:start] - else: - text_before_special_text = '' - end_iter = buffer.get_end_iter() - # we insert normal text - new_text += text_before_special_text + \ - '' + url + '' + new_text = '' + iterator = gajim.interface.link_pattern_re.finditer(text) + for match in iterator: + start, end = match.span() + url = text[start:end] + if start != 0: + text_before_special_text = text[index:start] + else: + text_before_special_text = '' + end_iter = buffer.get_end_iter() + # we insert normal text + new_text += text_before_special_text + \ + '' + url + '' - index = end # update index + index = end # update index - if end < len(text): - new_text += text[end:] + if end < len(text): + new_text += text[end:] - return new_text # the position after *last* special text + return new_text # the position after *last* special text - def get_active_tags(self): - buffer = self.get_buffer() - start, finish = self.get_active_iters() - active_tags = [] - for tag in start.get_tags(): - active_tags.append(tag.get_property('name')) - return active_tags + def get_active_tags(self): + buffer = self.get_buffer() + start, finish = self.get_active_iters() + active_tags = [] + for tag in start.get_tags(): + active_tags.append(tag.get_property('name')) + return active_tags - def get_active_iters(self): - buffer = self.get_buffer() - return_val = buffer.get_selection_bounds() - if return_val: # if sth was selected - start, finish = return_val[0], return_val[1] - else: - start, finish = buffer.get_bounds() - return (start, finish) + def get_active_iters(self): + buffer = self.get_buffer() + return_val = buffer.get_selection_bounds() + if return_val: # if sth was selected + start, finish = return_val[0], return_val[1] + else: + start, finish = buffer.get_bounds() + return (start, finish) - def set_tag(self, widget, tag): - buffer = self.get_buffer() - start, finish = self.get_active_iters() - if start.has_tag(self.other_tags[tag]): - buffer.remove_tag_by_name(tag, start, finish) - else: - if tag == 'underline': - buffer.remove_tag_by_name('strike', start, finish) - elif tag == 'strike': - buffer.remove_tag_by_name('underline', start, finish) - buffer.apply_tag_by_name(tag, start, finish) + def set_tag(self, widget, tag): + buffer = self.get_buffer() + start, finish = self.get_active_iters() + if start.has_tag(self.other_tags[tag]): + buffer.remove_tag_by_name(tag, start, finish) + else: + if tag == 'underline': + buffer.remove_tag_by_name('strike', start, finish) + elif tag == 'strike': + buffer.remove_tag_by_name('underline', start, finish) + buffer.apply_tag_by_name(tag, start, finish) - def clear_tags(self, widget): - buffer = self.get_buffer() - start, finish = self.get_active_iters() - buffer.remove_all_tags(start, finish) + def clear_tags(self, widget): + buffer = self.get_buffer() + start, finish = self.get_active_iters() + buffer.remove_all_tags(start, finish) - def color_set(self, widget, response, color): - if response == -6: - widget.destroy() - return - buffer = self.get_buffer() - color = color.get_current_color() - widget.destroy() - color_string = gtkgui_helpers.make_color_string(color) - tag_name = 'color' + color_string - if not tag_name in self.color_tags: - tagColor = buffer.create_tag(tag_name) - tagColor.set_property('foreground', color_string) - self.begin_tags[tag_name] = '' - self.end_tags[tag_name] = '' - self.color_tags.append(tag_name) + def color_set(self, widget, response, color): + if response == -6: + widget.destroy() + return + buffer = self.get_buffer() + color = color.get_current_color() + widget.destroy() + color_string = gtkgui_helpers.make_color_string(color) + tag_name = 'color' + color_string + if not tag_name in self.color_tags: + tagColor = buffer.create_tag(tag_name) + tagColor.set_property('foreground', color_string) + self.begin_tags[tag_name] = '' + self.end_tags[tag_name] = '' + self.color_tags.append(tag_name) - start, finish = self.get_active_iters() + start, finish = self.get_active_iters() - for tag in self.color_tags: - buffer.remove_tag_by_name(tag, start, finish) + for tag in self.color_tags: + buffer.remove_tag_by_name(tag, start, finish) - buffer.apply_tag_by_name(tag_name, start, finish) + buffer.apply_tag_by_name(tag_name, start, finish) - def font_set(self, widget, response, font): - if response == -6: - widget.destroy() - return + def font_set(self, widget, response, font): + if response == -6: + widget.destroy() + return - buffer = self.get_buffer() + buffer = self.get_buffer() - font = font.get_font_name() - font_desc = pango.FontDescription(font) - family = font_desc.get_family() - size = font_desc.get_size() - size = size / pango.SCALE - weight = font_desc.get_weight() - style = font_desc.get_style() + font = font.get_font_name() + font_desc = pango.FontDescription(font) + family = font_desc.get_family() + size = font_desc.get_size() + size = size / pango.SCALE + weight = font_desc.get_weight() + style = font_desc.get_style() - widget.destroy() + widget.destroy() - tag_name = 'font' + font - if not tag_name in self.fonts_tags: - tagFont = buffer.create_tag(tag_name) - tagFont.set_property('font', family + ' ' + str(size)) - self.begin_tags[tag_name] = \ - '' - self.end_tags[tag_name] = '' - self.fonts_tags.append(tag_name) + tag_name = 'font' + font + if not tag_name in self.fonts_tags: + tagFont = buffer.create_tag(tag_name) + tagFont.set_property('font', family + ' ' + str(size)) + self.begin_tags[tag_name] = \ + '' + self.end_tags[tag_name] = '' + self.fonts_tags.append(tag_name) - start, finish = self.get_active_iters() + start, finish = self.get_active_iters() - for tag in self.fonts_tags: - buffer.remove_tag_by_name(tag, start, finish) + for tag in self.fonts_tags: + buffer.remove_tag_by_name(tag, start, finish) - buffer.apply_tag_by_name(tag_name, start, finish) + buffer.apply_tag_by_name(tag_name, start, finish) - if weight == pango.WEIGHT_BOLD: - buffer.apply_tag_by_name('bold', start, finish) - else: - buffer.remove_tag_by_name('bold', start, finish) + if weight == pango.WEIGHT_BOLD: + buffer.apply_tag_by_name('bold', start, finish) + else: + buffer.remove_tag_by_name('bold', start, finish) - if style == pango.STYLE_ITALIC: - buffer.apply_tag_by_name('italic', start, finish) - else: - buffer.remove_tag_by_name('italic', start, finish) + if style == pango.STYLE_ITALIC: + buffer.apply_tag_by_name('italic', start, finish) + else: + buffer.remove_tag_by_name('italic', start, finish) - def get_xhtml(self): - buffer = self.get_buffer() - old = buffer.get_start_iter() - tags = {} - tags['bold'] = False - iter = buffer.get_start_iter() - old = buffer.get_start_iter() - text = '' - modified = False + def get_xhtml(self): + buffer = self.get_buffer() + old = buffer.get_start_iter() + tags = {} + tags['bold'] = False + iter = buffer.get_start_iter() + old = buffer.get_start_iter() + text = '' + modified = False - def xhtml_special(text): - text = text.replace('<', '<') - text = text.replace('>', '>') - text = text.replace('&', '&') - text = text.replace('\n', '
') - return text + def xhtml_special(text): + text = text.replace('<', '<') + text = text.replace('>', '>') + text = text.replace('&', '&') + text = text.replace('\n', '
') + return text - for tag in iter.get_toggled_tags(True): - tag_name = tag.get_property('name') - if tag_name not in self.begin_tags: - continue - text += self.begin_tags[tag_name] - modified = True - while (iter.forward_to_tag_toggle(None) and not iter.is_end()): - text += xhtml_special(buffer.get_text(old, iter)) - old.forward_to_tag_toggle(None) - new_tags, old_tags, end_tags = [], [], [] - for tag in iter.get_toggled_tags(True): - tag_name = tag.get_property('name') - if tag_name not in self.begin_tags: - continue - new_tags.append(tag_name) - modified = True + for tag in iter.get_toggled_tags(True): + tag_name = tag.get_property('name') + if tag_name not in self.begin_tags: + continue + text += self.begin_tags[tag_name] + modified = True + while (iter.forward_to_tag_toggle(None) and not iter.is_end()): + text += xhtml_special(buffer.get_text(old, iter)) + old.forward_to_tag_toggle(None) + new_tags, old_tags, end_tags = [], [], [] + for tag in iter.get_toggled_tags(True): + tag_name = tag.get_property('name') + if tag_name not in self.begin_tags: + continue + new_tags.append(tag_name) + modified = True - for tag in iter.get_tags(): - tag_name = tag.get_property('name') - if tag_name not in self.begin_tags or tag_name not in self.end_tags: - continue - if tag_name not in new_tags: - old_tags.append(tag_name) + for tag in iter.get_tags(): + tag_name = tag.get_property('name') + if tag_name not in self.begin_tags or tag_name not in self.end_tags: + continue + if tag_name not in new_tags: + old_tags.append(tag_name) - for tag in iter.get_toggled_tags(False): - tag_name = tag.get_property('name') - if tag_name not in self.end_tags: - continue - end_tags.append(tag_name) + for tag in iter.get_toggled_tags(False): + tag_name = tag.get_property('name') + if tag_name not in self.end_tags: + continue + end_tags.append(tag_name) - for tag in old_tags: - text += self.end_tags[tag] - for tag in end_tags: - text += self.end_tags[tag] - for tag in new_tags: - text += self.begin_tags[tag] - for tag in old_tags: - text += self.begin_tags[tag] + for tag in old_tags: + text += self.end_tags[tag] + for tag in end_tags: + text += self.end_tags[tag] + for tag in new_tags: + text += self.begin_tags[tag] + for tag in old_tags: + text += self.begin_tags[tag] - text += xhtml_special(buffer.get_text(old, buffer.get_end_iter())) - for tag in iter.get_toggled_tags(False): - tag_name = tag.get_property('name') - if tag_name not in self.end_tags: - continue - text += self.end_tags[tag_name] + text += xhtml_special(buffer.get_text(old, buffer.get_end_iter())) + for tag in iter.get_toggled_tags(False): + tag_name = tag.get_property('name') + if tag_name not in self.end_tags: + continue + text += self.end_tags[tag_name] - if modified: - return '

' + self.make_clickable_urls(text) + '

' - else: - return None + if modified: + return '

' + self.make_clickable_urls(text) + '

' + else: + return None - def destroy(self): - gobject.idle_add(gc.collect) + def destroy(self): + gobject.idle_add(gc.collect) - def clear(self, widget = None): - """ - Clear text in the textview - """ - buffer_ = self.get_buffer() - start, end = buffer_.get_bounds() - buffer_.delete(start, end) + def clear(self, widget = None): + """ + Clear text in the textview + """ + buffer_ = self.get_buffer() + start, end = buffer_.get_bounds() + buffer_.delete(start, end) # We register depending on keysym and modifier some bindings @@ -299,46 +299,44 @@ class MessageTextView(gtk.TextView): # CTRL + SHIFT + TAB gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.ISO_Left_Tab, - gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.ISO_Left_Tab, - gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) + gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.ISO_Left_Tab, + gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) # CTRL + TAB gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Tab, - gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Tab, - gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) + gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Tab, + gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) # TAB gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Tab, - 0, 'mykeypress', int, gtk.keysyms.Tab, gtk.gdk.ModifierType, 0) + 0, 'mykeypress', int, gtk.keysyms.Tab, gtk.gdk.ModifierType, 0) # CTRL + UP gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Up, - gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Up, - gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) + gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Up, + gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) # CTRL + DOWN gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Down, - gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Down, - gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) + gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Down, + gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) # ENTER gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Return, - 0, 'mykeypress', int, gtk.keysyms.Return, - gtk.gdk.ModifierType, 0) + 0, 'mykeypress', int, gtk.keysyms.Return, + gtk.gdk.ModifierType, 0) # Ctrl + Enter gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Return, - gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Return, - gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) + gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Return, + gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) # Keypad Enter gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.KP_Enter, - 0, 'mykeypress', int, gtk.keysyms.KP_Enter, - gtk.gdk.ModifierType, 0) + 0, 'mykeypress', int, gtk.keysyms.KP_Enter, + gtk.gdk.ModifierType, 0) # Ctrl + Keypad Enter gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.KP_Enter, - gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.KP_Enter, - gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) - -# vim: se ts=3: + gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.KP_Enter, + gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) diff --git a/src/message_window.py b/src/message_window.py index 8adb580d6..a68d2c76b 100644 --- a/src/message_window.py +++ b/src/message_window.py @@ -42,1160 +42,1158 @@ from common import gajim #################### class MessageWindow(object): - '''Class for windows which contain message like things; chats, - groupchats, etc.''' - - # DND_TARGETS is the targets needed by drag_source_set and drag_dest_set - DND_TARGETS = [('GAJIM_TAB', 0, 81)] - hid = 0 # drag_data_received handler id - ( - CLOSE_TAB_MIDDLE_CLICK, - CLOSE_ESC, - CLOSE_CLOSE_BUTTON, - CLOSE_COMMAND, - CLOSE_CTRL_KEY - ) = range(5) - - def __init__(self, acct, type_, parent_window=None, parent_paned=None): - # A dictionary of dictionaries - # where _contacts[account][jid] == A MessageControl - self._controls = {} - - # If None, the window is not tied to any specific account - self.account = acct - # If None, the window is not tied to any specific type - self.type_ = type_ - # dict { handler id: widget}. Keeps callbacks, which - # lead to cylcular references - self.handlers = {} - # Don't show warning dialogs when we want to delete the window - self.dont_warn_on_delete = False - - self.widget_name = 'message_window' - self.xml = gtkgui_helpers.get_glade('%s.glade' % self.widget_name) - self.window = self.xml.get_widget(self.widget_name) - self.notebook = self.xml.get_widget('notebook') - self.parent_paned = None - - if parent_window: - orig_window = self.window - self.window = parent_window - self.parent_paned = parent_paned - self.notebook.reparent(self.parent_paned) - self.parent_paned.pack2(self.notebook, resize=True, shrink=True) - orig_window.destroy() - del orig_window - - # NOTE: we use 'connect_after' here because in - # MessageWindowMgr._new_window we register handler that saves window - # state when closing it, and it should be called before - # MessageWindow._on_window_delete, which manually destroys window - # through win.destroy() - this means no additional handlers for - # 'delete-event' are called. - id_ = self.window.connect_after('delete-event', self._on_window_delete) - self.handlers[id_] = self.window - id_ = self.window.connect('destroy', self._on_window_destroy) - self.handlers[id_] = self.window - id_ = self.window.connect('focus-in-event', self._on_window_focus) - self.handlers[id_] = self.window - - keys=['f', 'g', 'h', 'i', - 'l', 'L', 'n', 'u', - 'b', 'Tab', 'Tab', 'F4', - 'w', 'Page_Up', 'Page_Down', 'Right', - 'Left', 'd', 'c', 'm', 't', 'Escape'] + \ - [''+str(i) for i in xrange(10)] - accel_group = gtk.AccelGroup() - for key in keys: - keyval, mod = gtk.accelerator_parse(key) - accel_group.connect_group(keyval, mod, gtk.ACCEL_VISIBLE, - self.accel_group_func) - self.window.add_accel_group(accel_group) - - # gtk+ doesn't make use of the motion notify on gtkwindow by default - # so this line adds that - self.window.add_events(gtk.gdk.POINTER_MOTION_MASK) - self.alignment = self.xml.get_widget('alignment') - - id_ = self.notebook.connect('switch-page', - self._on_notebook_switch_page) - self.handlers[id_] = self.notebook - id_ = self.notebook.connect('key-press-event', - self._on_notebook_key_press) - self.handlers[id_] = self.notebook - - # Remove the glade pages - while self.notebook.get_n_pages(): - self.notebook.remove_page(0) - # Tab customizations - pref_pos = gajim.config.get('tabs_position') - if pref_pos == 'bottom': - nb_pos = gtk.POS_BOTTOM - elif pref_pos == 'left': - nb_pos = gtk.POS_LEFT - elif pref_pos == 'right': - nb_pos = gtk.POS_RIGHT - else: - nb_pos = gtk.POS_TOP - self.notebook.set_tab_pos(nb_pos) - window_mode = gajim.interface.msg_win_mgr.mode - if gajim.config.get('tabs_always_visible') or \ - window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: - self.notebook.set_show_tabs(True) - self.alignment.set_property('top-padding', 2) - else: - self.notebook.set_show_tabs(False) - self.notebook.set_show_border(gajim.config.get('tabs_border')) - self.show_icon() - - def change_account_name(self, old_name, new_name): - if old_name in self._controls: - self._controls[new_name] = self._controls[old_name] - del self._controls[old_name] - - for ctrl in self.controls(): - if ctrl.account == old_name: - ctrl.account = new_name - if self.account == old_name: - self.account = new_name - - def change_jid(self, account, old_jid, new_jid): - ''' call then when the full jid of a contral change''' - if account not in self._controls: - return - if old_jid not in self._controls[account]: - return - if old_jid == new_jid: - return - self._controls[account][new_jid] = self._controls[account][old_jid] - del self._controls[account][old_jid] - - def get_num_controls(self): - return sum(len(d) for d in self._controls.values()) - - def resize(self, width, height): - gtkgui_helpers.resize_window(self.window, width, height) - - def _on_window_focus(self, widget, event): - # window received focus, so if we had urgency REMOVE IT - # NOTE: we do not have to read the message (it maybe in a bg tab) - # to remove urgency hint so this functions does that - gtkgui_helpers.set_unset_urgency_hint(self.window, False) - - ctrl = self.get_active_control() - if ctrl: - ctrl.set_control_active(True) - # Undo "unread" state display, etc. - if ctrl.type_id == message_control.TYPE_GC: - self.redraw_tab(ctrl, 'active') - else: - # NOTE: we do not send any chatstate to preserve - # inactive, gone, etc. - self.redraw_tab(ctrl) - - def _on_window_delete(self, win, event): - if self.dont_warn_on_delete: - # Destroy the window - return False - - # Number of controls that will be closed and for which we'll loose data: - # chat, pm, gc that won't go in roster - number_of_closed_control = 0 - for ctrl in self.controls(): - if not ctrl.safe_shutdown(): - number_of_closed_control += 1 - - if number_of_closed_control > 1: - def on_yes1(checked): - if checked: - gajim.config.set('confirm_close_multiple_tabs', False) - self.dont_warn_on_delete = True - for ctrl in self.controls(): - if ctrl.minimizable(): - ctrl.minimize() - win.destroy() - - if not gajim.config.get('confirm_close_multiple_tabs'): - # destroy window - return False - dialogs.YesNoDialog( - _('You are going to close several tabs'), - _('Do you really want to close them all?'), - checktext=_('Do _not ask me again'), on_response_yes=on_yes1) - return True - - def on_yes(ctrl): - if self.on_delete_ok == 1: - self.dont_warn_on_delete = True - win.destroy() - self.on_delete_ok -= 1 - - def on_no(ctrl): - return - - def on_minimize(ctrl): - ctrl.minimize() - if self.on_delete_ok == 1: - self.dont_warn_on_delete = True - win.destroy() - self.on_delete_ok -= 1 - - # Make sure all controls are okay with being deleted - self.on_delete_ok = self.get_nb_controls() - for ctrl in self.controls(): - ctrl.allow_shutdown(self.CLOSE_CLOSE_BUTTON, on_yes, on_no, - on_minimize) - return True # halt the delete for the moment - - def _on_window_destroy(self, win): - for ctrl in self.controls(): - ctrl.shutdown() - self._controls.clear() - # Clean up handlers connected to the parent window, this is important since - # self.window may be the RosterWindow - for i in self.handlers.keys(): - if self.handlers[i].handler_is_connected(i): - self.handlers[i].disconnect(i) - del self.handlers[i] - del self.handlers - - def new_tab(self, control): - fjid = control.get_full_jid() - - if control.account not in self._controls: - self._controls[control.account] = {} - - self._controls[control.account][fjid] = control - - if self.get_num_controls() == 2: - # is first conversation_textview scrolled down ? - scrolled = False - first_widget = self.notebook.get_nth_page(0) - ctrl = self._widget_to_control(first_widget) - conv_textview = ctrl.conv_textview - if conv_textview.at_the_end(): - scrolled = True - self.notebook.set_show_tabs(True) - if scrolled: - gobject.idle_add(conv_textview.scroll_to_end_iter) - self.alignment.set_property('top-padding', 2) - - # Add notebook page and connect up to the tab's close button - xml = gtkgui_helpers.get_glade('message_window.glade', 'chat_tab_ebox') - tab_label_box = xml.get_widget('chat_tab_ebox') - widget = xml.get_widget('tab_close_button') - id_ = widget.connect('clicked', self._on_close_button_clicked, control) - control.handlers[id_] = widget - - id_ = tab_label_box.connect('button-press-event', self.on_tab_eventbox_button_press_event, - control.widget) - control.handlers[id_] = tab_label_box - self.notebook.append_page(control.widget, tab_label_box) - - self.notebook.set_tab_reorderable(control.widget, True) - - self.redraw_tab(control) - if self.parent_paned: - self.notebook.show_all() - else: - self.window.show_all() - # NOTE: we do not call set_control_active(True) since we don't know whether - # the tab is the active one. - self.show_title() - - def on_tab_eventbox_button_press_event(self, widget, event, child): - if event.button == 3: # right click - n = self.notebook.page_num(child) - self.notebook.set_current_page(n) - self.popup_menu(event) - elif event.button == 2: # middle click - ctrl = self._widget_to_control(child) - self.remove_tab(ctrl, self.CLOSE_TAB_MIDDLE_CLICK) - - def _on_message_textview_mykeypress_event(self, widget, event_keyval, - event_keymod): - # NOTE: handles mykeypress which is custom signal; see message_textview.py - - # construct event instance from binding - event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here - event.keyval = event_keyval - event.state = event_keymod - event.time = 0 # assign current time - - if event.state & gtk.gdk.CONTROL_MASK: - # Tab switch bindings - if event.keyval == gtk.keysyms.Tab: # CTRL + TAB - self.move_to_next_unread_tab(True) - elif event.keyval == gtk.keysyms.ISO_Left_Tab: # CTRL + SHIFT + TAB - self.move_to_next_unread_tab(False) - elif event.keyval == gtk.keysyms.Page_Down: # CTRL + PAGE DOWN - self.notebook.emit('key_press_event', event) - elif event.keyval == gtk.keysyms.Page_Up: # CTRL + PAGE UP - self.notebook.emit('key_press_event', event) - - def accel_group_func(self, accel_group, acceleratable, keyval, modifier): - st = '1234567890' # alt+1 means the first tab (tab 0) - control = self.get_active_control() - if not control: - # No more control in this window - return - - # CTRL mask - if modifier & gtk.gdk.CONTROL_MASK: - if keyval == gtk.keysyms.h: # CTRL + h - control._on_history_menuitem_activate() - elif control.type_id == message_control.TYPE_CHAT and \ - keyval == gtk.keysyms.f: # CTRL + f - control._on_send_file_menuitem_activate(None) - elif control.type_id == message_control.TYPE_CHAT and \ - keyval == gtk.keysyms.g: # CTRL + g - control._on_convert_to_gc_menuitem_activate(None) - elif control.type_id in (message_control.TYPE_CHAT, - message_control.TYPE_PM) and keyval == gtk.keysyms.i: # CTRL + i - control._on_contact_information_menuitem_activate(None) - elif keyval == gtk.keysyms.l or keyval == gtk.keysyms.L: # CTRL + l|L - control.conv_textview.clear() - elif control.type_id == message_control.TYPE_GC and \ - keyval == gtk.keysyms.n: # CTRL + n - control._on_change_nick_menuitem_activate(None) - elif keyval == gtk.keysyms.u: # CTRL + u: emacs style clear line - control.clear(control.msg_textview) - elif control.type_id == message_control.TYPE_GC and \ - keyval == gtk.keysyms.b: # CTRL + b - control._on_bookmark_room_menuitem_activate(None) - # Tab switch bindings - elif keyval == gtk.keysyms.ISO_Left_Tab: # CTRL + SHIFT + TAB - self.move_to_next_unread_tab(False) - elif keyval == gtk.keysyms.Tab: # CTRL + TAB - self.move_to_next_unread_tab(True) - elif keyval == gtk.keysyms.F4: # CTRL + F4 - self.remove_tab(control, self.CLOSE_CTRL_KEY) - elif keyval == gtk.keysyms.w: # CTRL + w - # CTRL + w removes latest word before sursor when User uses emacs - # theme - if not gtk.settings_get_default().get_property( - 'gtk-key-theme-name') == 'Emacs': - self.remove_tab(control, self.CLOSE_CTRL_KEY) - elif keyval in (gtk.keysyms.Page_Up, gtk.keysyms.Page_Down): - # CTRL + PageUp | PageDown - # Create event and send it to notebook - event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) - event.window = self.window.window - event.time = int(time.time()) - event.state = gtk.gdk.CONTROL_MASK - event.keyval = int(keyval) - self.notebook.emit('key_press_event', event) - - # MOD1 (ALT) mask - elif modifier & gtk.gdk.MOD1_MASK: - # Tab switch bindings - if keyval == gtk.keysyms.Right: # ALT + RIGHT - new = self.notebook.get_current_page() + 1 - if new >= self.notebook.get_n_pages(): - new = 0 - self.notebook.set_current_page(new) - elif keyval == gtk.keysyms.Left: # ALT + LEFT - new = self.notebook.get_current_page() - 1 - if new < 0: - new = self.notebook.get_n_pages() - 1 - self.notebook.set_current_page(new) - elif chr(keyval) in st: # ALT + 1,2,3.. - self.notebook.set_current_page(st.index(chr(keyval))) - elif keyval == gtk.keysyms.c: # ALT + C toggles chat buttons - control.chat_buttons_set_visible(not control.hide_chat_buttons) - elif keyval == gtk.keysyms.m: # ALT + M show emoticons menu - control.show_emoticons_menu() - elif keyval == gtk.keysyms.d: # ALT + D show actions menu - control.on_actions_button_clicked(control.actions_button) - elif control.type_id == message_control.TYPE_GC and \ - keyval == gtk.keysyms.t: # ALT + t - control._on_change_subject_menuitem_activate(None) - # Close tab bindings - elif keyval == gtk.keysyms.Escape and \ - gajim.config.get('escape_key_closes'): # Escape - self.remove_tab(control, self.CLOSE_ESC) - return True - - def _on_close_button_clicked(self, button, control): - '''When close button is pressed: close a tab''' - self.remove_tab(control, self.CLOSE_CLOSE_BUTTON) - - def show_icon(self): - window_mode = gajim.interface.msg_win_mgr.mode - icon = None - if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_NEVER: - ctrl = self.get_active_control() - if not ctrl: - return - icon = ctrl.get_tab_image(count_unread=False) - elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS: - pass # keep default icon - elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: - pass # keep default icon - elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERACCT: - pass # keep default icon - elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERTYPE: - if self.type_ == 'gc': - icon = gtkgui_helpers.load_icon('muc_active') - else: - # chat, pm - icon = gtkgui_helpers.load_icon('online') - if icon: - self.window.set_icon(icon.get_pixbuf()) - - def show_title(self, urgent=True, control=None): - '''redraw the window's title''' - if not control: - control = self.get_active_control() - if not control: - # No more control in this window - return - unread = 0 - for ctrl in self.controls(): - if ctrl.type_id == message_control.TYPE_GC and not \ - gajim.config.get('notify_on_all_muc_messages') and not \ - ctrl.attention_flag: - # count only pm messages - unread += ctrl.get_nb_unread_pm() - continue - unread += ctrl.get_nb_unread() - - unread_str = '' - if unread > 1: - unread_str = '[' + unicode(unread) + '] ' - elif unread == 1: - unread_str = '* ' - else: - urgent = False - - if control.type_id == message_control.TYPE_GC: - name = control.room_jid.split('@')[0] - urgent = control.attention_flag - else: - name = control.contact.get_shown_name() - if control.resource: - name += '/' + control.resource - - window_mode = gajim.interface.msg_win_mgr.mode - if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERTYPE: - # Show the plural form since number of tabs > 1 - if self.type_ == 'chat': - label = _('Chats') - elif self.type_ == 'gc': - label = _('Group Chats') - else: - label = _('Private Chats') - elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: - label = None - elif self.get_num_controls() == 1: - label = name - else: - label = _('Messages') - - title = 'Gajim' - if label: - title = '%s - %s' % (label, title) - - if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERACCT: - title = title + ": " + control.account - - self.window.set_title(unread_str + title) - - if urgent: - gtkgui_helpers.set_unset_urgency_hint(self.window, unread) - else: - gtkgui_helpers.set_unset_urgency_hint(self.window, False) - - def set_active_tab(self, ctrl): - ctrl_page = self.notebook.page_num(ctrl.widget) - self.notebook.set_current_page(ctrl_page) - self.window.present() - - def remove_tab(self, ctrl, method, reason = None, force = False): - '''reason is only for gc (offline status message) - if force is True, do not ask any confirmation''' - def close(ctrl): - if reason is not None: # We are leaving gc with a status message - ctrl.shutdown(reason) - else: # We are leaving gc without status message or it's a chat - ctrl.shutdown() - # Update external state - gajim.events.remove_events(ctrl.account, ctrl.get_full_jid, - types = ['printed_msg', 'chat', 'gc_msg']) - - fjid = ctrl.get_full_jid() - jid = gajim.get_jid_without_resource(fjid) - - fctrl = self.get_control(fjid, ctrl.account) - bctrl = self.get_control(jid, ctrl.account) - # keep last_message_time around unless this was our last control with - # that jid - if not fctrl and not bctrl and \ - fjid in gajim.last_message_time[ctrl.account]: - del gajim.last_message_time[ctrl.account][fjid] - - self.notebook.remove_page(self.notebook.page_num(ctrl.widget)) - - del self._controls[ctrl.account][fjid] - - if len(self._controls[ctrl.account]) == 0: - del self._controls[ctrl.account] - - self.check_tabs() - self.show_title() - - def on_yes(ctrl): - close(ctrl) - - def on_no(ctrl): - return - - def on_minimize(ctrl): - if method != self.CLOSE_COMMAND: - ctrl.minimize() - self.check_tabs() - return - close(ctrl) - - # Shutdown the MessageControl - if force: - close(ctrl) - else: - ctrl.allow_shutdown(method, on_yes, on_no, on_minimize) - - def check_tabs(self): - if self.get_num_controls() == 0: - # These are not called when the window is destroyed like this, fake it - gajim.interface.msg_win_mgr._on_window_delete(self.window, None) - gajim.interface.msg_win_mgr._on_window_destroy(self.window) - # dnd clean up - self.notebook.drag_dest_unset() - if self.parent_paned: - # Don't close parent window, just remove the child - child = self.parent_paned.get_child2() - self.parent_paned.remove(child) - else: - self.window.destroy() - return # don't show_title, we are dead - elif self.get_num_controls() == 1: # we are going from two tabs to one - window_mode = gajim.interface.msg_win_mgr.mode - show_tabs_if_one_tab = gajim.config.get('tabs_always_visible') or \ - window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER - self.notebook.set_show_tabs(show_tabs_if_one_tab) - if not show_tabs_if_one_tab: - self.alignment.set_property('top-padding', 0) - - - def redraw_tab(self, ctrl, chatstate = None): - hbox = self.notebook.get_tab_label(ctrl.widget).get_children()[0] - status_img = hbox.get_children()[0] - nick_label = hbox.get_children()[1] - - # Optionally hide close button - close_button = hbox.get_children()[2] - if gajim.config.get('tabs_close_button'): - close_button.show() - else: - close_button.hide() - - # Update nick - nick_label.set_max_width_chars(10) - (tab_label_str, tab_label_color) = ctrl.get_tab_label(chatstate) - nick_label.set_markup(tab_label_str) - if tab_label_color: - nick_label.modify_fg(gtk.STATE_NORMAL, tab_label_color) - nick_label.modify_fg(gtk.STATE_ACTIVE, tab_label_color) - - tab_img = ctrl.get_tab_image() - if tab_img: - if tab_img.get_storage_type() == gtk.IMAGE_ANIMATION: - status_img.set_from_animation(tab_img.get_animation()) - else: - status_img.set_from_pixbuf(tab_img.get_pixbuf()) - - self.show_icon() - - def repaint_themed_widgets(self): - '''Repaint controls in the window with theme color''' - # iterate through controls and repaint - for ctrl in self.controls(): - ctrl.repaint_themed_widgets() - - def _widget_to_control(self, widget): - for ctrl in self.controls(): - if ctrl.widget == widget: - return ctrl - return None - - def get_active_control(self): - notebook = self.notebook - active_widget = notebook.get_nth_page(notebook.get_current_page()) - return self._widget_to_control(active_widget) - - def get_active_contact(self): - ctrl = self.get_active_control() - if ctrl: - return ctrl.contact - return None - - def get_active_jid(self): - contact = self.get_active_contact() - if contact: - return contact.jid - return None - - def is_active(self): - return self.window.is_active() - - def get_origin(self): - return self.window.window.get_origin() - - def toggle_emoticons(self): - for ctrl in self.controls(): - ctrl.toggle_emoticons() - - def update_font(self): - for ctrl in self.controls(): - ctrl.update_font() - - def update_tags(self): - for ctrl in self.controls(): - ctrl.update_tags() - - def get_control(self, key, acct): - '''Return the MessageControl for jid or n, where n is a notebook page index. - When key is an int index acct may be None''' - if isinstance(key, str): - key = unicode(key, 'utf-8') - - if isinstance(key, unicode): - jid = key - try: - return self._controls[acct][jid] - except Exception: - return None - else: - page_num = key - notebook = self.notebook - if page_num is None: - page_num = notebook.get_current_page() - nth_child = notebook.get_nth_page(page_num) - return self._widget_to_control(nth_child) - - def has_control(self, jid, acct): - return (acct in self._controls and jid in self._controls[acct]) - - def change_key(self, old_jid, new_jid, acct): - '''Change the JID key of a control''' - try: - # Check if controls exists - ctrl = self._controls[acct][old_jid] - except KeyError: - return - - self._controls[acct][new_jid] = ctrl - del self._controls[acct][old_jid] - - if old_jid in gajim.last_message_time[acct]: - gajim.last_message_time[acct][new_jid] = \ - gajim.last_message_time[acct][old_jid] - del gajim.last_message_time[acct][old_jid] - - def controls(self): - for jid_dict in self._controls.values(): - for ctrl in jid_dict.values(): - yield ctrl - - def get_nb_controls(self): - return sum(len(jid_dict) for jid_dict in self._controls.values()) - - def move_to_next_unread_tab(self, forward): - ind = self.notebook.get_current_page() - current = ind - found = False - first_composing_ind = -1 # id of first composing ctrl to switch to - # if no others controls have awaiting events - # loop until finding an unread tab or having done a complete cycle - while True: - if forward == True: # look for the first unread tab on the right - ind = ind + 1 - if ind >= self.notebook.get_n_pages(): - ind = 0 - else: # look for the first unread tab on the right - ind = ind - 1 - if ind < 0: - ind = self.notebook.get_n_pages() - 1 - ctrl = self.get_control(ind, None) - if ctrl.get_nb_unread() > 0: - found = True - break # found - elif gajim.config.get('ctrl_tab_go_to_next_composing') : # Search for a composing contact - contact = ctrl.contact - if first_composing_ind == -1 and contact.chatstate == 'composing': - # If no composing contact found yet, check if this one is composing - first_composing_ind = ind - if ind == current: - break # a complete cycle without finding an unread tab - if found: - self.notebook.set_current_page(ind) - elif first_composing_ind != -1: - self.notebook.set_current_page(first_composing_ind) - else: # not found and nobody composing - if forward: # CTRL + TAB - if current < (self.notebook.get_n_pages() - 1): - self.notebook.next_page() - else: # traverse for ever (eg. don't stop at last tab) - self.notebook.set_current_page(0) - else: # CTRL + SHIFT + TAB - if current > 0: - self.notebook.prev_page() - else: # traverse for ever (eg. don't stop at first tab) - self.notebook.set_current_page( - self.notebook.get_n_pages() - 1) - - def popup_menu(self, event): - menu = self.get_active_control().prepare_context_menu() - # show the menu - menu.popup(None, None, None, event.button, event.time) - menu.show_all() - - def _on_notebook_switch_page(self, notebook, page, page_num): - old_no = notebook.get_current_page() - if old_no >= 0: - old_ctrl = self._widget_to_control(notebook.get_nth_page(old_no)) - old_ctrl.set_control_active(False) - - new_ctrl = self._widget_to_control(notebook.get_nth_page(page_num)) - new_ctrl.set_control_active(True) - self.show_title(control = new_ctrl) - - control = self.get_active_control() - if isinstance(control, ChatControlBase): - control.msg_textview.grab_focus() - - def _on_notebook_key_press(self, widget, event): - # when tab itself is selected, make sure <- and -> are allowed for navigating between tabs - if event.keyval in (gtk.keysyms.Left, gtk.keysyms.Right): - return False - - control = self.get_active_control() - - if event.state & gtk.gdk.SHIFT_MASK: - # CTRL + SHIFT + TAB - if event.state & gtk.gdk.CONTROL_MASK and \ - event.keyval == gtk.keysyms.ISO_Left_Tab: - self.move_to_next_unread_tab(False) - return True - # SHIFT + PAGE_[UP|DOWN]: send to conv_textview - elif event.keyval in (gtk.keysyms.Page_Down, gtk.keysyms.Page_Up): - control.conv_textview.tv.emit('key_press_event', event) - return True - elif event.state & gtk.gdk.CONTROL_MASK: - if event.keyval == gtk.keysyms.Tab: # CTRL + TAB - self.move_to_next_unread_tab(True) - return True - # Ctrl+PageUP / DOWN has to be handled by notebook - elif event.keyval == gtk.keysyms.Page_Down: - self.move_to_next_unread_tab(True) - return True - elif event.keyval == gtk.keysyms.Page_Up: - self.move_to_next_unread_tab(False) - return True - if event.keyval in (gtk.keysyms.Shift_L, gtk.keysyms.Shift_R, - gtk.keysyms.Control_L, gtk.keysyms.Control_R, gtk.keysyms.Caps_Lock, - gtk.keysyms.Shift_Lock, gtk.keysyms.Meta_L, gtk.keysyms.Meta_R, - gtk.keysyms.Alt_L, gtk.keysyms.Alt_R, gtk.keysyms.Super_L, - gtk.keysyms.Super_R, gtk.keysyms.Hyper_L, gtk.keysyms.Hyper_R): - return True - - if isinstance(control, ChatControlBase): - # we forwarded it to message textview - control.msg_textview.emit('key_press_event', event) - control.msg_textview.grab_focus() - - def get_tab_at_xy(self, x, y): - '''Thanks to Gaim - Return the tab under xy and - if its nearer from left or right side of the tab - ''' - page_num = -1 - to_right = False - horiz = self.notebook.get_tab_pos() == gtk.POS_TOP or \ - self.notebook.get_tab_pos() == gtk.POS_BOTTOM - for i in xrange(self.notebook.get_n_pages()): - page = self.notebook.get_nth_page(i) - tab = self.notebook.get_tab_label(page) - tab_alloc = tab.get_allocation() - if horiz: - if (x >= tab_alloc.x) and \ - (x <= (tab_alloc.x + tab_alloc.width)): - page_num = i - if x >= tab_alloc.x + (tab_alloc.width / 2.0): - to_right = True - break - else: - if (y >= tab_alloc.y) and \ - (y <= (tab_alloc.y + tab_alloc.height)): - page_num = i - - if y > tab_alloc.y + (tab_alloc.height / 2.0): - to_right = True - break - return (page_num, to_right) - - def find_page_num_according_to_tab_label(self, tab_label): - '''Find the page num of the tab label''' - page_num = -1 - for i in xrange(self.notebook.get_n_pages()): - page = self.notebook.get_nth_page(i) - tab = self.notebook.get_tab_label(page) - if tab == tab_label: - page_num = i - break - return page_num + '''Class for windows which contain message like things; chats, + groupchats, etc.''' + + # DND_TARGETS is the targets needed by drag_source_set and drag_dest_set + DND_TARGETS = [('GAJIM_TAB', 0, 81)] + hid = 0 # drag_data_received handler id + ( + CLOSE_TAB_MIDDLE_CLICK, + CLOSE_ESC, + CLOSE_CLOSE_BUTTON, + CLOSE_COMMAND, + CLOSE_CTRL_KEY + ) = range(5) + + def __init__(self, acct, type_, parent_window=None, parent_paned=None): + # A dictionary of dictionaries + # where _contacts[account][jid] == A MessageControl + self._controls = {} + + # If None, the window is not tied to any specific account + self.account = acct + # If None, the window is not tied to any specific type + self.type_ = type_ + # dict { handler id: widget}. Keeps callbacks, which + # lead to cylcular references + self.handlers = {} + # Don't show warning dialogs when we want to delete the window + self.dont_warn_on_delete = False + + self.widget_name = 'message_window' + self.xml = gtkgui_helpers.get_glade('%s.glade' % self.widget_name) + self.window = self.xml.get_widget(self.widget_name) + self.notebook = self.xml.get_widget('notebook') + self.parent_paned = None + + if parent_window: + orig_window = self.window + self.window = parent_window + self.parent_paned = parent_paned + self.notebook.reparent(self.parent_paned) + self.parent_paned.pack2(self.notebook, resize=True, shrink=True) + orig_window.destroy() + del orig_window + + # NOTE: we use 'connect_after' here because in + # MessageWindowMgr._new_window we register handler that saves window + # state when closing it, and it should be called before + # MessageWindow._on_window_delete, which manually destroys window + # through win.destroy() - this means no additional handlers for + # 'delete-event' are called. + id_ = self.window.connect_after('delete-event', self._on_window_delete) + self.handlers[id_] = self.window + id_ = self.window.connect('destroy', self._on_window_destroy) + self.handlers[id_] = self.window + id_ = self.window.connect('focus-in-event', self._on_window_focus) + self.handlers[id_] = self.window + + keys=['f', 'g', 'h', 'i', + 'l', 'L', 'n', 'u', + 'b', 'Tab', 'Tab', 'F4', + 'w', 'Page_Up', 'Page_Down', 'Right', + 'Left', 'd', 'c', 'm', 't', 'Escape'] + \ + [''+str(i) for i in xrange(10)] + accel_group = gtk.AccelGroup() + for key in keys: + keyval, mod = gtk.accelerator_parse(key) + accel_group.connect_group(keyval, mod, gtk.ACCEL_VISIBLE, + self.accel_group_func) + self.window.add_accel_group(accel_group) + + # gtk+ doesn't make use of the motion notify on gtkwindow by default + # so this line adds that + self.window.add_events(gtk.gdk.POINTER_MOTION_MASK) + self.alignment = self.xml.get_widget('alignment') + + id_ = self.notebook.connect('switch-page', + self._on_notebook_switch_page) + self.handlers[id_] = self.notebook + id_ = self.notebook.connect('key-press-event', + self._on_notebook_key_press) + self.handlers[id_] = self.notebook + + # Remove the glade pages + while self.notebook.get_n_pages(): + self.notebook.remove_page(0) + # Tab customizations + pref_pos = gajim.config.get('tabs_position') + if pref_pos == 'bottom': + nb_pos = gtk.POS_BOTTOM + elif pref_pos == 'left': + nb_pos = gtk.POS_LEFT + elif pref_pos == 'right': + nb_pos = gtk.POS_RIGHT + else: + nb_pos = gtk.POS_TOP + self.notebook.set_tab_pos(nb_pos) + window_mode = gajim.interface.msg_win_mgr.mode + if gajim.config.get('tabs_always_visible') or \ + window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: + self.notebook.set_show_tabs(True) + self.alignment.set_property('top-padding', 2) + else: + self.notebook.set_show_tabs(False) + self.notebook.set_show_border(gajim.config.get('tabs_border')) + self.show_icon() + + def change_account_name(self, old_name, new_name): + if old_name in self._controls: + self._controls[new_name] = self._controls[old_name] + del self._controls[old_name] + + for ctrl in self.controls(): + if ctrl.account == old_name: + ctrl.account = new_name + if self.account == old_name: + self.account = new_name + + def change_jid(self, account, old_jid, new_jid): + ''' call then when the full jid of a contral change''' + if account not in self._controls: + return + if old_jid not in self._controls[account]: + return + if old_jid == new_jid: + return + self._controls[account][new_jid] = self._controls[account][old_jid] + del self._controls[account][old_jid] + + def get_num_controls(self): + return sum(len(d) for d in self._controls.values()) + + def resize(self, width, height): + gtkgui_helpers.resize_window(self.window, width, height) + + def _on_window_focus(self, widget, event): + # window received focus, so if we had urgency REMOVE IT + # NOTE: we do not have to read the message (it maybe in a bg tab) + # to remove urgency hint so this functions does that + gtkgui_helpers.set_unset_urgency_hint(self.window, False) + + ctrl = self.get_active_control() + if ctrl: + ctrl.set_control_active(True) + # Undo "unread" state display, etc. + if ctrl.type_id == message_control.TYPE_GC: + self.redraw_tab(ctrl, 'active') + else: + # NOTE: we do not send any chatstate to preserve + # inactive, gone, etc. + self.redraw_tab(ctrl) + + def _on_window_delete(self, win, event): + if self.dont_warn_on_delete: + # Destroy the window + return False + + # Number of controls that will be closed and for which we'll loose data: + # chat, pm, gc that won't go in roster + number_of_closed_control = 0 + for ctrl in self.controls(): + if not ctrl.safe_shutdown(): + number_of_closed_control += 1 + + if number_of_closed_control > 1: + def on_yes1(checked): + if checked: + gajim.config.set('confirm_close_multiple_tabs', False) + self.dont_warn_on_delete = True + for ctrl in self.controls(): + if ctrl.minimizable(): + ctrl.minimize() + win.destroy() + + if not gajim.config.get('confirm_close_multiple_tabs'): + # destroy window + return False + dialogs.YesNoDialog( + _('You are going to close several tabs'), +_('Do you really want to close them all?'), + checktext=_('Do _not ask me again'), on_response_yes=on_yes1) + return True + + def on_yes(ctrl): + if self.on_delete_ok == 1: + self.dont_warn_on_delete = True + win.destroy() + self.on_delete_ok -= 1 + + def on_no(ctrl): + return + + def on_minimize(ctrl): + ctrl.minimize() + if self.on_delete_ok == 1: + self.dont_warn_on_delete = True + win.destroy() + self.on_delete_ok -= 1 + + # Make sure all controls are okay with being deleted + self.on_delete_ok = self.get_nb_controls() + for ctrl in self.controls(): + ctrl.allow_shutdown(self.CLOSE_CLOSE_BUTTON, on_yes, on_no, + on_minimize) + return True # halt the delete for the moment + + def _on_window_destroy(self, win): + for ctrl in self.controls(): + ctrl.shutdown() + self._controls.clear() + # Clean up handlers connected to the parent window, this is important since + # self.window may be the RosterWindow + for i in self.handlers.keys(): + if self.handlers[i].handler_is_connected(i): + self.handlers[i].disconnect(i) + del self.handlers[i] + del self.handlers + + def new_tab(self, control): + fjid = control.get_full_jid() + + if control.account not in self._controls: + self._controls[control.account] = {} + + self._controls[control.account][fjid] = control + + if self.get_num_controls() == 2: + # is first conversation_textview scrolled down ? + scrolled = False + first_widget = self.notebook.get_nth_page(0) + ctrl = self._widget_to_control(first_widget) + conv_textview = ctrl.conv_textview + if conv_textview.at_the_end(): + scrolled = True + self.notebook.set_show_tabs(True) + if scrolled: + gobject.idle_add(conv_textview.scroll_to_end_iter) + self.alignment.set_property('top-padding', 2) + + # Add notebook page and connect up to the tab's close button + xml = gtkgui_helpers.get_glade('message_window.glade', 'chat_tab_ebox') + tab_label_box = xml.get_widget('chat_tab_ebox') + widget = xml.get_widget('tab_close_button') + id_ = widget.connect('clicked', self._on_close_button_clicked, control) + control.handlers[id_] = widget + + id_ = tab_label_box.connect('button-press-event', self.on_tab_eventbox_button_press_event, + control.widget) + control.handlers[id_] = tab_label_box + self.notebook.append_page(control.widget, tab_label_box) + + self.notebook.set_tab_reorderable(control.widget, True) + + self.redraw_tab(control) + if self.parent_paned: + self.notebook.show_all() + else: + self.window.show_all() + # NOTE: we do not call set_control_active(True) since we don't know whether + # the tab is the active one. + self.show_title() + + def on_tab_eventbox_button_press_event(self, widget, event, child): + if event.button == 3: # right click + n = self.notebook.page_num(child) + self.notebook.set_current_page(n) + self.popup_menu(event) + elif event.button == 2: # middle click + ctrl = self._widget_to_control(child) + self.remove_tab(ctrl, self.CLOSE_TAB_MIDDLE_CLICK) + + def _on_message_textview_mykeypress_event(self, widget, event_keyval, + event_keymod): + # NOTE: handles mykeypress which is custom signal; see message_textview.py + + # construct event instance from binding + event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here + event.keyval = event_keyval + event.state = event_keymod + event.time = 0 # assign current time + + if event.state & gtk.gdk.CONTROL_MASK: + # Tab switch bindings + if event.keyval == gtk.keysyms.Tab: # CTRL + TAB + self.move_to_next_unread_tab(True) + elif event.keyval == gtk.keysyms.ISO_Left_Tab: # CTRL + SHIFT + TAB + self.move_to_next_unread_tab(False) + elif event.keyval == gtk.keysyms.Page_Down: # CTRL + PAGE DOWN + self.notebook.emit('key_press_event', event) + elif event.keyval == gtk.keysyms.Page_Up: # CTRL + PAGE UP + self.notebook.emit('key_press_event', event) + + def accel_group_func(self, accel_group, acceleratable, keyval, modifier): + st = '1234567890' # alt+1 means the first tab (tab 0) + control = self.get_active_control() + if not control: + # No more control in this window + return + + # CTRL mask + if modifier & gtk.gdk.CONTROL_MASK: + if keyval == gtk.keysyms.h: # CTRL + h + control._on_history_menuitem_activate() + elif control.type_id == message_control.TYPE_CHAT and \ + keyval == gtk.keysyms.f: # CTRL + f + control._on_send_file_menuitem_activate(None) + elif control.type_id == message_control.TYPE_CHAT and \ + keyval == gtk.keysyms.g: # CTRL + g + control._on_convert_to_gc_menuitem_activate(None) + elif control.type_id in (message_control.TYPE_CHAT, + message_control.TYPE_PM) and keyval == gtk.keysyms.i: # CTRL + i + control._on_contact_information_menuitem_activate(None) + elif keyval == gtk.keysyms.l or keyval == gtk.keysyms.L: # CTRL + l|L + control.conv_textview.clear() + elif control.type_id == message_control.TYPE_GC and \ + keyval == gtk.keysyms.n: # CTRL + n + control._on_change_nick_menuitem_activate(None) + elif keyval == gtk.keysyms.u: # CTRL + u: emacs style clear line + control.clear(control.msg_textview) + elif control.type_id == message_control.TYPE_GC and \ + keyval == gtk.keysyms.b: # CTRL + b + control._on_bookmark_room_menuitem_activate(None) + # Tab switch bindings + elif keyval == gtk.keysyms.ISO_Left_Tab: # CTRL + SHIFT + TAB + self.move_to_next_unread_tab(False) + elif keyval == gtk.keysyms.Tab: # CTRL + TAB + self.move_to_next_unread_tab(True) + elif keyval == gtk.keysyms.F4: # CTRL + F4 + self.remove_tab(control, self.CLOSE_CTRL_KEY) + elif keyval == gtk.keysyms.w: # CTRL + w + # CTRL + w removes latest word before sursor when User uses emacs + # theme + if not gtk.settings_get_default().get_property( + 'gtk-key-theme-name') == 'Emacs': + self.remove_tab(control, self.CLOSE_CTRL_KEY) + elif keyval in (gtk.keysyms.Page_Up, gtk.keysyms.Page_Down): + # CTRL + PageUp | PageDown + # Create event and send it to notebook + event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) + event.window = self.window.window + event.time = int(time.time()) + event.state = gtk.gdk.CONTROL_MASK + event.keyval = int(keyval) + self.notebook.emit('key_press_event', event) + + # MOD1 (ALT) mask + elif modifier & gtk.gdk.MOD1_MASK: + # Tab switch bindings + if keyval == gtk.keysyms.Right: # ALT + RIGHT + new = self.notebook.get_current_page() + 1 + if new >= self.notebook.get_n_pages(): + new = 0 + self.notebook.set_current_page(new) + elif keyval == gtk.keysyms.Left: # ALT + LEFT + new = self.notebook.get_current_page() - 1 + if new < 0: + new = self.notebook.get_n_pages() - 1 + self.notebook.set_current_page(new) + elif chr(keyval) in st: # ALT + 1,2,3.. + self.notebook.set_current_page(st.index(chr(keyval))) + elif keyval == gtk.keysyms.c: # ALT + C toggles chat buttons + control.chat_buttons_set_visible(not control.hide_chat_buttons) + elif keyval == gtk.keysyms.m: # ALT + M show emoticons menu + control.show_emoticons_menu() + elif keyval == gtk.keysyms.d: # ALT + D show actions menu + control.on_actions_button_clicked(control.actions_button) + elif control.type_id == message_control.TYPE_GC and \ + keyval == gtk.keysyms.t: # ALT + t + control._on_change_subject_menuitem_activate(None) + # Close tab bindings + elif keyval == gtk.keysyms.Escape and \ + gajim.config.get('escape_key_closes'): # Escape + self.remove_tab(control, self.CLOSE_ESC) + return True + + def _on_close_button_clicked(self, button, control): + '''When close button is pressed: close a tab''' + self.remove_tab(control, self.CLOSE_CLOSE_BUTTON) + + def show_icon(self): + window_mode = gajim.interface.msg_win_mgr.mode + icon = None + if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_NEVER: + ctrl = self.get_active_control() + if not ctrl: + return + icon = ctrl.get_tab_image(count_unread=False) + elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS: + pass # keep default icon + elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: + pass # keep default icon + elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERACCT: + pass # keep default icon + elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERTYPE: + if self.type_ == 'gc': + icon = gtkgui_helpers.load_icon('muc_active') + else: + # chat, pm + icon = gtkgui_helpers.load_icon('online') + if icon: + self.window.set_icon(icon.get_pixbuf()) + + def show_title(self, urgent=True, control=None): + '''redraw the window's title''' + if not control: + control = self.get_active_control() + if not control: + # No more control in this window + return + unread = 0 + for ctrl in self.controls(): + if ctrl.type_id == message_control.TYPE_GC and not \ + gajim.config.get('notify_on_all_muc_messages') and not \ + ctrl.attention_flag: + # count only pm messages + unread += ctrl.get_nb_unread_pm() + continue + unread += ctrl.get_nb_unread() + + unread_str = '' + if unread > 1: + unread_str = '[' + unicode(unread) + '] ' + elif unread == 1: + unread_str = '* ' + else: + urgent = False + + if control.type_id == message_control.TYPE_GC: + name = control.room_jid.split('@')[0] + urgent = control.attention_flag + else: + name = control.contact.get_shown_name() + if control.resource: + name += '/' + control.resource + + window_mode = gajim.interface.msg_win_mgr.mode + if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERTYPE: + # Show the plural form since number of tabs > 1 + if self.type_ == 'chat': + label = _('Chats') + elif self.type_ == 'gc': + label = _('Group Chats') + else: + label = _('Private Chats') + elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: + label = None + elif self.get_num_controls() == 1: + label = name + else: + label = _('Messages') + + title = 'Gajim' + if label: + title = '%s - %s' % (label, title) + + if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERACCT: + title = title + ": " + control.account + + self.window.set_title(unread_str + title) + + if urgent: + gtkgui_helpers.set_unset_urgency_hint(self.window, unread) + else: + gtkgui_helpers.set_unset_urgency_hint(self.window, False) + + def set_active_tab(self, ctrl): + ctrl_page = self.notebook.page_num(ctrl.widget) + self.notebook.set_current_page(ctrl_page) + self.window.present() + + def remove_tab(self, ctrl, method, reason = None, force = False): + '''reason is only for gc (offline status message) + if force is True, do not ask any confirmation''' + def close(ctrl): + if reason is not None: # We are leaving gc with a status message + ctrl.shutdown(reason) + else: # We are leaving gc without status message or it's a chat + ctrl.shutdown() + # Update external state + gajim.events.remove_events(ctrl.account, ctrl.get_full_jid, + types = ['printed_msg', 'chat', 'gc_msg']) + + fjid = ctrl.get_full_jid() + jid = gajim.get_jid_without_resource(fjid) + + fctrl = self.get_control(fjid, ctrl.account) + bctrl = self.get_control(jid, ctrl.account) + # keep last_message_time around unless this was our last control with + # that jid + if not fctrl and not bctrl and \ + fjid in gajim.last_message_time[ctrl.account]: + del gajim.last_message_time[ctrl.account][fjid] + + self.notebook.remove_page(self.notebook.page_num(ctrl.widget)) + + del self._controls[ctrl.account][fjid] + + if len(self._controls[ctrl.account]) == 0: + del self._controls[ctrl.account] + + self.check_tabs() + self.show_title() + + def on_yes(ctrl): + close(ctrl) + + def on_no(ctrl): + return + + def on_minimize(ctrl): + if method != self.CLOSE_COMMAND: + ctrl.minimize() + self.check_tabs() + return + close(ctrl) + + # Shutdown the MessageControl + if force: + close(ctrl) + else: + ctrl.allow_shutdown(method, on_yes, on_no, on_minimize) + + def check_tabs(self): + if self.get_num_controls() == 0: + # These are not called when the window is destroyed like this, fake it + gajim.interface.msg_win_mgr._on_window_delete(self.window, None) + gajim.interface.msg_win_mgr._on_window_destroy(self.window) + # dnd clean up + self.notebook.drag_dest_unset() + if self.parent_paned: + # Don't close parent window, just remove the child + child = self.parent_paned.get_child2() + self.parent_paned.remove(child) + else: + self.window.destroy() + return # don't show_title, we are dead + elif self.get_num_controls() == 1: # we are going from two tabs to one + window_mode = gajim.interface.msg_win_mgr.mode + show_tabs_if_one_tab = gajim.config.get('tabs_always_visible') or \ + window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER + self.notebook.set_show_tabs(show_tabs_if_one_tab) + if not show_tabs_if_one_tab: + self.alignment.set_property('top-padding', 0) + + + def redraw_tab(self, ctrl, chatstate = None): + hbox = self.notebook.get_tab_label(ctrl.widget).get_children()[0] + status_img = hbox.get_children()[0] + nick_label = hbox.get_children()[1] + + # Optionally hide close button + close_button = hbox.get_children()[2] + if gajim.config.get('tabs_close_button'): + close_button.show() + else: + close_button.hide() + + # Update nick + nick_label.set_max_width_chars(10) + (tab_label_str, tab_label_color) = ctrl.get_tab_label(chatstate) + nick_label.set_markup(tab_label_str) + if tab_label_color: + nick_label.modify_fg(gtk.STATE_NORMAL, tab_label_color) + nick_label.modify_fg(gtk.STATE_ACTIVE, tab_label_color) + + tab_img = ctrl.get_tab_image() + if tab_img: + if tab_img.get_storage_type() == gtk.IMAGE_ANIMATION: + status_img.set_from_animation(tab_img.get_animation()) + else: + status_img.set_from_pixbuf(tab_img.get_pixbuf()) + + self.show_icon() + + def repaint_themed_widgets(self): + '''Repaint controls in the window with theme color''' + # iterate through controls and repaint + for ctrl in self.controls(): + ctrl.repaint_themed_widgets() + + def _widget_to_control(self, widget): + for ctrl in self.controls(): + if ctrl.widget == widget: + return ctrl + return None + + def get_active_control(self): + notebook = self.notebook + active_widget = notebook.get_nth_page(notebook.get_current_page()) + return self._widget_to_control(active_widget) + + def get_active_contact(self): + ctrl = self.get_active_control() + if ctrl: + return ctrl.contact + return None + + def get_active_jid(self): + contact = self.get_active_contact() + if contact: + return contact.jid + return None + + def is_active(self): + return self.window.is_active() + + def get_origin(self): + return self.window.window.get_origin() + + def toggle_emoticons(self): + for ctrl in self.controls(): + ctrl.toggle_emoticons() + + def update_font(self): + for ctrl in self.controls(): + ctrl.update_font() + + def update_tags(self): + for ctrl in self.controls(): + ctrl.update_tags() + + def get_control(self, key, acct): + '''Return the MessageControl for jid or n, where n is a notebook page index. + When key is an int index acct may be None''' + if isinstance(key, str): + key = unicode(key, 'utf-8') + + if isinstance(key, unicode): + jid = key + try: + return self._controls[acct][jid] + except Exception: + return None + else: + page_num = key + notebook = self.notebook + if page_num is None: + page_num = notebook.get_current_page() + nth_child = notebook.get_nth_page(page_num) + return self._widget_to_control(nth_child) + + def has_control(self, jid, acct): + return (acct in self._controls and jid in self._controls[acct]) + + def change_key(self, old_jid, new_jid, acct): + '''Change the JID key of a control''' + try: + # Check if controls exists + ctrl = self._controls[acct][old_jid] + except KeyError: + return + + self._controls[acct][new_jid] = ctrl + del self._controls[acct][old_jid] + + if old_jid in gajim.last_message_time[acct]: + gajim.last_message_time[acct][new_jid] = \ + gajim.last_message_time[acct][old_jid] + del gajim.last_message_time[acct][old_jid] + + def controls(self): + for jid_dict in self._controls.values(): + for ctrl in jid_dict.values(): + yield ctrl + + def get_nb_controls(self): + return sum(len(jid_dict) for jid_dict in self._controls.values()) + + def move_to_next_unread_tab(self, forward): + ind = self.notebook.get_current_page() + current = ind + found = False + first_composing_ind = -1 # id of first composing ctrl to switch to + # if no others controls have awaiting events + # loop until finding an unread tab or having done a complete cycle + while True: + if forward == True: # look for the first unread tab on the right + ind = ind + 1 + if ind >= self.notebook.get_n_pages(): + ind = 0 + else: # look for the first unread tab on the right + ind = ind - 1 + if ind < 0: + ind = self.notebook.get_n_pages() - 1 + ctrl = self.get_control(ind, None) + if ctrl.get_nb_unread() > 0: + found = True + break # found + elif gajim.config.get('ctrl_tab_go_to_next_composing') : # Search for a composing contact + contact = ctrl.contact + if first_composing_ind == -1 and contact.chatstate == 'composing': + # If no composing contact found yet, check if this one is composing + first_composing_ind = ind + if ind == current: + break # a complete cycle without finding an unread tab + if found: + self.notebook.set_current_page(ind) + elif first_composing_ind != -1: + self.notebook.set_current_page(first_composing_ind) + else: # not found and nobody composing + if forward: # CTRL + TAB + if current < (self.notebook.get_n_pages() - 1): + self.notebook.next_page() + else: # traverse for ever (eg. don't stop at last tab) + self.notebook.set_current_page(0) + else: # CTRL + SHIFT + TAB + if current > 0: + self.notebook.prev_page() + else: # traverse for ever (eg. don't stop at first tab) + self.notebook.set_current_page( + self.notebook.get_n_pages() - 1) + + def popup_menu(self, event): + menu = self.get_active_control().prepare_context_menu() + # show the menu + menu.popup(None, None, None, event.button, event.time) + menu.show_all() + + def _on_notebook_switch_page(self, notebook, page, page_num): + old_no = notebook.get_current_page() + if old_no >= 0: + old_ctrl = self._widget_to_control(notebook.get_nth_page(old_no)) + old_ctrl.set_control_active(False) + + new_ctrl = self._widget_to_control(notebook.get_nth_page(page_num)) + new_ctrl.set_control_active(True) + self.show_title(control = new_ctrl) + + control = self.get_active_control() + if isinstance(control, ChatControlBase): + control.msg_textview.grab_focus() + + def _on_notebook_key_press(self, widget, event): + # when tab itself is selected, make sure <- and -> are allowed for navigating between tabs + if event.keyval in (gtk.keysyms.Left, gtk.keysyms.Right): + return False + + control = self.get_active_control() + + if event.state & gtk.gdk.SHIFT_MASK: + # CTRL + SHIFT + TAB + if event.state & gtk.gdk.CONTROL_MASK and \ + event.keyval == gtk.keysyms.ISO_Left_Tab: + self.move_to_next_unread_tab(False) + return True + # SHIFT + PAGE_[UP|DOWN]: send to conv_textview + elif event.keyval in (gtk.keysyms.Page_Down, gtk.keysyms.Page_Up): + control.conv_textview.tv.emit('key_press_event', event) + return True + elif event.state & gtk.gdk.CONTROL_MASK: + if event.keyval == gtk.keysyms.Tab: # CTRL + TAB + self.move_to_next_unread_tab(True) + return True + # Ctrl+PageUP / DOWN has to be handled by notebook + elif event.keyval == gtk.keysyms.Page_Down: + self.move_to_next_unread_tab(True) + return True + elif event.keyval == gtk.keysyms.Page_Up: + self.move_to_next_unread_tab(False) + return True + if event.keyval in (gtk.keysyms.Shift_L, gtk.keysyms.Shift_R, + gtk.keysyms.Control_L, gtk.keysyms.Control_R, gtk.keysyms.Caps_Lock, + gtk.keysyms.Shift_Lock, gtk.keysyms.Meta_L, gtk.keysyms.Meta_R, + gtk.keysyms.Alt_L, gtk.keysyms.Alt_R, gtk.keysyms.Super_L, + gtk.keysyms.Super_R, gtk.keysyms.Hyper_L, gtk.keysyms.Hyper_R): + return True + + if isinstance(control, ChatControlBase): + # we forwarded it to message textview + control.msg_textview.emit('key_press_event', event) + control.msg_textview.grab_focus() + + def get_tab_at_xy(self, x, y): + '''Thanks to Gaim + Return the tab under xy and + if its nearer from left or right side of the tab + ''' + page_num = -1 + to_right = False + horiz = self.notebook.get_tab_pos() == gtk.POS_TOP or \ + self.notebook.get_tab_pos() == gtk.POS_BOTTOM + for i in xrange(self.notebook.get_n_pages()): + page = self.notebook.get_nth_page(i) + tab = self.notebook.get_tab_label(page) + tab_alloc = tab.get_allocation() + if horiz: + if (x >= tab_alloc.x) and \ + (x <= (tab_alloc.x + tab_alloc.width)): + page_num = i + if x >= tab_alloc.x + (tab_alloc.width / 2.0): + to_right = True + break + else: + if (y >= tab_alloc.y) and \ + (y <= (tab_alloc.y + tab_alloc.height)): + page_num = i + + if y > tab_alloc.y + (tab_alloc.height / 2.0): + to_right = True + break + return (page_num, to_right) + + def find_page_num_according_to_tab_label(self, tab_label): + '''Find the page num of the tab label''' + page_num = -1 + for i in xrange(self.notebook.get_n_pages()): + page = self.notebook.get_nth_page(i) + tab = self.notebook.get_tab_label(page) + if tab == tab_label: + page_num = i + break + return page_num ################################################################################ class MessageWindowMgr(gobject.GObject): - '''A manager and factory for MessageWindow objects''' - __gsignals__ = { - 'window-delete': (gobject.SIGNAL_RUN_LAST, None, (object,)), - } + '''A manager and factory for MessageWindow objects''' + __gsignals__ = { + 'window-delete': (gobject.SIGNAL_RUN_LAST, None, (object,)), + } - # These constants map to common.config.opt_one_window_types indices - ( - ONE_MSG_WINDOW_NEVER, - ONE_MSG_WINDOW_ALWAYS, - ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER, - ONE_MSG_WINDOW_PERACCT, - ONE_MSG_WINDOW_PERTYPE, - ) = range(5) - # A key constant for the main window in ONE_MSG_WINDOW_ALWAYS mode - MAIN_WIN = 'main' - # A key constant for the main window in ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER mode - ROSTER_MAIN_WIN = 'roster' + # These constants map to common.config.opt_one_window_types indices + ( + ONE_MSG_WINDOW_NEVER, + ONE_MSG_WINDOW_ALWAYS, + ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER, + ONE_MSG_WINDOW_PERACCT, + ONE_MSG_WINDOW_PERTYPE, + ) = range(5) + # A key constant for the main window in ONE_MSG_WINDOW_ALWAYS mode + MAIN_WIN = 'main' + # A key constant for the main window in ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER mode + ROSTER_MAIN_WIN = 'roster' - def __init__(self, parent_window, parent_paned): - ''' A dictionary of windows; the key depends on the config: - ONE_MSG_WINDOW_NEVER: The key is the contact JID - ONE_MSG_WINDOW_ALWAYS: The key is MessageWindowMgr.MAIN_WIN - ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: The key is MessageWindowMgr.MAIN_WIN - ONE_MSG_WINDOW_PERACCT: The key is the account name - ONE_MSG_WINDOW_PERTYPE: The key is a message type constant''' - gobject.GObject.__init__(self) - self._windows = {} + def __init__(self, parent_window, parent_paned): + ''' A dictionary of windows; the key depends on the config: + ONE_MSG_WINDOW_NEVER: The key is the contact JID + ONE_MSG_WINDOW_ALWAYS: The key is MessageWindowMgr.MAIN_WIN + ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: The key is MessageWindowMgr.MAIN_WIN + ONE_MSG_WINDOW_PERACCT: The key is the account name + ONE_MSG_WINDOW_PERTYPE: The key is a message type constant''' + gobject.GObject.__init__(self) + self._windows = {} - # Map the mode to a int constant for frequent compares - mode = gajim.config.get('one_message_window') - self.mode = common.config.opt_one_window_types.index(mode) + # Map the mode to a int constant for frequent compares + mode = gajim.config.get('one_message_window') + self.mode = common.config.opt_one_window_types.index(mode) - self.parent_win = parent_window - self.parent_paned = parent_paned + self.parent_win = parent_window + self.parent_paned = parent_paned - def change_account_name(self, old_name, new_name): - for win in self.windows(): - win.change_account_name(old_name, new_name) + def change_account_name(self, old_name, new_name): + for win in self.windows(): + win.change_account_name(old_name, new_name) - def _new_window(self, acct, type_): - parent_win = None - parent_paned = None - if self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: - parent_win = self.parent_win - parent_paned = self.parent_paned - win = MessageWindow(acct, type_, parent_win, parent_paned) - # we track the lifetime of this window - win.window.connect('delete-event', self._on_window_delete) - win.window.connect('destroy', self._on_window_destroy) - return win + def _new_window(self, acct, type_): + parent_win = None + parent_paned = None + if self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: + parent_win = self.parent_win + parent_paned = self.parent_paned + win = MessageWindow(acct, type_, parent_win, parent_paned) + # we track the lifetime of this window + win.window.connect('delete-event', self._on_window_delete) + win.window.connect('destroy', self._on_window_destroy) + return win - def _gtk_win_to_msg_win(self, gtk_win): - for w in self.windows(): - if w.window == gtk_win: - return w - return None + def _gtk_win_to_msg_win(self, gtk_win): + for w in self.windows(): + if w.window == gtk_win: + return w + return None - def get_window(self, jid, acct): - for win in self.windows(): - if win.has_control(jid, acct): - return win + def get_window(self, jid, acct): + for win in self.windows(): + if win.has_control(jid, acct): + return win - return None + return None - def has_window(self, jid, acct): - return self.get_window(jid, acct) is not None + def has_window(self, jid, acct): + return self.get_window(jid, acct) is not None - def one_window_opened(self, contact=None, acct=None, type_=None): - try: - return \ - self._windows[self._mode_to_key(contact, acct, type_)] is not None - except KeyError: - return False + def one_window_opened(self, contact=None, acct=None, type_=None): + try: + return \ + self._windows[self._mode_to_key(contact, acct, type_)] is not None + except KeyError: + return False - def _resize_window(self, win, acct, type_): - '''Resizes window according to config settings''' - if self.mode in (self.ONE_MSG_WINDOW_ALWAYS, - self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER): - size = (gajim.config.get('msgwin-width'), - gajim.config.get('msgwin-height')) - if self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: - parent_size = win.window.get_size() - # Need to add the size of the now visible paned handle, otherwise - # the saved width of the message window decreases by this amount - handle_size = win.parent_paned.style_get_property('handle-size') - size = (parent_size[0] + size[0] + handle_size, size[1]) - elif self.mode == self.ONE_MSG_WINDOW_PERACCT: - size = (gajim.config.get_per('accounts', acct, 'msgwin-width'), - gajim.config.get_per('accounts', acct, 'msgwin-height')) - elif self.mode in (self.ONE_MSG_WINDOW_NEVER, self.ONE_MSG_WINDOW_PERTYPE): - if type_ == message_control.TYPE_PM: - type_ = message_control.TYPE_CHAT - opt_width = type_ + '-msgwin-width' - opt_height = type_ + '-msgwin-height' - size = (gajim.config.get(opt_width), gajim.config.get(opt_height)) - else: - return - win.resize(size[0], size[1]) - if win.parent_paned: - win.parent_paned.set_position(parent_size[0]) + def _resize_window(self, win, acct, type_): + '''Resizes window according to config settings''' + if self.mode in (self.ONE_MSG_WINDOW_ALWAYS, + self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER): + size = (gajim.config.get('msgwin-width'), + gajim.config.get('msgwin-height')) + if self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: + parent_size = win.window.get_size() + # Need to add the size of the now visible paned handle, otherwise + # the saved width of the message window decreases by this amount + handle_size = win.parent_paned.style_get_property('handle-size') + size = (parent_size[0] + size[0] + handle_size, size[1]) + elif self.mode == self.ONE_MSG_WINDOW_PERACCT: + size = (gajim.config.get_per('accounts', acct, 'msgwin-width'), + gajim.config.get_per('accounts', acct, 'msgwin-height')) + elif self.mode in (self.ONE_MSG_WINDOW_NEVER, self.ONE_MSG_WINDOW_PERTYPE): + if type_ == message_control.TYPE_PM: + type_ = message_control.TYPE_CHAT + opt_width = type_ + '-msgwin-width' + opt_height = type_ + '-msgwin-height' + size = (gajim.config.get(opt_width), gajim.config.get(opt_height)) + else: + return + win.resize(size[0], size[1]) + if win.parent_paned: + win.parent_paned.set_position(parent_size[0]) - def _position_window(self, win, acct, type_): - '''Moves window according to config settings''' - if (self.mode in [self.ONE_MSG_WINDOW_NEVER, - self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER]): - return + def _position_window(self, win, acct, type_): + '''Moves window according to config settings''' + if (self.mode in [self.ONE_MSG_WINDOW_NEVER, + self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER]): + return - if self.mode == self.ONE_MSG_WINDOW_ALWAYS: - pos = (gajim.config.get('msgwin-x-position'), - gajim.config.get('msgwin-y-position')) - elif self.mode == self.ONE_MSG_WINDOW_PERACCT: - pos = (gajim.config.get_per('accounts', acct, 'msgwin-x-position'), - gajim.config.get_per('accounts', acct, 'msgwin-y-position')) - elif self.mode == self.ONE_MSG_WINDOW_PERTYPE: - pos = (gajim.config.get(type_ + '-msgwin-x-position'), - gajim.config.get(type_ + '-msgwin-y-position')) - else: - return + if self.mode == self.ONE_MSG_WINDOW_ALWAYS: + pos = (gajim.config.get('msgwin-x-position'), + gajim.config.get('msgwin-y-position')) + elif self.mode == self.ONE_MSG_WINDOW_PERACCT: + pos = (gajim.config.get_per('accounts', acct, 'msgwin-x-position'), + gajim.config.get_per('accounts', acct, 'msgwin-y-position')) + elif self.mode == self.ONE_MSG_WINDOW_PERTYPE: + pos = (gajim.config.get(type_ + '-msgwin-x-position'), + gajim.config.get(type_ + '-msgwin-y-position')) + else: + return - gtkgui_helpers.move_window(win.window, pos[0], pos[1]) + gtkgui_helpers.move_window(win.window, pos[0], pos[1]) - def _mode_to_key(self, contact, acct, type_, resource = None): - if self.mode == self.ONE_MSG_WINDOW_NEVER: - key = acct + contact.jid - if resource: - key += '/' + resource - return key - elif self.mode == self.ONE_MSG_WINDOW_ALWAYS: - return self.MAIN_WIN - elif self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: - return self.ROSTER_MAIN_WIN - elif self.mode == self.ONE_MSG_WINDOW_PERACCT: - return acct - elif self.mode == self.ONE_MSG_WINDOW_PERTYPE: - return type_ + def _mode_to_key(self, contact, acct, type_, resource = None): + if self.mode == self.ONE_MSG_WINDOW_NEVER: + key = acct + contact.jid + if resource: + key += '/' + resource + return key + elif self.mode == self.ONE_MSG_WINDOW_ALWAYS: + return self.MAIN_WIN + elif self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: + return self.ROSTER_MAIN_WIN + elif self.mode == self.ONE_MSG_WINDOW_PERACCT: + return acct + elif self.mode == self.ONE_MSG_WINDOW_PERTYPE: + return type_ - def create_window(self, contact, acct, type_, resource = None): - win_acct = None - win_type = None - win_role = None # X11 window role + def create_window(self, contact, acct, type_, resource = None): + win_acct = None + win_type = None + win_role = None # X11 window role - win_key = self._mode_to_key(contact, acct, type_, resource) - if self.mode == self.ONE_MSG_WINDOW_PERACCT: - win_acct = acct - win_role = acct - elif self.mode == self.ONE_MSG_WINDOW_PERTYPE: - win_type = type_ - win_role = type_ - elif self.mode == self.ONE_MSG_WINDOW_NEVER: - win_type = type_ - win_role = contact.jid - elif self.mode == self.ONE_MSG_WINDOW_ALWAYS: - win_role = 'messages' + win_key = self._mode_to_key(contact, acct, type_, resource) + if self.mode == self.ONE_MSG_WINDOW_PERACCT: + win_acct = acct + win_role = acct + elif self.mode == self.ONE_MSG_WINDOW_PERTYPE: + win_type = type_ + win_role = type_ + elif self.mode == self.ONE_MSG_WINDOW_NEVER: + win_type = type_ + win_role = contact.jid + elif self.mode == self.ONE_MSG_WINDOW_ALWAYS: + win_role = 'messages' - win = None - try: - win = self._windows[win_key] - except KeyError: - win = self._new_window(win_acct, win_type) + win = None + try: + win = self._windows[win_key] + except KeyError: + win = self._new_window(win_acct, win_type) - if win_role: - win.window.set_role(win_role) + if win_role: + win.window.set_role(win_role) - # Position and size window based on saved state and window mode - if not self.one_window_opened(contact, acct, type_): - if gajim.config.get('msgwin-max-state'): - win.window.maximize() - else: - self._resize_window(win, acct, type_) - self._position_window(win, acct, type_) + # Position and size window based on saved state and window mode + if not self.one_window_opened(contact, acct, type_): + if gajim.config.get('msgwin-max-state'): + win.window.maximize() + else: + self._resize_window(win, acct, type_) + self._position_window(win, acct, type_) - self._windows[win_key] = win - return win + self._windows[win_key] = win + return win - def change_key(self, old_jid, new_jid, acct): - win = self.get_window(old_jid, acct) - if self.mode == self.ONE_MSG_WINDOW_NEVER: - old_key = acct + old_jid - if old_jid not in self._windows: - return - new_key = acct + new_jid - self._windows[new_key] = self._windows[old_key] - del self._windows[old_key] - win.change_key(old_jid, new_jid, acct) + def change_key(self, old_jid, new_jid, acct): + win = self.get_window(old_jid, acct) + if self.mode == self.ONE_MSG_WINDOW_NEVER: + old_key = acct + old_jid + if old_jid not in self._windows: + return + new_key = acct + new_jid + self._windows[new_key] = self._windows[old_key] + del self._windows[old_key] + win.change_key(old_jid, new_jid, acct) - def _on_window_delete(self, win, event): - self.save_state(self._gtk_win_to_msg_win(win)) - gajim.interface.save_config() - return False + def _on_window_delete(self, win, event): + self.save_state(self._gtk_win_to_msg_win(win)) + gajim.interface.save_config() + return False - def _on_window_destroy(self, win): - for k in self._windows.keys(): - if self._windows[k].window == win: - self.emit('window-delete', self._windows[k]) - del self._windows[k] - return + def _on_window_destroy(self, win): + for k in self._windows.keys(): + if self._windows[k].window == win: + self.emit('window-delete', self._windows[k]) + del self._windows[k] + return - def get_control(self, jid, acct): - '''Amongst all windows, return the MessageControl for jid''' - win = self.get_window(jid, acct) - if win: - return win.get_control(jid, acct) - return None + def get_control(self, jid, acct): + '''Amongst all windows, return the MessageControl for jid''' + win = self.get_window(jid, acct) + if win: + return win.get_control(jid, acct) + return None - def get_gc_control(self, jid, acct): - '''Same as get_control. Was briefly required, is not any more. + def get_gc_control(self, jid, acct): + '''Same as get_control. Was briefly required, is not any more. May be useful some day in the future?''' - ctrl = self.get_control(jid, acct) - if ctrl and ctrl.type_id == message_control.TYPE_GC: - return ctrl - return None + ctrl = self.get_control(jid, acct) + if ctrl and ctrl.type_id == message_control.TYPE_GC: + return ctrl + return None - def get_controls(self, type_=None, acct=None): - ctrls = [] - for c in self.controls(): - if acct and c.account != acct: - continue - if not type_ or c.type_id == type_: - ctrls.append(c) - return ctrls + def get_controls(self, type_=None, acct=None): + ctrls = [] + for c in self.controls(): + if acct and c.account != acct: + continue + if not type_ or c.type_id == type_: + ctrls.append(c) + return ctrls - def windows(self): - for w in self._windows.values(): - yield w + def windows(self): + for w in self._windows.values(): + yield w - def controls(self): - for w in self._windows.values(): - for c in w.controls(): - yield c + def controls(self): + for w in self._windows.values(): + for c in w.controls(): + yield c - def shutdown(self, width_adjust=0): - for w in self.windows(): - self.save_state(w, width_adjust) - if not w.parent_paned: - w.window.hide() - w.window.destroy() + def shutdown(self, width_adjust=0): + for w in self.windows(): + self.save_state(w, width_adjust) + if not w.parent_paned: + w.window.hide() + w.window.destroy() - gajim.interface.save_config() + gajim.interface.save_config() - def save_state(self, msg_win, width_adjust=0): - # Save window size and position - max_win_key = 'msgwin-max-state' - pos_x_key = 'msgwin-x-position' - pos_y_key = 'msgwin-y-position' - size_width_key = 'msgwin-width' - size_height_key = 'msgwin-height' + def save_state(self, msg_win, width_adjust=0): + # Save window size and position + max_win_key = 'msgwin-max-state' + pos_x_key = 'msgwin-x-position' + pos_y_key = 'msgwin-y-position' + size_width_key = 'msgwin-width' + size_height_key = 'msgwin-height' - acct = None - x, y = msg_win.window.get_position() - width, height = msg_win.window.get_size() + acct = None + x, y = msg_win.window.get_position() + width, height = msg_win.window.get_size() - # If any of these values seem bogus don't update. - if x < 0 or y < 0 or width < 0 or height < 0: - return + # If any of these values seem bogus don't update. + if x < 0 or y < 0 or width < 0 or height < 0: + return - elif self.mode == self.ONE_MSG_WINDOW_PERACCT: - acct = msg_win.account - elif self.mode == self.ONE_MSG_WINDOW_PERTYPE: - type_ = msg_win.type_ - pos_x_key = type_ + '-msgwin-x-position' - pos_y_key = type_ + '-msgwin-y-position' - size_width_key = type_ + '-msgwin-width' - size_height_key = type_ + '-msgwin-height' - elif self.mode == self.ONE_MSG_WINDOW_NEVER: - type_ = msg_win.type_ - size_width_key = type_ + '-msgwin-width' - size_height_key = type_ + '-msgwin-height' - elif self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: - # Ignore any hpaned width - width = msg_win.notebook.allocation.width + elif self.mode == self.ONE_MSG_WINDOW_PERACCT: + acct = msg_win.account + elif self.mode == self.ONE_MSG_WINDOW_PERTYPE: + type_ = msg_win.type_ + pos_x_key = type_ + '-msgwin-x-position' + pos_y_key = type_ + '-msgwin-y-position' + size_width_key = type_ + '-msgwin-width' + size_height_key = type_ + '-msgwin-height' + elif self.mode == self.ONE_MSG_WINDOW_NEVER: + type_ = msg_win.type_ + size_width_key = type_ + '-msgwin-width' + size_height_key = type_ + '-msgwin-height' + elif self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: + # Ignore any hpaned width + width = msg_win.notebook.allocation.width - if acct: - gajim.config.set_per('accounts', acct, size_width_key, width) - gajim.config.set_per('accounts', acct, size_height_key, height) + if acct: + gajim.config.set_per('accounts', acct, size_width_key, width) + gajim.config.set_per('accounts', acct, size_height_key, height) - if self.mode != self.ONE_MSG_WINDOW_NEVER: - gajim.config.set_per('accounts', acct, pos_x_key, x) - gajim.config.set_per('accounts', acct, pos_y_key, y) + if self.mode != self.ONE_MSG_WINDOW_NEVER: + gajim.config.set_per('accounts', acct, pos_x_key, x) + gajim.config.set_per('accounts', acct, pos_y_key, y) - else: - win_maximized = msg_win.window.window.get_state() == \ - gtk.gdk.WINDOW_STATE_MAXIMIZED - gajim.config.set(max_win_key, win_maximized) - width += width_adjust - gajim.config.set(size_width_key, width) - gajim.config.set(size_height_key, height) + else: + win_maximized = msg_win.window.window.get_state() == \ + gtk.gdk.WINDOW_STATE_MAXIMIZED + gajim.config.set(max_win_key, win_maximized) + width += width_adjust + gajim.config.set(size_width_key, width) + gajim.config.set(size_height_key, height) - if self.mode != self.ONE_MSG_WINDOW_NEVER: - gajim.config.set(pos_x_key, x) - gajim.config.set(pos_y_key, y) + if self.mode != self.ONE_MSG_WINDOW_NEVER: + gajim.config.set(pos_x_key, x) + gajim.config.set(pos_y_key, y) - def reconfig(self): - for w in self.windows(): - self.save_state(w) - gajim.interface.save_config() - mode = gajim.config.get('one_message_window') - if self.mode == common.config.opt_one_window_types.index(mode): - # No change - return - self.mode = common.config.opt_one_window_types.index(mode) + def reconfig(self): + for w in self.windows(): + self.save_state(w) + gajim.interface.save_config() + mode = gajim.config.get('one_message_window') + if self.mode == common.config.opt_one_window_types.index(mode): + # No change + return + self.mode = common.config.opt_one_window_types.index(mode) - controls = [] - for w in self.windows(): - # Note, we are taking care not to hide/delete the roster window when the - # MessageWindow is embedded. - if not w.parent_paned: - w.window.hide() - else: - # Stash current size so it can be restored if the MessageWindow - # is not longer embedded - roster_width = w.parent_paned.get_child1().allocation.width - gajim.config.set('roster_width', roster_width) + controls = [] + for w in self.windows(): + # Note, we are taking care not to hide/delete the roster window when the + # MessageWindow is embedded. + if not w.parent_paned: + w.window.hide() + else: + # Stash current size so it can be restored if the MessageWindow + # is not longer embedded + roster_width = w.parent_paned.get_child1().allocation.width + gajim.config.set('roster_width', roster_width) - while w.notebook.get_n_pages(): - page = w.notebook.get_nth_page(0) - ctrl = w._widget_to_control(page) - w.notebook.remove_page(0) - page.unparent() - controls.append(ctrl) + while w.notebook.get_n_pages(): + page = w.notebook.get_nth_page(0) + ctrl = w._widget_to_control(page) + w.notebook.remove_page(0) + page.unparent() + controls.append(ctrl) - # Must clear _controls to prevent MessageControl.shutdown calls - w._controls = {} - if not w.parent_paned: - w.window.destroy() - else: - # Don't close parent window, just remove the child - child = w.parent_paned.get_child2() - w.parent_paned.remove(child) - gtkgui_helpers.resize_window(w.window, - gajim.config.get('roster_width'), - gajim.config.get('roster_height')) + # Must clear _controls to prevent MessageControl.shutdown calls + w._controls = {} + if not w.parent_paned: + w.window.destroy() + else: + # Don't close parent window, just remove the child + child = w.parent_paned.get_child2() + w.parent_paned.remove(child) + gtkgui_helpers.resize_window(w.window, + gajim.config.get('roster_width'), + gajim.config.get('roster_height')) - self._windows = {} + self._windows = {} - for ctrl in controls: - mw = self.get_window(ctrl.contact.jid, ctrl.account) - if not mw: - mw = self.create_window(ctrl.contact, ctrl.account, - ctrl.type_id) - ctrl.parent_win = mw - mw.new_tab(ctrl) - -# vim: se ts=3: + for ctrl in controls: + mw = self.get_window(ctrl.contact.jid, ctrl.account) + if not mw: + mw = self.create_window(ctrl.contact, ctrl.account, + ctrl.type_id) + ctrl.parent_win = mw + mw.new_tab(ctrl) diff --git a/src/music_track_listener.py b/src/music_track_listener.py index 07105a947..9de01dd02 100644 --- a/src/music_track_listener.py +++ b/src/music_track_listener.py @@ -25,282 +25,280 @@ import gobject if __name__ == '__main__': - # install _() func before importing dbus_support - from common import i18n + # install _() func before importing dbus_support + from common import i18n from common import dbus_support if dbus_support.supported: - import dbus - import dbus.glib + import dbus + import dbus.glib class MusicTrackInfo(object): - __slots__ = ['title', 'album', 'artist', 'duration', 'track_number', - 'paused'] + __slots__ = ['title', 'album', 'artist', 'duration', 'track_number', + 'paused'] class MusicTrackListener(gobject.GObject): - __gsignals__ = { - 'music-track-changed': (gobject.SIGNAL_RUN_LAST, None, (object,)), - } + __gsignals__ = { + 'music-track-changed': (gobject.SIGNAL_RUN_LAST, None, (object,)), + } - _instance = None - @classmethod - def get(cls): - if cls._instance is None: - cls._instance = cls() - return cls._instance + _instance = None + @classmethod + def get(cls): + if cls._instance is None: + cls._instance = cls() + return cls._instance - def __init__(self): - super(MusicTrackListener, self).__init__() - self._last_playing_music = None + def __init__(self): + super(MusicTrackListener, self).__init__() + self._last_playing_music = None - bus = dbus.SessionBus() + bus = dbus.SessionBus() - ## MPRIS - bus.add_signal_receiver(self._mpris_music_track_change_cb, 'TrackChange', - 'org.freedesktop.MediaPlayer') - bus.add_signal_receiver(self._mpris_playing_changed_cb, 'StatusChange', - 'org.freedesktop.MediaPlayer') - bus.add_signal_receiver(self._player_name_owner_changed, - 'NameOwnerChanged', 'org.freedesktop.DBus', - arg0='org.freedesktop.MediaPlayer') + ## MPRIS + bus.add_signal_receiver(self._mpris_music_track_change_cb, 'TrackChange', + 'org.freedesktop.MediaPlayer') + bus.add_signal_receiver(self._mpris_playing_changed_cb, 'StatusChange', + 'org.freedesktop.MediaPlayer') + bus.add_signal_receiver(self._player_name_owner_changed, + 'NameOwnerChanged', 'org.freedesktop.DBus', + arg0='org.freedesktop.MediaPlayer') - ## Muine - bus.add_signal_receiver(self._muine_music_track_change_cb, 'SongChanged', - 'org.gnome.Muine.Player') - bus.add_signal_receiver(self._player_name_owner_changed, - 'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Muine') - bus.add_signal_receiver(self._player_playing_changed_cb, 'StateChanged', - 'org.gnome.Muine.Player') + ## Muine + bus.add_signal_receiver(self._muine_music_track_change_cb, 'SongChanged', + 'org.gnome.Muine.Player') + bus.add_signal_receiver(self._player_name_owner_changed, + 'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Muine') + bus.add_signal_receiver(self._player_playing_changed_cb, 'StateChanged', + 'org.gnome.Muine.Player') - ## Rhythmbox - bus.add_signal_receiver(self._player_name_owner_changed, - 'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Rhythmbox') - bus.add_signal_receiver(self._rhythmbox_playing_changed_cb, - 'playingChanged', 'org.gnome.Rhythmbox.Player') - bus.add_signal_receiver(self._player_playing_song_property_changed_cb, - 'playingSongPropertyChanged', 'org.gnome.Rhythmbox.Player') + ## Rhythmbox + bus.add_signal_receiver(self._player_name_owner_changed, + 'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Rhythmbox') + bus.add_signal_receiver(self._rhythmbox_playing_changed_cb, + 'playingChanged', 'org.gnome.Rhythmbox.Player') + bus.add_signal_receiver(self._player_playing_song_property_changed_cb, + 'playingSongPropertyChanged', 'org.gnome.Rhythmbox.Player') - ## Banshee - bus.add_signal_receiver(self._banshee_state_changed_cb, - 'StateChanged', 'org.bansheeproject.Banshee.PlayerEngine') - bus.add_signal_receiver(self._player_name_owner_changed, - 'NameOwnerChanged', 'org.freedesktop.DBus', - arg0='org.bansheeproject.Banshee') + ## Banshee + bus.add_signal_receiver(self._banshee_state_changed_cb, + 'StateChanged', 'org.bansheeproject.Banshee.PlayerEngine') + bus.add_signal_receiver(self._player_name_owner_changed, + 'NameOwnerChanged', 'org.freedesktop.DBus', + arg0='org.bansheeproject.Banshee') - ## Quod Libet - bus.add_signal_receiver(self._quodlibet_state_change_cb, - 'SongStarted', 'net.sacredchao.QuodLibet') - bus.add_signal_receiver(self._quodlibet_state_change_cb, - 'Paused', 'net.sacredchao.QuodLibet') - bus.add_signal_receiver(self._quodlibet_state_change_cb, - 'Unpaused', 'net.sacredchao.QuodLibet') - bus.add_signal_receiver(self._player_name_owner_changed, - 'NameOwnerChanged', 'org.freedesktop.DBus', - arg0='net.sacredchao.QuodLibet') + ## Quod Libet + bus.add_signal_receiver(self._quodlibet_state_change_cb, + 'SongStarted', 'net.sacredchao.QuodLibet') + bus.add_signal_receiver(self._quodlibet_state_change_cb, + 'Paused', 'net.sacredchao.QuodLibet') + bus.add_signal_receiver(self._quodlibet_state_change_cb, + 'Unpaused', 'net.sacredchao.QuodLibet') + bus.add_signal_receiver(self._player_name_owner_changed, + 'NameOwnerChanged', 'org.freedesktop.DBus', + arg0='net.sacredchao.QuodLibet') - def _player_name_owner_changed(self, name, old, new): - if not new: - self.emit('music-track-changed', None) + def _player_name_owner_changed(self, name, old, new): + if not new: + self.emit('music-track-changed', None) - def _player_playing_changed_cb(self, playing): - if playing: - self.emit('music-track-changed', self._last_playing_music) - else: - self.emit('music-track-changed', None) + def _player_playing_changed_cb(self, playing): + if playing: + self.emit('music-track-changed', self._last_playing_music) + else: + self.emit('music-track-changed', None) - def _player_playing_song_property_changed_cb(self, a, b, c, d): - if b == 'rb:stream-song-title': - self.emit('music-track-changed', self._last_playing_music) + def _player_playing_song_property_changed_cb(self, a, b, c, d): + if b == 'rb:stream-song-title': + self.emit('music-track-changed', self._last_playing_music) - def _mpris_properties_extract(self, song): - info = MusicTrackInfo() - info.title = song.get('title', '') - info.album = song.get('album', '') - info.artist = song.get('artist', '') - info.duration = int(song.get('length', 0)) - return info + def _mpris_properties_extract(self, song): + info = MusicTrackInfo() + info.title = song.get('title', '') + info.album = song.get('album', '') + info.artist = song.get('artist', '') + info.duration = int(song.get('length', 0)) + return info - def _mpris_playing_changed_cb(self, playing): - if type(playing) is dbus.Struct: - if playing[0]: - self.emit('music-track-changed', None) - else: - self.emit('music-track-changed', self._last_playing_music) - else: # Workaround for e.g. Audacious - if playing: - self.emit('music-track-changed', None) - else: - self.emit('music-track-changed', self._last_playing_music) + def _mpris_playing_changed_cb(self, playing): + if type(playing) is dbus.Struct: + if playing[0]: + self.emit('music-track-changed', None) + else: + self.emit('music-track-changed', self._last_playing_music) + else: # Workaround for e.g. Audacious + if playing: + self.emit('music-track-changed', None) + else: + self.emit('music-track-changed', self._last_playing_music) - def _mpris_music_track_change_cb(self, arg): - self._last_playing_music = self._mpris_properties_extract(arg) - self.emit('music-track-changed', self._last_playing_music) + def _mpris_music_track_change_cb(self, arg): + self._last_playing_music = self._mpris_properties_extract(arg) + self.emit('music-track-changed', self._last_playing_music) - def _muine_properties_extract(self, song_string): - d = dict((x.strip() for x in s1.split(':', 1)) for s1 in \ - song_string.split('\n')) - info = MusicTrackInfo() - info.title = d['title'] - info.album = d['album'] - info.artist = d['artist'] - info.duration = int(d['duration']) - info.track_number = int(d['track_number']) - return info + def _muine_properties_extract(self, song_string): + d = dict((x.strip() for x in s1.split(':', 1)) for s1 in \ + song_string.split('\n')) + info = MusicTrackInfo() + info.title = d['title'] + info.album = d['album'] + info.artist = d['artist'] + info.duration = int(d['duration']) + info.track_number = int(d['track_number']) + return info - def _muine_music_track_change_cb(self, arg): - info = self._muine_properties_extract(arg) - self.emit('music-track-changed', info) + def _muine_music_track_change_cb(self, arg): + info = self._muine_properties_extract(arg) + self.emit('music-track-changed', info) - def _rhythmbox_playing_changed_cb(self, playing): - if playing: - info = self.get_playing_track() - self.emit('music-track-changed', info) - else: - self.emit('music-track-changed', None) + def _rhythmbox_playing_changed_cb(self, playing): + if playing: + info = self.get_playing_track() + self.emit('music-track-changed', info) + else: + self.emit('music-track-changed', None) - def _rhythmbox_properties_extract(self, props): - info = MusicTrackInfo() - info.title = props.get('title', None) - info.album = props.get('album', None) - info.artist = props.get('artist', None) - info.duration = int(props.get('duration', 0)) - info.track_number = int(props.get('track-number', 0)) - return info + def _rhythmbox_properties_extract(self, props): + info = MusicTrackInfo() + info.title = props.get('title', None) + info.album = props.get('album', None) + info.artist = props.get('artist', None) + info.duration = int(props.get('duration', 0)) + info.track_number = int(props.get('track-number', 0)) + return info - def _banshee_state_changed_cb(self, state): - if state == 'playing': - bus = dbus.SessionBus() - banshee = bus.get_object('org.bansheeproject.Banshee', - '/org/bansheeproject/Banshee/PlayerEngine') - currentTrack = banshee.GetCurrentTrack() - self._last_playing_music = self._banshee_properties_extract( - currentTrack) - self.emit('music-track-changed', self._last_playing_music) - elif state == 'paused': - self.emit('music-track-changed', None) + def _banshee_state_changed_cb(self, state): + if state == 'playing': + bus = dbus.SessionBus() + banshee = bus.get_object('org.bansheeproject.Banshee', + '/org/bansheeproject/Banshee/PlayerEngine') + currentTrack = banshee.GetCurrentTrack() + self._last_playing_music = self._banshee_properties_extract( + currentTrack) + self.emit('music-track-changed', self._last_playing_music) + elif state == 'paused': + self.emit('music-track-changed', None) - def _banshee_properties_extract(self, props): - info = MusicTrackInfo() - info.title = props.get('name', None) - info.album = props.get('album', None) - info.artist = props.get('artist', None) - info.duration = int(props.get('length', 0)) - return info + def _banshee_properties_extract(self, props): + info = MusicTrackInfo() + info.title = props.get('name', None) + info.album = props.get('album', None) + info.artist = props.get('artist', None) + info.duration = int(props.get('length', 0)) + return info - def _quodlibet_state_change_cb(self, state=None): - info = self.get_playing_track() - if info: - self.emit('music-track-changed', info) - else: - self.emit('music-track-changed', None) + def _quodlibet_state_change_cb(self, state=None): + info = self.get_playing_track() + if info: + self.emit('music-track-changed', info) + else: + self.emit('music-track-changed', None) - def _quodlibet_properties_extract(self, props): - info = MusicTrackInfo() - info.title = props.get('title', None) - info.album = props.get('album', None) - info.artist = props.get('artist', None) - info.duration = int(props.get('~#length', 0)) - return info + def _quodlibet_properties_extract(self, props): + info = MusicTrackInfo() + info.title = props.get('title', None) + info.album = props.get('album', None) + info.artist = props.get('artist', None) + info.duration = int(props.get('~#length', 0)) + return info - def get_playing_track(self): - '''Return a MusicTrackInfo for the currently playing - song, or None if no song is playing''' + def get_playing_track(self): + '''Return a MusicTrackInfo for the currently playing + song, or None if no song is playing''' - bus = dbus.SessionBus() + bus = dbus.SessionBus() - ## Check Muine playing track - test = False - if hasattr(bus, 'name_has_owner'): - if bus.name_has_owner('org.gnome.Muine'): - test = True - elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(), - 'org.gnome.Muine'): - test = True - if test: - obj = bus.get_object('org.gnome.Muine', '/org/gnome/Muine/Player') - player = dbus.Interface(obj, 'org.gnome.Muine.Player') - if player.GetPlaying(): - song_string = player.GetCurrentSong() - song = self._muine_properties_extract(song_string) - self._last_playing_music = song - return song + ## Check Muine playing track + test = False + if hasattr(bus, 'name_has_owner'): + if bus.name_has_owner('org.gnome.Muine'): + test = True + elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(), + 'org.gnome.Muine'): + test = True + if test: + obj = bus.get_object('org.gnome.Muine', '/org/gnome/Muine/Player') + player = dbus.Interface(obj, 'org.gnome.Muine.Player') + if player.GetPlaying(): + song_string = player.GetCurrentSong() + song = self._muine_properties_extract(song_string) + self._last_playing_music = song + return song - ## Check Rhythmbox playing song - test = False - if hasattr(bus, 'name_has_owner'): - if bus.name_has_owner('org.gnome.Rhythmbox'): - test = True - elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(), - 'org.gnome.Rhythmbox'): - test = True - if test: - rbshellobj = bus.get_object('org.gnome.Rhythmbox', - '/org/gnome/Rhythmbox/Shell') - player = dbus.Interface( - bus.get_object('org.gnome.Rhythmbox', - '/org/gnome/Rhythmbox/Player'), 'org.gnome.Rhythmbox.Player') - rbshell = dbus.Interface(rbshellobj, 'org.gnome.Rhythmbox.Shell') - try: - uri = player.getPlayingUri() - except dbus.DBusException: - uri = None - if not uri: - return None - props = rbshell.getSongProperties(uri) - info = self._rhythmbox_properties_extract(props) - self._last_playing_music = info - return info + ## Check Rhythmbox playing song + test = False + if hasattr(bus, 'name_has_owner'): + if bus.name_has_owner('org.gnome.Rhythmbox'): + test = True + elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(), + 'org.gnome.Rhythmbox'): + test = True + if test: + rbshellobj = bus.get_object('org.gnome.Rhythmbox', + '/org/gnome/Rhythmbox/Shell') + player = dbus.Interface( + bus.get_object('org.gnome.Rhythmbox', + '/org/gnome/Rhythmbox/Player'), 'org.gnome.Rhythmbox.Player') + rbshell = dbus.Interface(rbshellobj, 'org.gnome.Rhythmbox.Shell') + try: + uri = player.getPlayingUri() + except dbus.DBusException: + uri = None + if not uri: + return None + props = rbshell.getSongProperties(uri) + info = self._rhythmbox_properties_extract(props) + self._last_playing_music = info + return info - ## Check Banshee playing track - test = False - if hasattr(bus, 'name_has_owner'): - if bus.name_has_owner('org.bansheeproject.Banshee'): - test = True - elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(), - 'org.bansheeproject.Banshee'): - test = True - if test: - banshee = bus.get_object('org.bansheeproject.Banshee', - '/org/bansheeproject/Banshee/PlayerEngine') - currentTrack = banshee.GetCurrentTrack() - if currentTrack: - song = self._banshee_properties_extract(currentTrack) - self._last_playing_music = song - return song + ## Check Banshee playing track + test = False + if hasattr(bus, 'name_has_owner'): + if bus.name_has_owner('org.bansheeproject.Banshee'): + test = True + elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(), + 'org.bansheeproject.Banshee'): + test = True + if test: + banshee = bus.get_object('org.bansheeproject.Banshee', + '/org/bansheeproject/Banshee/PlayerEngine') + currentTrack = banshee.GetCurrentTrack() + if currentTrack: + song = self._banshee_properties_extract(currentTrack) + self._last_playing_music = song + return song - ## Check Quod Libet playing track - test = False - if hasattr(bus, 'name_has_owner'): - if bus.name_has_owner('net.sacredchao.QuodLibet'): - test = True - elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(), - 'net.sacredchao.QuodLibet'): - test = True - if test: - quodlibet = bus.get_object('net.sacredchao.QuodLibet', - '/net/sacredchao/QuodLibet') - if quodlibet.IsPlaying(): - currentTrack = quodlibet.CurrentSong() - song = self._quodlibet_properties_extract(currentTrack) - self._last_playing_music = song - return song + ## Check Quod Libet playing track + test = False + if hasattr(bus, 'name_has_owner'): + if bus.name_has_owner('net.sacredchao.QuodLibet'): + test = True + elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(), + 'net.sacredchao.QuodLibet'): + test = True + if test: + quodlibet = bus.get_object('net.sacredchao.QuodLibet', + '/net/sacredchao/QuodLibet') + if quodlibet.IsPlaying(): + currentTrack = quodlibet.CurrentSong() + song = self._quodlibet_properties_extract(currentTrack) + self._last_playing_music = song + return song - return None + return None # here we test :) if __name__ == '__main__': - def music_track_change_cb(listener, music_track_info): - if music_track_info is None: - print 'Stop!' - else: - print music_track_info.title - listener = MusicTrackListener.get() - listener.connect('music-track-changed', music_track_change_cb) - track = listener.get_playing_track() - if track is None: - print 'Now not playing anything' - else: - print 'Now playing: "%s" by %s' % (track.title, track.artist) - gobject.MainLoop().run() - -# vim: se ts=3: + def music_track_change_cb(listener, music_track_info): + if music_track_info is None: + print 'Stop!' + else: + print music_track_info.title + listener = MusicTrackListener.get() + listener.connect('music-track-changed', music_track_change_cb) + track = listener.get_playing_track() + if track is None: + print 'Now not playing anything' + else: + print 'Now playing: "%s" by %s' % (track.title, track.artist) + gobject.MainLoop().run() diff --git a/src/negotiation.py b/src/negotiation.py index bd5a32123..96642a6d5 100644 --- a/src/negotiation.py +++ b/src/negotiation.py @@ -27,61 +27,59 @@ from common import gajim from common import xmpp def describe_features(features): - '''a human-readable description of the features that have been negotiated''' - if features['logging'] == 'may': - return _('- messages will be logged') - elif features['logging'] == 'mustnot': - return _('- messages will not be logged') + '''a human-readable description of the features that have been negotiated''' + if features['logging'] == 'may': + return _('- messages will be logged') + elif features['logging'] == 'mustnot': + return _('- messages will not be logged') class FeatureNegotiationWindow: - '''FeatureNegotiotionWindow class''' - def __init__(self, account, jid, session, form): - self.account = account - self.jid = jid - self.form = form - self.session = session + '''FeatureNegotiotionWindow class''' + def __init__(self, account, jid, session, form): + self.account = account + self.jid = jid + self.form = form + self.session = session - self.xml = gtkgui_helpers.get_glade('data_form_window.glade', 'data_form_window') - self.window = self.xml.get_widget('data_form_window') + self.xml = gtkgui_helpers.get_glade('data_form_window.glade', 'data_form_window') + self.window = self.xml.get_widget('data_form_window') - config_vbox = self.xml.get_widget('config_vbox') - dataform = dataforms.ExtendForm(node = self.form) - self.data_form_widget = dataforms_widget.DataFormWidget(dataform) - self.data_form_widget.show() - config_vbox.pack_start(self.data_form_widget) + config_vbox = self.xml.get_widget('config_vbox') + dataform = dataforms.ExtendForm(node = self.form) + self.data_form_widget = dataforms_widget.DataFormWidget(dataform) + self.data_form_widget.show() + config_vbox.pack_start(self.data_form_widget) - self.xml.signal_autoconnect(self) - self.window.show_all() + self.xml.signal_autoconnect(self) + self.window.show_all() - def on_ok_button_clicked(self, widget): - acceptance = xmpp.Message(self.jid) - acceptance.setThread(self.session.thread_id) - feature = acceptance.NT.feature - feature.setNamespace(xmpp.NS_FEATURE) + def on_ok_button_clicked(self, widget): + acceptance = xmpp.Message(self.jid) + acceptance.setThread(self.session.thread_id) + feature = acceptance.NT.feature + feature.setNamespace(xmpp.NS_FEATURE) - form = self.data_form_widget.data_form - form.setAttr('type', 'submit') + form = self.data_form_widget.data_form + form.setAttr('type', 'submit') - feature.addChild(node=form) + feature.addChild(node=form) - gajim.connections[self.account].send_stanza(acceptance) + gajim.connections[self.account].send_stanza(acceptance) - self.window.destroy() + self.window.destroy() - def on_cancel_button_clicked(self, widget): - rejection = xmpp.Message(self.jid) - rejection.setThread(self.session.thread_id) - feature = rejection.NT.feature - feature.setNamespace(xmpp.NS_FEATURE) + def on_cancel_button_clicked(self, widget): + rejection = xmpp.Message(self.jid) + rejection.setThread(self.session.thread_id) + feature = rejection.NT.feature + feature.setNamespace(xmpp.NS_FEATURE) - x = xmpp.DataForm(typ='submit') - x.addChild(node=xmpp.DataField('FORM_TYPE', value='urn:xmpp:ssn')) - x.addChild(node=xmpp.DataField('accept', value='false', typ='boolean')) + x = xmpp.DataForm(typ='submit') + x.addChild(node=xmpp.DataField('FORM_TYPE', value='urn:xmpp:ssn')) + x.addChild(node=xmpp.DataField('accept', value='false', typ='boolean')) - feature.addChild(node=x) + feature.addChild(node=x) - gajim.connections[self.account].send_stanza(rejection) + gajim.connections[self.account].send_stanza(rejection) - self.window.destroy() - -# vim: se ts=3: + self.window.destroy() diff --git a/src/network_manager_listener.py b/src/network_manager_listener.py index 15a40bbf6..94ca1fd76 100644 --- a/src/network_manager_listener.py +++ b/src/network_manager_listener.py @@ -26,73 +26,71 @@ from common import gajim def device_now_active(self, *args): - '''For Network Manager 0.6''' - for connection in gajim.connections.itervalues(): - if gajim.config.get_per('accounts', connection.name, - 'listen_to_network_manager') and connection.time_to_reconnect: - connection._reconnect() + '''For Network Manager 0.6''' + for connection in gajim.connections.itervalues(): + if gajim.config.get_per('accounts', connection.name, + 'listen_to_network_manager') and connection.time_to_reconnect: + connection._reconnect() def device_no_longer_active(self, *args): - '''For Network Manager 0.6''' - for connection in gajim.connections.itervalues(): - if gajim.config.get_per('accounts', connection.name, - 'listen_to_network_manager') and connection.connected > 1: - connection._disconnectedReconnCB() + '''For Network Manager 0.6''' + for connection in gajim.connections.itervalues(): + if gajim.config.get_per('accounts', connection.name, + 'listen_to_network_manager') and connection.connected > 1: + connection._disconnectedReconnCB() def state_changed(state): - '''For Network Manager 0.7''' - if props.Get("org.freedesktop.NetworkManager", "State") == 3: - for connection in gajim.connections.itervalues(): - if gajim.config.get_per('accounts', connection.name, - 'listen_to_network_manager') and connection.time_to_reconnect: - connection._reconnect() - else: - for connection in gajim.connections.itervalues(): - if gajim.config.get_per('accounts', connection.name, - 'listen_to_network_manager') and connection.connected > 1: - connection._disconnectedReconnCB() + '''For Network Manager 0.7''' + if props.Get("org.freedesktop.NetworkManager", "State") == 3: + for connection in gajim.connections.itervalues(): + if gajim.config.get_per('accounts', connection.name, + 'listen_to_network_manager') and connection.time_to_reconnect: + connection._reconnect() + else: + for connection in gajim.connections.itervalues(): + if gajim.config.get_per('accounts', connection.name, + 'listen_to_network_manager') and connection.connected > 1: + connection._disconnectedReconnCB() supported = False from common import dbus_support if dbus_support.supported: - import dbus - import dbus.glib + import dbus + import dbus.glib - try: - from common.dbus_support import system_bus + try: + from common.dbus_support import system_bus - bus = system_bus.bus() + bus = system_bus.bus() - if 'org.freedesktop.NetworkManager' in bus.list_names(): - nm_object = bus.get_object('org.freedesktop.NetworkManager', - '/org/freedesktop/NetworkManager') - props = dbus.Interface(nm_object,"org.freedesktop.DBus.Properties") - bus.add_signal_receiver(state_changed, - 'StateChanged', - 'org.freedesktop.NetworkManager', - 'org.freedesktop.NetworkManager', - '/org/freedesktop/NetworkManager') - supported = True + if 'org.freedesktop.NetworkManager' in bus.list_names(): + nm_object = bus.get_object('org.freedesktop.NetworkManager', + '/org/freedesktop/NetworkManager') + props = dbus.Interface(nm_object, "org.freedesktop.DBus.Properties") + bus.add_signal_receiver(state_changed, + 'StateChanged', + 'org.freedesktop.NetworkManager', + 'org.freedesktop.NetworkManager', + '/org/freedesktop/NetworkManager') + supported = True - except dbus.DBusException: - try: - if 'org.freedesktop.NetworkManager' in bus.list_names(): - supported = True + except dbus.DBusException: + try: + if 'org.freedesktop.NetworkManager' in bus.list_names(): + supported = True - bus.add_signal_receiver(device_no_longer_active, - 'DeviceNoLongerActive', - 'org.freedesktop.NetworkManager', - 'org.freedesktop.NetworkManager', - '/org/freedesktop/NetworkManager') + bus.add_signal_receiver(device_no_longer_active, + 'DeviceNoLongerActive', + 'org.freedesktop.NetworkManager', + 'org.freedesktop.NetworkManager', + '/org/freedesktop/NetworkManager') - bus.add_signal_receiver(device_now_active, - 'DeviceNowActive', - 'org.freedesktop.NetworkManager', - 'org.freedesktop.NetworkManager', - '/org/freedesktop/NetworkManager') - except Exception: - pass - -# vim: se ts=3: + bus.add_signal_receiver(device_now_active, + 'DeviceNowActive', + 'org.freedesktop.NetworkManager', + 'org.freedesktop.NetworkManager', + '/org/freedesktop/NetworkManager') + except Exception: + pass diff --git a/src/notify.py b/src/notify.py index 3d9eb6051..8b80dff98 100644 --- a/src/notify.py +++ b/src/notify.py @@ -39,639 +39,637 @@ from common import helpers from common import dbus_support if dbus_support.supported: - import dbus - import dbus.glib + import dbus + import dbus.glib USER_HAS_PYNOTIFY = True # user has pynotify module try: - import pynotify - pynotify.init('Gajim Notification') + import pynotify + pynotify.init('Gajim Notification') except ImportError: - USER_HAS_PYNOTIFY = False + USER_HAS_PYNOTIFY = False if gajim.HAVE_INDICATOR: - import indicate + import indicate def setup_indicator_server(): - server = indicate.indicate_server_ref_default() - server.set_type('message.im') - server.set_desktop_file('/usr/share/applications/gajim.desktop') - server.connect('server-display', server_display) - server.show() + server = indicate.indicate_server_ref_default() + server.set_type('message.im') + server.set_desktop_file('/usr/share/applications/gajim.desktop') + server.connect('server-display', server_display) + server.show() def display(indicator, account, jid, msg_type): - gajim.interface.handle_event(account, jid, msg_type) - indicator.hide() + gajim.interface.handle_event(account, jid, msg_type) + indicator.hide() def server_display(server): - win = gajim.interface.roster.window - win.present() + win = gajim.interface.roster.window + win.present() def get_show_in_roster(event, account, contact, session=None): - '''Return True if this event must be shown in roster, else False''' - if event == 'gc_message_received': - return True - num = get_advanced_notification(event, account, contact) - if num is not None: - if gajim.config.get_per('notifications', str(num), 'roster') == 'yes': - return True - if gajim.config.get_per('notifications', str(num), 'roster') == 'no': - return False - if event == 'message_received': - if session and session.control: - return False - return True + '''Return True if this event must be shown in roster, else False''' + if event == 'gc_message_received': + return True + num = get_advanced_notification(event, account, contact) + if num is not None: + if gajim.config.get_per('notifications', str(num), 'roster') == 'yes': + return True + if gajim.config.get_per('notifications', str(num), 'roster') == 'no': + return False + if event == 'message_received': + if session and session.control: + return False + return True def get_show_in_systray(event, account, contact, type_=None): - '''Return True if this event must be shown in systray, else False''' - num = get_advanced_notification(event, account, contact) - if num is not None: - if gajim.config.get_per('notifications', str(num), 'systray') == 'yes': - return True - if gajim.config.get_per('notifications', str(num), 'systray') == 'no': - return False - if type_ == 'printed_gc_msg' and not gajim.config.get( - 'notify_on_all_muc_messages'): - # it's not an highlighted message, don't show in systray - return False - return gajim.config.get('trayicon_notification_on_events') + '''Return True if this event must be shown in systray, else False''' + num = get_advanced_notification(event, account, contact) + if num is not None: + if gajim.config.get_per('notifications', str(num), 'systray') == 'yes': + return True + if gajim.config.get_per('notifications', str(num), 'systray') == 'no': + return False + if type_ == 'printed_gc_msg' and not gajim.config.get( + 'notify_on_all_muc_messages'): + # it's not an highlighted message, don't show in systray + return False + return gajim.config.get('trayicon_notification_on_events') def get_advanced_notification(event, account, contact): - '''Returns the number of the first (top most) - advanced notification else None''' - num = 0 - notif = gajim.config.get_per('notifications', str(num)) - while notif: - recipient_ok = False - status_ok = False - tab_opened_ok = False - # test event - if gajim.config.get_per('notifications', str(num), 'event') == event: - # test recipient - recipient_type = gajim.config.get_per('notifications', str(num), - 'recipient_type') - recipients = gajim.config.get_per('notifications', str(num), - 'recipients').split() - if recipient_type == 'all': - recipient_ok = True - elif recipient_type == 'contact' and contact.jid in recipients: - recipient_ok = True - elif recipient_type == 'group': - for group in contact.groups: - if group in contact.groups: - recipient_ok = True - break - if recipient_ok: - # test status - our_status = gajim.SHOW_LIST[gajim.connections[account].connected] - status = gajim.config.get_per('notifications', str(num), 'status') - if status == 'all' or our_status in status.split(): - status_ok = True - if status_ok: - # test window_opened - tab_opened = gajim.config.get_per('notifications', str(num), - 'tab_opened') - if tab_opened == 'both': - tab_opened_ok = True - else: - chat_control = helpers.get_chat_control(account, contact) - if (chat_control and tab_opened == 'yes') or (not chat_control and \ - tab_opened == 'no'): - tab_opened_ok = True - if tab_opened_ok: - return num + '''Returns the number of the first (top most) + advanced notification else None''' + num = 0 + notif = gajim.config.get_per('notifications', str(num)) + while notif: + recipient_ok = False + status_ok = False + tab_opened_ok = False + # test event + if gajim.config.get_per('notifications', str(num), 'event') == event: + # test recipient + recipient_type = gajim.config.get_per('notifications', str(num), + 'recipient_type') + recipients = gajim.config.get_per('notifications', str(num), + 'recipients').split() + if recipient_type == 'all': + recipient_ok = True + elif recipient_type == 'contact' and contact.jid in recipients: + recipient_ok = True + elif recipient_type == 'group': + for group in contact.groups: + if group in contact.groups: + recipient_ok = True + break + if recipient_ok: + # test status + our_status = gajim.SHOW_LIST[gajim.connections[account].connected] + status = gajim.config.get_per('notifications', str(num), 'status') + if status == 'all' or our_status in status.split(): + status_ok = True + if status_ok: + # test window_opened + tab_opened = gajim.config.get_per('notifications', str(num), + 'tab_opened') + if tab_opened == 'both': + tab_opened_ok = True + else: + chat_control = helpers.get_chat_control(account, contact) + if (chat_control and tab_opened == 'yes') or (not chat_control and \ + tab_opened == 'no'): + tab_opened_ok = True + if tab_opened_ok: + return num - num += 1 - notif = gajim.config.get_per('notifications', str(num)) + num += 1 + notif = gajim.config.get_per('notifications', str(num)) def notify(event, jid, account, parameters, advanced_notif_num=None): - '''Check what type of notifications we want, depending on basic - and the advanced configuration of notifications and do these notifications; - advanced_notif_num holds the number of the first (top most) advanced - notification''' - # First, find what notifications we want - do_popup = False - do_sound = False - do_cmd = False - if event == 'status_change': - new_show = parameters[0] - status_message = parameters[1] - # Default: No popup for status change - elif event == 'contact_connected': - status_message = parameters - j = gajim.get_jid_without_resource(jid) - server = gajim.get_server_from_jid(j) - account_server = account + '/' + server - block_transport = False - if account_server in gajim.block_signed_in_notifications and \ - gajim.block_signed_in_notifications[account_server]: - block_transport = True - if helpers.allow_showing_notification(account, 'notify_on_signin') and \ - not gajim.block_signed_in_notifications[account] and not block_transport: - do_popup = True - if gajim.config.get_per('soundevents', 'contact_connected', - 'enabled') and not gajim.block_signed_in_notifications[account] and \ - not block_transport: - do_sound = True - elif event == 'contact_disconnected': - status_message = parameters - if helpers.allow_showing_notification(account, 'notify_on_signout'): - do_popup = True - if gajim.config.get_per('soundevents', 'contact_disconnected', - 'enabled'): - do_sound = True - elif event == 'new_message': - message_type = parameters[0] - is_first_message = parameters[1] - nickname = parameters[2] - if gajim.config.get('notification_preview_message'): - message = parameters[3] - if message.startswith('/me ') or message.startswith('/me\n'): - message = '* ' + nickname + message[3:] - else: - # We don't want message preview, do_preview = False - message = '' - focused = parameters[4] - if helpers.allow_showing_notification(account, 'notify_on_new_message', - advanced_notif_num, is_first_message): - do_popup = True - if is_first_message and helpers.allow_sound_notification(account, - 'first_message_received', advanced_notif_num): - do_sound = True - elif not is_first_message and focused and \ - helpers.allow_sound_notification(account, 'next_message_received_focused', - advanced_notif_num): - do_sound = True - elif not is_first_message and not focused and \ - helpers.allow_sound_notification(account, - 'next_message_received_unfocused', advanced_notif_num): - do_sound = True - else: - print '*Event not implemeted yet*' + '''Check what type of notifications we want, depending on basic + and the advanced configuration of notifications and do these notifications; + advanced_notif_num holds the number of the first (top most) advanced + notification''' + # First, find what notifications we want + do_popup = False + do_sound = False + do_cmd = False + if event == 'status_change': + new_show = parameters[0] + status_message = parameters[1] + # Default: No popup for status change + elif event == 'contact_connected': + status_message = parameters + j = gajim.get_jid_without_resource(jid) + server = gajim.get_server_from_jid(j) + account_server = account + '/' + server + block_transport = False + if account_server in gajim.block_signed_in_notifications and \ + gajim.block_signed_in_notifications[account_server]: + block_transport = True + if helpers.allow_showing_notification(account, 'notify_on_signin') and \ + not gajim.block_signed_in_notifications[account] and not block_transport: + do_popup = True + if gajim.config.get_per('soundevents', 'contact_connected', + 'enabled') and not gajim.block_signed_in_notifications[account] and \ + not block_transport: + do_sound = True + elif event == 'contact_disconnected': + status_message = parameters + if helpers.allow_showing_notification(account, 'notify_on_signout'): + do_popup = True + if gajim.config.get_per('soundevents', 'contact_disconnected', + 'enabled'): + do_sound = True + elif event == 'new_message': + message_type = parameters[0] + is_first_message = parameters[1] + nickname = parameters[2] + if gajim.config.get('notification_preview_message'): + message = parameters[3] + if message.startswith('/me ') or message.startswith('/me\n'): + message = '* ' + nickname + message[3:] + else: + # We don't want message preview, do_preview = False + message = '' + focused = parameters[4] + if helpers.allow_showing_notification(account, 'notify_on_new_message', + advanced_notif_num, is_first_message): + do_popup = True + if is_first_message and helpers.allow_sound_notification(account, + 'first_message_received', advanced_notif_num): + do_sound = True + elif not is_first_message and focused and \ + helpers.allow_sound_notification(account, 'next_message_received_focused', + advanced_notif_num): + do_sound = True + elif not is_first_message and not focused and \ + helpers.allow_sound_notification(account, + 'next_message_received_unfocused', advanced_notif_num): + do_sound = True + else: + print '*Event not implemeted yet*' - if advanced_notif_num is not None and gajim.config.get_per('notifications', - str(advanced_notif_num), 'run_command'): - do_cmd = True + if advanced_notif_num is not None and gajim.config.get_per('notifications', + str(advanced_notif_num), 'run_command'): + do_cmd = True - # Do the wanted notifications - if do_popup: - if event in ('contact_connected', 'contact_disconnected', - 'status_change'): # Common code for popup for these three events - if event == 'contact_disconnected': - show_image = 'offline.png' - suffix = '_notif_size_bw' - else: #Status Change or Connected - # FIXME: for status change, - # we don't always 'online.png', but we - # first need 48x48 for all status - show_image = 'online.png' - suffix = '_notif_size_colored' - transport_name = gajim.get_transport_name_from_jid(jid) - img = None - if transport_name: - img = os.path.join(helpers.get_transport_path(transport_name), - '48x48', show_image) - if not img or not os.path.isfile(img): - iconset = gajim.config.get('iconset') - img = os.path.join(helpers.get_iconset_path(iconset), '48x48', - show_image) - path = gtkgui_helpers.get_path_to_generic_or_avatar(img, - jid = jid, suffix = suffix) - if event == 'status_change': - title = _('%(nick)s Changed Status') % \ - {'nick': gajim.get_name_from_jid(account, jid)} - text = _('%(nick)s is now %(status)s') % \ - {'nick': gajim.get_name_from_jid(account, jid),\ - 'status': helpers.get_uf_show(gajim.SHOW_LIST[new_show])} - if status_message: - text = text + " : " + status_message - popup(_('Contact Changed Status'), jid, account, - path_to_image=path, title=title, text=text) - elif event == 'contact_connected': - title = _('%(nickname)s Signed In') % \ - {'nickname': gajim.get_name_from_jid(account, jid)} - text = '' - if status_message: - text = status_message - popup(_('Contact Signed In'), jid, account, - path_to_image=path, title=title, text=text) - elif event == 'contact_disconnected': - title = _('%(nickname)s Signed Out') % \ - {'nickname': gajim.get_name_from_jid(account, jid)} - text = '' - if status_message: - text = status_message - popup(_('Contact Signed Out'), jid, account, - path_to_image=path, title=title, text=text) - elif event == 'new_message': - if message_type == 'normal': # single message - event_type = _('New Single Message') - img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'single_msg_recv.png') - title = _('New Single Message from %(nickname)s') % \ - {'nickname': nickname} - text = message - elif message_type == 'pm': # private message - event_type = _('New Private Message') - room_name = gajim.get_nick_from_jid(jid) - img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'priv_msg_recv.png') - title = _('New Private Message from group chat %s') % room_name - if message: - text = _('%(nickname)s: %(message)s') % {'nickname': nickname, - 'message': message} - else: - text = _('Messaged by %(nickname)s') % {'nickname': nickname} + # Do the wanted notifications + if do_popup: + if event in ('contact_connected', 'contact_disconnected', + 'status_change'): # Common code for popup for these three events + if event == 'contact_disconnected': + show_image = 'offline.png' + suffix = '_notif_size_bw' + else: #Status Change or Connected + # FIXME: for status change, + # we don't always 'online.png', but we + # first need 48x48 for all status + show_image = 'online.png' + suffix = '_notif_size_colored' + transport_name = gajim.get_transport_name_from_jid(jid) + img = None + if transport_name: + img = os.path.join(helpers.get_transport_path(transport_name), + '48x48', show_image) + if not img or not os.path.isfile(img): + iconset = gajim.config.get('iconset') + img = os.path.join(helpers.get_iconset_path(iconset), '48x48', + show_image) + path = gtkgui_helpers.get_path_to_generic_or_avatar(img, + jid = jid, suffix = suffix) + if event == 'status_change': + title = _('%(nick)s Changed Status') % \ + {'nick': gajim.get_name_from_jid(account, jid)} + text = _('%(nick)s is now %(status)s') % \ + {'nick': gajim.get_name_from_jid(account, jid),\ + 'status': helpers.get_uf_show(gajim.SHOW_LIST[new_show])} + if status_message: + text = text + " : " + status_message + popup(_('Contact Changed Status'), jid, account, + path_to_image=path, title=title, text=text) + elif event == 'contact_connected': + title = _('%(nickname)s Signed In') % \ + {'nickname': gajim.get_name_from_jid(account, jid)} + text = '' + if status_message: + text = status_message + popup(_('Contact Signed In'), jid, account, + path_to_image=path, title=title, text=text) + elif event == 'contact_disconnected': + title = _('%(nickname)s Signed Out') % \ + {'nickname': gajim.get_name_from_jid(account, jid)} + text = '' + if status_message: + text = status_message + popup(_('Contact Signed Out'), jid, account, + path_to_image=path, title=title, text=text) + elif event == 'new_message': + if message_type == 'normal': # single message + event_type = _('New Single Message') + img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', + 'single_msg_recv.png') + title = _('New Single Message from %(nickname)s') % \ + {'nickname': nickname} + text = message + elif message_type == 'pm': # private message + event_type = _('New Private Message') + room_name = gajim.get_nick_from_jid(jid) + img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', + 'priv_msg_recv.png') + title = _('New Private Message from group chat %s') % room_name + if message: + text = _('%(nickname)s: %(message)s') % {'nickname': nickname, + 'message': message} + else: + text = _('Messaged by %(nickname)s') % {'nickname': nickname} - else: # chat message - event_type = _('New Message') - img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'chat_msg_recv.png') - title = _('New Message from %(nickname)s') % \ - {'nickname': nickname} - text = message - path = gtkgui_helpers.get_path_to_generic_or_avatar(img) - popup(event_type, jid, account, message_type, - path_to_image=path, title=title, text=text) + else: # chat message + event_type = _('New Message') + img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', + 'chat_msg_recv.png') + title = _('New Message from %(nickname)s') % \ + {'nickname': nickname} + text = message + path = gtkgui_helpers.get_path_to_generic_or_avatar(img) + popup(event_type, jid, account, message_type, + path_to_image=path, title=title, text=text) - if do_sound: - snd_file = None - snd_event = None # If not snd_file, play the event - if event == 'new_message': - if advanced_notif_num is not None and gajim.config.get_per( - 'notifications', str(advanced_notif_num), 'sound') == 'yes': - snd_file = gajim.config.get_per('notifications', - str(advanced_notif_num), 'sound_file') - elif advanced_notif_num is not None and gajim.config.get_per( - 'notifications', str(advanced_notif_num), 'sound') == 'no': - pass # do not set snd_event - elif is_first_message: - snd_event = 'first_message_received' - elif focused: - snd_event = 'next_message_received_focused' - else: - snd_event = 'next_message_received_unfocused' - elif event in ('contact_connected', 'contact_disconnected'): - snd_event = event - if snd_file: - helpers.play_sound_file(snd_file) - if snd_event: - helpers.play_sound(snd_event) + if do_sound: + snd_file = None + snd_event = None # If not snd_file, play the event + if event == 'new_message': + if advanced_notif_num is not None and gajim.config.get_per( + 'notifications', str(advanced_notif_num), 'sound') == 'yes': + snd_file = gajim.config.get_per('notifications', + str(advanced_notif_num), 'sound_file') + elif advanced_notif_num is not None and gajim.config.get_per( + 'notifications', str(advanced_notif_num), 'sound') == 'no': + pass # do not set snd_event + elif is_first_message: + snd_event = 'first_message_received' + elif focused: + snd_event = 'next_message_received_focused' + else: + snd_event = 'next_message_received_unfocused' + elif event in ('contact_connected', 'contact_disconnected'): + snd_event = event + if snd_file: + helpers.play_sound_file(snd_file) + if snd_event: + helpers.play_sound(snd_event) - if do_cmd: - command = gajim.config.get_per('notifications', str(advanced_notif_num), - 'command') - try: - helpers.exec_command(command) - except Exception: - pass + if do_cmd: + command = gajim.config.get_per('notifications', str(advanced_notif_num), + 'command') + try: + helpers.exec_command(command) + except Exception: + pass def popup(event_type, jid, account, msg_type='', path_to_image=None, - title=None, text=None): - '''Notifies a user of an event. It first tries to a valid implementation of - the Desktop Notification Specification. If that fails, then we fall back to - the older style PopupNotificationWindow method.''' + title=None, text=None): + '''Notifies a user of an event. It first tries to a valid implementation of + the Desktop Notification Specification. If that fails, then we fall back to + the older style PopupNotificationWindow method.''' - # default image - if not path_to_image: - path_to_image = os.path.abspath( - os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'chat_msg_recv.png')) # img to display + # default image + if not path_to_image: + path_to_image = os.path.abspath( + os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', + 'chat_msg_recv.png')) # img to display - if gajim.HAVE_INDICATOR and event_type in (_('New Message'), - _('New Single Message'), _('New Private Message')): - indicator = indicate.Indicator() - indicator.set_property('subtype', 'im') - indicator.set_property('sender', jid) - indicator.set_property('body', text) - indicator.set_property_time('time', time.time()) - pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_image) - indicator.set_property_icon('icon', pixbuf) - indicator.connect('user-display', display, account, jid, msg_type) - indicator.show() + if gajim.HAVE_INDICATOR and event_type in (_('New Message'), + _('New Single Message'), _('New Private Message')): + indicator = indicate.Indicator() + indicator.set_property('subtype', 'im') + indicator.set_property('sender', jid) + indicator.set_property('body', text) + indicator.set_property_time('time', time.time()) + pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_image) + indicator.set_property_icon('icon', pixbuf) + indicator.connect('user-display', display, account, jid, msg_type) + indicator.show() - # Try to show our popup via D-Bus and notification daemon - if gajim.config.get('use_notif_daemon') and dbus_support.supported: - try: - DesktopNotification(event_type, jid, account, msg_type, - path_to_image, title, gobject.markup_escape_text(text)) - return # sucessfully did D-Bus Notification procedure! - except dbus.DBusException, e: - # Connection to D-Bus failed - gajim.log.debug(str(e)) - except TypeError, e: - # This means that we sent the message incorrectly - gajim.log.debug(str(e)) + # Try to show our popup via D-Bus and notification daemon + if gajim.config.get('use_notif_daemon') and dbus_support.supported: + try: + DesktopNotification(event_type, jid, account, msg_type, + path_to_image, title, gobject.markup_escape_text(text)) + return # sucessfully did D-Bus Notification procedure! + except dbus.DBusException, e: + # Connection to D-Bus failed + gajim.log.debug(str(e)) + except TypeError, e: + # This means that we sent the message incorrectly + gajim.log.debug(str(e)) - # Ok, that failed. Let's try pynotify, which also uses notification daemon - if gajim.config.get('use_notif_daemon') and USER_HAS_PYNOTIFY: - if not text and event_type == 'new_message': - # empty text for new_message means do_preview = False - # -> default value for text - _text = gobject.markup_escape_text( - gajim.get_name_from_jid(account, jid)) - else: - _text = gobject.markup_escape_text(text) + # Ok, that failed. Let's try pynotify, which also uses notification daemon + if gajim.config.get('use_notif_daemon') and USER_HAS_PYNOTIFY: + if not text and event_type == 'new_message': + # empty text for new_message means do_preview = False + # -> default value for text + _text = gobject.markup_escape_text( + gajim.get_name_from_jid(account, jid)) + else: + _text = gobject.markup_escape_text(text) - if not title: - _title = '' - else: - _title = title + if not title: + _title = '' + else: + _title = title - notification = pynotify.Notification(_title, _text) - timeout = gajim.config.get('notification_timeout') * 1000 # make it ms - notification.set_timeout(timeout) + notification = pynotify.Notification(_title, _text) + timeout = gajim.config.get('notification_timeout') * 1000 # make it ms + notification.set_timeout(timeout) - notification.set_category(event_type) - notification.set_data('event_type', event_type) - notification.set_data('jid', jid) - notification.set_data('account', account) - notification.set_data('msg_type', msg_type) - notification.set_property('icon-name', path_to_image) - if 'actions' in pynotify.get_server_caps(): - notification.add_action('default', 'Default Action', - on_pynotify_notification_clicked) + notification.set_category(event_type) + notification.set_data('event_type', event_type) + notification.set_data('jid', jid) + notification.set_data('account', account) + notification.set_data('msg_type', msg_type) + notification.set_property('icon-name', path_to_image) + if 'actions' in pynotify.get_server_caps(): + notification.add_action('default', 'Default Action', + on_pynotify_notification_clicked) - try: - notification.show() - return - except gobject.GError, e: - # Connection to notification-daemon failed, see #2893 - gajim.log.debug(str(e)) + try: + notification.show() + return + except gobject.GError, e: + # Connection to notification-daemon failed, see #2893 + gajim.log.debug(str(e)) - # Either nothing succeeded or the user wants old-style notifications - instance = dialogs.PopupNotificationWindow(event_type, jid, account, - msg_type, path_to_image, title, text) - gajim.interface.roster.popup_notification_windows.append(instance) + # Either nothing succeeded or the user wants old-style notifications + instance = dialogs.PopupNotificationWindow(event_type, jid, account, + msg_type, path_to_image, title, text) + gajim.interface.roster.popup_notification_windows.append(instance) def on_pynotify_notification_clicked(notification, action): - jid = notification.get_data('jid') - account = notification.get_data('account') - msg_type = notification.get_data('msg_type') + jid = notification.get_data('jid') + account = notification.get_data('account') + msg_type = notification.get_data('msg_type') - notification.close() - gajim.interface.handle_event(account, jid, msg_type) + notification.close() + gajim.interface.handle_event(account, jid, msg_type) class NotificationResponseManager: - '''Collects references to pending DesktopNotifications and manages there - signalling. This is necessary due to a bug in DBus where you can't remove - a signal from an interface once it's connected.''' - def __init__(self): - self.pending = {} - self.received = [] - self.interface = None + '''Collects references to pending DesktopNotifications and manages there + signalling. This is necessary due to a bug in DBus where you can't remove + a signal from an interface once it's connected.''' + def __init__(self): + self.pending = {} + self.received = [] + self.interface = None - def attach_to_interface(self): - if self.interface is not None: - return - self.interface = dbus_support.get_notifications_interface() - self.interface.connect_to_signal('ActionInvoked', self.on_action_invoked) - self.interface.connect_to_signal('NotificationClosed', self.on_closed) + def attach_to_interface(self): + if self.interface is not None: + return + self.interface = dbus_support.get_notifications_interface() + self.interface.connect_to_signal('ActionInvoked', self.on_action_invoked) + self.interface.connect_to_signal('NotificationClosed', self.on_closed) - def on_action_invoked(self, id_, reason): - self.received.append((id_, time.time(), reason)) - if id_ in self.pending: - notification = self.pending[id_] - notification.on_action_invoked(id_, reason) - del self.pending[id_] - if len(self.received) > 20: - curt = time.time() - for rec in self.received: - diff = curt - rec[1] - if diff > 10: - self.received.remove(rec) + def on_action_invoked(self, id_, reason): + self.received.append((id_, time.time(), reason)) + if id_ in self.pending: + notification = self.pending[id_] + notification.on_action_invoked(id_, reason) + del self.pending[id_] + if len(self.received) > 20: + curt = time.time() + for rec in self.received: + diff = curt - rec[1] + if diff > 10: + self.received.remove(rec) - def on_closed(self, id_, reason=None): - if id_ in self.pending: - del self.pending[id_] + def on_closed(self, id_, reason=None): + if id_ in self.pending: + del self.pending[id_] - def add_pending(self, id_, object_): - # Check to make sure that we handle an event immediately if we're adding - # an id that's already been triggered - for rec in self.received: - if rec[0] == id_: - object_.on_action_invoked(id_, rec[2]) - self.received.remove(rec) - return - if id_ not in self.pending: - # Add it - self.pending[id_] = object_ - else: - # We've triggered an event that has a duplicate ID! - gajim.log.debug('Duplicate ID of notification. Can\'t handle this.') + def add_pending(self, id_, object_): + # Check to make sure that we handle an event immediately if we're adding + # an id that's already been triggered + for rec in self.received: + if rec[0] == id_: + object_.on_action_invoked(id_, rec[2]) + self.received.remove(rec) + return + if id_ not in self.pending: + # Add it + self.pending[id_] = object_ + else: + # We've triggered an event that has a duplicate ID! + gajim.log.debug('Duplicate ID of notification. Can\'t handle this.') notification_response_manager = NotificationResponseManager() class DesktopNotification: - '''A DesktopNotification that interfaces with D-Bus via the Desktop - Notification specification''' - def __init__(self, event_type, jid, account, msg_type='', - path_to_image=None, title=None, text=None): - self.path_to_image = path_to_image - self.event_type = event_type - self.title = title - self.text = text - # 0.3.1 is the only version of notification daemon that has no way - # to determine which version it is. If no method exists, it means - # they're using that one. - self.default_version = [0, 3, 1] - self.account = account - self.jid = jid - self.msg_type = msg_type + '''A DesktopNotification that interfaces with D-Bus via the Desktop + Notification specification''' + def __init__(self, event_type, jid, account, msg_type='', + path_to_image=None, title=None, text=None): + self.path_to_image = path_to_image + self.event_type = event_type + self.title = title + self.text = text + # 0.3.1 is the only version of notification daemon that has no way + # to determine which version it is. If no method exists, it means + # they're using that one. + self.default_version = [0, 3, 1] + self.account = account + self.jid = jid + self.msg_type = msg_type - # default value of text - if not text and event_type == 'new_message': - # empty text for new_message means do_preview = False - self.text = gajim.get_name_from_jid(account, jid) + # default value of text + if not text and event_type == 'new_message': + # empty text for new_message means do_preview = False + self.text = gajim.get_name_from_jid(account, jid) - if not title: - self.title = event_type # default value + if not title: + self.title = event_type # default value - if event_type == _('Contact Signed In'): - ntype = 'presence.online' - elif event_type == _('Contact Signed Out'): - ntype = 'presence.offline' - elif event_type in (_('New Message'), _('New Single Message'), - _('New Private Message')): - ntype = 'im.received' - elif event_type == _('File Transfer Request'): - ntype = 'transfer' - elif event_type == _('File Transfer Error'): - ntype = 'transfer.error' - elif event_type in (_('File Transfer Completed'), - _('File Transfer Stopped')): - ntype = 'transfer.complete' - elif event_type == _('New E-mail'): - ntype = 'email.arrived' - elif event_type == _('Groupchat Invitation'): - ntype = 'im.invitation' - elif event_type == _('Contact Changed Status'): - ntype = 'presence.status' - elif event_type == _('Connection Failed'): - ntype = 'connection.failed' - elif event_type == _('Subscription request'): - ntype = 'subscription.request' - elif event_type == _('Unsubscribed'): - ntype = 'unsubscribed' - else: - # default failsafe values - self.path_to_image = os.path.abspath( - os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'chat_msg_recv.png')) # img to display - ntype = 'im' # Notification Type + if event_type == _('Contact Signed In'): + ntype = 'presence.online' + elif event_type == _('Contact Signed Out'): + ntype = 'presence.offline' + elif event_type in (_('New Message'), _('New Single Message'), + _('New Private Message')): + ntype = 'im.received' + elif event_type == _('File Transfer Request'): + ntype = 'transfer' + elif event_type == _('File Transfer Error'): + ntype = 'transfer.error' + elif event_type in (_('File Transfer Completed'), + _('File Transfer Stopped')): + ntype = 'transfer.complete' + elif event_type == _('New E-mail'): + ntype = 'email.arrived' + elif event_type == _('Groupchat Invitation'): + ntype = 'im.invitation' + elif event_type == _('Contact Changed Status'): + ntype = 'presence.status' + elif event_type == _('Connection Failed'): + ntype = 'connection.failed' + elif event_type == _('Subscription request'): + ntype = 'subscription.request' + elif event_type == _('Unsubscribed'): + ntype = 'unsubscribed' + else: + # default failsafe values + self.path_to_image = os.path.abspath( + os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', + 'chat_msg_recv.png')) # img to display + ntype = 'im' # Notification Type - self.notif = dbus_support.get_notifications_interface(self) - if self.notif is None: - raise dbus.DBusException('unable to get notifications interface') - self.ntype = ntype + self.notif = dbus_support.get_notifications_interface(self) + if self.notif is None: + raise dbus.DBusException('unable to get notifications interface') + self.ntype = ntype - if self.kde_notifications: - self.attempt_notify() - else: - self.capabilities = self.notif.GetCapabilities() - if self.capabilities is None: - self.capabilities = ['actions'] - self.get_version() + if self.kde_notifications: + self.attempt_notify() + else: + self.capabilities = self.notif.GetCapabilities() + if self.capabilities is None: + self.capabilities = ['actions'] + self.get_version() - def attempt_notify(self): - timeout = gajim.config.get('notification_timeout') # in seconds - ntype = self.ntype - if self.kde_notifications: - notification_text = ('' \ - '%(title)s
%(text)s') % {'title': self.title, - 'text': self.text, 'image': self.path_to_image} - gajim_icon = os.path.abspath(os.path.join(gajim.DATA_DIR, 'pixmaps', - 'gajim.png')) - self.notif.Notify( - dbus.String(_('Gajim')), # app_name (string) - dbus.UInt32(0), # replaces_id (uint) - ntype, # event_id (string) - dbus.String(gajim_icon), # app_icon (string) - dbus.String(''), # summary (string) - dbus.String(notification_text), # body (string) - # actions (stringlist) - (dbus.String('default'), dbus.String(self.event_type), - dbus.String('ignore'), dbus.String(_('Ignore'))), - [], # hints (not used in KDE yet) - dbus.UInt32(timeout*1000), # timeout (int), in ms - reply_handler=self.attach_by_id, - error_handler=self.notify_another_way) - return - version = self.version - if version[:2] == [0, 2]: - actions = {} - if 'actions' in self.capabilities: - actions = {'default': 0} - try: - self.notif.Notify( - dbus.String(_('Gajim')), - dbus.String(self.path_to_image), - dbus.UInt32(0), - ntype, - dbus.Byte(0), - dbus.String(self.title), - dbus.String(self.text), - [dbus.String(self.path_to_image)], - actions, - [''], - True, - dbus.UInt32(timeout), - reply_handler=self.attach_by_id, - error_handler=self.notify_another_way) - except AttributeError: - version = [0, 3, 1] # we're actually dealing with the newer version - if version > [0, 3]: - if gajim.interface.systray_enabled and \ - gajim.config.get('attach_notifications_to_systray'): - x, y = gajim.interface.systray.img_tray.window.get_origin() - width, height, = \ - gajim.interface.systray.img_tray.window.get_geometry()[2:4] - pos_x = x + (width / 2) - pos_y = y + (height / 2) - hints = {'x': pos_x, 'y': pos_y} - else: - hints = {} - if version >= [0, 3, 2]: - hints['urgency'] = dbus.Byte(0) # Low Urgency - hints['category'] = dbus.String(ntype) - # it seems notification-daemon doesn't like empty text - if self.text: - text = self.text - else: - text = ' ' - actions = () - if 'actions' in self.capabilities: - actions = (dbus.String('default'), dbus.String(self.event_type)) - self.notif.Notify( - dbus.String(_('Gajim')), - dbus.UInt32(0), # this notification does not replace other - dbus.String(self.path_to_image), - dbus.String(self.title), - dbus.String(text), - actions, - hints, - dbus.UInt32(timeout*1000), - reply_handler=self.attach_by_id, - error_handler=self.notify_another_way) - else: - self.notif.Notify( - dbus.String(_('Gajim')), - dbus.String(self.path_to_image), - dbus.UInt32(0), - dbus.String(self.title), - dbus.String(self.text), - dbus.String(''), - hints, - dbus.UInt32(timeout*1000), - reply_handler=self.attach_by_id, - error_handler=self.notify_another_way) + def attempt_notify(self): + timeout = gajim.config.get('notification_timeout') # in seconds + ntype = self.ntype + if self.kde_notifications: + notification_text = ('' \ + '%(title)s
%(text)s') % {'title': self.title, + 'text': self.text, 'image': self.path_to_image} + gajim_icon = os.path.abspath(os.path.join(gajim.DATA_DIR, 'pixmaps', + 'gajim.png')) + self.notif.Notify( + dbus.String(_('Gajim')), # app_name (string) + dbus.UInt32(0), # replaces_id (uint) + ntype, # event_id (string) + dbus.String(gajim_icon), # app_icon (string) + dbus.String(''), # summary (string) + dbus.String(notification_text), # body (string) + # actions (stringlist) + (dbus.String('default'), dbus.String(self.event_type), + dbus.String('ignore'), dbus.String(_('Ignore'))), + [], # hints (not used in KDE yet) + dbus.UInt32(timeout*1000), # timeout (int), in ms + reply_handler=self.attach_by_id, + error_handler=self.notify_another_way) + return + version = self.version + if version[:2] == [0, 2]: + actions = {} + if 'actions' in self.capabilities: + actions = {'default': 0} + try: + self.notif.Notify( + dbus.String(_('Gajim')), + dbus.String(self.path_to_image), + dbus.UInt32(0), + ntype, + dbus.Byte(0), + dbus.String(self.title), + dbus.String(self.text), + [dbus.String(self.path_to_image)], + actions, + [''], + True, + dbus.UInt32(timeout), + reply_handler=self.attach_by_id, + error_handler=self.notify_another_way) + except AttributeError: + version = [0, 3, 1] # we're actually dealing with the newer version + if version > [0, 3]: + if gajim.interface.systray_enabled and \ + gajim.config.get('attach_notifications_to_systray'): + x, y = gajim.interface.systray.img_tray.window.get_origin() + width, height, = \ + gajim.interface.systray.img_tray.window.get_geometry()[2:4] + pos_x = x + (width / 2) + pos_y = y + (height / 2) + hints = {'x': pos_x, 'y': pos_y} + else: + hints = {} + if version >= [0, 3, 2]: + hints['urgency'] = dbus.Byte(0) # Low Urgency + hints['category'] = dbus.String(ntype) + # it seems notification-daemon doesn't like empty text + if self.text: + text = self.text + else: + text = ' ' + actions = () + if 'actions' in self.capabilities: + actions = (dbus.String('default'), dbus.String(self.event_type)) + self.notif.Notify( + dbus.String(_('Gajim')), + dbus.UInt32(0), # this notification does not replace other + dbus.String(self.path_to_image), + dbus.String(self.title), + dbus.String(text), + actions, + hints, + dbus.UInt32(timeout*1000), + reply_handler=self.attach_by_id, + error_handler=self.notify_another_way) + else: + self.notif.Notify( + dbus.String(_('Gajim')), + dbus.String(self.path_to_image), + dbus.UInt32(0), + dbus.String(self.title), + dbus.String(self.text), + dbus.String(''), + hints, + dbus.UInt32(timeout*1000), + reply_handler=self.attach_by_id, + error_handler=self.notify_another_way) - def attach_by_id(self, id_): - self.id = id_ - notification_response_manager.attach_to_interface() - notification_response_manager.add_pending(self.id, self) + def attach_by_id(self, id_): + self.id = id_ + notification_response_manager.attach_to_interface() + notification_response_manager.add_pending(self.id, self) - def notify_another_way(self,e): - gajim.log.debug(str(e)) - gajim.log.debug('Need to implement a new way of falling back') + def notify_another_way(self, e): + gajim.log.debug(str(e)) + gajim.log.debug('Need to implement a new way of falling back') - def on_action_invoked(self, id_, reason): - if self.notif is None: - return - self.notif.CloseNotification(dbus.UInt32(id_)) - self.notif = None + def on_action_invoked(self, id_, reason): + if self.notif is None: + return + self.notif.CloseNotification(dbus.UInt32(id_)) + self.notif = None - if reason == 'ignore': - return + if reason == 'ignore': + return - gajim.interface.handle_event(self.account, self.jid, self.msg_type) + gajim.interface.handle_event(self.account, self.jid, self.msg_type) - def version_reply_handler(self, name, vendor, version, spec_version=None): - if spec_version: - version = spec_version - elif vendor == 'Xfce' and version.startswith('0.1.0'): - version = '0.9' - version_list = version.split('.') - self.version = [] - try: - while len(version_list): - self.version.append(int(version_list.pop(0))) - except ValueError: - self.version_error_handler_3_x_try(None) - self.attempt_notify() + def version_reply_handler(self, name, vendor, version, spec_version=None): + if spec_version: + version = spec_version + elif vendor == 'Xfce' and version.startswith('0.1.0'): + version = '0.9' + version_list = version.split('.') + self.version = [] + try: + while len(version_list): + self.version.append(int(version_list.pop(0))) + except ValueError: + self.version_error_handler_3_x_try(None) + self.attempt_notify() - def get_version(self): - self.notif.GetServerInfo( - reply_handler=self.version_reply_handler, - error_handler=self.version_error_handler_2_x_try) + def get_version(self): + self.notif.GetServerInfo( + reply_handler=self.version_reply_handler, + error_handler=self.version_error_handler_2_x_try) - def version_error_handler_2_x_try(self, e): - self.notif.GetServerInformation(reply_handler=self.version_reply_handler, - error_handler=self.version_error_handler_3_x_try) + def version_error_handler_2_x_try(self, e): + self.notif.GetServerInformation(reply_handler=self.version_reply_handler, + error_handler=self.version_error_handler_3_x_try) - def version_error_handler_3_x_try(self, e): - self.version = self.default_version - self.attempt_notify() - -# vim: se ts=3: + def version_error_handler_3_x_try(self, e): + self.version = self.default_version + self.attempt_notify() diff --git a/src/profile_window.py b/src/profile_window.py index d00bfc335..bbfd83fad 100644 --- a/src/profile_window.py +++ b/src/profile_window.py @@ -36,325 +36,323 @@ from common import gajim class ProfileWindow: - '''Class for our information window''' + '''Class for our information window''' - def __init__(self, account): - self.xml = gtkgui_helpers.get_glade('profile_window.glade') - self.window = self.xml.get_widget('profile_window') - self.progressbar = self.xml.get_widget('progressbar') - self.statusbar = self.xml.get_widget('statusbar') - self.context_id = self.statusbar.get_context_id('profile') + def __init__(self, account): + self.xml = gtkgui_helpers.get_glade('profile_window.glade') + self.window = self.xml.get_widget('profile_window') + self.progressbar = self.xml.get_widget('progressbar') + self.statusbar = self.xml.get_widget('statusbar') + self.context_id = self.statusbar.get_context_id('profile') - self.account = account - self.jid = gajim.get_jid_from_account(account) + self.account = account + self.jid = gajim.get_jid_from_account(account) - self.dialog = None - self.avatar_mime_type = None - self.avatar_encoded = None - self.message_id = self.statusbar.push(self.context_id, - _('Retrieving profile...')) - self.update_progressbar_timeout_id = gobject.timeout_add(100, - self.update_progressbar) - self.remove_statusbar_timeout_id = None + self.dialog = None + self.avatar_mime_type = None + self.avatar_encoded = None + self.message_id = self.statusbar.push(self.context_id, + _('Retrieving profile...')) + self.update_progressbar_timeout_id = gobject.timeout_add(100, + self.update_progressbar) + self.remove_statusbar_timeout_id = None - # Create Image for avatar button - image = gtk.Image() - self.xml.get_widget('PHOTO_button').set_image(image) - self.xml.signal_autoconnect(self) - self.window.show_all() + # Create Image for avatar button + image = gtk.Image() + self.xml.get_widget('PHOTO_button').set_image(image) + self.xml.signal_autoconnect(self) + self.window.show_all() - def update_progressbar(self): - self.progressbar.pulse() - return True # loop forever + def update_progressbar(self): + self.progressbar.pulse() + return True # loop forever - def remove_statusbar(self, message_id): - self.statusbar.remove_message(self.context_id, message_id) - self.remove_statusbar_timeout_id = None + def remove_statusbar(self, message_id): + self.statusbar.remove_message(self.context_id, message_id) + self.remove_statusbar_timeout_id = None - def on_profile_window_destroy(self, widget): - if self.update_progressbar_timeout_id is not None: - gobject.source_remove(self.update_progressbar_timeout_id) - if self.remove_statusbar_timeout_id is not None: - gobject.source_remove(self.remove_statusbar_timeout_id) - del gajim.interface.instances[self.account]['profile'] - if self.dialog: # Image chooser dialog - self.dialog.destroy() + def on_profile_window_destroy(self, widget): + if self.update_progressbar_timeout_id is not None: + gobject.source_remove(self.update_progressbar_timeout_id) + if self.remove_statusbar_timeout_id is not None: + gobject.source_remove(self.remove_statusbar_timeout_id) + del gajim.interface.instances[self.account]['profile'] + if self.dialog: # Image chooser dialog + self.dialog.destroy() - def on_profile_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: - self.window.destroy() + def on_profile_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: + self.window.destroy() - def on_clear_button_clicked(self, widget): - # empty the image - button = self.xml.get_widget('PHOTO_button') - image = button.get_image() - image.set_from_pixbuf(None) - button.hide() - text_button = self.xml.get_widget('NOPHOTO_button') - text_button.show() - self.avatar_encoded = None - self.avatar_mime_type = None + def on_clear_button_clicked(self, widget): + # empty the image + button = self.xml.get_widget('PHOTO_button') + image = button.get_image() + image.set_from_pixbuf(None) + button.hide() + text_button = self.xml.get_widget('NOPHOTO_button') + text_button.show() + self.avatar_encoded = None + self.avatar_mime_type = None - def on_set_avatar_button_clicked(self, widget): - def on_ok(widget, path_to_file): - must_delete = False - filesize = os.path.getsize(path_to_file) # in bytes - invalid_file = False - msg = '' - if os.path.isfile(path_to_file): - stat = os.stat(path_to_file) - if stat[6] == 0: - invalid_file = True - msg = _('File is empty') - else: - invalid_file = True - msg = _('File does not exist') - if not invalid_file and filesize > 16384: # 16 kb - try: - pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file) - # get the image at 'notification size' - # and hope that user did not specify in ACE crazy size - scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, - 'tooltip') - except gobject.GError, msg: # unknown format - # msg should be string, not object instance - msg = str(msg) - invalid_file = True - if invalid_file: - if True: # keep identation - dialogs.ErrorDialog(_('Could not load image'), msg) - return - if filesize > 16384: - if scaled_pixbuf: - path_to_file = os.path.join(gajim.TMP, - 'avatar_scaled.png') - scaled_pixbuf.save(path_to_file, 'png') - must_delete = True + def on_set_avatar_button_clicked(self, widget): + def on_ok(widget, path_to_file): + must_delete = False + filesize = os.path.getsize(path_to_file) # in bytes + invalid_file = False + msg = '' + if os.path.isfile(path_to_file): + stat = os.stat(path_to_file) + if stat[6] == 0: + invalid_file = True + msg = _('File is empty') + else: + invalid_file = True + msg = _('File does not exist') + if not invalid_file and filesize > 16384: # 16 kb + try: + pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file) + # get the image at 'notification size' + # and hope that user did not specify in ACE crazy size + scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, + 'tooltip') + except gobject.GError, msg: # unknown format + # msg should be string, not object instance + msg = str(msg) + invalid_file = True + if invalid_file: + if True: # keep identation + dialogs.ErrorDialog(_('Could not load image'), msg) + return + if filesize > 16384: + if scaled_pixbuf: + path_to_file = os.path.join(gajim.TMP, + 'avatar_scaled.png') + scaled_pixbuf.save(path_to_file, 'png') + must_delete = True - fd = open(path_to_file, 'rb') - data = fd.read() - pixbuf = gtkgui_helpers.get_pixbuf_from_data(data) - try: - # rescale it - pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard') - except AttributeError: # unknown format - dialogs.ErrorDialog(_('Could not load image')) - return - self.dialog.destroy() - self.dialog = None - button = self.xml.get_widget('PHOTO_button') - image = button.get_image() - image.set_from_pixbuf(pixbuf) - button.show() - text_button = self.xml.get_widget('NOPHOTO_button') - text_button.hide() - self.avatar_encoded = base64.encodestring(data) - # returns None if unknown type - self.avatar_mime_type = mimetypes.guess_type(path_to_file)[0] - if must_delete: - try: - os.remove(path_to_file) - except OSError: - gajim.log.debug('Cannot remove %s' % path_to_file) + fd = open(path_to_file, 'rb') + data = fd.read() + pixbuf = gtkgui_helpers.get_pixbuf_from_data(data) + try: + # rescale it + pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard') + except AttributeError: # unknown format + dialogs.ErrorDialog(_('Could not load image')) + return + self.dialog.destroy() + self.dialog = None + button = self.xml.get_widget('PHOTO_button') + image = button.get_image() + image.set_from_pixbuf(pixbuf) + button.show() + text_button = self.xml.get_widget('NOPHOTO_button') + text_button.hide() + self.avatar_encoded = base64.encodestring(data) + # returns None if unknown type + self.avatar_mime_type = mimetypes.guess_type(path_to_file)[0] + if must_delete: + try: + os.remove(path_to_file) + except OSError: + gajim.log.debug('Cannot remove %s' % path_to_file) - def on_clear(widget): - self.dialog.destroy() - self.dialog = None - self.on_clear_button_clicked(widget) + def on_clear(widget): + self.dialog.destroy() + self.dialog = None + self.on_clear_button_clicked(widget) - def on_cancel(widget): - self.dialog.destroy() - self.dialog = None + def on_cancel(widget): + self.dialog.destroy() + self.dialog = None - if self.dialog: - self.dialog.present() - else: - self.dialog = dialogs.AvatarChooserDialog(on_response_ok = on_ok, - on_response_cancel = on_cancel, on_response_clear = on_clear) + if self.dialog: + self.dialog.present() + else: + self.dialog = dialogs.AvatarChooserDialog(on_response_ok = on_ok, + on_response_cancel = on_cancel, on_response_clear = on_clear) - def on_PHOTO_button_press_event(self, widget, event): - '''If right-clicked, show popup''' - if event.button == 3 and self.avatar_encoded: # right click - menu = gtk.Menu() + def on_PHOTO_button_press_event(self, widget, event): + '''If right-clicked, show popup''' + if event.button == 3 and self.avatar_encoded: # right click + menu = gtk.Menu() - # Try to get pixbuf - pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(self.jid, - use_local = False) + # Try to get pixbuf + pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(self.jid, + use_local = False) - if pixbuf: - nick = gajim.config.get_per('accounts', self.account, 'name') - menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS) - menuitem.connect('activate', - gtkgui_helpers.on_avatar_save_as_menuitem_activate, - self.jid, None, nick + '.jpeg') - menu.append(menuitem) - # show clear - menuitem = gtk.ImageMenuItem(gtk.STOCK_CLEAR) - menuitem.connect('activate', self.on_clear_button_clicked) - menu.append(menuitem) - menu.connect('selection-done', lambda w:w.destroy()) - # show the menu - menu.show_all() - menu.popup(None, None, None, event.button, event.time) - elif event.button == 1: # left click - self.on_set_avatar_button_clicked(widget) + if pixbuf: + nick = gajim.config.get_per('accounts', self.account, 'name') + menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS) + menuitem.connect('activate', + gtkgui_helpers.on_avatar_save_as_menuitem_activate, + self.jid, None, nick + '.jpeg') + menu.append(menuitem) + # show clear + menuitem = gtk.ImageMenuItem(gtk.STOCK_CLEAR) + menuitem.connect('activate', self.on_clear_button_clicked) + menu.append(menuitem) + menu.connect('selection-done', lambda w:w.destroy()) + # show the menu + menu.show_all() + menu.popup(None, None, None, event.button, event.time) + elif event.button == 1: # left click + self.on_set_avatar_button_clicked(widget) - def set_value(self, entry_name, value): - try: - self.xml.get_widget(entry_name).set_text(value) - except AttributeError: - pass + def set_value(self, entry_name, value): + try: + self.xml.get_widget(entry_name).set_text(value) + except AttributeError: + pass - def set_values(self, vcard_): - button = self.xml.get_widget('PHOTO_button') - image = button.get_image() - text_button = self.xml.get_widget('NOPHOTO_button') - if not 'PHOTO' in vcard_: - # set default image - image.set_from_pixbuf(None) - button.hide() - text_button.show() - for i in vcard_.keys(): - if i == 'PHOTO': - pixbuf, self.avatar_encoded, self.avatar_mime_type = \ - vcard.get_avatar_pixbuf_encoded_mime(vcard_[i]) - if not pixbuf: - image.set_from_pixbuf(None) - button.hide() - text_button.show() - continue - pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard') - image.set_from_pixbuf(pixbuf) - button.show() - text_button.hide() - continue - if i == 'ADR' or i == 'TEL' or i == 'EMAIL': - for entry in vcard_[i]: - add_on = '_HOME' - if 'WORK' in entry: - add_on = '_WORK' - for j in entry.keys(): - self.set_value(i + add_on + '_' + j + '_entry', entry[j]) - if isinstance(vcard_[i], dict): - for j in vcard_[i].keys(): - self.set_value(i + '_' + j + '_entry', vcard_[i][j]) - else: - if i == 'DESC': - self.xml.get_widget('DESC_textview').get_buffer().set_text( - vcard_[i], 0) - else: - self.set_value(i + '_entry', vcard_[i]) - if self.update_progressbar_timeout_id is not None: - if self.message_id: - self.statusbar.remove_message(self.context_id, self.message_id) - self.message_id = self.statusbar.push(self.context_id, - _('Information received')) - self.remove_statusbar_timeout_id = gobject.timeout_add_seconds(3, - self.remove_statusbar, self.message_id) - gobject.source_remove(self.update_progressbar_timeout_id) - self.progressbar.hide() - self.progressbar.set_fraction(0) - self.update_progressbar_timeout_id = None + def set_values(self, vcard_): + button = self.xml.get_widget('PHOTO_button') + image = button.get_image() + text_button = self.xml.get_widget('NOPHOTO_button') + if not 'PHOTO' in vcard_: + # set default image + image.set_from_pixbuf(None) + button.hide() + text_button.show() + for i in vcard_.keys(): + if i == 'PHOTO': + pixbuf, self.avatar_encoded, self.avatar_mime_type = \ + vcard.get_avatar_pixbuf_encoded_mime(vcard_[i]) + if not pixbuf: + image.set_from_pixbuf(None) + button.hide() + text_button.show() + continue + pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard') + image.set_from_pixbuf(pixbuf) + button.show() + text_button.hide() + continue + if i == 'ADR' or i == 'TEL' or i == 'EMAIL': + for entry in vcard_[i]: + add_on = '_HOME' + if 'WORK' in entry: + add_on = '_WORK' + for j in entry.keys(): + self.set_value(i + add_on + '_' + j + '_entry', entry[j]) + if isinstance(vcard_[i], dict): + for j in vcard_[i].keys(): + self.set_value(i + '_' + j + '_entry', vcard_[i][j]) + else: + if i == 'DESC': + self.xml.get_widget('DESC_textview').get_buffer().set_text( + vcard_[i], 0) + else: + self.set_value(i + '_entry', vcard_[i]) + if self.update_progressbar_timeout_id is not None: + if self.message_id: + self.statusbar.remove_message(self.context_id, self.message_id) + self.message_id = self.statusbar.push(self.context_id, + _('Information received')) + self.remove_statusbar_timeout_id = gobject.timeout_add_seconds(3, + self.remove_statusbar, self.message_id) + gobject.source_remove(self.update_progressbar_timeout_id) + self.progressbar.hide() + self.progressbar.set_fraction(0) + self.update_progressbar_timeout_id = None - def add_to_vcard(self, vcard_, entry, txt): - '''Add an information to the vCard dictionary''' - entries = entry.split('_') - loc = vcard_ - if len(entries) == 3: # We need to use lists - if entries[0] not in loc: - loc[entries[0]] = [] - found = False - for e in loc[entries[0]]: - if entries[1] in e: - e[entries[2]] = txt - break - else: - loc[entries[0]].append({entries[1]: '', entries[2]: txt}) - return vcard_ - while len(entries) > 1: - if entries[0] not in loc: - loc[entries[0]] = {} - loc = loc[entries[0]] - del entries[0] - loc[entries[0]] = txt - return vcard_ + def add_to_vcard(self, vcard_, entry, txt): + '''Add an information to the vCard dictionary''' + entries = entry.split('_') + loc = vcard_ + if len(entries) == 3: # We need to use lists + if entries[0] not in loc: + loc[entries[0]] = [] + found = False + for e in loc[entries[0]]: + if entries[1] in e: + e[entries[2]] = txt + break + else: + loc[entries[0]].append({entries[1]: '', entries[2]: txt}) + return vcard_ + while len(entries) > 1: + if entries[0] not in loc: + loc[entries[0]] = {} + loc = loc[entries[0]] + del entries[0] + loc[entries[0]] = txt + return vcard_ - def make_vcard(self): - '''make the vCard dictionary''' - entries = ['FN', 'NICKNAME', 'BDAY', 'EMAIL_HOME_USERID', 'URL', - 'TEL_HOME_NUMBER', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX', - 'N_SUFFIX', 'ADR_HOME_STREET', 'ADR_HOME_EXTADR', 'ADR_HOME_LOCALITY', - 'ADR_HOME_REGION', 'ADR_HOME_PCODE', 'ADR_HOME_CTRY', 'ORG_ORGNAME', - 'ORG_ORGUNIT', 'TITLE', 'ROLE', 'TEL_WORK_NUMBER', 'EMAIL_WORK_USERID', - 'ADR_WORK_STREET', 'ADR_WORK_EXTADR', 'ADR_WORK_LOCALITY', - 'ADR_WORK_REGION', 'ADR_WORK_PCODE', 'ADR_WORK_CTRY'] - vcard_ = {} - for e in entries: - txt = self.xml.get_widget(e + '_entry').get_text().decode('utf-8') - if txt != '': - vcard_ = self.add_to_vcard(vcard_, e, txt) + def make_vcard(self): + '''make the vCard dictionary''' + entries = ['FN', 'NICKNAME', 'BDAY', 'EMAIL_HOME_USERID', 'URL', + 'TEL_HOME_NUMBER', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX', + 'N_SUFFIX', 'ADR_HOME_STREET', 'ADR_HOME_EXTADR', 'ADR_HOME_LOCALITY', + 'ADR_HOME_REGION', 'ADR_HOME_PCODE', 'ADR_HOME_CTRY', 'ORG_ORGNAME', + 'ORG_ORGUNIT', 'TITLE', 'ROLE', 'TEL_WORK_NUMBER', 'EMAIL_WORK_USERID', + 'ADR_WORK_STREET', 'ADR_WORK_EXTADR', 'ADR_WORK_LOCALITY', + 'ADR_WORK_REGION', 'ADR_WORK_PCODE', 'ADR_WORK_CTRY'] + vcard_ = {} + for e in entries: + txt = self.xml.get_widget(e + '_entry').get_text().decode('utf-8') + if txt != '': + vcard_ = self.add_to_vcard(vcard_, e, txt) - # DESC textview - buff = self.xml.get_widget('DESC_textview').get_buffer() - start_iter = buff.get_start_iter() - end_iter = buff.get_end_iter() - txt = buff.get_text(start_iter, end_iter, 0) - if txt != '': - vcard_['DESC'] = txt.decode('utf-8') + # DESC textview + buff = self.xml.get_widget('DESC_textview').get_buffer() + start_iter = buff.get_start_iter() + end_iter = buff.get_end_iter() + txt = buff.get_text(start_iter, end_iter, 0) + if txt != '': + vcard_['DESC'] = txt.decode('utf-8') - # Avatar - if self.avatar_encoded: - vcard_['PHOTO'] = {'BINVAL': self.avatar_encoded} - if self.avatar_mime_type: - vcard_['PHOTO']['TYPE'] = self.avatar_mime_type - return vcard_ + # Avatar + if self.avatar_encoded: + vcard_['PHOTO'] = {'BINVAL': self.avatar_encoded} + if self.avatar_mime_type: + vcard_['PHOTO']['TYPE'] = self.avatar_mime_type + return vcard_ - def on_ok_button_clicked(self, widget): - if self.update_progressbar_timeout_id: - # Operation in progress - return - if gajim.connections[self.account].connected < 2: - dialogs.ErrorDialog(_('You are not connected to the server'), - _('Without a connection you can not publish your contact ' - 'information.')) - return - vcard_ = self.make_vcard() - nick = '' - if 'NICKNAME' in vcard_: - nick = vcard_['NICKNAME'] - gajim.connections[self.account].send_nickname(nick) - if nick == '': - nick = gajim.config.get_per('accounts', self.account, 'name') - gajim.nicks[self.account] = nick - gajim.connections[self.account].send_vcard(vcard_) - self.message_id = self.statusbar.push(self.context_id, - _('Sending profile...')) - self.progressbar.show() - self.update_progressbar_timeout_id = gobject.timeout_add(100, - self.update_progressbar) + def on_ok_button_clicked(self, widget): + if self.update_progressbar_timeout_id: + # Operation in progress + return + if gajim.connections[self.account].connected < 2: + dialogs.ErrorDialog(_('You are not connected to the server'), + _('Without a connection you can not publish your contact ' + 'information.')) + return + vcard_ = self.make_vcard() + nick = '' + if 'NICKNAME' in vcard_: + nick = vcard_['NICKNAME'] + gajim.connections[self.account].send_nickname(nick) + if nick == '': + nick = gajim.config.get_per('accounts', self.account, 'name') + gajim.nicks[self.account] = nick + gajim.connections[self.account].send_vcard(vcard_) + self.message_id = self.statusbar.push(self.context_id, + _('Sending profile...')) + self.progressbar.show() + self.update_progressbar_timeout_id = gobject.timeout_add(100, + self.update_progressbar) - def vcard_published(self): - if self.update_progressbar_timeout_id is not None: - gobject.source_remove(self.update_progressbar_timeout_id) - self.update_progressbar_timeout_id = None - self.window.destroy() + def vcard_published(self): + if self.update_progressbar_timeout_id is not None: + gobject.source_remove(self.update_progressbar_timeout_id) + self.update_progressbar_timeout_id = None + self.window.destroy() - def vcard_not_published(self): - if self.message_id: - self.statusbar.remove_message(self.context_id, self.message_id) - self.message_id = self.statusbar.push(self.context_id, - _('Information NOT published')) - self.remove_statusbar_timeout_id = gobject.timeout_add_seconds(3, - self.remove_statusbar, self.message_id) - if self.update_progressbar_timeout_id is not None: - gobject.source_remove(self.update_progressbar_timeout_id) - self.progressbar.set_fraction(0) - self.update_progressbar_timeout_id = None - dialogs.InformationDialog(_('vCard publication failed'), - _('There was an error while publishing your personal information, ' - 'try again later.')) + def vcard_not_published(self): + if self.message_id: + self.statusbar.remove_message(self.context_id, self.message_id) + self.message_id = self.statusbar.push(self.context_id, + _('Information NOT published')) + self.remove_statusbar_timeout_id = gobject.timeout_add_seconds(3, + self.remove_statusbar, self.message_id) + if self.update_progressbar_timeout_id is not None: + gobject.source_remove(self.update_progressbar_timeout_id) + self.progressbar.set_fraction(0) + self.update_progressbar_timeout_id = None + dialogs.InformationDialog(_('vCard publication failed'), + _('There was an error while publishing your personal information, ' + 'try again later.')) - def on_cancel_button_clicked(self, widget): - self.window.destroy() - -# vim: se ts=3: + def on_cancel_button_clicked(self, widget): + self.window.destroy() diff --git a/src/remote_control.py b/src/remote_control.py index f50370708..5648edf04 100644 --- a/src/remote_control.py +++ b/src/remote_control.py @@ -37,10 +37,10 @@ from dialogs import AddNewContactWindow, NewChatDialog, JoinGroupchatWindow from common import dbus_support if dbus_support.supported: - import dbus - if dbus_support: - import dbus.service - import dbus.glib + import dbus + if dbus_support: + import dbus.service + import dbus.glib INTERFACE = 'org.gajim.dbus.RemoteInterface' OBJ_PATH = '/org/gajim/dbus/RemoteObject' @@ -64,650 +64,648 @@ DBUS_DICT_SS = lambda : dbus.Dictionary({}, signature="ss") DBUS_NONE = lambda : dbus.Int32(0) def get_dbus_struct(obj): - ''' recursively go through all the items and replace - them with their casted dbus equivalents - ''' - if obj is None: - return DBUS_NONE() - if isinstance(obj, (unicode, str)): - return DBUS_STRING(obj) - if isinstance(obj, int): - return DBUS_INT32(obj) - if isinstance(obj, float): - return DBUS_DOUBLE(obj) - if isinstance(obj, bool): - return DBUS_BOOLEAN(obj) - if isinstance(obj, (list, tuple)): - result = dbus.Array([get_dbus_struct(i) for i in obj], - signature='v') - if result == []: - return DBUS_NONE() - return result - if isinstance(obj, dict): - result = DBUS_DICT_SV() - for key, value in obj.items(): - result[DBUS_STRING(key)] = get_dbus_struct(value) - if result == {}: - return DBUS_NONE() - return result - # unknown type - return DBUS_NONE() + ''' recursively go through all the items and replace + them with their casted dbus equivalents + ''' + if obj is None: + return DBUS_NONE() + if isinstance(obj, (unicode, str)): + return DBUS_STRING(obj) + if isinstance(obj, int): + return DBUS_INT32(obj) + if isinstance(obj, float): + return DBUS_DOUBLE(obj) + if isinstance(obj, bool): + return DBUS_BOOLEAN(obj) + if isinstance(obj, (list, tuple)): + result = dbus.Array([get_dbus_struct(i) for i in obj], + signature='v') + if result == []: + return DBUS_NONE() + return result + if isinstance(obj, dict): + result = DBUS_DICT_SV() + for key, value in obj.items(): + result[DBUS_STRING(key)] = get_dbus_struct(value) + if result == {}: + return DBUS_NONE() + return result + # unknown type + return DBUS_NONE() class Remote: - def __init__(self): - self.signal_object = None - session_bus = dbus_support.session_bus.SessionBus() + def __init__(self): + self.signal_object = None + session_bus = dbus_support.session_bus.SessionBus() - bus_name = dbus.service.BusName(SERVICE, bus=session_bus) - self.signal_object = SignalObject(bus_name) + bus_name = dbus.service.BusName(SERVICE, bus=session_bus) + self.signal_object = SignalObject(bus_name) - def raise_signal(self, signal, arg): - if self.signal_object: - try: - getattr(self.signal_object, signal)(get_dbus_struct(arg)) - except UnicodeDecodeError: - pass # ignore error when we fail to announce on dbus + def raise_signal(self, signal, arg): + if self.signal_object: + try: + getattr(self.signal_object, signal)(get_dbus_struct(arg)) + except UnicodeDecodeError: + pass # ignore error when we fail to announce on dbus class SignalObject(dbus.service.Object): - ''' Local object definition for /org/gajim/dbus/RemoteObject. - (This docstring is not be visible, because the clients can access only the remote object.)''' + ''' Local object definition for /org/gajim/dbus/RemoteObject. + (This docstring is not be visible, because the clients can access only the remote object.)''' - def __init__(self, bus_name): - self.first_show = True - self.vcard_account = None + def __init__(self, bus_name): + self.first_show = True + self.vcard_account = None - # register our dbus API - dbus.service.Object.__init__(self, bus_name, OBJ_PATH) + # register our dbus API + dbus.service.Object.__init__(self, bus_name, OBJ_PATH) - @dbus.service.signal(INTERFACE, signature='av') - def Roster(self, account_and_data): - pass + @dbus.service.signal(INTERFACE, signature='av') + def Roster(self, account_and_data): + pass - @dbus.service.signal(INTERFACE, signature='av') - def AccountPresence(self, status_and_account): - pass + @dbus.service.signal(INTERFACE, signature='av') + def AccountPresence(self, status_and_account): + pass - @dbus.service.signal(INTERFACE, signature='av') - def ContactPresence(self, account_and_array): - pass + @dbus.service.signal(INTERFACE, signature='av') + def ContactPresence(self, account_and_array): + pass - @dbus.service.signal(INTERFACE, signature='av') - def ContactAbsence(self, account_and_array): - pass + @dbus.service.signal(INTERFACE, signature='av') + def ContactAbsence(self, account_and_array): + pass - @dbus.service.signal(INTERFACE, signature='av') - def ContactStatus(self, account_and_array): - pass + @dbus.service.signal(INTERFACE, signature='av') + def ContactStatus(self, account_and_array): + pass - @dbus.service.signal(INTERFACE, signature='av') - def NewMessage(self, account_and_array): - pass + @dbus.service.signal(INTERFACE, signature='av') + def NewMessage(self, account_and_array): + pass - @dbus.service.signal(INTERFACE, signature='av') - def Subscribe(self, account_and_array): - pass + @dbus.service.signal(INTERFACE, signature='av') + def Subscribe(self, account_and_array): + pass - @dbus.service.signal(INTERFACE, signature='av') - def Subscribed(self, account_and_array): - pass + @dbus.service.signal(INTERFACE, signature='av') + def Subscribed(self, account_and_array): + pass - @dbus.service.signal(INTERFACE, signature='av') - def Unsubscribed(self, account_and_jid): - pass + @dbus.service.signal(INTERFACE, signature='av') + def Unsubscribed(self, account_and_jid): + pass - @dbus.service.signal(INTERFACE, signature='av') - def NewAccount(self, account_and_array): - pass + @dbus.service.signal(INTERFACE, signature='av') + def NewAccount(self, account_and_array): + pass - @dbus.service.signal(INTERFACE, signature='av') - def VcardInfo(self, account_and_vcard): - pass + @dbus.service.signal(INTERFACE, signature='av') + def VcardInfo(self, account_and_vcard): + pass - @dbus.service.signal(INTERFACE, signature='av') - def LastStatusTime(self, account_and_array): - pass + @dbus.service.signal(INTERFACE, signature='av') + def LastStatusTime(self, account_and_array): + pass - @dbus.service.signal(INTERFACE, signature='av') - def OsInfo(self, account_and_array): - pass + @dbus.service.signal(INTERFACE, signature='av') + def OsInfo(self, account_and_array): + pass - @dbus.service.signal(INTERFACE, signature='av') - def EntityTime(self, account_and_array): - pass + @dbus.service.signal(INTERFACE, signature='av') + def EntityTime(self, account_and_array): + pass - @dbus.service.signal(INTERFACE, signature='av') - def GCPresence(self, account_and_array): - pass + @dbus.service.signal(INTERFACE, signature='av') + def GCPresence(self, account_and_array): + pass - @dbus.service.signal(INTERFACE, signature='av') - def GCMessage(self, account_and_array): - pass + @dbus.service.signal(INTERFACE, signature='av') + def GCMessage(self, account_and_array): + pass - @dbus.service.signal(INTERFACE, signature='av') - def RosterInfo(self, account_and_array): - pass + @dbus.service.signal(INTERFACE, signature='av') + def RosterInfo(self, account_and_array): + pass - @dbus.service.signal(INTERFACE, signature='av') - def NewGmail(self, account_and_array): - pass + @dbus.service.signal(INTERFACE, signature='av') + def NewGmail(self, account_and_array): + pass - def raise_signal(self, signal, arg): - '''raise a signal, with a single argument of unspecified type - Instead of obj.raise_signal("Foo", bar), use obj.Foo(bar).''' - getattr(self, signal)(arg) + def raise_signal(self, signal, arg): + '''raise a signal, with a single argument of unspecified type + Instead of obj.raise_signal("Foo", bar), use obj.Foo(bar).''' + getattr(self, signal)(arg) - @dbus.service.method(INTERFACE, in_signature='s', out_signature='s') - def get_status(self, account): - '''Returns status (show to be exact) which is the global one - unless account is given''' - if not account: - # If user did not ask for account, returns the global status - return DBUS_STRING(helpers.get_global_show()) - # return show for the given account - index = gajim.connections[account].connected - return DBUS_STRING(gajim.SHOW_LIST[index]) + @dbus.service.method(INTERFACE, in_signature='s', out_signature='s') + def get_status(self, account): + '''Returns status (show to be exact) which is the global one + unless account is given''' + if not account: + # If user did not ask for account, returns the global status + return DBUS_STRING(helpers.get_global_show()) + # return show for the given account + index = gajim.connections[account].connected + return DBUS_STRING(gajim.SHOW_LIST[index]) - @dbus.service.method(INTERFACE, in_signature='s', out_signature='s') - def get_status_message(self, account): - '''Returns status which is the global one - unless account is given''' - if not account: - # If user did not ask for account, returns the global status - return DBUS_STRING(str(helpers.get_global_status())) - # return show for the given account - status = gajim.connections[account].status - return DBUS_STRING(status) + @dbus.service.method(INTERFACE, in_signature='s', out_signature='s') + def get_status_message(self, account): + '''Returns status which is the global one + unless account is given''' + if not account: + # If user did not ask for account, returns the global status + return DBUS_STRING(str(helpers.get_global_status())) + # return show for the given account + status = gajim.connections[account].status + return DBUS_STRING(status) - def _get_account_and_contact(self, account, jid): - '''get the account (if not given) and contact instance from jid''' - connected_account = None - contact = None - accounts = gajim.contacts.get_accounts() - # if there is only one account in roster, take it as default - # if user did not ask for account - if not account and len(accounts) == 1: - account = accounts[0] - if account: - if gajim.connections[account].connected > 1: # account is connected - connected_account = account - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - else: - for account in accounts: - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - if contact and gajim.connections[account].connected > 1: - # account is connected - connected_account = account - break - if not contact: - contact = jid + def _get_account_and_contact(self, account, jid): + '''get the account (if not given) and contact instance from jid''' + connected_account = None + contact = None + accounts = gajim.contacts.get_accounts() + # if there is only one account in roster, take it as default + # if user did not ask for account + if not account and len(accounts) == 1: + account = accounts[0] + if account: + if gajim.connections[account].connected > 1: # account is connected + connected_account = account + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + else: + for account in accounts: + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + if contact and gajim.connections[account].connected > 1: + # account is connected + connected_account = account + break + if not contact: + contact = jid - return connected_account, contact + return connected_account, contact - def _get_account_for_groupchat(self, account, room_jid): - '''get the account which is connected to groupchat (if not given) - or check if the given account is connected to the groupchat''' - connected_account = None - accounts = gajim.contacts.get_accounts() - # if there is only one account in roster, take it as default - # if user did not ask for account - if not account and len(accounts) == 1: - account = accounts[0] - if account: - if gajim.connections[account].connected > 1 and \ - room_jid in gajim.gc_connected[account] and \ - gajim.gc_connected[account][room_jid]: - # account and groupchat are connected - connected_account = account - else: - for account in accounts: - if gajim.connections[account].connected > 1 and \ - room_jid in gajim.gc_connected[account] and \ - gajim.gc_connected[account][room_jid]: - # account and groupchat are connected - connected_account = account - break - return connected_account + def _get_account_for_groupchat(self, account, room_jid): + '''get the account which is connected to groupchat (if not given) + or check if the given account is connected to the groupchat''' + connected_account = None + accounts = gajim.contacts.get_accounts() + # if there is only one account in roster, take it as default + # if user did not ask for account + if not account and len(accounts) == 1: + account = accounts[0] + if account: + if gajim.connections[account].connected > 1 and \ + room_jid in gajim.gc_connected[account] and \ + gajim.gc_connected[account][room_jid]: + # account and groupchat are connected + connected_account = account + else: + for account in accounts: + if gajim.connections[account].connected > 1 and \ + room_jid in gajim.gc_connected[account] and \ + gajim.gc_connected[account][room_jid]: + # account and groupchat are connected + connected_account = account + break + return connected_account - @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') - def send_file(self, file_path, jid, account): - '''send file, located at 'file_path' to 'jid', using account - (optional) 'account' ''' - jid = self._get_real_jid(jid, account) - connected_account, contact = self._get_account_and_contact(account, jid) + @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') + def send_file(self, file_path, jid, account): + '''send file, located at 'file_path' to 'jid', using account + (optional) 'account' ''' + jid = self._get_real_jid(jid, account) + connected_account, contact = self._get_account_and_contact(account, jid) - if connected_account: - if file_path.startswith('file://'): - file_path=file_path[7:] - if os.path.isfile(file_path): # is it file? - gajim.interface.instances['file_transfers'].send_file( - connected_account, contact, file_path) - return DBUS_BOOLEAN(True) - return DBUS_BOOLEAN(False) + if connected_account: + if file_path.startswith('file://'): + file_path=file_path[7:] + if os.path.isfile(file_path): # is it file? + gajim.interface.instances['file_transfers'].send_file( + connected_account, contact, file_path) + return DBUS_BOOLEAN(True) + return DBUS_BOOLEAN(False) - def _send_message(self, jid, message, keyID, account, type_ = 'chat', - subject = None): - '''can be called from send_chat_message (default when send_message) - or send_single_message''' - if not jid or not message: - return DBUS_BOOLEAN(False) - if not keyID: - keyID = '' + def _send_message(self, jid, message, keyID, account, type_ = 'chat', + subject = None): + '''can be called from send_chat_message (default when send_message) + or send_single_message''' + if not jid or not message: + return DBUS_BOOLEAN(False) + if not keyID: + keyID = '' - connected_account, contact = self._get_account_and_contact(account, jid) - if connected_account: - connection = gajim.connections[connected_account] - connection.send_message(jid, message, keyID, type_, subject) - return DBUS_BOOLEAN(True) - return DBUS_BOOLEAN(False) + connected_account, contact = self._get_account_and_contact(account, jid) + if connected_account: + connection = gajim.connections[connected_account] + connection.send_message(jid, message, keyID, type_, subject) + return DBUS_BOOLEAN(True) + return DBUS_BOOLEAN(False) - @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='b') - def send_chat_message(self, jid, message, keyID, account): - '''Send chat 'message' to 'jid', using account (optional) 'account'. - if keyID is specified, encrypt the message with the pgp key ''' - jid = self._get_real_jid(jid, account) - return self._send_message(jid, message, keyID, account) + @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='b') + def send_chat_message(self, jid, message, keyID, account): + '''Send chat 'message' to 'jid', using account (optional) 'account'. + if keyID is specified, encrypt the message with the pgp key ''' + jid = self._get_real_jid(jid, account) + return self._send_message(jid, message, keyID, account) - @dbus.service.method(INTERFACE, in_signature='sssss', out_signature='b') - def send_single_message(self, jid, subject, message, keyID, account): - '''Send single 'message' to 'jid', using account (optional) 'account'. - if keyID is specified, encrypt the message with the pgp key ''' - jid = self._get_real_jid(jid, account) - return self._send_message(jid, message, keyID, account, type, subject) + @dbus.service.method(INTERFACE, in_signature='sssss', out_signature='b') + def send_single_message(self, jid, subject, message, keyID, account): + '''Send single 'message' to 'jid', using account (optional) 'account'. + if keyID is specified, encrypt the message with the pgp key ''' + jid = self._get_real_jid(jid, account) + return self._send_message(jid, message, keyID, account, type, subject) - @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') - def send_groupchat_message(self, room_jid, message, account): - '''Send 'message' to groupchat 'room_jid', - using account (optional) 'account'.''' - if not room_jid or not message: - return DBUS_BOOLEAN(False) - connected_account = self._get_account_for_groupchat(account, room_jid) - if connected_account: - connection = gajim.connections[connected_account] - connection.send_gc_message(room_jid, message) - return DBUS_BOOLEAN(True) - return DBUS_BOOLEAN(False) + @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') + def send_groupchat_message(self, room_jid, message, account): + '''Send 'message' to groupchat 'room_jid', + using account (optional) 'account'.''' + if not room_jid or not message: + return DBUS_BOOLEAN(False) + connected_account = self._get_account_for_groupchat(account, room_jid) + if connected_account: + connection = gajim.connections[connected_account] + connection.send_gc_message(room_jid, message) + return DBUS_BOOLEAN(True) + return DBUS_BOOLEAN(False) - @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') - def open_chat(self, jid, account, message): - '''Shows the tabbed window for new message to 'jid', using account - (optional) 'account' ''' - if not jid: - raise dbus_support.MissingArgument() - jid = self._get_real_jid(jid, account) - try: - jid = helpers.parse_jid(jid) - except Exception: - # Jid is not conform, ignore it - return DBUS_BOOLEAN(False) + @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') + def open_chat(self, jid, account, message): + '''Shows the tabbed window for new message to 'jid', using account + (optional) 'account' ''' + if not jid: + raise dbus_support.MissingArgument() + jid = self._get_real_jid(jid, account) + try: + jid = helpers.parse_jid(jid) + except Exception: + # Jid is not conform, ignore it + return DBUS_BOOLEAN(False) - if account: - accounts = [account] - else: - accounts = gajim.connections.keys() - if len(accounts) == 1: - account = accounts[0] - connected_account = None - first_connected_acct = None - for acct in accounts: - if gajim.connections[acct].connected > 1: # account is online - contact = gajim.contacts.get_first_contact_from_jid(acct, jid) - if gajim.interface.msg_win_mgr.has_window(jid, acct): - connected_account = acct - break - # jid is in roster - elif contact: - connected_account = acct - break - # we send the message to jid not in roster, because account is - # specified, or there is only one account - elif account: - connected_account = acct - elif first_connected_acct is None: - first_connected_acct = acct + if account: + accounts = [account] + else: + accounts = gajim.connections.keys() + if len(accounts) == 1: + account = accounts[0] + connected_account = None + first_connected_acct = None + for acct in accounts: + if gajim.connections[acct].connected > 1: # account is online + contact = gajim.contacts.get_first_contact_from_jid(acct, jid) + if gajim.interface.msg_win_mgr.has_window(jid, acct): + connected_account = acct + break + # jid is in roster + elif contact: + connected_account = acct + break + # we send the message to jid not in roster, because account is + # specified, or there is only one account + elif account: + connected_account = acct + elif first_connected_acct is None: + first_connected_acct = acct - # if jid is not a conntact, open-chat with first connected account - if connected_account is None and first_connected_acct: - connected_account = first_connected_acct + # if jid is not a conntact, open-chat with first connected account + if connected_account is None and first_connected_acct: + connected_account = first_connected_acct - if connected_account: - gajim.interface.new_chat_from_jid(connected_account, jid, message) - # preserve the 'steal focus preservation' - win = gajim.interface.msg_win_mgr.get_window(jid, - connected_account).window - if win.get_property('visible'): - win.window.focus(gtk.get_current_event_time()) - return DBUS_BOOLEAN(True) - return DBUS_BOOLEAN(False) + if connected_account: + gajim.interface.new_chat_from_jid(connected_account, jid, message) + # preserve the 'steal focus preservation' + win = gajim.interface.msg_win_mgr.get_window(jid, + connected_account).window + if win.get_property('visible'): + win.window.focus(gtk.get_current_event_time()) + return DBUS_BOOLEAN(True) + return DBUS_BOOLEAN(False) - @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') - def change_status(self, status, message, account): - ''' change_status(status, message, account). account is optional - - if not specified status is changed for all accounts. ''' - if status not in ('offline', 'online', 'chat', - 'away', 'xa', 'dnd', 'invisible'): - return DBUS_BOOLEAN(False) - if account: - gobject.idle_add(gajim.interface.roster.send_status, account, - status, message) - else: - # account not specified, so change the status of all accounts - for acc in gajim.contacts.get_accounts(): - if not gajim.config.get_per('accounts', acc, - 'sync_with_global_status'): - continue - gobject.idle_add(gajim.interface.roster.send_status, acc, - status, message) - return DBUS_BOOLEAN(False) + @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') + def change_status(self, status, message, account): + ''' change_status(status, message, account). account is optional - + if not specified status is changed for all accounts. ''' + if status not in ('offline', 'online', 'chat', + 'away', 'xa', 'dnd', 'invisible'): + return DBUS_BOOLEAN(False) + if account: + gobject.idle_add(gajim.interface.roster.send_status, account, + status, message) + else: + # account not specified, so change the status of all accounts + for acc in gajim.contacts.get_accounts(): + if not gajim.config.get_per('accounts', acc, + 'sync_with_global_status'): + continue + gobject.idle_add(gajim.interface.roster.send_status, acc, + status, message) + return DBUS_BOOLEAN(False) - @dbus.service.method(INTERFACE, in_signature='ss', out_signature='') - def set_priority(self, prio, account): - ''' set_priority(prio, account). account is optional - - if not specified priority is changed for all accounts. that are synced - with global status''' - if account: - gajim.config.set_per('accounts', account, 'priority', prio) - show = gajim.SHOW_LIST[gajim.connections[account].connected] - status = gajim.connections[account].status - gobject.idle_add(gajim.connections[account].change_status, show, - status) - else: - # account not specified, so change prio of all accounts - for acc in gajim.contacts.get_accounts(): - if not gajim.account_is_connected(acc): - continue - if not gajim.config.get_per('accounts', acc, - 'sync_with_global_status'): - continue - gajim.config.set_per('accounts', acc, 'priority', prio) - show = gajim.SHOW_LIST[gajim.connections[acc].connected] - status = gajim.connections[acc].status - gobject.idle_add(gajim.connections[acc].change_status, show, - status) + @dbus.service.method(INTERFACE, in_signature='ss', out_signature='') + def set_priority(self, prio, account): + ''' set_priority(prio, account). account is optional - + if not specified priority is changed for all accounts. that are synced + with global status''' + if account: + gajim.config.set_per('accounts', account, 'priority', prio) + show = gajim.SHOW_LIST[gajim.connections[account].connected] + status = gajim.connections[account].status + gobject.idle_add(gajim.connections[account].change_status, show, + status) + else: + # account not specified, so change prio of all accounts + for acc in gajim.contacts.get_accounts(): + if not gajim.account_is_connected(acc): + continue + if not gajim.config.get_per('accounts', acc, + 'sync_with_global_status'): + continue + gajim.config.set_per('accounts', acc, 'priority', prio) + show = gajim.SHOW_LIST[gajim.connections[acc].connected] + status = gajim.connections[acc].status + gobject.idle_add(gajim.connections[acc].change_status, show, + status) - @dbus.service.method(INTERFACE, in_signature='', out_signature='') - def show_next_pending_event(self): - '''Show the window(s) with next pending event in tabbed/group chats.''' - if gajim.events.get_nb_events(): - gajim.interface.systray.handle_first_event() + @dbus.service.method(INTERFACE, in_signature='', out_signature='') + def show_next_pending_event(self): + '''Show the window(s) with next pending event in tabbed/group chats.''' + if gajim.events.get_nb_events(): + gajim.interface.systray.handle_first_event() - @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{sv}') - def contact_info(self, jid): - '''get vcard info for a contact. Return cached value of the vcard. - ''' - if not isinstance(jid, unicode): - jid = unicode(jid) - if not jid: - raise dbus_support.MissingArgument() - jid = self._get_real_jid(jid) + @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{sv}') + def contact_info(self, jid): + '''get vcard info for a contact. Return cached value of the vcard. + ''' + if not isinstance(jid, unicode): + jid = unicode(jid) + if not jid: + raise dbus_support.MissingArgument() + jid = self._get_real_jid(jid) - cached_vcard = gajim.connections.values()[0].get_cached_vcard(jid) - if cached_vcard: - return get_dbus_struct(cached_vcard) + cached_vcard = gajim.connections.values()[0].get_cached_vcard(jid) + if cached_vcard: + return get_dbus_struct(cached_vcard) - # return empty dict - return DBUS_DICT_SV() + # return empty dict + return DBUS_DICT_SV() - @dbus.service.method(INTERFACE, in_signature='', out_signature='as') - def list_accounts(self): - '''list register accounts''' - result = gajim.contacts.get_accounts() - result_array = dbus.Array([], signature='s') - if result and len(result) > 0: - for account in result: - result_array.append(DBUS_STRING(account)) - return result_array + @dbus.service.method(INTERFACE, in_signature='', out_signature='as') + def list_accounts(self): + '''list register accounts''' + result = gajim.contacts.get_accounts() + result_array = dbus.Array([], signature='s') + if result and len(result) > 0: + for account in result: + result_array.append(DBUS_STRING(account)) + return result_array - @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{ss}') - def account_info(self, account): - '''show info on account: resource, jid, nick, prio, message''' - result = DBUS_DICT_SS() - if account in gajim.connections: - # account is valid - con = gajim.connections[account] - index = con.connected - result['status'] = DBUS_STRING(gajim.SHOW_LIST[index]) - result['name'] = DBUS_STRING(con.name) - result['jid'] = DBUS_STRING(gajim.get_jid_from_account(con.name)) - result['message'] = DBUS_STRING(con.status) - result['priority'] = DBUS_STRING(unicode(con.priority)) - result['resource'] = DBUS_STRING(unicode(gajim.config.get_per( - 'accounts', con.name, 'resource'))) - return result + @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{ss}') + def account_info(self, account): + '''show info on account: resource, jid, nick, prio, message''' + result = DBUS_DICT_SS() + if account in gajim.connections: + # account is valid + con = gajim.connections[account] + index = con.connected + result['status'] = DBUS_STRING(gajim.SHOW_LIST[index]) + result['name'] = DBUS_STRING(con.name) + result['jid'] = DBUS_STRING(gajim.get_jid_from_account(con.name)) + result['message'] = DBUS_STRING(con.status) + result['priority'] = DBUS_STRING(unicode(con.priority)) + result['resource'] = DBUS_STRING(unicode(gajim.config.get_per( + 'accounts', con.name, 'resource'))) + return result - @dbus.service.method(INTERFACE, in_signature='s', out_signature='aa{sv}') - def list_contacts(self, account): - '''list all contacts in the roster. If the first argument is specified, - then return the contacts for the specified account''' - result = dbus.Array([], signature='aa{sv}') - accounts = gajim.contacts.get_accounts() - if len(accounts) == 0: - return result - if account: - accounts_to_search = [account] - else: - accounts_to_search = accounts - for acct in accounts_to_search: - if acct in accounts: - for jid in gajim.contacts.get_jid_list(acct): - item = self._contacts_as_dbus_structure( - gajim.contacts.get_contacts(acct, jid)) - if item: - result.append(item) - return result + @dbus.service.method(INTERFACE, in_signature='s', out_signature='aa{sv}') + def list_contacts(self, account): + '''list all contacts in the roster. If the first argument is specified, + then return the contacts for the specified account''' + result = dbus.Array([], signature='aa{sv}') + accounts = gajim.contacts.get_accounts() + if len(accounts) == 0: + return result + if account: + accounts_to_search = [account] + else: + accounts_to_search = accounts + for acct in accounts_to_search: + if acct in accounts: + for jid in gajim.contacts.get_jid_list(acct): + item = self._contacts_as_dbus_structure( + gajim.contacts.get_contacts(acct, jid)) + if item: + result.append(item) + return result - @dbus.service.method(INTERFACE, in_signature='', out_signature='') - def toggle_roster_appearance(self): - ''' shows/hides the roster window ''' - win = gajim.interface.roster.window - if win.get_property('visible'): - gobject.idle_add(win.hide) - else: - win.present() - # preserve the 'steal focus preservation' - if self._is_first(): - win.window.focus(gtk.get_current_event_time()) - else: - win.window.focus(long(time())) + @dbus.service.method(INTERFACE, in_signature='', out_signature='') + def toggle_roster_appearance(self): + ''' shows/hides the roster window ''' + win = gajim.interface.roster.window + if win.get_property('visible'): + gobject.idle_add(win.hide) + else: + win.present() + # preserve the 'steal focus preservation' + if self._is_first(): + win.window.focus(gtk.get_current_event_time()) + else: + win.window.focus(long(time())) - @dbus.service.method(INTERFACE, in_signature='', out_signature='') - def toggle_ipython(self): - ''' shows/hides the ipython window ''' - win = gajim.ipython_window - if win: - if win.window.is_visible(): - gobject.idle_add(win.hide) - else: - win.show_all() - win.present() - else: - gajim.interface.create_ipython_window() + @dbus.service.method(INTERFACE, in_signature='', out_signature='') + def toggle_ipython(self): + ''' shows/hides the ipython window ''' + win = gajim.ipython_window + if win: + if win.window.is_visible(): + gobject.idle_add(win.hide) + else: + win.show_all() + win.present() + else: + gajim.interface.create_ipython_window() - @dbus.service.method(INTERFACE, in_signature='', out_signature='a{ss}') - def prefs_list(self): - prefs_dict = DBUS_DICT_SS() - def get_prefs(data, name, path, value): - if value is None: - return - key = '' - if path is not None: - for node in path: - key += node + '#' - key += name - prefs_dict[DBUS_STRING(key)] = DBUS_STRING(value[1]) - gajim.config.foreach(get_prefs) - return prefs_dict + @dbus.service.method(INTERFACE, in_signature='', out_signature='a{ss}') + def prefs_list(self): + prefs_dict = DBUS_DICT_SS() + def get_prefs(data, name, path, value): + if value is None: + return + key = '' + if path is not None: + for node in path: + key += node + '#' + key += name + prefs_dict[DBUS_STRING(key)] = DBUS_STRING(value[1]) + gajim.config.foreach(get_prefs) + return prefs_dict - @dbus.service.method(INTERFACE, in_signature='', out_signature='b') - def prefs_store(self): - try: - gajim.interface.save_config() - except Exception, e: - return DBUS_BOOLEAN(False) - return DBUS_BOOLEAN(True) + @dbus.service.method(INTERFACE, in_signature='', out_signature='b') + def prefs_store(self): + try: + gajim.interface.save_config() + except Exception, e: + return DBUS_BOOLEAN(False) + return DBUS_BOOLEAN(True) - @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') - def prefs_del(self, key): - if not key: - return DBUS_BOOLEAN(False) - key_path = key.split('#', 2) - if len(key_path) != 3: - return DBUS_BOOLEAN(False) - if key_path[2] == '*': - gajim.config.del_per(key_path[0], key_path[1]) - else: - gajim.config.del_per(key_path[0], key_path[1], key_path[2]) - return DBUS_BOOLEAN(True) + @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') + def prefs_del(self, key): + if not key: + return DBUS_BOOLEAN(False) + key_path = key.split('#', 2) + if len(key_path) != 3: + return DBUS_BOOLEAN(False) + if key_path[2] == '*': + gajim.config.del_per(key_path[0], key_path[1]) + else: + gajim.config.del_per(key_path[0], key_path[1], key_path[2]) + return DBUS_BOOLEAN(True) - @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') - def prefs_put(self, key): - if not key: - return DBUS_BOOLEAN(False) - key_path = key.split('#', 2) - if len(key_path) < 3: - subname, value = key.split('=', 1) - gajim.config.set(subname, value) - return DBUS_BOOLEAN(True) - subname, value = key_path[2].split('=', 1) - gajim.config.set_per(key_path[0], key_path[1], subname, value) - return DBUS_BOOLEAN(True) + @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') + def prefs_put(self, key): + if not key: + return DBUS_BOOLEAN(False) + key_path = key.split('#', 2) + if len(key_path) < 3: + subname, value = key.split('=', 1) + gajim.config.set(subname, value) + return DBUS_BOOLEAN(True) + subname, value = key_path[2].split('=', 1) + gajim.config.set_per(key_path[0], key_path[1], subname, value) + return DBUS_BOOLEAN(True) - @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b') - def add_contact(self, jid, account): - if account: - if account in gajim.connections and \ - gajim.connections[account].connected > 1: - # if given account is active, use it - AddNewContactWindow(account = account, jid = jid) - else: - # wrong account - return DBUS_BOOLEAN(False) - else: - # if account is not given, show account combobox - AddNewContactWindow(account = None, jid = jid) - return DBUS_BOOLEAN(True) + @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b') + def add_contact(self, jid, account): + if account: + if account in gajim.connections and \ + gajim.connections[account].connected > 1: + # if given account is active, use it + AddNewContactWindow(account = account, jid = jid) + else: + # wrong account + return DBUS_BOOLEAN(False) + else: + # if account is not given, show account combobox + AddNewContactWindow(account = None, jid = jid) + return DBUS_BOOLEAN(True) - @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b') - def remove_contact(self, jid, account): - jid = self._get_real_jid(jid, account) - accounts = gajim.contacts.get_accounts() + @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b') + def remove_contact(self, jid, account): + jid = self._get_real_jid(jid, account) + accounts = gajim.contacts.get_accounts() - # if there is only one account in roster, take it as default - if account: - accounts = [account] - contact_exists = False - for account in accounts: - contacts = gajim.contacts.get_contacts(account, jid) - if contacts: - gajim.connections[account].unsubscribe(jid) - for contact in contacts: - gajim.interface.roster.remove_contact(contact, account) - gajim.contacts.remove_jid(account, jid) - contact_exists = True - return DBUS_BOOLEAN(contact_exists) + # if there is only one account in roster, take it as default + if account: + accounts = [account] + contact_exists = False + for account in accounts: + contacts = gajim.contacts.get_contacts(account, jid) + if contacts: + gajim.connections[account].unsubscribe(jid) + for contact in contacts: + gajim.interface.roster.remove_contact(contact, account) + gajim.contacts.remove_jid(account, jid) + contact_exists = True + return DBUS_BOOLEAN(contact_exists) - def _is_first(self): - if self.first_show: - self.first_show = False - return True - return False + def _is_first(self): + if self.first_show: + self.first_show = False + return True + return False - def _get_real_jid(self, jid, account = None): - '''get the real jid from the given one: removes xmpp: or get jid from nick - if account is specified, search only in this account - ''' - if account: - accounts = [account] - else: - accounts = gajim.connections.keys() - if jid.startswith('xmpp:'): - return jid[5:] # len('xmpp:') = 5 - nick_in_roster = None # Is jid a nick ? - for account in accounts: - # Does jid exists in roster of one account ? - if gajim.contacts.get_contacts(account, jid): - return jid - if not nick_in_roster: - # look in all contact if one has jid as nick - for jid_ in gajim.contacts.get_jid_list(account): - c = gajim.contacts.get_contacts(account, jid_) - if c[0].name == jid: - nick_in_roster = jid_ - break - if nick_in_roster: - # We have not found jid in roster, but we found is as a nick - return nick_in_roster - # We have not found it as jid nor as nick, probably a not in roster jid - return jid + def _get_real_jid(self, jid, account = None): + '''get the real jid from the given one: removes xmpp: or get jid from nick + if account is specified, search only in this account + ''' + if account: + accounts = [account] + else: + accounts = gajim.connections.keys() + if jid.startswith('xmpp:'): + return jid[5:] # len('xmpp:') = 5 + nick_in_roster = None # Is jid a nick ? + for account in accounts: + # Does jid exists in roster of one account ? + if gajim.contacts.get_contacts(account, jid): + return jid + if not nick_in_roster: + # look in all contact if one has jid as nick + for jid_ in gajim.contacts.get_jid_list(account): + c = gajim.contacts.get_contacts(account, jid_) + if c[0].name == jid: + nick_in_roster = jid_ + break + if nick_in_roster: + # We have not found jid in roster, but we found is as a nick + return nick_in_roster + # We have not found it as jid nor as nick, probably a not in roster jid + return jid - def _contacts_as_dbus_structure(self, contacts): - ''' get info from list of Contact objects and create dbus dict ''' - if not contacts: - return None - prim_contact = None # primary contact - for contact in contacts: - if prim_contact is None or contact.priority > prim_contact.priority: - prim_contact = contact - contact_dict = DBUS_DICT_SV() - contact_dict['name'] = DBUS_STRING(prim_contact.name) - contact_dict['show'] = DBUS_STRING(prim_contact.show) - contact_dict['jid'] = DBUS_STRING(prim_contact.jid) - if prim_contact.keyID: - keyID = None - if len(prim_contact.keyID) == 8: - keyID = prim_contact.keyID - elif len(prim_contact.keyID) == 16: - keyID = prim_contact.keyID[8:] - if keyID: - contact_dict['openpgp'] = keyID - contact_dict['resources'] = dbus.Array([], signature='(sis)') - for contact in contacts: - resource_props = dbus.Struct((DBUS_STRING(contact.resource), - dbus.Int32(contact.priority), DBUS_STRING(contact.status))) - contact_dict['resources'].append(resource_props) - contact_dict['groups'] = dbus.Array([], signature='(s)') - for group in prim_contact.groups: - contact_dict['groups'].append((DBUS_STRING(group),)) - return contact_dict + def _contacts_as_dbus_structure(self, contacts): + ''' get info from list of Contact objects and create dbus dict ''' + if not contacts: + return None + prim_contact = None # primary contact + for contact in contacts: + if prim_contact is None or contact.priority > prim_contact.priority: + prim_contact = contact + contact_dict = DBUS_DICT_SV() + contact_dict['name'] = DBUS_STRING(prim_contact.name) + contact_dict['show'] = DBUS_STRING(prim_contact.show) + contact_dict['jid'] = DBUS_STRING(prim_contact.jid) + if prim_contact.keyID: + keyID = None + if len(prim_contact.keyID) == 8: + keyID = prim_contact.keyID + elif len(prim_contact.keyID) == 16: + keyID = prim_contact.keyID[8:] + if keyID: + contact_dict['openpgp'] = keyID + contact_dict['resources'] = dbus.Array([], signature='(sis)') + for contact in contacts: + resource_props = dbus.Struct((DBUS_STRING(contact.resource), + dbus.Int32(contact.priority), DBUS_STRING(contact.status))) + contact_dict['resources'].append(resource_props) + contact_dict['groups'] = dbus.Array([], signature='(s)') + for group in prim_contact.groups: + contact_dict['groups'].append((DBUS_STRING(group),)) + return contact_dict - @dbus.service.method(INTERFACE, in_signature='', out_signature='s') - def get_unread_msgs_number(self): - return DBUS_STRING(str(gajim.events.get_nb_events())) + @dbus.service.method(INTERFACE, in_signature='', out_signature='s') + def get_unread_msgs_number(self): + return DBUS_STRING(str(gajim.events.get_nb_events())) - @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') - def start_chat(self, account): - if not account: - # error is shown in gajim-remote check_arguments(..) - return DBUS_BOOLEAN(False) - NewChatDialog(account) - return DBUS_BOOLEAN(True) + @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') + def start_chat(self, account): + if not account: + # error is shown in gajim-remote check_arguments(..) + return DBUS_BOOLEAN(False) + NewChatDialog(account) + return DBUS_BOOLEAN(True) - @dbus.service.method(INTERFACE, in_signature='ss', out_signature='') - def send_xml(self, xml, account): - if account: - gajim.connections[account].send_stanza(str(xml)) - else: - for acc in gajim.contacts.get_accounts(): - gajim.connections[acc].send_stanza(str(xml)) + @dbus.service.method(INTERFACE, in_signature='ss', out_signature='') + def send_xml(self, xml, account): + if account: + gajim.connections[account].send_stanza(str(xml)) + else: + for acc in gajim.contacts.get_accounts(): + gajim.connections[acc].send_stanza(str(xml)) - @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='') - def join_room(self, room_jid, nick, password, account): - if not account: - # get the first connected account - accounts = gajim.connections.keys() - for acct in accounts: - if gajim.account_is_connected(acct): - account = acct - break - if not account: - return - if not nick: - nick = '' - gajim.interface.instances[account]['join_gc'] = \ - JoinGroupchatWindow(account, room_jid, nick) - else: - gajim.interface.join_gc_room(account, room_jid, nick, password) - -# vim: se ts=3: + @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='') + def join_room(self, room_jid, nick, password, account): + if not account: + # get the first connected account + accounts = gajim.connections.keys() + for acct in accounts: + if gajim.account_is_connected(acct): + account = acct + break + if not account: + return + if not nick: + nick = '' + gajim.interface.instances[account]['join_gc'] = \ + JoinGroupchatWindow(account, room_jid, nick) + else: + gajim.interface.join_gc_room(account, room_jid, nick, password) diff --git a/src/roster_window.py b/src/roster_window.py index dce525e61..19a11e5d1 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -64,7 +64,7 @@ from message_window import MessageWindowMgr from common import dbus_support if dbus_support.supported: - import dbus + import dbus from common.xmpp.protocol import NS_FILE from common.pep import MOODS, ACTIVITIES @@ -84,5757 +84,5755 @@ C_PADLOCK_PIXBUF, # use for account row only ) = range(10) class RosterWindow: - '''Class for main window of the GTK+ interface''' + '''Class for main window of the GTK+ interface''' - def _get_account_iter(self, name, model=None): - ''' - Return the gtk.TreeIter of the given account or None - if not found. + def _get_account_iter(self, name, model=None): + ''' + Return the gtk.TreeIter of the given account or None + if not found. - Keyword arguments: - name -- the account name - model -- the data model (default TreeFilterModel) - ''' - if not model: - model = self.modelfilter - if model is None: - return - account_iter = model.get_iter_root() - if self.regroup: - return account_iter - while account_iter: - account_name = model[account_iter][C_ACCOUNT] - if account_name and name == account_name.decode('utf-8'): - break - account_iter = model.iter_next(account_iter) - return account_iter + Keyword arguments: + name -- the account name + model -- the data model (default TreeFilterModel) + ''' + if not model: + model = self.modelfilter + if model is None: + return + account_iter = model.get_iter_root() + if self.regroup: + return account_iter + while account_iter: + account_name = model[account_iter][C_ACCOUNT] + if account_name and name == account_name.decode('utf-8'): + break + account_iter = model.iter_next(account_iter) + return account_iter - def _get_group_iter(self, name, account, account_iter=None, model=None): - ''' - Return the gtk.TreeIter of the given group or None if not found. + def _get_group_iter(self, name, account, account_iter=None, model=None): + ''' + Return the gtk.TreeIter of the given group or None if not found. - Keyword arguments: - name -- the group name - account -- the account name - account_iter -- the iter of the account the model (default None) - model -- the data model (default TreeFilterModel) + Keyword arguments: + name -- the group name + account -- the account name + account_iter -- the iter of the account the model (default None) + model -- the data model (default TreeFilterModel) - ''' - if not model: - model = self.modelfilter - if not account_iter: - account_iter = self._get_account_iter(account, model) - group_iter = model.iter_children(account_iter) - # C_NAME column contacts the pango escaped group name - while group_iter: - group_name = model[group_iter][C_JID].decode('utf-8') - if name == group_name: - break - group_iter = model.iter_next(group_iter) - return group_iter + ''' + if not model: + model = self.modelfilter + if not account_iter: + account_iter = self._get_account_iter(account, model) + group_iter = model.iter_children(account_iter) + # C_NAME column contacts the pango escaped group name + while group_iter: + group_name = model[group_iter][C_JID].decode('utf-8') + if name == group_name: + break + group_iter = model.iter_next(group_iter) + return group_iter - def _get_self_contact_iter(self, account, model=None): - ''' Return the gtk.TreeIter of SelfContact or None if not found. + def _get_self_contact_iter(self, account, model=None): + ''' Return the gtk.TreeIter of SelfContact or None if not found. - Keyword arguments: - account -- the account of SelfContact - model -- the data model (default TreeFilterModel) + Keyword arguments: + account -- the account of SelfContact + model -- the data model (default TreeFilterModel) - ''' + ''' - if not model: - model = self.modelfilter - iterAcct = self._get_account_iter(account, model) - iterC = model.iter_children(iterAcct) + if not model: + model = self.modelfilter + iterAcct = self._get_account_iter(account, model) + iterC = model.iter_children(iterAcct) - # There might be several SelfContacts in merged account view - while iterC: - if model[iterC][C_TYPE] != 'self_contact': - break - iter_account = model[iterC][C_ACCOUNT] - if account == iter_account.decode('utf-8'): - return iterC - iterC = model.iter_next(iterC) - return None + # There might be several SelfContacts in merged account view + while iterC: + if model[iterC][C_TYPE] != 'self_contact': + break + iter_account = model[iterC][C_ACCOUNT] + if account == iter_account.decode('utf-8'): + return iterC + iterC = model.iter_next(iterC) + return None - def _get_contact_iter(self, jid, account, contact=None, model=None): - ''' Return a list of gtk.TreeIter of the given contact. + def _get_contact_iter(self, jid, account, contact=None, model=None): + ''' Return a list of gtk.TreeIter of the given contact. - Keyword arguments: - jid -- the jid without resource - account -- the account - contact -- the contact (default None) - model -- the data model (default TreeFilterModel) + Keyword arguments: + jid -- the jid without resource + account -- the account + contact -- the contact (default None) + model -- the data model (default TreeFilterModel) - ''' - if not model: - model = self.modelfilter - # when closing Gajim model can be none (async pbs?) - if model is None: - return [] + ''' + if not model: + model = self.modelfilter + # when closing Gajim model can be none (async pbs?) + if model is None: + return [] - if jid == gajim.get_jid_from_account(account): - contact_iter = self._get_self_contact_iter(account, model) - if contact_iter: - return [contact_iter] - else: - return [] + if jid == gajim.get_jid_from_account(account): + contact_iter = self._get_self_contact_iter(account, model) + if contact_iter: + return [contact_iter] + else: + return [] - if not contact: - contact = gajim.contacts.get_first_contact_from_jid(account, jid) - if not contact: - # We don't know this contact - return [] + if not contact: + contact = gajim.contacts.get_first_contact_from_jid(account, jid) + if not contact: + # We don't know this contact + return [] - acct = self._get_account_iter(account, model) - found = [] # the contact iters. One per group - for group in contact.get_shown_groups(): - group_iter = self._get_group_iter(group, account, acct, model) - contact_iter = model.iter_children(group_iter) + acct = self._get_account_iter(account, model) + found = [] # the contact iters. One per group + for group in contact.get_shown_groups(): + group_iter = self._get_group_iter(group, account, acct, model) + contact_iter = model.iter_children(group_iter) - while contact_iter: - # Loop over all contacts in this group - iter_jid = model[contact_iter][C_JID] - if iter_jid and jid == iter_jid.decode('utf-8') and \ - account == model[contact_iter][C_ACCOUNT].decode('utf-8'): - # only one iter per group - found.append(contact_iter) - contact_iter = None - elif model.iter_has_child(contact_iter): - # it's a big brother and has children - contact_iter = model.iter_children(contact_iter) - else: - # try to find next contact: - # other contact in this group or - # brother contact - next_contact_iter = model.iter_next(contact_iter) - if next_contact_iter: - contact_iter = next_contact_iter - else: - # It's the last one. - # Go up if we are big brother - parent_iter = model.iter_parent(contact_iter) - if parent_iter and model[parent_iter][C_TYPE] == 'contact': - contact_iter = model.iter_next(parent_iter) - else: - # we tested all - # contacts in this group - contact_iter = None - return found + while contact_iter: + # Loop over all contacts in this group + iter_jid = model[contact_iter][C_JID] + if iter_jid and jid == iter_jid.decode('utf-8') and \ + account == model[contact_iter][C_ACCOUNT].decode('utf-8'): + # only one iter per group + found.append(contact_iter) + contact_iter = None + elif model.iter_has_child(contact_iter): + # it's a big brother and has children + contact_iter = model.iter_children(contact_iter) + else: + # try to find next contact: + # other contact in this group or + # brother contact + next_contact_iter = model.iter_next(contact_iter) + if next_contact_iter: + contact_iter = next_contact_iter + else: + # It's the last one. + # Go up if we are big brother + parent_iter = model.iter_parent(contact_iter) + if parent_iter and model[parent_iter][C_TYPE] == 'contact': + contact_iter = model.iter_next(parent_iter) + else: + # we tested all + # contacts in this group + contact_iter = None + return found - def _iter_is_separator(self, model, titer): - ''' Return True if the given iter is a separator. + def _iter_is_separator(self, model, titer): + ''' Return True if the given iter is a separator. - Keyword arguments: - model -- the data model - iter -- the gtk.TreeIter to test - ''' - if model[titer][0] == 'SEPARATOR': - return True - return False + Keyword arguments: + model -- the data model + iter -- the gtk.TreeIter to test + ''' + if model[titer][0] == 'SEPARATOR': + return True + return False - def _iter_contact_rows(self, model=None): - '''Iterate over all contact rows in given model. + def _iter_contact_rows(self, model=None): + '''Iterate over all contact rows in given model. - Keyword argument - model -- the data model (default TreeFilterModel) - ''' - if not model: - model = self.modelfilter - account_iter = model.get_iter_root() - while account_iter: - group_iter = model.iter_children(account_iter) - while group_iter: - contact_iter = model.iter_children(group_iter) - while contact_iter: - yield model[contact_iter] - contact_iter = model.iter_next( - contact_iter) - group_iter = model.iter_next(group_iter) - account_iter = model.iter_next(account_iter) + Keyword argument + model -- the data model (default TreeFilterModel) + ''' + if not model: + model = self.modelfilter + account_iter = model.get_iter_root() + while account_iter: + group_iter = model.iter_children(account_iter) + while group_iter: + contact_iter = model.iter_children(group_iter) + while contact_iter: + yield model[contact_iter] + contact_iter = model.iter_next( + contact_iter) + group_iter = model.iter_next(group_iter) + account_iter = model.iter_next(account_iter) ############################################################################# ### Methods for adding and removing roster window items ############################################################################# - def add_account(self, account): - ''' - Add account to roster and draw it. Do nothing if it is - already in. - ''' - if self._get_account_iter(account): - # Will happen on reconnect or for merged accounts - return - - if self.regroup: - # Merged accounts view - show = helpers.get_global_show() - self.model.append(None, [ - gajim.interface.jabber_state_images['16'][show], - _('Merged accounts'), 'account', '', 'all', - None, None, None, None, None]) - else: - show = gajim.SHOW_LIST[gajim.connections[account].connected] - our_jid = gajim.get_jid_from_account(account) - - tls_pixbuf = None - if gajim.account_is_securely_connected(account): - # the only way to create a pixbuf from stock - tls_pixbuf = self.window.render_icon( - gtk.STOCK_DIALOG_AUTHENTICATION, - gtk.ICON_SIZE_MENU) - - self.model.append(None, [ - gajim.interface.jabber_state_images['16'][show], - gobject.markup_escape_text(account), 'account', - our_jid, account, None, None, None, None, - tls_pixbuf]) - - self.draw_account(account) - - - def add_account_contacts(self, account): - '''Add all contacts and groups of the given account to roster, - draw them and account. - ''' - self.starting = True - jids = gajim.contacts.get_jid_list(account) - - self.tree.freeze_child_notify() - for jid in jids: - self.add_contact(jid, account) - self.tree.thaw_child_notify() - - # Do not freeze the GUI when drawing the contacts - if jids: - # Overhead is big, only invoke when needed - self._idle_draw_jids_of_account(jids, account) - - # Draw all known groups - for group in gajim.groups[account]: - self.draw_group(group, account) - self.draw_account(account) - self.starting = False - - - def _add_entity(self, contact, account, groups=None, - big_brother_contact=None, big_brother_account=None): - '''Add the given contact to roster data model. - - Contact is added regardless if he is already in roster or not. - Return list of newly added iters. - - Keyword arguments: - contact -- the contact to add - account -- the contacts account - groups -- list of groups to add the contact to. - (default groups in contact.get_shown_groups()). - Parameter ignored when big_brother_contact is specified. - big_brother_contact -- if specified contact is added as child - big_brother_contact. (default None) - ''' - added_iters = [] - if big_brother_contact: - # Add contact under big brother - - parent_iters = self._get_contact_iter( - big_brother_contact.jid, big_brother_account, - big_brother_contact, self.model) - assert len(parent_iters) > 0, 'Big brother is not yet in roster!' - - # Do not confuse get_contact_iter: Sync groups of family members - contact.groups = big_brother_contact.get_shown_groups()[:] - - for child_iter in parent_iters: - it = self.model.append(child_iter, (None, contact.get_shown_name(), - 'contact', contact.jid, account, None, None, None, None, None)) - added_iters.append(it) - else: - # We are a normal contact. Add us to our groups. - if not groups: - groups = contact.get_shown_groups() - for group in groups: - child_iterG = self._get_group_iter(group, account, - model = self.model) - if not child_iterG: - # Group is not yet in roster, add it! - child_iterA = self._get_account_iter(account, self.model) - child_iterG = self.model.append(child_iterA, - [gajim.interface.jabber_state_images['16']['closed'], - gobject.markup_escape_text(group), - 'group', group, account, None, None, None, None, None]) - self.draw_group(group, account) - - if contact.is_transport(): - typestr = 'agent' - elif contact.is_groupchat(): - typestr = 'groupchat' - else: - typestr = 'contact' - - # we add some values here. see draw_contact - # for more - i_ = self.model.append(child_iterG, (None, - contact.get_shown_name(), typestr, - contact.jid, account, None, None, None, - None, None)) - added_iters.append(i_) - - # Restore the group expand state - if account + group in self.collapsed_rows: - is_expanded = False - else: - is_expanded = True - if group not in gajim.groups[account]: - gajim.groups[account][group] = {'expand': is_expanded} - - assert len(added_iters), '%s has not been added to roster!' % contact.jid - return added_iters - - def _remove_entity(self, contact, account, groups=None): - '''Remove the given contact from roster data model. - - Empty groups after contact removal are removed too. - Return False if contact still has children and deletion was - not performed. - Return True on success. - - Keyword arguments: - contact -- the contact to add - account -- the contacts account - groups -- list of groups to remove the contact from. - ''' - iters = self._get_contact_iter(contact.jid, account, contact, self.model) - assert iters, '%s shall be removed but is not in roster' % contact.jid - - parent_iter = self.model.iter_parent(iters[0]) - parent_type = self.model[parent_iter][C_TYPE] - - if groups: - # Only remove from specified groups - all_iters = iters[:] - group_iters = [self._get_group_iter(group, account) - for group in groups] - iters = [titer for titer in all_iters - if self.model.iter_parent(titer) in group_iters] - - iter_children = self.model.iter_children(iters[0]) - - if iter_children: - # We have children. We cannot be removed! - return False - else: - # Remove us and empty groups from the model - for i in iters: - assert self.model[i][C_JID] == contact.jid and \ - self.model[i][C_ACCOUNT] == account, \ - "Invalidated iters of %s" % contact.jid - - parent_i = self.model.iter_parent(i) - - if parent_type == 'group' and \ - self.model.iter_n_children(parent_i) == 1: - group = self.model[parent_i][C_JID].decode('utf-8') - if group in gajim.groups[account]: - del gajim.groups[account][group] - self.model.remove(parent_i) - else: - self.model.remove(i) - return True - - def _add_metacontact_family(self, family, account): - ''' - Add the give Metacontact family to roster data model. - - Add Big Brother to his groups and all others under him. - Return list of all added (contact, account) tuples with - Big Brother as first element. - - Keyword arguments: - family -- the family, see Contacts.get_metacontacts_family() - ''' - - nearby_family, big_brother_jid, big_brother_account = \ - self._get_nearby_family_and_big_brother(family, account) - big_brother_contact = gajim.contacts.get_first_contact_from_jid( - big_brother_account, big_brother_jid) - - assert len(self._get_contact_iter(big_brother_jid, - big_brother_account, big_brother_contact, self.model)) == 0, \ - 'Big brother %s already in roster\n Family: %s' \ - % (big_brother_jid, family) - self._add_entity(big_brother_contact, big_brother_account) - - brothers = [] - # Filter family members - for data in nearby_family: - _account = data['account'] - _jid = data['jid'] - _contact = gajim.contacts.get_first_contact_from_jid( - _account, _jid) - - if not _contact or _contact == big_brother_contact: - # Corresponding account is not connected - # or brother already added - continue - - assert len(self._get_contact_iter(_jid, _account, - _contact, self.model)) == 0, \ - "%s already in roster.\n Family: %s" % (_jid, nearby_family) - self._add_entity(_contact, _account, - big_brother_contact = big_brother_contact, - big_brother_account = big_brother_account) - brothers.append((_contact, _account)) - - brothers.insert(0, (big_brother_contact, big_brother_account)) - return brothers - - def _remove_metacontact_family(self, family, account): - ''' - Remove the given Metacontact family from roster data model. - - See Contacts.get_metacontacts_family() and - RosterWindow._remove_entity() - ''' - nearby_family = self._get_nearby_family_and_big_brother( - family, account)[0] - - # Family might has changed (actual big brother not on top). - # Remove childs first then big brother - family_in_roster = False - for data in nearby_family: - _account = data['account'] - _jid = data['jid'] - _contact = gajim.contacts.get_first_contact_from_jid(_account, _jid) - - iters = self._get_contact_iter(_jid, _account, _contact, self.model) - if not iters or not _contact: - # Family might not be up to date. - # Only try to remove what is actually in the roster - continue - assert iters, '%s shall be removed but is not in roster \ - \n Family: %s' % (_jid, family) - - family_in_roster = True - - parent_iter = self.model.iter_parent(iters[0]) - parent_type = self.model[parent_iter][C_TYPE] - - if parent_type != 'contact': - # The contact on top - old_big_account = _account - old_big_contact = _contact - old_big_jid = _jid - continue - - ok = self._remove_entity(_contact, _account) - assert ok, '%s was not removed' % _jid - assert len(self._get_contact_iter(_jid, _account, _contact, - self.model)) == 0, '%s is removed but still in roster' % _jid - - if not family_in_roster: - return False - - assert old_big_jid, 'No Big Brother in nearby family % (Family: %)' % \ - (nearby_family, family) - iters = self._get_contact_iter(old_big_jid, old_big_account, - old_big_contact, self.model) - assert len(iters) > 0, 'Old Big Brother %s is not in roster anymore' % \ - old_big_jid - assert not self.model.iter_children(iters[0]),\ - 'Old Big Brother %s still has children' % old_big_jid - - ok = self._remove_entity(old_big_contact, old_big_account) - assert ok, "Old Big Brother %s not removed" % old_big_jid - assert len(self._get_contact_iter(old_big_jid, old_big_account, - old_big_contact, self.model)) == 0,\ - 'Old Big Brother %s is removed but still in roster' % old_big_jid - - return True - - - def _recalibrate_metacontact_family(self, family, account): - '''Regroup metacontact family if necessary.''' - - brothers = [] - nearby_family, big_brother_jid, big_brother_account = \ - self._get_nearby_family_and_big_brother(family, account) - big_brother_contact = gajim.contacts.get_contact(big_brother_account, - big_brother_jid) - child_iters = self._get_contact_iter(big_brother_jid, big_brother_account, - model=self.model) - if child_iters: - parent_iter = self.model.iter_parent(child_iters[0]) - parent_type = self.model[parent_iter][C_TYPE] - - # Check if the current BigBrother has even been before. - if parent_type == 'contact': - for data in nearby_family: - # recalibrate after remove to keep highlight - if data['jid'] in gajim.to_be_removed[data['account']]: - return - - self._remove_metacontact_family(family, account) - brothers = self._add_metacontact_family(family, account) - - for c, acc in brothers: - self.draw_completely(c.jid, acc) - - # Check is small brothers are under the big brother - for child in nearby_family: - _jid = child['jid'] - _account = child['account'] - if _account == big_brother_account and _jid == big_brother_jid: - continue - child_iters = self._get_contact_iter(_jid, _account, model=self.model) - if not child_iters: - continue - parent_iter = self.model.iter_parent(child_iters[0]) - parent_type = self.model[parent_iter][C_TYPE] - if parent_type != 'contact': - _contact = gajim.contacts.get_contact(_account, _jid) - self._remove_entity(_contact, _account) - self._add_entity(_contact, _account, groups=None, - big_brother_contact=big_brother_contact, - big_brother_account=big_brother_account) - - def _get_nearby_family_and_big_brother(self, family, account): - return gajim.contacts.get_nearby_family_and_big_brother(family, account) - - def _add_self_contact(self, account): - '''Add account's SelfContact to roster and draw it and the account. - - Return the SelfContact contact instance - ''' - jid = gajim.get_jid_from_account(account) - contact = gajim.contacts.get_first_contact_from_jid(account, jid) - - assert len(self._get_contact_iter(jid, account, contact, self.model)) == \ - 0, 'Self contact %s already in roster' % jid - - child_iterA = self._get_account_iter(account, self.model) - self.model.append(child_iterA, (None, gajim.nicks[account], - 'self_contact', jid, account, None, None, None, None, - None)) - - self.draw_completely(jid, account) - self.draw_account(account) - - return contact - - def redraw_metacontacts(self, account): - for family in gajim.contacts.iter_metacontacts_families(account): - self._recalibrate_metacontact_family(family, account) - - def add_contact(self, jid, account): - '''Add contact to roster and draw him. - - Add contact to all its group and redraw the groups, the contact and the - account. If it's a Metacontact, add and draw the whole family. - Do nothing if the contact is already in roster. - - Return the added contact instance. If it is a Metacontact return - Big Brother. - - Keyword arguments: - jid -- the contact's jid or SelfJid to add SelfContact - account -- the corresponding account. - - ''' - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - if len(self._get_contact_iter(jid, account, contact, self.model)): - # If contact already in roster, do nothing - return - - if jid == gajim.get_jid_from_account(account): - show_self_contact = gajim.config.get('show_self_contact') - if show_self_contact == 'never': - return - if (contact.resource != gajim.connections[account].server_resource and\ - show_self_contact == 'when_other_resource') or show_self_contact == \ - 'always': - return self._add_self_contact(account) - return - - is_observer = contact.is_observer() - if is_observer: - # if he has a tag, remove it - gajim.contacts.remove_metacontact(account, jid) - - # Add contact to roster - family = gajim.contacts.get_metacontacts_family(account, jid) - contacts = [] - if family: - # We have a family. So we are a metacontact. - # Add all family members that we shall be grouped with - if self.regroup: - # remove existing family members to regroup them - self._remove_metacontact_family(family, account) - contacts = self._add_metacontact_family(family, account) - else: - # We are a normal contact - contacts = [(contact, account),] - self._add_entity(contact, account) - - # Draw the contact and its groups contact - if not self.starting: - for c, acc in contacts: - self.draw_completely(c.jid, acc) - for group in contact.get_shown_groups(): - self.draw_group(group, account) - self._adjust_group_expand_collapse_state(group, account) - self.draw_account(account) - - return contacts[0][0] # it's contact/big brother with highest priority - - def remove_contact(self, jid, account, force=False, backend=False): - '''Remove contact from roster. - - Remove contact from all its group. Remove empty groups or redraw - otherwise. - Draw the account. - If it's a Metacontact, remove the whole family. - Do nothing if the contact is not in roster. - - Keyword arguments: - jid -- the contact's jid or SelfJid to remove SelfContact - account -- the corresponding account. - force -- remove contact even it has pending evens (Default False) - backend -- also remove contact instance (Default False) - - ''' - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - if not contact: - return - - if not force and (self.contact_has_pending_roster_events(contact, - account) or gajim.interface.msg_win_mgr.get_control(jid, account)): - # Contact has pending events or window - #TODO: or single message windows? Bur they are not listed for the - # moment - key = (jid, account) - if not key in self.contacts_to_be_removed: - self.contacts_to_be_removed[key] = {'backend': backend} - # if more pending event, don't remove from roster - if self.contact_has_pending_roster_events(contact, account): - return False - - iters = self._get_contact_iter(jid, account, contact, self.model) - if iters: - # no more pending events - # Remove contact from roster directly - family = gajim.contacts.get_metacontacts_family(account, jid) - if family: - # We have a family. So we are a metacontact. - self._remove_metacontact_family(family, account) - else: - self._remove_entity(contact, account) - - if backend and (not gajim.interface.msg_win_mgr.get_control(jid, account)\ - or force): - # If a window is still opened: don't remove contact instance - # Remove contact before redrawing, otherwise the old - # numbers will still be show - gajim.contacts.remove_jid(account, jid, remove_meta=True) - if iters: - rest_of_family = [data for data in family - if account != data['account'] or jid != data['jid']] - if rest_of_family: - # reshow the rest of the family - brothers = self._add_metacontact_family(rest_of_family, account) - for c, acc in brothers: - self.draw_completely(c.jid, acc) - - if iters: - # Draw all groups of the contact - for group in contact.get_shown_groups(): - self.draw_group(group, account) - self.draw_account(account) - - return True - - def rename_self_contact(self, old_jid, new_jid, account): - '''Rename the self_contact jid - - Keyword arguments: - old_jid -- our old jid - new_jid -- our new jid - account -- the corresponding account. - ''' - gajim.contacts.change_contact_jid(old_jid, new_jid, account) - self_iter = self._get_self_contact_iter(account, model=self.model) - if not self_iter: - return - self.model[self_iter][C_JID] = new_jid - self.draw_contact(new_jid, account) - - def add_groupchat(self, jid, account, status=''): - '''Add groupchat to roster and draw it. - Return the added contact instance. - ''' - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - # Do not show gc if we are disconnected and minimize it - if gajim.account_is_connected(account): - show = 'online' - else: - show = 'offline' - status = '' - - if contact is None: - gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid, account) - if gc_control: - # there is a window that we can minimize - gajim.interface.minimized_controls[account][jid] = gc_control - name = gc_control.name - elif jid in gajim.interface.minimized_controls[account]: - name = gajim.interface.minimized_controls[account][jid].name - else: - name = jid.split('@')[0] - # New groupchat - #GCMIN - contact = gajim.contacts.create_contact(jid=jid, account=account, name=name, - groups=[_('Groupchats')], show=show, status=status, sub='none') - gajim.contacts.add_contact(account, contact) - self.add_contact(jid, account) - else: - if jid not in gajim.interface.minimized_controls[account]: - # there is a window that we can minimize - gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid, - account) - gajim.interface.minimized_controls[account][jid] = gc_control - contact.show = show - contact.status = status - self.adjust_and_draw_contact_context(jid, account) - - return contact - - - def remove_groupchat(self, jid, account): - '''Remove groupchat from roster and redraw account and group.''' - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - if contact.is_groupchat(): - if jid in gajim.interface.minimized_controls[account]: - del gajim.interface.minimized_controls[account][jid] - self.remove_contact(jid, account, force=True, backend=True) - return True - else: - return False - - - # FIXME: This function is yet unused! Port to new API - def add_transport(self, jid, account): - '''Add transport to roster and draw it. - Return the added contact instance.''' - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - if contact is None: - #TRANSP - contact = gajim.contacts.create_contact(jid=jid, account=account, name=jid, - groups=[_('Transports')], show='offline', status='offline', - sub='from') - gajim.contacts.add_contact(account, contact) - self.add_contact(jid, account) - return contact - - def remove_transport(self, jid, account): - '''Remove transport from roster and redraw account and group.''' - self.remove_contact(jid, account, force=True, backend=True) - return True - - def rename_group(self, old_name, new_name, account): - """ - rename a roster group - """ - if old_name == new_name: - return - - # Groups may not change name from or to a special groups - for g in helpers.special_groups: - if g in (new_name, old_name): - return - - # update all contacts in the given group - if self.regroup: - accounts = gajim.connections.keys() - else: - accounts = [account,] - - for acc in accounts: - changed_contacts = [] - for jid in gajim.contacts.get_jid_list(acc): - contact = gajim.contacts.get_first_contact_from_jid(acc, jid) - if old_name not in contact.groups: - continue - - self.remove_contact(jid, acc, force=True) - - contact.groups.remove(old_name) - if new_name not in contact.groups: - contact.groups.append(new_name) - - changed_contacts.append({'jid':jid, 'name':contact.name, - 'groups':contact.groups}) - - gajim.connections[acc].update_contacts(changed_contacts) - - for c in changed_contacts: - self.add_contact(c['jid'], acc) - - self._adjust_group_expand_collapse_state(new_name, acc) - - self.draw_group(old_name, acc) - self.draw_group(new_name, acc) - - - def add_contact_to_groups(self, jid, account, groups, update=True): - '''Add contact to given groups and redraw them. - - Contact on server is updated too. When the contact has a family, - the action will be performed for all members. - - Keyword Arguments: - jid -- the jid - account -- the corresponding account - groups -- list of Groups to add the contact to. - update -- update contact on the server - - ''' - self.remove_contact(jid, account, force=True) - for contact in gajim.contacts.get_contacts(account, jid): - for group in groups: - if group not in contact.groups: - # we might be dropped from meta to group - contact.groups.append(group) - if update: - gajim.connections[account].update_contact(jid, contact.name, - contact.groups) - - self.add_contact(jid, account) - - for group in groups: - self._adjust_group_expand_collapse_state(group, account) - - def remove_contact_from_groups(self, jid, account, groups, update=True): - '''Remove contact from given groups and redraw them. - - Contact on server is updated too. When the contact has a family, - the action will be performed for all members. - - Keyword Arguments: - jid -- the jid - account -- the corresponding account - groups -- list of Groups to remove the contact from - update -- update contact on the server - - ''' - self.remove_contact(jid, account, force=True) - for contact in gajim.contacts.get_contacts(account, jid): - for group in groups: - if group in contact.groups: - # Needed when we remove from "General" or "Observers" - contact.groups.remove(group) - if update: - gajim.connections[account].update_contact(jid, contact.name, - contact.groups) - self.add_contact(jid, account) - - # Also redraw old groups - for group in groups: - self.draw_group(group, account) - - # FIXME: maybe move to gajim.py - def remove_newly_added(self, jid, account): - if jid in gajim.newly_added[account]: - gajim.newly_added[account].remove(jid) - self.draw_contact(jid, account) - - # FIXME: maybe move to gajim.py - def remove_to_be_removed(self, jid, account): - if account not in gajim.interface.instances: - # Account has been deleted during the timeout that called us - return - if jid in gajim.newly_added[account]: - return - if jid in gajim.to_be_removed[account]: - gajim.to_be_removed[account].remove(jid) - family = gajim.contacts.get_metacontacts_family(account, jid) - if family: - # Peform delayed recalibration - self._recalibrate_metacontact_family(family, account) - self.draw_contact(jid, account) - - #FIXME: integrate into add_contact() - def add_to_not_in_the_roster(self, account, jid, nick='', resource=''): - keyID = '' - attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() - if jid in attached_keys: - keyID = attached_keys[attached_keys.index(jid) + 1] - contact = gajim.contacts.create_not_in_roster_contact(jid=jid, - account=account, resource=resource, name=nick, keyID=keyID) - gajim.contacts.add_contact(account, contact) - self.add_contact(contact.jid, account) - return contact + def add_account(self, account): + ''' + Add account to roster and draw it. Do nothing if it is + already in. + ''' + if self._get_account_iter(account): + # Will happen on reconnect or for merged accounts + return + + if self.regroup: + # Merged accounts view + show = helpers.get_global_show() + self.model.append(None, [ + gajim.interface.jabber_state_images['16'][show], + _('Merged accounts'), 'account', '', 'all', + None, None, None, None, None]) + else: + show = gajim.SHOW_LIST[gajim.connections[account].connected] + our_jid = gajim.get_jid_from_account(account) + + tls_pixbuf = None + if gajim.account_is_securely_connected(account): + # the only way to create a pixbuf from stock + tls_pixbuf = self.window.render_icon( + gtk.STOCK_DIALOG_AUTHENTICATION, + gtk.ICON_SIZE_MENU) + + self.model.append(None, [ + gajim.interface.jabber_state_images['16'][show], + gobject.markup_escape_text(account), 'account', + our_jid, account, None, None, None, None, + tls_pixbuf]) + + self.draw_account(account) + + + def add_account_contacts(self, account): + '''Add all contacts and groups of the given account to roster, + draw them and account. + ''' + self.starting = True + jids = gajim.contacts.get_jid_list(account) + + self.tree.freeze_child_notify() + for jid in jids: + self.add_contact(jid, account) + self.tree.thaw_child_notify() + + # Do not freeze the GUI when drawing the contacts + if jids: + # Overhead is big, only invoke when needed + self._idle_draw_jids_of_account(jids, account) + + # Draw all known groups + for group in gajim.groups[account]: + self.draw_group(group, account) + self.draw_account(account) + self.starting = False + + + def _add_entity(self, contact, account, groups=None, + big_brother_contact=None, big_brother_account=None): + '''Add the given contact to roster data model. + + Contact is added regardless if he is already in roster or not. + Return list of newly added iters. + + Keyword arguments: + contact -- the contact to add + account -- the contacts account + groups -- list of groups to add the contact to. + (default groups in contact.get_shown_groups()). + Parameter ignored when big_brother_contact is specified. + big_brother_contact -- if specified contact is added as child + big_brother_contact. (default None) + ''' + added_iters = [] + if big_brother_contact: + # Add contact under big brother + + parent_iters = self._get_contact_iter( + big_brother_contact.jid, big_brother_account, + big_brother_contact, self.model) + assert len(parent_iters) > 0, 'Big brother is not yet in roster!' + + # Do not confuse get_contact_iter: Sync groups of family members + contact.groups = big_brother_contact.get_shown_groups()[:] + + for child_iter in parent_iters: + it = self.model.append(child_iter, (None, contact.get_shown_name(), + 'contact', contact.jid, account, None, None, None, None, None)) + added_iters.append(it) + else: + # We are a normal contact. Add us to our groups. + if not groups: + groups = contact.get_shown_groups() + for group in groups: + child_iterG = self._get_group_iter(group, account, + model = self.model) + if not child_iterG: + # Group is not yet in roster, add it! + child_iterA = self._get_account_iter(account, self.model) + child_iterG = self.model.append(child_iterA, + [gajim.interface.jabber_state_images['16']['closed'], + gobject.markup_escape_text(group), + 'group', group, account, None, None, None, None, None]) + self.draw_group(group, account) + + if contact.is_transport(): + typestr = 'agent' + elif contact.is_groupchat(): + typestr = 'groupchat' + else: + typestr = 'contact' + + # we add some values here. see draw_contact + # for more + i_ = self.model.append(child_iterG, (None, + contact.get_shown_name(), typestr, + contact.jid, account, None, None, None, + None, None)) + added_iters.append(i_) + + # Restore the group expand state + if account + group in self.collapsed_rows: + is_expanded = False + else: + is_expanded = True + if group not in gajim.groups[account]: + gajim.groups[account][group] = {'expand': is_expanded} + + assert len(added_iters), '%s has not been added to roster!' % contact.jid + return added_iters + + def _remove_entity(self, contact, account, groups=None): + '''Remove the given contact from roster data model. + + Empty groups after contact removal are removed too. + Return False if contact still has children and deletion was + not performed. + Return True on success. + + Keyword arguments: + contact -- the contact to add + account -- the contacts account + groups -- list of groups to remove the contact from. + ''' + iters = self._get_contact_iter(contact.jid, account, contact, self.model) + assert iters, '%s shall be removed but is not in roster' % contact.jid + + parent_iter = self.model.iter_parent(iters[0]) + parent_type = self.model[parent_iter][C_TYPE] + + if groups: + # Only remove from specified groups + all_iters = iters[:] + group_iters = [self._get_group_iter(group, account) + for group in groups] + iters = [titer for titer in all_iters + if self.model.iter_parent(titer) in group_iters] + + iter_children = self.model.iter_children(iters[0]) + + if iter_children: + # We have children. We cannot be removed! + return False + else: + # Remove us and empty groups from the model + for i in iters: + assert self.model[i][C_JID] == contact.jid and \ + self.model[i][C_ACCOUNT] == account, \ + "Invalidated iters of %s" % contact.jid + + parent_i = self.model.iter_parent(i) + + if parent_type == 'group' and \ + self.model.iter_n_children(parent_i) == 1: + group = self.model[parent_i][C_JID].decode('utf-8') + if group in gajim.groups[account]: + del gajim.groups[account][group] + self.model.remove(parent_i) + else: + self.model.remove(i) + return True + + def _add_metacontact_family(self, family, account): + ''' + Add the give Metacontact family to roster data model. + + Add Big Brother to his groups and all others under him. + Return list of all added (contact, account) tuples with + Big Brother as first element. + + Keyword arguments: + family -- the family, see Contacts.get_metacontacts_family() + ''' + + nearby_family, big_brother_jid, big_brother_account = \ + self._get_nearby_family_and_big_brother(family, account) + big_brother_contact = gajim.contacts.get_first_contact_from_jid( + big_brother_account, big_brother_jid) + + assert len(self._get_contact_iter(big_brother_jid, + big_brother_account, big_brother_contact, self.model)) == 0, \ + 'Big brother %s already in roster\n Family: %s' \ + % (big_brother_jid, family) + self._add_entity(big_brother_contact, big_brother_account) + + brothers = [] + # Filter family members + for data in nearby_family: + _account = data['account'] + _jid = data['jid'] + _contact = gajim.contacts.get_first_contact_from_jid( + _account, _jid) + + if not _contact or _contact == big_brother_contact: + # Corresponding account is not connected + # or brother already added + continue + + assert len(self._get_contact_iter(_jid, _account, + _contact, self.model)) == 0, \ + "%s already in roster.\n Family: %s" % (_jid, nearby_family) + self._add_entity(_contact, _account, + big_brother_contact = big_brother_contact, + big_brother_account = big_brother_account) + brothers.append((_contact, _account)) + + brothers.insert(0, (big_brother_contact, big_brother_account)) + return brothers + + def _remove_metacontact_family(self, family, account): + ''' + Remove the given Metacontact family from roster data model. + + See Contacts.get_metacontacts_family() and + RosterWindow._remove_entity() + ''' + nearby_family = self._get_nearby_family_and_big_brother( + family, account)[0] + + # Family might has changed (actual big brother not on top). + # Remove childs first then big brother + family_in_roster = False + for data in nearby_family: + _account = data['account'] + _jid = data['jid'] + _contact = gajim.contacts.get_first_contact_from_jid(_account, _jid) + + iters = self._get_contact_iter(_jid, _account, _contact, self.model) + if not iters or not _contact: + # Family might not be up to date. + # Only try to remove what is actually in the roster + continue + assert iters, '%s shall be removed but is not in roster \ + \n Family: %s' % (_jid, family) + + family_in_roster = True + + parent_iter = self.model.iter_parent(iters[0]) + parent_type = self.model[parent_iter][C_TYPE] + + if parent_type != 'contact': + # The contact on top + old_big_account = _account + old_big_contact = _contact + old_big_jid = _jid + continue + + ok = self._remove_entity(_contact, _account) + assert ok, '%s was not removed' % _jid + assert len(self._get_contact_iter(_jid, _account, _contact, + self.model)) == 0, '%s is removed but still in roster' % _jid + + if not family_in_roster: + return False + + assert old_big_jid, 'No Big Brother in nearby family % (Family: %)' % \ + (nearby_family, family) + iters = self._get_contact_iter(old_big_jid, old_big_account, + old_big_contact, self.model) + assert len(iters) > 0, 'Old Big Brother %s is not in roster anymore' % \ + old_big_jid + assert not self.model.iter_children(iters[0]),\ + 'Old Big Brother %s still has children' % old_big_jid + + ok = self._remove_entity(old_big_contact, old_big_account) + assert ok, "Old Big Brother %s not removed" % old_big_jid + assert len(self._get_contact_iter(old_big_jid, old_big_account, + old_big_contact, self.model)) == 0,\ + 'Old Big Brother %s is removed but still in roster' % old_big_jid + + return True + + + def _recalibrate_metacontact_family(self, family, account): + '''Regroup metacontact family if necessary.''' + + brothers = [] + nearby_family, big_brother_jid, big_brother_account = \ + self._get_nearby_family_and_big_brother(family, account) + big_brother_contact = gajim.contacts.get_contact(big_brother_account, + big_brother_jid) + child_iters = self._get_contact_iter(big_brother_jid, big_brother_account, + model=self.model) + if child_iters: + parent_iter = self.model.iter_parent(child_iters[0]) + parent_type = self.model[parent_iter][C_TYPE] + + # Check if the current BigBrother has even been before. + if parent_type == 'contact': + for data in nearby_family: + # recalibrate after remove to keep highlight + if data['jid'] in gajim.to_be_removed[data['account']]: + return + + self._remove_metacontact_family(family, account) + brothers = self._add_metacontact_family(family, account) + + for c, acc in brothers: + self.draw_completely(c.jid, acc) + + # Check is small brothers are under the big brother + for child in nearby_family: + _jid = child['jid'] + _account = child['account'] + if _account == big_brother_account and _jid == big_brother_jid: + continue + child_iters = self._get_contact_iter(_jid, _account, model=self.model) + if not child_iters: + continue + parent_iter = self.model.iter_parent(child_iters[0]) + parent_type = self.model[parent_iter][C_TYPE] + if parent_type != 'contact': + _contact = gajim.contacts.get_contact(_account, _jid) + self._remove_entity(_contact, _account) + self._add_entity(_contact, _account, groups=None, + big_brother_contact=big_brother_contact, + big_brother_account=big_brother_account) + + def _get_nearby_family_and_big_brother(self, family, account): + return gajim.contacts.get_nearby_family_and_big_brother(family, account) + + def _add_self_contact(self, account): + '''Add account's SelfContact to roster and draw it and the account. + + Return the SelfContact contact instance + ''' + jid = gajim.get_jid_from_account(account) + contact = gajim.contacts.get_first_contact_from_jid(account, jid) + + assert len(self._get_contact_iter(jid, account, contact, self.model)) == \ + 0, 'Self contact %s already in roster' % jid + + child_iterA = self._get_account_iter(account, self.model) + self.model.append(child_iterA, (None, gajim.nicks[account], + 'self_contact', jid, account, None, None, None, None, + None)) + + self.draw_completely(jid, account) + self.draw_account(account) + + return contact + + def redraw_metacontacts(self, account): + for family in gajim.contacts.iter_metacontacts_families(account): + self._recalibrate_metacontact_family(family, account) + + def add_contact(self, jid, account): + '''Add contact to roster and draw him. + + Add contact to all its group and redraw the groups, the contact and the + account. If it's a Metacontact, add and draw the whole family. + Do nothing if the contact is already in roster. + + Return the added contact instance. If it is a Metacontact return + Big Brother. + + Keyword arguments: + jid -- the contact's jid or SelfJid to add SelfContact + account -- the corresponding account. + + ''' + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + if len(self._get_contact_iter(jid, account, contact, self.model)): + # If contact already in roster, do nothing + return + + if jid == gajim.get_jid_from_account(account): + show_self_contact = gajim.config.get('show_self_contact') + if show_self_contact == 'never': + return + if (contact.resource != gajim.connections[account].server_resource and\ + show_self_contact == 'when_other_resource') or show_self_contact == \ + 'always': + return self._add_self_contact(account) + return + + is_observer = contact.is_observer() + if is_observer: + # if he has a tag, remove it + gajim.contacts.remove_metacontact(account, jid) + + # Add contact to roster + family = gajim.contacts.get_metacontacts_family(account, jid) + contacts = [] + if family: + # We have a family. So we are a metacontact. + # Add all family members that we shall be grouped with + if self.regroup: + # remove existing family members to regroup them + self._remove_metacontact_family(family, account) + contacts = self._add_metacontact_family(family, account) + else: + # We are a normal contact + contacts = [(contact, account),] + self._add_entity(contact, account) + + # Draw the contact and its groups contact + if not self.starting: + for c, acc in contacts: + self.draw_completely(c.jid, acc) + for group in contact.get_shown_groups(): + self.draw_group(group, account) + self._adjust_group_expand_collapse_state(group, account) + self.draw_account(account) + + return contacts[0][0] # it's contact/big brother with highest priority + + def remove_contact(self, jid, account, force=False, backend=False): + '''Remove contact from roster. + + Remove contact from all its group. Remove empty groups or redraw + otherwise. + Draw the account. + If it's a Metacontact, remove the whole family. + Do nothing if the contact is not in roster. + + Keyword arguments: + jid -- the contact's jid or SelfJid to remove SelfContact + account -- the corresponding account. + force -- remove contact even it has pending evens (Default False) + backend -- also remove contact instance (Default False) + + ''' + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + if not contact: + return + + if not force and (self.contact_has_pending_roster_events(contact, + account) or gajim.interface.msg_win_mgr.get_control(jid, account)): + # Contact has pending events or window + #TODO: or single message windows? Bur they are not listed for the + # moment + key = (jid, account) + if not key in self.contacts_to_be_removed: + self.contacts_to_be_removed[key] = {'backend': backend} + # if more pending event, don't remove from roster + if self.contact_has_pending_roster_events(contact, account): + return False + + iters = self._get_contact_iter(jid, account, contact, self.model) + if iters: + # no more pending events + # Remove contact from roster directly + family = gajim.contacts.get_metacontacts_family(account, jid) + if family: + # We have a family. So we are a metacontact. + self._remove_metacontact_family(family, account) + else: + self._remove_entity(contact, account) + + if backend and (not gajim.interface.msg_win_mgr.get_control(jid, account)\ + or force): + # If a window is still opened: don't remove contact instance + # Remove contact before redrawing, otherwise the old + # numbers will still be show + gajim.contacts.remove_jid(account, jid, remove_meta=True) + if iters: + rest_of_family = [data for data in family + if account != data['account'] or jid != data['jid']] + if rest_of_family: + # reshow the rest of the family + brothers = self._add_metacontact_family(rest_of_family, account) + for c, acc in brothers: + self.draw_completely(c.jid, acc) + + if iters: + # Draw all groups of the contact + for group in contact.get_shown_groups(): + self.draw_group(group, account) + self.draw_account(account) + + return True + + def rename_self_contact(self, old_jid, new_jid, account): + '''Rename the self_contact jid + + Keyword arguments: + old_jid -- our old jid + new_jid -- our new jid + account -- the corresponding account. + ''' + gajim.contacts.change_contact_jid(old_jid, new_jid, account) + self_iter = self._get_self_contact_iter(account, model=self.model) + if not self_iter: + return + self.model[self_iter][C_JID] = new_jid + self.draw_contact(new_jid, account) + + def add_groupchat(self, jid, account, status=''): + '''Add groupchat to roster and draw it. + Return the added contact instance. + ''' + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + # Do not show gc if we are disconnected and minimize it + if gajim.account_is_connected(account): + show = 'online' + else: + show = 'offline' + status = '' + + if contact is None: + gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid, account) + if gc_control: + # there is a window that we can minimize + gajim.interface.minimized_controls[account][jid] = gc_control + name = gc_control.name + elif jid in gajim.interface.minimized_controls[account]: + name = gajim.interface.minimized_controls[account][jid].name + else: + name = jid.split('@')[0] + # New groupchat + #GCMIN + contact = gajim.contacts.create_contact(jid=jid, account=account, name=name, + groups=[_('Groupchats')], show=show, status=status, sub='none') + gajim.contacts.add_contact(account, contact) + self.add_contact(jid, account) + else: + if jid not in gajim.interface.minimized_controls[account]: + # there is a window that we can minimize + gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid, + account) + gajim.interface.minimized_controls[account][jid] = gc_control + contact.show = show + contact.status = status + self.adjust_and_draw_contact_context(jid, account) + + return contact + + + def remove_groupchat(self, jid, account): + '''Remove groupchat from roster and redraw account and group.''' + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + if contact.is_groupchat(): + if jid in gajim.interface.minimized_controls[account]: + del gajim.interface.minimized_controls[account][jid] + self.remove_contact(jid, account, force=True, backend=True) + return True + else: + return False + + + # FIXME: This function is yet unused! Port to new API + def add_transport(self, jid, account): + '''Add transport to roster and draw it. + Return the added contact instance.''' + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + if contact is None: + #TRANSP + contact = gajim.contacts.create_contact(jid=jid, account=account, name=jid, + groups=[_('Transports')], show='offline', status='offline', + sub='from') + gajim.contacts.add_contact(account, contact) + self.add_contact(jid, account) + return contact + + def remove_transport(self, jid, account): + '''Remove transport from roster and redraw account and group.''' + self.remove_contact(jid, account, force=True, backend=True) + return True + + def rename_group(self, old_name, new_name, account): + """ + rename a roster group + """ + if old_name == new_name: + return + + # Groups may not change name from or to a special groups + for g in helpers.special_groups: + if g in (new_name, old_name): + return + + # update all contacts in the given group + if self.regroup: + accounts = gajim.connections.keys() + else: + accounts = [account,] + + for acc in accounts: + changed_contacts = [] + for jid in gajim.contacts.get_jid_list(acc): + contact = gajim.contacts.get_first_contact_from_jid(acc, jid) + if old_name not in contact.groups: + continue + + self.remove_contact(jid, acc, force=True) + + contact.groups.remove(old_name) + if new_name not in contact.groups: + contact.groups.append(new_name) + + changed_contacts.append({'jid':jid, 'name':contact.name, + 'groups':contact.groups}) + + gajim.connections[acc].update_contacts(changed_contacts) + + for c in changed_contacts: + self.add_contact(c['jid'], acc) + + self._adjust_group_expand_collapse_state(new_name, acc) + + self.draw_group(old_name, acc) + self.draw_group(new_name, acc) + + + def add_contact_to_groups(self, jid, account, groups, update=True): + '''Add contact to given groups and redraw them. + + Contact on server is updated too. When the contact has a family, + the action will be performed for all members. + + Keyword Arguments: + jid -- the jid + account -- the corresponding account + groups -- list of Groups to add the contact to. + update -- update contact on the server + + ''' + self.remove_contact(jid, account, force=True) + for contact in gajim.contacts.get_contacts(account, jid): + for group in groups: + if group not in contact.groups: + # we might be dropped from meta to group + contact.groups.append(group) + if update: + gajim.connections[account].update_contact(jid, contact.name, + contact.groups) + + self.add_contact(jid, account) + + for group in groups: + self._adjust_group_expand_collapse_state(group, account) + + def remove_contact_from_groups(self, jid, account, groups, update=True): + '''Remove contact from given groups and redraw them. + + Contact on server is updated too. When the contact has a family, + the action will be performed for all members. + + Keyword Arguments: + jid -- the jid + account -- the corresponding account + groups -- list of Groups to remove the contact from + update -- update contact on the server + + ''' + self.remove_contact(jid, account, force=True) + for contact in gajim.contacts.get_contacts(account, jid): + for group in groups: + if group in contact.groups: + # Needed when we remove from "General" or "Observers" + contact.groups.remove(group) + if update: + gajim.connections[account].update_contact(jid, contact.name, + contact.groups) + self.add_contact(jid, account) + + # Also redraw old groups + for group in groups: + self.draw_group(group, account) + + # FIXME: maybe move to gajim.py + def remove_newly_added(self, jid, account): + if jid in gajim.newly_added[account]: + gajim.newly_added[account].remove(jid) + self.draw_contact(jid, account) + + # FIXME: maybe move to gajim.py + def remove_to_be_removed(self, jid, account): + if account not in gajim.interface.instances: + # Account has been deleted during the timeout that called us + return + if jid in gajim.newly_added[account]: + return + if jid in gajim.to_be_removed[account]: + gajim.to_be_removed[account].remove(jid) + family = gajim.contacts.get_metacontacts_family(account, jid) + if family: + # Peform delayed recalibration + self._recalibrate_metacontact_family(family, account) + self.draw_contact(jid, account) + + #FIXME: integrate into add_contact() + def add_to_not_in_the_roster(self, account, jid, nick='', resource=''): + keyID = '' + attached_keys = gajim.config.get_per('accounts', account, + 'attached_gpg_keys').split() + if jid in attached_keys: + keyID = attached_keys[attached_keys.index(jid) + 1] + contact = gajim.contacts.create_not_in_roster_contact(jid=jid, + account=account, resource=resource, name=nick, keyID=keyID) + gajim.contacts.add_contact(account, contact) + self.add_contact(contact.jid, account) + return contact ################################################################################ ### Methods for adding and removing roster window items ################################################################################ - def draw_account(self, account): - child_iter = self._get_account_iter(account, self.model) - if not child_iter: - assert False, 'Account iter of %s could not be found.' % account - return + def draw_account(self, account): + child_iter = self._get_account_iter(account, self.model) + if not child_iter: + assert False, 'Account iter of %s could not be found.' % account + return - num_of_accounts = gajim.get_number_of_connected_accounts() - num_of_secured = gajim.get_number_of_securely_connected_accounts() + num_of_accounts = gajim.get_number_of_connected_accounts() + num_of_secured = gajim.get_number_of_securely_connected_accounts() - if gajim.account_is_securely_connected(account) and not self.regroup or \ - self.regroup and num_of_secured and num_of_secured == num_of_accounts: - tls_pixbuf = self.window.render_icon(gtk.STOCK_DIALOG_AUTHENTICATION, - gtk.ICON_SIZE_MENU) # the only way to create a pixbuf from stock - self.model[child_iter][C_PADLOCK_PIXBUF] = tls_pixbuf - else: - self.model[child_iter][C_PADLOCK_PIXBUF] = None + if gajim.account_is_securely_connected(account) and not self.regroup or \ + self.regroup and num_of_secured and num_of_secured == num_of_accounts: + tls_pixbuf = self.window.render_icon(gtk.STOCK_DIALOG_AUTHENTICATION, + gtk.ICON_SIZE_MENU) # the only way to create a pixbuf from stock + self.model[child_iter][C_PADLOCK_PIXBUF] = tls_pixbuf + else: + self.model[child_iter][C_PADLOCK_PIXBUF] = None - if self.regroup: - account_name = _('Merged accounts') - accounts = [] - else: - account_name = account - accounts = [account] + if self.regroup: + account_name = _('Merged accounts') + accounts = [] + else: + account_name = account + accounts = [account] - if account in self.collapsed_rows and \ - self.model.iter_has_child(child_iter): - account_name = '[%s]' % account_name + if account in self.collapsed_rows and \ + self.model.iter_has_child(child_iter): + account_name = '[%s]' % account_name - if (gajim.account_is_connected(account) or (self.regroup and \ - gajim.get_number_of_connected_accounts())) and gajim.config.get( - 'show_contacts_number'): - nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts( - accounts = accounts) - account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total)) + if (gajim.account_is_connected(account) or (self.regroup and \ + gajim.get_number_of_connected_accounts())) and gajim.config.get( + 'show_contacts_number'): + nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts( + accounts = accounts) + account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total)) - self.model[child_iter][C_NAME] = account_name + self.model[child_iter][C_NAME] = account_name - pep = gajim.connections[account].pep - if gajim.config.get('show_mood_in_roster') and 'mood' in pep: - self.model[child_iter][C_MOOD_PIXBUF] = pep['mood'].asPixbufIcon() - else: - self.model[child_iter][C_MOOD_PIXBUF] = None + pep = gajim.connections[account].pep + if gajim.config.get('show_mood_in_roster') and 'mood' in pep: + self.model[child_iter][C_MOOD_PIXBUF] = pep['mood'].asPixbufIcon() + else: + self.model[child_iter][C_MOOD_PIXBUF] = None - if gajim.config.get('show_activity_in_roster') and 'activity' in pep: - self.model[child_iter][C_ACTIVITY_PIXBUF] = pep['activity'].asPixbufIcon() - else: - self.model[child_iter][C_ACTIVITY_PIXBUF] = None + if gajim.config.get('show_activity_in_roster') and 'activity' in pep: + self.model[child_iter][C_ACTIVITY_PIXBUF] = pep['activity'].asPixbufIcon() + else: + self.model[child_iter][C_ACTIVITY_PIXBUF] = None - if gajim.config.get('show_tunes_in_roster') and 'tune' in pep: - self.model[child_iter][C_TUNE_PIXBUF] = pep['tune'].asPixbufIcon() - else: - self.model[child_iter][C_TUNE_PIXBUF] = None - return False + if gajim.config.get('show_tunes_in_roster') and 'tune' in pep: + self.model[child_iter][C_TUNE_PIXBUF] = pep['tune'].asPixbufIcon() + else: + self.model[child_iter][C_TUNE_PIXBUF] = None + return False - def draw_group(self, group, account): - child_iter = self._get_group_iter(group, account, model=self.model) - if not child_iter: - # Eg. We redraw groups after we removed a entitiy - # and its empty groups - return - if self.regroup: - accounts = [] - else: - accounts = [account] - text = gobject.markup_escape_text(group) - if helpers.group_is_blocked(account, group): - text = '%s' % text - if gajim.config.get('show_contacts_number'): - nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts( - accounts = accounts, groups = [group]) - text += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total)) + def draw_group(self, group, account): + child_iter = self._get_group_iter(group, account, model=self.model) + if not child_iter: + # Eg. We redraw groups after we removed a entitiy + # and its empty groups + return + if self.regroup: + accounts = [] + else: + accounts = [account] + text = gobject.markup_escape_text(group) + if helpers.group_is_blocked(account, group): + text = '%s' % text + if gajim.config.get('show_contacts_number'): + nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts( + accounts = accounts, groups = [group]) + text += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total)) - self.model[child_iter][C_NAME] = text - return False + self.model[child_iter][C_NAME] = text + return False - def draw_parent_contact(self, jid, account): - child_iters = self._get_contact_iter(jid, account, model=self.model) - if not child_iters: - return False - parent_iter = self.model.iter_parent(child_iters[0]) - if self.model[parent_iter][C_TYPE] != 'contact': - # parent is not a contact - return - parent_jid = self.model[parent_iter][C_JID].decode('utf-8') - parent_account = self.model[parent_iter][C_ACCOUNT].decode('utf-8') - self.draw_contact(parent_jid, parent_account) - return False + def draw_parent_contact(self, jid, account): + child_iters = self._get_contact_iter(jid, account, model=self.model) + if not child_iters: + return False + parent_iter = self.model.iter_parent(child_iters[0]) + if self.model[parent_iter][C_TYPE] != 'contact': + # parent is not a contact + return + parent_jid = self.model[parent_iter][C_JID].decode('utf-8') + parent_account = self.model[parent_iter][C_ACCOUNT].decode('utf-8') + self.draw_contact(parent_jid, parent_account) + return False - def draw_contact(self, jid, account, selected=False, focus=False): - '''draw the correct state image, name BUT not avatar''' - # focus is about if the roster window has toplevel-focus or not - # FIXME: We really need a custom cell_renderer + def draw_contact(self, jid, account, selected=False, focus=False): + '''draw the correct state image, name BUT not avatar''' + # focus is about if the roster window has toplevel-focus or not + # FIXME: We really need a custom cell_renderer - contact_instances = gajim.contacts.get_contacts(account, jid) - contact = gajim.contacts.get_highest_prio_contact_from_contacts( - contact_instances) + contact_instances = gajim.contacts.get_contacts(account, jid) + contact = gajim.contacts.get_highest_prio_contact_from_contacts( + contact_instances) - child_iters = self._get_contact_iter(jid, account, contact, self.model) - if not child_iters: - return False + child_iters = self._get_contact_iter(jid, account, contact, self.model) + if not child_iters: + return False - name = gobject.markup_escape_text(contact.get_shown_name()) + name = gobject.markup_escape_text(contact.get_shown_name()) - # gets number of unread gc marked messages - if jid in gajim.interface.minimized_controls[account] and \ - gajim.interface.minimized_controls[account][jid]: - nb_unread = len(gajim.events.get_events(account, jid, - ['printed_marked_gc_msg'])) - nb_unread += gajim.interface.minimized_controls \ - [account][jid].get_nb_unread_pm() + # gets number of unread gc marked messages + if jid in gajim.interface.minimized_controls[account] and \ + gajim.interface.minimized_controls[account][jid]: + nb_unread = len(gajim.events.get_events(account, jid, + ['printed_marked_gc_msg'])) + nb_unread += gajim.interface.minimized_controls \ + [account][jid].get_nb_unread_pm() - if nb_unread == 1: - name = '%s *' % name - elif nb_unread > 1: - name = '%s [%s]' % (name, str(nb_unread)) + if nb_unread == 1: + name = '%s *' % name + elif nb_unread > 1: + name = '%s [%s]' % (name, str(nb_unread)) - # Strike name if blocked - strike = False - if helpers.jid_is_blocked(account, jid): - strike = True - else: - for group in contact.get_shown_groups(): - if helpers.group_is_blocked(account, group): - strike = True - break - if strike: - name = '%s' % name + # Strike name if blocked + strike = False + if helpers.jid_is_blocked(account, jid): + strike = True + else: + for group in contact.get_shown_groups(): + if helpers.group_is_blocked(account, group): + strike = True + break + if strike: + name = '%s' % name - # Show resource counter - nb_connected_contact = 0 - for c in contact_instances: - if c.show not in ('error', 'offline'): - nb_connected_contact += 1 - if nb_connected_contact > 1: - # switch back to default writing direction - name += i18n.paragraph_direction_mark(unicode(name)) - name += u' (%d)' % nb_connected_contact + # Show resource counter + nb_connected_contact = 0 + for c in contact_instances: + if c.show not in ('error', 'offline'): + nb_connected_contact += 1 + if nb_connected_contact > 1: + # switch back to default writing direction + name += i18n.paragraph_direction_mark(unicode(name)) + name += u' (%d)' % nb_connected_contact - # show (account_name) if there are 2 contact with same jid - # in merged mode - if self.regroup: - add_acct = False - # look through all contacts of all accounts - for account_ in gajim.connections: - # useless to add account name - if account_ == account: - continue - for jid_ in gajim.contacts.get_jid_list(account_): - contact_ = gajim.contacts.get_first_contact_from_jid( - account_, jid_) - if contact_.get_shown_name() == contact.get_shown_name() and \ - (jid_, account_) != (jid, account): - add_acct = True - break - if add_acct: - # No need to continue in other account - # if we already found one - break - if add_acct: - name += ' (' + account + ')' + # show (account_name) if there are 2 contact with same jid + # in merged mode + if self.regroup: + add_acct = False + # look through all contacts of all accounts + for account_ in gajim.connections: + # useless to add account name + if account_ == account: + continue + for jid_ in gajim.contacts.get_jid_list(account_): + contact_ = gajim.contacts.get_first_contact_from_jid( + account_, jid_) + if contact_.get_shown_name() == contact.get_shown_name() and \ + (jid_, account_) != (jid, account): + add_acct = True + break + if add_acct: + # No need to continue in other account + # if we already found one + break + if add_acct: + name += ' (' + account + ')' - # add status msg, if not empty, under contact name in - # the treeview - if contact.status and gajim.config.get('show_status_msgs_in_roster'): - status = contact.status.strip() - if status != '': - status = helpers.reduce_chars_newlines(status, - max_lines = 1) - # escape markup entities and make them small - # italic and fg color color is calcuted to be - # always readable - color = gtkgui_helpers._get_fade_color(self.tree, selected, focus) - colorstring = '#%04x%04x%04x' % (color.red, color.green, color.blue) - name += '\n%s' % ( - colorstring, - gobject.markup_escape_text(status)) + # add status msg, if not empty, under contact name in + # the treeview + if contact.status and gajim.config.get('show_status_msgs_in_roster'): + status = contact.status.strip() + if status != '': + status = helpers.reduce_chars_newlines(status, + max_lines = 1) + # escape markup entities and make them small + # italic and fg color color is calcuted to be + # always readable + color = gtkgui_helpers._get_fade_color(self.tree, selected, focus) + colorstring = '#%04x%04x%04x' % (color.red, color.green, color.blue) + name += '\n%s' % ( + colorstring, + gobject.markup_escape_text(status)) - icon_name = helpers.get_icon_name_to_show(contact, account) - # look if another resource has awaiting events - for c in contact_instances: - c_icon_name = helpers.get_icon_name_to_show(c, account) - if c_icon_name in ('event', 'muc_active', 'muc_inactive'): - icon_name = c_icon_name - break + icon_name = helpers.get_icon_name_to_show(contact, account) + # look if another resource has awaiting events + for c in contact_instances: + c_icon_name = helpers.get_icon_name_to_show(c, account) + if c_icon_name in ('event', 'muc_active', 'muc_inactive'): + icon_name = c_icon_name + break - # Check for events of collapsed (hidden) brothers - family = gajim.contacts.get_metacontacts_family(account, jid) - is_big_brother = False - have_visible_children = False - if family: - bb_jid, bb_account = \ - self._get_nearby_family_and_big_brother(family, account)[1:] - is_big_brother = (jid, account) == (bb_jid, bb_account) - iters = self._get_contact_iter(jid, account) - have_visible_children = iters \ - and self.modelfilter.iter_has_child(iters[0]) + # Check for events of collapsed (hidden) brothers + family = gajim.contacts.get_metacontacts_family(account, jid) + is_big_brother = False + have_visible_children = False + if family: + bb_jid, bb_account = \ + self._get_nearby_family_and_big_brother(family, account)[1:] + is_big_brother = (jid, account) == (bb_jid, bb_account) + iters = self._get_contact_iter(jid, account) + have_visible_children = iters \ + and self.modelfilter.iter_has_child(iters[0]) - if have_visible_children: - # We are the big brother and have a visible family - for child_iter in child_iters: - child_path = self.model.get_path(child_iter) - path = self.modelfilter.convert_child_path_to_path(child_path) + if have_visible_children: + # We are the big brother and have a visible family + for child_iter in child_iters: + child_path = self.model.get_path(child_iter) + path = self.modelfilter.convert_child_path_to_path(child_path) - if not path: - continue + if not path: + continue - if not self.tree.row_expanded(path) and icon_name != 'event': - iterC = self.model.iter_children(child_iter) - while iterC: - # a child has awaiting messages? - jidC = self.model[iterC][C_JID].decode('utf-8') - accountC = self.model[iterC][C_ACCOUNT].decode('utf-8') - if len(gajim.events.get_events(accountC, jidC)): - icon_name = 'event' - break - iterC = self.model.iter_next(iterC) + if not self.tree.row_expanded(path) and icon_name != 'event': + iterC = self.model.iter_children(child_iter) + while iterC: + # a child has awaiting messages? + jidC = self.model[iterC][C_JID].decode('utf-8') + accountC = self.model[iterC][C_ACCOUNT].decode('utf-8') + if len(gajim.events.get_events(accountC, jidC)): + icon_name = 'event' + break + iterC = self.model.iter_next(iterC) - if self.tree.row_expanded(path): - state_images = self.get_appropriate_state_images( - jid, size = 'opened', - icon_name = icon_name) - else: - state_images = self.get_appropriate_state_images( - jid, size = 'closed', - icon_name = icon_name) + if self.tree.row_expanded(path): + state_images = self.get_appropriate_state_images( + jid, size = 'opened', + icon_name = icon_name) + else: + state_images = self.get_appropriate_state_images( + jid, size = 'closed', + icon_name = icon_name) - # Expand/collapse icon might differ per iter - # (group) - img = state_images[icon_name] - self.model[child_iter][C_IMG] = img - self.model[child_iter][C_NAME] = name - else: - # A normal contact or little brother - state_images = self.get_appropriate_state_images(jid, - icon_name = icon_name) + # Expand/collapse icon might differ per iter + # (group) + img = state_images[icon_name] + self.model[child_iter][C_IMG] = img + self.model[child_iter][C_NAME] = name + else: + # A normal contact or little brother + state_images = self.get_appropriate_state_images(jid, + icon_name = icon_name) - # All iters have the same icon (no expand/collapse) - img = state_images[icon_name] - for child_iter in child_iters: - self.model[child_iter][C_IMG] = img - self.model[child_iter][C_NAME] = name + # All iters have the same icon (no expand/collapse) + img = state_images[icon_name] + for child_iter in child_iters: + self.model[child_iter][C_IMG] = img + self.model[child_iter][C_NAME] = name - # We are a little brother - if family and not is_big_brother and not self.starting: - self.draw_parent_contact(jid, account) + # We are a little brother + if family and not is_big_brother and not self.starting: + self.draw_parent_contact(jid, account) - for group in contact.get_shown_groups(): - # We need to make sure that _visible_func is called for - # our groups otherwise we might not be shown - iterG = self._get_group_iter(group, account, model=self.model) - if iterG: - # it's not self contact - self.model[iterG][C_JID] = self.model[iterG][C_JID] + for group in contact.get_shown_groups(): + # We need to make sure that _visible_func is called for + # our groups otherwise we might not be shown + iterG = self._get_group_iter(group, account, model=self.model) + if iterG: + # it's not self contact + self.model[iterG][C_JID] = self.model[iterG][C_JID] - return False + return False - def _is_pep_shown_in_roster(self, pep_type): - if pep_type == 'mood': - return gajim.config.get('show_mood_in_roster') - elif pep_type == 'activity': - return gajim.config.get('show_activity_in_roster') - elif pep_type == 'tune': - return gajim.config.get('show_tunes_in_roster') - else: - return False - - def draw_all_pep_types(self, jid, account): - for pep_type in self._pep_type_to_model_column: - self.draw_pep(jid, account, pep_type) - - def draw_pep(self, jid, account, pep_type): - if pep_type not in self._pep_type_to_model_column: - return - if not self._is_pep_shown_in_roster(pep_type): - return - - model_column = self._pep_type_to_model_column[pep_type] - iters = self._get_contact_iter(jid, account, model=self.model) - if not iters: - return - jid = self.model[iters[0]][C_JID] - jid = jid.decode('utf-8') - contact = gajim.contacts.get_contact(account, jid) - if pep_type in contact.pep: - pixbuf = contact.pep[pep_type].asPixbufIcon() - else: - pixbuf = None - for child_iter in iters: - self.model[child_iter][model_column] = pixbuf + def _is_pep_shown_in_roster(self, pep_type): + if pep_type == 'mood': + return gajim.config.get('show_mood_in_roster') + elif pep_type == 'activity': + return gajim.config.get('show_activity_in_roster') + elif pep_type == 'tune': + return gajim.config.get('show_tunes_in_roster') + else: + return False - def draw_avatar(self, jid, account): - iters = self._get_contact_iter(jid, account, model=self.model) - if not iters or not gajim.config.get('show_avatars_in_roster'): - return - jid = self.model[iters[0]][C_JID] - jid = jid.decode('utf-8') - pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid) - if pixbuf is None or pixbuf == 'ask': - scaled_pixbuf = None - else: - scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'roster') - for child_iter in iters: - self.model[child_iter][C_AVATAR_PIXBUF] = scaled_pixbuf - return False + def draw_all_pep_types(self, jid, account): + for pep_type in self._pep_type_to_model_column: + self.draw_pep(jid, account, pep_type) - def draw_completely(self, jid, account): - self.draw_contact(jid, account) - self.draw_all_pep_types(jid, account) - self.draw_avatar(jid, account) + def draw_pep(self, jid, account, pep_type): + if pep_type not in self._pep_type_to_model_column: + return + if not self._is_pep_shown_in_roster(pep_type): + return - def adjust_and_draw_contact_context(self, jid, account): - '''Draw contact, account and groups of given jid - Show contact if it has pending events - ''' - contact = gajim.contacts.get_first_contact_from_jid(account, jid) - if not contact: - # idle draw or just removed SelfContact - return + model_column = self._pep_type_to_model_column[pep_type] + iters = self._get_contact_iter(jid, account, model=self.model) + if not iters: + return + jid = self.model[iters[0]][C_JID] + jid = jid.decode('utf-8') + contact = gajim.contacts.get_contact(account, jid) + if pep_type in contact.pep: + pixbuf = contact.pep[pep_type].asPixbufIcon() + else: + pixbuf = None + for child_iter in iters: + self.model[child_iter][model_column] = pixbuf - family = gajim.contacts.get_metacontacts_family(account, jid) - if family: - # There might be a new big brother - self._recalibrate_metacontact_family(family, account) - self.draw_contact(jid, account) - self.draw_account(account) + def draw_avatar(self, jid, account): + iters = self._get_contact_iter(jid, account, model=self.model) + if not iters or not gajim.config.get('show_avatars_in_roster'): + return + jid = self.model[iters[0]][C_JID] + jid = jid.decode('utf-8') + pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid) + if pixbuf is None or pixbuf == 'ask': + scaled_pixbuf = None + else: + scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'roster') + for child_iter in iters: + self.model[child_iter][C_AVATAR_PIXBUF] = scaled_pixbuf + return False - for group in contact.get_shown_groups(): - self.draw_group(group, account) - self._adjust_group_expand_collapse_state(group, account) + def draw_completely(self, jid, account): + self.draw_contact(jid, account) + self.draw_all_pep_types(jid, account) + self.draw_avatar(jid, account) - def _idle_draw_jids_of_account(self, jids, account): - '''Draw given contacts and their avatars in a lazy fashion. + def adjust_and_draw_contact_context(self, jid, account): + '''Draw contact, account and groups of given jid + Show contact if it has pending events + ''' + contact = gajim.contacts.get_first_contact_from_jid(account, jid) + if not contact: + # idle draw or just removed SelfContact + return - Keyword arguments: - jids -- a list of jids to draw - account -- the corresponding account - ''' - def _draw_all_contacts(jids, account): - for jid in jids: - family = gajim.contacts.get_metacontacts_family(account, jid) - if family: - # For metacontacts over several accounts: - # When we connect a new account existing brothers - # must be redrawn (got removed and readded) - for data in family: - self.draw_completely(data['jid'], data['account']) - else: - self.draw_completely(jid, account) - yield True - yield False + family = gajim.contacts.get_metacontacts_family(account, jid) + if family: + # There might be a new big brother + self._recalibrate_metacontact_family(family, account) + self.draw_contact(jid, account) + self.draw_account(account) - task = _draw_all_contacts(jids, account) - gobject.idle_add(task.next) + for group in contact.get_shown_groups(): + self.draw_group(group, account) + self._adjust_group_expand_collapse_state(group, account) - def setup_and_draw_roster(self): - '''create new empty model and draw roster''' - self.modelfilter = None - # (icon, name, type, jid, account, editable, mood_pixbuf, - # activity_pixbuf, tune_pixbuf avatar_pixbuf, padlock_pixbuf) - self.model = gtk.TreeStore(gtk.Image, str, str, str, str, - gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, - gtk.gdk.Pixbuf, gtk.gdk.Pixbuf) + def _idle_draw_jids_of_account(self, jids, account): + '''Draw given contacts and their avatars in a lazy fashion. - self.model.set_sort_func(1, self._compareIters) - self.model.set_sort_column_id(1, gtk.SORT_ASCENDING) - self.modelfilter = self.model.filter_new() - self.modelfilter.set_visible_func(self._visible_func) - self.modelfilter.connect('row-has-child-toggled', - self.on_modelfilter_row_has_child_toggled) - self.tree.set_model(self.modelfilter) + Keyword arguments: + jids -- a list of jids to draw + account -- the corresponding account + ''' + def _draw_all_contacts(jids, account): + for jid in jids: + family = gajim.contacts.get_metacontacts_family(account, jid) + if family: + # For metacontacts over several accounts: + # When we connect a new account existing brothers + # must be redrawn (got removed and readded) + for data in family: + self.draw_completely(data['jid'], data['account']) + else: + self.draw_completely(jid, account) + yield True + yield False - for acct in gajim.connections: - self.add_account(acct) - self.add_account_contacts(acct) - # Recalculate column width for ellipsizing - self.tree.columns_autosize() + task = _draw_all_contacts(jids, account) + gobject.idle_add(task.next) + + def setup_and_draw_roster(self): + '''create new empty model and draw roster''' + self.modelfilter = None + # (icon, name, type, jid, account, editable, mood_pixbuf, + # activity_pixbuf, tune_pixbuf avatar_pixbuf, padlock_pixbuf) + self.model = gtk.TreeStore(gtk.Image, str, str, str, str, + gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, + gtk.gdk.Pixbuf, gtk.gdk.Pixbuf) + + self.model.set_sort_func(1, self._compareIters) + self.model.set_sort_column_id(1, gtk.SORT_ASCENDING) + self.modelfilter = self.model.filter_new() + self.modelfilter.set_visible_func(self._visible_func) + self.modelfilter.connect('row-has-child-toggled', + self.on_modelfilter_row_has_child_toggled) + self.tree.set_model(self.modelfilter) + + for acct in gajim.connections: + self.add_account(acct) + self.add_account_contacts(acct) + # Recalculate column width for ellipsizing + self.tree.columns_autosize() - def select_contact(self, jid, account): - '''Select contact in roster. If contact is hidden but has events, - show him.''' - # Refiltering SHOULD NOT be needed: - # When a contact gets a new event he will be redrawn and his - # icon changes, so _visible_func WILL be called on him anyway - iters = self._get_contact_iter(jid, account) - if not iters: - # Not visible in roster - return - path = self.modelfilter.get_path(iters[0]) - if self.dragging or not gajim.config.get('scroll_roster_to_last_message'): - # do not change selection while DND'ing - return - # Expand his parent, so this path is visible, don't expand it. - self.tree.expand_to_path(path[:-1]) - self.tree.scroll_to_cell(path) - self.tree.set_cursor(path) + def select_contact(self, jid, account): + '''Select contact in roster. If contact is hidden but has events, + show him.''' + # Refiltering SHOULD NOT be needed: + # When a contact gets a new event he will be redrawn and his + # icon changes, so _visible_func WILL be called on him anyway + iters = self._get_contact_iter(jid, account) + if not iters: + # Not visible in roster + return + path = self.modelfilter.get_path(iters[0]) + if self.dragging or not gajim.config.get('scroll_roster_to_last_message'): + # do not change selection while DND'ing + return + # Expand his parent, so this path is visible, don't expand it. + self.tree.expand_to_path(path[:-1]) + self.tree.scroll_to_cell(path) + self.tree.set_cursor(path) - def _adjust_account_expand_collapse_state(self, account): - '''Expand/collapse account row based on self.collapsed_rows''' - iterA = self._get_account_iter(account) - if not iterA: - # thank you modelfilter - return - path = self.modelfilter.get_path(iterA) - if account in self.collapsed_rows: - self.tree.collapse_row(path) - else: - self.tree.expand_row(path, False) - return False + def _adjust_account_expand_collapse_state(self, account): + '''Expand/collapse account row based on self.collapsed_rows''' + iterA = self._get_account_iter(account) + if not iterA: + # thank you modelfilter + return + path = self.modelfilter.get_path(iterA) + if account in self.collapsed_rows: + self.tree.collapse_row(path) + else: + self.tree.expand_row(path, False) + return False - def _adjust_group_expand_collapse_state(self, group, account): - '''Expand/collapse group row based on self.collapsed_rows''' - iterG = self._get_group_iter(group, account) - if not iterG: - # Group not visible - return - path = self.modelfilter.get_path(iterG) - if account + group in self.collapsed_rows: - self.tree.collapse_row(path) - else: - self.tree.expand_row(path, False) - return False + def _adjust_group_expand_collapse_state(self, group, account): + '''Expand/collapse group row based on self.collapsed_rows''' + iterG = self._get_group_iter(group, account) + if not iterG: + # Group not visible + return + path = self.modelfilter.get_path(iterG) + if account + group in self.collapsed_rows: + self.tree.collapse_row(path) + else: + self.tree.expand_row(path, False) + return False ############################################################################## ### Roster and Modelfilter handling ############################################################################## - def _search_roster_func(self, model, column, key, titer): - key = key.decode('utf-8').lower() - name = model[titer][C_NAME].decode('utf-8').lower() - return not (key in name) + def _search_roster_func(self, model, column, key, titer): + key = key.decode('utf-8').lower() + name = model[titer][C_NAME].decode('utf-8').lower() + return not (key in name) - def refilter_shown_roster_items(self): - self.filtering = True - self.modelfilter.refilter() - self.filtering = False + def refilter_shown_roster_items(self): + self.filtering = True + self.modelfilter.refilter() + self.filtering = False - def contact_has_pending_roster_events(self, contact, account): - '''Return True if the contact or one if it resources has pending events''' - # jid has pending events - if gajim.events.get_nb_roster_events(account, contact.jid) > 0: - return True - # check events of all resources - for contact_ in gajim.contacts.get_contacts(account, contact.jid): - if contact_.resource and gajim.events.get_nb_roster_events(account, - contact_.get_full_jid()) > 0: - return True - return False + def contact_has_pending_roster_events(self, contact, account): + '''Return True if the contact or one if it resources has pending events''' + # jid has pending events + if gajim.events.get_nb_roster_events(account, contact.jid) > 0: + return True + # check events of all resources + for contact_ in gajim.contacts.get_contacts(account, contact.jid): + if contact_.resource and gajim.events.get_nb_roster_events(account, + contact_.get_full_jid()) > 0: + return True + return False - def contact_is_visible(self, contact, account): - if self.contact_has_pending_roster_events(contact, account): - return True + def contact_is_visible(self, contact, account): + if self.contact_has_pending_roster_events(contact, account): + return True - if contact.show in ('offline', 'error'): - if contact.jid in gajim.to_be_removed[account]: - return True - return False - if gajim.config.get('show_only_chat_and_online') and contact.show in ( - 'away', 'xa', 'busy'): - return False - return True + if contact.show in ('offline', 'error'): + if contact.jid in gajim.to_be_removed[account]: + return True + return False + if gajim.config.get('show_only_chat_and_online') and contact.show in ( + 'away', 'xa', 'busy'): + return False + return True - def _visible_func(self, model, titer): - '''Determine whether iter should be visible in the treeview''' - type_ = model[titer][C_TYPE] - if not type_: - return False - if type_ == 'account': - # Always show account - return True + def _visible_func(self, model, titer): + '''Determine whether iter should be visible in the treeview''' + type_ = model[titer][C_TYPE] + if not type_: + return False + if type_ == 'account': + # Always show account + return True - account = model[titer][C_ACCOUNT] - if not account: - return False + account = model[titer][C_ACCOUNT] + if not account: + return False - account = account.decode('utf-8') - jid = model[titer][C_JID] - if not jid: - return False - jid = jid.decode('utf-8') - if type_ == 'group': - group = jid - if group == _('Transports'): - return gajim.config.get('show_transports_group') and \ - (gajim.account_is_connected(account) or \ - gajim.config.get('showoffline')) - if gajim.config.get('showoffline'): - return True + account = account.decode('utf-8') + jid = model[titer][C_JID] + if not jid: + return False + jid = jid.decode('utf-8') + if type_ == 'group': + group = jid + if group == _('Transports'): + return gajim.config.get('show_transports_group') and \ + (gajim.account_is_connected(account) or \ + gajim.config.get('showoffline')) + if gajim.config.get('showoffline'): + return True - if self.regroup: - # C_ACCOUNT for groups depends on the order - # accounts were connected - # Check all accounts for online group contacts - accounts = gajim.contacts.get_accounts() - else: - accounts = [account] - for _acc in accounts: - for contact in gajim.contacts.iter_contacts(_acc): - # Is this contact in this group ? (last part of if check if it's - # self contact) - if group in contact.get_shown_groups(): - if self.contact_is_visible(contact, _acc): - return True - return False - if type_ == 'contact': - if gajim.config.get('showoffline'): - return True - bb_jid = None - bb_account = None - family = gajim.contacts.get_metacontacts_family(account, jid) - if family: - nearby_family, bb_jid, bb_account = \ - self._get_nearby_family_and_big_brother(family, account) - if (bb_jid, bb_account) == (jid, account): - # Show the big brother if a child has pending events - for data in nearby_family: - jid = data['jid'] - account = data['account'] - contact = gajim.contacts.get_contact_with_highest_priority( - account, jid) - if contact and self.contact_is_visible(contact, account): - return True - return False - else: - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - return self.contact_is_visible(contact, account) - if type_ == 'agent': - return gajim.config.get('show_transports_group') and \ - (gajim.account_is_connected(account) or \ - gajim.config.get('showoffline')) - return True + if self.regroup: + # C_ACCOUNT for groups depends on the order + # accounts were connected + # Check all accounts for online group contacts + accounts = gajim.contacts.get_accounts() + else: + accounts = [account] + for _acc in accounts: + for contact in gajim.contacts.iter_contacts(_acc): + # Is this contact in this group ? (last part of if check if it's + # self contact) + if group in contact.get_shown_groups(): + if self.contact_is_visible(contact, _acc): + return True + return False + if type_ == 'contact': + if gajim.config.get('showoffline'): + return True + bb_jid = None + bb_account = None + family = gajim.contacts.get_metacontacts_family(account, jid) + if family: + nearby_family, bb_jid, bb_account = \ + self._get_nearby_family_and_big_brother(family, account) + if (bb_jid, bb_account) == (jid, account): + # Show the big brother if a child has pending events + for data in nearby_family: + jid = data['jid'] + account = data['account'] + contact = gajim.contacts.get_contact_with_highest_priority( + account, jid) + if contact and self.contact_is_visible(contact, account): + return True + return False + else: + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + return self.contact_is_visible(contact, account) + if type_ == 'agent': + return gajim.config.get('show_transports_group') and \ + (gajim.account_is_connected(account) or \ + gajim.config.get('showoffline')) + return True - def _compareIters(self, model, iter1, iter2, data=None): - '''Compare two iters to sort them''' - name1 = model[iter1][C_NAME] - name2 = model[iter2][C_NAME] - if not name1 or not name2: - return 0 - name1 = name1.decode('utf-8') - name2 = name2.decode('utf-8') - type1 = model[iter1][C_TYPE] - type2 = model[iter2][C_TYPE] - if type1 == 'self_contact': - return -1 - if type2 == 'self_contact': - return 1 - if type1 == 'group': - name1 = model[iter1][C_JID] - name2 = model[iter2][C_JID] - if name1 == _('Transports'): - return 1 - if name2 == _('Transports'): - return -1 - if name1 == _('Not in Roster'): - return 1 - if name2 == _('Not in Roster'): - return -1 - if name1 == _('Groupchats'): - return 1 - if name2 == _('Groupchats'): - return -1 - account1 = model[iter1][C_ACCOUNT] - account2 = model[iter2][C_ACCOUNT] - if not account1 or not account2: - return 0 - account1 = account1.decode('utf-8') - account2 = account2.decode('utf-8') - if type1 == 'account': - return locale.strcoll(account1, account2) - jid1 = model[iter1][C_JID].decode('utf-8') - jid2 = model[iter2][C_JID].decode('utf-8') - if type1 == 'contact': - lcontact1 = gajim.contacts.get_contacts(account1, jid1) - contact1 = gajim.contacts.get_first_contact_from_jid(account1, jid1) - if not contact1: - return 0 - name1 = contact1.get_shown_name() - if type2 == 'contact': - lcontact2 = gajim.contacts.get_contacts(account2, jid2) - contact2 = gajim.contacts.get_first_contact_from_jid(account2, jid2) - if not contact2: - return 0 - name2 = contact2.get_shown_name() - # We first compare by show if sort_by_show_in_roster is True or if it's a - # child contact - if type1 == 'contact' and type2 == 'contact' and \ - gajim.config.get('sort_by_show_in_roster'): - cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4, - 'invisible': 5, 'offline': 6, 'not in roster': 7, 'error': 8} - s = self.get_show(lcontact1) - show1 = cshow.get(s, 9) - s = self.get_show(lcontact2) - show2 = cshow.get(s, 9) - removing1 = False - removing2 = False - if show1 == 6 and jid1 in gajim.to_be_removed[account1]: - removing1 = True - if show2 == 6 and jid2 in gajim.to_be_removed[account2]: - removing2 = True - if removing1 and not removing2: - return 1 - if removing2 and not removing1: - return -1 - sub1 = contact1.sub - sub2 = contact2.sub - # none and from goes after - if sub1 not in ['none', 'from'] and sub2 in ['none', 'from']: - return -1 - if sub1 in ['none', 'from'] and sub2 not in ['none', 'from']: - return 1 - if show1 < show2: - return -1 - elif show1 > show2: - return 1 - # We compare names - cmp_result = locale.strcoll(name1.lower(), name2.lower()) - if cmp_result < 0: - return -1 - if cmp_result > 0: - return 1 - if type1 == 'contact' and type2 == 'contact': - # We compare account names - cmp_result = locale.strcoll(account1.lower(), account2.lower()) - if cmp_result < 0: - return -1 - if cmp_result > 0: - return 1 - # We compare jids - cmp_result = locale.strcoll(jid1.lower(), jid2.lower()) - if cmp_result < 0: - return -1 - if cmp_result > 0: - return 1 - return 0 + def _compareIters(self, model, iter1, iter2, data=None): + '''Compare two iters to sort them''' + name1 = model[iter1][C_NAME] + name2 = model[iter2][C_NAME] + if not name1 or not name2: + return 0 + name1 = name1.decode('utf-8') + name2 = name2.decode('utf-8') + type1 = model[iter1][C_TYPE] + type2 = model[iter2][C_TYPE] + if type1 == 'self_contact': + return -1 + if type2 == 'self_contact': + return 1 + if type1 == 'group': + name1 = model[iter1][C_JID] + name2 = model[iter2][C_JID] + if name1 == _('Transports'): + return 1 + if name2 == _('Transports'): + return -1 + if name1 == _('Not in Roster'): + return 1 + if name2 == _('Not in Roster'): + return -1 + if name1 == _('Groupchats'): + return 1 + if name2 == _('Groupchats'): + return -1 + account1 = model[iter1][C_ACCOUNT] + account2 = model[iter2][C_ACCOUNT] + if not account1 or not account2: + return 0 + account1 = account1.decode('utf-8') + account2 = account2.decode('utf-8') + if type1 == 'account': + return locale.strcoll(account1, account2) + jid1 = model[iter1][C_JID].decode('utf-8') + jid2 = model[iter2][C_JID].decode('utf-8') + if type1 == 'contact': + lcontact1 = gajim.contacts.get_contacts(account1, jid1) + contact1 = gajim.contacts.get_first_contact_from_jid(account1, jid1) + if not contact1: + return 0 + name1 = contact1.get_shown_name() + if type2 == 'contact': + lcontact2 = gajim.contacts.get_contacts(account2, jid2) + contact2 = gajim.contacts.get_first_contact_from_jid(account2, jid2) + if not contact2: + return 0 + name2 = contact2.get_shown_name() + # We first compare by show if sort_by_show_in_roster is True or if it's a + # child contact + if type1 == 'contact' and type2 == 'contact' and \ + gajim.config.get('sort_by_show_in_roster'): + cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4, + 'invisible': 5, 'offline': 6, 'not in roster': 7, 'error': 8} + s = self.get_show(lcontact1) + show1 = cshow.get(s, 9) + s = self.get_show(lcontact2) + show2 = cshow.get(s, 9) + removing1 = False + removing2 = False + if show1 == 6 and jid1 in gajim.to_be_removed[account1]: + removing1 = True + if show2 == 6 and jid2 in gajim.to_be_removed[account2]: + removing2 = True + if removing1 and not removing2: + return 1 + if removing2 and not removing1: + return -1 + sub1 = contact1.sub + sub2 = contact2.sub + # none and from goes after + if sub1 not in ['none', 'from'] and sub2 in ['none', 'from']: + return -1 + if sub1 in ['none', 'from'] and sub2 not in ['none', 'from']: + return 1 + if show1 < show2: + return -1 + elif show1 > show2: + return 1 + # We compare names + cmp_result = locale.strcoll(name1.lower(), name2.lower()) + if cmp_result < 0: + return -1 + if cmp_result > 0: + return 1 + if type1 == 'contact' and type2 == 'contact': + # We compare account names + cmp_result = locale.strcoll(account1.lower(), account2.lower()) + if cmp_result < 0: + return -1 + if cmp_result > 0: + return 1 + # We compare jids + cmp_result = locale.strcoll(jid1.lower(), jid2.lower()) + if cmp_result < 0: + return -1 + if cmp_result > 0: + return 1 + return 0 ################################################################################ ### FIXME: Methods that don't belong to roster window... -### ... atleast not in there current form +### ... atleast not in there current form ################################################################################ - def fire_up_unread_messages_events(self, account): - '''reads from db the unread messages, and fire them up, and - if we find very old unread messages, delete them from unread table''' - results = gajim.logger.get_unread_msgs() - for result in results: - jid = result[4] - shown = result[5] - if gajim.contacts.get_first_contact_from_jid(account, jid) and not \ - shown: - # We have this jid in our contacts list - # XXX unread messages should probably have their session saved with - # them - session = gajim.connections[account].make_new_session(jid) + def fire_up_unread_messages_events(self, account): + '''reads from db the unread messages, and fire them up, and + if we find very old unread messages, delete them from unread table''' + results = gajim.logger.get_unread_msgs() + for result in results: + jid = result[4] + shown = result[5] + if gajim.contacts.get_first_contact_from_jid(account, jid) and not \ + shown: + # We have this jid in our contacts list + # XXX unread messages should probably have their session saved with + # them + session = gajim.connections[account].make_new_session(jid) - tim = time.localtime(float(result[2])) - session.roster_message(jid, result[1], tim, msg_type='chat', - msg_id=result[0]) - gajim.logger.set_shown_unread_msgs(result[0]) + tim = time.localtime(float(result[2])) + session.roster_message(jid, result[1], tim, msg_type='chat', + msg_id=result[0]) + gajim.logger.set_shown_unread_msgs(result[0]) - elif (time.time() - result[2]) > 2592000: - # ok, here we see that we have a message in unread messages table - # that is older than a month. It is probably from someone not in our - # roster for accounts we usually launch, so we will delete this id - # from unread message tables. - gajim.logger.set_read_messages([result[0]]) + elif (time.time() - result[2]) > 2592000: + # ok, here we see that we have a message in unread messages table + # that is older than a month. It is probably from someone not in our + # roster for accounts we usually launch, so we will delete this id + # from unread message tables. + gajim.logger.set_read_messages([result[0]]) - def fill_contacts_and_groups_dicts(self, array, account): - '''fill gajim.contacts and gajim.groups''' - # FIXME: This function needs to be splitted - # Most of the logic SHOULD NOT be done at GUI level - if account not in gajim.contacts.get_accounts(): - gajim.contacts.add_account(account) - if account not in gajim.groups: - gajim.groups[account] = {} - if gajim.config.get('show_self_contact') == 'always': - self_jid = gajim.get_jid_from_account(account) - if gajim.connections[account].server_resource: - self_jid += '/' + gajim.connections[account].server_resource - array[self_jid] = {'name': gajim.nicks[account], - 'groups': ['self_contact'], 'subscription': 'both', 'ask': 'none'} - # .keys() is needed - for jid in array.keys(): - # Remove the contact in roster. It might has changed - self.remove_contact(jid, account, force=True) - # Remove old Contact instances - gajim.contacts.remove_jid(account, jid, remove_meta=False) - jids = jid.split('/') - # get jid - ji = jids[0] - # get resource - resource = '' - if len(jids) > 1: - resource = '/'.join(jids[1:]) - # get name - name = array[jid]['name'] or '' - show = 'offline' # show is offline by default - status = '' # no status message by default + def fill_contacts_and_groups_dicts(self, array, account): + '''fill gajim.contacts and gajim.groups''' + # FIXME: This function needs to be splitted + # Most of the logic SHOULD NOT be done at GUI level + if account not in gajim.contacts.get_accounts(): + gajim.contacts.add_account(account) + if account not in gajim.groups: + gajim.groups[account] = {} + if gajim.config.get('show_self_contact') == 'always': + self_jid = gajim.get_jid_from_account(account) + if gajim.connections[account].server_resource: + self_jid += '/' + gajim.connections[account].server_resource + array[self_jid] = {'name': gajim.nicks[account], + 'groups': ['self_contact'], 'subscription': 'both', 'ask': 'none'} + # .keys() is needed + for jid in array.keys(): + # Remove the contact in roster. It might has changed + self.remove_contact(jid, account, force=True) + # Remove old Contact instances + gajim.contacts.remove_jid(account, jid, remove_meta=False) + jids = jid.split('/') + # get jid + ji = jids[0] + # get resource + resource = '' + if len(jids) > 1: + resource = '/'.join(jids[1:]) + # get name + name = array[jid]['name'] or '' + show = 'offline' # show is offline by default + status = '' # no status message by default - keyID = '' - attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() - if jid in attached_keys: - keyID = attached_keys[attached_keys.index(jid) + 1] + keyID = '' + attached_keys = gajim.config.get_per('accounts', account, + 'attached_gpg_keys').split() + if jid in attached_keys: + keyID = attached_keys[attached_keys.index(jid) + 1] - if gajim.jid_is_transport(jid): - array[jid]['groups'] = [_('Transports')] - #TRANSP - potential - contact1 = gajim.contacts.create_contact(jid=ji, account=account, name=name, - groups=array[jid]['groups'], show=show, status=status, - sub=array[jid]['subscription'], ask=array[jid]['ask'], - resource=resource, keyID=keyID) - gajim.contacts.add_contact(account, contact1) + if gajim.jid_is_transport(jid): + array[jid]['groups'] = [_('Transports')] + #TRANSP - potential + contact1 = gajim.contacts.create_contact(jid=ji, account=account, name=name, + groups=array[jid]['groups'], show=show, status=status, + sub=array[jid]['subscription'], ask=array[jid]['ask'], + resource=resource, keyID=keyID) + gajim.contacts.add_contact(account, contact1) - if gajim.config.get('ask_avatars_on_startup'): - pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(ji) - if pixbuf == 'ask': - transport = gajim.get_transport_name_from_jid(contact1.jid) - if not transport or gajim.jid_is_transport(contact1.jid): - jid_with_resource = contact1.jid - if contact1.resource: - jid_with_resource += '/' + contact1.resource - gajim.connections[account].request_vcard(jid_with_resource) - else: - host = gajim.get_server_from_jid(contact1.jid) - if host not in gajim.transport_avatar[account]: - gajim.transport_avatar[account][host] = [contact1.jid] - else: - gajim.transport_avatar[account][host].append(contact1.jid) + if gajim.config.get('ask_avatars_on_startup'): + pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(ji) + if pixbuf == 'ask': + transport = gajim.get_transport_name_from_jid(contact1.jid) + if not transport or gajim.jid_is_transport(contact1.jid): + jid_with_resource = contact1.jid + if contact1.resource: + jid_with_resource += '/' + contact1.resource + gajim.connections[account].request_vcard(jid_with_resource) + else: + host = gajim.get_server_from_jid(contact1.jid) + if host not in gajim.transport_avatar[account]: + gajim.transport_avatar[account][host] = [contact1.jid] + else: + gajim.transport_avatar[account][host].append(contact1.jid) - # If we already have chat windows opened, update them with new contact - # instance - chat_control = gajim.interface.msg_win_mgr.get_control(ji, account) - if chat_control: - chat_control.contact = contact1 + # If we already have chat windows opened, update them with new contact + # instance + chat_control = gajim.interface.msg_win_mgr.get_control(ji, account) + if chat_control: + chat_control.contact = contact1 - def connected_rooms(self, account): - if account in gajim.gc_connected[account].values(): - return True - return False + def connected_rooms(self, account): + if account in gajim.gc_connected[account].values(): + return True + return False - def on_event_removed(self, event_list): - '''Remove contacts on last events removed. + def on_event_removed(self, event_list): + '''Remove contacts on last events removed. - Only performed if removal was requested before but the contact - still had pending events - ''' - contact_list = ((event.jid.split('/')[0], event.account) for event in \ - event_list) + Only performed if removal was requested before but the contact + still had pending events + ''' + contact_list = ((event.jid.split('/')[0], event.account) for event in \ + event_list) - for jid, account in contact_list: - self.draw_contact(jid, account) - # Remove contacts in roster if removal was requested - key = (jid, account) - if key in self.contacts_to_be_removed.keys(): - backend = self.contacts_to_be_removed[key]['backend'] - del self.contacts_to_be_removed[key] - # Remove contact will delay removal if there are more events pending - self.remove_contact(jid, account, backend=backend) - self.show_title() + for jid, account in contact_list: + self.draw_contact(jid, account) + # Remove contacts in roster if removal was requested + key = (jid, account) + if key in self.contacts_to_be_removed.keys(): + backend = self.contacts_to_be_removed[key]['backend'] + del self.contacts_to_be_removed[key] + # Remove contact will delay removal if there are more events pending + self.remove_contact(jid, account, backend=backend) + self.show_title() - def open_event(self, account, jid, event): - '''If an event was handled, return True, else return False''' - data = event.parameters - ft = gajim.interface.instances['file_transfers'] - event = gajim.events.get_first_event(account, jid, event.type_) - if event.type_ == 'normal': - dialogs.SingleMessageWindow(account, jid, - action='receive', from_whom=jid, subject=data[1], message=data[0], - resource=data[5], session=data[8], form_node=data[9]) - gajim.events.remove_events(account, jid, event) - return True - elif event.type_ == 'file-request': - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - ft.show_file_request(account, contact, data) - gajim.events.remove_events(account, jid, event) - return True - elif event.type_ in ('file-request-error', 'file-send-error'): - ft.show_send_error(data) - gajim.events.remove_events(account, jid, event) - return True - elif event.type_ in ('file-error', 'file-stopped'): - msg_err = '' - if data['error'] == -1: - msg_err = _('Remote contact stopped transfer') - elif data['error'] == -6: - msg_err = _('Error opening file') - ft.show_stopped(jid, data, error_msg=msg_err) - gajim.events.remove_events(account, jid, event) - return True - elif event.type_ == 'file-completed': - ft.show_completed(jid, data) - gajim.events.remove_events(account, jid, event) - return True - elif event.type_ == 'gc-invitation': - dialogs.InvitationReceivedDialog(account, data[0], jid, data[2], - data[1]) - gajim.events.remove_events(account, jid, event) - return True - elif event.type_ == 'subscription_request': - dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1]) - gajim.events.remove_events(account, jid, event) - return True - elif event.type_ == 'unsubscribed': - gajim.interface.show_unsubscribed_dialog(account, data) - gajim.events.remove_events(account, jid, event) - return True - return False + def open_event(self, account, jid, event): + '''If an event was handled, return True, else return False''' + data = event.parameters + ft = gajim.interface.instances['file_transfers'] + event = gajim.events.get_first_event(account, jid, event.type_) + if event.type_ == 'normal': + dialogs.SingleMessageWindow(account, jid, + action='receive', from_whom=jid, subject=data[1], message=data[0], + resource=data[5], session=data[8], form_node=data[9]) + gajim.events.remove_events(account, jid, event) + return True + elif event.type_ == 'file-request': + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + ft.show_file_request(account, contact, data) + gajim.events.remove_events(account, jid, event) + return True + elif event.type_ in ('file-request-error', 'file-send-error'): + ft.show_send_error(data) + gajim.events.remove_events(account, jid, event) + return True + elif event.type_ in ('file-error', 'file-stopped'): + msg_err = '' + if data['error'] == -1: + msg_err = _('Remote contact stopped transfer') + elif data['error'] == -6: + msg_err = _('Error opening file') + ft.show_stopped(jid, data, error_msg=msg_err) + gajim.events.remove_events(account, jid, event) + return True + elif event.type_ == 'file-completed': + ft.show_completed(jid, data) + gajim.events.remove_events(account, jid, event) + return True + elif event.type_ == 'gc-invitation': + dialogs.InvitationReceivedDialog(account, data[0], jid, data[2], + data[1]) + gajim.events.remove_events(account, jid, event) + return True + elif event.type_ == 'subscription_request': + dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1]) + gajim.events.remove_events(account, jid, event) + return True + elif event.type_ == 'unsubscribed': + gajim.interface.show_unsubscribed_dialog(account, data) + gajim.events.remove_events(account, jid, event) + return True + return False ################################################################################ ### This and that... random. ################################################################################ - def show_roster_vbox(self, active): - if active: - self.xml.get_widget('roster_vbox2').show() - else: - self.xml.get_widget('roster_vbox2').hide() + def show_roster_vbox(self, active): + if active: + self.xml.get_widget('roster_vbox2').show() + else: + self.xml.get_widget('roster_vbox2').hide() - def show_tooltip(self, contact): - pointer = self.tree.get_pointer() - props = self.tree.get_path_at_pos(pointer[0], pointer[1]) - # check if the current pointer is at the same path - # as it was before setting the timeout - if props and self.tooltip.id == props[0]: - # bounding rectangle of coordinates for the cell within the treeview - rect = self.tree.get_cell_area(props[0], props[1]) + def show_tooltip(self, contact): + pointer = self.tree.get_pointer() + props = self.tree.get_path_at_pos(pointer[0], pointer[1]) + # check if the current pointer is at the same path + # as it was before setting the timeout + if props and self.tooltip.id == props[0]: + # bounding rectangle of coordinates for the cell within the treeview + rect = self.tree.get_cell_area(props[0], props[1]) - # position of the treeview on the screen - position = self.tree.window.get_origin() - self.tooltip.show_tooltip(contact, rect.height, position[1] + rect.y) - else: - self.tooltip.hide_tooltip() + # position of the treeview on the screen + position = self.tree.window.get_origin() + self.tooltip.show_tooltip(contact, rect.height, position[1] + rect.y) + else: + self.tooltip.hide_tooltip() - def authorize(self, widget, jid, account): - '''Authorize a contact (by re-sending auth menuitem)''' - gajim.connections[account].send_authorization(jid) - dialogs.InformationDialog(_('Authorization has been sent'), - _('Now "%s" will know your status.') %jid) + def authorize(self, widget, jid, account): + '''Authorize a contact (by re-sending auth menuitem)''' + gajim.connections[account].send_authorization(jid) + dialogs.InformationDialog(_('Authorization has been sent'), + _('Now "%s" will know your status.') %jid) - def req_sub(self, widget, jid, txt, account, groups=[], nickname=None, - auto_auth=False): - '''Request subscription to a contact''' - gajim.connections[account].request_subscription(jid, txt, nickname, - groups, auto_auth, gajim.nicks[account]) - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - if not contact: - keyID = '' - attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() - if jid in attached_keys: - keyID = attached_keys[attached_keys.index(jid) + 1] - contact = gajim.contacts.create_contact(jid=jid, account=account, name=nickname, - groups=groups, show='requested', status='', ask='none', - sub='subscribe', keyID=keyID) - gajim.contacts.add_contact(account, contact) - else: - if not _('Not in Roster') in contact.get_shown_groups(): - dialogs.InformationDialog(_('Subscription request has been sent'), - _('If "%s" accepts this request you will know his or her status.' - ) % jid) - return - self.remove_contact(contact.jid, account, force=True) - contact.groups = groups - if nickname: - contact.name = nickname - self.add_contact(jid, account) + def req_sub(self, widget, jid, txt, account, groups=[], nickname=None, + auto_auth=False): + '''Request subscription to a contact''' + gajim.connections[account].request_subscription(jid, txt, nickname, + groups, auto_auth, gajim.nicks[account]) + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + if not contact: + keyID = '' + attached_keys = gajim.config.get_per('accounts', account, + 'attached_gpg_keys').split() + if jid in attached_keys: + keyID = attached_keys[attached_keys.index(jid) + 1] + contact = gajim.contacts.create_contact(jid=jid, account=account, name=nickname, + groups=groups, show='requested', status='', ask='none', + sub='subscribe', keyID=keyID) + gajim.contacts.add_contact(account, contact) + else: + if not _('Not in Roster') in contact.get_shown_groups(): + dialogs.InformationDialog(_('Subscription request has been sent'), + _('If "%s" accepts this request you will know his or her status.' + ) % jid) + return + self.remove_contact(contact.jid, account, force=True) + contact.groups = groups + if nickname: + contact.name = nickname + self.add_contact(jid, account) - def revoke_auth(self, widget, jid, account): - '''Revoke a contact's authorization''' - gajim.connections[account].refuse_authorization(jid) - dialogs.InformationDialog(_('Authorization has been removed'), - _('Now "%s" will always see you as offline.') %jid) + def revoke_auth(self, widget, jid, account): + '''Revoke a contact's authorization''' + gajim.connections[account].refuse_authorization(jid) + dialogs.InformationDialog(_('Authorization has been removed'), + _('Now "%s" will always see you as offline.') %jid) - def set_state(self, account, state): - child_iterA = self._get_account_iter(account, self.model) - if child_iterA: - self.model[child_iterA][0] = \ - gajim.interface.jabber_state_images['16'][state] - if gajim.interface.systray_enabled: - gajim.interface.systray.change_status(state) + def set_state(self, account, state): + child_iterA = self._get_account_iter(account, self.model) + if child_iterA: + self.model[child_iterA][0] = \ + gajim.interface.jabber_state_images['16'][state] + if gajim.interface.systray_enabled: + gajim.interface.systray.change_status(state) - def set_connecting_state(self, account): - self.set_state(account, 'connecting') + def set_connecting_state(self, account): + self.set_state(account, 'connecting') - def send_status(self, account, status, txt, auto=False, to=None): - child_iterA = self._get_account_iter(account, self.model) - if status != 'offline': - if to is None: - if status == gajim.connections[account].get_status() and \ - txt == gajim.connections[account].status: - return - gajim.config.set_per('accounts', account, 'last_status', status) - gajim.config.set_per('accounts', account, 'last_status_msg', - helpers.to_one_line(txt)) - if gajim.connections[account].connected < 2: - self.set_connecting_state(account) + def send_status(self, account, status, txt, auto=False, to=None): + child_iterA = self._get_account_iter(account, self.model) + if status != 'offline': + if to is None: + if status == gajim.connections[account].get_status() and \ + txt == gajim.connections[account].status: + return + gajim.config.set_per('accounts', account, 'last_status', status) + gajim.config.set_per('accounts', account, 'last_status_msg', + helpers.to_one_line(txt)) + if gajim.connections[account].connected < 2: + self.set_connecting_state(account) - keyid = gajim.config.get_per('accounts', account, 'keyid') - if keyid and not gajim.connections[account].gpg: - dialogs.WarningDialog(_('GPG is not usable'), - _('You will be connected to %s without OpenPGP.') % account) + keyid = gajim.config.get_per('accounts', account, 'keyid') + if keyid and not gajim.connections[account].gpg: + dialogs.WarningDialog(_('GPG is not usable'), + _('You will be connected to %s without OpenPGP.') % account) - self.send_status_continue(account, status, txt, auto, to) + self.send_status_continue(account, status, txt, auto, to) - def send_pep(self, account, pep_dict): - connection = gajim.connections[account] + def send_pep(self, account, pep_dict): + connection = gajim.connections[account] - if 'activity' in pep_dict: - activity = pep_dict['activity'] - subactivity = pep_dict.get('subactivity', None) - activity_text = pep_dict.get('activity_text', None) - connection.send_activity(activity, subactivity, activity_text) - else: - connection.retract_activity() + if 'activity' in pep_dict: + activity = pep_dict['activity'] + subactivity = pep_dict.get('subactivity', None) + activity_text = pep_dict.get('activity_text', None) + connection.send_activity(activity, subactivity, activity_text) + else: + connection.retract_activity() - if 'mood' in pep_dict: - mood = pep_dict['mood'] - mood_text = pep_dict.get('mood_text', None) - connection.send_mood(mood, mood_text) - else: - connection.retract_mood() - - def delete_pep(self, jid, account): - if jid == gajim.get_jid_from_account(account): - gajim.connections[account].pep = {} - self.draw_account(account) - - for contact in gajim.contacts.get_contacts(account, jid): - contact.pep = {} - - self.draw_all_pep_types(jid, account) - ctrl = gajim.interface.msg_win_mgr.get_control(jid, account) - if ctrl: - ctrl.update_all_pep_types() + if 'mood' in pep_dict: + mood = pep_dict['mood'] + mood_text = pep_dict.get('mood_text', None) + connection.send_mood(mood, mood_text) + else: + connection.retract_mood() - def send_status_continue(self, account, status, txt, auto, to): - if gajim.account_is_connected(account) and not to: - if status == 'online' and gajim.interface.sleeper.getState() != \ - common.sleepy.STATE_UNKNOWN: - gajim.sleeper_state[account] = 'online' - elif gajim.sleeper_state[account] not in ('autoaway', 'autoxa') or \ - status == 'offline': - gajim.sleeper_state[account] = 'off' + def delete_pep(self, jid, account): + if jid == gajim.get_jid_from_account(account): + gajim.connections[account].pep = {} + self.draw_account(account) - if to: - gajim.connections[account].send_custom_status(status, txt, to) - else: - if status in ('invisible', 'offline'): - self.delete_pep(gajim.get_jid_from_account(account), account) - was_invisible = gajim.connections[account].connected == \ - gajim.SHOW_LIST.index('invisible') - gajim.connections[account].change_status(status, txt, auto) + for contact in gajim.contacts.get_contacts(account, jid): + contact.pep = {} - if account in gajim.interface.status_sent_to_users: - gajim.interface.status_sent_to_users[account] = {} - if account in gajim.interface.status_sent_to_groups: - gajim.interface.status_sent_to_groups[account] = {} - for gc_control in gajim.interface.msg_win_mgr.get_controls( - message_control.TYPE_GC) + \ - gajim.interface.minimized_controls[account].values(): - if gc_control.account == account: - if gajim.gc_connected[account][gc_control.room_jid]: - gajim.connections[account].send_gc_status(gc_control.nick, - gc_control.room_jid, status, txt) - else: - # for some reason, we are not connected to the room even if - # tab is opened, send initial join_gc() - gajim.connections[account].join_gc(gc_control.nick, - gc_control.room_jid, None) - if was_invisible and status != 'offline': - # We come back from invisible, join bookmarks - gajim.interface.auto_join_bookmarks(account) + self.draw_all_pep_types(jid, account) + ctrl = gajim.interface.msg_win_mgr.get_control(jid, account) + if ctrl: + ctrl.update_all_pep_types() + + def send_status_continue(self, account, status, txt, auto, to): + if gajim.account_is_connected(account) and not to: + if status == 'online' and gajim.interface.sleeper.getState() != \ + common.sleepy.STATE_UNKNOWN: + gajim.sleeper_state[account] = 'online' + elif gajim.sleeper_state[account] not in ('autoaway', 'autoxa') or \ + status == 'offline': + gajim.sleeper_state[account] = 'off' + + if to: + gajim.connections[account].send_custom_status(status, txt, to) + else: + if status in ('invisible', 'offline'): + self.delete_pep(gajim.get_jid_from_account(account), account) + was_invisible = gajim.connections[account].connected == \ + gajim.SHOW_LIST.index('invisible') + gajim.connections[account].change_status(status, txt, auto) + + if account in gajim.interface.status_sent_to_users: + gajim.interface.status_sent_to_users[account] = {} + if account in gajim.interface.status_sent_to_groups: + gajim.interface.status_sent_to_groups[account] = {} + for gc_control in gajim.interface.msg_win_mgr.get_controls( + message_control.TYPE_GC) + \ + gajim.interface.minimized_controls[account].values(): + if gc_control.account == account: + if gajim.gc_connected[account][gc_control.room_jid]: + gajim.connections[account].send_gc_status(gc_control.nick, + gc_control.room_jid, status, txt) + else: + # for some reason, we are not connected to the room even if + # tab is opened, send initial join_gc() + gajim.connections[account].join_gc(gc_control.nick, + gc_control.room_jid, None) + if was_invisible and status != 'offline': + # We come back from invisible, join bookmarks + gajim.interface.auto_join_bookmarks(account) - def chg_contact_status(self, contact, show, status, account): - '''When a contact changes his or her status''' - contact_instances = gajim.contacts.get_contacts(account, contact.jid) - contact.show = show - contact.status = status - # name is to show in conversation window - name = contact.get_shown_name() - fjid = contact.get_full_jid() + def chg_contact_status(self, contact, show, status, account): + '''When a contact changes his or her status''' + contact_instances = gajim.contacts.get_contacts(account, contact.jid) + contact.show = show + contact.status = status + # name is to show in conversation window + name = contact.get_shown_name() + fjid = contact.get_full_jid() - # The contact has several resources - if len(contact_instances) > 1: - if contact.resource != '': - name += '/' + contact.resource + # The contact has several resources + if len(contact_instances) > 1: + if contact.resource != '': + name += '/' + contact.resource - # Remove resource when going offline - if show in ('offline', 'error') and \ - not self.contact_has_pending_roster_events(contact, account): - ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account) - if ctrl: - ctrl.update_ui() - ctrl.parent_win.redraw_tab(ctrl) - # keep the contact around, since it's - # already attached to the control - else: - gajim.contacts.remove_contact(account, contact) + # Remove resource when going offline + if show in ('offline', 'error') and \ + not self.contact_has_pending_roster_events(contact, account): + ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account) + if ctrl: + ctrl.update_ui() + ctrl.parent_win.redraw_tab(ctrl) + # keep the contact around, since it's + # already attached to the control + else: + gajim.contacts.remove_contact(account, contact) - elif contact.jid == gajim.get_jid_from_account(account) and \ - show in ('offline', 'error'): - if gajim.config.get('show_self_contact') != 'never': - # SelfContact went offline. Remove him when last pending - # message was read - self.remove_contact(contact.jid, account, backend=True) + elif contact.jid == gajim.get_jid_from_account(account) and \ + show in ('offline', 'error'): + if gajim.config.get('show_self_contact') != 'never': + # SelfContact went offline. Remove him when last pending + # message was read + self.remove_contact(contact.jid, account, backend=True) - uf_show = helpers.get_uf_show(show) + uf_show = helpers.get_uf_show(show) - # print status in chat window and update status/GPG image - ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account) - if ctrl and ctrl.type_id != message_control.TYPE_GC: - ctrl.contact = gajim.contacts.get_contact_with_highest_priority( - account, contact.jid) - ctrl.update_status_display(name, uf_show, status) + # print status in chat window and update status/GPG image + ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account) + if ctrl and ctrl.type_id != message_control.TYPE_GC: + ctrl.contact = gajim.contacts.get_contact_with_highest_priority( + account, contact.jid) + ctrl.update_status_display(name, uf_show, status) - if contact.resource: - ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account) - if ctrl: - ctrl.update_status_display(name, uf_show, status) + if contact.resource: + ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account) + if ctrl: + ctrl.update_status_display(name, uf_show, status) - # Delete pep if needed - keep_pep = any(c.show not in ('error', 'offline') for c in - contact_instances) - if not keep_pep and contact.jid != gajim.get_jid_from_account(account) \ - and not contact.is_groupchat(): - self.delete_pep(contact.jid, account) + # Delete pep if needed + keep_pep = any(c.show not in ('error', 'offline') for c in + contact_instances) + if not keep_pep and contact.jid != gajim.get_jid_from_account(account) \ + and not contact.is_groupchat(): + self.delete_pep(contact.jid, account) - # Redraw everything and select the sender - self.adjust_and_draw_contact_context(contact.jid, account) + # Redraw everything and select the sender + self.adjust_and_draw_contact_context(contact.jid, account) - def on_status_changed(self, account, show): - '''the core tells us that our status has changed''' - if account not in gajim.contacts.get_accounts(): - return - child_iterA = self._get_account_iter(account, self.model) - if gajim.config.get('show_self_contact') == 'always': - self_resource = gajim.connections[account].server_resource - self_contact = gajim.contacts.get_contact(account, - gajim.get_jid_from_account(account), resource=self_resource) - if self_contact: - status = gajim.connections[account].status - self.chg_contact_status(self_contact, show, status, account) - self.set_account_status_icon(account) - if show == 'offline': - if self.quit_on_next_offline > -1: - # we want to quit, we are waiting for all accounts to be offline - self.quit_on_next_offline -= 1 - if self.quit_on_next_offline < 1: - # all accounts offline, quit - self.quit_gtkgui_interface() - else: - # No need to redraw contacts if we're quitting - if child_iterA: - self.model[child_iterA][C_AVATAR_PIXBUF] = None - if account in gajim.con_types: - gajim.con_types[account] = None - for jid in gajim.contacts.get_jid_list(account): - lcontact = gajim.contacts.get_contacts(account, jid) - ctrl = gajim.interface.msg_win_mgr.get_gc_control(jid, account) - for contact in [c for c in lcontact if ((c.show != 'offline' or \ - c.is_transport()) and not ctrl)]: - self.chg_contact_status(contact, 'offline', '', account) - self.actions_menu_needs_rebuild = True - self.update_status_combobox() + def on_status_changed(self, account, show): + '''the core tells us that our status has changed''' + if account not in gajim.contacts.get_accounts(): + return + child_iterA = self._get_account_iter(account, self.model) + if gajim.config.get('show_self_contact') == 'always': + self_resource = gajim.connections[account].server_resource + self_contact = gajim.contacts.get_contact(account, + gajim.get_jid_from_account(account), resource=self_resource) + if self_contact: + status = gajim.connections[account].status + self.chg_contact_status(self_contact, show, status, account) + self.set_account_status_icon(account) + if show == 'offline': + if self.quit_on_next_offline > -1: + # we want to quit, we are waiting for all accounts to be offline + self.quit_on_next_offline -= 1 + if self.quit_on_next_offline < 1: + # all accounts offline, quit + self.quit_gtkgui_interface() + else: + # No need to redraw contacts if we're quitting + if child_iterA: + self.model[child_iterA][C_AVATAR_PIXBUF] = None + if account in gajim.con_types: + gajim.con_types[account] = None + for jid in gajim.contacts.get_jid_list(account): + lcontact = gajim.contacts.get_contacts(account, jid) + ctrl = gajim.interface.msg_win_mgr.get_gc_control(jid, account) + for contact in [c for c in lcontact if ((c.show != 'offline' or \ + c.is_transport()) and not ctrl)]: + self.chg_contact_status(contact, 'offline', '', account) + self.actions_menu_needs_rebuild = True + self.update_status_combobox() - def get_status_message(self, show, on_response, show_pep=True, - always_ask=False): - ''' get the status message by: - 1/ looking in default status message - 2/ asking to user if needed depending on ask_on(ff)line_status and - always_ask - show_pep can be False to hide pep things from status message or True - ''' - empty_pep = {'activity': '', 'subactivity': '', 'activity_text': '', - 'mood': '', 'mood_text': ''} - if show in gajim.config.get_per('defaultstatusmsg'): - if gajim.config.get_per('defaultstatusmsg', show, 'enabled'): - on_response(gajim.config.get_per('defaultstatusmsg', show, - 'message'), empty_pep) - return - if not always_ask and ((show == 'online' and not gajim.config.get( - 'ask_online_status')) or (show in ('offline', 'invisible') and not \ - gajim.config.get('ask_offline_status'))): - on_response('', empty_pep) - return + def get_status_message(self, show, on_response, show_pep=True, + always_ask=False): + ''' get the status message by: + 1/ looking in default status message + 2/ asking to user if needed depending on ask_on(ff)line_status and + always_ask + show_pep can be False to hide pep things from status message or True + ''' + empty_pep = {'activity': '', 'subactivity': '', 'activity_text': '', + 'mood': '', 'mood_text': ''} + if show in gajim.config.get_per('defaultstatusmsg'): + if gajim.config.get_per('defaultstatusmsg', show, 'enabled'): + on_response(gajim.config.get_per('defaultstatusmsg', show, + 'message'), empty_pep) + return + if not always_ask and ((show == 'online' and not gajim.config.get( + 'ask_online_status')) or (show in ('offline', 'invisible') and not \ + gajim.config.get('ask_offline_status'))): + on_response('', empty_pep) + return - dlg = dialogs.ChangeStatusMessageDialog(on_response, show, show_pep) - dlg.dialog.present() # show it on current workspace + dlg = dialogs.ChangeStatusMessageDialog(on_response, show, show_pep) + dlg.dialog.present() # show it on current workspace - def change_status(self, widget, account, status): - def change(account, status): - def on_response(message, pep_dict): - if message is None: - # user pressed Cancel to change status message dialog - return - self.send_status(account, status, message) - self.send_pep(account, pep_dict) - self.get_status_message(status, on_response) + def change_status(self, widget, account, status): + def change(account, status): + def on_response(message, pep_dict): + if message is None: + # user pressed Cancel to change status message dialog + return + self.send_status(account, status, message) + self.send_pep(account, pep_dict) + self.get_status_message(status, on_response) - if status == 'invisible' and self.connected_rooms(account): - dialogs.ConfirmationDialog( - _('You are participating in one or more group chats'), - _('Changing your status to invisible will result in disconnection ' - 'from those group chats. Are you sure you want to go invisible?'), - on_response_ok = (change, account, status)) - else: - change(account, status) + if status == 'invisible' and self.connected_rooms(account): + dialogs.ConfirmationDialog( + _('You are participating in one or more group chats'), + _('Changing your status to invisible will result in disconnection ' + 'from those group chats. Are you sure you want to go invisible?'), + on_response_ok = (change, account, status)) + else: + change(account, status) - def update_status_combobox(self): - # table to change index in connection.connected to index in combobox - table = {'offline':9, 'connecting':9, 'online':0, 'chat':1, 'away':2, - 'xa':3, 'dnd':4, 'invisible':5} + def update_status_combobox(self): + # table to change index in connection.connected to index in combobox + table = {'offline':9, 'connecting':9, 'online':0, 'chat':1, 'away':2, + 'xa':3, 'dnd':4, 'invisible':5} - # we check if there are more options in the combobox that it should - # if yes, we remove the first ones - while len(self.status_combobox.get_model()) > len(table)+2: - self.status_combobox.remove_text(0) + # we check if there are more options in the combobox that it should + # if yes, we remove the first ones + while len(self.status_combobox.get_model()) > len(table)+2: + self.status_combobox.remove_text(0) - show = helpers.get_global_show() - # temporarily block signal in order not to send status that we show - # in the combobox - self.combobox_callback_active = False - if helpers.statuses_unified(): - self.status_combobox.set_active(table[show]) - else: - uf_show = helpers.get_uf_show(show) - liststore = self.status_combobox.get_model() - liststore.prepend(['SEPARATOR', None, '', True]) - status_combobox_text = uf_show + ' (' + _("desync'ed") +')' - liststore.prepend([status_combobox_text, - gajim.interface.jabber_state_images['16'][show], show, False]) - self.status_combobox.set_active(0) - gajim.interface._change_awn_icon_status(show) - self.combobox_callback_active = True - if gajim.interface.systray_enabled: - gajim.interface.systray.change_status(show) + show = helpers.get_global_show() + # temporarily block signal in order not to send status that we show + # in the combobox + self.combobox_callback_active = False + if helpers.statuses_unified(): + self.status_combobox.set_active(table[show]) + else: + uf_show = helpers.get_uf_show(show) + liststore = self.status_combobox.get_model() + liststore.prepend(['SEPARATOR', None, '', True]) + status_combobox_text = uf_show + ' (' + _("desync'ed") +')' + liststore.prepend([status_combobox_text, + gajim.interface.jabber_state_images['16'][show], show, False]) + self.status_combobox.set_active(0) + gajim.interface._change_awn_icon_status(show) + self.combobox_callback_active = True + if gajim.interface.systray_enabled: + gajim.interface.systray.change_status(show) - def get_show(self, lcontact): - prio = lcontact[0].priority - show = lcontact[0].show - for u in lcontact: - if u.priority > prio: - prio = u.priority - show = u.show - return show + def get_show(self, lcontact): + prio = lcontact[0].priority + show = lcontact[0].show + for u in lcontact: + if u.priority > prio: + prio = u.priority + show = u.show + return show - def on_message_window_delete(self, win_mgr, msg_win): - if gajim.config.get('one_message_window') == 'always_with_roster': - self.show_roster_vbox(True) - gtkgui_helpers.resize_window(self.window, - gajim.config.get('roster_width'), - gajim.config.get('roster_height')) + def on_message_window_delete(self, win_mgr, msg_win): + if gajim.config.get('one_message_window') == 'always_with_roster': + self.show_roster_vbox(True) + gtkgui_helpers.resize_window(self.window, + gajim.config.get('roster_width'), + gajim.config.get('roster_height')) - def close_all_from_dict(self, dic): - '''close all the windows in the given dictionary''' - for w in dic.values(): - if isinstance(w, dict): - self.close_all_from_dict(w) - else: - w.window.destroy() + def close_all_from_dict(self, dic): + '''close all the windows in the given dictionary''' + for w in dic.values(): + if isinstance(w, dict): + self.close_all_from_dict(w) + else: + w.window.destroy() - def close_all(self, account, force=False): - '''close all the windows from an account - if force is True, do not ask confirmation before closing chat/gc windows - ''' - if account in gajim.interface.instances: - self.close_all_from_dict(gajim.interface.instances[account]) - for ctrl in gajim.interface.msg_win_mgr.get_controls(acct=account): - ctrl.parent_win.remove_tab(ctrl, ctrl.parent_win.CLOSE_CLOSE_BUTTON, - force = force) + def close_all(self, account, force=False): + '''close all the windows from an account + if force is True, do not ask confirmation before closing chat/gc windows + ''' + if account in gajim.interface.instances: + self.close_all_from_dict(gajim.interface.instances[account]) + for ctrl in gajim.interface.msg_win_mgr.get_controls(acct=account): + ctrl.parent_win.remove_tab(ctrl, ctrl.parent_win.CLOSE_CLOSE_BUTTON, + force = force) - def on_roster_window_delete_event(self, widget, event): - '''Main window X button was clicked''' - if gajim.interface.systray_enabled and not gajim.config.get( - 'quit_on_roster_x_button') and gajim.config.get('trayicon') != 'on_event': - self.tooltip.hide_tooltip() - self.window.hide() - elif gajim.config.get('quit_on_roster_x_button'): - self.on_quit_request() - else: - def on_ok(checked): - if checked: - gajim.config.set('quit_on_roster_x_button', True) - self.on_quit_request() - dialogs.ConfirmationDialogCheck(_('Really quit Gajim?'), - _('Are you sure you want to quit Gajim?'), - _('Always close Gajim'), on_response_ok=on_ok) - return True # do NOT destroy the window + def on_roster_window_delete_event(self, widget, event): + '''Main window X button was clicked''' + if gajim.interface.systray_enabled and not gajim.config.get( + 'quit_on_roster_x_button') and gajim.config.get('trayicon') != 'on_event': + self.tooltip.hide_tooltip() + self.window.hide() + elif gajim.config.get('quit_on_roster_x_button'): + self.on_quit_request() + else: + def on_ok(checked): + if checked: + gajim.config.set('quit_on_roster_x_button', True) + self.on_quit_request() + dialogs.ConfirmationDialogCheck(_('Really quit Gajim?'), + _('Are you sure you want to quit Gajim?'), + _('Always close Gajim'), on_response_ok=on_ok) + return True # do NOT destroy the window - def prepare_quit(self): - msgwin_width_adjust = 0 + def prepare_quit(self): + msgwin_width_adjust = 0 - # in case show_roster_on_start is False and roster is never shown - # window.window is None - if self.window.window is not None: - x, y = self.window.window.get_root_origin() - gajim.config.set('roster_x-position', x) - gajim.config.set('roster_y-position', y) - width, height = self.window.get_size() - # For the width use the size of the vbox containing the tree and - # status combo, this will cancel out any hpaned width - width = self.xml.get_widget('roster_vbox2').allocation.width - gajim.config.set('roster_width', width) - gajim.config.set('roster_height', height) - if not self.xml.get_widget('roster_vbox2').get_property('visible'): - # The roster vbox is hidden, so the message window is larger - # then we want to save (i.e. the window will grow every startup) - # so adjust. - msgwin_width_adjust = -1 * width - gajim.config.set('show_roster_on_startup', - self.window.get_property('visible')) - gajim.interface.msg_win_mgr.shutdown(msgwin_width_adjust) + # in case show_roster_on_start is False and roster is never shown + # window.window is None + if self.window.window is not None: + x, y = self.window.window.get_root_origin() + gajim.config.set('roster_x-position', x) + gajim.config.set('roster_y-position', y) + width, height = self.window.get_size() + # For the width use the size of the vbox containing the tree and + # status combo, this will cancel out any hpaned width + width = self.xml.get_widget('roster_vbox2').allocation.width + gajim.config.set('roster_width', width) + gajim.config.set('roster_height', height) + if not self.xml.get_widget('roster_vbox2').get_property('visible'): + # The roster vbox is hidden, so the message window is larger + # then we want to save (i.e. the window will grow every startup) + # so adjust. + msgwin_width_adjust = -1 * width + gajim.config.set('show_roster_on_startup', + self.window.get_property('visible')) + gajim.interface.msg_win_mgr.shutdown(msgwin_width_adjust) - gajim.config.set('collapsed_rows', '\t'.join(self.collapsed_rows)) - gajim.interface.save_config() - for account in gajim.connections: - gajim.connections[account].quit(True) - self.close_all(account) - if gajim.interface.systray_enabled: - gajim.interface.hide_systray() + gajim.config.set('collapsed_rows', '\t'.join(self.collapsed_rows)) + gajim.interface.save_config() + for account in gajim.connections: + gajim.connections[account].quit(True) + self.close_all(account) + if gajim.interface.systray_enabled: + gajim.interface.hide_systray() - def quit_gtkgui_interface(self): - '''When we quit the gtk interface : exit gtk''' - self.prepare_quit() - gtk.main_quit() + def quit_gtkgui_interface(self): + '''When we quit the gtk interface : exit gtk''' + self.prepare_quit() + gtk.main_quit() - def on_quit_request(self, widget=None): - ''' user want to quit. Check if he should be warned about messages - pending. Terminate all sessions and send offline to all connected - account. We do NOT really quit gajim here ''' - accounts = gajim.connections.keys() - get_msg = False - for acct in accounts: - if gajim.connections[acct].connected: - get_msg = True - break + def on_quit_request(self, widget=None): + ''' user want to quit. Check if he should be warned about messages + pending. Terminate all sessions and send offline to all connected + account. We do NOT really quit gajim here ''' + accounts = gajim.connections.keys() + get_msg = False + for acct in accounts: + if gajim.connections[acct].connected: + get_msg = True + break - def on_continue2(message, pep_dict): - self.quit_on_next_offline = 0 - accounts_to_disconnect = [] - for acct in accounts: - if gajim.connections[acct].connected: - self.quit_on_next_offline += 1 - accounts_to_disconnect.append(acct) + def on_continue2(message, pep_dict): + self.quit_on_next_offline = 0 + accounts_to_disconnect = [] + for acct in accounts: + if gajim.connections[acct].connected: + self.quit_on_next_offline += 1 + accounts_to_disconnect.append(acct) - for acct in accounts_to_disconnect: - self.send_status(acct, 'offline', message) - self.send_pep(acct, pep_dict) + for acct in accounts_to_disconnect: + self.send_status(acct, 'offline', message) + self.send_pep(acct, pep_dict) - if not self.quit_on_next_offline: - self.quit_gtkgui_interface() + if not self.quit_on_next_offline: + self.quit_gtkgui_interface() - def on_continue(message, pep_dict): - if message is None: - # user pressed Cancel to change status message dialog - return - # check if we have unread messages - unread = gajim.events.get_nb_events() - if not gajim.config.get('notify_on_all_muc_messages'): - unread_not_to_notify = gajim.events.get_nb_events( - ['printed_gc_msg']) - unread -= unread_not_to_notify + def on_continue(message, pep_dict): + if message is None: + # user pressed Cancel to change status message dialog + return + # check if we have unread messages + unread = gajim.events.get_nb_events() + if not gajim.config.get('notify_on_all_muc_messages'): + unread_not_to_notify = gajim.events.get_nb_events( + ['printed_gc_msg']) + unread -= unread_not_to_notify - # check if we have recent messages - recent = False - for win in gajim.interface.msg_win_mgr.windows(): - for ctrl in win.controls(): - fjid = ctrl.get_full_jid() - if fjid in gajim.last_message_time[ctrl.account]: - if time.time() - gajim.last_message_time[ctrl.account][fjid] \ - < 2: - recent = True - break - if recent: - break + # check if we have recent messages + recent = False + for win in gajim.interface.msg_win_mgr.windows(): + for ctrl in win.controls(): + fjid = ctrl.get_full_jid() + if fjid in gajim.last_message_time[ctrl.account]: + if time.time() - gajim.last_message_time[ctrl.account][fjid] \ + < 2: + recent = True + break + if recent: + break - if unread or recent: - dialogs.ConfirmationDialog(_('You have unread messages'), - _('Messages will only be available for reading them later if you' - ' have history enabled and contact is in your roster.'), - on_response_ok=(on_continue2, message, pep_dict)) - return - on_continue2(message, pep_dict) + if unread or recent: + dialogs.ConfirmationDialog(_('You have unread messages'), + _('Messages will only be available for reading them later if you' + ' have history enabled and contact is in your roster.'), + on_response_ok=(on_continue2, message, pep_dict)) + return + on_continue2(message, pep_dict) - if get_msg: - self.get_status_message('offline', on_continue, show_pep=False) - else: - on_continue('', None) + if get_msg: + self.get_status_message('offline', on_continue, show_pep=False) + else: + on_continue('', None) ################################################################################ ### Menu and GUI callbacks ### FIXME: order callbacks in itself... ################################################################################ - def on_actions_menuitem_activate(self, widget): - self.make_menu() - - def on_edit_menuitem_activate(self, widget): - '''need to call make_menu to build profile, avatar item''' - self.make_menu() - - def on_bookmark_menuitem_activate(self, widget, account, bookmark): - gajim.interface.join_gc_room(account, bookmark['jid'], bookmark['nick'], - bookmark['password']) - - def on_send_server_message_menuitem_activate(self, widget, account): - server = gajim.config.get_per('accounts', account, 'hostname') - server += '/announce/online' - dialogs.SingleMessageWindow(account, server, 'send') - - def on_xml_console_menuitem_activate(self, widget, account): - if 'xml_console' in gajim.interface.instances[account]: - gajim.interface.instances[account]['xml_console'].window.present() - else: - gajim.interface.instances[account]['xml_console'] = \ - dialogs.XMLConsoleWindow(account) - - def on_archiving_preferences_menuitem_activate(self, widget, account): - if 'archiving_preferences' in gajim.interface.instances[account]: - gajim.interface.instances[account]['archiving_preferences'].window.\ - present() - else: - gajim.interface.instances[account]['archiving_preferences'] = \ - dialogs.ArchivingPreferencesWindow(account) - - def on_privacy_lists_menuitem_activate(self, widget, account): - if 'privacy_lists' in gajim.interface.instances[account]: - gajim.interface.instances[account]['privacy_lists'].window.present() - else: - gajim.interface.instances[account]['privacy_lists'] = \ - dialogs.PrivacyListsWindow(account) - - def on_set_motd_menuitem_activate(self, widget, account): - server = gajim.config.get_per('accounts', account, 'hostname') - server += '/announce/motd' - dialogs.SingleMessageWindow(account, server, 'send') - - def on_update_motd_menuitem_activate(self, widget, account): - server = gajim.config.get_per('accounts', account, 'hostname') - server += '/announce/motd/update' - dialogs.SingleMessageWindow(account, server, 'send') - - def on_delete_motd_menuitem_activate(self, widget, account): - server = gajim.config.get_per('accounts', account, 'hostname') - server += '/announce/motd/delete' - gajim.connections[account].send_motd(server) - - def on_history_manager_menuitem_activate(self, widget): - if os.name == 'nt': - if os.path.exists('history_manager.exe'): # user is running stable - helpers.exec_command('history_manager.exe') - else: # user is running svn - helpers.exec_command('%s history_manager.py' % sys.executable) - else: # Unix user - helpers.exec_command('%s history_manager.py' % sys.executable) - - def on_info(self, widget, contact, account): - '''Call vcard_information_window class to display contact's information''' - if gajim.connections[account].is_zeroconf: - self.on_info_zeroconf(widget, contact, account) - return - - info = gajim.interface.instances[account]['infos'] - if contact.jid in info: - info[contact.jid].window.present() - else: - info[contact.jid] = vcard.VcardWindow(contact, account) - - def on_info_zeroconf(self, widget, contact, account): - info = gajim.interface.instances[account]['infos'] - if contact.jid in info: - info[contact.jid].window.present() - else: - contact = gajim.contacts.get_first_contact_from_jid(account, - contact.jid) - if contact.show in ('offline', 'error'): - # don't show info on offline contacts - return - info[contact.jid] = vcard.ZeroconfVcardWindow(contact, account) - - def on_roster_treeview_leave_notify_event(self, widget, event): - props = widget.get_path_at_pos(int(event.x), int(event.y)) - if self.tooltip.timeout > 0: - if not props or self.tooltip.id == props[0]: - self.tooltip.hide_tooltip() - - def on_roster_treeview_motion_notify_event(self, widget, event): - model = widget.get_model() - props = widget.get_path_at_pos(int(event.x), int(event.y)) - if self.tooltip.timeout > 0: - if not props or self.tooltip.id != props[0]: - self.tooltip.hide_tooltip() - if props: - row = props[0] - titer = None - try: - titer = model.get_iter(row) - except Exception: - self.tooltip.hide_tooltip() - return - if model[titer][C_TYPE] in ('contact', 'self_contact'): - # we're on a contact entry in the roster - account = model[titer][C_ACCOUNT].decode('utf-8') - jid = model[titer][C_JID].decode('utf-8') - if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: - self.tooltip.id = row - contacts = gajim.contacts.get_contacts(account, jid) - connected_contacts = [] - for c in contacts: - if c.show not in ('offline', 'error'): - connected_contacts.append(c) - if not connected_contacts: - # no connected contacts, show the ofline one - connected_contacts = contacts - self.tooltip.account = account - self.tooltip.timeout = gobject.timeout_add(500, - self.show_tooltip, connected_contacts) - elif model[titer][C_TYPE] == 'groupchat': - account = model[titer][C_ACCOUNT].decode('utf-8') - jid = model[titer][C_JID].decode('utf-8') - if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: - self.tooltip.id = row - contact = gajim.contacts.get_contacts(account, jid) - self.tooltip.account = account - self.tooltip.timeout = gobject.timeout_add(500, - self.show_tooltip, contact) - elif model[titer][C_TYPE] == 'account': - # we're on an account entry in the roster - account = model[titer][C_ACCOUNT].decode('utf-8') - if account == 'all': - if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: - self.tooltip.id = row - self.tooltip.account = None - self.tooltip.timeout = gobject.timeout_add(500, - self.show_tooltip, []) - return - jid = gajim.get_jid_from_account(account) - contacts = [] - connection = gajim.connections[account] - # get our current contact info - - nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts( - accounts = [account]) - account_name = account - if gajim.account_is_connected(account): - account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total)) - contact = gajim.contacts.create_self_contact(jid=jid, account=account, - name=account_name, show=connection.get_status(), - status=connection.status, resource=connection.server_resource, - priority=connection.priority) - if gajim.connections[account].gpg: - contact.keyID = gajim.config.get_per('accounts', connection.name, - 'keyid') - contacts.append(contact) - # if we're online ... - if connection.connection: - roster = connection.connection.getRoster() - # in threadless connection when no roster stanza is sent, - # 'roster' is None - if roster and roster.getItem(jid): - resources = roster.getResources(jid) - # ...get the contact info for our other online resources - for resource in resources: - # Check if we already have this resource - found = False - for contact_ in contacts: - if contact_.resource == resource: - found = True - break - if found: - continue - show = roster.getShow(jid+'/'+resource) - if not show: - show = 'online' - contact = gajim.contacts.create_self_contact(jid=jid, - account=account, show=show, status=roster.getStatus(jid + '/' + resource), - priority=roster.getPriority(jid + '/' + resource), - resource=resource) - contacts.append(contact) - if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: - self.tooltip.id = row - self.tooltip.account = None - self.tooltip.timeout = gobject.timeout_add(500, - self.show_tooltip, contacts) - - def on_agent_logging(self, widget, jid, state, account): - '''When an agent is requested to log in or off''' - gajim.connections[account].send_agent_status(jid, state) - - def on_edit_agent(self, widget, contact, account): - '''When we want to modify the agent registration''' - gajim.connections[account].request_register_agent_info(contact.jid) - - def on_remove_agent(self, widget, list_): - '''When an agent is requested to be removed. list_ is a list of - (contact, account) tuple''' - for (contact, account) in list_: - if gajim.config.get_per('accounts', account, 'hostname') == \ - contact.jid: - # We remove the server contact - # remove it from treeview - gajim.connections[account].unsubscribe(contact.jid) - self.remove_contact(contact.jid, account, backend=True) - return - - def remove(list_): - for (contact, account) in list_: - full_jid = contact.get_full_jid() - gajim.connections[account].unsubscribe_agent(full_jid) - # remove transport from treeview - self.remove_contact(contact.jid, account, backend=True) - - # Check if there are unread events from some contacts - has_unread_events = False - for (contact, account) in list_: - for jid in gajim.events.get_events(account): - if jid.endswith(contact.jid): - has_unread_events = True - break - if has_unread_events: - dialogs.ErrorDialog(_('You have unread messages'), - _('You must read them before removing this transport.')) - return - if len(list_) == 1: - pritext = _('Transport "%s" will be removed') % list_[0][0].jid - sectext = _('You will no longer be able to send and receive messages ' - 'from contacts using this transport.') - else: - pritext = _('Transports will be removed') - jids = '' - for (contact, account) in list_: - jids += '\n ' + contact.get_shown_name() + ',' - jids = jids[:-1] + '.' - sectext = _('You will no longer be able to send and receive messages ' - 'to contacts from these transports: %s') % jids - dialogs.ConfirmationDialog(pritext, sectext, - on_response_ok = (remove, list_)) - - def on_block(self, widget, list_, group=None): - ''' When clicked on the 'block' button in context menu. - list_ is a list of (contact, account)''' - def on_continue(msg, pep_dict): - if msg is None: - # user pressed Cancel to change status message dialog - return - accounts = [] - if group is None: - for (contact, account) in list_: - if account not in accounts: - if not gajim.connections[account].privacy_rules_supported: - continue - accounts.append(account) - self.send_status(account, 'offline', msg, to=contact.jid) - new_rule = {'order': u'1', 'type': u'jid', 'action': u'deny', - 'value' : contact.jid, 'child': [u'message', u'iq', - u'presence-out']} - gajim.connections[account].blocked_list.append(new_rule) - # needed for draw_contact: - gajim.connections[account].blocked_contacts.append( - contact.jid) - self.draw_contact(contact.jid, account) - else: - for (contact, account) in list_: - if account not in accounts: - if not gajim.connections[account].privacy_rules_supported: - continue - accounts.append(account) - # needed for draw_group: - gajim.connections[account].blocked_groups.append(group) - self.draw_group(group, account) - self.send_status(account, 'offline', msg, to=contact.jid) - self.draw_contact(contact.jid, account) - new_rule = {'order': u'1', 'type': u'group', 'action': u'deny', - 'value' : group, 'child': [u'message', u'iq', u'presence-out']} - gajim.connections[account].blocked_list.append(new_rule) - for account in accounts: - connection = gajim.connections[account] - connection.set_privacy_list('block', connection.blocked_list) - if len(connection.blocked_list) == 1: - connection.set_active_list('block') - connection.set_default_list('block') - connection.get_privacy_list('block') - - def _block_it(is_checked=None): - if is_checked is not None: # dialog has been shown - if is_checked: # user does not want to be asked again - gajim.config.set('confirm_block', 'no') - else: - gajim.config.set('confirm_block', 'yes') - self.get_status_message('offline', on_continue, show_pep=False) - - confirm_block = gajim.config.get('confirm_block') - if confirm_block == 'no': - _block_it() - return - pritext = _('You are about to block a contact. Are you sure you want' - ' to continue?') - sectext = _('This contact will see you offline and you will not receive ' - 'messages he will send you.') - dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, - _('Do _not ask me again'), on_response_ok=_block_it) - - def on_unblock(self, widget, list_, group=None): - ''' When clicked on the 'unblock' button in context menu. ''' - accounts = [] - if group is None: - for (contact, account) in list_: - if account not in accounts: - if gajim.connections[account].privacy_rules_supported: - accounts.append(account) - gajim.connections[account].new_blocked_list = [] - gajim.connections[account].to_unblock = [] - gajim.connections[account].to_unblock.append(contact.jid) - else: - gajim.connections[account].to_unblock.append(contact.jid) - # needed for draw_contact: - if contact.jid in gajim.connections[account].blocked_contacts: - gajim.connections[account].blocked_contacts.remove(contact.jid) - self.draw_contact(contact.jid, account) - for account in accounts: - for rule in gajim.connections[account].blocked_list: - if rule['action'] != 'deny' or rule['type'] != 'jid' \ - or rule['value'] not in gajim.connections[account].to_unblock: - gajim.connections[account].new_blocked_list.append(rule) - else: - for (contact, account) in list_: - if account not in accounts: - if gajim.connections[account].privacy_rules_supported: - accounts.append(account) - # needed for draw_group: - if group in gajim.connections[account].blocked_groups: - gajim.connections[account].blocked_groups.remove(group) - self.draw_group(group, account) - gajim.connections[account].new_blocked_list = [] - for rule in gajim.connections[account].blocked_list: - if rule['action'] != 'deny' or rule['type'] != 'group' \ - or rule['value'] != group: - gajim.connections[account].new_blocked_list.append(rule) - self.draw_contact(contact.jid, account) - for account in accounts: - gajim.connections[account].set_privacy_list('block', - gajim.connections[account].new_blocked_list) - gajim.connections[account].get_privacy_list('block') - if len(gajim.connections[account].new_blocked_list) == 0: - gajim.connections[account].blocked_list = [] - gajim.connections[account].blocked_contacts = [] - gajim.connections[account].blocked_groups = [] - gajim.connections[account].set_default_list('') - gajim.connections[account].set_active_list('') - gajim.connections[account].del_privacy_list('block') - if 'blocked_contacts' in gajim.interface.instances[account]: - gajim.interface.instances[account]['blocked_contacts'].\ - privacy_list_received([]) - for (contact, account) in list_: - if not self.regroup: - show = gajim.SHOW_LIST[gajim.connections[account].connected] - else: # accounts merged - show = helpers.get_global_show() - if show == 'invisible': - # Don't send our presence if we're invisible - continue - if account not in accounts: - accounts.append(account) - if gajim.connections[account].privacy_rules_supported: - self.send_status(account, show, - gajim.connections[account].status, to=contact.jid) - else: - self.send_status(account, show, - gajim.connections[account].status, to=contact.jid) - - def on_rename(self, widget, row_type, jid, account): - # this function is called either by F2 or by Rename menuitem - if 'rename' in gajim.interface.instances: - gajim.interface.instances['rename'].dialog.present() - return - - # account is offline, don't allow to rename - if gajim.connections[account].connected < 2: - return - if row_type in ('contact', 'agent'): - # it's jid - title = _('Rename Contact') - message = _('Enter a new nickname for contact %s') % jid - old_text = gajim.contacts.get_contact_with_highest_priority(account, - jid).name - elif row_type == 'group': - if jid in helpers.special_groups + (_('General'),): - return - old_text = jid - title = _('Rename Group') - message = _('Enter a new name for group %s') % \ - gobject.markup_escape_text(jid) - - def on_renamed(new_text, account, row_type, jid, old_text): - if 'rename' in gajim.interface.instances: - del gajim.interface.instances['rename'] - if row_type in ('contact', 'agent'): - if old_text == new_text: - return - for contact in gajim.contacts.get_contacts(account, jid): - contact.name = new_text - gajim.connections[account].update_contact(jid, new_text, \ - contact.groups) - self.draw_contact(jid, account) - # Update opened chats - for ctrl in gajim.interface.msg_win_mgr.get_controls(jid, account): - ctrl.update_ui() - win = gajim.interface.msg_win_mgr.get_window(jid, account) - win.redraw_tab(ctrl) - win.show_title() - elif row_type == 'group': - # in C_JID column, we hold the group name (which is not escaped) - self.rename_group(old_text, new_text, account) - - def on_canceled(): - if 'rename' in gajim.interface.instances: - del gajim.interface.instances['rename'] - - gajim.interface.instances['rename'] = dialogs.InputDialog(title, message, - old_text, False, (on_renamed, account, row_type, jid, old_text), - on_canceled) - - def on_remove_group_item_activated(self, widget, group, account): - def on_ok(checked): - for contact in gajim.contacts.get_contacts_from_group(account, group): - if not checked: - self.remove_contact_from_groups(contact.jid,account, [group]) - else: - gajim.connections[account].unsubscribe(contact.jid) - self.remove_contact(contact.jid, account, backend=True) - - dialogs.ConfirmationDialogCheck(_('Remove Group'), - _('Do you want to remove group %s from the roster?') % group, - _('Also remove all contacts in this group from your roster'), - on_response_ok=on_ok) - - def on_assign_pgp_key(self, widget, contact, account): - attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() - keys = {} - keyID = _('None') - for i in xrange(len(attached_keys)/2): - keys[attached_keys[2*i]] = attached_keys[2*i+1] - if attached_keys[2*i] == contact.jid: - keyID = attached_keys[2*i+1] - public_keys = gajim.connections[account].ask_gpg_keys() - public_keys[_('None')] = _('None') - - def on_key_selected(keyID): - if keyID is None: - return - if keyID[0] == _('None'): - if contact.jid in keys: - del keys[contact.jid] - keyID = '' - else: - keyID = keyID[0] - keys[contact.jid] = keyID - - ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account) - if ctrl: - ctrl.update_ui() - - keys_str = '' - for jid in keys: - keys_str += jid + ' ' + keys[jid] + ' ' - gajim.config.set_per('accounts', account, 'attached_gpg_keys', - keys_str) - for u in gajim.contacts.get_contacts(account, contact.jid): - u.keyID = helpers.prepare_and_validate_gpg_keyID(account, - contact.jid, keyID) - - dialogs.ChooseGPGKeyDialog(_('Assign OpenPGP Key'), - _('Select a key to apply to the contact'), public_keys, - on_key_selected, selected=keyID) - - def on_set_custom_avatar_activate(self, widget, contact, account): - def on_ok(widget, path_to_file): - filesize = os.path.getsize(path_to_file) # in bytes - invalid_file = False - msg = '' - if os.path.isfile(path_to_file): - stat = os.stat(path_to_file) - if stat[6] == 0: - invalid_file = True - msg = _('File is empty') - else: - invalid_file = True - msg = _('File does not exist') - if invalid_file: - dialogs.ErrorDialog(_('Could not load image'), msg) - return - try: - pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file) - if filesize > 16384: # 16 kb - # get the image at 'tooltip size' - # and hope that user did not specify in ACE crazy size - pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'tooltip') - except gobject.GError, msg: # unknown format - # msg should be string, not object instance - msg = str(msg) - dialogs.ErrorDialog(_('Could not load image'), msg) - return - gajim.interface.save_avatar_files(contact.jid, pixbuf, local=True) - dlg.destroy() - self.update_avatar_in_gui(contact.jid, account) - - def on_clear(widget): - dlg.destroy() - # Delete file: - gajim.interface.remove_avatar_files(contact.jid, local=True) - self.update_avatar_in_gui(contact.jid, account) - - dlg = dialogs.AvatarChooserDialog(on_response_ok=on_ok, - on_response_clear=on_clear) - - def on_edit_groups(self, widget, list_): - dialogs.EditGroupsDialog(list_) - - def on_history(self, widget, contact, account): - '''When history menuitem is activated: call log window''' - if 'logs' in gajim.interface.instances: - gajim.interface.instances['logs'].window.present() - gajim.interface.instances['logs'].open_history(contact.jid, account) - else: - gajim.interface.instances['logs'] = history_window.\ - HistoryWindow(contact.jid, account) - - def on_disconnect(self, widget, jid, account): - '''When disconnect menuitem is activated: disconect from room''' - if jid in gajim.interface.minimized_controls[account]: - ctrl = gajim.interface.minimized_controls[account][jid] - ctrl.shutdown() - ctrl.got_disconnected() - self.remove_groupchat(jid, account) - - def on_reconnect(self, widget, jid, account): - '''When disconnect menuitem is activated: disconect from room''' - if jid in gajim.interface.minimized_controls[account]: - ctrl = gajim.interface.minimized_controls[account][jid] - gajim.interface.join_gc_room(account, jid, ctrl.nick, - gajim.gc_passwords.get(jid, '')) - - def on_send_single_message_menuitem_activate(self, widget, account, - contact=None): - if contact is None: - dialogs.SingleMessageWindow(account, action='send') - elif isinstance(contact, list): - dialogs.SingleMessageWindow(account, contact, 'send') - else: - jid = contact.jid - if contact.jid == gajim.get_jid_from_account(account): - jid += '/' + contact.resource - dialogs.SingleMessageWindow(account, jid, 'send') - - def on_send_file_menuitem_activate(self, widget, contact, account, - resource=None): - gajim.interface.instances['file_transfers'].show_file_send_request( - account, contact) - - def on_add_special_notification_menuitem_activate(self, widget, jid): - dialogs.AddSpecialNotificationDialog(jid) - - def on_invite_to_new_room(self, widget, list_, resource=None): - ''' resource parameter MUST NOT be used if more than one contact in - list ''' - account_list = [] - jid_list = [] - for (contact, account) in list_: - if contact.jid not in jid_list: - if resource: # we MUST have one contact only in list_ - fjid = contact.jid + '/' + resource - jid_list.append(fjid) - else: - jid_list.append(contact.jid) - if account not in account_list: - account_list.append(account) - # transform None in 'jabber' - type_ = gajim.get_transport_name_from_jid(jid_list[0]) or 'jabber' - for account in account_list: - if gajim.connections[account].muc_jid[type_]: - # create the room on this muc server - if 'join_gc' in gajim.interface.instances[account]: - gajim.interface.instances[account]['join_gc'].window.destroy() - try: - gajim.interface.instances[account]['join_gc'] = \ - dialogs.JoinGroupchatWindow(account, - gajim.connections[account].muc_jid[type_], - automatic = {'invities': jid_list}) - except GajimGeneralException: - continue - break - - def on_invite_to_room(self, widget, list_, room_jid, room_account, - resource=None): - ''' resource parameter MUST NOT be used if more than one contact in - list ''' - for e in list_: - contact = e[0] - contact_jid = contact.jid - if resource: # we MUST have one contact only in list_ - contact_jid += '/' + resource - gajim.connections[room_account].send_invite(room_jid, contact_jid) - - def on_all_groupchat_maximized(self, widget, group_list): - for (contact, account) in group_list: - self.on_groupchat_maximized(widget, contact.jid, account) - - def on_groupchat_maximized(self, widget, jid, account): - '''When a groupchat is maximised''' - if not jid in gajim.interface.minimized_controls[account]: - # Already opened? - gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid, account) - if gc_control: - mw = gajim.interface.msg_win_mgr.get_window(jid, account) - mw.set_active_tab(gc_control) - mw.window.window.focus(gtk.get_current_event_time()) - return - ctrl = gajim.interface.minimized_controls[account][jid] - mw = gajim.interface.msg_win_mgr.get_window(jid, account) - if not mw: - mw = gajim.interface.msg_win_mgr.create_window(ctrl.contact, - ctrl.account, ctrl.type_id) - ctrl.parent_win = mw - mw.new_tab(ctrl) - mw.set_active_tab(ctrl) - mw.window.window.focus(gtk.get_current_event_time()) - self.remove_groupchat(jid, account) - - def on_edit_account(self, widget, account): - if 'accounts' in gajim.interface.instances: - gajim.interface.instances['accounts'].window.present() - else: - gajim.interface.instances['accounts'] = config.AccountsWindow() - gajim.interface.instances['accounts'].select_account(account) - - def on_zeroconf_properties(self, widget, account): - if 'accounts' in gajim.interface.instances: - gajim.interface.instances['accounts'].window.present() - else: - gajim.interface.instances['accounts'] = config.AccountsWindow() - gajim.interface.instances['accounts'].select_account(account) - - def on_open_gmail_inbox(self, widget, account): - url = gajim.connections[account].gmail_url - if url: - helpers.launch_browser_mailer('url', url) - - def on_change_status_message_activate(self, widget, account): - show = gajim.SHOW_LIST[gajim.connections[account].connected] - def on_response(message, pep_dict): - if message is None: # None is if user pressed Cancel - return - self.send_status(account, show, message) - self.send_pep(account, pep_dict) - dialogs.ChangeStatusMessageDialog(on_response, show) - - def on_add_to_roster(self, widget, contact, account): - dialogs.AddNewContactWindow(account, contact.jid, contact.name) - - - def on_roster_treeview_scroll_event(self, widget, event): - self.tooltip.hide_tooltip() - - def on_roster_treeview_key_press_event(self, widget, event): - '''when a key is pressed in the treeviews''' - self.tooltip.hide_tooltip() - if event.keyval == gtk.keysyms.Escape: - self.tree.get_selection().unselect_all() - elif event.keyval == gtk.keysyms.F2: - treeselection = self.tree.get_selection() - model, list_of_paths = treeselection.get_selected_rows() - if len(list_of_paths) != 1: - return - path = list_of_paths[0] - type_ = model[path][C_TYPE] - if type_ in ('contact', 'group', 'agent'): - jid = model[path][C_JID].decode('utf-8') - account = model[path][C_ACCOUNT].decode('utf-8') - self.on_rename(widget, type_, jid, account) - - elif event.keyval == gtk.keysyms.Delete: - treeselection = self.tree.get_selection() - model, list_of_paths = treeselection.get_selected_rows() - if not len(list_of_paths): - return - type_ = model[list_of_paths[0]][C_TYPE] - account = model[list_of_paths[0]][C_ACCOUNT].decode('utf-8') - list_ = [] - for path in list_of_paths: - if model[path][C_TYPE] != type_: - return - jid = model[path][C_JID].decode('utf-8') - account = model[path][C_ACCOUNT].decode('utf-8') - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - list_.append((contact, account)) - if type_ in ('account', 'group', 'self_contact') or \ - account == gajim.ZEROCONF_ACC_NAME: - return - if type_ == 'contact': - self.on_req_usub(widget, list_) - elif type_ == 'agent': - self.on_remove_agent(widget, list_) - - def on_roster_treeview_button_release_event(self, widget, event): - try: - path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0] - except TypeError: - return False - - if event.button == 1: # Left click - if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \ - not event.state & gtk.gdk.CONTROL_MASK: - # Check if button has been pressed on the same row - if self.clicked_path == path: - self.on_row_activated(widget, path) - self.clicked_path = None - - def on_roster_treeview_button_press_event(self, widget, event): - # hide tooltip, no matter the button is pressed - self.tooltip.hide_tooltip() - try: - pos = self.tree.get_path_at_pos(int(event.x), int(event.y)) - path, x = pos[0], pos[2] - except TypeError: - self.tree.get_selection().unselect_all() - return False - - if event.button == 3: # Right click - try: - model, list_of_paths = self.tree.get_selection().get_selected_rows() - except TypeError: - list_of_paths = [] - if path not in list_of_paths: - self.tree.get_selection().unselect_all() - self.tree.get_selection().select_path(path) - return self.show_treeview_menu(event) - - elif event.button == 2: # Middle click - try: - model, list_of_paths = self.tree.get_selection().get_selected_rows() - except TypeError: - list_of_paths = [] - if list_of_paths != [path]: - self.tree.get_selection().unselect_all() - self.tree.get_selection().select_path(path) - type_ = model[path][C_TYPE] - if type_ in ('agent', 'contact', 'self_contact', 'groupchat'): - self.on_row_activated(widget, path) - elif type_ == 'account': - account = model[path][C_ACCOUNT].decode('utf-8') - if account != 'all': - show = gajim.connections[account].connected - if show > 1: # We are connected - self.on_change_status_message_activate(widget, account) - return True - show = helpers.get_global_show() - if show == 'offline': - return True - def on_response(message, pep_dict): - if message is None: - return True - for acct in gajim.connections: - if not gajim.config.get_per('accounts', acct, - 'sync_with_global_status'): - continue - current_show = gajim.SHOW_LIST[gajim.connections[acct].\ - connected] - self.send_status(acct, current_show, message) - self.send_pep(acct, pep_dict) - dialogs.ChangeStatusMessageDialog(on_response, show) - return True - - elif event.button == 1: # Left click - model = self.modelfilter - type_ = model[path][C_TYPE] - # x_min is the x start position of status icon column - if gajim.config.get('avatar_position_in_roster') == 'left': - x_min = gajim.config.get('roster_avatar_width') - else: - x_min = 0 - if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \ - not event.state & gtk.gdk.CONTROL_MASK: - # Don't handle double click if we press icon of a metacontact - titer = model.get_iter(path) - if x > x_min and x < x_min + 27 and type_ == 'contact' and \ - model.iter_has_child(titer): - if (self.tree.row_expanded(path)): - self.tree.collapse_row(path) - else: - self.tree.expand_row(path, False) - return - # We just save on which row we press button, and open chat window on - # button release to be able to do DND without opening chat window - self.clicked_path = path - return - else: - if type_ == 'group' and x < 27: - # first cell in 1st column (the arrow SINGLE clicked) - if (self.tree.row_expanded(path)): - self.tree.collapse_row(path) - else: - self.tree.expand_row(path, False) - - elif type_ == 'contact' and x > x_min and x < x_min + 27: - if (self.tree.row_expanded(path)): - self.tree.collapse_row(path) - else: - self.tree.expand_row(path, False) - - def on_req_usub(self, widget, list_): - '''Remove a contact. list_ is a list of (contact, account) tuples''' - def on_ok(is_checked, list_): - remove_auth = True - if len(list_) == 1: - contact = list_[0][0] - if contact.sub != 'to' and is_checked: - remove_auth = False - for (contact, account) in list_: - if _('Not in Roster') not in contact.get_shown_groups(): - gajim.connections[account].unsubscribe(contact.jid, remove_auth) - self.remove_contact(contact.jid, account, backend=True) - if not remove_auth and contact.sub == 'both': - contact.name = '' - contact.groups = [] - contact.sub = 'from' - # we can't see him, but have to set it manually in contact - contact.show = 'offline' - gajim.contacts.add_contact(account, contact) - self.add_contact(contact.jid, account) - def on_ok2(list_): - on_ok(False, list_) - - if len(list_) == 1: - contact = list_[0][0] - pritext = _('Contact "%s" will be removed from your roster') % \ - contact.get_shown_name() - sectext = _('You are about to remove "%(name)s" (%(jid)s) from your ' - 'roster.\n') % {'name': contact.get_shown_name(), - 'jid': contact.jid} - if contact.sub == 'to': - dialogs.ConfirmationDialog(pritext, sectext + \ - _('By removing this contact you also remove authorization ' - 'resulting in him or her always seeing you as offline.'), - on_response_ok = (on_ok2, list_)) - elif _('Not in Roster') in contact.get_shown_groups(): - # Contact is not in roster - dialogs.ConfirmationDialog(pritext, sectext + \ - _('Do you want to continue?'), on_response_ok = (on_ok2, list_)) - else: - dialogs.ConfirmationDialogCheck(pritext, sectext + \ - _('By removing this contact you also by default remove ' - 'authorization resulting in him or her always seeing you as ' - 'offline.'), - _('I want this contact to know my status after removal'), - on_response_ok = (on_ok, list_)) - else: - # several contact to remove at the same time - pritext = _('Contacts will be removed from your roster') - jids = '' - for (contact, account) in list_: - jids += '\n ' + contact.get_shown_name() + ' (%s)' % contact.jid +\ - ',' - sectext = _('By removing these contacts:%s\nyou also remove ' - 'authorization resulting in them always seeing you as offline.') % \ - jids - dialogs.ConfirmationDialog(pritext, sectext, - on_response_ok = (on_ok2, list_)) - - def on_send_custom_status(self, widget, contact_list, show, group=None): - '''send custom status''' - # contact_list has only one element except if group != None - def on_response(message, pep_dict): - if message is None: # None if user pressed Cancel - return - account_list = [] - for (contact, account) in contact_list: - if account not in account_list: - account_list.append(account) - # 1. update status_sent_to_[groups|users] list - if group: - for account in account_list: - if account not in gajim.interface.status_sent_to_groups: - gajim.interface.status_sent_to_groups[account] = {} - gajim.interface.status_sent_to_groups[account][group] = show - else: - for (contact, account) in contact_list: - if account not in gajim.interface.status_sent_to_users: - gajim.interface.status_sent_to_users[account] = {} - gajim.interface.status_sent_to_users[account][contact.jid] = show - - # 2. update privacy lists if main status is invisible - for account in account_list: - if gajim.SHOW_LIST[gajim.connections[account].connected] == \ - 'invisible': - gajim.connections[account].set_invisible_rule() - - # 3. send directed presence - for (contact, account) in contact_list: - our_jid = gajim.get_jid_from_account(account) - jid = contact.jid - if jid == our_jid: - jid += '/' + contact.resource - self.send_status(account, show, message, to=jid) - - def send_it(is_checked=None): - if is_checked is not None: # dialog has been shown - if is_checked: # user does not want to be asked again - gajim.config.set('confirm_custom_status', 'no') - else: - gajim.config.set('confirm_custom_status', 'yes') - self.get_status_message(show, on_response, show_pep=False, - always_ask=True) - - confirm_custom_status = gajim.config.get('confirm_custom_status') - if confirm_custom_status == 'no': - send_it() - return - pritext = _('You are about to send a custom status. Are you sure you want' - ' to continue?') - sectext = _('This contact will temporarily see you as %(status)s, ' - 'but only until you change your status. Then he will see your global ' - 'status.') % {'status': show} - dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, - _('Do _not ask me again'), on_response_ok=send_it) - - def on_status_combobox_changed(self, widget): - '''When we change our status via the combobox''' - model = self.status_combobox.get_model() - active = self.status_combobox.get_active() - if active == -1: # no active item - return - if not self.combobox_callback_active: - self.previous_status_combobox_active = active - return - accounts = gajim.connections.keys() - if len(accounts) == 0: - dialogs.ErrorDialog(_('No account available'), - _('You must create an account before you can chat with other contacts.')) - self.update_status_combobox() - return - status = model[active][2].decode('utf-8') - statuses_unified = helpers.statuses_unified() # status "desync'ed" or not - if (active == 7 and statuses_unified) or (active == 9 and \ - not statuses_unified): - # 'Change status message' selected: - # do not change show, just show change status dialog - status = model[self.previous_status_combobox_active][2].decode('utf-8') - def on_response(message, pep_dict): - if message is not None: # None if user pressed Cancel - for account in accounts: - if not gajim.config.get_per('accounts', account, - 'sync_with_global_status'): - continue - current_show = gajim.SHOW_LIST[ - gajim.connections[account].connected] - self.send_status(account, current_show, message) - self.send_pep(account, pep_dict) - self.combobox_callback_active = False - self.status_combobox.set_active( - self.previous_status_combobox_active) - self.combobox_callback_active = True - dialogs.ChangeStatusMessageDialog(on_response, status) - return - # we are about to change show, so save this new show so in case - # after user chooses "Change status message" menuitem - # we can return to this show - self.previous_status_combobox_active = active - connected_accounts = gajim.get_number_of_connected_accounts() - - def on_continue(message, pep_dict): - if message is None: - # user pressed Cancel to change status message dialog - self.update_status_combobox() - return - global_sync_accounts = [] - for acct in accounts: - if gajim.config.get_per('accounts', acct, - 'sync_with_global_status'): - global_sync_accounts.append(acct) - global_sync_connected_accounts = \ - gajim.get_number_of_connected_accounts(global_sync_accounts) - for account in accounts: - if not gajim.config.get_per('accounts', account, - 'sync_with_global_status'): - continue - # we are connected (so we wanna change show and status) - # or no account is connected and we want to connect with new show - # and status - - if not global_sync_connected_accounts > 0 or \ - gajim.connections[account].connected > 0: - self.send_status(account, status, message) - self.send_pep(account, pep_dict) - self.update_status_combobox() - - if status == 'invisible': - bug_user = False - for account in accounts: - if connected_accounts < 1 or gajim.account_is_connected(account): - if not gajim.config.get_per('accounts', account, - 'sync_with_global_status'): - continue - # We're going to change our status to invisible - if self.connected_rooms(account): - bug_user = True - break - if bug_user: - def on_ok(): - self.get_status_message(status, on_continue, show_pep=False) - - def on_cancel(): - self.update_status_combobox() - - dialogs.ConfirmationDialog( - _('You are participating in one or more group chats'), - _('Changing your status to invisible will result in ' - 'disconnection from those group chats. Are you sure you want to ' - 'go invisible?'), on_reponse_ok=on_ok, - on_response_cancel=on_cancel) - return - - self.get_status_message(status, on_continue) - - def on_preferences_menuitem_activate(self, widget): - if 'preferences' in gajim.interface.instances: - gajim.interface.instances['preferences'].window.present() - else: - gajim.interface.instances['preferences'] = config.PreferencesWindow() - - def on_publish_tune_toggled(self, widget, account): - active = widget.get_active() - gajim.config.set_per('accounts', account, 'publish_tune', active) - if active: - gajim.interface.enable_music_listener() - else: - gajim.connections[account].retract_tune() - # disable music listener only if no other account uses it - for acc in gajim.connections: - if gajim.config.get_per('accounts', acc, 'publish_tune'): - break - else: - gajim.interface.disable_music_listener() - - helpers.update_optional_features(account) - - def on_pep_services_menuitem_activate(self, widget, account): - if 'pep_services' in gajim.interface.instances[account]: - gajim.interface.instances[account]['pep_services'].window.present() - else: - gajim.interface.instances[account]['pep_services'] = \ - config.ManagePEPServicesWindow(account) - - def on_add_new_contact(self, widget, account): - dialogs.AddNewContactWindow(account) - - def on_join_gc_activate(self, widget, account): - '''when the join gc menuitem is clicked, show the join gc window''' - invisible_show = gajim.SHOW_LIST.index('invisible') - if gajim.connections[account].connected == invisible_show: - dialogs.ErrorDialog(_('You cannot join a group chat while you are ' - 'invisible')) - return - if 'join_gc' in gajim.interface.instances[account]: - gajim.interface.instances[account]['join_gc'].window.present() - else: - # c http://nkour.blogspot.com/2005/05/pythons-init-return-none-doesnt-return.html - try: - gajim.interface.instances[account]['join_gc'] = \ - dialogs.JoinGroupchatWindow(account) - except GajimGeneralException: - pass - - def on_new_chat_menuitem_activate(self, widget, account): - dialogs.NewChatDialog(account) - - def on_contents_menuitem_activate(self, widget): - helpers.launch_browser_mailer('url', 'http://trac.gajim.org/wiki') - - def on_faq_menuitem_activate(self, widget): - helpers.launch_browser_mailer('url', - 'http://trac.gajim.org/wiki/GajimFaq') - - def on_features_menuitem_activate(self, widget): - features_window.FeaturesWindow() - - def on_about_menuitem_activate(self, widget): - dialogs.AboutDialog() - - def on_accounts_menuitem_activate(self, widget): - if 'accounts' in gajim.interface.instances: - gajim.interface.instances['accounts'].window.present() - else: - gajim.interface.instances['accounts'] = config.AccountsWindow() - - def on_file_transfers_menuitem_activate(self, widget): - if gajim.interface.instances['file_transfers'].window.get_property( - 'visible'): - gajim.interface.instances['file_transfers'].window.present() - else: - gajim.interface.instances['file_transfers'].window.show_all() - - def on_history_menuitem_activate(self, widget): - if 'logs' in gajim.interface.instances: - gajim.interface.instances['logs'].window.present() - else: - gajim.interface.instances['logs'] = history_window.\ - HistoryWindow() - - def on_show_transports_menuitem_activate(self, widget): - gajim.config.set('show_transports_group', widget.get_active()) - self.refilter_shown_roster_items() - - def on_manage_bookmarks_menuitem_activate(self, widget): - config.ManageBookmarksWindow() - - def on_profile_avatar_menuitem_activate(self, widget, account): - gajim.interface.edit_own_details(account) - - def on_execute_command(self, widget, contact, account, resource=None): - '''Execute command. Full JID needed; if it is other contact, - resource is necessary. Widget is unnecessary, only to be - able to make this a callback.''' - jid = contact.jid - if resource is not None: - jid = jid + u'/' + resource - adhoc_commands.CommandWindow(account, jid) - - def on_roster_window_focus_in_event(self, widget, event): - # roster received focus, so if we had urgency REMOVE IT - # NOTE: we do not have to read the message to remove urgency - # so this functions does that - gtkgui_helpers.set_unset_urgency_hint(widget, False) - - # if a contact row is selected, update colors (eg. for status msg) - # because gtk engines may differ in bg when window is selected - # or not - if len(self._last_selected_contact): - for (jid, account) in self._last_selected_contact: - self.draw_contact(jid, account, selected=True, focus=True) - - def on_roster_window_focus_out_event(self, widget, event): - # if a contact row is selected, update colors (eg. for status msg) - # because gtk engines may differ in bg when window is selected - # or not - if len(self._last_selected_contact): - for (jid, account) in self._last_selected_contact: - self.draw_contact(jid, account, selected=True, focus=False) - - def on_roster_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: - if gajim.interface.msg_win_mgr.mode == \ - MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER and \ - gajim.interface.msg_win_mgr.one_window_opened(): - # let message window close the tab - return - list_of_paths = self.tree.get_selection().get_selected_rows()[1] - if not len(list_of_paths) and gajim.interface.systray_enabled and \ - not gajim.config.get('quit_on_roster_x_button'): - self.tooltip.hide_tooltip() - self.window.hide() - elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == gtk.keysyms.i: - treeselection = self.tree.get_selection() - model, list_of_paths = treeselection.get_selected_rows() - for path in list_of_paths: - type_ = model[path][C_TYPE] - if type_ in ('contact', 'agent'): - jid = model[path][C_JID].decode('utf-8') - account = model[path][C_ACCOUNT].decode('utf-8') - contact = gajim.contacts.get_first_contact_from_jid(account, jid) - self.on_info(widget, contact, account) - elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == gtk.keysyms.h: - treeselection = self.tree.get_selection() - model, list_of_paths = treeselection.get_selected_rows() - if len(list_of_paths) != 1: - return - path = list_of_paths[0] - type_ = model[path][C_TYPE] - if type_ in ('contact', 'agent'): - jid = model[path][C_JID].decode('utf-8') - account = model[path][C_ACCOUNT].decode('utf-8') - contact = gajim.contacts.get_first_contact_from_jid(account, jid) - self.on_history(widget, contact, account) - - def on_roster_window_popup_menu(self, widget): - event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) - self.show_treeview_menu(event) - - def on_row_activated(self, widget, path): - '''When an iter is activated (double-click or single click if gnome is - set this way)''' - model = self.modelfilter - account = model[path][C_ACCOUNT].decode('utf-8') - type_ = model[path][C_TYPE] - if type_ in ('group', 'account'): - if self.tree.row_expanded(path): - self.tree.collapse_row(path) - else: - self.tree.expand_row(path, False) - return - jid = model[path][C_JID].decode('utf-8') - resource = None - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - titer = model.get_iter(path) - if contact.is_groupchat(): - first_ev = gajim.events.get_first_event(account, jid) - if first_ev and self.open_event(account, jid, first_ev): - # We are invited to a GC - # open event cares about connecting to it - self.remove_groupchat(jid, account) - else: - self.on_groupchat_maximized(None, jid, account) - return - - # else - first_ev = gajim.events.get_first_event(account, jid) - if not first_ev: - # look in other resources - for c in gajim.contacts.get_contacts(account, jid): - fjid = c.get_full_jid() - first_ev = gajim.events.get_first_event(account, fjid) - if first_ev: - resource = c.resource - break - if not first_ev and model.iter_has_child(titer): - child_iter = model.iter_children(titer) - while not first_ev and child_iter: - child_jid = model[child_iter][C_JID].decode('utf-8') - first_ev = gajim.events.get_first_event(account, child_jid) - if first_ev: - jid = child_jid - else: - child_iter = model.iter_next(child_iter) - session = None - if first_ev: - if first_ev.type_ in ('chat', 'normal'): - session = first_ev.parameters[8] - fjid = jid - if resource: - fjid += '/' + resource - if self.open_event(account, fjid, first_ev): - return - # else - contact = gajim.contacts.get_contact(account, jid, resource) - if not contact or isinstance(contact, list): - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - if jid == gajim.get_jid_from_account(account): - resource = contact.resource - - gajim.interface.on_open_chat_window(None, contact, account, \ - resource=resource, session=session) - - def on_roster_treeview_row_activated(self, widget, path, col=0): - '''When an iter is double clicked: open the first event window''' - if not gajim.single_click: - self.on_row_activated(widget, path) - - def on_roster_treeview_row_expanded(self, widget, titer, path): - '''When a row is expanded change the icon of the arrow''' - self._toggeling_row = True - model = widget.get_model() - child_model = model.get_model() - child_iter = model.convert_iter_to_child_iter(titer) - - if self.regroup: # merged accounts - accounts = gajim.connections.keys() - else: - accounts = [model[titer][C_ACCOUNT].decode('utf-8')] - - type_ = model[titer][C_TYPE] - if type_ == 'group': - group = model[titer][C_JID].decode('utf-8') - child_model[child_iter][C_IMG] = gajim.interface.jabber_state_images[ - '16']['opened'] - for account in accounts: - if group in gajim.groups[account]: # This account has this group - gajim.groups[account][group]['expand'] = True - if account + group in self.collapsed_rows: - self.collapsed_rows.remove(account + group) - for contact in gajim.contacts.iter_contacts(account): - jid = contact.jid - if group in contact.groups and gajim.contacts.is_big_brother( - account, jid, accounts) and account + group + jid \ - not in self.collapsed_rows: - titers = self._get_contact_iter(jid, account) - for titer in titers: - path = model.get_path(titer) - self.tree.expand_row(path, False) - elif type_ == 'account': - account = accounts[0] # There is only one cause we don't use merge - if account in self.collapsed_rows: - self.collapsed_rows.remove(account) - self.draw_account(account) - # When we expand, groups are collapsed. Restore expand state - for group in gajim.groups[account]: - if gajim.groups[account][group]['expand']: - titer = self._get_group_iter(group, account) - if titer: - path = model.get_path(titer) - self.tree.expand_row(path, False) - elif type_ == 'contact': - # Metacontact got toggled, update icon - jid = model[titer][C_JID].decode('utf-8') - account = model[titer][C_ACCOUNT].decode('utf-8') - contact = gajim.contacts.get_contact(account, jid) - for group in contact.groups: - if account + group + jid in self.collapsed_rows: - self.collapsed_rows.remove(account + group + jid) - family = gajim.contacts.get_metacontacts_family(account, jid) - nearby_family = \ - self._get_nearby_family_and_big_brother(family, account)[0] - # Redraw all brothers to show pending events - for data in nearby_family: - self.draw_contact(data['jid'], data['account']) - - self._toggeling_row = False - - def on_roster_treeview_row_collapsed(self, widget, titer, path): - '''When a row is collapsed change the icon of the arrow''' - self._toggeling_row = True - model = widget.get_model() - child_model = model.get_model() - child_iter = model.convert_iter_to_child_iter(titer) - - if self.regroup: # merged accounts - accounts = gajim.connections.keys() - else: - accounts = [model[titer][C_ACCOUNT].decode('utf-8')] - - type_ = model[titer][C_TYPE] - if type_ == 'group': - child_model[child_iter][C_IMG] = gajim.interface.jabber_state_images[ - '16']['closed'] - group = model[titer][C_JID].decode('utf-8') - for account in accounts: - if group in gajim.groups[account]: # This account has this group - gajim.groups[account][group]['expand'] = False - if account + group not in self.collapsed_rows: - self.collapsed_rows.append(account + group) - elif type_ == 'account': - account = accounts[0] # There is only one cause we don't use merge - if account not in self.collapsed_rows: - self.collapsed_rows.append(account) - self.draw_account(account) - elif type_ == 'contact': - # Metacontact got toggled, update icon - jid = model[titer][C_JID].decode('utf-8') - account = model[titer][C_ACCOUNT].decode('utf-8') - contact = gajim.contacts.get_contact(account, jid) - for group in contact.groups: - if account + group + jid not in self.collapsed_rows: - self.collapsed_rows.append(account + group + jid) - family = gajim.contacts.get_metacontacts_family(account, jid) - nearby_family = \ - self._get_nearby_family_and_big_brother(family, account)[0] - # Redraw all brothers to show pending events - for data in nearby_family: - self.draw_contact(data['jid'], data['account']) - - self._toggeling_row = False - - def on_modelfilter_row_has_child_toggled(self, model, path, titer): - '''Called when a row has gotten the first or lost its last child row. - - Expand Parent if necessary. - ''' - if self._toggeling_row: - # Signal is emitted when we write to our model - return - - type_ = model[titer][C_TYPE] - account = model[titer][C_ACCOUNT] - if not account: - return - - account = account.decode('utf-8') - - if type_ == 'contact': - child_iter = model.convert_iter_to_child_iter(titer) - if self.model.iter_has_child(child_iter): - # we are a bigbrother metacontact - # redraw us to show/hide expand icon - if self.filtering: - # Prevent endless loops - jid = model[titer][C_JID].decode('utf-8') - gobject.idle_add(self.draw_contact, jid, account) - elif type_ == 'group': - group = model[titer][C_JID].decode('utf-8') - self._adjust_group_expand_collapse_state(group, account) - elif type_ == 'account': - self._adjust_account_expand_collapse_state(account) + def on_actions_menuitem_activate(self, widget): + self.make_menu() + + def on_edit_menuitem_activate(self, widget): + '''need to call make_menu to build profile, avatar item''' + self.make_menu() + + def on_bookmark_menuitem_activate(self, widget, account, bookmark): + gajim.interface.join_gc_room(account, bookmark['jid'], bookmark['nick'], + bookmark['password']) + + def on_send_server_message_menuitem_activate(self, widget, account): + server = gajim.config.get_per('accounts', account, 'hostname') + server += '/announce/online' + dialogs.SingleMessageWindow(account, server, 'send') + + def on_xml_console_menuitem_activate(self, widget, account): + if 'xml_console' in gajim.interface.instances[account]: + gajim.interface.instances[account]['xml_console'].window.present() + else: + gajim.interface.instances[account]['xml_console'] = \ + dialogs.XMLConsoleWindow(account) + + def on_archiving_preferences_menuitem_activate(self, widget, account): + if 'archiving_preferences' in gajim.interface.instances[account]: + gajim.interface.instances[account]['archiving_preferences'].window.\ + present() + else: + gajim.interface.instances[account]['archiving_preferences'] = \ + dialogs.ArchivingPreferencesWindow(account) + + def on_privacy_lists_menuitem_activate(self, widget, account): + if 'privacy_lists' in gajim.interface.instances[account]: + gajim.interface.instances[account]['privacy_lists'].window.present() + else: + gajim.interface.instances[account]['privacy_lists'] = \ + dialogs.PrivacyListsWindow(account) + + def on_set_motd_menuitem_activate(self, widget, account): + server = gajim.config.get_per('accounts', account, 'hostname') + server += '/announce/motd' + dialogs.SingleMessageWindow(account, server, 'send') + + def on_update_motd_menuitem_activate(self, widget, account): + server = gajim.config.get_per('accounts', account, 'hostname') + server += '/announce/motd/update' + dialogs.SingleMessageWindow(account, server, 'send') + + def on_delete_motd_menuitem_activate(self, widget, account): + server = gajim.config.get_per('accounts', account, 'hostname') + server += '/announce/motd/delete' + gajim.connections[account].send_motd(server) + + def on_history_manager_menuitem_activate(self, widget): + if os.name == 'nt': + if os.path.exists('history_manager.exe'): # user is running stable + helpers.exec_command('history_manager.exe') + else: # user is running svn + helpers.exec_command('%s history_manager.py' % sys.executable) + else: # Unix user + helpers.exec_command('%s history_manager.py' % sys.executable) + + def on_info(self, widget, contact, account): + '''Call vcard_information_window class to display contact's information''' + if gajim.connections[account].is_zeroconf: + self.on_info_zeroconf(widget, contact, account) + return + + info = gajim.interface.instances[account]['infos'] + if contact.jid in info: + info[contact.jid].window.present() + else: + info[contact.jid] = vcard.VcardWindow(contact, account) + + def on_info_zeroconf(self, widget, contact, account): + info = gajim.interface.instances[account]['infos'] + if contact.jid in info: + info[contact.jid].window.present() + else: + contact = gajim.contacts.get_first_contact_from_jid(account, + contact.jid) + if contact.show in ('offline', 'error'): + # don't show info on offline contacts + return + info[contact.jid] = vcard.ZeroconfVcardWindow(contact, account) + + def on_roster_treeview_leave_notify_event(self, widget, event): + props = widget.get_path_at_pos(int(event.x), int(event.y)) + if self.tooltip.timeout > 0: + if not props or self.tooltip.id == props[0]: + self.tooltip.hide_tooltip() + + def on_roster_treeview_motion_notify_event(self, widget, event): + model = widget.get_model() + props = widget.get_path_at_pos(int(event.x), int(event.y)) + if self.tooltip.timeout > 0: + if not props or self.tooltip.id != props[0]: + self.tooltip.hide_tooltip() + if props: + row = props[0] + titer = None + try: + titer = model.get_iter(row) + except Exception: + self.tooltip.hide_tooltip() + return + if model[titer][C_TYPE] in ('contact', 'self_contact'): + # we're on a contact entry in the roster + account = model[titer][C_ACCOUNT].decode('utf-8') + jid = model[titer][C_JID].decode('utf-8') + if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: + self.tooltip.id = row + contacts = gajim.contacts.get_contacts(account, jid) + connected_contacts = [] + for c in contacts: + if c.show not in ('offline', 'error'): + connected_contacts.append(c) + if not connected_contacts: + # no connected contacts, show the ofline one + connected_contacts = contacts + self.tooltip.account = account + self.tooltip.timeout = gobject.timeout_add(500, + self.show_tooltip, connected_contacts) + elif model[titer][C_TYPE] == 'groupchat': + account = model[titer][C_ACCOUNT].decode('utf-8') + jid = model[titer][C_JID].decode('utf-8') + if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: + self.tooltip.id = row + contact = gajim.contacts.get_contacts(account, jid) + self.tooltip.account = account + self.tooltip.timeout = gobject.timeout_add(500, + self.show_tooltip, contact) + elif model[titer][C_TYPE] == 'account': + # we're on an account entry in the roster + account = model[titer][C_ACCOUNT].decode('utf-8') + if account == 'all': + if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: + self.tooltip.id = row + self.tooltip.account = None + self.tooltip.timeout = gobject.timeout_add(500, + self.show_tooltip, []) + return + jid = gajim.get_jid_from_account(account) + contacts = [] + connection = gajim.connections[account] + # get our current contact info + + nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts( + accounts = [account]) + account_name = account + if gajim.account_is_connected(account): + account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total)) + contact = gajim.contacts.create_self_contact(jid=jid, account=account, + name=account_name, show=connection.get_status(), + status=connection.status, resource=connection.server_resource, + priority=connection.priority) + if gajim.connections[account].gpg: + contact.keyID = gajim.config.get_per('accounts', connection.name, + 'keyid') + contacts.append(contact) + # if we're online ... + if connection.connection: + roster = connection.connection.getRoster() + # in threadless connection when no roster stanza is sent, + # 'roster' is None + if roster and roster.getItem(jid): + resources = roster.getResources(jid) + # ...get the contact info for our other online resources + for resource in resources: + # Check if we already have this resource + found = False + for contact_ in contacts: + if contact_.resource == resource: + found = True + break + if found: + continue + show = roster.getShow(jid+'/'+resource) + if not show: + show = 'online' + contact = gajim.contacts.create_self_contact(jid=jid, + account=account, show=show, status=roster.getStatus(jid + '/' + resource), + priority=roster.getPriority(jid + '/' + resource), + resource=resource) + contacts.append(contact) + if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: + self.tooltip.id = row + self.tooltip.account = None + self.tooltip.timeout = gobject.timeout_add(500, + self.show_tooltip, contacts) + + def on_agent_logging(self, widget, jid, state, account): + '''When an agent is requested to log in or off''' + gajim.connections[account].send_agent_status(jid, state) + + def on_edit_agent(self, widget, contact, account): + '''When we want to modify the agent registration''' + gajim.connections[account].request_register_agent_info(contact.jid) + + def on_remove_agent(self, widget, list_): + '''When an agent is requested to be removed. list_ is a list of + (contact, account) tuple''' + for (contact, account) in list_: + if gajim.config.get_per('accounts', account, 'hostname') == \ + contact.jid: + # We remove the server contact + # remove it from treeview + gajim.connections[account].unsubscribe(contact.jid) + self.remove_contact(contact.jid, account, backend=True) + return + + def remove(list_): + for (contact, account) in list_: + full_jid = contact.get_full_jid() + gajim.connections[account].unsubscribe_agent(full_jid) + # remove transport from treeview + self.remove_contact(contact.jid, account, backend=True) + + # Check if there are unread events from some contacts + has_unread_events = False + for (contact, account) in list_: + for jid in gajim.events.get_events(account): + if jid.endswith(contact.jid): + has_unread_events = True + break + if has_unread_events: + dialogs.ErrorDialog(_('You have unread messages'), + _('You must read them before removing this transport.')) + return + if len(list_) == 1: + pritext = _('Transport "%s" will be removed') % list_[0][0].jid + sectext = _('You will no longer be able to send and receive messages ' + 'from contacts using this transport.') + else: + pritext = _('Transports will be removed') + jids = '' + for (contact, account) in list_: + jids += '\n ' + contact.get_shown_name() + ',' + jids = jids[:-1] + '.' + sectext = _('You will no longer be able to send and receive messages ' + 'to contacts from these transports: %s') % jids + dialogs.ConfirmationDialog(pritext, sectext, + on_response_ok = (remove, list_)) + + def on_block(self, widget, list_, group=None): + ''' When clicked on the 'block' button in context menu. + list_ is a list of (contact, account)''' + def on_continue(msg, pep_dict): + if msg is None: + # user pressed Cancel to change status message dialog + return + accounts = [] + if group is None: + for (contact, account) in list_: + if account not in accounts: + if not gajim.connections[account].privacy_rules_supported: + continue + accounts.append(account) + self.send_status(account, 'offline', msg, to=contact.jid) + new_rule = {'order': u'1', 'type': u'jid', 'action': u'deny', + 'value' : contact.jid, 'child': [u'message', u'iq', + u'presence-out']} + gajim.connections[account].blocked_list.append(new_rule) + # needed for draw_contact: + gajim.connections[account].blocked_contacts.append( + contact.jid) + self.draw_contact(contact.jid, account) + else: + for (contact, account) in list_: + if account not in accounts: + if not gajim.connections[account].privacy_rules_supported: + continue + accounts.append(account) + # needed for draw_group: + gajim.connections[account].blocked_groups.append(group) + self.draw_group(group, account) + self.send_status(account, 'offline', msg, to=contact.jid) + self.draw_contact(contact.jid, account) + new_rule = {'order': u'1', 'type': u'group', 'action': u'deny', + 'value' : group, 'child': [u'message', u'iq', u'presence-out']} + gajim.connections[account].blocked_list.append(new_rule) + for account in accounts: + connection = gajim.connections[account] + connection.set_privacy_list('block', connection.blocked_list) + if len(connection.blocked_list) == 1: + connection.set_active_list('block') + connection.set_default_list('block') + connection.get_privacy_list('block') + + def _block_it(is_checked=None): + if is_checked is not None: # dialog has been shown + if is_checked: # user does not want to be asked again + gajim.config.set('confirm_block', 'no') + else: + gajim.config.set('confirm_block', 'yes') + self.get_status_message('offline', on_continue, show_pep=False) + + confirm_block = gajim.config.get('confirm_block') + if confirm_block == 'no': + _block_it() + return + pritext = _('You are about to block a contact. Are you sure you want' + ' to continue?') + sectext = _('This contact will see you offline and you will not receive ' + 'messages he will send you.') + dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, + _('Do _not ask me again'), on_response_ok=_block_it) + + def on_unblock(self, widget, list_, group=None): + ''' When clicked on the 'unblock' button in context menu. ''' + accounts = [] + if group is None: + for (contact, account) in list_: + if account not in accounts: + if gajim.connections[account].privacy_rules_supported: + accounts.append(account) + gajim.connections[account].new_blocked_list = [] + gajim.connections[account].to_unblock = [] + gajim.connections[account].to_unblock.append(contact.jid) + else: + gajim.connections[account].to_unblock.append(contact.jid) + # needed for draw_contact: + if contact.jid in gajim.connections[account].blocked_contacts: + gajim.connections[account].blocked_contacts.remove(contact.jid) + self.draw_contact(contact.jid, account) + for account in accounts: + for rule in gajim.connections[account].blocked_list: + if rule['action'] != 'deny' or rule['type'] != 'jid' \ + or rule['value'] not in gajim.connections[account].to_unblock: + gajim.connections[account].new_blocked_list.append(rule) + else: + for (contact, account) in list_: + if account not in accounts: + if gajim.connections[account].privacy_rules_supported: + accounts.append(account) + # needed for draw_group: + if group in gajim.connections[account].blocked_groups: + gajim.connections[account].blocked_groups.remove(group) + self.draw_group(group, account) + gajim.connections[account].new_blocked_list = [] + for rule in gajim.connections[account].blocked_list: + if rule['action'] != 'deny' or rule['type'] != 'group' \ + or rule['value'] != group: + gajim.connections[account].new_blocked_list.append(rule) + self.draw_contact(contact.jid, account) + for account in accounts: + gajim.connections[account].set_privacy_list('block', + gajim.connections[account].new_blocked_list) + gajim.connections[account].get_privacy_list('block') + if len(gajim.connections[account].new_blocked_list) == 0: + gajim.connections[account].blocked_list = [] + gajim.connections[account].blocked_contacts = [] + gajim.connections[account].blocked_groups = [] + gajim.connections[account].set_default_list('') + gajim.connections[account].set_active_list('') + gajim.connections[account].del_privacy_list('block') + if 'blocked_contacts' in gajim.interface.instances[account]: + gajim.interface.instances[account]['blocked_contacts'].\ + privacy_list_received([]) + for (contact, account) in list_: + if not self.regroup: + show = gajim.SHOW_LIST[gajim.connections[account].connected] + else: # accounts merged + show = helpers.get_global_show() + if show == 'invisible': + # Don't send our presence if we're invisible + continue + if account not in accounts: + accounts.append(account) + if gajim.connections[account].privacy_rules_supported: + self.send_status(account, show, + gajim.connections[account].status, to=contact.jid) + else: + self.send_status(account, show, + gajim.connections[account].status, to=contact.jid) + + def on_rename(self, widget, row_type, jid, account): + # this function is called either by F2 or by Rename menuitem + if 'rename' in gajim.interface.instances: + gajim.interface.instances['rename'].dialog.present() + return + + # account is offline, don't allow to rename + if gajim.connections[account].connected < 2: + return + if row_type in ('contact', 'agent'): + # it's jid + title = _('Rename Contact') + message = _('Enter a new nickname for contact %s') % jid + old_text = gajim.contacts.get_contact_with_highest_priority(account, + jid).name + elif row_type == 'group': + if jid in helpers.special_groups + (_('General'),): + return + old_text = jid + title = _('Rename Group') + message = _('Enter a new name for group %s') % \ + gobject.markup_escape_text(jid) + + def on_renamed(new_text, account, row_type, jid, old_text): + if 'rename' in gajim.interface.instances: + del gajim.interface.instances['rename'] + if row_type in ('contact', 'agent'): + if old_text == new_text: + return + for contact in gajim.contacts.get_contacts(account, jid): + contact.name = new_text + gajim.connections[account].update_contact(jid, new_text, \ + contact.groups) + self.draw_contact(jid, account) + # Update opened chats + for ctrl in gajim.interface.msg_win_mgr.get_controls(jid, account): + ctrl.update_ui() + win = gajim.interface.msg_win_mgr.get_window(jid, account) + win.redraw_tab(ctrl) + win.show_title() + elif row_type == 'group': + # in C_JID column, we hold the group name (which is not escaped) + self.rename_group(old_text, new_text, account) + + def on_canceled(): + if 'rename' in gajim.interface.instances: + del gajim.interface.instances['rename'] + + gajim.interface.instances['rename'] = dialogs.InputDialog(title, message, + old_text, False, (on_renamed, account, row_type, jid, old_text), + on_canceled) + + def on_remove_group_item_activated(self, widget, group, account): + def on_ok(checked): + for contact in gajim.contacts.get_contacts_from_group(account, group): + if not checked: + self.remove_contact_from_groups(contact.jid, account, [group]) + else: + gajim.connections[account].unsubscribe(contact.jid) + self.remove_contact(contact.jid, account, backend=True) + + dialogs.ConfirmationDialogCheck(_('Remove Group'), + _('Do you want to remove group %s from the roster?') % group, + _('Also remove all contacts in this group from your roster'), + on_response_ok=on_ok) + + def on_assign_pgp_key(self, widget, contact, account): + attached_keys = gajim.config.get_per('accounts', account, + 'attached_gpg_keys').split() + keys = {} + keyID = _('None') + for i in xrange(len(attached_keys)/2): + keys[attached_keys[2*i]] = attached_keys[2*i+1] + if attached_keys[2*i] == contact.jid: + keyID = attached_keys[2*i+1] + public_keys = gajim.connections[account].ask_gpg_keys() + public_keys[_('None')] = _('None') + + def on_key_selected(keyID): + if keyID is None: + return + if keyID[0] == _('None'): + if contact.jid in keys: + del keys[contact.jid] + keyID = '' + else: + keyID = keyID[0] + keys[contact.jid] = keyID + + ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account) + if ctrl: + ctrl.update_ui() + + keys_str = '' + for jid in keys: + keys_str += jid + ' ' + keys[jid] + ' ' + gajim.config.set_per('accounts', account, 'attached_gpg_keys', + keys_str) + for u in gajim.contacts.get_contacts(account, contact.jid): + u.keyID = helpers.prepare_and_validate_gpg_keyID(account, + contact.jid, keyID) + + dialogs.ChooseGPGKeyDialog(_('Assign OpenPGP Key'), + _('Select a key to apply to the contact'), public_keys, + on_key_selected, selected=keyID) + + def on_set_custom_avatar_activate(self, widget, contact, account): + def on_ok(widget, path_to_file): + filesize = os.path.getsize(path_to_file) # in bytes + invalid_file = False + msg = '' + if os.path.isfile(path_to_file): + stat = os.stat(path_to_file) + if stat[6] == 0: + invalid_file = True + msg = _('File is empty') + else: + invalid_file = True + msg = _('File does not exist') + if invalid_file: + dialogs.ErrorDialog(_('Could not load image'), msg) + return + try: + pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file) + if filesize > 16384: # 16 kb + # get the image at 'tooltip size' + # and hope that user did not specify in ACE crazy size + pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'tooltip') + except gobject.GError, msg: # unknown format + # msg should be string, not object instance + msg = str(msg) + dialogs.ErrorDialog(_('Could not load image'), msg) + return + gajim.interface.save_avatar_files(contact.jid, pixbuf, local=True) + dlg.destroy() + self.update_avatar_in_gui(contact.jid, account) + + def on_clear(widget): + dlg.destroy() + # Delete file: + gajim.interface.remove_avatar_files(contact.jid, local=True) + self.update_avatar_in_gui(contact.jid, account) + + dlg = dialogs.AvatarChooserDialog(on_response_ok=on_ok, + on_response_clear=on_clear) + + def on_edit_groups(self, widget, list_): + dialogs.EditGroupsDialog(list_) + + def on_history(self, widget, contact, account): + '''When history menuitem is activated: call log window''' + if 'logs' in gajim.interface.instances: + gajim.interface.instances['logs'].window.present() + gajim.interface.instances['logs'].open_history(contact.jid, account) + else: + gajim.interface.instances['logs'] = history_window.\ + HistoryWindow(contact.jid, account) + + def on_disconnect(self, widget, jid, account): + '''When disconnect menuitem is activated: disconect from room''' + if jid in gajim.interface.minimized_controls[account]: + ctrl = gajim.interface.minimized_controls[account][jid] + ctrl.shutdown() + ctrl.got_disconnected() + self.remove_groupchat(jid, account) + + def on_reconnect(self, widget, jid, account): + '''When disconnect menuitem is activated: disconect from room''' + if jid in gajim.interface.minimized_controls[account]: + ctrl = gajim.interface.minimized_controls[account][jid] + gajim.interface.join_gc_room(account, jid, ctrl.nick, + gajim.gc_passwords.get(jid, '')) + + def on_send_single_message_menuitem_activate(self, widget, account, + contact=None): + if contact is None: + dialogs.SingleMessageWindow(account, action='send') + elif isinstance(contact, list): + dialogs.SingleMessageWindow(account, contact, 'send') + else: + jid = contact.jid + if contact.jid == gajim.get_jid_from_account(account): + jid += '/' + contact.resource + dialogs.SingleMessageWindow(account, jid, 'send') + + def on_send_file_menuitem_activate(self, widget, contact, account, + resource=None): + gajim.interface.instances['file_transfers'].show_file_send_request( + account, contact) + + def on_add_special_notification_menuitem_activate(self, widget, jid): + dialogs.AddSpecialNotificationDialog(jid) + + def on_invite_to_new_room(self, widget, list_, resource=None): + ''' resource parameter MUST NOT be used if more than one contact in + list ''' + account_list = [] + jid_list = [] + for (contact, account) in list_: + if contact.jid not in jid_list: + if resource: # we MUST have one contact only in list_ + fjid = contact.jid + '/' + resource + jid_list.append(fjid) + else: + jid_list.append(contact.jid) + if account not in account_list: + account_list.append(account) + # transform None in 'jabber' + type_ = gajim.get_transport_name_from_jid(jid_list[0]) or 'jabber' + for account in account_list: + if gajim.connections[account].muc_jid[type_]: + # create the room on this muc server + if 'join_gc' in gajim.interface.instances[account]: + gajim.interface.instances[account]['join_gc'].window.destroy() + try: + gajim.interface.instances[account]['join_gc'] = \ + dialogs.JoinGroupchatWindow(account, + gajim.connections[account].muc_jid[type_], + automatic = {'invities': jid_list}) + except GajimGeneralException: + continue + break + + def on_invite_to_room(self, widget, list_, room_jid, room_account, + resource=None): + ''' resource parameter MUST NOT be used if more than one contact in + list ''' + for e in list_: + contact = e[0] + contact_jid = contact.jid + if resource: # we MUST have one contact only in list_ + contact_jid += '/' + resource + gajim.connections[room_account].send_invite(room_jid, contact_jid) + + def on_all_groupchat_maximized(self, widget, group_list): + for (contact, account) in group_list: + self.on_groupchat_maximized(widget, contact.jid, account) + + def on_groupchat_maximized(self, widget, jid, account): + '''When a groupchat is maximised''' + if not jid in gajim.interface.minimized_controls[account]: + # Already opened? + gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid, account) + if gc_control: + mw = gajim.interface.msg_win_mgr.get_window(jid, account) + mw.set_active_tab(gc_control) + mw.window.window.focus(gtk.get_current_event_time()) + return + ctrl = gajim.interface.minimized_controls[account][jid] + mw = gajim.interface.msg_win_mgr.get_window(jid, account) + if not mw: + mw = gajim.interface.msg_win_mgr.create_window(ctrl.contact, + ctrl.account, ctrl.type_id) + ctrl.parent_win = mw + mw.new_tab(ctrl) + mw.set_active_tab(ctrl) + mw.window.window.focus(gtk.get_current_event_time()) + self.remove_groupchat(jid, account) + + def on_edit_account(self, widget, account): + if 'accounts' in gajim.interface.instances: + gajim.interface.instances['accounts'].window.present() + else: + gajim.interface.instances['accounts'] = config.AccountsWindow() + gajim.interface.instances['accounts'].select_account(account) + + def on_zeroconf_properties(self, widget, account): + if 'accounts' in gajim.interface.instances: + gajim.interface.instances['accounts'].window.present() + else: + gajim.interface.instances['accounts'] = config.AccountsWindow() + gajim.interface.instances['accounts'].select_account(account) + + def on_open_gmail_inbox(self, widget, account): + url = gajim.connections[account].gmail_url + if url: + helpers.launch_browser_mailer('url', url) + + def on_change_status_message_activate(self, widget, account): + show = gajim.SHOW_LIST[gajim.connections[account].connected] + def on_response(message, pep_dict): + if message is None: # None is if user pressed Cancel + return + self.send_status(account, show, message) + self.send_pep(account, pep_dict) + dialogs.ChangeStatusMessageDialog(on_response, show) + + def on_add_to_roster(self, widget, contact, account): + dialogs.AddNewContactWindow(account, contact.jid, contact.name) + + + def on_roster_treeview_scroll_event(self, widget, event): + self.tooltip.hide_tooltip() + + def on_roster_treeview_key_press_event(self, widget, event): + '''when a key is pressed in the treeviews''' + self.tooltip.hide_tooltip() + if event.keyval == gtk.keysyms.Escape: + self.tree.get_selection().unselect_all() + elif event.keyval == gtk.keysyms.F2: + treeselection = self.tree.get_selection() + model, list_of_paths = treeselection.get_selected_rows() + if len(list_of_paths) != 1: + return + path = list_of_paths[0] + type_ = model[path][C_TYPE] + if type_ in ('contact', 'group', 'agent'): + jid = model[path][C_JID].decode('utf-8') + account = model[path][C_ACCOUNT].decode('utf-8') + self.on_rename(widget, type_, jid, account) + + elif event.keyval == gtk.keysyms.Delete: + treeselection = self.tree.get_selection() + model, list_of_paths = treeselection.get_selected_rows() + if not len(list_of_paths): + return + type_ = model[list_of_paths[0]][C_TYPE] + account = model[list_of_paths[0]][C_ACCOUNT].decode('utf-8') + list_ = [] + for path in list_of_paths: + if model[path][C_TYPE] != type_: + return + jid = model[path][C_JID].decode('utf-8') + account = model[path][C_ACCOUNT].decode('utf-8') + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + list_.append((contact, account)) + if type_ in ('account', 'group', 'self_contact') or \ + account == gajim.ZEROCONF_ACC_NAME: + return + if type_ == 'contact': + self.on_req_usub(widget, list_) + elif type_ == 'agent': + self.on_remove_agent(widget, list_) + + def on_roster_treeview_button_release_event(self, widget, event): + try: + path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0] + except TypeError: + return False + + if event.button == 1: # Left click + if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \ + not event.state & gtk.gdk.CONTROL_MASK: + # Check if button has been pressed on the same row + if self.clicked_path == path: + self.on_row_activated(widget, path) + self.clicked_path = None + + def on_roster_treeview_button_press_event(self, widget, event): + # hide tooltip, no matter the button is pressed + self.tooltip.hide_tooltip() + try: + pos = self.tree.get_path_at_pos(int(event.x), int(event.y)) + path, x = pos[0], pos[2] + except TypeError: + self.tree.get_selection().unselect_all() + return False + + if event.button == 3: # Right click + try: + model, list_of_paths = self.tree.get_selection().get_selected_rows() + except TypeError: + list_of_paths = [] + if path not in list_of_paths: + self.tree.get_selection().unselect_all() + self.tree.get_selection().select_path(path) + return self.show_treeview_menu(event) + + elif event.button == 2: # Middle click + try: + model, list_of_paths = self.tree.get_selection().get_selected_rows() + except TypeError: + list_of_paths = [] + if list_of_paths != [path]: + self.tree.get_selection().unselect_all() + self.tree.get_selection().select_path(path) + type_ = model[path][C_TYPE] + if type_ in ('agent', 'contact', 'self_contact', 'groupchat'): + self.on_row_activated(widget, path) + elif type_ == 'account': + account = model[path][C_ACCOUNT].decode('utf-8') + if account != 'all': + show = gajim.connections[account].connected + if show > 1: # We are connected + self.on_change_status_message_activate(widget, account) + return True + show = helpers.get_global_show() + if show == 'offline': + return True + def on_response(message, pep_dict): + if message is None: + return True + for acct in gajim.connections: + if not gajim.config.get_per('accounts', acct, + 'sync_with_global_status'): + continue + current_show = gajim.SHOW_LIST[gajim.connections[acct].\ + connected] + self.send_status(acct, current_show, message) + self.send_pep(acct, pep_dict) + dialogs.ChangeStatusMessageDialog(on_response, show) + return True + + elif event.button == 1: # Left click + model = self.modelfilter + type_ = model[path][C_TYPE] + # x_min is the x start position of status icon column + if gajim.config.get('avatar_position_in_roster') == 'left': + x_min = gajim.config.get('roster_avatar_width') + else: + x_min = 0 + if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \ + not event.state & gtk.gdk.CONTROL_MASK: + # Don't handle double click if we press icon of a metacontact + titer = model.get_iter(path) + if x > x_min and x < x_min + 27 and type_ == 'contact' and \ + model.iter_has_child(titer): + if (self.tree.row_expanded(path)): + self.tree.collapse_row(path) + else: + self.tree.expand_row(path, False) + return + # We just save on which row we press button, and open chat window on + # button release to be able to do DND without opening chat window + self.clicked_path = path + return + else: + if type_ == 'group' and x < 27: + # first cell in 1st column (the arrow SINGLE clicked) + if (self.tree.row_expanded(path)): + self.tree.collapse_row(path) + else: + self.tree.expand_row(path, False) + + elif type_ == 'contact' and x > x_min and x < x_min + 27: + if (self.tree.row_expanded(path)): + self.tree.collapse_row(path) + else: + self.tree.expand_row(path, False) + + def on_req_usub(self, widget, list_): + '''Remove a contact. list_ is a list of (contact, account) tuples''' + def on_ok(is_checked, list_): + remove_auth = True + if len(list_) == 1: + contact = list_[0][0] + if contact.sub != 'to' and is_checked: + remove_auth = False + for (contact, account) in list_: + if _('Not in Roster') not in contact.get_shown_groups(): + gajim.connections[account].unsubscribe(contact.jid, remove_auth) + self.remove_contact(contact.jid, account, backend=True) + if not remove_auth and contact.sub == 'both': + contact.name = '' + contact.groups = [] + contact.sub = 'from' + # we can't see him, but have to set it manually in contact + contact.show = 'offline' + gajim.contacts.add_contact(account, contact) + self.add_contact(contact.jid, account) + def on_ok2(list_): + on_ok(False, list_) + + if len(list_) == 1: + contact = list_[0][0] + pritext = _('Contact "%s" will be removed from your roster') % \ + contact.get_shown_name() + sectext = _('You are about to remove "%(name)s" (%(jid)s) from your ' + 'roster.\n') % {'name': contact.get_shown_name(), + 'jid': contact.jid} + if contact.sub == 'to': + dialogs.ConfirmationDialog(pritext, sectext + \ + _('By removing this contact you also remove authorization ' + 'resulting in him or her always seeing you as offline.'), + on_response_ok = (on_ok2, list_)) + elif _('Not in Roster') in contact.get_shown_groups(): + # Contact is not in roster + dialogs.ConfirmationDialog(pritext, sectext + \ + _('Do you want to continue?'), on_response_ok = (on_ok2, list_)) + else: + dialogs.ConfirmationDialogCheck(pritext, sectext + \ + _('By removing this contact you also by default remove ' + 'authorization resulting in him or her always seeing you as ' + 'offline.'), + _('I want this contact to know my status after removal'), + on_response_ok = (on_ok, list_)) + else: + # several contact to remove at the same time + pritext = _('Contacts will be removed from your roster') + jids = '' + for (contact, account) in list_: + jids += '\n ' + contact.get_shown_name() + ' (%s)' % contact.jid +\ + ',' + sectext = _('By removing these contacts:%s\nyou also remove ' + 'authorization resulting in them always seeing you as offline.') % \ + jids + dialogs.ConfirmationDialog(pritext, sectext, + on_response_ok = (on_ok2, list_)) + + def on_send_custom_status(self, widget, contact_list, show, group=None): + '''send custom status''' + # contact_list has only one element except if group != None + def on_response(message, pep_dict): + if message is None: # None if user pressed Cancel + return + account_list = [] + for (contact, account) in contact_list: + if account not in account_list: + account_list.append(account) + # 1. update status_sent_to_[groups|users] list + if group: + for account in account_list: + if account not in gajim.interface.status_sent_to_groups: + gajim.interface.status_sent_to_groups[account] = {} + gajim.interface.status_sent_to_groups[account][group] = show + else: + for (contact, account) in contact_list: + if account not in gajim.interface.status_sent_to_users: + gajim.interface.status_sent_to_users[account] = {} + gajim.interface.status_sent_to_users[account][contact.jid] = show + + # 2. update privacy lists if main status is invisible + for account in account_list: + if gajim.SHOW_LIST[gajim.connections[account].connected] == \ + 'invisible': + gajim.connections[account].set_invisible_rule() + + # 3. send directed presence + for (contact, account) in contact_list: + our_jid = gajim.get_jid_from_account(account) + jid = contact.jid + if jid == our_jid: + jid += '/' + contact.resource + self.send_status(account, show, message, to=jid) + + def send_it(is_checked=None): + if is_checked is not None: # dialog has been shown + if is_checked: # user does not want to be asked again + gajim.config.set('confirm_custom_status', 'no') + else: + gajim.config.set('confirm_custom_status', 'yes') + self.get_status_message(show, on_response, show_pep=False, + always_ask=True) + + confirm_custom_status = gajim.config.get('confirm_custom_status') + if confirm_custom_status == 'no': + send_it() + return + pritext = _('You are about to send a custom status. Are you sure you want' + ' to continue?') + sectext = _('This contact will temporarily see you as %(status)s, ' + 'but only until you change your status. Then he will see your global ' + 'status.') % {'status': show} + dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, + _('Do _not ask me again'), on_response_ok=send_it) + + def on_status_combobox_changed(self, widget): + '''When we change our status via the combobox''' + model = self.status_combobox.get_model() + active = self.status_combobox.get_active() + if active == -1: # no active item + return + if not self.combobox_callback_active: + self.previous_status_combobox_active = active + return + accounts = gajim.connections.keys() + if len(accounts) == 0: + dialogs.ErrorDialog(_('No account available'), + _('You must create an account before you can chat with other contacts.')) + self.update_status_combobox() + return + status = model[active][2].decode('utf-8') + statuses_unified = helpers.statuses_unified() # status "desync'ed" or not + if (active == 7 and statuses_unified) or (active == 9 and \ + not statuses_unified): + # 'Change status message' selected: + # do not change show, just show change status dialog + status = model[self.previous_status_combobox_active][2].decode('utf-8') + def on_response(message, pep_dict): + if message is not None: # None if user pressed Cancel + for account in accounts: + if not gajim.config.get_per('accounts', account, + 'sync_with_global_status'): + continue + current_show = gajim.SHOW_LIST[ + gajim.connections[account].connected] + self.send_status(account, current_show, message) + self.send_pep(account, pep_dict) + self.combobox_callback_active = False + self.status_combobox.set_active( + self.previous_status_combobox_active) + self.combobox_callback_active = True + dialogs.ChangeStatusMessageDialog(on_response, status) + return + # we are about to change show, so save this new show so in case + # after user chooses "Change status message" menuitem + # we can return to this show + self.previous_status_combobox_active = active + connected_accounts = gajim.get_number_of_connected_accounts() + + def on_continue(message, pep_dict): + if message is None: + # user pressed Cancel to change status message dialog + self.update_status_combobox() + return + global_sync_accounts = [] + for acct in accounts: + if gajim.config.get_per('accounts', acct, + 'sync_with_global_status'): + global_sync_accounts.append(acct) + global_sync_connected_accounts = \ + gajim.get_number_of_connected_accounts(global_sync_accounts) + for account in accounts: + if not gajim.config.get_per('accounts', account, + 'sync_with_global_status'): + continue + # we are connected (so we wanna change show and status) + # or no account is connected and we want to connect with new show + # and status + + if not global_sync_connected_accounts > 0 or \ + gajim.connections[account].connected > 0: + self.send_status(account, status, message) + self.send_pep(account, pep_dict) + self.update_status_combobox() + + if status == 'invisible': + bug_user = False + for account in accounts: + if connected_accounts < 1 or gajim.account_is_connected(account): + if not gajim.config.get_per('accounts', account, + 'sync_with_global_status'): + continue + # We're going to change our status to invisible + if self.connected_rooms(account): + bug_user = True + break + if bug_user: + def on_ok(): + self.get_status_message(status, on_continue, show_pep=False) + + def on_cancel(): + self.update_status_combobox() + + dialogs.ConfirmationDialog( + _('You are participating in one or more group chats'), + _('Changing your status to invisible will result in ' + 'disconnection from those group chats. Are you sure you want to ' + 'go invisible?'), on_reponse_ok=on_ok, + on_response_cancel=on_cancel) + return + + self.get_status_message(status, on_continue) + + def on_preferences_menuitem_activate(self, widget): + if 'preferences' in gajim.interface.instances: + gajim.interface.instances['preferences'].window.present() + else: + gajim.interface.instances['preferences'] = config.PreferencesWindow() + + def on_publish_tune_toggled(self, widget, account): + active = widget.get_active() + gajim.config.set_per('accounts', account, 'publish_tune', active) + if active: + gajim.interface.enable_music_listener() + else: + gajim.connections[account].retract_tune() + # disable music listener only if no other account uses it + for acc in gajim.connections: + if gajim.config.get_per('accounts', acc, 'publish_tune'): + break + else: + gajim.interface.disable_music_listener() + + helpers.update_optional_features(account) + + def on_pep_services_menuitem_activate(self, widget, account): + if 'pep_services' in gajim.interface.instances[account]: + gajim.interface.instances[account]['pep_services'].window.present() + else: + gajim.interface.instances[account]['pep_services'] = \ + config.ManagePEPServicesWindow(account) + + def on_add_new_contact(self, widget, account): + dialogs.AddNewContactWindow(account) + + def on_join_gc_activate(self, widget, account): + '''when the join gc menuitem is clicked, show the join gc window''' + invisible_show = gajim.SHOW_LIST.index('invisible') + if gajim.connections[account].connected == invisible_show: + dialogs.ErrorDialog(_('You cannot join a group chat while you are ' + 'invisible')) + return + if 'join_gc' in gajim.interface.instances[account]: + gajim.interface.instances[account]['join_gc'].window.present() + else: + # c http://nkour.blogspot.com/2005/05/pythons-init-return-none-doesnt-return.html + try: + gajim.interface.instances[account]['join_gc'] = \ + dialogs.JoinGroupchatWindow(account) + except GajimGeneralException: + pass + + def on_new_chat_menuitem_activate(self, widget, account): + dialogs.NewChatDialog(account) + + def on_contents_menuitem_activate(self, widget): + helpers.launch_browser_mailer('url', 'http://trac.gajim.org/wiki') + + def on_faq_menuitem_activate(self, widget): + helpers.launch_browser_mailer('url', + 'http://trac.gajim.org/wiki/GajimFaq') + + def on_features_menuitem_activate(self, widget): + features_window.FeaturesWindow() + + def on_about_menuitem_activate(self, widget): + dialogs.AboutDialog() + + def on_accounts_menuitem_activate(self, widget): + if 'accounts' in gajim.interface.instances: + gajim.interface.instances['accounts'].window.present() + else: + gajim.interface.instances['accounts'] = config.AccountsWindow() + + def on_file_transfers_menuitem_activate(self, widget): + if gajim.interface.instances['file_transfers'].window.get_property( + 'visible'): + gajim.interface.instances['file_transfers'].window.present() + else: + gajim.interface.instances['file_transfers'].window.show_all() + + def on_history_menuitem_activate(self, widget): + if 'logs' in gajim.interface.instances: + gajim.interface.instances['logs'].window.present() + else: + gajim.interface.instances['logs'] = history_window.\ + HistoryWindow() + + def on_show_transports_menuitem_activate(self, widget): + gajim.config.set('show_transports_group', widget.get_active()) + self.refilter_shown_roster_items() + + def on_manage_bookmarks_menuitem_activate(self, widget): + config.ManageBookmarksWindow() + + def on_profile_avatar_menuitem_activate(self, widget, account): + gajim.interface.edit_own_details(account) + + def on_execute_command(self, widget, contact, account, resource=None): + '''Execute command. Full JID needed; if it is other contact, + resource is necessary. Widget is unnecessary, only to be + able to make this a callback.''' + jid = contact.jid + if resource is not None: + jid = jid + u'/' + resource + adhoc_commands.CommandWindow(account, jid) + + def on_roster_window_focus_in_event(self, widget, event): + # roster received focus, so if we had urgency REMOVE IT + # NOTE: we do not have to read the message to remove urgency + # so this functions does that + gtkgui_helpers.set_unset_urgency_hint(widget, False) + + # if a contact row is selected, update colors (eg. for status msg) + # because gtk engines may differ in bg when window is selected + # or not + if len(self._last_selected_contact): + for (jid, account) in self._last_selected_contact: + self.draw_contact(jid, account, selected=True, focus=True) + + def on_roster_window_focus_out_event(self, widget, event): + # if a contact row is selected, update colors (eg. for status msg) + # because gtk engines may differ in bg when window is selected + # or not + if len(self._last_selected_contact): + for (jid, account) in self._last_selected_contact: + self.draw_contact(jid, account, selected=True, focus=False) + + def on_roster_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: + if gajim.interface.msg_win_mgr.mode == \ + MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER and \ + gajim.interface.msg_win_mgr.one_window_opened(): + # let message window close the tab + return + list_of_paths = self.tree.get_selection().get_selected_rows()[1] + if not len(list_of_paths) and gajim.interface.systray_enabled and \ + not gajim.config.get('quit_on_roster_x_button'): + self.tooltip.hide_tooltip() + self.window.hide() + elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == gtk.keysyms.i: + treeselection = self.tree.get_selection() + model, list_of_paths = treeselection.get_selected_rows() + for path in list_of_paths: + type_ = model[path][C_TYPE] + if type_ in ('contact', 'agent'): + jid = model[path][C_JID].decode('utf-8') + account = model[path][C_ACCOUNT].decode('utf-8') + contact = gajim.contacts.get_first_contact_from_jid(account, jid) + self.on_info(widget, contact, account) + elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == gtk.keysyms.h: + treeselection = self.tree.get_selection() + model, list_of_paths = treeselection.get_selected_rows() + if len(list_of_paths) != 1: + return + path = list_of_paths[0] + type_ = model[path][C_TYPE] + if type_ in ('contact', 'agent'): + jid = model[path][C_JID].decode('utf-8') + account = model[path][C_ACCOUNT].decode('utf-8') + contact = gajim.contacts.get_first_contact_from_jid(account, jid) + self.on_history(widget, contact, account) + + def on_roster_window_popup_menu(self, widget): + event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) + self.show_treeview_menu(event) + + def on_row_activated(self, widget, path): + '''When an iter is activated (double-click or single click if gnome is + set this way)''' + model = self.modelfilter + account = model[path][C_ACCOUNT].decode('utf-8') + type_ = model[path][C_TYPE] + if type_ in ('group', 'account'): + if self.tree.row_expanded(path): + self.tree.collapse_row(path) + else: + self.tree.expand_row(path, False) + return + jid = model[path][C_JID].decode('utf-8') + resource = None + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + titer = model.get_iter(path) + if contact.is_groupchat(): + first_ev = gajim.events.get_first_event(account, jid) + if first_ev and self.open_event(account, jid, first_ev): + # We are invited to a GC + # open event cares about connecting to it + self.remove_groupchat(jid, account) + else: + self.on_groupchat_maximized(None, jid, account) + return + + # else + first_ev = gajim.events.get_first_event(account, jid) + if not first_ev: + # look in other resources + for c in gajim.contacts.get_contacts(account, jid): + fjid = c.get_full_jid() + first_ev = gajim.events.get_first_event(account, fjid) + if first_ev: + resource = c.resource + break + if not first_ev and model.iter_has_child(titer): + child_iter = model.iter_children(titer) + while not first_ev and child_iter: + child_jid = model[child_iter][C_JID].decode('utf-8') + first_ev = gajim.events.get_first_event(account, child_jid) + if first_ev: + jid = child_jid + else: + child_iter = model.iter_next(child_iter) + session = None + if first_ev: + if first_ev.type_ in ('chat', 'normal'): + session = first_ev.parameters[8] + fjid = jid + if resource: + fjid += '/' + resource + if self.open_event(account, fjid, first_ev): + return + # else + contact = gajim.contacts.get_contact(account, jid, resource) + if not contact or isinstance(contact, list): + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + if jid == gajim.get_jid_from_account(account): + resource = contact.resource + + gajim.interface.on_open_chat_window(None, contact, account, \ + resource=resource, session=session) + + def on_roster_treeview_row_activated(self, widget, path, col=0): + '''When an iter is double clicked: open the first event window''' + if not gajim.single_click: + self.on_row_activated(widget, path) + + def on_roster_treeview_row_expanded(self, widget, titer, path): + '''When a row is expanded change the icon of the arrow''' + self._toggeling_row = True + model = widget.get_model() + child_model = model.get_model() + child_iter = model.convert_iter_to_child_iter(titer) + + if self.regroup: # merged accounts + accounts = gajim.connections.keys() + else: + accounts = [model[titer][C_ACCOUNT].decode('utf-8')] + + type_ = model[titer][C_TYPE] + if type_ == 'group': + group = model[titer][C_JID].decode('utf-8') + child_model[child_iter][C_IMG] = gajim.interface.jabber_state_images[ + '16']['opened'] + for account in accounts: + if group in gajim.groups[account]: # This account has this group + gajim.groups[account][group]['expand'] = True + if account + group in self.collapsed_rows: + self.collapsed_rows.remove(account + group) + for contact in gajim.contacts.iter_contacts(account): + jid = contact.jid + if group in contact.groups and gajim.contacts.is_big_brother( + account, jid, accounts) and account + group + jid \ + not in self.collapsed_rows: + titers = self._get_contact_iter(jid, account) + for titer in titers: + path = model.get_path(titer) + self.tree.expand_row(path, False) + elif type_ == 'account': + account = accounts[0] # There is only one cause we don't use merge + if account in self.collapsed_rows: + self.collapsed_rows.remove(account) + self.draw_account(account) + # When we expand, groups are collapsed. Restore expand state + for group in gajim.groups[account]: + if gajim.groups[account][group]['expand']: + titer = self._get_group_iter(group, account) + if titer: + path = model.get_path(titer) + self.tree.expand_row(path, False) + elif type_ == 'contact': + # Metacontact got toggled, update icon + jid = model[titer][C_JID].decode('utf-8') + account = model[titer][C_ACCOUNT].decode('utf-8') + contact = gajim.contacts.get_contact(account, jid) + for group in contact.groups: + if account + group + jid in self.collapsed_rows: + self.collapsed_rows.remove(account + group + jid) + family = gajim.contacts.get_metacontacts_family(account, jid) + nearby_family = \ + self._get_nearby_family_and_big_brother(family, account)[0] + # Redraw all brothers to show pending events + for data in nearby_family: + self.draw_contact(data['jid'], data['account']) + + self._toggeling_row = False + + def on_roster_treeview_row_collapsed(self, widget, titer, path): + '''When a row is collapsed change the icon of the arrow''' + self._toggeling_row = True + model = widget.get_model() + child_model = model.get_model() + child_iter = model.convert_iter_to_child_iter(titer) + + if self.regroup: # merged accounts + accounts = gajim.connections.keys() + else: + accounts = [model[titer][C_ACCOUNT].decode('utf-8')] + + type_ = model[titer][C_TYPE] + if type_ == 'group': + child_model[child_iter][C_IMG] = gajim.interface.jabber_state_images[ + '16']['closed'] + group = model[titer][C_JID].decode('utf-8') + for account in accounts: + if group in gajim.groups[account]: # This account has this group + gajim.groups[account][group]['expand'] = False + if account + group not in self.collapsed_rows: + self.collapsed_rows.append(account + group) + elif type_ == 'account': + account = accounts[0] # There is only one cause we don't use merge + if account not in self.collapsed_rows: + self.collapsed_rows.append(account) + self.draw_account(account) + elif type_ == 'contact': + # Metacontact got toggled, update icon + jid = model[titer][C_JID].decode('utf-8') + account = model[titer][C_ACCOUNT].decode('utf-8') + contact = gajim.contacts.get_contact(account, jid) + for group in contact.groups: + if account + group + jid not in self.collapsed_rows: + self.collapsed_rows.append(account + group + jid) + family = gajim.contacts.get_metacontacts_family(account, jid) + nearby_family = \ + self._get_nearby_family_and_big_brother(family, account)[0] + # Redraw all brothers to show pending events + for data in nearby_family: + self.draw_contact(data['jid'], data['account']) + + self._toggeling_row = False + + def on_modelfilter_row_has_child_toggled(self, model, path, titer): + '''Called when a row has gotten the first or lost its last child row. + + Expand Parent if necessary. + ''' + if self._toggeling_row: + # Signal is emitted when we write to our model + return + + type_ = model[titer][C_TYPE] + account = model[titer][C_ACCOUNT] + if not account: + return + + account = account.decode('utf-8') + + if type_ == 'contact': + child_iter = model.convert_iter_to_child_iter(titer) + if self.model.iter_has_child(child_iter): + # we are a bigbrother metacontact + # redraw us to show/hide expand icon + if self.filtering: + # Prevent endless loops + jid = model[titer][C_JID].decode('utf-8') + gobject.idle_add(self.draw_contact, jid, account) + elif type_ == 'group': + group = model[titer][C_JID].decode('utf-8') + self._adjust_group_expand_collapse_state(group, account) + elif type_ == 'account': + self._adjust_account_expand_collapse_state(account) # Selection can change when the model is filtered # Only write to the model when filtering is finished! # # FIXME: When we are filtering our custom colors are somehow lost # -# def on_treeview_selection_changed(self, selection): -# '''Called when selection in TreeView has changed. +# def on_treeview_selection_changed(self, selection): +# '''Called when selection in TreeView has changed. # -# Redraw unselected rows to make status message readable -# on all possible backgrounds. -# ''' -# model, list_of_paths = selection.get_selected_rows() -# if len(self._last_selected_contact): -# # update unselected rows -# for (jid, account) in self._last_selected_contact: -# gobject.idle_add(self.draw_contact, jid, account) -# self._last_selected_contact = [] -# if len(list_of_paths) == 0: -# return -# for path in list_of_paths: -# row = model[path] -# if row[C_TYPE] != 'contact': -# self._last_selected_contact = [] -# return -# jid = row[C_JID].decode('utf-8') -# account = row[C_ACCOUNT].decode('utf-8') -# self._last_selected_contact.append((jid, account)) -# gobject.idle_add(self.draw_contact, jid, account, True) +# Redraw unselected rows to make status message readable +# on all possible backgrounds. +# ''' +# model, list_of_paths = selection.get_selected_rows() +# if len(self._last_selected_contact): +# # update unselected rows +# for (jid, account) in self._last_selected_contact: +# gobject.idle_add(self.draw_contact, jid, account) +# self._last_selected_contact = [] +# if len(list_of_paths) == 0: +# return +# for path in list_of_paths: +# row = model[path] +# if row[C_TYPE] != 'contact': +# self._last_selected_contact = [] +# return +# jid = row[C_JID].decode('utf-8') +# account = row[C_ACCOUNT].decode('utf-8') +# self._last_selected_contact.append((jid, account)) +# gobject.idle_add(self.draw_contact, jid, account, True) - def on_service_disco_menuitem_activate(self, widget, account): - server_jid = gajim.config.get_per('accounts', account, 'hostname') - if server_jid in gajim.interface.instances[account]['disco']: - gajim.interface.instances[account]['disco'][server_jid].\ - window.present() - else: - try: - # Object will add itself to the window dict - disco.ServiceDiscoveryWindow(account, address_entry=True) - except GajimGeneralException: - pass + def on_service_disco_menuitem_activate(self, widget, account): + server_jid = gajim.config.get_per('accounts', account, 'hostname') + if server_jid in gajim.interface.instances[account]['disco']: + gajim.interface.instances[account]['disco'][server_jid].\ + window.present() + else: + try: + # Object will add itself to the window dict + disco.ServiceDiscoveryWindow(account, address_entry=True) + except GajimGeneralException: + pass - def on_show_offline_contacts_menuitem_activate(self, widget): - '''when show offline option is changed: - redraw the treeview''' - gajim.config.set('showoffline', not gajim.config.get('showoffline')) - self.refilter_shown_roster_items() - w = self.xml.get_widget('show_only_active_contacts_menuitem') - if gajim.config.get('showoffline'): - # We need to filter twice to show groups with no contacts inside - # in the correct expand state - self.refilter_shown_roster_items() - w.set_sensitive(False) - else: - w.set_sensitive(True) + def on_show_offline_contacts_menuitem_activate(self, widget): + '''when show offline option is changed: + redraw the treeview''' + gajim.config.set('showoffline', not gajim.config.get('showoffline')) + self.refilter_shown_roster_items() + w = self.xml.get_widget('show_only_active_contacts_menuitem') + if gajim.config.get('showoffline'): + # We need to filter twice to show groups with no contacts inside + # in the correct expand state + self.refilter_shown_roster_items() + w.set_sensitive(False) + else: + w.set_sensitive(True) - def on_show_only_active_contacts_menuitem_activate(self, widget): - '''when show only active contact option is changed: - redraw the treeview''' - gajim.config.set('show_only_chat_and_online', not gajim.config.get( - 'show_only_chat_and_online')) - self.refilter_shown_roster_items() - w = self.xml.get_widget('show_offline_contacts_menuitem') - if gajim.config.get('show_only_chat_and_online'): - # We need to filter twice to show groups with no contacts inside - # in the correct expand state - self.refilter_shown_roster_items() - w.set_sensitive(False) - else: - w.set_sensitive(True) + def on_show_only_active_contacts_menuitem_activate(self, widget): + '''when show only active contact option is changed: + redraw the treeview''' + gajim.config.set('show_only_chat_and_online', not gajim.config.get( + 'show_only_chat_and_online')) + self.refilter_shown_roster_items() + w = self.xml.get_widget('show_offline_contacts_menuitem') + if gajim.config.get('show_only_chat_and_online'): + # We need to filter twice to show groups with no contacts inside + # in the correct expand state + self.refilter_shown_roster_items() + w.set_sensitive(False) + else: + w.set_sensitive(True) - def on_view_menu_activate(self, widget): - # Hide the show roster menu if we are not in the right windowing mode. - if self.hpaned.get_child2() is not None: - self.xml.get_widget('show_roster_menuitem').show() - else: - self.xml.get_widget('show_roster_menuitem').hide() + def on_view_menu_activate(self, widget): + # Hide the show roster menu if we are not in the right windowing mode. + if self.hpaned.get_child2() is not None: + self.xml.get_widget('show_roster_menuitem').show() + else: + self.xml.get_widget('show_roster_menuitem').hide() - def on_show_roster_menuitem_toggled(self, widget): - # when num controls is 0 this menuitem is hidden, but still need to - # disable keybinding - if self.hpaned.get_child2() is not None: - self.show_roster_vbox(widget.get_active()) + def on_show_roster_menuitem_toggled(self, widget): + # when num controls is 0 this menuitem is hidden, but still need to + # disable keybinding + if self.hpaned.get_child2() is not None: + self.show_roster_vbox(widget.get_active()) ################################################################################ ### Drag and Drop handling ################################################################################ - def drag_data_get_data(self, treeview, context, selection, target_id, etime): - model, list_of_paths = self.tree.get_selection().get_selected_rows() - if len(list_of_paths) != 1: - return - path = list_of_paths[0] - data = '' - if len(path) >= 3: - data = model[path][C_JID] - selection.set(selection.target, 8, data) + def drag_data_get_data(self, treeview, context, selection, target_id, etime): + model, list_of_paths = self.tree.get_selection().get_selected_rows() + if len(list_of_paths) != 1: + return + path = list_of_paths[0] + data = '' + if len(path) >= 3: + data = model[path][C_JID] + selection.set(selection.target, 8, data) - def drag_begin(self, treeview, context): - self.dragging = True + def drag_begin(self, treeview, context): + self.dragging = True - def drag_end(self, treeview, context): - self.dragging = False + def drag_end(self, treeview, context): + self.dragging = False - def on_drop_rosterx(self, widget, account_source, c_source, account_dest, - c_dest, was_big_brother, context, etime): - gajim.connections[account_dest].send_contacts([c_source], c_dest.jid) + def on_drop_rosterx(self, widget, account_source, c_source, account_dest, + c_dest, was_big_brother, context, etime): + gajim.connections[account_dest].send_contacts([c_source], c_dest.jid) - def on_drop_in_contact(self, widget, account_source, c_source, account_dest, - c_dest, was_big_brother, context, etime): + def on_drop_in_contact(self, widget, account_source, c_source, account_dest, + c_dest, was_big_brother, context, etime): - if not gajim.connections[account_source].private_storage_supported or not\ - gajim.connections[account_dest].private_storage_supported: - dialogs.WarningDialog(_('Metacontacts storage not supported by your ' - 'server'), - _('Your server does not support storing metacontacts information. ' - 'So those information will not be saved on next reconnection.')) + if not gajim.connections[account_source].private_storage_supported or not\ + gajim.connections[account_dest].private_storage_supported: + dialogs.WarningDialog(_('Metacontacts storage not supported by your ' + 'server'), + _('Your server does not support storing metacontacts information. ' + 'So those information will not be saved on next reconnection.')) - def merge_contacts(is_checked=None): - contacts = 0 - if is_checked is not None: # dialog has been shown - if is_checked: # user does not want to be asked again - gajim.config.set('confirm_metacontacts', 'no') - else: - gajim.config.set('confirm_metacontacts', 'yes') + def merge_contacts(is_checked=None): + contacts = 0 + if is_checked is not None: # dialog has been shown + if is_checked: # user does not want to be asked again + gajim.config.set('confirm_metacontacts', 'no') + else: + gajim.config.set('confirm_metacontacts', 'yes') - # We might have dropped on a metacontact. - # Remove it and readd later with updated family info - dest_family = gajim.contacts.get_metacontacts_family(account_dest, - c_dest.jid) - if dest_family: - self._remove_metacontact_family(dest_family, account_dest) - source_family = gajim.contacts.get_metacontacts_family(account_source, c_source.jid) - if dest_family == source_family: - n = contacts = len(dest_family) - for tag in source_family: - if tag['jid'] == c_source.jid: - tag['order'] = contacts - continue - if 'order' in tag: - n -= 1 - tag['order'] = n - else: - self._remove_entity(c_dest, account_dest) + # We might have dropped on a metacontact. + # Remove it and readd later with updated family info + dest_family = gajim.contacts.get_metacontacts_family(account_dest, + c_dest.jid) + if dest_family: + self._remove_metacontact_family(dest_family, account_dest) + source_family = gajim.contacts.get_metacontacts_family(account_source, c_source.jid) + if dest_family == source_family: + n = contacts = len(dest_family) + for tag in source_family: + if tag['jid'] == c_source.jid: + tag['order'] = contacts + continue + if 'order' in tag: + n -= 1 + tag['order'] = n + else: + self._remove_entity(c_dest, account_dest) - old_family = gajim.contacts.get_metacontacts_family(account_source, - c_source.jid) - old_groups = c_source.groups + old_family = gajim.contacts.get_metacontacts_family(account_source, + c_source.jid) + old_groups = c_source.groups - # Remove old source contact(s) - if was_big_brother: - # We have got little brothers. Readd them all - self._remove_metacontact_family(old_family, account_source) - else: - # We are only a litle brother. Simply remove us from our big brother - if self._get_contact_iter(c_source.jid, account_source): - # When we have been in the group before. - # Do not try to remove us again - self._remove_entity(c_source, account_source) + # Remove old source contact(s) + if was_big_brother: + # We have got little brothers. Readd them all + self._remove_metacontact_family(old_family, account_source) + else: + # We are only a litle brother. Simply remove us from our big brother + if self._get_contact_iter(c_source.jid, account_source): + # When we have been in the group before. + # Do not try to remove us again + self._remove_entity(c_source, account_source) - own_data = {} - own_data['jid'] = c_source.jid - own_data['account'] = account_source - # Don't touch the rest of the family - old_family = [own_data] + own_data = {} + own_data['jid'] = c_source.jid + own_data['account'] = account_source + # Don't touch the rest of the family + old_family = [own_data] - # Apply new tag and update contact - for data in old_family: - if account_source != data['account'] and not self.regroup: - continue + # Apply new tag and update contact + for data in old_family: + if account_source != data['account'] and not self.regroup: + continue - _account = data['account'] - _jid = data['jid'] - _contact = gajim.contacts.get_first_contact_from_jid(_account, _jid) + _account = data['account'] + _jid = data['jid'] + _contact = gajim.contacts.get_first_contact_from_jid(_account, _jid) - _contact.groups = c_dest.groups[:] - gajim.contacts.add_metacontact(account_dest, c_dest.jid, - _account, _contact.jid, contacts) - gajim.connections[account_source].update_contact(_contact.jid, - _contact.name, _contact.groups) + _contact.groups = c_dest.groups[:] + gajim.contacts.add_metacontact(account_dest, c_dest.jid, + _account, _contact.jid, contacts) + gajim.connections[account_source].update_contact(_contact.jid, + _contact.name, _contact.groups) - # Re-add all and update GUI - new_family = gajim.contacts.get_metacontacts_family(account_source, - c_source.jid) - brothers = self._add_metacontact_family(new_family, account_source) + # Re-add all and update GUI + new_family = gajim.contacts.get_metacontacts_family(account_source, + c_source.jid) + brothers = self._add_metacontact_family(new_family, account_source) - for c, acc in brothers: - self.draw_completely(c.jid, acc) + for c, acc in brothers: + self.draw_completely(c.jid, acc) - old_groups.extend(c_dest.groups) - for g in old_groups: - self.draw_group(g, account_source) + old_groups.extend(c_dest.groups) + for g in old_groups: + self.draw_group(g, account_source) - self.draw_account(account_source) - context.finish(True, True, etime) + self.draw_account(account_source) + context.finish(True, True, etime) - confirm_metacontacts = gajim.config.get('confirm_metacontacts') - if confirm_metacontacts == 'no': - merge_contacts() - return - pritext = _('You are about to create a metacontact. Are you sure you want' - ' to continue?') - sectext = _('Metacontacts are a way to regroup several contacts in one ' - 'line. Generally it is used when the same person has several Jabber ' - 'accounts or transport accounts.') - dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, - _('Do _not ask me again'), on_response_ok=merge_contacts) - if not confirm_metacontacts: # First time we see this window - dlg.checkbutton.set_active(True) + confirm_metacontacts = gajim.config.get('confirm_metacontacts') + if confirm_metacontacts == 'no': + merge_contacts() + return + pritext = _('You are about to create a metacontact. Are you sure you want' + ' to continue?') + sectext = _('Metacontacts are a way to regroup several contacts in one ' + 'line. Generally it is used when the same person has several Jabber ' + 'accounts or transport accounts.') + dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, + _('Do _not ask me again'), on_response_ok=merge_contacts) + if not confirm_metacontacts: # First time we see this window + dlg.checkbutton.set_active(True) - def on_drop_in_group(self, widget, account, c_source, grp_dest, - is_big_brother, context, etime, grp_source = None): - if is_big_brother: - # add whole metacontact to new group - self.add_contact_to_groups(c_source.jid, account, [grp_dest,]) - # remove afterwards so the contact is not moved to General in the - # meantime - if grp_dest != grp_source: - self.remove_contact_from_groups(c_source.jid, account, [grp_source]) - else: - # Normal contact or little brother - family = gajim.contacts.get_metacontacts_family(account, - c_source.jid) - if family: - # Little brother - # Remove whole family. Remove us from the family. - # Then re-add other family members. - self._remove_metacontact_family(family, account) - gajim.contacts.remove_metacontact(account, c_source.jid) - for data in family: - if account != data['account'] and not self.regroup: - continue - if data['jid'] == c_source.jid and\ - data['account'] == account: - continue - self.add_contact(data['jid'], data['account']) - break + def on_drop_in_group(self, widget, account, c_source, grp_dest, + is_big_brother, context, etime, grp_source = None): + if is_big_brother: + # add whole metacontact to new group + self.add_contact_to_groups(c_source.jid, account, [grp_dest,]) + # remove afterwards so the contact is not moved to General in the + # meantime + if grp_dest != grp_source: + self.remove_contact_from_groups(c_source.jid, account, [grp_source]) + else: + # Normal contact or little brother + family = gajim.contacts.get_metacontacts_family(account, + c_source.jid) + if family: + # Little brother + # Remove whole family. Remove us from the family. + # Then re-add other family members. + self._remove_metacontact_family(family, account) + gajim.contacts.remove_metacontact(account, c_source.jid) + for data in family: + if account != data['account'] and not self.regroup: + continue + if data['jid'] == c_source.jid and\ + data['account'] == account: + continue + self.add_contact(data['jid'], data['account']) + break - self.add_contact_to_groups(c_source.jid, account, [grp_dest,]) + self.add_contact_to_groups(c_source.jid, account, [grp_dest,]) - else: - # Normal contact - self.add_contact_to_groups(c_source.jid, account, [grp_dest,]) - # remove afterwards so the contact is not moved to General in the - # meantime - if grp_dest != grp_source: - self.remove_contact_from_groups(c_source.jid, account, - [grp_source]) + else: + # Normal contact + self.add_contact_to_groups(c_source.jid, account, [grp_dest,]) + # remove afterwards so the contact is not moved to General in the + # meantime + if grp_dest != grp_source: + self.remove_contact_from_groups(c_source.jid, account, + [grp_source]) - if context.action in (gtk.gdk.ACTION_MOVE, gtk.gdk.ACTION_COPY): - context.finish(True, True, etime) + if context.action in (gtk.gdk.ACTION_MOVE, gtk.gdk.ACTION_COPY): + context.finish(True, True, etime) - def drag_drop(self, treeview, context, x, y, timestamp): - target_list = treeview.drag_dest_get_target_list() - target = treeview.drag_dest_find_target(context, target_list) - treeview.drag_get_data(context, target) - context.finish(False, True) - return True + def drag_drop(self, treeview, context, x, y, timestamp): + target_list = treeview.drag_dest_get_target_list() + target = treeview.drag_dest_find_target(context, target_list) + treeview.drag_get_data(context, target) + context.finish(False, True) + return True - def drag_data_received_data(self, treeview, context, x, y, selection, info, - etime): - treeview.stop_emission('drag_data_received') - drop_info = treeview.get_dest_row_at_pos(x, y) - if not drop_info: - return - if not selection.data: - return # prevents tb when several entrys are dragged - model = treeview.get_model() - data = selection.data - path_dest, position = drop_info + def drag_data_received_data(self, treeview, context, x, y, selection, info, + etime): + treeview.stop_emission('drag_data_received') + drop_info = treeview.get_dest_row_at_pos(x, y) + if not drop_info: + return + if not selection.data: + return # prevents tb when several entrys are dragged + model = treeview.get_model() + data = selection.data + path_dest, position = drop_info - if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2 \ - and path_dest[1] == 0: # dropped before the first group - return - if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2: - # dropped before a group: we drop it in the previous group every time - path_dest = (path_dest[0], path_dest[1]-1) - # destination: the row something got dropped on - iter_dest = model.get_iter(path_dest) - type_dest = model[iter_dest][C_TYPE].decode('utf-8') - jid_dest = model[iter_dest][C_JID].decode('utf-8') - account_dest = model[iter_dest][C_ACCOUNT].decode('utf-8') + if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2 \ + and path_dest[1] == 0: # dropped before the first group + return + if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2: + # dropped before a group: we drop it in the previous group every time + path_dest = (path_dest[0], path_dest[1]-1) + # destination: the row something got dropped on + iter_dest = model.get_iter(path_dest) + type_dest = model[iter_dest][C_TYPE].decode('utf-8') + jid_dest = model[iter_dest][C_JID].decode('utf-8') + account_dest = model[iter_dest][C_ACCOUNT].decode('utf-8') - # drop on account row in merged mode, we cannot know the desired account - if account_dest == 'all': - return - # nothing can be done, if destination account is offline - if gajim.connections[account_dest].connected < 2: - return + # drop on account row in merged mode, we cannot know the desired account + if account_dest == 'all': + return + # nothing can be done, if destination account is offline + if gajim.connections[account_dest].connected < 2: + return - # A file got dropped on the roster - if info == self.TARGET_TYPE_URI_LIST: - if len(path_dest) < 3: - return - if type_dest != 'contact': - return - c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest, - jid_dest) - if not c_dest.supports(NS_FILE): - return - uri = data.strip() - uri_splitted = uri.split() # we may have more than one file dropped - try: - # This is always the last element in windows - uri_splitted.remove('\0') - except ValueError: - pass - nb_uri = len(uri_splitted) - # Check the URIs - bad_uris = [] - for a_uri in uri_splitted: - path = helpers.get_file_path_from_dnd_dropped_uri(a_uri) - if not os.path.isfile(path): - bad_uris.append(a_uri) - if len(bad_uris): - dialogs.ErrorDialog(_('Invalid file URI:'), '\n'.join(bad_uris)) - return - def _on_send_files(account, jid, uris): - c = gajim.contacts.get_contact_with_highest_priority(account, jid) - for uri in uris: - path = helpers.get_file_path_from_dnd_dropped_uri(uri) - if os.path.isfile(path): # is it file? - gajim.interface.instances['file_transfers'].send_file( - account, c, path) - # Popup dialog to confirm sending - prim_text = 'Send file?' - sec_text = i18n.ngettext('Do you want to send this file to %s:', - 'Do you want to send these files to %s:', nb_uri) %\ - c_dest.get_shown_name() - for uri in uri_splitted: - path = helpers.get_file_path_from_dnd_dropped_uri(uri) - sec_text += '\n' + os.path.basename(path) - dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text, - on_response_ok = (_on_send_files, account_dest, jid_dest, - uri_splitted)) - dialog.popup() - return + # A file got dropped on the roster + if info == self.TARGET_TYPE_URI_LIST: + if len(path_dest) < 3: + return + if type_dest != 'contact': + return + c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest, + jid_dest) + if not c_dest.supports(NS_FILE): + return + uri = data.strip() + uri_splitted = uri.split() # we may have more than one file dropped + try: + # This is always the last element in windows + uri_splitted.remove('\0') + except ValueError: + pass + nb_uri = len(uri_splitted) + # Check the URIs + bad_uris = [] + for a_uri in uri_splitted: + path = helpers.get_file_path_from_dnd_dropped_uri(a_uri) + if not os.path.isfile(path): + bad_uris.append(a_uri) + if len(bad_uris): + dialogs.ErrorDialog(_('Invalid file URI:'), '\n'.join(bad_uris)) + return + def _on_send_files(account, jid, uris): + c = gajim.contacts.get_contact_with_highest_priority(account, jid) + for uri in uris: + path = helpers.get_file_path_from_dnd_dropped_uri(uri) + if os.path.isfile(path): # is it file? + gajim.interface.instances['file_transfers'].send_file( + account, c, path) + # Popup dialog to confirm sending + prim_text = 'Send file?' + sec_text = i18n.ngettext('Do you want to send this file to %s:', + 'Do you want to send these files to %s:', nb_uri) %\ + c_dest.get_shown_name() + for uri in uri_splitted: + path = helpers.get_file_path_from_dnd_dropped_uri(uri) + sec_text += '\n' + os.path.basename(path) + dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text, + on_response_ok = (_on_send_files, account_dest, jid_dest, + uri_splitted)) + dialog.popup() + return - # a roster entry was dragged and dropped somewhere in the roster + # a roster entry was dragged and dropped somewhere in the roster - # source: the row that was dragged - path_source = treeview.get_selection().get_selected_rows()[1][0] - iter_source = model.get_iter(path_source) - type_source = model[iter_source][C_TYPE] - account_source = model[iter_source][C_ACCOUNT].decode('utf-8') + # source: the row that was dragged + path_source = treeview.get_selection().get_selected_rows()[1][0] + iter_source = model.get_iter(path_source) + type_source = model[iter_source][C_TYPE] + account_source = model[iter_source][C_ACCOUNT].decode('utf-8') - # Only normal contacts can be dragged - if type_source != 'contact': - return - if gajim.config.get_per('accounts', account_source, 'is_zeroconf'): - return + # Only normal contacts can be dragged + if type_source != 'contact': + return + if gajim.config.get_per('accounts', account_source, 'is_zeroconf'): + return - # A contact was dropped - if gajim.config.get_per('accounts', account_dest, 'is_zeroconf'): - # drop on zeroconf account, adding not possible - return - if type_dest == 'self_contact': - # drop on self contact row - return - if type_dest == 'account' and account_source == account_dest: - # drop on the account it was dragged from - return - if type_dest == 'groupchat': - # drop on a minimized groupchat - # TODO: Invite to groupchat - return + # A contact was dropped + if gajim.config.get_per('accounts', account_dest, 'is_zeroconf'): + # drop on zeroconf account, adding not possible + return + if type_dest == 'self_contact': + # drop on self contact row + return + if type_dest == 'account' and account_source == account_dest: + # drop on the account it was dragged from + return + if type_dest == 'groupchat': + # drop on a minimized groupchat + # TODO: Invite to groupchat + return - # Get valid source group, jid and contact - it = iter_source - while model[it][C_TYPE] == 'contact': - it = model.iter_parent(it) - grp_source = model[it][C_JID].decode('utf-8') - if grp_source in helpers.special_groups and \ - grp_source not in ('Not in Roster', 'Observers'): - # a transport or a minimized groupchat was dragged - # we can add it to other accounts but not move it to another group, - # see below - return - jid_source = data.decode('utf-8') - c_source = gajim.contacts.get_contact_with_highest_priority( - account_source, jid_source) + # Get valid source group, jid and contact + it = iter_source + while model[it][C_TYPE] == 'contact': + it = model.iter_parent(it) + grp_source = model[it][C_JID].decode('utf-8') + if grp_source in helpers.special_groups and \ + grp_source not in ('Not in Roster', 'Observers'): + # a transport or a minimized groupchat was dragged + # we can add it to other accounts but not move it to another group, + # see below + return + jid_source = data.decode('utf-8') + c_source = gajim.contacts.get_contact_with_highest_priority( + account_source, jid_source) - # Get destination group - grp_dest = None - if type_dest == 'group': - grp_dest = model[iter_dest][C_JID].decode('utf-8') - elif type_dest in ('contact', 'agent'): - it = iter_dest - while model[it][C_TYPE] != 'group': - it = model.iter_parent(it) - grp_dest = model[it][C_JID].decode('utf-8') - if grp_dest in helpers.special_groups: - return + # Get destination group + grp_dest = None + if type_dest == 'group': + grp_dest = model[iter_dest][C_JID].decode('utf-8') + elif type_dest in ('contact', 'agent'): + it = iter_dest + while model[it][C_TYPE] != 'group': + it = model.iter_parent(it) + grp_dest = model[it][C_JID].decode('utf-8') + if grp_dest in helpers.special_groups: + return - if jid_source == jid_dest: - if grp_source == grp_dest and account_source == account_dest: - # Drop on self - return + if jid_source == jid_dest: + if grp_source == grp_dest and account_source == account_dest: + # Drop on self + return - # contact drop somewhere in or on a foreign account - if (type_dest == 'account' or not self.regroup) and \ - account_source != account_dest: - # add to account in specified group - dialogs.AddNewContactWindow(account=account_dest, jid=jid_source, - user_nick=c_source.name, group=grp_dest) - return + # contact drop somewhere in or on a foreign account + if (type_dest == 'account' or not self.regroup) and \ + account_source != account_dest: + # add to account in specified group + dialogs.AddNewContactWindow(account=account_dest, jid=jid_source, + user_nick=c_source.name, group=grp_dest) + return - # we may not add contacts from special_groups - if grp_source in helpers.special_groups : - return + # we may not add contacts from special_groups + if grp_source in helpers.special_groups : + return - # Is the contact we drag a meta contact? - accounts = (self.regroup and gajim.contacts.get_accounts()) or \ - account_source - is_big_brother = gajim.contacts.is_big_brother(account_source, jid_source, - accounts) + # Is the contact we drag a meta contact? + accounts = (self.regroup and gajim.contacts.get_accounts()) or \ + account_source + is_big_brother = gajim.contacts.is_big_brother(account_source, jid_source, + accounts) - drop_in_middle_of_meta = False - if type_dest == 'contact': - if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 4: - drop_in_middle_of_meta = True - if position == gtk.TREE_VIEW_DROP_AFTER and (len(path_dest) == 4 or \ - self.modelfilter.iter_has_child(iter_dest)): - drop_in_middle_of_meta = True - # Contact drop on group row or between two contacts that are - # not metacontacts - if (type_dest == 'group' or position in (gtk.TREE_VIEW_DROP_BEFORE, - gtk.TREE_VIEW_DROP_AFTER)) and not drop_in_middle_of_meta: - self.on_drop_in_group(None, account_source, c_source, grp_dest, - is_big_brother, context, etime, grp_source) - return + drop_in_middle_of_meta = False + if type_dest == 'contact': + if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 4: + drop_in_middle_of_meta = True + if position == gtk.TREE_VIEW_DROP_AFTER and (len(path_dest) == 4 or \ + self.modelfilter.iter_has_child(iter_dest)): + drop_in_middle_of_meta = True + # Contact drop on group row or between two contacts that are + # not metacontacts + if (type_dest == 'group' or position in (gtk.TREE_VIEW_DROP_BEFORE, + gtk.TREE_VIEW_DROP_AFTER)) and not drop_in_middle_of_meta: + self.on_drop_in_group(None, account_source, c_source, grp_dest, + is_big_brother, context, etime, grp_source) + return - # Contact drop on another contact, make meta contacts - if position == gtk.TREE_VIEW_DROP_INTO_OR_AFTER or \ - position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE or drop_in_middle_of_meta: - c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest, - jid_dest) - if not c_dest: - # c_dest is None if jid_dest doesn't belong to account - return - menu = gtk.Menu() - item = gtk.MenuItem(_('Send %s to %s') % (c_source.get_shown_name(), - c_dest.get_shown_name())) - item.connect('activate', self.on_drop_rosterx, account_source, - c_source, account_dest, c_dest, is_big_brother, context, etime) - menu.append(item) + # Contact drop on another contact, make meta contacts + if position == gtk.TREE_VIEW_DROP_INTO_OR_AFTER or \ + position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE or drop_in_middle_of_meta: + c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest, + jid_dest) + if not c_dest: + # c_dest is None if jid_dest doesn't belong to account + return + menu = gtk.Menu() + item = gtk.MenuItem(_('Send %s to %s') % (c_source.get_shown_name(), + c_dest.get_shown_name())) + item.connect('activate', self.on_drop_rosterx, account_source, + c_source, account_dest, c_dest, is_big_brother, context, etime) + menu.append(item) - item = gtk.MenuItem(_('Make %s and %s metacontacts') % ( - c_source.get_shown_name(), c_dest.get_shown_name())) - item.connect('activate', self.on_drop_in_contact, account_source, - c_source, account_dest, c_dest, is_big_brother, context, etime) + item = gtk.MenuItem(_('Make %s and %s metacontacts') % ( + c_source.get_shown_name(), c_dest.get_shown_name())) + item.connect('activate', self.on_drop_in_contact, account_source, + c_source, account_dest, c_dest, is_big_brother, context, etime) - menu.append(item) + menu.append(item) - menu.attach_to_widget(self.tree, None) - menu.connect('selection-done', gtkgui_helpers.destroy_widget) - menu.show_all() - menu.popup(None, None, None, 1, etime) + menu.attach_to_widget(self.tree, None) + menu.connect('selection-done', gtkgui_helpers.destroy_widget) + menu.show_all() + menu.popup(None, None, None, 1, etime) ################################################################################ ### Everything about images and icons.... ### Cleanup assigned to Jim++ :-) ################################################################################ - def get_appropriate_state_images(self, jid, size='16', icon_name='online'): - '''check jid and return the appropriate state images dict for - the demanded size. icon_name is taken into account when jid is from - transport: transport iconset doesn't contain all icons, so we fall back - to jabber one''' - transport = gajim.get_transport_name_from_jid(jid) - if transport and size in self.transports_state_images: - if transport not in self.transports_state_images[size]: - # we don't have iconset for this transport loaded yet. Let's do it - self.make_transport_state_images(transport) - if transport in self.transports_state_images[size] and \ - icon_name in self.transports_state_images[size][transport]: - return self.transports_state_images[size][transport] - return gajim.interface.jabber_state_images[size] + def get_appropriate_state_images(self, jid, size='16', icon_name='online'): + '''check jid and return the appropriate state images dict for + the demanded size. icon_name is taken into account when jid is from + transport: transport iconset doesn't contain all icons, so we fall back + to jabber one''' + transport = gajim.get_transport_name_from_jid(jid) + if transport and size in self.transports_state_images: + if transport not in self.transports_state_images[size]: + # we don't have iconset for this transport loaded yet. Let's do it + self.make_transport_state_images(transport) + if transport in self.transports_state_images[size] and \ + icon_name in self.transports_state_images[size][transport]: + return self.transports_state_images[size][transport] + return gajim.interface.jabber_state_images[size] - def make_transport_state_images(self, transport): - '''initialise opened and closed 'transport' iconset dict''' - if gajim.config.get('use_transports_iconsets'): - folder = os.path.join(helpers.get_transport_path(transport), - '16x16') - pixo, pixc = gtkgui_helpers.load_icons_meta() - self.transports_state_images['opened'][transport] = \ - gtkgui_helpers.load_iconset(folder, pixo, transport=True) - self.transports_state_images['closed'][transport] = \ - gtkgui_helpers.load_iconset(folder, pixc, transport=True) - folder = os.path.join(helpers.get_transport_path(transport), '32x32') - self.transports_state_images['32'][transport] = \ - gtkgui_helpers.load_iconset(folder, transport=True) - folder = os.path.join(helpers.get_transport_path(transport), '16x16') - self.transports_state_images['16'][transport] = \ - gtkgui_helpers.load_iconset(folder, transport=True) + def make_transport_state_images(self, transport): + '''initialise opened and closed 'transport' iconset dict''' + if gajim.config.get('use_transports_iconsets'): + folder = os.path.join(helpers.get_transport_path(transport), + '16x16') + pixo, pixc = gtkgui_helpers.load_icons_meta() + self.transports_state_images['opened'][transport] = \ + gtkgui_helpers.load_iconset(folder, pixo, transport=True) + self.transports_state_images['closed'][transport] = \ + gtkgui_helpers.load_iconset(folder, pixc, transport=True) + folder = os.path.join(helpers.get_transport_path(transport), '32x32') + self.transports_state_images['32'][transport] = \ + gtkgui_helpers.load_iconset(folder, transport=True) + folder = os.path.join(helpers.get_transport_path(transport), '16x16') + self.transports_state_images['16'][transport] = \ + gtkgui_helpers.load_iconset(folder, transport=True) - def update_jabber_state_images(self): - # Update the roster - self.setup_and_draw_roster() - # Update the status combobox - model = self.status_combobox.get_model() - titer = model.get_iter_root() - while titer: - if model[titer][2] != '': - # If it's not change status message iter - # eg. if it has show parameter not '' - model[titer][1] = gajim.interface.jabber_state_images['16'][model[ - titer][2]] - titer = model.iter_next(titer) - # Update the systray - if gajim.interface.systray_enabled: - gajim.interface.systray.set_img() + def update_jabber_state_images(self): + # Update the roster + self.setup_and_draw_roster() + # Update the status combobox + model = self.status_combobox.get_model() + titer = model.get_iter_root() + while titer: + if model[titer][2] != '': + # If it's not change status message iter + # eg. if it has show parameter not '' + model[titer][1] = gajim.interface.jabber_state_images['16'][model[ + titer][2]] + titer = model.iter_next(titer) + # Update the systray + if gajim.interface.systray_enabled: + gajim.interface.systray.set_img() - for win in gajim.interface.msg_win_mgr.windows(): - for ctrl in win.controls(): - ctrl.update_ui() - win.redraw_tab(ctrl) + for win in gajim.interface.msg_win_mgr.windows(): + for ctrl in win.controls(): + ctrl.update_ui() + win.redraw_tab(ctrl) - self.update_status_combobox() + self.update_status_combobox() - def set_account_status_icon(self, account): - status = gajim.connections[account].connected - child_iterA = self._get_account_iter(account, self.model) - if not child_iterA: - return - if not self.regroup: - show = gajim.SHOW_LIST[status] - else: # accounts merged - show = helpers.get_global_show() - self.model[child_iterA][C_IMG] = gajim.interface.jabber_state_images[ - '16'][show] + def set_account_status_icon(self, account): + status = gajim.connections[account].connected + child_iterA = self._get_account_iter(account, self.model) + if not child_iterA: + return + if not self.regroup: + show = gajim.SHOW_LIST[status] + else: # accounts merged + show = helpers.get_global_show() + self.model[child_iterA][C_IMG] = gajim.interface.jabber_state_images[ + '16'][show] ################################################################################ ### Style and theme related methods ################################################################################ - def show_title(self): - change_title_allowed = gajim.config.get('change_roster_title') - if not change_title_allowed: - return + def show_title(self): + change_title_allowed = gajim.config.get('change_roster_title') + if not change_title_allowed: + return - if gajim.config.get('one_message_window') == 'always_with_roster': - # always_with_roster mode defers to the MessageWindow - if not gajim.interface.msg_win_mgr.one_window_opened(): - # No MessageWindow to defer to - self.window.set_title('Gajim') - return + if gajim.config.get('one_message_window') == 'always_with_roster': + # always_with_roster mode defers to the MessageWindow + if not gajim.interface.msg_win_mgr.one_window_opened(): + # No MessageWindow to defer to + self.window.set_title('Gajim') + return - nb_unread = 0 - start = '' - for account in gajim.connections: - # Count events in roster title only if we don't auto open them - if not helpers.allow_popup_window(account): - nb_unread += gajim.events.get_nb_events(['chat', 'normal', - 'file-request', 'file-error', 'file-completed', - 'file-request-error', 'file-send-error', 'file-stopped', - 'printed_chat'], account) - if nb_unread > 1: - start = '[' + str(nb_unread) + '] ' - elif nb_unread == 1: - start = '* ' + nb_unread = 0 + start = '' + for account in gajim.connections: + # Count events in roster title only if we don't auto open them + if not helpers.allow_popup_window(account): + nb_unread += gajim.events.get_nb_events(['chat', 'normal', + 'file-request', 'file-error', 'file-completed', + 'file-request-error', 'file-send-error', 'file-stopped', + 'printed_chat'], account) + if nb_unread > 1: + start = '[' + str(nb_unread) + '] ' + elif nb_unread == 1: + start = '* ' - self.window.set_title(start + 'Gajim') + self.window.set_title(start + 'Gajim') - gtkgui_helpers.set_unset_urgency_hint(self.window, nb_unread) + gtkgui_helpers.set_unset_urgency_hint(self.window, nb_unread) - def _change_style(self, model, path, titer, option): - if option is None or model[titer][C_TYPE] == option: - # We changed style for this type of row - model[titer][C_NAME] = model[titer][C_NAME] + def _change_style(self, model, path, titer, option): + if option is None or model[titer][C_TYPE] == option: + # We changed style for this type of row + model[titer][C_NAME] = model[titer][C_NAME] - def change_roster_style(self, option): - self.model.foreach(self._change_style, option) - for win in gajim.interface.msg_win_mgr.windows(): - win.repaint_themed_widgets() + def change_roster_style(self, option): + self.model.foreach(self._change_style, option) + for win in gajim.interface.msg_win_mgr.windows(): + win.repaint_themed_widgets() - def repaint_themed_widgets(self): - '''Notify windows that contain themed widgets to repaint them''' - for win in gajim.interface.msg_win_mgr.windows(): - win.repaint_themed_widgets() - for account in gajim.connections: - for addr in gajim.interface.instances[account]['disco']: - gajim.interface.instances[account]['disco'][addr].paint_banner() - for ctrl in gajim.interface.minimized_controls[account].values(): - ctrl.repaint_themed_widgets() + def repaint_themed_widgets(self): + '''Notify windows that contain themed widgets to repaint them''' + for win in gajim.interface.msg_win_mgr.windows(): + win.repaint_themed_widgets() + for account in gajim.connections: + for addr in gajim.interface.instances[account]['disco']: + gajim.interface.instances[account]['disco'][addr].paint_banner() + for ctrl in gajim.interface.minimized_controls[account].values(): + ctrl.repaint_themed_widgets() - def update_avatar_in_gui(self, jid, account): - # Update roster - self.draw_avatar(jid, account) - # Update chat window + def update_avatar_in_gui(self, jid, account): + # Update roster + self.draw_avatar(jid, account) + # Update chat window - ctrl = gajim.interface.msg_win_mgr.get_control(jid, account) - if ctrl: - ctrl.show_avatar() + ctrl = gajim.interface.msg_win_mgr.get_control(jid, account) + if ctrl: + ctrl.show_avatar() - def on_roster_treeview_style_set(self, treeview, style): - '''When style (theme) changes, redraw all contacts''' - for contact in self._iter_contact_rows(): - self.draw_contact(contact[C_JID].decode('utf-8'), - contact[C_ACCOUNT].decode('utf-8')) + def on_roster_treeview_style_set(self, treeview, style): + '''When style (theme) changes, redraw all contacts''' + for contact in self._iter_contact_rows(): + self.draw_contact(contact[C_JID].decode('utf-8'), + contact[C_ACCOUNT].decode('utf-8')) - def set_renderer_color(self, renderer, style, set_background=True): - '''set style for treeview cell, using PRELIGHT system color''' - if set_background: - bgcolor = self.tree.style.bg[style] - renderer.set_property('cell-background-gdk', bgcolor) - else: - fgcolor = self.tree.style.fg[style] - renderer.set_property('foreground-gdk', fgcolor) + def set_renderer_color(self, renderer, style, set_background=True): + '''set style for treeview cell, using PRELIGHT system color''' + if set_background: + bgcolor = self.tree.style.bg[style] + renderer.set_property('cell-background-gdk', bgcolor) + else: + fgcolor = self.tree.style.fg[style] + renderer.set_property('foreground-gdk', fgcolor) - def _iconCellDataFunc(self, column, renderer, model, titer, data=None): - '''When a row is added, set properties for icon renderer''' - theme = gajim.config.get('roster_theme') - type_ = model[titer][C_TYPE] - if type_ == 'account': - color = gajim.config.get_per('themes', theme, 'accountbgcolor') - if color: - renderer.set_property('cell-background', color) - else: - self.set_renderer_color(renderer, gtk.STATE_ACTIVE) - renderer.set_property('xalign', 0) - elif type_ == 'group': - color = gajim.config.get_per('themes', theme, 'groupbgcolor') - if color: - renderer.set_property('cell-background', color) - else: - self.set_renderer_color(renderer, gtk.STATE_PRELIGHT) - renderer.set_property('xalign', 0.2) - elif type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534 - if not model[titer][C_JID] or not model[titer][C_ACCOUNT]: - # This can append when at the moment we add the row - return - jid = model[titer][C_JID].decode('utf-8') - account = model[titer][C_ACCOUNT].decode('utf-8') - if jid in gajim.newly_added[account]: - renderer.set_property('cell-background', gajim.config.get( - 'just_connected_bg_color')) - elif jid in gajim.to_be_removed[account]: - renderer.set_property('cell-background', gajim.config.get( - 'just_disconnected_bg_color')) - else: - color = gajim.config.get_per('themes', theme, 'contactbgcolor') - if color: - renderer.set_property('cell-background', color) - else: - renderer.set_property('cell-background', None) - parent_iter = model.iter_parent(titer) - if model[parent_iter][C_TYPE] == 'contact': - renderer.set_property('xalign', 1) - else: - renderer.set_property('xalign', 0.4) - renderer.set_property('width', 26) + def _iconCellDataFunc(self, column, renderer, model, titer, data=None): + '''When a row is added, set properties for icon renderer''' + theme = gajim.config.get('roster_theme') + type_ = model[titer][C_TYPE] + if type_ == 'account': + color = gajim.config.get_per('themes', theme, 'accountbgcolor') + if color: + renderer.set_property('cell-background', color) + else: + self.set_renderer_color(renderer, gtk.STATE_ACTIVE) + renderer.set_property('xalign', 0) + elif type_ == 'group': + color = gajim.config.get_per('themes', theme, 'groupbgcolor') + if color: + renderer.set_property('cell-background', color) + else: + self.set_renderer_color(renderer, gtk.STATE_PRELIGHT) + renderer.set_property('xalign', 0.2) + elif type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534 + if not model[titer][C_JID] or not model[titer][C_ACCOUNT]: + # This can append when at the moment we add the row + return + jid = model[titer][C_JID].decode('utf-8') + account = model[titer][C_ACCOUNT].decode('utf-8') + if jid in gajim.newly_added[account]: + renderer.set_property('cell-background', gajim.config.get( + 'just_connected_bg_color')) + elif jid in gajim.to_be_removed[account]: + renderer.set_property('cell-background', gajim.config.get( + 'just_disconnected_bg_color')) + else: + color = gajim.config.get_per('themes', theme, 'contactbgcolor') + if color: + renderer.set_property('cell-background', color) + else: + renderer.set_property('cell-background', None) + parent_iter = model.iter_parent(titer) + if model[parent_iter][C_TYPE] == 'contact': + renderer.set_property('xalign', 1) + else: + renderer.set_property('xalign', 0.4) + renderer.set_property('width', 26) - def _nameCellDataFunc(self, column, renderer, model, titer, data=None): - '''When a row is added, set properties for name renderer''' - theme = gajim.config.get('roster_theme') - type_ = model[titer][C_TYPE] - if type_ == 'account': - color = gajim.config.get_per('themes', theme, 'accounttextcolor') - if color: - renderer.set_property('foreground', color) - else: - self.set_renderer_color(renderer, gtk.STATE_ACTIVE, False) - color = gajim.config.get_per('themes', theme, 'accountbgcolor') - if color: - renderer.set_property('cell-background', color) - else: - self.set_renderer_color(renderer, gtk.STATE_ACTIVE) - renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'accountfont')) - renderer.set_property('xpad', 0) - renderer.set_property('width', 3) - elif type_ == 'group': - color = gajim.config.get_per('themes', theme, 'grouptextcolor') - if color: - renderer.set_property('foreground', color) - else: - self.set_renderer_color(renderer, gtk.STATE_PRELIGHT, False) - color = gajim.config.get_per('themes', theme, 'groupbgcolor') - if color: - renderer.set_property('cell-background', color) - else: - self.set_renderer_color(renderer, gtk.STATE_PRELIGHT) - renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont')) - renderer.set_property('xpad', 4) - elif type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534 - if not model[titer][C_JID] or not model[titer][C_ACCOUNT]: - # This can append when at the moment we add the row - return - jid = model[titer][C_JID].decode('utf-8') - account = model[titer][C_ACCOUNT].decode('utf-8') - color = None - if type_ == 'groupchat': - ctrl = gajim.interface.minimized_controls[account].get(jid, None) - if ctrl and ctrl.attention_flag: - color = gajim.config.get_per('themes', theme, - 'state_muc_directed_msg_color') - renderer.set_property('foreground', 'red') - if not color: - color = gajim.config.get_per('themes', theme, 'contacttextcolor') - if color: - renderer.set_property('foreground', color) - else: - renderer.set_property('foreground', None) - if jid in gajim.newly_added[account]: - renderer.set_property('cell-background', gajim.config.get( - 'just_connected_bg_color')) - elif jid in gajim.to_be_removed[account]: - renderer.set_property('cell-background', gajim.config.get( - 'just_disconnected_bg_color')) - else: - color = gajim.config.get_per('themes', theme, 'contactbgcolor') - if color: - renderer.set_property('cell-background', color) - else: - renderer.set_property('cell-background', None) - renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont')) - parent_iter = model.iter_parent(titer) - if model[parent_iter][C_TYPE] == 'contact': - renderer.set_property('xpad', 16) - else: - renderer.set_property('xpad', 8) + def _nameCellDataFunc(self, column, renderer, model, titer, data=None): + '''When a row is added, set properties for name renderer''' + theme = gajim.config.get('roster_theme') + type_ = model[titer][C_TYPE] + if type_ == 'account': + color = gajim.config.get_per('themes', theme, 'accounttextcolor') + if color: + renderer.set_property('foreground', color) + else: + self.set_renderer_color(renderer, gtk.STATE_ACTIVE, False) + color = gajim.config.get_per('themes', theme, 'accountbgcolor') + if color: + renderer.set_property('cell-background', color) + else: + self.set_renderer_color(renderer, gtk.STATE_ACTIVE) + renderer.set_property('font', + gtkgui_helpers.get_theme_font_for_option(theme, 'accountfont')) + renderer.set_property('xpad', 0) + renderer.set_property('width', 3) + elif type_ == 'group': + color = gajim.config.get_per('themes', theme, 'grouptextcolor') + if color: + renderer.set_property('foreground', color) + else: + self.set_renderer_color(renderer, gtk.STATE_PRELIGHT, False) + color = gajim.config.get_per('themes', theme, 'groupbgcolor') + if color: + renderer.set_property('cell-background', color) + else: + self.set_renderer_color(renderer, gtk.STATE_PRELIGHT) + renderer.set_property('font', + gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont')) + renderer.set_property('xpad', 4) + elif type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534 + if not model[titer][C_JID] or not model[titer][C_ACCOUNT]: + # This can append when at the moment we add the row + return + jid = model[titer][C_JID].decode('utf-8') + account = model[titer][C_ACCOUNT].decode('utf-8') + color = None + if type_ == 'groupchat': + ctrl = gajim.interface.minimized_controls[account].get(jid, None) + if ctrl and ctrl.attention_flag: + color = gajim.config.get_per('themes', theme, + 'state_muc_directed_msg_color') + renderer.set_property('foreground', 'red') + if not color: + color = gajim.config.get_per('themes', theme, 'contacttextcolor') + if color: + renderer.set_property('foreground', color) + else: + renderer.set_property('foreground', None) + if jid in gajim.newly_added[account]: + renderer.set_property('cell-background', gajim.config.get( + 'just_connected_bg_color')) + elif jid in gajim.to_be_removed[account]: + renderer.set_property('cell-background', gajim.config.get( + 'just_disconnected_bg_color')) + else: + color = gajim.config.get_per('themes', theme, 'contactbgcolor') + if color: + renderer.set_property('cell-background', color) + else: + renderer.set_property('cell-background', None) + renderer.set_property('font', + gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont')) + parent_iter = model.iter_parent(titer) + if model[parent_iter][C_TYPE] == 'contact': + renderer.set_property('xpad', 16) + else: + renderer.set_property('xpad', 8) - def _fill_pep_pixbuf_renderer(self, column, renderer, model, titer, - data=None): - '''When a row is added, draw the respective pep icon''' - theme = gajim.config.get('roster_theme') - type_ = model[titer][C_TYPE] - if type_ == 'group': - renderer.set_property('visible', False) - return + def _fill_pep_pixbuf_renderer(self, column, renderer, model, titer, + data=None): + '''When a row is added, draw the respective pep icon''' + theme = gajim.config.get('roster_theme') + type_ = model[titer][C_TYPE] + if type_ == 'group': + renderer.set_property('visible', False) + return - # allocate space for the icon only if needed - if model[titer][data]: - renderer.set_property('visible', True) - else: - renderer.set_property('visible', False) - if type_ == 'account': - color = gajim.config.get_per('themes', theme, 'accountbgcolor') - if color: - renderer.set_property('cell-background', color) - else: - self.set_renderer_color(renderer, gtk.STATE_ACTIVE) - # align pixbuf to the right) - renderer.set_property('xalign', 1) - # prevent type_ = None, see http://trac.gajim.org/ticket/2534 - elif type_: - if not model[titer][C_JID] or not model[titer][C_ACCOUNT]: - # This can append at the moment we add the row - return - jid = model[titer][C_JID].decode('utf-8') - account = model[titer][C_ACCOUNT].decode('utf-8') - if jid in gajim.newly_added[account]: - renderer.set_property('cell-background', gajim.config.get( - 'just_connected_bg_color')) - elif jid in gajim.to_be_removed[account]: - renderer.set_property('cell-background', gajim.config.get( - 'just_disconnected_bg_color')) - else: - color = gajim.config.get_per('themes', theme, 'contactbgcolor') - renderer.set_property('cell-background', color if color else None) - # align pixbuf to the right - renderer.set_property('xalign', 1) + # allocate space for the icon only if needed + if model[titer][data]: + renderer.set_property('visible', True) + else: + renderer.set_property('visible', False) + if type_ == 'account': + color = gajim.config.get_per('themes', theme, 'accountbgcolor') + if color: + renderer.set_property('cell-background', color) + else: + self.set_renderer_color(renderer, gtk.STATE_ACTIVE) + # align pixbuf to the right) + renderer.set_property('xalign', 1) + # prevent type_ = None, see http://trac.gajim.org/ticket/2534 + elif type_: + if not model[titer][C_JID] or not model[titer][C_ACCOUNT]: + # This can append at the moment we add the row + return + jid = model[titer][C_JID].decode('utf-8') + account = model[titer][C_ACCOUNT].decode('utf-8') + if jid in gajim.newly_added[account]: + renderer.set_property('cell-background', gajim.config.get( + 'just_connected_bg_color')) + elif jid in gajim.to_be_removed[account]: + renderer.set_property('cell-background', gajim.config.get( + 'just_disconnected_bg_color')) + else: + color = gajim.config.get_per('themes', theme, 'contactbgcolor') + renderer.set_property('cell-background', color if color else None) + # align pixbuf to the right + renderer.set_property('xalign', 1) - def _fill_avatar_pixbuf_renderer(self, column, renderer, model, titer, - data = None): - '''When a row is added, set properties for avatar renderer''' - theme = gajim.config.get('roster_theme') - type_ = model[titer][C_TYPE] - if type_ in ('group', 'account'): - renderer.set_property('visible', False) - return + def _fill_avatar_pixbuf_renderer(self, column, renderer, model, titer, + data = None): + '''When a row is added, set properties for avatar renderer''' + theme = gajim.config.get('roster_theme') + type_ = model[titer][C_TYPE] + if type_ in ('group', 'account'): + renderer.set_property('visible', False) + return - # allocate space for the icon only if needed - if model[titer][C_AVATAR_PIXBUF] or \ - gajim.config.get('avatar_position_in_roster') == 'left': - renderer.set_property('visible', True) - else: - renderer.set_property('visible', False) - if type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534 - if not model[titer][C_JID] or not model[titer][C_ACCOUNT]: - # This can append at the moment we add the row - return - jid = model[titer][C_JID].decode('utf-8') - account = model[titer][C_ACCOUNT].decode('utf-8') - if jid in gajim.newly_added[account]: - renderer.set_property('cell-background', gajim.config.get( - 'just_connected_bg_color')) - elif jid in gajim.to_be_removed[account]: - renderer.set_property('cell-background', gajim.config.get( - 'just_disconnected_bg_color')) - else: - color = gajim.config.get_per('themes', theme, 'contactbgcolor') - if color: - renderer.set_property('cell-background', color) - else: - renderer.set_property('cell-background', None) - if gajim.config.get('avatar_position_in_roster') == 'left': - renderer.set_property('width', gajim.config.get('roster_avatar_width')) - renderer.set_property('xalign', 0.5) - else: - renderer.set_property('xalign', 1) # align pixbuf to the right + # allocate space for the icon only if needed + if model[titer][C_AVATAR_PIXBUF] or \ + gajim.config.get('avatar_position_in_roster') == 'left': + renderer.set_property('visible', True) + else: + renderer.set_property('visible', False) + if type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534 + if not model[titer][C_JID] or not model[titer][C_ACCOUNT]: + # This can append at the moment we add the row + return + jid = model[titer][C_JID].decode('utf-8') + account = model[titer][C_ACCOUNT].decode('utf-8') + if jid in gajim.newly_added[account]: + renderer.set_property('cell-background', gajim.config.get( + 'just_connected_bg_color')) + elif jid in gajim.to_be_removed[account]: + renderer.set_property('cell-background', gajim.config.get( + 'just_disconnected_bg_color')) + else: + color = gajim.config.get_per('themes', theme, 'contactbgcolor') + if color: + renderer.set_property('cell-background', color) + else: + renderer.set_property('cell-background', None) + if gajim.config.get('avatar_position_in_roster') == 'left': + renderer.set_property('width', gajim.config.get('roster_avatar_width')) + renderer.set_property('xalign', 0.5) + else: + renderer.set_property('xalign', 1) # align pixbuf to the right - def _fill_padlock_pixbuf_renderer(self, column, renderer, model, titer, - data = None): - '''When a row is added, set properties for padlock renderer''' - theme = gajim.config.get('roster_theme') - type_ = model[titer][C_TYPE] - # allocate space for the icon only if needed - if type_ == 'account' and model[titer][C_PADLOCK_PIXBUF]: - renderer.set_property('visible', True) - color = gajim.config.get_per('themes', theme, 'accountbgcolor') - if color: - renderer.set_property('cell-background', color) - else: - self.set_renderer_color(renderer, gtk.STATE_ACTIVE) - renderer.set_property('xalign', 1) # align pixbuf to the right - else: - renderer.set_property('visible', False) + def _fill_padlock_pixbuf_renderer(self, column, renderer, model, titer, + data = None): + '''When a row is added, set properties for padlock renderer''' + theme = gajim.config.get('roster_theme') + type_ = model[titer][C_TYPE] + # allocate space for the icon only if needed + if type_ == 'account' and model[titer][C_PADLOCK_PIXBUF]: + renderer.set_property('visible', True) + color = gajim.config.get_per('themes', theme, 'accountbgcolor') + if color: + renderer.set_property('cell-background', color) + else: + self.set_renderer_color(renderer, gtk.STATE_ACTIVE) + renderer.set_property('xalign', 1) # align pixbuf to the right + else: + renderer.set_property('visible', False) ################################################################################ ### Everything about building menus ### FIXME: We really need to make it simpler! 1465 lines are a few to much.... ################################################################################ - def make_menu(self, force=False): - '''create the main window\'s menus''' - if not force and not self.actions_menu_needs_rebuild: - return - new_chat_menuitem = self.xml.get_widget('new_chat_menuitem') - single_message_menuitem = self.xml.get_widget( - 'send_single_message_menuitem') - join_gc_menuitem = self.xml.get_widget('join_gc_menuitem') - muc_icon = gtkgui_helpers.load_icon('muc_active') - if muc_icon: - join_gc_menuitem.set_image(muc_icon) - add_new_contact_menuitem = self.xml.get_widget('add_new_contact_menuitem') - service_disco_menuitem = self.xml.get_widget('service_disco_menuitem') - advanced_menuitem = self.xml.get_widget('advanced_menuitem') - profile_avatar_menuitem = self.xml.get_widget('profile_avatar_menuitem') - - # destroy old advanced menus - for m in self.advanced_menus: - m.destroy() - - # make it sensitive. it is insensitive only if no accounts are *available* - advanced_menuitem.set_sensitive(True) - - if self.add_new_contact_handler_id: - add_new_contact_menuitem.handler_disconnect( - self.add_new_contact_handler_id) - self.add_new_contact_handler_id = None - - if self.service_disco_handler_id: - service_disco_menuitem.handler_disconnect( - self.service_disco_handler_id) - self.service_disco_handler_id = None - - if self.new_chat_menuitem_handler_id: - new_chat_menuitem.handler_disconnect( - self.new_chat_menuitem_handler_id) - self.new_chat_menuitem_handler_id = None - - if self.single_message_menuitem_handler_id: - single_message_menuitem.handler_disconnect( - self.single_message_menuitem_handler_id) - self.single_message_menuitem_handler_id = None - - if self.profile_avatar_menuitem_handler_id: - profile_avatar_menuitem.handler_disconnect( - self.profile_avatar_menuitem_handler_id) - self.profile_avatar_menuitem_handler_id = None - - # remove the existing submenus - add_new_contact_menuitem.remove_submenu() - service_disco_menuitem.remove_submenu() - join_gc_menuitem.remove_submenu() - single_message_menuitem.remove_submenu() - new_chat_menuitem.remove_submenu() - advanced_menuitem.remove_submenu() - profile_avatar_menuitem.remove_submenu() - - # remove the existing accelerator - if self.have_new_chat_accel: - ag = gtk.accel_groups_from_object(self.window)[0] - new_chat_menuitem.remove_accelerator(ag, gtk.keysyms.n, - gtk.gdk.CONTROL_MASK) - self.have_new_chat_accel = False - - gc_sub_menu = gtk.Menu() # gc is always a submenu - join_gc_menuitem.set_submenu(gc_sub_menu) - - connected_accounts = gajim.get_number_of_connected_accounts() - - connected_accounts_with_private_storage = 0 - - # items that get shown whether an account is zeroconf or not - accounts_list = sorted(gajim.contacts.get_accounts()) - if connected_accounts > 1: # 2 or more accounts? make submenus - new_chat_sub_menu = gtk.Menu() - - for account in accounts_list: - if gajim.connections[account].connected <= 1: - # if offline or connecting - continue - - # new chat - new_chat_item = gtk.MenuItem(_('using account %s') % account, - False) - new_chat_sub_menu.append(new_chat_item) - new_chat_item.connect('activate', - self.on_new_chat_menuitem_activate, account) - - new_chat_menuitem.set_submenu(new_chat_sub_menu) - new_chat_sub_menu.show_all() - - elif connected_accounts == 1: # user has only one account - for account in gajim.connections: - if gajim.account_is_connected(account): # THE connected account - # new chat - if not self.new_chat_menuitem_handler_id: - self.new_chat_menuitem_handler_id = new_chat_menuitem.\ - connect('activate', self.on_new_chat_menuitem_activate, - account) - - break - - # menu items that don't apply to zeroconf connections - if connected_accounts == 1 or (connected_accounts == 2 and \ - gajim.zeroconf_is_connected()): - # only one 'real' (non-zeroconf) account is connected, don't need submenus - - for account in accounts_list: - if gajim.account_is_connected(account) and \ - not gajim.config.get_per('accounts', account, 'is_zeroconf'): - # gc - if gajim.connections[account].private_storage_supported: - connected_accounts_with_private_storage += 1 - self.add_bookmarks_list(gc_sub_menu, account) - gc_sub_menu.show_all() - # add - if not self.add_new_contact_handler_id: - self.add_new_contact_handler_id =\ - add_new_contact_menuitem.connect( - 'activate', self.on_add_new_contact, account) - # disco - if not self.service_disco_handler_id: - self.service_disco_handler_id = service_disco_menuitem.\ - connect('activate', - self.on_service_disco_menuitem_activate, account) - - # single message - if not self.single_message_menuitem_handler_id: - self.single_message_menuitem_handler_id = \ - single_message_menuitem.connect('activate', \ - self.on_send_single_message_menuitem_activate, account) - - # new chat accel - if not self.have_new_chat_accel: - ag = gtk.accel_groups_from_object(self.window)[0] - new_chat_menuitem.add_accelerator('activate', ag, - gtk.keysyms.n, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) - self.have_new_chat_accel = True - - break # No other account connected - else: - # 2 or more 'real' accounts are connected, make submenus - single_message_sub_menu = gtk.Menu() - add_sub_menu = gtk.Menu() - disco_sub_menu = gtk.Menu() - - for account in accounts_list: - if gajim.connections[account].connected <= 1 or \ - gajim.config.get_per('accounts', account, 'is_zeroconf'): - # skip account if it's offline or connecting or is zeroconf - continue - - # single message - single_message_item = gtk.MenuItem(_('using account %s') % account, - False) - single_message_sub_menu.append(single_message_item) - single_message_item.connect('activate', - self.on_send_single_message_menuitem_activate, account) - - # join gc - if gajim.connections[account].private_storage_supported: - connected_accounts_with_private_storage += 1 - gc_item = gtk.MenuItem(_('using account %s') % account, False) - gc_sub_menu.append(gc_item) - gc_menuitem_menu = gtk.Menu() - self.add_bookmarks_list(gc_menuitem_menu, account) - gc_item.set_submenu(gc_menuitem_menu) - - # add - add_item = gtk.MenuItem(_('to %s account') % account, False) - add_sub_menu.append(add_item) - add_item.connect('activate', self.on_add_new_contact, account) - - # disco - disco_item = gtk.MenuItem(_('using %s account') % account, False) - disco_sub_menu.append(disco_item) - disco_item.connect('activate', - self.on_service_disco_menuitem_activate, account) - - single_message_menuitem.set_submenu(single_message_sub_menu) - single_message_sub_menu.show_all() - gc_sub_menu.show_all() - add_new_contact_menuitem.set_submenu(add_sub_menu) - add_sub_menu.show_all() - service_disco_menuitem.set_submenu(disco_sub_menu) - disco_sub_menu.show_all() - - if connected_accounts == 0: - # no connected accounts, make the menuitems insensitive - for item in (new_chat_menuitem, join_gc_menuitem,\ - add_new_contact_menuitem, service_disco_menuitem,\ - single_message_menuitem): - item.set_sensitive(False) - else: # we have one or more connected accounts - for item in (new_chat_menuitem, join_gc_menuitem, - add_new_contact_menuitem, service_disco_menuitem, - single_message_menuitem): - item.set_sensitive(True) - # disable some fields if only local account is there - if connected_accounts == 1: - for account in gajim.connections: - if gajim.account_is_connected(account) and \ - gajim.connections[account].is_zeroconf: - for item in (join_gc_menuitem, add_new_contact_menuitem, - service_disco_menuitem, single_message_menuitem): - item.set_sensitive(False) - - # Manage GC bookmarks - newitem = gtk.SeparatorMenuItem() # separator - gc_sub_menu.append(newitem) - - newitem = gtk.ImageMenuItem(_('_Manage Bookmarks...')) - img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, - gtk.ICON_SIZE_MENU) - newitem.set_image(img) - newitem.connect('activate', self.on_manage_bookmarks_menuitem_activate) - gc_sub_menu.append(newitem) - gc_sub_menu.show_all() - if connected_accounts_with_private_storage == 0: - newitem.set_sensitive(False) - - connected_accounts_with_vcard = [] - for account in gajim.connections: - if gajim.account_is_connected(account) and \ - gajim.connections[account].vcard_supported: - connected_accounts_with_vcard.append(account) - if len(connected_accounts_with_vcard) > 1: - # 2 or more accounts? make submenus - profile_avatar_sub_menu = gtk.Menu() - for account in connected_accounts_with_vcard: - # profile, avatar - profile_avatar_item = gtk.MenuItem(_('of account %s') % account, - False) - profile_avatar_sub_menu.append(profile_avatar_item) - profile_avatar_item.connect('activate', - self.on_profile_avatar_menuitem_activate, account) - profile_avatar_menuitem.set_submenu(profile_avatar_sub_menu) - profile_avatar_sub_menu.show_all() - elif len(connected_accounts_with_vcard) == 1: # user has only one account - account = connected_accounts_with_vcard[0] - # profile, avatar - if not self.profile_avatar_menuitem_handler_id: - self.profile_avatar_menuitem_handler_id = \ - profile_avatar_menuitem.connect('activate', - self.on_profile_avatar_menuitem_activate, account) - - if len(connected_accounts_with_vcard) == 0: - profile_avatar_menuitem.set_sensitive(False) - else: - profile_avatar_menuitem.set_sensitive(True) - - # Advanced Actions - if len(gajim.connections) == 0: # user has no accounts - advanced_menuitem.set_sensitive(False) - elif len(gajim.connections) == 1: # we have one acccount - account = gajim.connections.keys()[0] - advanced_menuitem_menu = self.get_and_connect_advanced_menuitem_menu( - account) - self.advanced_menus.append(advanced_menuitem_menu) - - self.add_history_manager_menuitem(advanced_menuitem_menu) - - advanced_menuitem.set_submenu(advanced_menuitem_menu) - advanced_menuitem_menu.show_all() - else: # user has *more* than one account : build advanced submenus - advanced_sub_menu = gtk.Menu() - accounts = [] # Put accounts in a list to sort them - for account in gajim.connections: - accounts.append(account) - accounts.sort() - for account in accounts: - advanced_item = gtk.MenuItem(_('for account %s') % account, False) - advanced_sub_menu.append(advanced_item) - advanced_menuitem_menu = \ - self.get_and_connect_advanced_menuitem_menu(account) - self.advanced_menus.append(advanced_menuitem_menu) - advanced_item.set_submenu(advanced_menuitem_menu) - - self.add_history_manager_menuitem(advanced_sub_menu) - - advanced_menuitem.set_submenu(advanced_sub_menu) - advanced_sub_menu.show_all() - - self.actions_menu_needs_rebuild = False - - def build_account_menu(self, account): - # we have to create our own set of icons for the menu - # using self.jabber_status_images is poopoo - iconset = gajim.config.get('iconset') - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - state_images = gtkgui_helpers.load_iconset(path) - - if not gajim.config.get_per('accounts', account, 'is_zeroconf'): - xml = gtkgui_helpers.get_glade('account_context_menu.glade') - account_context_menu = xml.get_widget('account_context_menu') - - status_menuitem = xml.get_widget('status_menuitem') - start_chat_menuitem = xml.get_widget('start_chat_menuitem') - join_group_chat_menuitem = xml.get_widget('join_group_chat_menuitem') - muc_icon = gtkgui_helpers.load_icon('muc_active') - if muc_icon: - join_group_chat_menuitem.set_image(muc_icon) - open_gmail_inbox_menuitem = xml.get_widget('open_gmail_inbox_menuitem') - add_contact_menuitem = xml.get_widget('add_contact_menuitem') - service_discovery_menuitem = xml.get_widget( - 'service_discovery_menuitem') - execute_command_menuitem = xml.get_widget('execute_command_menuitem') - edit_account_menuitem = xml.get_widget('edit_account_menuitem') - sub_menu = gtk.Menu() - status_menuitem.set_submenu(sub_menu) - - for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'): - uf_show = helpers.get_uf_show(show, use_mnemonic=True) - item = gtk.ImageMenuItem(uf_show) - icon = state_images[show] - item.set_image(icon) - sub_menu.append(item) - con = gajim.connections[account] - if show == 'invisible' and con.connected > 1 and \ - not con.privacy_rules_supported: - item.set_sensitive(False) - else: - item.connect('activate', self.change_status, account, show) - - item = gtk.SeparatorMenuItem() - sub_menu.append(item) - - item = gtk.ImageMenuItem(_('_Change Status Message')) - path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png') - img = gtk.Image() - img.set_from_file(path) - item.set_image(img) - sub_menu.append(item) - item.connect('activate', self.on_change_status_message_activate, - account) - if gajim.connections[account].connected < 2: - item.set_sensitive(False) - - item = gtk.SeparatorMenuItem() - sub_menu.append(item) - - uf_show = helpers.get_uf_show('offline', use_mnemonic=True) - item = gtk.ImageMenuItem(uf_show) - icon = state_images['offline'] - item.set_image(icon) - sub_menu.append(item) - item.connect('activate', self.change_status, account, 'offline') - - pep_menuitem = xml.get_widget('pep_menuitem') - if gajim.connections[account].pep_supported: - have_tune = gajim.config.get_per('accounts', account, - 'publish_tune') - pep_submenu = gtk.Menu() - pep_menuitem.set_submenu(pep_submenu) - item = gtk.CheckMenuItem(_('Publish Tune')) - pep_submenu.append(item) - if not dbus_support.supported: - item.set_sensitive(False) - else: - item.set_active(have_tune) - item.connect('toggled', self.on_publish_tune_toggled, account) - - pep_config = gtk.ImageMenuItem(_('Configure Services...')) - item = gtk.SeparatorMenuItem() - pep_submenu.append(item) - pep_config.set_sensitive(True) - pep_submenu.append(pep_config) - pep_config.connect('activate', - self.on_pep_services_menuitem_activate, account) - img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, - gtk.ICON_SIZE_MENU) - pep_config.set_image(img) - - else: - pep_menuitem.set_sensitive(False) - - if not gajim.connections[account].gmail_url: - open_gmail_inbox_menuitem.set_no_show_all(True) - open_gmail_inbox_menuitem.hide() - else: - open_gmail_inbox_menuitem.connect('activate', - self.on_open_gmail_inbox, account) - - edit_account_menuitem.connect('activate', self.on_edit_account, - account) - add_contact_menuitem.connect('activate', self.on_add_new_contact, - account) - service_discovery_menuitem.connect('activate', - self.on_service_disco_menuitem_activate, account) - hostname = gajim.config.get_per('accounts', account, 'hostname') - contact = gajim.contacts.create_contact(jid=hostname, account=account) # Fake contact - execute_command_menuitem.connect('activate', - self.on_execute_command, contact, account) - - start_chat_menuitem.connect('activate', - self.on_new_chat_menuitem_activate, account) - - gc_sub_menu = gtk.Menu() # gc is always a submenu - join_group_chat_menuitem.set_submenu(gc_sub_menu) - self.add_bookmarks_list(gc_sub_menu, account) - - # make some items insensitive if account is offline - if gajim.connections[account].connected < 2: - for widget in (add_contact_menuitem, service_discovery_menuitem, - join_group_chat_menuitem, execute_command_menuitem, pep_menuitem, - start_chat_menuitem): - widget.set_sensitive(False) - else: - xml = gtkgui_helpers.get_glade('zeroconf_context_menu.glade') - account_context_menu = xml.get_widget('zeroconf_context_menu') - - status_menuitem = xml.get_widget('status_menuitem') - zeroconf_properties_menuitem = xml.get_widget( - 'zeroconf_properties_menuitem') - sub_menu = gtk.Menu() - status_menuitem.set_submenu(sub_menu) - - for show in ('online', 'away', 'dnd', 'invisible'): - uf_show = helpers.get_uf_show(show, use_mnemonic=True) - item = gtk.ImageMenuItem(uf_show) - icon = state_images[show] - item.set_image(icon) - sub_menu.append(item) - item.connect('activate', self.change_status, account, show) - - item = gtk.SeparatorMenuItem() - sub_menu.append(item) - - item = gtk.ImageMenuItem(_('_Change Status Message')) - path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png') - img = gtk.Image() - img.set_from_file(path) - item.set_image(img) - sub_menu.append(item) - item.connect('activate', self.on_change_status_message_activate, - account) - if gajim.connections[account].connected < 2: - item.set_sensitive(False) - - uf_show = helpers.get_uf_show('offline', use_mnemonic=True) - item = gtk.ImageMenuItem(uf_show) - icon = state_images['offline'] - item.set_image(icon) - sub_menu.append(item) - item.connect('activate', self.change_status, account, 'offline') - - zeroconf_properties_menuitem.connect('activate', - self.on_zeroconf_properties, account) - - return account_context_menu - - def make_account_menu(self, event, titer): - '''Make account's popup menu''' - model = self.modelfilter - account = model[titer][C_ACCOUNT].decode('utf-8') - - if account != 'all': # not in merged mode - menu = self.build_account_menu(account) - else: - menu = gtk.Menu() - iconset = gajim.config.get('iconset') - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - accounts = [] # Put accounts in a list to sort them - for account in gajim.connections: - accounts.append(account) - accounts.sort() - for account in accounts: - state_images = gtkgui_helpers.load_iconset(path) - item = gtk.ImageMenuItem(account) - show = gajim.SHOW_LIST[gajim.connections[account].connected] - icon = state_images[show] - item.set_image(icon) - account_menu = self.build_account_menu(account) - item.set_submenu(account_menu) - menu.append(item) - - event_button = gtkgui_helpers.get_possible_button_event(event) - - menu.attach_to_widget(self.tree, None) - menu.connect('selection-done', gtkgui_helpers.destroy_widget) - menu.show_all() - menu.popup(None, None, None, event_button, event.time) - - def make_group_menu(self, event, titer): - '''Make group's popup menu''' - model = self.modelfilter - path = model.get_path(titer) - group = model[titer][C_JID].decode('utf-8') - account = model[titer][C_ACCOUNT].decode('utf-8') - - list_ = [] # list of (jid, account) tuples - list_online = [] # list of (jid, account) tuples - - group = model[titer][C_JID] - for jid in gajim.contacts.get_jid_list(account): - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - if group in contact.get_shown_groups(): - if contact.show not in ('offline', 'error'): - list_online.append((contact, account)) - list_.append((contact, account)) - menu = gtk.Menu() - - # Make special context menu if group is Groupchats - if group == _('Groupchats'): - maximize_menuitem = gtk.ImageMenuItem(_('_Maximize All')) - icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP, gtk.ICON_SIZE_MENU) - maximize_menuitem.set_image(icon) - maximize_menuitem.connect('activate', self.on_all_groupchat_maximized,\ - list_) - menu.append(maximize_menuitem) - else: - # Send Group Message - send_group_message_item = gtk.ImageMenuItem(_('Send Group M_essage')) - icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) - send_group_message_item.set_image(icon) - - send_group_message_submenu = gtk.Menu() - send_group_message_item.set_submenu(send_group_message_submenu) - menu.append(send_group_message_item) - - group_message_to_all_item = gtk.MenuItem(_('To all users')) - send_group_message_submenu.append(group_message_to_all_item) - - group_message_to_all_online_item = gtk.MenuItem( - _('To all online users')) - send_group_message_submenu.append(group_message_to_all_online_item) - - group_message_to_all_online_item.connect('activate', - self.on_send_single_message_menuitem_activate, account, list_online) - group_message_to_all_item.connect('activate', - self.on_send_single_message_menuitem_activate, account, list_) - - # Invite to - invite_menuitem = gtk.ImageMenuItem(_('In_vite to')) - muc_icon = gtkgui_helpers.load_icon('muc_active') - if muc_icon: - invite_menuitem.set_image(muc_icon) - - gui_menu_builder.build_invite_submenu(invite_menuitem, list_online) - menu.append(invite_menuitem) - - # Send Custom Status - send_custom_status_menuitem = gtk.ImageMenuItem( - _('Send Cus_tom Status')) - # add a special img for this menuitem - if helpers.group_is_blocked(account, group): - send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( - 'offline')) - send_custom_status_menuitem.set_sensitive(False) - else: - icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, - gtk.ICON_SIZE_MENU) - send_custom_status_menuitem.set_image(icon) - status_menuitems = gtk.Menu() - send_custom_status_menuitem.set_submenu(status_menuitems) - iconset = gajim.config.get('iconset') - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'): - # icon MUST be different instance for every item - state_images = gtkgui_helpers.load_iconset(path) - status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s)) - status_menuitem.connect('activate', self.on_send_custom_status, - list_, s, group) - icon = state_images[s] - status_menuitem.set_image(icon) - status_menuitems.append(status_menuitem) - menu.append(send_custom_status_menuitem) - - # there is no singlemessage and custom status for zeroconf - if gajim.config.get_per('accounts', account, 'is_zeroconf'): - send_custom_status_menuitem.set_sensitive(False) - send_group_message_item.set_sensitive(False) - - if not group in helpers.special_groups: - item = gtk.SeparatorMenuItem() # separator - menu.append(item) - - # Rename - rename_item = gtk.ImageMenuItem(_('Re_name')) - # add a special img for rename menuitem - path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps', - 'kbd_input.png') - img = gtk.Image() - img.set_from_file(path_to_kbd_input_img) - rename_item.set_image(img) - menu.append(rename_item) - rename_item.connect('activate', self.on_rename, 'group', group, - account) - - # Block group - is_blocked = False - if self.regroup: - for g_account in gajim.connections: - if helpers.group_is_blocked(g_account, group): - is_blocked = True - else: - if helpers.group_is_blocked(account, group): - is_blocked = True - - if is_blocked and gajim.connections[account].privacy_rules_supported: - unblock_menuitem = gtk.ImageMenuItem(_('_Unblock')) - icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) - unblock_menuitem.set_image(icon) - unblock_menuitem.connect('activate', self.on_unblock, list_, group) - menu.append(unblock_menuitem) - else: - block_menuitem = gtk.ImageMenuItem(_('_Block')) - icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) - block_menuitem.set_image(icon) - block_menuitem.connect('activate', self.on_block, list_, group) - menu.append(block_menuitem) - if not gajim.connections[account].privacy_rules_supported: - block_menuitem.set_sensitive(False) - - # Remove group - remove_item = gtk.ImageMenuItem(_('_Remove')) - icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) - remove_item.set_image(icon) - menu.append(remove_item) - remove_item.connect('activate', self.on_remove_group_item_activated, - group, account) - - # unsensitive if account is not connected - if gajim.connections[account].connected < 2: - rename_item.set_sensitive(False) - - # General group cannot be changed - if group == _('General'): - rename_item.set_sensitive(False) - block_menuitem.set_sensitive(False) - remove_item.set_sensitive(False) - - event_button = gtkgui_helpers.get_possible_button_event(event) - - menu.attach_to_widget(self.tree, None) - menu.connect('selection-done', gtkgui_helpers.destroy_widget) - menu.show_all() - menu.popup(None, None, None, event_button, event.time) - - def make_contact_menu(self, event, titer): - '''Make contact\'s popup menu''' - model = self.modelfilter - jid = model[titer][C_JID].decode('utf-8') - tree_path = model.get_path(titer) - account = model[titer][C_ACCOUNT].decode('utf-8') - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - menu = gui_menu_builder.get_contact_menu(contact, account) - event_button = gtkgui_helpers.get_possible_button_event(event) - menu.attach_to_widget(self.tree, None) - menu.popup(None, None, None, event_button, event.time) - - def make_multiple_contact_menu(self, event, iters): - '''Make group's popup menu''' - model = self.modelfilter - list_ = [] # list of (jid, account) tuples - one_account_offline = False - is_blocked = True - privacy_rules_supported = True - for titer in iters: - jid = model[titer][C_JID].decode('utf-8') - account = model[titer][C_ACCOUNT].decode('utf-8') - if gajim.connections[account].connected < 2: - one_account_offline = True - if not gajim.connections[account].privacy_rules_supported: - privacy_rules_supported = False - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - if helpers.jid_is_blocked(account, jid): - is_blocked = False - list_.append((contact, account)) - - menu = gtk.Menu() - account = None - for (contact, current_account) in list_: - # check that we use the same account for every sender - if account is not None and account != current_account: - account = None - break - account = current_account - if account is not None: - send_group_message_item = gtk.ImageMenuItem(_('Send Group M_essage')) - icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) - send_group_message_item.set_image(icon) - menu.append(send_group_message_item) - send_group_message_item.connect('activate', - self.on_send_single_message_menuitem_activate, account, list_) - - # Invite to Groupchat - invite_item = gtk.ImageMenuItem(_('In_vite to')) - muc_icon = gtkgui_helpers.load_icon('muc_active') - if muc_icon: - invite_item.set_image(muc_icon) - - gui_menu_builder.build_invite_submenu(invite_item, list_) - menu.append(invite_item) - - item = gtk.SeparatorMenuItem() # separator - menu.append(item) - - # Manage Transport submenu - item = gtk.ImageMenuItem(_('_Manage Contacts')) - icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES, gtk.ICON_SIZE_MENU) - item.set_image(icon) - manage_contacts_submenu = gtk.Menu() - item.set_submenu(manage_contacts_submenu) - menu.append(item) - - # Edit Groups - edit_groups_item = gtk.ImageMenuItem(_('Edit _Groups')) - icon = gtk.image_new_from_stock(gtk.STOCK_EDIT, gtk.ICON_SIZE_MENU) - edit_groups_item.set_image(icon) - manage_contacts_submenu.append(edit_groups_item) - edit_groups_item.connect('activate', self.on_edit_groups, list_) - - item = gtk.SeparatorMenuItem() # separator - manage_contacts_submenu.append(item) - - # Block - if is_blocked and privacy_rules_supported: - unblock_menuitem = gtk.ImageMenuItem(_('_Unblock')) - icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) - unblock_menuitem.set_image(icon) - unblock_menuitem.connect('activate', self.on_unblock, list_) - manage_contacts_submenu.append(unblock_menuitem) - else: - block_menuitem = gtk.ImageMenuItem(_('_Block')) - icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) - block_menuitem.set_image(icon) - block_menuitem.connect('activate', self.on_block, list_) - manage_contacts_submenu.append(block_menuitem) - - if not privacy_rules_supported: - block_menuitem.set_sensitive(False) - - # Remove - remove_item = gtk.ImageMenuItem(_('_Remove')) - icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) - remove_item.set_image(icon) - manage_contacts_submenu.append(remove_item) - remove_item.connect('activate', self.on_req_usub, list_) - # unsensitive remove if one account is not connected - if one_account_offline: - remove_item.set_sensitive(False) - - event_button = gtkgui_helpers.get_possible_button_event(event) - - menu.attach_to_widget(self.tree, None) - menu.connect('selection-done', gtkgui_helpers.destroy_widget) - menu.show_all() - menu.popup(None, None, None, event_button, event.time) - - def make_transport_menu(self, event, titer): - '''Make transport\'s popup menu''' - model = self.modelfilter - jid = model[titer][C_JID].decode('utf-8') - path = model.get_path(titer) - account = model[titer][C_ACCOUNT].decode('utf-8') - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - menu = gtk.Menu() - - # Send single message - item = gtk.ImageMenuItem(_('Send Single Message')) - icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) - item.set_image(icon) - item.connect('activate', - self.on_send_single_message_menuitem_activate, account, contact) - menu.append(item) - - blocked = False - if helpers.jid_is_blocked(account, jid): - blocked = True - - # Send Custom Status - send_custom_status_menuitem = gtk.ImageMenuItem(_('Send Cus_tom Status')) - # add a special img for this menuitem - if blocked: - send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( - 'offline')) - send_custom_status_menuitem.set_sensitive(False) - else: - if account in gajim.interface.status_sent_to_users and \ - jid in gajim.interface.status_sent_to_users[account]: - send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( - gajim.interface.status_sent_to_users[account][jid])) - else: - icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, - gtk.ICON_SIZE_MENU) - send_custom_status_menuitem.set_image(icon) - status_menuitems = gtk.Menu() - send_custom_status_menuitem.set_submenu(status_menuitems) - iconset = gajim.config.get('iconset') - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'): - # icon MUST be different instance for every item - state_images = gtkgui_helpers.load_iconset(path) - status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s)) - status_menuitem.connect('activate', self.on_send_custom_status, - [(contact, account)], s) - icon = state_images[s] - status_menuitem.set_image(icon) - status_menuitems.append(status_menuitem) - menu.append(send_custom_status_menuitem) - - item = gtk.SeparatorMenuItem() # separator - menu.append(item) - - # Execute Command - item = gtk.ImageMenuItem(_('Execute Command...')) - icon = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU) - item.set_image(icon) - menu.append(item) - item.connect('activate', self.on_execute_command, contact, account, - contact.resource) - if gajim.account_is_disconnected(account): - item.set_sensitive(False) - - # Manage Transport submenu - item = gtk.ImageMenuItem(_('_Manage Transport')) - icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES, gtk.ICON_SIZE_MENU) - item.set_image(icon) - manage_transport_submenu = gtk.Menu() - item.set_submenu(manage_transport_submenu) - menu.append(item) - - # Modify Transport - item = gtk.ImageMenuItem(_('_Modify Transport')) - icon = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU) - item.set_image(icon) - manage_transport_submenu.append(item) - item.connect('activate', self.on_edit_agent, contact, account) - if gajim.account_is_disconnected(account): - item.set_sensitive(False) - - # Rename - item = gtk.ImageMenuItem(_('_Rename')) - # add a special img for rename menuitem - path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps', - 'kbd_input.png') - img = gtk.Image() - img.set_from_file(path_to_kbd_input_img) - item.set_image(img) - manage_transport_submenu.append(item) - item.connect('activate', self.on_rename, 'agent', jid, account) - if gajim.account_is_disconnected(account): - item.set_sensitive(False) - - item = gtk.SeparatorMenuItem() # separator - manage_transport_submenu.append(item) - - # Block - if blocked: - item = gtk.ImageMenuItem(_('_Unblock')) - item.connect('activate', self.on_unblock, [(contact, account)]) - else: - item = gtk.ImageMenuItem(_('_Block')) - item.connect('activate', self.on_block, [(contact, account)]) - - icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) - item.set_image(icon) - manage_transport_submenu.append(item) - if gajim.account_is_disconnected(account): - item.set_sensitive(False) - - # Remove - item = gtk.ImageMenuItem(_('_Remove')) - icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) - item.set_image(icon) - manage_transport_submenu.append(item) - item.connect('activate', self.on_remove_agent, [(contact, account)]) - if gajim.account_is_disconnected(account): - item.set_sensitive(False) - - item = gtk.SeparatorMenuItem() # separator - menu.append(item) - - # Information - information_menuitem = gtk.ImageMenuItem(_('_Information')) - icon = gtk.image_new_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_MENU) - information_menuitem.set_image(icon) - menu.append(information_menuitem) - information_menuitem.connect('activate', self.on_info, contact, account) - - - event_button = gtkgui_helpers.get_possible_button_event(event) - - menu.attach_to_widget(self.tree, None) - menu.connect('selection-done', gtkgui_helpers.destroy_widget) - menu.show_all() - menu.popup(None, None, None, event_button, event.time) - - def make_groupchat_menu(self, event, titer): - model = self.modelfilter - - jid = model[titer][C_JID].decode('utf-8') - account = model[titer][C_ACCOUNT].decode('utf-8') - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - menu = gtk.Menu() - - if jid in gajim.interface.minimized_controls[account]: - maximize_menuitem = gtk.ImageMenuItem(_('_Maximize')) - icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP, gtk.ICON_SIZE_MENU) - maximize_menuitem.set_image(icon) - maximize_menuitem.connect('activate', self.on_groupchat_maximized, \ - jid, account) - menu.append(maximize_menuitem) - - if not gajim.gc_connected[account].get(jid, False): - connect_menuitem = gtk.ImageMenuItem(_('_Reconnect')) - connect_icon = gtk.image_new_from_stock(gtk.STOCK_CONNECT, \ - gtk.ICON_SIZE_MENU) - connect_menuitem.set_image(connect_icon) - connect_menuitem.connect('activate', self.on_reconnect, jid, account) - menu.append(connect_menuitem) - disconnect_menuitem = gtk.ImageMenuItem(_('_Disconnect')) - disconnect_icon = gtk.image_new_from_stock(gtk.STOCK_DISCONNECT, \ - gtk.ICON_SIZE_MENU) - disconnect_menuitem.set_image(disconnect_icon) - disconnect_menuitem.connect('activate', self.on_disconnect, jid, account) - menu.append(disconnect_menuitem) - - item = gtk.SeparatorMenuItem() # separator - menu.append(item) - - history_menuitem = gtk.ImageMenuItem(_('_History')) - history_icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL, \ - gtk.ICON_SIZE_MENU) - history_menuitem.set_image(history_icon) - history_menuitem .connect('activate', self.on_history, \ - contact, account) - menu.append(history_menuitem) - - event_button = gtkgui_helpers.get_possible_button_event(event) - - menu.attach_to_widget(self.tree, None) - menu.connect('selection-done', gtkgui_helpers.destroy_widget) - menu.show_all() - menu.popup(None, None, None, event_button, event.time) - - def get_and_connect_advanced_menuitem_menu(self, account): - '''adds FOR ACCOUNT options''' - xml = gtkgui_helpers.get_glade('advanced_menuitem_menu.glade') - advanced_menuitem_menu = xml.get_widget('advanced_menuitem_menu') - - xml_console_menuitem = xml.get_widget('xml_console_menuitem') - archiving_preferences_menuitem = \ - xml.get_widget('archiving_preferences_menuitem') - privacy_lists_menuitem = xml.get_widget('privacy_lists_menuitem') - administrator_menuitem = xml.get_widget('administrator_menuitem') - send_server_message_menuitem = xml.get_widget( - 'send_server_message_menuitem') - set_motd_menuitem = xml.get_widget('set_motd_menuitem') - update_motd_menuitem = xml.get_widget('update_motd_menuitem') - delete_motd_menuitem = xml.get_widget('delete_motd_menuitem') - - xml_console_menuitem.connect('activate', - self.on_xml_console_menuitem_activate, account) - - if gajim.connections[account]: - if gajim.connections[account].privacy_rules_supported: - privacy_lists_menuitem.connect('activate', - self.on_privacy_lists_menuitem_activate, account) - else: - privacy_lists_menuitem.set_sensitive(False) - if gajim.connections[account].archive_pref_supported: - archiving_preferences_menuitem.connect('activate', - self.on_archiving_preferences_menuitem_activate, account) - else: - archiving_preferences_menuitem.set_sensitive(False) - - if gajim.connections[account].is_zeroconf: - administrator_menuitem.set_sensitive(False) - send_server_message_menuitem.set_sensitive(False) - set_motd_menuitem.set_sensitive(False) - update_motd_menuitem.set_sensitive(False) - delete_motd_menuitem.set_sensitive(False) - else: - send_server_message_menuitem.connect('activate', - self.on_send_server_message_menuitem_activate, account) - - set_motd_menuitem.connect('activate', - self.on_set_motd_menuitem_activate, account) - - update_motd_menuitem.connect('activate', - self.on_update_motd_menuitem_activate, account) - - delete_motd_menuitem.connect('activate', - self.on_delete_motd_menuitem_activate, account) - - advanced_menuitem_menu.show_all() - - return advanced_menuitem_menu - - def add_history_manager_menuitem(self, menu): - '''adds a seperator and History Manager menuitem BELOW for account - menuitems''' - item = gtk.SeparatorMenuItem() # separator - menu.append(item) - - # History manager - item = gtk.ImageMenuItem(_('History Manager')) - icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL, - gtk.ICON_SIZE_MENU) - item.set_image(icon) - menu.append(item) - item.connect('activate', self.on_history_manager_menuitem_activate) - - def add_bookmarks_list(self, gc_sub_menu, account): - '''Show join new group chat item and bookmarks list for an account''' - item = gtk.ImageMenuItem(_('_Join New Group Chat')) - icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) - item.set_image(icon) - item.connect('activate', self.on_join_gc_activate, account) - gc_sub_menu.append(item) - - # user has at least one bookmark - if len(gajim.connections[account].bookmarks) > 0: - item = gtk.SeparatorMenuItem() # separator - gc_sub_menu.append(item) - - for bookmark in gajim.connections[account].bookmarks: - item = gtk.MenuItem(bookmark['name'], False) # Do not use underline - item.connect('activate', self.on_bookmark_menuitem_activate, - account, bookmark) - gc_sub_menu.append(item) - - def set_actions_menu_needs_rebuild(self): - self.actions_menu_needs_rebuild = True - - def show_appropriate_context_menu(self, event, iters): - # iters must be all of the same type - model = self.modelfilter - type_ = model[iters[0]][C_TYPE] - for titer in iters[1:]: - if model[titer][C_TYPE] != type_: - return - if type_ == 'group' and len(iters) == 1: - self.make_group_menu(event, iters[0]) - if type_ == 'groupchat' and len(iters) == 1: - self.make_groupchat_menu(event, iters[0]) - elif type_ == 'agent' and len(iters) == 1: - self.make_transport_menu(event, iters[0]) - elif type_ in ('contact', 'self_contact') and len(iters) == 1: - self.make_contact_menu(event, iters[0]) - elif type_ == 'contact': - self.make_multiple_contact_menu(event, iters) - elif type_ == 'account' and len(iters) == 1: - self.make_account_menu(event, iters[0]) - - def show_treeview_menu(self, event): - try: - model, list_of_paths = self.tree.get_selection().get_selected_rows() - except TypeError: - self.tree.get_selection().unselect_all() - return - if not len(list_of_paths): - # no row is selected - return - if len(list_of_paths) > 1: - iters = [] - for path in list_of_paths: - iters.append(model.get_iter(path)) - else: - path = list_of_paths[0] - iters = [model.get_iter(path)] - self.show_appropriate_context_menu(event, iters) - - return True + def make_menu(self, force=False): + '''create the main window\'s menus''' + if not force and not self.actions_menu_needs_rebuild: + return + new_chat_menuitem = self.xml.get_widget('new_chat_menuitem') + single_message_menuitem = self.xml.get_widget( + 'send_single_message_menuitem') + join_gc_menuitem = self.xml.get_widget('join_gc_menuitem') + muc_icon = gtkgui_helpers.load_icon('muc_active') + if muc_icon: + join_gc_menuitem.set_image(muc_icon) + add_new_contact_menuitem = self.xml.get_widget('add_new_contact_menuitem') + service_disco_menuitem = self.xml.get_widget('service_disco_menuitem') + advanced_menuitem = self.xml.get_widget('advanced_menuitem') + profile_avatar_menuitem = self.xml.get_widget('profile_avatar_menuitem') + + # destroy old advanced menus + for m in self.advanced_menus: + m.destroy() + + # make it sensitive. it is insensitive only if no accounts are *available* + advanced_menuitem.set_sensitive(True) + + if self.add_new_contact_handler_id: + add_new_contact_menuitem.handler_disconnect( + self.add_new_contact_handler_id) + self.add_new_contact_handler_id = None + + if self.service_disco_handler_id: + service_disco_menuitem.handler_disconnect( + self.service_disco_handler_id) + self.service_disco_handler_id = None + + if self.new_chat_menuitem_handler_id: + new_chat_menuitem.handler_disconnect( + self.new_chat_menuitem_handler_id) + self.new_chat_menuitem_handler_id = None + + if self.single_message_menuitem_handler_id: + single_message_menuitem.handler_disconnect( + self.single_message_menuitem_handler_id) + self.single_message_menuitem_handler_id = None + + if self.profile_avatar_menuitem_handler_id: + profile_avatar_menuitem.handler_disconnect( + self.profile_avatar_menuitem_handler_id) + self.profile_avatar_menuitem_handler_id = None + + # remove the existing submenus + add_new_contact_menuitem.remove_submenu() + service_disco_menuitem.remove_submenu() + join_gc_menuitem.remove_submenu() + single_message_menuitem.remove_submenu() + new_chat_menuitem.remove_submenu() + advanced_menuitem.remove_submenu() + profile_avatar_menuitem.remove_submenu() + + # remove the existing accelerator + if self.have_new_chat_accel: + ag = gtk.accel_groups_from_object(self.window)[0] + new_chat_menuitem.remove_accelerator(ag, gtk.keysyms.n, + gtk.gdk.CONTROL_MASK) + self.have_new_chat_accel = False + + gc_sub_menu = gtk.Menu() # gc is always a submenu + join_gc_menuitem.set_submenu(gc_sub_menu) + + connected_accounts = gajim.get_number_of_connected_accounts() + + connected_accounts_with_private_storage = 0 + + # items that get shown whether an account is zeroconf or not + accounts_list = sorted(gajim.contacts.get_accounts()) + if connected_accounts > 1: # 2 or more accounts? make submenus + new_chat_sub_menu = gtk.Menu() + + for account in accounts_list: + if gajim.connections[account].connected <= 1: + # if offline or connecting + continue + + # new chat + new_chat_item = gtk.MenuItem(_('using account %s') % account, + False) + new_chat_sub_menu.append(new_chat_item) + new_chat_item.connect('activate', + self.on_new_chat_menuitem_activate, account) + + new_chat_menuitem.set_submenu(new_chat_sub_menu) + new_chat_sub_menu.show_all() + + elif connected_accounts == 1: # user has only one account + for account in gajim.connections: + if gajim.account_is_connected(account): # THE connected account + # new chat + if not self.new_chat_menuitem_handler_id: + self.new_chat_menuitem_handler_id = new_chat_menuitem.\ + connect('activate', self.on_new_chat_menuitem_activate, + account) + + break + + # menu items that don't apply to zeroconf connections + if connected_accounts == 1 or (connected_accounts == 2 and \ + gajim.zeroconf_is_connected()): + # only one 'real' (non-zeroconf) account is connected, don't need submenus + + for account in accounts_list: + if gajim.account_is_connected(account) and \ + not gajim.config.get_per('accounts', account, 'is_zeroconf'): + # gc + if gajim.connections[account].private_storage_supported: + connected_accounts_with_private_storage += 1 + self.add_bookmarks_list(gc_sub_menu, account) + gc_sub_menu.show_all() + # add + if not self.add_new_contact_handler_id: + self.add_new_contact_handler_id =\ + add_new_contact_menuitem.connect( + 'activate', self.on_add_new_contact, account) + # disco + if not self.service_disco_handler_id: + self.service_disco_handler_id = service_disco_menuitem.\ + connect('activate', + self.on_service_disco_menuitem_activate, account) + + # single message + if not self.single_message_menuitem_handler_id: + self.single_message_menuitem_handler_id = \ + single_message_menuitem.connect('activate', \ + self.on_send_single_message_menuitem_activate, account) + + # new chat accel + if not self.have_new_chat_accel: + ag = gtk.accel_groups_from_object(self.window)[0] + new_chat_menuitem.add_accelerator('activate', ag, + gtk.keysyms.n, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) + self.have_new_chat_accel = True + + break # No other account connected + else: + # 2 or more 'real' accounts are connected, make submenus + single_message_sub_menu = gtk.Menu() + add_sub_menu = gtk.Menu() + disco_sub_menu = gtk.Menu() + + for account in accounts_list: + if gajim.connections[account].connected <= 1 or \ + gajim.config.get_per('accounts', account, 'is_zeroconf'): + # skip account if it's offline or connecting or is zeroconf + continue + + # single message + single_message_item = gtk.MenuItem(_('using account %s') % account, + False) + single_message_sub_menu.append(single_message_item) + single_message_item.connect('activate', + self.on_send_single_message_menuitem_activate, account) + + # join gc + if gajim.connections[account].private_storage_supported: + connected_accounts_with_private_storage += 1 + gc_item = gtk.MenuItem(_('using account %s') % account, False) + gc_sub_menu.append(gc_item) + gc_menuitem_menu = gtk.Menu() + self.add_bookmarks_list(gc_menuitem_menu, account) + gc_item.set_submenu(gc_menuitem_menu) + + # add + add_item = gtk.MenuItem(_('to %s account') % account, False) + add_sub_menu.append(add_item) + add_item.connect('activate', self.on_add_new_contact, account) + + # disco + disco_item = gtk.MenuItem(_('using %s account') % account, False) + disco_sub_menu.append(disco_item) + disco_item.connect('activate', + self.on_service_disco_menuitem_activate, account) + + single_message_menuitem.set_submenu(single_message_sub_menu) + single_message_sub_menu.show_all() + gc_sub_menu.show_all() + add_new_contact_menuitem.set_submenu(add_sub_menu) + add_sub_menu.show_all() + service_disco_menuitem.set_submenu(disco_sub_menu) + disco_sub_menu.show_all() + + if connected_accounts == 0: + # no connected accounts, make the menuitems insensitive + for item in (new_chat_menuitem, join_gc_menuitem,\ + add_new_contact_menuitem, service_disco_menuitem,\ + single_message_menuitem): + item.set_sensitive(False) + else: # we have one or more connected accounts + for item in (new_chat_menuitem, join_gc_menuitem, + add_new_contact_menuitem, service_disco_menuitem, + single_message_menuitem): + item.set_sensitive(True) + # disable some fields if only local account is there + if connected_accounts == 1: + for account in gajim.connections: + if gajim.account_is_connected(account) and \ + gajim.connections[account].is_zeroconf: + for item in (join_gc_menuitem, add_new_contact_menuitem, + service_disco_menuitem, single_message_menuitem): + item.set_sensitive(False) + + # Manage GC bookmarks + newitem = gtk.SeparatorMenuItem() # separator + gc_sub_menu.append(newitem) + + newitem = gtk.ImageMenuItem(_('_Manage Bookmarks...')) + img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, + gtk.ICON_SIZE_MENU) + newitem.set_image(img) + newitem.connect('activate', self.on_manage_bookmarks_menuitem_activate) + gc_sub_menu.append(newitem) + gc_sub_menu.show_all() + if connected_accounts_with_private_storage == 0: + newitem.set_sensitive(False) + + connected_accounts_with_vcard = [] + for account in gajim.connections: + if gajim.account_is_connected(account) and \ + gajim.connections[account].vcard_supported: + connected_accounts_with_vcard.append(account) + if len(connected_accounts_with_vcard) > 1: + # 2 or more accounts? make submenus + profile_avatar_sub_menu = gtk.Menu() + for account in connected_accounts_with_vcard: + # profile, avatar + profile_avatar_item = gtk.MenuItem(_('of account %s') % account, + False) + profile_avatar_sub_menu.append(profile_avatar_item) + profile_avatar_item.connect('activate', + self.on_profile_avatar_menuitem_activate, account) + profile_avatar_menuitem.set_submenu(profile_avatar_sub_menu) + profile_avatar_sub_menu.show_all() + elif len(connected_accounts_with_vcard) == 1: # user has only one account + account = connected_accounts_with_vcard[0] + # profile, avatar + if not self.profile_avatar_menuitem_handler_id: + self.profile_avatar_menuitem_handler_id = \ + profile_avatar_menuitem.connect('activate', + self.on_profile_avatar_menuitem_activate, account) + + if len(connected_accounts_with_vcard) == 0: + profile_avatar_menuitem.set_sensitive(False) + else: + profile_avatar_menuitem.set_sensitive(True) + + # Advanced Actions + if len(gajim.connections) == 0: # user has no accounts + advanced_menuitem.set_sensitive(False) + elif len(gajim.connections) == 1: # we have one acccount + account = gajim.connections.keys()[0] + advanced_menuitem_menu = self.get_and_connect_advanced_menuitem_menu( + account) + self.advanced_menus.append(advanced_menuitem_menu) + + self.add_history_manager_menuitem(advanced_menuitem_menu) + + advanced_menuitem.set_submenu(advanced_menuitem_menu) + advanced_menuitem_menu.show_all() + else: # user has *more* than one account : build advanced submenus + advanced_sub_menu = gtk.Menu() + accounts = [] # Put accounts in a list to sort them + for account in gajim.connections: + accounts.append(account) + accounts.sort() + for account in accounts: + advanced_item = gtk.MenuItem(_('for account %s') % account, False) + advanced_sub_menu.append(advanced_item) + advanced_menuitem_menu = \ + self.get_and_connect_advanced_menuitem_menu(account) + self.advanced_menus.append(advanced_menuitem_menu) + advanced_item.set_submenu(advanced_menuitem_menu) + + self.add_history_manager_menuitem(advanced_sub_menu) + + advanced_menuitem.set_submenu(advanced_sub_menu) + advanced_sub_menu.show_all() + + self.actions_menu_needs_rebuild = False + + def build_account_menu(self, account): + # we have to create our own set of icons for the menu + # using self.jabber_status_images is poopoo + iconset = gajim.config.get('iconset') + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + state_images = gtkgui_helpers.load_iconset(path) + + if not gajim.config.get_per('accounts', account, 'is_zeroconf'): + xml = gtkgui_helpers.get_glade('account_context_menu.glade') + account_context_menu = xml.get_widget('account_context_menu') + + status_menuitem = xml.get_widget('status_menuitem') + start_chat_menuitem = xml.get_widget('start_chat_menuitem') + join_group_chat_menuitem = xml.get_widget('join_group_chat_menuitem') + muc_icon = gtkgui_helpers.load_icon('muc_active') + if muc_icon: + join_group_chat_menuitem.set_image(muc_icon) + open_gmail_inbox_menuitem = xml.get_widget('open_gmail_inbox_menuitem') + add_contact_menuitem = xml.get_widget('add_contact_menuitem') + service_discovery_menuitem = xml.get_widget( + 'service_discovery_menuitem') + execute_command_menuitem = xml.get_widget('execute_command_menuitem') + edit_account_menuitem = xml.get_widget('edit_account_menuitem') + sub_menu = gtk.Menu() + status_menuitem.set_submenu(sub_menu) + + for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'): + uf_show = helpers.get_uf_show(show, use_mnemonic=True) + item = gtk.ImageMenuItem(uf_show) + icon = state_images[show] + item.set_image(icon) + sub_menu.append(item) + con = gajim.connections[account] + if show == 'invisible' and con.connected > 1 and \ + not con.privacy_rules_supported: + item.set_sensitive(False) + else: + item.connect('activate', self.change_status, account, show) + + item = gtk.SeparatorMenuItem() + sub_menu.append(item) + + item = gtk.ImageMenuItem(_('_Change Status Message')) + path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png') + img = gtk.Image() + img.set_from_file(path) + item.set_image(img) + sub_menu.append(item) + item.connect('activate', self.on_change_status_message_activate, + account) + if gajim.connections[account].connected < 2: + item.set_sensitive(False) + + item = gtk.SeparatorMenuItem() + sub_menu.append(item) + + uf_show = helpers.get_uf_show('offline', use_mnemonic=True) + item = gtk.ImageMenuItem(uf_show) + icon = state_images['offline'] + item.set_image(icon) + sub_menu.append(item) + item.connect('activate', self.change_status, account, 'offline') + + pep_menuitem = xml.get_widget('pep_menuitem') + if gajim.connections[account].pep_supported: + have_tune = gajim.config.get_per('accounts', account, + 'publish_tune') + pep_submenu = gtk.Menu() + pep_menuitem.set_submenu(pep_submenu) + item = gtk.CheckMenuItem(_('Publish Tune')) + pep_submenu.append(item) + if not dbus_support.supported: + item.set_sensitive(False) + else: + item.set_active(have_tune) + item.connect('toggled', self.on_publish_tune_toggled, account) + + pep_config = gtk.ImageMenuItem(_('Configure Services...')) + item = gtk.SeparatorMenuItem() + pep_submenu.append(item) + pep_config.set_sensitive(True) + pep_submenu.append(pep_config) + pep_config.connect('activate', + self.on_pep_services_menuitem_activate, account) + img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, + gtk.ICON_SIZE_MENU) + pep_config.set_image(img) + + else: + pep_menuitem.set_sensitive(False) + + if not gajim.connections[account].gmail_url: + open_gmail_inbox_menuitem.set_no_show_all(True) + open_gmail_inbox_menuitem.hide() + else: + open_gmail_inbox_menuitem.connect('activate', + self.on_open_gmail_inbox, account) + + edit_account_menuitem.connect('activate', self.on_edit_account, + account) + add_contact_menuitem.connect('activate', self.on_add_new_contact, + account) + service_discovery_menuitem.connect('activate', + self.on_service_disco_menuitem_activate, account) + hostname = gajim.config.get_per('accounts', account, 'hostname') + contact = gajim.contacts.create_contact(jid=hostname, account=account) # Fake contact + execute_command_menuitem.connect('activate', + self.on_execute_command, contact, account) + + start_chat_menuitem.connect('activate', + self.on_new_chat_menuitem_activate, account) + + gc_sub_menu = gtk.Menu() # gc is always a submenu + join_group_chat_menuitem.set_submenu(gc_sub_menu) + self.add_bookmarks_list(gc_sub_menu, account) + + # make some items insensitive if account is offline + if gajim.connections[account].connected < 2: + for widget in (add_contact_menuitem, service_discovery_menuitem, + join_group_chat_menuitem, execute_command_menuitem, pep_menuitem, + start_chat_menuitem): + widget.set_sensitive(False) + else: + xml = gtkgui_helpers.get_glade('zeroconf_context_menu.glade') + account_context_menu = xml.get_widget('zeroconf_context_menu') + + status_menuitem = xml.get_widget('status_menuitem') + zeroconf_properties_menuitem = xml.get_widget( + 'zeroconf_properties_menuitem') + sub_menu = gtk.Menu() + status_menuitem.set_submenu(sub_menu) + + for show in ('online', 'away', 'dnd', 'invisible'): + uf_show = helpers.get_uf_show(show, use_mnemonic=True) + item = gtk.ImageMenuItem(uf_show) + icon = state_images[show] + item.set_image(icon) + sub_menu.append(item) + item.connect('activate', self.change_status, account, show) + + item = gtk.SeparatorMenuItem() + sub_menu.append(item) + + item = gtk.ImageMenuItem(_('_Change Status Message')) + path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png') + img = gtk.Image() + img.set_from_file(path) + item.set_image(img) + sub_menu.append(item) + item.connect('activate', self.on_change_status_message_activate, + account) + if gajim.connections[account].connected < 2: + item.set_sensitive(False) + + uf_show = helpers.get_uf_show('offline', use_mnemonic=True) + item = gtk.ImageMenuItem(uf_show) + icon = state_images['offline'] + item.set_image(icon) + sub_menu.append(item) + item.connect('activate', self.change_status, account, 'offline') + + zeroconf_properties_menuitem.connect('activate', + self.on_zeroconf_properties, account) + + return account_context_menu + + def make_account_menu(self, event, titer): + '''Make account's popup menu''' + model = self.modelfilter + account = model[titer][C_ACCOUNT].decode('utf-8') + + if account != 'all': # not in merged mode + menu = self.build_account_menu(account) + else: + menu = gtk.Menu() + iconset = gajim.config.get('iconset') + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + accounts = [] # Put accounts in a list to sort them + for account in gajim.connections: + accounts.append(account) + accounts.sort() + for account in accounts: + state_images = gtkgui_helpers.load_iconset(path) + item = gtk.ImageMenuItem(account) + show = gajim.SHOW_LIST[gajim.connections[account].connected] + icon = state_images[show] + item.set_image(icon) + account_menu = self.build_account_menu(account) + item.set_submenu(account_menu) + menu.append(item) + + event_button = gtkgui_helpers.get_possible_button_event(event) + + menu.attach_to_widget(self.tree, None) + menu.connect('selection-done', gtkgui_helpers.destroy_widget) + menu.show_all() + menu.popup(None, None, None, event_button, event.time) + + def make_group_menu(self, event, titer): + '''Make group's popup menu''' + model = self.modelfilter + path = model.get_path(titer) + group = model[titer][C_JID].decode('utf-8') + account = model[titer][C_ACCOUNT].decode('utf-8') + + list_ = [] # list of (jid, account) tuples + list_online = [] # list of (jid, account) tuples + + group = model[titer][C_JID] + for jid in gajim.contacts.get_jid_list(account): + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + if group in contact.get_shown_groups(): + if contact.show not in ('offline', 'error'): + list_online.append((contact, account)) + list_.append((contact, account)) + menu = gtk.Menu() + + # Make special context menu if group is Groupchats + if group == _('Groupchats'): + maximize_menuitem = gtk.ImageMenuItem(_('_Maximize All')) + icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP, gtk.ICON_SIZE_MENU) + maximize_menuitem.set_image(icon) + maximize_menuitem.connect('activate', self.on_all_groupchat_maximized,\ + list_) + menu.append(maximize_menuitem) + else: + # Send Group Message + send_group_message_item = gtk.ImageMenuItem(_('Send Group M_essage')) + icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) + send_group_message_item.set_image(icon) + + send_group_message_submenu = gtk.Menu() + send_group_message_item.set_submenu(send_group_message_submenu) + menu.append(send_group_message_item) + + group_message_to_all_item = gtk.MenuItem(_('To all users')) + send_group_message_submenu.append(group_message_to_all_item) + + group_message_to_all_online_item = gtk.MenuItem( + _('To all online users')) + send_group_message_submenu.append(group_message_to_all_online_item) + + group_message_to_all_online_item.connect('activate', + self.on_send_single_message_menuitem_activate, account, list_online) + group_message_to_all_item.connect('activate', + self.on_send_single_message_menuitem_activate, account, list_) + + # Invite to + invite_menuitem = gtk.ImageMenuItem(_('In_vite to')) + muc_icon = gtkgui_helpers.load_icon('muc_active') + if muc_icon: + invite_menuitem.set_image(muc_icon) + + gui_menu_builder.build_invite_submenu(invite_menuitem, list_online) + menu.append(invite_menuitem) + + # Send Custom Status + send_custom_status_menuitem = gtk.ImageMenuItem( + _('Send Cus_tom Status')) + # add a special img for this menuitem + if helpers.group_is_blocked(account, group): + send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( + 'offline')) + send_custom_status_menuitem.set_sensitive(False) + else: + icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, + gtk.ICON_SIZE_MENU) + send_custom_status_menuitem.set_image(icon) + status_menuitems = gtk.Menu() + send_custom_status_menuitem.set_submenu(status_menuitems) + iconset = gajim.config.get('iconset') + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'): + # icon MUST be different instance for every item + state_images = gtkgui_helpers.load_iconset(path) + status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s)) + status_menuitem.connect('activate', self.on_send_custom_status, + list_, s, group) + icon = state_images[s] + status_menuitem.set_image(icon) + status_menuitems.append(status_menuitem) + menu.append(send_custom_status_menuitem) + + # there is no singlemessage and custom status for zeroconf + if gajim.config.get_per('accounts', account, 'is_zeroconf'): + send_custom_status_menuitem.set_sensitive(False) + send_group_message_item.set_sensitive(False) + + if not group in helpers.special_groups: + item = gtk.SeparatorMenuItem() # separator + menu.append(item) + + # Rename + rename_item = gtk.ImageMenuItem(_('Re_name')) + # add a special img for rename menuitem + path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps', + 'kbd_input.png') + img = gtk.Image() + img.set_from_file(path_to_kbd_input_img) + rename_item.set_image(img) + menu.append(rename_item) + rename_item.connect('activate', self.on_rename, 'group', group, + account) + + # Block group + is_blocked = False + if self.regroup: + for g_account in gajim.connections: + if helpers.group_is_blocked(g_account, group): + is_blocked = True + else: + if helpers.group_is_blocked(account, group): + is_blocked = True + + if is_blocked and gajim.connections[account].privacy_rules_supported: + unblock_menuitem = gtk.ImageMenuItem(_('_Unblock')) + icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) + unblock_menuitem.set_image(icon) + unblock_menuitem.connect('activate', self.on_unblock, list_, group) + menu.append(unblock_menuitem) + else: + block_menuitem = gtk.ImageMenuItem(_('_Block')) + icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) + block_menuitem.set_image(icon) + block_menuitem.connect('activate', self.on_block, list_, group) + menu.append(block_menuitem) + if not gajim.connections[account].privacy_rules_supported: + block_menuitem.set_sensitive(False) + + # Remove group + remove_item = gtk.ImageMenuItem(_('_Remove')) + icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) + remove_item.set_image(icon) + menu.append(remove_item) + remove_item.connect('activate', self.on_remove_group_item_activated, + group, account) + + # unsensitive if account is not connected + if gajim.connections[account].connected < 2: + rename_item.set_sensitive(False) + + # General group cannot be changed + if group == _('General'): + rename_item.set_sensitive(False) + block_menuitem.set_sensitive(False) + remove_item.set_sensitive(False) + + event_button = gtkgui_helpers.get_possible_button_event(event) + + menu.attach_to_widget(self.tree, None) + menu.connect('selection-done', gtkgui_helpers.destroy_widget) + menu.show_all() + menu.popup(None, None, None, event_button, event.time) + + def make_contact_menu(self, event, titer): + '''Make contact\'s popup menu''' + model = self.modelfilter + jid = model[titer][C_JID].decode('utf-8') + tree_path = model.get_path(titer) + account = model[titer][C_ACCOUNT].decode('utf-8') + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + menu = gui_menu_builder.get_contact_menu(contact, account) + event_button = gtkgui_helpers.get_possible_button_event(event) + menu.attach_to_widget(self.tree, None) + menu.popup(None, None, None, event_button, event.time) + + def make_multiple_contact_menu(self, event, iters): + '''Make group's popup menu''' + model = self.modelfilter + list_ = [] # list of (jid, account) tuples + one_account_offline = False + is_blocked = True + privacy_rules_supported = True + for titer in iters: + jid = model[titer][C_JID].decode('utf-8') + account = model[titer][C_ACCOUNT].decode('utf-8') + if gajim.connections[account].connected < 2: + one_account_offline = True + if not gajim.connections[account].privacy_rules_supported: + privacy_rules_supported = False + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + if helpers.jid_is_blocked(account, jid): + is_blocked = False + list_.append((contact, account)) + + menu = gtk.Menu() + account = None + for (contact, current_account) in list_: + # check that we use the same account for every sender + if account is not None and account != current_account: + account = None + break + account = current_account + if account is not None: + send_group_message_item = gtk.ImageMenuItem(_('Send Group M_essage')) + icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) + send_group_message_item.set_image(icon) + menu.append(send_group_message_item) + send_group_message_item.connect('activate', + self.on_send_single_message_menuitem_activate, account, list_) + + # Invite to Groupchat + invite_item = gtk.ImageMenuItem(_('In_vite to')) + muc_icon = gtkgui_helpers.load_icon('muc_active') + if muc_icon: + invite_item.set_image(muc_icon) + + gui_menu_builder.build_invite_submenu(invite_item, list_) + menu.append(invite_item) + + item = gtk.SeparatorMenuItem() # separator + menu.append(item) + + # Manage Transport submenu + item = gtk.ImageMenuItem(_('_Manage Contacts')) + icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES, gtk.ICON_SIZE_MENU) + item.set_image(icon) + manage_contacts_submenu = gtk.Menu() + item.set_submenu(manage_contacts_submenu) + menu.append(item) + + # Edit Groups + edit_groups_item = gtk.ImageMenuItem(_('Edit _Groups')) + icon = gtk.image_new_from_stock(gtk.STOCK_EDIT, gtk.ICON_SIZE_MENU) + edit_groups_item.set_image(icon) + manage_contacts_submenu.append(edit_groups_item) + edit_groups_item.connect('activate', self.on_edit_groups, list_) + + item = gtk.SeparatorMenuItem() # separator + manage_contacts_submenu.append(item) + + # Block + if is_blocked and privacy_rules_supported: + unblock_menuitem = gtk.ImageMenuItem(_('_Unblock')) + icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) + unblock_menuitem.set_image(icon) + unblock_menuitem.connect('activate', self.on_unblock, list_) + manage_contacts_submenu.append(unblock_menuitem) + else: + block_menuitem = gtk.ImageMenuItem(_('_Block')) + icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) + block_menuitem.set_image(icon) + block_menuitem.connect('activate', self.on_block, list_) + manage_contacts_submenu.append(block_menuitem) + + if not privacy_rules_supported: + block_menuitem.set_sensitive(False) + + # Remove + remove_item = gtk.ImageMenuItem(_('_Remove')) + icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) + remove_item.set_image(icon) + manage_contacts_submenu.append(remove_item) + remove_item.connect('activate', self.on_req_usub, list_) + # unsensitive remove if one account is not connected + if one_account_offline: + remove_item.set_sensitive(False) + + event_button = gtkgui_helpers.get_possible_button_event(event) + + menu.attach_to_widget(self.tree, None) + menu.connect('selection-done', gtkgui_helpers.destroy_widget) + menu.show_all() + menu.popup(None, None, None, event_button, event.time) + + def make_transport_menu(self, event, titer): + '''Make transport\'s popup menu''' + model = self.modelfilter + jid = model[titer][C_JID].decode('utf-8') + path = model.get_path(titer) + account = model[titer][C_ACCOUNT].decode('utf-8') + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + menu = gtk.Menu() + + # Send single message + item = gtk.ImageMenuItem(_('Send Single Message')) + icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) + item.set_image(icon) + item.connect('activate', + self.on_send_single_message_menuitem_activate, account, contact) + menu.append(item) + + blocked = False + if helpers.jid_is_blocked(account, jid): + blocked = True + + # Send Custom Status + send_custom_status_menuitem = gtk.ImageMenuItem(_('Send Cus_tom Status')) + # add a special img for this menuitem + if blocked: + send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( + 'offline')) + send_custom_status_menuitem.set_sensitive(False) + else: + if account in gajim.interface.status_sent_to_users and \ + jid in gajim.interface.status_sent_to_users[account]: + send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( + gajim.interface.status_sent_to_users[account][jid])) + else: + icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, + gtk.ICON_SIZE_MENU) + send_custom_status_menuitem.set_image(icon) + status_menuitems = gtk.Menu() + send_custom_status_menuitem.set_submenu(status_menuitems) + iconset = gajim.config.get('iconset') + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'): + # icon MUST be different instance for every item + state_images = gtkgui_helpers.load_iconset(path) + status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s)) + status_menuitem.connect('activate', self.on_send_custom_status, + [(contact, account)], s) + icon = state_images[s] + status_menuitem.set_image(icon) + status_menuitems.append(status_menuitem) + menu.append(send_custom_status_menuitem) + + item = gtk.SeparatorMenuItem() # separator + menu.append(item) + + # Execute Command + item = gtk.ImageMenuItem(_('Execute Command...')) + icon = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU) + item.set_image(icon) + menu.append(item) + item.connect('activate', self.on_execute_command, contact, account, + contact.resource) + if gajim.account_is_disconnected(account): + item.set_sensitive(False) + + # Manage Transport submenu + item = gtk.ImageMenuItem(_('_Manage Transport')) + icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES, gtk.ICON_SIZE_MENU) + item.set_image(icon) + manage_transport_submenu = gtk.Menu() + item.set_submenu(manage_transport_submenu) + menu.append(item) + + # Modify Transport + item = gtk.ImageMenuItem(_('_Modify Transport')) + icon = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU) + item.set_image(icon) + manage_transport_submenu.append(item) + item.connect('activate', self.on_edit_agent, contact, account) + if gajim.account_is_disconnected(account): + item.set_sensitive(False) + + # Rename + item = gtk.ImageMenuItem(_('_Rename')) + # add a special img for rename menuitem + path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps', + 'kbd_input.png') + img = gtk.Image() + img.set_from_file(path_to_kbd_input_img) + item.set_image(img) + manage_transport_submenu.append(item) + item.connect('activate', self.on_rename, 'agent', jid, account) + if gajim.account_is_disconnected(account): + item.set_sensitive(False) + + item = gtk.SeparatorMenuItem() # separator + manage_transport_submenu.append(item) + + # Block + if blocked: + item = gtk.ImageMenuItem(_('_Unblock')) + item.connect('activate', self.on_unblock, [(contact, account)]) + else: + item = gtk.ImageMenuItem(_('_Block')) + item.connect('activate', self.on_block, [(contact, account)]) + + icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) + item.set_image(icon) + manage_transport_submenu.append(item) + if gajim.account_is_disconnected(account): + item.set_sensitive(False) + + # Remove + item = gtk.ImageMenuItem(_('_Remove')) + icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) + item.set_image(icon) + manage_transport_submenu.append(item) + item.connect('activate', self.on_remove_agent, [(contact, account)]) + if gajim.account_is_disconnected(account): + item.set_sensitive(False) + + item = gtk.SeparatorMenuItem() # separator + menu.append(item) + + # Information + information_menuitem = gtk.ImageMenuItem(_('_Information')) + icon = gtk.image_new_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_MENU) + information_menuitem.set_image(icon) + menu.append(information_menuitem) + information_menuitem.connect('activate', self.on_info, contact, account) + + + event_button = gtkgui_helpers.get_possible_button_event(event) + + menu.attach_to_widget(self.tree, None) + menu.connect('selection-done', gtkgui_helpers.destroy_widget) + menu.show_all() + menu.popup(None, None, None, event_button, event.time) + + def make_groupchat_menu(self, event, titer): + model = self.modelfilter + + jid = model[titer][C_JID].decode('utf-8') + account = model[titer][C_ACCOUNT].decode('utf-8') + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + menu = gtk.Menu() + + if jid in gajim.interface.minimized_controls[account]: + maximize_menuitem = gtk.ImageMenuItem(_('_Maximize')) + icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP, gtk.ICON_SIZE_MENU) + maximize_menuitem.set_image(icon) + maximize_menuitem.connect('activate', self.on_groupchat_maximized, \ + jid, account) + menu.append(maximize_menuitem) + + if not gajim.gc_connected[account].get(jid, False): + connect_menuitem = gtk.ImageMenuItem(_('_Reconnect')) + connect_icon = gtk.image_new_from_stock(gtk.STOCK_CONNECT, \ + gtk.ICON_SIZE_MENU) + connect_menuitem.set_image(connect_icon) + connect_menuitem.connect('activate', self.on_reconnect, jid, account) + menu.append(connect_menuitem) + disconnect_menuitem = gtk.ImageMenuItem(_('_Disconnect')) + disconnect_icon = gtk.image_new_from_stock(gtk.STOCK_DISCONNECT, \ + gtk.ICON_SIZE_MENU) + disconnect_menuitem.set_image(disconnect_icon) + disconnect_menuitem.connect('activate', self.on_disconnect, jid, account) + menu.append(disconnect_menuitem) + + item = gtk.SeparatorMenuItem() # separator + menu.append(item) + + history_menuitem = gtk.ImageMenuItem(_('_History')) + history_icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL, \ + gtk.ICON_SIZE_MENU) + history_menuitem.set_image(history_icon) + history_menuitem .connect('activate', self.on_history, \ + contact, account) + menu.append(history_menuitem) + + event_button = gtkgui_helpers.get_possible_button_event(event) + + menu.attach_to_widget(self.tree, None) + menu.connect('selection-done', gtkgui_helpers.destroy_widget) + menu.show_all() + menu.popup(None, None, None, event_button, event.time) + + def get_and_connect_advanced_menuitem_menu(self, account): + '''adds FOR ACCOUNT options''' + xml = gtkgui_helpers.get_glade('advanced_menuitem_menu.glade') + advanced_menuitem_menu = xml.get_widget('advanced_menuitem_menu') + + xml_console_menuitem = xml.get_widget('xml_console_menuitem') + archiving_preferences_menuitem = \ + xml.get_widget('archiving_preferences_menuitem') + privacy_lists_menuitem = xml.get_widget('privacy_lists_menuitem') + administrator_menuitem = xml.get_widget('administrator_menuitem') + send_server_message_menuitem = xml.get_widget( + 'send_server_message_menuitem') + set_motd_menuitem = xml.get_widget('set_motd_menuitem') + update_motd_menuitem = xml.get_widget('update_motd_menuitem') + delete_motd_menuitem = xml.get_widget('delete_motd_menuitem') + + xml_console_menuitem.connect('activate', + self.on_xml_console_menuitem_activate, account) + + if gajim.connections[account]: + if gajim.connections[account].privacy_rules_supported: + privacy_lists_menuitem.connect('activate', + self.on_privacy_lists_menuitem_activate, account) + else: + privacy_lists_menuitem.set_sensitive(False) + if gajim.connections[account].archive_pref_supported: + archiving_preferences_menuitem.connect('activate', + self.on_archiving_preferences_menuitem_activate, account) + else: + archiving_preferences_menuitem.set_sensitive(False) + + if gajim.connections[account].is_zeroconf: + administrator_menuitem.set_sensitive(False) + send_server_message_menuitem.set_sensitive(False) + set_motd_menuitem.set_sensitive(False) + update_motd_menuitem.set_sensitive(False) + delete_motd_menuitem.set_sensitive(False) + else: + send_server_message_menuitem.connect('activate', + self.on_send_server_message_menuitem_activate, account) + + set_motd_menuitem.connect('activate', + self.on_set_motd_menuitem_activate, account) + + update_motd_menuitem.connect('activate', + self.on_update_motd_menuitem_activate, account) + + delete_motd_menuitem.connect('activate', + self.on_delete_motd_menuitem_activate, account) + + advanced_menuitem_menu.show_all() + + return advanced_menuitem_menu + + def add_history_manager_menuitem(self, menu): + '''adds a seperator and History Manager menuitem BELOW for account + menuitems''' + item = gtk.SeparatorMenuItem() # separator + menu.append(item) + + # History manager + item = gtk.ImageMenuItem(_('History Manager')) + icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL, + gtk.ICON_SIZE_MENU) + item.set_image(icon) + menu.append(item) + item.connect('activate', self.on_history_manager_menuitem_activate) + + def add_bookmarks_list(self, gc_sub_menu, account): + '''Show join new group chat item and bookmarks list for an account''' + item = gtk.ImageMenuItem(_('_Join New Group Chat')) + icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) + item.set_image(icon) + item.connect('activate', self.on_join_gc_activate, account) + gc_sub_menu.append(item) + + # user has at least one bookmark + if len(gajim.connections[account].bookmarks) > 0: + item = gtk.SeparatorMenuItem() # separator + gc_sub_menu.append(item) + + for bookmark in gajim.connections[account].bookmarks: + item = gtk.MenuItem(bookmark['name'], False) # Do not use underline + item.connect('activate', self.on_bookmark_menuitem_activate, + account, bookmark) + gc_sub_menu.append(item) + + def set_actions_menu_needs_rebuild(self): + self.actions_menu_needs_rebuild = True + + def show_appropriate_context_menu(self, event, iters): + # iters must be all of the same type + model = self.modelfilter + type_ = model[iters[0]][C_TYPE] + for titer in iters[1:]: + if model[titer][C_TYPE] != type_: + return + if type_ == 'group' and len(iters) == 1: + self.make_group_menu(event, iters[0]) + if type_ == 'groupchat' and len(iters) == 1: + self.make_groupchat_menu(event, iters[0]) + elif type_ == 'agent' and len(iters) == 1: + self.make_transport_menu(event, iters[0]) + elif type_ in ('contact', 'self_contact') and len(iters) == 1: + self.make_contact_menu(event, iters[0]) + elif type_ == 'contact': + self.make_multiple_contact_menu(event, iters) + elif type_ == 'account' and len(iters) == 1: + self.make_account_menu(event, iters[0]) + + def show_treeview_menu(self, event): + try: + model, list_of_paths = self.tree.get_selection().get_selected_rows() + except TypeError: + self.tree.get_selection().unselect_all() + return + if not len(list_of_paths): + # no row is selected + return + if len(list_of_paths) > 1: + iters = [] + for path in list_of_paths: + iters.append(model.get_iter(path)) + else: + path = list_of_paths[0] + iters = [model.get_iter(path)] + self.show_appropriate_context_menu(event, iters) + + return True ################################################################################ ### ################################################################################ - def __init__(self): - self.filtering = False - self.xml = gtkgui_helpers.get_glade('roster_window.glade') - self.window = self.xml.get_widget('roster_window') - self.hpaned = self.xml.get_widget('roster_hpaned') - gajim.interface.msg_win_mgr = MessageWindowMgr(self.window, self.hpaned) - gajim.interface.msg_win_mgr.connect('window-delete', - self.on_message_window_delete) - self.advanced_menus = [] # We keep them to destroy them - if gajim.config.get('roster_window_skip_taskbar'): - self.window.set_property('skip-taskbar-hint', True) - self.tree = self.xml.get_widget('roster_treeview') - sel = self.tree.get_selection() - sel.set_mode(gtk.SELECTION_MULTIPLE) - #sel.connect('changed', - # self.on_treeview_selection_changed) + def __init__(self): + self.filtering = False + self.xml = gtkgui_helpers.get_glade('roster_window.glade') + self.window = self.xml.get_widget('roster_window') + self.hpaned = self.xml.get_widget('roster_hpaned') + gajim.interface.msg_win_mgr = MessageWindowMgr(self.window, self.hpaned) + gajim.interface.msg_win_mgr.connect('window-delete', + self.on_message_window_delete) + self.advanced_menus = [] # We keep them to destroy them + if gajim.config.get('roster_window_skip_taskbar'): + self.window.set_property('skip-taskbar-hint', True) + self.tree = self.xml.get_widget('roster_treeview') + sel = self.tree.get_selection() + sel.set_mode(gtk.SELECTION_MULTIPLE) + #sel.connect('changed', + # self.on_treeview_selection_changed) - self._last_selected_contact = [] # holds a list of (jid, account) tupples - self.transports_state_images = {'16': {}, '32': {}, 'opened': {}, - 'closed': {}} + self._last_selected_contact = [] # holds a list of (jid, account) tupples + self.transports_state_images = {'16': {}, '32': {}, 'opened': {}, + 'closed': {}} - self.last_save_dir = None - self.editing_path = None # path of row with cell in edit mode - self.add_new_contact_handler_id = False - self.service_disco_handler_id = False - self.new_chat_menuitem_handler_id = False - self.single_message_menuitem_handler_id = False - self.profile_avatar_menuitem_handler_id = False - self.actions_menu_needs_rebuild = True - self.regroup = gajim.config.get('mergeaccounts') - self.clicked_path = None # Used remember on wich row we clicked - if len(gajim.connections) < 2: # Do not merge accounts if only one exists - self.regroup = False - #FIXME: When list_accel_closures will be wrapped in pygtk - # no need of this variable - self.have_new_chat_accel = False # Is the "Ctrl+N" shown ? - gtkgui_helpers.resize_window(self.window, - gajim.config.get('roster_width'), - gajim.config.get('roster_height')) - gtkgui_helpers.move_window(self.window, - gajim.config.get('roster_x-position'), - gajim.config.get('roster_y-position')) + self.last_save_dir = None + self.editing_path = None # path of row with cell in edit mode + self.add_new_contact_handler_id = False + self.service_disco_handler_id = False + self.new_chat_menuitem_handler_id = False + self.single_message_menuitem_handler_id = False + self.profile_avatar_menuitem_handler_id = False + self.actions_menu_needs_rebuild = True + self.regroup = gajim.config.get('mergeaccounts') + self.clicked_path = None # Used remember on wich row we clicked + if len(gajim.connections) < 2: # Do not merge accounts if only one exists + self.regroup = False + #FIXME: When list_accel_closures will be wrapped in pygtk + # no need of this variable + self.have_new_chat_accel = False # Is the "Ctrl+N" shown ? + gtkgui_helpers.resize_window(self.window, + gajim.config.get('roster_width'), + gajim.config.get('roster_height')) + gtkgui_helpers.move_window(self.window, + gajim.config.get('roster_x-position'), + gajim.config.get('roster_y-position')) - self.popups_notification_height = 0 - self.popup_notification_windows = [] + self.popups_notification_height = 0 + self.popup_notification_windows = [] - # Remove contact from roster when last event opened - # { (contact, account): { backend: boolean } - self.contacts_to_be_removed = {} - gajim.events.event_removed_subscribe(self.on_event_removed) + # Remove contact from roster when last event opened + # { (contact, account): { backend: boolean } + self.contacts_to_be_removed = {} + gajim.events.event_removed_subscribe(self.on_event_removed) - # when this value become 0 we quit main application. If it's more than 0 - # it means we are waiting for this number of accounts to disconnect before - # quitting - self.quit_on_next_offline = -1 + # when this value become 0 we quit main application. If it's more than 0 + # it means we are waiting for this number of accounts to disconnect before + # quitting + self.quit_on_next_offline = -1 - # uf_show, img, show, sensitive - liststore = gtk.ListStore(str, gtk.Image, str, bool) - self.status_combobox = self.xml.get_widget('status_combobox') + # uf_show, img, show, sensitive + liststore = gtk.ListStore(str, gtk.Image, str, bool) + self.status_combobox = self.xml.get_widget('status_combobox') - cell = cell_renderer_image.CellRendererImage(0, 1) - self.status_combobox.pack_start(cell, False) + cell = cell_renderer_image.CellRendererImage(0, 1) + self.status_combobox.pack_start(cell, False) - # img to show is in in 2nd column of liststore - self.status_combobox.add_attribute(cell, 'image', 1) - # if it will be sensitive or not it is in the fourth column - # all items in the 'row' must have sensitive to False - # if we want False (so we add it for img_cell too) - self.status_combobox.add_attribute(cell, 'sensitive', 3) + # img to show is in in 2nd column of liststore + self.status_combobox.add_attribute(cell, 'image', 1) + # if it will be sensitive or not it is in the fourth column + # all items in the 'row' must have sensitive to False + # if we want False (so we add it for img_cell too) + self.status_combobox.add_attribute(cell, 'sensitive', 3) - cell = gtk.CellRendererText() - cell.set_property('xpad', 5) # padding for status text - self.status_combobox.pack_start(cell, True) - # text to show is in in first column of liststore - self.status_combobox.add_attribute(cell, 'text', 0) - # if it will be sensitive or not it is in the fourth column - self.status_combobox.add_attribute(cell, 'sensitive', 3) + cell = gtk.CellRendererText() + cell.set_property('xpad', 5) # padding for status text + self.status_combobox.pack_start(cell, True) + # text to show is in in first column of liststore + self.status_combobox.add_attribute(cell, 'text', 0) + # if it will be sensitive or not it is in the fourth column + self.status_combobox.add_attribute(cell, 'sensitive', 3) - self.status_combobox.set_row_separator_func(self._iter_is_separator) + self.status_combobox.set_row_separator_func(self._iter_is_separator) - for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'): - uf_show = helpers.get_uf_show(show) - liststore.append([uf_show, gajim.interface.jabber_state_images['16'][ - show], show, True]) - # Add a Separator (self._iter_is_separator() checks on string SEPARATOR) - liststore.append(['SEPARATOR', None, '', True]) + for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'): + uf_show = helpers.get_uf_show(show) + liststore.append([uf_show, gajim.interface.jabber_state_images['16'][ + show], show, True]) + # Add a Separator (self._iter_is_separator() checks on string SEPARATOR) + liststore.append(['SEPARATOR', None, '', True]) - path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png') - img = gtk.Image() - img.set_from_file(path) - # sensitivity to False because by default we're offline - self.status_message_menuitem_iter = liststore.append( - [_('Change Status Message...'), img, '', False]) - # Add a Separator (self._iter_is_separator() checks on string SEPARATOR) - liststore.append(['SEPARATOR', None, '', True]) + path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png') + img = gtk.Image() + img.set_from_file(path) + # sensitivity to False because by default we're offline + self.status_message_menuitem_iter = liststore.append( + [_('Change Status Message...'), img, '', False]) + # Add a Separator (self._iter_is_separator() checks on string SEPARATOR) + liststore.append(['SEPARATOR', None, '', True]) - uf_show = helpers.get_uf_show('offline') - liststore.append([uf_show, gajim.interface.jabber_state_images['16'][ - 'offline'], 'offline', True]) + uf_show = helpers.get_uf_show('offline') + liststore.append([uf_show, gajim.interface.jabber_state_images['16'][ + 'offline'], 'offline', True]) - status_combobox_items = ['online', 'chat', 'away', 'xa', 'dnd', - 'invisible', 'separator1', 'change_status_msg', 'separator2', - 'offline'] - self.status_combobox.set_model(liststore) + status_combobox_items = ['online', 'chat', 'away', 'xa', 'dnd', + 'invisible', 'separator1', 'change_status_msg', 'separator2', + 'offline'] + self.status_combobox.set_model(liststore) - # default to offline - number_of_menuitem = status_combobox_items.index('offline') - self.status_combobox.set_active(number_of_menuitem) + # default to offline + number_of_menuitem = status_combobox_items.index('offline') + self.status_combobox.set_active(number_of_menuitem) - # holds index to previously selected item so if "change status message..." - # is selected we can fallback to previously selected item and not stay - # with that item selected - self.previous_status_combobox_active = number_of_menuitem + # holds index to previously selected item so if "change status message..." + # is selected we can fallback to previously selected item and not stay + # with that item selected + self.previous_status_combobox_active = number_of_menuitem - showOffline = gajim.config.get('showoffline') - showOnlyChatAndOnline = gajim.config.get('show_only_chat_and_online') + showOffline = gajim.config.get('showoffline') + showOnlyChatAndOnline = gajim.config.get('show_only_chat_and_online') - w = self.xml.get_widget('show_offline_contacts_menuitem') - w.set_active(showOffline) - if showOnlyChatAndOnline: - w.set_sensitive(False) + w = self.xml.get_widget('show_offline_contacts_menuitem') + w.set_active(showOffline) + if showOnlyChatAndOnline: + w.set_sensitive(False) - w = self.xml.get_widget('show_only_active_contacts_menuitem') - w.set_active(showOnlyChatAndOnline) - if showOffline: - w.set_sensitive(False) + w = self.xml.get_widget('show_only_active_contacts_menuitem') + w.set_active(showOnlyChatAndOnline) + if showOffline: + w.set_sensitive(False) - show_transports_group = gajim.config.get('show_transports_group') - self.xml.get_widget('show_transports_menuitem').set_active( - show_transports_group) + show_transports_group = gajim.config.get('show_transports_group') + self.xml.get_widget('show_transports_menuitem').set_active( + show_transports_group) - self.xml.get_widget('show_roster_menuitem').set_active(True) + self.xml.get_widget('show_roster_menuitem').set_active(True) - # columns + # columns - # this col has 3 cells: - # first one img, second one text, third is sec pixbuf - col = gtk.TreeViewColumn() + # this col has 3 cells: + # first one img, second one text, third is sec pixbuf + col = gtk.TreeViewColumn() - def add_avatar_renderer(): - render_pixbuf = gtk.CellRendererPixbuf() # avatar img - col.pack_start(render_pixbuf, expand=False) - col.add_attribute(render_pixbuf, 'pixbuf', - C_AVATAR_PIXBUF) - col.set_cell_data_func(render_pixbuf, - self._fill_avatar_pixbuf_renderer, None) + def add_avatar_renderer(): + render_pixbuf = gtk.CellRendererPixbuf() # avatar img + col.pack_start(render_pixbuf, expand=False) + col.add_attribute(render_pixbuf, 'pixbuf', + C_AVATAR_PIXBUF) + col.set_cell_data_func(render_pixbuf, + self._fill_avatar_pixbuf_renderer, None) - if gajim.config.get('avatar_position_in_roster') == 'left': - add_avatar_renderer() + if gajim.config.get('avatar_position_in_roster') == 'left': + add_avatar_renderer() - render_image = cell_renderer_image.CellRendererImage(0, 0) - # show img or +- - col.pack_start(render_image, expand=False) - col.add_attribute(render_image, 'image', C_IMG) - col.set_cell_data_func(render_image, self._iconCellDataFunc, None) + render_image = cell_renderer_image.CellRendererImage(0, 0) + # show img or +- + col.pack_start(render_image, expand=False) + col.add_attribute(render_image, 'image', C_IMG) + col.set_cell_data_func(render_image, self._iconCellDataFunc, None) - render_text = gtk.CellRendererText() # contact or group or account name - render_text.set_property('ellipsize', pango.ELLIPSIZE_END) - col.pack_start(render_text, expand=True) - col.add_attribute(render_text, 'markup', C_NAME) # where we hold the name - col.set_cell_data_func(render_text, self._nameCellDataFunc, None) + render_text = gtk.CellRendererText() # contact or group or account name + render_text.set_property('ellipsize', pango.ELLIPSIZE_END) + col.pack_start(render_text, expand=True) + col.add_attribute(render_text, 'markup', C_NAME) # where we hold the name + col.set_cell_data_func(render_text, self._nameCellDataFunc, None) - render_pixbuf = gtk.CellRendererPixbuf() - col.pack_start(render_pixbuf, expand=False) - col.add_attribute(render_pixbuf, 'pixbuf', C_MOOD_PIXBUF) - col.set_cell_data_func(render_pixbuf, - self._fill_pep_pixbuf_renderer, C_MOOD_PIXBUF) + render_pixbuf = gtk.CellRendererPixbuf() + col.pack_start(render_pixbuf, expand=False) + col.add_attribute(render_pixbuf, 'pixbuf', C_MOOD_PIXBUF) + col.set_cell_data_func(render_pixbuf, + self._fill_pep_pixbuf_renderer, C_MOOD_PIXBUF) - render_pixbuf = gtk.CellRendererPixbuf() - col.pack_start(render_pixbuf, expand=False) - col.add_attribute(render_pixbuf, 'pixbuf', C_ACTIVITY_PIXBUF) - col.set_cell_data_func(render_pixbuf, - self._fill_pep_pixbuf_renderer, C_ACTIVITY_PIXBUF) + render_pixbuf = gtk.CellRendererPixbuf() + col.pack_start(render_pixbuf, expand=False) + col.add_attribute(render_pixbuf, 'pixbuf', C_ACTIVITY_PIXBUF) + col.set_cell_data_func(render_pixbuf, + self._fill_pep_pixbuf_renderer, C_ACTIVITY_PIXBUF) - render_pixbuf = gtk.CellRendererPixbuf() - col.pack_start(render_pixbuf, expand=False) - col.add_attribute(render_pixbuf, 'pixbuf', C_TUNE_PIXBUF) - col.set_cell_data_func(render_pixbuf, - self._fill_pep_pixbuf_renderer, C_TUNE_PIXBUF) - - self._pep_type_to_model_column = {'mood': C_MOOD_PIXBUF, - 'activity': C_ACTIVITY_PIXBUF, - 'tune': C_TUNE_PIXBUF} + render_pixbuf = gtk.CellRendererPixbuf() + col.pack_start(render_pixbuf, expand=False) + col.add_attribute(render_pixbuf, 'pixbuf', C_TUNE_PIXBUF) + col.set_cell_data_func(render_pixbuf, + self._fill_pep_pixbuf_renderer, C_TUNE_PIXBUF) - if gajim.config.get('avatar_position_in_roster') == 'right': - add_avatar_renderer() + self._pep_type_to_model_column = {'mood': C_MOOD_PIXBUF, + 'activity': C_ACTIVITY_PIXBUF, + 'tune': C_TUNE_PIXBUF} - render_pixbuf = gtk.CellRendererPixbuf() # tls/ssl img - col.pack_start(render_pixbuf, expand=False) - col.add_attribute(render_pixbuf, 'pixbuf', C_PADLOCK_PIXBUF) - col.set_cell_data_func(render_pixbuf, - self._fill_padlock_pixbuf_renderer, None) - self.tree.append_column(col) + if gajim.config.get('avatar_position_in_roster') == 'right': + add_avatar_renderer() - # do not show gtk arrows workaround - col = gtk.TreeViewColumn() - render_pixbuf = gtk.CellRendererPixbuf() - col.pack_start(render_pixbuf, expand=False) - self.tree.append_column(col) - col.set_visible(False) - self.tree.set_expander_column(col) + render_pixbuf = gtk.CellRendererPixbuf() # tls/ssl img + col.pack_start(render_pixbuf, expand=False) + col.add_attribute(render_pixbuf, 'pixbuf', C_PADLOCK_PIXBUF) + col.set_cell_data_func(render_pixbuf, + self._fill_padlock_pixbuf_renderer, None) + self.tree.append_column(col) - # set search function - self.tree.set_search_equal_func(self._search_roster_func) + # do not show gtk arrows workaround + col = gtk.TreeViewColumn() + render_pixbuf = gtk.CellRendererPixbuf() + col.pack_start(render_pixbuf, expand=False) + self.tree.append_column(col) + col.set_visible(False) + self.tree.set_expander_column(col) - # signals - self.TARGET_TYPE_URI_LIST = 80 - TARGETS = [('MY_TREE_MODEL_ROW', - gtk.TARGET_SAME_APP | gtk.TARGET_SAME_WIDGET, 0)] - TARGETS2 = [('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0), - ('text/uri-list', 0, self.TARGET_TYPE_URI_LIST)] - self.tree.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, TARGETS, - gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE | gtk.gdk.ACTION_COPY) - self.tree.enable_model_drag_dest(TARGETS2, gtk.gdk.ACTION_DEFAULT) - self.tree.connect('drag_begin', self.drag_begin) - self.tree.connect('drag_end', self.drag_end) - self.tree.connect('drag_drop', self.drag_drop) - self.tree.connect('drag_data_get', self.drag_data_get_data) - self.tree.connect('drag_data_received', self.drag_data_received_data) - self.dragging = False - self.xml.signal_autoconnect(self) - self.combobox_callback_active = True + # set search function + self.tree.set_search_equal_func(self._search_roster_func) - self.collapsed_rows = gajim.config.get('collapsed_rows').split('\t') - self.tooltip = tooltips.RosterTooltip() - # Workaroung: For strange reasons signal is behaving like row-changed - self._toggeling_row = False - self.setup_and_draw_roster() + # signals + self.TARGET_TYPE_URI_LIST = 80 + TARGETS = [('MY_TREE_MODEL_ROW', + gtk.TARGET_SAME_APP | gtk.TARGET_SAME_WIDGET, 0)] + TARGETS2 = [('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0), + ('text/uri-list', 0, self.TARGET_TYPE_URI_LIST)] + self.tree.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, TARGETS, + gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE | gtk.gdk.ACTION_COPY) + self.tree.enable_model_drag_dest(TARGETS2, gtk.gdk.ACTION_DEFAULT) + self.tree.connect('drag_begin', self.drag_begin) + self.tree.connect('drag_end', self.drag_end) + self.tree.connect('drag_drop', self.drag_drop) + self.tree.connect('drag_data_get', self.drag_data_get_data) + self.tree.connect('drag_data_received', self.drag_data_received_data) + self.dragging = False + self.xml.signal_autoconnect(self) + self.combobox_callback_active = True - if gajim.config.get('show_roster_on_startup'): - self.window.show_all() - else: - if gajim.config.get('trayicon') != 'always': - # Without trayicon, user should see the roster! - self.window.show_all() - gajim.config.set('show_roster_on_startup', True) + self.collapsed_rows = gajim.config.get('collapsed_rows').split('\t') + self.tooltip = tooltips.RosterTooltip() + # Workaroung: For strange reasons signal is behaving like row-changed + self._toggeling_row = False + self.setup_and_draw_roster() - if len(gajim.connections) == 0: # if we have no account - gajim.interface.instances['account_creation_wizard'] = \ - config.AccountCreationWizardWindow() - if not gajim.ZEROCONF_ACC_NAME in gajim.config.get_per('accounts'): - # Create zeroconf in config file - from common.zeroconf import connection_zeroconf - connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME) + if gajim.config.get('show_roster_on_startup'): + self.window.show_all() + else: + if gajim.config.get('trayicon') != 'always': + # Without trayicon, user should see the roster! + self.window.show_all() + gajim.config.set('show_roster_on_startup', True) -# vim: se ts=3: + if len(gajim.connections) == 0: # if we have no account + gajim.interface.instances['account_creation_wizard'] = \ + config.AccountCreationWizardWindow() + if not gajim.ZEROCONF_ACC_NAME in gajim.config.get_per('accounts'): + # Create zeroconf in config file + from common.zeroconf import connection_zeroconf + connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME) diff --git a/src/search_window.py b/src/search_window.py index 3a21db543..b0ba46aa5 100644 --- a/src/search_window.py +++ b/src/search_window.py @@ -31,205 +31,203 @@ import config import dataforms_widget class SearchWindow: - def __init__(self, account, jid): - '''Create new window.''' + def __init__(self, account, jid): + '''Create new window.''' - # an account object - self.account = account - self.jid = jid + # an account object + self.account = account + self.jid = jid - # retrieving widgets from xml - self.xml = gtkgui_helpers.get_glade('search_window.glade') - self.window = self.xml.get_widget('search_window') - for name in ('label', 'progressbar', 'search_vbox', 'search_button', - 'add_contact_button', 'information_button'): - self.__dict__[name] = self.xml.get_widget(name) + # retrieving widgets from xml + self.xml = gtkgui_helpers.get_glade('search_window.glade') + self.window = self.xml.get_widget('search_window') + for name in ('label', 'progressbar', 'search_vbox', 'search_button', + 'add_contact_button', 'information_button'): + self.__dict__[name] = self.xml.get_widget(name) - # displaying the window - self.xml.signal_autoconnect(self) - self.window.show_all() - self.request_form() - self.pulse_id = gobject.timeout_add(80, self.pulse_callback) + # displaying the window + self.xml.signal_autoconnect(self) + self.window.show_all() + self.request_form() + self.pulse_id = gobject.timeout_add(80, self.pulse_callback) - self.is_form = None + self.is_form = None - # Is there a jid column in results ? if -1: no, else column number - self.jid_column = -1 + # Is there a jid column in results ? if -1: no, else column number + self.jid_column = -1 - def request_form(self): - gajim.connections[self.account].request_search_fields(self.jid) + def request_form(self): + gajim.connections[self.account].request_search_fields(self.jid) - def pulse_callback(self): - self.progressbar.pulse() - return True + def pulse_callback(self): + self.progressbar.pulse() + return True - def on_search_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: - self.window.destroy() + def on_search_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: + self.window.destroy() - def on_search_window_destroy(self, widget): - if self.pulse_id: - gobject.source_remove(self.pulse_id) - del gajim.interface.instances[self.account]['search'][self.jid] + def on_search_window_destroy(self, widget): + if self.pulse_id: + gobject.source_remove(self.pulse_id) + del gajim.interface.instances[self.account]['search'][self.jid] - def on_close_button_clicked(self, button): - self.window.destroy() + def on_close_button_clicked(self, button): + self.window.destroy() - def on_search_button_clicked(self, button): - if self.is_form: - self.data_form_widget.data_form.type = 'submit' - gajim.connections[self.account].send_search_form(self.jid, - self.data_form_widget.data_form.get_purged(), True) - else: - infos = self.data_form_widget.get_infos() - if 'instructions' in infos: - del infos['instructions'] - gajim.connections[self.account].send_search_form(self.jid, infos, - False) + def on_search_button_clicked(self, button): + if self.is_form: + self.data_form_widget.data_form.type = 'submit' + gajim.connections[self.account].send_search_form(self.jid, + self.data_form_widget.data_form.get_purged(), True) + else: + infos = self.data_form_widget.get_infos() + if 'instructions' in infos: + del infos['instructions'] + gajim.connections[self.account].send_search_form(self.jid, infos, + False) - self.search_vbox.remove(self.data_form_widget) + self.search_vbox.remove(self.data_form_widget) - self.progressbar.show() - self.label.set_text(_('Waiting for results')) - self.label.show() - self.pulse_id = gobject.timeout_add(80, self.pulse_callback) - self.search_button.hide() + self.progressbar.show() + self.label.set_text(_('Waiting for results')) + self.label.show() + self.pulse_id = gobject.timeout_add(80, self.pulse_callback) + self.search_button.hide() - def on_add_contact_button_clicked(self, widget): - (model, iter_) = self.result_treeview.get_selection().get_selected() - if not iter_: - return - jid = model[iter_][self.jid_column] - dialogs.AddNewContactWindow(self.account, jid) + def on_add_contact_button_clicked(self, widget): + (model, iter_) = self.result_treeview.get_selection().get_selected() + if not iter_: + return + jid = model[iter_][self.jid_column] + dialogs.AddNewContactWindow(self.account, jid) - def on_information_button_clicked(self, widget): - (model, iter_) = self.result_treeview.get_selection().get_selected() - if not iter_: - return - jid = model[iter_][self.jid_column] - if jid in gajim.interface.instances[self.account]['infos']: - gajim.interface.instances[self.account]['infos'][jid].window.present() - else: - contact = gajim.contacts.create_contact(jid=jid, account=self.account) - gajim.interface.instances[self.account]['infos'][jid] = \ - vcard.VcardWindow(contact, self.account) + def on_information_button_clicked(self, widget): + (model, iter_) = self.result_treeview.get_selection().get_selected() + if not iter_: + return + jid = model[iter_][self.jid_column] + if jid in gajim.interface.instances[self.account]['infos']: + gajim.interface.instances[self.account]['infos'][jid].window.present() + else: + contact = gajim.contacts.create_contact(jid=jid, account=self.account) + gajim.interface.instances[self.account]['infos'][jid] = \ + vcard.VcardWindow(contact, self.account) - def on_form_arrived(self, form, is_form): - if self.pulse_id: - gobject.source_remove(self.pulse_id) - self.progressbar.hide() - self.label.hide() + def on_form_arrived(self, form, is_form): + if self.pulse_id: + gobject.source_remove(self.pulse_id) + self.progressbar.hide() + self.label.hide() - if is_form: - self.is_form = True - self.data_form_widget = dataforms_widget.DataFormWidget() - self.dataform = dataforms.ExtendForm(node = form) - self.data_form_widget.set_sensitive(True) - try: - self.data_form_widget.data_form = self.dataform - except dataforms.Error: - self.label.set_text(_('Error in received dataform')) - self.label.show() - return - if self.data_form_widget.title: - self.window.set_title('%s - Search - Gajim' % \ - self.data_form_widget.title) - else: - self.is_form = False - self.data_form_widget = config.FakeDataForm(form) + if is_form: + self.is_form = True + self.data_form_widget = dataforms_widget.DataFormWidget() + self.dataform = dataforms.ExtendForm(node = form) + self.data_form_widget.set_sensitive(True) + try: + self.data_form_widget.data_form = self.dataform + except dataforms.Error: + self.label.set_text(_('Error in received dataform')) + self.label.show() + return + if self.data_form_widget.title: + self.window.set_title('%s - Search - Gajim' % \ + self.data_form_widget.title) + else: + self.is_form = False + self.data_form_widget = config.FakeDataForm(form) - self.data_form_widget.show_all() - self.search_vbox.pack_start(self.data_form_widget) + self.data_form_widget.show_all() + self.search_vbox.pack_start(self.data_form_widget) - def on_result_treeview_cursor_changed(self, treeview): - if self.jid_column == -1: - return - (model, iter_) = treeview.get_selection().get_selected() - if not iter_: - return - if model[iter_][self.jid_column]: - self.add_contact_button.set_sensitive(True) - self.information_button.set_sensitive(True) - else: - self.add_contact_button.set_sensitive(False) - self.information_button.set_sensitive(False) + def on_result_treeview_cursor_changed(self, treeview): + if self.jid_column == -1: + return + (model, iter_) = treeview.get_selection().get_selected() + if not iter_: + return + if model[iter_][self.jid_column]: + self.add_contact_button.set_sensitive(True) + self.information_button.set_sensitive(True) + else: + self.add_contact_button.set_sensitive(False) + self.information_button.set_sensitive(False) - def on_result_arrived(self, form, is_form): - if self.pulse_id: - gobject.source_remove(self.pulse_id) - self.progressbar.hide() - self.label.hide() + def on_result_arrived(self, form, is_form): + if self.pulse_id: + gobject.source_remove(self.pulse_id) + self.progressbar.hide() + self.label.hide() - if not is_form: - if not form: - self.label.set_text(_('No result')) - self.label.show() - return - # We suppose all items have the same fields - sw = gtk.ScrolledWindow() - sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - self.result_treeview = gtk.TreeView() - self.result_treeview.connect('cursor-changed', - self.on_result_treeview_cursor_changed) - sw.add(self.result_treeview) - # Create model - fieldtypes = [str]*len(form[0]) - model = gtk.ListStore(*fieldtypes) - # Copy data to model - for item in form: - model.append(item.values()) - # Create columns - counter = 0 - for field in form[0].keys(): - self.result_treeview.append_column( - gtk.TreeViewColumn(field, gtk.CellRendererText(), - text = counter)) - if field == 'jid': - self.jid_column = counter - counter += 1 - self.result_treeview.set_model(model) - sw.show_all() - self.search_vbox.pack_start(sw) - if self.jid_column > -1: - self.add_contact_button.show() - self.information_button.show() - return + if not is_form: + if not form: + self.label.set_text(_('No result')) + self.label.show() + return + # We suppose all items have the same fields + sw = gtk.ScrolledWindow() + sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.result_treeview = gtk.TreeView() + self.result_treeview.connect('cursor-changed', + self.on_result_treeview_cursor_changed) + sw.add(self.result_treeview) + # Create model + fieldtypes = [str]*len(form[0]) + model = gtk.ListStore(*fieldtypes) + # Copy data to model + for item in form: + model.append(item.values()) + # Create columns + counter = 0 + for field in form[0].keys(): + self.result_treeview.append_column( + gtk.TreeViewColumn(field, gtk.CellRendererText(), + text = counter)) + if field == 'jid': + self.jid_column = counter + counter += 1 + self.result_treeview.set_model(model) + sw.show_all() + self.search_vbox.pack_start(sw) + if self.jid_column > -1: + self.add_contact_button.show() + self.information_button.show() + return - self.dataform = dataforms.ExtendForm(node = form) - if len(self.dataform.items) == 0: - # No result - self.label.set_text(_('No result')) - self.label.show() - return + self.dataform = dataforms.ExtendForm(node = form) + if len(self.dataform.items) == 0: + # No result + self.label.set_text(_('No result')) + self.label.show() + return - self.data_form_widget.set_sensitive(True) - try: - self.data_form_widget.data_form = self.dataform - except dataforms.Error: - self.label.set_text(_('Error in received dataform')) - self.label.show() - return + self.data_form_widget.set_sensitive(True) + try: + self.data_form_widget.data_form = self.dataform + except dataforms.Error: + self.label.set_text(_('Error in received dataform')) + self.label.show() + return - self.result_treeview = self.data_form_widget.records_treeview - selection = self.result_treeview.get_selection() - selection.set_mode(gtk.SELECTION_SINGLE) - self.result_treeview.connect('cursor-changed', - self.on_result_treeview_cursor_changed) + self.result_treeview = self.data_form_widget.records_treeview + selection = self.result_treeview.get_selection() + selection.set_mode(gtk.SELECTION_SINGLE) + self.result_treeview.connect('cursor-changed', + self.on_result_treeview_cursor_changed) - counter = 0 - for field in self.dataform.items[0].fields: - if field.var == 'jid': - self.jid_column = counter - break - counter += 1 - self.search_vbox.pack_start(self.data_form_widget) - self.data_form_widget.show() - if self.jid_column > -1: - self.add_contact_button.show() - self.information_button.show() - if self.data_form_widget.title: - self.window.set_title('%s - Search - Gajim' % \ - self.data_form_widget.title) + counter = 0 + for field in self.dataform.items[0].fields: + if field.var == 'jid': + self.jid_column = counter + break + counter += 1 + self.search_vbox.pack_start(self.data_form_widget) + self.data_form_widget.show() + if self.jid_column > -1: + self.add_contact_button.show() + self.information_button.show() + if self.data_form_widget.title: + self.window.set_title('%s - Search - Gajim' % \ + self.data_form_widget.title) - -# vim: se ts=3: diff --git a/src/secrets.py b/src/secrets.py index 94247e606..17cb5b6e3 100644 --- a/src/secrets.py +++ b/src/secrets.py @@ -32,90 +32,88 @@ secrets_filename = gajimpaths['SECRETS_FILE'] secrets_cache = None class Secrets: - def __init__(self, filename): - self.filename = filename - self.srs = {} - self.pubkeys = {} - self.privkeys = {} + def __init__(self, filename): + self.filename = filename + self.srs = {} + self.pubkeys = {} + self.privkeys = {} - def cancel(self): - raise exceptions.Cancelled + def cancel(self): + raise exceptions.Cancelled - def save(self): - f = open(secrets_filename, 'w') - pickle.dump(self, f) - f.close() + def save(self): + f = open(secrets_filename, 'w') + pickle.dump(self, f) + f.close() - def retained_secrets(self, account, bare_jid): - try: - return self.srs[account][bare_jid] - except KeyError: - return [] + def retained_secrets(self, account, bare_jid): + try: + return self.srs[account][bare_jid] + except KeyError: + return [] - # retained secrets are stored as a tuple of the secret and whether the user - # has verified it - def save_new_srs(self, account, jid, secret, verified): - if not account in self.srs: - self.srs[account] = {} + # retained secrets are stored as a tuple of the secret and whether the user + # has verified it + def save_new_srs(self, account, jid, secret, verified): + if not account in self.srs: + self.srs[account] = {} - if not jid in self.srs[account]: - self.srs[account][jid] = [] + if not jid in self.srs[account]: + self.srs[account][jid] = [] - self.srs[account][jid].append((secret, verified)) + self.srs[account][jid].append((secret, verified)) - self.save() + self.save() - def find_srs(self, account, jid, srs): - our_secrets = self.srs[account][jid] - return [(x, y) for x, y in our_secrets if x == srs][0] + def find_srs(self, account, jid, srs): + our_secrets = self.srs[account][jid] + return [(x, y) for x, y in our_secrets if x == srs][0] - # has the user verified this retained secret? - def srs_verified(self, account, jid, srs): - return self.find_srs(account, jid, srs)[1] + # has the user verified this retained secret? + def srs_verified(self, account, jid, srs): + return self.find_srs(account, jid, srs)[1] - def replace_srs(self, account, jid, old_secret, new_secret, verified): - our_secrets = self.srs[account][jid] + def replace_srs(self, account, jid, old_secret, new_secret, verified): + our_secrets = self.srs[account][jid] - idx = our_secrets.index(self.find_srs(account, jid, old_secret)) + idx = our_secrets.index(self.find_srs(account, jid, old_secret)) - our_secrets[idx] = (new_secret, verified) + our_secrets[idx] = (new_secret, verified) - self.save() + self.save() - # the public key associated with 'account' - def my_pubkey(self, account): - try: - pk = self.privkeys[account] - except KeyError: - pk = Crypto.PublicKey.RSA.generate(384, crypto.random_bytes) + # the public key associated with 'account' + def my_pubkey(self, account): + try: + pk = self.privkeys[account] + except KeyError: + pk = Crypto.PublicKey.RSA.generate(384, crypto.random_bytes) - self.privkeys[account] = pk - self.save() + self.privkeys[account] = pk + self.save() - return pk + return pk def load_secrets(filename): - f = open(filename, 'r') + f = open(filename, 'r') - try: - secrets = pickle.load(f) - except KeyError: - f.close() - secrets = Secrets(filename) + try: + secrets = pickle.load(f) + except KeyError: + f.close() + secrets = Secrets(filename) - return secrets + return secrets def secrets(): - global secrets_cache + global secrets_cache - if secrets_cache: - return secrets_cache + if secrets_cache: + return secrets_cache - if os.path.exists(secrets_filename): - secrets_cache = load_secrets(secrets_filename) - else: - secrets_cache = Secrets(secrets_filename) + if os.path.exists(secrets_filename): + secrets_cache = load_secrets(secrets_filename) + else: + secrets_cache = Secrets(secrets_filename) - return secrets_cache - -# vim: se ts=3: + return secrets_cache diff --git a/src/session.py b/src/session.py index 6a830fe13..0154df6c5 100644 --- a/src/session.py +++ b/src/session.py @@ -38,503 +38,501 @@ import dialogs import negotiation class ChatControlSession(stanza_session.EncryptedStanzaSession): - def __init__(self, conn, jid, thread_id, type_='chat'): - stanza_session.EncryptedStanzaSession.__init__(self, conn, jid, thread_id, - type_='chat') - - self.control = None - - def detach_from_control(self): - if self.control: - self.control.set_session(None) - - def acknowledge_termination(self): - self.detach_from_control() - stanza_session.EncryptedStanzaSession.acknowledge_termination(self) - - def terminate(self, send_termination = True): - stanza_session.EncryptedStanzaSession.terminate(self, send_termination) - self.detach_from_control() - - def get_chatstate(self, msg, msgtxt): - '''extracts chatstate from a stanza''' - composing_xep = None - chatstate = None - - # chatstates - look for chatstate tags in a message if not delayed - delayed = msg.getTag('x', namespace=common.xmpp.NS_DELAY) is not None - if not delayed: - composing_xep = False - children = msg.getChildren() - for child in children: - if child.getNamespace() == 'http://jabber.org/protocol/chatstates': - chatstate = child.getName() - composing_xep = 'XEP-0085' - break - # No XEP-0085 support, fallback to XEP-0022 - if not chatstate: - chatstate_child = msg.getTag('x', namespace=common.xmpp.NS_EVENT) - if chatstate_child: - chatstate = 'active' - composing_xep = 'XEP-0022' - if not msgtxt and chatstate_child.getTag('composing'): - chatstate = 'composing' - - return (composing_xep, chatstate) - - def received(self, full_jid_with_resource, msgtxt, tim, encrypted, msg): - '''dispatch a received stanza''' - msg_type = msg.getType() - subject = msg.getSubject() - resource = gajim.get_resource_from_jid(full_jid_with_resource) - if self.resource != resource: - self.resource = resource - if self.control and self.control.resource: - self.control.change_resource(self.resource) - - if not msg_type or msg_type not in ('chat', 'groupchat', 'error'): - msg_type = 'normal' - - msg_id = None - - # XEP-0172 User Nickname - user_nick = msg.getTagData('nick') - if not user_nick: - user_nick = '' - - form_node = None - for xtag in msg.getTags('x'): - if xtag.getNamespace() == common.xmpp.NS_DATA: - form_node = xtag - break - - composing_xep, chatstate = self.get_chatstate(msg, msgtxt) - - xhtml = msg.getXHTML() - - if msg_type == 'chat': - if not msg.getTag('body') and chatstate is None: - return - - log_type = 'chat_msg_recv' - else: - log_type = 'single_msg_recv' - - if self.is_loggable() and msgtxt: - try: - msg_id = gajim.logger.write(log_type, full_jid_with_resource, - msgtxt, tim=tim, subject=subject) - except exceptions.PysqliteOperationalError, e: - self.conn.dispatch('ERROR', (_('Disk WriteError'), str(e))) - except exceptions.DatabaseMalformed: - pritext = _('Database Error') - sectext = _('The database file (%s) cannot be read. Try to repair ' - 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove ' - 'it (all history will be lost).') % common.logger.LOG_DB_PATH - self.conn.dispatch('ERROR', (pritext, sectext)) - - treat_as = gajim.config.get('treat_incoming_messages') - if treat_as: - msg_type = treat_as - - jid = gajim.get_jid_without_resource(full_jid_with_resource) - resource = gajim.get_resource_from_jid(full_jid_with_resource) - - if gajim.config.get('ignore_incoming_xhtml'): - xhtml = None - if gajim.jid_is_transport(jid): - jid = jid.replace('@', '') - - groupchat_control = gajim.interface.msg_win_mgr.get_gc_control(jid, - self.conn.name) - - if not groupchat_control and \ - jid in gajim.interface.minimized_controls[self.conn.name]: - groupchat_control = gajim.interface.minimized_controls[self.conn.name]\ - [jid] - - pm = False - if groupchat_control and groupchat_control.type_id == \ - message_control.TYPE_GC and resource: - # It's a Private message - pm = True - msg_type = 'pm' - - highest_contact = gajim.contacts.get_contact_with_highest_priority( - self.conn.name, jid) - - # does this resource have the highest priority of any available? - is_highest = not highest_contact or not highest_contact.resource or \ - resource == highest_contact.resource or highest_contact.show == \ - 'offline' - - # Handle chat states - contact = gajim.contacts.get_contact(self.conn.name, jid, resource) - if contact: - if contact.composing_xep != 'XEP-0085': # We cache xep85 support - contact.composing_xep = composing_xep - if self.control and self.control.type_id == message_control.TYPE_CHAT: - if chatstate is not None: - # other peer sent us reply, so he supports jep85 or jep22 - contact.chatstate = chatstate - if contact.our_chatstate == 'ask': # we were jep85 disco? - contact.our_chatstate = 'active' # no more - self.control.handle_incoming_chatstate() - elif contact.chatstate != 'active': - # got no valid jep85 answer, peer does not support it - contact.chatstate = False - elif chatstate == 'active': - # Brand new message, incoming. - contact.our_chatstate = chatstate - contact.chatstate = chatstate - if msg_id: # Do not overwrite an existing msg_id with None - contact.msg_id = msg_id - - # THIS MUST BE AFTER chatstates handling - # AND BEFORE playsound (else we ear sounding on chatstates!) - if not msgtxt: # empty message text - return - - if gajim.config.get_per('accounts', self.conn.name, - 'ignore_unknown_contacts') and not gajim.contacts.get_contacts( - self.conn.name, jid) and not pm: - return - - if not contact: - # contact is not in the roster, create a fake one to display - # notification - contact = gajim.contacts.create_not_in_roster_contact(jid=jid, - account=self.conn.name, resource=resource) - - advanced_notif_num = notify.get_advanced_notification('message_received', - self.conn.name, contact) - - if not pm and is_highest: - jid_of_control = jid - else: - jid_of_control = full_jid_with_resource - - if not self.control: - ctrl = gajim.interface.msg_win_mgr.get_control(jid_of_control, - self.conn.name) - if ctrl: - self.control = ctrl - self.control.set_session(self) - - # Is it a first or next message received ? - first = False - if not self.control and not gajim.events.get_events(self.conn.name, \ - jid_of_control, [msg_type]): - first = True - - if pm: - nickname = resource - if self.control: - # print if a control is open - self.control.print_conversation(msgtxt, tim=tim, xhtml=xhtml, - encrypted=encrypted) - else: - # otherwise pass it off to the control to be queued - groupchat_control.on_private_message(nickname, msgtxt, tim, - xhtml, self, msg_id=msg_id, encrypted=encrypted) - else: - self.roster_message(jid, msgtxt, tim, encrypted, msg_type, - subject, resource, msg_id, user_nick, advanced_notif_num, - xhtml=xhtml, form_node=form_node) - - nickname = gajim.get_name_from_jid(self.conn.name, jid) - - # Check and do wanted notifications - msg = msgtxt - if subject: - msg = _('Subject: %s') % subject + '\n' + msg - focused = False - - if self.control: - parent_win = self.control.parent_win - if parent_win and self.control == parent_win.get_active_control() and \ - parent_win.window.has_focus: - focused = True - - notify.notify('new_message', jid_of_control, self.conn.name, [msg_type, - first, nickname, msg, focused], advanced_notif_num) - - if gajim.interface.remote_ctrl: - gajim.interface.remote_ctrl.raise_signal('NewMessage', (self.conn.name, - [full_jid_with_resource, msgtxt, tim, encrypted, msg_type, subject, - chatstate, msg_id, composing_xep, user_nick, xhtml, form_node])) - - def roster_message(self, jid, msg, tim, encrypted=False, msg_type='', - subject=None, resource='', msg_id=None, user_nick='', - advanced_notif_num=None, xhtml=None, form_node=None): - '''display the message or show notification in the roster''' - contact = None - # if chat window will be for specific resource - resource_for_chat = resource - - fjid = jid - - # Try to catch the contact with correct resource - if resource: - fjid = jid + '/' + resource - contact = gajim.contacts.get_contact(self.conn.name, jid, resource) - - highest_contact = gajim.contacts.get_contact_with_highest_priority( - self.conn.name, jid) - if not contact: - # If there is another resource, it may be a message from an invisible - # resource - lcontact = gajim.contacts.get_contacts(self.conn.name, jid) - if (len(lcontact) > 1 or (lcontact and lcontact[0].resource and \ - lcontact[0].show != 'offline')) and jid.find('@') > 0: - contact = gajim.contacts.copy_contact(highest_contact) - contact.resource = resource - if resource: - fjid = jid + '/' + resource - contact.priority = 0 - contact.show = 'offline' - contact.status = '' - gajim.contacts.add_contact(self.conn.name, contact) - - else: - # Default to highest prio - fjid = jid - resource_for_chat = None - contact = highest_contact - - if not contact: - # contact is not in roster - contact = gajim.interface.roster.add_to_not_in_the_roster( - self.conn.name, jid, user_nick) - - if not self.control: - ctrl = gajim.interface.msg_win_mgr.get_control(fjid, self.conn.name) - if ctrl: - self.control = ctrl - self.control.set_session(self) - else: - # if no control exists and message comes from highest prio, the new - # control shouldn't have a resource - if highest_contact and contact.resource == highest_contact.resource\ - and not jid == gajim.get_jid_from_account(self.conn.name): - fjid = jid - resource_for_chat = None - - # Do we have a queue? - no_queue = len(gajim.events.get_events(self.conn.name, fjid)) == 0 - - popup = helpers.allow_popup_window(self.conn.name, advanced_notif_num) - - if msg_type == 'normal' and popup: # it's single message to be autopopuped - dialogs.SingleMessageWindow(self.conn.name, contact.jid, - action='receive', from_whom=jid, subject=subject, message=msg, - resource=resource, session=self, form_node=form_node) - return - - # We print if window is opened and it's not a single message - if self.control and msg_type != 'normal': - typ = '' - - if msg_type == 'error': - typ = 'error' - - self.control.print_conversation(msg, typ, tim=tim, encrypted=encrypted, - subject=subject, xhtml=xhtml) - - if msg_id: - gajim.logger.set_read_messages([msg_id]) - - return - - # We save it in a queue - type_ = 'chat' - event_type = 'message_received' - - if msg_type == 'normal': - type_ = 'normal' - event_type = 'single_message_received' - - show_in_roster = notify.get_show_in_roster(event_type, self.conn.name, - contact, self) - show_in_systray = notify.get_show_in_systray(event_type, self.conn.name, - contact) - - event = gajim.events.create_event(type_, (msg, subject, msg_type, tim, - encrypted, resource, msg_id, xhtml, self, form_node), - show_in_roster=show_in_roster, show_in_systray=show_in_systray) - - gajim.events.add_event(self.conn.name, fjid, event) - - if popup: - if not self.control: - self.control = gajim.interface.new_chat(contact, - self.conn.name, resource=resource_for_chat, session=self) - - if len(gajim.events.get_events(self.conn.name, fjid)): - self.control.read_queue() - else: - if no_queue: # We didn't have a queue: we change icons - gajim.interface.roster.draw_contact(jid, self.conn.name) - - gajim.interface.roster.show_title() # we show the * or [n] - # Select the big brother contact in roster, it's visible because it has - # events. - family = gajim.contacts.get_metacontacts_family(self.conn.name, jid) - if family: - nearby_family, bb_jid, bb_account = \ - gajim.contacts.get_nearby_family_and_big_brother(family, - self.conn.name) - else: - bb_jid, bb_account = jid, self.conn.name - gajim.interface.roster.select_contact(bb_jid, bb_account) - - # ---- ESessions stuff --- - - def handle_negotiation(self, form): - if form.getField('accept') and not form['accept'] in ('1', 'true'): - self.cancelled_negotiation() - return - - # encrypted session states. these are described in stanza_session.py - - try: - if form.getType() == 'form' and 'security' in form.asDict(): - security_options = [x[1] for x in form.getField('security').getOptions()] - if security_options == ['none']: - self.respond_archiving(form) - else: - # bob responds - - # we don't support 3-message negotiation as the responder - if 'dhkeys' in form.asDict(): - self.fail_bad_negotiation('3 message negotiation not supported ' - 'when responding', ('dhkeys',)) - return - - negotiated, not_acceptable, ask_user = self.verify_options_bob(form) - - if ask_user: - def accept_nondefault_options(is_checked): - self.dialog.destroy() - negotiated.update(ask_user) - self.respond_e2e_bob(form, negotiated, not_acceptable) - - def reject_nondefault_options(): - self.dialog.destroy() - for key in ask_user.keys(): - not_acceptable.append(key) - self.respond_e2e_bob(form, negotiated, not_acceptable) - - self.dialog = dialogs.YesNoDialog(_('Confirm these session ' - 'options'), - _('''The remote client wants to negotiate an session with these features: - - %s - - Are these options acceptable?''') % (negotiation.describe_features( - ask_user)), - on_response_yes=accept_nondefault_options, - on_response_no=reject_nondefault_options) - else: - self.respond_e2e_bob(form, negotiated, not_acceptable) - - return - - elif self.status == 'requested-archiving' and form.getType() == \ - 'submit': - try: - self.archiving_accepted(form) - except exceptions.NegotiationError, details: - self.fail_bad_negotiation(details) - - return - - # alice accepts - elif self.status == 'requested-e2e' and form.getType() == 'submit': - negotiated, not_acceptable, ask_user = self.verify_options_alice( - form) - - if ask_user: - def accept_nondefault_options(is_checked): - dialog.destroy() - - negotiated.update(ask_user) - - try: - self.accept_e2e_alice(form, negotiated) - except exceptions.NegotiationError, details: - self.fail_bad_negotiation(details) - - def reject_nondefault_options(): - self.reject_negotiation() - dialog.destroy() - - dialog = dialogs.YesNoDialog(_('Confirm these session options'), - _('The remote client selected these options:\n\n%s\n\n' - 'Continue with the session?') % ( - negotiation.describe_features(ask_user)), - on_response_yes = accept_nondefault_options, - on_response_no = reject_nondefault_options) - else: - try: - self.accept_e2e_alice(form, negotiated) - except exceptions.NegotiationError, details: - self.fail_bad_negotiation(details) - - return - elif self.status == 'responded-archiving' and form.getType() == \ - 'result': - try: - self.we_accept_archiving(form) - except exceptions.NegotiationError, details: - self.fail_bad_negotiation(details) - - return - elif self.status == 'responded-e2e' and form.getType() == 'result': - try: - self.accept_e2e_bob(form) - except exceptions.NegotiationError, details: - self.fail_bad_negotiation(details) - - return - elif self.status == 'identified-alice' and form.getType() == 'result': - try: - self.final_steps_alice(form) - except exceptions.NegotiationError, details: - self.fail_bad_negotiation(details) - - return - except exceptions.Cancelled: - # user cancelled the negotiation - - self.reject_negotiation() - - return - - if form.getField('terminate') and\ - form.getField('terminate').getValue() in ('1', 'true'): - self.acknowledge_termination() - - self.conn.delete_session(str(self.jid), self.thread_id) - - return - - # non-esession negotiation. this isn't very useful, but i'm keeping it - # around to test my test suite. - if form.getType() == 'form': - if not self.control: - jid, resource = gajim.get_room_and_nick_from_fjid(self.jid) - - account = self.conn.name - contact = gajim.contacts.get_contact(account, self.jid, resource) - - if not contact: - contact = gajim.contacts.create_contact(jid=jid, account=account, - resource=resource, show=self.conn.get_status()) - - gajim.interface.new_chat(contact, account, resource=resource, - session=self) - - negotiation.FeatureNegotiationWindow(account, self.jid, self, form) - -# vim: se ts=3: + def __init__(self, conn, jid, thread_id, type_='chat'): + stanza_session.EncryptedStanzaSession.__init__(self, conn, jid, thread_id, + type_='chat') + + self.control = None + + def detach_from_control(self): + if self.control: + self.control.set_session(None) + + def acknowledge_termination(self): + self.detach_from_control() + stanza_session.EncryptedStanzaSession.acknowledge_termination(self) + + def terminate(self, send_termination = True): + stanza_session.EncryptedStanzaSession.terminate(self, send_termination) + self.detach_from_control() + + def get_chatstate(self, msg, msgtxt): + '''extracts chatstate from a stanza''' + composing_xep = None + chatstate = None + + # chatstates - look for chatstate tags in a message if not delayed + delayed = msg.getTag('x', namespace=common.xmpp.NS_DELAY) is not None + if not delayed: + composing_xep = False + children = msg.getChildren() + for child in children: + if child.getNamespace() == 'http://jabber.org/protocol/chatstates': + chatstate = child.getName() + composing_xep = 'XEP-0085' + break + # No XEP-0085 support, fallback to XEP-0022 + if not chatstate: + chatstate_child = msg.getTag('x', namespace=common.xmpp.NS_EVENT) + if chatstate_child: + chatstate = 'active' + composing_xep = 'XEP-0022' + if not msgtxt and chatstate_child.getTag('composing'): + chatstate = 'composing' + + return (composing_xep, chatstate) + + def received(self, full_jid_with_resource, msgtxt, tim, encrypted, msg): + '''dispatch a received stanza''' + msg_type = msg.getType() + subject = msg.getSubject() + resource = gajim.get_resource_from_jid(full_jid_with_resource) + if self.resource != resource: + self.resource = resource + if self.control and self.control.resource: + self.control.change_resource(self.resource) + + if not msg_type or msg_type not in ('chat', 'groupchat', 'error'): + msg_type = 'normal' + + msg_id = None + + # XEP-0172 User Nickname + user_nick = msg.getTagData('nick') + if not user_nick: + user_nick = '' + + form_node = None + for xtag in msg.getTags('x'): + if xtag.getNamespace() == common.xmpp.NS_DATA: + form_node = xtag + break + + composing_xep, chatstate = self.get_chatstate(msg, msgtxt) + + xhtml = msg.getXHTML() + + if msg_type == 'chat': + if not msg.getTag('body') and chatstate is None: + return + + log_type = 'chat_msg_recv' + else: + log_type = 'single_msg_recv' + + if self.is_loggable() and msgtxt: + try: + msg_id = gajim.logger.write(log_type, full_jid_with_resource, + msgtxt, tim=tim, subject=subject) + except exceptions.PysqliteOperationalError, e: + self.conn.dispatch('ERROR', (_('Disk WriteError'), str(e))) + except exceptions.DatabaseMalformed: + pritext = _('Database Error') + sectext = _('The database file (%s) cannot be read. Try to repair ' + 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove ' + 'it (all history will be lost).') % common.logger.LOG_DB_PATH + self.conn.dispatch('ERROR', (pritext, sectext)) + + treat_as = gajim.config.get('treat_incoming_messages') + if treat_as: + msg_type = treat_as + + jid = gajim.get_jid_without_resource(full_jid_with_resource) + resource = gajim.get_resource_from_jid(full_jid_with_resource) + + if gajim.config.get('ignore_incoming_xhtml'): + xhtml = None + if gajim.jid_is_transport(jid): + jid = jid.replace('@', '') + + groupchat_control = gajim.interface.msg_win_mgr.get_gc_control(jid, + self.conn.name) + + if not groupchat_control and \ + jid in gajim.interface.minimized_controls[self.conn.name]: + groupchat_control = gajim.interface.minimized_controls[self.conn.name]\ + [jid] + + pm = False + if groupchat_control and groupchat_control.type_id == \ + message_control.TYPE_GC and resource: + # It's a Private message + pm = True + msg_type = 'pm' + + highest_contact = gajim.contacts.get_contact_with_highest_priority( + self.conn.name, jid) + + # does this resource have the highest priority of any available? + is_highest = not highest_contact or not highest_contact.resource or \ + resource == highest_contact.resource or highest_contact.show == \ + 'offline' + + # Handle chat states + contact = gajim.contacts.get_contact(self.conn.name, jid, resource) + if contact: + if contact.composing_xep != 'XEP-0085': # We cache xep85 support + contact.composing_xep = composing_xep + if self.control and self.control.type_id == message_control.TYPE_CHAT: + if chatstate is not None: + # other peer sent us reply, so he supports jep85 or jep22 + contact.chatstate = chatstate + if contact.our_chatstate == 'ask': # we were jep85 disco? + contact.our_chatstate = 'active' # no more + self.control.handle_incoming_chatstate() + elif contact.chatstate != 'active': + # got no valid jep85 answer, peer does not support it + contact.chatstate = False + elif chatstate == 'active': + # Brand new message, incoming. + contact.our_chatstate = chatstate + contact.chatstate = chatstate + if msg_id: # Do not overwrite an existing msg_id with None + contact.msg_id = msg_id + + # THIS MUST BE AFTER chatstates handling + # AND BEFORE playsound (else we ear sounding on chatstates!) + if not msgtxt: # empty message text + return + + if gajim.config.get_per('accounts', self.conn.name, + 'ignore_unknown_contacts') and not gajim.contacts.get_contacts( + self.conn.name, jid) and not pm: + return + + if not contact: + # contact is not in the roster, create a fake one to display + # notification + contact = gajim.contacts.create_not_in_roster_contact(jid=jid, + account=self.conn.name, resource=resource) + + advanced_notif_num = notify.get_advanced_notification('message_received', + self.conn.name, contact) + + if not pm and is_highest: + jid_of_control = jid + else: + jid_of_control = full_jid_with_resource + + if not self.control: + ctrl = gajim.interface.msg_win_mgr.get_control(jid_of_control, + self.conn.name) + if ctrl: + self.control = ctrl + self.control.set_session(self) + + # Is it a first or next message received ? + first = False + if not self.control and not gajim.events.get_events(self.conn.name, \ + jid_of_control, [msg_type]): + first = True + + if pm: + nickname = resource + if self.control: + # print if a control is open + self.control.print_conversation(msgtxt, tim=tim, xhtml=xhtml, + encrypted=encrypted) + else: + # otherwise pass it off to the control to be queued + groupchat_control.on_private_message(nickname, msgtxt, tim, + xhtml, self, msg_id=msg_id, encrypted=encrypted) + else: + self.roster_message(jid, msgtxt, tim, encrypted, msg_type, + subject, resource, msg_id, user_nick, advanced_notif_num, + xhtml=xhtml, form_node=form_node) + + nickname = gajim.get_name_from_jid(self.conn.name, jid) + + # Check and do wanted notifications + msg = msgtxt + if subject: + msg = _('Subject: %s') % subject + '\n' + msg + focused = False + + if self.control: + parent_win = self.control.parent_win + if parent_win and self.control == parent_win.get_active_control() and \ + parent_win.window.has_focus: + focused = True + + notify.notify('new_message', jid_of_control, self.conn.name, [msg_type, + first, nickname, msg, focused], advanced_notif_num) + + if gajim.interface.remote_ctrl: + gajim.interface.remote_ctrl.raise_signal('NewMessage', (self.conn.name, + [full_jid_with_resource, msgtxt, tim, encrypted, msg_type, subject, + chatstate, msg_id, composing_xep, user_nick, xhtml, form_node])) + + def roster_message(self, jid, msg, tim, encrypted=False, msg_type='', + subject=None, resource='', msg_id=None, user_nick='', + advanced_notif_num=None, xhtml=None, form_node=None): + '''display the message or show notification in the roster''' + contact = None + # if chat window will be for specific resource + resource_for_chat = resource + + fjid = jid + + # Try to catch the contact with correct resource + if resource: + fjid = jid + '/' + resource + contact = gajim.contacts.get_contact(self.conn.name, jid, resource) + + highest_contact = gajim.contacts.get_contact_with_highest_priority( + self.conn.name, jid) + if not contact: + # If there is another resource, it may be a message from an invisible + # resource + lcontact = gajim.contacts.get_contacts(self.conn.name, jid) + if (len(lcontact) > 1 or (lcontact and lcontact[0].resource and \ + lcontact[0].show != 'offline')) and jid.find('@') > 0: + contact = gajim.contacts.copy_contact(highest_contact) + contact.resource = resource + if resource: + fjid = jid + '/' + resource + contact.priority = 0 + contact.show = 'offline' + contact.status = '' + gajim.contacts.add_contact(self.conn.name, contact) + + else: + # Default to highest prio + fjid = jid + resource_for_chat = None + contact = highest_contact + + if not contact: + # contact is not in roster + contact = gajim.interface.roster.add_to_not_in_the_roster( + self.conn.name, jid, user_nick) + + if not self.control: + ctrl = gajim.interface.msg_win_mgr.get_control(fjid, self.conn.name) + if ctrl: + self.control = ctrl + self.control.set_session(self) + else: + # if no control exists and message comes from highest prio, the new + # control shouldn't have a resource + if highest_contact and contact.resource == highest_contact.resource\ + and not jid == gajim.get_jid_from_account(self.conn.name): + fjid = jid + resource_for_chat = None + + # Do we have a queue? + no_queue = len(gajim.events.get_events(self.conn.name, fjid)) == 0 + + popup = helpers.allow_popup_window(self.conn.name, advanced_notif_num) + + if msg_type == 'normal' and popup: # it's single message to be autopopuped + dialogs.SingleMessageWindow(self.conn.name, contact.jid, + action='receive', from_whom=jid, subject=subject, message=msg, + resource=resource, session=self, form_node=form_node) + return + + # We print if window is opened and it's not a single message + if self.control and msg_type != 'normal': + typ = '' + + if msg_type == 'error': + typ = 'error' + + self.control.print_conversation(msg, typ, tim=tim, encrypted=encrypted, + subject=subject, xhtml=xhtml) + + if msg_id: + gajim.logger.set_read_messages([msg_id]) + + return + + # We save it in a queue + type_ = 'chat' + event_type = 'message_received' + + if msg_type == 'normal': + type_ = 'normal' + event_type = 'single_message_received' + + show_in_roster = notify.get_show_in_roster(event_type, self.conn.name, + contact, self) + show_in_systray = notify.get_show_in_systray(event_type, self.conn.name, + contact) + + event = gajim.events.create_event(type_, (msg, subject, msg_type, tim, + encrypted, resource, msg_id, xhtml, self, form_node), + show_in_roster=show_in_roster, show_in_systray=show_in_systray) + + gajim.events.add_event(self.conn.name, fjid, event) + + if popup: + if not self.control: + self.control = gajim.interface.new_chat(contact, + self.conn.name, resource=resource_for_chat, session=self) + + if len(gajim.events.get_events(self.conn.name, fjid)): + self.control.read_queue() + else: + if no_queue: # We didn't have a queue: we change icons + gajim.interface.roster.draw_contact(jid, self.conn.name) + + gajim.interface.roster.show_title() # we show the * or [n] + # Select the big brother contact in roster, it's visible because it has + # events. + family = gajim.contacts.get_metacontacts_family(self.conn.name, jid) + if family: + nearby_family, bb_jid, bb_account = \ + gajim.contacts.get_nearby_family_and_big_brother(family, + self.conn.name) + else: + bb_jid, bb_account = jid, self.conn.name + gajim.interface.roster.select_contact(bb_jid, bb_account) + + # ---- ESessions stuff --- + + def handle_negotiation(self, form): + if form.getField('accept') and not form['accept'] in ('1', 'true'): + self.cancelled_negotiation() + return + + # encrypted session states. these are described in stanza_session.py + + try: + if form.getType() == 'form' and 'security' in form.asDict(): + security_options = [x[1] for x in form.getField('security').getOptions()] + if security_options == ['none']: + self.respond_archiving(form) + else: + # bob responds + + # we don't support 3-message negotiation as the responder + if 'dhkeys' in form.asDict(): + self.fail_bad_negotiation('3 message negotiation not supported ' + 'when responding', ('dhkeys',)) + return + + negotiated, not_acceptable, ask_user = self.verify_options_bob(form) + + if ask_user: + def accept_nondefault_options(is_checked): + self.dialog.destroy() + negotiated.update(ask_user) + self.respond_e2e_bob(form, negotiated, not_acceptable) + + def reject_nondefault_options(): + self.dialog.destroy() + for key in ask_user.keys(): + not_acceptable.append(key) + self.respond_e2e_bob(form, negotiated, not_acceptable) + + self.dialog = dialogs.YesNoDialog(_('Confirm these session ' + 'options'), + _('''The remote client wants to negotiate an session with these features: + +%s + +Are these options acceptable?''') % (negotiation.describe_features( + ask_user)), + on_response_yes=accept_nondefault_options, + on_response_no=reject_nondefault_options) + else: + self.respond_e2e_bob(form, negotiated, not_acceptable) + + return + + elif self.status == 'requested-archiving' and form.getType() == \ + 'submit': + try: + self.archiving_accepted(form) + except exceptions.NegotiationError, details: + self.fail_bad_negotiation(details) + + return + + # alice accepts + elif self.status == 'requested-e2e' and form.getType() == 'submit': + negotiated, not_acceptable, ask_user = self.verify_options_alice( + form) + + if ask_user: + def accept_nondefault_options(is_checked): + dialog.destroy() + + negotiated.update(ask_user) + + try: + self.accept_e2e_alice(form, negotiated) + except exceptions.NegotiationError, details: + self.fail_bad_negotiation(details) + + def reject_nondefault_options(): + self.reject_negotiation() + dialog.destroy() + + dialog = dialogs.YesNoDialog(_('Confirm these session options'), + _('The remote client selected these options:\n\n%s\n\n' + 'Continue with the session?') % ( + negotiation.describe_features(ask_user)), + on_response_yes = accept_nondefault_options, + on_response_no = reject_nondefault_options) + else: + try: + self.accept_e2e_alice(form, negotiated) + except exceptions.NegotiationError, details: + self.fail_bad_negotiation(details) + + return + elif self.status == 'responded-archiving' and form.getType() == \ + 'result': + try: + self.we_accept_archiving(form) + except exceptions.NegotiationError, details: + self.fail_bad_negotiation(details) + + return + elif self.status == 'responded-e2e' and form.getType() == 'result': + try: + self.accept_e2e_bob(form) + except exceptions.NegotiationError, details: + self.fail_bad_negotiation(details) + + return + elif self.status == 'identified-alice' and form.getType() == 'result': + try: + self.final_steps_alice(form) + except exceptions.NegotiationError, details: + self.fail_bad_negotiation(details) + + return + except exceptions.Cancelled: + # user cancelled the negotiation + + self.reject_negotiation() + + return + + if form.getField('terminate') and\ + form.getField('terminate').getValue() in ('1', 'true'): + self.acknowledge_termination() + + self.conn.delete_session(str(self.jid), self.thread_id) + + return + + # non-esession negotiation. this isn't very useful, but i'm keeping it + # around to test my test suite. + if form.getType() == 'form': + if not self.control: + jid, resource = gajim.get_room_and_nick_from_fjid(self.jid) + + account = self.conn.name + contact = gajim.contacts.get_contact(account, self.jid, resource) + + if not contact: + contact = gajim.contacts.create_contact(jid=jid, account=account, + resource=resource, show=self.conn.get_status()) + + gajim.interface.new_chat(contact, account, resource=resource, + session=self) + + negotiation.FeatureNegotiationWindow(account, self.jid, self, form) diff --git a/src/statusicon.py b/src/statusicon.py index e28b67cfd..dd6de19db 100644 --- a/src/statusicon.py +++ b/src/statusicon.py @@ -39,381 +39,379 @@ from common import helpers from common import pep class StatusIcon: - '''Class for the notification area icon''' - def __init__(self): - self.single_message_handler_id = None - self.new_chat_handler_id = None - # click somewhere else does not popdown menu. workaround this. - self.added_hide_menuitem = False - self.status = 'offline' - self.xml = gtkgui_helpers.get_glade('systray_context_menu.glade') - self.systray_context_menu = self.xml.get_widget('systray_context_menu') - self.xml.signal_autoconnect(self) - self.popup_menus = [] - self.status_icon = None - self.tooltip = tooltips.NotificationAreaTooltip() + '''Class for the notification area icon''' + def __init__(self): + self.single_message_handler_id = None + self.new_chat_handler_id = None + # click somewhere else does not popdown menu. workaround this. + self.added_hide_menuitem = False + self.status = 'offline' + self.xml = gtkgui_helpers.get_glade('systray_context_menu.glade') + self.systray_context_menu = self.xml.get_widget('systray_context_menu') + self.xml.signal_autoconnect(self) + self.popup_menus = [] + self.status_icon = None + self.tooltip = tooltips.NotificationAreaTooltip() - def subscribe_events(self): - '''Register listeners to the events class''' - gajim.events.event_added_subscribe(self.on_event_added) - gajim.events.event_removed_subscribe(self.on_event_removed) + def subscribe_events(self): + '''Register listeners to the events class''' + gajim.events.event_added_subscribe(self.on_event_added) + gajim.events.event_removed_subscribe(self.on_event_removed) - def unsubscribe_events(self): - '''Unregister listeners to the events class''' - gajim.events.event_added_unsubscribe(self.on_event_added) - gajim.events.event_removed_unsubscribe(self.on_event_removed) + def unsubscribe_events(self): + '''Unregister listeners to the events class''' + gajim.events.event_added_unsubscribe(self.on_event_added) + gajim.events.event_removed_unsubscribe(self.on_event_removed) - def on_event_added(self, event): - '''Called when an event is added to the event list''' - if event.show_in_systray: - self.set_img() + def on_event_added(self, event): + '''Called when an event is added to the event list''' + if event.show_in_systray: + self.set_img() - def on_event_removed(self, event_list): - '''Called when one or more events are removed from the event list''' - self.set_img() + def on_event_removed(self, event_list): + '''Called when one or more events are removed from the event list''' + self.set_img() - def show_icon(self): - if not self.status_icon: - self.status_icon = gtk.StatusIcon() - self.status_icon.set_property('has-tooltip', True) - self.status_icon.connect('activate', self.on_status_icon_left_clicked) - self.status_icon.connect('popup-menu', - self.on_status_icon_right_clicked) - self.status_icon.connect('query-tooltip', - self.on_status_icon_query_tooltip) + def show_icon(self): + if not self.status_icon: + self.status_icon = gtk.StatusIcon() + self.status_icon.set_property('has-tooltip', True) + self.status_icon.connect('activate', self.on_status_icon_left_clicked) + self.status_icon.connect('popup-menu', + self.on_status_icon_right_clicked) + self.status_icon.connect('query-tooltip', + self.on_status_icon_query_tooltip) - self.set_img() - self.status_icon.set_visible(True) - self.subscribe_events() + self.set_img() + self.status_icon.set_visible(True) + self.subscribe_events() - def on_status_icon_right_clicked(self, widget, event_button, event_time): - self.make_menu(event_button, event_time) + def on_status_icon_right_clicked(self, widget, event_button, event_time): + self.make_menu(event_button, event_time) - def on_status_icon_query_tooltip(self, widget, x, y, keyboard_mode, tooltip): - self.tooltip.populate() - tooltip.set_custom(self.tooltip.hbox) - return True + def on_status_icon_query_tooltip(self, widget, x, y, keyboard_mode, tooltip): + self.tooltip.populate() + tooltip.set_custom(self.tooltip.hbox) + return True - def hide_icon(self): - self.status_icon.set_visible(False) - self.unsubscribe_events() + def hide_icon(self): + self.status_icon.set_visible(False) + self.unsubscribe_events() - def on_status_icon_left_clicked(self, widget): - self.on_left_click() + def on_status_icon_left_clicked(self, widget): + self.on_left_click() - def set_img(self): - '''apart from image, we also update tooltip text here''' - if not gajim.interface.systray_enabled: - return - if gajim.events.get_nb_systray_events(): - self.status_icon.set_blinking(True) - else: - self.status_icon.set_blinking(False) + def set_img(self): + '''apart from image, we also update tooltip text here''' + if not gajim.interface.systray_enabled: + return + if gajim.events.get_nb_systray_events(): + self.status_icon.set_blinking(True) + else: + self.status_icon.set_blinking(False) - # FIXME: do not always use 16x16 (ask actually used size and use that) - image = gajim.interface.jabber_state_images['16'][self.status] - if image.get_storage_type() == gtk.IMAGE_PIXBUF: - self.status_icon.set_from_pixbuf(image.get_pixbuf()) - # FIXME: oops they forgot to support GIF animation? - # or they were lazy to get it to work under Windows! WTF! - elif image.get_storage_type() == gtk.IMAGE_ANIMATION: - self.status_icon.set_from_pixbuf( - image.get_animation().get_static_image()) - # self.img_tray.set_from_animation(image.get_animation()) + # FIXME: do not always use 16x16 (ask actually used size and use that) + image = gajim.interface.jabber_state_images['16'][self.status] + if image.get_storage_type() == gtk.IMAGE_PIXBUF: + self.status_icon.set_from_pixbuf(image.get_pixbuf()) + # FIXME: oops they forgot to support GIF animation? + # or they were lazy to get it to work under Windows! WTF! + elif image.get_storage_type() == gtk.IMAGE_ANIMATION: + self.status_icon.set_from_pixbuf( + image.get_animation().get_static_image()) + # self.img_tray.set_from_animation(image.get_animation()) - def change_status(self, global_status): - ''' set tray image to 'global_status' ''' - # change image and status, only if it is different - if global_status is not None and self.status != global_status: - self.status = global_status - self.set_img() + def change_status(self, global_status): + ''' set tray image to 'global_status' ''' + # change image and status, only if it is different + if global_status is not None and self.status != global_status: + self.status = global_status + self.set_img() - def start_chat(self, widget, account, jid): - contact = gajim.contacts.get_first_contact_from_jid(account, jid) - if gajim.interface.msg_win_mgr.has_window(jid, account): - gajim.interface.msg_win_mgr.get_window(jid, account).set_active_tab( - jid, account) - elif contact: - gajim.interface.new_chat(contact, account) - gajim.interface.msg_win_mgr.get_window(jid, account).set_active_tab( - jid, account) + def start_chat(self, widget, account, jid): + contact = gajim.contacts.get_first_contact_from_jid(account, jid) + if gajim.interface.msg_win_mgr.has_window(jid, account): + gajim.interface.msg_win_mgr.get_window(jid, account).set_active_tab( + jid, account) + elif contact: + gajim.interface.new_chat(contact, account) + gajim.interface.msg_win_mgr.get_window(jid, account).set_active_tab( + jid, account) - def on_single_message_menuitem_activate(self, widget, account): - dialogs.SingleMessageWindow(account, action='send') + def on_single_message_menuitem_activate(self, widget, account): + dialogs.SingleMessageWindow(account, action='send') - def on_new_chat(self, widget, account): - dialogs.NewChatDialog(account) + def on_new_chat(self, widget, account): + dialogs.NewChatDialog(account) - def make_menu(self, event_button, event_time): - '''create chat with and new message (sub) menus/menuitems''' - for m in self.popup_menus: - m.destroy() + def make_menu(self, event_button, event_time): + '''create chat with and new message (sub) menus/menuitems''' + for m in self.popup_menus: + m.destroy() - chat_with_menuitem = self.xml.get_widget('chat_with_menuitem') - single_message_menuitem = self.xml.get_widget( - 'single_message_menuitem') - status_menuitem = self.xml.get_widget('status_menu') - join_gc_menuitem = self.xml.get_widget('join_gc_menuitem') - sounds_mute_menuitem = self.xml.get_widget('sounds_mute_menuitem') + chat_with_menuitem = self.xml.get_widget('chat_with_menuitem') + single_message_menuitem = self.xml.get_widget( + 'single_message_menuitem') + status_menuitem = self.xml.get_widget('status_menu') + join_gc_menuitem = self.xml.get_widget('join_gc_menuitem') + sounds_mute_menuitem = self.xml.get_widget('sounds_mute_menuitem') - if self.single_message_handler_id: - single_message_menuitem.handler_disconnect( - self.single_message_handler_id) - self.single_message_handler_id = None - if self.new_chat_handler_id: - chat_with_menuitem.disconnect(self.new_chat_handler_id) - self.new_chat_handler_id = None + if self.single_message_handler_id: + single_message_menuitem.handler_disconnect( + self.single_message_handler_id) + self.single_message_handler_id = None + if self.new_chat_handler_id: + chat_with_menuitem.disconnect(self.new_chat_handler_id) + self.new_chat_handler_id = None - sub_menu = gtk.Menu() - self.popup_menus.append(sub_menu) - status_menuitem.set_submenu(sub_menu) + sub_menu = gtk.Menu() + self.popup_menus.append(sub_menu) + status_menuitem.set_submenu(sub_menu) - gc_sub_menu = gtk.Menu() # gc is always a submenu - join_gc_menuitem.set_submenu(gc_sub_menu) + gc_sub_menu = gtk.Menu() # gc is always a submenu + join_gc_menuitem.set_submenu(gc_sub_menu) - # We need our own set of status icons, let's make 'em! - iconset = gajim.config.get('iconset') - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - state_images = gtkgui_helpers.load_iconset(path) + # We need our own set of status icons, let's make 'em! + iconset = gajim.config.get('iconset') + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + state_images = gtkgui_helpers.load_iconset(path) - if 'muc_active' in state_images: - join_gc_menuitem.set_image(state_images['muc_active']) + if 'muc_active' in state_images: + join_gc_menuitem.set_image(state_images['muc_active']) - for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'): - uf_show = helpers.get_uf_show(show, use_mnemonic = True) - item = gtk.ImageMenuItem(uf_show) - item.set_image(state_images[show]) - sub_menu.append(item) - item.connect('activate', self.on_show_menuitem_activate, show) + for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'): + uf_show = helpers.get_uf_show(show, use_mnemonic = True) + item = gtk.ImageMenuItem(uf_show) + item.set_image(state_images[show]) + sub_menu.append(item) + item.connect('activate', self.on_show_menuitem_activate, show) - item = gtk.SeparatorMenuItem() - sub_menu.append(item) + item = gtk.SeparatorMenuItem() + sub_menu.append(item) - item = gtk.ImageMenuItem(_('_Change Status Message...')) - path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png') - img = gtk.Image() - img.set_from_file(path) - item.set_image(img) - sub_menu.append(item) - item.connect('activate', self.on_change_status_message_activate) + item = gtk.ImageMenuItem(_('_Change Status Message...')) + path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png') + img = gtk.Image() + img.set_from_file(path) + item.set_image(img) + sub_menu.append(item) + item.connect('activate', self.on_change_status_message_activate) - connected_accounts = gajim.get_number_of_connected_accounts() - if connected_accounts < 1: - item.set_sensitive(False) + connected_accounts = gajim.get_number_of_connected_accounts() + if connected_accounts < 1: + item.set_sensitive(False) - connected_accounts_with_private_storage = 0 + connected_accounts_with_private_storage = 0 - item = gtk.SeparatorMenuItem() - sub_menu.append(item) + item = gtk.SeparatorMenuItem() + sub_menu.append(item) - uf_show = helpers.get_uf_show('offline', use_mnemonic = True) - item = gtk.ImageMenuItem(uf_show) - item.set_image(state_images['offline']) - sub_menu.append(item) - item.connect('activate', self.on_show_menuitem_activate, 'offline') + uf_show = helpers.get_uf_show('offline', use_mnemonic = True) + item = gtk.ImageMenuItem(uf_show) + item.set_image(state_images['offline']) + sub_menu.append(item) + item.connect('activate', self.on_show_menuitem_activate, 'offline') - iskey = connected_accounts > 0 and not (connected_accounts == 1 and - gajim.connections[gajim.connections.keys()[0]].is_zeroconf) - chat_with_menuitem.set_sensitive(iskey) - single_message_menuitem.set_sensitive(iskey) - join_gc_menuitem.set_sensitive(iskey) + iskey = connected_accounts > 0 and not (connected_accounts == 1 and + gajim.connections[gajim.connections.keys()[0]].is_zeroconf) + chat_with_menuitem.set_sensitive(iskey) + single_message_menuitem.set_sensitive(iskey) + join_gc_menuitem.set_sensitive(iskey) - accounts_list = sorted(gajim.contacts.get_accounts()) - # items that get shown whether an account is zeroconf or not - if connected_accounts > 1: # 2 or more connections? make submenus - account_menu_for_chat_with = gtk.Menu() - chat_with_menuitem.set_submenu(account_menu_for_chat_with) - self.popup_menus.append(account_menu_for_chat_with) + accounts_list = sorted(gajim.contacts.get_accounts()) + # items that get shown whether an account is zeroconf or not + if connected_accounts > 1: # 2 or more connections? make submenus + account_menu_for_chat_with = gtk.Menu() + chat_with_menuitem.set_submenu(account_menu_for_chat_with) + self.popup_menus.append(account_menu_for_chat_with) - for account in accounts_list: - if gajim.account_is_connected(account): - # for chat_with - item = gtk.MenuItem(_('using account %s') % account) - account_menu_for_chat_with.append(item) - item.connect('activate', self.on_new_chat, account) + for account in accounts_list: + if gajim.account_is_connected(account): + # for chat_with + item = gtk.MenuItem(_('using account %s') % account) + account_menu_for_chat_with.append(item) + item.connect('activate', self.on_new_chat, account) - elif connected_accounts == 1: # one account - # one account connected, no need to show 'as jid' - for account in gajim.connections: - if gajim.connections[account].connected > 1: - # for start chat - self.new_chat_handler_id = chat_with_menuitem.connect( - 'activate', self.on_new_chat, account) - break # No other connected account + elif connected_accounts == 1: # one account + # one account connected, no need to show 'as jid' + for account in gajim.connections: + if gajim.connections[account].connected > 1: + # for start chat + self.new_chat_handler_id = chat_with_menuitem.connect( + 'activate', self.on_new_chat, account) + break # No other connected account - # menu items that don't apply to zeroconf connections - if connected_accounts == 1 or (connected_accounts == 2 and \ - gajim.zeroconf_is_connected()): - # only one 'real' (non-zeroconf) account is connected, don't need - # submenus - for account in gajim.connections: - if gajim.account_is_connected(account) and \ - not gajim.config.get_per('accounts', account, 'is_zeroconf'): - if gajim.connections[account].private_storage_supported: - connected_accounts_with_private_storage += 1 + # menu items that don't apply to zeroconf connections + if connected_accounts == 1 or (connected_accounts == 2 and \ + gajim.zeroconf_is_connected()): + # only one 'real' (non-zeroconf) account is connected, don't need + # submenus + for account in gajim.connections: + if gajim.account_is_connected(account) and \ + not gajim.config.get_per('accounts', account, 'is_zeroconf'): + if gajim.connections[account].private_storage_supported: + connected_accounts_with_private_storage += 1 - # for single message - single_message_menuitem.remove_submenu() - self.single_message_handler_id = single_message_menuitem.\ - connect('activate', - self.on_single_message_menuitem_activate, account) - # join gc - gajim.interface.roster.add_bookmarks_list(gc_sub_menu, - account) - break # No other account connected - else: - # 2 or more 'real' accounts are connected, make submenus - account_menu_for_single_message = gtk.Menu() - single_message_menuitem.set_submenu( - account_menu_for_single_message) - self.popup_menus.append(account_menu_for_single_message) + # for single message + single_message_menuitem.remove_submenu() + self.single_message_handler_id = single_message_menuitem.\ + connect('activate', + self.on_single_message_menuitem_activate, account) + # join gc + gajim.interface.roster.add_bookmarks_list(gc_sub_menu, + account) + break # No other account connected + else: + # 2 or more 'real' accounts are connected, make submenus + account_menu_for_single_message = gtk.Menu() + single_message_menuitem.set_submenu( + account_menu_for_single_message) + self.popup_menus.append(account_menu_for_single_message) - for account in accounts_list: - if gajim.connections[account].is_zeroconf or \ - not gajim.account_is_connected(account): - continue - if gajim.connections[account].private_storage_supported: - connected_accounts_with_private_storage += 1 - # for single message - item = gtk.MenuItem(_('using account %s') % account) - item.connect('activate', - self.on_single_message_menuitem_activate, account) - account_menu_for_single_message.append(item) + for account in accounts_list: + if gajim.connections[account].is_zeroconf or \ + not gajim.account_is_connected(account): + continue + if gajim.connections[account].private_storage_supported: + connected_accounts_with_private_storage += 1 + # for single message + item = gtk.MenuItem(_('using account %s') % account) + item.connect('activate', + self.on_single_message_menuitem_activate, account) + account_menu_for_single_message.append(item) - # join gc - gc_item = gtk.MenuItem(_('using account %s') % account, False) - gc_sub_menu.append(gc_item) - gc_menuitem_menu = gtk.Menu() - gajim.interface.roster.add_bookmarks_list(gc_menuitem_menu, - account) - gc_item.set_submenu(gc_menuitem_menu) - gc_sub_menu.show_all() + # join gc + gc_item = gtk.MenuItem(_('using account %s') % account, False) + gc_sub_menu.append(gc_item) + gc_menuitem_menu = gtk.Menu() + gajim.interface.roster.add_bookmarks_list(gc_menuitem_menu, + account) + gc_item.set_submenu(gc_menuitem_menu) + gc_sub_menu.show_all() - newitem = gtk.SeparatorMenuItem() # separator - gc_sub_menu.append(newitem) - newitem = gtk.ImageMenuItem(_('_Manage Bookmarks...')) - img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU) - newitem.set_image(img) - newitem.connect('activate', - gajim.interface.roster.on_manage_bookmarks_menuitem_activate) - gc_sub_menu.append(newitem) - if connected_accounts_with_private_storage == 0: - newitem.set_sensitive(False) + newitem = gtk.SeparatorMenuItem() # separator + gc_sub_menu.append(newitem) + newitem = gtk.ImageMenuItem(_('_Manage Bookmarks...')) + img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU) + newitem.set_image(img) + newitem.connect('activate', + gajim.interface.roster.on_manage_bookmarks_menuitem_activate) + gc_sub_menu.append(newitem) + if connected_accounts_with_private_storage == 0: + newitem.set_sensitive(False) - sounds_mute_menuitem.set_active(not gajim.config.get('sounds_on')) + sounds_mute_menuitem.set_active(not gajim.config.get('sounds_on')) - if os.name == 'nt': - if self.added_hide_menuitem is False: - self.systray_context_menu.prepend(gtk.SeparatorMenuItem()) - item = gtk.MenuItem(_('Hide this menu')) - self.systray_context_menu.prepend(item) - self.added_hide_menuitem = True + if os.name == 'nt': + if self.added_hide_menuitem is False: + self.systray_context_menu.prepend(gtk.SeparatorMenuItem()) + item = gtk.MenuItem(_('Hide this menu')) + self.systray_context_menu.prepend(item) + self.added_hide_menuitem = True - self.systray_context_menu.show_all() - self.systray_context_menu.popup(None, None, None, 0, - event_time) + self.systray_context_menu.show_all() + self.systray_context_menu.popup(None, None, None, 0, + event_time) - def on_show_all_events_menuitem_activate(self, widget): - events = gajim.events.get_systray_events() - for account in events: - for jid in events[account]: - for event in events[account][jid]: - gajim.interface.handle_event(account, jid, event.type_) + def on_show_all_events_menuitem_activate(self, widget): + events = gajim.events.get_systray_events() + for account in events: + for jid in events[account]: + for event in events[account][jid]: + gajim.interface.handle_event(account, jid, event.type_) - def on_sounds_mute_menuitem_activate(self, widget): - gajim.config.set('sounds_on', not widget.get_active()) - gajim.interface.save_config() + def on_sounds_mute_menuitem_activate(self, widget): + gajim.config.set('sounds_on', not widget.get_active()) + gajim.interface.save_config() - def on_show_roster_menuitem_activate(self, widget): - win = gajim.interface.roster.window - win.present() + def on_show_roster_menuitem_activate(self, widget): + win = gajim.interface.roster.window + win.present() - def on_preferences_menuitem_activate(self, widget): - if 'preferences' in gajim.interface.instances: - gajim.interface.instances['preferences'].window.present() - else: - gajim.interface.instances['preferences'] = config.PreferencesWindow() + def on_preferences_menuitem_activate(self, widget): + if 'preferences' in gajim.interface.instances: + gajim.interface.instances['preferences'].window.present() + else: + gajim.interface.instances['preferences'] = config.PreferencesWindow() - def on_quit_menuitem_activate(self, widget): - gajim.interface.roster.on_quit_request() + def on_quit_menuitem_activate(self, widget): + gajim.interface.roster.on_quit_request() - def on_left_click(self): - win = gajim.interface.roster.window - if len(gajim.events.get_systray_events()) == 0: - # No pending events, so toggle visible/hidden for roster window - if not win.iconify_initially and (win.get_property( - 'has-toplevel-focus') or os.name == 'nt'): - # visible in ANY virtual desktop? + def on_left_click(self): + win = gajim.interface.roster.window + if len(gajim.events.get_systray_events()) == 0: + # No pending events, so toggle visible/hidden for roster window + if not win.iconify_initially and (win.get_property( + 'has-toplevel-focus') or os.name == 'nt'): + # visible in ANY virtual desktop? - # we could be in another VD right now. eg vd2 - # and we want to show it in vd2 - if not gtkgui_helpers.possibly_move_window_in_current_desktop(win): - win.set_property('skip-taskbar-hint', False) - win.iconify() # else we hide it from VD that was visible in - win.set_property('skip-taskbar-hint', True) - else: - win.deiconify() - if not gajim.config.get('roster_window_skip_taskbar'): - win.set_property('skip-taskbar-hint', False) - win.present_with_time(gtk.get_current_event_time()) - else: - self.handle_first_event() + # we could be in another VD right now. eg vd2 + # and we want to show it in vd2 + if not gtkgui_helpers.possibly_move_window_in_current_desktop(win): + win.set_property('skip-taskbar-hint', False) + win.iconify() # else we hide it from VD that was visible in + win.set_property('skip-taskbar-hint', True) + else: + win.deiconify() + if not gajim.config.get('roster_window_skip_taskbar'): + win.set_property('skip-taskbar-hint', False) + win.present_with_time(gtk.get_current_event_time()) + else: + self.handle_first_event() - def handle_first_event(self): - account, jid, event = gajim.events.get_first_systray_event() - if not event: - return - gajim.interface.handle_event(account, jid, event.type_) + def handle_first_event(self): + account, jid, event = gajim.events.get_first_systray_event() + if not event: + return + gajim.interface.handle_event(account, jid, event.type_) - def on_middle_click(self): - '''middle click raises window to have complete focus (fe. get kbd events) - but if already raised, it hides it''' - win = gajim.interface.roster.window - if win.is_active(): # is it fully raised? (eg does it receive kbd events?) - win.hide() - else: - win.present() + def on_middle_click(self): + '''middle click raises window to have complete focus (fe. get kbd events) + but if already raised, it hides it''' + win = gajim.interface.roster.window + if win.is_active(): # is it fully raised? (eg does it receive kbd events?) + win.hide() + else: + win.present() - def on_clicked(self, widget, event): - self.on_tray_leave_notify_event(widget, None) - if event.type != gtk.gdk.BUTTON_PRESS: - return - if event.button == 1: # Left click - self.on_left_click() - elif event.button == 2: # middle click - self.on_middle_click() - elif event.button == 3: # right click - self.make_menu(event.button, event.time) + def on_clicked(self, widget, event): + self.on_tray_leave_notify_event(widget, None) + if event.type != gtk.gdk.BUTTON_PRESS: + return + if event.button == 1: # Left click + self.on_left_click() + elif event.button == 2: # middle click + self.on_middle_click() + elif event.button == 3: # right click + self.make_menu(event.button, event.time) - def on_show_menuitem_activate(self, widget, show): - # we all add some fake (we cannot select those nor have them as show) - # but this helps to align with roster's status_combobox index positions - l = ['online', 'chat', 'away', 'xa', 'dnd', 'invisible', 'SEPARATOR', - 'CHANGE_STATUS_MSG_MENUITEM', 'SEPARATOR', 'offline'] - index = l.index(show) - if not helpers.statuses_unified(): - gajim.interface.roster.status_combobox.set_active(index + 2) - return - current = gajim.interface.roster.status_combobox.get_active() - if index != current: - gajim.interface.roster.status_combobox.set_active(index) + def on_show_menuitem_activate(self, widget, show): + # we all add some fake (we cannot select those nor have them as show) + # but this helps to align with roster's status_combobox index positions + l = ['online', 'chat', 'away', 'xa', 'dnd', 'invisible', 'SEPARATOR', + 'CHANGE_STATUS_MSG_MENUITEM', 'SEPARATOR', 'offline'] + index = l.index(show) + if not helpers.statuses_unified(): + gajim.interface.roster.status_combobox.set_active(index + 2) + return + current = gajim.interface.roster.status_combobox.get_active() + if index != current: + gajim.interface.roster.status_combobox.set_active(index) - def on_change_status_message_activate(self, widget): - model = gajim.interface.roster.status_combobox.get_model() - active = gajim.interface.roster.status_combobox.get_active() - status = model[active][2].decode('utf-8') - def on_response(message, pep_dict): - if message is None: # None if user press Cancel - return - accounts = gajim.connections.keys() - for acct in accounts: - if not gajim.config.get_per('accounts', acct, - 'sync_with_global_status'): - continue - show = gajim.SHOW_LIST[gajim.connections[acct].connected] - gajim.interface.roster.send_status(acct, show, message) - gajim.interface.roster.send_pep(acct, pep_dict) - dlg = dialogs.ChangeStatusMessageDialog(on_response, status) - dlg.dialog.present() - -# vim: se ts=3: + def on_change_status_message_activate(self, widget): + model = gajim.interface.roster.status_combobox.get_model() + active = gajim.interface.roster.status_combobox.get_active() + status = model[active][2].decode('utf-8') + def on_response(message, pep_dict): + if message is None: # None if user press Cancel + return + accounts = gajim.connections.keys() + for acct in accounts: + if not gajim.config.get_per('accounts', acct, + 'sync_with_global_status'): + continue + show = gajim.SHOW_LIST[gajim.connections[acct].connected] + gajim.interface.roster.send_status(acct, show, message) + gajim.interface.roster.send_pep(acct, pep_dict) + dlg = dialogs.ChangeStatusMessageDialog(on_response, status) + dlg.dialog.present() diff --git a/src/tooltips.py b/src/tooltips.py index 5e55166c0..b8ef538a0 100644 --- a/src/tooltips.py +++ b/src/tooltips.py @@ -41,659 +41,657 @@ from common import helpers from common.pep import MOODS, ACTIVITIES class BaseTooltip: - ''' Base Tooltip class; - Usage: - tooltip = BaseTooltip() - .... - tooltip.show_tooltip(data, widget_height, widget_y_position) - .... - if tooltip.timeout != 0: - tooltip.hide_tooltip() + ''' Base Tooltip class; + Usage: + tooltip = BaseTooltip() + .... + tooltip.show_tooltip(data, widget_height, widget_y_position) + .... + if tooltip.timeout != 0: + tooltip.hide_tooltip() - * data - the text to be displayed (extenders override this argument and - display more complex contents) - * widget_height - the height of the widget on which we want to show tooltip - * widget_y_position - the vertical position of the widget on the screen + * data - the text to be displayed (extenders override this argument and + display more complex contents) + * widget_height - the height of the widget on which we want to show tooltip + * widget_y_position - the vertical position of the widget on the screen - Tooltip is displayed aligned centered to the mouse poiner and 4px below the widget. - In case tooltip goes below the visible area it is shown above the widget. - ''' - def __init__(self): - self.timeout = 0 - self.preferred_position = [0, 0] - self.win = None - self.id = None + Tooltip is displayed aligned centered to the mouse poiner and 4px below the widget. + In case tooltip goes below the visible area it is shown above the widget. + ''' + def __init__(self): + self.timeout = 0 + self.preferred_position = [0, 0] + self.win = None + self.id = None - def populate(self, data): - ''' this method must be overriden by all extenders - This is the most simple implementation: show data as value of a label - ''' - self.create_window() - self.win.add(gtk.Label(data)) + def populate(self, data): + ''' this method must be overriden by all extenders + This is the most simple implementation: show data as value of a label + ''' + self.create_window() + self.win.add(gtk.Label(data)) - def create_window(self): - ''' create a popup window each time tooltip is requested ''' - self.win = gtk.Window(gtk.WINDOW_POPUP) - self.win.set_border_width(3) - self.win.set_resizable(False) - self.win.set_name('gtk-tooltips') - self.win.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_TOOLTIP) + def create_window(self): + ''' create a popup window each time tooltip is requested ''' + self.win = gtk.Window(gtk.WINDOW_POPUP) + self.win.set_border_width(3) + self.win.set_resizable(False) + self.win.set_name('gtk-tooltips') + self.win.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_TOOLTIP) - self.win.set_events(gtk.gdk.POINTER_MOTION_MASK) - self.win.connect_after('expose_event', self.expose) - self.win.connect('size-request', self.on_size_request) - self.win.connect('motion-notify-event', self.motion_notify_event) - self.screen = self.win.get_screen() + self.win.set_events(gtk.gdk.POINTER_MOTION_MASK) + self.win.connect_after('expose_event', self.expose) + self.win.connect('size-request', self.on_size_request) + self.win.connect('motion-notify-event', self.motion_notify_event) + self.screen = self.win.get_screen() - def _get_icon_name_for_tooltip(self, contact): - ''' helper function used for tooltip contacts/acounts - Tooltip on account has fake contact with sub == '', in this case we show - real status of the account - ''' - if contact.ask == 'subscribe': - return 'requested' - elif contact.sub in ('both', 'to', ''): - return contact.show - return 'not in roster' + def _get_icon_name_for_tooltip(self, contact): + ''' helper function used for tooltip contacts/acounts + Tooltip on account has fake contact with sub == '', in this case we show + real status of the account + ''' + if contact.ask == 'subscribe': + return 'requested' + elif contact.sub in ('both', 'to', ''): + return contact.show + return 'not in roster' - def motion_notify_event(self, widget, event): - self.hide_tooltip() + def motion_notify_event(self, widget, event): + self.hide_tooltip() - def on_size_request(self, widget, requisition): - half_width = requisition.width / 2 + 1 - if self.preferred_position[0] < half_width: - self.preferred_position[0] = 0 - elif self.preferred_position[0] + requisition.width > \ - self.screen.get_width() + half_width: - self.preferred_position[0] = self.screen.get_width() - \ - requisition.width - else: - self.preferred_position[0] -= half_width - self.screen.get_height() - if self.preferred_position[1] + requisition.height > \ - self.screen.get_height(): - # flip tooltip up - self.preferred_position[1] -= requisition.height + \ - self.widget_height + 8 - if self.preferred_position[1] < 0: - self.preferred_position[1] = 0 - self.win.move(self.preferred_position[0], self.preferred_position[1]) + def on_size_request(self, widget, requisition): + half_width = requisition.width / 2 + 1 + if self.preferred_position[0] < half_width: + self.preferred_position[0] = 0 + elif self.preferred_position[0] + requisition.width > \ + self.screen.get_width() + half_width: + self.preferred_position[0] = self.screen.get_width() - \ + requisition.width + else: + self.preferred_position[0] -= half_width + self.screen.get_height() + if self.preferred_position[1] + requisition.height > \ + self.screen.get_height(): + # flip tooltip up + self.preferred_position[1] -= requisition.height + \ + self.widget_height + 8 + if self.preferred_position[1] < 0: + self.preferred_position[1] = 0 + self.win.move(self.preferred_position[0], self.preferred_position[1]) - def expose(self, widget, event): - style = self.win.get_style() - size = self.win.get_size() - style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, - None, self.win, 'tooltip', 0, 0, -1, 1) - style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, - None, self.win, 'tooltip', 0, size[1] - 1, -1, 1) - style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, - None, self.win, 'tooltip', 0, 0, 1, -1) - style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, - None, self.win, 'tooltip', size[0] - 1, 0, 1, -1) - return True + def expose(self, widget, event): + style = self.win.get_style() + size = self.win.get_size() + style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, + None, self.win, 'tooltip', 0, 0, -1, 1) + style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, + None, self.win, 'tooltip', 0, size[1] - 1, -1, 1) + style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, + None, self.win, 'tooltip', 0, 0, 1, -1) + style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, + None, self.win, 'tooltip', size[0] - 1, 0, 1, -1) + return True - def show_tooltip(self, data, widget_height, widget_y_position): - ''' show tooltip on widget. - data contains needed data for tooltip contents - widget_height is the height of the widget on which we show the tooltip - widget_y_position is vertical position of the widget on the screen - ''' - # set tooltip contents - self.populate(data) + def show_tooltip(self, data, widget_height, widget_y_position): + ''' show tooltip on widget. + data contains needed data for tooltip contents + widget_height is the height of the widget on which we show the tooltip + widget_y_position is vertical position of the widget on the screen + ''' + # set tooltip contents + self.populate(data) - # get the X position of mouse pointer on the screen - pointer_x = self.screen.get_display().get_pointer()[1] + # get the X position of mouse pointer on the screen + pointer_x = self.screen.get_display().get_pointer()[1] - # get the prefered X position of the tooltip on the screen in case this position is > - # than the height of the screen, tooltip will be shown above the widget - preferred_y = widget_y_position + widget_height + 4 + # get the prefered X position of the tooltip on the screen in case this position is > + # than the height of the screen, tooltip will be shown above the widget + preferred_y = widget_y_position + widget_height + 4 - self.preferred_position = [pointer_x, preferred_y] - self.widget_height = widget_height - self.win.ensure_style() - self.win.show_all() + self.preferred_position = [pointer_x, preferred_y] + self.widget_height = widget_height + self.win.ensure_style() + self.win.show_all() - def hide_tooltip(self): - if self.timeout > 0: - gobject.source_remove(self.timeout) - self.timeout = 0 - if self.win: - self.win.destroy() - self.win = None - self.id = None + def hide_tooltip(self): + if self.timeout > 0: + gobject.source_remove(self.timeout) + self.timeout = 0 + if self.win: + self.win.destroy() + self.win = None + self.id = None class StatusTable: - ''' Contains methods for creating status table. This - is used in Roster and NotificationArea tooltips ''' - def __init__(self): - self.current_row = 1 - self.table = None - self.text_label = None - self.spacer_label = ' ' + ''' Contains methods for creating status table. This + is used in Roster and NotificationArea tooltips ''' + def __init__(self): + self.current_row = 1 + self.table = None + self.text_label = None + self.spacer_label = ' ' - def create_table(self): - self.table = gtk.Table(4, 1) - self.table.set_property('column-spacing', 2) + def create_table(self): + self.table = gtk.Table(4, 1) + self.table.set_property('column-spacing', 2) - def add_text_row(self, text, col_inc = 0): - self.current_row += 1 - self.text_label = gtk.Label() - self.text_label.set_line_wrap(True) - self.text_label.set_alignment(0, 0) - self.text_label.set_selectable(False) - self.text_label.set_markup(text) - self.table.attach(self.text_label, 1 + col_inc, 4, self.current_row, - self.current_row + 1) + def add_text_row(self, text, col_inc = 0): + self.current_row += 1 + self.text_label = gtk.Label() + self.text_label.set_line_wrap(True) + self.text_label.set_alignment(0, 0) + self.text_label.set_selectable(False) + self.text_label.set_markup(text) + self.table.attach(self.text_label, 1 + col_inc, 4, self.current_row, + self.current_row + 1) - def get_status_info(self, resource, priority, show, status): - str_status = resource + ' (' + unicode(priority) + ')' - if status: - status = status.strip() - if status != '': - # make sure 'status' is unicode before we send to to reduce_chars - if isinstance(status, str): - status = unicode(status, encoding='utf-8') - # reduce to 100 chars, 1 line - status = helpers.reduce_chars_newlines(status, 100, 1) - str_status = gobject.markup_escape_text(str_status) - status = gobject.markup_escape_text(status) - str_status += ' - ' + status + '' - return str_status + def get_status_info(self, resource, priority, show, status): + str_status = resource + ' (' + unicode(priority) + ')' + if status: + status = status.strip() + if status != '': + # make sure 'status' is unicode before we send to to reduce_chars + if isinstance(status, str): + status = unicode(status, encoding='utf-8') + # reduce to 100 chars, 1 line + status = helpers.reduce_chars_newlines(status, 100, 1) + str_status = gobject.markup_escape_text(str_status) + status = gobject.markup_escape_text(status) + str_status += ' - ' + status + '' + return str_status - def add_status_row(self, file_path, show, str_status, status_time=None, - show_lock=False, indent=True): - ''' appends a new row with status icon to the table ''' - self.current_row += 1 - state_file = show.replace(' ', '_') - files = [] - files.append(os.path.join(file_path, state_file + '.png')) - files.append(os.path.join(file_path, state_file + '.gif')) - image = gtk.Image() - image.set_from_pixbuf(None) - for f in files: - if os.path.exists(f): - image.set_from_file(f) - break - spacer = gtk.Label(self.spacer_label) - image.set_alignment(1, 0.5) - if indent: - self.table.attach(spacer, 1, 2, self.current_row, - self.current_row + 1, 0, 0, 0, 0) - self.table.attach(image, 2, 3, self.current_row, - self.current_row + 1, gtk.FILL, gtk.FILL, 2, 0) - status_label = gtk.Label() - status_label.set_markup(str_status) - status_label.set_alignment(0, 0) - status_label.set_line_wrap(True) - self.table.attach(status_label, 3, 4, self.current_row, - self.current_row + 1, gtk.FILL | gtk.EXPAND, 0, 0, 0) - if show_lock: - lock_image = gtk.Image() - lock_image.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, - gtk.ICON_SIZE_MENU) - self.table.attach(lock_image, 4, 5, self.current_row, - self.current_row + 1, 0, 0, 0, 0) + def add_status_row(self, file_path, show, str_status, status_time=None, + show_lock=False, indent=True): + ''' appends a new row with status icon to the table ''' + self.current_row += 1 + state_file = show.replace(' ', '_') + files = [] + files.append(os.path.join(file_path, state_file + '.png')) + files.append(os.path.join(file_path, state_file + '.gif')) + image = gtk.Image() + image.set_from_pixbuf(None) + for f in files: + if os.path.exists(f): + image.set_from_file(f) + break + spacer = gtk.Label(self.spacer_label) + image.set_alignment(1, 0.5) + if indent: + self.table.attach(spacer, 1, 2, self.current_row, + self.current_row + 1, 0, 0, 0, 0) + self.table.attach(image, 2, 3, self.current_row, + self.current_row + 1, gtk.FILL, gtk.FILL, 2, 0) + status_label = gtk.Label() + status_label.set_markup(str_status) + status_label.set_alignment(0, 0) + status_label.set_line_wrap(True) + self.table.attach(status_label, 3, 4, self.current_row, + self.current_row + 1, gtk.FILL | gtk.EXPAND, 0, 0, 0) + if show_lock: + lock_image = gtk.Image() + lock_image.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, + gtk.ICON_SIZE_MENU) + self.table.attach(lock_image, 4, 5, self.current_row, + self.current_row + 1, 0, 0, 0, 0) class NotificationAreaTooltip(BaseTooltip, StatusTable): - ''' Tooltip that is shown in the notification area ''' - def __init__(self): - BaseTooltip.__init__(self) - StatusTable.__init__(self) + ''' Tooltip that is shown in the notification area ''' + def __init__(self): + BaseTooltip.__init__(self) + StatusTable.__init__(self) - def fill_table_with_accounts(self, accounts): - iconset = gajim.config.get('iconset') - if not iconset: - iconset = 'dcraven' - file_path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - for acct in accounts: - message = acct['message'] - # before reducing the chars we should assure we send unicode, else - # there are possible pango TBs on 'set_markup' - if isinstance(message, str): - message = unicode(message, encoding = 'utf-8') - message = helpers.reduce_chars_newlines(message, 100, 1) - message = gobject.markup_escape_text(message) - if acct['name'] in gajim.con_types and \ - gajim.con_types[acct['name']] in ('tls', 'ssl'): - show_lock = True - else: - show_lock = False - if message: - self.add_status_row(file_path, acct['show'], - gobject.markup_escape_text(acct['name']) + \ - ' - ' + message, show_lock=show_lock, indent=False) - else: - self.add_status_row(file_path, acct['show'], - gobject.markup_escape_text(acct['name']) - , show_lock=show_lock, indent=False) - for line in acct['event_lines']: - self.add_text_row(' ' + line, 1) + def fill_table_with_accounts(self, accounts): + iconset = gajim.config.get('iconset') + if not iconset: + iconset = 'dcraven' + file_path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + for acct in accounts: + message = acct['message'] + # before reducing the chars we should assure we send unicode, else + # there are possible pango TBs on 'set_markup' + if isinstance(message, str): + message = unicode(message, encoding = 'utf-8') + message = helpers.reduce_chars_newlines(message, 100, 1) + message = gobject.markup_escape_text(message) + if acct['name'] in gajim.con_types and \ + gajim.con_types[acct['name']] in ('tls', 'ssl'): + show_lock = True + else: + show_lock = False + if message: + self.add_status_row(file_path, acct['show'], + gobject.markup_escape_text(acct['name']) + \ + ' - ' + message, show_lock=show_lock, indent=False) + else: + self.add_status_row(file_path, acct['show'], + gobject.markup_escape_text(acct['name']) + , show_lock=show_lock, indent=False) + for line in acct['event_lines']: + self.add_text_row(' ' + line, 1) - def populate(self, data=''): - self.create_window() - self.create_table() + def populate(self, data=''): + self.create_window() + self.create_table() - accounts = helpers.get_notification_icon_tooltip_dict() - self.table.resize(2, 1) - self.fill_table_with_accounts(accounts) - self.hbox = gtk.HBox() - self.table.set_property('column-spacing', 1) + accounts = helpers.get_notification_icon_tooltip_dict() + self.table.resize(2, 1) + self.fill_table_with_accounts(accounts) + self.hbox = gtk.HBox() + self.table.set_property('column-spacing', 1) - self.hbox.add(self.table) - self.hbox.show_all() + self.hbox.add(self.table) + self.hbox.show_all() class GCTooltip(BaseTooltip): - ''' Tooltip that is shown in the GC treeview ''' - def __init__(self): - self.account = None - self.text_label = gtk.Label() - self.text_label.set_line_wrap(True) - self.text_label.set_alignment(0, 0) - self.text_label.set_selectable(False) - self.avatar_image = gtk.Image() + ''' Tooltip that is shown in the GC treeview ''' + def __init__(self): + self.account = None + self.text_label = gtk.Label() + self.text_label.set_line_wrap(True) + self.text_label.set_alignment(0, 0) + self.text_label.set_selectable(False) + self.avatar_image = gtk.Image() - BaseTooltip.__init__(self) + BaseTooltip.__init__(self) - def populate(self, contact): - if not contact: - return - self.create_window() - vcard_table = gtk.Table(3, 1) - vcard_table.set_property('column-spacing', 2) - vcard_table.set_homogeneous(False) - vcard_current_row = 1 - properties = [] + def populate(self, contact): + if not contact: + return + self.create_window() + vcard_table = gtk.Table(3, 1) + vcard_table.set_property('column-spacing', 2) + vcard_table.set_homogeneous(False) + vcard_current_row = 1 + properties = [] - nick_markup = '' + \ - gobject.markup_escape_text(contact.get_shown_name()) \ - + '' - properties.append((nick_markup, None)) + nick_markup = '' + \ + gobject.markup_escape_text(contact.get_shown_name()) \ + + '' + properties.append((nick_markup, None)) - if contact.status: # status message - status = contact.status.strip() - if status != '': - # escape markup entities - status = helpers.reduce_chars_newlines(status, 300, 5) - status = '' +\ - gobject.markup_escape_text(status) + '' - properties.append((status, None)) - else: # no status message, show SHOW instead - show = helpers.get_uf_show(contact.show) - show = '' + show + '' - properties.append((show, None)) + if contact.status: # status message + status = contact.status.strip() + if status != '': + # escape markup entities + status = helpers.reduce_chars_newlines(status, 300, 5) + status = '' +\ + gobject.markup_escape_text(status) + '' + properties.append((status, None)) + else: # no status message, show SHOW instead + show = helpers.get_uf_show(contact.show) + show = '' + show + '' + properties.append((show, None)) - if contact.jid.strip() != '': - properties.append((_('Jabber ID: '), contact.jid)) + if contact.jid.strip() != '': + properties.append((_('Jabber ID: '), contact.jid)) - if hasattr(contact, 'resource') and contact.resource.strip() != '': - properties.append((_('Resource: '), - gobject.markup_escape_text(contact.resource) )) - if contact.affiliation != 'none': - uf_affiliation = helpers.get_uf_affiliation(contact.affiliation) - affiliation_str = \ - _('%(owner_or_admin_or_member)s of this group chat') %\ - {'owner_or_admin_or_member': uf_affiliation} - properties.append((affiliation_str, None)) + if hasattr(contact, 'resource') and contact.resource.strip() != '': + properties.append((_('Resource: '), + gobject.markup_escape_text(contact.resource) )) + if contact.affiliation != 'none': + uf_affiliation = helpers.get_uf_affiliation(contact.affiliation) + affiliation_str = \ + _('%(owner_or_admin_or_member)s of this group chat') %\ + {'owner_or_admin_or_member': uf_affiliation} + properties.append((affiliation_str, None)) - # Add avatar - puny_name = helpers.sanitize_filename(contact.name) - puny_room = helpers.sanitize_filename(contact.room_jid) - file_ = helpers.get_avatar_path(os.path.join(gajim.AVATAR_PATH, puny_room, - puny_name)) - if file_: - self.avatar_image.set_from_file(file_) - pix = self.avatar_image.get_pixbuf() - pix = gtkgui_helpers.get_scaled_pixbuf(pix, 'tooltip') - self.avatar_image.set_from_pixbuf(pix) - else: - self.avatar_image.set_from_pixbuf(None) - while properties: - property_ = properties.pop(0) - vcard_current_row += 1 - vertical_fill = gtk.FILL - if not properties: - vertical_fill |= gtk.EXPAND - label = gtk.Label() - label.set_alignment(0, 0) - if property_[1]: - label.set_markup(property_[0]) - vcard_table.attach(label, 1, 2, vcard_current_row, - vcard_current_row + 1, gtk.FILL, vertical_fill, 0, 0) - label = gtk.Label() - label.set_alignment(0, 0) - label.set_markup(property_[1]) - label.set_line_wrap(True) - vcard_table.attach(label, 2, 3, vcard_current_row, - vcard_current_row + 1, gtk.EXPAND | gtk.FILL, - vertical_fill, 0, 0) - else: - label.set_markup(property_[0]) - label.set_line_wrap(True) - vcard_table.attach(label, 1, 3, vcard_current_row, - vcard_current_row + 1, gtk.FILL, vertical_fill, 0) + # Add avatar + puny_name = helpers.sanitize_filename(contact.name) + puny_room = helpers.sanitize_filename(contact.room_jid) + file_ = helpers.get_avatar_path(os.path.join(gajim.AVATAR_PATH, puny_room, + puny_name)) + if file_: + self.avatar_image.set_from_file(file_) + pix = self.avatar_image.get_pixbuf() + pix = gtkgui_helpers.get_scaled_pixbuf(pix, 'tooltip') + self.avatar_image.set_from_pixbuf(pix) + else: + self.avatar_image.set_from_pixbuf(None) + while properties: + property_ = properties.pop(0) + vcard_current_row += 1 + vertical_fill = gtk.FILL + if not properties: + vertical_fill |= gtk.EXPAND + label = gtk.Label() + label.set_alignment(0, 0) + if property_[1]: + label.set_markup(property_[0]) + vcard_table.attach(label, 1, 2, vcard_current_row, + vcard_current_row + 1, gtk.FILL, vertical_fill, 0, 0) + label = gtk.Label() + label.set_alignment(0, 0) + label.set_markup(property_[1]) + label.set_line_wrap(True) + vcard_table.attach(label, 2, 3, vcard_current_row, + vcard_current_row + 1, gtk.EXPAND | gtk.FILL, + vertical_fill, 0, 0) + else: + label.set_markup(property_[0]) + label.set_line_wrap(True) + vcard_table.attach(label, 1, 3, vcard_current_row, + vcard_current_row + 1, gtk.FILL, vertical_fill, 0) - self.avatar_image.set_alignment(0, 0) - vcard_table.attach(self.avatar_image, 3, 4, 2, vcard_current_row + 1, - gtk.FILL, gtk.FILL | gtk.EXPAND, 3, 3) - self.win.add(vcard_table) + self.avatar_image.set_alignment(0, 0) + vcard_table.attach(self.avatar_image, 3, 4, 2, vcard_current_row + 1, + gtk.FILL, gtk.FILL | gtk.EXPAND, 3, 3) + self.win.add(vcard_table) class RosterTooltip(NotificationAreaTooltip): - ''' Tooltip that is shown in the roster treeview ''' - def __init__(self): - self.account = None - self.image = gtk.Image() - self.image.set_alignment(0, 0) - # padding is independent of the total length and better than alignment - self.image.set_padding(1, 2) - self.avatar_image = gtk.Image() - NotificationAreaTooltip.__init__(self) + ''' Tooltip that is shown in the roster treeview ''' + def __init__(self): + self.account = None + self.image = gtk.Image() + self.image.set_alignment(0, 0) + # padding is independent of the total length and better than alignment + self.image.set_padding(1, 2) + self.avatar_image = gtk.Image() + NotificationAreaTooltip.__init__(self) - def populate(self, contacts): - self.create_window() + def populate(self, contacts): + self.create_window() - self.create_table() - if not contacts or len(contacts) == 0: - # Tooltip for merged accounts row - accounts = helpers.get_notification_icon_tooltip_dict() - self.table.resize(2, 1) - self.spacer_label = '' - self.fill_table_with_accounts(accounts) - self.win.add(self.table) - return + self.create_table() + if not contacts or len(contacts) == 0: + # Tooltip for merged accounts row + accounts = helpers.get_notification_icon_tooltip_dict() + self.table.resize(2, 1) + self.spacer_label = '' + self.fill_table_with_accounts(accounts) + self.win.add(self.table) + return - # primary contact - prim_contact = gajim.contacts.get_highest_prio_contact_from_contacts( - contacts) + # primary contact + prim_contact = gajim.contacts.get_highest_prio_contact_from_contacts( + contacts) - puny_jid = helpers.sanitize_filename(prim_contact.jid) - table_size = 3 + puny_jid = helpers.sanitize_filename(prim_contact.jid) + table_size = 3 - file_ = helpers.get_avatar_path(os.path.join(gajim.AVATAR_PATH, puny_jid)) - if file_: - self.avatar_image.set_from_file(file_) - pix = self.avatar_image.get_pixbuf() - pix = gtkgui_helpers.get_scaled_pixbuf(pix, 'tooltip') - self.avatar_image.set_from_pixbuf(pix) - table_size = 4 - else: - self.avatar_image.set_from_pixbuf(None) - vcard_table = gtk.Table(table_size, 1) - vcard_table.set_property('column-spacing', 2) - vcard_table.set_homogeneous(False) - vcard_current_row = 1 - properties = [] + file_ = helpers.get_avatar_path(os.path.join(gajim.AVATAR_PATH, puny_jid)) + if file_: + self.avatar_image.set_from_file(file_) + pix = self.avatar_image.get_pixbuf() + pix = gtkgui_helpers.get_scaled_pixbuf(pix, 'tooltip') + self.avatar_image.set_from_pixbuf(pix) + table_size = 4 + else: + self.avatar_image.set_from_pixbuf(None) + vcard_table = gtk.Table(table_size, 1) + vcard_table.set_property('column-spacing', 2) + vcard_table.set_homogeneous(False) + vcard_current_row = 1 + properties = [] - name_markup = u'' + \ - gobject.markup_escape_text(prim_contact.get_shown_name())\ - + '' - if self.account and helpers.jid_is_blocked(self.account, - prim_contact.jid): - name_markup += _(' [blocked]') - if self.account and \ - self.account in gajim.interface.minimized_controls and \ - prim_contact.jid in gajim.interface.minimized_controls[self.account]: - name_markup += _(' [minimized]') - properties.append((name_markup, None)) + name_markup = u'' + \ + gobject.markup_escape_text(prim_contact.get_shown_name())\ + + '' + if self.account and helpers.jid_is_blocked(self.account, + prim_contact.jid): + name_markup += _(' [blocked]') + if self.account and \ + self.account in gajim.interface.minimized_controls and \ + prim_contact.jid in gajim.interface.minimized_controls[self.account]: + name_markup += _(' [minimized]') + properties.append((name_markup, None)) - num_resources = 0 - # put contacts in dict, where key is priority - contacts_dict = {} - for contact in contacts: - if contact.resource: - num_resources += 1 - if contact.priority in contacts_dict: - contacts_dict[contact.priority].append(contact) - else: - contacts_dict[contact.priority] = [contact] + num_resources = 0 + # put contacts in dict, where key is priority + contacts_dict = {} + for contact in contacts: + if contact.resource: + num_resources += 1 + if contact.priority in contacts_dict: + contacts_dict[contact.priority].append(contact) + else: + contacts_dict[contact.priority] = [contact] - if num_resources > 1: - properties.append((_('Status: '), ' ')) - transport = gajim.get_transport_name_from_jid( - prim_contact.jid) - if transport: - file_path = os.path.join(helpers.get_transport_path(transport), - '16x16') - else: - iconset = gajim.config.get('iconset') - if not iconset: - iconset = 'dcraven' - file_path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + if num_resources > 1: + properties.append((_('Status: '), ' ')) + transport = gajim.get_transport_name_from_jid( + prim_contact.jid) + if transport: + file_path = os.path.join(helpers.get_transport_path(transport), + '16x16') + else: + iconset = gajim.config.get('iconset') + if not iconset: + iconset = 'dcraven' + file_path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - contact_keys = sorted(contacts_dict.keys()) - contact_keys.reverse() - for priority in contact_keys: - for acontact in contacts_dict[priority]: - status_line = self.get_status_info(acontact.resource, - acontact.priority, acontact.show, acontact.status) + contact_keys = sorted(contacts_dict.keys()) + contact_keys.reverse() + for priority in contact_keys: + for acontact in contacts_dict[priority]: + status_line = self.get_status_info(acontact.resource, + acontact.priority, acontact.show, acontact.status) - icon_name = self._get_icon_name_for_tooltip(acontact) - self.add_status_row(file_path, icon_name, status_line, - acontact.last_status_time) - properties.append((self.table, None)) + icon_name = self._get_icon_name_for_tooltip(acontact) + self.add_status_row(file_path, icon_name, status_line, + acontact.last_status_time) + properties.append((self.table, None)) - else: # only one resource - if contact.show: - show = helpers.get_uf_show(contact.show) - if contact.last_status_time: - vcard_current_row += 1 - if contact.show == 'offline': - text = ' - ' + _('Last status: %s') - else: - text = _(' since %s') + else: # only one resource + if contact.show: + show = helpers.get_uf_show(contact.show) + if contact.last_status_time: + vcard_current_row += 1 + if contact.show == 'offline': + text = ' - ' + _('Last status: %s') + else: + text = _(' since %s') - if time.strftime('%j', time.localtime())== \ - time.strftime('%j', contact.last_status_time): - # it's today, show only the locale hour representation - local_time = time.strftime('%X', - contact.last_status_time) - else: - # time.strftime returns locale encoded string - local_time = time.strftime('%c', - contact.last_status_time) - local_time = local_time.decode( - locale.getpreferredencoding()) - text = text % local_time - show += text - if self.account and \ - prim_contact.jid in gajim.gc_connected[self.account]: - if gajim.gc_connected[self.account][prim_contact.jid]: - show = _('Connected') - else: - show = _('Disconnected') - show = '' + show + '' - # we append show below + if time.strftime('%j', time.localtime())== \ + time.strftime('%j', contact.last_status_time): + # it's today, show only the locale hour representation + local_time = time.strftime('%X', + contact.last_status_time) + else: + # time.strftime returns locale encoded string + local_time = time.strftime('%c', + contact.last_status_time) + local_time = local_time.decode( + locale.getpreferredencoding()) + text = text % local_time + show += text + if self.account and \ + prim_contact.jid in gajim.gc_connected[self.account]: + if gajim.gc_connected[self.account][prim_contact.jid]: + show = _('Connected') + else: + show = _('Disconnected') + show = '' + show + '' + # we append show below - if contact.status: - status = contact.status.strip() - if status: - # reduce long status - # (no more than 300 chars on line and no more than 5 lines) - # status is wrapped - status = helpers.reduce_chars_newlines(status, 300, 5) - # escape markup entities. - status = gobject.markup_escape_text(status) - properties.append(('%s' % status, None)) - properties.append((show, None)) + if contact.status: + status = contact.status.strip() + if status: + # reduce long status + # (no more than 300 chars on line and no more than 5 lines) + # status is wrapped + status = helpers.reduce_chars_newlines(status, 300, 5) + # escape markup entities. + status = gobject.markup_escape_text(status) + properties.append(('%s' % status, None)) + properties.append((show, None)) - self._append_pep_info(contact, properties) + self._append_pep_info(contact, properties) - properties.append((_('Jabber ID: '), prim_contact.jid )) + properties.append((_('Jabber ID: '), prim_contact.jid )) - # contact has only one ressource - if num_resources == 1 and contact.resource: - properties.append((_('Resource: '), - gobject.markup_escape_text(contact.resource) +\ - ' (' + unicode(contact.priority) + ')')) + # contact has only one ressource + if num_resources == 1 and contact.resource: + properties.append((_('Resource: '), + gobject.markup_escape_text(contact.resource) +\ + ' (' + unicode(contact.priority) + ')')) - if self.account and prim_contact.sub and prim_contact.sub != 'both' and\ - prim_contact.jid not in gajim.gc_connected[self.account]: - # ('both' is the normal sub so we don't show it) - properties.append(( _('Subscription: '), - gobject.markup_escape_text(helpers.get_uf_sub(prim_contact.sub)))) + if self.account and prim_contact.sub and prim_contact.sub != 'both' and\ + prim_contact.jid not in gajim.gc_connected[self.account]: + # ('both' is the normal sub so we don't show it) + properties.append(( _('Subscription: '), + gobject.markup_escape_text(helpers.get_uf_sub(prim_contact.sub)))) - if prim_contact.keyID: - keyID = None - if len(prim_contact.keyID) == 8: - keyID = prim_contact.keyID - elif len(prim_contact.keyID) == 16: - keyID = prim_contact.keyID[8:] - if keyID: - properties.append((_('OpenPGP: '), - gobject.markup_escape_text(keyID))) + if prim_contact.keyID: + keyID = None + if len(prim_contact.keyID) == 8: + keyID = prim_contact.keyID + elif len(prim_contact.keyID) == 16: + keyID = prim_contact.keyID[8:] + if keyID: + properties.append((_('OpenPGP: '), + gobject.markup_escape_text(keyID))) - while properties: - property_ = properties.pop(0) - vcard_current_row += 1 - vertical_fill = gtk.FILL - if not properties and table_size == 4: - vertical_fill |= gtk.EXPAND - label = gtk.Label() - label.set_alignment(0, 0) - if property_[1]: - label.set_markup(property_[0]) - vcard_table.attach(label, 1, 2, vcard_current_row, - vcard_current_row + 1, gtk.FILL, vertical_fill, 0, 0) - label = gtk.Label() - label.set_alignment(0, 0) - label.set_markup(property_[1]) - label.set_line_wrap(True) - vcard_table.attach(label, 2, 3, vcard_current_row, - vcard_current_row + 1, gtk.EXPAND | gtk.FILL, - vertical_fill, 0, 0) - else: - if isinstance(property_[0], (unicode, str)): #FIXME: rm unicode? - label.set_markup(property_[0]) - label.set_line_wrap(True) - else: - label = property_[0] - vcard_table.attach(label, 1, 3, vcard_current_row, - vcard_current_row + 1, gtk.FILL, vertical_fill, 0) - self.avatar_image.set_alignment(0, 0) - if table_size == 4: - vcard_table.attach(self.avatar_image, 3, 4, 2, - vcard_current_row + 1, gtk.FILL, gtk.FILL | gtk.EXPAND, 3, 3) - self.win.add(vcard_table) + while properties: + property_ = properties.pop(0) + vcard_current_row += 1 + vertical_fill = gtk.FILL + if not properties and table_size == 4: + vertical_fill |= gtk.EXPAND + label = gtk.Label() + label.set_alignment(0, 0) + if property_[1]: + label.set_markup(property_[0]) + vcard_table.attach(label, 1, 2, vcard_current_row, + vcard_current_row + 1, gtk.FILL, vertical_fill, 0, 0) + label = gtk.Label() + label.set_alignment(0, 0) + label.set_markup(property_[1]) + label.set_line_wrap(True) + vcard_table.attach(label, 2, 3, vcard_current_row, + vcard_current_row + 1, gtk.EXPAND | gtk.FILL, + vertical_fill, 0, 0) + else: + if isinstance(property_[0], (unicode, str)): #FIXME: rm unicode? + label.set_markup(property_[0]) + label.set_line_wrap(True) + else: + label = property_[0] + vcard_table.attach(label, 1, 3, vcard_current_row, + vcard_current_row + 1, gtk.FILL, vertical_fill, 0) + self.avatar_image.set_alignment(0, 0) + if table_size == 4: + vcard_table.attach(self.avatar_image, 3, 4, 2, + vcard_current_row + 1, gtk.FILL, gtk.FILL | gtk.EXPAND, 3, 3) + self.win.add(vcard_table) - def _append_pep_info(self, contact, properties): - ''' - Append Tune, Mood, Activity information of the specified contact - to the given property list. - ''' - if 'mood' in contact.pep: - mood = contact.pep['mood'].asMarkupText() - mood_string = _('Mood:') + ' %s' % mood - properties.append((mood_string, None)) + def _append_pep_info(self, contact, properties): + ''' + Append Tune, Mood, Activity information of the specified contact + to the given property list. + ''' + if 'mood' in contact.pep: + mood = contact.pep['mood'].asMarkupText() + mood_string = _('Mood:') + ' %s' % mood + properties.append((mood_string, None)) - if 'activity' in contact.pep: - activity = contact.pep['activity'].asMarkupText() - activity_string = _('Activity:') + ' %s' % activity - properties.append((activity_string, None)) + if 'activity' in contact.pep: + activity = contact.pep['activity'].asMarkupText() + activity_string = _('Activity:') + ' %s' % activity + properties.append((activity_string, None)) - if 'tune' in contact.pep: - tune = contact.pep['tune'].asMarkupText() - tune_string = _('Tune:') + ' %s' % tune - properties.append((tune_string, None)) + if 'tune' in contact.pep: + tune = contact.pep['tune'].asMarkupText() + tune_string = _('Tune:') + ' %s' % tune + properties.append((tune_string, None)) class FileTransfersTooltip(BaseTooltip): - ''' Tooltip that is shown in the notification area ''' - def __init__(self): - BaseTooltip.__init__(self) + ''' Tooltip that is shown in the notification area ''' + def __init__(self): + BaseTooltip.__init__(self) - def populate(self, file_props): - ft_table = gtk.Table(2, 1) - ft_table.set_property('column-spacing', 2) - current_row = 1 - self.create_window() - properties = [] - name = file_props['name'] - if file_props['type'] == 'r': - file_name = os.path.split(file_props['file-name'])[1] - else: - file_name = file_props['name'] - properties.append((_('Name: '), - gobject.markup_escape_text(file_name))) - if file_props['type'] == 'r': - type_ = _('Download') - actor = _('Sender: ') - sender = unicode(file_props['sender']).split('/')[0] - name = gajim.contacts.get_first_contact_from_jid( - file_props['tt_account'], sender).get_shown_name() - else: - type_ = _('Upload') - actor = _('Recipient: ') - receiver = file_props['receiver'] - if hasattr(receiver, 'name'): - name = receiver.get_shown_name() - else: - name = receiver.split('/')[0] - properties.append((_('Type: '), type_)) - properties.append((actor, gobject.markup_escape_text(name))) + def populate(self, file_props): + ft_table = gtk.Table(2, 1) + ft_table.set_property('column-spacing', 2) + current_row = 1 + self.create_window() + properties = [] + name = file_props['name'] + if file_props['type'] == 'r': + file_name = os.path.split(file_props['file-name'])[1] + else: + file_name = file_props['name'] + properties.append((_('Name: '), + gobject.markup_escape_text(file_name))) + if file_props['type'] == 'r': + type_ = _('Download') + actor = _('Sender: ') + sender = unicode(file_props['sender']).split('/')[0] + name = gajim.contacts.get_first_contact_from_jid( + file_props['tt_account'], sender).get_shown_name() + else: + type_ = _('Upload') + actor = _('Recipient: ') + receiver = file_props['receiver'] + if hasattr(receiver, 'name'): + name = receiver.get_shown_name() + else: + name = receiver.split('/')[0] + properties.append((_('Type: '), type_)) + properties.append((actor, gobject.markup_escape_text(name))) - transfered_len = file_props.get('received-len', 0) - properties.append((_('Transferred: '), helpers.convert_bytes(transfered_len))) - status = '' - if 'started' not in file_props or not file_props['started']: - status = _('Not started') - elif 'connected' in file_props: - if 'stopped' in file_props and \ - file_props['stopped'] == True: - status = _('Stopped') - elif file_props['completed']: - status = _('Completed') - elif file_props['connected'] == False: - if file_props['completed']: - status = _('Completed') - else: - if 'paused' in file_props and \ - file_props['paused'] == True: - status = _('?transfer status:Paused') - elif 'stalled' in file_props and \ - file_props['stalled'] == True: - #stalled is not paused. it is like 'frozen' it stopped alone - status = _('Stalled') - else: - status = _('Transferring') - else: - status = _('Not started') - properties.append((_('Status: '), status)) - if 'desc' in file_props: - file_desc = file_props['desc'] - properties.append((_('Description: '), gobject.markup_escape_text( - file_desc))) - while properties: - property_ = properties.pop(0) - current_row += 1 - label = gtk.Label() - label.set_alignment(0, 0) - label.set_markup(property_[0]) - ft_table.attach(label, 1, 2, current_row, current_row + 1, - gtk.FILL, gtk.FILL, 0, 0) - label = gtk.Label() - label.set_alignment(0, 0) - label.set_line_wrap(True) - label.set_markup(property_[1]) - ft_table.attach(label, 2, 3, current_row, current_row + 1, - gtk.EXPAND | gtk.FILL, gtk.FILL, 0, 0) + transfered_len = file_props.get('received-len', 0) + properties.append((_('Transferred: '), helpers.convert_bytes(transfered_len))) + status = '' + if 'started' not in file_props or not file_props['started']: + status = _('Not started') + elif 'connected' in file_props: + if 'stopped' in file_props and \ + file_props['stopped'] == True: + status = _('Stopped') + elif file_props['completed']: + status = _('Completed') + elif file_props['connected'] == False: + if file_props['completed']: + status = _('Completed') + else: + if 'paused' in file_props and \ + file_props['paused'] == True: + status = _('?transfer status:Paused') + elif 'stalled' in file_props and \ + file_props['stalled'] == True: + #stalled is not paused. it is like 'frozen' it stopped alone + status = _('Stalled') + else: + status = _('Transferring') + else: + status = _('Not started') + properties.append((_('Status: '), status)) + if 'desc' in file_props: + file_desc = file_props['desc'] + properties.append((_('Description: '), gobject.markup_escape_text( + file_desc))) + while properties: + property_ = properties.pop(0) + current_row += 1 + label = gtk.Label() + label.set_alignment(0, 0) + label.set_markup(property_[0]) + ft_table.attach(label, 1, 2, current_row, current_row + 1, + gtk.FILL, gtk.FILL, 0, 0) + label = gtk.Label() + label.set_alignment(0, 0) + label.set_line_wrap(True) + label.set_markup(property_[1]) + ft_table.attach(label, 2, 3, current_row, current_row + 1, + gtk.EXPAND | gtk.FILL, gtk.FILL, 0, 0) - self.win.add(ft_table) + self.win.add(ft_table) class ServiceDiscoveryTooltip(BaseTooltip): - ''' Tooltip that is shown when hovering over a service discovery row ''' - def populate(self, status): - self.create_window() - label = gtk.Label() - label.set_line_wrap(True) - label.set_alignment(0, 0) - label.set_selectable(False) - if status == 1: - label.set_text( - _('This service has not yet responded with detailed information')) - elif status == 2: - label.set_text( - _('This service could not respond with detailed information.\n' - 'It is most likely legacy or broken')) - self.win.add(label) - -# vim: se ts=3: + ''' Tooltip that is shown when hovering over a service discovery row ''' + def populate(self, status): + self.create_window() + label = gtk.Label() + label.set_line_wrap(True) + label.set_alignment(0, 0) + label.set_selectable(False) + if status == 1: + label.set_text( + _('This service has not yet responded with detailed information')) + elif status == 2: + label.set_text( + _('This service could not respond with detailed information.\n' + 'It is most likely legacy or broken')) + self.win.add(label) diff --git a/src/vcard.py b/src/vcard.py index 5ee86044b..967175306 100644 --- a/src/vcard.py +++ b/src/vcard.py @@ -45,519 +45,517 @@ from common import gajim from common.i18n import Q_ def get_avatar_pixbuf_encoded_mime(photo): - '''return the pixbuf of the image - photo is a dictionary containing PHOTO information''' - if not isinstance(photo, dict): - return None, None, None - img_decoded = None - avatar_encoded = None - avatar_mime_type = None - if 'BINVAL' in photo: - img_encoded = photo['BINVAL'] - avatar_encoded = img_encoded - try: - img_decoded = base64.decodestring(img_encoded) - except Exception: - pass - if img_decoded: - if 'TYPE' in photo: - avatar_mime_type = photo['TYPE'] - pixbuf = gtkgui_helpers.get_pixbuf_from_data(img_decoded) - else: - pixbuf, avatar_mime_type = gtkgui_helpers.get_pixbuf_from_data( - img_decoded, want_type=True) - else: - pixbuf = None - return pixbuf, avatar_encoded, avatar_mime_type + '''return the pixbuf of the image + photo is a dictionary containing PHOTO information''' + if not isinstance(photo, dict): + return None, None, None + img_decoded = None + avatar_encoded = None + avatar_mime_type = None + if 'BINVAL' in photo: + img_encoded = photo['BINVAL'] + avatar_encoded = img_encoded + try: + img_decoded = base64.decodestring(img_encoded) + except Exception: + pass + if img_decoded: + if 'TYPE' in photo: + avatar_mime_type = photo['TYPE'] + pixbuf = gtkgui_helpers.get_pixbuf_from_data(img_decoded) + else: + pixbuf, avatar_mime_type = gtkgui_helpers.get_pixbuf_from_data( + img_decoded, want_type=True) + else: + pixbuf = None + return pixbuf, avatar_encoded, avatar_mime_type class VcardWindow: - '''Class for contact's information window''' + '''Class for contact's information window''' - def __init__(self, contact, account, gc_contact = None): - # the contact variable is the jid if vcard is true - self.xml = gtkgui_helpers.get_glade('vcard_information_window.glade') - self.window = self.xml.get_widget('vcard_information_window') - self.progressbar = self.xml.get_widget('progressbar') + def __init__(self, contact, account, gc_contact = None): + # the contact variable is the jid if vcard is true + self.xml = gtkgui_helpers.get_glade('vcard_information_window.glade') + self.window = self.xml.get_widget('vcard_information_window') + self.progressbar = self.xml.get_widget('progressbar') - self.contact = contact - self.account = account - self.gc_contact = gc_contact + self.contact = contact + self.account = account + self.gc_contact = gc_contact - # Get real jid - if gc_contact: - # Don't use real jid if room is (semi-)anonymous - gc_control = gajim.interface.msg_win_mgr.get_gc_control( - gc_contact.room_jid, account) - if gc_contact.jid and not gc_control.is_anonymous: - self.real_jid = gc_contact.jid - self.real_jid_for_vcard = gc_contact.jid - if gc_contact.resource: - self.real_jid += '/' + gc_contact.resource - else: - self.real_jid = gc_contact.get_full_jid() - self.real_jid_for_vcard = self.real_jid - self.real_resource = gc_contact.name - else: - self.real_jid = contact.get_full_jid() - self.real_resource = contact.resource + # Get real jid + if gc_contact: + # Don't use real jid if room is (semi-)anonymous + gc_control = gajim.interface.msg_win_mgr.get_gc_control( + gc_contact.room_jid, account) + if gc_contact.jid and not gc_control.is_anonymous: + self.real_jid = gc_contact.jid + self.real_jid_for_vcard = gc_contact.jid + if gc_contact.resource: + self.real_jid += '/' + gc_contact.resource + else: + self.real_jid = gc_contact.get_full_jid() + self.real_jid_for_vcard = self.real_jid + self.real_resource = gc_contact.name + else: + self.real_jid = contact.get_full_jid() + self.real_resource = contact.resource - puny_jid = helpers.sanitize_filename(contact.jid) - local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid) + \ - '_local' - for extension in ('.png', '.jpeg'): - local_avatar_path = local_avatar_basepath + extension - if os.path.isfile(local_avatar_path): - image = self.xml.get_widget('custom_avatar_image') - image.set_from_file(local_avatar_path) - image.show() - self.xml.get_widget('custom_avatar_label').show() - break - self.avatar_mime_type = None - self.avatar_encoded = None - self.vcard_arrived = False - self.os_info_arrived = False - self.entity_time_arrived = False - self.update_progressbar_timeout_id = gobject.timeout_add(100, - self.update_progressbar) + puny_jid = helpers.sanitize_filename(contact.jid) + local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid) + \ + '_local' + for extension in ('.png', '.jpeg'): + local_avatar_path = local_avatar_basepath + extension + if os.path.isfile(local_avatar_path): + image = self.xml.get_widget('custom_avatar_image') + image.set_from_file(local_avatar_path) + image.show() + self.xml.get_widget('custom_avatar_label').show() + break + self.avatar_mime_type = None + self.avatar_encoded = None + self.vcard_arrived = False + self.os_info_arrived = False + self.entity_time_arrived = False + self.update_progressbar_timeout_id = gobject.timeout_add(100, + self.update_progressbar) - self.fill_jabber_page() - annotations = gajim.connections[self.account].annotations - if self.contact.jid in annotations: - buffer_ = self.xml.get_widget('textview_annotation').get_buffer() - buffer_.set_text(annotations[self.contact.jid]) + self.fill_jabber_page() + annotations = gajim.connections[self.account].annotations + if self.contact.jid in annotations: + buffer_ = self.xml.get_widget('textview_annotation').get_buffer() + buffer_.set_text(annotations[self.contact.jid]) - self.xml.signal_autoconnect(self) - self.window.show_all() - self.xml.get_widget('close_button').grab_focus() + self.xml.signal_autoconnect(self) + self.window.show_all() + self.xml.get_widget('close_button').grab_focus() - def update_progressbar(self): - self.progressbar.pulse() - return True # loop forever + def update_progressbar(self): + self.progressbar.pulse() + return True # loop forever - def on_vcard_information_window_destroy(self, widget): - if self.update_progressbar_timeout_id is not None: - gobject.source_remove(self.update_progressbar_timeout_id) - del gajim.interface.instances[self.account]['infos'][self.contact.jid] - buffer_ = self.xml.get_widget('textview_annotation').get_buffer() - annotation = buffer_.get_text(buffer_.get_start_iter(), - buffer_.get_end_iter()) - connection = gajim.connections[self.account] - if annotation != connection.annotations.get(self.contact.jid, ''): - connection.annotations[self.contact.jid] = annotation - connection.store_annotations() + def on_vcard_information_window_destroy(self, widget): + if self.update_progressbar_timeout_id is not None: + gobject.source_remove(self.update_progressbar_timeout_id) + del gajim.interface.instances[self.account]['infos'][self.contact.jid] + buffer_ = self.xml.get_widget('textview_annotation').get_buffer() + annotation = buffer_.get_text(buffer_.get_start_iter(), + buffer_.get_end_iter()) + connection = gajim.connections[self.account] + if annotation != connection.annotations.get(self.contact.jid, ''): + connection.annotations[self.contact.jid] = annotation + connection.store_annotations() - def on_vcard_information_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: - self.window.destroy() + def on_vcard_information_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: + self.window.destroy() - def on_PHOTO_eventbox_button_press_event(self, widget, event): - '''If right-clicked, show popup''' - if event.button == 3: # right click - menu = gtk.Menu() - menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS) - menuitem.connect('activate', - gtkgui_helpers.on_avatar_save_as_menuitem_activate, - self.contact.jid, self.account, self.contact.get_shown_name() + - '.jpeg') - menu.append(menuitem) - menu.connect('selection-done', lambda w:w.destroy()) - # show the menu - menu.show_all() - menu.popup(None, None, None, event.button, event.time) + def on_PHOTO_eventbox_button_press_event(self, widget, event): + '''If right-clicked, show popup''' + if event.button == 3: # right click + menu = gtk.Menu() + menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS) + menuitem.connect('activate', + gtkgui_helpers.on_avatar_save_as_menuitem_activate, + self.contact.jid, self.account, self.contact.get_shown_name() + + '.jpeg') + menu.append(menuitem) + menu.connect('selection-done', lambda w:w.destroy()) + # show the menu + menu.show_all() + menu.popup(None, None, None, event.button, event.time) - def set_value(self, entry_name, value): - try: - if value and entry_name == 'URL_label': - widget = gtk.LinkButton(value, value) - widget.set_alignment(0, 0) - widget.show() - table = self.xml.get_widget('personal_info_table') - table.attach(widget, 1, 4, 3, 4, yoptions = 0) - else: - self.xml.get_widget(entry_name).set_text(value) - except AttributeError: - pass + def set_value(self, entry_name, value): + try: + if value and entry_name == 'URL_label': + widget = gtk.LinkButton(value, value) + widget.set_alignment(0, 0) + widget.show() + table = self.xml.get_widget('personal_info_table') + table.attach(widget, 1, 4, 3, 4, yoptions = 0) + else: + self.xml.get_widget(entry_name).set_text(value) + except AttributeError: + pass - def set_values(self, vcard): - for i in vcard.keys(): - if i == 'PHOTO' and self.xml.get_widget('information_notebook').\ - get_n_pages() > 4: - pixbuf, self.avatar_encoded, self.avatar_mime_type = \ - get_avatar_pixbuf_encoded_mime(vcard[i]) - image = self.xml.get_widget('PHOTO_image') - image.show() - self.xml.get_widget('user_avatar_label').show() - if not pixbuf: - image.set_from_icon_name('stock_person', - gtk.ICON_SIZE_DIALOG) - continue - pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard') - image.set_from_pixbuf(pixbuf) - continue - if i in ('ADR', 'TEL', 'EMAIL'): - for entry in vcard[i]: - add_on = '_HOME' - if 'WORK' in entry: - add_on = '_WORK' - for j in entry.keys(): - self.set_value(i + add_on + '_' + j + '_label', entry[j]) - if isinstance(vcard[i], dict): - for j in vcard[i].keys(): - self.set_value(i + '_' + j + '_label', vcard[i][j]) - else: - if i == 'DESC': - self.xml.get_widget('DESC_textview').get_buffer().set_text( - vcard[i], 0) - elif i != 'jid': # Do not override jid_label - self.set_value(i + '_label', vcard[i]) - self.vcard_arrived = True - self.test_remove_progressbar() + def set_values(self, vcard): + for i in vcard.keys(): + if i == 'PHOTO' and self.xml.get_widget('information_notebook').\ + get_n_pages() > 4: + pixbuf, self.avatar_encoded, self.avatar_mime_type = \ + get_avatar_pixbuf_encoded_mime(vcard[i]) + image = self.xml.get_widget('PHOTO_image') + image.show() + self.xml.get_widget('user_avatar_label').show() + if not pixbuf: + image.set_from_icon_name('stock_person', + gtk.ICON_SIZE_DIALOG) + continue + pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard') + image.set_from_pixbuf(pixbuf) + continue + if i in ('ADR', 'TEL', 'EMAIL'): + for entry in vcard[i]: + add_on = '_HOME' + if 'WORK' in entry: + add_on = '_WORK' + for j in entry.keys(): + self.set_value(i + add_on + '_' + j + '_label', entry[j]) + if isinstance(vcard[i], dict): + for j in vcard[i].keys(): + self.set_value(i + '_' + j + '_label', vcard[i][j]) + else: + if i == 'DESC': + self.xml.get_widget('DESC_textview').get_buffer().set_text( + vcard[i], 0) + elif i != 'jid': # Do not override jid_label + self.set_value(i + '_label', vcard[i]) + self.vcard_arrived = True + self.test_remove_progressbar() - def test_remove_progressbar(self): - if self.update_progressbar_timeout_id is not None and \ - self.vcard_arrived and self.os_info_arrived and self.entity_time_arrived: - gobject.source_remove(self.update_progressbar_timeout_id) - self.progressbar.hide() - self.update_progressbar_timeout_id = None + def test_remove_progressbar(self): + if self.update_progressbar_timeout_id is not None and \ + self.vcard_arrived and self.os_info_arrived and self.entity_time_arrived: + gobject.source_remove(self.update_progressbar_timeout_id) + self.progressbar.hide() + self.update_progressbar_timeout_id = None - def set_last_status_time(self): - self.fill_status_label() + def set_last_status_time(self): + self.fill_status_label() - def set_os_info(self, resource, client_info, os_info): - if self.xml.get_widget('information_notebook').get_n_pages() < 5: - return - i = 0 - client = '' - os = '' - while i in self.os_info: - if not self.os_info[i]['resource'] or \ - self.os_info[i]['resource'] == resource: - self.os_info[i]['client'] = client_info - self.os_info[i]['os'] = os_info - if i > 0: - client += '\n' - os += '\n' - client += self.os_info[i]['client'] - os += self.os_info[i]['os'] - i += 1 + def set_os_info(self, resource, client_info, os_info): + if self.xml.get_widget('information_notebook').get_n_pages() < 5: + return + i = 0 + client = '' + os = '' + while i in self.os_info: + if not self.os_info[i]['resource'] or \ + self.os_info[i]['resource'] == resource: + self.os_info[i]['client'] = client_info + self.os_info[i]['os'] = os_info + if i > 0: + client += '\n' + os += '\n' + client += self.os_info[i]['client'] + os += self.os_info[i]['os'] + i += 1 - if client == '': - client = Q_('?Client:Unknown') - if os == '': - os = Q_('?OS:Unknown') - self.xml.get_widget('client_name_version_label').set_text(client) - self.xml.get_widget('os_label').set_text(os) - self.os_info_arrived = True - self.test_remove_progressbar() + if client == '': + client = Q_('?Client:Unknown') + if os == '': + os = Q_('?OS:Unknown') + self.xml.get_widget('client_name_version_label').set_text(client) + self.xml.get_widget('os_label').set_text(os) + self.os_info_arrived = True + self.test_remove_progressbar() - def set_entity_time(self, resource, time_info): - if self.xml.get_widget('information_notebook').get_n_pages() < 5: - return - i = 0 - time_s = '' - while i in self.time_info: - if not self.time_info[i]['resource'] or \ - self.time_info[i]['resource'] == resource: - self.time_info[i]['time'] = time_info - if i > 0: - time_s += '\n' - time_s += self.time_info[i]['time'] - i += 1 + def set_entity_time(self, resource, time_info): + if self.xml.get_widget('information_notebook').get_n_pages() < 5: + return + i = 0 + time_s = '' + while i in self.time_info: + if not self.time_info[i]['resource'] or \ + self.time_info[i]['resource'] == resource: + self.time_info[i]['time'] = time_info + if i > 0: + time_s += '\n' + time_s += self.time_info[i]['time'] + i += 1 - if time_s == '': - time_s = Q_('?Time:Unknown') - self.xml.get_widget('time_label').set_text(time_s) - self.entity_time_arrived = True - self.test_remove_progressbar() + if time_s == '': + time_s = Q_('?Time:Unknown') + self.xml.get_widget('time_label').set_text(time_s) + self.entity_time_arrived = True + self.test_remove_progressbar() - def fill_status_label(self): - if self.xml.get_widget('information_notebook').get_n_pages() < 5: - return - contact_list = gajim.contacts.get_contacts(self.account, self.contact.jid) - connected_contact_list = [] - for c in contact_list: - if c.show not in ('offline', 'error'): - connected_contact_list.append(c) - if not connected_contact_list: - # no connected contact, get the offline one - connected_contact_list = contact_list - # stats holds show and status message - stats = '' - if connected_contact_list: - # Start with self.contact, as with resources - stats = helpers.get_uf_show(self.contact.show) - if self.contact.status: - stats += ': ' + self.contact.status - if self.contact.last_status_time: - stats += '\n' + _('since %s') % time.strftime('%c', - self.contact.last_status_time).decode( - locale.getpreferredencoding()) - for c in connected_contact_list: - if c.resource != self.contact.resource: - stats += '\n' - stats += helpers.get_uf_show(c.show) - if c.status: - stats += ': ' + c.status - if c.last_status_time: - stats += '\n' + _('since %s') % time.strftime('%c', - c.last_status_time).decode(locale.getpreferredencoding()) - else: # Maybe gc_vcard ? - stats = helpers.get_uf_show(self.contact.show) - if self.contact.status: - stats += ': ' + self.contact.status - status_label = self.xml.get_widget('status_label') - status_label.set_max_width_chars(15) - status_label.set_text(stats) + def fill_status_label(self): + if self.xml.get_widget('information_notebook').get_n_pages() < 5: + return + contact_list = gajim.contacts.get_contacts(self.account, self.contact.jid) + connected_contact_list = [] + for c in contact_list: + if c.show not in ('offline', 'error'): + connected_contact_list.append(c) + if not connected_contact_list: + # no connected contact, get the offline one + connected_contact_list = contact_list + # stats holds show and status message + stats = '' + if connected_contact_list: + # Start with self.contact, as with resources + stats = helpers.get_uf_show(self.contact.show) + if self.contact.status: + stats += ': ' + self.contact.status + if self.contact.last_status_time: + stats += '\n' + _('since %s') % time.strftime('%c', + self.contact.last_status_time).decode( + locale.getpreferredencoding()) + for c in connected_contact_list: + if c.resource != self.contact.resource: + stats += '\n' + stats += helpers.get_uf_show(c.show) + if c.status: + stats += ': ' + c.status + if c.last_status_time: + stats += '\n' + _('since %s') % time.strftime('%c', + c.last_status_time).decode(locale.getpreferredencoding()) + else: # Maybe gc_vcard ? + stats = helpers.get_uf_show(self.contact.show) + if self.contact.status: + stats += ': ' + self.contact.status + status_label = self.xml.get_widget('status_label') + status_label.set_max_width_chars(15) + status_label.set_text(stats) - status_label_eventbox = self.xml.get_widget('status_label_eventbox') - status_label_eventbox.set_tooltip_text(stats) + status_label_eventbox = self.xml.get_widget('status_label_eventbox') + status_label_eventbox.set_tooltip_text(stats) - def fill_jabber_page(self): - self.xml.get_widget('nickname_label').set_markup( - '' + - self.contact.get_shown_name() + - '') - self.xml.get_widget('jid_label').set_text(self.contact.jid) + def fill_jabber_page(self): + self.xml.get_widget('nickname_label').set_markup( + '' + + self.contact.get_shown_name() + + '') + self.xml.get_widget('jid_label').set_text(self.contact.jid) - subscription_label = self.xml.get_widget('subscription_label') - ask_label = self.xml.get_widget('ask_label') - if self.gc_contact: - self.xml.get_widget('subscription_title_label').set_markup(_("Role:")) - uf_role = helpers.get_uf_role(self.gc_contact.role) - subscription_label.set_text(uf_role) + subscription_label = self.xml.get_widget('subscription_label') + ask_label = self.xml.get_widget('ask_label') + if self.gc_contact: + self.xml.get_widget('subscription_title_label').set_markup(_("Role:")) + uf_role = helpers.get_uf_role(self.gc_contact.role) + subscription_label.set_text(uf_role) - self.xml.get_widget('ask_title_label').set_markup(_("Affiliation:")) - uf_affiliation = helpers.get_uf_affiliation(self.gc_contact.affiliation) - ask_label.set_text(uf_affiliation) - else: - uf_sub = helpers.get_uf_sub(self.contact.sub) - subscription_label.set_text(uf_sub) - eb = self.xml.get_widget('subscription_label_eventbox') - if self.contact.sub == 'from': - tt_text = _("This contact is interested in your presence information, but you are not interested in his/her presence") - elif self.contact.sub == 'to': - tt_text = _("You are interested in the contact's presence information, but he/she is not interested in yours") - elif self.contact.sub == 'both': - tt_text = _("You and the contact are interested in each other's presence information") - else: # None - tt_text = _("You are not interested in the contact's presence, and neither he/she is interested in yours") - eb.set_tooltip_text(tt_text) + self.xml.get_widget('ask_title_label').set_markup(_("Affiliation:")) + uf_affiliation = helpers.get_uf_affiliation(self.gc_contact.affiliation) + ask_label.set_text(uf_affiliation) + else: + uf_sub = helpers.get_uf_sub(self.contact.sub) + subscription_label.set_text(uf_sub) + eb = self.xml.get_widget('subscription_label_eventbox') + if self.contact.sub == 'from': + tt_text = _("This contact is interested in your presence information, but you are not interested in his/her presence") + elif self.contact.sub == 'to': + tt_text = _("You are interested in the contact's presence information, but he/she is not interested in yours") + elif self.contact.sub == 'both': + tt_text = _("You and the contact are interested in each other's presence information") + else: # None + tt_text = _("You are not interested in the contact's presence, and neither he/she is interested in yours") + eb.set_tooltip_text(tt_text) - uf_ask = helpers.get_uf_ask(self.contact.ask) - ask_label.set_text(uf_ask) - eb = self.xml.get_widget('ask_label_eventbox') - if self.contact.ask == 'subscribe': - tt_text = _("You are waiting contact's answer about your subscription request") - else: - tt_text = _("There is no pending subscription request.") - eb.set_tooltip_text(tt_text) + uf_ask = helpers.get_uf_ask(self.contact.ask) + ask_label.set_text(uf_ask) + eb = self.xml.get_widget('ask_label_eventbox') + if self.contact.ask == 'subscribe': + tt_text = _("You are waiting contact's answer about your subscription request") + else: + tt_text = _("There is no pending subscription request.") + eb.set_tooltip_text(tt_text) - resources = '%s (%s)' % (self.contact.resource, unicode( - self.contact.priority)) - uf_resources = self.contact.resource + _(' resource with priority ')\ - + unicode(self.contact.priority) - if not self.contact.status: - self.contact.status = '' + resources = '%s (%s)' % (self.contact.resource, unicode( + self.contact.priority)) + uf_resources = self.contact.resource + _(' resource with priority ')\ + + unicode(self.contact.priority) + if not self.contact.status: + self.contact.status = '' - # Request list time status only if contact is offline - if self.contact.show == 'offline': - if self.gc_contact: - j, r = gajim.get_room_and_nick_from_fjid(self.real_jid) - gajim.connections[self.account].request_last_status_time(j, r, - self.contact.jid) - else: - gajim.connections[self.account].request_last_status_time( - self.contact.jid, self.contact.resource) + # Request list time status only if contact is offline + if self.contact.show == 'offline': + if self.gc_contact: + j, r = gajim.get_room_and_nick_from_fjid(self.real_jid) + gajim.connections[self.account].request_last_status_time(j, r, + self.contact.jid) + else: + gajim.connections[self.account].request_last_status_time( + self.contact.jid, self.contact.resource) - # do not wait for os_info if contact is not connected or has error - # additional check for observer is needed, as show is offline for him - if self.contact.show in ('offline', 'error')\ - and not self.contact.is_observer(): - self.os_info_arrived = True - else: # Request os info if contact is connected - if self.gc_contact: - j, r = gajim.get_room_and_nick_from_fjid(self.real_jid) - gobject.idle_add(gajim.connections[self.account].request_os_info, - j, r, self.contact.jid) - else: - gobject.idle_add(gajim.connections[self.account].request_os_info, - self.contact.jid, self.contact.resource) + # do not wait for os_info if contact is not connected or has error + # additional check for observer is needed, as show is offline for him + if self.contact.show in ('offline', 'error')\ + and not self.contact.is_observer(): + self.os_info_arrived = True + else: # Request os info if contact is connected + if self.gc_contact: + j, r = gajim.get_room_and_nick_from_fjid(self.real_jid) + gobject.idle_add(gajim.connections[self.account].request_os_info, + j, r, self.contact.jid) + else: + gobject.idle_add(gajim.connections[self.account].request_os_info, + self.contact.jid, self.contact.resource) - # do not wait for entity_time if contact is not connected or has error - # additional check for observer is needed, as show is offline for him - if self.contact.show in ('offline', 'error')\ - and not self.contact.is_observer(): - self.entity_time_arrived = True - else: # Request entity time if contact is connected - if self.gc_contact: - j, r = gajim.get_room_and_nick_from_fjid(self.real_jid) - gobject.idle_add(gajim.connections[self.account].\ - request_entity_time, j, r, self.contact.jid) - else: - gobject.idle_add(gajim.connections[self.account].\ - request_entity_time, self.contact.jid, self.contact.resource) + # do not wait for entity_time if contact is not connected or has error + # additional check for observer is needed, as show is offline for him + if self.contact.show in ('offline', 'error')\ + and not self.contact.is_observer(): + self.entity_time_arrived = True + else: # Request entity time if contact is connected + if self.gc_contact: + j, r = gajim.get_room_and_nick_from_fjid(self.real_jid) + gobject.idle_add(gajim.connections[self.account].\ + request_entity_time, j, r, self.contact.jid) + else: + gobject.idle_add(gajim.connections[self.account].\ + request_entity_time, self.contact.jid, self.contact.resource) - self.os_info = {0: {'resource': self.real_resource, 'client': '', - 'os': ''}} - self.time_info = {0: {'resource': self.real_resource, 'time': ''}} - i = 1 - contact_list = gajim.contacts.get_contacts(self.account, self.contact.jid) - if contact_list: - for c in contact_list: - if c.resource != self.contact.resource: - resources += '\n%s (%s)' % (c.resource, - unicode(c.priority)) - uf_resources += '\n' + c.resource + \ - _(' resource with priority ') + unicode(c.priority) - if c.show not in ('offline', 'error'): - gobject.idle_add( - gajim.connections[self.account].request_os_info, c.jid, - c.resource) - gobject.idle_add(gajim.connections[self.account].\ - request_entity_time, c.jid, c.resource) - self.os_info[i] = {'resource': c.resource, 'client': '', - 'os': ''} - self.time_info[i] = {'resource': c.resource, 'time': ''} - i += 1 + self.os_info = {0: {'resource': self.real_resource, 'client': '', + 'os': ''}} + self.time_info = {0: {'resource': self.real_resource, 'time': ''}} + i = 1 + contact_list = gajim.contacts.get_contacts(self.account, self.contact.jid) + if contact_list: + for c in contact_list: + if c.resource != self.contact.resource: + resources += '\n%s (%s)' % (c.resource, + unicode(c.priority)) + uf_resources += '\n' + c.resource + \ + _(' resource with priority ') + unicode(c.priority) + if c.show not in ('offline', 'error'): + gobject.idle_add( + gajim.connections[self.account].request_os_info, c.jid, + c.resource) + gobject.idle_add(gajim.connections[self.account].\ + request_entity_time, c.jid, c.resource) + self.os_info[i] = {'resource': c.resource, 'client': '', + 'os': ''} + self.time_info[i] = {'resource': c.resource, 'time': ''} + i += 1 - self.xml.get_widget('resource_prio_label').set_text(resources) - resource_prio_label_eventbox = self.xml.get_widget( - 'resource_prio_label_eventbox') - resource_prio_label_eventbox.set_tooltip_text(uf_resources) + self.xml.get_widget('resource_prio_label').set_text(resources) + resource_prio_label_eventbox = self.xml.get_widget( + 'resource_prio_label_eventbox') + resource_prio_label_eventbox.set_tooltip_text(uf_resources) - self.fill_status_label() + self.fill_status_label() - if self.gc_contact: - # If we know the real jid, remove the resource from vcard request - gajim.connections[self.account].request_vcard(self.real_jid_for_vcard, - self.gc_contact.get_full_jid()) - else: - gajim.connections[self.account].request_vcard(self.contact.jid) + if self.gc_contact: + # If we know the real jid, remove the resource from vcard request + gajim.connections[self.account].request_vcard(self.real_jid_for_vcard, + self.gc_contact.get_full_jid()) + else: + gajim.connections[self.account].request_vcard(self.contact.jid) - def on_close_button_clicked(self, widget): - self.window.destroy() + def on_close_button_clicked(self, widget): + self.window.destroy() class ZeroconfVcardWindow: - def __init__(self, contact, account, is_fake = False): - # the contact variable is the jid if vcard is true - self.xml = gtkgui_helpers.get_glade('zeroconf_information_window.glade') - self.window = self.xml.get_widget('zeroconf_information_window') + def __init__(self, contact, account, is_fake = False): + # the contact variable is the jid if vcard is true + self.xml = gtkgui_helpers.get_glade('zeroconf_information_window.glade') + self.window = self.xml.get_widget('zeroconf_information_window') - self.contact = contact - self.account = account - self.is_fake = is_fake + self.contact = contact + self.account = account + self.is_fake = is_fake - # self.avatar_mime_type = None - # self.avatar_encoded = None + # self.avatar_mime_type = None + # self.avatar_encoded = None - self.fill_contact_page() - self.fill_personal_page() + self.fill_contact_page() + self.fill_personal_page() - self.xml.signal_autoconnect(self) - self.window.show_all() + self.xml.signal_autoconnect(self) + self.window.show_all() - def on_zeroconf_information_window_destroy(self, widget): - del gajim.interface.instances[self.account]['infos'][self.contact.jid] + def on_zeroconf_information_window_destroy(self, widget): + del gajim.interface.instances[self.account]['infos'][self.contact.jid] - def on_zeroconf_information_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: - self.window.destroy() + def on_zeroconf_information_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: + self.window.destroy() - def on_PHOTO_eventbox_button_press_event(self, widget, event): - '''If right-clicked, show popup''' - if event.button == 3: # right click - menu = gtk.Menu() - menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS) - menuitem.connect('activate', - gtkgui_helpers.on_avatar_save_as_menuitem_activate, - self.contact.jid, self.account, self.contact.get_shown_name() + - '.jpeg') - menu.append(menuitem) - menu.connect('selection-done', lambda w:w.destroy()) - # show the menu - menu.show_all() - menu.popup(None, None, None, event.button, event.time) + def on_PHOTO_eventbox_button_press_event(self, widget, event): + '''If right-clicked, show popup''' + if event.button == 3: # right click + menu = gtk.Menu() + menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS) + menuitem.connect('activate', + gtkgui_helpers.on_avatar_save_as_menuitem_activate, + self.contact.jid, self.account, self.contact.get_shown_name() + + '.jpeg') + menu.append(menuitem) + menu.connect('selection-done', lambda w:w.destroy()) + # show the menu + menu.show_all() + menu.popup(None, None, None, event.button, event.time) - def set_value(self, entry_name, value): - try: - if value and entry_name == 'URL_label': - widget = gtk.LinkButton(value, value) - widget.set_alignment(0, 0) - table = self.xml.get_widget('personal_info_table') - table.attach(widget, 1, 4, 3, 4, yoptions = 0) - else: - self.xml.get_widget(entry_name).set_text(value) - except AttributeError: - pass + def set_value(self, entry_name, value): + try: + if value and entry_name == 'URL_label': + widget = gtk.LinkButton(value, value) + widget.set_alignment(0, 0) + table = self.xml.get_widget('personal_info_table') + table.attach(widget, 1, 4, 3, 4, yoptions = 0) + else: + self.xml.get_widget(entry_name).set_text(value) + except AttributeError: + pass - def fill_status_label(self): - if self.xml.get_widget('information_notebook').get_n_pages() < 2: - return - contact_list = gajim.contacts.get_contacts(self.account, self.contact.jid) - # stats holds show and status message - stats = '' - one = True # Are we adding the first line ? - if contact_list: - for c in contact_list: - if not one: - stats += '\n' - stats += helpers.get_uf_show(c.show) - if c.status: - stats += ': ' + c.status - if c.last_status_time: - stats += '\n' + _('since %s') % time.strftime('%c', - c.last_status_time).decode(locale.getpreferredencoding()) - one = False - else: # Maybe gc_vcard ? - stats = helpers.get_uf_show(self.contact.show) - if self.contact.status: - stats += ': ' + self.contact.status - status_label = self.xml.get_widget('status_label') - status_label.set_max_width_chars(15) - status_label.set_text(stats) + def fill_status_label(self): + if self.xml.get_widget('information_notebook').get_n_pages() < 2: + return + contact_list = gajim.contacts.get_contacts(self.account, self.contact.jid) + # stats holds show and status message + stats = '' + one = True # Are we adding the first line ? + if contact_list: + for c in contact_list: + if not one: + stats += '\n' + stats += helpers.get_uf_show(c.show) + if c.status: + stats += ': ' + c.status + if c.last_status_time: + stats += '\n' + _('since %s') % time.strftime('%c', + c.last_status_time).decode(locale.getpreferredencoding()) + one = False + else: # Maybe gc_vcard ? + stats = helpers.get_uf_show(self.contact.show) + if self.contact.status: + stats += ': ' + self.contact.status + status_label = self.xml.get_widget('status_label') + status_label.set_max_width_chars(15) + status_label.set_text(stats) - status_label_eventbox = self.xml.get_widget('status_label_eventbox') - status_label_eventbox.set_tooltip_text(stats) + status_label_eventbox = self.xml.get_widget('status_label_eventbox') + status_label_eventbox.set_tooltip_text(stats) - def fill_contact_page(self): - self.xml.get_widget('nickname_label').set_markup( - '' + - self.contact.get_shown_name() + - '') - self.xml.get_widget('local_jid_label').set_text(self.contact.jid) + def fill_contact_page(self): + self.xml.get_widget('nickname_label').set_markup( + '' + + self.contact.get_shown_name() + + '') + self.xml.get_widget('local_jid_label').set_text(self.contact.jid) - resources = '%s (%s)' % (self.contact.resource, unicode( - self.contact.priority)) - uf_resources = self.contact.resource + _(' resource with priority ')\ - + unicode(self.contact.priority) - if not self.contact.status: - self.contact.status = '' + resources = '%s (%s)' % (self.contact.resource, unicode( + self.contact.priority)) + uf_resources = self.contact.resource + _(' resource with priority ')\ + + unicode(self.contact.priority) + if not self.contact.status: + self.contact.status = '' - # Request list time status - # gajim.connections[self.account].request_last_status_time(self.contact.jid, - # self.contact.resource) + # Request list time status + # gajim.connections[self.account].request_last_status_time(self.contact.jid, + # self.contact.resource) - self.xml.get_widget('resource_prio_label').set_text(resources) - resource_prio_label_eventbox = self.xml.get_widget( - 'resource_prio_label_eventbox') - resource_prio_label_eventbox.set_tooltip_text(uf_resources) + self.xml.get_widget('resource_prio_label').set_text(resources) + resource_prio_label_eventbox = self.xml.get_widget( + 'resource_prio_label_eventbox') + resource_prio_label_eventbox.set_tooltip_text(uf_resources) - self.fill_status_label() + self.fill_status_label() - # gajim.connections[self.account].request_vcard(self.contact.jid, self.is_fake) + # gajim.connections[self.account].request_vcard(self.contact.jid, self.is_fake) - def fill_personal_page(self): - contact = gajim.connections[gajim.ZEROCONF_ACC_NAME].roster.getItem(self.contact.jid) - for key in ('1st', 'last', 'jid', 'email'): - if key not in contact['txt_dict']: - contact['txt_dict'][key] = '' - self.xml.get_widget('first_name_label').set_text(contact['txt_dict']['1st']) - self.xml.get_widget('last_name_label').set_text(contact['txt_dict']['last']) - self.xml.get_widget('jabber_id_label').set_text(contact['txt_dict']['jid']) - self.xml.get_widget('email_label').set_text(contact['txt_dict']['email']) + def fill_personal_page(self): + contact = gajim.connections[gajim.ZEROCONF_ACC_NAME].roster.getItem(self.contact.jid) + for key in ('1st', 'last', 'jid', 'email'): + if key not in contact['txt_dict']: + contact['txt_dict'][key] = '' + self.xml.get_widget('first_name_label').set_text(contact['txt_dict']['1st']) + self.xml.get_widget('last_name_label').set_text(contact['txt_dict']['last']) + self.xml.get_widget('jabber_id_label').set_text(contact['txt_dict']['jid']) + self.xml.get_widget('email_label').set_text(contact['txt_dict']['email']) - def on_close_button_clicked(self, widget): - self.window.destroy() - -# vim: se ts=3: + def on_close_button_clicked(self, widget): + self.window.destroy() diff --git a/test/integration/__init__.py b/test/integration/__init__.py index 0adf844f1..aaeacfbef 100644 --- a/test/integration/__init__.py +++ b/test/integration/__init__.py @@ -3,4 +3,4 @@ This package contains integration tests. Integration tests are tests which require or include UI, network or both. -''' \ No newline at end of file +''' diff --git a/test/integration/test_gui_event_integration.py b/test/integration/test_gui_event_integration.py index 8759e2259..1a715b1b7 100644 --- a/test/integration/test_gui_event_integration.py +++ b/test/integration/test_gui_event_integration.py @@ -23,171 +23,169 @@ import roster_window import notify class TestStatusChange(unittest.TestCase): - '''tests gajim.py's incredibly complex handle_event_notify''' + '''tests gajim.py's incredibly complex handle_event_notify''' - def setUp(self): - - gajim.connections = {} - gajim.contacts = contacts_module.Contacts() - gajim.interface.roster = roster_window.RosterWindow() + def setUp(self): - for acc in contacts: - gajim.connections[acc] = MockConnection(acc) + gajim.connections = {} + gajim.contacts = contacts_module.Contacts() + gajim.interface.roster = roster_window.RosterWindow() - gajim.interface.roster.fill_contacts_and_groups_dicts(contacts[acc], - acc) - gajim.interface.roster.add_account(acc) - gajim.interface.roster.add_account_contacts(acc) + for acc in contacts: + gajim.connections[acc] = MockConnection(acc) - self.assertEqual(0, len(notify.notifications)) + gajim.interface.roster.fill_contacts_and_groups_dicts(contacts[acc], + acc) + gajim.interface.roster.add_account(acc) + gajim.interface.roster.add_account_contacts(acc) - def tearDown(self): - notify.notifications = [] + self.assertEqual(0, len(notify.notifications)) - def contact_comes_online(self, account, jid, resource, prio): - '''a remote contact comes online''' - gajim.interface.handle_event_notify(account, (jid, 'online', "I'm back!", - resource, prio, None, time.time(), None)) + def tearDown(self): + notify.notifications = [] - contact = None - for c in gajim.contacts.get_contacts(account, jid): - if c.resource == resource: - contact = c - break + def contact_comes_online(self, account, jid, resource, prio): + '''a remote contact comes online''' + gajim.interface.handle_event_notify(account, (jid, 'online', "I'm back!", + resource, prio, None, time.time(), None)) - self.assertEqual('online', contact.show) - self.assertEqual("I'm back!", contact.status) - self.assertEqual(prio, contact.priority) + contact = None + for c in gajim.contacts.get_contacts(account, jid): + if c.resource == resource: + contact = c + break - # the most recent notification is that the contact connected - self.assertEqual('contact_connected', notify.notifications[-1][0]) + self.assertEqual('online', contact.show) + self.assertEqual("I'm back!", contact.status) + self.assertEqual(prio, contact.priority) - def contact_goes_offline(self, account, jid, resource, prio, - still_exists = True): - '''a remote contact goes offline.''' - gajim.interface.handle_event_notify(account, (jid, 'offline', 'Goodbye!', - resource, prio, None, time.time(), None)) + # the most recent notification is that the contact connected + self.assertEqual('contact_connected', notify.notifications[-1][0]) - contact = None - for c in gajim.contacts.get_contacts(account, jid): - if c.resource == resource: - contact = c - break + def contact_goes_offline(self, account, jid, resource, prio, + still_exists = True): + '''a remote contact goes offline.''' + gajim.interface.handle_event_notify(account, (jid, 'offline', 'Goodbye!', + resource, prio, None, time.time(), None)) - if not still_exists: - self.assert_(contact is None) - return + contact = None + for c in gajim.contacts.get_contacts(account, jid): + if c.resource == resource: + contact = c + break - self.assertEqual('offline', contact.show) - self.assertEqual('Goodbye!', contact.status) - self.assertEqual(prio, contact.priority) + if not still_exists: + self.assert_(contact is None) + return - self.assertEqual('contact_disconnected', notify.notifications[-1][0]) + self.assertEqual('offline', contact.show) + self.assertEqual('Goodbye!', contact.status) + self.assertEqual(prio, contact.priority) - def user_starts_chatting(self, jid, account, resource=None): - '''the user opens a chat window and starts talking''' - ctrl = MockChatControl(jid, account) - win = MockWindow() - win.new_tab(ctrl) - gajim.interface.msg_win_mgr._windows['test'] = win + self.assertEqual('contact_disconnected', notify.notifications[-1][0]) - if resource: - jid = jid + '/' + resource + def user_starts_chatting(self, jid, account, resource=None): + '''the user opens a chat window and starts talking''' + ctrl = MockChatControl(jid, account) + win = MockWindow() + win.new_tab(ctrl) + gajim.interface.msg_win_mgr._windows['test'] = win - # a basic session is started - session = gajim.connections[account1].make_new_session(jid, - '01234567890abcdef', cls=MockSession) - ctrl.set_session(session) + if resource: + jid = jid + '/' + resource - return ctrl + # a basic session is started + session = gajim.connections[account1].make_new_session(jid, + '01234567890abcdef', cls=MockSession) + ctrl.set_session(session) - def user_starts_esession(self, jid, resource, account): - '''the user opens a chat window and starts an encrypted session''' - ctrl = self.user_starts_chatting(jid, account, resource) - ctrl.session.status = 'active' - ctrl.session.enable_encryption = True + return ctrl - return ctrl + def user_starts_esession(self, jid, resource, account): + '''the user opens a chat window and starts an encrypted session''' + ctrl = self.user_starts_chatting(jid, account, resource) + ctrl.session.status = 'active' + ctrl.session.enable_encryption = True - def test_contact_comes_online(self): - jid = 'default1@gajim.org' + return ctrl - # contact is offline initially - contacts = gajim.contacts.get_contacts(account1, jid) - self.assertEqual(1, len(contacts)) - self.assertEqual('offline', contacts[0].show) - self.assertEqual('', contacts[0].status) + def test_contact_comes_online(self): + jid = 'default1@gajim.org' - self.contact_comes_online(account1, jid, 'lowprio', 1) + # contact is offline initially + contacts = gajim.contacts.get_contacts(account1, jid) + self.assertEqual(1, len(contacts)) + self.assertEqual('offline', contacts[0].show) + self.assertEqual('', contacts[0].status) - def test_contact_goes_offline(self): - jid = 'default1@gajim.org' + self.contact_comes_online(account1, jid, 'lowprio', 1) - self.contact_comes_online(account1, jid, 'lowprio', 1) + def test_contact_goes_offline(self): + jid = 'default1@gajim.org' - ctrl = self.user_starts_chatting(jid, account1) - orig_sess = ctrl.session + self.contact_comes_online(account1, jid, 'lowprio', 1) - self.contact_goes_offline(account1, jid, 'lowprio', 1) + ctrl = self.user_starts_chatting(jid, account1) + orig_sess = ctrl.session - # session hasn't changed since we were talking to the bare jid - self.assertEqual(orig_sess, ctrl.session) + self.contact_goes_offline(account1, jid, 'lowprio', 1) - def test_two_resources_higher_comes_online(self): - jid = 'default1@gajim.org' + # session hasn't changed since we were talking to the bare jid + self.assertEqual(orig_sess, ctrl.session) - self.contact_comes_online(account1, jid, 'lowprio', 1) + def test_two_resources_higher_comes_online(self): + jid = 'default1@gajim.org' - ctrl = self.user_starts_chatting(jid, account1) + self.contact_comes_online(account1, jid, 'lowprio', 1) - self.contact_comes_online(account1, jid, 'highprio', 50) + ctrl = self.user_starts_chatting(jid, account1) - # old session was dropped - self.assertEqual(None, ctrl.session) + self.contact_comes_online(account1, jid, 'highprio', 50) - def test_two_resources_higher_goes_offline(self): - jid = 'default1@gajim.org' + # old session was dropped + self.assertEqual(None, ctrl.session) - self.contact_comes_online(account1, jid, 'lowprio', 1) - self.contact_comes_online(account1, jid, 'highprio', 50) + def test_two_resources_higher_goes_offline(self): + jid = 'default1@gajim.org' - ctrl = self.user_starts_chatting(jid, account1) + self.contact_comes_online(account1, jid, 'lowprio', 1) + self.contact_comes_online(account1, jid, 'highprio', 50) - self.contact_goes_offline(account1, jid, 'highprio', 50, - still_exists=False) + ctrl = self.user_starts_chatting(jid, account1) - # old session was dropped - self.assertEqual(None, ctrl.session) + self.contact_goes_offline(account1, jid, 'highprio', 50, + still_exists=False) - def test_two_resources_higher_comes_online_with_esession(self): - jid = 'default1@gajim.org' + # old session was dropped + self.assertEqual(None, ctrl.session) - self.contact_comes_online(account1, jid, 'lowprio', 1) + def test_two_resources_higher_comes_online_with_esession(self): + jid = 'default1@gajim.org' - ctrl = self.user_starts_esession(jid, 'lowprio', account1) + self.contact_comes_online(account1, jid, 'lowprio', 1) - self.contact_comes_online(account1, jid, 'highprio', 50) + ctrl = self.user_starts_esession(jid, 'lowprio', account1) - # session was associated with the low priority full jid, so it should - # have been removed from the control - self.assertEqual(None, ctrl.session) + self.contact_comes_online(account1, jid, 'highprio', 50) - def test_two_resources_higher_goes_offline_with_esession(self): - jid = 'default1@gajim.org' + # session was associated with the low priority full jid, so it should + # have been removed from the control + self.assertEqual(None, ctrl.session) - self.contact_comes_online(account1, jid, 'lowprio', 1) - self.contact_comes_online(account1, jid, 'highprio', 50) + def test_two_resources_higher_goes_offline_with_esession(self): + jid = 'default1@gajim.org' - ctrl = self.user_starts_esession(jid, 'highprio', account1) + self.contact_comes_online(account1, jid, 'lowprio', 1) + self.contact_comes_online(account1, jid, 'highprio', 50) - self.contact_goes_offline(account1, jid, 'highprio', 50, - still_exists=False) + ctrl = self.user_starts_esession(jid, 'highprio', account1) - # session was associated with the high priority full jid, so it should - # have been removed from the control - self.assertEqual(None, ctrl.session) + self.contact_goes_offline(account1, jid, 'highprio', 50, + still_exists=False) + + # session was associated with the high priority full jid, so it should + # have been removed from the control + self.assertEqual(None, ctrl.session) if __name__ == '__main__': - unittest.main() - -# vim: se ts=3: + unittest.main() diff --git a/test/integration/test_resolver.py b/test/integration/test_resolver.py index 1836d3c7b..d80ffee87 100644 --- a/test/integration/test_resolver.py +++ b/test/integration/test_resolver.py @@ -16,87 +16,85 @@ NONSENSE_NAME = 'sfsdfsdfsdf.sdfs.fsd' JABBERCZ_TXT_NAME = '_xmppconnect.jabber.cz' JABBERCZ_SRV_NAME = '_xmpp-client._tcp.jabber.cz' -TEST_LIST = [(GMAIL_SRV_NAME, 'srv', True), - (NONSENSE_NAME, 'srv', False), - (JABBERCZ_SRV_NAME, 'srv', True)] +TEST_LIST = [(GMAIL_SRV_NAME, 'srv', True), + (NONSENSE_NAME, 'srv', False), + (JABBERCZ_SRV_NAME, 'srv', True)] class TestResolver(unittest.TestCase): - ''' - Test for LibAsyncNSResolver and NSLookupResolver. Requires working - network connection. - ''' - def setUp(self): - self.idlequeue_thread = IdleQueueThread() - self.idlequeue_thread.start() + ''' + Test for LibAsyncNSResolver and NSLookupResolver. Requires working + network connection. + ''' + def setUp(self): + self.idlequeue_thread = IdleQueueThread() + self.idlequeue_thread.start() - self.iq = self.idlequeue_thread.iq - self._reset() - self.resolver = None + self.iq = self.idlequeue_thread.iq + self._reset() + self.resolver = None - def tearDown(self): - self.idlequeue_thread.stop_thread() - self.idlequeue_thread.join() + def tearDown(self): + self.idlequeue_thread.stop_thread() + self.idlequeue_thread.join() - def _reset(self): - self.flag = False - self.expect_results = False - self.nslookup = False - self.resolver = None + def _reset(self): + self.flag = False + self.expect_results = False + self.nslookup = False + self.resolver = None - def testLibAsyncNSResolver(self): - self._reset() - if not resolver.USE_LIBASYNCNS: - print 'testLibAsyncResolver: libasyncns-python not installed' - return - self.resolver = resolver.LibAsyncNSResolver() + def testLibAsyncNSResolver(self): + self._reset() + if not resolver.USE_LIBASYNCNS: + print 'testLibAsyncResolver: libasyncns-python not installed' + return + self.resolver = resolver.LibAsyncNSResolver() - for name, type, expect_results in TEST_LIST: - self.expect_results = expect_results - self._runLANSR(name, type) - self.flag = False + for name, type, expect_results in TEST_LIST: + self.expect_results = expect_results + self._runLANSR(name, type) + self.flag = False - def _runLANSR(self, name, type): - self.resolver.resolve( - host = name, - type = type, - on_ready = self._myonready) - while not self.flag: - time.sleep(1) - self.resolver.process() + def _runLANSR(self, name, type): + self.resolver.resolve( + host = name, + type = type, + on_ready = self._myonready) + while not self.flag: + time.sleep(1) + self.resolver.process() - def _myonready(self, name, result_set): - if __name__ == '__main__': - from pprint import pprint - pprint('on_ready called ...') - pprint('hostname: %s' % name) - pprint('result set: %s' % result_set) - pprint('res.resolved_hosts: %s' % self.resolver.resolved_hosts) - pprint('') - if self.expect_results: - self.assert_(len(result_set) > 0) - else: - self.assert_(result_set == []) - self.flag = True - if self.nslookup: - self._testNSLR() - - def testNSLookupResolver(self): - self._reset() - self.nslookup = True - self.resolver = resolver.NSLookupResolver(self.iq) - self.test_list = TEST_LIST - self._testNSLR() + def _myonready(self, name, result_set): + if __name__ == '__main__': + from pprint import pprint + pprint('on_ready called ...') + pprint('hostname: %s' % name) + pprint('result set: %s' % result_set) + pprint('res.resolved_hosts: %s' % self.resolver.resolved_hosts) + pprint('') + if self.expect_results: + self.assert_(len(result_set) > 0) + else: + self.assert_(result_set == []) + self.flag = True + if self.nslookup: + self._testNSLR() - def _testNSLR(self): - if self.test_list == []: - return - name, type, self.expect_results = self.test_list.pop() - self.resolver.resolve( - host = name, - type = type, - on_ready = self._myonready) + def testNSLookupResolver(self): + self._reset() + self.nslookup = True + self.resolver = resolver.NSLookupResolver(self.iq) + self.test_list = TEST_LIST + self._testNSLR() + + def _testNSLR(self): + if self.test_list == []: + return + name, type, self.expect_results = self.test_list.pop() + self.resolver.resolve( + host = name, + type = type, + on_ready = self._myonready) if __name__ == '__main__': - unittest.main() - -# vim: se ts=3: + unittest.main() diff --git a/test/integration/test_roster.py b/test/integration/test_roster.py index acb91b71c..a93c49d81 100644 --- a/test/integration/test_roster.py +++ b/test/integration/test_roster.py @@ -17,189 +17,187 @@ gajim.get_jid_from_account = lambda acc: 'myjid@' + acc class TestRosterWindow(unittest.TestCase): - def setUp(self): - gajim.interface = MockInterface() + def setUp(self): + gajim.interface = MockInterface() - self.C_NAME = roster_window.C_NAME - self.C_TYPE = roster_window.C_TYPE - self.C_JID = roster_window.C_JID - self.C_ACCOUNT = roster_window.C_ACCOUNT + self.C_NAME = roster_window.C_NAME + self.C_TYPE = roster_window.C_TYPE + self.C_JID = roster_window.C_JID + self.C_ACCOUNT = roster_window.C_ACCOUNT - # Add after creating RosterWindow - # We want to test the filling explicitly - gajim.contacts = contacts_module.Contacts() - gajim.connections = {} - self.roster = roster_window.RosterWindow() - - for acc in contacts: - gajim.connections[acc] = MockConnection(acc) - gajim.contacts.add_account(acc) + # Add after creating RosterWindow + # We want to test the filling explicitly + gajim.contacts = contacts_module.Contacts() + gajim.connections = {} + self.roster = roster_window.RosterWindow() - ### Custom assertions - def assert_all_contacts_are_in_roster(self, acc): - for jid in contacts[acc]: - self.assert_contact_is_in_roster(jid, acc) + for acc in contacts: + gajim.connections[acc] = MockConnection(acc) + gajim.contacts.add_account(acc) - def assert_contact_is_in_roster(self, jid, account): - contacts = gajim.contacts.get_contacts(account, jid) - # check for all resources - for contact in contacts: - iters = self.roster._get_contact_iter(jid, account, - model=self.roster.model) + ### Custom assertions + def assert_all_contacts_are_in_roster(self, acc): + for jid in contacts[acc]: + self.assert_contact_is_in_roster(jid, acc) - if jid != gajim.get_jid_from_account(account): - # We don't care for groups of SelfContact - self.assertTrue(len(iters) == len(contact.get_shown_groups()), - msg='Contact is not in all his groups') + def assert_contact_is_in_roster(self, jid, account): + contacts = gajim.contacts.get_contacts(account, jid) + # check for all resources + for contact in contacts: + iters = self.roster._get_contact_iter(jid, account, + model=self.roster.model) - # Are we big brother? - bb_jid = None - bb_account = None - family = gajim.contacts.get_metacontacts_family(account, jid) - if family: - nearby_family, bb_jid, bb_account = \ - self.roster._get_nearby_family_and_big_brother(family, account) + if jid != gajim.get_jid_from_account(account): + # We don't care for groups of SelfContact + self.assertTrue(len(iters) == len(contact.get_shown_groups()), + msg='Contact is not in all his groups') - is_in_nearby_family = (jid, account) in ( - (data['jid'], data['account']) for data in nearby_family) - self.assertTrue(is_in_nearby_family, - msg='Contact not in his own nearby family') + # Are we big brother? + bb_jid = None + bb_account = None + family = gajim.contacts.get_metacontacts_family(account, jid) + if family: + nearby_family, bb_jid, bb_account = \ + self.roster._get_nearby_family_and_big_brother(family, account) - is_big_brother = (bb_jid, bb_account) == (jid, account) + is_in_nearby_family = (jid, account) in ( + (data['jid'], data['account']) for data in nearby_family) + self.assertTrue(is_in_nearby_family, + msg='Contact not in his own nearby family') - # check for each group tag - for titerC in iters: - self.assertTrue(self.roster.model.iter_is_valid(titerC), - msg='Contact iter invalid') + is_big_brother = (bb_jid, bb_account) == (jid, account) - c_model = self.roster.model[titerC] - self.assertEquals(contact.get_shown_name(), c_model[self.C_NAME], - msg='Contact name missmatch') - self.assertEquals(contact.jid, c_model[self.C_JID], - msg='Jid missmatch') + # check for each group tag + for titerC in iters: + self.assertTrue(self.roster.model.iter_is_valid(titerC), + msg='Contact iter invalid') - if not self.roster.regroup: - self.assertEquals(account, c_model[self.C_ACCOUNT], - msg='Account missmatch') + c_model = self.roster.model[titerC] + self.assertEquals(contact.get_shown_name(), c_model[self.C_NAME], + msg='Contact name missmatch') + self.assertEquals(contact.jid, c_model[self.C_JID], + msg='Jid missmatch') - # Check for correct nesting - parent_iter = self.roster.model.iter_parent(titerC) - p_model = self.roster.model[parent_iter] - if family: - if is_big_brother: - self.assertTrue(p_model[self.C_TYPE] == 'group', - msg='Big Brother is not on top') - else: - self.assertTrue(p_model[self.C_TYPE] == 'contact', - msg='Little Brother brother has no BigB') - else: - if jid == gajim.get_jid_from_account(account): - self.assertTrue(p_model[self.C_TYPE] == 'account', - msg='SelfContact is not on top') - else: - self.assertTrue(p_model[self.C_TYPE] == 'group', - msg='Contact not found in a group') + if not self.roster.regroup: + self.assertEquals(account, c_model[self.C_ACCOUNT], + msg='Account missmatch') - def assert_group_is_in_roster(self, group, account): - #TODO - pass + # Check for correct nesting + parent_iter = self.roster.model.iter_parent(titerC) + p_model = self.roster.model[parent_iter] + if family: + if is_big_brother: + self.assertTrue(p_model[self.C_TYPE] == 'group', + msg='Big Brother is not on top') + else: + self.assertTrue(p_model[self.C_TYPE] == 'contact', + msg='Little Brother brother has no BigB') + else: + if jid == gajim.get_jid_from_account(account): + self.assertTrue(p_model[self.C_TYPE] == 'account', + msg='SelfContact is not on top') + else: + self.assertTrue(p_model[self.C_TYPE] == 'group', + msg='Contact not found in a group') - def assert_account_is_in_roster(self, acc): - titerA = self.roster._get_account_iter(acc, model=self.roster.model) - self.assertTrue(self.roster.model.iter_is_valid(titerA), - msg='Account iter is invalid') + def assert_group_is_in_roster(self, group, account): + #TODO + pass - acc_model = self.roster.model[titerA] - self.assertEquals(acc_model[self.C_TYPE], 'account', - msg='No account found') + def assert_account_is_in_roster(self, acc): + titerA = self.roster._get_account_iter(acc, model=self.roster.model) + self.assertTrue(self.roster.model.iter_is_valid(titerA), + msg='Account iter is invalid') - if not self.roster.regroup: - self.assertEquals(acc_model[self.C_ACCOUNT], acc, - msg='Account not found') + acc_model = self.roster.model[titerA] + self.assertEquals(acc_model[self.C_TYPE], 'account', + msg='No account found') - self_jid = gajim.get_jid_from_account(acc) - self.assertEquals(acc_model[self.C_JID], self_jid, - msg='Account JID not found in account row') + if not self.roster.regroup: + self.assertEquals(acc_model[self.C_ACCOUNT], acc, + msg='Account not found') - def assert_model_is_in_sync(self): - #TODO: check that iter_n_children returns the correct numbers - pass + self_jid = gajim.get_jid_from_account(acc) + self.assertEquals(acc_model[self.C_JID], self_jid, + msg='Account JID not found in account row') - # tests - def test_fill_contacts_and_groups_dicts(self): - for acc in contacts: - self.roster.fill_contacts_and_groups_dicts(contacts[acc], acc) + def assert_model_is_in_sync(self): + #TODO: check that iter_n_children returns the correct numbers + pass - for jid in contacts[acc]: - instances = gajim.contacts.get_contacts(acc, jid) + # tests + def test_fill_contacts_and_groups_dicts(self): + for acc in contacts: + self.roster.fill_contacts_and_groups_dicts(contacts[acc], acc) - # Created a contact for each single jid? - self.assertTrue(len(instances) == 1) + for jid in contacts[acc]: + instances = gajim.contacts.get_contacts(acc, jid) - # Contacts kept their info - contact = instances[0] - self.assertEquals(contact.groups, contacts[acc][jid]['groups'], - msg='Group Missmatch') + # Created a contact for each single jid? + self.assertTrue(len(instances) == 1) - groups = contacts[acc][jid]['groups'] or ['General',] + # Contacts kept their info + contact = instances[0] + self.assertEquals(contact.groups, contacts[acc][jid]['groups'], + msg='Group Missmatch') - def test_fill_roster_model(self): - for acc in contacts: - self.roster.fill_contacts_and_groups_dicts(contacts[acc], acc) + groups = contacts[acc][jid]['groups'] or ['General',] - self.roster.add_account(acc) - self.assert_account_is_in_roster(acc) + def test_fill_roster_model(self): + for acc in contacts: + self.roster.fill_contacts_and_groups_dicts(contacts[acc], acc) - self.roster.add_account_contacts(acc) - self.assert_all_contacts_are_in_roster(acc) + self.roster.add_account(acc) + self.assert_account_is_in_roster(acc) - self.assert_model_is_in_sync() + self.roster.add_account_contacts(acc) + self.assert_all_contacts_are_in_roster(acc) + + self.assert_model_is_in_sync() class TestRosterWindowRegrouped(TestRosterWindow): - def setUp(self): - gajim.config.set('mergeaccounts', True) - TestRosterWindow.setUp(self) + def setUp(self): + gajim.config.set('mergeaccounts', True) + TestRosterWindow.setUp(self) - def test_toggle_regroup(self): - self.roster.regroup = not self.roster.regroup - self.roster.setup_and_draw_roster() - self.roster.regroup = not self.roster.regroup - self.roster.setup_and_draw_roster() + def test_toggle_regroup(self): + self.roster.regroup = not self.roster.regroup + self.roster.setup_and_draw_roster() + self.roster.regroup = not self.roster.regroup + self.roster.setup_and_draw_roster() class TestRosterWindowMetaContacts(TestRosterWindowRegrouped): - def test_receive_metacontact_data(self): - for complete_data in metacontact_data: - t_acc = complete_data[0]['account'] - t_jid = complete_data[0]['jid'] - data = complete_data[1:] - for brother in data: - acc = brother['account'] - jid = brother['jid'] - gajim.contacts.add_metacontact(t_acc, t_jid, acc, jid) - self.roster.setup_and_draw_roster() + def test_receive_metacontact_data(self): + for complete_data in metacontact_data: + t_acc = complete_data[0]['account'] + t_jid = complete_data[0]['jid'] + data = complete_data[1:] + for brother in data: + acc = brother['account'] + jid = brother['jid'] + gajim.contacts.add_metacontact(t_acc, t_jid, acc, jid) + self.roster.setup_and_draw_roster() - def test_connect_new_metacontact(self): - self.test_fill_roster_model() + def test_connect_new_metacontact(self): + self.test_fill_roster_model() - jid = u'coolstuff@gajim.org' - contact = gajim.contacts.create_contact(jid, account1) - gajim.contacts.add_contact(account1, contact) - self.roster.add_contact(jid, account1) - self.roster.chg_contact_status(contact, 'offline', '', account1) + jid = u'coolstuff@gajim.org' + contact = gajim.contacts.create_contact(jid, account1) + gajim.contacts.add_contact(account1, contact) + self.roster.add_contact(jid, account1) + self.roster.chg_contact_status(contact, 'offline', '', account1) - gajim.contacts.add_metacontact(account1, u'samejid@gajim.org', - account1, jid) - self.roster.chg_contact_status(contact, 'online', '', account1) + gajim.contacts.add_metacontact(account1, u'samejid@gajim.org', + account1, jid) + self.roster.chg_contact_status(contact, 'online', '', account1) - self.assert_model_is_in_sync() + self.assert_model_is_in_sync() if __name__ == '__main__': - unittest.main() - -# vim: se ts=3: + unittest.main() diff --git a/test/integration/test_xmpp_client_nb.py b/test/integration/test_xmpp_client_nb.py index 24a54a3ca..2efdcfe91 100644 --- a/test/integration/test_xmpp_client_nb.py +++ b/test/integration/test_xmpp_client_nb.py @@ -24,148 +24,146 @@ xmpp_server_port = ('gajim.org', 5222) credentials = ['unittest', 'testtest', 'res'] class TestNonBlockingClient(unittest.TestCase): - ''' - Test Cases class for NonBlockingClient. - ''' - def setUp(self): - ''' IdleQueue thread is run and dummy connection is created. ''' - self.idlequeue_thread = IdleQueueThread() - self.connection = MockConnection() # for dummy callbacks - self.idlequeue_thread.start() + ''' + Test Cases class for NonBlockingClient. + ''' + def setUp(self): + ''' IdleQueue thread is run and dummy connection is created. ''' + self.idlequeue_thread = IdleQueueThread() + self.connection = MockConnection() # for dummy callbacks + self.idlequeue_thread.start() - def tearDown(self): - ''' IdleQueue thread is stopped. ''' - self.idlequeue_thread.stop_thread() - self.idlequeue_thread.join() + def tearDown(self): + ''' IdleQueue thread is stopped. ''' + self.idlequeue_thread.stop_thread() + self.idlequeue_thread.join() - self.client = None + self.client = None - def open_stream(self, server_port, wrong_pass=False): - ''' - Method opening the XMPP connection. It returns when - is received from server. + def open_stream(self, server_port, wrong_pass=False): + ''' + Method opening the XMPP connection. It returns when + is received from server. - :param server_port: tuple of (hostname, port) for where the client should - connect. - ''' + :param server_port: tuple of (hostname, port) for where the client should + connect. + ''' - class TempConnection(): - def get_password(self, cb): - if wrong_pass: - cb('wrong pass') - else: - cb(credentials[1]) - def on_connect_failure(self): - pass + class TempConnection(): + def get_password(self, cb): + if wrong_pass: + cb('wrong pass') + else: + cb(credentials[1]) + def on_connect_failure(self): + pass - self.client = client_nb.NonBlockingClient( - domain=server_port[0], - idlequeue=self.idlequeue_thread.iq, - caller=Mock(realClass=TempConnection)) + self.client = client_nb.NonBlockingClient( + domain=server_port[0], + idlequeue=self.idlequeue_thread.iq, + caller=Mock(realClass=TempConnection)) - self.client.connect( - hostname=server_port[0], - port=server_port[1], - on_connect=lambda *args: self.connection.on_connect(True, *args), - on_connect_failure=lambda *args: self.connection.on_connect( - False, *args)) + self.client.connect( + hostname=server_port[0], + port=server_port[1], + on_connect=lambda *args: self.connection.on_connect(True, *args), + on_connect_failure=lambda *args: self.connection.on_connect( + False, *args)) - self.assert_(self.connection.wait(), - msg='waiting for callback from client constructor') - - # if on_connect was called, client has to be connected and vice versa - if self.connection.connect_succeeded: - self.assert_(self.client.get_connect_type()) - else: - self.assert_(not self.client.get_connect_type()) + self.assert_(self.connection.wait(), + msg='waiting for callback from client constructor') - def client_auth(self, username, password, resource, sasl): - ''' - Method authenticating connected client with supplied credentials. Returns - when authentication is over. + # if on_connect was called, client has to be connected and vice versa + if self.connection.connect_succeeded: + self.assert_(self.client.get_connect_type()) + else: + self.assert_(not self.client.get_connect_type()) - :param sasl: whether to use sasl (sasl=1) or old (sasl=0) authentication - :todo: to check and be more specific about when it returns - (bind, session..) - ''' - self.client.auth(username, password, resource, sasl, - on_auth=self.connection.on_auth) + def client_auth(self, username, password, resource, sasl): + ''' + Method authenticating connected client with supplied credentials. Returns + when authentication is over. - self.assert_(self.connection.wait(), msg='waiting for authentication') + :param sasl: whether to use sasl (sasl=1) or old (sasl=0) authentication + :todo: to check and be more specific about when it returns + (bind, session..) + ''' + self.client.auth(username, password, resource, sasl, + on_auth=self.connection.on_auth) - def do_disconnect(self): - ''' - Does disconnecting of connected client. Returns when TCP connection is - closed. - ''' - self.client.RegisterDisconnectHandler(self.connection.set_event) - self.client.disconnect() + self.assert_(self.connection.wait(), msg='waiting for authentication') - self.assertTrue(self.connection.wait(), msg='waiting for disconnecting') + def do_disconnect(self): + ''' + Does disconnecting of connected client. Returns when TCP connection is + closed. + ''' + self.client.RegisterDisconnectHandler(self.connection.set_event) + self.client.disconnect() - def test_proper_connect_sasl(self): - ''' - The ideal testcase - client is connected, authenticated with SASL and - then disconnected. - ''' - self.open_stream(xmpp_server_port) + self.assertTrue(self.connection.wait(), msg='waiting for disconnecting') - # if client is not connected, lets raise the AssertionError - self.assert_(self.client.get_connect_type()) - # client.disconnect() is already called from NBClient via - # _on_connected_failure, no need to call it here + def test_proper_connect_sasl(self): + ''' + The ideal testcase - client is connected, authenticated with SASL and + then disconnected. + ''' + self.open_stream(xmpp_server_port) - self.client_auth(credentials[0], credentials[1], credentials[2], sasl=1) - self.assert_(self.connection.con) - self.assert_(self.connection.auth=='sasl', msg='Unable to auth via SASL') + # if client is not connected, lets raise the AssertionError + self.assert_(self.client.get_connect_type()) + # client.disconnect() is already called from NBClient via + # _on_connected_failure, no need to call it here - self.do_disconnect() + self.client_auth(credentials[0], credentials[1], credentials[2], sasl=1) + self.assert_(self.connection.con) + self.assert_(self.connection.auth=='sasl', msg='Unable to auth via SASL') - def test_proper_connect_oldauth(self): - ''' - The ideal testcase - client is connected, authenticated with old auth and - then disconnected. - ''' - self.open_stream(xmpp_server_port) - self.assert_(self.client.get_connect_type()) - self.client_auth(credentials[0], credentials[1], credentials[2], sasl=0) - self.assert_(self.connection.con) - features = self.client.Dispatcher.Stream.features - if not features.getTag('auth'): - print "Server doesn't support old authentication type, ignoring test" - else: - self.assert_(self.connection.auth=='old_auth', - msg='Unable to auth via old_auth') - self.do_disconnect() + self.do_disconnect() - def test_connect_to_nonexisting_host(self): - ''' - Connect to nonexisting host. DNS request for A records should return - nothing. - ''' - self.open_stream(('fdsfsdf.fdsf.fss', 5222)) - self.assert_(not self.client.get_connect_type()) + def test_proper_connect_oldauth(self): + ''' + The ideal testcase - client is connected, authenticated with old auth and + then disconnected. + ''' + self.open_stream(xmpp_server_port) + self.assert_(self.client.get_connect_type()) + self.client_auth(credentials[0], credentials[1], credentials[2], sasl=0) + self.assert_(self.connection.con) + features = self.client.Dispatcher.Stream.features + if not features.getTag('auth'): + print "Server doesn't support old authentication type, ignoring test" + else: + self.assert_(self.connection.auth=='old_auth', + msg='Unable to auth via old_auth') + self.do_disconnect() - def test_connect_to_wrong_port(self): - ''' - Connect to nonexisting server. DNS request for A records should return an - IP but there shouldn't be XMPP server running on specified port. - ''' - self.open_stream((xmpp_server_port[0], 31337)) - self.assert_(not self.client.get_connect_type()) + def test_connect_to_nonexisting_host(self): + ''' + Connect to nonexisting host. DNS request for A records should return + nothing. + ''' + self.open_stream(('fdsfsdf.fdsf.fss', 5222)) + self.assert_(not self.client.get_connect_type()) - def test_connect_with_wrong_creds(self): - ''' - Connecting with invalid password. - ''' - self.open_stream(xmpp_server_port, wrong_pass=True) - self.assert_(self.client.get_connect_type()) - self.client_auth(credentials[0], 'wrong pass', credentials[2], sasl=1) - self.assert_(self.connection.auth is None) - self.do_disconnect() + def test_connect_to_wrong_port(self): + ''' + Connect to nonexisting server. DNS request for A records should return an + IP but there shouldn't be XMPP server running on specified port. + ''' + self.open_stream((xmpp_server_port[0], 31337)) + self.assert_(not self.client.get_connect_type()) + + def test_connect_with_wrong_creds(self): + ''' + Connecting with invalid password. + ''' + self.open_stream(xmpp_server_port, wrong_pass=True) + self.assert_(self.client.get_connect_type()) + self.client_auth(credentials[0], 'wrong pass', credentials[2], sasl=1) + self.assert_(self.connection.auth is None) + self.do_disconnect() if __name__ == '__main__': - unittest.main() - -# vim: se ts=3: + unittest.main() diff --git a/test/integration/test_xmpp_transports_nb.py b/test/integration/test_xmpp_transports_nb.py index ef9908903..98f2c87fc 100644 --- a/test/integration/test_xmpp_transports_nb.py +++ b/test/integration/test_xmpp_transports_nb.py @@ -14,265 +14,263 @@ from common.xmpp import transports_nb class AbstractTransportTest(unittest.TestCase): - ''' Encapsulates Idlequeue instantiation for transports and more...''' + ''' Encapsulates Idlequeue instantiation for transports and more...''' - def setUp(self): - ''' IdleQueue thread is run and dummy connection is created. ''' - self.idlequeue_thread = IdleQueueThread() - self.idlequeue_thread.start() - self._setup_hook() + def setUp(self): + ''' IdleQueue thread is run and dummy connection is created. ''' + self.idlequeue_thread = IdleQueueThread() + self.idlequeue_thread.start() + self._setup_hook() - def tearDown(self): - ''' IdleQueue thread is stopped. ''' - self._teardown_hook() - self.idlequeue_thread.stop_thread() - self.idlequeue_thread.join() + def tearDown(self): + ''' IdleQueue thread is stopped. ''' + self._teardown_hook() + self.idlequeue_thread.stop_thread() + self.idlequeue_thread.join() - def _setup_hook(self): - pass + def _setup_hook(self): + pass - def _teardown_hook(self): - pass + def _teardown_hook(self): + pass - def expect_receive(self, expected, count=1, msg=None): - ''' - Returns a callback function that will assert whether the data passed to - it equals the one specified when calling this function. + def expect_receive(self, expected, count=1, msg=None): + ''' + Returns a callback function that will assert whether the data passed to + it equals the one specified when calling this function. - Can be used to make sure transport dispatch correct data. - ''' - def receive(data, *args, **kwargs): - self.assertEqual(data, expected, msg=msg) - self._expected_count -= 1 - self._expected_count = count - return receive + Can be used to make sure transport dispatch correct data. + ''' + def receive(data, *args, **kwargs): + self.assertEqual(data, expected, msg=msg) + self._expected_count -= 1 + self._expected_count = count + return receive - def have_received_expected(self): - ''' - Plays together with expect_receive(). Will return true if expected_rcv - callback was called as often as specified - ''' - return self._expected_count == 0 + def have_received_expected(self): + ''' + Plays together with expect_receive(). Will return true if expected_rcv + callback was called as often as specified + ''' + return self._expected_count == 0 class TestNonBlockingTCP(AbstractTransportTest): - ''' - Test class for NonBlockingTCP. Will actually try to connect to an existing - XMPP server. - ''' - class MockClient(IdleMock): - ''' Simple client to test transport functionality ''' - def __init__(self, idlequeue, testcase): - self.idlequeue = idlequeue - self.testcase = testcase - IdleMock.__init__(self) + ''' + Test class for NonBlockingTCP. Will actually try to connect to an existing + XMPP server. + ''' + class MockClient(IdleMock): + ''' Simple client to test transport functionality ''' + def __init__(self, idlequeue, testcase): + self.idlequeue = idlequeue + self.testcase = testcase + IdleMock.__init__(self) - def do_connect(self, establish_tls=False, proxy_dict=None): - try: - ips = socket.getaddrinfo('gajim.org', 5222, - socket.AF_UNSPEC,socket.SOCK_STREAM) - ip = ips[0] - except socket.error, e: - self.testcase.fail(msg=str(e)) + def do_connect(self, establish_tls=False, proxy_dict=None): + try: + ips = socket.getaddrinfo('gajim.org', 5222, + socket.AF_UNSPEC, socket.SOCK_STREAM) + ip = ips[0] + except socket.error, e: + self.testcase.fail(msg=str(e)) - self.socket = transports_nb.NonBlockingTCP( - raise_event=lambda event_type, data: self.testcase.assertTrue( - event_type and data), - on_disconnect=lambda: self.on_success(mode='SocketDisconnect'), - idlequeue=self.idlequeue, - estabilish_tls=establish_tls, - certs=('../data/other/cacerts.pem', 'tmp/cacerts.pem'), - proxy_dict=proxy_dict) + self.socket = transports_nb.NonBlockingTCP( + raise_event=lambda event_type, data: self.testcase.assertTrue( + event_type and data), + on_disconnect=lambda: self.on_success(mode='SocketDisconnect'), + idlequeue=self.idlequeue, + estabilish_tls=establish_tls, + certs=('../data/other/cacerts.pem', 'tmp/cacerts.pem'), + proxy_dict=proxy_dict) - self.socket.PlugIn(self) + self.socket.PlugIn(self) - self.socket.connect(conn_5tuple=ip, - on_connect=lambda: self.on_success(mode='TCPconnect'), - on_connect_failure=self.on_failure) - self.testcase.assertTrue(self.wait(), msg='Connection timed out') + self.socket.connect(conn_5tuple=ip, + on_connect=lambda: self.on_success(mode='TCPconnect'), + on_connect_failure=self.on_failure) + self.testcase.assertTrue(self.wait(), msg='Connection timed out') - def do_disconnect(self): - self.socket.disconnect() - self.testcase.assertTrue(self.wait(), msg='Disconnect timed out') + def do_disconnect(self): + self.socket.disconnect() + self.testcase.assertTrue(self.wait(), msg='Disconnect timed out') - def on_failure(self, err_message): - self.set_event() - self.testcase.fail(msg=err_message) + def on_failure(self, err_message): + self.set_event() + self.testcase.fail(msg=err_message) - def on_success(self, mode, data=None): - if mode == "TCPconnect": - pass - if mode == "SocketDisconnect": - pass - self.set_event() + def on_success(self, mode, data=None): + if mode == "TCPconnect": + pass + if mode == "SocketDisconnect": + pass + self.set_event() - def _setup_hook(self): - self.client = self.MockClient(idlequeue=self.idlequeue_thread.iq, - testcase=self) - - def _teardown_hook(self): - if self.client.socket.state == 'CONNECTED': - self.client.do_disconnect() + def _setup_hook(self): + self.client = self.MockClient(idlequeue=self.idlequeue_thread.iq, + testcase=self) - def test_connect_disconnect_plain(self): - ''' Establish plain connection ''' - self.client.do_connect(establish_tls=False) - self.assertEquals(self.client.socket.state, 'CONNECTED') - self.client.do_disconnect() - self.assertEquals(self.client.socket.state, 'DISCONNECTED') - -# def test_connect_disconnect_ssl(self): -# ''' Establish SSL (not TLS) connection ''' -# self.client.do_connect(establish_tls=True) -# self.assertEquals(self.client.socket.state, 'CONNECTED') -# self.client.do_disconnect() -# self.assertEquals(self.client.socket.state, 'DISCONNECTED') + def _teardown_hook(self): + if self.client.socket.state == 'CONNECTED': + self.client.do_disconnect() - def test_do_receive(self): - ''' Test _do_receive method by overwriting socket.recv ''' - self.client.do_connect() - sock = self.client.socket + def test_connect_disconnect_plain(self): + ''' Establish plain connection ''' + self.client.do_connect(establish_tls=False) + self.assertEquals(self.client.socket.state, 'CONNECTED') + self.client.do_disconnect() + self.assertEquals(self.client.socket.state, 'DISCONNECTED') - # transport shall receive data - data = "Please don't fail" - sock._recv = lambda buffer: data - sock.onreceive(self.expect_receive(data)) - sock._do_receive() - self.assertTrue(self.have_received_expected(), msg='Did not receive data') - self.assert_(self.client.socket.state == 'CONNECTED') +# def test_connect_disconnect_ssl(self): +# ''' Establish SSL (not TLS) connection ''' +# self.client.do_connect(establish_tls=True) +# self.assertEquals(self.client.socket.state, 'CONNECTED') +# self.client.do_disconnect() +# self.assertEquals(self.client.socket.state, 'DISCONNECTED') - # transport shall do nothing as an non-fatal SSL is simulated - sock._recv = lambda buffer: None - sock.onreceive(self.assertFalse) # we did not receive anything... - sock._do_receive() - self.assert_(self.client.socket.state == 'CONNECTED') + def test_do_receive(self): + ''' Test _do_receive method by overwriting socket.recv ''' + self.client.do_connect() + sock = self.client.socket - # transport shall disconnect as remote side closed the connection - sock._recv = lambda buffer: '' - sock.onreceive(self.assertFalse) # we did not receive anything... - sock._do_receive() - self.assert_(self.client.socket.state == 'DISCONNECTED') + # transport shall receive data + data = "Please don't fail" + sock._recv = lambda buffer: data + sock.onreceive(self.expect_receive(data)) + sock._do_receive() + self.assertTrue(self.have_received_expected(), msg='Did not receive data') + self.assert_(self.client.socket.state == 'CONNECTED') - def test_do_send(self): - ''' Test _do_send method by overwriting socket.send ''' - self.client.do_connect() - sock = self.client.socket + # transport shall do nothing as an non-fatal SSL is simulated + sock._recv = lambda buffer: None + sock.onreceive(self.assertFalse) # we did not receive anything... + sock._do_receive() + self.assert_(self.client.socket.state == 'CONNECTED') - outgoing = [] # what we have actually send to our socket.socket - data_part1 = "Please don't " - data_part2 = "fail!" - data_complete = data_part1 + data_part2 - - # Simulate everything could be send in one go - def _send_all(data): - outgoing.append(data) - return len(data) - sock._send = _send_all - sock.send(data_part1) - sock.send(data_part2) - sock._do_send() - sock._do_send() - self.assertTrue(self.client.socket.state == 'CONNECTED') - self.assertTrue(data_part1 in outgoing and data_part2 in outgoing) - self.assertFalse(sock.sendqueue and sock.sendbuff, - msg='There is still unsend data in buffers') + # transport shall disconnect as remote side closed the connection + sock._recv = lambda buffer: '' + sock.onreceive(self.assertFalse) # we did not receive anything... + sock._do_receive() + self.assert_(self.client.socket.state == 'DISCONNECTED') - # Simulate data could only be sent in chunks - self.chunk_count = 0 - outgoing = [] - def _send_chunks(data): - if self.chunk_count == 0: - outgoing.append(data_part1) - self.chunk_count += 1 - return len(data_part1) - else: - outgoing.append(data_part2) - return len(data_part2) - sock._send = _send_chunks - sock.send(data_complete) - sock._do_send() # process first chunk - sock._do_send() # process the second one - self.assertTrue(self.client.socket.state == 'CONNECTED') - self.assertTrue(data_part1 in outgoing and data_part2 in outgoing) - self.assertFalse(sock.sendqueue and sock.sendbuff, - msg='There is still unsend data in buffers') + def test_do_send(self): + ''' Test _do_send method by overwriting socket.send ''' + self.client.do_connect() + sock = self.client.socket + + outgoing = [] # what we have actually send to our socket.socket + data_part1 = "Please don't " + data_part2 = "fail!" + data_complete = data_part1 + data_part2 + + # Simulate everything could be send in one go + def _send_all(data): + outgoing.append(data) + return len(data) + sock._send = _send_all + sock.send(data_part1) + sock.send(data_part2) + sock._do_send() + sock._do_send() + self.assertTrue(self.client.socket.state == 'CONNECTED') + self.assertTrue(data_part1 in outgoing and data_part2 in outgoing) + self.assertFalse(sock.sendqueue and sock.sendbuff, + msg='There is still unsend data in buffers') + + # Simulate data could only be sent in chunks + self.chunk_count = 0 + outgoing = [] + def _send_chunks(data): + if self.chunk_count == 0: + outgoing.append(data_part1) + self.chunk_count += 1 + return len(data_part1) + else: + outgoing.append(data_part2) + return len(data_part2) + sock._send = _send_chunks + sock.send(data_complete) + sock._do_send() # process first chunk + sock._do_send() # process the second one + self.assertTrue(self.client.socket.state == 'CONNECTED') + self.assertTrue(data_part1 in outgoing and data_part2 in outgoing) + self.assertFalse(sock.sendqueue and sock.sendbuff, + msg='There is still unsend data in buffers') class TestNonBlockingHTTP(AbstractTransportTest): - ''' Test class for NonBlockingHTTP transport''' + ''' Test class for NonBlockingHTTP transport''' - bosh_http_dict = { - 'http_uri': 'http://gajim.org:5280/http-bind', - 'http_version': 'HTTP/1.1', - 'http_persistent': True, - 'add_proxy_headers': False - } + bosh_http_dict = { + 'http_uri': 'http://gajim.org:5280/http-bind', + 'http_version': 'HTTP/1.1', + 'http_persistent': True, + 'add_proxy_headers': False + } - def _get_transport(self, http_dict, proxy_dict=None): - return transports_nb.NonBlockingHTTP( - raise_event=None, - on_disconnect=None, - idlequeue=self.idlequeue_thread.iq, - estabilish_tls=False, - certs=None, - on_http_request_possible=lambda: None, - on_persistent_fallback=None, - http_dict=http_dict, - proxy_dict=proxy_dict, - ) - - def test_parse_own_http_message(self): - ''' Build a HTTP message and try to parse it afterwards ''' - transport = self._get_transport(self.bosh_http_dict) + def _get_transport(self, http_dict, proxy_dict=None): + return transports_nb.NonBlockingHTTP( + raise_event=None, + on_disconnect=None, + idlequeue=self.idlequeue_thread.iq, + estabilish_tls=False, + certs=None, + on_http_request_possible=lambda: None, + on_persistent_fallback=None, + http_dict=http_dict, + proxy_dict=proxy_dict, + ) - data = "Please don't fail!" - http_message = transport.build_http_message(data) - statusline, headers, http_body, buffer_rest = transport.parse_http_message( - http_message) + def test_parse_own_http_message(self): + ''' Build a HTTP message and try to parse it afterwards ''' + transport = self._get_transport(self.bosh_http_dict) - self.assertFalse(bool(buffer_rest)) - self.assertTrue(statusline and isinstance(statusline, list)) - self.assertTrue(headers and isinstance(headers, dict)) - self.assertEqual(data, http_body, msg='Input and output are different') + data = "Please don't fail!" + http_message = transport.build_http_message(data) + statusline, headers, http_body, buffer_rest = transport.parse_http_message( + http_message) - def test_receive_http_message(self): - ''' Let _on_receive handle some http messages ''' - transport = self._get_transport(self.bosh_http_dict) - - header = ("HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\n" + - "Content-Length: 88\r\n\r\n") - payload = "Please don't fail!" - body = "%s" \ - % payload - message = "%s%s" % (header, body) + self.assertFalse(bool(buffer_rest)) + self.assertTrue(statusline and isinstance(statusline, list)) + self.assertTrue(headers and isinstance(headers, dict)) + self.assertEqual(data, http_body, msg='Input and output are different') - # try to receive in one go - transport.onreceive(self.expect_receive(body, msg='Failed: In one go')) - transport._on_receive(message) - self.assertTrue(self.have_received_expected(), msg='Failed: In one go') + def test_receive_http_message(self): + ''' Let _on_receive handle some http messages ''' + transport = self._get_transport(self.bosh_http_dict) - def test_receive_http_message_in_chunks(self): - ''' Let _on_receive handle some chunked http messages ''' - transport = self._get_transport(self.bosh_http_dict) + header = ("HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\n" + + "Content-Length: 88\r\n\r\n") + payload = "Please don't fail!" + body = "%s" \ + % payload + message = "%s%s" % (header, body) - payload = "Please don't fail!\n\n" - body = "%s" \ - % payload - header = "HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\n" +\ - "Content-Length: %i\r\n\r\n" % len(body) - message = "%s%s" % (header, body) + # try to receive in one go + transport.onreceive(self.expect_receive(body, msg='Failed: In one go')) + transport._on_receive(message) + self.assertTrue(self.have_received_expected(), msg='Failed: In one go') - chunk1, chunk2, chunk3, chunk4 = message[:20], message[20:73], \ - message[73:85], message[85:] - nextmessage_chunk = "\r\n\r\nHTTP/1.1 200 OK\r\nContent-Type: text/x" - chunks = (chunk1, chunk2, chunk3, chunk4, nextmessage_chunk) + def test_receive_http_message_in_chunks(self): + ''' Let _on_receive handle some chunked http messages ''' + transport = self._get_transport(self.bosh_http_dict) - transport.onreceive(self.expect_receive(body, msg='Failed: In chunks')) - for chunk in chunks: - transport._on_receive(chunk) - self.assertTrue(self.have_received_expected(), msg='Failed: In chunks') + payload = "Please don't fail!\n\n" + body = "%s" \ + % payload + header = "HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\n" +\ + "Content-Length: %i\r\n\r\n" % len(body) + message = "%s%s" % (header, body) + + chunk1, chunk2, chunk3, chunk4 = message[:20], message[20:73], \ + message[73:85], message[85:] + nextmessage_chunk = "\r\n\r\nHTTP/1.1 200 OK\r\nContent-Type: text/x" + chunks = (chunk1, chunk2, chunk3, chunk4, nextmessage_chunk) + + transport.onreceive(self.expect_receive(body, msg='Failed: In chunks')) + for chunk in chunks: + transport._on_receive(chunk) + self.assertTrue(self.have_received_expected(), msg='Failed: In chunks') if __name__ == '__main__': - unittest.main() - -# vim: se ts=3: + unittest.main() diff --git a/test/lib/__init__.py b/test/lib/__init__.py index 4b5f8ca12..2699dd648 100644 --- a/test/lib/__init__.py +++ b/test/lib/__init__.py @@ -7,8 +7,8 @@ shortargs = 'hnv:' longargs = 'help no-x verbose=' opts, args = getopt.getopt(sys.argv[1:], shortargs, longargs.split()) for o, a in opts: - if o in ('-n', '--no-x'): - use_x = False + if o in ('-n', '--no-x'): + use_x = False gajim_root = os.path.join(os.path.abspath(os.path.dirname(__file__)), '../..') @@ -25,28 +25,26 @@ import __builtin__ __builtin__._ = lambda x: x def setup_env(): - # wipe config directory - if os.path.isdir(configdir): - import shutil - shutil.rmtree(configdir) + # wipe config directory + if os.path.isdir(configdir): + import shutil + shutil.rmtree(configdir) - os.mkdir(configdir) + os.mkdir(configdir) - import common.configpaths - common.configpaths.gajimpaths.init(configdir) - common.configpaths.gajimpaths.init_profile() + import common.configpaths + common.configpaths.gajimpaths.init(configdir) + common.configpaths.gajimpaths.init_profile() - # for some reason common.gajim needs to be imported before xmpppy? - from common import gajim + # for some reason common.gajim needs to be imported before xmpppy? + from common import gajim - import logging - logging.basicConfig() + import logging + logging.basicConfig() - gajim.DATA_DIR = gajim_root + '/data' - gajim.use_x = use_x + gajim.DATA_DIR = gajim_root + '/data' + gajim.use_x = use_x - if use_x: - import gtkgui_helpers - gtkgui_helpers.GLADE_DIR = gajim_root + '/data/glade' - -# vim: se ts=3: + if use_x: + import gtkgui_helpers + gtkgui_helpers.GLADE_DIR = gajim_root + '/data/glade' diff --git a/test/lib/data.py b/test/lib/data.py index c713de0ac..af2a87057 100755 --- a/test/lib/data.py +++ b/test/lib/data.py @@ -5,75 +5,73 @@ account3 = u'dingdong.org' contacts = {} contacts[account1] = { - u'myjid@'+account1: { - 'ask': None, 'groups': [], 'name': None, 'resources': {}, - 'subscription': u'both'}, - u'default1@gajim.org': { - 'ask': None, 'groups': [], 'name': None, 'resources': {}, - 'subscription': u'both'}, - u'default2@gajim.org': { - 'ask': None, 'groups': [u'GroupA',], 'name': None, 'resources': {}, - 'subscription': u'both'}, - u'Cool"chârßéµö@gajim.org': { - 'ask': None, 'groups': [u'' % self.thread_id + def __repr__(self): + return '' % self.thread_id - def __nonzero__(self): - return True + def __nonzero__(self): + return True - def __eq__(self, other): - return self is other - -# vim: se ts=3: + def __eq__(self, other): + return self is other diff --git a/test/lib/mock.py b/test/lib/mock.py index 2c87d53f8..0dab5ef14 100644 --- a/test/lib/mock.py +++ b/test/lib/mock.py @@ -94,7 +94,7 @@ class Mock(object): self._setupSubclassMethodInterceptors() def _setupSubclassMethodInterceptors(self): - methods = inspect.getmembers(self.realClass,inspect.isroutine) + methods = inspect.getmembers(self.realClass, inspect.isroutine) baseMethods = dict(inspect.getmembers(Mock, inspect.ismethod)) for m in methods: name = m[0] @@ -109,7 +109,7 @@ class Mock(object): self.mockReturnValues.update(methodReturnValues) def mockSetExpectation(self, name, testFn, after=0, until=0): - self.mockExpectations.setdefault(name, []).append((testFn,after,until)) + self.mockExpectations.setdefault(name, []).append((testFn, after, until)) def _checkInterfaceCall(self, name, callParams, callKwParams): """ @@ -231,7 +231,7 @@ class MockCall: s = s + sep + repr(p) sep = ', ' items = sorted(self.kwparams.items()) - for k,v in items: + for k, v in items: s = s + sep + k + '=' + repr(v) sep = ', ' s = s + ')' @@ -252,7 +252,7 @@ class MockCallable: def __call__(self, *params, **kwparams): self.mock._checkInterfaceCall(self.name, params, kwparams) - thisCall = self.recordCall(params,kwparams) + thisCall = self.recordCall(params, kwparams) self.checkExpectations(thisCall, params, kwparams) return self.makeCall(params, kwparams) @@ -464,5 +464,3 @@ CALLABLE = callable - -# vim: se ts=3: diff --git a/test/lib/notify.py b/test/lib/notify.py index f14100af3..b13d03678 100644 --- a/test/lib/notify.py +++ b/test/lib/notify.py @@ -3,15 +3,13 @@ notifications = [] def notify(event, jid, account, parameters, advanced_notif_num = None): - notifications.append((event, jid, account, parameters, advanced_notif_num)) + notifications.append((event, jid, account, parameters, advanced_notif_num)) def get_advanced_notification(event, account, contact): - return None + return None def get_show_in_roster(event, account, contact, session = None): - return True + return True def get_show_in_systray(event, account, contact, type_ = None): - return True - -# vim: se ts=3: \ No newline at end of file + return True diff --git a/test/lib/xmpp_mocks.py b/test/lib/xmpp_mocks.py index bf60ae6dc..0b4055949 100644 --- a/test/lib/xmpp_mocks.py +++ b/test/lib/xmpp_mocks.py @@ -12,86 +12,84 @@ IDLEQUEUE_INTERVAL = 0.2 # polling interval. 200ms is used in Gajim as default IDLEMOCK_TIMEOUT = 30 # how long we wait for an event class IdleQueueThread(threading.Thread): - ''' - Thread for regular processing of idlequeue. - ''' - def __init__(self): - self.iq = idlequeue.IdleQueue() - self.stop = threading.Event() # Event to stop the thread main loop. - self.stop.clear() - threading.Thread.__init__(self) - - def run(self): - while not self.stop.isSet(): - self.iq.process() - time.sleep(IDLEQUEUE_INTERVAL) + ''' + Thread for regular processing of idlequeue. + ''' + def __init__(self): + self.iq = idlequeue.IdleQueue() + self.stop = threading.Event() # Event to stop the thread main loop. + self.stop.clear() + threading.Thread.__init__(self) + + def run(self): + while not self.stop.isSet(): + self.iq.process() + time.sleep(IDLEQUEUE_INTERVAL) + + def stop_thread(self): + self.stop.set() - def stop_thread(self): - self.stop.set() - class IdleMock: - ''' - Serves as template for testing objects that are normally controlled by GUI. - Allows to wait for asynchronous callbacks with wait() method. - ''' - def __init__(self): - self._event = threading.Event() - self._event.clear() + ''' + Serves as template for testing objects that are normally controlled by GUI. + Allows to wait for asynchronous callbacks with wait() method. + ''' + def __init__(self): + self._event = threading.Event() + self._event.clear() - def wait(self): - ''' - Block until some callback sets the event and clearing the event - subsequently. - Returns True if event was set, False on timeout - ''' - self._event.wait(IDLEMOCK_TIMEOUT) - if self._event.isSet(): - self._event.clear() - return True - else: - return False + def wait(self): + ''' + Block until some callback sets the event and clearing the event + subsequently. + Returns True if event was set, False on timeout + ''' + self._event.wait(IDLEMOCK_TIMEOUT) + if self._event.isSet(): + self._event.clear() + return True + else: + return False - def set_event(self): - self._event.set() + def set_event(self): + self._event.set() class MockConnection(IdleMock, Mock): - ''' - Class simulating Connection class from src/common/connection.py + ''' + Class simulating Connection class from src/common/connection.py - It is derived from Mock in order to avoid defining all methods - from real Connection that are called from NBClient or Dispatcher - ( _event_dispatcher for example) - ''' + It is derived from Mock in order to avoid defining all methods + from real Connection that are called from NBClient or Dispatcher + ( _event_dispatcher for example) + ''' - def __init__(self, *args): - self.connect_succeeded = True - IdleMock.__init__(self) - Mock.__init__(self, *args) + def __init__(self, *args): + self.connect_succeeded = True + IdleMock.__init__(self) + Mock.__init__(self, *args) - def on_connect(self, success, *args): - ''' - Method called after connecting - after receiving - from server (NOT after TLS stream restart) or connect failure - ''' - self.connect_succeeded = success - self.set_event() - + def on_connect(self, success, *args): + ''' + Method called after connecting - after receiving + from server (NOT after TLS stream restart) or connect failure + ''' + self.connect_succeeded = success + self.set_event() - def on_auth(self, con, auth): - ''' - Method called after authentication, regardless of the result. - :Parameters: - con : NonBlockingClient - reference to authenticated object - auth : string - type of authetication in case of success ('old_auth', 'sasl') or - None in case of auth failure - ''' - self.auth_connection = con - self.auth = auth - self.set_event() + def on_auth(self, con, auth): + ''' + Method called after authentication, regardless of the result. -# vim: se ts=3: + :Parameters: + con : NonBlockingClient + reference to authenticated object + auth : string + type of authetication in case of success ('old_auth', 'sasl') or + None in case of auth failure + ''' + self.auth_connection = con + self.auth = auth + self.set_event() diff --git a/test/runtests.py b/test/runtests.py index 4f8ebea30..c82ea8ffe 100755 --- a/test/runtests.py +++ b/test/runtests.py @@ -14,54 +14,52 @@ use_x = True verbose = 1 try: - shortargs = 'hnv:' - longargs = 'help no-x verbose=' - opts, args = getopt.getopt(sys.argv[1:], shortargs, longargs.split()) + shortargs = 'hnv:' + longargs = 'help no-x verbose=' + opts, args = getopt.getopt(sys.argv[1:], shortargs, longargs.split()) except getopt.error, msg: - print msg - print 'for help use --help' - sys.exit(2) + print msg + print 'for help use --help' + sys.exit(2) for o, a in opts: - if o in ('-h', '--help'): - print 'runtests [--help] [--no-x] [--verbose level]' - sys.exit() - elif o in ('-n', '--no-x'): - use_x = False - elif o in ('-v', '--verbose'): - try: - verbose = int(a) - except Exception: - print 'verbose must be a number >= 0' - sys.exit(2) + if o in ('-h', '--help'): + print 'runtests [--help] [--no-x] [--verbose level]' + sys.exit() + elif o in ('-n', '--no-x'): + use_x = False + elif o in ('-v', '--verbose'): + try: + verbose = int(a) + except Exception: + print 'verbose must be a number >= 0' + sys.exit(2) # new test modules need to be added manually modules = ( 'unit.test_xmpp_dispatcher_nb', - 'unit.test_xmpp_transports_nb', - 'unit.test_caps', - 'unit.test_contacts', - 'unit.test_gui_interface', - 'unit.test_sessions', - 'unit.test_account', - ) + 'unit.test_xmpp_transports_nb', + 'unit.test_caps', + 'unit.test_contacts', + 'unit.test_gui_interface', + 'unit.test_sessions', + 'unit.test_account', + ) #modules = () if use_x: - modules += ('integration.test_gui_event_integration', - 'integration.test_roster', - 'integration.test_resolver', - 'integration.test_xmpp_client_nb', - 'integration.test_xmpp_transports_nb' - ) + modules += ('integration.test_gui_event_integration', + 'integration.test_roster', + 'integration.test_resolver', + 'integration.test_xmpp_client_nb', + 'integration.test_xmpp_transports_nb' + ) nb_errors = 0 nb_failures = 0 for mod in modules: - suite = unittest.defaultTestLoader.loadTestsFromName(mod) - result = unittest.TextTestRunner(verbosity=verbose).run(suite) - nb_errors += len(result.errors) - nb_failures += len(result.failures) + suite = unittest.defaultTestLoader.loadTestsFromName(mod) + result = unittest.TextTestRunner(verbosity=verbose).run(suite) + nb_errors += len(result.errors) + nb_failures += len(result.failures) sys.exit(nb_errors + nb_failures) - -# vim: se ts=3: diff --git a/test/unit/__init__.py b/test/unit/__init__.py index 8e5f2ad7c..0252a7b2f 100644 --- a/test/unit/__init__.py +++ b/test/unit/__init__.py @@ -2,4 +2,4 @@ This package just contains plain unit tests -''' \ No newline at end of file +''' diff --git a/test/unit/test_account.py b/test/unit/test_account.py index d655aae2f..962403752 100644 --- a/test/unit/test_account.py +++ b/test/unit/test_account.py @@ -10,12 +10,12 @@ from common.account import Account class Test(unittest.TestCase): - def testInstantiate(self): - account = Account(name='MyAcc', contacts=None, gc_contacts=None) - - self.assertEquals('MyAcc', account.name) - self.assertTrue(account.gc_contacts is None) - self.assertTrue(account.contacts is None) + def testInstantiate(self): + account = Account(name='MyAcc', contacts=None, gc_contacts=None) + + self.assertEquals('MyAcc', account.name) + self.assertTrue(account.gc_contacts is None) + self.assertTrue(account.contacts is None) if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/test/unit/test_caps.py b/test/unit/test_caps.py index a143f225c..e788eeef5 100644 --- a/test/unit/test_caps.py +++ b/test/unit/test_caps.py @@ -12,141 +12,139 @@ from common.contacts import Contact from mock import Mock - -class CommonCapsTest(unittest.TestCase): - - def setUp(self): - self.caps_method = 'sha-1' - self.caps_hash = 'm3P2WeXPMGVH2tZPe7yITnfY0Dw=' - self.client_caps = (self.caps_method, self.caps_hash) - - self.node = "http://gajim.org" - self.identity = {'category': 'client', 'type': 'pc', 'name':'Gajim'} - self.identities = [self.identity] - self.features = [NS_MUC, NS_XHTML_IM] # NS_MUC not supported! - - # Simulate a filled db - db_caps_cache = [ - (self.caps_method, self.caps_hash, self.identities, self.features), - ('old', self.node + '#' + self.caps_hash, self.identities, self.features)] - self.logger = Mock(returnValues={"iter_caps_data":db_caps_cache}) - - self.cc = caps.CapsCache(self.logger) - caps.capscache = self.cc - - +class CommonCapsTest(unittest.TestCase): + + def setUp(self): + self.caps_method = 'sha-1' + self.caps_hash = 'm3P2WeXPMGVH2tZPe7yITnfY0Dw=' + self.client_caps = (self.caps_method, self.caps_hash) + + self.node = "http://gajim.org" + self.identity = {'category': 'client', 'type': 'pc', 'name':'Gajim'} + + self.identities = [self.identity] + self.features = [NS_MUC, NS_XHTML_IM] # NS_MUC not supported! + + # Simulate a filled db + db_caps_cache = [ + (self.caps_method, self.caps_hash, self.identities, self.features), + ('old', self.node + '#' + self.caps_hash, self.identities, self.features)] + self.logger = Mock(returnValues={"iter_caps_data":db_caps_cache}) + + self.cc = caps.CapsCache(self.logger) + caps.capscache = self.cc + + class TestCapsCache(CommonCapsTest): - - def test_set_retrieve(self): - ''' Test basic set / retrieve cycle ''' - self.cc[self.client_caps].identities = self.identities - self.cc[self.client_caps].features = self.features + def test_set_retrieve(self): + ''' Test basic set / retrieve cycle ''' - self.assert_(NS_MUC in self.cc[self.client_caps].features) - self.assert_(NS_PING not in self.cc[self.client_caps].features) + self.cc[self.client_caps].identities = self.identities + self.cc[self.client_caps].features = self.features - identities = self.cc[self.client_caps].identities + self.assert_(NS_MUC in self.cc[self.client_caps].features) + self.assert_(NS_PING not in self.cc[self.client_caps].features) - self.assertEqual(1, len(identities)) + identities = self.cc[self.client_caps].identities - identity = identities[0] - self.assertEqual('client', identity['category']) - self.assertEqual('pc', identity['type']) - - def test_set_and_store(self): - ''' Test client_caps update gets logged into db ''' - - item = self.cc[self.client_caps] - item.set_and_store(self.identities, self.features) - - self.logger.mockCheckCall(0, "add_caps_entry", self.caps_method, - self.caps_hash, self.identities, self.features) - - def test_initialize_from_db(self): - ''' Read cashed dummy data from db ''' - self.assertEqual(self.cc[self.client_caps].status, caps.NEW) - self.cc.initialize_from_db() - self.assertEqual(self.cc[self.client_caps].status, caps.CACHED) + self.assertEqual(1, len(identities)) - def test_preload_triggering_query(self): - ''' Make sure that preload issues a disco ''' - connection = Mock() - client_caps = caps.ClientCaps(self.caps_hash, self.node, self.caps_method) - - self.cc.query_client_of_jid_if_unknown(connection, "test@gajim.org", - client_caps) - - self.assertEqual(1, len(connection.mockGetAllCalls())) - - def test_no_preload_query_if_cashed(self): - ''' Preload must not send a query if the data is already cached ''' - connection = Mock() - client_caps = caps.ClientCaps(self.caps_hash, self.node, self.caps_method) + identity = identities[0] + self.assertEqual('client', identity['category']) + self.assertEqual('pc', identity['type']) - self.cc.initialize_from_db() - self.cc.query_client_of_jid_if_unknown(connection, "test@gajim.org", - client_caps) - - self.assertEqual(0, len(connection.mockGetAllCalls())) - - def test_hash(self): - '''tests the hash computation''' - computed_hash = caps.compute_caps_hash(self.identities, self.features) - self.assertEqual(self.caps_hash, computed_hash) + def test_set_and_store(self): + ''' Test client_caps update gets logged into db ''' + + item = self.cc[self.client_caps] + item.set_and_store(self.identities, self.features) + + self.logger.mockCheckCall(0, "add_caps_entry", self.caps_method, + self.caps_hash, self.identities, self.features) + + def test_initialize_from_db(self): + ''' Read cashed dummy data from db ''' + self.assertEqual(self.cc[self.client_caps].status, caps.NEW) + self.cc.initialize_from_db() + self.assertEqual(self.cc[self.client_caps].status, caps.CACHED) + + def test_preload_triggering_query(self): + ''' Make sure that preload issues a disco ''' + connection = Mock() + client_caps = caps.ClientCaps(self.caps_hash, self.node, self.caps_method) + + self.cc.query_client_of_jid_if_unknown(connection, "test@gajim.org", + client_caps) + + self.assertEqual(1, len(connection.mockGetAllCalls())) + + def test_no_preload_query_if_cashed(self): + ''' Preload must not send a query if the data is already cached ''' + connection = Mock() + client_caps = caps.ClientCaps(self.caps_hash, self.node, self.caps_method) + + self.cc.initialize_from_db() + self.cc.query_client_of_jid_if_unknown(connection, "test@gajim.org", + client_caps) + + self.assertEqual(0, len(connection.mockGetAllCalls())) + + def test_hash(self): + '''tests the hash computation''' + computed_hash = caps.compute_caps_hash(self.identities, self.features) + self.assertEqual(self.caps_hash, computed_hash) class TestClientCaps(CommonCapsTest): - - def setUp(self): - CommonCapsTest.setUp(self) - self.client_caps = caps.ClientCaps(self.caps_hash, self.node, self.caps_method) - - def test_query_by_get_discover_strategy(self): - ''' Client must be queried if the data is unkown ''' - connection = Mock() - discover = self.client_caps.get_discover_strategy() - discover(connection, "test@gajim.org") - - connection.mockCheckCall(0, "discoverInfo", "test@gajim.org", - "http://gajim.org#m3P2WeXPMGVH2tZPe7yITnfY0Dw=") - - def test_client_supports(self): - self.assertTrue(caps.client_supports(self.client_caps, NS_PING), - msg="Assume supported, if we don't have caps") - - self.assertFalse(caps.client_supports(self.client_caps, NS_XHTML_IM), - msg="Must not assume blacklisted feature is supported on default") - - self.cc.initialize_from_db() - - self.assertFalse(caps.client_supports(self.client_caps, NS_PING), - msg="Must return false on unsupported feature") - - self.assertTrue(caps.client_supports(self.client_caps, NS_XHTML_IM), - msg="Must return True on supported feature") - - self.assertTrue(caps.client_supports(self.client_caps, NS_MUC), - msg="Must return True on supported feature") - -class TestOldClientCaps(TestClientCaps): + def setUp(self): + CommonCapsTest.setUp(self) + self.client_caps = caps.ClientCaps(self.caps_hash, self.node, self.caps_method) - def setUp(self): - TestClientCaps.setUp(self) - self.client_caps = caps.OldClientCaps(self.caps_hash, self.node) - - def test_query_by_get_discover_strategy(self): - ''' Client must be queried if the data is unknown ''' - connection = Mock() - discover = self.client_caps.get_discover_strategy() - discover(connection, "test@gajim.org") - - connection.mockCheckCall(0, "discoverInfo", "test@gajim.org") + def test_query_by_get_discover_strategy(self): + ''' Client must be queried if the data is unkown ''' + connection = Mock() + discover = self.client_caps.get_discover_strategy() + discover(connection, "test@gajim.org") + + connection.mockCheckCall(0, "discoverInfo", "test@gajim.org", + "http://gajim.org#m3P2WeXPMGVH2tZPe7yITnfY0Dw=") + + def test_client_supports(self): + self.assertTrue(caps.client_supports(self.client_caps, NS_PING), + msg="Assume supported, if we don't have caps") + + self.assertFalse(caps.client_supports(self.client_caps, NS_XHTML_IM), + msg="Must not assume blacklisted feature is supported on default") + + self.cc.initialize_from_db() + + self.assertFalse(caps.client_supports(self.client_caps, NS_PING), + msg="Must return false on unsupported feature") + + self.assertTrue(caps.client_supports(self.client_caps, NS_XHTML_IM), + msg="Must return True on supported feature") + + self.assertTrue(caps.client_supports(self.client_caps, NS_MUC), + msg="Must return True on supported feature") + + +class TestOldClientCaps(TestClientCaps): + + def setUp(self): + TestClientCaps.setUp(self) + self.client_caps = caps.OldClientCaps(self.caps_hash, self.node) + + def test_query_by_get_discover_strategy(self): + ''' Client must be queried if the data is unknown ''' + connection = Mock() + discover = self.client_caps.get_discover_strategy() + discover(connection, "test@gajim.org") + + connection.mockCheckCall(0, "discoverInfo", "test@gajim.org") if __name__ == '__main__': - unittest.main() - -# vim: se ts=3: + unittest.main() diff --git a/test/unit/test_contacts.py b/test/unit/test_contacts.py index 3cd4d5110..2f94b3a38 100644 --- a/test/unit/test_contacts.py +++ b/test/unit/test_contacts.py @@ -12,118 +12,118 @@ from common.xmpp import NS_MUC from common import caps class TestCommonContact(unittest.TestCase): - - def setUp(self): - self.contact = CommonContact(jid='', account="", resource='', show='', - status='', name='', our_chatstate=None, composing_xep=None, - chatstate=None, client_caps=None) - def test_default_client_supports(self): - ''' - Test the caps support method of contacts. - See test_caps for more enhanced tests. - ''' - caps.capscache = caps.CapsCache() - self.assertTrue(self.contact.supports(NS_MUC), - msg="Must not backtrace on simple check for supported feature") - - self.contact.client_caps = caps.NullClientCaps() - - self.assertTrue(self.contact.supports(NS_MUC), - msg="Must not backtrace on simple check for supported feature") - - + def setUp(self): + self.contact = CommonContact(jid='', account="", resource='', show='', + status='', name='', our_chatstate=None, composing_xep=None, + chatstate=None, client_caps=None) + + def test_default_client_supports(self): + ''' + Test the caps support method of contacts. + See test_caps for more enhanced tests. + ''' + caps.capscache = caps.CapsCache() + self.assertTrue(self.contact.supports(NS_MUC), + msg="Must not backtrace on simple check for supported feature") + + self.contact.client_caps = caps.NullClientCaps() + + self.assertTrue(self.contact.supports(NS_MUC), + msg="Must not backtrace on simple check for supported feature") + + class TestContact(TestCommonContact): - - def setUp(self): - TestCommonContact.setUp(self) - self.contact = Contact(jid="test@gajim.org", account="account") - def test_attributes_available(self): - '''This test supports the migration from the old to the new contact - domain model by smoke testing that no attribute values are lost''' - - attributes = ["jid", "resource", "show", "status", "name", "our_chatstate", - "composing_xep", "chatstate", "client_caps", "priority", "sub"] - for attr in attributes: - self.assertTrue(hasattr(self.contact, attr), msg="expected: " + attr) - + def setUp(self): + TestCommonContact.setUp(self) + self.contact = Contact(jid="test@gajim.org", account="account") + + def test_attributes_available(self): + '''This test supports the migration from the old to the new contact + domain model by smoke testing that no attribute values are lost''' + + attributes = ["jid", "resource", "show", "status", "name", "our_chatstate", + "composing_xep", "chatstate", "client_caps", "priority", "sub"] + for attr in attributes: + self.assertTrue(hasattr(self.contact, attr), msg="expected: " + attr) + class TestGC_Contact(TestCommonContact): - def setUp(self): - TestCommonContact.setUp(self) - self.contact = GC_Contact(room_jid="confernce@gajim.org", account="account") - - def test_attributes_available(self): - '''This test supports the migration from the old to the new contact - domain model by asserting no attributes have been lost''' - - attributes = ["jid", "resource", "show", "status", "name", "our_chatstate", - "composing_xep", "chatstate", "client_caps", "role", "room_jid"] - for attr in attributes: - self.assertTrue(hasattr(self.contact, attr), msg="expected: " + attr) - - -class TestContacts(unittest.TestCase): - - def setUp(self): - self.contacts = Contacts() - - def test_create_add_get_contact(self): - jid = 'test@gajim.org' - account = "account" - - contact = self.contacts.create_contact(jid=jid, account=account) - self.contacts.add_contact(account, contact) - - retrieved_contact = self.contacts.get_contact(account, jid) - self.assertEqual(contact, retrieved_contact, "Contact must be known") - - self.contacts.remove_contact(account, contact) + def setUp(self): + TestCommonContact.setUp(self) + self.contact = GC_Contact(room_jid="confernce@gajim.org", account="account") + + def test_attributes_available(self): + '''This test supports the migration from the old to the new contact + domain model by asserting no attributes have been lost''' + + attributes = ["jid", "resource", "show", "status", "name", "our_chatstate", + "composing_xep", "chatstate", "client_caps", "role", "room_jid"] + for attr in attributes: + self.assertTrue(hasattr(self.contact, attr), msg="expected: " + attr) + + +class TestContacts(unittest.TestCase): + + def setUp(self): + self.contacts = Contacts() + + def test_create_add_get_contact(self): + jid = 'test@gajim.org' + account = "account" + + contact = self.contacts.create_contact(jid=jid, account=account) + self.contacts.add_contact(account, contact) + + retrieved_contact = self.contacts.get_contact(account, jid) + self.assertEqual(contact, retrieved_contact, "Contact must be known") + + self.contacts.remove_contact(account, contact) + + retrieved_contact = self.contacts.get_contact(account, jid) + self.assertNotEqual(contact, retrieved_contact, + msg="Contact must not be known any longer") + + + def test_copy_contact(self): + jid = 'test@gajim.org' + account = "account" + + contact = self.contacts.create_contact(jid=jid, account=account) + copy = self.contacts.copy_contact(contact) + self.assertFalse(contact is copy, msg="Must not be the same") + + # Not yet implemented to remain backwart compatible + # self.assertEqual(contact, copy, msg="Must be equal") + + def test_legacy_accounts_handling(self): + self.contacts.add_account("one") + self.contacts.add_account("two") + + self.contacts.change_account_name("two", "old") + self.contacts.remove_account("one") + + self.assertEqual(["old"], self.contacts.get_accounts()) + + def test_legacy_contacts_from_groups(self): + jid1 = "test1@gajim.org" + jid2 = "test2@gajim.org" + account = "account" + group = "GroupA" + + contact1 = self.contacts.create_contact(jid=jid1, account=account, + groups=[group]) + self.contacts.add_contact(account, contact1) + + contact2 = self.contacts.create_contact(jid=jid2, account=account, + groups=[group]) + self.contacts.add_contact(account, contact2) + + self.assertEqual(2, len(self.contacts.get_contacts_from_group(account, group))) + self.assertEqual(0, len(self.contacts.get_contacts_from_group(account, ''))) + - retrieved_contact = self.contacts.get_contact(account, jid) - self.assertNotEqual(contact, retrieved_contact, - msg="Contact must not be known any longer") - - - def test_copy_contact(self): - jid = 'test@gajim.org' - account = "account" - - contact = self.contacts.create_contact(jid=jid, account=account) - copy = self.contacts.copy_contact(contact) - self.assertFalse(contact is copy, msg="Must not be the same") - - # Not yet implemented to remain backwart compatible - # self.assertEqual(contact, copy, msg="Must be equal") - - def test_legacy_accounts_handling(self): - self.contacts.add_account("one") - self.contacts.add_account("two") - - self.contacts.change_account_name("two", "old") - self.contacts.remove_account("one") - - self.assertEqual(["old"], self.contacts.get_accounts()) - - def test_legacy_contacts_from_groups(self): - jid1 = "test1@gajim.org" - jid2 = "test2@gajim.org" - account = "account" - group = "GroupA" - - contact1 = self.contacts.create_contact(jid=jid1, account=account, - groups=[group]) - self.contacts.add_contact(account, contact1) - - contact2 = self.contacts.create_contact(jid=jid2, account=account, - groups=[group]) - self.contacts.add_contact(account, contact2) - - self.assertEqual(2, len(self.contacts.get_contacts_from_group(account, group))) - self.assertEqual(0, len(self.contacts.get_contacts_from_group(account, ''))) - - if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/test/unit/test_gui_interface.py b/test/unit/test_gui_interface.py index 57b91ad4b..1a5f4b90f 100644 --- a/test/unit/test_gui_interface.py +++ b/test/unit/test_gui_interface.py @@ -6,7 +6,7 @@ lib.setup_env() from common import logging_helpers logging_helpers.set_quiet() -from common import gajim +from common import gajim from gajim_mocks import MockLogger gajim.logger = MockLogger() @@ -15,98 +15,98 @@ from gui_interface import Interface class Test(unittest.TestCase): - def test_instantiation(self): - ''' Test that we can proper initialize and do not fail on globals ''' - interface = Interface() - interface.run() - - def test_dispatch(self): - ''' Test dispatcher forwarding network events to handler_* methods ''' - sut = Interface() - - success = sut.dispatch('No Such Event', None, None) - self.assertFalse(success, msg="Unexisting event handled") - - success = sut.dispatch('STANZA_ARRIVED', None, None) - self.assertTrue(success, msg="Existing event must be handled") - - def test_register_unregister_single_handler(self): - ''' Register / Unregister a custom event handler ''' - sut = Interface() - event = 'TESTS_ARE_COOL_EVENT' - - self.called = False - def handler(account, data): - self.assertEqual(account, 'account') - self.assertEqual(data, 'data') - self.called = True - - self.assertFalse(self.called) - sut.register_handler('TESTS_ARE_COOL_EVENT', handler) - sut.dispatch(event, 'account', 'data') - self.assertTrue(self.called, msg="Handler should have been called") + def test_instantiation(self): + ''' Test that we can proper initialize and do not fail on globals ''' + interface = Interface() + interface.run() - self.called = False - sut.unregister_handler('TESTS_ARE_COOL_EVENT', handler) - sut.dispatch(event, 'account', 'data') - self.assertFalse(self.called, msg="Handler should no longer be called") - - - def test_dispatch_to_multiple_handlers(self): - ''' Register and dispatch a single event to multiple handlers ''' - sut = Interface() - event = 'SINGLE_EVENT' - - self.called_a = False - self.called_b = False - - def handler_a(account, data): - self.assertFalse(self.called_a, msg="One must only be notified once") - self.called_a = True - - def handler_b(account, data): - self.assertFalse(self.called_b, msg="One must only be notified once") - self.called_b = True - - sut.register_handler(event, handler_a) - sut.register_handler(event, handler_b) - - # register again - sut.register_handler('SOME_OTHER_EVENT', handler_b) - sut.register_handler(event, handler_a) - - sut.dispatch(event, 'account', 'data') - self.assertTrue(self.called_a and self.called_b, - msg="Both handlers should have been called") + def test_dispatch(self): + ''' Test dispatcher forwarding network events to handler_* methods ''' + sut = Interface() - def test_links_regexp_entire(self): - sut = Interface() - def assert_matches_all(str_): - m = sut.basic_pattern_re.match(str_) + success = sut.dispatch('No Such Event', None, None) + self.assertFalse(success, msg="Unexisting event handled") - # the match should equal the string - str_span = (0, len(str_)) - self.assertEqual(m.span(), str_span) + success = sut.dispatch('STANZA_ARRIVED', None, None) + self.assertTrue(success, msg="Existing event must be handled") - # these entire strings should be parsed as links - assert_matches_all('http://google.com/') - assert_matches_all('http://google.com') - assert_matches_all('http://www.google.ca/search?q=xmpp') + def test_register_unregister_single_handler(self): + ''' Register / Unregister a custom event handler ''' + sut = Interface() + event = 'TESTS_ARE_COOL_EVENT' - assert_matches_all('http://tools.ietf.org/html/draft-saintandre-rfc3920bis-05#section-12.3') + self.called = False + def handler(account, data): + self.assertEqual(account, 'account') + self.assertEqual(data, 'data') + self.called = True - assert_matches_all('http://en.wikipedia.org/wiki/Protocol_(computing)') - assert_matches_all( - 'http://en.wikipedia.org/wiki/Protocol_%28computing%29') + self.assertFalse(self.called) + sut.register_handler('TESTS_ARE_COOL_EVENT', handler) + sut.dispatch(event, 'account', 'data') + self.assertTrue(self.called, msg="Handler should have been called") - assert_matches_all('mailto:test@example.org') + self.called = False + sut.unregister_handler('TESTS_ARE_COOL_EVENT', handler) + sut.dispatch(event, 'account', 'data') + self.assertFalse(self.called, msg="Handler should no longer be called") - assert_matches_all('xmpp:example-node@example.com') - assert_matches_all('xmpp:example-node@example.com/some-resource') - assert_matches_all('xmpp:example-node@example.com?message') - assert_matches_all('xmpp://guest@example.com/support@example.com?message') + + def test_dispatch_to_multiple_handlers(self): + ''' Register and dispatch a single event to multiple handlers ''' + sut = Interface() + event = 'SINGLE_EVENT' + + self.called_a = False + self.called_b = False + + def handler_a(account, data): + self.assertFalse(self.called_a, msg="One must only be notified once") + self.called_a = True + + def handler_b(account, data): + self.assertFalse(self.called_b, msg="One must only be notified once") + self.called_b = True + + sut.register_handler(event, handler_a) + sut.register_handler(event, handler_b) + + # register again + sut.register_handler('SOME_OTHER_EVENT', handler_b) + sut.register_handler(event, handler_a) + + sut.dispatch(event, 'account', 'data') + self.assertTrue(self.called_a and self.called_b, + msg="Both handlers should have been called") + + def test_links_regexp_entire(self): + sut = Interface() + def assert_matches_all(str_): + m = sut.basic_pattern_re.match(str_) + + # the match should equal the string + str_span = (0, len(str_)) + self.assertEqual(m.span(), str_span) + + # these entire strings should be parsed as links + assert_matches_all('http://google.com/') + assert_matches_all('http://google.com') + assert_matches_all('http://www.google.ca/search?q=xmpp') + + assert_matches_all('http://tools.ietf.org/html/draft-saintandre-rfc3920bis-05#section-12.3') + + assert_matches_all('http://en.wikipedia.org/wiki/Protocol_(computing)') + assert_matches_all( + 'http://en.wikipedia.org/wiki/Protocol_%28computing%29') + + assert_matches_all('mailto:test@example.org') + + assert_matches_all('xmpp:example-node@example.com') + assert_matches_all('xmpp:example-node@example.com/some-resource') + assert_matches_all('xmpp:example-node@example.com?message') + assert_matches_all('xmpp://guest@example.com/support@example.com?message') if __name__ == "__main__": - #import sys;sys.argv = ['', 'Test.test'] - unittest.main() \ No newline at end of file + #import sys;sys.argv = ['', 'Test.test'] + unittest.main() diff --git a/test/unit/test_sessions.py b/test/unit/test_sessions.py index 724bac56d..05a4351a7 100644 --- a/test/unit/test_sessions.py +++ b/test/unit/test_sessions.py @@ -24,191 +24,189 @@ gajim.interface = MockInterface() account_name = 'test' class TestStanzaSession(unittest.TestCase): - ''' Testclass for common/stanzasession.py ''' + ''' Testclass for common/stanzasession.py ''' - def setUp(self): - self.jid = 'test@example.org/Gajim' - self.conn = MockConnection(account_name, {'send_stanza': None}) - self.sess = StanzaSession(self.conn, self.jid, None, 'chat') + def setUp(self): + self.jid = 'test@example.org/Gajim' + self.conn = MockConnection(account_name, {'send_stanza': None}) + self.sess = StanzaSession(self.conn, self.jid, None, 'chat') - def test_generate_thread_id(self): - # thread_id is a string - self.assert_(isinstance(self.sess.thread_id, str)) + def test_generate_thread_id(self): + # thread_id is a string + self.assert_(isinstance(self.sess.thread_id, str)) - # it should be somewhat long, to avoid clashes - self.assert_(len(self.sess.thread_id) >= 32) + # it should be somewhat long, to avoid clashes + self.assert_(len(self.sess.thread_id) >= 32) - def test_is_loggable(self): - # by default a session should be loggable - # (unless the no_log_for setting says otherwise) - self.assert_(self.sess.is_loggable()) + def test_is_loggable(self): + # by default a session should be loggable + # (unless the no_log_for setting says otherwise) + self.assert_(self.sess.is_loggable()) - def test_terminate(self): - # termination is sent by default - self.sess.last_send = time.time() - self.sess.terminate() + def test_terminate(self): + # termination is sent by default + self.sess.last_send = time.time() + self.sess.terminate() - self.assertEqual(None, self.sess.status) + self.assertEqual(None, self.sess.status) - calls = self.conn.mockGetNamedCalls('send_stanza') - msg = calls[0].getParam(0) + calls = self.conn.mockGetNamedCalls('send_stanza') + msg = calls[0].getParam(0) - self.assertEqual(msg.getThread(), self.sess.thread_id) + self.assertEqual(msg.getThread(), self.sess.thread_id) - def test_terminate_without_sending(self): - # no termination is sent if no messages have been sent in the session - self.sess.terminate() + def test_terminate_without_sending(self): + # no termination is sent if no messages have been sent in the session + self.sess.terminate() - self.assertEqual(None, self.sess.status) + self.assertEqual(None, self.sess.status) - calls = self.conn.mockGetNamedCalls('send_stanza') - self.assertEqual(0, len(calls)) + calls = self.conn.mockGetNamedCalls('send_stanza') + self.assertEqual(0, len(calls)) - def test_terminate_no_remote_xep_201(self): - # no termination is sent if only messages without thread ids have been - # received - self.sess.last_send = time.time() - self.sess.last_receive = time.time() - self.sess.terminate() + def test_terminate_no_remote_xep_201(self): + # no termination is sent if only messages without thread ids have been + # received + self.sess.last_send = time.time() + self.sess.last_receive = time.time() + self.sess.terminate() - self.assertEqual(None, self.sess.status) + self.assertEqual(None, self.sess.status) - calls = self.conn.mockGetNamedCalls('send_stanza') - self.assertEqual(0, len(calls)) + calls = self.conn.mockGetNamedCalls('send_stanza') + self.assertEqual(0, len(calls)) class TestChatControlSession(unittest.TestCase): - ''' Testclass for session.py ''' + ''' Testclass for session.py ''' - def setUp(self): - self.jid = 'test@example.org/Gajim' - self.conn = MockConnection(account_name, {'send_stanza': None}) - self.sess = ChatControlSession(self.conn, self.jid, None) - gajim.logger = MockLogger() + def setUp(self): + self.jid = 'test@example.org/Gajim' + self.conn = MockConnection(account_name, {'send_stanza': None}) + self.sess = ChatControlSession(self.conn, self.jid, None) + gajim.logger = MockLogger() - # initially there are no events - self.assertEqual(0, len(gajim.events.get_events(account_name))) + # initially there are no events + self.assertEqual(0, len(gajim.events.get_events(account_name))) - # no notifications have been sent - self.assertEqual(0, len(notify.notifications)) + # no notifications have been sent + self.assertEqual(0, len(notify.notifications)) - def tearDown(self): - # remove all events and notifications that were added - gajim.events._events = {} - notify.notifications = [] + def tearDown(self): + # remove all events and notifications that were added + gajim.events._events = {} + notify.notifications = [] - def receive_chat_msg(self, jid, msgtxt): - '''simulate receiving a chat message from jid''' - msg = xmpp.Message() - msg.setBody(msgtxt) - msg.setType('chat') + def receive_chat_msg(self, jid, msgtxt): + '''simulate receiving a chat message from jid''' + msg = xmpp.Message() + msg.setBody(msgtxt) + msg.setType('chat') - tim = time.localtime() - encrypted = False - self.sess.received(jid, msgtxt, tim, encrypted, msg) + tim = time.localtime() + encrypted = False + self.sess.received(jid, msgtxt, tim, encrypted, msg) - # ----- custom assertions ----- - def assert_new_message_notification(self): - '''a new_message notification has been sent''' - self.assertEqual(1, len(notify.notifications)) - notif = notify.notifications[0] - self.assertEqual('new_message', notif[0]) + # ----- custom assertions ----- + def assert_new_message_notification(self): + '''a new_message notification has been sent''' + self.assertEqual(1, len(notify.notifications)) + notif = notify.notifications[0] + self.assertEqual('new_message', notif[0]) - def assert_first_message_notification(self): - '''this message was treated as a first message''' - self.assert_new_message_notification() - notif = notify.notifications[0] - params = notif[3] - first = params[1] - self.assert_(first, 'message should have been treated as a first message') + def assert_first_message_notification(self): + '''this message was treated as a first message''' + self.assert_new_message_notification() + notif = notify.notifications[0] + params = notif[3] + first = params[1] + self.assert_(first, 'message should have been treated as a first message') - def assert_not_first_message_notification(self): - '''this message was not treated as a first message''' - self.assert_new_message_notification() - notif = notify.notifications[0] - params = notif[3] - first = params[1] - self.assert_(not first, - 'message was unexpectedly treated as a first message') + def assert_not_first_message_notification(self): + '''this message was not treated as a first message''' + self.assert_new_message_notification() + notif = notify.notifications[0] + params = notif[3] + first = params[1] + self.assert_(not first, + 'message was unexpectedly treated as a first message') - # ----- tests ----- - def test_receive_nocontrol(self): - '''test receiving a message in a blank state''' - jid = 'bct@necronomicorp.com/Gajim' - msgtxt = 'testing one two three' + # ----- tests ----- + def test_receive_nocontrol(self): + '''test receiving a message in a blank state''' + jid = 'bct@necronomicorp.com/Gajim' + msgtxt = 'testing one two three' - self.receive_chat_msg(jid, msgtxt) + self.receive_chat_msg(jid, msgtxt) - # message was logged - calls = gajim.logger.mockGetNamedCalls('write') - self.assertEqual(1, len(calls)) + # message was logged + calls = gajim.logger.mockGetNamedCalls('write') + self.assertEqual(1, len(calls)) - # no ChatControl was open and autopopup was off - # so the message goes into the event queue - self.assertEqual(1, len(gajim.events.get_events(account_name))) + # no ChatControl was open and autopopup was off + # so the message goes into the event queue + self.assertEqual(1, len(gajim.events.get_events(account_name))) - self.assert_first_message_notification() + self.assert_first_message_notification() - # no control is attached to the session - self.assertEqual(None, self.sess.control) + # no control is attached to the session + self.assertEqual(None, self.sess.control) - def test_receive_already_has_control(self): - '''test receiving a message with a session already attached to a - control''' + def test_receive_already_has_control(self): + '''test receiving a message with a session already attached to a + control''' - jid = 'bct@necronomicorp.com/Gajim' - msgtxt = 'testing one two three' + jid = 'bct@necronomicorp.com/Gajim' + msgtxt = 'testing one two three' - self.sess.control = MockChatControl(jid, account_name) + self.sess.control = MockChatControl(jid, account_name) - self.receive_chat_msg(jid, msgtxt) + self.receive_chat_msg(jid, msgtxt) - # message was logged - calls = gajim.logger.mockGetNamedCalls('write') - self.assertEqual(1, len(calls)) + # message was logged + calls = gajim.logger.mockGetNamedCalls('write') + self.assertEqual(1, len(calls)) - # the message does not go into the event queue - self.assertEqual(0, len(gajim.events.get_events(account_name))) + # the message does not go into the event queue + self.assertEqual(0, len(gajim.events.get_events(account_name))) - self.assert_not_first_message_notification() + self.assert_not_first_message_notification() - # message was printed to the control - calls = self.sess.control.mockGetNamedCalls('print_conversation') - self.assertEqual(1, len(calls)) + # message was printed to the control + calls = self.sess.control.mockGetNamedCalls('print_conversation') + self.assertEqual(1, len(calls)) - def test_received_orphaned_control(self): - '''test receiving a message when a control that doesn't have a session - attached exists''' + def test_received_orphaned_control(self): + '''test receiving a message when a control that doesn't have a session + attached exists''' - jid = 'bct@necronomicorp.com' - fjid = jid + '/Gajim' - msgtxt = 'testing one two three' + jid = 'bct@necronomicorp.com' + fjid = jid + '/Gajim' + msgtxt = 'testing one two three' - ctrl = MockChatControl(jid, account_name) - gajim.interface.msg_win_mgr = Mock({'get_control': ctrl}) - gajim.interface.msg_win_mgr.mockSetExpectation('get_control', - expectParams(jid, account_name)) + ctrl = MockChatControl(jid, account_name) + gajim.interface.msg_win_mgr = Mock({'get_control': ctrl}) + gajim.interface.msg_win_mgr.mockSetExpectation('get_control', + expectParams(jid, account_name)) - self.receive_chat_msg(fjid, msgtxt) + self.receive_chat_msg(fjid, msgtxt) - # message was logged - calls = gajim.logger.mockGetNamedCalls('write') - self.assertEqual(1, len(calls)) + # message was logged + calls = gajim.logger.mockGetNamedCalls('write') + self.assertEqual(1, len(calls)) - # the message does not go into the event queue - self.assertEqual(0, len(gajim.events.get_events(account_name))) + # the message does not go into the event queue + self.assertEqual(0, len(gajim.events.get_events(account_name))) - self.assert_not_first_message_notification() + self.assert_not_first_message_notification() - # this session is now attached to that control - self.assertEqual(self.sess, ctrl.session) - self.assertEqual(ctrl, self.sess.control, 'foo') + # this session is now attached to that control + self.assertEqual(self.sess, ctrl.session) + self.assertEqual(ctrl, self.sess.control, 'foo') - # message was printed to the control - calls = ctrl.mockGetNamedCalls('print_conversation') - self.assertEqual(1, len(calls)) + # message was printed to the control + calls = ctrl.mockGetNamedCalls('print_conversation') + self.assertEqual(1, len(calls)) if __name__ == '__main__': unittest.main() - -# vim: se ts=3: diff --git a/test/unit/test_xmpp_dispatcher_nb.py b/test/unit/test_xmpp_dispatcher_nb.py index 7d57cae6e..660b6d572 100644 --- a/test/unit/test_xmpp_dispatcher_nb.py +++ b/test/unit/test_xmpp_dispatcher_nb.py @@ -12,88 +12,86 @@ from common.xmpp import dispatcher_nb from common.xmpp import protocol class TestDispatcherNB(unittest.TestCase): - ''' - Test class for NonBlocking dispatcher. Tested dispatcher will be plugged - into a mock client - ''' - def setUp(self): - self.dispatcher = dispatcher_nb.XMPPDispatcher() + ''' + Test class for NonBlocking dispatcher. Tested dispatcher will be plugged + into a mock client + ''' + def setUp(self): + self.dispatcher = dispatcher_nb.XMPPDispatcher() - # Setup mock client - self.client = Mock() - self.client.__str__ = lambda: 'Mock' # FIXME: why do I need this one? - self.client._caller = Mock() - self.client.defaultNamespace = protocol.NS_CLIENT - self.client.Connection = Mock() # mock transport - self.con = self.client.Connection - - def tearDown(self): - # Unplug if needed - if hasattr(self.dispatcher, '_owner'): - self.dispatcher.PlugOut() + # Setup mock client + self.client = Mock() + self.client.__str__ = lambda: 'Mock' # FIXME: why do I need this one? + self.client._caller = Mock() + self.client.defaultNamespace = protocol.NS_CLIENT + self.client.Connection = Mock() # mock transport + self.con = self.client.Connection - def _simulate_connect(self): - self.dispatcher.PlugIn(self.client) # client is owner - # Simulate that we have established a connection - self.dispatcher.StreamInit() - self.dispatcher.ProcessNonBlocking("") + def tearDown(self): + # Unplug if needed + if hasattr(self.dispatcher, '_owner'): + self.dispatcher.PlugOut() - def test_unbound_namespace_prefix(self): - '''tests our handling of a message with an unbound namespace prefix''' - self._simulate_connect() - - msgs = [] - def _got_message(conn, msg): - msgs.append(msg) - self.dispatcher.RegisterHandler('message', _got_message) + def _simulate_connect(self): + self.dispatcher.PlugIn(self.client) # client is owner + # Simulate that we have established a connection + self.dispatcher.StreamInit() + self.dispatcher.ProcessNonBlocking("") - # should be able to parse a normal message - self.dispatcher.ProcessNonBlocking('hello') - self.assertEqual(1, len(msgs)) + def test_unbound_namespace_prefix(self): + '''tests our handling of a message with an unbound namespace prefix''' + self._simulate_connect() - self.dispatcher.ProcessNonBlocking('') - self.assertEqual(2, len(msgs)) - # we should not have been disconnected after that message - self.assertEqual(0, len(self.con.mockGetNamedCalls('pollend'))) - self.assertEqual(0, len(self.con.mockGetNamedCalls('disconnect'))) + msgs = [] + def _got_message(conn, msg): + msgs.append(msg) + self.dispatcher.RegisterHandler('message', _got_message) - # we should be able to keep parsing - self.dispatcher.ProcessNonBlocking('still here?') - self.assertEqual(3, len(msgs)) + # should be able to parse a normal message + self.dispatcher.ProcessNonBlocking('hello') + self.assertEqual(1, len(msgs)) - def test_process_non_blocking(self): - ''' Check for ProcessNonBlocking return types ''' - self._simulate_connect() - process = self.dispatcher.ProcessNonBlocking + self.dispatcher.ProcessNonBlocking('') + self.assertEqual(2, len(msgs)) + # we should not have been disconnected after that message + self.assertEqual(0, len(self.con.mockGetNamedCalls('pollend'))) + self.assertEqual(0, len(self.con.mockGetNamedCalls('disconnect'))) - # length of data expected - data = "Please don't fail" - result = process(data) - self.assertEqual(result, len(data)) + # we should be able to keep parsing + self.dispatcher.ProcessNonBlocking('still here?') + self.assertEqual(3, len(msgs)) - # no data processed, link shall still be active - result = process('') - self.assertEqual(result, '0') - self.assertEqual(0, len(self.con.mockGetNamedCalls('pollend')) + - len(self.con.mockGetNamedCalls('disconnect'))) + def test_process_non_blocking(self): + ''' Check for ProcessNonBlocking return types ''' + self._simulate_connect() + process = self.dispatcher.ProcessNonBlocking - # simulate disconnect - result = process('') - self.assertEqual(1, len(self.client.mockGetNamedCalls('disconnect'))) + # length of data expected + data = "Please don't fail" + result = process(data) + self.assertEqual(result, len(data)) - def test_return_stanza_handler(self): - ''' Test sasl_error_conditions transformation in protocol.py ''' - # quick'n dirty...I wasn't aware of it existance and thought it would - # always fail :-) - self._simulate_connect() - stanza = "" - def send(data): - self.assertEqual(str(data), 'The feature requested is not implemented by the recipient or server and therefore cannot be processed.') - self.client.send = send - self.dispatcher.ProcessNonBlocking(stanza) + # no data processed, link shall still be active + result = process('') + self.assertEqual(result, '0') + self.assertEqual(0, len(self.con.mockGetNamedCalls('pollend')) + + len(self.con.mockGetNamedCalls('disconnect'))) + + # simulate disconnect + result = process('') + self.assertEqual(1, len(self.client.mockGetNamedCalls('disconnect'))) + + def test_return_stanza_handler(self): + ''' Test sasl_error_conditions transformation in protocol.py ''' + # quick'n dirty...I wasn't aware of it existance and thought it would + # always fail :-) + self._simulate_connect() + stanza = "" + def send(data): + self.assertEqual(str(data), 'The feature requested is not implemented by the recipient or server and therefore cannot be processed.') + self.client.send = send + self.dispatcher.ProcessNonBlocking(stanza) if __name__ == '__main__': - unittest.main() - -# vim: se ts=3: + unittest.main() diff --git a/test/unit/test_xmpp_transports_nb.py b/test/unit/test_xmpp_transports_nb.py index b81f60d61..633549560 100644 --- a/test/unit/test_xmpp_transports_nb.py +++ b/test/unit/test_xmpp_transports_nb.py @@ -11,70 +11,68 @@ from common.xmpp import transports_nb class TestModuleLevelFunctions(unittest.TestCase): - ''' - Test class for functions defined at module level - ''' - def test_urisplit(self): - def check_uri(uri, proto, host, port, path): - _proto, _host, _port, _path = transports_nb.urisplit(uri) - self.assertEqual(proto, _proto) - self.assertEqual(host, _host) - self.assertEqual(path, _path) - self.assertEqual(port, _port) - - check_uri('http://httpcm.jabber.org:5280/webclient', proto='http', - host='httpcm.jabber.org', port=5280, path='/webclient') - - check_uri('http://httpcm.jabber.org/webclient', proto='http', - host='httpcm.jabber.org', port=80, path='/webclient') - - check_uri('https://httpcm.jabber.org/webclient', proto='https', - host='httpcm.jabber.org', port=443, path='/webclient') + ''' + Test class for functions defined at module level + ''' + def test_urisplit(self): + def check_uri(uri, proto, host, port, path): + _proto, _host, _port, _path = transports_nb.urisplit(uri) + self.assertEqual(proto, _proto) + self.assertEqual(host, _host) + self.assertEqual(path, _path) + self.assertEqual(port, _port) - def test_get_proxy_data_from_dict(self): - def check_dict(proxy_dict, host, port, user, passwd): - _host, _port, _user, _passwd = transports_nb.get_proxy_data_from_dict( - proxy_dict) - self.assertEqual(_host, host) - self.assertEqual(_port, port) - self.assertEqual(_user, user) - self.assertEqual(_passwd, passwd) + check_uri('http://httpcm.jabber.org:5280/webclient', proto='http', + host='httpcm.jabber.org', port=5280, path='/webclient') + + check_uri('http://httpcm.jabber.org/webclient', proto='http', + host='httpcm.jabber.org', port=80, path='/webclient') + + check_uri('https://httpcm.jabber.org/webclient', proto='https', + host='httpcm.jabber.org', port=443, path='/webclient') + + def test_get_proxy_data_from_dict(self): + def check_dict(proxy_dict, host, port, user, passwd): + _host, _port, _user, _passwd = transports_nb.get_proxy_data_from_dict( + proxy_dict) + self.assertEqual(_host, host) + self.assertEqual(_port, port) + self.assertEqual(_user, user) + self.assertEqual(_passwd, passwd) + + bosh_dict = {'bosh_content': u'text/xml; charset=utf-8', + 'bosh_hold': 2, + 'bosh_http_pipelining': False, + 'bosh_uri': u'http://gajim.org:5280/http-bind', + 'bosh_useproxy': False, + 'bosh_wait': 30, + 'bosh_wait_for_restart_response': False, + 'host': u'172.16.99.11', + 'pass': u'pass', + 'port': 3128, + 'type': u'bosh', + 'useauth': True, + 'user': u'user'} + check_dict(bosh_dict, host=u'gajim.org', port=5280, user=u'user', + passwd=u'pass') + + proxy_dict = {'bosh_content': u'text/xml; charset=utf-8', + 'bosh_hold': 2, + 'bosh_http_pipelining': False, + 'bosh_port': 5280, + 'bosh_uri': u'', + 'bosh_useproxy': True, + 'bosh_wait': 30, + 'bosh_wait_for_restart_response': False, + 'host': u'172.16.99.11', + 'pass': u'pass', + 'port': 3128, + 'type': 'socks5', + 'useauth': True, + 'user': u'user'} + check_dict(proxy_dict, host=u'172.16.99.11', port=3128, user=u'user', + passwd=u'pass') - bosh_dict = {'bosh_content': u'text/xml; charset=utf-8', - 'bosh_hold': 2, - 'bosh_http_pipelining': False, - 'bosh_uri': u'http://gajim.org:5280/http-bind', - 'bosh_useproxy': False, - 'bosh_wait': 30, - 'bosh_wait_for_restart_response': False, - 'host': u'172.16.99.11', - 'pass': u'pass', - 'port': 3128, - 'type': u'bosh', - 'useauth': True, - 'user': u'user'} - check_dict(bosh_dict, host=u'gajim.org', port=5280, user=u'user', - passwd=u'pass') - proxy_dict = {'bosh_content': u'text/xml; charset=utf-8', - 'bosh_hold': 2, - 'bosh_http_pipelining': False, - 'bosh_port': 5280, - 'bosh_uri': u'', - 'bosh_useproxy': True, - 'bosh_wait': 30, - 'bosh_wait_for_restart_response': False, - 'host': u'172.16.99.11', - 'pass': u'pass', - 'port': 3128, - 'type': 'socks5', - 'useauth': True, - 'user': u'user'} - check_dict(proxy_dict, host=u'172.16.99.11', port=3128, user=u'user', - passwd=u'pass') - - if __name__ == '__main__': - unittest.main() - -# vim: se ts=3: + unittest.main() From ad8c7a0f4111c933d198ebf9d7cd4a68f9ba4780 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Fri, 12 Feb 2010 09:14:47 +0100 Subject: [PATCH 062/200] add adjustment to spinbutton in length notifier plugin --- plugins/length_notifier/length_notifier.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/length_notifier/length_notifier.py b/plugins/length_notifier/length_notifier.py index d1e0f8329..86413ba13 100644 --- a/plugins/length_notifier/length_notifier.py +++ b/plugins/length_notifier/length_notifier.py @@ -123,15 +123,17 @@ class LengthNotifierPluginConfigDialog(GajimPluginConfigDialog): ['length_notifier_config_table']) self.config_table = self.xml.get_object('length_notifier_config_table') self.child.pack_start(self.config_table) - + self.message_length_spinbutton = self.xml.get_object( 'message_length_spinbutton') + self.message_length_spinbutton.get_adjustment().set_all(140, 0, 500, 1, + 10, 0) self.notification_colorbutton = self.xml.get_object( 'notification_colorbutton') self.jids_entry = self.xml.get_object('jids_entry') - + self.xml.connect_signals(self) - + def on_run(self): self.message_length_spinbutton.set_value(self.plugin.config['MESSAGE_WARNING_LENGTH']) self.notification_colorbutton.set_color(gtk.gdk.color_parse(self.plugin.config['WARNING_COLOR'])) From 73a4c4e1b9f425a8c382417995820ad6d3efe9a1 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Fri, 12 Feb 2010 20:41:21 +0100 Subject: [PATCH 063/200] fix acronyms expander plugin --- plugins/acronyms_expander.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/plugins/acronyms_expander.py b/plugins/acronyms_expander.py index 6dec95614..69e2448e5 100644 --- a/plugins/acronyms_expander.py +++ b/plugins/acronyms_expander.py @@ -27,6 +27,7 @@ Acronyms expander plugin. import sys import gtk +import gobject from plugins import GajimPlugin from plugins.helpers import log, log_calls @@ -70,19 +71,15 @@ class AcronymsExpanderPlugin(GajimPlugin): t = tb.get_text(tb.get_start_iter(), tb.get_end_iter()) #log.debug('%s %d'%(t, len(t))) if t and t[-1] == INVOKER: - #log.debug("changing msg text") + #log.debug('changing msg text') base,sep,head=t[:-1].rpartition(INVOKER) - #log.debug('%s | %s | %s'%(base, sep, head)) + log.debug('%s | %s | %s'%(base, sep, head)) if head in ACRONYMS: head = ACRONYMS[head] - log.debug("head: %s"%(head)) - t = "".join((base, sep, head, INVOKER)) - #log.debug("turning off notify") - tb.freeze_notify() + #log.debug('head: %s'%(head)) + t = ''.join((base, sep, head, INVOKER)) #log.debug("setting text: '%s'"%(t)) - tb.set_text(t) - #log.debug("turning on notify") - tb.thaw_notify() + gobject.idle_add(tb.set_text, t) @log_calls('AcronymsExpanderPlugin') def connect_with_chat_control_base(self, chat_control): @@ -101,4 +98,4 @@ class AcronymsExpanderPlugin(GajimPlugin): d = chat_control.acronyms_expander_plugin_data tv = chat_control.msg_textview tv.get_buffer().disconnect(d['h_id']) - \ No newline at end of file + From 57cd53aea13ecd05fdd1782534b9921901e1ec62 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Fri, 12 Feb 2010 21:08:40 +0100 Subject: [PATCH 064/200] rename gc_banner_status_image to banner_status_image so it's the same than schat control --- data/gui/groupchat_control.ui | 2 +- src/groupchat_control.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/gui/groupchat_control.ui b/data/gui/groupchat_control.ui index f9ebc2798..e87e0b9b3 100644 --- a/data/gui/groupchat_control.ui +++ b/data/gui/groupchat_control.ui @@ -19,7 +19,7 @@ True - + diff --git a/src/groupchat_control.py b/src/groupchat_control.py index 94efd08f8..4fdcc55fe 100644 --- a/src/groupchat_control.py +++ b/src/groupchat_control.py @@ -580,7 +580,7 @@ class GroupchatControl(ChatControlBase): self.change_roster_style() def _update_banner_state_image(self): - banner_status_img = self.xml.get_object('gc_banner_status_image') + banner_status_img = self.xml.get_object('banner_status_image') images = gajim.interface.jabber_state_images if self.room_jid in gajim.gc_connected[self.account] and \ gajim.gc_connected[self.account][self.room_jid]: From 9b5ee1e13b3a3e9f31aeec48e62fa69271c35e1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Araujo?= Date: Thu, 8 Apr 2010 01:20:17 +0200 Subject: [PATCH 065/200] convert tabs to spaces in source code thanks to reindent.py Also use sed to remove now unneeded Vim lines, 2to3 -f ws_comma to fix some whitespace, and fix some other madness manually. --- data/emoticons/animated/emoticons.py | 96 +- data/emoticons/static-big/emoticons.py | 82 +- data/emoticons/static/emoticons.py | 82 +- data/emoticons/tango/emoticons.py | 2 +- plugins/acronyms_expander.py | 123 +- plugins/banner_tweaks/__init__.py | 2 +- plugins/banner_tweaks/plugin.py | 312 +- plugins/dbus_plugin/__init__.py | 2 +- plugins/dbus_plugin/plugin.py | 1363 +- plugins/events_dump/__init__.py | 2 +- plugins/events_dump/plugin.py | 188 +- plugins/google_translation/__init__.py | 2 +- plugins/google_translation/plugin.py | 151 +- plugins/length_notifier/__init__.py | 2 +- plugins/length_notifier/length_notifier.py | 243 +- plugins/new_events_example/__init__.py | 2 +- plugins/new_events_example/plugin.py | 204 +- plugins/roster_buttons/plugin.py | 99 +- plugins/snarl_notifications/PySnarl.py | 1544 +-- plugins/snarl_notifications/__init__.py | 2 +- plugins/snarl_notifications/plugin.py | 97 +- scripts/dev/run-build-test.py | 15 +- scripts/dev/run-pylint.py | 5 +- setup_win32.py | 46 +- src/adhoc_commands.py | 848 +- src/advanced_configuration_window.py | 434 +- src/atom_window.py | 190 +- src/cell_renderer_image.py | 194 +- src/chat_control.py | 5598 ++++---- src/common/GnuPG.py | 368 +- src/common/GnuPGInterface.py | 2 - src/common/__init__.py | 2 - src/common/account.py | 20 +- src/common/atom.py | 220 +- src/common/caps_cache.py | 566 +- src/common/check_paths.py | 530 +- src/common/commands.py | 678 +- src/common/config.py | 1296 +- src/common/configpaths.py | 224 +- src/common/connection.py | 4376 +++--- src/common/connection_handlers.py | 4314 +++--- src/common/contacts.py | 1360 +- src/common/crypto.py | 88 +- src/common/dataforms.py | 949 +- src/common/dbus_support.py | 238 +- src/common/defs.py | 4 +- src/common/dh.py | 49 +- src/common/events.py | 550 +- src/common/exceptions.py | 148 +- src/common/fuzzyclock.py | 56 +- src/common/gajim.py | 428 +- src/common/ged.py | 51 +- src/common/helpers.py | 2207 ++- src/common/i18n.py | 92 +- src/common/idle.py | 116 +- src/common/jingle.py | 180 +- src/common/jingle_content.py | 200 +- src/common/jingle_rtp.py | 532 +- src/common/jingle_session.py | 1228 +- src/common/jingle_transport.py | 206 +- src/common/kwalletbinding.py | 86 +- src/common/latex.py | 176 +- src/common/location_listener.py | 196 +- src/common/logger.py | 1994 ++- src/common/logging_helpers.py | 252 +- src/common/multimedia_helpers.py | 136 +- src/common/nec.py | 197 +- src/common/optparser.py | 1686 ++- src/common/passwords.py | 324 +- src/common/pep.py | 1088 +- src/common/protocol/__init__.py | 2 +- src/common/protocol/bytestream.py | 1094 +- src/common/protocol/caps.py | 130 +- src/common/proxy65_manager.py | 768 +- src/common/pubsub.py | 294 +- src/common/resolver.py | 546 +- src/common/rst_xhtml_generator.py | 226 +- src/common/sleepy.py | 172 +- src/common/socks5.py | 2070 ++- src/common/stanza_session.py | 1742 ++- src/common/xmpp/__init__.py | 2 - src/common/xmpp/auth_nb.py | 944 +- src/common/xmpp/bosh.py | 1020 +- src/common/xmpp/c14n.py | 57 +- src/common/xmpp/client_nb.py | 1000 +- src/common/xmpp/dispatcher_nb.py | 998 +- src/common/xmpp/features_nb.py | 290 +- src/common/xmpp/idlequeue.py | 879 +- src/common/xmpp/plugin.py | 130 +- src/common/xmpp/protocol.py | 2012 ++- src/common/xmpp/proxy_connectors.py | 386 +- src/common/xmpp/roster_nb.py | 602 +- src/common/xmpp/simplexml.py | 1178 +- src/common/xmpp/stringprepare.py | 274 +- src/common/xmpp/tls_nb.py | 670 +- src/common/xmpp/transports_nb.py | 1230 +- src/common/zeroconf/__init__.py | 2 - src/common/zeroconf/client_zeroconf.py | 1374 +- .../zeroconf/connection_handlers_zeroconf.py | 222 +- src/common/zeroconf/connection_zeroconf.py | 562 +- src/common/zeroconf/roster_zeroconf.py | 232 +- src/common/zeroconf/zeroconf.py | 36 +- src/common/zeroconf/zeroconf_avahi.py | 734 +- src/common/zeroconf/zeroconf_bonjour.py | 487 +- src/config.py | 7513 ++++++----- src/conversation_textview.py | 2544 ++-- src/dataforms_widget.py | 972 +- src/dialogs.py | 8391 ++++++------ src/disco.py | 3860 +++--- src/features_window.py | 394 +- src/filetransfers_window.py | 1830 ++- src/gajim-remote-plugin.py | 946 +- src/gajim-remote.py | 1038 +- src/gajim.py | 516 +- src/gajim_themes_window.py | 676 +- src/groupchat_control.py | 4664 ++++--- src/groups.py | 78 +- src/gtkexcepthook.py | 112 +- src/gtkgui_helpers.py | 1748 ++- src/gtkspell.py | 1 - src/gui_interface.py | 6490 +++++---- src/gui_menu_builder.py | 790 +- src/history_manager.py | 1164 +- src/history_window.py | 1182 +- src/htmltextview.py | 1672 ++- src/ipython_view.py | 889 +- src/message_control.py | 326 +- src/message_textview.py | 511 +- src/message_window.py | 2226 ++- src/music_track_listener.py | 478 +- src/negotiation.py | 86 +- src/network_manager_listener.py | 118 +- src/notify.py | 1146 +- src/plugins/gui.py | 346 +- src/plugins/helpers.py | 42 +- src/plugins/plugin.py | 376 +- src/plugins/pluginmanager.py | 798 +- src/profile_window.py | 604 +- src/pycallgraph.py | 4 +- src/remote_control.py | 1444 +- src/roster_window.py | 11178 ++++++++-------- src/search_window.py | 354 +- src/secrets.py | 122 +- src/session.py | 978 +- src/statusicon.py | 672 +- src/tooltips.py | 1302 +- src/vcard.py | 922 +- test/integration/__init__.py | 2 +- .../integration/test_gui_event_integration.py | 234 +- test/integration/test_resolver.py | 142 +- test/integration/test_roster.py | 282 +- test/integration/test_xmpp_client_nb.py | 240 +- test/integration/test_xmpp_transports_nb.py | 440 +- test/lib/__init__.py | 40 +- test/lib/data.py | 124 +- test/lib/gajim_mocks.py | 194 +- test/lib/mock.py | 13 +- test/lib/notify.py | 10 +- test/lib/xmpp_mocks.py | 136 +- test/runtests.py | 72 +- test/test_pluginmanager.py | 38 +- test/unit/__init__.py | 2 +- test/unit/test_account.py | 14 +- test/unit/test_caps_cache.py | 178 +- test/unit/test_contacts.py | 214 +- test/unit/test_gui_interface.py | 170 +- test/unit/test_protocol_caps.py | 86 +- test/unit/test_sessions.py | 270 +- test/unit/test_xmpp_dispatcher_nb.py | 140 +- test/unit/test_xmpp_transports_nb.py | 124 +- 170 files changed, 68046 insertions(+), 68330 deletions(-) diff --git a/data/emoticons/animated/emoticons.py b/data/emoticons/animated/emoticons.py index 59591e6cc..222358ef9 100644 --- a/data/emoticons/animated/emoticons.py +++ b/data/emoticons/animated/emoticons.py @@ -1,52 +1,50 @@ # coding=utf-8 emoticons = { - 'smile.png': [':-)', ':)'], - 'coolglasses.png': ['B-)', '(H)'], - 'wink.gif': [';-)', ';)'], - 'biggrin.png': [':-D', ':D'], - 'unhappy.png': [':-(', ':('], - 'cry.gif': [":'-(", ":'(", ';-(', ';(', ";'-("], - 'frowning.png': [':-/', ':/', ':-\\', ':\\', ':-S', ':S'], - 'blush.png': [':-$', ':$'], - 'angry.png': [':-@', ':@'], - 'bat.gif': [':-[', ':['], - 'kiss.png': [':-{}', ':-*', ':*', '(K)'], - 'stare.png': [':-|', ':|'], - 'devil.png': [']:->', '>:-)', '>:)', '(6)'], - 'tongue.png': [':-P', ':P', ':-þ', ':þ'], - 'oh.png': ['=-O', ':-O', ':O'], - 'heart.png': ['<3', '(L)', '*IN LOVE*'], - 'pussy.png': ['(@)'], - 'cuffs.png': ['(%)'], - 'moon.png': ['(S)'], - 'lamp.png': ['(I)'], - 'music.png': ['(8)'], - 'beer.png': ['(B)', '*DRINK*'], - 'brflower.png': ['(W)'], - 'boy.png': ['(Z)'], - 'girl.png': ['(X)'], - 'mail.png': ['(E)'], - 'thumbdown.png': ['(N)'], - 'photo.png': ['(P)'], - 'thumbup.png': ['(Y)', '*THUMBS UP*'], - 'hugleft.png': ['(})'], - 'brheart.png': ['--', '(F)'], - 'drink.png': ['(D)'], - 'phone.png': ['(T)'], - 'coffee.png': ['(C)'], - 'hugright.png': ['({)'], - 'star.png': ['(*)'], - 'rainbow.png': ['(R)'], - 'cigarette.gif': ['(ci)'], - 'cake.gif': ['(^)'], - 'dontknow.gif': [':^)'], - 'eyeroll.gif': ['8-)'], - 'lightning.gif': ['(li)'], - 'party.gif': ['<:o)'], - 'sleepy.gif': ['|-)'], - 'think.gif': ['*-)'], - 'puke.gif': [':-!'], + 'smile.png': [':-)', ':)'], + 'coolglasses.png': ['B-)', '(H)'], + 'wink.gif': [';-)', ';)'], + 'biggrin.png': [':-D', ':D'], + 'unhappy.png': [':-(', ':('], + 'cry.gif': [":'-(", ":'(", ';-(', ';(', ";'-("], + 'frowning.png': [':-/', ':/', ':-\\', ':\\', ':-S', ':S'], + 'blush.png': [':-$', ':$'], + 'angry.png': [':-@', ':@'], + 'bat.gif': [':-[', ':['], + 'kiss.png': [':-{}', ':-*', ':*', '(K)'], + 'stare.png': [':-|', ':|'], + 'devil.png': [']:->', '>:-)', '>:)', '(6)'], + 'tongue.png': [':-P', ':P', ':-þ', ':þ'], + 'oh.png': ['=-O', ':-O', ':O'], + 'heart.png': ['<3', '(L)', '*IN LOVE*'], + 'pussy.png': ['(@)'], + 'cuffs.png': ['(%)'], + 'moon.png': ['(S)'], + 'lamp.png': ['(I)'], + 'music.png': ['(8)'], + 'beer.png': ['(B)', '*DRINK*'], + 'brflower.png': ['(W)'], + 'boy.png': ['(Z)'], + 'girl.png': ['(X)'], + 'mail.png': ['(E)'], + 'thumbdown.png': ['(N)'], + 'photo.png': ['(P)'], + 'thumbup.png': ['(Y)', '*THUMBS UP*'], + 'hugleft.png': ['(})'], + 'brheart.png': ['--', '(F)'], + 'drink.png': ['(D)'], + 'phone.png': ['(T)'], + 'coffee.png': ['(C)'], + 'hugright.png': ['({)'], + 'star.png': ['(*)'], + 'rainbow.png': ['(R)'], + 'cigarette.gif': ['(ci)'], + 'cake.gif': ['(^)'], + 'dontknow.gif': [':^)'], + 'eyeroll.gif': ['8-)'], + 'lightning.gif': ['(li)'], + 'party.gif': ['<:o)'], + 'sleepy.gif': ['|-)'], + 'think.gif': ['*-)'], + 'puke.gif': [':-!'], } - -# vim: se ts=3: diff --git a/data/emoticons/static-big/emoticons.py b/data/emoticons/static-big/emoticons.py index 3c2d08b72..8ed1da834 100644 --- a/data/emoticons/static-big/emoticons.py +++ b/data/emoticons/static-big/emoticons.py @@ -1,45 +1,43 @@ # coding=utf-8 emoticons = { - 'smile.png': [':-)', ':)'], - 'coolglasses.png': ['8-)', 'B-)', '(H)'], - 'wink.png': [';-)', ';)'], - 'biggrin.png': [':-D', ':D'], - 'unhappy.png': [':-(', ':('], - 'cry.png': [":'-(", ":'(", ';-(', ';(', ";'-("], - 'frowning.png': [':-/', ':/', ':-\\', ':\\', ':-S', ':S'], - 'blush.png': [':-$', ':$'], - 'angry.png': [':-@', ':@'], - 'bat.png': [':-[', ':['], - 'kiss.png': [':-{}', ':-*', ':*', '(K)'], - 'stare.png': [':-|', ':|'], - 'devil.png': [']:->', '>:-)', '>:)', '(6)'], - 'tongue.png': [':-P', ':P', ':-þ', ':þ'], - 'oh.png': ['=-O', ':-O', ':O'], - 'heart.png': ['<3', '(L)', '*IN LOVE*'], - 'lion.png': [':3', '>:3'], - 'pussy.png': ['(@)', '=^.^='], - 'cuffs.png': ['(%)'], - 'moon.png': ['(S)'], - 'lamp.png': ['(I)'], - 'music.png': ['(8)'], - 'beer.png': ['(B)', '*DRINK*'], - 'brflower.png': ['(W)'], - 'boy.png': ['(Z)'], - 'girl.png': ['(X)'], - 'mail.png': ['(E)'], - 'thumbdown.png': ['(N)'], - 'photo.png': ['(P)'], - 'thumbup.png': ['(Y)', '*THUMBS UP*'], - 'hugleft.png': ['(})'], - 'brheart.png': ['--', '(F)'], - 'drink.png': ['(D)'], - 'phone.png': ['(T)'], - 'coffee.png': ['(C)'], - 'hugright.png': ['({)'], - 'star.png': ['(*)'], - 'rainbow.png': ['(R)'], - 'puke.png': [':-!'], + 'smile.png': [':-)', ':)'], + 'coolglasses.png': ['8-)', 'B-)', '(H)'], + 'wink.png': [';-)', ';)'], + 'biggrin.png': [':-D', ':D'], + 'unhappy.png': [':-(', ':('], + 'cry.png': [":'-(", ":'(", ';-(', ';(', ";'-("], + 'frowning.png': [':-/', ':/', ':-\\', ':\\', ':-S', ':S'], + 'blush.png': [':-$', ':$'], + 'angry.png': [':-@', ':@'], + 'bat.png': [':-[', ':['], + 'kiss.png': [':-{}', ':-*', ':*', '(K)'], + 'stare.png': [':-|', ':|'], + 'devil.png': [']:->', '>:-)', '>:)', '(6)'], + 'tongue.png': [':-P', ':P', ':-þ', ':þ'], + 'oh.png': ['=-O', ':-O', ':O'], + 'heart.png': ['<3', '(L)', '*IN LOVE*'], + 'lion.png': [':3', '>:3'], + 'pussy.png': ['(@)', '=^.^='], + 'cuffs.png': ['(%)'], + 'moon.png': ['(S)'], + 'lamp.png': ['(I)'], + 'music.png': ['(8)'], + 'beer.png': ['(B)', '*DRINK*'], + 'brflower.png': ['(W)'], + 'boy.png': ['(Z)'], + 'girl.png': ['(X)'], + 'mail.png': ['(E)'], + 'thumbdown.png': ['(N)'], + 'photo.png': ['(P)'], + 'thumbup.png': ['(Y)', '*THUMBS UP*'], + 'hugleft.png': ['(})'], + 'brheart.png': ['--', '(F)'], + 'drink.png': ['(D)'], + 'phone.png': ['(T)'], + 'coffee.png': ['(C)'], + 'hugright.png': ['({)'], + 'star.png': ['(*)'], + 'rainbow.png': ['(R)'], + 'puke.png': [':-!'], } - -# vim: se ts=3: diff --git a/data/emoticons/static/emoticons.py b/data/emoticons/static/emoticons.py index 3c2d08b72..8ed1da834 100644 --- a/data/emoticons/static/emoticons.py +++ b/data/emoticons/static/emoticons.py @@ -1,45 +1,43 @@ # coding=utf-8 emoticons = { - 'smile.png': [':-)', ':)'], - 'coolglasses.png': ['8-)', 'B-)', '(H)'], - 'wink.png': [';-)', ';)'], - 'biggrin.png': [':-D', ':D'], - 'unhappy.png': [':-(', ':('], - 'cry.png': [":'-(", ":'(", ';-(', ';(', ";'-("], - 'frowning.png': [':-/', ':/', ':-\\', ':\\', ':-S', ':S'], - 'blush.png': [':-$', ':$'], - 'angry.png': [':-@', ':@'], - 'bat.png': [':-[', ':['], - 'kiss.png': [':-{}', ':-*', ':*', '(K)'], - 'stare.png': [':-|', ':|'], - 'devil.png': [']:->', '>:-)', '>:)', '(6)'], - 'tongue.png': [':-P', ':P', ':-þ', ':þ'], - 'oh.png': ['=-O', ':-O', ':O'], - 'heart.png': ['<3', '(L)', '*IN LOVE*'], - 'lion.png': [':3', '>:3'], - 'pussy.png': ['(@)', '=^.^='], - 'cuffs.png': ['(%)'], - 'moon.png': ['(S)'], - 'lamp.png': ['(I)'], - 'music.png': ['(8)'], - 'beer.png': ['(B)', '*DRINK*'], - 'brflower.png': ['(W)'], - 'boy.png': ['(Z)'], - 'girl.png': ['(X)'], - 'mail.png': ['(E)'], - 'thumbdown.png': ['(N)'], - 'photo.png': ['(P)'], - 'thumbup.png': ['(Y)', '*THUMBS UP*'], - 'hugleft.png': ['(})'], - 'brheart.png': ['--', '(F)'], - 'drink.png': ['(D)'], - 'phone.png': ['(T)'], - 'coffee.png': ['(C)'], - 'hugright.png': ['({)'], - 'star.png': ['(*)'], - 'rainbow.png': ['(R)'], - 'puke.png': [':-!'], + 'smile.png': [':-)', ':)'], + 'coolglasses.png': ['8-)', 'B-)', '(H)'], + 'wink.png': [';-)', ';)'], + 'biggrin.png': [':-D', ':D'], + 'unhappy.png': [':-(', ':('], + 'cry.png': [":'-(", ":'(", ';-(', ';(', ";'-("], + 'frowning.png': [':-/', ':/', ':-\\', ':\\', ':-S', ':S'], + 'blush.png': [':-$', ':$'], + 'angry.png': [':-@', ':@'], + 'bat.png': [':-[', ':['], + 'kiss.png': [':-{}', ':-*', ':*', '(K)'], + 'stare.png': [':-|', ':|'], + 'devil.png': [']:->', '>:-)', '>:)', '(6)'], + 'tongue.png': [':-P', ':P', ':-þ', ':þ'], + 'oh.png': ['=-O', ':-O', ':O'], + 'heart.png': ['<3', '(L)', '*IN LOVE*'], + 'lion.png': [':3', '>:3'], + 'pussy.png': ['(@)', '=^.^='], + 'cuffs.png': ['(%)'], + 'moon.png': ['(S)'], + 'lamp.png': ['(I)'], + 'music.png': ['(8)'], + 'beer.png': ['(B)', '*DRINK*'], + 'brflower.png': ['(W)'], + 'boy.png': ['(Z)'], + 'girl.png': ['(X)'], + 'mail.png': ['(E)'], + 'thumbdown.png': ['(N)'], + 'photo.png': ['(P)'], + 'thumbup.png': ['(Y)', '*THUMBS UP*'], + 'hugleft.png': ['(})'], + 'brheart.png': ['--', '(F)'], + 'drink.png': ['(D)'], + 'phone.png': ['(T)'], + 'coffee.png': ['(C)'], + 'hugright.png': ['({)'], + 'star.png': ['(*)'], + 'rainbow.png': ['(R)'], + 'puke.png': [':-!'], } - -# vim: se ts=3: diff --git a/data/emoticons/tango/emoticons.py b/data/emoticons/tango/emoticons.py index 43c7ce37f..db3376b93 100644 --- a/data/emoticons/tango/emoticons.py +++ b/data/emoticons/tango/emoticons.py @@ -49,4 +49,4 @@ emoticons = { 'pissed-off.png': ['*WALL*'], 'mail.png': ['*WRITE*', '(E)'], 'tremble.png': ['*SCRATCH*'], -} \ No newline at end of file +} diff --git a/plugins/acronyms_expander.py b/plugins/acronyms_expander.py index 69e2448e5..12ad677b0 100644 --- a/plugins/acronyms_expander.py +++ b/plugins/acronyms_expander.py @@ -33,69 +33,70 @@ from plugins import GajimPlugin from plugins.helpers import log, log_calls class AcronymsExpanderPlugin(GajimPlugin): - name = u'Acronyms Expander' - short_name = u'acronyms_expander' - version = u'0.1' - description = u'''Replaces acronyms (or other strings) with given expansions/substitutes.''' - authors = [u'Mateusz Biliński '] - homepage = u'http://blog.bilinski.it' - - @log_calls('AcronymsExpanderPlugin') - def init(self): - self.config_dialog = None - - self.gui_extension_points = { - 'chat_control_base' : (self.connect_with_chat_control_base, - self.disconnect_from_chat_control_base) - } + name = u'Acronyms Expander' + short_name = u'acronyms_expander' + version = u'0.1' + description = u'''Replaces acronyms (or other strings) with given expansions/substitutes.''' + authors = [u'Mateusz Biliński '] + homepage = u'http://blog.bilinski.it' - self.config_default_values = {'INVOKER' : (' ', _('')), - 'ACRONYMS' : ({'RTFM' : 'Read The Friendly Manual', - '/slap' : '/me slaps', - 'PS-' : 'plug-in system', - 'G-' : 'Gajim', - 'GNT-' : 'http://trac.gajim.org/newticket', - 'GW-' : 'http://trac.gajim.org/', - 'GTS-' : 'http://trac.gajim.org/report' - }, _('')), - } + @log_calls('AcronymsExpanderPlugin') + def init(self): + self.config_dialog = None - @log_calls('AcronymsExpanderPlugin') - def textbuffer_live_acronym_expander(self, tb): - """ - @param tb gtk.TextBuffer - """ - #assert isinstance(tb,gtk.TextBuffer) - ACRONYMS = self.config['ACRONYMS'] - INVOKER = self.config['INVOKER'] - t = tb.get_text(tb.get_start_iter(), tb.get_end_iter()) - #log.debug('%s %d'%(t, len(t))) - if t and t[-1] == INVOKER: - #log.debug('changing msg text') - base,sep,head=t[:-1].rpartition(INVOKER) - log.debug('%s | %s | %s'%(base, sep, head)) - if head in ACRONYMS: - head = ACRONYMS[head] - #log.debug('head: %s'%(head)) - t = ''.join((base, sep, head, INVOKER)) - #log.debug("setting text: '%s'"%(t)) - gobject.idle_add(tb.set_text, t) - - @log_calls('AcronymsExpanderPlugin') - def connect_with_chat_control_base(self, chat_control): - d = {} - tv = chat_control.msg_textview - tb = tv.get_buffer() - h_id = tb.connect('changed', self.textbuffer_live_acronym_expander) - d['h_id'] = h_id + self.gui_extension_points = { + 'chat_control_base': (self.connect_with_chat_control_base, + self.disconnect_from_chat_control_base) + } - chat_control.acronyms_expander_plugin_data = d + self.config_default_values = { + 'INVOKER': (' ', _('')), + 'ACRONYMS': ({'RTFM': 'Read The Friendly Manual', + '/slap': '/me slaps', + 'PS-': 'plug-in system', + 'G-': 'Gajim', + 'GNT-': 'http://trac.gajim.org/newticket', + 'GW-': 'http://trac.gajim.org/', + 'GTS-': 'http://trac.gajim.org/report', + }, + _('')), + } - return True + @log_calls('AcronymsExpanderPlugin') + def textbuffer_live_acronym_expander(self, tb): + """ + @param tb gtk.TextBuffer + """ + #assert isinstance(tb,gtk.TextBuffer) + ACRONYMS = self.config['ACRONYMS'] + INVOKER = self.config['INVOKER'] + t = tb.get_text(tb.get_start_iter(), tb.get_end_iter()) + #log.debug('%s %d'%(t, len(t))) + if t and t[-1] == INVOKER: + #log.debug('changing msg text') + base, sep, head=t[:-1].rpartition(INVOKER) + log.debug('%s | %s | %s'%(base, sep, head)) + if head in ACRONYMS: + head = ACRONYMS[head] + #log.debug('head: %s'%(head)) + t = ''.join((base, sep, head, INVOKER)) + #log.debug("setting text: '%s'"%(t)) + gobject.idle_add(tb.set_text, t) - @log_calls('AcronymsExpanderPlugin') - def disconnect_from_chat_control_base(self, chat_control): - d = chat_control.acronyms_expander_plugin_data - tv = chat_control.msg_textview - tv.get_buffer().disconnect(d['h_id']) - + @log_calls('AcronymsExpanderPlugin') + def connect_with_chat_control_base(self, chat_control): + d = {} + tv = chat_control.msg_textview + tb = tv.get_buffer() + h_id = tb.connect('changed', self.textbuffer_live_acronym_expander) + d['h_id'] = h_id + + chat_control.acronyms_expander_plugin_data = d + + return True + + @log_calls('AcronymsExpanderPlugin') + def disconnect_from_chat_control_base(self, chat_control): + d = chat_control.acronyms_expander_plugin_data + tv = chat_control.msg_textview + tv.get_buffer().disconnect(d['h_id']) diff --git a/plugins/banner_tweaks/__init__.py b/plugins/banner_tweaks/__init__.py index f8266d228..a328f68ee 100644 --- a/plugins/banner_tweaks/__init__.py +++ b/plugins/banner_tweaks/__init__.py @@ -1,2 +1,2 @@ -from plugin import BannerTweaksPlugin \ No newline at end of file +from plugin import BannerTweaksPlugin diff --git a/plugins/banner_tweaks/plugin.py b/plugins/banner_tweaks/plugin.py index ad03765db..121606aa4 100644 --- a/plugins/banner_tweaks/plugin.py +++ b/plugins/banner_tweaks/plugin.py @@ -43,163 +43,163 @@ from plugins.helpers import log, log_calls from plugins.gui import GajimPluginConfigDialog class BannerTweaksPlugin(GajimPlugin): - name = u'Banner Tweaks' - short_name = u'banner_tweaks' - version = u'0.1' - description = u'''Allows user to tweak chat window banner appearance (eg. make it compact). - -Based on patch by pb in ticket #4133: + name = u'Banner Tweaks' + short_name = u'banner_tweaks' + version = u'0.1' + description = u'''Allows user to tweak chat window banner appearance (eg. make it compact). + +Based on patch by pb in ticket #4133: http://trac.gajim.org/attachment/ticket/4133''' - authors = [u'Mateusz Biliński '] - homepage = u'http://blog.bilinski.it' - - @log_calls('BannerTweaksPlugin') - def init(self): - self.config_dialog = BannerTweaksPluginConfigDialog(self) - - self.gui_extension_points = { - 'chat_control_base_draw_banner' : (self.chat_control_base_draw_banner_called, - self.chat_control_base_draw_banner_deactivation) - } - - self.config_default_values = {'show_banner_image': (True, _('If True, Gajim will display a status icon in the banner of chat windows.')), - 'show_banner_online_msg': (True, _('If True, Gajim will display the status message of the contact in the banner of chat windows.')), - 'show_banner_resource': (False, _('If True, Gajim will display the resource name of the contact in the banner of chat windows.')), - 'banner_small_fonts': (False, _('If True, Gajim will use small fonts for contact name and resource name in the banner of chat windows.')), - 'old_chat_avatar_height' : (52, _('chat_avatar_height value before plugin was activated')), - } - - @log_calls('BannerTweaksPlugin') - def activate(self): - self.config['old_chat_avatar_height'] = gajim.config.get('chat_avatar_height') - #gajim.config.set('chat_avatar_height', 28) - - @log_calls('BannerTweaksPlugin') - def deactivate(self): - gajim.config.set('chat_avatar_height', self.config['old_chat_avatar_height']) - - @log_calls('BannerTweaksPlugin') - def chat_control_base_draw_banner_called(self, chat_control): - if not self.config['show_banner_online_msg']: - chat_control.banner_status_label.hide() - chat_control.banner_status_label.set_no_show_all(True) - status_text = '' - chat_control.banner_status_label.set_markup(status_text) - - if not self.config['show_banner_image']: - banner_status_img = chat_control.xml.get_object('banner_status_image') - banner_status_img.clear() - - # TODO: part below repeats a lot of code from ChatControl.draw_banner_text() - # This could be rewritten using re module: getting markup text from - # banner_name_label and replacing some elements based on plugin config. - # Would it be faster? - if self.config['show_banner_resource'] or self.config['banner_small_fonts']: - banner_name_label = chat_control.xml.get_object('banner_name_label') - label_text = banner_name_label.get_label() - - contact = chat_control.contact - jid = contact.jid - - name = contact.get_shown_name() - if chat_control.resource: - name += '/' + chat_control.resource - elif contact.resource and self.config['show_banner_resource']: - name += '/' + contact.resource - - if chat_control.TYPE_ID == message_control.TYPE_PM: - name = _('%(nickname)s from group chat %(room_name)s') %\ - {'nickname': name, 'room_name': chat_control.room_name} - name = gobject.markup_escape_text(name) - - # We know our contacts nick, but if another contact has the same nick - # in another account we need to also display the account. - # except if we are talking to two different resources of the same contact - acct_info = '' - for account in gajim.contacts.get_accounts(): - if account == chat_control.account: - continue - if acct_info: # We already found a contact with same nick - break - for jid in gajim.contacts.get_jid_list(account): - other_contact_ = \ - gajim.contacts.get_first_contact_from_jid(account, jid) - if other_contact_.get_shown_name() == chat_control.contact.get_shown_name(): - acct_info = ' (%s)' % \ - gobject.markup_escape_text(chat_control.account) - break - - font_attrs, font_attrs_small = chat_control.get_font_attrs() - if self.config['banner_small_fonts']: - font_attrs = font_attrs_small - - st = gajim.config.get('displayed_chat_state_notifications') - cs = contact.chatstate - if cs and st in ('composing_only', 'all'): - if contact.show == 'offline': - chatstate = '' - elif contact.composing_xep == 'XEP-0085': - if st == 'all' or cs == 'composing': - chatstate = helpers.get_uf_chatstate(cs) - else: - chatstate = '' - elif contact.composing_xep == 'XEP-0022': - if cs in ('composing', 'paused'): - # only print composing, paused - chatstate = helpers.get_uf_chatstate(cs) - else: - chatstate = '' - else: - # When does that happen ? See [7797] and [7804] - chatstate = helpers.get_uf_chatstate(cs) - - label_text = '%s%s %s' % \ - (font_attrs, name, font_attrs_small, acct_info, chatstate) - else: - # weight="heavy" size="x-large" - label_text = '%s%s' % \ - (font_attrs, name, font_attrs_small, acct_info) - - banner_name_label.set_markup(label_text) + authors = [u'Mateusz Biliński '] + homepage = u'http://blog.bilinski.it' + + @log_calls('BannerTweaksPlugin') + def init(self): + self.config_dialog = BannerTweaksPluginConfigDialog(self) + + self.gui_extension_points = { + 'chat_control_base_draw_banner': (self.chat_control_base_draw_banner_called, + self.chat_control_base_draw_banner_deactivation) + } + + self.config_default_values = { + 'show_banner_image': (True, _('If True, Gajim will display a status icon in the banner of chat windows.')), + 'show_banner_online_msg': (True, _('If True, Gajim will display the status message of the contact in the banner of chat windows.')), + 'show_banner_resource': (False, _('If True, Gajim will display the resource name of the contact in the banner of chat windows.')), + 'banner_small_fonts': (False, _('If True, Gajim will use small fonts for contact name and resource name in the banner of chat windows.')), + 'old_chat_avatar_height': (52, _('chat_avatar_height value before plugin was activated')), + } + + @log_calls('BannerTweaksPlugin') + def activate(self): + self.config['old_chat_avatar_height'] = gajim.config.get('chat_avatar_height') + #gajim.config.set('chat_avatar_height', 28) + + @log_calls('BannerTweaksPlugin') + def deactivate(self): + gajim.config.set('chat_avatar_height', self.config['old_chat_avatar_height']) + + @log_calls('BannerTweaksPlugin') + def chat_control_base_draw_banner_called(self, chat_control): + if not self.config['show_banner_online_msg']: + chat_control.banner_status_label.hide() + chat_control.banner_status_label.set_no_show_all(True) + status_text = '' + chat_control.banner_status_label.set_markup(status_text) + + if not self.config['show_banner_image']: + banner_status_img = chat_control.xml.get_object('banner_status_image') + banner_status_img.clear() + + # TODO: part below repeats a lot of code from ChatControl.draw_banner_text() + # This could be rewritten using re module: getting markup text from + # banner_name_label and replacing some elements based on plugin config. + # Would it be faster? + if self.config['show_banner_resource'] or self.config['banner_small_fonts']: + banner_name_label = chat_control.xml.get_object('banner_name_label') + label_text = banner_name_label.get_label() + + contact = chat_control.contact + jid = contact.jid + + name = contact.get_shown_name() + if chat_control.resource: + name += '/' + chat_control.resource + elif contact.resource and self.config['show_banner_resource']: + name += '/' + contact.resource + + if chat_control.TYPE_ID == message_control.TYPE_PM: + name = _('%(nickname)s from group chat %(room_name)s') %\ + {'nickname': name, 'room_name': chat_control.room_name} + name = gobject.markup_escape_text(name) + + # We know our contacts nick, but if another contact has the same nick + # in another account we need to also display the account. + # except if we are talking to two different resources of the same contact + acct_info = '' + for account in gajim.contacts.get_accounts(): + if account == chat_control.account: + continue + if acct_info: # We already found a contact with same nick + break + for jid in gajim.contacts.get_jid_list(account): + other_contact_ = \ + gajim.contacts.get_first_contact_from_jid(account, jid) + if other_contact_.get_shown_name() == chat_control.contact.get_shown_name(): + acct_info = ' (%s)' % \ + gobject.markup_escape_text(chat_control.account) + break + + font_attrs, font_attrs_small = chat_control.get_font_attrs() + if self.config['banner_small_fonts']: + font_attrs = font_attrs_small + + st = gajim.config.get('displayed_chat_state_notifications') + cs = contact.chatstate + if cs and st in ('composing_only', 'all'): + if contact.show == 'offline': + chatstate = '' + elif contact.composing_xep == 'XEP-0085': + if st == 'all' or cs == 'composing': + chatstate = helpers.get_uf_chatstate(cs) + else: + chatstate = '' + elif contact.composing_xep == 'XEP-0022': + if cs in ('composing', 'paused'): + # only print composing, paused + chatstate = helpers.get_uf_chatstate(cs) + else: + chatstate = '' + else: + # When does that happen ? See [7797] and [7804] + chatstate = helpers.get_uf_chatstate(cs) + + label_text = '%s%s %s' % \ + (font_attrs, name, font_attrs_small, acct_info, chatstate) + else: + # weight="heavy" size="x-large" + label_text = '%s%s' % \ + (font_attrs, name, font_attrs_small, acct_info) + + banner_name_label.set_markup(label_text) + + @log_calls('BannerTweaksPlugin') + def chat_control_base_draw_banner_deactivation(self, chat_control): + pass + #chat_control.draw_banner() - @log_calls('BannerTweaksPlugin') - def chat_control_base_draw_banner_deactivation(self, chat_control): - pass - #chat_control.draw_banner() - class BannerTweaksPluginConfigDialog(GajimPluginConfigDialog): - def init(self): - self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path( - 'config_dialog.ui') - self.xml = gtk.Builder() - self.xml.set_translation_domain(i18n.APP) - self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH, - ['banner_tweaks_config_vbox']) - self.config_vbox = self.xml.get_object('banner_tweaks_config_vbox') - self.child.pack_start(self.config_vbox) - - self.show_banner_image_checkbutton = self.xml.get_object('show_banner_image_checkbutton') - self.show_banner_online_msg_checkbutton = self.xml.get_object('show_banner_online_msg_checkbutton') - self.show_banner_resource_checkbutton = self.xml.get_object('show_banner_resource_checkbutton') - self.banner_small_fonts_checkbutton = self.xml.get_object('banner_small_fonts_checkbutton') - - self.xml.connect_signals(self) - - def on_run(self): - self.show_banner_image_checkbutton.set_active(self.plugin.config['show_banner_image']) - self.show_banner_online_msg_checkbutton.set_active(self.plugin.config['show_banner_online_msg']) - self.show_banner_resource_checkbutton.set_active(self.plugin.config['show_banner_resource']) - self.banner_small_fonts_checkbutton.set_active(self.plugin.config['banner_small_fonts']) + def init(self): + self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path( + 'config_dialog.ui') + self.xml = gtk.Builder() + self.xml.set_translation_domain(i18n.APP) + self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH, + ['banner_tweaks_config_vbox']) + self.config_vbox = self.xml.get_object('banner_tweaks_config_vbox') + self.child.pack_start(self.config_vbox) - def on_show_banner_image_checkbutton_toggled(self, button): - self.plugin.config['show_banner_image'] = button.get_active() - - def on_show_banner_online_msg_checkbutton_toggled(self, button): - self.plugin.config['show_banner_online_msg'] = button.get_active() - - def on_show_banner_resource_checkbutton_toggled(self, button): - self.plugin.config['show_banner_resource'] = button.get_active() - - def on_banner_small_fonts_checkbutton_toggled(self, button): - self.plugin.config['banner_small_fonts'] = button.get_active() - + self.show_banner_image_checkbutton = self.xml.get_object('show_banner_image_checkbutton') + self.show_banner_online_msg_checkbutton = self.xml.get_object('show_banner_online_msg_checkbutton') + self.show_banner_resource_checkbutton = self.xml.get_object('show_banner_resource_checkbutton') + self.banner_small_fonts_checkbutton = self.xml.get_object('banner_small_fonts_checkbutton') + + self.xml.connect_signals(self) + + def on_run(self): + self.show_banner_image_checkbutton.set_active(self.plugin.config['show_banner_image']) + self.show_banner_online_msg_checkbutton.set_active(self.plugin.config['show_banner_online_msg']) + self.show_banner_resource_checkbutton.set_active(self.plugin.config['show_banner_resource']) + self.banner_small_fonts_checkbutton.set_active(self.plugin.config['banner_small_fonts']) + + def on_show_banner_image_checkbutton_toggled(self, button): + self.plugin.config['show_banner_image'] = button.get_active() + + def on_show_banner_online_msg_checkbutton_toggled(self, button): + self.plugin.config['show_banner_online_msg'] = button.get_active() + + def on_show_banner_resource_checkbutton_toggled(self, button): + self.plugin.config['show_banner_resource'] = button.get_active() + + def on_banner_small_fonts_checkbutton_toggled(self, button): + self.plugin.config['banner_small_fonts'] = button.get_active() diff --git a/plugins/dbus_plugin/__init__.py b/plugins/dbus_plugin/__init__.py index c5c296ad7..3851c6bb9 100644 --- a/plugins/dbus_plugin/__init__.py +++ b/plugins/dbus_plugin/__init__.py @@ -1 +1 @@ -from plugin import DBusPlugin \ No newline at end of file +from plugin import DBusPlugin diff --git a/plugins/dbus_plugin/plugin.py b/plugins/dbus_plugin/plugin.py index ac26c3b1d..c34e0ad0c 100644 --- a/plugins/dbus_plugin/plugin.py +++ b/plugins/dbus_plugin/plugin.py @@ -6,7 +6,7 @@ ## Copyright (C) 2005-2006 Andrew Sayman ## Copyright (C) 2007 Lukas Petrovicky ## Copyright (C) 2007 Julien Pivotto -## Copyright (C) 2007 Travis Shirk +## Copyright (C) 2007 Travis Shirk ## Copyright (C) 2008 Mateusz Biliński ## ## This file is part of Gajim. @@ -41,634 +41,634 @@ import gobject from common import dbus_support if dbus_support.supported: - import dbus - if dbus_support: - INTERFACE = 'org.gajim.dbusplugin.RemoteInterface' - OBJ_PATH = '/org/gajim/dbusplugin/RemoteObject' - SERVICE = 'org.gajim.dbusplugin' - - import dbus.service - import dbus.glib - # type mapping + import dbus + if dbus_support: + INTERFACE = 'org.gajim.dbusplugin.RemoteInterface' + OBJ_PATH = '/org/gajim/dbusplugin/RemoteObject' + SERVICE = 'org.gajim.dbusplugin' - # in most cases it is a utf-8 string - DBUS_STRING = dbus.String - - # general type (for use in dicts, where all values should have the same type) - DBUS_BOOLEAN = dbus.Boolean - DBUS_DOUBLE = dbus.Double - DBUS_INT32 = dbus.Int32 - # dictionary with string key and binary value - DBUS_DICT_SV = lambda : dbus.Dictionary({}, signature="sv") - # dictionary with string key and value - DBUS_DICT_SS = lambda : dbus.Dictionary({}, signature="ss") - # empty type (there is no equivalent of None on D-Bus, but historically gajim - # used 0 instead) - DBUS_NONE = lambda : dbus.Int32(0) - - def get_dbus_struct(obj): - ''' recursively go through all the items and replace - them with their casted dbus equivalents - ''' - if obj is None: - return DBUS_NONE() - if isinstance(obj, (unicode, str)): - return DBUS_STRING(obj) - if isinstance(obj, int): - return DBUS_INT32(obj) - if isinstance(obj, float): - return DBUS_DOUBLE(obj) - if isinstance(obj, bool): - return DBUS_BOOLEAN(obj) - if isinstance(obj, (list, tuple)): - result = dbus.Array([get_dbus_struct(i) for i in obj], - signature='v') - if result == []: - return DBUS_NONE() - return result - if isinstance(obj, dict): - result = DBUS_DICT_SV() - for key, value in obj.items(): - result[DBUS_STRING(key)] = get_dbus_struct(value) - if result == {}: - return DBUS_NONE() - return result - # unknown type - return DBUS_NONE() - - class SignalObject(dbus.service.Object): - ''' Local object definition for /org/gajim/dbus/RemoteObject. - (This docstring is not be visible, because the clients can access only the remote object.)''' - - def __init__(self, bus_name): - self.first_show = True - self.vcard_account = None - - # register our dbus API - dbus.service.Object.__init__(self, bus_name, OBJ_PATH) - - @dbus.service.signal(INTERFACE, signature='av') - def Roster(self, account_and_data): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def AccountPresence(self, status_and_account): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def ContactPresence(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def ContactAbsence(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def ContactStatus(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def NewMessage(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def Subscribe(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def Subscribed(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def Unsubscribed(self, account_and_jid): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def NewAccount(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def VcardInfo(self, account_and_vcard): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def LastStatusTime(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def OsInfo(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def GCPresence(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def GCMessage(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def RosterInfo(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def NewGmail(self, account_and_array): - pass - - def raise_signal(self, signal, arg): - '''raise a signal, with a single argument of unspecified type - Instead of obj.raise_signal("Foo", bar), use obj.Foo(bar).''' - getattr(self, signal)(arg) - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='s') - def get_status(self, account): - '''Returns status (show to be exact) which is the global one - unless account is given''' - if not account: - # If user did not ask for account, returns the global status - return DBUS_STRING(helpers.get_global_show()) - # return show for the given account - index = gajim.connections[account].connected - return DBUS_STRING(gajim.SHOW_LIST[index]) - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='s') - def get_status_message(self, account): - '''Returns status which is the global one - unless account is given''' - if not account: - # If user did not ask for account, returns the global status - return DBUS_STRING(str(helpers.get_global_status())) - # return show for the given account - status = gajim.connections[account].status - return DBUS_STRING(status) - - def _get_account_and_contact(self, account, jid): - '''get the account (if not given) and contact instance from jid''' - connected_account = None - contact = None - accounts = gajim.contacts.get_accounts() - # if there is only one account in roster, take it as default - # if user did not ask for account - if not account and len(accounts) == 1: - account = accounts[0] - if account: - if gajim.connections[account].connected > 1: # account is connected - connected_account = account - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - else: - for account in accounts: - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - if contact and gajim.connections[account].connected > 1: - # account is connected - connected_account = account - break - if not contact: - contact = jid - - return connected_account, contact - - def _get_account_for_groupchat(self, account, room_jid): - '''get the account which is connected to groupchat (if not given) - or check if the given account is connected to the groupchat''' - connected_account = None - accounts = gajim.contacts.get_accounts() - # if there is only one account in roster, take it as default - # if user did not ask for account - if not account and len(accounts) == 1: - account = accounts[0] - if account: - if gajim.connections[account].connected > 1 and \ - room_jid in gajim.gc_connected[account] and \ - gajim.gc_connected[account][room_jid]: - # account and groupchat are connected - connected_account = account - else: - for account in accounts: - if gajim.connections[account].connected > 1 and \ - room_jid in gajim.gc_connected[account] and \ - gajim.gc_connected[account][room_jid]: - # account and groupchat are connected - connected_account = account - break - return connected_account - - @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') - def send_file(self, file_path, jid, account): - '''send file, located at 'file_path' to 'jid', using account - (optional) 'account' ''' - jid = self._get_real_jid(jid, account) - connected_account, contact = self._get_account_and_contact(account, jid) - - if connected_account: - if file_path[:7] == 'file://': - file_path=file_path[7:] - if os.path.isfile(file_path): # is it file? - gajim.interface.instances['file_transfers'].send_file( - connected_account, contact, file_path) - return DBUS_BOOLEAN(True) - return DBUS_BOOLEAN(False) - - def _send_message(self, jid, message, keyID, account, type = 'chat', - subject = None): - '''can be called from send_chat_message (default when send_message) - or send_single_message''' - if not jid or not message: - return DBUS_BOOLEAN(False) - if not keyID: - keyID = '' - - connected_account, contact = self._get_account_and_contact(account, jid) - if connected_account: - connection = gajim.connections[connected_account] - connection.send_message(jid, message, keyID, type, subject) - return DBUS_BOOLEAN(True) - return DBUS_BOOLEAN(False) - - @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='b') - def send_chat_message(self, jid, message, keyID, account): - '''Send chat 'message' to 'jid', using account (optional) 'account'. - if keyID is specified, encrypt the message with the pgp key ''' - jid = self._get_real_jid(jid, account) - return self._send_message(jid, message, keyID, account) - - @dbus.service.method(INTERFACE, in_signature='sssss', out_signature='b') - def send_single_message(self, jid, subject, message, keyID, account): - '''Send single 'message' to 'jid', using account (optional) 'account'. - if keyID is specified, encrypt the message with the pgp key ''' - jid = self._get_real_jid(jid, account) - return self._send_message(jid, message, keyID, account, type, subject) - - @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') - def send_groupchat_message(self, room_jid, message, account): - '''Send 'message' to groupchat 'room_jid', - using account (optional) 'account'.''' - if not room_jid or not message: - return DBUS_BOOLEAN(False) - connected_account = self._get_account_for_groupchat(account, room_jid) - if connected_account: - connection = gajim.connections[connected_account] - connection.send_gc_message(room_jid, message) - return DBUS_BOOLEAN(True) - return DBUS_BOOLEAN(False) - - @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b') - def open_chat(self, jid, account): - '''Shows the tabbed window for new message to 'jid', using account - (optional) 'account' ''' - if not jid: - raise MissingArgument - return DBUS_BOOLEAN(False) - jid = self._get_real_jid(jid, account) - try: - jid = helpers.parse_jid(jid) - except: - # Jid is not conform, ignore it - return DBUS_BOOLEAN(False) - - if account: - accounts = [account] - else: - accounts = gajim.connections.keys() - if len(accounts) == 1: - account = accounts[0] - connected_account = None - first_connected_acct = None - for acct in accounts: - if gajim.connections[acct].connected > 1: # account is online - contact = gajim.contacts.get_first_contact_from_jid(acct, jid) - if gajim.interface.msg_win_mgr.has_window(jid, acct): - connected_account = acct - break - # jid is in roster - elif contact: - connected_account = acct - break - # we send the message to jid not in roster, because account is - # specified, or there is only one account - elif account: - connected_account = acct - elif first_connected_acct is None: - first_connected_acct = acct - - # if jid is not a conntact, open-chat with first connected account - if connected_account is None and first_connected_acct: - connected_account = first_connected_acct - - if connected_account: - gajim.interface.new_chat_from_jid(connected_account, jid) - # preserve the 'steal focus preservation' - win = gajim.interface.msg_win_mgr.get_window(jid, - connected_account).window - if win.get_property('visible'): - win.window.focus() - return DBUS_BOOLEAN(True) - return DBUS_BOOLEAN(False) - - @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') - def change_status(self, status, message, account): - ''' change_status(status, message, account). account is optional - - if not specified status is changed for all accounts. ''' - if status not in ('offline', 'online', 'chat', - 'away', 'xa', 'dnd', 'invisible'): - return DBUS_BOOLEAN(False) - if account: - gobject.idle_add(gajim.interface.roster.send_status, account, - status, message) - else: - # account not specified, so change the status of all accounts - for acc in gajim.contacts.get_accounts(): - if not gajim.config.get_per('accounts', acc, - 'sync_with_global_status'): - continue - gobject.idle_add(gajim.interface.roster.send_status, acc, - status, message) - return DBUS_BOOLEAN(False) - - @dbus.service.method(INTERFACE, in_signature='', out_signature='') - def show_next_pending_event(self): - '''Show the window(s) with next pending event in tabbed/group chats.''' - if gajim.events.get_nb_events(): - gajim.interface.systray.handle_first_event() - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{sv}') - def contact_info(self, jid): - '''get vcard info for a contact. Return cached value of the vcard. - ''' - if not isinstance(jid, unicode): - jid = unicode(jid) - if not jid: - raise MissingArgument - return DBUS_DICT_SV() - jid = self._get_real_jid(jid) - - cached_vcard = gajim.connections.values()[0].get_cached_vcard(jid) - if cached_vcard: - return get_dbus_struct(cached_vcard) - - # return empty dict - return DBUS_DICT_SV() - - @dbus.service.method(INTERFACE, in_signature='', out_signature='as') - def list_accounts(self): - '''list register accounts''' - result = gajim.contacts.get_accounts() - result_array = dbus.Array([], signature='s') - if result and len(result) > 0: - for account in result: - result_array.append(DBUS_STRING(account)) - return result_array - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{ss}') - def account_info(self, account): - '''show info on account: resource, jid, nick, prio, message''' - result = DBUS_DICT_SS() - if gajim.connections.has_key(account): - # account is valid - con = gajim.connections[account] - index = con.connected - result['status'] = DBUS_STRING(gajim.SHOW_LIST[index]) - result['name'] = DBUS_STRING(con.name) - result['jid'] = DBUS_STRING(gajim.get_jid_from_account(con.name)) - result['message'] = DBUS_STRING(con.status) - result['priority'] = DBUS_STRING(unicode(con.priority)) - result['resource'] = DBUS_STRING(unicode(gajim.config.get_per( - 'accounts', con.name, 'resource'))) - return result - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='aa{sv}') - def list_contacts(self, account): - '''list all contacts in the roster. If the first argument is specified, - then return the contacts for the specified account''' - result = dbus.Array([], signature='aa{sv}') - accounts = gajim.contacts.get_accounts() - if len(accounts) == 0: - return result - if account: - accounts_to_search = [account] - else: - accounts_to_search = accounts - for acct in accounts_to_search: - if acct in accounts: - for jid in gajim.contacts.get_jid_list(acct): - item = self._contacts_as_dbus_structure( - gajim.contacts.get_contacts(acct, jid)) - if item: - result.append(item) - return result - - @dbus.service.method(INTERFACE, in_signature='', out_signature='') - def toggle_roster_appearance(self): - ''' shows/hides the roster window ''' - win = gajim.interface.roster.window - if win.get_property('visible'): - gobject.idle_add(win.hide) - else: - win.present() - # preserve the 'steal focus preservation' - if self._is_first(): - win.window.focus() - else: - win.window.focus(long(time())) - - @dbus.service.method(INTERFACE, in_signature='', out_signature='') - def toggle_ipython(self): - ''' shows/hides the ipython window ''' - win = gajim.ipython_window - if win: - if win.window.is_visible(): - gobject.idle_add(win.hide) - else: - win.show_all() - win.present() - else: - gajim.interface.create_ipython_window() - - @dbus.service.method(INTERFACE, in_signature='', out_signature='a{ss}') - def prefs_list(self): - prefs_dict = DBUS_DICT_SS() - def get_prefs(data, name, path, value): - if value is None: - return - key = '' - if path is not None: - for node in path: - key += node + '#' - key += name - prefs_dict[DBUS_STRING(key)] = DBUS_STRING(value[1]) - gajim.config.foreach(get_prefs) - return prefs_dict - - @dbus.service.method(INTERFACE, in_signature='', out_signature='b') - def prefs_store(self): - try: - gajim.interface.save_config() - except Exception, e: - return DBUS_BOOLEAN(False) - return DBUS_BOOLEAN(True) - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') - def prefs_del(self, key): - if not key: - return DBUS_BOOLEAN(False) - key_path = key.split('#', 2) - if len(key_path) != 3: - return DBUS_BOOLEAN(False) - if key_path[2] == '*': - gajim.config.del_per(key_path[0], key_path[1]) - else: - gajim.config.del_per(key_path[0], key_path[1], key_path[2]) - return DBUS_BOOLEAN(True) - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') - def prefs_put(self, key): - if not key: - return DBUS_BOOLEAN(False) - key_path = key.split('#', 2) - if len(key_path) < 3: - subname, value = key.split('=', 1) - gajim.config.set(subname, value) - return DBUS_BOOLEAN(True) - subname, value = key_path[2].split('=', 1) - gajim.config.set_per(key_path[0], key_path[1], subname, value) - return DBUS_BOOLEAN(True) - - @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b') - def add_contact(self, jid, account): - if account: - if account in gajim.connections and \ - gajim.connections[account].connected > 1: - # if given account is active, use it - AddNewContactWindow(account = account, jid = jid) - else: - # wrong account - return DBUS_BOOLEAN(False) - else: - # if account is not given, show account combobox - AddNewContactWindow(account = None, jid = jid) - return DBUS_BOOLEAN(True) - - @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b') - def remove_contact(self, jid, account): - jid = self._get_real_jid(jid, account) - accounts = gajim.contacts.get_accounts() - - # if there is only one account in roster, take it as default - if account: - accounts = [account] - contact_exists = False - for account in accounts: - contacts = gajim.contacts.get_contacts(account, jid) - if contacts: - gajim.connections[account].unsubscribe(jid) - for contact in contacts: - gajim.interface.roster.remove_contact(contact, account) - gajim.contacts.remove_jid(account, jid) - contact_exists = True - return DBUS_BOOLEAN(contact_exists) - - def _is_first(self): - if self.first_show: - self.first_show = False - return True - return False - - def _get_real_jid(self, jid, account = None): - '''get the real jid from the given one: removes xmpp: or get jid from nick - if account is specified, search only in this account - ''' - if account: - accounts = [account] - else: - accounts = gajim.connections.keys() - if jid.startswith('xmpp:'): - return jid[5:] # len('xmpp:') = 5 - nick_in_roster = None # Is jid a nick ? - for account in accounts: - # Does jid exists in roster of one account ? - if gajim.contacts.get_contacts(account, jid): - return jid - if not nick_in_roster: - # look in all contact if one has jid as nick - for jid_ in gajim.contacts.get_jid_list(account): - c = gajim.contacts.get_contacts(account, jid_) - if c[0].name == jid: - nick_in_roster = jid_ - break - if nick_in_roster: - # We have not found jid in roster, but we found is as a nick - return nick_in_roster - # We have not found it as jid nor as nick, probably a not in roster jid - return jid - - def _contacts_as_dbus_structure(self, contacts): - ''' get info from list of Contact objects and create dbus dict ''' - if not contacts: - return None - prim_contact = None # primary contact - for contact in contacts: - if prim_contact is None or contact.priority > prim_contact.priority: - prim_contact = contact - contact_dict = DBUS_DICT_SV() - contact_dict['name'] = DBUS_STRING(prim_contact.name) - contact_dict['show'] = DBUS_STRING(prim_contact.show) - contact_dict['jid'] = DBUS_STRING(prim_contact.jid) - if prim_contact.keyID: - keyID = None - if len(prim_contact.keyID) == 8: - keyID = prim_contact.keyID - elif len(prim_contact.keyID) == 16: - keyID = prim_contact.keyID[8:] - if keyID: - contact_dict['openpgp'] = keyID - contact_dict['resources'] = dbus.Array([], signature='(sis)') - for contact in contacts: - resource_props = dbus.Struct((DBUS_STRING(contact.resource), - dbus.Int32(contact.priority), DBUS_STRING(contact.status))) - contact_dict['resources'].append(resource_props) - contact_dict['groups'] = dbus.Array([], signature='(s)') - for group in prim_contact.groups: - contact_dict['groups'].append((DBUS_STRING(group),)) - return contact_dict - - @dbus.service.method(INTERFACE, in_signature='', out_signature='s') - def get_unread_msgs_number(self): - return DBUS_STRING(str(gajim.events.get_nb_events())) - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') - def start_chat(self, account): - if not account: - # error is shown in gajim-remote check_arguments(..) - return DBUS_BOOLEAN(False) - NewChatDialog(account) - return DBUS_BOOLEAN(True) - - @dbus.service.method(INTERFACE, in_signature='ss', out_signature='') - def send_xml(self, xml, account): - if account: - gajim.connections[account].send_stanza(xml) - else: - for acc in gajim.contacts.get_accounts(): - gajim.connections[acc].send_stanza(xml) - - @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='') - def join_room(self, room_jid, nick, password, account): - if not account: - # get the first connected account - accounts = gajim.connections.keys() - for acct in accounts: - if gajim.account_is_connected(acct): - account = acct - break - if not account: - return - if not nick: - nick = '' - gajim.interface.instances[account]['join_gc'] = \ - JoinGroupchatWindow(account, room_jid, nick) - else: - gajim.interface.join_gc_room(account, room_jid, nick, password) + import dbus.service + import dbus.glib + # type mapping + + # in most cases it is a utf-8 string + DBUS_STRING = dbus.String + + # general type (for use in dicts, where all values should have the same type) + DBUS_BOOLEAN = dbus.Boolean + DBUS_DOUBLE = dbus.Double + DBUS_INT32 = dbus.Int32 + # dictionary with string key and binary value + DBUS_DICT_SV = lambda : dbus.Dictionary({}, signature="sv") + # dictionary with string key and value + DBUS_DICT_SS = lambda : dbus.Dictionary({}, signature="ss") + # empty type (there is no equivalent of None on D-Bus, but historically gajim + # used 0 instead) + DBUS_NONE = lambda : dbus.Int32(0) + + def get_dbus_struct(obj): + ''' recursively go through all the items and replace + them with their casted dbus equivalents + ''' + if obj is None: + return DBUS_NONE() + if isinstance(obj, (unicode, str)): + return DBUS_STRING(obj) + if isinstance(obj, int): + return DBUS_INT32(obj) + if isinstance(obj, float): + return DBUS_DOUBLE(obj) + if isinstance(obj, bool): + return DBUS_BOOLEAN(obj) + if isinstance(obj, (list, tuple)): + result = dbus.Array([get_dbus_struct(i) for i in obj], + signature='v') + if result == []: + return DBUS_NONE() + return result + if isinstance(obj, dict): + result = DBUS_DICT_SV() + for key, value in obj.items(): + result[DBUS_STRING(key)] = get_dbus_struct(value) + if result == {}: + return DBUS_NONE() + return result + # unknown type + return DBUS_NONE() + + class SignalObject(dbus.service.Object): + ''' Local object definition for /org/gajim/dbus/RemoteObject. + (This docstring is not be visible, because the clients can access only the remote object.)''' + + def __init__(self, bus_name): + self.first_show = True + self.vcard_account = None + + # register our dbus API + dbus.service.Object.__init__(self, bus_name, OBJ_PATH) + + @dbus.service.signal(INTERFACE, signature='av') + def Roster(self, account_and_data): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def AccountPresence(self, status_and_account): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def ContactPresence(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def ContactAbsence(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def ContactStatus(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def NewMessage(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def Subscribe(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def Subscribed(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def Unsubscribed(self, account_and_jid): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def NewAccount(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def VcardInfo(self, account_and_vcard): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def LastStatusTime(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def OsInfo(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def GCPresence(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def GCMessage(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def RosterInfo(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def NewGmail(self, account_and_array): + pass + + def raise_signal(self, signal, arg): + '''raise a signal, with a single argument of unspecified type + Instead of obj.raise_signal("Foo", bar), use obj.Foo(bar).''' + getattr(self, signal)(arg) + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='s') + def get_status(self, account): + '''Returns status (show to be exact) which is the global one + unless account is given''' + if not account: + # If user did not ask for account, returns the global status + return DBUS_STRING(helpers.get_global_show()) + # return show for the given account + index = gajim.connections[account].connected + return DBUS_STRING(gajim.SHOW_LIST[index]) + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='s') + def get_status_message(self, account): + '''Returns status which is the global one + unless account is given''' + if not account: + # If user did not ask for account, returns the global status + return DBUS_STRING(str(helpers.get_global_status())) + # return show for the given account + status = gajim.connections[account].status + return DBUS_STRING(status) + + def _get_account_and_contact(self, account, jid): + '''get the account (if not given) and contact instance from jid''' + connected_account = None + contact = None + accounts = gajim.contacts.get_accounts() + # if there is only one account in roster, take it as default + # if user did not ask for account + if not account and len(accounts) == 1: + account = accounts[0] + if account: + if gajim.connections[account].connected > 1: # account is connected + connected_account = account + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + else: + for account in accounts: + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + if contact and gajim.connections[account].connected > 1: + # account is connected + connected_account = account + break + if not contact: + contact = jid + + return connected_account, contact + + def _get_account_for_groupchat(self, account, room_jid): + '''get the account which is connected to groupchat (if not given) + or check if the given account is connected to the groupchat''' + connected_account = None + accounts = gajim.contacts.get_accounts() + # if there is only one account in roster, take it as default + # if user did not ask for account + if not account and len(accounts) == 1: + account = accounts[0] + if account: + if gajim.connections[account].connected > 1 and \ + room_jid in gajim.gc_connected[account] and \ + gajim.gc_connected[account][room_jid]: + # account and groupchat are connected + connected_account = account + else: + for account in accounts: + if gajim.connections[account].connected > 1 and \ + room_jid in gajim.gc_connected[account] and \ + gajim.gc_connected[account][room_jid]: + # account and groupchat are connected + connected_account = account + break + return connected_account + + @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') + def send_file(self, file_path, jid, account): + '''send file, located at 'file_path' to 'jid', using account + (optional) 'account' ''' + jid = self._get_real_jid(jid, account) + connected_account, contact = self._get_account_and_contact(account, jid) + + if connected_account: + if file_path[:7] == 'file://': + file_path=file_path[7:] + if os.path.isfile(file_path): # is it file? + gajim.interface.instances['file_transfers'].send_file( + connected_account, contact, file_path) + return DBUS_BOOLEAN(True) + return DBUS_BOOLEAN(False) + + def _send_message(self, jid, message, keyID, account, type = 'chat', + subject = None): + '''can be called from send_chat_message (default when send_message) + or send_single_message''' + if not jid or not message: + return DBUS_BOOLEAN(False) + if not keyID: + keyID = '' + + connected_account, contact = self._get_account_and_contact(account, jid) + if connected_account: + connection = gajim.connections[connected_account] + connection.send_message(jid, message, keyID, type, subject) + return DBUS_BOOLEAN(True) + return DBUS_BOOLEAN(False) + + @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='b') + def send_chat_message(self, jid, message, keyID, account): + '''Send chat 'message' to 'jid', using account (optional) 'account'. + if keyID is specified, encrypt the message with the pgp key ''' + jid = self._get_real_jid(jid, account) + return self._send_message(jid, message, keyID, account) + + @dbus.service.method(INTERFACE, in_signature='sssss', out_signature='b') + def send_single_message(self, jid, subject, message, keyID, account): + '''Send single 'message' to 'jid', using account (optional) 'account'. + if keyID is specified, encrypt the message with the pgp key ''' + jid = self._get_real_jid(jid, account) + return self._send_message(jid, message, keyID, account, type, subject) + + @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') + def send_groupchat_message(self, room_jid, message, account): + '''Send 'message' to groupchat 'room_jid', + using account (optional) 'account'.''' + if not room_jid or not message: + return DBUS_BOOLEAN(False) + connected_account = self._get_account_for_groupchat(account, room_jid) + if connected_account: + connection = gajim.connections[connected_account] + connection.send_gc_message(room_jid, message) + return DBUS_BOOLEAN(True) + return DBUS_BOOLEAN(False) + + @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b') + def open_chat(self, jid, account): + '''Shows the tabbed window for new message to 'jid', using account + (optional) 'account' ''' + if not jid: + raise MissingArgument + return DBUS_BOOLEAN(False) + jid = self._get_real_jid(jid, account) + try: + jid = helpers.parse_jid(jid) + except: + # Jid is not conform, ignore it + return DBUS_BOOLEAN(False) + + if account: + accounts = [account] + else: + accounts = gajim.connections.keys() + if len(accounts) == 1: + account = accounts[0] + connected_account = None + first_connected_acct = None + for acct in accounts: + if gajim.connections[acct].connected > 1: # account is online + contact = gajim.contacts.get_first_contact_from_jid(acct, jid) + if gajim.interface.msg_win_mgr.has_window(jid, acct): + connected_account = acct + break + # jid is in roster + elif contact: + connected_account = acct + break + # we send the message to jid not in roster, because account is + # specified, or there is only one account + elif account: + connected_account = acct + elif first_connected_acct is None: + first_connected_acct = acct + + # if jid is not a conntact, open-chat with first connected account + if connected_account is None and first_connected_acct: + connected_account = first_connected_acct + + if connected_account: + gajim.interface.new_chat_from_jid(connected_account, jid) + # preserve the 'steal focus preservation' + win = gajim.interface.msg_win_mgr.get_window(jid, + connected_account).window + if win.get_property('visible'): + win.window.focus() + return DBUS_BOOLEAN(True) + return DBUS_BOOLEAN(False) + + @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') + def change_status(self, status, message, account): + ''' change_status(status, message, account). account is optional - + if not specified status is changed for all accounts. ''' + if status not in ('offline', 'online', 'chat', + 'away', 'xa', 'dnd', 'invisible'): + return DBUS_BOOLEAN(False) + if account: + gobject.idle_add(gajim.interface.roster.send_status, account, + status, message) + else: + # account not specified, so change the status of all accounts + for acc in gajim.contacts.get_accounts(): + if not gajim.config.get_per('accounts', acc, + 'sync_with_global_status'): + continue + gobject.idle_add(gajim.interface.roster.send_status, acc, + status, message) + return DBUS_BOOLEAN(False) + + @dbus.service.method(INTERFACE, in_signature='', out_signature='') + def show_next_pending_event(self): + '''Show the window(s) with next pending event in tabbed/group chats.''' + if gajim.events.get_nb_events(): + gajim.interface.systray.handle_first_event() + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{sv}') + def contact_info(self, jid): + '''get vcard info for a contact. Return cached value of the vcard. + ''' + if not isinstance(jid, unicode): + jid = unicode(jid) + if not jid: + raise MissingArgument + return DBUS_DICT_SV() + jid = self._get_real_jid(jid) + + cached_vcard = gajim.connections.values()[0].get_cached_vcard(jid) + if cached_vcard: + return get_dbus_struct(cached_vcard) + + # return empty dict + return DBUS_DICT_SV() + + @dbus.service.method(INTERFACE, in_signature='', out_signature='as') + def list_accounts(self): + '''list register accounts''' + result = gajim.contacts.get_accounts() + result_array = dbus.Array([], signature='s') + if result and len(result) > 0: + for account in result: + result_array.append(DBUS_STRING(account)) + return result_array + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{ss}') + def account_info(self, account): + '''show info on account: resource, jid, nick, prio, message''' + result = DBUS_DICT_SS() + if gajim.connections.has_key(account): + # account is valid + con = gajim.connections[account] + index = con.connected + result['status'] = DBUS_STRING(gajim.SHOW_LIST[index]) + result['name'] = DBUS_STRING(con.name) + result['jid'] = DBUS_STRING(gajim.get_jid_from_account(con.name)) + result['message'] = DBUS_STRING(con.status) + result['priority'] = DBUS_STRING(unicode(con.priority)) + result['resource'] = DBUS_STRING(unicode(gajim.config.get_per( + 'accounts', con.name, 'resource'))) + return result + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='aa{sv}') + def list_contacts(self, account): + '''list all contacts in the roster. If the first argument is specified, + then return the contacts for the specified account''' + result = dbus.Array([], signature='aa{sv}') + accounts = gajim.contacts.get_accounts() + if len(accounts) == 0: + return result + if account: + accounts_to_search = [account] + else: + accounts_to_search = accounts + for acct in accounts_to_search: + if acct in accounts: + for jid in gajim.contacts.get_jid_list(acct): + item = self._contacts_as_dbus_structure( + gajim.contacts.get_contacts(acct, jid)) + if item: + result.append(item) + return result + + @dbus.service.method(INTERFACE, in_signature='', out_signature='') + def toggle_roster_appearance(self): + ''' shows/hides the roster window ''' + win = gajim.interface.roster.window + if win.get_property('visible'): + gobject.idle_add(win.hide) + else: + win.present() + # preserve the 'steal focus preservation' + if self._is_first(): + win.window.focus() + else: + win.window.focus(long(time())) + + @dbus.service.method(INTERFACE, in_signature='', out_signature='') + def toggle_ipython(self): + ''' shows/hides the ipython window ''' + win = gajim.ipython_window + if win: + if win.window.is_visible(): + gobject.idle_add(win.hide) + else: + win.show_all() + win.present() + else: + gajim.interface.create_ipython_window() + + @dbus.service.method(INTERFACE, in_signature='', out_signature='a{ss}') + def prefs_list(self): + prefs_dict = DBUS_DICT_SS() + def get_prefs(data, name, path, value): + if value is None: + return + key = '' + if path is not None: + for node in path: + key += node + '#' + key += name + prefs_dict[DBUS_STRING(key)] = DBUS_STRING(value[1]) + gajim.config.foreach(get_prefs) + return prefs_dict + + @dbus.service.method(INTERFACE, in_signature='', out_signature='b') + def prefs_store(self): + try: + gajim.interface.save_config() + except Exception, e: + return DBUS_BOOLEAN(False) + return DBUS_BOOLEAN(True) + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') + def prefs_del(self, key): + if not key: + return DBUS_BOOLEAN(False) + key_path = key.split('#', 2) + if len(key_path) != 3: + return DBUS_BOOLEAN(False) + if key_path[2] == '*': + gajim.config.del_per(key_path[0], key_path[1]) + else: + gajim.config.del_per(key_path[0], key_path[1], key_path[2]) + return DBUS_BOOLEAN(True) + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') + def prefs_put(self, key): + if not key: + return DBUS_BOOLEAN(False) + key_path = key.split('#', 2) + if len(key_path) < 3: + subname, value = key.split('=', 1) + gajim.config.set(subname, value) + return DBUS_BOOLEAN(True) + subname, value = key_path[2].split('=', 1) + gajim.config.set_per(key_path[0], key_path[1], subname, value) + return DBUS_BOOLEAN(True) + + @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b') + def add_contact(self, jid, account): + if account: + if account in gajim.connections and \ + gajim.connections[account].connected > 1: + # if given account is active, use it + AddNewContactWindow(account = account, jid = jid) + else: + # wrong account + return DBUS_BOOLEAN(False) + else: + # if account is not given, show account combobox + AddNewContactWindow(account = None, jid = jid) + return DBUS_BOOLEAN(True) + + @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b') + def remove_contact(self, jid, account): + jid = self._get_real_jid(jid, account) + accounts = gajim.contacts.get_accounts() + + # if there is only one account in roster, take it as default + if account: + accounts = [account] + contact_exists = False + for account in accounts: + contacts = gajim.contacts.get_contacts(account, jid) + if contacts: + gajim.connections[account].unsubscribe(jid) + for contact in contacts: + gajim.interface.roster.remove_contact(contact, account) + gajim.contacts.remove_jid(account, jid) + contact_exists = True + return DBUS_BOOLEAN(contact_exists) + + def _is_first(self): + if self.first_show: + self.first_show = False + return True + return False + + def _get_real_jid(self, jid, account = None): + '''get the real jid from the given one: removes xmpp: or get jid from nick + if account is specified, search only in this account + ''' + if account: + accounts = [account] + else: + accounts = gajim.connections.keys() + if jid.startswith('xmpp:'): + return jid[5:] # len('xmpp:') = 5 + nick_in_roster = None # Is jid a nick ? + for account in accounts: + # Does jid exists in roster of one account ? + if gajim.contacts.get_contacts(account, jid): + return jid + if not nick_in_roster: + # look in all contact if one has jid as nick + for jid_ in gajim.contacts.get_jid_list(account): + c = gajim.contacts.get_contacts(account, jid_) + if c[0].name == jid: + nick_in_roster = jid_ + break + if nick_in_roster: + # We have not found jid in roster, but we found is as a nick + return nick_in_roster + # We have not found it as jid nor as nick, probably a not in roster jid + return jid + + def _contacts_as_dbus_structure(self, contacts): + ''' get info from list of Contact objects and create dbus dict ''' + if not contacts: + return None + prim_contact = None # primary contact + for contact in contacts: + if prim_contact is None or contact.priority > prim_contact.priority: + prim_contact = contact + contact_dict = DBUS_DICT_SV() + contact_dict['name'] = DBUS_STRING(prim_contact.name) + contact_dict['show'] = DBUS_STRING(prim_contact.show) + contact_dict['jid'] = DBUS_STRING(prim_contact.jid) + if prim_contact.keyID: + keyID = None + if len(prim_contact.keyID) == 8: + keyID = prim_contact.keyID + elif len(prim_contact.keyID) == 16: + keyID = prim_contact.keyID[8:] + if keyID: + contact_dict['openpgp'] = keyID + contact_dict['resources'] = dbus.Array([], signature='(sis)') + for contact in contacts: + resource_props = dbus.Struct((DBUS_STRING(contact.resource), + dbus.Int32(contact.priority), DBUS_STRING(contact.status))) + contact_dict['resources'].append(resource_props) + contact_dict['groups'] = dbus.Array([], signature='(s)') + for group in prim_contact.groups: + contact_dict['groups'].append((DBUS_STRING(group),)) + return contact_dict + + @dbus.service.method(INTERFACE, in_signature='', out_signature='s') + def get_unread_msgs_number(self): + return DBUS_STRING(str(gajim.events.get_nb_events())) + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') + def start_chat(self, account): + if not account: + # error is shown in gajim-remote check_arguments(..) + return DBUS_BOOLEAN(False) + NewChatDialog(account) + return DBUS_BOOLEAN(True) + + @dbus.service.method(INTERFACE, in_signature='ss', out_signature='') + def send_xml(self, xml, account): + if account: + gajim.connections[account].send_stanza(xml) + else: + for acc in gajim.contacts.get_accounts(): + gajim.connections[acc].send_stanza(xml) + + @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='') + def join_room(self, room_jid, nick, password, account): + if not account: + # get the first connected account + accounts = gajim.connections.keys() + for acct in accounts: + if gajim.account_is_connected(acct): + account = acct + break + if not account: + return + if not nick: + nick = '' + gajim.interface.instances[account]['join_gc'] = \ + JoinGroupchatWindow(account, room_jid, nick) + else: + gajim.interface.join_gc_room(account, room_jid, nick, password) from common import gajim from common import helpers @@ -680,62 +680,59 @@ from plugins.helpers import log_calls, log from common import ged class DBusPlugin(GajimPlugin): - name = u'D-Bus Support' - short_name = u'dbus' - version = u'0.1' - description = u'''D-Bus support. Based on remote_control module from + name = u'D-Bus Support' + short_name = u'dbus' + version = u'0.1' + description = u'''D-Bus support. Based on remote_control module from Gajim core but uses new events handling system.''' - authors = [u'Mateusz Biliński '] - homepage = u'http://blog.bilinski.it' - - @log_calls('DBusPlugin') - def init(self): - self.config_dialog = None - #self.gui_extension_points = {} - #self.config_default_values = {} - - self.events_names = ['Roster', 'AccountPresence', 'ContactPresence', - 'ContactAbsence', 'ContactStatus', 'NewMessage', - 'Subscribe', 'Subscribed', 'Unsubscribed', - 'NewAccount', 'VcardInfo', 'LastStatusTime', - 'OsInfo', 'GCPresence', 'GCMessage', 'RosterInfo', - 'NewGmail'] - - self.signal_object = None - - self.events_handlers = {} - self._set_handling_methods() - - @log_calls('DBusPlugin') - def activate(self): - session_bus = dbus_support.session_bus.SessionBus() + authors = [u'Mateusz Biliński '] + homepage = u'http://blog.bilinski.it' - bus_name = dbus.service.BusName(SERVICE, bus=session_bus) - self.signal_object = SignalObject(bus_name) - - @log_calls('DBusPlugin') - def deactivate(self): - self.signal_object.remove_from_connection() - self.signal_object = None + @log_calls('DBusPlugin') + def init(self): + self.config_dialog = None + #self.gui_extension_points = {} + #self.config_default_values = {} - @log_calls('DBusPlugin') - def _set_handling_methods(self): - for event_name in self.events_names: - setattr(self, event_name, - new.instancemethod( - self._generate_handling_method(event_name), - self, - DBusPlugin)) - self.events_handlers[event_name] = (ged.POSTCORE, - getattr(self, event_name)) - - def _generate_handling_method(self, event_name): - def handler(self, arg): - #print "Handler of event %s called"%(event_name) - if self.signal_object: - getattr(self.signal_object, event_name)(get_dbus_struct(arg)) - - return handler - + self.events_names = ['Roster', 'AccountPresence', 'ContactPresence', + 'ContactAbsence', 'ContactStatus', 'NewMessage', + 'Subscribe', 'Subscribed', 'Unsubscribed', + 'NewAccount', 'VcardInfo', 'LastStatusTime', + 'OsInfo', 'GCPresence', 'GCMessage', 'RosterInfo', + 'NewGmail'] - \ No newline at end of file + self.signal_object = None + + self.events_handlers = {} + self._set_handling_methods() + + @log_calls('DBusPlugin') + def activate(self): + session_bus = dbus_support.session_bus.SessionBus() + + bus_name = dbus.service.BusName(SERVICE, bus=session_bus) + self.signal_object = SignalObject(bus_name) + + @log_calls('DBusPlugin') + def deactivate(self): + self.signal_object.remove_from_connection() + self.signal_object = None + + @log_calls('DBusPlugin') + def _set_handling_methods(self): + for event_name in self.events_names: + setattr(self, event_name, + new.instancemethod( + self._generate_handling_method(event_name), + self, + DBusPlugin)) + self.events_handlers[event_name] = (ged.POSTCORE, + getattr(self, event_name)) + + def _generate_handling_method(self, event_name): + def handler(self, arg): + #print "Handler of event %s called"%(event_name) + if self.signal_object: + getattr(self.signal_object, event_name)(get_dbus_struct(arg)) + + return handler diff --git a/plugins/events_dump/__init__.py b/plugins/events_dump/__init__.py index fc198d87d..de174c1b9 100644 --- a/plugins/events_dump/__init__.py +++ b/plugins/events_dump/__init__.py @@ -1 +1 @@ -from plugin import EventsDumpPlugin \ No newline at end of file +from plugin import EventsDumpPlugin diff --git a/plugins/events_dump/plugin.py b/plugins/events_dump/plugin.py index 9aa4565b1..3e816ae3a 100644 --- a/plugins/events_dump/plugin.py +++ b/plugins/events_dump/plugin.py @@ -17,7 +17,7 @@ ''' Events Dump plugin. -Dumps info about selected events to console. +Dumps info about selected events to console. :author: Mateusz Biliński :since: 10th August 2008 @@ -33,97 +33,97 @@ from plugins.helpers import log_calls, log from common import ged class EventsDumpPlugin(GajimPlugin): - name = u'Events Dump' - short_name = u'events_dump' - version = u'0.1' - description = u'''Dumps info about selected events to console.''' - authors = [u'Mateusz Biliński '] - homepage = u'http://blog.bilinski.it' - - @log_calls('EventsDumpPlugin') - def init(self): - self.config_dialog = None - #self.gui_extension_points = {} - #self.config_default_values = {} - events_from_old_dbus_support = [ - 'Roster', 'AccountPresence', 'ContactPresence', - 'ContactAbsence', 'ContactStatus', 'NewMessage', - 'Subscribe', 'Subscribed', 'Unsubscribed', - 'NewAccount', 'VcardInfo', 'LastStatusTime', - 'OsInfo', 'GCPresence', 'GCMessage', 'RosterInfo', - 'NewGmail'] - - events_from_src_gajim = [ - 'ROSTER', 'WARNING', 'ERROR', - 'INFORMATION', 'ERROR_ANSWER', 'STATUS', - 'NOTIFY', 'MSGERROR', 'MSGSENT', 'MSGNOTSENT', - 'SUBSCRIBED', 'UNSUBSCRIBED', 'SUBSCRIBE', - 'AGENT_ERROR_INFO', 'AGENT_ERROR_ITEMS', - 'AGENT_REMOVED', 'REGISTER_AGENT_INFO', - 'AGENT_INFO_ITEMS', 'AGENT_INFO_INFO', - 'QUIT', 'NEW_ACC_CONNECTED', - 'NEW_ACC_NOT_CONNECTED', 'ACC_OK', 'ACC_NOT_OK', - 'MYVCARD', 'VCARD', 'LAST_STATUS_TIME', 'OS_INFO', - 'GC_NOTIFY', 'GC_MSG', 'GC_SUBJECT', 'GC_CONFIG', - 'GC_CONFIG_CHANGE', 'GC_INVITATION', - 'GC_AFFILIATION', 'GC_PASSWORD_REQUIRED', - 'BAD_PASSPHRASE', 'ROSTER_INFO', 'BOOKMARKS', - 'CON_TYPE', 'CONNECTION_LOST', 'FILE_REQUEST', - 'GMAIL_NOTIFY', 'FILE_REQUEST_ERROR', - 'FILE_SEND_ERROR', 'STANZA_ARRIVED', 'STANZA_SENT', - 'HTTP_AUTH', 'VCARD_PUBLISHED', - 'VCARD_NOT_PUBLISHED', 'ASK_NEW_NICK', 'SIGNED_IN', - 'METACONTACTS', 'ATOM_ENTRY', 'FAILED_DECRYPT', - 'PRIVACY_LISTS_RECEIVED', 'PRIVACY_LIST_RECEIVED', - 'PRIVACY_LISTS_ACTIVE_DEFAULT', - 'PRIVACY_LIST_REMOVED', 'ZC_NAME_CONFLICT', - 'PING_SENT', 'PING_REPLY', 'PING_ERROR', - 'SEARCH_FORM', 'SEARCH_RESULT', - 'RESOURCE_CONFLICT', 'PEP_CONFIG', - 'UNIQUE_ROOM_ID_UNSUPPORTED', - 'UNIQUE_ROOM_ID_SUPPORTED', 'SESSION_NEG', - 'GPG_PASSWORD_REQUIRED', 'SSL_ERROR', - 'FINGERPRINT_ERROR', 'PLAIN_CONNECTION', - 'PUBSUB_NODE_REMOVED', 'PUBSUB_NODE_NOT_REMOVED'] - - network_events_from_core = ['raw-message-received', - 'raw-iq-received', - 'raw-pres-received'] - - network_events_generated_in_nec = [ - 'customized-message-received', - 'more-customized-message-received', - 'modify-only-message-received', - 'enriched-chat-message-received'] - - self.events_names = [] - self.events_names += network_events_from_core - self.events_names += network_events_generated_in_nec - - self.events_handlers = {} - self._set_handling_methods() - - @log_calls('EventsDumpPlugin') - def activate(self): - pass - - @log_calls('EventsDumpPlugin') - def deactivate(self): - pass + name = u'Events Dump' + short_name = u'events_dump' + version = u'0.1' + description = u'''Dumps info about selected events to console.''' + authors = [u'Mateusz Biliński '] + homepage = u'http://blog.bilinski.it' - @log_calls('EventsDumpPlugin') - def _set_handling_methods(self): - for event_name in self.events_names: - setattr(self, event_name, - new.instancemethod( - self._generate_handling_method(event_name), - self, - EventsDumpPlugin)) - self.events_handlers[event_name] = (ged.POSTCORE, - getattr(self, event_name)) - - def _generate_handling_method(self, event_name): - def handler(self, *args): - print "Event '%s' occured. Arguments: %s\n\n===\n"%(event_name, pformat(args)) - - return handler \ No newline at end of file + @log_calls('EventsDumpPlugin') + def init(self): + self.config_dialog = None + #self.gui_extension_points = {} + #self.config_default_values = {} + events_from_old_dbus_support = [ + 'Roster', 'AccountPresence', 'ContactPresence', + 'ContactAbsence', 'ContactStatus', 'NewMessage', + 'Subscribe', 'Subscribed', 'Unsubscribed', + 'NewAccount', 'VcardInfo', 'LastStatusTime', + 'OsInfo', 'GCPresence', 'GCMessage', 'RosterInfo', + 'NewGmail'] + + events_from_src_gajim = [ + 'ROSTER', 'WARNING', 'ERROR', + 'INFORMATION', 'ERROR_ANSWER', 'STATUS', + 'NOTIFY', 'MSGERROR', 'MSGSENT', 'MSGNOTSENT', + 'SUBSCRIBED', 'UNSUBSCRIBED', 'SUBSCRIBE', + 'AGENT_ERROR_INFO', 'AGENT_ERROR_ITEMS', + 'AGENT_REMOVED', 'REGISTER_AGENT_INFO', + 'AGENT_INFO_ITEMS', 'AGENT_INFO_INFO', + 'QUIT', 'NEW_ACC_CONNECTED', + 'NEW_ACC_NOT_CONNECTED', 'ACC_OK', 'ACC_NOT_OK', + 'MYVCARD', 'VCARD', 'LAST_STATUS_TIME', 'OS_INFO', + 'GC_NOTIFY', 'GC_MSG', 'GC_SUBJECT', 'GC_CONFIG', + 'GC_CONFIG_CHANGE', 'GC_INVITATION', + 'GC_AFFILIATION', 'GC_PASSWORD_REQUIRED', + 'BAD_PASSPHRASE', 'ROSTER_INFO', 'BOOKMARKS', + 'CON_TYPE', 'CONNECTION_LOST', 'FILE_REQUEST', + 'GMAIL_NOTIFY', 'FILE_REQUEST_ERROR', + 'FILE_SEND_ERROR', 'STANZA_ARRIVED', 'STANZA_SENT', + 'HTTP_AUTH', 'VCARD_PUBLISHED', + 'VCARD_NOT_PUBLISHED', 'ASK_NEW_NICK', 'SIGNED_IN', + 'METACONTACTS', 'ATOM_ENTRY', 'FAILED_DECRYPT', + 'PRIVACY_LISTS_RECEIVED', 'PRIVACY_LIST_RECEIVED', + 'PRIVACY_LISTS_ACTIVE_DEFAULT', + 'PRIVACY_LIST_REMOVED', 'ZC_NAME_CONFLICT', + 'PING_SENT', 'PING_REPLY', 'PING_ERROR', + 'SEARCH_FORM', 'SEARCH_RESULT', + 'RESOURCE_CONFLICT', 'PEP_CONFIG', + 'UNIQUE_ROOM_ID_UNSUPPORTED', + 'UNIQUE_ROOM_ID_SUPPORTED', 'SESSION_NEG', + 'GPG_PASSWORD_REQUIRED', 'SSL_ERROR', + 'FINGERPRINT_ERROR', 'PLAIN_CONNECTION', + 'PUBSUB_NODE_REMOVED', 'PUBSUB_NODE_NOT_REMOVED'] + + network_events_from_core = ['raw-message-received', + 'raw-iq-received', + 'raw-pres-received'] + + network_events_generated_in_nec = [ + 'customized-message-received', + 'more-customized-message-received', + 'modify-only-message-received', + 'enriched-chat-message-received'] + + self.events_names = [] + self.events_names += network_events_from_core + self.events_names += network_events_generated_in_nec + + self.events_handlers = {} + self._set_handling_methods() + + @log_calls('EventsDumpPlugin') + def activate(self): + pass + + @log_calls('EventsDumpPlugin') + def deactivate(self): + pass + + @log_calls('EventsDumpPlugin') + def _set_handling_methods(self): + for event_name in self.events_names: + setattr(self, event_name, + new.instancemethod( + self._generate_handling_method(event_name), + self, + EventsDumpPlugin)) + self.events_handlers[event_name] = (ged.POSTCORE, + getattr(self, event_name)) + + def _generate_handling_method(self, event_name): + def handler(self, *args): + print "Event '%s' occured. Arguments: %s\n\n===\n"%(event_name, pformat(args)) + + return handler diff --git a/plugins/google_translation/__init__.py b/plugins/google_translation/__init__.py index 33fb27f0f..dc2c3bc36 100644 --- a/plugins/google_translation/__init__.py +++ b/plugins/google_translation/__init__.py @@ -1 +1 @@ -from plugin import GoogleTranslationPlugin \ No newline at end of file +from plugin import GoogleTranslationPlugin diff --git a/plugins/google_translation/plugin.py b/plugins/google_translation/plugin.py index b2f920e37..a20664060 100644 --- a/plugins/google_translation/plugin.py +++ b/plugins/google_translation/plugin.py @@ -40,80 +40,79 @@ from common import ged from common import nec class GoogleTranslationPlugin(GajimPlugin): - name = u'Google Translation' - short_name = u'google_translation' - version = u'0.1' - description = u'''Translates (currently only incoming) messages using Google Translate.''' - authors = [u'Mateusz Biliński '] - homepage = u'http://blog.bilinski.it' - - @log_calls('GoogleTranslationPlugin') - def init(self): - self.config_dialog = None - #self.gui_extension_points = {} - self.config_default_values = {'from_lang' : (u'en', _(u'Language of text to be translated')), - 'to_lang' : (u'fr', _(u'Language to which translation will be made')), - 'user_agent' : (u'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.12) Gecko/20080213 Firefox/2.0.0.11', - _(u'User Agent data to be used with urllib2 when connecting to Google Translate service'))} - - #self.events_handlers = {} - - self.events = [GoogleTranslateMessageReceivedEvent] - - self.translated_text_re = \ - re.compile(r'google.language.callbacks.id100\(\'22\',{"translatedText":"(?P[^"]*)"}, 200, null, 200\)') - - @log_calls('GoogleTranslationPlugin') - def translate_text(self, text, from_lang, to_lang): - text = self.prepare_text_for_url(text) - headers = { 'User-Agent' : self.config['user_agent'] } - translation_url = u'http://www.google.com/uds/Gtranslate?callback=google.language.callbacks.id100&context=22&q=%(text)s&langpair=%(from_lang)s%%7C%(to_lang)s&key=notsupplied&v=1.0'%locals() - - request = urllib2.Request(translation_url, headers=headers) - response = urllib2.urlopen(request) - results = response.read() - - translated_text = self.translated_text_re.search(results).group('text') - - return translated_text - - @log_calls('GoogleTranslationPlugin') - def prepare_text_for_url(self, text): - ''' - Converts text so it can be used within URL as query to Google Translate. - ''' - - # There should be more replacements for plugin to work in any case: - char_replacements = { ' ' : '%20', - '+' : '%2B'} - - for char, replacement in char_replacements.iteritems(): - text = text.replace(char, replacement) - - return text - - @log_calls('GoogleTranslationPlugin') - def activate(self): - pass - - @log_calls('GoogleTranslationPlugin') - def deactivate(self): - pass - + name = u'Google Translation' + short_name = u'google_translation' + version = u'0.1' + description = u'''Translates (currently only incoming) messages using Google Translate.''' + authors = [u'Mateusz Biliński '] + homepage = u'http://blog.bilinski.it' + + @log_calls('GoogleTranslationPlugin') + def init(self): + self.config_dialog = None + #self.gui_extension_points = {} + self.config_default_values = {'from_lang' : (u'en', _(u'Language of text to be translated')), + 'to_lang' : (u'fr', _(u'Language to which translation will be made')), + 'user_agent' : (u'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.12) Gecko/20080213 Firefox/2.0.0.11', + _(u'User Agent data to be used with urllib2 when connecting to Google Translate service'))} + + #self.events_handlers = {} + + self.events = [GoogleTranslateMessageReceivedEvent] + + self.translated_text_re = \ + re.compile(r'google.language.callbacks.id100\(\'22\',{"translatedText":"(?P[^"]*)"}, 200, null, 200\)') + + @log_calls('GoogleTranslationPlugin') + def translate_text(self, text, from_lang, to_lang): + text = self.prepare_text_for_url(text) + headers = { 'User-Agent' : self.config['user_agent'] } + translation_url = u'http://www.google.com/uds/Gtranslate?callback=google.language.callbacks.id100&context=22&q=%(text)s&langpair=%(from_lang)s%%7C%(to_lang)s&key=notsupplied&v=1.0'%locals() + + request = urllib2.Request(translation_url, headers=headers) + response = urllib2.urlopen(request) + results = response.read() + + translated_text = self.translated_text_re.search(results).group('text') + + return translated_text + + @log_calls('GoogleTranslationPlugin') + def prepare_text_for_url(self, text): + ''' + Converts text so it can be used within URL as query to Google Translate. + ''' + + # There should be more replacements for plugin to work in any case: + char_replacements = { ' ' : '%20', + '+' : '%2B'} + + for char, replacement in char_replacements.iteritems(): + text = text.replace(char, replacement) + + return text + + @log_calls('GoogleTranslationPlugin') + def activate(self): + pass + + @log_calls('GoogleTranslationPlugin') + def deactivate(self): + pass + class GoogleTranslateMessageReceivedEvent(nec.NetworkIncomingEvent): - name = 'google-translate-message-received' - base_network_events = ['raw-message-received'] - - def generate(self): - msg_type = self.base_event.xmpp_msg.attrs.get('type', None) - if msg_type == u'chat': - msg_text = "".join(self.base_event.xmpp_msg.kids[0].data) - if msg_text: - from_lang = self.plugin.config['from_lang'] - to_lang = self.plugin.config['to_lang'] - self.base_event.xmpp_msg.kids[0].setData( - self.plugin.translate_text(msg_text, from_lang, to_lang)) - - return False # We only want to modify old event, not emit another, - # so we return False here. - + name = 'google-translate-message-received' + base_network_events = ['raw-message-received'] + + def generate(self): + msg_type = self.base_event.xmpp_msg.attrs.get('type', None) + if msg_type == u'chat': + msg_text = "".join(self.base_event.xmpp_msg.kids[0].data) + if msg_text: + from_lang = self.plugin.config['from_lang'] + to_lang = self.plugin.config['to_lang'] + self.base_event.xmpp_msg.kids[0].setData( + self.plugin.translate_text(msg_text, from_lang, to_lang)) + + return False # We only want to modify old event, not emit another, + # so we return False here. diff --git a/plugins/length_notifier/__init__.py b/plugins/length_notifier/__init__.py index fc433a0af..67c8c614a 100644 --- a/plugins/length_notifier/__init__.py +++ b/plugins/length_notifier/__init__.py @@ -1,2 +1,2 @@ -from length_notifier import LengthNotifierPlugin \ No newline at end of file +from length_notifier import LengthNotifierPlugin diff --git a/plugins/length_notifier/length_notifier.py b/plugins/length_notifier/length_notifier.py index 86413ba13..cde132208 100644 --- a/plugins/length_notifier/length_notifier.py +++ b/plugins/length_notifier/length_notifier.py @@ -34,129 +34,128 @@ from plugins.helpers import log, log_calls from plugins.gui import GajimPluginConfigDialog class LengthNotifierPlugin(GajimPlugin): - name = u'Message Length Notifier' - short_name = u'length_notifier' - version = u'0.1' - description = u'''Highlights message entry field in chat window when given length of message is exceeded.''' - authors = [u'Mateusz Biliński '] - homepage = u'http://blog.bilinski.it' - - @log_calls('LengthNotifierPlugin') - def init(self): - self.config_dialog = LengthNotifierPluginConfigDialog(self) - - self.gui_extension_points = { - 'chat_control' : (self.connect_with_chat_control, - self.disconnect_from_chat_control) - } - - self.config_default_values = {'MESSAGE_WARNING_LENGTH' : (140, _('Message length at which notification is invoked.')), - 'WARNING_COLOR' : ('#F0DB3E', _('Background color of text entry field in chat window when notification is invoked.')), - 'JIDS' : ([], _('JabberIDs that plugin should be used with (eg. restrict only to one microblogging bot). If empty plugin is used with every JID. [not implemented]')) - } - - @log_calls('LengthNotifierPlugin') - def textview_length_warning(self, tb, chat_control): - tv = chat_control.msg_textview - d = chat_control.length_notifier_plugin_data - t = tb.get_text(tb.get_start_iter(), tb.get_end_iter()) - if t: - len_t = len(t) - #print("len_t: %d"%(len_t)) - if len_t>self.config['MESSAGE_WARNING_LENGTH']: - if not d['prev_color']: - d['prev_color'] = tv.style.copy().base[gtk.STATE_NORMAL] - tv.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.config['WARNING_COLOR'])) - elif d['prev_color']: - tv.modify_base(gtk.STATE_NORMAL, d['prev_color']) - d['prev_color'] = None - - @log_calls('LengthNotifierPlugin') - def connect_with_chat_control(self, chat_control): - jid = chat_control.contact.jid - if self.jid_is_ok(jid): - d = {'prev_color' : None} - tv = chat_control.msg_textview - tb = tv.get_buffer() - h_id = tb.connect('changed', self.textview_length_warning, chat_control) - d['h_id'] = h_id - - t = tb.get_text(tb.get_start_iter(), tb.get_end_iter()) - if t: - len_t = len(t) - if len_t>self.config['MESSAGE_WARNING_LENGTH']: - d['prev_color'] = tv.style.copy().base[gtk.STATE_NORMAL] - tv.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.config['WARNING_COLOR'])) - - chat_control.length_notifier_plugin_data = d - - return True - - return False - - @log_calls('LengthNotifierPlugin') - def disconnect_from_chat_control(self, chat_control): - try: - d = chat_control.length_notifier_plugin_data - tv = chat_control.msg_textview - tv.get_buffer().disconnect(d['h_id']) - if d['prev_color']: - tv.modify_base(gtk.STATE_NORMAL, d['prev_color']) - except AttributeError, error: - pass - #log.debug('Length Notifier Plugin was (probably) never connected with this chat window.\n Error: %s' % (error)) - - @log_calls('LengthNotifierPlugin') - def jid_is_ok(self, jid): - if jid in self.config['JIDS'] or not self.config['JIDS']: - return True - - return False - + name = u'Message Length Notifier' + short_name = u'length_notifier' + version = u'0.1' + description = u'''Highlights message entry field in chat window when given length of message is exceeded.''' + authors = [u'Mateusz Biliński '] + homepage = u'http://blog.bilinski.it' + + @log_calls('LengthNotifierPlugin') + def init(self): + self.config_dialog = LengthNotifierPluginConfigDialog(self) + + self.gui_extension_points = { + 'chat_control' : (self.connect_with_chat_control, + self.disconnect_from_chat_control) + } + + self.config_default_values = {'MESSAGE_WARNING_LENGTH' : (140, _('Message length at which notification is invoked.')), + 'WARNING_COLOR' : ('#F0DB3E', _('Background color of text entry field in chat window when notification is invoked.')), + 'JIDS' : ([], _('JabberIDs that plugin should be used with (eg. restrict only to one microblogging bot). If empty plugin is used with every JID. [not implemented]')) + } + + @log_calls('LengthNotifierPlugin') + def textview_length_warning(self, tb, chat_control): + tv = chat_control.msg_textview + d = chat_control.length_notifier_plugin_data + t = tb.get_text(tb.get_start_iter(), tb.get_end_iter()) + if t: + len_t = len(t) + #print("len_t: %d"%(len_t)) + if len_t>self.config['MESSAGE_WARNING_LENGTH']: + if not d['prev_color']: + d['prev_color'] = tv.style.copy().base[gtk.STATE_NORMAL] + tv.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.config['WARNING_COLOR'])) + elif d['prev_color']: + tv.modify_base(gtk.STATE_NORMAL, d['prev_color']) + d['prev_color'] = None + + @log_calls('LengthNotifierPlugin') + def connect_with_chat_control(self, chat_control): + jid = chat_control.contact.jid + if self.jid_is_ok(jid): + d = {'prev_color' : None} + tv = chat_control.msg_textview + tb = tv.get_buffer() + h_id = tb.connect('changed', self.textview_length_warning, chat_control) + d['h_id'] = h_id + + t = tb.get_text(tb.get_start_iter(), tb.get_end_iter()) + if t: + len_t = len(t) + if len_t>self.config['MESSAGE_WARNING_LENGTH']: + d['prev_color'] = tv.style.copy().base[gtk.STATE_NORMAL] + tv.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.config['WARNING_COLOR'])) + + chat_control.length_notifier_plugin_data = d + + return True + + return False + + @log_calls('LengthNotifierPlugin') + def disconnect_from_chat_control(self, chat_control): + try: + d = chat_control.length_notifier_plugin_data + tv = chat_control.msg_textview + tv.get_buffer().disconnect(d['h_id']) + if d['prev_color']: + tv.modify_base(gtk.STATE_NORMAL, d['prev_color']) + except AttributeError, error: + pass + #log.debug('Length Notifier Plugin was (probably) never connected with this chat window.\n Error: %s' % (error)) + + @log_calls('LengthNotifierPlugin') + def jid_is_ok(self, jid): + if jid in self.config['JIDS'] or not self.config['JIDS']: + return True + + return False + class LengthNotifierPluginConfigDialog(GajimPluginConfigDialog): - def init(self): - self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path( - 'config_dialog.ui') - self.xml = gtk.Builder() - self.xml.set_translation_domain(i18n.APP) - self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH, - ['length_notifier_config_table']) - self.config_table = self.xml.get_object('length_notifier_config_table') - self.child.pack_start(self.config_table) + def init(self): + self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path( + 'config_dialog.ui') + self.xml = gtk.Builder() + self.xml.set_translation_domain(i18n.APP) + self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH, + ['length_notifier_config_table']) + self.config_table = self.xml.get_object('length_notifier_config_table') + self.child.pack_start(self.config_table) - self.message_length_spinbutton = self.xml.get_object( - 'message_length_spinbutton') - self.message_length_spinbutton.get_adjustment().set_all(140, 0, 500, 1, - 10, 0) - self.notification_colorbutton = self.xml.get_object( - 'notification_colorbutton') - self.jids_entry = self.xml.get_object('jids_entry') + self.message_length_spinbutton = self.xml.get_object( + 'message_length_spinbutton') + self.message_length_spinbutton.get_adjustment().set_all(140, 0, 500, 1, + 10, 0) + self.notification_colorbutton = self.xml.get_object( + 'notification_colorbutton') + self.jids_entry = self.xml.get_object('jids_entry') - self.xml.connect_signals(self) + self.xml.connect_signals(self) - def on_run(self): - self.message_length_spinbutton.set_value(self.plugin.config['MESSAGE_WARNING_LENGTH']) - self.notification_colorbutton.set_color(gtk.gdk.color_parse(self.plugin.config['WARNING_COLOR'])) - #self.jids_entry.set_text(self.plugin.config['JIDS']) - self.jids_entry.set_text(','.join(self.plugin.config['JIDS'])) - - @log_calls('LengthNotifierPluginConfigDialog') - def on_message_length_spinbutton_value_changed(self, spinbutton): - self.plugin.config['MESSAGE_WARNING_LENGTH'] = spinbutton.get_value() - - @log_calls('LengthNotifierPluginConfigDialog') - def on_notification_colorbutton_color_set(self, colorbutton): - self.plugin.config['WARNING_COLOR'] = colorbutton.get_color().to_string() - - @log_calls('LengthNotifierPluginConfigDialog') - def on_jids_entry_changed(self, entry): - text = entry.get_text() - if len(text)>0: - self.plugin.config['JIDS'] = entry.get_text().split(',') - else: - self.plugin.config['JIDS'] = [] - - @log_calls('LengthNotifierPluginConfigDialog') - def on_jids_entry_editing_done(self, entry): - pass - + def on_run(self): + self.message_length_spinbutton.set_value(self.plugin.config['MESSAGE_WARNING_LENGTH']) + self.notification_colorbutton.set_color(gtk.gdk.color_parse(self.plugin.config['WARNING_COLOR'])) + #self.jids_entry.set_text(self.plugin.config['JIDS']) + self.jids_entry.set_text(','.join(self.plugin.config['JIDS'])) + + @log_calls('LengthNotifierPluginConfigDialog') + def on_message_length_spinbutton_value_changed(self, spinbutton): + self.plugin.config['MESSAGE_WARNING_LENGTH'] = spinbutton.get_value() + + @log_calls('LengthNotifierPluginConfigDialog') + def on_notification_colorbutton_color_set(self, colorbutton): + self.plugin.config['WARNING_COLOR'] = colorbutton.get_color().to_string() + + @log_calls('LengthNotifierPluginConfigDialog') + def on_jids_entry_changed(self, entry): + text = entry.get_text() + if len(text)>0: + self.plugin.config['JIDS'] = entry.get_text().split(',') + else: + self.plugin.config['JIDS'] = [] + + @log_calls('LengthNotifierPluginConfigDialog') + def on_jids_entry_editing_done(self, entry): + pass diff --git a/plugins/new_events_example/__init__.py b/plugins/new_events_example/__init__.py index a0eca896c..523d43e14 100644 --- a/plugins/new_events_example/__init__.py +++ b/plugins/new_events_example/__init__.py @@ -1 +1 @@ -from plugin import NewEventsExamplePlugin \ No newline at end of file +from plugin import NewEventsExamplePlugin diff --git a/plugins/new_events_example/plugin.py b/plugins/new_events_example/plugin.py index 29740d1a4..ff40dd56f 100644 --- a/plugins/new_events_example/plugin.py +++ b/plugins/new_events_example/plugin.py @@ -38,110 +38,110 @@ from common import ged from common import nec class NewEventsExamplePlugin(GajimPlugin): - name = u'New Events Example' - short_name = u'new_events_example' - version = u'0.1' - description = u'''Shows how to generate new network events based on existing one using Network Events Controller.''' - authors = [u'Mateusz Biliński '] - homepage = u'http://blog.bilinski.it' - - @log_calls('NewEventsExamplePlugin') - def init(self): - self.config_dialog = None - #self.gui_extension_points = {} - #self.config_default_values = {} - - self.events_handlers = {'raw-message-received' : - (ged.POSTCORE, - self.raw_message_received), - 'customized-message-received' : - (ged.POSTCORE, - self.customized_message_received), - 'enriched-chat-message-received' : - (ged.POSTCORE, - self.enriched_chat_message_received)} - - self.events = [CustomizedMessageReceivedEvent, - MoreCustomizedMessageReceivedEvent, - ModifyOnlyMessageReceivedEvent, - EnrichedChatMessageReceivedEvent] - - def enriched_chat_message_received(self, event_object): - pass - #print "Event '%s' occured. Event object: %s\n\n===\n"%(event_object.name, - #event_object) - - def raw_message_received(self, event_object): - pass - #print "Event '%s' occured. Event object: %s\n\n===\n"%(event_object.name, - #event_object) - - def customized_message_received(self, event_object): - pass - #print "Event '%s' occured. Event object: %s\n\n===\n"%(event_object.name, - #event_object - - @log_calls('NewEventsExamplePlugin') - def activate(self): - pass + name = u'New Events Example' + short_name = u'new_events_example' + version = u'0.1' + description = u'''Shows how to generate new network events based on existing one using Network Events Controller.''' + authors = [u'Mateusz Biliński '] + homepage = u'http://blog.bilinski.it' + + @log_calls('NewEventsExamplePlugin') + def init(self): + self.config_dialog = None + #self.gui_extension_points = {} + #self.config_default_values = {} + + self.events_handlers = {'raw-message-received' : + (ged.POSTCORE, + self.raw_message_received), + 'customized-message-received' : + (ged.POSTCORE, + self.customized_message_received), + 'enriched-chat-message-received' : + (ged.POSTCORE, + self.enriched_chat_message_received)} + + self.events = [CustomizedMessageReceivedEvent, + MoreCustomizedMessageReceivedEvent, + ModifyOnlyMessageReceivedEvent, + EnrichedChatMessageReceivedEvent] + + def enriched_chat_message_received(self, event_object): + pass + #print "Event '%s' occured. Event object: %s\n\n===\n"%(event_object.name, + #event_object) + + def raw_message_received(self, event_object): + pass + #print "Event '%s' occured. Event object: %s\n\n===\n"%(event_object.name, + #event_object) + + def customized_message_received(self, event_object): + pass + #print "Event '%s' occured. Event object: %s\n\n===\n"%(event_object.name, + #event_object + + @log_calls('NewEventsExamplePlugin') + def activate(self): + pass + + @log_calls('NewEventsExamplePlugin') + def deactivate(self): + pass - @log_calls('NewEventsExamplePlugin') - def deactivate(self): - pass - class CustomizedMessageReceivedEvent(nec.NetworkIncomingEvent): - name = 'customized-message-received' - base_network_events = ['raw-message-received'] - - def generate(self): - return True - + name = 'customized-message-received' + base_network_events = ['raw-message-received'] + + def generate(self): + return True + class MoreCustomizedMessageReceivedEvent(nec.NetworkIncomingEvent): - ''' - Shows chain of custom created events. - - This one is based on custom 'customized-messsage-received'. - ''' - name = 'more-customized-message-received' - base_network_events = ['customized-message-received'] - - def generate(self): - return True - + ''' + Shows chain of custom created events. + + This one is based on custom 'customized-messsage-received'. + ''' + name = 'more-customized-message-received' + base_network_events = ['customized-message-received'] + + def generate(self): + return True + class ModifyOnlyMessageReceivedEvent(nec.NetworkIncomingEvent): - name = 'modify-only-message-received' - base_network_events = ['raw-message-received'] - - def generate(self): - msg_type = self.base_event.xmpp_msg.attrs.get('type', None) - if msg_type == u'chat': - msg_text = "".join(self.base_event.xmpp_msg.kids[0].data) - self.base_event.xmpp_msg.kids[0].setData( - u'%s [MODIFIED BY CUSTOM NETWORK EVENT]'%(msg_text)) - - return False - + name = 'modify-only-message-received' + base_network_events = ['raw-message-received'] + + def generate(self): + msg_type = self.base_event.xmpp_msg.attrs.get('type', None) + if msg_type == u'chat': + msg_text = "".join(self.base_event.xmpp_msg.kids[0].data) + self.base_event.xmpp_msg.kids[0].setData( + u'%s [MODIFIED BY CUSTOM NETWORK EVENT]'%(msg_text)) + + return False + class EnrichedChatMessageReceivedEvent(nec.NetworkIncomingEvent): - ''' - Generates more friendly (in use by handlers) network event for - received chat message. - ''' - name = 'enriched-chat-message-received' - base_network_events = ['raw-message-received'] - - def generate(self): - msg_type = self.base_event.xmpp_msg.attrs.get('type', None) - if msg_type == u'chat': - self.xmpp_msg = self.base_event.xmpp_msg - self.conn = self.base_event.conn - self.from_jid = helpers.get_full_jid_from_iq(self.xmpp_msg) - self.from_jid_without_resource = gajim.get_jid_without_resource(self.from_jid) - self.account = self.base_event.account - self.from_nickname = gajim.get_contact_name_from_jid( - self.account, - self.from_jid_without_resource) - self.msg_text = "".join(self.xmpp_msg.kids[0].data) - - return True - - return False + ''' + Generates more friendly (in use by handlers) network event for + received chat message. + ''' + name = 'enriched-chat-message-received' + base_network_events = ['raw-message-received'] + + def generate(self): + msg_type = self.base_event.xmpp_msg.attrs.get('type', None) + if msg_type == u'chat': + self.xmpp_msg = self.base_event.xmpp_msg + self.conn = self.base_event.conn + self.from_jid = helpers.get_full_jid_from_iq(self.xmpp_msg) + self.from_jid_without_resource = gajim.get_jid_without_resource(self.from_jid) + self.account = self.base_event.account + self.from_nickname = gajim.get_contact_name_from_jid( + self.account, + self.from_jid_without_resource) + self.msg_text = "".join(self.xmpp_msg.kids[0].data) + + return True + + return False diff --git a/plugins/roster_buttons/plugin.py b/plugins/roster_buttons/plugin.py index 90a636ce5..a75fd8c6c 100644 --- a/plugins/roster_buttons/plugin.py +++ b/plugins/roster_buttons/plugin.py @@ -34,54 +34,53 @@ from plugins import GajimPlugin from plugins.helpers import log, log_calls class RosterButtonsPlugin(GajimPlugin): - name = u'Roster Buttons' - short_name = u'roster_buttons' - version = u'0.1' - description = u'''Adds quick action buttons to roster window.''' - authors = [u'Mateusz Biliński '] - homepage = u'http://blog.bilinski.it' + name = u'Roster Buttons' + short_name = u'roster_buttons' + version = u'0.1' + description = u'''Adds quick action buttons to roster window.''' + authors = [u'Mateusz Biliński '] + homepage = u'http://blog.bilinski.it' - @log_calls('RosterButtonsPlugin') - def init(self): - self.GTK_BUILDER_FILE_PATH = self.local_file_path('roster_buttons.ui') - self.roster_vbox = gajim.interface.roster.xml.get_object('roster_vbox2') - self.show_offline_contacts_menuitem = gajim.interface.roster.xml.get_object('show_offline_contacts_menuitem') - - self.config_dialog = None - - @log_calls('RosterButtonsPlugin') - def activate(self): - self.xml = gtk.Builder() - self.xml.set_translation_domain(i18n.APP) - self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH, - ['roster_buttons_buttonbox']) - self.buttonbox = self.xml.get_object('roster_buttons_buttonbox') - - self.roster_vbox.pack_start(self.buttonbox, expand=False) - self.roster_vbox.reorder_child(self.buttonbox, 0) - self.xml.connect_signals(self) - - @log_calls('RosterButtonsPlugin') - def deactivate(self): - self.roster_vbox.remove(self.buttonbox) - - self.buttonbox = None - self.xml = None - - @log_calls('RosterButtonsPlugin') - def on_roster_button_1_clicked(self, button): - #gajim.interface.roster.on_show_offline_contacts_menuitem_activate(None) - self.show_offline_contacts_menuitem.set_active(not self.show_offline_contacts_menuitem.get_active()) - - @log_calls('RosterButtonsPlugin') - def on_roster_button_2_clicked(self, button): - pass - - @log_calls('RosterButtonsPlugin') - def on_roster_button_3_clicked(self, button): - pass - - @log_calls('RosterButtonsPlugin') - def on_roster_button_4_clicked(self, button): - pass - + @log_calls('RosterButtonsPlugin') + def init(self): + self.GTK_BUILDER_FILE_PATH = self.local_file_path('roster_buttons.ui') + self.roster_vbox = gajim.interface.roster.xml.get_object('roster_vbox2') + self.show_offline_contacts_menuitem = gajim.interface.roster.xml.get_object('show_offline_contacts_menuitem') + + self.config_dialog = None + + @log_calls('RosterButtonsPlugin') + def activate(self): + self.xml = gtk.Builder() + self.xml.set_translation_domain(i18n.APP) + self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH, + ['roster_buttons_buttonbox']) + self.buttonbox = self.xml.get_object('roster_buttons_buttonbox') + + self.roster_vbox.pack_start(self.buttonbox, expand=False) + self.roster_vbox.reorder_child(self.buttonbox, 0) + self.xml.connect_signals(self) + + @log_calls('RosterButtonsPlugin') + def deactivate(self): + self.roster_vbox.remove(self.buttonbox) + + self.buttonbox = None + self.xml = None + + @log_calls('RosterButtonsPlugin') + def on_roster_button_1_clicked(self, button): + #gajim.interface.roster.on_show_offline_contacts_menuitem_activate(None) + self.show_offline_contacts_menuitem.set_active(not self.show_offline_contacts_menuitem.get_active()) + + @log_calls('RosterButtonsPlugin') + def on_roster_button_2_clicked(self, button): + pass + + @log_calls('RosterButtonsPlugin') + def on_roster_button_3_clicked(self, button): + pass + + @log_calls('RosterButtonsPlugin') + def on_roster_button_4_clicked(self, button): + pass diff --git a/plugins/snarl_notifications/PySnarl.py b/plugins/snarl_notifications/PySnarl.py index 118a7b25c..c3c657e56 100755 --- a/plugins/snarl_notifications/PySnarl.py +++ b/plugins/snarl_notifications/PySnarl.py @@ -1,772 +1,772 @@ -""" -A python version of the main functions to use Snarl -(http://www.fullphat.net/snarl) - -Version 1.0 - -This module can be used in two ways. One is the normal way -the other snarl interfaces work. This means you can call snShowMessage -and get an ID back for manipulations. - -The other way is there is a class this module exposes called SnarlMessage. -This allows you to keep track of the message as a python object. If you -use the send without specifying False as the argument it will set the ID -to what the return of the last SendMessage was. This is of course only -useful for the SHOW message. - -Requires one of: - pywin32 extensions from http://pywin32.sourceforge.net - ctypes (included in Python 2.5, downloadable for earlier versions) - -Creator: Sam Listopad II (samlii@users.sourceforge.net) - -Copyright 2006-2008 Samuel Listopad II - -Licensed under the Apache License, Version 2.0 (the "License"); you may not -use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required -by applicable law or agreed to in writing, software distributed under the -License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS -OF ANY KIND, either express or implied. See the License for the specific -language governing permissions and limitations under the License. -""" - -import array, struct - -def LOWORD(dword): - """Return the low WORD of the passed in integer""" - return dword & 0x0000ffff -#get the hi word -def HIWORD(dword): - """Return the high WORD of the passed in integer""" - return dword >> 16 - -class Win32FuncException(Exception): - def __init__(self, value): - self.value = value - - def __str__(self): - return repr(self.value) - -class Win32Funcs: - """Just a little class to hide the details of finding and using the -correct win32 functions. The functions may throw a UnicodeEncodeError if -there is not a unicode version and it is sent a unicode string that cannot -be converted to ASCII.""" - WM_USER = 0x400 - WM_COPYDATA = 0x4a - #Type of String the functions are expecting. - #Used like function(myWin32Funcs.strType(param)). - __strType = str - #FindWindow function to use - __FindWindow = None - #FindWindow function to use - __FindWindowEx = None - #SendMessage function to use - __SendMessage = None - #SendMessageTimeout function to use - __SendMessageTimeout = None - #IsWindow function to use - __IsWindow = None - #RegisterWindowMessage to use - __RegisterWindowMessage = None - #GetWindowText to use - __GetWindowText = None - - def FindWindow(self, lpClassName, lpWindowName): - """Wraps the windows API call of FindWindow""" - if lpClassName is not None: - lpClassName = self.__strType(lpClassName) - if lpWindowName is not None: - lpWindowName = self.__strType(lpWindowName) - return self.__FindWindow(lpClassName, lpWindowName) - - def FindWindowEx(self, hwndParent, hwndChildAfter, lpClassName, lpWindowName): - """Wraps the windows API call of FindWindow""" - if lpClassName is not None: - lpClassName = self.__strType(lpClassName) - if lpWindowName is not None: - lpWindowName = self.__strType(lpWindowName) - return self.__FindWindowEx(hwndParent, hwndChildAfter, lpClassName, lpWindowName) - - def SendMessage(self, hWnd, Msg, wParam, lParam): - """Wraps the windows API call of SendMessage""" - return self.__SendMessage(hWnd, Msg, wParam, lParam) - - def SendMessageTimeout(self, hWnd, Msg, - wParam, lParam, fuFlags, - uTimeout, lpdwResult = None): - """Wraps the windows API call of SendMessageTimeout""" - idToRet = None - try: - idFromMsg = array.array('I', [0]) - result = idFromMsg.buffer_info()[0] - response = self.__SendMessageTimeout(hWnd, Msg, wParam, - lParam, fuFlags, - uTimeout, result) - if response == 0: - raise Win32FuncException, "SendMessageTimeout TimedOut" - - idToRet = idFromMsg[0] - except TypeError: - idToRet = self.__SendMessageTimeout(hWnd, Msg, wParam, - lParam, fuFlags, - uTimeout) - - if lpdwResult is not None and lpdwResult.typecode == 'I': - lpdwResult[0] = idToRet - - return idToRet - - def IsWindow(self, hWnd): - """Wraps the windows API call of IsWindow""" - return self.__IsWindow(hWnd) - - def RegisterWindowMessage(self, lpString): - """Wraps the windows API call of RegisterWindowMessage""" - return self.__RegisterWindowMessage(self.__strType(lpString)) - - def GetWindowText(self, hWnd, lpString = None, nMaxCount = None): - """Wraps the windows API call of SendMessageTimeout""" - text = '' - if hWnd == 0: - return text - - if nMaxCount is None: - nMaxCount = 1025 - - try: - arrayType = 'c' - if self.__strType == unicode: - arrayType = 'u' - path_string = array.array(arrayType, self.__strType('\x00') * nMaxCount) - path_buffer = path_string.buffer_info()[0] - result = self.__GetWindowText(hWnd, - path_buffer, - nMaxCount) - if result > 0: - if self.__strType == unicode: - text = path_string[0:result].tounicode() - else: - text = path_string[0:result].tostring() - except TypeError: - text = self.__GetWindowText(hWnd) - - if lpString is not None and lpString.typecode == 'c': - lpdwResult[0:len(text)] = array.array('c',str(text)); - - if lpString is not None and lpString.typecode == 'u': - lpdwResult[0:len(text)] = array.array('u',unicode(text)); - - return text - - def __init__(self): - """Load up my needed functions""" - # First see if they already have win32gui imported. If so use it. - # This has to be checked first since the auto check looks for ctypes - # first. - try: - self.__FindWindow = win32gui.FindWindow - self.__FindWindowEx = win32gui.FindWindowEx - self.__GetWindowText = win32gui.GetWindowText - self.__IsWindow = win32gui.IsWindow - self.__SendMessage = win32gui.SendMessage - self.__SendMessageTimeout = win32gui.SendMessageTimeout - self.__RegisterWindowMessage = win32gui.RegisterWindowMessage - self.__strType = unicode - - #Something threw a NameError, most likely the win32gui lines - #so do auto check - except NameError: - try: - from ctypes import windll - self.__FindWindow = windll.user32.FindWindowW - self.__FindWindowEx = windll.user32.FindWindowExW - self.__GetWindowText = windll.user32.GetWindowTextW - self.__IsWindow = windll.user32.IsWindow - self.__SendMessage = windll.user32.SendMessageW - self.__SendMessageTimeout = windll.user32.SendMessageTimeoutW - self.__RegisterWindowMessage = windll.user32.RegisterWindowMessageW - self.__strType = unicode - - #FindWindowW wasn't found, look for FindWindowA - except AttributeError: - try: - self.__FindWindow = windll.user32.FindWindowA - self.__FindWindowEx = windll.user32.FindWindowExA - self.__GetWindowText = windll.user32.GetWindowTextA - self.__IsWindow = windll.user32.IsWindow - self.__SendMessage = windll.user32.SendMessageA - self.__SendMessageTimeout = windll.user32.SendMessageTimeoutA - self.__RegisterWindowMessage = windll.user32.RegisterWindowMessageA - # Couldn't find either so Die and tell user why. - except AttributeError: - import sys - sys.stderr.write("Your Windows TM setup seems to be corrupt."+ - " No FindWindow found in user32.\n") - sys.stderr.flush() - sys.exit(3) - - except ImportError: - try: - import win32gui - self.__FindWindow = win32gui.FindWindow - self.__FindWindowEx = win32gui.FindWindowEx - self.__GetWindowText = win32gui.GetWindowText - self.__IsWindow = win32gui.IsWindow - self.__SendMessage = win32gui.SendMessage - self.__SendMessageTimeout = win32gui.SendMessageTimeout - self.__RegisterWindowMessage = win32gui.RegisterWindowMessage - self.__strType = unicode - - except ImportError: - import sys - sys.stderr.write("You need to have either"+ - " ctypes or pywin32 installed.\n") - sys.stderr.flush() - #sys.exit(2) - - -myWin32Funcs = Win32Funcs() - - -SHOW = 1 -HIDE = 2 -UPDATE = 3 -IS_VISIBLE = 4 -GET_VERSION = 5 -REGISTER_CONFIG_WINDOW = 6 -REVOKE_CONFIG_WINDOW = 7 -REGISTER_ALERT = 8 -REVOKE_ALERT = 9 -REGISTER_CONFIG_WINDOW_2 = 10 -GET_VERSION_EX = 11 -SET_TIMEOUT = 12 - -EX_SHOW = 32 - -GLOBAL_MESSAGE = "SnarlGlobalMessage" -GLOBAL_MSG = "SnarlGlobalEvent" - -#Messages That may be received from Snarl -SNARL_LAUNCHED = 1 -SNARL_QUIT = 2 -SNARL_ASK_APPLET_VER = 3 -SNARL_SHOW_APP_UI = 4 - -SNARL_NOTIFICATION_CLICKED = 32 #notification was right-clicked by user -SNARL_NOTIFICATION_CANCELLED = SNARL_NOTIFICATION_CLICKED #Name clarified -SNARL_NOTIFICATION_TIMED_OUT = 33 -SNARL_NOTIFICATION_ACK = 34 #notification was left-clicked by user - -#Snarl Test Message -WM_SNARLTEST = myWin32Funcs.WM_USER + 237 - -M_ABORTED = 0x80000007L -M_ACCESS_DENIED = 0x80000009L -M_ALREADY_EXISTS = 0x8000000CL -M_BAD_HANDLE = 0x80000006L -M_BAD_POINTER = 0x80000005L -M_FAILED = 0x80000008L -M_INVALID_ARGS = 0x80000003L -M_NO_INTERFACE = 0x80000004L -M_NOT_FOUND = 0x8000000BL -M_NOT_IMPLEMENTED = 0x80000001L -M_OK = 0x00000000L -M_OUT_OF_MEMORY = 0x80000002L -M_TIMED_OUT = 0x8000000AL - -ErrorCodeRev = { - 0x80000007L : "M_ABORTED", - 0x80000009L : "M_ACCESS_DENIED", - 0x8000000CL : "M_ALREADY_EXISTS", - 0x80000006L : "M_BAD_HANDLE", - 0x80000005L : "M_BAD_POINTER", - 0x80000008L : "M_FAILED", - 0x80000003L : "M_INVALID_ARGS", - 0x80000004L : "M_NO_INTERFACE", - 0x8000000BL : "M_NOT_FOUND", - 0x80000001L : "M_NOT_IMPLEMENTED", - 0x00000000L : "M_OK", - 0x80000002L : "M_OUT_OF_MEMORY", - 0x8000000AL : "M_TIMED_OUT" - } - -class SnarlMessage(object): - """The main Snarl interface object. - - ID = Snarl Message ID for most operations. See SDK for more info - as to other values to put here. - type = Snarl Message Type. Valid values are : SHOW, HIDE, UPDATE, - IS_VISIBLE, GET_VERSION, REGISTER_CONFIG_WINDOW, REVOKE_CONFIG_WINDOW - all which are constants in the PySnarl module. - timeout = Timeout in seconds for the Snarl Message - data = Snarl Message data. This is dependant upon message type. See SDK - title = Snarl Message title. - text = Snarl Message text. - icon = Path to the icon to display in the Snarl Message. - """ - __msgType = 0 - __msgID = 0 - __msgTimeout = 0 - __msgData = 0 - __msgTitle = "" - __msgText = "" - __msgIcon = "" - __msgClass = "" - __msgExtra = "" - __msgExtra2 = "" - __msgRsvd1 = 0 - __msgRsvd2 = 0 - __msgHWnd = 0 - - lastKnownHWnd = 0 - - def getType(self): - """Type Attribute getter.""" - return self.__msgType - def setType(self, value): - """Type Attribute setter.""" - if( isinstance(value, (int, long)) ): - self.__msgType = value - type = property(getType, setType, doc="The Snarl Message Type") - - def getID(self): - """ID Attribute getter.""" - return self.__msgID - def setID(self, value): - """ID Attribute setter.""" - if( isinstance(value, (int, long)) ): - self.__msgID = value - ID = property(getID, setID, doc="The Snarl Message ID") - - def getTimeout(self): - """Timeout Attribute getter.""" - return self.__msgTimeout - def updateTimeout(self, value): - """Timeout Attribute setter.""" - if( isinstance(value, (int, long)) ): - self.__msgTimeout = value - timeout = property(getTimeout, updateTimeout, - doc="The Snarl Message Timeout") - - def getData(self): - """Data Attribute getter.""" - return self.__msgData - def setData(self, value): - """Data Attribute setter.""" - if( isinstance(value, (int, long)) ): - self.__msgData = value - data = property(getData, setData, doc="The Snarl Message Data") - - def getTitle(self): - """Title Attribute getter.""" - return self.__msgTitle - def setTitle(self, value): - """Title Attribute setter.""" - if( isinstance(value, basestring) ): - self.__msgTitle = value - title = property(getTitle, setTitle, doc="The Snarl Message Title") - - def getText(self): - """Text Attribute getter.""" - return self.__msgText - def setText(self, value): - """Text Attribute setter.""" - if( isinstance(value, basestring) ): - self.__msgText = value - text = property(getText, setText, doc="The Snarl Message Text") - - def getIcon(self): - """Icon Attribute getter.""" - return self.__msgIcon - def setIcon(self, value): - """Icon Attribute setter.""" - if( isinstance(value, basestring) ): - self.__msgIcon = value - icon = property(getIcon, setIcon, doc="The Snarl Message Icon") - - def getClass(self): - """Class Attribute getter.""" - return self.__msgClass - def setClass(self, value): - """Class Attribute setter.""" - if( isinstance(value, basestring) ): - self.__msgClass = value - msgclass = property(getClass, setClass, doc="The Snarl Message Class") - - def getExtra(self): - """Extra Attribute getter.""" - return self.__msgExtra - def setExtra(self, value): - """Extra Attribute setter.""" - if( isinstance(value, basestring) ): - self.__msgExtra = value - extra = property(getExtra, setExtra, doc="Extra Info for the Snarl Message") - - def getExtra2(self): - """Extra2 Attribute getter.""" - return self.__msgExtra2 - def setExtra2(self, value): - """Extra2 Attribute setter.""" - if( isinstance(value, basestring) ): - self.__msgExtra2 = value - extra2 = property(getExtra2, setExtra2, - doc="More Extra Info for the Snarl Message") - - def getRsvd1(self): - """Rsvd1 Attribute getter.""" - return self.__msgRsvd1 - def setRsvd1(self, value): - """Rsvd1 Attribute setter.""" - if( isinstance(value, (int, long)) ): - self.__msgRsvd1 = value - rsvd1 = property(getRsvd1, setRsvd1, doc="The Snarl Message Field Rsvd1") - - def getRsvd2(self): - """Rsvd2 Attribute getter.""" - return self.__msgRsvd2 - def setRsvd2(self, value): - """Rsvd2 Attribute setter.""" - if( isinstance(value, (int, long)) ): - self.__msgRsvd2 = value - rsvd2 = property(getRsvd2, setRsvd2, doc="The Snarl Message Field Rsvd2") - - def getHwnd(self): - """hWnd Attribute getter.""" - return self.__msgHWnd - def setHwnd(self, value): - """hWnd Attribute setter.""" - if( isinstance(value, (int, long)) ): - self.__msgHWnd = value - - hWnd = property(getHwnd, setHwnd, doc="The hWnd of the window this message is being sent from") - - - def __init__(self, title="", text="", icon="", msg_type=1, msg_id=0): - self.__msgTimeout = 0 - self.__msgData = 0 - self.__msgClass = "" - self.__msgExtra = "" - self.__msgExtra2 = "" - self.__msgRsvd1 = 0 - self.__msgRsvd2 = 0 - self.__msgType = msg_type - self.__msgText = text - self.__msgTitle = title - self.__msgIcon = icon - self.__msgID = msg_id - - def createCopyStruct(self): - """Creates the struct to send as the copyData in the message.""" - return struct.pack("ILLL1024s1024s1024s1024s1024s1024sLL", - self.__msgType, - self.__msgID, - self.__msgTimeout, - self.__msgData, - self.__msgTitle.encode('utf-8'), - self.__msgText.encode('utf-8'), - self.__msgIcon.encode('utf-8'), - self.__msgClass.encode('utf-8'), - self.__msgExtra.encode('utf-8'), - self.__msgExtra2.encode('utf-8'), - self.__msgRsvd1, - self.__msgRsvd2 - ) - __lpData = None - __cds = None - - def packData(self, dwData): - """This packs the data in the necessary format for a -WM_COPYDATA message.""" - self.__lpData = None - self.__cds = None - item = self.createCopyStruct() - self.__lpData = array.array('c', item) - lpData_ad = self.__lpData.buffer_info()[0] - cbData = self.__lpData.buffer_info()[1] - self.__cds = array.array('c', - struct.pack("IIP", - dwData, - cbData, - lpData_ad) - ) - cds_ad = self.__cds.buffer_info()[0] - return cds_ad - - def reset(self): - """Reset this SnarlMessage to the default state.""" - self.__msgType = 0 - self.__msgID = 0 - self.__msgTimeout = 0 - self.__msgData = 0 - self.__msgTitle = "" - self.__msgText = "" - self.__msgIcon = "" - self.__msgClass = "" - self.__msgExtra = "" - self.__msgExtra2 = "" - self.__msgRsvd1 = 0 - self.__msgRsvd2 = 0 - - - def send(self, setid=True): - """Send this SnarlMessage to the Snarl window. -Args: - setid - Boolean defining whether or not to set the ID - of this SnarlMessage to the return value of - the SendMessage call. Default is True to - make simple case of SHOW easy. - """ - hwnd = myWin32Funcs.FindWindow(None, "Snarl") - if myWin32Funcs.IsWindow(hwnd): - if self.type == REGISTER_CONFIG_WINDOW or self.type == REGISTER_CONFIG_WINDOW_2: - self.hWnd = self.data - try: - response = myWin32Funcs.SendMessageTimeout(hwnd, - myWin32Funcs.WM_COPYDATA, - self.hWnd, self.packData(2), - 2, 500) - except Win32FuncException: - return False - - idFromMsg = response - if setid: - self.ID = idFromMsg - return True - else: - return idFromMsg - print "No snarl window found" - return False - - def hide(self): - """Hide this message. Type will revert to type before calling hide -to allow for better reuse of object.""" - oldType = self.__msgType - self.__msgType = HIDE - retVal = bool(self.send(False)) - self.__msgType = oldType - return retVal - - def isVisible(self): - """Is this message visible. Type will revert to type before calling -hide to allow for better reuse of object.""" - oldType = self.__msgType - self.__msgType = IS_VISIBLE - retVal = bool(self.send(False)) - self.__msgType = oldType - return retVal - - def update(self, title=None, text=None, icon=None): - """Update this message with given title and text. Type will revert -to type before calling hide to allow for better reuse of object.""" - oldType = self.__msgType - self.__msgType = UPDATE - if text: - self.__msgText = text - if title: - self.__msgTitle = title - if icon: - self.__msgIcon = icon - retVal = self.send(False) - self.__msgType = oldType - return retVal - - def setTimeout(self, timeout): - """Set the timeout in seconds of the message""" - oldType = self.__msgType - oldData = self.__msgData - self.__msgType = SET_TIMEOUT - #self.timeout = timeout - #self.__msgData = self.__msgTimeout - self.__msgData = timeout - retVal = self.send(False) - self.__msgType = oldType - self.__msgData = oldData - return retVal - - def show(self, timeout=None, title=None, - text=None, icon=None, - replyWindow=None, replyMsg=None, msgclass=None, soundPath=None): - """Show a message""" - oldType = self.__msgType - oldTimeout = self.__msgTimeout - self.__msgType = SHOW - if text: - self.__msgText = text - if title: - self.__msgTitle = title - if timeout: - self.__msgTimeout = timeout - if icon: - self.__msgIcon = icon - if replyWindow: - self.__msgID = replyMsg - if replyMsg: - self.__msgData = replyWindow - if soundPath: - self.__msgExtra = soundPath - if msgclass: - self.__msgClass = msgclass - - if ((self.__msgClass and self.__msgClass != "") or - (self.__msgExtra and self.__msgExtra != "")): - self.__msgType = EX_SHOW - - - retVal = bool(self.send()) - self.__msgType = oldType - self.__msgTimeout = oldTimeout - return retVal - - -def snGetVersion(): - """ Get the version of Snarl that is running as a tuple. (Major, Minor) - -If Snarl is not running or there was an error it will -return False.""" - msg = SnarlMessage(msg_type=GET_VERSION) - version = msg.send(False) - if not version: - return False - return (HIWORD(version), LOWORD(version)) - -def snGetVersionEx(): - """ Get the internal version of Snarl that is running. - -If Snarl is not running or there was an error it will -return False.""" - sm = SnarlMessage(msg_type=GET_VERSION_EX) - verNum = sm.send(False) - if not verNum: - return False - return verNum - -def snGetGlobalMessage(): - """Get the Snarl global message id from windows.""" - return myWin32Funcs.RegisterWindowMessage(GLOBAL_MSG) - -def snShowMessage(title, text, timeout=0, iconPath="", - replyWindow=0, replyMsg=0): - """Show a message using Snarl and return its ID. See SDK for arguments.""" - sm = SnarlMessage( title, text, iconPath, msg_id=replyMsg) - sm.data = replyWindow - if sm.show(timeout): - return sm.ID - else: - return False - -def snShowMessageEx(msgClass, title, text, timeout=0, iconPath="", - replyWindow=0, replyMsg=0, soundFile=None, hWndFrom=None): - """Show a message using Snarl and return its ID. See SDK for arguments. - One added argument is hWndFrom that allows one to make the messages appear - to come from a specific window. This window should be the one you registered - earlier with RegisterConfig""" - sm = SnarlMessage( title, text, iconPath, msg_id=replyMsg) - sm.data = replyWindow - if hWndFrom is not None: - sm.hWnd = hWndFrom - else: - sm.hWnd = SnarlMessage.lastKnownHWnd - if sm.show(timeout, msgclass=msgClass, soundPath=soundFile): - return sm.ID - else: - return False - -def snUpdateMessage(msgId, msgTitle, msgText, icon=None): - """Update a message""" - sm = SnarlMessage(msg_id=msgId) - if icon: - sm.icon = icon - return sm.update(msgTitle, msgText) - -def snHideMessage(msgId): - """Hide a message""" - return SnarlMessage(msg_id=msgId).hide() - -def snSetTimeout(msgId, timeout): - """Update the timeout of a message already shown.""" - sm = SnarlMessage(msg_id=msgId) - return sm.setTimeout(timeout) - -def snIsMessageVisible(msgId): - """Returns True if the message is visible False otherwise.""" - return SnarlMessage(msg_id=msgId).isVisible() - -def snRegisterConfig(replyWnd, appName, replyMsg): - """Register a config window. See SDK for more info.""" - global lastRegisteredSnarlMsg - sm = SnarlMessage(msg_type=REGISTER_CONFIG_WINDOW, - title=appName, - msg_id=replyMsg) - sm.data = replyWnd - SnarlMessage.lastKnownHWnd = replyWnd - - return sm.send(False) - -def snRegisterConfig2(replyWnd, appName, replyMsg, icon): - """Register a config window. See SDK for more info.""" - global lastRegisteredSnarlMsg - sm = SnarlMessage(msg_type=REGISTER_CONFIG_WINDOW_2, - title=appName, - msg_id=replyMsg, - icon=icon) - sm.data = replyWnd - SnarlMessage.lastKnownHWnd = replyWnd - return sm.send(False) - -def snRegisterAlert(appName, classStr) : - """Register an alert for an already registered config. See SDK for more info.""" - sm = SnarlMessage(msg_type=REGISTER_ALERT, - title=appName, - text=classStr) - return sm.send(False) - -def snRevokeConfig(replyWnd): - """Revoke a config window""" - sm = SnarlMessage(msg_type=REVOKE_CONFIG_WINDOW) - sm.data = replyWnd - if replyWnd == SnarlMessage.lastKnownHWnd: - SnarlMessage.lastKnownHWnd = 0 - return sm.send(False) - -def snGetSnarlWindow(): - """Returns the hWnd of the snarl window""" - return myWin32Funcs.FindWindow(None, "Snarl") - -def snGetAppPath(): - """Returns the application path of the currently running snarl window""" - app_path = None - snarl_handle = snGetSnarlWindow() - if snarl_handle != 0: - pathwin_handle = myWin32Funcs.FindWindowEx(snarl_handle, - 0, - "static", - None) - if pathwin_handle != 0: - try: - result = myWin32Funcs.GetWindowText(pathwin_handle) - app_path = result - except Win32FuncException: - pass - - - return app_path - -def snGetIconsPath(): - """Returns the path to the icons of the program""" - s = snGetAppPath() - if s is None: - return "" - else: - return s + "etc\\icons\\" - -def snSendTestMessage(data=None): - """Sends a test message to Snarl. Used to make sure the -api is connecting""" - param = 0 - command = 0 - if data: - param = struct.pack("I", data) - command = 1 - myWin32Funcs.SendMessage(snGetSnarlWindow(), WM_SNARLTEST, command, param) +""" +A python version of the main functions to use Snarl +(http://www.fullphat.net/snarl) + +Version 1.0 + +This module can be used in two ways. One is the normal way +the other snarl interfaces work. This means you can call snShowMessage +and get an ID back for manipulations. + +The other way is there is a class this module exposes called SnarlMessage. +This allows you to keep track of the message as a python object. If you +use the send without specifying False as the argument it will set the ID +to what the return of the last SendMessage was. This is of course only +useful for the SHOW message. + +Requires one of: + pywin32 extensions from http://pywin32.sourceforge.net + ctypes (included in Python 2.5, downloadable for earlier versions) + +Creator: Sam Listopad II (samlii@users.sourceforge.net) + +Copyright 2006-2008 Samuel Listopad II + +Licensed under the Apache License, Version 2.0 (the "License"); you may not +use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required +by applicable law or agreed to in writing, software distributed under the +License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS +OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +""" + +import array, struct + +def LOWORD(dword): + """Return the low WORD of the passed in integer""" + return dword & 0x0000ffff +#get the hi word +def HIWORD(dword): + """Return the high WORD of the passed in integer""" + return dword >> 16 + +class Win32FuncException(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + +class Win32Funcs: + """Just a little class to hide the details of finding and using the +correct win32 functions. The functions may throw a UnicodeEncodeError if +there is not a unicode version and it is sent a unicode string that cannot +be converted to ASCII.""" + WM_USER = 0x400 + WM_COPYDATA = 0x4a + #Type of String the functions are expecting. + #Used like function(myWin32Funcs.strType(param)). + __strType = str + #FindWindow function to use + __FindWindow = None + #FindWindow function to use + __FindWindowEx = None + #SendMessage function to use + __SendMessage = None + #SendMessageTimeout function to use + __SendMessageTimeout = None + #IsWindow function to use + __IsWindow = None + #RegisterWindowMessage to use + __RegisterWindowMessage = None + #GetWindowText to use + __GetWindowText = None + + def FindWindow(self, lpClassName, lpWindowName): + """Wraps the windows API call of FindWindow""" + if lpClassName is not None: + lpClassName = self.__strType(lpClassName) + if lpWindowName is not None: + lpWindowName = self.__strType(lpWindowName) + return self.__FindWindow(lpClassName, lpWindowName) + + def FindWindowEx(self, hwndParent, hwndChildAfter, lpClassName, lpWindowName): + """Wraps the windows API call of FindWindow""" + if lpClassName is not None: + lpClassName = self.__strType(lpClassName) + if lpWindowName is not None: + lpWindowName = self.__strType(lpWindowName) + return self.__FindWindowEx(hwndParent, hwndChildAfter, lpClassName, lpWindowName) + + def SendMessage(self, hWnd, Msg, wParam, lParam): + """Wraps the windows API call of SendMessage""" + return self.__SendMessage(hWnd, Msg, wParam, lParam) + + def SendMessageTimeout(self, hWnd, Msg, + wParam, lParam, fuFlags, + uTimeout, lpdwResult = None): + """Wraps the windows API call of SendMessageTimeout""" + idToRet = None + try: + idFromMsg = array.array('I', [0]) + result = idFromMsg.buffer_info()[0] + response = self.__SendMessageTimeout(hWnd, Msg, wParam, + lParam, fuFlags, + uTimeout, result) + if response == 0: + raise Win32FuncException, "SendMessageTimeout TimedOut" + + idToRet = idFromMsg[0] + except TypeError: + idToRet = self.__SendMessageTimeout(hWnd, Msg, wParam, + lParam, fuFlags, + uTimeout) + + if lpdwResult is not None and lpdwResult.typecode == 'I': + lpdwResult[0] = idToRet + + return idToRet + + def IsWindow(self, hWnd): + """Wraps the windows API call of IsWindow""" + return self.__IsWindow(hWnd) + + def RegisterWindowMessage(self, lpString): + """Wraps the windows API call of RegisterWindowMessage""" + return self.__RegisterWindowMessage(self.__strType(lpString)) + + def GetWindowText(self, hWnd, lpString = None, nMaxCount = None): + """Wraps the windows API call of SendMessageTimeout""" + text = '' + if hWnd == 0: + return text + + if nMaxCount is None: + nMaxCount = 1025 + + try: + arrayType = 'c' + if self.__strType == unicode: + arrayType = 'u' + path_string = array.array(arrayType, self.__strType('\x00') * nMaxCount) + path_buffer = path_string.buffer_info()[0] + result = self.__GetWindowText(hWnd, + path_buffer, + nMaxCount) + if result > 0: + if self.__strType == unicode: + text = path_string[0:result].tounicode() + else: + text = path_string[0:result].tostring() + except TypeError: + text = self.__GetWindowText(hWnd) + + if lpString is not None and lpString.typecode == 'c': + lpdwResult[0:len(text)] = array.array('c', str(text)); + + if lpString is not None and lpString.typecode == 'u': + lpdwResult[0:len(text)] = array.array('u', unicode(text)); + + return text + + def __init__(self): + """Load up my needed functions""" + # First see if they already have win32gui imported. If so use it. + # This has to be checked first since the auto check looks for ctypes + # first. + try: + self.__FindWindow = win32gui.FindWindow + self.__FindWindowEx = win32gui.FindWindowEx + self.__GetWindowText = win32gui.GetWindowText + self.__IsWindow = win32gui.IsWindow + self.__SendMessage = win32gui.SendMessage + self.__SendMessageTimeout = win32gui.SendMessageTimeout + self.__RegisterWindowMessage = win32gui.RegisterWindowMessage + self.__strType = unicode + + #Something threw a NameError, most likely the win32gui lines + #so do auto check + except NameError: + try: + from ctypes import windll + self.__FindWindow = windll.user32.FindWindowW + self.__FindWindowEx = windll.user32.FindWindowExW + self.__GetWindowText = windll.user32.GetWindowTextW + self.__IsWindow = windll.user32.IsWindow + self.__SendMessage = windll.user32.SendMessageW + self.__SendMessageTimeout = windll.user32.SendMessageTimeoutW + self.__RegisterWindowMessage = windll.user32.RegisterWindowMessageW + self.__strType = unicode + + #FindWindowW wasn't found, look for FindWindowA + except AttributeError: + try: + self.__FindWindow = windll.user32.FindWindowA + self.__FindWindowEx = windll.user32.FindWindowExA + self.__GetWindowText = windll.user32.GetWindowTextA + self.__IsWindow = windll.user32.IsWindow + self.__SendMessage = windll.user32.SendMessageA + self.__SendMessageTimeout = windll.user32.SendMessageTimeoutA + self.__RegisterWindowMessage = windll.user32.RegisterWindowMessageA + # Couldn't find either so Die and tell user why. + except AttributeError: + import sys + sys.stderr.write("Your Windows TM setup seems to be corrupt."+ + " No FindWindow found in user32.\n") + sys.stderr.flush() + sys.exit(3) + + except ImportError: + try: + import win32gui + self.__FindWindow = win32gui.FindWindow + self.__FindWindowEx = win32gui.FindWindowEx + self.__GetWindowText = win32gui.GetWindowText + self.__IsWindow = win32gui.IsWindow + self.__SendMessage = win32gui.SendMessage + self.__SendMessageTimeout = win32gui.SendMessageTimeout + self.__RegisterWindowMessage = win32gui.RegisterWindowMessage + self.__strType = unicode + + except ImportError: + import sys + sys.stderr.write("You need to have either"+ + " ctypes or pywin32 installed.\n") + sys.stderr.flush() + #sys.exit(2) + + +myWin32Funcs = Win32Funcs() + + +SHOW = 1 +HIDE = 2 +UPDATE = 3 +IS_VISIBLE = 4 +GET_VERSION = 5 +REGISTER_CONFIG_WINDOW = 6 +REVOKE_CONFIG_WINDOW = 7 +REGISTER_ALERT = 8 +REVOKE_ALERT = 9 +REGISTER_CONFIG_WINDOW_2 = 10 +GET_VERSION_EX = 11 +SET_TIMEOUT = 12 + +EX_SHOW = 32 + +GLOBAL_MESSAGE = "SnarlGlobalMessage" +GLOBAL_MSG = "SnarlGlobalEvent" + +#Messages That may be received from Snarl +SNARL_LAUNCHED = 1 +SNARL_QUIT = 2 +SNARL_ASK_APPLET_VER = 3 +SNARL_SHOW_APP_UI = 4 + +SNARL_NOTIFICATION_CLICKED = 32 #notification was right-clicked by user +SNARL_NOTIFICATION_CANCELLED = SNARL_NOTIFICATION_CLICKED #Name clarified +SNARL_NOTIFICATION_TIMED_OUT = 33 +SNARL_NOTIFICATION_ACK = 34 #notification was left-clicked by user + +#Snarl Test Message +WM_SNARLTEST = myWin32Funcs.WM_USER + 237 + +M_ABORTED = 0x80000007L +M_ACCESS_DENIED = 0x80000009L +M_ALREADY_EXISTS = 0x8000000CL +M_BAD_HANDLE = 0x80000006L +M_BAD_POINTER = 0x80000005L +M_FAILED = 0x80000008L +M_INVALID_ARGS = 0x80000003L +M_NO_INTERFACE = 0x80000004L +M_NOT_FOUND = 0x8000000BL +M_NOT_IMPLEMENTED = 0x80000001L +M_OK = 0x00000000L +M_OUT_OF_MEMORY = 0x80000002L +M_TIMED_OUT = 0x8000000AL + +ErrorCodeRev = { + 0x80000007L : "M_ABORTED", + 0x80000009L : "M_ACCESS_DENIED", + 0x8000000CL : "M_ALREADY_EXISTS", + 0x80000006L : "M_BAD_HANDLE", + 0x80000005L : "M_BAD_POINTER", + 0x80000008L : "M_FAILED", + 0x80000003L : "M_INVALID_ARGS", + 0x80000004L : "M_NO_INTERFACE", + 0x8000000BL : "M_NOT_FOUND", + 0x80000001L : "M_NOT_IMPLEMENTED", + 0x00000000L : "M_OK", + 0x80000002L : "M_OUT_OF_MEMORY", + 0x8000000AL : "M_TIMED_OUT" + } + +class SnarlMessage(object): + """The main Snarl interface object. + + ID = Snarl Message ID for most operations. See SDK for more info + as to other values to put here. + type = Snarl Message Type. Valid values are : SHOW, HIDE, UPDATE, + IS_VISIBLE, GET_VERSION, REGISTER_CONFIG_WINDOW, REVOKE_CONFIG_WINDOW + all which are constants in the PySnarl module. + timeout = Timeout in seconds for the Snarl Message + data = Snarl Message data. This is dependant upon message type. See SDK + title = Snarl Message title. + text = Snarl Message text. + icon = Path to the icon to display in the Snarl Message. + """ + __msgType = 0 + __msgID = 0 + __msgTimeout = 0 + __msgData = 0 + __msgTitle = "" + __msgText = "" + __msgIcon = "" + __msgClass = "" + __msgExtra = "" + __msgExtra2 = "" + __msgRsvd1 = 0 + __msgRsvd2 = 0 + __msgHWnd = 0 + + lastKnownHWnd = 0 + + def getType(self): + """Type Attribute getter.""" + return self.__msgType + def setType(self, value): + """Type Attribute setter.""" + if( isinstance(value, (int, long)) ): + self.__msgType = value + type = property(getType, setType, doc="The Snarl Message Type") + + def getID(self): + """ID Attribute getter.""" + return self.__msgID + def setID(self, value): + """ID Attribute setter.""" + if( isinstance(value, (int, long)) ): + self.__msgID = value + ID = property(getID, setID, doc="The Snarl Message ID") + + def getTimeout(self): + """Timeout Attribute getter.""" + return self.__msgTimeout + def updateTimeout(self, value): + """Timeout Attribute setter.""" + if( isinstance(value, (int, long)) ): + self.__msgTimeout = value + timeout = property(getTimeout, updateTimeout, + doc="The Snarl Message Timeout") + + def getData(self): + """Data Attribute getter.""" + return self.__msgData + def setData(self, value): + """Data Attribute setter.""" + if( isinstance(value, (int, long)) ): + self.__msgData = value + data = property(getData, setData, doc="The Snarl Message Data") + + def getTitle(self): + """Title Attribute getter.""" + return self.__msgTitle + def setTitle(self, value): + """Title Attribute setter.""" + if( isinstance(value, basestring) ): + self.__msgTitle = value + title = property(getTitle, setTitle, doc="The Snarl Message Title") + + def getText(self): + """Text Attribute getter.""" + return self.__msgText + def setText(self, value): + """Text Attribute setter.""" + if( isinstance(value, basestring) ): + self.__msgText = value + text = property(getText, setText, doc="The Snarl Message Text") + + def getIcon(self): + """Icon Attribute getter.""" + return self.__msgIcon + def setIcon(self, value): + """Icon Attribute setter.""" + if( isinstance(value, basestring) ): + self.__msgIcon = value + icon = property(getIcon, setIcon, doc="The Snarl Message Icon") + + def getClass(self): + """Class Attribute getter.""" + return self.__msgClass + def setClass(self, value): + """Class Attribute setter.""" + if( isinstance(value, basestring) ): + self.__msgClass = value + msgclass = property(getClass, setClass, doc="The Snarl Message Class") + + def getExtra(self): + """Extra Attribute getter.""" + return self.__msgExtra + def setExtra(self, value): + """Extra Attribute setter.""" + if( isinstance(value, basestring) ): + self.__msgExtra = value + extra = property(getExtra, setExtra, doc="Extra Info for the Snarl Message") + + def getExtra2(self): + """Extra2 Attribute getter.""" + return self.__msgExtra2 + def setExtra2(self, value): + """Extra2 Attribute setter.""" + if( isinstance(value, basestring) ): + self.__msgExtra2 = value + extra2 = property(getExtra2, setExtra2, + doc="More Extra Info for the Snarl Message") + + def getRsvd1(self): + """Rsvd1 Attribute getter.""" + return self.__msgRsvd1 + def setRsvd1(self, value): + """Rsvd1 Attribute setter.""" + if( isinstance(value, (int, long)) ): + self.__msgRsvd1 = value + rsvd1 = property(getRsvd1, setRsvd1, doc="The Snarl Message Field Rsvd1") + + def getRsvd2(self): + """Rsvd2 Attribute getter.""" + return self.__msgRsvd2 + def setRsvd2(self, value): + """Rsvd2 Attribute setter.""" + if( isinstance(value, (int, long)) ): + self.__msgRsvd2 = value + rsvd2 = property(getRsvd2, setRsvd2, doc="The Snarl Message Field Rsvd2") + + def getHwnd(self): + """hWnd Attribute getter.""" + return self.__msgHWnd + def setHwnd(self, value): + """hWnd Attribute setter.""" + if( isinstance(value, (int, long)) ): + self.__msgHWnd = value + + hWnd = property(getHwnd, setHwnd, doc="The hWnd of the window this message is being sent from") + + + def __init__(self, title="", text="", icon="", msg_type=1, msg_id=0): + self.__msgTimeout = 0 + self.__msgData = 0 + self.__msgClass = "" + self.__msgExtra = "" + self.__msgExtra2 = "" + self.__msgRsvd1 = 0 + self.__msgRsvd2 = 0 + self.__msgType = msg_type + self.__msgText = text + self.__msgTitle = title + self.__msgIcon = icon + self.__msgID = msg_id + + def createCopyStruct(self): + """Creates the struct to send as the copyData in the message.""" + return struct.pack("ILLL1024s1024s1024s1024s1024s1024sLL", + self.__msgType, + self.__msgID, + self.__msgTimeout, + self.__msgData, + self.__msgTitle.encode('utf-8'), + self.__msgText.encode('utf-8'), + self.__msgIcon.encode('utf-8'), + self.__msgClass.encode('utf-8'), + self.__msgExtra.encode('utf-8'), + self.__msgExtra2.encode('utf-8'), + self.__msgRsvd1, + self.__msgRsvd2 + ) + __lpData = None + __cds = None + + def packData(self, dwData): + """This packs the data in the necessary format for a +WM_COPYDATA message.""" + self.__lpData = None + self.__cds = None + item = self.createCopyStruct() + self.__lpData = array.array('c', item) + lpData_ad = self.__lpData.buffer_info()[0] + cbData = self.__lpData.buffer_info()[1] + self.__cds = array.array('c', + struct.pack("IIP", + dwData, + cbData, + lpData_ad) + ) + cds_ad = self.__cds.buffer_info()[0] + return cds_ad + + def reset(self): + """Reset this SnarlMessage to the default state.""" + self.__msgType = 0 + self.__msgID = 0 + self.__msgTimeout = 0 + self.__msgData = 0 + self.__msgTitle = "" + self.__msgText = "" + self.__msgIcon = "" + self.__msgClass = "" + self.__msgExtra = "" + self.__msgExtra2 = "" + self.__msgRsvd1 = 0 + self.__msgRsvd2 = 0 + + + def send(self, setid=True): + """Send this SnarlMessage to the Snarl window. +Args: + setid - Boolean defining whether or not to set the ID + of this SnarlMessage to the return value of + the SendMessage call. Default is True to + make simple case of SHOW easy. + """ + hwnd = myWin32Funcs.FindWindow(None, "Snarl") + if myWin32Funcs.IsWindow(hwnd): + if self.type == REGISTER_CONFIG_WINDOW or self.type == REGISTER_CONFIG_WINDOW_2: + self.hWnd = self.data + try: + response = myWin32Funcs.SendMessageTimeout(hwnd, + myWin32Funcs.WM_COPYDATA, + self.hWnd, self.packData(2), + 2, 500) + except Win32FuncException: + return False + + idFromMsg = response + if setid: + self.ID = idFromMsg + return True + else: + return idFromMsg + print "No snarl window found" + return False + + def hide(self): + """Hide this message. Type will revert to type before calling hide +to allow for better reuse of object.""" + oldType = self.__msgType + self.__msgType = HIDE + retVal = bool(self.send(False)) + self.__msgType = oldType + return retVal + + def isVisible(self): + """Is this message visible. Type will revert to type before calling +hide to allow for better reuse of object.""" + oldType = self.__msgType + self.__msgType = IS_VISIBLE + retVal = bool(self.send(False)) + self.__msgType = oldType + return retVal + + def update(self, title=None, text=None, icon=None): + """Update this message with given title and text. Type will revert +to type before calling hide to allow for better reuse of object.""" + oldType = self.__msgType + self.__msgType = UPDATE + if text: + self.__msgText = text + if title: + self.__msgTitle = title + if icon: + self.__msgIcon = icon + retVal = self.send(False) + self.__msgType = oldType + return retVal + + def setTimeout(self, timeout): + """Set the timeout in seconds of the message""" + oldType = self.__msgType + oldData = self.__msgData + self.__msgType = SET_TIMEOUT + #self.timeout = timeout + #self.__msgData = self.__msgTimeout + self.__msgData = timeout + retVal = self.send(False) + self.__msgType = oldType + self.__msgData = oldData + return retVal + + def show(self, timeout=None, title=None, + text=None, icon=None, + replyWindow=None, replyMsg=None, msgclass=None, soundPath=None): + """Show a message""" + oldType = self.__msgType + oldTimeout = self.__msgTimeout + self.__msgType = SHOW + if text: + self.__msgText = text + if title: + self.__msgTitle = title + if timeout: + self.__msgTimeout = timeout + if icon: + self.__msgIcon = icon + if replyWindow: + self.__msgID = replyMsg + if replyMsg: + self.__msgData = replyWindow + if soundPath: + self.__msgExtra = soundPath + if msgclass: + self.__msgClass = msgclass + + if ((self.__msgClass and self.__msgClass != "") or + (self.__msgExtra and self.__msgExtra != "")): + self.__msgType = EX_SHOW + + + retVal = bool(self.send()) + self.__msgType = oldType + self.__msgTimeout = oldTimeout + return retVal + + +def snGetVersion(): + """ Get the version of Snarl that is running as a tuple. (Major, Minor) + +If Snarl is not running or there was an error it will +return False.""" + msg = SnarlMessage(msg_type=GET_VERSION) + version = msg.send(False) + if not version: + return False + return (HIWORD(version), LOWORD(version)) + +def snGetVersionEx(): + """ Get the internal version of Snarl that is running. + +If Snarl is not running or there was an error it will +return False.""" + sm = SnarlMessage(msg_type=GET_VERSION_EX) + verNum = sm.send(False) + if not verNum: + return False + return verNum + +def snGetGlobalMessage(): + """Get the Snarl global message id from windows.""" + return myWin32Funcs.RegisterWindowMessage(GLOBAL_MSG) + +def snShowMessage(title, text, timeout=0, iconPath="", + replyWindow=0, replyMsg=0): + """Show a message using Snarl and return its ID. See SDK for arguments.""" + sm = SnarlMessage( title, text, iconPath, msg_id=replyMsg) + sm.data = replyWindow + if sm.show(timeout): + return sm.ID + else: + return False + +def snShowMessageEx(msgClass, title, text, timeout=0, iconPath="", + replyWindow=0, replyMsg=0, soundFile=None, hWndFrom=None): + """Show a message using Snarl and return its ID. See SDK for arguments. + One added argument is hWndFrom that allows one to make the messages appear + to come from a specific window. This window should be the one you registered + earlier with RegisterConfig""" + sm = SnarlMessage( title, text, iconPath, msg_id=replyMsg) + sm.data = replyWindow + if hWndFrom is not None: + sm.hWnd = hWndFrom + else: + sm.hWnd = SnarlMessage.lastKnownHWnd + if sm.show(timeout, msgclass=msgClass, soundPath=soundFile): + return sm.ID + else: + return False + +def snUpdateMessage(msgId, msgTitle, msgText, icon=None): + """Update a message""" + sm = SnarlMessage(msg_id=msgId) + if icon: + sm.icon = icon + return sm.update(msgTitle, msgText) + +def snHideMessage(msgId): + """Hide a message""" + return SnarlMessage(msg_id=msgId).hide() + +def snSetTimeout(msgId, timeout): + """Update the timeout of a message already shown.""" + sm = SnarlMessage(msg_id=msgId) + return sm.setTimeout(timeout) + +def snIsMessageVisible(msgId): + """Returns True if the message is visible False otherwise.""" + return SnarlMessage(msg_id=msgId).isVisible() + +def snRegisterConfig(replyWnd, appName, replyMsg): + """Register a config window. See SDK for more info.""" + global lastRegisteredSnarlMsg + sm = SnarlMessage(msg_type=REGISTER_CONFIG_WINDOW, + title=appName, + msg_id=replyMsg) + sm.data = replyWnd + SnarlMessage.lastKnownHWnd = replyWnd + + return sm.send(False) + +def snRegisterConfig2(replyWnd, appName, replyMsg, icon): + """Register a config window. See SDK for more info.""" + global lastRegisteredSnarlMsg + sm = SnarlMessage(msg_type=REGISTER_CONFIG_WINDOW_2, + title=appName, + msg_id=replyMsg, + icon=icon) + sm.data = replyWnd + SnarlMessage.lastKnownHWnd = replyWnd + return sm.send(False) + +def snRegisterAlert(appName, classStr) : + """Register an alert for an already registered config. See SDK for more info.""" + sm = SnarlMessage(msg_type=REGISTER_ALERT, + title=appName, + text=classStr) + return sm.send(False) + +def snRevokeConfig(replyWnd): + """Revoke a config window""" + sm = SnarlMessage(msg_type=REVOKE_CONFIG_WINDOW) + sm.data = replyWnd + if replyWnd == SnarlMessage.lastKnownHWnd: + SnarlMessage.lastKnownHWnd = 0 + return sm.send(False) + +def snGetSnarlWindow(): + """Returns the hWnd of the snarl window""" + return myWin32Funcs.FindWindow(None, "Snarl") + +def snGetAppPath(): + """Returns the application path of the currently running snarl window""" + app_path = None + snarl_handle = snGetSnarlWindow() + if snarl_handle != 0: + pathwin_handle = myWin32Funcs.FindWindowEx(snarl_handle, + 0, + "static", + None) + if pathwin_handle != 0: + try: + result = myWin32Funcs.GetWindowText(pathwin_handle) + app_path = result + except Win32FuncException: + pass + + + return app_path + +def snGetIconsPath(): + """Returns the path to the icons of the program""" + s = snGetAppPath() + if s is None: + return "" + else: + return s + "etc\\icons\\" + +def snSendTestMessage(data=None): + """Sends a test message to Snarl. Used to make sure the +api is connecting""" + param = 0 + command = 0 + if data: + param = struct.pack("I", data) + command = 1 + myWin32Funcs.SendMessage(snGetSnarlWindow(), WM_SNARLTEST, command, param) diff --git a/plugins/snarl_notifications/__init__.py b/plugins/snarl_notifications/__init__.py index ad9c96aaa..b504dfd50 100644 --- a/plugins/snarl_notifications/__init__.py +++ b/plugins/snarl_notifications/__init__.py @@ -1 +1 @@ -from plugin import SnarlNotificationsPlugin \ No newline at end of file +from plugin import SnarlNotificationsPlugin diff --git a/plugins/snarl_notifications/plugin.py b/plugins/snarl_notifications/plugin.py index 4d831ee39..3e21acb77 100644 --- a/plugins/snarl_notifications/plugin.py +++ b/plugins/snarl_notifications/plugin.py @@ -38,54 +38,53 @@ from plugins.helpers import log_calls, log from common import ged class SnarlNotificationsPlugin(GajimPlugin): - name = u'Snarl Notifications' - short_name = u'snarl_notifications' - version = u'0.1' - description = u'''Shows events notification using Snarl (http://www.fullphat.net/) under Windows. Snarl needs to be installed in system. + name = u'Snarl Notifications' + short_name = u'snarl_notifications' + version = u'0.1' + description = u'''Shows events notification using Snarl (http://www.fullphat.net/) under Windows. Snarl needs to be installed in system. PySnarl bindings are used (http://code.google.com/p/pysnarl/).''' - authors = [u'Mateusz Biliński '] - homepage = u'http://blog.bilinski.it' - - @log_calls('SnarlNotificationsPlugin') - def init(self): - self.config_dialog = None - #self.gui_extension_points = {} - #self.config_default_values = {} - - self.events_handlers = {'NewMessage' : (ged.POSTCORE, self.newMessage)} + authors = [u'Mateusz Biliński '] + homepage = u'http://blog.bilinski.it' - @log_calls('SnarlNotificationsPlugin') - def activate(self): - pass - - @log_calls('SnarlNotificationsPlugin') - def deactivate(self): - pass - - @log_calls('SnarlNotificationsPlugin') - def newMessage(self, args): - event_name = "NewMessage" - data = args - account = data[0] - jid = data[1][0] - jid_without_resource = gajim.get_jid_without_resource(jid) - msg = data[1][1] - msg_type = data[1][4] - if msg_type == 'chat': - nickname = gajim.get_contact_name_from_jid(account, - jid_without_resource) - elif msg_type == 'pm': - nickname = gajim.get_resource_from_jid(jid) - - print "Event '%s' occured. Arguments: %s\n\n===\n"%(event_name, pformat(args)) - print "Event '%s' occured. Arguments: \naccount = %s\njid = %s\nmsg = %s\nnickname = %s"%( - event_name, account, jid, msg, nickname) - - - #if PySnarl.snGetVersion() != False: - #(major, minor) = PySnarl.snGetVersion() - #print "Found Snarl version",str(major)+"."+str(minor),"running." - #PySnarl.snShowMessage(nickname, msg[:20]+'...') - #else: - #print "Sorry Snarl does not appear to be running" - \ No newline at end of file + @log_calls('SnarlNotificationsPlugin') + def init(self): + self.config_dialog = None + #self.gui_extension_points = {} + #self.config_default_values = {} + + self.events_handlers = {'NewMessage' : (ged.POSTCORE, self.newMessage)} + + @log_calls('SnarlNotificationsPlugin') + def activate(self): + pass + + @log_calls('SnarlNotificationsPlugin') + def deactivate(self): + pass + + @log_calls('SnarlNotificationsPlugin') + def newMessage(self, args): + event_name = "NewMessage" + data = args + account = data[0] + jid = data[1][0] + jid_without_resource = gajim.get_jid_without_resource(jid) + msg = data[1][1] + msg_type = data[1][4] + if msg_type == 'chat': + nickname = gajim.get_contact_name_from_jid(account, + jid_without_resource) + elif msg_type == 'pm': + nickname = gajim.get_resource_from_jid(jid) + + print "Event '%s' occured. Arguments: %s\n\n===\n"%(event_name, pformat(args)) + print "Event '%s' occured. Arguments: \naccount = %s\njid = %s\nmsg = %s\nnickname = %s"%( + event_name, account, jid, msg, nickname) + + + #if PySnarl.snGetVersion() != False: + #(major, minor) = PySnarl.snGetVersion() + #print "Found Snarl version",str(major)+"."+str(minor),"running." + #PySnarl.snShowMessage(nickname, msg[:20]+'...') + #else: + #print "Sorry Snarl does not appear to be running" diff --git a/scripts/dev/run-build-test.py b/scripts/dev/run-build-test.py index daf177e87..c8b3b355f 100755 --- a/scripts/dev/run-build-test.py +++ b/scripts/dev/run-build-test.py @@ -4,19 +4,16 @@ import os import sys if os.getcwd().endswith('dev'): - os.chdir('../../') # we were in scripts/dev + os.chdir('../../') # we were in scripts/dev ret = 0 ret += os.system("make clean > " + os.devnull) -ret += os.system("make > " + os.devnull) +ret += os.system("make > " + os.devnull) ret += os.system("make check > " + os.devnull) if ret == 0: - print "Build successfull" - sys.exit(0) + print "Build successfull" + sys.exit(0) else: - print >>sys.stderr, "Build failed" - sys.exit(1) - -# vim: se ts=3: - + print >>sys.stderr, "Build failed" + sys.exit(1) diff --git a/scripts/dev/run-pylint.py b/scripts/dev/run-pylint.py index c840d8b48..480497f59 100755 --- a/scripts/dev/run-pylint.py +++ b/scripts/dev/run-pylint.py @@ -5,9 +5,6 @@ import os import sys if os.getcwd().endswith('dev'): - os.chdir('../../src/') # we were in scripts/dev + os.chdir('../../src/') # we were in scripts/dev os.system("pylint --indent-string='\t' --additional-builtins='_' --disable-msg=C0111,C0103,C0111,C0112 --disable-checker=design " + "".join(sys.argv[1:])) - - -# vim: se ts=3: diff --git a/setup_win32.py b/setup_win32.py index 13096217c..da5a9de50 100644 --- a/setup_win32.py +++ b/setup_win32.py @@ -52,41 +52,37 @@ if 'gtk' in os.listdir('.'): opts = { 'py2exe': { # ConfigParser,UserString,roman are needed for docutils - 'includes': 'pango,atk,gobject,cairo,pangocairo,gtk.keysyms,encodings,encodings.*,ConfigParser,UserString', + 'includes': ('pango,atk,gobject,cairo,pangocairo,gtk.keysyms,' + 'encodings,encodings.*,ConfigParser,UserString'), 'dll_excludes': [ - 'iconv.dll','intl.dll','libatk-1.0-0.dll', - 'libgdk_pixbuf-2.0-0.dll','libgdk-win32-2.0-0.dll', - 'libglib-2.0-0.dll','libgmodule-2.0-0.dll', - 'libgobject-2.0-0.dll','libgthread-2.0-0.dll', - 'libgtk-win32-2.0-0.dll','libpango-1.0-0.dll', - 'libpangowin32-1.0-0.dll','libcairo-2.dll', - 'libpangocairo-1.0-0.dll','libpangoft2-1.0-0.dll', + 'iconv.dll', 'intl.dll', 'libatk-1.0-0.dll', + 'libgdk_pixbuf-2.0-0.dll', 'libgdk-win32-2.0-0.dll', + 'libglib-2.0-0.dll', 'libgmodule-2.0-0.dll', + 'libgobject-2.0-0.dll', 'libgthread-2.0-0.dll', + 'libgtk-win32-2.0-0.dll', 'libpango-1.0-0.dll', + 'libpangowin32-1.0-0.dll', 'libcairo-2.dll', + 'libpangocairo-1.0-0.dll', 'libpangoft2-1.0-0.dll', ], 'excludes': [ 'docutils' ], - 'optimize': 2, + 'optimize': 2, } } setup( - name = 'Gajim', - version = '0.12.1', - description = 'A full featured Jabber client', - author = 'Gajim Development Team', - url = 'http://www.gajim.org/', - download_url = 'http://www.gajim.org/downloads.php', - license = 'GPL', - - windows = [{'script': 'src/gajim.py', - 'icon_resources': [(1, 'data/pixmaps/gajim.ico')]}, - {'script': 'src/history_manager.py', - 'icon_resources': [(1, 'data/pixmaps/gajim.ico')]}], + name='Gajim', + version='0.12.1', + description='A full featured Jabber client', + author='Gajim Development Team', + url='http://www.gajim.org/', + download_url='http://www.gajim.org/downloads.php', + license='GPL', + windows=[{'script': 'src/gajim.py', + 'icon_resources': [(1, 'data/pixmaps/gajim.ico')]}, + {'script': 'src/history_manager.py', + 'icon_resources': [(1, 'data/pixmaps/gajim.ico')]}], options=opts, - data_files=docutils_files, - ) - -# vim: se ts=3: diff --git a/src/adhoc_commands.py b/src/adhoc_commands.py index a657b8203..29a9ed5c9 100644 --- a/src/adhoc_commands.py +++ b/src/adhoc_commands.py @@ -35,537 +35,535 @@ import dialogs import dataforms_widget class CommandWindow: - """ - Class for a window for single ad-hoc commands session + """ + Class for a window for single ad-hoc commands session - Note, that there might be more than one for one account/jid pair in one - moment. + Note, that there might be more than one for one account/jid pair in one + moment. - TODO: Maybe put this window into MessageWindow? consider this when it will - be possible to manage more than one window of one. - TODO: Account/jid pair in MessageWindowMgr. - TODO: GTK 2.10 has a special wizard-widget, consider using it... - """ + TODO: Maybe put this window into MessageWindow? consider this when it will + be possible to manage more than one window of one. + TODO: Account/jid pair in MessageWindowMgr. + TODO: GTK 2.10 has a special wizard-widget, consider using it... + """ - def __init__(self, account, jid, commandnode=None): - """ - Create new window - """ + def __init__(self, account, jid, commandnode=None): + """ + Create new window + """ - # an account object - self.account = gajim.connections[account] - self.jid = jid + # an account object + self.account = gajim.connections[account] + self.jid = jid - self.pulse_id=None # to satisfy self.setup_pulsing() - self.commandlist=None # a list of (commandname, commanddescription) + self.pulse_id=None # to satisfy self.setup_pulsing() + self.commandlist=None # a list of (commandname, commanddescription) - # command's data - self.commandnode = commandnode - self.sessionid = None - self.dataform = None - self.allow_stage3_close = False + # command's data + self.commandnode = commandnode + self.sessionid = None + self.dataform = None + self.allow_stage3_close = False - # retrieving widgets from xml - self.xml = gtkgui_helpers.get_gtk_builder('adhoc_commands_window.ui') - self.window = self.xml.get_object('adhoc_commands_window') - self.window.connect('delete-event', self.on_adhoc_commands_window_delete_event) - for name in ('back_button', 'forward_button', - 'execute_button','close_button','stages_notebook', - 'retrieving_commands_stage_vbox', - 'command_list_stage_vbox','command_list_vbox', - 'sending_form_stage_vbox','sending_form_progressbar', - 'notes_label','no_commands_stage_vbox','error_stage_vbox', - 'error_description_label'): - self.__dict__[name] = self.xml.get_object(name) + # retrieving widgets from xml + self.xml = gtkgui_helpers.get_gtk_builder('adhoc_commands_window.ui') + self.window = self.xml.get_object('adhoc_commands_window') + self.window.connect('delete-event', self.on_adhoc_commands_window_delete_event) + for name in ('back_button', 'forward_button', + 'execute_button', 'close_button', 'stages_notebook', + 'retrieving_commands_stage_vbox', + 'command_list_stage_vbox', 'command_list_vbox', + 'sending_form_stage_vbox', 'sending_form_progressbar', + 'notes_label', 'no_commands_stage_vbox', 'error_stage_vbox', + 'error_description_label'): + self.__dict__[name] = self.xml.get_object(name) - # creating data forms widget - self.data_form_widget = dataforms_widget.DataFormWidget() - self.data_form_widget.show() - self.sending_form_stage_vbox.pack_start(self.data_form_widget) + # creating data forms widget + self.data_form_widget = dataforms_widget.DataFormWidget() + self.data_form_widget.show() + self.sending_form_stage_vbox.pack_start(self.data_form_widget) - if self.commandnode: - # Execute command - self.stage3() - else: - # setting initial stage - self.stage1() + if self.commandnode: + # Execute command + self.stage3() + else: + # setting initial stage + self.stage1() - # displaying the window - self.xml.connect_signals(self) - self.window.show_all() + # displaying the window + self.xml.connect_signals(self) + self.window.show_all() - # These functions are set up by appropriate stageX methods. - def stage_finish(self, *anything): - pass + # These functions are set up by appropriate stageX methods. + def stage_finish(self, *anything): + pass - def stage_back_button_clicked(self, *anything): - assert False + def stage_back_button_clicked(self, *anything): + assert False - def stage_forward_button_clicked(self, *anything): - assert False + def stage_forward_button_clicked(self, *anything): + assert False - def stage_execute_button_clicked(self, *anything): - assert False + def stage_execute_button_clicked(self, *anything): + assert False - def stage_close_button_clicked(self, *anything): - assert False + def stage_close_button_clicked(self, *anything): + assert False - def stage_adhoc_commands_window_delete_event(self, *anything): - assert False + def stage_adhoc_commands_window_delete_event(self, *anything): + assert False - def do_nothing(self, *anything): - return False + def do_nothing(self, *anything): + return False - # Widget callbacks... - def on_back_button_clicked(self, *anything): - return self.stage_back_button_clicked(*anything) + # Widget callbacks... + def on_back_button_clicked(self, *anything): + return self.stage_back_button_clicked(*anything) - def on_forward_button_clicked(self, *anything): - return self.stage_forward_button_clicked(*anything) + def on_forward_button_clicked(self, *anything): + return self.stage_forward_button_clicked(*anything) - def on_execute_button_clicked(self, *anything): - return self.stage_execute_button_clicked(*anything) + def on_execute_button_clicked(self, *anything): + return self.stage_execute_button_clicked(*anything) - def on_close_button_clicked(self, *anything): - return self.stage_close_button_clicked(*anything) + def on_close_button_clicked(self, *anything): + return self.stage_close_button_clicked(*anything) - def on_adhoc_commands_window_destroy(self, *anything): - # TODO: do all actions that are needed to remove this object from memory... - self.remove_pulsing() + def on_adhoc_commands_window_destroy(self, *anything): + # TODO: do all actions that are needed to remove this object from memory... + self.remove_pulsing() - def on_adhoc_commands_window_delete_event(self, *anything): - return self.stage_adhoc_commands_window_delete_event(self.window) + def on_adhoc_commands_window_delete_event(self, *anything): + return self.stage_adhoc_commands_window_delete_event(self.window) - def __del__(self): - print 'Object has been deleted.' + def __del__(self): + print 'Object has been deleted.' # stage 1: waiting for command list - def stage1(self): - """ - Prepare the first stage. Request command list, set appropriate state of - widgets - """ - # close old stage... - self.stage_finish() + def stage1(self): + """ + Prepare the first stage. Request command list, set appropriate state of + widgets + """ + # close old stage... + self.stage_finish() - # show the stage - self.stages_notebook.set_current_page( - self.stages_notebook.page_num( - self.retrieving_commands_stage_vbox)) + # show the stage + self.stages_notebook.set_current_page( + self.stages_notebook.page_num( + self.retrieving_commands_stage_vbox)) - # set widgets' state - self.close_button.set_sensitive(True) - self.back_button.set_sensitive(False) - self.forward_button.set_sensitive(False) - self.execute_button.set_sensitive(False) + # set widgets' state + self.close_button.set_sensitive(True) + self.back_button.set_sensitive(False) + self.forward_button.set_sensitive(False) + self.execute_button.set_sensitive(False) - # request command list - self.request_command_list() - self.setup_pulsing( - self.xml.get_object('retrieving_commands_progressbar')) + # request command list + self.request_command_list() + self.setup_pulsing( + self.xml.get_object('retrieving_commands_progressbar')) - # setup the callbacks - self.stage_finish = self.stage1_finish - self.stage_close_button_clicked = self.stage1_close_button_clicked - self.stage_adhoc_commands_window_delete_event = self.stage1_adhoc_commands_window_delete_event + # setup the callbacks + self.stage_finish = self.stage1_finish + self.stage_close_button_clicked = self.stage1_close_button_clicked + self.stage_adhoc_commands_window_delete_event = self.stage1_adhoc_commands_window_delete_event - def stage1_finish(self): - self.remove_pulsing() + def stage1_finish(self): + self.remove_pulsing() - def stage1_close_button_clicked(self, widget): - # cancelling in this stage is not critical, so we don't - # show any popups to user - self.stage1_finish() - self.window.destroy() + def stage1_close_button_clicked(self, widget): + # cancelling in this stage is not critical, so we don't + # show any popups to user + self.stage1_finish() + self.window.destroy() - def stage1_adhoc_commands_window_delete_event(self, widget): - self.stage1_finish() - return True + def stage1_adhoc_commands_window_delete_event(self, widget): + self.stage1_finish() + return True # stage 2: choosing the command to execute - def stage2(self): - """ - Populate the command list vbox with radiobuttons + def stage2(self): + """ + Populate the command list vbox with radiobuttons - FIXME: If there is more commands, maybe some kind of list, set widgets - state - """ - # close old stage - self.stage_finish() + FIXME: If there is more commands, maybe some kind of list, set widgets + state + """ + # close old stage + self.stage_finish() - assert len(self.commandlist)>0 + assert len(self.commandlist)>0 - self.stages_notebook.set_current_page( - self.stages_notebook.page_num( - self.command_list_stage_vbox)) + self.stages_notebook.set_current_page( + self.stages_notebook.page_num( + self.command_list_stage_vbox)) - self.close_button.set_sensitive(True) - self.back_button.set_sensitive(False) - self.forward_button.set_sensitive(True) - self.execute_button.set_sensitive(False) + self.close_button.set_sensitive(True) + self.back_button.set_sensitive(False) + self.forward_button.set_sensitive(True) + self.execute_button.set_sensitive(False) - # build the commands list radiobuttons - first_radio = None - for (commandnode, commandname) in self.commandlist: - radio = gtk.RadioButton(first_radio, label=commandname) - radio.connect("toggled", self.on_command_radiobutton_toggled, commandnode) - if not first_radio: - first_radio = radio - self.commandnode = commandnode - self.command_list_vbox.pack_start(radio, expand=False) - self.command_list_vbox.show_all() + # build the commands list radiobuttons + first_radio = None + for (commandnode, commandname) in self.commandlist: + radio = gtk.RadioButton(first_radio, label=commandname) + radio.connect("toggled", self.on_command_radiobutton_toggled, commandnode) + if not first_radio: + first_radio = radio + self.commandnode = commandnode + self.command_list_vbox.pack_start(radio, expand=False) + self.command_list_vbox.show_all() - self.stage_finish = self.stage2_finish - self.stage_close_button_clicked = self.stage2_close_button_clicked - self.stage_forward_button_clicked = self.stage2_forward_button_clicked - self.stage_adhoc_commands_window_delete_event = self.do_nothing + self.stage_finish = self.stage2_finish + self.stage_close_button_clicked = self.stage2_close_button_clicked + self.stage_forward_button_clicked = self.stage2_forward_button_clicked + self.stage_adhoc_commands_window_delete_event = self.do_nothing - def stage2_finish(self): - """ - Remove widgets we created. Not needed when the window is destroyed - """ - def remove_widget(widget): - self.command_list_vbox.remove(widget) - self.command_list_vbox.foreach(remove_widget) + def stage2_finish(self): + """ + Remove widgets we created. Not needed when the window is destroyed + """ + def remove_widget(widget): + self.command_list_vbox.remove(widget) + self.command_list_vbox.foreach(remove_widget) - def stage2_close_button_clicked(self, widget): - self.stage_finish() - self.window.destroy() + def stage2_close_button_clicked(self, widget): + self.stage_finish() + self.window.destroy() - def stage2_forward_button_clicked(self, widget): - self.stage3() + def stage2_forward_button_clicked(self, widget): + self.stage3() - def on_command_radiobutton_toggled(self, widget, commandnode): - self.commandnode = commandnode + def on_command_radiobutton_toggled(self, widget, commandnode): + self.commandnode = commandnode - def on_check_commands_1_button_clicked(self, widget): - self.stage1() + def on_check_commands_1_button_clicked(self, widget): + self.stage1() # stage 3: command invocation - def stage3(self): - # close old stage - self.stage_finish() + def stage3(self): + # close old stage + self.stage_finish() - assert isinstance(self.commandnode, unicode) + assert isinstance(self.commandnode, unicode) - self.form_status = None + self.form_status = None - self.stages_notebook.set_current_page( - self.stages_notebook.page_num( - self.sending_form_stage_vbox)) + self.stages_notebook.set_current_page( + self.stages_notebook.page_num( + self.sending_form_stage_vbox)) - self.close_button.set_sensitive(True) - self.back_button.set_sensitive(False) - self.forward_button.set_sensitive(False) - self.execute_button.set_sensitive(False) + self.close_button.set_sensitive(True) + self.back_button.set_sensitive(False) + self.forward_button.set_sensitive(False) + self.execute_button.set_sensitive(False) - self.stage3_submit_form() + self.stage3_submit_form() - self.stage_finish = self.stage3_finish - self.stage_back_button_clicked = self.stage3_back_button_clicked - self.stage_forward_button_clicked = self.stage3_forward_button_clicked - self.stage_execute_button_clicked = self.stage3_execute_button_clicked - self.stage_close_button_clicked = self.stage3_close_button_clicked - self.stage_adhoc_commands_window_delete_event = self.stage3_close_button_clicked + self.stage_finish = self.stage3_finish + self.stage_back_button_clicked = self.stage3_back_button_clicked + self.stage_forward_button_clicked = self.stage3_forward_button_clicked + self.stage_execute_button_clicked = self.stage3_execute_button_clicked + self.stage_close_button_clicked = self.stage3_close_button_clicked + self.stage_adhoc_commands_window_delete_event = self.stage3_close_button_clicked - def stage3_finish(self): - pass + def stage3_finish(self): + pass - def stage3_close_button_clicked(self, widget): - """ - We are in the middle of executing command. Ask user if he really want to - cancel the process, then cancel it - """ - # this works also as a handler for window_delete_event, so we have to return appropriate - # values - if self.form_status == 'completed': - if widget!=self.window: - self.window.destroy() - return False + def stage3_close_button_clicked(self, widget): + """ + We are in the middle of executing command. Ask user if he really want to + cancel the process, then cancel it + """ + # this works also as a handler for window_delete_event, so we have to return appropriate + # values + if self.form_status == 'completed': + if widget!=self.window: + self.window.destroy() + return False - if self.allow_stage3_close: - return False + if self.allow_stage3_close: + return False - def on_yes(button): - self.send_cancel() - self.allow_stage3_close = True - self.window.destroy() + def on_yes(button): + self.send_cancel() + self.allow_stage3_close = True + self.window.destroy() - dialog = dialogs.HigDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT | \ - gtk.DIALOG_MODAL, gtk.BUTTONS_YES_NO, _('Cancel confirmation'), - _('You are in process of executing command. Do you really want to ' - 'cancel it?'), on_response_yes=on_yes) - dialog.popup() - return True # Block event, don't close window + dialog = dialogs.HigDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT | \ + gtk.DIALOG_MODAL, gtk.BUTTONS_YES_NO, _('Cancel confirmation'), + _('You are in process of executing command. Do you really want to ' + 'cancel it?'), on_response_yes=on_yes) + dialog.popup() + return True # Block event, don't close window - def stage3_back_button_clicked(self, widget): - self.stage3_submit_form('prev') + def stage3_back_button_clicked(self, widget): + self.stage3_submit_form('prev') - def stage3_forward_button_clicked(self, widget): - self.stage3_submit_form('next') + def stage3_forward_button_clicked(self, widget): + self.stage3_submit_form('next') - def stage3_execute_button_clicked(self, widget): - self.stage3_submit_form('execute') + def stage3_execute_button_clicked(self, widget): + self.stage3_submit_form('execute') - def stage3_submit_form(self, action='execute'): - self.data_form_widget.set_sensitive(False) - if self.data_form_widget.get_data_form(): - self.data_form_widget.data_form.type='submit' - else: - self.data_form_widget.hide() + def stage3_submit_form(self, action='execute'): + self.data_form_widget.set_sensitive(False) + if self.data_form_widget.get_data_form(): + self.data_form_widget.data_form.type='submit' + else: + self.data_form_widget.hide() - self.close_button.set_sensitive(True) - self.back_button.set_sensitive(False) - self.forward_button.set_sensitive(False) - self.execute_button.set_sensitive(False) + self.close_button.set_sensitive(True) + self.back_button.set_sensitive(False) + self.forward_button.set_sensitive(False) + self.execute_button.set_sensitive(False) - self.sending_form_progressbar.show() - self.setup_pulsing(self.sending_form_progressbar) - self.send_command(action) + self.sending_form_progressbar.show() + self.setup_pulsing(self.sending_form_progressbar) + self.send_command(action) - def stage3_next_form(self, command): - if not isinstance(command, xmpp.Node): - self.stage5(error=_('Service sent malformed data'), senderror=True) - return + def stage3_next_form(self, command): + if not isinstance(command, xmpp.Node): + self.stage5(error=_('Service sent malformed data'), senderror=True) + return - self.remove_pulsing() - self.sending_form_progressbar.hide() + self.remove_pulsing() + self.sending_form_progressbar.hide() - if not self.sessionid: - self.sessionid = command.getAttr('sessionid') - elif self.sessionid != command.getAttr('sessionid'): - self.stage5(error=_('Service changed the session identifier.'), - senderror=True) - return + if not self.sessionid: + self.sessionid = command.getAttr('sessionid') + elif self.sessionid != command.getAttr('sessionid'): + self.stage5(error=_('Service changed the session identifier.'), + senderror=True) + return - self.form_status = command.getAttr('status') + self.form_status = command.getAttr('status') - self.commandnode = command.getAttr('node') - if command.getTag('x'): - self.dataform = dataforms.ExtendForm(node=command.getTag('x')) + self.commandnode = command.getAttr('node') + if command.getTag('x'): + self.dataform = dataforms.ExtendForm(node=command.getTag('x')) - self.data_form_widget.set_sensitive(True) - try: - self.data_form_widget.data_form=self.dataform - except dataforms.Error: - self.stage5(error=_('Service sent malformed data'), senderror=True) - return - self.data_form_widget.show() - if self.data_form_widget.title: - self.window.set_title('%s - Ad-hoc Commands - Gajim' % \ - self.data_form_widget.title) - else: - self.data_form_widget.hide() + self.data_form_widget.set_sensitive(True) + try: + self.data_form_widget.data_form=self.dataform + except dataforms.Error: + self.stage5(error=_('Service sent malformed data'), senderror=True) + return + self.data_form_widget.show() + if self.data_form_widget.title: + self.window.set_title('%s - Ad-hoc Commands - Gajim' % \ + self.data_form_widget.title) + else: + self.data_form_widget.hide() - actions = command.getTag('actions') - if actions: - # actions, actions, actions... - self.close_button.set_sensitive(True) - self.back_button.set_sensitive(actions.getTag('prev') is not None) - self.forward_button.set_sensitive(actions.getTag('next') is not None) - self.execute_button.set_sensitive(True) - else: - self.close_button.set_sensitive(True) - self.back_button.set_sensitive(False) - self.forward_button.set_sensitive(False) - self.execute_button.set_sensitive(True) + actions = command.getTag('actions') + if actions: + # actions, actions, actions... + self.close_button.set_sensitive(True) + self.back_button.set_sensitive(actions.getTag('prev') is not None) + self.forward_button.set_sensitive(actions.getTag('next') is not None) + self.execute_button.set_sensitive(True) + else: + self.close_button.set_sensitive(True) + self.back_button.set_sensitive(False) + self.forward_button.set_sensitive(False) + self.execute_button.set_sensitive(True) - if self.form_status == 'completed': - self.close_button.set_sensitive(True) - self.back_button.hide() - self.forward_button.hide() - self.execute_button.hide() - self.close_button.show() - self.stage_adhoc_commands_window_delete_event = self.stage3_close_button_clicked + if self.form_status == 'completed': + self.close_button.set_sensitive(True) + self.back_button.hide() + self.forward_button.hide() + self.execute_button.hide() + self.close_button.show() + self.stage_adhoc_commands_window_delete_event = self.stage3_close_button_clicked - note = command.getTag('note') - if note: - self.notes_label.set_text(note.getData().decode('utf-8')) - self.notes_label.set_no_show_all(False) - self.notes_label.show() - else: - self.notes_label.set_no_show_all(True) - self.notes_label.hide() + note = command.getTag('note') + if note: + self.notes_label.set_text(note.getData().decode('utf-8')) + self.notes_label.set_no_show_all(False) + self.notes_label.show() + else: + self.notes_label.set_no_show_all(True) + self.notes_label.hide() # stage 4: no commands are exposed - def stage4(self): - """ - Display the message. Wait for user to close the window - """ - # close old stage - self.stage_finish() + def stage4(self): + """ + Display the message. Wait for user to close the window + """ + # close old stage + self.stage_finish() - self.stages_notebook.set_current_page( - self.stages_notebook.page_num( - self.no_commands_stage_vbox)) + self.stages_notebook.set_current_page( + self.stages_notebook.page_num( + self.no_commands_stage_vbox)) - self.close_button.set_sensitive(True) - self.back_button.set_sensitive(False) - self.forward_button.set_sensitive(False) - self.execute_button.set_sensitive(False) + self.close_button.set_sensitive(True) + self.back_button.set_sensitive(False) + self.forward_button.set_sensitive(False) + self.execute_button.set_sensitive(False) - self.stage_finish = self.do_nothing - self.stage_close_button_clicked = self.stage4_close_button_clicked - self.stage_adhoc_commands_window_delete_event = self.do_nothing + self.stage_finish = self.do_nothing + self.stage_close_button_clicked = self.stage4_close_button_clicked + self.stage_adhoc_commands_window_delete_event = self.do_nothing - def stage4_close_button_clicked(self, widget): - self.window.destroy() + def stage4_close_button_clicked(self, widget): + self.window.destroy() - def on_check_commands_2_button_clicked(self, widget): - self.stage1() + def on_check_commands_2_button_clicked(self, widget): + self.stage1() # stage 5: an error has occured - def stage5(self, error=None, errorid=None, senderror=False): - """ - Display the error message. Wait for user to close the window - """ - # FIXME: sending error to responder - # close old stage - self.stage_finish() + def stage5(self, error=None, errorid=None, senderror=False): + """ + Display the error message. Wait for user to close the window + """ + # FIXME: sending error to responder + # close old stage + self.stage_finish() - assert errorid or error + assert errorid or error - if errorid: - # we've got error code, display appropriate message - try: - errorname = xmpp.NS_STANZAS + ' ' + str(errorid) - errordesc = xmpp.ERRORS[errorname][2] - error = errordesc.decode('utf-8') - del errorname, errordesc - except KeyError: # when stanza doesn't have error description - error = _('Service returned an error.') - elif error: - # we've got error message - pass - else: - # we don't know what's that, bailing out - assert False + if errorid: + # we've got error code, display appropriate message + try: + errorname = xmpp.NS_STANZAS + ' ' + str(errorid) + errordesc = xmpp.ERRORS[errorname][2] + error = errordesc.decode('utf-8') + del errorname, errordesc + except KeyError: # when stanza doesn't have error description + error = _('Service returned an error.') + elif error: + # we've got error message + pass + else: + # we don't know what's that, bailing out + assert False - self.stages_notebook.set_current_page( - self.stages_notebook.page_num( - self.error_stage_vbox)) + self.stages_notebook.set_current_page( + self.stages_notebook.page_num( + self.error_stage_vbox)) - self.close_button.set_sensitive(True) - self.back_button.hide() - self.forward_button.hide() - self.execute_button.hide() + self.close_button.set_sensitive(True) + self.back_button.hide() + self.forward_button.hide() + self.execute_button.hide() - self.error_description_label.set_text(error) + self.error_description_label.set_text(error) - self.stage_finish = self.do_nothing - self.stage_close_button_clicked = self.stage5_close_button_clicked - self.stage_adhoc_commands_window_delete_event = self.do_nothing + self.stage_finish = self.do_nothing + self.stage_close_button_clicked = self.stage5_close_button_clicked + self.stage_adhoc_commands_window_delete_event = self.do_nothing - def stage5_close_button_clicked(self, widget): - self.window.destroy() + def stage5_close_button_clicked(self, widget): + self.window.destroy() # helpers to handle pulsing in progressbar - def setup_pulsing(self, progressbar): - """ - Set the progressbar to pulse. Makes a custom function to repeatedly call - progressbar.pulse() method - """ - assert not self.pulse_id - assert isinstance(progressbar, gtk.ProgressBar) + def setup_pulsing(self, progressbar): + """ + Set the progressbar to pulse. Makes a custom function to repeatedly call + progressbar.pulse() method + """ + assert not self.pulse_id + assert isinstance(progressbar, gtk.ProgressBar) - def callback(): - progressbar.pulse() - return True # important to keep callback be called back! + def callback(): + progressbar.pulse() + return True # important to keep callback be called back! - # 12 times per second (80 miliseconds) - self.pulse_id = gobject.timeout_add(80, callback) + # 12 times per second (80 miliseconds) + self.pulse_id = gobject.timeout_add(80, callback) - def remove_pulsing(self): - """ - Stop pulsing, useful when especially when removing widget - """ - if self.pulse_id: - gobject.source_remove(self.pulse_id) - self.pulse_id=None + def remove_pulsing(self): + """ + Stop pulsing, useful when especially when removing widget + """ + if self.pulse_id: + gobject.source_remove(self.pulse_id) + self.pulse_id=None # handling xml stanzas - def request_command_list(self): - """ - Request the command list. Change stage on delivery - """ - query = xmpp.Iq(typ='get', to=xmpp.JID(self.jid), queryNS=xmpp.NS_DISCO_ITEMS) - query.setQuerynode(xmpp.NS_COMMANDS) + def request_command_list(self): + """ + Request the command list. Change stage on delivery + """ + query = xmpp.Iq(typ='get', to=xmpp.JID(self.jid), queryNS=xmpp.NS_DISCO_ITEMS) + query.setQuerynode(xmpp.NS_COMMANDS) - def callback(response): - '''Called on response to query.''' - # FIXME: move to connection_handlers.py - # is error => error stage - error = response.getError() - if error: - # extracting error description from xmpp/protocol.py - self.stage5(errorid = error) - return + def callback(response): + '''Called on response to query.''' + # FIXME: move to connection_handlers.py + # is error => error stage + error = response.getError() + if error: + # extracting error description from xmpp/protocol.py + self.stage5(errorid = error) + return - # no commands => no commands stage - # commands => command selection stage - query = response.getTag('query') - if query and query.getAttr('node') == xmpp.NS_COMMANDS: - items = query.getTags('item') - else: - items = [] - if len(items)==0: - self.commandlist = [] - self.stage4() - else: - self.commandlist = [(t.getAttr('node'), t.getAttr('name')) for t in items] - self.stage2() + # no commands => no commands stage + # commands => command selection stage + query = response.getTag('query') + if query and query.getAttr('node') == xmpp.NS_COMMANDS: + items = query.getTags('item') + else: + items = [] + if len(items)==0: + self.commandlist = [] + self.stage4() + else: + self.commandlist = [(t.getAttr('node'), t.getAttr('name')) for t in items] + self.stage2() - self.account.connection.SendAndCallForResponse(query, callback) + self.account.connection.SendAndCallForResponse(query, callback) - def send_command(self, action='execute'): - """ - Send the command with data form. Wait for reply - """ - # create the stanza - assert isinstance(self.commandnode, unicode) - assert action in ('execute', 'prev', 'next', 'complete') + def send_command(self, action='execute'): + """ + Send the command with data form. Wait for reply + """ + # create the stanza + assert isinstance(self.commandnode, unicode) + assert action in ('execute', 'prev', 'next', 'complete') - stanza = xmpp.Iq(typ='set', to=self.jid) - cmdnode = stanza.addChild('command', namespace=xmpp.NS_COMMANDS, attrs={ - 'node':self.commandnode, 'action':action}) + stanza = xmpp.Iq(typ='set', to=self.jid) + cmdnode = stanza.addChild('command', namespace=xmpp.NS_COMMANDS, attrs={ + 'node':self.commandnode, 'action':action}) - if self.sessionid: - cmdnode.setAttr('sessionid', self.sessionid) + if self.sessionid: + cmdnode.setAttr('sessionid', self.sessionid) - if self.data_form_widget.data_form: -# cmdnode.addChild(node=dataforms.DataForm(tofill=self.data_form_widget.data_form)) - # FIXME: simplified form to send + if self.data_form_widget.data_form: +# cmdnode.addChild(node=dataforms.DataForm(tofill=self.data_form_widget.data_form)) + # FIXME: simplified form to send - cmdnode.addChild(node=self.data_form_widget.data_form) + cmdnode.addChild(node=self.data_form_widget.data_form) - def callback(response): - # FIXME: move to connection_handlers.py - err = response.getError() - if err: - self.stage5(errorid = err) - else: - self.stage3_next_form(response.getTag('command')) + def callback(response): + # FIXME: move to connection_handlers.py + err = response.getError() + if err: + self.stage5(errorid = err) + else: + self.stage3_next_form(response.getTag('command')) - self.account.connection.SendAndCallForResponse(stanza, callback) + self.account.connection.SendAndCallForResponse(stanza, callback) - def send_cancel(self): - """ - Send the command with action='cancel' - """ - assert self.commandnode - if self.sessionid and self.account.connection: - # we already have sessionid, so the service sent at least one reply. - stanza = xmpp.Iq(typ='set', to=self.jid) - stanza.addChild('command', namespace=xmpp.NS_COMMANDS, attrs={ - 'node':self.commandnode, - 'sessionid':self.sessionid, - 'action':'cancel' - }) + def send_cancel(self): + """ + Send the command with action='cancel' + """ + assert self.commandnode + if self.sessionid and self.account.connection: + # we already have sessionid, so the service sent at least one reply. + stanza = xmpp.Iq(typ='set', to=self.jid) + stanza.addChild('command', namespace=xmpp.NS_COMMANDS, attrs={ + 'node':self.commandnode, + 'sessionid':self.sessionid, + 'action':'cancel' + }) - self.account.connection.send(stanza) - else: - # we did not received any reply from service; FIXME: we should wait and - # then send cancel; for now we do nothing - pass - -# vim: se ts=3: + self.account.connection.send(stanza) + else: + # we did not received any reply from service; FIXME: we should wait and + # then send cancel; for now we do nothing + pass diff --git a/src/advanced_configuration_window.py b/src/advanced_configuration_window.py index d97dac76c..33831f827 100644 --- a/src/advanced_configuration_window.py +++ b/src/advanced_configuration_window.py @@ -43,250 +43,248 @@ C_TYPE GTKGUI_GLADE = 'manage_accounts_window.ui' def rate_limit(rate): - """ - Call func at most *rate* times per second - """ - def decorator(func): - timeout = [None] - def f(*args, **kwargs): - if timeout[0] is not None: - gobject.source_remove(timeout[0]) - timeout[0] = None - def timeout_func(): - func(*args, **kwargs) - timeout[0] = None - timeout[0] = gobject.timeout_add(int(1000.0 / rate), timeout_func) - return f - return decorator + """ + Call func at most *rate* times per second + """ + def decorator(func): + timeout = [None] + def f(*args, **kwargs): + if timeout[0] is not None: + gobject.source_remove(timeout[0]) + timeout[0] = None + def timeout_func(): + func(*args, **kwargs) + timeout[0] = None + timeout[0] = gobject.timeout_add(int(1000.0 / rate), timeout_func) + return f + return decorator def tree_model_iter_children(model, treeiter): - it = model.iter_children(treeiter) - while it: - yield it - it = model.iter_next(it) + it = model.iter_children(treeiter) + while it: + yield it + it = model.iter_next(it) def tree_model_pre_order(model, treeiter): - yield treeiter - for childiter in tree_model_iter_children(model, treeiter): - for it in tree_model_pre_order(model, childiter): - yield it + yield treeiter + for childiter in tree_model_iter_children(model, treeiter): + for it in tree_model_pre_order(model, childiter): + yield it class AdvancedConfigurationWindow(object): - def __init__(self): - self.xml = gtkgui_helpers.get_gtk_builder('advanced_configuration_window.ui') - self.window = self.xml.get_object('advanced_configuration_window') - self.window.set_transient_for( - gajim.interface.instances['preferences'].window) - self.entry = self.xml.get_object('advanced_entry') - self.desc_label = self.xml.get_object('advanced_desc_label') - self.restart_label = self.xml.get_object('restart_label') + def __init__(self): + self.xml = gtkgui_helpers.get_gtk_builder('advanced_configuration_window.ui') + self.window = self.xml.get_object('advanced_configuration_window') + self.window.set_transient_for( + gajim.interface.instances['preferences'].window) + self.entry = self.xml.get_object('advanced_entry') + self.desc_label = self.xml.get_object('advanced_desc_label') + self.restart_label = self.xml.get_object('restart_label') - # Format: - # key = option name (root/subopt/opt separated by \n then) - # value = array(oldval, newval) - self.changed_opts = {} + # Format: + # key = option name (root/subopt/opt separated by \n then) + # value = array(oldval, newval) + self.changed_opts = {} - # For i18n - self.right_true_dict = {True: _('Activated'), False: _('Deactivated')} - self.types = { - 'boolean': _('Boolean'), - 'integer': _('Integer'), - 'string': _('Text'), - 'color': _('Color')} + # For i18n + self.right_true_dict = {True: _('Activated'), False: _('Deactivated')} + self.types = { + 'boolean': _('Boolean'), + 'integer': _('Integer'), + 'string': _('Text'), + 'color': _('Color')} - treeview = self.xml.get_object('advanced_treeview') - self.treeview = treeview - self.model = gtk.TreeStore(str, str, str) - self.fill_model() - self.model.set_sort_column_id(0, gtk.SORT_ASCENDING) - self.modelfilter = self.model.filter_new() - self.modelfilter.set_visible_func(self.visible_func) + treeview = self.xml.get_object('advanced_treeview') + self.treeview = treeview + self.model = gtk.TreeStore(str, str, str) + self.fill_model() + self.model.set_sort_column_id(0, gtk.SORT_ASCENDING) + self.modelfilter = self.model.filter_new() + self.modelfilter.set_visible_func(self.visible_func) - renderer_text = gtk.CellRendererText() - col = treeview.insert_column_with_attributes(-1, _('Preference Name'), - renderer_text, text = 0) - col.set_resizable(True) + renderer_text = gtk.CellRendererText() + col = treeview.insert_column_with_attributes(-1, _('Preference Name'), + renderer_text, text = 0) + col.set_resizable(True) - renderer_text = gtk.CellRendererText() - renderer_text.connect('edited', self.on_config_edited) - col = treeview.insert_column_with_attributes(-1, _('Value'), - renderer_text, text = 1) - col.set_cell_data_func(renderer_text, self.cb_value_column_data) + renderer_text = gtk.CellRendererText() + renderer_text.connect('edited', self.on_config_edited) + col = treeview.insert_column_with_attributes(-1, _('Value'), + renderer_text, text = 1) + col.set_cell_data_func(renderer_text, self.cb_value_column_data) - col.props.resizable = True - col.set_max_width(250) + col.props.resizable = True + col.set_max_width(250) - renderer_text = gtk.CellRendererText() - treeview.insert_column_with_attributes(-1, _('Type'), - renderer_text, text = 2) + renderer_text = gtk.CellRendererText() + treeview.insert_column_with_attributes(-1, _('Type'), + renderer_text, text = 2) - treeview.set_model(self.modelfilter) + treeview.set_model(self.modelfilter) - # connect signal for selection change - treeview.get_selection().connect('changed', - self.on_advanced_treeview_selection_changed) + # connect signal for selection change + treeview.get_selection().connect('changed', + self.on_advanced_treeview_selection_changed) - self.xml.connect_signals(self) - self.window.show_all() - self.restart_label.hide() - gajim.interface.instances['advanced_config'] = self + self.xml.connect_signals(self) + self.window.show_all() + self.restart_label.hide() + gajim.interface.instances['advanced_config'] = self - def cb_value_column_data(self, col, cell, model, iter_): - """ - Check if it's boolen or holds password stuff and if yes make the - cellrenderertext not editable, else - it's editable - """ - optname = model[iter_][C_PREFNAME] - opttype = model[iter_][C_TYPE] - if opttype == self.types['boolean'] or optname == 'password': - cell.set_property('editable', False) - else: - cell.set_property('editable', True) + def cb_value_column_data(self, col, cell, model, iter_): + """ + Check if it's boolen or holds password stuff and if yes make the + cellrenderertext not editable, else - it's editable + """ + optname = model[iter_][C_PREFNAME] + opttype = model[iter_][C_TYPE] + if opttype == self.types['boolean'] or optname == 'password': + cell.set_property('editable', False) + else: + cell.set_property('editable', True) - def get_option_path(self, model, iter_): - # It looks like path made from reversed array - # path[0] is the true one optname - # path[1] is the key name - # path[2] is the root of tree - # last two is optional - path = [model[iter_][0].decode('utf-8')] - parent = model.iter_parent(iter_) - while parent: - path.append(model[parent][0].decode('utf-8')) - parent = model.iter_parent(parent) - return path + def get_option_path(self, model, iter_): + # It looks like path made from reversed array + # path[0] is the true one optname + # path[1] is the key name + # path[2] is the root of tree + # last two is optional + path = [model[iter_][0].decode('utf-8')] + parent = model.iter_parent(iter_) + while parent: + path.append(model[parent][0].decode('utf-8')) + parent = model.iter_parent(parent) + return path - def on_advanced_treeview_selection_changed(self, treeselection): - model, iter_ = treeselection.get_selected() - # Check for GtkTreeIter - if iter_: - opt_path = self.get_option_path(model, iter_) - # Get text from first column in this row - desc = None - if len(opt_path) == 3: - desc = gajim.config.get_desc_per(opt_path[2], opt_path[1], - opt_path[0]) - elif len(opt_path) == 1: - desc = gajim.config.get_desc(opt_path[0]) - if desc: - self.desc_label.set_text(desc) - else: - #we talk about option description in advanced configuration editor - self.desc_label.set_text(_('(None)')) + def on_advanced_treeview_selection_changed(self, treeselection): + model, iter_ = treeselection.get_selected() + # Check for GtkTreeIter + if iter_: + opt_path = self.get_option_path(model, iter_) + # Get text from first column in this row + desc = None + if len(opt_path) == 3: + desc = gajim.config.get_desc_per(opt_path[2], opt_path[1], + opt_path[0]) + elif len(opt_path) == 1: + desc = gajim.config.get_desc(opt_path[0]) + if desc: + self.desc_label.set_text(desc) + else: + #we talk about option description in advanced configuration editor + self.desc_label.set_text(_('(None)')) - def remember_option(self, option, oldval, newval): - if option in self.changed_opts: - self.changed_opts[option] = (self.changed_opts[option][0], newval) - else: - self.changed_opts[option] = (oldval, newval) + def remember_option(self, option, oldval, newval): + if option in self.changed_opts: + self.changed_opts[option] = (self.changed_opts[option][0], newval) + else: + self.changed_opts[option] = (oldval, newval) - def on_advanced_treeview_row_activated(self, treeview, path, column): - modelpath = self.modelfilter.convert_path_to_child_path(path) - modelrow = self.model[modelpath] - option = modelrow[0].decode('utf-8') - if modelrow[2] == self.types['boolean']: - for key in self.right_true_dict.keys(): - if self.right_true_dict[key] == modelrow[1]: - modelrow[1] = key - newval = {'False': True, 'True': False}[modelrow[1]] - if len(modelpath) > 1: - optnamerow = self.model[modelpath[0]] - optname = optnamerow[0].decode('utf-8') - keyrow = self.model[modelpath[:2]] - key = keyrow[0].decode('utf-8') - gajim.config.get_desc_per(optname, key, option) - self.remember_option(option + '\n' + key + '\n' + optname, - modelrow[1], newval) - gajim.config.set_per(optname, key, option, newval) - else: - self.remember_option(option, modelrow[1], newval) - gajim.config.set(option, newval) - gajim.interface.save_config() - modelrow[1] = self.right_true_dict[newval] - self.check_for_restart() + def on_advanced_treeview_row_activated(self, treeview, path, column): + modelpath = self.modelfilter.convert_path_to_child_path(path) + modelrow = self.model[modelpath] + option = modelrow[0].decode('utf-8') + if modelrow[2] == self.types['boolean']: + for key in self.right_true_dict.keys(): + if self.right_true_dict[key] == modelrow[1]: + modelrow[1] = key + newval = {'False': True, 'True': False}[modelrow[1]] + if len(modelpath) > 1: + optnamerow = self.model[modelpath[0]] + optname = optnamerow[0].decode('utf-8') + keyrow = self.model[modelpath[:2]] + key = keyrow[0].decode('utf-8') + gajim.config.get_desc_per(optname, key, option) + self.remember_option(option + '\n' + key + '\n' + optname, + modelrow[1], newval) + gajim.config.set_per(optname, key, option, newval) + else: + self.remember_option(option, modelrow[1], newval) + gajim.config.set(option, newval) + gajim.interface.save_config() + modelrow[1] = self.right_true_dict[newval] + self.check_for_restart() - def check_for_restart(self): - self.restart_label.hide() - for opt in self.changed_opts: - opt_path = opt.split('\n') - if len(opt_path)==3: - restart = gajim.config.get_restart_per(opt_path[2], opt_path[1], - opt_path[0]) - else: - restart = gajim.config.get_restart(opt_path[0]) - if restart: - if self.changed_opts[opt][0] != self.changed_opts[opt][1]: - self.restart_label.show() - break + def check_for_restart(self): + self.restart_label.hide() + for opt in self.changed_opts: + opt_path = opt.split('\n') + if len(opt_path)==3: + restart = gajim.config.get_restart_per(opt_path[2], opt_path[1], + opt_path[0]) + else: + restart = gajim.config.get_restart(opt_path[0]) + if restart: + if self.changed_opts[opt][0] != self.changed_opts[opt][1]: + self.restart_label.show() + break - def on_config_edited(self, cell, path, text): - # convert modelfilter path to model path - modelpath = self.modelfilter.convert_path_to_child_path(path) - modelrow = self.model[modelpath] - option = modelrow[0].decode('utf-8') - text = text.decode('utf-8') - if len(modelpath) > 1: - optnamerow = self.model[modelpath[0]] - optname = optnamerow[0].decode('utf-8') - keyrow = self.model[modelpath[:2]] - key = keyrow[0].decode('utf-8') - self.remember_option(option + '\n' + key + '\n' + optname, modelrow[1], - text) - gajim.config.set_per(optname, key, option, text) - else: - self.remember_option(option, modelrow[1], text) - gajim.config.set(option, text) - gajim.interface.save_config() - modelrow[1] = text - self.check_for_restart() + def on_config_edited(self, cell, path, text): + # convert modelfilter path to model path + modelpath = self.modelfilter.convert_path_to_child_path(path) + modelrow = self.model[modelpath] + option = modelrow[0].decode('utf-8') + text = text.decode('utf-8') + if len(modelpath) > 1: + optnamerow = self.model[modelpath[0]] + optname = optnamerow[0].decode('utf-8') + keyrow = self.model[modelpath[:2]] + key = keyrow[0].decode('utf-8') + self.remember_option(option + '\n' + key + '\n' + optname, modelrow[1], + text) + gajim.config.set_per(optname, key, option, text) + else: + self.remember_option(option, modelrow[1], text) + gajim.config.set(option, text) + gajim.interface.save_config() + modelrow[1] = text + self.check_for_restart() - def on_advanced_configuration_window_destroy(self, widget): - del gajim.interface.instances['advanced_config'] + def on_advanced_configuration_window_destroy(self, widget): + del gajim.interface.instances['advanced_config'] - def on_advanced_close_button_clicked(self, widget): - self.window.destroy() + def on_advanced_close_button_clicked(self, widget): + self.window.destroy() - def fill_model(self, node=None, parent=None): - for item, option in gajim.config.get_children(node): - name = item[-1] - if option is None: # Node - newparent = self.model.append(parent, [name, '', '']) - self.fill_model(item, newparent) - else: # Leaf - type_ = self.types[option[OPT_TYPE][0]] - if name == 'password': - value = _('Hidden') - else: - if type_ == self.types['boolean']: - value = self.right_true_dict[option[OPT_VAL]] - else: - value = option[OPT_VAL] - self.model.append(parent, [name, value, type_]) + def fill_model(self, node=None, parent=None): + for item, option in gajim.config.get_children(node): + name = item[-1] + if option is None: # Node + newparent = self.model.append(parent, [name, '', '']) + self.fill_model(item, newparent) + else: # Leaf + type_ = self.types[option[OPT_TYPE][0]] + if name == 'password': + value = _('Hidden') + else: + if type_ == self.types['boolean']: + value = self.right_true_dict[option[OPT_VAL]] + else: + value = option[OPT_VAL] + self.model.append(parent, [name, value, type_]) - def visible_func(self, model, treeiter): - search_string = self.entry.get_text().decode('utf-8').lower() - for it in tree_model_pre_order(model,treeiter): - if model[it][C_TYPE] != '': - opt_path = self.get_option_path(model, it) - if len(opt_path) == 3: - desc = gajim.config.get_desc_per(opt_path[2], opt_path[1], - opt_path[0]) - elif len(opt_path) == 1: - desc = gajim.config.get_desc(opt_path[0]) - if search_string in model[it][C_PREFNAME] or (desc and \ - search_string in desc.lower()): - return True - return False + def visible_func(self, model, treeiter): + search_string = self.entry.get_text().decode('utf-8').lower() + for it in tree_model_pre_order(model, treeiter): + if model[it][C_TYPE] != '': + opt_path = self.get_option_path(model, it) + if len(opt_path) == 3: + desc = gajim.config.get_desc_per(opt_path[2], opt_path[1], + opt_path[0]) + elif len(opt_path) == 1: + desc = gajim.config.get_desc(opt_path[0]) + if search_string in model[it][C_PREFNAME] or (desc and \ + search_string in desc.lower()): + return True + return False - @rate_limit(3) - def on_advanced_entry_changed(self, widget): - self.modelfilter.refilter() - if not widget.get_text(): - # Maybe the expanded rows should be remembered here ... - self.treeview.collapse_all() - else: - # ... and be restored correctly here - self.treeview.expand_all() - -# vim: se ts=3: + @rate_limit(3) + def on_advanced_entry_changed(self, widget): + self.modelfilter.refilter() + if not widget.get_text(): + # Maybe the expanded rows should be remembered here ... + self.treeview.collapse_all() + else: + # ... and be restored correctly here + self.treeview.expand_all() diff --git a/src/atom_window.py b/src/atom_window.py index 394d4ac27..9052ed1ae 100644 --- a/src/atom_window.py +++ b/src/atom_window.py @@ -30,121 +30,119 @@ from common import helpers from common import i18n class AtomWindow: - window = None - entries = [] + window = None + entries = [] - @classmethod - def newAtomEntry(cls, entry): - """ - Queue new entry, open window if there's no one opened - """ - cls.entries.append(entry) + @classmethod + def newAtomEntry(cls, entry): + """ + Queue new entry, open window if there's no one opened + """ + cls.entries.append(entry) - if cls.window is None: - cls.window = AtomWindow() - else: - cls.window.updateCounter() + if cls.window is None: + cls.window = AtomWindow() + else: + cls.window.updateCounter() - @classmethod - def windowClosed(cls): - cls.window = None + @classmethod + def windowClosed(cls): + cls.window = None - def __init__(self): - """ - Create new window... only if we have anything to show - """ - assert len(self.__class__.entries)>0 + def __init__(self): + """ + Create new window... only if we have anything to show + """ + assert len(self.__class__.entries)>0 - self.entry = None # the entry actually displayed + self.entry = None # the entry actually displayed - self.xml = gtkgui_helpers.get_gtk_builder('atom_entry_window.ui') - self.window = self.xml.get_object('atom_entry_window') - for name in ('new_entry_label', 'feed_title_label', 'feed_title_eventbox', - 'feed_tagline_label', 'entry_title_label', 'entry_title_eventbox', - 'last_modified_label', 'close_button', 'next_button'): - self.__dict__[name] = self.xml.get_object(name) + self.xml = gtkgui_helpers.get_gtk_builder('atom_entry_window.ui') + self.window = self.xml.get_object('atom_entry_window') + for name in ('new_entry_label', 'feed_title_label', 'feed_title_eventbox', + 'feed_tagline_label', 'entry_title_label', 'entry_title_eventbox', + 'last_modified_label', 'close_button', 'next_button'): + self.__dict__[name] = self.xml.get_object(name) - self.displayNextEntry() + self.displayNextEntry() - self.xml.connect_signals(self) - self.window.show_all() + self.xml.connect_signals(self) + self.window.show_all() - self.entry_title_eventbox.add_events(gtk.gdk.BUTTON_PRESS_MASK) - self.feed_title_eventbox.add_events(gtk.gdk.BUTTON_PRESS_MASK) + self.entry_title_eventbox.add_events(gtk.gdk.BUTTON_PRESS_MASK) + self.feed_title_eventbox.add_events(gtk.gdk.BUTTON_PRESS_MASK) - def displayNextEntry(self): - """ - Get next entry from the queue and display it in the window - """ - assert len(self.__class__.entries)>0 + def displayNextEntry(self): + """ + Get next entry from the queue and display it in the window + """ + assert len(self.__class__.entries)>0 - newentry = self.__class__.entries.pop(0) + newentry = self.__class__.entries.pop(0) - # fill the fields - if newentry.feed_link is not None: - self.feed_title_label.set_markup( - u'%s' % \ - gobject.markup_escape_text(newentry.feed_title)) - else: - self.feed_title_label.set_markup( - gobject.markup_escape_text(newentry.feed_title)) + # fill the fields + if newentry.feed_link is not None: + self.feed_title_label.set_markup( + u'%s' % \ + gobject.markup_escape_text(newentry.feed_title)) + else: + self.feed_title_label.set_markup( + gobject.markup_escape_text(newentry.feed_title)) - self.feed_tagline_label.set_markup( - u'%s' % \ - gobject.markup_escape_text(newentry.feed_tagline)) + self.feed_tagline_label.set_markup( + u'%s' % \ + gobject.markup_escape_text(newentry.feed_tagline)) - if newentry.uri is not None: - self.entry_title_label.set_markup( - u'%s' % \ - gobject.markup_escape_text(newentry.title)) - else: - self.entry_title_label.set_markup( - gobject.markup_escape_text(newentry.title)) + if newentry.uri is not None: + self.entry_title_label.set_markup( + u'%s' % \ + gobject.markup_escape_text(newentry.title)) + else: + self.entry_title_label.set_markup( + gobject.markup_escape_text(newentry.title)) - self.last_modified_label.set_text(newentry.updated) + self.last_modified_label.set_text(newentry.updated) - # update the counters - self.updateCounter() + # update the counters + self.updateCounter() - self.entry = newentry + self.entry = newentry - def updateCounter(self): - """ - Display number of events on the top of window, sometimes it needs to be - changed - """ - count = len(self.__class__.entries) - if count>0: - self.new_entry_label.set_text(i18n.ngettext( - 'You have received new entries (and %d not displayed):', - 'You have received new entries (and %d not displayed):', count, - count, count)) - self.next_button.set_sensitive(True) - else: - self.new_entry_label.set_text(_('You have received new entry:')) - self.next_button.set_sensitive(False) + def updateCounter(self): + """ + Display number of events on the top of window, sometimes it needs to be + changed + """ + count = len(self.__class__.entries) + if count>0: + self.new_entry_label.set_text(i18n.ngettext( + 'You have received new entries (and %d not displayed):', + 'You have received new entries (and %d not displayed):', count, + count, count)) + self.next_button.set_sensitive(True) + else: + self.new_entry_label.set_text(_('You have received new entry:')) + self.next_button.set_sensitive(False) - def on_close_button_clicked(self, widget): - self.window.destroy() - self.windowClosed() + def on_close_button_clicked(self, widget): + self.window.destroy() + self.windowClosed() - def on_next_button_clicked(self, widget): - self.displayNextEntry() + def on_next_button_clicked(self, widget): + self.displayNextEntry() - def on_entry_title_button_press_event(self, widget, event): - #FIXME: make it using special gtk2.10 widget - if event.button == 1: # left click - uri = self.entry.uri - if uri is not None: - helpers.launch_browser_mailer('url', uri) - return True + def on_entry_title_button_press_event(self, widget, event): + #FIXME: make it using special gtk2.10 widget + if event.button == 1: # left click + uri = self.entry.uri + if uri is not None: + helpers.launch_browser_mailer('url', uri) + return True - def on_feed_title_button_press_event(self, widget, event): - #FIXME: make it using special gtk2.10 widget - if event.button == 1: # left click - uri = self.entry.feed_uri - if uri is not None: - helpers.launch_browser_mailer('url', uri) - return True - -# vim: se ts=3: + def on_feed_title_button_press_event(self, widget, event): + #FIXME: make it using special gtk2.10 widget + if event.button == 1: # left click + uri = self.entry.feed_uri + if uri is not None: + helpers.launch_browser_mailer('url', uri) + return True diff --git a/src/cell_renderer_image.py b/src/cell_renderer_image.py index e1cdf8c81..0b85e8c44 100644 --- a/src/cell_renderer_image.py +++ b/src/cell_renderer_image.py @@ -27,113 +27,111 @@ import gobject class CellRendererImage(gtk.GenericCellRenderer): - __gproperties__ = { - 'image': (gobject.TYPE_OBJECT, 'Image', - 'Image', gobject.PARAM_READWRITE), - } + __gproperties__ = { + 'image': (gobject.TYPE_OBJECT, 'Image', + 'Image', gobject.PARAM_READWRITE), + } - def __init__(self, col_index, tv_index): - self.__gobject_init__() - self.image = None - self.col_index = col_index - self.tv_index = tv_index - self.iters = {} + def __init__(self, col_index, tv_index): + self.__gobject_init__() + self.image = None + self.col_index = col_index + self.tv_index = tv_index + self.iters = {} - def do_set_property(self, pspec, value): - setattr(self, pspec.name, value) + def do_set_property(self, pspec, value): + setattr(self, pspec.name, value) - def do_get_property(self, pspec): - return getattr(self, pspec.name) + def do_get_property(self, pspec): + return getattr(self, pspec.name) - def func(self, model, path, iter_, image_tree): - image, tree = image_tree - if model.get_value(iter_, self.tv_index) != image: - return - self.redraw = 1 - col = tree.get_column(self.col_index) - cell_area = tree.get_cell_area(path, col) + def func(self, model, path, iter_, image_tree): + image, tree = image_tree + if model.get_value(iter_, self.tv_index) != image: + return + self.redraw = 1 + col = tree.get_column(self.col_index) + cell_area = tree.get_cell_area(path, col) - tree.queue_draw_area(cell_area.x, cell_area.y, - cell_area.width, cell_area.height) + tree.queue_draw_area(cell_area.x, cell_area.y, + cell_area.width, cell_area.height) - def animation_timeout(self, tree, image): - if image.get_storage_type() != gtk.IMAGE_ANIMATION: - return - self.redraw = 0 - iter_ = self.iters[image] - iter_.advance() - model = tree.get_model() - if model: - model.foreach(self.func, (image, tree)) - if self.redraw: - gobject.timeout_add(iter_.get_delay_time(), - self.animation_timeout, tree, image) - elif image in self.iters: - del self.iters[image] + def animation_timeout(self, tree, image): + if image.get_storage_type() != gtk.IMAGE_ANIMATION: + return + self.redraw = 0 + iter_ = self.iters[image] + iter_.advance() + model = tree.get_model() + if model: + model.foreach(self.func, (image, tree)) + if self.redraw: + gobject.timeout_add(iter_.get_delay_time(), + self.animation_timeout, tree, image) + elif image in self.iters: + del self.iters[image] - def on_render(self, window, widget, background_area, cell_area, - expose_area, flags): - if not self.image: - return - pix_rect = gtk.gdk.Rectangle() - pix_rect.x, pix_rect.y, pix_rect.width, pix_rect.height = \ - self.on_get_size(widget, cell_area) + def on_render(self, window, widget, background_area, cell_area, + expose_area, flags): + if not self.image: + return + pix_rect = gtk.gdk.Rectangle() + pix_rect.x, pix_rect.y, pix_rect.width, pix_rect.height = \ + self.on_get_size(widget, cell_area) - pix_rect.x += cell_area.x - pix_rect.y += cell_area.y - pix_rect.width -= 2 * self.get_property('xpad') - pix_rect.height -= 2 * self.get_property('ypad') + pix_rect.x += cell_area.x + pix_rect.y += cell_area.y + pix_rect.width -= 2 * self.get_property('xpad') + pix_rect.height -= 2 * self.get_property('ypad') - draw_rect = cell_area.intersect(pix_rect) - draw_rect = expose_area.intersect(draw_rect) + draw_rect = cell_area.intersect(pix_rect) + draw_rect = expose_area.intersect(draw_rect) - if self.image.get_storage_type() == gtk.IMAGE_ANIMATION: - if self.image not in self.iters: - if not isinstance(widget, gtk.TreeView): - return - animation = self.image.get_animation() - iter_ = animation.get_iter() - self.iters[self.image] = iter_ - gobject.timeout_add(iter_.get_delay_time(), - self.animation_timeout, widget, self.image) + if self.image.get_storage_type() == gtk.IMAGE_ANIMATION: + if self.image not in self.iters: + if not isinstance(widget, gtk.TreeView): + return + animation = self.image.get_animation() + iter_ = animation.get_iter() + self.iters[self.image] = iter_ + gobject.timeout_add(iter_.get_delay_time(), + self.animation_timeout, widget, self.image) - pix = self.iters[self.image].get_pixbuf() - elif self.image.get_storage_type() == gtk.IMAGE_PIXBUF: - pix = self.image.get_pixbuf() - else: - return - if draw_rect.x < 1: - return - window.draw_pixbuf(widget.style.black_gc, pix, - draw_rect.x - pix_rect.x, - draw_rect.y - pix_rect.y, - draw_rect.x, draw_rect.y, - draw_rect.width, draw_rect.height, - gtk.gdk.RGB_DITHER_NONE, 0, 0) + pix = self.iters[self.image].get_pixbuf() + elif self.image.get_storage_type() == gtk.IMAGE_PIXBUF: + pix = self.image.get_pixbuf() + else: + return + if draw_rect.x < 1: + return + window.draw_pixbuf(widget.style.black_gc, pix, + draw_rect.x - pix_rect.x, + draw_rect.y - pix_rect.y, + draw_rect.x, draw_rect.y, + draw_rect.width, draw_rect.height, + gtk.gdk.RGB_DITHER_NONE, 0, 0) - def on_get_size(self, widget, cell_area): - if not self.image: - return 0, 0, 0, 0 - if self.image.get_storage_type() == gtk.IMAGE_ANIMATION: - animation = self.image.get_animation() - pix = animation.get_iter().get_pixbuf() - elif self.image.get_storage_type() == gtk.IMAGE_PIXBUF: - pix = self.image.get_pixbuf() - else: - return 0, 0, 0, 0 - pixbuf_width = pix.get_width() - pixbuf_height = pix.get_height() - calc_width = self.get_property('xpad') * 2 + pixbuf_width - calc_height = self.get_property('ypad') * 2 + pixbuf_height - x_offset = 0 - y_offset = 0 - if cell_area and pixbuf_width > 0 and pixbuf_height > 0: - x_offset = self.get_property('xalign') * \ - (cell_area.width - calc_width - \ - self.get_property('xpad')) - y_offset = self.get_property('yalign') * \ - (cell_area.height - calc_height - \ - self.get_property('ypad')) - return x_offset, y_offset, calc_width, calc_height - -# vim: se ts=3: + def on_get_size(self, widget, cell_area): + if not self.image: + return 0, 0, 0, 0 + if self.image.get_storage_type() == gtk.IMAGE_ANIMATION: + animation = self.image.get_animation() + pix = animation.get_iter().get_pixbuf() + elif self.image.get_storage_type() == gtk.IMAGE_PIXBUF: + pix = self.image.get_pixbuf() + else: + return 0, 0, 0, 0 + pixbuf_width = pix.get_width() + pixbuf_height = pix.get_height() + calc_width = self.get_property('xpad') * 2 + pixbuf_width + calc_height = self.get_property('ypad') * 2 + pixbuf_height + x_offset = 0 + y_offset = 0 + if cell_area and pixbuf_width > 0 and pixbuf_height > 0: + x_offset = self.get_property('xalign') * \ + (cell_area.width - calc_width - \ + self.get_property('xpad')) + y_offset = self.get_property('yalign') * \ + (cell_area.height - calc_height - \ + self.get_property('ypad')) + return x_offset, y_offset, calc_width, calc_height diff --git a/src/chat_control.py b/src/chat_control.py index 45181cec3..26b2241ce 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -62,10 +62,10 @@ from command_system.implementation.hosts import ChatCommands import command_system.implementation.standard try: - import gtkspell - HAS_GTK_SPELL = True + import gtkspell + HAS_GTK_SPELL = True except ImportError: - HAS_GTK_SPELL = False + HAS_GTK_SPELL = False # the next script, executed in the "po" directory, # generates the following list. @@ -75,2806 +75,2804 @@ except ImportError: langs = {_('English'): 'en', _('Belarusian'): 'be', _('Bulgarian'): 'bg', _('Breton'): 'br', _('Czech'): 'cs', _('German'): 'de', _('Greek'): 'el', _('British'): 'en_GB', _('Esperanto'): 'eo', _('Spanish'): 'es', _('Basque'): 'eu', _('French'): 'fr', _('Croatian'): 'hr', _('Italian'): 'it', _('Norwegian (b)'): 'nb', _('Dutch'): 'nl', _('Norwegian'): 'no', _('Polish'): 'pl', _('Portuguese'): 'pt', _('Brazilian Portuguese'): 'pt_BR', _('Russian'): 'ru', _('Serbian'): 'sr', _('Slovak'): 'sk', _('Swedish'): 'sv', _('Chinese (Ch)'): 'zh_CN'} if gajim.config.get('use_speller') and HAS_GTK_SPELL: - # loop removing non-existent dictionaries - # iterating on a copy - tv = gtk.TextView() - spell = gtkspell.Spell(tv) - for lang in dict(langs): - try: - spell.set_language(langs[lang]) - except OSError: - del langs[lang] - if spell: - spell.detach() - del tv + # loop removing non-existent dictionaries + # iterating on a copy + tv = gtk.TextView() + spell = gtkspell.Spell(tv) + for lang in dict(langs): + try: + spell.set_language(langs[lang]) + except OSError: + del langs[lang] + if spell: + spell.detach() + del tv ################################################################################ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): - """ - A base class containing a banner, ConversationTextview, MessageTextView - """ - - def make_href(self, match): - url_color = gajim.config.get('urlmsgcolor') - url = match.group() - if not '://' in url: - url = 'http://' + url - return '%s' % (url, - url_color, match.group()) - - def get_font_attrs(self): - """ - Get pango font attributes for banner from theme settings - """ - theme = gajim.config.get('roster_theme') - bannerfont = gajim.config.get_per('themes', theme, 'bannerfont') - bannerfontattrs = gajim.config.get_per('themes', theme, 'bannerfontattrs') - - if bannerfont: - font = pango.FontDescription(bannerfont) - else: - font = pango.FontDescription('Normal') - if bannerfontattrs: - # B attribute is set by default - if 'B' in bannerfontattrs: - font.set_weight(pango.WEIGHT_HEAVY) - if 'I' in bannerfontattrs: - font.set_style(pango.STYLE_ITALIC) - - font_attrs = 'font_desc="%s"' % font.to_string() - - # in case there is no font specified we use x-large font size - if font.get_size() == 0: - font_attrs = '%s size="x-large"' % font_attrs - font.set_weight(pango.WEIGHT_NORMAL) - font_attrs_small = 'font_desc="%s" size="small"' % font.to_string() - return (font_attrs, font_attrs_small) - - def get_nb_unread(self): - jid = self.contact.jid - if self.resource: - jid += '/' + self.resource - type_ = self.type_id - return len(gajim.events.get_events(self.account, jid, ['printed_' + type_, - type_])) - - def draw_banner(self): - """ - Draw the fat line at the top of the window that houses the icon, jid, etc - - Derived types MAY implement this. - """ - self.draw_banner_text() - self._update_banner_state_image() - gajim.plugin_manager.gui_extension_point('chat_control_base_draw_banner', - self) - - def draw_banner_text(self): - """ - Derived types SHOULD implement this - """ - pass - - def update_ui(self): - """ - Derived types SHOULD implement this - """ - self.draw_banner() - - def repaint_themed_widgets(self): - """ - Derived types MAY implement this - """ - self._paint_banner() - self.draw_banner() - - def _update_banner_state_image(self): - """ - Derived types MAY implement this - """ - pass - - def handle_message_textview_mykey_press(self, widget, event_keyval, - event_keymod): - """ - Derives types SHOULD implement this, rather than connection to the even - itself - """ - event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) - event.keyval = event_keyval - event.state = event_keymod - event.time = 0 - - _buffer = widget.get_buffer() - start, end = _buffer.get_bounds() - - if event.keyval -- gtk.keysyms.Tab: - position = _buffer.get_insert() - end = _buffer.get_iter_at_mark(position) - - text = _buffer.get_text(start, end, False) - text = text.decode('utf8') - - splitted = text.split() - - if (text.startswith(self.COMMAND_PREFIX) and not - text.startswith(self.COMMAND_PREFIX * 2) and len(splitted) == 1): - - text = splitted[0] - bare = text.lstrip(self.COMMAND_PREFIX) - - if len(text) == 1: - self.command_hits = [] - for command in self.list_commands(): - for name in command.names: - self.command_hits.append(name) - else: - if (self.last_key_tabs and self.command_hits and - self.command_hits[0].startswith(bare)): - self.command_hits.append(self.command_hits.pop(0)) - else: - self.command_hits = [] - for command in self.list_commands(): - for name in command.names: - if name.startswith(bare): - self.command_hits.append(name) - - if self.command_hits: - _buffer.delete(start, end) - _buffer.insert_at_cursor(self.COMMAND_PREFIX + self.command_hits[0] + ' ') - self.last_key_tabs = True - - return True - - self.last_key_tabs = False - - def status_url_clicked(self, widget, url): - helpers.launch_browser_mailer('url', url) - - def __init__(self, type_id, parent_win, widget_name, contact, acct, - resource=None): - # Undo needs this variable to know if space has been pressed. - # Initialize it to True so empty textview is saved in undo list - self.space_pressed = True - - if resource is None: - # We very likely got a contact with a random resource. - # This is bad, we need the highest for caps etc. - c = gajim.contacts.get_contact_with_highest_priority( - acct, contact.jid) - if c and not isinstance(c, GC_Contact): - contact = c - - MessageControl.__init__(self, type_id, parent_win, widget_name, - contact, acct, resource=resource) - - widget = self.xml.get_object('history_button') - id_ = widget.connect('clicked', self._on_history_menuitem_activate) - self.handlers[id_] = widget - - # when/if we do XHTML we will put formatting buttons back - widget = self.xml.get_object('emoticons_button') - id_ = widget.connect('clicked', self.on_emoticons_button_clicked) - self.handlers[id_] = widget - - # Create banner and connect signals - widget = self.xml.get_object('banner_eventbox') - id_ = widget.connect('button-press-event', - self._on_banner_eventbox_button_press_event) - self.handlers[id_] = widget - - self.urlfinder = re.compile( - r"(www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'\"]+[^!,\.\s<>\)'\"\]]") - - self.banner_status_label = self.xml.get_object('banner_label') - id_ = self.banner_status_label.connect('populate_popup', - self.on_banner_label_populate_popup) - self.handlers[id_] = self.banner_status_label - - # Init DND - self.TARGET_TYPE_URI_LIST = 80 - self.dnd_list = [ ( 'text/uri-list', 0, self.TARGET_TYPE_URI_LIST ), - ('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_APP, 0)] - id_ = self.widget.connect('drag_data_received', - self._on_drag_data_received) - self.handlers[id_] = self.widget - self.widget.drag_dest_set(gtk.DEST_DEFAULT_MOTION | - gtk.DEST_DEFAULT_HIGHLIGHT | - gtk.DEST_DEFAULT_DROP, - self.dnd_list, gtk.gdk.ACTION_COPY) - - # Create textviews and connect signals - self.conv_textview = ConversationTextview(self.account) - id_ = self.conv_textview.connect('quote', self.on_quote) - self.handlers[id_] = self.conv_textview.tv - id_ = self.conv_textview.tv.connect('key_press_event', - self._conv_textview_key_press_event) - self.handlers[id_] = self.conv_textview.tv - # FIXME: DND on non editable TextView, find a better way - self.drag_entered = False - id_ = self.conv_textview.tv.connect('drag_data_received', - self._on_drag_data_received) - self.handlers[id_] = self.conv_textview.tv - id_ = self.conv_textview.tv.connect('drag_motion', self._on_drag_motion) - self.handlers[id_] = self.conv_textview.tv - id_ = self.conv_textview.tv.connect('drag_leave', self._on_drag_leave) - self.handlers[id_] = self.conv_textview.tv - self.conv_textview.tv.drag_dest_set(gtk.DEST_DEFAULT_MOTION | - gtk.DEST_DEFAULT_HIGHLIGHT | - gtk.DEST_DEFAULT_DROP, - self.dnd_list, gtk.gdk.ACTION_COPY) - - self.conv_scrolledwindow = self.xml.get_object( - 'conversation_scrolledwindow') - self.conv_scrolledwindow.add(self.conv_textview.tv) - widget = self.conv_scrolledwindow.get_vadjustment() - id_ = widget.connect('value-changed', - self.on_conversation_vadjustment_value_changed) - self.handlers[id_] = widget - id_ = widget.connect('changed', - self.on_conversation_vadjustment_changed) - self.handlers[id_] = widget - self.scroll_to_end_id = None - self.was_at_the_end = True - - # add MessageTextView to UI and connect signals - self.msg_scrolledwindow = self.xml.get_object('message_scrolledwindow') - self.msg_textview = MessageTextView() - id_ = self.msg_textview.connect('mykeypress', - self._on_message_textview_mykeypress_event) - self.handlers[id_] = self.msg_textview - self.msg_scrolledwindow.add(self.msg_textview) - id_ = self.msg_textview.connect('key_press_event', - self._on_message_textview_key_press_event) - self.handlers[id_] = self.msg_textview - id_ = self.msg_textview.connect('size-request', self.size_request) - self.handlers[id_] = self.msg_textview - id_ = self.msg_textview.connect('populate_popup', - self.on_msg_textview_populate_popup) - self.handlers[id_] = self.msg_textview - # Setup DND - id_ = self.msg_textview.connect('drag_data_received', - self._on_drag_data_received) - self.handlers[id_] = self.msg_textview - self.msg_textview.drag_dest_set(gtk.DEST_DEFAULT_MOTION | - gtk.DEST_DEFAULT_HIGHLIGHT, - self.dnd_list, gtk.gdk.ACTION_COPY) - - self.update_font() - - # Hook up send button - widget = self.xml.get_object('send_button') - id_ = widget.connect('clicked', self._on_send_button_clicked) - self.handlers[id_] = widget - - widget = self.xml.get_object('formattings_button') - id_ = widget.connect('clicked', self.on_formattings_button_clicked) - self.handlers[id_] = widget - - # the following vars are used to keep history of user's messages - self.sent_history = [] - self.sent_history_pos = 0 - self.orig_msg = None - - # Emoticons menu - # set image no matter if user wants at this time emoticons or not - # (so toggle works ok) - img = self.xml.get_object('emoticons_button_image') - img.set_from_file(os.path.join(gajim.DATA_DIR, 'emoticons', 'static', - 'smile.png')) - self.toggle_emoticons() - - # Attach speller - if gajim.config.get('use_speller') and HAS_GTK_SPELL: - self.set_speller() - self.conv_textview.tv.show() - self._paint_banner() - - # For XEP-0172 - self.user_nick = None - - self.smooth = True - self.msg_textview.grab_focus() - - self.command_hits = [] - self.last_key_tabs = False - - # PluginSystem: adding GUI extension point for ChatControlBase - # instance object (also subclasses, eg. ChatControl or GroupchatControl) - gajim.plugin_manager.gui_extension_point('chat_control_base', self) - - - def set_speller(self): - # now set the one the user selected - per_type = 'contacts' - if self.type_id == message_control.TYPE_GC: - per_type = 'rooms' - lang = gajim.config.get_per(per_type, self.contact.jid, - 'speller_language') - if not lang: - # use the default one - lang = gajim.config.get('speller_language') - if not lang: - lang = gajim.LANG - if lang: - try: - gtkspell.Spell(self.msg_textview, lang) - self.msg_textview.lang = lang - except (gobject.GError, RuntimeError, TypeError, OSError): - dialogs.AspellDictError(lang) - - def on_banner_label_populate_popup(self, label, menu): - """ - Override the default context menu and add our own menutiems - """ - item = gtk.SeparatorMenuItem() - menu.prepend(item) - - menu2 = self.prepare_context_menu() - i = 0 - for item in menu2: - menu2.remove(item) - menu.prepend(item) - menu.reorder_child(item, i) - i += 1 - menu.show_all() - - - def shutdown(self): - # PluginSystem: removing GUI extension points connected with ChatControlBase - # instance object - gajim.plugin_manager.remove_gui_extension_point('chat_control_base', self) - gajim.plugin_manager.remove_gui_extension_point('chat_control_base_draw_banner', self) - - def on_msg_textview_populate_popup(self, textview, menu): - """ - Override the default context menu and we prepend an option to switch - languages - """ - def _on_select_dictionary(widget, lang): - per_type = 'contacts' - if self.type_id == message_control.TYPE_GC: - per_type = 'rooms' - if not gajim.config.get_per(per_type, self.contact.jid): - gajim.config.add_per(per_type, self.contact.jid) - gajim.config.set_per(per_type, self.contact.jid, 'speller_language', - lang) - spell = gtkspell.get_from_text_view(self.msg_textview) - self.msg_textview.lang = lang - spell.set_language(lang) - widget.set_active(True) - - item = gtk.ImageMenuItem(gtk.STOCK_UNDO) - menu.prepend(item) - id_ = item.connect('activate', self.msg_textview.undo) - self.handlers[id_] = item - - item = gtk.SeparatorMenuItem() - menu.prepend(item) - - item = gtk.ImageMenuItem(gtk.STOCK_CLEAR) - menu.prepend(item) - id_ = item.connect('activate', self.msg_textview.clear) - self.handlers[id_] = item - - if gajim.config.get('use_speller') and HAS_GTK_SPELL: - item = gtk.MenuItem(_('Spelling language')) - menu.prepend(item) - submenu = gtk.Menu() - item.set_submenu(submenu) - for lang in sorted(langs): - item = gtk.CheckMenuItem(lang) - if langs[lang] == self.msg_textview.lang: - item.set_active(True) - submenu.append(item) - id_ = item.connect('activate', _on_select_dictionary, langs[lang]) - self.handlers[id_] = item - - menu.show_all() - - def on_quote(self, widget, text): - text = '>' + text.replace('\n', '\n>') + '\n' - message_buffer = self.msg_textview.get_buffer() - message_buffer.insert_at_cursor(text) - - # moved from ChatControl - def _on_banner_eventbox_button_press_event(self, widget, event): - """ - If right-clicked, show popup - """ - if event.button == 3: # right click - self.parent_win.popup_menu(event) - - def _on_send_button_clicked(self, widget): - """ - When send button is pressed: send the current message - """ - if gajim.connections[self.account].connected < 2: # we are not connected - dialogs.ErrorDialog(_('A connection is not available'), - _('Your message can not be sent until you are connected.')) - return - message_buffer = self.msg_textview.get_buffer() - start_iter = message_buffer.get_start_iter() - end_iter = message_buffer.get_end_iter() - message = message_buffer.get_text(start_iter, end_iter, 0).decode('utf-8') - xhtml = self.msg_textview.get_xhtml() - - # send the message - self.send_message(message, xhtml=xhtml) - - def _paint_banner(self): - """ - Repaint banner with theme color - """ - theme = gajim.config.get('roster_theme') - bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor') - textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor') - # the backgrounds are colored by using an eventbox by - # setting the bg color of the eventbox and the fg of the name_label - banner_eventbox = self.xml.get_object('banner_eventbox') - banner_name_label = self.xml.get_object('banner_name_label') - self.disconnect_style_event(banner_name_label) - self.disconnect_style_event(self.banner_status_label) - if bgcolor: - banner_eventbox.modify_bg(gtk.STATE_NORMAL, - gtk.gdk.color_parse(bgcolor)) - default_bg = False - else: - default_bg = True - if textcolor: - banner_name_label.modify_fg(gtk.STATE_NORMAL, - gtk.gdk.color_parse(textcolor)) - self.banner_status_label.modify_fg(gtk.STATE_NORMAL, - gtk.gdk.color_parse(textcolor)) - default_fg = False - else: - default_fg = True - if default_bg or default_fg: - self._on_style_set_event(banner_name_label, None, default_fg, - default_bg) - if self.banner_status_label.flags() & gtk.REALIZED: - # Widget is realized - self._on_style_set_event(self.banner_status_label, None, default_fg, - default_bg) - - def disconnect_style_event(self, widget): - # Try to find the event_id - for id_ in self.handlers.keys(): - if self.handlers[id_] == widget: - widget.disconnect(id_) - del self.handlers[id_] - break - - def connect_style_event(self, widget, set_fg = False, set_bg = False): - self.disconnect_style_event(widget) - id_ = widget.connect('style-set', self._on_style_set_event, set_fg, - set_bg) - self.handlers[id_] = widget - - def _on_style_set_event(self, widget, style, *opts): - """ - Set style of widget from style class *.Frame.Eventbox - opts[0] == True -> set fg color - opts[1] == True -> set bg color - """ - banner_eventbox = self.xml.get_object('banner_eventbox') - self.disconnect_style_event(widget) - if opts[1]: - bg_color = widget.style.bg[gtk.STATE_SELECTED] - banner_eventbox.modify_bg(gtk.STATE_NORMAL, bg_color) - if opts[0]: - fg_color = widget.style.fg[gtk.STATE_SELECTED] - widget.modify_fg(gtk.STATE_NORMAL, fg_color) - self.connect_style_event(widget, opts[0], opts[1]) - - def _conv_textview_key_press_event(self, widget, event): - if (event.state & gtk.gdk.CONTROL_MASK and event.keyval in (gtk.keysyms.c, - gtk.keysyms.Insert)) or (event.state & gtk.gdk.SHIFT_MASK and \ - event.keyval in (gtk.keysyms.Page_Down, gtk.keysyms.Page_Up)): - return False - self.parent_win.notebook.emit('key_press_event', event) - return True - - def show_emoticons_menu(self): - if not gajim.config.get('emoticons_theme'): - return - def set_emoticons_menu_position(w, msg_tv = self.msg_textview): - window = msg_tv.get_window(gtk.TEXT_WINDOW_WIDGET) - # get the window position - origin = window.get_origin() - size = window.get_size() - buf = msg_tv.get_buffer() - # get the cursor position - cursor = msg_tv.get_iter_location(buf.get_iter_at_mark( - buf.get_insert())) - cursor = msg_tv.buffer_to_window_coords(gtk.TEXT_WINDOW_TEXT, - cursor.x, cursor.y) - x = origin[0] + cursor[0] - y = origin[1] + size[1] - menu_height = gajim.interface.emoticons_menu.size_request()[1] - #FIXME: get_line_count is not so good - #get the iter of cursor, then tv.get_line_yrange - # so we know in which y we are typing (not how many lines we have - # then go show just above the current cursor line for up - # or just below the current cursor line for down - #TEST with having 3 lines and writing in the 2nd - if y + menu_height > gtk.gdk.screen_height(): - # move menu just above cursor - y -= menu_height + (msg_tv.allocation.height / buf.get_line_count()) - #else: # move menu just below cursor - # y -= (msg_tv.allocation.height / buf.get_line_count()) - return (x, y, True) # push_in True - gajim.interface.emoticon_menuitem_clicked = self.append_emoticon - gajim.interface.emoticons_menu.popup(None, None, - set_emoticons_menu_position, 1, 0) - - def _on_message_textview_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.space: - self.space_pressed = True - - elif (self.space_pressed or self.msg_textview.undo_pressed) and \ - event.keyval not in (gtk.keysyms.Control_L, gtk.keysyms.Control_R) and \ - not (event.keyval == gtk.keysyms.z and event.state & gtk.gdk.CONTROL_MASK): - # If the space key has been pressed and now it hasnt, - # we save the buffer into the undo list. But be carefull we're not - # pressiong Control again (as in ctrl+z) - _buffer = widget.get_buffer() - start_iter, end_iter = _buffer.get_bounds() - self.msg_textview.save_undo(_buffer.get_text(start_iter, end_iter)) - self.space_pressed = False - - # Ctrl [+ Shift] + Tab are not forwarded to notebook. We handle it here - if self.widget_name == 'groupchat_control': - if event.keyval not in (gtk.keysyms.ISO_Left_Tab, gtk.keysyms.Tab): - self.last_key_tabs = False - if event.state & gtk.gdk.SHIFT_MASK: - # CTRL + SHIFT + TAB - if event.state & gtk.gdk.CONTROL_MASK and \ - event.keyval == gtk.keysyms.ISO_Left_Tab: - self.parent_win.move_to_next_unread_tab(False) - return True - # SHIFT + PAGE_[UP|DOWN]: send to conv_textview - elif event.keyval == gtk.keysyms.Page_Down or \ - event.keyval == gtk.keysyms.Page_Up: - self.conv_textview.tv.emit('key_press_event', event) - return True - elif event.state & gtk.gdk.CONTROL_MASK: - if event.keyval == gtk.keysyms.Tab: # CTRL + TAB - self.parent_win.move_to_next_unread_tab(True) - return True - return False - - def _on_message_textview_mykeypress_event(self, widget, event_keyval, - event_keymod): - """ - When a key is pressed: if enter is pressed without the shift key, message - (if not empty) is sent and printed in the conversation - """ - # NOTE: handles mykeypress which is custom signal connected to this - # CB in new_tab(). for this singal see message_textview.py - message_textview = widget - message_buffer = message_textview.get_buffer() - start_iter, end_iter = message_buffer.get_bounds() - message = message_buffer.get_text(start_iter, end_iter, False).decode( - 'utf-8') - xhtml = self.msg_textview.get_xhtml() - - # construct event instance from binding - event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here - event.keyval = event_keyval - event.state = event_keymod - event.time = 0 # assign current time - - if event.keyval == gtk.keysyms.Up: - if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+UP - self.sent_messages_scroll('up', widget.get_buffer()) - elif event.keyval == gtk.keysyms.Down: - if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+Down - self.sent_messages_scroll('down', widget.get_buffer()) - elif event.keyval == gtk.keysyms.Return or \ - event.keyval == gtk.keysyms.KP_Enter: # ENTER - # NOTE: SHIFT + ENTER is not needed to be emulated as it is not - # binding at all (textview's default action is newline) - - if gajim.config.get('send_on_ctrl_enter'): - # here, we emulate GTK default action on ENTER (add new line) - # normally I would add in keypress but it gets way to complex - # to get instant result on changing this advanced setting - if event.state == 0: # no ctrl, no shift just ENTER add newline - end_iter = message_buffer.get_end_iter() - message_buffer.insert_at_cursor('\n') - send_message = False - elif event.state & gtk.gdk.CONTROL_MASK: # CTRL + ENTER - send_message = True - else: # send on Enter, do newline on Ctrl Enter - if event.state & gtk.gdk.CONTROL_MASK: # Ctrl + ENTER - end_iter = message_buffer.get_end_iter() - message_buffer.insert_at_cursor('\n') - send_message = False - else: # ENTER - send_message = True - - if gajim.connections[self.account].connected < 2 and send_message: - # we are not connected - dialogs.ErrorDialog(_('A connection is not available'), - _('Your message can not be sent until you are connected.')) - send_message = False - - if send_message: - self.send_message(message, xhtml=xhtml) # send the message - elif event.keyval == gtk.keysyms.z: # CTRL+z - if event.state & gtk.gdk.CONTROL_MASK: - self.msg_textview.undo() - else: - # Give the control itself a chance to process - self.handle_message_textview_mykey_press(widget, event_keyval, - event_keymod) - - def _on_drag_data_received(self, widget, context, x, y, selection, - target_type, timestamp): - """ - Derived types SHOULD implement this - """ - pass - - def _on_drag_leave(self, widget, context, time): - # FIXME: DND on non editable TextView, find a better way - self.drag_entered = False - self.conv_textview.tv.set_editable(False) - - def _on_drag_motion(self, widget, context, x, y, time): - # FIXME: DND on non editable TextView, find a better way - if not self.drag_entered: - # We drag new data over the TextView, make it editable to catch dnd - self.drag_entered_conv = True - self.conv_textview.tv.set_editable(True) - - def send_message(self, message, keyID='', type_='chat', chatstate=None, - msg_id=None, composing_xep=None, resource=None, xhtml=None, - callback=None, callback_args=[], process_commands=True): - """ - Send the given message to the active tab. Doesn't return None if error - """ - if not message or message == '\n': - return None - - if process_commands and self.process_as_command(message): - return - - MessageControl.send_message(self, message, keyID, type_=type_, - chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep, - resource=resource, user_nick=self.user_nick, xhtml=xhtml, - callback=callback, callback_args=callback_args) - - # Record message history - self.save_sent_message(message) - - # Be sure to send user nickname only once according to JEP-0172 - self.user_nick = None - - # Clear msg input - message_buffer = self.msg_textview.get_buffer() - message_buffer.set_text('') # clear message buffer (and tv of course) - - def save_sent_message(self, message): - # save the message, so user can scroll though the list with key up/down - size = len(self.sent_history) - # we don't want size of the buffer to grow indefinately - max_size = gajim.config.get('key_up_lines') - if size >= max_size: - for i in xrange(0, size - 1): - self.sent_history[i] = self.sent_history[i + 1] - self.sent_history[max_size - 1] = message - # self.sent_history_pos has changed if we browsed sent_history, - # reset to real value - self.sent_history_pos = max_size - else: - self.sent_history.append(message) - self.sent_history_pos = size + 1 - self.orig_msg = None - - def print_conversation_line(self, text, kind, name, tim, - other_tags_for_name=[], other_tags_for_time=[], - other_tags_for_text=[], count_as_new=True, subject=None, - old_kind=None, xhtml=None, simple=False, xep0184_id=None, - graphics=True): - """ - Print 'chat' type messages - """ - jid = self.contact.jid - full_jid = self.get_full_jid() - textview = self.conv_textview - end = False - if self.was_at_the_end or kind == 'outgoing': - end = True - textview.print_conversation_line(text, jid, kind, name, tim, - other_tags_for_name, other_tags_for_time, other_tags_for_text, - subject, old_kind, xhtml, simple=simple, graphics=graphics) - - if xep0184_id is not None: - textview.show_xep0184_warning(xep0184_id) - - if not count_as_new: - return - if kind == 'incoming': - if not self.type_id == message_control.TYPE_GC or \ - gajim.config.get('notify_on_all_muc_messages') or \ - 'marked' in other_tags_for_text: - # it's a normal message, or a muc message with want to be - # notified about if quitting just after - # other_tags_for_text == ['marked'] --> highlighted gc message - gajim.last_message_time[self.account][full_jid] = time.time() - - if kind in ('incoming', 'incoming_queue', 'error'): - gc_message = False - if self.type_id == message_control.TYPE_GC: - gc_message = True - - if ((self.parent_win and (not self.parent_win.get_active_control() or \ - self != self.parent_win.get_active_control() or \ - not self.parent_win.is_active() or not end)) or \ - (gc_message and \ - jid in gajim.interface.minimized_controls[self.account])) and \ - kind in ('incoming', 'incoming_queue', 'error'): - # we want to have save this message in events list - # other_tags_for_text == ['marked'] --> highlighted gc message - if gc_message: - if 'marked' in other_tags_for_text: - type_ = 'printed_marked_gc_msg' - else: - type_ = 'printed_gc_msg' - event = 'gc_message_received' - else: - type_ = 'printed_' + self.type_id - event = 'message_received' - show_in_roster = notify.get_show_in_roster(event, - self.account, self.contact, self.session) - show_in_systray = notify.get_show_in_systray(event, - self.account, self.contact, type_) - - event = gajim.events.create_event(type_, (self,), - show_in_roster = show_in_roster, - show_in_systray = show_in_systray) - gajim.events.add_event(self.account, full_jid, event) - # We need to redraw contact if we show in roster - if show_in_roster: - gajim.interface.roster.draw_contact(self.contact.jid, - self.account) - - if not self.parent_win: - return - - if (not self.parent_win.get_active_control() or \ - self != self.parent_win.get_active_control() or \ - not self.parent_win.is_active() or not end) and \ - kind in ('incoming', 'incoming_queue', 'error'): - self.parent_win.redraw_tab(self) - if not self.parent_win.is_active(): - self.parent_win.show_title(True, self) # Enabled Urgent hint - else: - self.parent_win.show_title(False, self) # Disabled Urgent hint - - def toggle_emoticons(self): - """ - Hide show emoticons_button and make sure emoticons_menu is always there - when needed - """ - emoticons_button = self.xml.get_object('emoticons_button') - if gajim.config.get('emoticons_theme'): - emoticons_button.show() - emoticons_button.set_no_show_all(False) - else: - emoticons_button.hide() - emoticons_button.set_no_show_all(True) - - def append_emoticon(self, str_): - buffer_ = self.msg_textview.get_buffer() - if buffer_.get_char_count(): - buffer_.insert_at_cursor(' %s ' % str_) - else: # we are the beginning of buffer - buffer_.insert_at_cursor('%s ' % str_) - self.msg_textview.grab_focus() - - def on_emoticons_button_clicked(self, widget): - """ - Popup emoticons menu - """ - gajim.interface.emoticon_menuitem_clicked = self.append_emoticon - gajim.interface.popup_emoticons_under_button(widget, self.parent_win) - - def on_formattings_button_clicked(self, widget): - """ - Popup formattings menu - """ - menu = gtk.Menu() - - menuitems = ((_('Bold'), 'bold'), - (_('Italic'), 'italic'), - (_('Underline'), 'underline'), - (_('Strike'), 'strike')) - - active_tags = self.msg_textview.get_active_tags() - - for menuitem in menuitems: - item = gtk.CheckMenuItem(menuitem[0]) - if menuitem[1] in active_tags: - item.set_active(True) - else: - item.set_active(False) - item.connect('activate', self.msg_textview.set_tag, - menuitem[1]) - menu.append(item) - - item = gtk.SeparatorMenuItem() # separator - menu.append(item) - - item = gtk.ImageMenuItem(_('Color')) - icon = gtk.image_new_from_stock(gtk.STOCK_SELECT_COLOR, gtk.ICON_SIZE_MENU) - item.set_image(icon) - item.connect('activate', self.on_color_menuitem_activale) - menu.append(item) - - item = gtk.ImageMenuItem(_('Font')) - icon = gtk.image_new_from_stock(gtk.STOCK_SELECT_FONT, gtk.ICON_SIZE_MENU) - item.set_image(icon) - item.connect('activate', self.on_font_menuitem_activale) - menu.append(item) - - item = gtk.SeparatorMenuItem() # separator - menu.append(item) - - item = gtk.ImageMenuItem(_('Clear formating')) - icon = gtk.image_new_from_stock(gtk.STOCK_CLEAR, gtk.ICON_SIZE_MENU) - item.set_image(icon) - item.connect('activate', self.msg_textview.clear_tags) - menu.append(item) - - menu.show_all() - gtkgui_helpers.popup_emoticons_under_button(menu, widget, - self.parent_win) - - def on_color_menuitem_activale(self, widget): - color_dialog = gtk.ColorSelectionDialog('Select a color') - color_dialog.connect('response', self.msg_textview.color_set, - color_dialog.colorsel) - color_dialog.show_all() - - def on_font_menuitem_activale(self, widget): - font_dialog = gtk.FontSelectionDialog('Select a font') - font_dialog.connect('response', self.msg_textview.font_set, - font_dialog.fontsel) - font_dialog.show_all() - - - def on_actions_button_clicked(self, widget): - """ - Popup action menu - """ - menu = self.prepare_context_menu(hide_buttonbar_items=True) - menu.show_all() - gtkgui_helpers.popup_emoticons_under_button(menu, widget, - self.parent_win) - - def update_font(self): - font = pango.FontDescription(gajim.config.get('conversation_font')) - self.conv_textview.tv.modify_font(font) - self.msg_textview.modify_font(font) - - def update_tags(self): - self.conv_textview.update_tags() - - def clear(self, tv): - buffer_ = tv.get_buffer() - start, end = buffer_.get_bounds() - buffer_.delete(start, end) - - def _on_history_menuitem_activate(self, widget = None, jid = None): - """ - When history menuitem is pressed: call history window - """ - if not jid: - jid = self.contact.jid - - if 'logs' in gajim.interface.instances: - gajim.interface.instances['logs'].window.present() - gajim.interface.instances['logs'].open_history(jid, self.account) - else: - gajim.interface.instances['logs'] = \ - history_window.HistoryWindow(jid, self.account) - - def _on_send_file(self, gc_contact=None): - """ - gc_contact can be set when we are in a groupchat control - """ - def _on_ok(c): - gajim.interface.instances['file_transfers'].show_file_send_request( - self.account, c) - if self.TYPE_ID == message_control.TYPE_PM: - gc_contact = self.gc_contact - if gc_contact: - # gc or pm - gc_control = gajim.interface.msg_win_mgr.get_gc_control( - gc_contact.room_jid, self.account) - self_contact = gajim.contacts.get_gc_contact(self.account, - gc_control.room_jid, gc_control.nick) - if gc_control.is_anonymous and gc_contact.affiliation not in ['admin', - 'owner'] and self_contact.affiliation in ['admin', 'owner']: - contact = gajim.contacts.get_contact(self.account, gc_contact.jid) - if not contact or contact.sub not in ('both', 'to'): - prim_text = _('Really send file?') - sec_text = _('If you send a file to %s, he/she will know your ' - 'real Jabber ID.') % gc_contact.name - dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text, - on_response_ok = (_on_ok, gc_contact)) - dialog.popup() - return - _on_ok(gc_contact) - return - _on_ok(self.contact) - - def on_minimize_menuitem_toggled(self, widget): - """ - When a grouchat is minimized, unparent the tab, put it in roster etc - """ - old_value = False - minimized_gc = gajim.config.get_per('accounts', self.account, - 'minimized_gc').split() - if self.contact.jid in minimized_gc: - old_value = True - minimize = widget.get_active() - if minimize and not self.contact.jid in minimized_gc: - minimized_gc.append(self.contact.jid) - if not minimize and self.contact.jid in minimized_gc: - minimized_gc.remove(self.contact.jid) - if old_value != minimize: - gajim.config.set_per('accounts', self.account, 'minimized_gc', - ' '.join(minimized_gc)) - - def set_control_active(self, state): - if state: - jid = self.contact.jid - if self.was_at_the_end: - # we are at the end - type_ = ['printed_' + self.type_id] - if self.type_id == message_control.TYPE_GC: - type_ = ['printed_gc_msg', 'printed_marked_gc_msg'] - if not gajim.events.remove_events(self.account, self.get_full_jid(), - types = type_): - # There were events to remove - self.redraw_after_event_removed(jid) - - - def bring_scroll_to_end(self, textview, diff_y = 0): - """ - Scroll to the end of textview if end is not visible - """ - if self.scroll_to_end_id: - # a scroll is already planned - return - buffer_ = textview.get_buffer() - end_iter = buffer_.get_end_iter() - end_rect = textview.get_iter_location(end_iter) - visible_rect = textview.get_visible_rect() - # scroll only if expected end is not visible - if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y): - self.scroll_to_end_id = gobject.idle_add(self.scroll_to_end_iter, - textview) - - def scroll_to_end_iter(self, textview): - buffer_ = textview.get_buffer() - end_iter = buffer_.get_end_iter() - textview.scroll_to_iter(end_iter, 0, False, 1, 1) - self.scroll_to_end_id = None - return False - - def size_request(self, msg_textview , requisition): - """ - When message_textview changes its size: if the new height will enlarge - the window, enable the scrollbar automatic policy. Also enable scrollbar - automatic policy for horizontal scrollbar if message we have in - message_textview is too big - """ - if msg_textview.window is None: - return - - min_height = self.conv_scrolledwindow.get_property('height-request') - conversation_height = self.conv_textview.tv.window.get_size()[1] - message_height = msg_textview.window.get_size()[1] - message_width = msg_textview.window.get_size()[0] - # new tab is not exposed yet - if conversation_height < 2: - return - - if conversation_height < min_height: - min_height = conversation_height - - # we don't want to always resize in height the message_textview - # so we have minimum on conversation_textview's scrolled window - # but we also want to avoid window resizing so if we reach that - # minimum for conversation_textview and maximum for message_textview - # we set to automatic the scrollbar policy - diff_y = message_height - requisition.height - if diff_y != 0: - if conversation_height + diff_y < min_height: - if message_height + conversation_height - min_height > min_height: - policy = self.msg_scrolledwindow.get_property( - 'vscrollbar-policy') - # scroll only when scrollbar appear - if policy != gtk.POLICY_AUTOMATIC: - self.msg_scrolledwindow.set_property('vscrollbar-policy', - gtk.POLICY_AUTOMATIC) - self.msg_scrolledwindow.set_property('height-request', - message_height + conversation_height - min_height) - self.bring_scroll_to_end(msg_textview) - else: - self.msg_scrolledwindow.set_property('vscrollbar-policy', - gtk.POLICY_NEVER) - self.msg_scrolledwindow.set_property('height-request', -1) - self.conv_textview.bring_scroll_to_end(diff_y - 18, False) - else: - self.conv_textview.bring_scroll_to_end(diff_y - 18, self.smooth) - self.smooth = True # reinit the flag - # enable scrollbar automatic policy for horizontal scrollbar - # if message we have in message_textview is too big - if requisition.width > message_width: - self.msg_scrolledwindow.set_property('hscrollbar-policy', - gtk.POLICY_AUTOMATIC) - else: - self.msg_scrolledwindow.set_property('hscrollbar-policy', - gtk.POLICY_NEVER) - - return True - - def on_conversation_vadjustment_changed(self, adjustment): - # used to stay at the end of the textview when we shrink conversation - # textview. - if self.was_at_the_end: - self.conv_textview.bring_scroll_to_end(-18) - self.was_at_the_end = (adjustment.upper - adjustment.value - adjustment.page_size) < 18 - - def on_conversation_vadjustment_value_changed(self, adjustment): - # stop automatic scroll when we manually scroll - if not self.conv_textview.auto_scrolling: - self.conv_textview.stop_scrolling() - self.was_at_the_end = (adjustment.upper - adjustment.value - adjustment.page_size) < 18 - if self.resource: - jid = self.contact.get_full_jid() - else: - jid = self.contact.jid - types_list = [] - type_ = self.type_id - if type_ == message_control.TYPE_GC: - type_ = 'gc_msg' - types_list = ['printed_' + type_, type_, 'printed_marked_gc_msg'] - else: # Not a GC - types_list = ['printed_' + type_, type_] - - if not len(gajim.events.get_events(self.account, jid, types_list)): - return - if not self.parent_win: - return - if self.conv_textview.at_the_end() and \ - self.parent_win.get_active_control() == self and \ - self.parent_win.window.is_active(): - # we are at the end - if self.type_id == message_control.TYPE_GC: - if not gajim.events.remove_events(self.account, jid, - types=types_list): - self.redraw_after_event_removed(jid) - elif self.session and self.session.remove_events(types_list): - # There were events to remove - self.redraw_after_event_removed(jid) - - def redraw_after_event_removed(self, jid): - """ - We just removed a 'printed_*' event, redraw contact in roster or - gc_roster and titles in roster and msg_win - """ - self.parent_win.redraw_tab(self) - self.parent_win.show_title() - # TODO : get the contact and check notify.get_show_in_roster() - if self.type_id == message_control.TYPE_PM: - room_jid, nick = gajim.get_room_and_nick_from_fjid(jid) - groupchat_control = gajim.interface.msg_win_mgr.get_gc_control( - room_jid, self.account) - if room_jid in gajim.interface.minimized_controls[self.account]: - groupchat_control = \ - gajim.interface.minimized_controls[self.account][room_jid] - contact = \ - gajim.contacts.get_contact_with_highest_priority(self.account, \ - room_jid) - if contact: - gajim.interface.roster.draw_contact(room_jid, self.account) - if groupchat_control: - groupchat_control.draw_contact(nick) - if groupchat_control.parent_win: - groupchat_control.parent_win.redraw_tab(groupchat_control) - else: - gajim.interface.roster.draw_contact(jid, self.account) - gajim.interface.roster.show_title() - - def sent_messages_scroll(self, direction, conv_buf): - size = len(self.sent_history) - if self.orig_msg is None: - # user was typing something and then went into history, so save - # whatever is already typed - start_iter = conv_buf.get_start_iter() - end_iter = conv_buf.get_end_iter() - self.orig_msg = conv_buf.get_text(start_iter, end_iter, 0).decode( - 'utf-8') - if direction == 'up': - if self.sent_history_pos == 0: - return - self.sent_history_pos = self.sent_history_pos - 1 - self.smooth = False - conv_buf.set_text(self.sent_history[self.sent_history_pos]) - elif direction == 'down': - if self.sent_history_pos >= size - 1: - conv_buf.set_text(self.orig_msg) - self.orig_msg = None - self.sent_history_pos = size - return - - self.sent_history_pos = self.sent_history_pos + 1 - self.smooth = False - conv_buf.set_text(self.sent_history[self.sent_history_pos]) - - def lighten_color(self, color): - p = 0.4 - mask = 0 - color.red = int((color.red * p) + (mask * (1 - p))) - color.green = int((color.green * p) + (mask * (1 - p))) - color.blue = int((color.blue * p) + (mask * (1 - p))) - return color - - def widget_set_visible(self, widget, state): - """ - Show or hide a widget - """ - # make the last message visible, when changing to "full view" - if not state: - gobject.idle_add(self.conv_textview.scroll_to_end_iter) - - widget.set_no_show_all(state) - if state: - widget.hide() - else: - widget.show_all() - - def chat_buttons_set_visible(self, state): - """ - Toggle chat buttons - """ - MessageControl.chat_buttons_set_visible(self, state) - self.widget_set_visible(self.xml.get_object('actions_hbox'), state) - - def got_connected(self): - self.msg_textview.set_sensitive(True) - self.msg_textview.set_editable(True) - # FIXME: Set sensitivity for toolbar - - def got_disconnected(self): - self.msg_textview.set_sensitive(False) - self.msg_textview.set_editable(False) - self.conv_textview.tv.grab_focus() - - self.no_autonegotiation = False - # FIXME: Set sensitivity for toolbar + """ + A base class containing a banner, ConversationTextview, MessageTextView + """ + + def make_href(self, match): + url_color = gajim.config.get('urlmsgcolor') + url = match.group() + if not '://' in url: + url = 'http://' + url + return '%s' % (url, + url_color, match.group()) + + def get_font_attrs(self): + """ + Get pango font attributes for banner from theme settings + """ + theme = gajim.config.get('roster_theme') + bannerfont = gajim.config.get_per('themes', theme, 'bannerfont') + bannerfontattrs = gajim.config.get_per('themes', theme, 'bannerfontattrs') + + if bannerfont: + font = pango.FontDescription(bannerfont) + else: + font = pango.FontDescription('Normal') + if bannerfontattrs: + # B attribute is set by default + if 'B' in bannerfontattrs: + font.set_weight(pango.WEIGHT_HEAVY) + if 'I' in bannerfontattrs: + font.set_style(pango.STYLE_ITALIC) + + font_attrs = 'font_desc="%s"' % font.to_string() + + # in case there is no font specified we use x-large font size + if font.get_size() == 0: + font_attrs = '%s size="x-large"' % font_attrs + font.set_weight(pango.WEIGHT_NORMAL) + font_attrs_small = 'font_desc="%s" size="small"' % font.to_string() + return (font_attrs, font_attrs_small) + + def get_nb_unread(self): + jid = self.contact.jid + if self.resource: + jid += '/' + self.resource + type_ = self.type_id + return len(gajim.events.get_events(self.account, jid, ['printed_' + type_, + type_])) + + def draw_banner(self): + """ + Draw the fat line at the top of the window that houses the icon, jid, etc + + Derived types MAY implement this. + """ + self.draw_banner_text() + self._update_banner_state_image() + gajim.plugin_manager.gui_extension_point('chat_control_base_draw_banner', + self) + + def draw_banner_text(self): + """ + Derived types SHOULD implement this + """ + pass + + def update_ui(self): + """ + Derived types SHOULD implement this + """ + self.draw_banner() + + def repaint_themed_widgets(self): + """ + Derived types MAY implement this + """ + self._paint_banner() + self.draw_banner() + + def _update_banner_state_image(self): + """ + Derived types MAY implement this + """ + pass + + def handle_message_textview_mykey_press(self, widget, event_keyval, + event_keymod): + """ + Derives types SHOULD implement this, rather than connection to the even + itself + """ + event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) + event.keyval = event_keyval + event.state = event_keymod + event.time = 0 + + _buffer = widget.get_buffer() + start, end = _buffer.get_bounds() + + if event.keyval -- gtk.keysyms.Tab: + position = _buffer.get_insert() + end = _buffer.get_iter_at_mark(position) + + text = _buffer.get_text(start, end, False) + text = text.decode('utf8') + + splitted = text.split() + + if (text.startswith(self.COMMAND_PREFIX) and not + text.startswith(self.COMMAND_PREFIX * 2) and len(splitted) == 1): + + text = splitted[0] + bare = text.lstrip(self.COMMAND_PREFIX) + + if len(text) == 1: + self.command_hits = [] + for command in self.list_commands(): + for name in command.names: + self.command_hits.append(name) + else: + if (self.last_key_tabs and self.command_hits and + self.command_hits[0].startswith(bare)): + self.command_hits.append(self.command_hits.pop(0)) + else: + self.command_hits = [] + for command in self.list_commands(): + for name in command.names: + if name.startswith(bare): + self.command_hits.append(name) + + if self.command_hits: + _buffer.delete(start, end) + _buffer.insert_at_cursor(self.COMMAND_PREFIX + self.command_hits[0] + ' ') + self.last_key_tabs = True + + return True + + self.last_key_tabs = False + + def status_url_clicked(self, widget, url): + helpers.launch_browser_mailer('url', url) + + def __init__(self, type_id, parent_win, widget_name, contact, acct, + resource=None): + # Undo needs this variable to know if space has been pressed. + # Initialize it to True so empty textview is saved in undo list + self.space_pressed = True + + if resource is None: + # We very likely got a contact with a random resource. + # This is bad, we need the highest for caps etc. + c = gajim.contacts.get_contact_with_highest_priority( + acct, contact.jid) + if c and not isinstance(c, GC_Contact): + contact = c + + MessageControl.__init__(self, type_id, parent_win, widget_name, + contact, acct, resource=resource) + + widget = self.xml.get_object('history_button') + id_ = widget.connect('clicked', self._on_history_menuitem_activate) + self.handlers[id_] = widget + + # when/if we do XHTML we will put formatting buttons back + widget = self.xml.get_object('emoticons_button') + id_ = widget.connect('clicked', self.on_emoticons_button_clicked) + self.handlers[id_] = widget + + # Create banner and connect signals + widget = self.xml.get_object('banner_eventbox') + id_ = widget.connect('button-press-event', + self._on_banner_eventbox_button_press_event) + self.handlers[id_] = widget + + self.urlfinder = re.compile( + r"(www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'\"]+[^!,\.\s<>\)'\"\]]") + + self.banner_status_label = self.xml.get_object('banner_label') + id_ = self.banner_status_label.connect('populate_popup', + self.on_banner_label_populate_popup) + self.handlers[id_] = self.banner_status_label + + # Init DND + self.TARGET_TYPE_URI_LIST = 80 + self.dnd_list = [ ( 'text/uri-list', 0, self.TARGET_TYPE_URI_LIST ), + ('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_APP, 0)] + id_ = self.widget.connect('drag_data_received', + self._on_drag_data_received) + self.handlers[id_] = self.widget + self.widget.drag_dest_set(gtk.DEST_DEFAULT_MOTION | + gtk.DEST_DEFAULT_HIGHLIGHT | + gtk.DEST_DEFAULT_DROP, + self.dnd_list, gtk.gdk.ACTION_COPY) + + # Create textviews and connect signals + self.conv_textview = ConversationTextview(self.account) + id_ = self.conv_textview.connect('quote', self.on_quote) + self.handlers[id_] = self.conv_textview.tv + id_ = self.conv_textview.tv.connect('key_press_event', + self._conv_textview_key_press_event) + self.handlers[id_] = self.conv_textview.tv + # FIXME: DND on non editable TextView, find a better way + self.drag_entered = False + id_ = self.conv_textview.tv.connect('drag_data_received', + self._on_drag_data_received) + self.handlers[id_] = self.conv_textview.tv + id_ = self.conv_textview.tv.connect('drag_motion', self._on_drag_motion) + self.handlers[id_] = self.conv_textview.tv + id_ = self.conv_textview.tv.connect('drag_leave', self._on_drag_leave) + self.handlers[id_] = self.conv_textview.tv + self.conv_textview.tv.drag_dest_set(gtk.DEST_DEFAULT_MOTION | + gtk.DEST_DEFAULT_HIGHLIGHT | + gtk.DEST_DEFAULT_DROP, + self.dnd_list, gtk.gdk.ACTION_COPY) + + self.conv_scrolledwindow = self.xml.get_object( + 'conversation_scrolledwindow') + self.conv_scrolledwindow.add(self.conv_textview.tv) + widget = self.conv_scrolledwindow.get_vadjustment() + id_ = widget.connect('value-changed', + self.on_conversation_vadjustment_value_changed) + self.handlers[id_] = widget + id_ = widget.connect('changed', + self.on_conversation_vadjustment_changed) + self.handlers[id_] = widget + self.scroll_to_end_id = None + self.was_at_the_end = True + + # add MessageTextView to UI and connect signals + self.msg_scrolledwindow = self.xml.get_object('message_scrolledwindow') + self.msg_textview = MessageTextView() + id_ = self.msg_textview.connect('mykeypress', + self._on_message_textview_mykeypress_event) + self.handlers[id_] = self.msg_textview + self.msg_scrolledwindow.add(self.msg_textview) + id_ = self.msg_textview.connect('key_press_event', + self._on_message_textview_key_press_event) + self.handlers[id_] = self.msg_textview + id_ = self.msg_textview.connect('size-request', self.size_request) + self.handlers[id_] = self.msg_textview + id_ = self.msg_textview.connect('populate_popup', + self.on_msg_textview_populate_popup) + self.handlers[id_] = self.msg_textview + # Setup DND + id_ = self.msg_textview.connect('drag_data_received', + self._on_drag_data_received) + self.handlers[id_] = self.msg_textview + self.msg_textview.drag_dest_set(gtk.DEST_DEFAULT_MOTION | + gtk.DEST_DEFAULT_HIGHLIGHT, + self.dnd_list, gtk.gdk.ACTION_COPY) + + self.update_font() + + # Hook up send button + widget = self.xml.get_object('send_button') + id_ = widget.connect('clicked', self._on_send_button_clicked) + self.handlers[id_] = widget + + widget = self.xml.get_object('formattings_button') + id_ = widget.connect('clicked', self.on_formattings_button_clicked) + self.handlers[id_] = widget + + # the following vars are used to keep history of user's messages + self.sent_history = [] + self.sent_history_pos = 0 + self.orig_msg = None + + # Emoticons menu + # set image no matter if user wants at this time emoticons or not + # (so toggle works ok) + img = self.xml.get_object('emoticons_button_image') + img.set_from_file(os.path.join(gajim.DATA_DIR, 'emoticons', 'static', + 'smile.png')) + self.toggle_emoticons() + + # Attach speller + if gajim.config.get('use_speller') and HAS_GTK_SPELL: + self.set_speller() + self.conv_textview.tv.show() + self._paint_banner() + + # For XEP-0172 + self.user_nick = None + + self.smooth = True + self.msg_textview.grab_focus() + + self.command_hits = [] + self.last_key_tabs = False + + # PluginSystem: adding GUI extension point for ChatControlBase + # instance object (also subclasses, eg. ChatControl or GroupchatControl) + gajim.plugin_manager.gui_extension_point('chat_control_base', self) + + + def set_speller(self): + # now set the one the user selected + per_type = 'contacts' + if self.type_id == message_control.TYPE_GC: + per_type = 'rooms' + lang = gajim.config.get_per(per_type, self.contact.jid, + 'speller_language') + if not lang: + # use the default one + lang = gajim.config.get('speller_language') + if not lang: + lang = gajim.LANG + if lang: + try: + gtkspell.Spell(self.msg_textview, lang) + self.msg_textview.lang = lang + except (gobject.GError, RuntimeError, TypeError, OSError): + dialogs.AspellDictError(lang) + + def on_banner_label_populate_popup(self, label, menu): + """ + Override the default context menu and add our own menutiems + """ + item = gtk.SeparatorMenuItem() + menu.prepend(item) + + menu2 = self.prepare_context_menu() + i = 0 + for item in menu2: + menu2.remove(item) + menu.prepend(item) + menu.reorder_child(item, i) + i += 1 + menu.show_all() + + + def shutdown(self): + # PluginSystem: removing GUI extension points connected with ChatControlBase + # instance object + gajim.plugin_manager.remove_gui_extension_point('chat_control_base', self) + gajim.plugin_manager.remove_gui_extension_point('chat_control_base_draw_banner', self) + + def on_msg_textview_populate_popup(self, textview, menu): + """ + Override the default context menu and we prepend an option to switch + languages + """ + def _on_select_dictionary(widget, lang): + per_type = 'contacts' + if self.type_id == message_control.TYPE_GC: + per_type = 'rooms' + if not gajim.config.get_per(per_type, self.contact.jid): + gajim.config.add_per(per_type, self.contact.jid) + gajim.config.set_per(per_type, self.contact.jid, 'speller_language', + lang) + spell = gtkspell.get_from_text_view(self.msg_textview) + self.msg_textview.lang = lang + spell.set_language(lang) + widget.set_active(True) + + item = gtk.ImageMenuItem(gtk.STOCK_UNDO) + menu.prepend(item) + id_ = item.connect('activate', self.msg_textview.undo) + self.handlers[id_] = item + + item = gtk.SeparatorMenuItem() + menu.prepend(item) + + item = gtk.ImageMenuItem(gtk.STOCK_CLEAR) + menu.prepend(item) + id_ = item.connect('activate', self.msg_textview.clear) + self.handlers[id_] = item + + if gajim.config.get('use_speller') and HAS_GTK_SPELL: + item = gtk.MenuItem(_('Spelling language')) + menu.prepend(item) + submenu = gtk.Menu() + item.set_submenu(submenu) + for lang in sorted(langs): + item = gtk.CheckMenuItem(lang) + if langs[lang] == self.msg_textview.lang: + item.set_active(True) + submenu.append(item) + id_ = item.connect('activate', _on_select_dictionary, langs[lang]) + self.handlers[id_] = item + + menu.show_all() + + def on_quote(self, widget, text): + text = '>' + text.replace('\n', '\n>') + '\n' + message_buffer = self.msg_textview.get_buffer() + message_buffer.insert_at_cursor(text) + + # moved from ChatControl + def _on_banner_eventbox_button_press_event(self, widget, event): + """ + If right-clicked, show popup + """ + if event.button == 3: # right click + self.parent_win.popup_menu(event) + + def _on_send_button_clicked(self, widget): + """ + When send button is pressed: send the current message + """ + if gajim.connections[self.account].connected < 2: # we are not connected + dialogs.ErrorDialog(_('A connection is not available'), + _('Your message can not be sent until you are connected.')) + return + message_buffer = self.msg_textview.get_buffer() + start_iter = message_buffer.get_start_iter() + end_iter = message_buffer.get_end_iter() + message = message_buffer.get_text(start_iter, end_iter, 0).decode('utf-8') + xhtml = self.msg_textview.get_xhtml() + + # send the message + self.send_message(message, xhtml=xhtml) + + def _paint_banner(self): + """ + Repaint banner with theme color + """ + theme = gajim.config.get('roster_theme') + bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor') + textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor') + # the backgrounds are colored by using an eventbox by + # setting the bg color of the eventbox and the fg of the name_label + banner_eventbox = self.xml.get_object('banner_eventbox') + banner_name_label = self.xml.get_object('banner_name_label') + self.disconnect_style_event(banner_name_label) + self.disconnect_style_event(self.banner_status_label) + if bgcolor: + banner_eventbox.modify_bg(gtk.STATE_NORMAL, + gtk.gdk.color_parse(bgcolor)) + default_bg = False + else: + default_bg = True + if textcolor: + banner_name_label.modify_fg(gtk.STATE_NORMAL, + gtk.gdk.color_parse(textcolor)) + self.banner_status_label.modify_fg(gtk.STATE_NORMAL, + gtk.gdk.color_parse(textcolor)) + default_fg = False + else: + default_fg = True + if default_bg or default_fg: + self._on_style_set_event(banner_name_label, None, default_fg, + default_bg) + if self.banner_status_label.flags() & gtk.REALIZED: + # Widget is realized + self._on_style_set_event(self.banner_status_label, None, default_fg, + default_bg) + + def disconnect_style_event(self, widget): + # Try to find the event_id + for id_ in self.handlers.keys(): + if self.handlers[id_] == widget: + widget.disconnect(id_) + del self.handlers[id_] + break + + def connect_style_event(self, widget, set_fg = False, set_bg = False): + self.disconnect_style_event(widget) + id_ = widget.connect('style-set', self._on_style_set_event, set_fg, + set_bg) + self.handlers[id_] = widget + + def _on_style_set_event(self, widget, style, *opts): + """ + Set style of widget from style class *.Frame.Eventbox + opts[0] == True -> set fg color + opts[1] == True -> set bg color + """ + banner_eventbox = self.xml.get_object('banner_eventbox') + self.disconnect_style_event(widget) + if opts[1]: + bg_color = widget.style.bg[gtk.STATE_SELECTED] + banner_eventbox.modify_bg(gtk.STATE_NORMAL, bg_color) + if opts[0]: + fg_color = widget.style.fg[gtk.STATE_SELECTED] + widget.modify_fg(gtk.STATE_NORMAL, fg_color) + self.connect_style_event(widget, opts[0], opts[1]) + + def _conv_textview_key_press_event(self, widget, event): + if (event.state & gtk.gdk.CONTROL_MASK and event.keyval in (gtk.keysyms.c, + gtk.keysyms.Insert)) or (event.state & gtk.gdk.SHIFT_MASK and \ + event.keyval in (gtk.keysyms.Page_Down, gtk.keysyms.Page_Up)): + return False + self.parent_win.notebook.emit('key_press_event', event) + return True + + def show_emoticons_menu(self): + if not gajim.config.get('emoticons_theme'): + return + def set_emoticons_menu_position(w, msg_tv = self.msg_textview): + window = msg_tv.get_window(gtk.TEXT_WINDOW_WIDGET) + # get the window position + origin = window.get_origin() + size = window.get_size() + buf = msg_tv.get_buffer() + # get the cursor position + cursor = msg_tv.get_iter_location(buf.get_iter_at_mark( + buf.get_insert())) + cursor = msg_tv.buffer_to_window_coords(gtk.TEXT_WINDOW_TEXT, + cursor.x, cursor.y) + x = origin[0] + cursor[0] + y = origin[1] + size[1] + menu_height = gajim.interface.emoticons_menu.size_request()[1] + #FIXME: get_line_count is not so good + #get the iter of cursor, then tv.get_line_yrange + # so we know in which y we are typing (not how many lines we have + # then go show just above the current cursor line for up + # or just below the current cursor line for down + #TEST with having 3 lines and writing in the 2nd + if y + menu_height > gtk.gdk.screen_height(): + # move menu just above cursor + y -= menu_height + (msg_tv.allocation.height / buf.get_line_count()) + #else: # move menu just below cursor + # y -= (msg_tv.allocation.height / buf.get_line_count()) + return (x, y, True) # push_in True + gajim.interface.emoticon_menuitem_clicked = self.append_emoticon + gajim.interface.emoticons_menu.popup(None, None, + set_emoticons_menu_position, 1, 0) + + def _on_message_textview_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.space: + self.space_pressed = True + + elif (self.space_pressed or self.msg_textview.undo_pressed) and \ + event.keyval not in (gtk.keysyms.Control_L, gtk.keysyms.Control_R) and \ + not (event.keyval == gtk.keysyms.z and event.state & gtk.gdk.CONTROL_MASK): + # If the space key has been pressed and now it hasnt, + # we save the buffer into the undo list. But be carefull we're not + # pressiong Control again (as in ctrl+z) + _buffer = widget.get_buffer() + start_iter, end_iter = _buffer.get_bounds() + self.msg_textview.save_undo(_buffer.get_text(start_iter, end_iter)) + self.space_pressed = False + + # Ctrl [+ Shift] + Tab are not forwarded to notebook. We handle it here + if self.widget_name == 'groupchat_control': + if event.keyval not in (gtk.keysyms.ISO_Left_Tab, gtk.keysyms.Tab): + self.last_key_tabs = False + if event.state & gtk.gdk.SHIFT_MASK: + # CTRL + SHIFT + TAB + if event.state & gtk.gdk.CONTROL_MASK and \ + event.keyval == gtk.keysyms.ISO_Left_Tab: + self.parent_win.move_to_next_unread_tab(False) + return True + # SHIFT + PAGE_[UP|DOWN]: send to conv_textview + elif event.keyval == gtk.keysyms.Page_Down or \ + event.keyval == gtk.keysyms.Page_Up: + self.conv_textview.tv.emit('key_press_event', event) + return True + elif event.state & gtk.gdk.CONTROL_MASK: + if event.keyval == gtk.keysyms.Tab: # CTRL + TAB + self.parent_win.move_to_next_unread_tab(True) + return True + return False + + def _on_message_textview_mykeypress_event(self, widget, event_keyval, + event_keymod): + """ + When a key is pressed: if enter is pressed without the shift key, message + (if not empty) is sent and printed in the conversation + """ + # NOTE: handles mykeypress which is custom signal connected to this + # CB in new_tab(). for this singal see message_textview.py + message_textview = widget + message_buffer = message_textview.get_buffer() + start_iter, end_iter = message_buffer.get_bounds() + message = message_buffer.get_text(start_iter, end_iter, False).decode( + 'utf-8') + xhtml = self.msg_textview.get_xhtml() + + # construct event instance from binding + event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here + event.keyval = event_keyval + event.state = event_keymod + event.time = 0 # assign current time + + if event.keyval == gtk.keysyms.Up: + if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+UP + self.sent_messages_scroll('up', widget.get_buffer()) + elif event.keyval == gtk.keysyms.Down: + if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+Down + self.sent_messages_scroll('down', widget.get_buffer()) + elif event.keyval == gtk.keysyms.Return or \ + event.keyval == gtk.keysyms.KP_Enter: # ENTER + # NOTE: SHIFT + ENTER is not needed to be emulated as it is not + # binding at all (textview's default action is newline) + + if gajim.config.get('send_on_ctrl_enter'): + # here, we emulate GTK default action on ENTER (add new line) + # normally I would add in keypress but it gets way to complex + # to get instant result on changing this advanced setting + if event.state == 0: # no ctrl, no shift just ENTER add newline + end_iter = message_buffer.get_end_iter() + message_buffer.insert_at_cursor('\n') + send_message = False + elif event.state & gtk.gdk.CONTROL_MASK: # CTRL + ENTER + send_message = True + else: # send on Enter, do newline on Ctrl Enter + if event.state & gtk.gdk.CONTROL_MASK: # Ctrl + ENTER + end_iter = message_buffer.get_end_iter() + message_buffer.insert_at_cursor('\n') + send_message = False + else: # ENTER + send_message = True + + if gajim.connections[self.account].connected < 2 and send_message: + # we are not connected + dialogs.ErrorDialog(_('A connection is not available'), + _('Your message can not be sent until you are connected.')) + send_message = False + + if send_message: + self.send_message(message, xhtml=xhtml) # send the message + elif event.keyval == gtk.keysyms.z: # CTRL+z + if event.state & gtk.gdk.CONTROL_MASK: + self.msg_textview.undo() + else: + # Give the control itself a chance to process + self.handle_message_textview_mykey_press(widget, event_keyval, + event_keymod) + + def _on_drag_data_received(self, widget, context, x, y, selection, + target_type, timestamp): + """ + Derived types SHOULD implement this + """ + pass + + def _on_drag_leave(self, widget, context, time): + # FIXME: DND on non editable TextView, find a better way + self.drag_entered = False + self.conv_textview.tv.set_editable(False) + + def _on_drag_motion(self, widget, context, x, y, time): + # FIXME: DND on non editable TextView, find a better way + if not self.drag_entered: + # We drag new data over the TextView, make it editable to catch dnd + self.drag_entered_conv = True + self.conv_textview.tv.set_editable(True) + + def send_message(self, message, keyID='', type_='chat', chatstate=None, + msg_id=None, composing_xep=None, resource=None, xhtml=None, + callback=None, callback_args=[], process_commands=True): + """ + Send the given message to the active tab. Doesn't return None if error + """ + if not message or message == '\n': + return None + + if process_commands and self.process_as_command(message): + return + + MessageControl.send_message(self, message, keyID, type_=type_, + chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep, + resource=resource, user_nick=self.user_nick, xhtml=xhtml, + callback=callback, callback_args=callback_args) + + # Record message history + self.save_sent_message(message) + + # Be sure to send user nickname only once according to JEP-0172 + self.user_nick = None + + # Clear msg input + message_buffer = self.msg_textview.get_buffer() + message_buffer.set_text('') # clear message buffer (and tv of course) + + def save_sent_message(self, message): + # save the message, so user can scroll though the list with key up/down + size = len(self.sent_history) + # we don't want size of the buffer to grow indefinately + max_size = gajim.config.get('key_up_lines') + if size >= max_size: + for i in xrange(0, size - 1): + self.sent_history[i] = self.sent_history[i + 1] + self.sent_history[max_size - 1] = message + # self.sent_history_pos has changed if we browsed sent_history, + # reset to real value + self.sent_history_pos = max_size + else: + self.sent_history.append(message) + self.sent_history_pos = size + 1 + self.orig_msg = None + + def print_conversation_line(self, text, kind, name, tim, + other_tags_for_name=[], other_tags_for_time=[], + other_tags_for_text=[], count_as_new=True, subject=None, + old_kind=None, xhtml=None, simple=False, xep0184_id=None, + graphics=True): + """ + Print 'chat' type messages + """ + jid = self.contact.jid + full_jid = self.get_full_jid() + textview = self.conv_textview + end = False + if self.was_at_the_end or kind == 'outgoing': + end = True + textview.print_conversation_line(text, jid, kind, name, tim, + other_tags_for_name, other_tags_for_time, other_tags_for_text, + subject, old_kind, xhtml, simple=simple, graphics=graphics) + + if xep0184_id is not None: + textview.show_xep0184_warning(xep0184_id) + + if not count_as_new: + return + if kind == 'incoming': + if not self.type_id == message_control.TYPE_GC or \ + gajim.config.get('notify_on_all_muc_messages') or \ + 'marked' in other_tags_for_text: + # it's a normal message, or a muc message with want to be + # notified about if quitting just after + # other_tags_for_text == ['marked'] --> highlighted gc message + gajim.last_message_time[self.account][full_jid] = time.time() + + if kind in ('incoming', 'incoming_queue', 'error'): + gc_message = False + if self.type_id == message_control.TYPE_GC: + gc_message = True + + if ((self.parent_win and (not self.parent_win.get_active_control() or \ + self != self.parent_win.get_active_control() or \ + not self.parent_win.is_active() or not end)) or \ + (gc_message and \ + jid in gajim.interface.minimized_controls[self.account])) and \ + kind in ('incoming', 'incoming_queue', 'error'): + # we want to have save this message in events list + # other_tags_for_text == ['marked'] --> highlighted gc message + if gc_message: + if 'marked' in other_tags_for_text: + type_ = 'printed_marked_gc_msg' + else: + type_ = 'printed_gc_msg' + event = 'gc_message_received' + else: + type_ = 'printed_' + self.type_id + event = 'message_received' + show_in_roster = notify.get_show_in_roster(event, + self.account, self.contact, self.session) + show_in_systray = notify.get_show_in_systray(event, + self.account, self.contact, type_) + + event = gajim.events.create_event(type_, (self,), + show_in_roster = show_in_roster, + show_in_systray = show_in_systray) + gajim.events.add_event(self.account, full_jid, event) + # We need to redraw contact if we show in roster + if show_in_roster: + gajim.interface.roster.draw_contact(self.contact.jid, + self.account) + + if not self.parent_win: + return + + if (not self.parent_win.get_active_control() or \ + self != self.parent_win.get_active_control() or \ + not self.parent_win.is_active() or not end) and \ + kind in ('incoming', 'incoming_queue', 'error'): + self.parent_win.redraw_tab(self) + if not self.parent_win.is_active(): + self.parent_win.show_title(True, self) # Enabled Urgent hint + else: + self.parent_win.show_title(False, self) # Disabled Urgent hint + + def toggle_emoticons(self): + """ + Hide show emoticons_button and make sure emoticons_menu is always there + when needed + """ + emoticons_button = self.xml.get_object('emoticons_button') + if gajim.config.get('emoticons_theme'): + emoticons_button.show() + emoticons_button.set_no_show_all(False) + else: + emoticons_button.hide() + emoticons_button.set_no_show_all(True) + + def append_emoticon(self, str_): + buffer_ = self.msg_textview.get_buffer() + if buffer_.get_char_count(): + buffer_.insert_at_cursor(' %s ' % str_) + else: # we are the beginning of buffer + buffer_.insert_at_cursor('%s ' % str_) + self.msg_textview.grab_focus() + + def on_emoticons_button_clicked(self, widget): + """ + Popup emoticons menu + """ + gajim.interface.emoticon_menuitem_clicked = self.append_emoticon + gajim.interface.popup_emoticons_under_button(widget, self.parent_win) + + def on_formattings_button_clicked(self, widget): + """ + Popup formattings menu + """ + menu = gtk.Menu() + + menuitems = ((_('Bold'), 'bold'), + (_('Italic'), 'italic'), + (_('Underline'), 'underline'), + (_('Strike'), 'strike')) + + active_tags = self.msg_textview.get_active_tags() + + for menuitem in menuitems: + item = gtk.CheckMenuItem(menuitem[0]) + if menuitem[1] in active_tags: + item.set_active(True) + else: + item.set_active(False) + item.connect('activate', self.msg_textview.set_tag, + menuitem[1]) + menu.append(item) + + item = gtk.SeparatorMenuItem() # separator + menu.append(item) + + item = gtk.ImageMenuItem(_('Color')) + icon = gtk.image_new_from_stock(gtk.STOCK_SELECT_COLOR, gtk.ICON_SIZE_MENU) + item.set_image(icon) + item.connect('activate', self.on_color_menuitem_activale) + menu.append(item) + + item = gtk.ImageMenuItem(_('Font')) + icon = gtk.image_new_from_stock(gtk.STOCK_SELECT_FONT, gtk.ICON_SIZE_MENU) + item.set_image(icon) + item.connect('activate', self.on_font_menuitem_activale) + menu.append(item) + + item = gtk.SeparatorMenuItem() # separator + menu.append(item) + + item = gtk.ImageMenuItem(_('Clear formating')) + icon = gtk.image_new_from_stock(gtk.STOCK_CLEAR, gtk.ICON_SIZE_MENU) + item.set_image(icon) + item.connect('activate', self.msg_textview.clear_tags) + menu.append(item) + + menu.show_all() + gtkgui_helpers.popup_emoticons_under_button(menu, widget, + self.parent_win) + + def on_color_menuitem_activale(self, widget): + color_dialog = gtk.ColorSelectionDialog('Select a color') + color_dialog.connect('response', self.msg_textview.color_set, + color_dialog.colorsel) + color_dialog.show_all() + + def on_font_menuitem_activale(self, widget): + font_dialog = gtk.FontSelectionDialog('Select a font') + font_dialog.connect('response', self.msg_textview.font_set, + font_dialog.fontsel) + font_dialog.show_all() + + + def on_actions_button_clicked(self, widget): + """ + Popup action menu + """ + menu = self.prepare_context_menu(hide_buttonbar_items=True) + menu.show_all() + gtkgui_helpers.popup_emoticons_under_button(menu, widget, + self.parent_win) + + def update_font(self): + font = pango.FontDescription(gajim.config.get('conversation_font')) + self.conv_textview.tv.modify_font(font) + self.msg_textview.modify_font(font) + + def update_tags(self): + self.conv_textview.update_tags() + + def clear(self, tv): + buffer_ = tv.get_buffer() + start, end = buffer_.get_bounds() + buffer_.delete(start, end) + + def _on_history_menuitem_activate(self, widget = None, jid = None): + """ + When history menuitem is pressed: call history window + """ + if not jid: + jid = self.contact.jid + + if 'logs' in gajim.interface.instances: + gajim.interface.instances['logs'].window.present() + gajim.interface.instances['logs'].open_history(jid, self.account) + else: + gajim.interface.instances['logs'] = \ + history_window.HistoryWindow(jid, self.account) + + def _on_send_file(self, gc_contact=None): + """ + gc_contact can be set when we are in a groupchat control + """ + def _on_ok(c): + gajim.interface.instances['file_transfers'].show_file_send_request( + self.account, c) + if self.TYPE_ID == message_control.TYPE_PM: + gc_contact = self.gc_contact + if gc_contact: + # gc or pm + gc_control = gajim.interface.msg_win_mgr.get_gc_control( + gc_contact.room_jid, self.account) + self_contact = gajim.contacts.get_gc_contact(self.account, + gc_control.room_jid, gc_control.nick) + if gc_control.is_anonymous and gc_contact.affiliation not in ['admin', + 'owner'] and self_contact.affiliation in ['admin', 'owner']: + contact = gajim.contacts.get_contact(self.account, gc_contact.jid) + if not contact or contact.sub not in ('both', 'to'): + prim_text = _('Really send file?') + sec_text = _('If you send a file to %s, he/she will know your ' + 'real Jabber ID.') % gc_contact.name + dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text, + on_response_ok = (_on_ok, gc_contact)) + dialog.popup() + return + _on_ok(gc_contact) + return + _on_ok(self.contact) + + def on_minimize_menuitem_toggled(self, widget): + """ + When a grouchat is minimized, unparent the tab, put it in roster etc + """ + old_value = False + minimized_gc = gajim.config.get_per('accounts', self.account, + 'minimized_gc').split() + if self.contact.jid in minimized_gc: + old_value = True + minimize = widget.get_active() + if minimize and not self.contact.jid in minimized_gc: + minimized_gc.append(self.contact.jid) + if not minimize and self.contact.jid in minimized_gc: + minimized_gc.remove(self.contact.jid) + if old_value != minimize: + gajim.config.set_per('accounts', self.account, 'minimized_gc', + ' '.join(minimized_gc)) + + def set_control_active(self, state): + if state: + jid = self.contact.jid + if self.was_at_the_end: + # we are at the end + type_ = ['printed_' + self.type_id] + if self.type_id == message_control.TYPE_GC: + type_ = ['printed_gc_msg', 'printed_marked_gc_msg'] + if not gajim.events.remove_events(self.account, self.get_full_jid(), + types = type_): + # There were events to remove + self.redraw_after_event_removed(jid) + + + def bring_scroll_to_end(self, textview, diff_y = 0): + """ + Scroll to the end of textview if end is not visible + """ + if self.scroll_to_end_id: + # a scroll is already planned + return + buffer_ = textview.get_buffer() + end_iter = buffer_.get_end_iter() + end_rect = textview.get_iter_location(end_iter) + visible_rect = textview.get_visible_rect() + # scroll only if expected end is not visible + if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y): + self.scroll_to_end_id = gobject.idle_add(self.scroll_to_end_iter, + textview) + + def scroll_to_end_iter(self, textview): + buffer_ = textview.get_buffer() + end_iter = buffer_.get_end_iter() + textview.scroll_to_iter(end_iter, 0, False, 1, 1) + self.scroll_to_end_id = None + return False + + def size_request(self, msg_textview, requisition): + """ + When message_textview changes its size: if the new height will enlarge + the window, enable the scrollbar automatic policy. Also enable scrollbar + automatic policy for horizontal scrollbar if message we have in + message_textview is too big + """ + if msg_textview.window is None: + return + + min_height = self.conv_scrolledwindow.get_property('height-request') + conversation_height = self.conv_textview.tv.window.get_size()[1] + message_height = msg_textview.window.get_size()[1] + message_width = msg_textview.window.get_size()[0] + # new tab is not exposed yet + if conversation_height < 2: + return + + if conversation_height < min_height: + min_height = conversation_height + + # we don't want to always resize in height the message_textview + # so we have minimum on conversation_textview's scrolled window + # but we also want to avoid window resizing so if we reach that + # minimum for conversation_textview and maximum for message_textview + # we set to automatic the scrollbar policy + diff_y = message_height - requisition.height + if diff_y != 0: + if conversation_height + diff_y < min_height: + if message_height + conversation_height - min_height > min_height: + policy = self.msg_scrolledwindow.get_property( + 'vscrollbar-policy') + # scroll only when scrollbar appear + if policy != gtk.POLICY_AUTOMATIC: + self.msg_scrolledwindow.set_property('vscrollbar-policy', + gtk.POLICY_AUTOMATIC) + self.msg_scrolledwindow.set_property('height-request', + message_height + conversation_height - min_height) + self.bring_scroll_to_end(msg_textview) + else: + self.msg_scrolledwindow.set_property('vscrollbar-policy', + gtk.POLICY_NEVER) + self.msg_scrolledwindow.set_property('height-request', -1) + self.conv_textview.bring_scroll_to_end(diff_y - 18, False) + else: + self.conv_textview.bring_scroll_to_end(diff_y - 18, self.smooth) + self.smooth = True # reinit the flag + # enable scrollbar automatic policy for horizontal scrollbar + # if message we have in message_textview is too big + if requisition.width > message_width: + self.msg_scrolledwindow.set_property('hscrollbar-policy', + gtk.POLICY_AUTOMATIC) + else: + self.msg_scrolledwindow.set_property('hscrollbar-policy', + gtk.POLICY_NEVER) + + return True + + def on_conversation_vadjustment_changed(self, adjustment): + # used to stay at the end of the textview when we shrink conversation + # textview. + if self.was_at_the_end: + self.conv_textview.bring_scroll_to_end(-18) + self.was_at_the_end = (adjustment.upper - adjustment.value - adjustment.page_size) < 18 + + def on_conversation_vadjustment_value_changed(self, adjustment): + # stop automatic scroll when we manually scroll + if not self.conv_textview.auto_scrolling: + self.conv_textview.stop_scrolling() + self.was_at_the_end = (adjustment.upper - adjustment.value - adjustment.page_size) < 18 + if self.resource: + jid = self.contact.get_full_jid() + else: + jid = self.contact.jid + types_list = [] + type_ = self.type_id + if type_ == message_control.TYPE_GC: + type_ = 'gc_msg' + types_list = ['printed_' + type_, type_, 'printed_marked_gc_msg'] + else: # Not a GC + types_list = ['printed_' + type_, type_] + + if not len(gajim.events.get_events(self.account, jid, types_list)): + return + if not self.parent_win: + return + if self.conv_textview.at_the_end() and \ + self.parent_win.get_active_control() == self and \ + self.parent_win.window.is_active(): + # we are at the end + if self.type_id == message_control.TYPE_GC: + if not gajim.events.remove_events(self.account, jid, + types=types_list): + self.redraw_after_event_removed(jid) + elif self.session and self.session.remove_events(types_list): + # There were events to remove + self.redraw_after_event_removed(jid) + + def redraw_after_event_removed(self, jid): + """ + We just removed a 'printed_*' event, redraw contact in roster or + gc_roster and titles in roster and msg_win + """ + self.parent_win.redraw_tab(self) + self.parent_win.show_title() + # TODO : get the contact and check notify.get_show_in_roster() + if self.type_id == message_control.TYPE_PM: + room_jid, nick = gajim.get_room_and_nick_from_fjid(jid) + groupchat_control = gajim.interface.msg_win_mgr.get_gc_control( + room_jid, self.account) + if room_jid in gajim.interface.minimized_controls[self.account]: + groupchat_control = \ + gajim.interface.minimized_controls[self.account][room_jid] + contact = \ + gajim.contacts.get_contact_with_highest_priority(self.account, \ + room_jid) + if contact: + gajim.interface.roster.draw_contact(room_jid, self.account) + if groupchat_control: + groupchat_control.draw_contact(nick) + if groupchat_control.parent_win: + groupchat_control.parent_win.redraw_tab(groupchat_control) + else: + gajim.interface.roster.draw_contact(jid, self.account) + gajim.interface.roster.show_title() + + def sent_messages_scroll(self, direction, conv_buf): + size = len(self.sent_history) + if self.orig_msg is None: + # user was typing something and then went into history, so save + # whatever is already typed + start_iter = conv_buf.get_start_iter() + end_iter = conv_buf.get_end_iter() + self.orig_msg = conv_buf.get_text(start_iter, end_iter, 0).decode( + 'utf-8') + if direction == 'up': + if self.sent_history_pos == 0: + return + self.sent_history_pos = self.sent_history_pos - 1 + self.smooth = False + conv_buf.set_text(self.sent_history[self.sent_history_pos]) + elif direction == 'down': + if self.sent_history_pos >= size - 1: + conv_buf.set_text(self.orig_msg) + self.orig_msg = None + self.sent_history_pos = size + return + + self.sent_history_pos = self.sent_history_pos + 1 + self.smooth = False + conv_buf.set_text(self.sent_history[self.sent_history_pos]) + + def lighten_color(self, color): + p = 0.4 + mask = 0 + color.red = int((color.red * p) + (mask * (1 - p))) + color.green = int((color.green * p) + (mask * (1 - p))) + color.blue = int((color.blue * p) + (mask * (1 - p))) + return color + + def widget_set_visible(self, widget, state): + """ + Show or hide a widget + """ + # make the last message visible, when changing to "full view" + if not state: + gobject.idle_add(self.conv_textview.scroll_to_end_iter) + + widget.set_no_show_all(state) + if state: + widget.hide() + else: + widget.show_all() + + def chat_buttons_set_visible(self, state): + """ + Toggle chat buttons + """ + MessageControl.chat_buttons_set_visible(self, state) + self.widget_set_visible(self.xml.get_object('actions_hbox'), state) + + def got_connected(self): + self.msg_textview.set_sensitive(True) + self.msg_textview.set_editable(True) + # FIXME: Set sensitivity for toolbar + + def got_disconnected(self): + self.msg_textview.set_sensitive(False) + self.msg_textview.set_editable(False) + self.conv_textview.tv.grab_focus() + + self.no_autonegotiation = False + # FIXME: Set sensitivity for toolbar ################################################################################ class ChatControl(ChatControlBase): - """ - A control for standard 1-1 chat - """ - ( - JINGLE_STATE_NOT_AVAILABLE, - JINGLE_STATE_AVAILABLE, - JINGLE_STATE_CONNECTING, - JINGLE_STATE_CONNECTION_RECEIVED, - JINGLE_STATE_CONNECTED, - JINGLE_STATE_ERROR - ) = range(6) - - TYPE_ID = message_control.TYPE_CHAT - old_msg_kind = None # last kind of the printed message - - # Set a command host to bound to. Every command given through a chat will be - # processed with this command host. - COMMAND_HOST = ChatCommands - - def __init__(self, parent_win, contact, acct, session, resource = None): - ChatControlBase.__init__(self, self.TYPE_ID, parent_win, - 'chat_control', contact, acct, resource) - - self.gpg_is_active = False - # for muc use: - # widget = self.xml.get_object('muc_window_actions_button') - self.actions_button = self.xml.get_object('message_window_actions_button') - id_ = self.actions_button.connect('clicked', - self.on_actions_button_clicked) - self.handlers[id_] = self.actions_button - - self._formattings_button = self.xml.get_object('formattings_button') - - self._add_to_roster_button = self.xml.get_object( - 'add_to_roster_button') - id_ = self._add_to_roster_button.connect('clicked', - self._on_add_to_roster_menuitem_activate) - self.handlers[id_] = self._add_to_roster_button - - self._audio_button = self.xml.get_object('audio_togglebutton') - id_ = self._audio_button.connect('toggled', self.on_audio_button_toggled) - self.handlers[id_] = self._audio_button - # add a special img - gtkgui_helpers.add_image_to_button(self._audio_button, - 'gajim-mic_inactive') - - self._video_button = self.xml.get_object('video_togglebutton') - id_ = self._video_button.connect('toggled', self.on_video_button_toggled) - self.handlers[id_] = self._video_button - # add a special img - gtkgui_helpers.add_image_to_button(self._video_button, - 'gajim-cam_inactive') - - self._send_file_button = self.xml.get_object('send_file_button') - # add a special img for send file button - path_to_upload_img = gtkgui_helpers.get_icon_path('gajim-upload') - img = gtk.Image() - img.set_from_file(path_to_upload_img) - self._send_file_button.set_image(img) - id_ = self._send_file_button.connect('clicked', - self._on_send_file_menuitem_activate) - self.handlers[id_] = self._send_file_button - - self._convert_to_gc_button = self.xml.get_object( - 'convert_to_gc_button') - id_ = self._convert_to_gc_button.connect('clicked', - self._on_convert_to_gc_menuitem_activate) - self.handlers[id_] = self._convert_to_gc_button - - contact_information_button = self.xml.get_object( - 'contact_information_button') - id_ = contact_information_button.connect('clicked', - self._on_contact_information_menuitem_activate) - self.handlers[id_] = contact_information_button - - compact_view = gajim.config.get('compact_view') - self.chat_buttons_set_visible(compact_view) - self.widget_set_visible(self.xml.get_object('banner_eventbox'), - gajim.config.get('hide_chat_banner')) - - self.authentication_button = self.xml.get_object( - 'authentication_button') - id_ = self.authentication_button.connect('clicked', - self._on_authentication_button_clicked) - self.handlers[id_] = self.authentication_button - - # Add lock image to show chat encryption - self.lock_image = self.xml.get_object('lock_image') - - # Convert to GC icon - img = self.xml.get_object('convert_to_gc_button_image') - img.set_from_pixbuf(gtkgui_helpers.load_icon( - 'muc_active').get_pixbuf()) - - self._audio_banner_image = self.xml.get_object('audio_banner_image') - self._video_banner_image = self.xml.get_object('video_banner_image') - self.audio_sid = None - self.audio_state = self.JINGLE_STATE_NOT_AVAILABLE - self.video_sid = None - self.video_state = self.JINGLE_STATE_NOT_AVAILABLE - - self.update_toolbar() - - self._pep_images = {} - self._pep_images['mood'] = self.xml.get_object('mood_image') - self._pep_images['activity'] = self.xml.get_object('activity_image') - self._pep_images['tune'] = self.xml.get_object('tune_image') - self._pep_images['location'] = self.xml.get_object('location_image') - self.update_all_pep_types() - - # keep timeout id and window obj for possible big avatar - # it is on enter-notify and leave-notify so no need to be - # per jid - self.show_bigger_avatar_timeout_id = None - self.bigger_avatar_window = None - self.show_avatar() - - # chatstate timers and state - self.reset_kbd_mouse_timeout_vars() - self._schedule_activity_timers() - - # Hook up signals - id_ = self.parent_win.window.connect('motion-notify-event', - self._on_window_motion_notify) - self.handlers[id_] = self.parent_win.window - message_tv_buffer = self.msg_textview.get_buffer() - id_ = message_tv_buffer.connect('changed', - self._on_message_tv_buffer_changed) - self.handlers[id_] = message_tv_buffer - - widget = self.xml.get_object('avatar_eventbox') - widget.set_property('height-request', gajim.config.get( - 'chat_avatar_height')) - id_ = widget.connect('enter-notify-event', - self.on_avatar_eventbox_enter_notify_event) - self.handlers[id_] = widget - - id_ = widget.connect('leave-notify-event', - self.on_avatar_eventbox_leave_notify_event) - self.handlers[id_] = widget - - id_ = widget.connect('button-press-event', - self.on_avatar_eventbox_button_press_event) - self.handlers[id_] = widget - - widget = self.xml.get_object('location_eventbox') - id_ = widget.connect('button-release-event', - self.on_location_eventbox_button_release_event) - self.handlers[id_] = widget - - for key in ('1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'): - widget = self.xml.get_object(key + '_button') - id_ = widget.connect('pressed', self.on_num_button_pressed, key) - self.handlers[id_] = widget - id_ = widget.connect('released', self.on_num_button_released) - self.handlers[id_] = widget - - widget = self.xml.get_object('mic_hscale') - id_ = widget.connect('value_changed', self.on_mic_hscale_value_changed) - self.handlers[id_] = widget - - widget = self.xml.get_object('sound_hscale') - id_ = widget.connect('value_changed', self.on_sound_hscale_value_changed) - self.handlers[id_] = widget - - if not session: - # Don't use previous session if we want to a specific resource - # and it's not the same - if not resource: - resource = contact.resource - session = gajim.connections[self.account].find_controlless_session( - self.contact.jid, resource) - - if session: - session.control = self - self.session = session - - if session.enable_encryption: - self.print_esession_details() - - # Enable encryption if needed - self.no_autonegotiation = False - e2e_is_active = self.session and self.session.enable_encryption - gpg_pref = gajim.config.get_per('contacts', contact.jid, - 'gpg_enabled') - - # try GPG first - if not e2e_is_active and gpg_pref and \ - gajim.config.get_per('accounts', self.account, 'keyid') and \ - gajim.connections[self.account].USE_GPG: - self.gpg_is_active = True - gajim.encrypted_chats[self.account].append(contact.jid) - msg = _('GPG encryption enabled') - ChatControlBase.print_conversation_line(self, msg, - 'status', '', None) - - if self.session: - self.session.loggable = gajim.config.get_per('accounts', - self.account, 'log_encrypted_sessions') - # GPG is always authenticated as we use GPG's WoT - self._show_lock_image(self.gpg_is_active, 'GPG', self.gpg_is_active, - self.session and self.session.is_loggable(), True) - - self.update_ui() - # restore previous conversation - self.restore_conversation() - self.msg_textview.grab_focus() - - def update_toolbar(self): - # Formatting - if self.contact.supports(NS_XHTML_IM) and not self.gpg_is_active: - self._formattings_button.set_sensitive(True) - else: - self._formattings_button.set_sensitive(False) - - # Add to roster - if not isinstance(self.contact, GC_Contact) \ - and _('Not in Roster') in self.contact.groups: - self._add_to_roster_button.show() - else: - self._add_to_roster_button.hide() - - # Jingle detection - if self.contact.supports(NS_JINGLE_ICE_UDP) and \ - gajim.HAVE_FARSIGHT and self.contact.resource: - if self.contact.supports(NS_JINGLE_RTP_AUDIO): - if self.audio_state == self.JINGLE_STATE_NOT_AVAILABLE: - self.set_audio_state('available') - else: - self.set_audio_state('not_available') - - if self.contact.supports(NS_JINGLE_RTP_VIDEO): - if self.video_state == self.JINGLE_STATE_NOT_AVAILABLE: - self.set_video_state('available') - else: - self.set_video_state('not_available') - else: - if self.audio_state != self.JINGLE_STATE_NOT_AVAILABLE: - self.set_audio_state('not_available') - if self.video_state != self.JINGLE_STATE_NOT_AVAILABLE: - self.set_video_state('not_available') - - # Audio buttons - if self.audio_state == self.JINGLE_STATE_NOT_AVAILABLE: - self._audio_button.set_sensitive(False) - else: - self._audio_button.set_sensitive(True) - - # Video buttons - if self.video_state == self.JINGLE_STATE_NOT_AVAILABLE: - self._video_button.set_sensitive(False) - else: - self._video_button.set_sensitive(True) - - # Send file - if self.contact.supports(NS_FILE) and self.contact.resource: - self._send_file_button.set_sensitive(True) - else: - self._send_file_button.set_sensitive(False) - if not self.contact.supports(NS_FILE): - self._send_file_button.set_tooltip_text(_( - "This contact does not support file transfer.")) - else: - self._send_file_button.set_tooltip_text( - _("You need to know the real JID of the contact to send him or " - "her a file.")) - - # Convert to GC - if self.contact.supports(NS_MUC): - self._convert_to_gc_button.set_sensitive(True) - else: - self._convert_to_gc_button.set_sensitive(False) - - def update_all_pep_types(self): - for pep_type in self._pep_images: - self.update_pep(pep_type) - - def update_pep(self, pep_type): - if isinstance(self.contact, GC_Contact): - return - if pep_type not in self._pep_images: - return - pep = self.contact.pep - img = self._pep_images[pep_type] - if pep_type in pep: - img.set_from_pixbuf(pep[pep_type].asPixbufIcon()) - img.set_tooltip_markup(pep[pep_type].asMarkupText()) - img.show() - else: - img.hide() - - # PluginSystem: adding GUI extension point for this ChatControl - # instance object - gajim.plugin_manager.gui_extension_point('chat_control', self) - - - def _update_jingle(self, jingle_type): - if jingle_type not in ('audio', 'video'): - return - banner_image = getattr(self, '_' + jingle_type + '_banner_image') - state = getattr(self, jingle_type + '_state') - if state in (self.JINGLE_STATE_NOT_AVAILABLE, - self.JINGLE_STATE_AVAILABLE): - banner_image.hide() - else: - banner_image.show() - if state == self.JINGLE_STATE_CONNECTING: - banner_image.set_from_stock( - gtk.STOCK_CONVERT, 1) - elif state == self.JINGLE_STATE_CONNECTION_RECEIVED: - banner_image.set_from_stock( - gtk.STOCK_NETWORK, 1) - elif state == self.JINGLE_STATE_CONNECTED: - banner_image.set_from_stock( - gtk.STOCK_CONNECT, 1) - elif state == self.JINGLE_STATE_ERROR: - banner_image.set_from_stock( - gtk.STOCK_DIALOG_WARNING, 1) - self.update_toolbar() - - def update_audio(self): - self._update_jingle('audio') - vbox = self.xml.get_object('audio_vbox') - if self.audio_state == self.JINGLE_STATE_CONNECTED: - # Set volume from config - input_vol = gajim.config.get('audio_input_volume') - output_vol = gajim.config.get('audio_output_volume') - input_vol = max(min(input_vol, 100), 0) - output_vol = max(min(output_vol, 100), 0) - self.xml.get_object('mic_hscale').set_value(input_vol) - self.xml.get_object('sound_hscale').set_value(output_vol) - # Show vbox - vbox.set_no_show_all(False) - vbox.show_all() - elif not self.audio_sid: - vbox.set_no_show_all(True) - vbox.hide() - - def update_video(self): - self._update_jingle('video') - - def change_resource(self, resource): - old_full_jid = self.get_full_jid() - self.resource = resource - new_full_jid = self.get_full_jid() - # update gajim.last_message_time - if old_full_jid in gajim.last_message_time[self.account]: - gajim.last_message_time[self.account][new_full_jid] = \ - gajim.last_message_time[self.account][old_full_jid] - # update events - gajim.events.change_jid(self.account, old_full_jid, new_full_jid) - # update MessageWindow._controls - self.parent_win.change_jid(self.account, old_full_jid, new_full_jid) - - def _set_jingle_state(self, jingle_type, state, sid=None, reason=None): - if jingle_type not in ('audio', 'video'): - return - if state in ('connecting', 'connected', 'stop') and reason: - str = _('%(type)s state : %(state)s, reason: %(reason)s') % { - 'type': jingle_type.capitalize(), 'state': state, 'reason': reason} - self.print_conversation(str, 'info') - - states = {'not_available': self.JINGLE_STATE_NOT_AVAILABLE, - 'available': self.JINGLE_STATE_AVAILABLE, - 'connecting': self.JINGLE_STATE_CONNECTING, - 'connection_received': self.JINGLE_STATE_CONNECTION_RECEIVED, - 'connected': self.JINGLE_STATE_CONNECTED, - 'stop': self.JINGLE_STATE_AVAILABLE, - 'error': self.JINGLE_STATE_ERROR} - - if state in states: - jingle_state = states[state] - if getattr(self, jingle_type + '_state') == jingle_state: - return - setattr(self, jingle_type + '_state', jingle_state) - - # Destroy existing session with the user when he signs off - # We need to do that before modifying the sid - if state == 'not_available': - gajim.connections[self.account].delete_jingle_session( - self.contact.get_full_jid(), getattr(self, jingle_type + '_sid')) - - if state in ('not_available', 'available', 'stop'): - setattr(self, jingle_type + '_sid', None) - if state in ('connection_received', 'connecting'): - setattr(self, jingle_type + '_sid', sid) - - if state in ('connecting', 'connected', 'connection_received'): - getattr(self, '_' + jingle_type + '_button').set_active(True) - elif state in ('not_available', 'stop'): - getattr(self, '_' + jingle_type + '_button').set_active(False) - - getattr(self, 'update_' + jingle_type)() - - def set_audio_state(self, state, sid=None, reason=None): - self._set_jingle_state('audio', state, sid=sid, reason=reason) - - def set_video_state(self, state, sid=None, reason=None): - self._set_jingle_state('video', state, sid=sid, reason=reason) - - def _get_audio_content(self): - session = gajim.connections[self.account].get_jingle_session( - self.contact.get_full_jid(), self.audio_sid) - return session.get_content('audio') - - def on_num_button_pressed(self, widget, num): - self._get_audio_content()._start_dtmf(num) - - def on_num_button_released(self, released): - self._get_audio_content()._stop_dtmf() - - def on_mic_hscale_value_changed(self, widget): - value = widget.get_value() - self._get_audio_content().set_mic_volume(value / 100) - # Save volume to config - # FIXME: Putting it here is maybe not the right thing to do? - gajim.config.set('audio_input_volume', value) - - - def on_sound_hscale_value_changed(self, widget): - value = widget.get_value() - self._get_audio_content().set_out_volume(value / 100) - # Save volume to config - # FIXME: Putting it here is maybe not the right thing to do? - gajim.config.set('audio_output_volume', value) - - def on_avatar_eventbox_enter_notify_event(self, widget, event): - """ - Enter the eventbox area so we under conditions add a timeout to show a - bigger avatar after 0.5 sec - """ - jid = self.contact.jid - avatar_pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid) - if avatar_pixbuf in ('ask', None): - return - avatar_w = avatar_pixbuf.get_width() - avatar_h = avatar_pixbuf.get_height() - - scaled_buf = self.xml.get_object('avatar_image').get_pixbuf() - scaled_buf_w = scaled_buf.get_width() - scaled_buf_h = scaled_buf.get_height() - - # do we have something bigger to show? - if avatar_w > scaled_buf_w or avatar_h > scaled_buf_h: - # wait for 0.5 sec in case we leave earlier - self.show_bigger_avatar_timeout_id = gobject.timeout_add(500, - self.show_bigger_avatar, widget) - - def on_avatar_eventbox_leave_notify_event(self, widget, event): - """ - Left the eventbox area that holds the avatar img - """ - # did we add a timeout? if yes remove it - if self.show_bigger_avatar_timeout_id is not None: - gobject.source_remove(self.show_bigger_avatar_timeout_id) - - def on_avatar_eventbox_button_press_event(self, widget, event): - """ - If right-clicked, show popup - """ - if event.button == 3: # right click - menu = gtk.Menu() - menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS) - id_ = menuitem.connect('activate', - gtkgui_helpers.on_avatar_save_as_menuitem_activate, - self.contact.jid, self.account, self.contact.get_shown_name()) - self.handlers[id_] = menuitem - menu.append(menuitem) - menu.show_all() - menu.connect('selection-done', lambda w:w.destroy()) - # show the menu - menu.show_all() - menu.popup(None, None, None, event.button, event.time) - return True - - def on_location_eventbox_button_release_event(self, widget, event): - if 'location' in self.contact.pep: - location = self.contact.pep['location']._pep_specific_data - if ('lat' in location) and ('lon' in location): - uri = 'http://www.openstreetmap.org/?' + \ - 'mlat=%(lat)s&mlon=%(lon)s&zoom=16' % {'lat': location['lat'], - 'lon': location['lon']} - helpers.launch_browser_mailer('url', uri) - - def _on_window_motion_notify(self, widget, event): - """ - It gets called no matter if it is the active window or not - """ - if self.parent_win.get_active_jid() == self.contact.jid: - # if window is the active one, change vars assisting chatstate - self.mouse_over_in_last_5_secs = True - self.mouse_over_in_last_30_secs = True - - def _schedule_activity_timers(self): - self.possible_paused_timeout_id = gobject.timeout_add_seconds(5, - self.check_for_possible_paused_chatstate, None) - self.possible_inactive_timeout_id = gobject.timeout_add_seconds(30, - self.check_for_possible_inactive_chatstate, None) - - def update_ui(self): - # The name banner is drawn here - ChatControlBase.update_ui(self) - self.update_toolbar() - - def _update_banner_state_image(self): - contact = gajim.contacts.get_contact_with_highest_priority(self.account, - self.contact.jid) - if not contact or self.resource: - # For transient contacts - contact = self.contact - show = contact.show - jid = contact.jid - - # Set banner image - img_32 = gajim.interface.roster.get_appropriate_state_images(jid, - size = '32', icon_name = show) - img_16 = gajim.interface.roster.get_appropriate_state_images(jid, - icon_name = show) - if show in img_32 and img_32[show].get_pixbuf(): - # we have 32x32! use it! - banner_image = img_32[show] - use_size_32 = True - else: - banner_image = img_16[show] - use_size_32 = False - - banner_status_img = self.xml.get_object('banner_status_image') - if banner_image.get_storage_type() == gtk.IMAGE_ANIMATION: - banner_status_img.set_from_animation(banner_image.get_animation()) - else: - pix = banner_image.get_pixbuf() - if pix is not None: - if use_size_32: - banner_status_img.set_from_pixbuf(pix) - else: # we need to scale 16x16 to 32x32 - scaled_pix = pix.scale_simple(32, 32, - gtk.gdk.INTERP_BILINEAR) - banner_status_img.set_from_pixbuf(scaled_pix) - - def draw_banner_text(self): - """ - Draw the text in the fat line at the top of the window that houses the - name, jid - """ - contact = self.contact - jid = contact.jid - - banner_name_label = self.xml.get_object('banner_name_label') - - name = contact.get_shown_name() - if self.resource: - name += '/' + self.resource - if self.TYPE_ID == message_control.TYPE_PM: - name = _('%(nickname)s from group chat %(room_name)s') %\ - {'nickname': name, 'room_name': self.room_name} - name = gobject.markup_escape_text(name) - - # We know our contacts nick, but if another contact has the same nick - # in another account we need to also display the account. - # except if we are talking to two different resources of the same contact - acct_info = '' - for account in gajim.contacts.get_accounts(): - if account == self.account: - continue - if acct_info: # We already found a contact with same nick - break - for jid in gajim.contacts.get_jid_list(account): - other_contact_ = \ - gajim.contacts.get_first_contact_from_jid(account, jid) - if other_contact_.get_shown_name() == self.contact.get_shown_name(): - acct_info = ' (%s)' % \ - gobject.markup_escape_text(self.account) - break - - status = contact.status - if status is not None: - banner_name_label.set_ellipsize(pango.ELLIPSIZE_END) - self.banner_status_label.set_ellipsize(pango.ELLIPSIZE_END) - status_reduced = helpers.reduce_chars_newlines(status, max_lines = 1) - status_escaped = gobject.markup_escape_text(status_reduced) - - font_attrs, font_attrs_small = self.get_font_attrs() - st = gajim.config.get('displayed_chat_state_notifications') - cs = contact.chatstate - if cs and st in ('composing_only', 'all'): - if contact.show == 'offline': - chatstate = '' - elif contact.composing_xep == 'XEP-0085': - if st == 'all' or cs == 'composing': - chatstate = helpers.get_uf_chatstate(cs) - else: - chatstate = '' - elif contact.composing_xep == 'XEP-0022': - if cs in ('composing', 'paused'): - # only print composing, paused - chatstate = helpers.get_uf_chatstate(cs) - else: - chatstate = '' - else: - # When does that happen ? See [7797] and [7804] - chatstate = helpers.get_uf_chatstate(cs) - - label_text = '%s%s %s' \ - % (font_attrs, name, font_attrs_small, - acct_info, chatstate) - if acct_info: - acct_info = ' ' + acct_info - label_tooltip = '%s%s %s' % (name, acct_info, chatstate) - else: - # weight="heavy" size="x-large" - label_text = '%s%s' % \ - (font_attrs, name, font_attrs_small, acct_info) - if acct_info: - acct_info = ' ' + acct_info - label_tooltip = '%s%s' % (name, acct_info) - - if status_escaped: - status_text = self.urlfinder.sub(self.make_href, status_escaped) - status_text = '%s' % (font_attrs_small, status_escaped) - self.banner_status_label.set_tooltip_text(status) - self.banner_status_label.set_no_show_all(False) - self.banner_status_label.show() - else: - status_text = '' - self.banner_status_label.hide() - self.banner_status_label.set_no_show_all(True) - - self.banner_status_label.set_markup(status_text) - # setup the label that holds name and jid - banner_name_label.set_markup(label_text) - banner_name_label.set_tooltip_text(label_tooltip) - - def close_jingle_content(self, jingle_type): - sid = getattr(self, jingle_type + '_sid') - if not sid: - return - session = gajim.connections[self.account].get_jingle_session( - self.contact.get_full_jid(), sid) - if session: - content = session.get_content(jingle_type) - if content: - session.remove_content(content.creator, content.name) - - def on_jingle_button_toggled(self, widget, jingle_type): - img_name = 'gajim-%s_%s' % ({'audio': 'mic', 'video': 'cam'}[jingle_type], - {True: 'active', False: 'inactive'}[widget.get_active()]) - path_to_img = gtkgui_helpers.get_icon_path(img_name) - - if widget.get_active(): - if getattr(self, jingle_type + '_state') == \ - self.JINGLE_STATE_AVAILABLE: - sid = getattr(gajim.connections[self.account], - 'start_' + jingle_type)(self.contact.get_full_jid()) - getattr(self, 'set_' + jingle_type + '_state')('connecting', sid) - else: - self.close_jingle_content(jingle_type) - - img = getattr(self, '_' + jingle_type + '_button').get_property('image') - img.set_from_file(path_to_img) - - def on_audio_button_toggled(self, widget): - self.on_jingle_button_toggled(widget, 'audio') - - def on_video_button_toggled(self, widget): - self.on_jingle_button_toggled(widget, 'video') - - def _toggle_gpg(self): - if not self.gpg_is_active and not self.contact.keyID: - dialogs.ErrorDialog(_('No GPG key assigned'), - _('No GPG key is assigned to this contact. So you cannot ' - 'encrypt messages with GPG.')) - return - ec = gajim.encrypted_chats[self.account] - if self.gpg_is_active: - # Disable encryption - ec.remove(self.contact.jid) - self.gpg_is_active = False - loggable = False - msg = _('GPG encryption disabled') - ChatControlBase.print_conversation_line(self, msg, - 'status', '', None) - if self.session: - self.session.loggable = True - - else: - # Enable encryption - ec.append(self.contact.jid) - self.gpg_is_active = True - msg = _('GPG encryption enabled') - ChatControlBase.print_conversation_line(self, msg, - 'status', '', None) - - loggable = gajim.config.get_per('accounts', self.account, - 'log_encrypted_sessions') - - if self.session: - self.session.loggable = loggable - - loggable = self.session.is_loggable() - else: - loggable = loggable and gajim.config.should_log(self.account, - self.contact.jid) - - if loggable: - msg = _('Session WILL be logged') - else: - msg = _('Session WILL NOT be logged') - - ChatControlBase.print_conversation_line(self, msg, - 'status', '', None) - - gajim.config.set_per('contacts', self.contact.jid, - 'gpg_enabled', self.gpg_is_active) - - self._show_lock_image(self.gpg_is_active, 'GPG', - self.gpg_is_active, loggable, True) - - def _show_lock_image(self, visible, enc_type = '', enc_enabled = False, - chat_logged = False, authenticated = False): - """ - Set lock icon visibility and create tooltip - """ - #encryption %s active - status_string = enc_enabled and _('is') or _('is NOT') - #chat session %s be logged - logged_string = chat_logged and _('will') or _('will NOT') - - if authenticated: - #About encrypted chat session - authenticated_string = _('and authenticated') - img_path = gtkgui_helpers.get_icon_path('gajim-security_high') - else: - #About encrypted chat session - authenticated_string = _('and NOT authenticated') - img_path = gtkgui_helpers.get_icon_path('gajim-security_low') - self.lock_image.set_from_file(img_path) - - #status will become 'is' or 'is not', authentificaed will become - #'and authentificated' or 'and not authentificated', logged will become - #'will' or 'will not' - tooltip = _('%(type)s encryption %(status)s active %(authenticated)s.\n' - 'Your chat session %(logged)s be logged.') % {'type': enc_type, - 'status': status_string, 'authenticated': authenticated_string, - 'logged': logged_string} - - self.authentication_button.set_tooltip_text(tooltip) - self.widget_set_visible(self.authentication_button, not visible) - self.lock_image.set_sensitive(enc_enabled) - - def _on_authentication_button_clicked(self, widget): - if self.gpg_is_active: - dialogs.GPGInfoWindow(self) - elif self.session and self.session.enable_encryption: - dialogs.ESessionInfoWindow(self.session) - - def send_message(self, message, keyID='', chatstate=None, xhtml=None, - process_commands=True): - """ - Send a message to contact - """ - if message in ('', None, '\n'): - return None - - # refresh timers - self.reset_kbd_mouse_timeout_vars() - - contact = self.contact - - encrypted = bool(self.session) and self.session.enable_encryption - - keyID = '' - if self.gpg_is_active: - keyID = contact.keyID - encrypted = True - if not keyID: - keyID = 'UNKNOWN' - - chatstates_on = gajim.config.get('outgoing_chat_state_notifications') != \ - 'disabled' - composing_xep = contact.composing_xep - chatstate_to_send = None - if chatstates_on and contact is not None: - if composing_xep is None: - # no info about peer - # send active to discover chat state capabilities - # this is here (and not in send_chatstate) - # because we want it sent with REAL message - # (not standlone) eg. one that has body - - if contact.our_chatstate: - # We already asked for xep 85, don't ask it twice - composing_xep = 'asked_once' - - chatstate_to_send = 'active' - contact.our_chatstate = 'ask' # pseudo state - # if peer supports jep85 and we are not 'ask', send 'active' - # NOTE: first active and 'ask' is set in gajim.py - elif composing_xep is not False: - # send active chatstate on every message (as XEP says) - chatstate_to_send = 'active' - contact.our_chatstate = 'active' - - gobject.source_remove(self.possible_paused_timeout_id) - gobject.source_remove(self.possible_inactive_timeout_id) - self._schedule_activity_timers() - - def _on_sent(id_, contact, message, encrypted, xhtml): - if contact.supports(NS_RECEIPTS) and gajim.config.get_per('accounts', - self.account, 'request_receipt'): - xep0184_id = id_ - else: - xep0184_id = None - - self.print_conversation(message, self.contact.jid, encrypted=encrypted, - xep0184_id=xep0184_id, xhtml=xhtml) - - ChatControlBase.send_message(self, message, keyID, type_='chat', - chatstate=chatstate_to_send, composing_xep=composing_xep, - xhtml=xhtml, callback=_on_sent, - callback_args=[contact, message, encrypted, xhtml], - process_commands=process_commands) - - def check_for_possible_paused_chatstate(self, arg): - """ - Did we move mouse of that window or write something in message textview - in the last 5 seconds? If yes - we go active for mouse, composing for - kbd. If not - we go paused if we were previously composing - """ - contact = self.contact - jid = contact.jid - current_state = contact.our_chatstate - if current_state is False: # jid doesn't support chatstates - return False # stop looping - - message_buffer = self.msg_textview.get_buffer() - if self.kbd_activity_in_last_5_secs and message_buffer.get_char_count(): - # Only composing if the keyboard activity was in text entry - self.send_chatstate('composing') - elif self.mouse_over_in_last_5_secs and\ - jid == self.parent_win.get_active_jid(): - self.send_chatstate('active') - else: - if current_state == 'composing': - self.send_chatstate('paused') # pause composing - - # assume no activity and let the motion-notify or 'insert-text' make them - # True refresh 30 seconds vars too or else it's 30 - 5 = 25 seconds! - self.reset_kbd_mouse_timeout_vars() - return True # loop forever - - def check_for_possible_inactive_chatstate(self, arg): - """ - Did we move mouse over that window or wrote something in message textview - in the last 30 seconds? if yes - we go active. If no - we go inactive - """ - contact = self.contact - - current_state = contact.our_chatstate - if current_state is False: # jid doesn't support chatstates - return False # stop looping - - if self.mouse_over_in_last_5_secs or self.kbd_activity_in_last_5_secs: - return True # loop forever - - if not self.mouse_over_in_last_30_secs or \ - self.kbd_activity_in_last_30_secs: - self.send_chatstate('inactive', contact) - - # assume no activity and let the motion-notify or 'insert-text' make them - # True refresh 30 seconds too or else it's 30 - 5 = 25 seconds! - self.reset_kbd_mouse_timeout_vars() - return True # loop forever - - def reset_kbd_mouse_timeout_vars(self): - self.kbd_activity_in_last_5_secs = False - self.mouse_over_in_last_5_secs = False - self.mouse_over_in_last_30_secs = False - self.kbd_activity_in_last_30_secs = False - - def on_cancel_session_negotiation(self): - msg = _('Session negotiation cancelled') - ChatControlBase.print_conversation_line(self, msg, 'status', '', None) - - def print_esession_details(self): - """ - Print esession settings to textview - """ - e2e_is_active = bool(self.session) and self.session.enable_encryption - if e2e_is_active: - msg = _('This session is encrypted') - - if self.session.is_loggable(): - msg += _(' and WILL be logged') - else: - msg += _(' and WILL NOT be logged') - - ChatControlBase.print_conversation_line(self, msg, 'status', '', None) - - if not self.session.verified_identity: - ChatControlBase.print_conversation_line(self, _("Remote contact's identity not verified. Click the shield button for more details."), 'status', '', None) - else: - msg = _('E2E encryption disabled') - ChatControlBase.print_conversation_line(self, msg, 'status', '', None) - - self._show_lock_image(e2e_is_active, 'E2E', e2e_is_active, self.session and \ - self.session.is_loggable(), self.session and self.session.verified_identity) - - def print_conversation(self, text, frm='', tim=None, encrypted=False, - subject=None, xhtml=None, simple=False, xep0184_id=None): - """ - Print a line in the conversation - - If frm is set to status: it's a status message. - if frm is set to error: it's an error message. The difference between - status and error is mainly that with error, msg count as a new message - (in systray and in control). - If frm is set to info: it's a information message. - If frm is set to print_queue: it is incomming from queue. - If frm is set to another value: it's an outgoing message. - If frm is not set: it's an incomming message. - """ - contact = self.contact - - if frm == 'status': - if not gajim.config.get('print_status_in_chats'): - return - kind = 'status' - name = '' - elif frm == 'error': - kind = 'error' - name = '' - elif frm == 'info': - kind = 'info' - name = '' - else: - if self.session and self.session.enable_encryption: - # ESessions - if not encrypted: - msg = _('The following message was NOT encrypted') - ChatControlBase.print_conversation_line(self, msg, 'status', '', - tim) - else: - # GPG encryption - if encrypted and not self.gpg_is_active: - msg = _('The following message was encrypted') - ChatControlBase.print_conversation_line(self, msg, 'status', '', - tim) - # turn on OpenPGP if this was in fact a XEP-0027 encrypted message - if encrypted == 'xep27': - self._toggle_gpg() - elif not encrypted and self.gpg_is_active: - msg = _('The following message was NOT encrypted') - ChatControlBase.print_conversation_line(self, msg, 'status', '', - tim) - if not frm: - kind = 'incoming' - name = contact.get_shown_name() - elif frm == 'print_queue': # incoming message, but do not update time - kind = 'incoming_queue' - name = contact.get_shown_name() - else: - kind = 'outgoing' - name = gajim.nicks[self.account] - if not xhtml and not (encrypted and self.gpg_is_active) and \ - gajim.config.get('rst_formatting_outgoing_messages'): - from common.rst_xhtml_generator import create_xhtml - xhtml = create_xhtml(text) - if xhtml: - xhtml = '%s' % (NS_XHTML, xhtml) - ChatControlBase.print_conversation_line(self, text, kind, name, tim, - subject=subject, old_kind=self.old_msg_kind, xhtml=xhtml, - simple=simple, xep0184_id=xep0184_id) - if text.startswith('/me ') or text.startswith('/me\n'): - self.old_msg_kind = None - else: - self.old_msg_kind = kind - - def get_tab_label(self, chatstate): - unread = '' - if self.resource: - jid = self.contact.get_full_jid() - else: - jid = self.contact.jid - num_unread = len(gajim.events.get_events(self.account, jid, - ['printed_' + self.type_id, self.type_id])) - if num_unread == 1 and not gajim.config.get('show_unread_tab_icon'): - unread = '*' - elif num_unread > 1: - unread = '[' + unicode(num_unread) + ']' - - # Draw tab label using chatstate - theme = gajim.config.get('roster_theme') - color = None - if not chatstate: - chatstate = self.contact.chatstate - if chatstate is not None: - if chatstate == 'composing': - color = gajim.config.get_per('themes', theme, - 'state_composing_color') - elif chatstate == 'inactive': - color = gajim.config.get_per('themes', theme, - 'state_inactive_color') - elif chatstate == 'gone': - color = gajim.config.get_per('themes', theme, - 'state_gone_color') - elif chatstate == 'paused': - color = gajim.config.get_per('themes', theme, - 'state_paused_color') - if color: - # We set the color for when it's the current tab or not - color = gtk.gdk.colormap_get_system().alloc_color(color) - # In inactive tab color to be lighter against the darker inactive - # background - if chatstate in ('inactive', 'gone') and\ - self.parent_win.get_active_control() != self: - color = self.lighten_color(color) - else: # active or not chatstate, get color from gtk - color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE] - - - name = self.contact.get_shown_name() - if self.resource: - name += '/' + self.resource - label_str = gobject.markup_escape_text(name) - if num_unread: # if unread, text in the label becomes bold - label_str = '' + unread + label_str + '' - return (label_str, color) - - def get_tab_image(self, count_unread=True): - if self.resource: - jid = self.contact.get_full_jid() - else: - jid = self.contact.jid - if count_unread: - num_unread = len(gajim.events.get_events(self.account, jid, - ['printed_' + self.type_id, self.type_id])) - else: - num_unread = 0 - # Set tab image (always 16x16); unread messages show the 'event' image - tab_img = None - - if num_unread and gajim.config.get('show_unread_tab_icon'): - img_16 = gajim.interface.roster.get_appropriate_state_images( - self.contact.jid, icon_name = 'event') - tab_img = img_16['event'] - else: - contact = gajim.contacts.get_contact_with_highest_priority( - self.account, self.contact.jid) - if not contact or self.resource: - # For transient contacts - contact = self.contact - img_16 = gajim.interface.roster.get_appropriate_state_images( - self.contact.jid, icon_name=contact.show) - tab_img = img_16[contact.show] - - return tab_img - - def prepare_context_menu(self, hide_buttonbar_items=False): - """ - Set compact view menuitem active state sets active and sensitivity state - for toggle_gpg_menuitem sets sensitivity for history_menuitem (False for - tranasports) and file_transfer_menuitem and hide()/show() for - add_to_roster_menuitem - """ - menu = gui_menu_builder.get_contact_menu(self.contact, self.account, - use_multiple_contacts=False, show_start_chat=False, - show_encryption=True, control=self, - show_buttonbar_items=not hide_buttonbar_items) - return menu - - def send_chatstate(self, state, contact = None): - """ - Send OUR chatstate as STANDLONE chat state message (eg. no body) - to contact only if new chatstate is different from the previous one - if jid is not specified, send to active tab - """ - # JEP 85 does not allow resending the same chatstate - # this function checks for that and just returns so it's safe to call it - # with same state. - - # This functions also checks for violation in state transitions - # and raises RuntimeException with appropriate message - # more on that http://www.jabber.org/jeps/jep-0085.html#statechart - - # do not send nothing if we have chat state notifications disabled - # that means we won't reply to the from other peer - # so we do not broadcast jep85 capabalities - chatstate_setting = gajim.config.get('outgoing_chat_state_notifications') - if chatstate_setting == 'disabled': - return - elif chatstate_setting == 'composing_only' and state != 'active' and\ - state != 'composing': - return - - if contact is None: - contact = self.parent_win.get_active_contact() - if contact is None: - # contact was from pm in MUC, and left the room so contact is None - # so we cannot send chatstate anymore - return - - # Don't send chatstates to offline contacts - if contact.show == 'offline': - return - - if contact.composing_xep is False: # jid cannot do xep85 nor xep22 - return - - # if the new state we wanna send (state) equals - # the current state (contact.our_chatstate) then return - if contact.our_chatstate == state: - return - - if contact.composing_xep is None: - # we don't know anything about jid, so return - # NOTE: - # send 'active', set current state to 'ask' and return is done - # in self.send_message() because we need REAL message (with ) - # for that procedure so return to make sure we send only once - # 'active' until we know peer supports jep85 - return - - if contact.our_chatstate == 'ask': - return - - # in JEP22, when we already sent stop composing - # notification on paused, don't resend it - if contact.composing_xep == 'XEP-0022' and \ - contact.our_chatstate in ('paused', 'active', 'inactive') and \ - state is not 'composing': # not composing == in (active, inactive, gone) - contact.our_chatstate = 'active' - self.reset_kbd_mouse_timeout_vars() - return - - # prevent going paused if we we were not composing (JEP violation) - if state == 'paused' and not contact.our_chatstate == 'composing': - # go active before - MessageControl.send_message(self, None, chatstate = 'active') - contact.our_chatstate = 'active' - self.reset_kbd_mouse_timeout_vars() - - # if we're inactive prevent composing (JEP violation) - elif contact.our_chatstate == 'inactive' and state == 'composing': - # go active before - MessageControl.send_message(self, None, chatstate = 'active') - contact.our_chatstate = 'active' - self.reset_kbd_mouse_timeout_vars() - - MessageControl.send_message(self, None, chatstate = state, - msg_id = contact.msg_id, composing_xep = contact.composing_xep) - contact.our_chatstate = state - if contact.our_chatstate == 'active': - self.reset_kbd_mouse_timeout_vars() - - def shutdown(self): - # PluginSystem: calling shutdown of super class (ChatControlBase) to let it remove - # it's GUI extension points - super(ChatControl, self).shutdown() - # PluginSystem: removing GUI extension points connected with ChatControl - # instance object - gajim.plugin_manager.remove_gui_extension_point('chat_control', self) - - # Send 'gone' chatstate - self.send_chatstate('gone', self.contact) - self.contact.chatstate = None - self.contact.our_chatstate = None - - for jingle_type in ('audio', 'video'): - self.close_jingle_content(jingle_type) - - # disconnect self from session - if self.session: - self.session.control = None - - # Disconnect timer callbacks - gobject.source_remove(self.possible_paused_timeout_id) - gobject.source_remove(self.possible_inactive_timeout_id) - # Remove bigger avatar window - if self.bigger_avatar_window: - self.bigger_avatar_window.destroy() - # Clean events - gajim.events.remove_events(self.account, self.get_full_jid(), - types = ['printed_' + self.type_id, self.type_id]) - # Remove contact instance if contact has been removed - key = (self.contact.jid, self.account) - roster = gajim.interface.roster - if key in roster.contacts_to_be_removed.keys() and \ - not roster.contact_has_pending_roster_events(self.contact, self.account): - backend = roster.contacts_to_be_removed[key]['backend'] - del roster.contacts_to_be_removed[key] - roster.remove_contact(self.contact.jid, self.account, force=True, - backend=backend) - # remove all register handlers on widgets, created by self.xml - # to prevent circular references among objects - for i in self.handlers.keys(): - if self.handlers[i].handler_is_connected(i): - self.handlers[i].disconnect(i) - del self.handlers[i] - self.conv_textview.del_handlers() - if gajim.config.get('use_speller') and HAS_GTK_SPELL: - spell_obj = gtkspell.get_from_text_view(self.msg_textview) - if spell_obj: - spell_obj.detach() - self.msg_textview.destroy() - - def minimizable(self): - return False - - def safe_shutdown(self): - return False - - def allow_shutdown(self, method, on_yes, on_no, on_minimize): - if time.time() - gajim.last_message_time[self.account]\ - [self.get_full_jid()] < 2: - # 2 seconds - def on_ok(): - on_yes(self) - - def on_cancel(): - on_no(self) - - dialogs.ConfirmationDialog( - # %s is being replaced in the code with JID - _('You just received a new message from "%s"') % self.contact.jid, - _('If you close this tab and you have history disabled, '\ - 'this message will be lost.'), on_response_ok=on_ok, - on_response_cancel=on_cancel) - return - on_yes(self) - - def handle_incoming_chatstate(self): - """ - Handle incoming chatstate that jid SENT TO us - """ - self.draw_banner_text() - # update chatstate in tab for this chat - self.parent_win.redraw_tab(self, self.contact.chatstate) - - def set_control_active(self, state): - ChatControlBase.set_control_active(self, state) - # send chatstate inactive to the one we're leaving - # and active to the one we visit - if state: - self.send_chatstate('active', self.contact) - else: - self.send_chatstate('inactive', self.contact) - # Hide bigger avatar window - if self.bigger_avatar_window: - self.bigger_avatar_window.destroy() - self.bigger_avatar_window = None - # Re-show the small avatar - self.show_avatar() - - def show_avatar(self): - if not gajim.config.get('show_avatar_in_chat'): - return - - jid_with_resource = self.contact.get_full_jid() - pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid_with_resource) - if pixbuf == 'ask': - # we don't have the vcard - if self.TYPE_ID == message_control.TYPE_PM: - if self.gc_contact.jid: - # We know the real jid of this contact - real_jid = self.gc_contact.jid - if self.gc_contact.resource: - real_jid += '/' + self.gc_contact.resource - else: - real_jid = jid_with_resource - gajim.connections[self.account].request_vcard(real_jid, - jid_with_resource) - else: - gajim.connections[self.account].request_vcard(jid_with_resource) - return - elif pixbuf: - scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'chat') - else: - scaled_pixbuf = None - - image = self.xml.get_object('avatar_image') - image.set_from_pixbuf(scaled_pixbuf) - image.show_all() - - def _on_drag_data_received(self, widget, context, x, y, selection, - target_type, timestamp): - if not selection.data: - return - if self.TYPE_ID == message_control.TYPE_PM: - c = self.gc_contact - else: - c = self.contact - if target_type == self.TARGET_TYPE_URI_LIST: - if not c.resource: # If no resource is known, we can't send a file - return - uri = selection.data.strip() - uri_splitted = uri.split() # we may have more than one file dropped - for uri in uri_splitted: - path = helpers.get_file_path_from_dnd_dropped_uri(uri) - if os.path.isfile(path): # is it file? - ft = gajim.interface.instances['file_transfers'] - ft.send_file(self.account, c, path) - return - - # chat2muc - treeview = gajim.interface.roster.tree - model = treeview.get_model() - data = selection.data - path = treeview.get_selection().get_selected_rows()[1][0] - iter_ = model.get_iter(path) - type_ = model[iter_][2] - if type_ != 'contact': # source is not a contact - return - dropped_jid = data.decode('utf-8') - - dropped_transport = gajim.get_transport_name_from_jid(dropped_jid) - c_transport = gajim.get_transport_name_from_jid(c.jid) - if dropped_transport or c_transport: - return # transport contacts cannot be invited - - dialogs.TransformChatToMUC(self.account, [c.jid], [dropped_jid]) - - def _on_message_tv_buffer_changed(self, textbuffer): - self.kbd_activity_in_last_5_secs = True - self.kbd_activity_in_last_30_secs = True - if textbuffer.get_char_count(): - self.send_chatstate('composing', self.contact) - - e2e_is_active = self.session and \ - self.session.enable_encryption - e2e_pref = gajim.config.get_per('accounts', self.account, - 'enable_esessions') and gajim.config.get_per('accounts', - self.account, 'autonegotiate_esessions') and gajim.config.get_per( - 'contacts', self.contact.jid, 'autonegotiate_esessions') - want_e2e = not e2e_is_active and not self.gpg_is_active \ - and e2e_pref - - if want_e2e and not self.no_autonegotiation \ - and gajim.HAVE_PYCRYPTO and self.contact.supports(NS_ESESSION): - self.begin_e2e_negotiation() - else: - self.send_chatstate('active', self.contact) - - def restore_conversation(self): - jid = self.contact.jid - # don't restore lines if it's a transport - if gajim.jid_is_transport(jid): - return - - # How many lines to restore and when to time them out - restore_how_many = gajim.config.get('restore_lines') - if restore_how_many <= 0: - return - timeout = gajim.config.get('restore_timeout') # in minutes - - # number of messages that are in queue and are already logged, we want - # to avoid duplication - pending_how_many = len(gajim.events.get_events(self.account, jid, - ['chat', 'pm'])) - if self.resource: - pending_how_many += len(gajim.events.get_events(self.account, - self.contact.get_full_jid(), ['chat', 'pm'])) - - try: - rows = gajim.logger.get_last_conversation_lines(jid, restore_how_many, - pending_how_many, timeout, self.account) - except exceptions.DatabaseMalformed: - import common.logger - dialogs.ErrorDialog(_('Database Error'), - _('The database file (%s) cannot be read. Try to repair it or remove it (all history will be lost).') % common.logger.LOG_DB_PATH) - rows = [] - local_old_kind = None - for row in rows: # row[0] time, row[1] has kind, row[2] the message - if not row[2]: # message is empty, we don't print it - continue - if row[1] in (constants.KIND_CHAT_MSG_SENT, - constants.KIND_SINGLE_MSG_SENT): - kind = 'outgoing' - name = gajim.nicks[self.account] - elif row[1] in (constants.KIND_SINGLE_MSG_RECV, - constants.KIND_CHAT_MSG_RECV): - kind = 'incoming' - name = self.contact.get_shown_name() - elif row[1] == constants.KIND_ERROR: - kind = 'status' - name = self.contact.get_shown_name() - - tim = time.localtime(float(row[0])) - - if gajim.config.get('restored_messages_small'): - small_attr = ['small'] - else: - small_attr = [] - ChatControlBase.print_conversation_line(self, row[2], kind, name, tim, - small_attr, - small_attr + ['restored_message'], - small_attr + ['restored_message'], - False, old_kind = local_old_kind) - if row[2].startswith('/me ') or row[2].startswith('/me\n'): - local_old_kind = None - else: - local_old_kind = kind - if len(rows): - self.conv_textview.print_empty_line() - - def read_queue(self): - """ - Read queue and print messages containted in it - """ - jid = self.contact.jid - jid_with_resource = jid - if self.resource: - jid_with_resource += '/' + self.resource - events = gajim.events.get_events(self.account, jid_with_resource) - - # list of message ids which should be marked as read - message_ids = [] - for event in events: - if event.type_ != self.type_id: - continue - data = event.parameters - kind = data[2] - if kind == 'error': - kind = 'info' - else: - kind = 'print_queue' - self.print_conversation(data[0], kind, tim = data[3], - encrypted = data[4], subject = data[1], xhtml = data[7]) - if len(data) > 6 and isinstance(data[6], int): - message_ids.append(data[6]) - - if len(data) > 8: - self.set_session(data[8]) - if message_ids: - gajim.logger.set_read_messages(message_ids) - gajim.events.remove_events(self.account, jid_with_resource, - types = [self.type_id]) - - typ = 'chat' # Is it a normal chat or a pm ? - - # reset to status image in gc if it is a pm - # Is it a pm ? - room_jid, nick = gajim.get_room_and_nick_from_fjid(jid) - control = gajim.interface.msg_win_mgr.get_gc_control(room_jid, - self.account) - if control and control.type_id == message_control.TYPE_GC: - control.update_ui() - control.parent_win.show_title() - typ = 'pm' - - self.redraw_after_event_removed(jid) - if (self.contact.show in ('offline', 'error')): - show_offline = gajim.config.get('showoffline') - show_transports = gajim.config.get('show_transports_group') - if (not show_transports and gajim.jid_is_transport(jid)) or \ - (not show_offline and typ == 'chat' and \ - len(gajim.contacts.get_contacts(self.account, jid)) < 2): - gajim.interface.roster.remove_to_be_removed(self.contact.jid, - self.account) - elif typ == 'pm': - control.remove_contact(nick) - - def show_bigger_avatar(self, small_avatar): - """ - Resize the avatar, if needed, so it has at max half the screen size and - shows it - """ - if not small_avatar.window: - # Tab has been closed since we hovered the avatar - return - avatar_pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache( - self.contact.jid) - if avatar_pixbuf in ('ask', None): - return - # Hide the small avatar - # this code hides the small avatar when we show a bigger one in case - # the avatar has a transparency hole in the middle - # so when we show the big one we avoid seeing the small one behind. - # It's why I set it transparent. - image = self.xml.get_object('avatar_image') - pixbuf = image.get_pixbuf() - pixbuf.fill(0xffffff00L) # RGBA - image.queue_draw() - - screen_w = gtk.gdk.screen_width() - screen_h = gtk.gdk.screen_height() - avatar_w = avatar_pixbuf.get_width() - avatar_h = avatar_pixbuf.get_height() - half_scr_w = screen_w / 2 - half_scr_h = screen_h / 2 - if avatar_w > half_scr_w: - avatar_w = half_scr_w - if avatar_h > half_scr_h: - avatar_h = half_scr_h - window = gtk.Window(gtk.WINDOW_POPUP) - self.bigger_avatar_window = window - pixmap, mask = avatar_pixbuf.render_pixmap_and_mask() - window.set_size_request(avatar_w, avatar_h) - # we should make the cursor visible - # gtk+ doesn't make use of the motion notify on gtkwindow by default - # so this line adds that - window.set_events(gtk.gdk.POINTER_MOTION_MASK) - window.set_app_paintable(True) - window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_TOOLTIP) - - window.realize() - window.window.set_back_pixmap(pixmap, False) # make it transparent - window.window.shape_combine_mask(mask, 0, 0) - - # make the bigger avatar window show up centered - x0, y0 = small_avatar.window.get_origin() - x0 += small_avatar.allocation.x - y0 += small_avatar.allocation.y - center_x= x0 + (small_avatar.allocation.width / 2) - center_y = y0 + (small_avatar.allocation.height / 2) - pos_x, pos_y = center_x - (avatar_w / 2), center_y - (avatar_h / 2) - window.move(pos_x, pos_y) - # make the cursor invisible so we can see the image - invisible_cursor = gtkgui_helpers.get_invisible_cursor() - window.window.set_cursor(invisible_cursor) - - # we should hide the window - window.connect('leave_notify_event', - self._on_window_avatar_leave_notify_event) - window.connect('motion-notify-event', - self._on_window_motion_notify_event) - - window.show_all() - - def _on_window_avatar_leave_notify_event(self, widget, event): - """ - Just left the popup window that holds avatar - """ - self.bigger_avatar_window.destroy() - self.bigger_avatar_window = None - # Re-show the small avatar - self.show_avatar() - - def _on_window_motion_notify_event(self, widget, event): - """ - Just moved the mouse so show the cursor - """ - cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR) - self.bigger_avatar_window.window.set_cursor(cursor) - - def _on_send_file_menuitem_activate(self, widget): - self._on_send_file() - - def _on_add_to_roster_menuitem_activate(self, widget): - dialogs.AddNewContactWindow(self.account, self.contact.jid) - - def _on_contact_information_menuitem_activate(self, widget): - gajim.interface.roster.on_info(widget, self.contact, self.account) - - def _on_toggle_gpg_menuitem_activate(self, widget): - self._toggle_gpg() - - def _on_convert_to_gc_menuitem_activate(self, widget): - """ - User wants to invite some friends to chat - """ - dialogs.TransformChatToMUC(self.account, [self.contact.jid]) - - def _on_toggle_e2e_menuitem_activate(self, widget): - if self.session and self.session.enable_encryption: - # e2e was enabled, disable it - jid = str(self.session.jid) - thread_id = self.session.thread_id - - self.session.terminate_e2e() - - gajim.connections[self.account].delete_session(jid, thread_id) - - # presumably the user had a good reason to shut it off, so - # disable autonegotiation too - self.no_autonegotiation = True - else: - self.begin_e2e_negotiation() - - def begin_e2e_negotiation(self): - self.no_autonegotiation = True - - if not self.session: - fjid = self.contact.get_full_jid() - new_sess = gajim.connections[self.account].make_new_session(fjid, type_=self.type_id) - self.set_session(new_sess) - - self.session.negotiate_e2e(False) - - def got_connected(self): - ChatControlBase.got_connected(self) - # Refreshing contact - contact = gajim.contacts.get_contact_with_highest_priority( - self.account, self.contact.jid) - if isinstance(contact, GC_Contact): - contact = contact.as_contact() - if contact: - self.contact = contact - self.draw_banner() - - def update_status_display(self, name, uf_show, status): - """ - Print the contact's status and update the status/GPG image - """ - self.update_ui() - self.parent_win.redraw_tab(self) - - self.print_conversation(_('%(name)s is now %(status)s') % {'name': name, - 'status': uf_show}, 'status') - - if status: - self.print_conversation(' (', 'status', simple=True) - self.print_conversation('%s' % (status), 'status', simple=True) - self.print_conversation(')', 'status', simple=True) - -# vim: se ts=3: + """ + A control for standard 1-1 chat + """ + ( + JINGLE_STATE_NOT_AVAILABLE, + JINGLE_STATE_AVAILABLE, + JINGLE_STATE_CONNECTING, + JINGLE_STATE_CONNECTION_RECEIVED, + JINGLE_STATE_CONNECTED, + JINGLE_STATE_ERROR + ) = range(6) + + TYPE_ID = message_control.TYPE_CHAT + old_msg_kind = None # last kind of the printed message + + # Set a command host to bound to. Every command given through a chat will be + # processed with this command host. + COMMAND_HOST = ChatCommands + + def __init__(self, parent_win, contact, acct, session, resource = None): + ChatControlBase.__init__(self, self.TYPE_ID, parent_win, + 'chat_control', contact, acct, resource) + + self.gpg_is_active = False + # for muc use: + # widget = self.xml.get_object('muc_window_actions_button') + self.actions_button = self.xml.get_object('message_window_actions_button') + id_ = self.actions_button.connect('clicked', + self.on_actions_button_clicked) + self.handlers[id_] = self.actions_button + + self._formattings_button = self.xml.get_object('formattings_button') + + self._add_to_roster_button = self.xml.get_object( + 'add_to_roster_button') + id_ = self._add_to_roster_button.connect('clicked', + self._on_add_to_roster_menuitem_activate) + self.handlers[id_] = self._add_to_roster_button + + self._audio_button = self.xml.get_object('audio_togglebutton') + id_ = self._audio_button.connect('toggled', self.on_audio_button_toggled) + self.handlers[id_] = self._audio_button + # add a special img + gtkgui_helpers.add_image_to_button(self._audio_button, + 'gajim-mic_inactive') + + self._video_button = self.xml.get_object('video_togglebutton') + id_ = self._video_button.connect('toggled', self.on_video_button_toggled) + self.handlers[id_] = self._video_button + # add a special img + gtkgui_helpers.add_image_to_button(self._video_button, + 'gajim-cam_inactive') + + self._send_file_button = self.xml.get_object('send_file_button') + # add a special img for send file button + path_to_upload_img = gtkgui_helpers.get_icon_path('gajim-upload') + img = gtk.Image() + img.set_from_file(path_to_upload_img) + self._send_file_button.set_image(img) + id_ = self._send_file_button.connect('clicked', + self._on_send_file_menuitem_activate) + self.handlers[id_] = self._send_file_button + + self._convert_to_gc_button = self.xml.get_object( + 'convert_to_gc_button') + id_ = self._convert_to_gc_button.connect('clicked', + self._on_convert_to_gc_menuitem_activate) + self.handlers[id_] = self._convert_to_gc_button + + contact_information_button = self.xml.get_object( + 'contact_information_button') + id_ = contact_information_button.connect('clicked', + self._on_contact_information_menuitem_activate) + self.handlers[id_] = contact_information_button + + compact_view = gajim.config.get('compact_view') + self.chat_buttons_set_visible(compact_view) + self.widget_set_visible(self.xml.get_object('banner_eventbox'), + gajim.config.get('hide_chat_banner')) + + self.authentication_button = self.xml.get_object( + 'authentication_button') + id_ = self.authentication_button.connect('clicked', + self._on_authentication_button_clicked) + self.handlers[id_] = self.authentication_button + + # Add lock image to show chat encryption + self.lock_image = self.xml.get_object('lock_image') + + # Convert to GC icon + img = self.xml.get_object('convert_to_gc_button_image') + img.set_from_pixbuf(gtkgui_helpers.load_icon( + 'muc_active').get_pixbuf()) + + self._audio_banner_image = self.xml.get_object('audio_banner_image') + self._video_banner_image = self.xml.get_object('video_banner_image') + self.audio_sid = None + self.audio_state = self.JINGLE_STATE_NOT_AVAILABLE + self.video_sid = None + self.video_state = self.JINGLE_STATE_NOT_AVAILABLE + + self.update_toolbar() + + self._pep_images = {} + self._pep_images['mood'] = self.xml.get_object('mood_image') + self._pep_images['activity'] = self.xml.get_object('activity_image') + self._pep_images['tune'] = self.xml.get_object('tune_image') + self._pep_images['location'] = self.xml.get_object('location_image') + self.update_all_pep_types() + + # keep timeout id and window obj for possible big avatar + # it is on enter-notify and leave-notify so no need to be + # per jid + self.show_bigger_avatar_timeout_id = None + self.bigger_avatar_window = None + self.show_avatar() + + # chatstate timers and state + self.reset_kbd_mouse_timeout_vars() + self._schedule_activity_timers() + + # Hook up signals + id_ = self.parent_win.window.connect('motion-notify-event', + self._on_window_motion_notify) + self.handlers[id_] = self.parent_win.window + message_tv_buffer = self.msg_textview.get_buffer() + id_ = message_tv_buffer.connect('changed', + self._on_message_tv_buffer_changed) + self.handlers[id_] = message_tv_buffer + + widget = self.xml.get_object('avatar_eventbox') + widget.set_property('height-request', gajim.config.get( + 'chat_avatar_height')) + id_ = widget.connect('enter-notify-event', + self.on_avatar_eventbox_enter_notify_event) + self.handlers[id_] = widget + + id_ = widget.connect('leave-notify-event', + self.on_avatar_eventbox_leave_notify_event) + self.handlers[id_] = widget + + id_ = widget.connect('button-press-event', + self.on_avatar_eventbox_button_press_event) + self.handlers[id_] = widget + + widget = self.xml.get_object('location_eventbox') + id_ = widget.connect('button-release-event', + self.on_location_eventbox_button_release_event) + self.handlers[id_] = widget + + for key in ('1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'): + widget = self.xml.get_object(key + '_button') + id_ = widget.connect('pressed', self.on_num_button_pressed, key) + self.handlers[id_] = widget + id_ = widget.connect('released', self.on_num_button_released) + self.handlers[id_] = widget + + widget = self.xml.get_object('mic_hscale') + id_ = widget.connect('value_changed', self.on_mic_hscale_value_changed) + self.handlers[id_] = widget + + widget = self.xml.get_object('sound_hscale') + id_ = widget.connect('value_changed', self.on_sound_hscale_value_changed) + self.handlers[id_] = widget + + if not session: + # Don't use previous session if we want to a specific resource + # and it's not the same + if not resource: + resource = contact.resource + session = gajim.connections[self.account].find_controlless_session( + self.contact.jid, resource) + + if session: + session.control = self + self.session = session + + if session.enable_encryption: + self.print_esession_details() + + # Enable encryption if needed + self.no_autonegotiation = False + e2e_is_active = self.session and self.session.enable_encryption + gpg_pref = gajim.config.get_per('contacts', contact.jid, + 'gpg_enabled') + + # try GPG first + if not e2e_is_active and gpg_pref and \ + gajim.config.get_per('accounts', self.account, 'keyid') and \ + gajim.connections[self.account].USE_GPG: + self.gpg_is_active = True + gajim.encrypted_chats[self.account].append(contact.jid) + msg = _('GPG encryption enabled') + ChatControlBase.print_conversation_line(self, msg, + 'status', '', None) + + if self.session: + self.session.loggable = gajim.config.get_per('accounts', + self.account, 'log_encrypted_sessions') + # GPG is always authenticated as we use GPG's WoT + self._show_lock_image(self.gpg_is_active, 'GPG', self.gpg_is_active, + self.session and self.session.is_loggable(), True) + + self.update_ui() + # restore previous conversation + self.restore_conversation() + self.msg_textview.grab_focus() + + def update_toolbar(self): + # Formatting + if self.contact.supports(NS_XHTML_IM) and not self.gpg_is_active: + self._formattings_button.set_sensitive(True) + else: + self._formattings_button.set_sensitive(False) + + # Add to roster + if not isinstance(self.contact, GC_Contact) \ + and _('Not in Roster') in self.contact.groups: + self._add_to_roster_button.show() + else: + self._add_to_roster_button.hide() + + # Jingle detection + if self.contact.supports(NS_JINGLE_ICE_UDP) and \ + gajim.HAVE_FARSIGHT and self.contact.resource: + if self.contact.supports(NS_JINGLE_RTP_AUDIO): + if self.audio_state == self.JINGLE_STATE_NOT_AVAILABLE: + self.set_audio_state('available') + else: + self.set_audio_state('not_available') + + if self.contact.supports(NS_JINGLE_RTP_VIDEO): + if self.video_state == self.JINGLE_STATE_NOT_AVAILABLE: + self.set_video_state('available') + else: + self.set_video_state('not_available') + else: + if self.audio_state != self.JINGLE_STATE_NOT_AVAILABLE: + self.set_audio_state('not_available') + if self.video_state != self.JINGLE_STATE_NOT_AVAILABLE: + self.set_video_state('not_available') + + # Audio buttons + if self.audio_state == self.JINGLE_STATE_NOT_AVAILABLE: + self._audio_button.set_sensitive(False) + else: + self._audio_button.set_sensitive(True) + + # Video buttons + if self.video_state == self.JINGLE_STATE_NOT_AVAILABLE: + self._video_button.set_sensitive(False) + else: + self._video_button.set_sensitive(True) + + # Send file + if self.contact.supports(NS_FILE) and self.contact.resource: + self._send_file_button.set_sensitive(True) + else: + self._send_file_button.set_sensitive(False) + if not self.contact.supports(NS_FILE): + self._send_file_button.set_tooltip_text(_( + "This contact does not support file transfer.")) + else: + self._send_file_button.set_tooltip_text( + _("You need to know the real JID of the contact to send him or " + "her a file.")) + + # Convert to GC + if self.contact.supports(NS_MUC): + self._convert_to_gc_button.set_sensitive(True) + else: + self._convert_to_gc_button.set_sensitive(False) + + def update_all_pep_types(self): + for pep_type in self._pep_images: + self.update_pep(pep_type) + + def update_pep(self, pep_type): + if isinstance(self.contact, GC_Contact): + return + if pep_type not in self._pep_images: + return + pep = self.contact.pep + img = self._pep_images[pep_type] + if pep_type in pep: + img.set_from_pixbuf(pep[pep_type].asPixbufIcon()) + img.set_tooltip_markup(pep[pep_type].asMarkupText()) + img.show() + else: + img.hide() + + # PluginSystem: adding GUI extension point for this ChatControl + # instance object + gajim.plugin_manager.gui_extension_point('chat_control', self) + + + def _update_jingle(self, jingle_type): + if jingle_type not in ('audio', 'video'): + return + banner_image = getattr(self, '_' + jingle_type + '_banner_image') + state = getattr(self, jingle_type + '_state') + if state in (self.JINGLE_STATE_NOT_AVAILABLE, + self.JINGLE_STATE_AVAILABLE): + banner_image.hide() + else: + banner_image.show() + if state == self.JINGLE_STATE_CONNECTING: + banner_image.set_from_stock( + gtk.STOCK_CONVERT, 1) + elif state == self.JINGLE_STATE_CONNECTION_RECEIVED: + banner_image.set_from_stock( + gtk.STOCK_NETWORK, 1) + elif state == self.JINGLE_STATE_CONNECTED: + banner_image.set_from_stock( + gtk.STOCK_CONNECT, 1) + elif state == self.JINGLE_STATE_ERROR: + banner_image.set_from_stock( + gtk.STOCK_DIALOG_WARNING, 1) + self.update_toolbar() + + def update_audio(self): + self._update_jingle('audio') + vbox = self.xml.get_object('audio_vbox') + if self.audio_state == self.JINGLE_STATE_CONNECTED: + # Set volume from config + input_vol = gajim.config.get('audio_input_volume') + output_vol = gajim.config.get('audio_output_volume') + input_vol = max(min(input_vol, 100), 0) + output_vol = max(min(output_vol, 100), 0) + self.xml.get_object('mic_hscale').set_value(input_vol) + self.xml.get_object('sound_hscale').set_value(output_vol) + # Show vbox + vbox.set_no_show_all(False) + vbox.show_all() + elif not self.audio_sid: + vbox.set_no_show_all(True) + vbox.hide() + + def update_video(self): + self._update_jingle('video') + + def change_resource(self, resource): + old_full_jid = self.get_full_jid() + self.resource = resource + new_full_jid = self.get_full_jid() + # update gajim.last_message_time + if old_full_jid in gajim.last_message_time[self.account]: + gajim.last_message_time[self.account][new_full_jid] = \ + gajim.last_message_time[self.account][old_full_jid] + # update events + gajim.events.change_jid(self.account, old_full_jid, new_full_jid) + # update MessageWindow._controls + self.parent_win.change_jid(self.account, old_full_jid, new_full_jid) + + def _set_jingle_state(self, jingle_type, state, sid=None, reason=None): + if jingle_type not in ('audio', 'video'): + return + if state in ('connecting', 'connected', 'stop') and reason: + str = _('%(type)s state : %(state)s, reason: %(reason)s') % { + 'type': jingle_type.capitalize(), 'state': state, 'reason': reason} + self.print_conversation(str, 'info') + + states = {'not_available': self.JINGLE_STATE_NOT_AVAILABLE, + 'available': self.JINGLE_STATE_AVAILABLE, + 'connecting': self.JINGLE_STATE_CONNECTING, + 'connection_received': self.JINGLE_STATE_CONNECTION_RECEIVED, + 'connected': self.JINGLE_STATE_CONNECTED, + 'stop': self.JINGLE_STATE_AVAILABLE, + 'error': self.JINGLE_STATE_ERROR} + + if state in states: + jingle_state = states[state] + if getattr(self, jingle_type + '_state') == jingle_state: + return + setattr(self, jingle_type + '_state', jingle_state) + + # Destroy existing session with the user when he signs off + # We need to do that before modifying the sid + if state == 'not_available': + gajim.connections[self.account].delete_jingle_session( + self.contact.get_full_jid(), getattr(self, jingle_type + '_sid')) + + if state in ('not_available', 'available', 'stop'): + setattr(self, jingle_type + '_sid', None) + if state in ('connection_received', 'connecting'): + setattr(self, jingle_type + '_sid', sid) + + if state in ('connecting', 'connected', 'connection_received'): + getattr(self, '_' + jingle_type + '_button').set_active(True) + elif state in ('not_available', 'stop'): + getattr(self, '_' + jingle_type + '_button').set_active(False) + + getattr(self, 'update_' + jingle_type)() + + def set_audio_state(self, state, sid=None, reason=None): + self._set_jingle_state('audio', state, sid=sid, reason=reason) + + def set_video_state(self, state, sid=None, reason=None): + self._set_jingle_state('video', state, sid=sid, reason=reason) + + def _get_audio_content(self): + session = gajim.connections[self.account].get_jingle_session( + self.contact.get_full_jid(), self.audio_sid) + return session.get_content('audio') + + def on_num_button_pressed(self, widget, num): + self._get_audio_content()._start_dtmf(num) + + def on_num_button_released(self, released): + self._get_audio_content()._stop_dtmf() + + def on_mic_hscale_value_changed(self, widget): + value = widget.get_value() + self._get_audio_content().set_mic_volume(value / 100) + # Save volume to config + # FIXME: Putting it here is maybe not the right thing to do? + gajim.config.set('audio_input_volume', value) + + + def on_sound_hscale_value_changed(self, widget): + value = widget.get_value() + self._get_audio_content().set_out_volume(value / 100) + # Save volume to config + # FIXME: Putting it here is maybe not the right thing to do? + gajim.config.set('audio_output_volume', value) + + def on_avatar_eventbox_enter_notify_event(self, widget, event): + """ + Enter the eventbox area so we under conditions add a timeout to show a + bigger avatar after 0.5 sec + """ + jid = self.contact.jid + avatar_pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid) + if avatar_pixbuf in ('ask', None): + return + avatar_w = avatar_pixbuf.get_width() + avatar_h = avatar_pixbuf.get_height() + + scaled_buf = self.xml.get_object('avatar_image').get_pixbuf() + scaled_buf_w = scaled_buf.get_width() + scaled_buf_h = scaled_buf.get_height() + + # do we have something bigger to show? + if avatar_w > scaled_buf_w or avatar_h > scaled_buf_h: + # wait for 0.5 sec in case we leave earlier + self.show_bigger_avatar_timeout_id = gobject.timeout_add(500, + self.show_bigger_avatar, widget) + + def on_avatar_eventbox_leave_notify_event(self, widget, event): + """ + Left the eventbox area that holds the avatar img + """ + # did we add a timeout? if yes remove it + if self.show_bigger_avatar_timeout_id is not None: + gobject.source_remove(self.show_bigger_avatar_timeout_id) + + def on_avatar_eventbox_button_press_event(self, widget, event): + """ + If right-clicked, show popup + """ + if event.button == 3: # right click + menu = gtk.Menu() + menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS) + id_ = menuitem.connect('activate', + gtkgui_helpers.on_avatar_save_as_menuitem_activate, + self.contact.jid, self.account, self.contact.get_shown_name()) + self.handlers[id_] = menuitem + menu.append(menuitem) + menu.show_all() + menu.connect('selection-done', lambda w:w.destroy()) + # show the menu + menu.show_all() + menu.popup(None, None, None, event.button, event.time) + return True + + def on_location_eventbox_button_release_event(self, widget, event): + if 'location' in self.contact.pep: + location = self.contact.pep['location']._pep_specific_data + if ('lat' in location) and ('lon' in location): + uri = 'http://www.openstreetmap.org/?' + \ + 'mlat=%(lat)s&mlon=%(lon)s&zoom=16' % {'lat': location['lat'], + 'lon': location['lon']} + helpers.launch_browser_mailer('url', uri) + + def _on_window_motion_notify(self, widget, event): + """ + It gets called no matter if it is the active window or not + """ + if self.parent_win.get_active_jid() == self.contact.jid: + # if window is the active one, change vars assisting chatstate + self.mouse_over_in_last_5_secs = True + self.mouse_over_in_last_30_secs = True + + def _schedule_activity_timers(self): + self.possible_paused_timeout_id = gobject.timeout_add_seconds(5, + self.check_for_possible_paused_chatstate, None) + self.possible_inactive_timeout_id = gobject.timeout_add_seconds(30, + self.check_for_possible_inactive_chatstate, None) + + def update_ui(self): + # The name banner is drawn here + ChatControlBase.update_ui(self) + self.update_toolbar() + + def _update_banner_state_image(self): + contact = gajim.contacts.get_contact_with_highest_priority(self.account, + self.contact.jid) + if not contact or self.resource: + # For transient contacts + contact = self.contact + show = contact.show + jid = contact.jid + + # Set banner image + img_32 = gajim.interface.roster.get_appropriate_state_images(jid, + size = '32', icon_name = show) + img_16 = gajim.interface.roster.get_appropriate_state_images(jid, + icon_name = show) + if show in img_32 and img_32[show].get_pixbuf(): + # we have 32x32! use it! + banner_image = img_32[show] + use_size_32 = True + else: + banner_image = img_16[show] + use_size_32 = False + + banner_status_img = self.xml.get_object('banner_status_image') + if banner_image.get_storage_type() == gtk.IMAGE_ANIMATION: + banner_status_img.set_from_animation(banner_image.get_animation()) + else: + pix = banner_image.get_pixbuf() + if pix is not None: + if use_size_32: + banner_status_img.set_from_pixbuf(pix) + else: # we need to scale 16x16 to 32x32 + scaled_pix = pix.scale_simple(32, 32, + gtk.gdk.INTERP_BILINEAR) + banner_status_img.set_from_pixbuf(scaled_pix) + + def draw_banner_text(self): + """ + Draw the text in the fat line at the top of the window that houses the + name, jid + """ + contact = self.contact + jid = contact.jid + + banner_name_label = self.xml.get_object('banner_name_label') + + name = contact.get_shown_name() + if self.resource: + name += '/' + self.resource + if self.TYPE_ID == message_control.TYPE_PM: + name = _('%(nickname)s from group chat %(room_name)s') %\ + {'nickname': name, 'room_name': self.room_name} + name = gobject.markup_escape_text(name) + + # We know our contacts nick, but if another contact has the same nick + # in another account we need to also display the account. + # except if we are talking to two different resources of the same contact + acct_info = '' + for account in gajim.contacts.get_accounts(): + if account == self.account: + continue + if acct_info: # We already found a contact with same nick + break + for jid in gajim.contacts.get_jid_list(account): + other_contact_ = \ + gajim.contacts.get_first_contact_from_jid(account, jid) + if other_contact_.get_shown_name() == self.contact.get_shown_name(): + acct_info = ' (%s)' % \ + gobject.markup_escape_text(self.account) + break + + status = contact.status + if status is not None: + banner_name_label.set_ellipsize(pango.ELLIPSIZE_END) + self.banner_status_label.set_ellipsize(pango.ELLIPSIZE_END) + status_reduced = helpers.reduce_chars_newlines(status, max_lines = 1) + status_escaped = gobject.markup_escape_text(status_reduced) + + font_attrs, font_attrs_small = self.get_font_attrs() + st = gajim.config.get('displayed_chat_state_notifications') + cs = contact.chatstate + if cs and st in ('composing_only', 'all'): + if contact.show == 'offline': + chatstate = '' + elif contact.composing_xep == 'XEP-0085': + if st == 'all' or cs == 'composing': + chatstate = helpers.get_uf_chatstate(cs) + else: + chatstate = '' + elif contact.composing_xep == 'XEP-0022': + if cs in ('composing', 'paused'): + # only print composing, paused + chatstate = helpers.get_uf_chatstate(cs) + else: + chatstate = '' + else: + # When does that happen ? See [7797] and [7804] + chatstate = helpers.get_uf_chatstate(cs) + + label_text = '%s%s %s' \ + % (font_attrs, name, font_attrs_small, + acct_info, chatstate) + if acct_info: + acct_info = ' ' + acct_info + label_tooltip = '%s%s %s' % (name, acct_info, chatstate) + else: + # weight="heavy" size="x-large" + label_text = '%s%s' % \ + (font_attrs, name, font_attrs_small, acct_info) + if acct_info: + acct_info = ' ' + acct_info + label_tooltip = '%s%s' % (name, acct_info) + + if status_escaped: + status_text = self.urlfinder.sub(self.make_href, status_escaped) + status_text = '%s' % (font_attrs_small, status_escaped) + self.banner_status_label.set_tooltip_text(status) + self.banner_status_label.set_no_show_all(False) + self.banner_status_label.show() + else: + status_text = '' + self.banner_status_label.hide() + self.banner_status_label.set_no_show_all(True) + + self.banner_status_label.set_markup(status_text) + # setup the label that holds name and jid + banner_name_label.set_markup(label_text) + banner_name_label.set_tooltip_text(label_tooltip) + + def close_jingle_content(self, jingle_type): + sid = getattr(self, jingle_type + '_sid') + if not sid: + return + session = gajim.connections[self.account].get_jingle_session( + self.contact.get_full_jid(), sid) + if session: + content = session.get_content(jingle_type) + if content: + session.remove_content(content.creator, content.name) + + def on_jingle_button_toggled(self, widget, jingle_type): + img_name = 'gajim-%s_%s' % ({'audio': 'mic', 'video': 'cam'}[jingle_type], + {True: 'active', False: 'inactive'}[widget.get_active()]) + path_to_img = gtkgui_helpers.get_icon_path(img_name) + + if widget.get_active(): + if getattr(self, jingle_type + '_state') == \ + self.JINGLE_STATE_AVAILABLE: + sid = getattr(gajim.connections[self.account], + 'start_' + jingle_type)(self.contact.get_full_jid()) + getattr(self, 'set_' + jingle_type + '_state')('connecting', sid) + else: + self.close_jingle_content(jingle_type) + + img = getattr(self, '_' + jingle_type + '_button').get_property('image') + img.set_from_file(path_to_img) + + def on_audio_button_toggled(self, widget): + self.on_jingle_button_toggled(widget, 'audio') + + def on_video_button_toggled(self, widget): + self.on_jingle_button_toggled(widget, 'video') + + def _toggle_gpg(self): + if not self.gpg_is_active and not self.contact.keyID: + dialogs.ErrorDialog(_('No GPG key assigned'), + _('No GPG key is assigned to this contact. So you cannot ' + 'encrypt messages with GPG.')) + return + ec = gajim.encrypted_chats[self.account] + if self.gpg_is_active: + # Disable encryption + ec.remove(self.contact.jid) + self.gpg_is_active = False + loggable = False + msg = _('GPG encryption disabled') + ChatControlBase.print_conversation_line(self, msg, + 'status', '', None) + if self.session: + self.session.loggable = True + + else: + # Enable encryption + ec.append(self.contact.jid) + self.gpg_is_active = True + msg = _('GPG encryption enabled') + ChatControlBase.print_conversation_line(self, msg, + 'status', '', None) + + loggable = gajim.config.get_per('accounts', self.account, + 'log_encrypted_sessions') + + if self.session: + self.session.loggable = loggable + + loggable = self.session.is_loggable() + else: + loggable = loggable and gajim.config.should_log(self.account, + self.contact.jid) + + if loggable: + msg = _('Session WILL be logged') + else: + msg = _('Session WILL NOT be logged') + + ChatControlBase.print_conversation_line(self, msg, + 'status', '', None) + + gajim.config.set_per('contacts', self.contact.jid, + 'gpg_enabled', self.gpg_is_active) + + self._show_lock_image(self.gpg_is_active, 'GPG', + self.gpg_is_active, loggable, True) + + def _show_lock_image(self, visible, enc_type = '', enc_enabled = False, + chat_logged = False, authenticated = False): + """ + Set lock icon visibility and create tooltip + """ + #encryption %s active + status_string = enc_enabled and _('is') or _('is NOT') + #chat session %s be logged + logged_string = chat_logged and _('will') or _('will NOT') + + if authenticated: + #About encrypted chat session + authenticated_string = _('and authenticated') + img_path = gtkgui_helpers.get_icon_path('gajim-security_high') + else: + #About encrypted chat session + authenticated_string = _('and NOT authenticated') + img_path = gtkgui_helpers.get_icon_path('gajim-security_low') + self.lock_image.set_from_file(img_path) + + #status will become 'is' or 'is not', authentificaed will become + #'and authentificated' or 'and not authentificated', logged will become + #'will' or 'will not' + tooltip = _('%(type)s encryption %(status)s active %(authenticated)s.\n' + 'Your chat session %(logged)s be logged.') % {'type': enc_type, + 'status': status_string, 'authenticated': authenticated_string, + 'logged': logged_string} + + self.authentication_button.set_tooltip_text(tooltip) + self.widget_set_visible(self.authentication_button, not visible) + self.lock_image.set_sensitive(enc_enabled) + + def _on_authentication_button_clicked(self, widget): + if self.gpg_is_active: + dialogs.GPGInfoWindow(self) + elif self.session and self.session.enable_encryption: + dialogs.ESessionInfoWindow(self.session) + + def send_message(self, message, keyID='', chatstate=None, xhtml=None, + process_commands=True): + """ + Send a message to contact + """ + if message in ('', None, '\n'): + return None + + # refresh timers + self.reset_kbd_mouse_timeout_vars() + + contact = self.contact + + encrypted = bool(self.session) and self.session.enable_encryption + + keyID = '' + if self.gpg_is_active: + keyID = contact.keyID + encrypted = True + if not keyID: + keyID = 'UNKNOWN' + + chatstates_on = gajim.config.get('outgoing_chat_state_notifications') != \ + 'disabled' + composing_xep = contact.composing_xep + chatstate_to_send = None + if chatstates_on and contact is not None: + if composing_xep is None: + # no info about peer + # send active to discover chat state capabilities + # this is here (and not in send_chatstate) + # because we want it sent with REAL message + # (not standlone) eg. one that has body + + if contact.our_chatstate: + # We already asked for xep 85, don't ask it twice + composing_xep = 'asked_once' + + chatstate_to_send = 'active' + contact.our_chatstate = 'ask' # pseudo state + # if peer supports jep85 and we are not 'ask', send 'active' + # NOTE: first active and 'ask' is set in gajim.py + elif composing_xep is not False: + # send active chatstate on every message (as XEP says) + chatstate_to_send = 'active' + contact.our_chatstate = 'active' + + gobject.source_remove(self.possible_paused_timeout_id) + gobject.source_remove(self.possible_inactive_timeout_id) + self._schedule_activity_timers() + + def _on_sent(id_, contact, message, encrypted, xhtml): + if contact.supports(NS_RECEIPTS) and gajim.config.get_per('accounts', + self.account, 'request_receipt'): + xep0184_id = id_ + else: + xep0184_id = None + + self.print_conversation(message, self.contact.jid, encrypted=encrypted, + xep0184_id=xep0184_id, xhtml=xhtml) + + ChatControlBase.send_message(self, message, keyID, type_='chat', + chatstate=chatstate_to_send, composing_xep=composing_xep, + xhtml=xhtml, callback=_on_sent, + callback_args=[contact, message, encrypted, xhtml], + process_commands=process_commands) + + def check_for_possible_paused_chatstate(self, arg): + """ + Did we move mouse of that window or write something in message textview + in the last 5 seconds? If yes - we go active for mouse, composing for + kbd. If not - we go paused if we were previously composing + """ + contact = self.contact + jid = contact.jid + current_state = contact.our_chatstate + if current_state is False: # jid doesn't support chatstates + return False # stop looping + + message_buffer = self.msg_textview.get_buffer() + if self.kbd_activity_in_last_5_secs and message_buffer.get_char_count(): + # Only composing if the keyboard activity was in text entry + self.send_chatstate('composing') + elif self.mouse_over_in_last_5_secs and\ + jid == self.parent_win.get_active_jid(): + self.send_chatstate('active') + else: + if current_state == 'composing': + self.send_chatstate('paused') # pause composing + + # assume no activity and let the motion-notify or 'insert-text' make them + # True refresh 30 seconds vars too or else it's 30 - 5 = 25 seconds! + self.reset_kbd_mouse_timeout_vars() + return True # loop forever + + def check_for_possible_inactive_chatstate(self, arg): + """ + Did we move mouse over that window or wrote something in message textview + in the last 30 seconds? if yes - we go active. If no - we go inactive + """ + contact = self.contact + + current_state = contact.our_chatstate + if current_state is False: # jid doesn't support chatstates + return False # stop looping + + if self.mouse_over_in_last_5_secs or self.kbd_activity_in_last_5_secs: + return True # loop forever + + if not self.mouse_over_in_last_30_secs or \ + self.kbd_activity_in_last_30_secs: + self.send_chatstate('inactive', contact) + + # assume no activity and let the motion-notify or 'insert-text' make them + # True refresh 30 seconds too or else it's 30 - 5 = 25 seconds! + self.reset_kbd_mouse_timeout_vars() + return True # loop forever + + def reset_kbd_mouse_timeout_vars(self): + self.kbd_activity_in_last_5_secs = False + self.mouse_over_in_last_5_secs = False + self.mouse_over_in_last_30_secs = False + self.kbd_activity_in_last_30_secs = False + + def on_cancel_session_negotiation(self): + msg = _('Session negotiation cancelled') + ChatControlBase.print_conversation_line(self, msg, 'status', '', None) + + def print_esession_details(self): + """ + Print esession settings to textview + """ + e2e_is_active = bool(self.session) and self.session.enable_encryption + if e2e_is_active: + msg = _('This session is encrypted') + + if self.session.is_loggable(): + msg += _(' and WILL be logged') + else: + msg += _(' and WILL NOT be logged') + + ChatControlBase.print_conversation_line(self, msg, 'status', '', None) + + if not self.session.verified_identity: + ChatControlBase.print_conversation_line(self, _("Remote contact's identity not verified. Click the shield button for more details."), 'status', '', None) + else: + msg = _('E2E encryption disabled') + ChatControlBase.print_conversation_line(self, msg, 'status', '', None) + + self._show_lock_image(e2e_is_active, 'E2E', e2e_is_active, self.session and \ + self.session.is_loggable(), self.session and self.session.verified_identity) + + def print_conversation(self, text, frm='', tim=None, encrypted=False, + subject=None, xhtml=None, simple=False, xep0184_id=None): + """ + Print a line in the conversation + + If frm is set to status: it's a status message. + if frm is set to error: it's an error message. The difference between + status and error is mainly that with error, msg count as a new message + (in systray and in control). + If frm is set to info: it's a information message. + If frm is set to print_queue: it is incomming from queue. + If frm is set to another value: it's an outgoing message. + If frm is not set: it's an incomming message. + """ + contact = self.contact + + if frm == 'status': + if not gajim.config.get('print_status_in_chats'): + return + kind = 'status' + name = '' + elif frm == 'error': + kind = 'error' + name = '' + elif frm == 'info': + kind = 'info' + name = '' + else: + if self.session and self.session.enable_encryption: + # ESessions + if not encrypted: + msg = _('The following message was NOT encrypted') + ChatControlBase.print_conversation_line(self, msg, 'status', '', + tim) + else: + # GPG encryption + if encrypted and not self.gpg_is_active: + msg = _('The following message was encrypted') + ChatControlBase.print_conversation_line(self, msg, 'status', '', + tim) + # turn on OpenPGP if this was in fact a XEP-0027 encrypted message + if encrypted == 'xep27': + self._toggle_gpg() + elif not encrypted and self.gpg_is_active: + msg = _('The following message was NOT encrypted') + ChatControlBase.print_conversation_line(self, msg, 'status', '', + tim) + if not frm: + kind = 'incoming' + name = contact.get_shown_name() + elif frm == 'print_queue': # incoming message, but do not update time + kind = 'incoming_queue' + name = contact.get_shown_name() + else: + kind = 'outgoing' + name = gajim.nicks[self.account] + if not xhtml and not (encrypted and self.gpg_is_active) and \ + gajim.config.get('rst_formatting_outgoing_messages'): + from common.rst_xhtml_generator import create_xhtml + xhtml = create_xhtml(text) + if xhtml: + xhtml = '%s' % (NS_XHTML, xhtml) + ChatControlBase.print_conversation_line(self, text, kind, name, tim, + subject=subject, old_kind=self.old_msg_kind, xhtml=xhtml, + simple=simple, xep0184_id=xep0184_id) + if text.startswith('/me ') or text.startswith('/me\n'): + self.old_msg_kind = None + else: + self.old_msg_kind = kind + + def get_tab_label(self, chatstate): + unread = '' + if self.resource: + jid = self.contact.get_full_jid() + else: + jid = self.contact.jid + num_unread = len(gajim.events.get_events(self.account, jid, + ['printed_' + self.type_id, self.type_id])) + if num_unread == 1 and not gajim.config.get('show_unread_tab_icon'): + unread = '*' + elif num_unread > 1: + unread = '[' + unicode(num_unread) + ']' + + # Draw tab label using chatstate + theme = gajim.config.get('roster_theme') + color = None + if not chatstate: + chatstate = self.contact.chatstate + if chatstate is not None: + if chatstate == 'composing': + color = gajim.config.get_per('themes', theme, + 'state_composing_color') + elif chatstate == 'inactive': + color = gajim.config.get_per('themes', theme, + 'state_inactive_color') + elif chatstate == 'gone': + color = gajim.config.get_per('themes', theme, + 'state_gone_color') + elif chatstate == 'paused': + color = gajim.config.get_per('themes', theme, + 'state_paused_color') + if color: + # We set the color for when it's the current tab or not + color = gtk.gdk.colormap_get_system().alloc_color(color) + # In inactive tab color to be lighter against the darker inactive + # background + if chatstate in ('inactive', 'gone') and\ + self.parent_win.get_active_control() != self: + color = self.lighten_color(color) + else: # active or not chatstate, get color from gtk + color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE] + + + name = self.contact.get_shown_name() + if self.resource: + name += '/' + self.resource + label_str = gobject.markup_escape_text(name) + if num_unread: # if unread, text in the label becomes bold + label_str = '' + unread + label_str + '' + return (label_str, color) + + def get_tab_image(self, count_unread=True): + if self.resource: + jid = self.contact.get_full_jid() + else: + jid = self.contact.jid + if count_unread: + num_unread = len(gajim.events.get_events(self.account, jid, + ['printed_' + self.type_id, self.type_id])) + else: + num_unread = 0 + # Set tab image (always 16x16); unread messages show the 'event' image + tab_img = None + + if num_unread and gajim.config.get('show_unread_tab_icon'): + img_16 = gajim.interface.roster.get_appropriate_state_images( + self.contact.jid, icon_name = 'event') + tab_img = img_16['event'] + else: + contact = gajim.contacts.get_contact_with_highest_priority( + self.account, self.contact.jid) + if not contact or self.resource: + # For transient contacts + contact = self.contact + img_16 = gajim.interface.roster.get_appropriate_state_images( + self.contact.jid, icon_name=contact.show) + tab_img = img_16[contact.show] + + return tab_img + + def prepare_context_menu(self, hide_buttonbar_items=False): + """ + Set compact view menuitem active state sets active and sensitivity state + for toggle_gpg_menuitem sets sensitivity for history_menuitem (False for + tranasports) and file_transfer_menuitem and hide()/show() for + add_to_roster_menuitem + """ + menu = gui_menu_builder.get_contact_menu(self.contact, self.account, + use_multiple_contacts=False, show_start_chat=False, + show_encryption=True, control=self, + show_buttonbar_items=not hide_buttonbar_items) + return menu + + def send_chatstate(self, state, contact = None): + """ + Send OUR chatstate as STANDLONE chat state message (eg. no body) + to contact only if new chatstate is different from the previous one + if jid is not specified, send to active tab + """ + # JEP 85 does not allow resending the same chatstate + # this function checks for that and just returns so it's safe to call it + # with same state. + + # This functions also checks for violation in state transitions + # and raises RuntimeException with appropriate message + # more on that http://www.jabber.org/jeps/jep-0085.html#statechart + + # do not send nothing if we have chat state notifications disabled + # that means we won't reply to the from other peer + # so we do not broadcast jep85 capabalities + chatstate_setting = gajim.config.get('outgoing_chat_state_notifications') + if chatstate_setting == 'disabled': + return + elif chatstate_setting == 'composing_only' and state != 'active' and\ + state != 'composing': + return + + if contact is None: + contact = self.parent_win.get_active_contact() + if contact is None: + # contact was from pm in MUC, and left the room so contact is None + # so we cannot send chatstate anymore + return + + # Don't send chatstates to offline contacts + if contact.show == 'offline': + return + + if contact.composing_xep is False: # jid cannot do xep85 nor xep22 + return + + # if the new state we wanna send (state) equals + # the current state (contact.our_chatstate) then return + if contact.our_chatstate == state: + return + + if contact.composing_xep is None: + # we don't know anything about jid, so return + # NOTE: + # send 'active', set current state to 'ask' and return is done + # in self.send_message() because we need REAL message (with ) + # for that procedure so return to make sure we send only once + # 'active' until we know peer supports jep85 + return + + if contact.our_chatstate == 'ask': + return + + # in JEP22, when we already sent stop composing + # notification on paused, don't resend it + if contact.composing_xep == 'XEP-0022' and \ + contact.our_chatstate in ('paused', 'active', 'inactive') and \ + state is not 'composing': # not composing == in (active, inactive, gone) + contact.our_chatstate = 'active' + self.reset_kbd_mouse_timeout_vars() + return + + # prevent going paused if we we were not composing (JEP violation) + if state == 'paused' and not contact.our_chatstate == 'composing': + # go active before + MessageControl.send_message(self, None, chatstate = 'active') + contact.our_chatstate = 'active' + self.reset_kbd_mouse_timeout_vars() + + # if we're inactive prevent composing (JEP violation) + elif contact.our_chatstate == 'inactive' and state == 'composing': + # go active before + MessageControl.send_message(self, None, chatstate = 'active') + contact.our_chatstate = 'active' + self.reset_kbd_mouse_timeout_vars() + + MessageControl.send_message(self, None, chatstate = state, + msg_id = contact.msg_id, composing_xep = contact.composing_xep) + contact.our_chatstate = state + if contact.our_chatstate == 'active': + self.reset_kbd_mouse_timeout_vars() + + def shutdown(self): + # PluginSystem: calling shutdown of super class (ChatControlBase) to let it remove + # it's GUI extension points + super(ChatControl, self).shutdown() + # PluginSystem: removing GUI extension points connected with ChatControl + # instance object + gajim.plugin_manager.remove_gui_extension_point('chat_control', self) + + # Send 'gone' chatstate + self.send_chatstate('gone', self.contact) + self.contact.chatstate = None + self.contact.our_chatstate = None + + for jingle_type in ('audio', 'video'): + self.close_jingle_content(jingle_type) + + # disconnect self from session + if self.session: + self.session.control = None + + # Disconnect timer callbacks + gobject.source_remove(self.possible_paused_timeout_id) + gobject.source_remove(self.possible_inactive_timeout_id) + # Remove bigger avatar window + if self.bigger_avatar_window: + self.bigger_avatar_window.destroy() + # Clean events + gajim.events.remove_events(self.account, self.get_full_jid(), + types = ['printed_' + self.type_id, self.type_id]) + # Remove contact instance if contact has been removed + key = (self.contact.jid, self.account) + roster = gajim.interface.roster + if key in roster.contacts_to_be_removed.keys() and \ + not roster.contact_has_pending_roster_events(self.contact, self.account): + backend = roster.contacts_to_be_removed[key]['backend'] + del roster.contacts_to_be_removed[key] + roster.remove_contact(self.contact.jid, self.account, force=True, + backend=backend) + # remove all register handlers on widgets, created by self.xml + # to prevent circular references among objects + for i in self.handlers.keys(): + if self.handlers[i].handler_is_connected(i): + self.handlers[i].disconnect(i) + del self.handlers[i] + self.conv_textview.del_handlers() + if gajim.config.get('use_speller') and HAS_GTK_SPELL: + spell_obj = gtkspell.get_from_text_view(self.msg_textview) + if spell_obj: + spell_obj.detach() + self.msg_textview.destroy() + + def minimizable(self): + return False + + def safe_shutdown(self): + return False + + def allow_shutdown(self, method, on_yes, on_no, on_minimize): + if time.time() - gajim.last_message_time[self.account]\ + [self.get_full_jid()] < 2: + # 2 seconds + def on_ok(): + on_yes(self) + + def on_cancel(): + on_no(self) + + dialogs.ConfirmationDialog( + # %s is being replaced in the code with JID + _('You just received a new message from "%s"') % self.contact.jid, + _('If you close this tab and you have history disabled, '\ + 'this message will be lost.'), on_response_ok=on_ok, + on_response_cancel=on_cancel) + return + on_yes(self) + + def handle_incoming_chatstate(self): + """ + Handle incoming chatstate that jid SENT TO us + """ + self.draw_banner_text() + # update chatstate in tab for this chat + self.parent_win.redraw_tab(self, self.contact.chatstate) + + def set_control_active(self, state): + ChatControlBase.set_control_active(self, state) + # send chatstate inactive to the one we're leaving + # and active to the one we visit + if state: + self.send_chatstate('active', self.contact) + else: + self.send_chatstate('inactive', self.contact) + # Hide bigger avatar window + if self.bigger_avatar_window: + self.bigger_avatar_window.destroy() + self.bigger_avatar_window = None + # Re-show the small avatar + self.show_avatar() + + def show_avatar(self): + if not gajim.config.get('show_avatar_in_chat'): + return + + jid_with_resource = self.contact.get_full_jid() + pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid_with_resource) + if pixbuf == 'ask': + # we don't have the vcard + if self.TYPE_ID == message_control.TYPE_PM: + if self.gc_contact.jid: + # We know the real jid of this contact + real_jid = self.gc_contact.jid + if self.gc_contact.resource: + real_jid += '/' + self.gc_contact.resource + else: + real_jid = jid_with_resource + gajim.connections[self.account].request_vcard(real_jid, + jid_with_resource) + else: + gajim.connections[self.account].request_vcard(jid_with_resource) + return + elif pixbuf: + scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'chat') + else: + scaled_pixbuf = None + + image = self.xml.get_object('avatar_image') + image.set_from_pixbuf(scaled_pixbuf) + image.show_all() + + def _on_drag_data_received(self, widget, context, x, y, selection, + target_type, timestamp): + if not selection.data: + return + if self.TYPE_ID == message_control.TYPE_PM: + c = self.gc_contact + else: + c = self.contact + if target_type == self.TARGET_TYPE_URI_LIST: + if not c.resource: # If no resource is known, we can't send a file + return + uri = selection.data.strip() + uri_splitted = uri.split() # we may have more than one file dropped + for uri in uri_splitted: + path = helpers.get_file_path_from_dnd_dropped_uri(uri) + if os.path.isfile(path): # is it file? + ft = gajim.interface.instances['file_transfers'] + ft.send_file(self.account, c, path) + return + + # chat2muc + treeview = gajim.interface.roster.tree + model = treeview.get_model() + data = selection.data + path = treeview.get_selection().get_selected_rows()[1][0] + iter_ = model.get_iter(path) + type_ = model[iter_][2] + if type_ != 'contact': # source is not a contact + return + dropped_jid = data.decode('utf-8') + + dropped_transport = gajim.get_transport_name_from_jid(dropped_jid) + c_transport = gajim.get_transport_name_from_jid(c.jid) + if dropped_transport or c_transport: + return # transport contacts cannot be invited + + dialogs.TransformChatToMUC(self.account, [c.jid], [dropped_jid]) + + def _on_message_tv_buffer_changed(self, textbuffer): + self.kbd_activity_in_last_5_secs = True + self.kbd_activity_in_last_30_secs = True + if textbuffer.get_char_count(): + self.send_chatstate('composing', self.contact) + + e2e_is_active = self.session and \ + self.session.enable_encryption + e2e_pref = gajim.config.get_per('accounts', self.account, + 'enable_esessions') and gajim.config.get_per('accounts', + self.account, 'autonegotiate_esessions') and gajim.config.get_per( + 'contacts', self.contact.jid, 'autonegotiate_esessions') + want_e2e = not e2e_is_active and not self.gpg_is_active \ + and e2e_pref + + if want_e2e and not self.no_autonegotiation \ + and gajim.HAVE_PYCRYPTO and self.contact.supports(NS_ESESSION): + self.begin_e2e_negotiation() + else: + self.send_chatstate('active', self.contact) + + def restore_conversation(self): + jid = self.contact.jid + # don't restore lines if it's a transport + if gajim.jid_is_transport(jid): + return + + # How many lines to restore and when to time them out + restore_how_many = gajim.config.get('restore_lines') + if restore_how_many <= 0: + return + timeout = gajim.config.get('restore_timeout') # in minutes + + # number of messages that are in queue and are already logged, we want + # to avoid duplication + pending_how_many = len(gajim.events.get_events(self.account, jid, + ['chat', 'pm'])) + if self.resource: + pending_how_many += len(gajim.events.get_events(self.account, + self.contact.get_full_jid(), ['chat', 'pm'])) + + try: + rows = gajim.logger.get_last_conversation_lines(jid, restore_how_many, + pending_how_many, timeout, self.account) + except exceptions.DatabaseMalformed: + import common.logger + dialogs.ErrorDialog(_('Database Error'), + _('The database file (%s) cannot be read. Try to repair it or remove it (all history will be lost).') % common.logger.LOG_DB_PATH) + rows = [] + local_old_kind = None + for row in rows: # row[0] time, row[1] has kind, row[2] the message + if not row[2]: # message is empty, we don't print it + continue + if row[1] in (constants.KIND_CHAT_MSG_SENT, + constants.KIND_SINGLE_MSG_SENT): + kind = 'outgoing' + name = gajim.nicks[self.account] + elif row[1] in (constants.KIND_SINGLE_MSG_RECV, + constants.KIND_CHAT_MSG_RECV): + kind = 'incoming' + name = self.contact.get_shown_name() + elif row[1] == constants.KIND_ERROR: + kind = 'status' + name = self.contact.get_shown_name() + + tim = time.localtime(float(row[0])) + + if gajim.config.get('restored_messages_small'): + small_attr = ['small'] + else: + small_attr = [] + ChatControlBase.print_conversation_line(self, row[2], kind, name, tim, + small_attr, + small_attr + ['restored_message'], + small_attr + ['restored_message'], + False, old_kind = local_old_kind) + if row[2].startswith('/me ') or row[2].startswith('/me\n'): + local_old_kind = None + else: + local_old_kind = kind + if len(rows): + self.conv_textview.print_empty_line() + + def read_queue(self): + """ + Read queue and print messages containted in it + """ + jid = self.contact.jid + jid_with_resource = jid + if self.resource: + jid_with_resource += '/' + self.resource + events = gajim.events.get_events(self.account, jid_with_resource) + + # list of message ids which should be marked as read + message_ids = [] + for event in events: + if event.type_ != self.type_id: + continue + data = event.parameters + kind = data[2] + if kind == 'error': + kind = 'info' + else: + kind = 'print_queue' + self.print_conversation(data[0], kind, tim = data[3], + encrypted = data[4], subject = data[1], xhtml = data[7]) + if len(data) > 6 and isinstance(data[6], int): + message_ids.append(data[6]) + + if len(data) > 8: + self.set_session(data[8]) + if message_ids: + gajim.logger.set_read_messages(message_ids) + gajim.events.remove_events(self.account, jid_with_resource, + types = [self.type_id]) + + typ = 'chat' # Is it a normal chat or a pm ? + + # reset to status image in gc if it is a pm + # Is it a pm ? + room_jid, nick = gajim.get_room_and_nick_from_fjid(jid) + control = gajim.interface.msg_win_mgr.get_gc_control(room_jid, + self.account) + if control and control.type_id == message_control.TYPE_GC: + control.update_ui() + control.parent_win.show_title() + typ = 'pm' + + self.redraw_after_event_removed(jid) + if (self.contact.show in ('offline', 'error')): + show_offline = gajim.config.get('showoffline') + show_transports = gajim.config.get('show_transports_group') + if (not show_transports and gajim.jid_is_transport(jid)) or \ + (not show_offline and typ == 'chat' and \ + len(gajim.contacts.get_contacts(self.account, jid)) < 2): + gajim.interface.roster.remove_to_be_removed(self.contact.jid, + self.account) + elif typ == 'pm': + control.remove_contact(nick) + + def show_bigger_avatar(self, small_avatar): + """ + Resize the avatar, if needed, so it has at max half the screen size and + shows it + """ + if not small_avatar.window: + # Tab has been closed since we hovered the avatar + return + avatar_pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache( + self.contact.jid) + if avatar_pixbuf in ('ask', None): + return + # Hide the small avatar + # this code hides the small avatar when we show a bigger one in case + # the avatar has a transparency hole in the middle + # so when we show the big one we avoid seeing the small one behind. + # It's why I set it transparent. + image = self.xml.get_object('avatar_image') + pixbuf = image.get_pixbuf() + pixbuf.fill(0xffffff00L) # RGBA + image.queue_draw() + + screen_w = gtk.gdk.screen_width() + screen_h = gtk.gdk.screen_height() + avatar_w = avatar_pixbuf.get_width() + avatar_h = avatar_pixbuf.get_height() + half_scr_w = screen_w / 2 + half_scr_h = screen_h / 2 + if avatar_w > half_scr_w: + avatar_w = half_scr_w + if avatar_h > half_scr_h: + avatar_h = half_scr_h + window = gtk.Window(gtk.WINDOW_POPUP) + self.bigger_avatar_window = window + pixmap, mask = avatar_pixbuf.render_pixmap_and_mask() + window.set_size_request(avatar_w, avatar_h) + # we should make the cursor visible + # gtk+ doesn't make use of the motion notify on gtkwindow by default + # so this line adds that + window.set_events(gtk.gdk.POINTER_MOTION_MASK) + window.set_app_paintable(True) + window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_TOOLTIP) + + window.realize() + window.window.set_back_pixmap(pixmap, False) # make it transparent + window.window.shape_combine_mask(mask, 0, 0) + + # make the bigger avatar window show up centered + x0, y0 = small_avatar.window.get_origin() + x0 += small_avatar.allocation.x + y0 += small_avatar.allocation.y + center_x= x0 + (small_avatar.allocation.width / 2) + center_y = y0 + (small_avatar.allocation.height / 2) + pos_x, pos_y = center_x - (avatar_w / 2), center_y - (avatar_h / 2) + window.move(pos_x, pos_y) + # make the cursor invisible so we can see the image + invisible_cursor = gtkgui_helpers.get_invisible_cursor() + window.window.set_cursor(invisible_cursor) + + # we should hide the window + window.connect('leave_notify_event', + self._on_window_avatar_leave_notify_event) + window.connect('motion-notify-event', + self._on_window_motion_notify_event) + + window.show_all() + + def _on_window_avatar_leave_notify_event(self, widget, event): + """ + Just left the popup window that holds avatar + """ + self.bigger_avatar_window.destroy() + self.bigger_avatar_window = None + # Re-show the small avatar + self.show_avatar() + + def _on_window_motion_notify_event(self, widget, event): + """ + Just moved the mouse so show the cursor + """ + cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR) + self.bigger_avatar_window.window.set_cursor(cursor) + + def _on_send_file_menuitem_activate(self, widget): + self._on_send_file() + + def _on_add_to_roster_menuitem_activate(self, widget): + dialogs.AddNewContactWindow(self.account, self.contact.jid) + + def _on_contact_information_menuitem_activate(self, widget): + gajim.interface.roster.on_info(widget, self.contact, self.account) + + def _on_toggle_gpg_menuitem_activate(self, widget): + self._toggle_gpg() + + def _on_convert_to_gc_menuitem_activate(self, widget): + """ + User wants to invite some friends to chat + """ + dialogs.TransformChatToMUC(self.account, [self.contact.jid]) + + def _on_toggle_e2e_menuitem_activate(self, widget): + if self.session and self.session.enable_encryption: + # e2e was enabled, disable it + jid = str(self.session.jid) + thread_id = self.session.thread_id + + self.session.terminate_e2e() + + gajim.connections[self.account].delete_session(jid, thread_id) + + # presumably the user had a good reason to shut it off, so + # disable autonegotiation too + self.no_autonegotiation = True + else: + self.begin_e2e_negotiation() + + def begin_e2e_negotiation(self): + self.no_autonegotiation = True + + if not self.session: + fjid = self.contact.get_full_jid() + new_sess = gajim.connections[self.account].make_new_session(fjid, type_=self.type_id) + self.set_session(new_sess) + + self.session.negotiate_e2e(False) + + def got_connected(self): + ChatControlBase.got_connected(self) + # Refreshing contact + contact = gajim.contacts.get_contact_with_highest_priority( + self.account, self.contact.jid) + if isinstance(contact, GC_Contact): + contact = contact.as_contact() + if contact: + self.contact = contact + self.draw_banner() + + def update_status_display(self, name, uf_show, status): + """ + Print the contact's status and update the status/GPG image + """ + self.update_ui() + self.parent_win.redraw_tab(self) + + self.print_conversation(_('%(name)s is now %(status)s') % {'name': name, + 'status': uf_show}, 'status') + + if status: + self.print_conversation(' (', 'status', simple=True) + self.print_conversation('%s' % (status), 'status', simple=True) + self.print_conversation(')', 'status', simple=True) diff --git a/src/common/GnuPG.py b/src/common/GnuPG.py index 64abe75ce..a5c0c0559 100644 --- a/src/common/GnuPG.py +++ b/src/common/GnuPG.py @@ -28,217 +28,215 @@ from os import tmpfile from common import helpers if gajim.HAVE_GPG: - import GnuPGInterface + import GnuPGInterface - class GnuPG(GnuPGInterface.GnuPG): - def __init__(self, use_agent = False): - GnuPGInterface.GnuPG.__init__(self) - self.use_agent = use_agent - self._setup_my_options() - self.always_trust = False + class GnuPG(GnuPGInterface.GnuPG): + def __init__(self, use_agent = False): + GnuPGInterface.GnuPG.__init__(self) + self.use_agent = use_agent + self._setup_my_options() + self.always_trust = False - def _setup_my_options(self): - self.options.armor = 1 - self.options.meta_interactive = 0 - self.options.extra_args.append('--no-secmem-warning') - # disable photo viewer when verifying keys - self.options.extra_args.append('--verify-options') - self.options.extra_args.append('no-show-photo') - if self.use_agent: - self.options.extra_args.append('--use-agent') + def _setup_my_options(self): + self.options.armor = 1 + self.options.meta_interactive = 0 + self.options.extra_args.append('--no-secmem-warning') + # disable photo viewer when verifying keys + self.options.extra_args.append('--verify-options') + self.options.extra_args.append('no-show-photo') + if self.use_agent: + self.options.extra_args.append('--use-agent') - def _read_response(self, child_stdout): - # Internal method: reads all the output from GPG, taking notice - # only of lines that begin with the magic [GNUPG:] prefix. - # (See doc/DETAILS in the GPG distribution for info on GPG's - # output when --status-fd is specified.) - # - # Returns a dictionary, mapping GPG's keywords to the arguments - # for that keyword. + def _read_response(self, child_stdout): + # Internal method: reads all the output from GPG, taking notice + # only of lines that begin with the magic [GNUPG:] prefix. + # (See doc/DETAILS in the GPG distribution for info on GPG's + # output when --status-fd is specified.) + # + # Returns a dictionary, mapping GPG's keywords to the arguments + # for that keyword. - resp = {} - while True: - line = helpers.temp_failure_retry(child_stdout.readline) - if line == "": break - line = line.rstrip() - if line[0:9] == '[GNUPG:] ': - # Chop off the prefix - line = line[9:] - L = line.split(None, 1) - keyword = L[0] - if len(L) > 1: - resp[ keyword ] = L[1] - else: - resp[ keyword ] = "" - return resp + resp = {} + while True: + line = helpers.temp_failure_retry(child_stdout.readline) + if line == "": break + line = line.rstrip() + if line[0:9] == '[GNUPG:] ': + # Chop off the prefix + line = line[9:] + L = line.split(None, 1) + keyword = L[0] + if len(L) > 1: + resp[ keyword ] = L[1] + else: + resp[ keyword ] = "" + return resp - def encrypt(self, str_, recipients, always_trust=False): - self.options.recipients = recipients # a list! + def encrypt(self, str_, recipients, always_trust=False): + self.options.recipients = recipients # a list! - opt = ['--encrypt'] - if always_trust or self.always_trust: - opt.append('--always-trust') - proc = self.run(opt, create_fhs=['stdin', 'stdout', 'status', - 'stderr']) - proc.handles['stdin'].write(str_) - try: - proc.handles['stdin'].close() - except IOError: - pass + opt = ['--encrypt'] + if always_trust or self.always_trust: + opt.append('--always-trust') + proc = self.run(opt, create_fhs=['stdin', 'stdout', 'status', + 'stderr']) + proc.handles['stdin'].write(str_) + try: + proc.handles['stdin'].close() + except IOError: + pass - output = proc.handles['stdout'].read() - try: - proc.handles['stdout'].close() - except IOError: - pass + output = proc.handles['stdout'].read() + try: + proc.handles['stdout'].close() + except IOError: + pass - stat = proc.handles['status'] - resp = self._read_response(stat) - try: - proc.handles['status'].close() - except IOError: - pass + stat = proc.handles['status'] + resp = self._read_response(stat) + try: + proc.handles['status'].close() + except IOError: + pass - error = proc.handles['stderr'].read() - proc.handles['stderr'].close() + error = proc.handles['stderr'].read() + proc.handles['stderr'].close() - try: proc.wait() - except IOError: pass - if 'INV_RECP' in resp and resp['INV_RECP'].split()[0] == '10': - # unusable recipient "Key not trusted" - return '', 'NOT_TRUSTED' - if 'BEGIN_ENCRYPTION' in resp and 'END_ENCRYPTION' in resp: - # Encryption succeeded, even if there is output on stderr. Maybe - # verbose is on - error = '' - return self._stripHeaderFooter(output), helpers.decode_string(error) + try: proc.wait() + except IOError: pass + if 'INV_RECP' in resp and resp['INV_RECP'].split()[0] == '10': + # unusable recipient "Key not trusted" + return '', 'NOT_TRUSTED' + if 'BEGIN_ENCRYPTION' in resp and 'END_ENCRYPTION' in resp: + # Encryption succeeded, even if there is output on stderr. Maybe + # verbose is on + error = '' + return self._stripHeaderFooter(output), helpers.decode_string(error) - def decrypt(self, str_, keyID): - proc = self.run(['--decrypt', '-q', '-u %s'%keyID], create_fhs=['stdin', 'stdout']) - enc = self._addHeaderFooter(str_, 'MESSAGE') - proc.handles['stdin'].write(enc) - proc.handles['stdin'].close() + def decrypt(self, str_, keyID): + proc = self.run(['--decrypt', '-q', '-u %s'%keyID], create_fhs=['stdin', 'stdout']) + enc = self._addHeaderFooter(str_, 'MESSAGE') + proc.handles['stdin'].write(enc) + proc.handles['stdin'].close() - output = proc.handles['stdout'].read() - proc.handles['stdout'].close() + output = proc.handles['stdout'].read() + proc.handles['stdout'].close() - try: proc.wait() - except IOError: pass - return output + try: proc.wait() + except IOError: pass + return output - def sign(self, str_, keyID): - proc = self.run(['-b', '-u %s'%keyID], create_fhs=['stdin', 'stdout', 'status', 'stderr']) - proc.handles['stdin'].write(str_) - try: - proc.handles['stdin'].close() - except IOError: - pass + def sign(self, str_, keyID): + proc = self.run(['-b', '-u %s'%keyID], create_fhs=['stdin', 'stdout', 'status', 'stderr']) + proc.handles['stdin'].write(str_) + try: + proc.handles['stdin'].close() + except IOError: + pass - output = proc.handles['stdout'].read() - try: - proc.handles['stdout'].close() - proc.handles['stderr'].close() - except IOError: - pass + output = proc.handles['stdout'].read() + try: + proc.handles['stdout'].close() + proc.handles['stderr'].close() + except IOError: + pass - stat = proc.handles['status'] - resp = self._read_response(stat) - try: - proc.handles['status'].close() - except IOError: - pass + stat = proc.handles['status'] + resp = self._read_response(stat) + try: + proc.handles['status'].close() + except IOError: + pass - try: proc.wait() - except IOError: pass - if 'GOOD_PASSPHRASE' in resp or 'SIG_CREATED' in resp: - return self._stripHeaderFooter(output) - if 'KEYEXPIRED' in resp: - return 'KEYEXPIRED' - return 'BAD_PASSPHRASE' + try: proc.wait() + except IOError: pass + if 'GOOD_PASSPHRASE' in resp or 'SIG_CREATED' in resp: + return self._stripHeaderFooter(output) + if 'KEYEXPIRED' in resp: + return 'KEYEXPIRED' + return 'BAD_PASSPHRASE' - def verify(self, str_, sign): - if str_ is None: - return '' - f = tmpfile() - fd = f.fileno() - f.write(str_) - f.seek(0) + def verify(self, str_, sign): + if str_ is None: + return '' + f = tmpfile() + fd = f.fileno() + f.write(str_) + f.seek(0) - proc = self.run(['--verify', '--enable-special-filenames', '-', '-&%s'%fd], create_fhs=['stdin', 'status', 'stderr']) + proc = self.run(['--verify', '--enable-special-filenames', '-', '-&%s'%fd], create_fhs=['stdin', 'status', 'stderr']) - f.close() - sign = self._addHeaderFooter(sign, 'SIGNATURE') - proc.handles['stdin'].write(sign) - proc.handles['stdin'].close() - proc.handles['stderr'].close() + f.close() + sign = self._addHeaderFooter(sign, 'SIGNATURE') + proc.handles['stdin'].write(sign) + proc.handles['stdin'].close() + proc.handles['stderr'].close() - stat = proc.handles['status'] - resp = self._read_response(stat) - proc.handles['status'].close() + stat = proc.handles['status'] + resp = self._read_response(stat) + proc.handles['status'].close() - try: proc.wait() - except IOError: pass + try: proc.wait() + except IOError: pass - keyid = '' - if 'GOODSIG' in resp: - keyid = resp['GOODSIG'].split()[0] - return keyid + keyid = '' + if 'GOODSIG' in resp: + keyid = resp['GOODSIG'].split()[0] + return keyid - def get_keys(self, secret = False): - if secret: - opt = '--list-secret-keys' - else: - opt = '--list-keys' - proc = self.run(['--with-colons', opt], - create_fhs=['stdout']) - output = proc.handles['stdout'].read() - proc.handles['stdout'].close() + def get_keys(self, secret = False): + if secret: + opt = '--list-secret-keys' + else: + opt = '--list-keys' + proc = self.run(['--with-colons', opt], + create_fhs=['stdout']) + output = proc.handles['stdout'].read() + proc.handles['stdout'].close() - try: proc.wait() - except IOError: pass + try: proc.wait() + except IOError: pass - keys = {} - lines = output.split('\n') - for line in lines: - sline = line.split(':') - if (sline[0] == 'sec' and secret) or \ - (sline[0] == 'pub' and not secret): - # decode escaped chars - name = eval('"' + sline[9].replace('"', '\\"') + '"') - # make it unicode instance - keys[sline[4][8:]] = helpers.decode_string(name) - return keys + keys = {} + lines = output.split('\n') + for line in lines: + sline = line.split(':') + if (sline[0] == 'sec' and secret) or \ + (sline[0] == 'pub' and not secret): + # decode escaped chars + name = eval('"' + sline[9].replace('"', '\\"') + '"') + # make it unicode instance + keys[sline[4][8:]] = helpers.decode_string(name) + return keys - def get_secret_keys(self): - return self.get_keys(True) + def get_secret_keys(self): + return self.get_keys(True) - def _stripHeaderFooter(self, data): - """ - Remove header and footer from data - """ - if not data: return '' - lines = data.split('\n') - while lines[0] != '': - lines.remove(lines[0]) - while lines[0] == '': - lines.remove(lines[0]) - i = 0 - for line in lines: - if line: - if line[0] == '-': break - i = i+1 - line = '\n'.join(lines[0:i]) - return line + def _stripHeaderFooter(self, data): + """ + Remove header and footer from data + """ + if not data: return '' + lines = data.split('\n') + while lines[0] != '': + lines.remove(lines[0]) + while lines[0] == '': + lines.remove(lines[0]) + i = 0 + for line in lines: + if line: + if line[0] == '-': break + i = i+1 + line = '\n'.join(lines[0:i]) + return line - def _addHeaderFooter(self, data, type_): - """ - Add header and footer from data - """ - out = "-----BEGIN PGP %s-----\n" % type_ - out = out + "Version: PGP\n" - out = out + "\n" - out = out + data + "\n" - out = out + "-----END PGP %s-----\n" % type_ - return out - -# vim: set ts=3: + def _addHeaderFooter(self, data, type_): + """ + Add header and footer from data + """ + out = "-----BEGIN PGP %s-----\n" % type_ + out = out + "Version: PGP\n" + out = out + "\n" + out = out + data + "\n" + out = out + "-----END PGP %s-----\n" % type_ + return out diff --git a/src/common/GnuPGInterface.py b/src/common/GnuPGInterface.py index aa4a9eeee..f7f5cb522 100644 --- a/src/common/GnuPGInterface.py +++ b/src/common/GnuPGInterface.py @@ -671,5 +671,3 @@ GnuPGInterface = GnuPG if __name__ == '__main__': _run_doctests() - -# vim: se ts=3: diff --git a/src/common/__init__.py b/src/common/__init__.py index de18c8159..e69de29bb 100644 --- a/src/common/__init__.py +++ b/src/common/__init__.py @@ -1,2 +0,0 @@ - -# vim: se ts=3: diff --git a/src/common/account.py b/src/common/account.py index c8a4c4604..976b6bf43 100644 --- a/src/common/account.py +++ b/src/common/account.py @@ -20,16 +20,16 @@ class Account(object): - def __init__(self, name, contacts, gc_contacts): - self.name = name - self.contacts = contacts - self.gc_contacts = gc_contacts + def __init__(self, name, contacts, gc_contacts): + self.name = name + self.contacts = contacts + self.gc_contacts = gc_contacts - def change_contact_jid(self, old_jid, new_jid): - self.contacts.change_contact_jid(old_jid, new_jid) + def change_contact_jid(self, old_jid, new_jid): + self.contacts.change_contact_jid(old_jid, new_jid) - def __repr__(self): - return self.name + def __repr__(self): + return self.name - def __hash__(self): - return hash(self.name) + def __hash__(self): + return hash(self.name) diff --git a/src/common/atom.py b/src/common/atom.py index 689df5cee..12101b886 100644 --- a/src/common/atom.py +++ b/src/common/atom.py @@ -33,146 +33,144 @@ import xmpp import time class PersonConstruct(xmpp.Node, object): - """ - Not used for now, as we don't need authors/contributors in pubsub.com feeds. - They rarely exist there - """ + """ + Not used for now, as we don't need authors/contributors in pubsub.com feeds. + They rarely exist there + """ - def __init__(self, node): - ''' Create person construct from node. ''' - xmpp.Node.__init__(self, node=node) + def __init__(self, node): + ''' Create person construct from node. ''' + xmpp.Node.__init__(self, node=node) - def get_name(self): - return self.getTagData('name') + def get_name(self): + return self.getTagData('name') - name = property(get_name, None, None, - '''Conveys a human-readable name for the person. Should not be None, - although some badly generated atom feeds don't put anything here - (this is non-standard behavior, still pubsub.com sometimes does that.)''') + name = property(get_name, None, None, + '''Conveys a human-readable name for the person. Should not be None, + although some badly generated atom feeds don't put anything here + (this is non-standard behavior, still pubsub.com sometimes does that.)''') - def get_uri(self): - return self.getTagData('uri') + def get_uri(self): + return self.getTagData('uri') - uri = property(get_uri, None, None, - '''Conveys an IRI associated with the person. Might be None when not set.''') + uri = property(get_uri, None, None, + '''Conveys an IRI associated with the person. Might be None when not set.''') - def get_email(self): - return self.getTagData('email') + def get_email(self): + return self.getTagData('email') - email = property(get_email, None, None, - '''Conveys an e-mail address associated with the person. Might be None when - not set.''') + email = property(get_email, None, None, + '''Conveys an e-mail address associated with the person. Might be None when + not set.''') class Entry(xmpp.Node, object): - def __init__(self, node=None): - xmpp.Node.__init__(self, 'entry', node=node) + def __init__(self, node=None): + xmpp.Node.__init__(self, 'entry', node=node) - def __repr__(self): - return '' % self.getAttr('id') + def __repr__(self): + return '' % self.getAttr('id') class OldEntry(xmpp.Node, object): - """ - Parser for feeds from pubsub.com. They use old Atom 0.3 format with their - extensions - """ + """ + Parser for feeds from pubsub.com. They use old Atom 0.3 format with their + extensions + """ - def __init__(self, node=None): - ''' Create new Atom 0.3 entry object. ''' - xmpp.Node.__init__(self, 'entry', node=node) + def __init__(self, node=None): + ''' Create new Atom 0.3 entry object. ''' + xmpp.Node.__init__(self, 'entry', node=node) - def __repr__(self): - return '' % self.getAttr('id') + def __repr__(self): + return '' % self.getAttr('id') - def get_feed_title(self): - """ - Return title of feed, where the entry was created. The result is the feed - name concatenated with source-feed title - """ - if self.parent is not None: - main_feed = self.parent.getTagData('title') - else: - main_feed = None + def get_feed_title(self): + """ + Return title of feed, where the entry was created. The result is the feed + name concatenated with source-feed title + """ + if self.parent is not None: + main_feed = self.parent.getTagData('title') + else: + main_feed = None - if self.getTag('feed') is not None: - source_feed = self.getTag('feed').getTagData('title') - else: - source_feed = None + if self.getTag('feed') is not None: + source_feed = self.getTag('feed').getTagData('title') + else: + source_feed = None - if main_feed is not None and source_feed is not None: - return u'%s: %s' % (main_feed, source_feed) - elif main_feed is not None: - return main_feed - elif source_feed is not None: - return source_feed - else: - return u'' + if main_feed is not None and source_feed is not None: + return u'%s: %s' % (main_feed, source_feed) + elif main_feed is not None: + return main_feed + elif source_feed is not None: + return source_feed + else: + return u'' - feed_title = property(get_feed_title, None, None, - ''' Title of feed. It is built from entry''s original feed title and title of feed - which delivered this entry. ''') + feed_title = property(get_feed_title, None, None, + ''' Title of feed. It is built from entry''s original feed title and title of feed + which delivered this entry. ''') - def get_feed_link(self): - """ - Get source link - """ - try: - return self.getTag('feed').getTags('link',{'rel':'alternate'})[1].getData() - except Exception: - return None + def get_feed_link(self): + """ + Get source link + """ + try: + return self.getTag('feed').getTags('link', {'rel':'alternate'})[1].getData() + except Exception: + return None - feed_link = property(get_feed_link, None, None, - ''' Link to main webpage of the feed. ''') + feed_link = property(get_feed_link, None, None, + ''' Link to main webpage of the feed. ''') - def get_title(self): - """ - Get an entry's title - """ - return self.getTagData('title') + def get_title(self): + """ + Get an entry's title + """ + return self.getTagData('title') - title = property(get_title, None, None, - ''' Entry's title. ''') + title = property(get_title, None, None, + ''' Entry's title. ''') - def get_uri(self): - """ - Get the uri the entry points to (entry's first link element with - rel='alternate' or without rel attribute) - """ - for element in self.getTags('link'): - if 'rel' in element.attrs and element.attrs['rel']!='alternate': continue - try: - return element.attrs['href'] - except AttributeError: - pass - return None + def get_uri(self): + """ + Get the uri the entry points to (entry's first link element with + rel='alternate' or without rel attribute) + """ + for element in self.getTags('link'): + if 'rel' in element.attrs and element.attrs['rel']!='alternate': continue + try: + return element.attrs['href'] + except AttributeError: + pass + return None - uri = property(get_uri, None, None, - ''' URI that is pointed by the entry. ''') + uri = property(get_uri, None, None, + ''' URI that is pointed by the entry. ''') - def get_updated(self): - """ - Get the time the entry was updated last time + def get_updated(self): + """ + Get the time the entry was updated last time - This should be standarized, but pubsub.com sends it in human-readable - format. We won't try to parse it. (Atom 0.3 uses the word «modified» for - that). + This should be standarized, but pubsub.com sends it in human-readable + format. We won't try to parse it. (Atom 0.3 uses the word «modified» for + that). - If there's no time given in the entry, we try with - and elements. - """ - for name in ('updated', 'modified', 'published', 'issued'): - date = self.getTagData(name) - if date is not None: break + If there's no time given in the entry, we try with + and elements. + """ + for name in ('updated', 'modified', 'published', 'issued'): + date = self.getTagData(name) + if date is not None: break - if date is None: - # it is not in the standard format - return time.asctime() + if date is None: + # it is not in the standard format + return time.asctime() - return date + return date - updated = property(get_updated, None, None, - ''' Last significant modification time. ''') + updated = property(get_updated, None, None, + ''' Last significant modification time. ''') - feed_tagline = u'' - -# vim: se ts=3: + feed_tagline = u'' diff --git a/src/common/caps_cache.py b/src/common/caps_cache.py index 603c348cb..23c54c279 100644 --- a/src/common/caps_cache.py +++ b/src/common/caps_cache.py @@ -38,10 +38,10 @@ import logging log = logging.getLogger('gajim.c.caps_cache') from common.xmpp import (NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION, NS_CHATSTATES, - NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, NS_CAPS) + NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, NS_CAPS) # Features where we cannot safely assume that the other side supports them FEATURE_BLACKLIST = [NS_CHATSTATES, NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION, - NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO] + NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO] # Query entry status codes NEW = 0 @@ -55,109 +55,109 @@ FAKED = 3 # allow NullClientCaps to behave as it has a cached item capscache = None def initialize(logger): - """ - Initialize this module - """ - global capscache - capscache = CapsCache(logger) + """ + Initialize this module + """ + global capscache + capscache = CapsCache(logger) def client_supports(client_caps, requested_feature): - lookup_item = client_caps.get_cache_lookup_strategy() - cache_item = lookup_item(capscache) + lookup_item = client_caps.get_cache_lookup_strategy() + cache_item = lookup_item(capscache) + + supported_features = cache_item.features + if requested_feature in supported_features: + return True + elif not supported_features and cache_item.status in (NEW, QUERIED, FAKED): + # assume feature is supported, if we don't know yet, what the client + # is capable of + return requested_feature not in FEATURE_BLACKLIST + else: + return False - supported_features = cache_item.features - if requested_feature in supported_features: - return True - elif not supported_features and cache_item.status in (NEW, QUERIED, FAKED): - # assume feature is supported, if we don't know yet, what the client - # is capable of - return requested_feature not in FEATURE_BLACKLIST - else: - return False - def create_suitable_client_caps(node, caps_hash, hash_method): - """ - Create and return a suitable ClientCaps object for the given node, - caps_hash, hash_method combination. - """ - if not node or not caps_hash: - # improper caps, ignore client capabilities. - client_caps = NullClientCaps() - elif not hash_method: - client_caps = OldClientCaps(caps_hash, node) - else: - client_caps = ClientCaps(caps_hash, node, hash_method) - return client_caps + """ + Create and return a suitable ClientCaps object for the given node, + caps_hash, hash_method combination. + """ + if not node or not caps_hash: + # improper caps, ignore client capabilities. + client_caps = NullClientCaps() + elif not hash_method: + client_caps = OldClientCaps(caps_hash, node) + else: + client_caps = ClientCaps(caps_hash, node, hash_method) + return client_caps def compute_caps_hash(identities, features, dataforms=[], hash_method='sha-1'): - """ - Compute caps hash according to XEP-0115, V1.5 + """ + Compute caps hash according to XEP-0115, V1.5 - dataforms are xmpp.DataForms objects as common.dataforms don't allow several - values without a field type list-multi - """ - def sort_identities_func(i1, i2): - cat1 = i1['category'] - cat2 = i2['category'] - if cat1 < cat2: - return -1 - if cat1 > cat2: - return 1 - type1 = i1.get('type', '') - type2 = i2.get('type', '') - if type1 < type2: - return -1 - if type1 > type2: - return 1 - lang1 = i1.get('xml:lang', '') - lang2 = i2.get('xml:lang', '') - if lang1 < lang2: - return -1 - if lang1 > lang2: - return 1 - return 0 + dataforms are xmpp.DataForms objects as common.dataforms don't allow several + values without a field type list-multi + """ + def sort_identities_func(i1, i2): + cat1 = i1['category'] + cat2 = i2['category'] + if cat1 < cat2: + return -1 + if cat1 > cat2: + return 1 + type1 = i1.get('type', '') + type2 = i2.get('type', '') + if type1 < type2: + return -1 + if type1 > type2: + return 1 + lang1 = i1.get('xml:lang', '') + lang2 = i2.get('xml:lang', '') + if lang1 < lang2: + return -1 + if lang1 > lang2: + return 1 + return 0 - def sort_dataforms_func(d1, d2): - f1 = d1.getField('FORM_TYPE') - f2 = d2.getField('FORM_TYPE') - if f1 and f2 and (f1.getValue() < f2.getValue()): - return -1 - return 1 + def sort_dataforms_func(d1, d2): + f1 = d1.getField('FORM_TYPE') + f2 = d2.getField('FORM_TYPE') + if f1 and f2 and (f1.getValue() < f2.getValue()): + return -1 + return 1 - S = '' - identities.sort(cmp=sort_identities_func) - for i in identities: - c = i['category'] - type_ = i.get('type', '') - lang = i.get('xml:lang', '') - name = i.get('name', '') - S += '%s/%s/%s/%s<' % (c, type_, lang, name) - features.sort() - for f in features: - S += '%s<' % f - dataforms.sort(cmp=sort_dataforms_func) - for dataform in dataforms: - # fields indexed by var - fields = {} - for f in dataform.getChildren(): - fields[f.getVar()] = f - form_type = fields.get('FORM_TYPE') - if form_type: - S += form_type.getValue() + '<' - del fields['FORM_TYPE'] - for var in sorted(fields.keys()): - S += '%s<' % var - values = sorted(fields[var].getValues()) - for value in values: - S += '%s<' % value + S = '' + identities.sort(cmp=sort_identities_func) + for i in identities: + c = i['category'] + type_ = i.get('type', '') + lang = i.get('xml:lang', '') + name = i.get('name', '') + S += '%s/%s/%s/%s<' % (c, type_, lang, name) + features.sort() + for f in features: + S += '%s<' % f + dataforms.sort(cmp=sort_dataforms_func) + for dataform in dataforms: + # fields indexed by var + fields = {} + for f in dataform.getChildren(): + fields[f.getVar()] = f + form_type = fields.get('FORM_TYPE') + if form_type: + S += form_type.getValue() + '<' + del fields['FORM_TYPE'] + for var in sorted(fields.keys()): + S += '%s<' % var + values = sorted(fields[var].getValues()) + for value in values: + S += '%s<' % value - if hash_method == 'sha-1': - hash_ = hashlib.sha1(S) - elif hash_method == 'md5': - hash_ = hashlib.md5(S) - else: - return '' - return base64.b64encode(hash_.digest()) + if hash_method == 'sha-1': + hash_ = hashlib.sha1(S) + elif hash_method == 'md5': + hash_ = hashlib.md5(S) + else: + return '' + return base64.b64encode(hash_.digest()) ################################################################################ @@ -165,241 +165,239 @@ def compute_caps_hash(identities, features, dataforms=[], hash_method='sha-1'): ################################################################################ class AbstractClientCaps(object): - """ - Base class representing a client and its capabilities as advertised by a - caps tag in a presence - """ - def __init__(self, caps_hash, node): - self._hash = caps_hash - self._node = node + """ + Base class representing a client and its capabilities as advertised by a + caps tag in a presence + """ + def __init__(self, caps_hash, node): + self._hash = caps_hash + self._node = node - def get_discover_strategy(self): - return self._discover + def get_discover_strategy(self): + return self._discover - def _discover(self, connection, jid): - """ - To be implemented by subclassess - """ - raise NotImplementedError + def _discover(self, connection, jid): + """ + To be implemented by subclassess + """ + raise NotImplementedError - def get_cache_lookup_strategy(self): - return self._lookup_in_cache + def get_cache_lookup_strategy(self): + return self._lookup_in_cache - def _lookup_in_cache(self, caps_cache): - """ - To be implemented by subclassess - """ - raise NotImplementedError + def _lookup_in_cache(self, caps_cache): + """ + To be implemented by subclassess + """ + raise NotImplementedError - def get_hash_validation_strategy(self): - return self._is_hash_valid + def get_hash_validation_strategy(self): + return self._is_hash_valid - def _is_hash_valid(self, identities, features, dataforms): - """ - To be implemented by subclassess - """ - raise NotImplementedError + def _is_hash_valid(self, identities, features, dataforms): + """ + To be implemented by subclassess + """ + raise NotImplementedError class ClientCaps(AbstractClientCaps): - """ - The current XEP-115 implementation - """ - def __init__(self, caps_hash, node, hash_method): - AbstractClientCaps.__init__(self, caps_hash, node) - assert hash_method != 'old' - self._hash_method = hash_method + """ + The current XEP-115 implementation + """ + def __init__(self, caps_hash, node, hash_method): + AbstractClientCaps.__init__(self, caps_hash, node) + assert hash_method != 'old' + self._hash_method = hash_method - def _lookup_in_cache(self, caps_cache): - return caps_cache[(self._hash_method, self._hash)] + def _lookup_in_cache(self, caps_cache): + return caps_cache[(self._hash_method, self._hash)] - def _discover(self, connection, jid): - connection.discoverInfo(jid, '%s#%s' % (self._node, self._hash)) + def _discover(self, connection, jid): + connection.discoverInfo(jid, '%s#%s' % (self._node, self._hash)) - def _is_hash_valid(self, identities, features, dataforms): - computed_hash = compute_caps_hash(identities, features, - dataforms=dataforms, hash_method=self._hash_method) - return computed_hash == self._hash + def _is_hash_valid(self, identities, features, dataforms): + computed_hash = compute_caps_hash(identities, features, + dataforms=dataforms, hash_method=self._hash_method) + return computed_hash == self._hash class OldClientCaps(AbstractClientCaps): - """ - Old XEP-115 implemtation. Kept around for background competability - """ - def __init__(self, caps_hash, node): - AbstractClientCaps.__init__(self, caps_hash, node) + """ + Old XEP-115 implemtation. Kept around for background competability + """ + def __init__(self, caps_hash, node): + AbstractClientCaps.__init__(self, caps_hash, node) - def _lookup_in_cache(self, caps_cache): - return caps_cache[('old', self._node + '#' + self._hash)] + def _lookup_in_cache(self, caps_cache): + return caps_cache[('old', self._node + '#' + self._hash)] - def _discover(self, connection, jid): - connection.discoverInfo(jid) + def _discover(self, connection, jid): + connection.discoverInfo(jid) - def _is_hash_valid(self, identities, features, dataforms): - return True + def _is_hash_valid(self, identities, features, dataforms): + return True class NullClientCaps(AbstractClientCaps): - """ - This is a NULL-Object to streamline caps handling if a client has not - advertised any caps or has advertised them in an improper way + """ + This is a NULL-Object to streamline caps handling if a client has not + advertised any caps or has advertised them in an improper way - Assumes (almost) everything is supported. - """ - _instance = None - def __new__(cls, *args, **kwargs): - """ - Make it a singleton. - """ - if not cls._instance: - cls._instance = super(NullClientCaps, cls).__new__( - cls, *args, **kwargs) - return cls._instance + Assumes (almost) everything is supported. + """ + _instance = None + def __new__(cls, *args, **kwargs): + """ + Make it a singleton. + """ + if not cls._instance: + cls._instance = super(NullClientCaps, cls).__new__( + cls, *args, **kwargs) + return cls._instance - def __init__(self): - AbstractClientCaps.__init__(self, None, None) + def __init__(self): + AbstractClientCaps.__init__(self, None, None) - def _lookup_in_cache(self, caps_cache): - # lookup something which does not exist to get a new CacheItem created - cache_item = caps_cache[('dummy', '')] - # Mark the item as cached so that protocol/caps.py does not update it - cache_item.status = FAKED - return cache_item + def _lookup_in_cache(self, caps_cache): + # lookup something which does not exist to get a new CacheItem created + cache_item = caps_cache[('dummy', '')] + # Mark the item as cached so that protocol/caps.py does not update it + cache_item.status = FAKED + return cache_item - def _discover(self, connection, jid): - pass + def _discover(self, connection, jid): + pass - def _is_hash_valid(self, identities, features, dataforms): - return False + def _is_hash_valid(self, identities, features, dataforms): + return False class CapsCache(object): - """ - This object keeps the mapping between caps data and real disco features they - represent, and provides simple way to query that info - """ - def __init__(self, logger=None): - # our containers: - # __cache is a dictionary mapping: pair of hash method and hash maps - # to CapsCacheItem object - # __CacheItem is a class that stores data about particular - # client (hash method/hash pair) - self.__cache = {} + """ + This object keeps the mapping between caps data and real disco features they + represent, and provides simple way to query that info + """ + def __init__(self, logger=None): + # our containers: + # __cache is a dictionary mapping: pair of hash method and hash maps + # to CapsCacheItem object + # __CacheItem is a class that stores data about particular + # client (hash method/hash pair) + self.__cache = {} - class CacheItem(object): - # __names is a string cache; every string long enough is given - # another object, and we will have plenty of identical long - # strings. therefore we can cache them - __names = {} + class CacheItem(object): + # __names is a string cache; every string long enough is given + # another object, and we will have plenty of identical long + # strings. therefore we can cache them + __names = {} - def __init__(self, hash_method, hash_, logger): - # cached into db - self.hash_method = hash_method - self.hash = hash_ - self._features = [] - self._identities = [] - self._logger = logger + def __init__(self, hash_method, hash_, logger): + # cached into db + self.hash_method = hash_method + self.hash = hash_ + self._features = [] + self._identities = [] + self._logger = logger - self.status = NEW - self._recently_seen = False + self.status = NEW + self._recently_seen = False - def _get_features(self): - return self._features + def _get_features(self): + return self._features - def _set_features(self, value): - self._features = [] - for feature in value: - self._features.append(self.__names.setdefault(feature, feature)) + def _set_features(self, value): + self._features = [] + for feature in value: + self._features.append(self.__names.setdefault(feature, feature)) - features = property(_get_features, _set_features) + features = property(_get_features, _set_features) - def _get_identities(self): - list_ = [] - for i in self._identities: - # transforms it back in a dict - d = dict() - d['category'] = i[0] - if i[1]: - d['type'] = i[1] - if i[2]: - d['xml:lang'] = i[2] - if i[3]: - d['name'] = i[3] - list_.append(d) - return list_ + def _get_identities(self): + list_ = [] + for i in self._identities: + # transforms it back in a dict + d = dict() + d['category'] = i[0] + if i[1]: + d['type'] = i[1] + if i[2]: + d['xml:lang'] = i[2] + if i[3]: + d['name'] = i[3] + list_.append(d) + return list_ - def _set_identities(self, value): - self._identities = [] - for identity in value: - # dict are not hashable, so transform it into a tuple - t = (identity['category'], identity.get('type'), - identity.get('xml:lang'), identity.get('name')) - self._identities.append(self.__names.setdefault(t, t)) + def _set_identities(self, value): + self._identities = [] + for identity in value: + # dict are not hashable, so transform it into a tuple + t = (identity['category'], identity.get('type'), + identity.get('xml:lang'), identity.get('name')) + self._identities.append(self.__names.setdefault(t, t)) - identities = property(_get_identities, _set_identities) + identities = property(_get_identities, _set_identities) - def set_and_store(self, identities, features): - self.identities = identities - self.features = features - self._logger.add_caps_entry(self.hash_method, self.hash, - identities, features) - self.status = CACHED + def set_and_store(self, identities, features): + self.identities = identities + self.features = features + self._logger.add_caps_entry(self.hash_method, self.hash, + identities, features) + self.status = CACHED - def update_last_seen(self): - if not self._recently_seen: - self._recently_seen = True - self._logger.update_caps_time(self.hash_method, self.hash) - - def is_valid(self): - """ - Returns True if identities and features for this cache item - are known. - """ - return self.status in (CACHED, FAKED) + def update_last_seen(self): + if not self._recently_seen: + self._recently_seen = True + self._logger.update_caps_time(self.hash_method, self.hash) - self.__CacheItem = CacheItem - self.logger = logger + def is_valid(self): + """ + Returns True if identities and features for this cache item + are known. + """ + return self.status in (CACHED, FAKED) - def initialize_from_db(self): - self._remove_outdated_caps() - for hash_method, hash_, identities, features in \ - self.logger.iter_caps_data(): - x = self[(hash_method, hash_)] - x.identities = identities - x.features = features - x.status = CACHED + self.__CacheItem = CacheItem + self.logger = logger - def _remove_outdated_caps(self): - """ - Remove outdated values from the db - """ - self.logger.clean_caps_table() + def initialize_from_db(self): + self._remove_outdated_caps() + for hash_method, hash_, identities, features in \ + self.logger.iter_caps_data(): + x = self[(hash_method, hash_)] + x.identities = identities + x.features = features + x.status = CACHED - def __getitem__(self, caps): - if caps in self.__cache: - return self.__cache[caps] + def _remove_outdated_caps(self): + """ + Remove outdated values from the db + """ + self.logger.clean_caps_table() - hash_method, hash_ = caps + def __getitem__(self, caps): + if caps in self.__cache: + return self.__cache[caps] - x = self.__CacheItem(hash_method, hash_, self.logger) - self.__cache[(hash_method, hash_)] = x - return x + hash_method, hash_ = caps - def query_client_of_jid_if_unknown(self, connection, jid, client_caps): - """ - Start a disco query to determine caps (node, ver, exts). Won't query if - the data is already in cache - """ - lookup_cache_item = client_caps.get_cache_lookup_strategy() - q = lookup_cache_item(self) + x = self.__CacheItem(hash_method, hash_, self.logger) + self.__cache[(hash_method, hash_)] = x + return x - if q.status == NEW: - # do query for bare node+hash pair - # this will create proper object - q.status = QUERIED - discover = client_caps.get_discover_strategy() - discover(connection, jid) - else: - q.update_last_seen() + def query_client_of_jid_if_unknown(self, connection, jid, client_caps): + """ + Start a disco query to determine caps (node, ver, exts). Won't query if + the data is already in cache + """ + lookup_cache_item = client_caps.get_cache_lookup_strategy() + q = lookup_cache_item(self) -# vim: se ts=3: + if q.status == NEW: + # do query for bare node+hash pair + # this will create proper object + q.status = QUERIED + discover = client_caps.get_discover_strategy() + discover(connection, jid) + else: + q.update_last_seen() diff --git a/src/common/check_paths.py b/src/common/check_paths.py index 273d05491..5b18ac480 100644 --- a/src/common/check_paths.py +++ b/src/common/check_paths.py @@ -34,313 +34,311 @@ import logger import sqlite3 as sqlite def create_log_db(): - print _('creating logs database') - con = sqlite.connect(logger.LOG_DB_PATH) - os.chmod(logger.LOG_DB_PATH, 0600) # rw only for us - cur = con.cursor() - # create the tables - # kind can be - # status, gcstatus, gc_msg, (we only recv for those 3), - # single_msg_recv, chat_msg_recv, chat_msg_sent, single_msg_sent - # to meet all our needs - # logs.jid_id --> jids.jid_id but Sqlite doesn't do FK etc so it's done in python code - # jids.jid text column will be JID if TC-related, room_jid if GC-related, - # ROOM_JID/nick if pm-related. - # also check optparser.py, which updates databases on gajim updates - cur.executescript( - ''' - CREATE TABLE jids( - jid_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, - jid TEXT UNIQUE, - type INTEGER - ); + print _('creating logs database') + con = sqlite.connect(logger.LOG_DB_PATH) + os.chmod(logger.LOG_DB_PATH, 0600) # rw only for us + cur = con.cursor() + # create the tables + # kind can be + # status, gcstatus, gc_msg, (we only recv for those 3), + # single_msg_recv, chat_msg_recv, chat_msg_sent, single_msg_sent + # to meet all our needs + # logs.jid_id --> jids.jid_id but Sqlite doesn't do FK etc so it's done in python code + # jids.jid text column will be JID if TC-related, room_jid if GC-related, + # ROOM_JID/nick if pm-related. + # also check optparser.py, which updates databases on gajim updates + cur.executescript( + ''' + CREATE TABLE jids( + jid_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, + jid TEXT UNIQUE, + type INTEGER + ); - CREATE TABLE unread_messages( - message_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, - jid_id INTEGER, - shown BOOLEAN default 0 - ); + CREATE TABLE unread_messages( + message_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, + jid_id INTEGER, + shown BOOLEAN default 0 + ); - CREATE INDEX idx_unread_messages_jid_id ON unread_messages (jid_id); + CREATE INDEX idx_unread_messages_jid_id ON unread_messages (jid_id); - CREATE TABLE logs( - log_line_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, - jid_id INTEGER, - contact_name TEXT, - time INTEGER, - kind INTEGER, - show INTEGER, - message TEXT, - subject TEXT - ); + CREATE TABLE logs( + log_line_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, + jid_id INTEGER, + contact_name TEXT, + time INTEGER, + kind INTEGER, + show INTEGER, + message TEXT, + subject TEXT + ); - CREATE INDEX idx_logs_jid_id_time ON logs (jid_id, time DESC); - ''' - ) + CREATE INDEX idx_logs_jid_id_time ON logs (jid_id, time DESC); + ''' + ) - con.commit() - con.close() + con.commit() + con.close() def create_cache_db(): - print _('creating cache database') - con = sqlite.connect(logger.CACHE_DB_PATH) - os.chmod(logger.CACHE_DB_PATH, 0600) # rw only for us - cur = con.cursor() - cur.executescript( - ''' - CREATE TABLE transports_cache ( - transport TEXT UNIQUE, - type INTEGER - ); + print _('creating cache database') + con = sqlite.connect(logger.CACHE_DB_PATH) + os.chmod(logger.CACHE_DB_PATH, 0600) # rw only for us + cur = con.cursor() + cur.executescript( + ''' + CREATE TABLE transports_cache ( + transport TEXT UNIQUE, + type INTEGER + ); - CREATE TABLE caps_cache ( - hash_method TEXT, - hash TEXT, - data BLOB, - last_seen INTEGER); + CREATE TABLE caps_cache ( + hash_method TEXT, + hash TEXT, + data BLOB, + last_seen INTEGER); - CREATE TABLE rooms_last_message_time( - jid_id INTEGER PRIMARY KEY UNIQUE, - time INTEGER - ); + CREATE TABLE rooms_last_message_time( + jid_id INTEGER PRIMARY KEY UNIQUE, + time INTEGER + ); - CREATE TABLE IF NOT EXISTS roster_entry( - account_jid_id INTEGER, - jid_id INTEGER, - name TEXT, - subscription INTEGER, - ask BOOLEAN, - PRIMARY KEY (account_jid_id, jid_id) - ); + CREATE TABLE IF NOT EXISTS roster_entry( + account_jid_id INTEGER, + jid_id INTEGER, + name TEXT, + subscription INTEGER, + ask BOOLEAN, + PRIMARY KEY (account_jid_id, jid_id) + ); - CREATE TABLE IF NOT EXISTS roster_group( - account_jid_id INTEGER, - jid_id INTEGER, - group_name TEXT, - PRIMARY KEY (account_jid_id, jid_id, group_name) - ); - ''' - ) + CREATE TABLE IF NOT EXISTS roster_group( + account_jid_id INTEGER, + jid_id INTEGER, + group_name TEXT, + PRIMARY KEY (account_jid_id, jid_id, group_name) + ); + ''' + ) - con.commit() - con.close() + con.commit() + con.close() def split_db(): - print 'spliting database' - if os.name == 'nt': - try: - OLD_LOG_DB_FOLDER = os.path.join(fse(os.environ[u'appdata']), u'Gajim') - except KeyError: - OLD_LOG_DB_FOLDER = u'.' - else: - OLD_LOG_DB_FOLDER = os.path.expanduser(u'~/.gajim') + print 'spliting database' + if os.name == 'nt': + try: + OLD_LOG_DB_FOLDER = os.path.join(fse(os.environ[u'appdata']), u'Gajim') + except KeyError: + OLD_LOG_DB_FOLDER = u'.' + else: + OLD_LOG_DB_FOLDER = os.path.expanduser(u'~/.gajim') - tmp = logger.CACHE_DB_PATH - logger.CACHE_DB_PATH = os.path.join(OLD_LOG_DB_FOLDER, 'cache.db') - create_cache_db() - back = os.getcwd() - os.chdir(OLD_LOG_DB_FOLDER) - con = sqlite.connect('logs.db') - os.chdir(back) - cur = con.cursor() - cur.execute('''SELECT name FROM sqlite_master WHERE type = 'table';''') - tables = cur.fetchall() # we get [(u'jids',), (u'unread_messages',), ... - tables = [t[0] for t in tables] - cur.execute("ATTACH DATABASE '%s' AS cache" % logger.CACHE_DB_PATH) - for table in ('caps_cache', 'rooms_last_message_time', 'roster_entry', - 'roster_group', 'transports_cache'): - if table not in tables: - continue - try: - cur.executescript( - 'INSERT INTO cache.%s SELECT * FROM %s;' % (table, table)) - con.commit() - cur.executescript('DROP TABLE %s;' % table) - con.commit() - except sqlite.OperationalError, e: - print >> sys.stderr, 'error moving table %s to cache.db: %s' % \ - (table, str(e)) - con.close() - logger.CACHE_DB_PATH = tmp + tmp = logger.CACHE_DB_PATH + logger.CACHE_DB_PATH = os.path.join(OLD_LOG_DB_FOLDER, 'cache.db') + create_cache_db() + back = os.getcwd() + os.chdir(OLD_LOG_DB_FOLDER) + con = sqlite.connect('logs.db') + os.chdir(back) + cur = con.cursor() + cur.execute('''SELECT name FROM sqlite_master WHERE type = 'table';''') + tables = cur.fetchall() # we get [(u'jids',), (u'unread_messages',), ... + tables = [t[0] for t in tables] + cur.execute("ATTACH DATABASE '%s' AS cache" % logger.CACHE_DB_PATH) + for table in ('caps_cache', 'rooms_last_message_time', 'roster_entry', + 'roster_group', 'transports_cache'): + if table not in tables: + continue + try: + cur.executescript( + 'INSERT INTO cache.%s SELECT * FROM %s;' % (table, table)) + con.commit() + cur.executescript('DROP TABLE %s;' % table) + con.commit() + except sqlite.OperationalError, e: + print >> sys.stderr, 'error moving table %s to cache.db: %s' % \ + (table, str(e)) + con.close() + logger.CACHE_DB_PATH = tmp def check_and_possibly_move_config(): - LOG_DB_PATH = logger.LOG_DB_PATH - CACHE_DB_PATH = logger.CACHE_DB_PATH - vars = {} - vars['VCARD_PATH'] = gajim.VCARD_PATH - vars['AVATAR_PATH'] = gajim.AVATAR_PATH - vars['MY_EMOTS_PATH'] = gajim.MY_EMOTS_PATH - vars['MY_ICONSETS_PATH'] = gajim.MY_ICONSETS_PATH - vars['MY_MOOD_ICONSETS_PATH'] = gajim.MY_MOOD_ICONSETS_PATH - vars['MY_ACTIVITY_ICONSETS_PATH'] = gajim.MY_ACTIVITY_ICONSETS_PATH - import configpaths - MY_DATA = configpaths.gajimpaths['MY_DATA'] - MY_CONFIG = configpaths.gajimpaths['MY_CONFIG'] - MY_CACHE = configpaths.gajimpaths['MY_CACHE'] + LOG_DB_PATH = logger.LOG_DB_PATH + CACHE_DB_PATH = logger.CACHE_DB_PATH + vars = {} + vars['VCARD_PATH'] = gajim.VCARD_PATH + vars['AVATAR_PATH'] = gajim.AVATAR_PATH + vars['MY_EMOTS_PATH'] = gajim.MY_EMOTS_PATH + vars['MY_ICONSETS_PATH'] = gajim.MY_ICONSETS_PATH + vars['MY_MOOD_ICONSETS_PATH'] = gajim.MY_MOOD_ICONSETS_PATH + vars['MY_ACTIVITY_ICONSETS_PATH'] = gajim.MY_ACTIVITY_ICONSETS_PATH + import configpaths + MY_DATA = configpaths.gajimpaths['MY_DATA'] + MY_CONFIG = configpaths.gajimpaths['MY_CONFIG'] + MY_CACHE = configpaths.gajimpaths['MY_CACHE'] - if os.path.exists(LOG_DB_PATH): - # File already exists - return + if os.path.exists(LOG_DB_PATH): + # File already exists + return - if os.name == 'nt': - try: - OLD_LOG_DB_FOLDER = os.path.join(fse(os.environ[u'appdata']), u'Gajim') - except KeyError: - OLD_LOG_DB_FOLDER = u'.' - else: - OLD_LOG_DB_FOLDER = os.path.expanduser(u'~/.gajim') - if not os.path.exists(OLD_LOG_DB_FOLDER): - return - OLD_LOG_DB_PATH = os.path.join(OLD_LOG_DB_FOLDER, u'logs.db') - OLD_CACHE_DB_PATH = os.path.join(OLD_LOG_DB_FOLDER, u'cache.db') - vars['OLD_VCARD_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, u'vcards') - vars['OLD_AVATAR_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, u'avatars') - vars['OLD_MY_EMOTS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, u'emoticons') - vars['OLD_MY_ICONSETS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, u'iconsets') - vars['OLD_MY_MOOD_ICONSETS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, u'moods') - vars['OLD_MY_ACTIVITY_ICONSETS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, - u'activities') - OLD_CONFIG_FILES = [] - OLD_DATA_FILES = [] - for f in os.listdir(OLD_LOG_DB_FOLDER): - if f == 'config' or f.startswith('config.'): - OLD_CONFIG_FILES.append(f) - if f == 'secrets' or f.startswith('secrets.'): - OLD_DATA_FILES.append(f) - if f == 'cacerts.pem': - OLD_DATA_FILES.append(f) + if os.name == 'nt': + try: + OLD_LOG_DB_FOLDER = os.path.join(fse(os.environ[u'appdata']), u'Gajim') + except KeyError: + OLD_LOG_DB_FOLDER = u'.' + else: + OLD_LOG_DB_FOLDER = os.path.expanduser(u'~/.gajim') + if not os.path.exists(OLD_LOG_DB_FOLDER): + return + OLD_LOG_DB_PATH = os.path.join(OLD_LOG_DB_FOLDER, u'logs.db') + OLD_CACHE_DB_PATH = os.path.join(OLD_LOG_DB_FOLDER, u'cache.db') + vars['OLD_VCARD_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, u'vcards') + vars['OLD_AVATAR_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, u'avatars') + vars['OLD_MY_EMOTS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, u'emoticons') + vars['OLD_MY_ICONSETS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, u'iconsets') + vars['OLD_MY_MOOD_ICONSETS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, u'moods') + vars['OLD_MY_ACTIVITY_ICONSETS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, + u'activities') + OLD_CONFIG_FILES = [] + OLD_DATA_FILES = [] + for f in os.listdir(OLD_LOG_DB_FOLDER): + if f == 'config' or f.startswith('config.'): + OLD_CONFIG_FILES.append(f) + if f == 'secrets' or f.startswith('secrets.'): + OLD_DATA_FILES.append(f) + if f == 'cacerts.pem': + OLD_DATA_FILES.append(f) - if not os.path.exists(OLD_LOG_DB_PATH): - return + if not os.path.exists(OLD_LOG_DB_PATH): + return - if not os.path.exists(OLD_CACHE_DB_PATH): - # split database - split_db() + if not os.path.exists(OLD_CACHE_DB_PATH): + # split database + split_db() - to_move = {} - to_move[OLD_LOG_DB_PATH] = LOG_DB_PATH - to_move[OLD_CACHE_DB_PATH] = CACHE_DB_PATH + to_move = {} + to_move[OLD_LOG_DB_PATH] = LOG_DB_PATH + to_move[OLD_CACHE_DB_PATH] = CACHE_DB_PATH - for folder in ('VCARD_PATH', 'AVATAR_PATH', 'MY_EMOTS_PATH', - 'MY_ICONSETS_PATH', 'MY_MOOD_ICONSETS_PATH', 'MY_ACTIVITY_ICONSETS_PATH'): - src = vars['OLD_' + folder] - dst = vars[folder] - to_move[src] = dst + for folder in ('VCARD_PATH', 'AVATAR_PATH', 'MY_EMOTS_PATH', + 'MY_ICONSETS_PATH', 'MY_MOOD_ICONSETS_PATH', 'MY_ACTIVITY_ICONSETS_PATH'): + src = vars['OLD_' + folder] + dst = vars[folder] + to_move[src] = dst - # move config files - for f in OLD_CONFIG_FILES: - src = os.path.join(OLD_LOG_DB_FOLDER, f) - dst = os.path.join(MY_CONFIG, f) - to_move[src] = dst + # move config files + for f in OLD_CONFIG_FILES: + src = os.path.join(OLD_LOG_DB_FOLDER, f) + dst = os.path.join(MY_CONFIG, f) + to_move[src] = dst - # Move data files (secrets, cacert.pem) - for f in OLD_DATA_FILES: - src = os.path.join(OLD_LOG_DB_FOLDER, f) - dst = os.path.join(MY_DATA, f) - to_move[src] = dst + # Move data files (secrets, cacert.pem) + for f in OLD_DATA_FILES: + src = os.path.join(OLD_LOG_DB_FOLDER, f) + dst = os.path.join(MY_DATA, f) + to_move[src] = dst - for src, dst in to_move.items(): - if os.path.exists(dst): - continue - if not os.path.exists(src): - continue - print 'moving %s to %s' % (src, dst) - os.renames(src, dst) - gajim.logger.init_vars() - gajim.logger.attach_cache_database() + for src, dst in to_move.items(): + if os.path.exists(dst): + continue + if not os.path.exists(src): + continue + print 'moving %s to %s' % (src, dst) + os.renames(src, dst) + gajim.logger.init_vars() + gajim.logger.attach_cache_database() def check_and_possibly_create_paths(): - check_and_possibly_move_config() + check_and_possibly_move_config() - LOG_DB_PATH = logger.LOG_DB_PATH - LOG_DB_FOLDER, LOG_DB_FILE = os.path.split(LOG_DB_PATH) + LOG_DB_PATH = logger.LOG_DB_PATH + LOG_DB_FOLDER, LOG_DB_FILE = os.path.split(LOG_DB_PATH) - CACHE_DB_PATH = logger.CACHE_DB_PATH - CACHE_DB_FOLDER, CACHE_DB_FILE = os.path.split(CACHE_DB_PATH) + CACHE_DB_PATH = logger.CACHE_DB_PATH + CACHE_DB_FOLDER, CACHE_DB_FILE = os.path.split(CACHE_DB_PATH) - VCARD_PATH = gajim.VCARD_PATH - AVATAR_PATH = gajim.AVATAR_PATH - import configpaths - MY_DATA = configpaths.gajimpaths['MY_DATA'] - MY_CONFIG = configpaths.gajimpaths['MY_CONFIG'] - MY_CACHE = configpaths.gajimpaths['MY_CACHE'] + VCARD_PATH = gajim.VCARD_PATH + AVATAR_PATH = gajim.AVATAR_PATH + import configpaths + MY_DATA = configpaths.gajimpaths['MY_DATA'] + MY_CONFIG = configpaths.gajimpaths['MY_CONFIG'] + MY_CACHE = configpaths.gajimpaths['MY_CACHE'] - PLUGINS_CONFIG_PATH = gajim.PLUGINS_CONFIG_DIR + PLUGINS_CONFIG_PATH = gajim.PLUGINS_CONFIG_DIR - if not os.path.exists(MY_DATA): - create_path(MY_DATA) - elif os.path.isfile(MY_DATA): - print _('%s is a file but it should be a directory') % MY_DATA - print _('Gajim will now exit') - sys.exit() + if not os.path.exists(MY_DATA): + create_path(MY_DATA) + elif os.path.isfile(MY_DATA): + print _('%s is a file but it should be a directory') % MY_DATA + print _('Gajim will now exit') + sys.exit() - if not os.path.exists(MY_CONFIG): - create_path(MY_CONFIG) - elif os.path.isfile(MY_CONFIG): - print _('%s is a file but it should be a directory') % MY_CONFIG - print _('Gajim will now exit') - sys.exit() + if not os.path.exists(MY_CONFIG): + create_path(MY_CONFIG) + elif os.path.isfile(MY_CONFIG): + print _('%s is a file but it should be a directory') % MY_CONFIG + print _('Gajim will now exit') + sys.exit() - if not os.path.exists(MY_CACHE): - create_path(MY_CACHE) - elif os.path.isfile(MY_CACHE): - print _('%s is a file but it should be a directory') % MY_CACHE - print _('Gajim will now exit') - sys.exit() + if not os.path.exists(MY_CACHE): + create_path(MY_CACHE) + elif os.path.isfile(MY_CACHE): + print _('%s is a file but it should be a directory') % MY_CACHE + print _('Gajim will now exit') + sys.exit() - if not os.path.exists(VCARD_PATH): - create_path(VCARD_PATH) - elif os.path.isfile(VCARD_PATH): - print _('%s is a file but it should be a directory') % VCARD_PATH - print _('Gajim will now exit') - sys.exit() + if not os.path.exists(VCARD_PATH): + create_path(VCARD_PATH) + elif os.path.isfile(VCARD_PATH): + print _('%s is a file but it should be a directory') % VCARD_PATH + print _('Gajim will now exit') + sys.exit() - if not os.path.exists(AVATAR_PATH): - create_path(AVATAR_PATH) - elif os.path.isfile(AVATAR_PATH): - print _('%s is a file but it should be a directory') % AVATAR_PATH - print _('Gajim will now exit') - sys.exit() + if not os.path.exists(AVATAR_PATH): + create_path(AVATAR_PATH) + elif os.path.isfile(AVATAR_PATH): + print _('%s is a file but it should be a directory') % AVATAR_PATH + print _('Gajim will now exit') + sys.exit() - if not os.path.exists(LOG_DB_FOLDER): - create_path(LOG_DB_FOLDER) - elif os.path.isfile(LOG_DB_FOLDER): - print _('%s is a file but it should be a directory') % LOG_DB_FOLDER - print _('Gajim will now exit') - sys.exit() + if not os.path.exists(LOG_DB_FOLDER): + create_path(LOG_DB_FOLDER) + elif os.path.isfile(LOG_DB_FOLDER): + print _('%s is a file but it should be a directory') % LOG_DB_FOLDER + print _('Gajim will now exit') + sys.exit() - if not os.path.exists(LOG_DB_PATH): - create_log_db() - gajim.logger.init_vars() - elif os.path.isdir(LOG_DB_PATH): - print _('%s is a directory but should be a file') % LOG_DB_PATH - print _('Gajim will now exit') - sys.exit() + if not os.path.exists(LOG_DB_PATH): + create_log_db() + gajim.logger.init_vars() + elif os.path.isdir(LOG_DB_PATH): + print _('%s is a directory but should be a file') % LOG_DB_PATH + print _('Gajim will now exit') + sys.exit() - if not os.path.exists(CACHE_DB_FOLDER): - create_path(CACHE_DB_FOLDER) - elif os.path.isfile(CACHE_DB_FOLDER): - print _('%s is a file but it should be a directory') % CACHE_DB_FOLDER - print _('Gajim will now exit') - sys.exit() + if not os.path.exists(CACHE_DB_FOLDER): + create_path(CACHE_DB_FOLDER) + elif os.path.isfile(CACHE_DB_FOLDER): + print _('%s is a file but it should be a directory') % CACHE_DB_FOLDER + print _('Gajim will now exit') + sys.exit() - if not os.path.exists(CACHE_DB_PATH): - create_cache_db() - gajim.logger.attach_cache_database() - elif os.path.isdir(CACHE_DB_PATH): - print _('%s is a directory but should be a file') % CACHE_DB_PATH - print _('Gajim will now exit') - sys.exit() + if not os.path.exists(CACHE_DB_PATH): + create_cache_db() + gajim.logger.attach_cache_database() + elif os.path.isdir(CACHE_DB_PATH): + print _('%s is a directory but should be a file') % CACHE_DB_PATH + print _('Gajim will now exit') + sys.exit() - if not os.path.exists(PLUGINS_CONFIG_PATH): - create_path(PLUGINS_CONFIG_PATH) - elif os.path.isfile(PLUGINS_CONFIG_PATH): - print _('%s is a file but it should be a directory') % PLUGINS_CONFIG_PATH - print _('Gajim will now exit') - sys.exit() + if not os.path.exists(PLUGINS_CONFIG_PATH): + create_path(PLUGINS_CONFIG_PATH) + elif os.path.isfile(PLUGINS_CONFIG_PATH): + print _('%s is a file but it should be a directory') % PLUGINS_CONFIG_PATH + print _('Gajim will now exit') + sys.exit() def create_path(directory): - print _('creating %s directory') % directory - os.mkdir(directory, 0700) - -# vim: se ts=3: + print _('creating %s directory') % directory + os.mkdir(directory, 0700) diff --git a/src/common/commands.py b/src/common/commands.py index 07c866194..88f9f3332 100644 --- a/src/common/commands.py +++ b/src/common/commands.py @@ -28,418 +28,416 @@ import dataforms import gajim class AdHocCommand: - commandnode = 'command' - commandname = 'The Command' - commandfeatures = (xmpp.NS_DATA,) + commandnode = 'command' + commandname = 'The Command' + commandfeatures = (xmpp.NS_DATA,) - @staticmethod - def isVisibleFor(samejid): - """ - This returns True if that command should be visible and invokable for - others + @staticmethod + def isVisibleFor(samejid): + """ + This returns True if that command should be visible and invokable for + others - samejid - True when command is invoked by an entity with the same bare - jid. - """ - return True + samejid - True when command is invoked by an entity with the same bare + jid. + """ + return True - def __init__(self, conn, jid, sessionid): - self.connection = conn - self.jid = jid - self.sessionid = sessionid + def __init__(self, conn, jid, sessionid): + self.connection = conn + self.jid = jid + self.sessionid = sessionid - def buildResponse(self, request, status = 'executing', defaultaction = None, - actions = None): - assert status in ('executing', 'completed', 'canceled') + def buildResponse(self, request, status = 'executing', defaultaction = None, + actions = None): + assert status in ('executing', 'completed', 'canceled') - response = request.buildReply('result') - cmd = response.addChild('command', namespace=xmpp.NS_COMMANDS, attrs={ - 'sessionid': self.sessionid, - 'node': self.commandnode, - 'status': status}) - if defaultaction is not None or actions is not None: - if defaultaction is not None: - assert defaultaction in ('cancel', 'execute', 'prev', 'next', - 'complete') - attrs = {'action': defaultaction} - else: - attrs = {} + response = request.buildReply('result') + cmd = response.addChild('command', namespace=xmpp.NS_COMMANDS, attrs={ + 'sessionid': self.sessionid, + 'node': self.commandnode, + 'status': status}) + if defaultaction is not None or actions is not None: + if defaultaction is not None: + assert defaultaction in ('cancel', 'execute', 'prev', 'next', + 'complete') + attrs = {'action': defaultaction} + else: + attrs = {} - cmd.addChild('actions', attrs, actions) - return response, cmd + cmd.addChild('actions', attrs, actions) + return response, cmd - def badRequest(self, stanza): - self.connection.connection.send(xmpp.Error(stanza, xmpp.NS_STANZAS + \ - ' bad-request')) + def badRequest(self, stanza): + self.connection.connection.send(xmpp.Error(stanza, xmpp.NS_STANZAS + \ + ' bad-request')) - def cancel(self, request): - response = self.buildResponse(request, status = 'canceled')[0] - self.connection.connection.send(response) - return False # finish the session + def cancel(self, request): + response = self.buildResponse(request, status = 'canceled')[0] + self.connection.connection.send(response) + return False # finish the session class ChangeStatusCommand(AdHocCommand): - commandnode = 'change-status' - commandname = _('Change status information') + commandnode = 'change-status' + commandname = _('Change status information') - @staticmethod - def isVisibleFor(samejid): - """ - Change status is visible only if the entity has the same bare jid - """ - return samejid + @staticmethod + def isVisibleFor(samejid): + """ + Change status is visible only if the entity has the same bare jid + """ + return samejid - def execute(self, request): - # first query... - response, cmd = self.buildResponse(request, defaultaction = 'execute', - actions = ['execute']) + def execute(self, request): + # first query... + response, cmd = self.buildResponse(request, defaultaction = 'execute', + actions = ['execute']) - cmd.addChild(node = dataforms.SimpleDataForm( - title = _('Change status'), - instructions = _('Set the presence type and description'), - fields = [ - dataforms.Field('list-single', - var = 'presence-type', - label = 'Type of presence:', - options = [ - (u'chat', _('Free for chat')), - (u'online', _('Online')), - (u'away', _('Away')), - (u'xa', _('Extended away')), - (u'dnd', _('Do not disturb')), - (u'offline', _('Offline - disconnect'))], - value = 'online', - required = True), - dataforms.Field('text-multi', - var = 'presence-desc', - label = _('Presence description:'))])) + cmd.addChild(node = dataforms.SimpleDataForm( + title = _('Change status'), + instructions = _('Set the presence type and description'), + fields = [ + dataforms.Field('list-single', + var = 'presence-type', + label = 'Type of presence:', + options = [ + (u'chat', _('Free for chat')), + (u'online', _('Online')), + (u'away', _('Away')), + (u'xa', _('Extended away')), + (u'dnd', _('Do not disturb')), + (u'offline', _('Offline - disconnect'))], + value = 'online', + required = True), + dataforms.Field('text-multi', + var = 'presence-desc', + label = _('Presence description:'))])) - self.connection.connection.send(response) + self.connection.connection.send(response) - # for next invocation - self.execute = self.changestatus + # for next invocation + self.execute = self.changestatus - return True # keep the session + return True # keep the session - def changestatus(self, request): - # check if the data is correct - try: - form = dataforms.SimpleDataForm(extend = request.getTag('command').\ - getTag('x')) - except Exception: - self.badRequest(request) - return False + def changestatus(self, request): + # check if the data is correct + try: + form = dataforms.SimpleDataForm(extend = request.getTag('command').\ + getTag('x')) + except Exception: + self.badRequest(request) + return False - try: - presencetype = form['presence-type'].value - if not presencetype in \ - ('chat', 'online', 'away', 'xa', 'dnd', 'offline'): - self.badRequest(request) - return False - except Exception: # KeyError if there's no presence-type field in form or - # AttributeError if that field is of wrong type - self.badRequest(request) - return False + try: + presencetype = form['presence-type'].value + if not presencetype in \ + ('chat', 'online', 'away', 'xa', 'dnd', 'offline'): + self.badRequest(request) + return False + except Exception: # KeyError if there's no presence-type field in form or + # AttributeError if that field is of wrong type + self.badRequest(request) + return False - try: - presencedesc = form['presence-desc'].value - except Exception: # same exceptions as in last comment - presencedesc = u'' + try: + presencedesc = form['presence-desc'].value + except Exception: # same exceptions as in last comment + presencedesc = u'' - response, cmd = self.buildResponse(request, status = 'completed') - cmd.addChild('note', {}, _('The status has been changed.')) + response, cmd = self.buildResponse(request, status = 'completed') + cmd.addChild('note', {}, _('The status has been changed.')) - # if going offline, we need to push response so it won't go into - # queue and disappear - self.connection.connection.send(response, now = presencetype == 'offline') + # if going offline, we need to push response so it won't go into + # queue and disappear + self.connection.connection.send(response, now = presencetype == 'offline') - # send new status - gajim.interface.roster.send_status(self.connection.name, presencetype, - presencedesc) + # send new status + gajim.interface.roster.send_status(self.connection.name, presencetype, + presencedesc) - return False # finish the session + return False # finish the session def find_current_groupchats(account): - import message_control - rooms = [] - for gc_control in gajim.interface.msg_win_mgr.get_controls( - message_control.TYPE_GC) + gajim.interface.minimized_controls[account].\ - values(): - acct = gc_control.account - # check if account is the good one - if acct != account: - continue - room_jid = gc_control.room_jid - nick = gc_control.nick - if room_jid in gajim.gc_connected[acct] and \ - gajim.gc_connected[acct][room_jid]: - rooms.append((room_jid, nick,)) - return rooms + import message_control + rooms = [] + for gc_control in gajim.interface.msg_win_mgr.get_controls( + message_control.TYPE_GC) + gajim.interface.minimized_controls[account].\ + values(): + acct = gc_control.account + # check if account is the good one + if acct != account: + continue + room_jid = gc_control.room_jid + nick = gc_control.nick + if room_jid in gajim.gc_connected[acct] and \ + gajim.gc_connected[acct][room_jid]: + rooms.append((room_jid, nick,)) + return rooms class LeaveGroupchatsCommand(AdHocCommand): - commandnode = 'leave-groupchats' - commandname = _('Leave Groupchats') + commandnode = 'leave-groupchats' + commandname = _('Leave Groupchats') - @staticmethod - def isVisibleFor(samejid): - """ - Change status is visible only if the entity has the same bare jid - """ - return samejid + @staticmethod + def isVisibleFor(samejid): + """ + Change status is visible only if the entity has the same bare jid + """ + return samejid - def execute(self, request): - # first query... - response, cmd = self.buildResponse(request, defaultaction = 'execute', - actions=['execute']) - options = [] - account = self.connection.name - for gc in find_current_groupchats(account): - options.append((u'%s' %(gc[0]), _('%(nickname)s on %(room_jid)s') % \ - {'nickname': gc[1], 'room_jid': gc[0]})) - if not len(options): - response, cmd = self.buildResponse(request, status = 'completed') - cmd.addChild('note', {}, _('You have not joined a groupchat.')) + def execute(self, request): + # first query... + response, cmd = self.buildResponse(request, defaultaction = 'execute', + actions=['execute']) + options = [] + account = self.connection.name + for gc in find_current_groupchats(account): + options.append((u'%s' %(gc[0]), _('%(nickname)s on %(room_jid)s') % \ + {'nickname': gc[1], 'room_jid': gc[0]})) + if not len(options): + response, cmd = self.buildResponse(request, status = 'completed') + cmd.addChild('note', {}, _('You have not joined a groupchat.')) - self.connection.connection.send(response) - return False + self.connection.connection.send(response) + return False - cmd.addChild(node=dataforms.SimpleDataForm( - title = _('Leave Groupchats'), - instructions = _('Choose the groupchats you want to leave'), - fields=[ - dataforms.Field('list-multi', - var = 'groupchats', - label = _('Groupchats'), - options = options, - required = True)])) + cmd.addChild(node=dataforms.SimpleDataForm( + title = _('Leave Groupchats'), + instructions = _('Choose the groupchats you want to leave'), + fields=[ + dataforms.Field('list-multi', + var = 'groupchats', + label = _('Groupchats'), + options = options, + required = True)])) - self.connection.connection.send(response) + self.connection.connection.send(response) - # for next invocation - self.execute = self.leavegroupchats + # for next invocation + self.execute = self.leavegroupchats - return True # keep the session + return True # keep the session - def leavegroupchats(self, request): - # check if the data is correct - try: - form = dataforms.SimpleDataForm(extend = request.getTag('command').\ - getTag('x')) - except Exception: - self.badRequest(request) - return False + def leavegroupchats(self, request): + # check if the data is correct + try: + form = dataforms.SimpleDataForm(extend = request.getTag('command').\ + getTag('x')) + except Exception: + self.badRequest(request) + return False - try: - gc = form['groupchats'].values - except Exception: # KeyError if there's no groupchats in form - self.badRequest(request) - return False - account = self.connection.name - try: - for room_jid in gc: - gc_control = gajim.interface.msg_win_mgr.get_gc_control(room_jid, - account) - if not gc_control: - gc_control = gajim.interface.minimized_controls[account]\ - [room_jid] - gc_control.shutdown() - gajim.interface.roster.remove_groupchat(room_jid, account) - continue - gc_control.parent_win.remove_tab(gc_control, None, force = True) - except Exception: # KeyError if there's no such room opened - self.badRequest(request) - return False - response, cmd = self.buildResponse(request, status = 'completed') - note = _('You left the following groupchats:') - for room_jid in gc: - note += '\n\t' + room_jid - cmd.addChild('note', {}, note) + try: + gc = form['groupchats'].values + except Exception: # KeyError if there's no groupchats in form + self.badRequest(request) + return False + account = self.connection.name + try: + for room_jid in gc: + gc_control = gajim.interface.msg_win_mgr.get_gc_control(room_jid, + account) + if not gc_control: + gc_control = gajim.interface.minimized_controls[account]\ + [room_jid] + gc_control.shutdown() + gajim.interface.roster.remove_groupchat(room_jid, account) + continue + gc_control.parent_win.remove_tab(gc_control, None, force = True) + except Exception: # KeyError if there's no such room opened + self.badRequest(request) + return False + response, cmd = self.buildResponse(request, status = 'completed') + note = _('You left the following groupchats:') + for room_jid in gc: + note += '\n\t' + room_jid + cmd.addChild('note', {}, note) - self.connection.connection.send(response) - return False + self.connection.connection.send(response) + return False class ForwardMessagesCommand(AdHocCommand): - # http://www.xmpp.org/extensions/xep-0146.html#forward - commandnode = 'forward-messages' - commandname = _('Forward unread messages') + # http://www.xmpp.org/extensions/xep-0146.html#forward + commandnode = 'forward-messages' + commandname = _('Forward unread messages') - @staticmethod - def isVisibleFor(samejid): - """ - Change status is visible only if the entity has the same bare jid - """ - return samejid + @staticmethod + def isVisibleFor(samejid): + """ + Change status is visible only if the entity has the same bare jid + """ + return samejid - def execute(self, request): - account = self.connection.name - # Forward messages - events = gajim.events.get_events(account, types=['chat', 'normal']) - j, resource = gajim.get_room_and_nick_from_fjid(self.jid) - for jid in events: - for event in events[jid]: - self.connection.send_message(j, event.parameters[0], '', - type_=event.type_, subject=event.parameters[1], - resource=resource, forward_from=jid, delayed=event.time_) + def execute(self, request): + account = self.connection.name + # Forward messages + events = gajim.events.get_events(account, types=['chat', 'normal']) + j, resource = gajim.get_room_and_nick_from_fjid(self.jid) + for jid in events: + for event in events[jid]: + self.connection.send_message(j, event.parameters[0], '', + type_=event.type_, subject=event.parameters[1], + resource=resource, forward_from=jid, delayed=event.time_) - # Inform other client of completion - response, cmd = self.buildResponse(request, status = 'completed') - cmd.addChild('note', {}, _('All unread messages have been forwarded.')) + # Inform other client of completion + response, cmd = self.buildResponse(request, status = 'completed') + cmd.addChild('note', {}, _('All unread messages have been forwarded.')) - self.connection.connection.send(response) + self.connection.connection.send(response) - return False # finish the session + return False # finish the session class ConnectionCommands: - """ - This class depends on that it is a part of Connection() class - """ + """ + This class depends on that it is a part of Connection() class + """ - def __init__(self): - # a list of all commands exposed: node -> command class - self.__commands = {} - for cmdobj in (ChangeStatusCommand, ForwardMessagesCommand, - LeaveGroupchatsCommand): - self.__commands[cmdobj.commandnode] = cmdobj + def __init__(self): + # a list of all commands exposed: node -> command class + self.__commands = {} + for cmdobj in (ChangeStatusCommand, ForwardMessagesCommand, + LeaveGroupchatsCommand): + self.__commands[cmdobj.commandnode] = cmdobj - # a list of sessions; keys are tuples (jid, sessionid, node) - self.__sessions = {} + # a list of sessions; keys are tuples (jid, sessionid, node) + self.__sessions = {} - def getOurBareJID(self): - return gajim.get_jid_from_account(self.name) + def getOurBareJID(self): + return gajim.get_jid_from_account(self.name) - def isSameJID(self, jid): - """ - Test if the bare jid given is the same as our bare jid - """ - return xmpp.JID(jid).getStripped() == self.getOurBareJID() + def isSameJID(self, jid): + """ + Test if the bare jid given is the same as our bare jid + """ + return xmpp.JID(jid).getStripped() == self.getOurBareJID() - def commandListQuery(self, con, iq_obj): - iq = iq_obj.buildReply('result') - jid = helpers.get_full_jid_from_iq(iq_obj) - q = iq.getTag('query') - # buildReply don't copy the node attribute. Re-add it - q.setAttr('node', xmpp.NS_COMMANDS) + def commandListQuery(self, con, iq_obj): + iq = iq_obj.buildReply('result') + jid = helpers.get_full_jid_from_iq(iq_obj) + q = iq.getTag('query') + # buildReply don't copy the node attribute. Re-add it + q.setAttr('node', xmpp.NS_COMMANDS) - for node, cmd in self.__commands.iteritems(): - if cmd.isVisibleFor(self.isSameJID(jid)): - q.addChild('item', { - # TODO: find the jid - 'jid': self.getOurBareJID() + u'/' + self.server_resource, - 'node': node, - 'name': cmd.commandname}) + for node, cmd in self.__commands.iteritems(): + if cmd.isVisibleFor(self.isSameJID(jid)): + q.addChild('item', { + # TODO: find the jid + 'jid': self.getOurBareJID() + u'/' + self.server_resource, + 'node': node, + 'name': cmd.commandname}) - self.connection.send(iq) + self.connection.send(iq) - def commandInfoQuery(self, con, iq_obj): - """ - Send disco#info result for query for command (JEP-0050, example 6.). - Return True if the result was sent, False if not - """ - jid = helpers.get_full_jid_from_iq(iq_obj) - node = iq_obj.getTagAttr('query', 'node') + def commandInfoQuery(self, con, iq_obj): + """ + Send disco#info result for query for command (JEP-0050, example 6.). + Return True if the result was sent, False if not + """ + jid = helpers.get_full_jid_from_iq(iq_obj) + node = iq_obj.getTagAttr('query', 'node') - if node not in self.__commands: return False + if node not in self.__commands: return False - cmd = self.__commands[node] - if cmd.isVisibleFor(self.isSameJID(jid)): - iq = iq_obj.buildReply('result') - q = iq.getTag('query') - q.addChild('identity', attrs = {'type': 'command-node', - 'category': 'automation', - 'name': cmd.commandname}) - q.addChild('feature', attrs = {'var': xmpp.NS_COMMANDS}) - for feature in cmd.commandfeatures: - q.addChild('feature', attrs = {'var': feature}) + cmd = self.__commands[node] + if cmd.isVisibleFor(self.isSameJID(jid)): + iq = iq_obj.buildReply('result') + q = iq.getTag('query') + q.addChild('identity', attrs = {'type': 'command-node', + 'category': 'automation', + 'name': cmd.commandname}) + q.addChild('feature', attrs = {'var': xmpp.NS_COMMANDS}) + for feature in cmd.commandfeatures: + q.addChild('feature', attrs = {'var': feature}) - self.connection.send(iq) - return True + self.connection.send(iq) + return True - return False + return False - def commandItemsQuery(self, con, iq_obj): - """ - Send disco#items result for query for command. Return True if the result - was sent, False if not. - """ - jid = helpers.get_full_jid_from_iq(iq_obj) - node = iq_obj.getTagAttr('query', 'node') + def commandItemsQuery(self, con, iq_obj): + """ + Send disco#items result for query for command. Return True if the result + was sent, False if not. + """ + jid = helpers.get_full_jid_from_iq(iq_obj) + node = iq_obj.getTagAttr('query', 'node') - if node not in self.__commands: return False + if node not in self.__commands: return False - cmd = self.__commands[node] - if cmd.isVisibleFor(self.isSameJID(jid)): - iq = iq_obj.buildReply('result') - self.connection.send(iq) - return True + cmd = self.__commands[node] + if cmd.isVisibleFor(self.isSameJID(jid)): + iq = iq_obj.buildReply('result') + self.connection.send(iq) + return True - return False + return False - def _CommandExecuteCB(self, con, iq_obj): - jid = helpers.get_full_jid_from_iq(iq_obj) + def _CommandExecuteCB(self, con, iq_obj): + jid = helpers.get_full_jid_from_iq(iq_obj) - cmd = iq_obj.getTag('command') - if cmd is None: return + cmd = iq_obj.getTag('command') + if cmd is None: return - node = cmd.getAttr('node') - if node is None: return + node = cmd.getAttr('node') + if node is None: return - sessionid = cmd.getAttr('sessionid') - if sessionid is None: - # we start a new command session... only if we are visible for the jid - # and command exist - if node not in self.__commands.keys(): - self.connection.send( - xmpp.Error(iq_obj, xmpp.NS_STANZAS + ' item-not-found')) - raise xmpp.NodeProcessed + sessionid = cmd.getAttr('sessionid') + if sessionid is None: + # we start a new command session... only if we are visible for the jid + # and command exist + if node not in self.__commands.keys(): + self.connection.send( + xmpp.Error(iq_obj, xmpp.NS_STANZAS + ' item-not-found')) + raise xmpp.NodeProcessed - newcmd = self.__commands[node] - if not newcmd.isVisibleFor(self.isSameJID(jid)): - return + newcmd = self.__commands[node] + if not newcmd.isVisibleFor(self.isSameJID(jid)): + return - # generate new sessionid - sessionid = self.connection.getAnID() + # generate new sessionid + sessionid = self.connection.getAnID() - # create new instance and run it - obj = newcmd(conn = self, jid = jid, sessionid = sessionid) - rc = obj.execute(iq_obj) - if rc: - self.__sessions[(jid, sessionid, node)] = obj - raise xmpp.NodeProcessed - else: - # the command is already running, check for it - magictuple = (jid, sessionid, node) - if magictuple not in self.__sessions: - # we don't have this session... ha! - return + # create new instance and run it + obj = newcmd(conn = self, jid = jid, sessionid = sessionid) + rc = obj.execute(iq_obj) + if rc: + self.__sessions[(jid, sessionid, node)] = obj + raise xmpp.NodeProcessed + else: + # the command is already running, check for it + magictuple = (jid, sessionid, node) + if magictuple not in self.__sessions: + # we don't have this session... ha! + return - action = cmd.getAttr('action') - obj = self.__sessions[magictuple] + action = cmd.getAttr('action') + obj = self.__sessions[magictuple] - try: - if action == 'cancel': - rc = obj.cancel(iq_obj) - elif action == 'prev': - rc = obj.prev(iq_obj) - elif action == 'next': - rc = obj.next(iq_obj) - elif action == 'execute' or action is None: - rc = obj.execute(iq_obj) - elif action == 'complete': - rc = obj.complete(iq_obj) - else: - # action is wrong. stop the session, send error - raise AttributeError - except AttributeError: - # the command probably doesn't handle invoked action... - # stop the session, return error - del self.__sessions[magictuple] - return + try: + if action == 'cancel': + rc = obj.cancel(iq_obj) + elif action == 'prev': + rc = obj.prev(iq_obj) + elif action == 'next': + rc = obj.next(iq_obj) + elif action == 'execute' or action is None: + rc = obj.execute(iq_obj) + elif action == 'complete': + rc = obj.complete(iq_obj) + else: + # action is wrong. stop the session, send error + raise AttributeError + except AttributeError: + # the command probably doesn't handle invoked action... + # stop the session, return error + del self.__sessions[magictuple] + return - # delete the session if rc is False - if not rc: - del self.__sessions[magictuple] + # delete the session if rc is False + if not rc: + del self.__sessions[magictuple] - raise xmpp.NodeProcessed - -# vim: se ts=3: + raise xmpp.NodeProcessed diff --git a/src/common/config.py b/src/common/config.py index 68d86d762..383a289a5 100644 --- a/src/common/config.py +++ b/src/common/config.py @@ -56,691 +56,689 @@ opt_treat_incoming_messages = ['', 'chat', 'normal'] class Config: - DEFAULT_ICONSET = 'dcraven' - DEFAULT_MOOD_ICONSET = 'default' - DEFAULT_ACTIVITY_ICONSET = 'default' - DEFAULT_OPENWITH = 'gnome-open' - DEFAULT_BROWSER = 'firefox' - DEFAULT_MAILAPP = 'mozilla-thunderbird -compose' - DEFAULT_FILE_MANAGER = 'xffm' + DEFAULT_ICONSET = 'dcraven' + DEFAULT_MOOD_ICONSET = 'default' + DEFAULT_ACTIVITY_ICONSET = 'default' + DEFAULT_OPENWITH = 'gnome-open' + DEFAULT_BROWSER = 'firefox' + DEFAULT_MAILAPP = 'mozilla-thunderbird -compose' + DEFAULT_FILE_MANAGER = 'xffm' - __options = { - # name: [ type, default_value, help_string ] - 'verbose': [ opt_bool, False, '', True ], - 'autopopup': [ opt_bool, False ], - 'notify_on_signin': [ opt_bool, True ], - 'notify_on_signout': [ opt_bool, False ], - 'notify_on_new_message': [ opt_bool, True ], - 'autopopupaway': [ opt_bool, False ], - 'sounddnd': [ opt_bool, False, _('Play sound when user is busy')], - 'use_notif_daemon': [ opt_bool, True , _('Use D-Bus and Notification-Daemon to show notifications') ], - 'showoffline': [ opt_bool, False ], - 'show_only_chat_and_online': [ opt_bool, False, _('Show only online and free for chat contacts in roster.')], - 'show_transports_group': [ opt_bool, True ], - 'autoaway': [ opt_bool, True ], - 'autoawaytime': [ opt_int, 5, _('Time in minutes, after which your status changes to away.') ], - 'autoaway_message': [ opt_str, _('$S (Away as a result of being idle more than $T min)'), _('$S will be replaced by current status message, $T by autoaway time.') ], - 'autoxa': [ opt_bool, True ], - 'autoxatime': [ opt_int, 15, _('Time in minutes, after which your status changes to not available.') ], - 'autoxa_message': [ opt_str, _('$S (Not available as a result of being idle more than $T min)'), _('$S will be replaced by current status message, $T by autoxa time.') ], - 'ask_online_status': [ opt_bool, False ], - 'ask_offline_status': [ opt_bool, False ], - 'trayicon': [opt_str, 'always', _("When to show systray icon. Can be 'never', 'on_event', 'always'."), True], - 'iconset': [ opt_str, DEFAULT_ICONSET, '', True ], - 'mood_iconset': [ opt_str, DEFAULT_MOOD_ICONSET, '', True ], - 'activity_iconset': [ opt_str, DEFAULT_ACTIVITY_ICONSET, '', True ], - 'use_transports_iconsets': [ opt_bool, True, '', True ], - 'inmsgcolor': [ opt_color, '#a40000', _('Incoming nickname color.'), True ], - 'outmsgcolor': [ opt_color, '#3465a4', _('Outgoing nickname color.'), True ], - 'inmsgtxtcolor': [ opt_color, '', _('Incoming text color.'), True ], - 'outmsgtxtcolor': [ opt_color, '#555753', _('Outgoing text color.'), True ], - 'statusmsgcolor': [ opt_color, '#4e9a06', _('Status message text color.'), True ], - 'markedmsgcolor': [ opt_color, '#ff8080', '', True ], - 'urlmsgcolor': [ opt_color, '#204a87', '', True ], - 'inmsgfont': [ opt_str, '', _('Incoming nickname font.'), True ], - 'outmsgfont': [ opt_str, '', _('Outgoing nickname font.'), True ], - 'inmsgtxtfont': [ opt_str, '', _('Incoming text font.'), True ], - 'outmsgtxtfont': [ opt_str, '', _('Outgoing text font.'), True ], - 'statusmsgfont': [ opt_str, '', _('Status message text font.'), True ], - 'collapsed_rows': [ opt_str, '', _('List (space separated) of rows (accounts and groups) that are collapsed.'), True ], - 'roster_theme': [ opt_str, _('default'), '', True ], - 'mergeaccounts': [ opt_bool, False, '', True ], - 'sort_by_show_in_roster': [ opt_bool, True, '', True ], - 'sort_by_show_in_muc': [ opt_bool, False, '', True ], - 'use_speller': [ opt_bool, False, ], - 'ignore_incoming_xhtml': [ opt_bool, False, ], - 'speller_language': [ opt_str, '', _('Language used by speller')], - 'print_time': [ opt_str, 'always', _('\'always\' - print time for every message.\n\'sometimes\' - print time every print_ichat_every_foo_minutes minute.\n\'never\' - never print time.')], - 'print_time_fuzzy': [ opt_int, 0, _('Print time in chats using Fuzzy Clock. Value of fuzziness from 1 to 4, or 0 to disable fuzzyclock. 1 is the most precise clock, 4 the least precise one. This is used only if print_time is \'sometimes\'.') ], - 'emoticons_theme': [opt_str, 'static', '', True ], - 'ascii_formatting': [ opt_bool, True, - _('Treat * / _ pairs as possible formatting characters.'), True], - 'show_ascii_formatting_chars': [ opt_bool, True , _('If True, do not ' - 'remove */_ . So *abc* will be bold but with * * not removed.')], - 'rst_formatting_outgoing_messages': [ opt_bool, False, - _('Uses ReStructured text markup to send HTML, plus ascii formatting if selected. For syntax, see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html (If you want to use this, install docutils)')], - 'sounds_on': [ opt_bool, True ], - # 'aplay', 'play', 'esdplay', 'artsplay' detected first time only - 'soundplayer': [ opt_str, '' ], - 'openwith': [ opt_str, DEFAULT_OPENWITH ], - 'custombrowser': [ opt_str, DEFAULT_BROWSER ], - 'custommailapp': [ opt_str, DEFAULT_MAILAPP ], - 'custom_file_manager': [ opt_str, DEFAULT_FILE_MANAGER ], - 'gc-hpaned-position': [opt_int, 430], - 'gc_refer_to_nick_char': [opt_str, ',', _('Character to add after nickname when using nick completion (tab) in group chat.')], - 'gc_proposed_nick_char': [opt_str, '_', _('Character to propose to add after desired nickname when desired nickname is used by someone else in group chat.')], - 'msgwin-max-state': [opt_bool, False], - 'msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide - 'msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide - 'msgwin-width': [opt_int, 500], - 'msgwin-height': [opt_int, 440], - 'chat-msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide - 'chat-msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide - 'chat-msgwin-width': [opt_int, 480], - 'chat-msgwin-height': [opt_int, 440], - 'gc-msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide - 'gc-msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide - 'gc-msgwin-width': [opt_int, 600], - 'gc-msgwin-height': [opt_int, 440], - 'single-msg-x-position': [opt_int, 0], - 'single-msg-y-position': [opt_int, 0], - 'single-msg-width': [opt_int, 400], - 'single-msg-height': [opt_int, 280], - 'roster_x-position': [ opt_int, 0 ], - 'roster_y-position': [ opt_int, 0 ], - 'roster_width': [ opt_int, 200 ], - 'roster_height': [ opt_int, 400 ], - 'history_window_width': [ opt_int, 650 ], - 'history_window_height': [ opt_int, 450 ], - 'history_window_x-position': [ opt_int, 0 ], - 'history_window_y-position': [ opt_int, 0 ], - 'latest_disco_addresses': [ opt_str, '' ], - 'recently_groupchat': [ opt_str, '' ], - 'time_stamp': [ opt_str, '[%X] ', _('This option let you customize timestamp that is printed in conversation. For exemple "[%H:%M] " will show "[hour:minute] ". See python doc on strftime for full documentation: http://docs.python.org/lib/module-time.html') ], - 'before_nickname': [ opt_str, '', _('Characters that are printed before the nickname in conversations') ], - 'after_nickname': [ opt_str, ':', _('Characters that are printed after the nickname in conversations') ], - 'notify_on_new_gmail_email': [ opt_bool, True ], - 'notify_on_new_gmail_email_extra': [ opt_bool, False ], - 'use_gpg_agent': [ opt_bool, False ], - 'change_roster_title': [ opt_bool, True, _('Add * and [n] in roster title?')], - 'restore_lines': [opt_int, 4, _('How many lines to remember from previous conversation when a chat tab/window is reopened.')], - 'restore_timeout': [opt_int, 60, _('How many minutes should last lines from previous conversation last.')], - 'muc_restore_lines': [opt_int, 20, _('How many lines to request to server when entering a groupchat.')], - 'muc_restore_timeout': [opt_int, 60, _('How many minutes back to request logs when a entering a groupchat.')], - 'muc_autorejoin_timeout': [opt_int, 1, _('How many seconds to wait before trying to autorejoin to a conference you are being disconnected from. Set to 0 to disable autorejoining.')], - 'muc_autorejoin_on_kick': [opt_bool, False, 'Should autorejoin be activated when we are being kicked from a conference?'], - 'send_on_ctrl_enter': [opt_bool, False, _('Send message on Ctrl+Enter and with Enter make new line (Mirabilis ICQ Client default behaviour).')], - 'show_roster_on_startup': [opt_bool, True], - 'key_up_lines': [opt_int, 25, _('How many lines to store for Ctrl+KeyUP.')], - 'version': [ opt_str, defs.version ], # which version created the config - 'search_engine': [opt_str, 'http://www.google.com/search?&q=%s&sourceid=gajim'], - 'dictionary_url': [opt_str, 'WIKTIONARY', _("Either custom url with %s in it where %s is the word/phrase or 'WIKTIONARY' which means use wiktionary.")], - 'always_english_wikipedia': [opt_bool, False], - 'always_english_wiktionary': [opt_bool, True], - 'remote_control': [opt_bool, True, _('If checked, Gajim can be controlled remotely using gajim-remote.'), True], - 'networkmanager_support': [opt_bool, True, _('If True, listen to D-Bus signals from NetworkManager and change the status of accounts (provided they do not have listen_to_network_manager set to False and they sync with global status) based upon the status of the network connection.'), True], - 'outgoing_chat_state_notifications': [opt_str, 'all', _('Sent chat state notifications. Can be one of all, composing_only, disabled.')], - 'displayed_chat_state_notifications': [opt_str, 'all', _('Displayed chat state notifications in chat windows. Can be one of all, composing_only, disabled.')], - 'autodetect_browser_mailer': [opt_bool, False, '', True], - 'print_ichat_every_foo_minutes': [opt_int, 5, _('When not printing time for every message (print_time==sometimes), print it every x minutes.')], - 'confirm_close_muc': [opt_bool, True, _('Ask before closing a group chat tab/window.')], - 'confirm_close_muc_rooms': [opt_str, '', _('Always ask before closing group chat tab/window in this space separated list of group chat jids.')], - 'noconfirm_close_muc_rooms': [opt_str, '', _('Never ask before closing group chat tab/window in this space separated list of group chat jids.')], - 'confirm_close_multiple_tabs': [opt_bool, True, _('Ask before closing tabbed chat window if there are control that can loose data (chat, private chat, groupchat that will not be minimized)')], - 'notify_on_file_complete': [opt_bool, True], - 'file_transfers_port': [opt_int, 28011], - 'ft_add_hosts_to_send': [opt_str, '', _('Comma separated list of hosts that we send, in addition of local interfaces, for File Transfer in case of address translation/port forwarding.')], - 'conversation_font': [opt_str, ''], - 'use_kib_mib': [opt_bool, False, _('IEC standard says KiB = 1024 bytes, KB = 1000 bytes.')], - 'notify_on_all_muc_messages': [opt_bool, False], - 'trayicon_notification_on_events': [opt_bool, True, _('Notify of events in the system trayicon.')], - 'last_save_dir': [opt_str, ''], - 'last_send_dir': [opt_str, ''], - 'last_emoticons_dir': [opt_str, ''], - 'last_sounds_dir': [opt_str, ''], - 'tabs_position': [opt_str, 'top'], - 'tabs_always_visible': [opt_bool, False, _('Show tab when only one conversation?')], - 'tabs_border': [opt_bool, False, _('Show tabbed notebook border in chat windows?')], - 'tabs_close_button': [opt_bool, True, _('Show close button in tab?')], - 'esession_modp': [opt_str, '5,14', _('A list of modp groups to use in a Diffie-Hellman, highest preference first, separated by commas. Valid groups are 1, 2, 5, 14, 15, 16, 17 and 18. Higher numbers are more secure, but take longer to calculate when you start a session.')], - 'chat_avatar_width': [opt_int, 52], - 'chat_avatar_height': [opt_int, 52], - 'roster_avatar_width': [opt_int, 32], - 'roster_avatar_height': [opt_int, 32], - 'tooltip_avatar_width': [opt_int, 125], - 'tooltip_avatar_height': [opt_int, 125], - 'vcard_avatar_width': [opt_int, 200], - 'vcard_avatar_height': [opt_int, 200], - 'notification_preview_message': [opt_bool, True, _('Preview new messages in notification popup?')], - 'notification_position_x': [opt_int, -1], - 'notification_position_y': [opt_int, -1], - 'notification_avatar_width': [opt_int, 48], - 'notification_avatar_height': [opt_int, 48], - 'muc_highlight_words': [opt_str, '', _('A semicolon-separated list of words that will be highlighted in group chats.')], - 'quit_on_roster_x_button': [opt_bool, False, _('If True, quits Gajim when X button of Window Manager is clicked. This setting is taken into account only if trayicon is used.')], - 'check_if_gajim_is_default': [opt_bool, True, _('If True, Gajim will check if it\'s the default jabber client on each startup.')], - 'show_unread_tab_icon': [opt_bool, False, _('If True, Gajim will display an icon on each tab containing unread messages. Depending on the theme, this icon may be animated.')], - 'show_status_msgs_in_roster': [opt_bool, True, _('If True, Gajim will display the status message, if not empty, for every contact under the contact name in roster window.'), True], - 'show_avatars_in_roster': [opt_bool, True, '', True], - 'show_mood_in_roster': [opt_bool, True, '', True], - 'show_activity_in_roster': [opt_bool, True, '', True], - 'show_tunes_in_roster': [opt_bool, True, '', True], - 'show_location_in_roster': [opt_bool, True, '', True], - 'avatar_position_in_roster': [opt_str, 'right', _('Define the position of the avatar in roster. Can be left or right'), True], - 'ask_avatars_on_startup': [opt_bool, True, _('If True, Gajim will ask for avatar each contact that did not have an avatar last time or has one cached that is too old.')], - 'print_status_in_chats': [opt_bool, True, _('If False, Gajim will no longer print status line in chats when a contact changes his or her status and/or his or her status message.')], - 'print_status_in_muc': [opt_str, 'in_and_out', _('can be "none", "all" or "in_and_out". If "none", Gajim will no longer print status line in groupchats when a member changes his or her status and/or his or her status message. If "all" Gajim will print all status messages. If "in_and_out", Gajim will only print FOO enters/leaves group chat.')], - 'log_contact_status_changes': [opt_bool, False], - 'just_connected_bg_color': [opt_str, '#adc3c6', _('Background color of contacts when they just signed in.')], - 'just_disconnected_bg_color': [opt_str, '#ab6161', _('Background color of contacts when they just signed out.')], - 'restored_messages_color': [opt_color, '#555753'], - 'restored_messages_small': [opt_bool, True, _('If True, restored messages will use a smaller font than the default one.')], - 'hide_avatar_of_transport': [opt_bool, False, _('Don\'t show avatar for the transport itself.')], - 'roster_window_skip_taskbar': [opt_bool, False, _('Don\'t show roster in the system taskbar.')], - 'use_urgency_hint': [opt_bool, True, _('If True and installed GTK+ and PyGTK versions are at least 2.8, make the window flash (the default behaviour in most Window Managers) when holding pending events.')], - 'notification_timeout': [opt_int, 5], - 'send_sha_in_gc_presence': [opt_bool, True, _('Jabberd1.4 does not like sha info when one join a password protected group chat. Turn this option to False to stop sending sha info in group chat presences.')], - 'one_message_window': [opt_str, 'always', + __options = { + # name: [ type, default_value, help_string ] + 'verbose': [ opt_bool, False, '', True ], + 'autopopup': [ opt_bool, False ], + 'notify_on_signin': [ opt_bool, True ], + 'notify_on_signout': [ opt_bool, False ], + 'notify_on_new_message': [ opt_bool, True ], + 'autopopupaway': [ opt_bool, False ], + 'sounddnd': [ opt_bool, False, _('Play sound when user is busy')], + 'use_notif_daemon': [ opt_bool, True, _('Use D-Bus and Notification-Daemon to show notifications') ], + 'showoffline': [ opt_bool, False ], + 'show_only_chat_and_online': [ opt_bool, False, _('Show only online and free for chat contacts in roster.')], + 'show_transports_group': [ opt_bool, True ], + 'autoaway': [ opt_bool, True ], + 'autoawaytime': [ opt_int, 5, _('Time in minutes, after which your status changes to away.') ], + 'autoaway_message': [ opt_str, _('$S (Away as a result of being idle more than $T min)'), _('$S will be replaced by current status message, $T by autoaway time.') ], + 'autoxa': [ opt_bool, True ], + 'autoxatime': [ opt_int, 15, _('Time in minutes, after which your status changes to not available.') ], + 'autoxa_message': [ opt_str, _('$S (Not available as a result of being idle more than $T min)'), _('$S will be replaced by current status message, $T by autoxa time.') ], + 'ask_online_status': [ opt_bool, False ], + 'ask_offline_status': [ opt_bool, False ], + 'trayicon': [opt_str, 'always', _("When to show systray icon. Can be 'never', 'on_event', 'always'."), True], + 'iconset': [ opt_str, DEFAULT_ICONSET, '', True ], + 'mood_iconset': [ opt_str, DEFAULT_MOOD_ICONSET, '', True ], + 'activity_iconset': [ opt_str, DEFAULT_ACTIVITY_ICONSET, '', True ], + 'use_transports_iconsets': [ opt_bool, True, '', True ], + 'inmsgcolor': [ opt_color, '#a40000', _('Incoming nickname color.'), True ], + 'outmsgcolor': [ opt_color, '#3465a4', _('Outgoing nickname color.'), True ], + 'inmsgtxtcolor': [ opt_color, '', _('Incoming text color.'), True ], + 'outmsgtxtcolor': [ opt_color, '#555753', _('Outgoing text color.'), True ], + 'statusmsgcolor': [ opt_color, '#4e9a06', _('Status message text color.'), True ], + 'markedmsgcolor': [ opt_color, '#ff8080', '', True ], + 'urlmsgcolor': [ opt_color, '#204a87', '', True ], + 'inmsgfont': [ opt_str, '', _('Incoming nickname font.'), True ], + 'outmsgfont': [ opt_str, '', _('Outgoing nickname font.'), True ], + 'inmsgtxtfont': [ opt_str, '', _('Incoming text font.'), True ], + 'outmsgtxtfont': [ opt_str, '', _('Outgoing text font.'), True ], + 'statusmsgfont': [ opt_str, '', _('Status message text font.'), True ], + 'collapsed_rows': [ opt_str, '', _('List (space separated) of rows (accounts and groups) that are collapsed.'), True ], + 'roster_theme': [ opt_str, _('default'), '', True ], + 'mergeaccounts': [ opt_bool, False, '', True ], + 'sort_by_show_in_roster': [ opt_bool, True, '', True ], + 'sort_by_show_in_muc': [ opt_bool, False, '', True ], + 'use_speller': [ opt_bool, False, ], + 'ignore_incoming_xhtml': [ opt_bool, False, ], + 'speller_language': [ opt_str, '', _('Language used by speller')], + 'print_time': [ opt_str, 'always', _('\'always\' - print time for every message.\n\'sometimes\' - print time every print_ichat_every_foo_minutes minute.\n\'never\' - never print time.')], + 'print_time_fuzzy': [ opt_int, 0, _('Print time in chats using Fuzzy Clock. Value of fuzziness from 1 to 4, or 0 to disable fuzzyclock. 1 is the most precise clock, 4 the least precise one. This is used only if print_time is \'sometimes\'.') ], + 'emoticons_theme': [opt_str, 'static', '', True ], + 'ascii_formatting': [ opt_bool, True, + _('Treat * / _ pairs as possible formatting characters.'), True], + 'show_ascii_formatting_chars': [ opt_bool, True, _('If True, do not ' + 'remove */_ . So *abc* will be bold but with * * not removed.')], + 'rst_formatting_outgoing_messages': [ opt_bool, False, + _('Uses ReStructured text markup to send HTML, plus ascii formatting if selected. For syntax, see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html (If you want to use this, install docutils)')], + 'sounds_on': [ opt_bool, True ], + # 'aplay', 'play', 'esdplay', 'artsplay' detected first time only + 'soundplayer': [ opt_str, '' ], + 'openwith': [ opt_str, DEFAULT_OPENWITH ], + 'custombrowser': [ opt_str, DEFAULT_BROWSER ], + 'custommailapp': [ opt_str, DEFAULT_MAILAPP ], + 'custom_file_manager': [ opt_str, DEFAULT_FILE_MANAGER ], + 'gc-hpaned-position': [opt_int, 430], + 'gc_refer_to_nick_char': [opt_str, ',', _('Character to add after nickname when using nick completion (tab) in group chat.')], + 'gc_proposed_nick_char': [opt_str, '_', _('Character to propose to add after desired nickname when desired nickname is used by someone else in group chat.')], + 'msgwin-max-state': [opt_bool, False], + 'msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide + 'msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide + 'msgwin-width': [opt_int, 500], + 'msgwin-height': [opt_int, 440], + 'chat-msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide + 'chat-msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide + 'chat-msgwin-width': [opt_int, 480], + 'chat-msgwin-height': [opt_int, 440], + 'gc-msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide + 'gc-msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide + 'gc-msgwin-width': [opt_int, 600], + 'gc-msgwin-height': [opt_int, 440], + 'single-msg-x-position': [opt_int, 0], + 'single-msg-y-position': [opt_int, 0], + 'single-msg-width': [opt_int, 400], + 'single-msg-height': [opt_int, 280], + 'roster_x-position': [ opt_int, 0 ], + 'roster_y-position': [ opt_int, 0 ], + 'roster_width': [ opt_int, 200 ], + 'roster_height': [ opt_int, 400 ], + 'history_window_width': [ opt_int, 650 ], + 'history_window_height': [ opt_int, 450 ], + 'history_window_x-position': [ opt_int, 0 ], + 'history_window_y-position': [ opt_int, 0 ], + 'latest_disco_addresses': [ opt_str, '' ], + 'recently_groupchat': [ opt_str, '' ], + 'time_stamp': [ opt_str, '[%X] ', _('This option let you customize timestamp that is printed in conversation. For exemple "[%H:%M] " will show "[hour:minute] ". See python doc on strftime for full documentation: http://docs.python.org/lib/module-time.html') ], + 'before_nickname': [ opt_str, '', _('Characters that are printed before the nickname in conversations') ], + 'after_nickname': [ opt_str, ':', _('Characters that are printed after the nickname in conversations') ], + 'notify_on_new_gmail_email': [ opt_bool, True ], + 'notify_on_new_gmail_email_extra': [ opt_bool, False ], + 'use_gpg_agent': [ opt_bool, False ], + 'change_roster_title': [ opt_bool, True, _('Add * and [n] in roster title?')], + 'restore_lines': [opt_int, 4, _('How many lines to remember from previous conversation when a chat tab/window is reopened.')], + 'restore_timeout': [opt_int, 60, _('How many minutes should last lines from previous conversation last.')], + 'muc_restore_lines': [opt_int, 20, _('How many lines to request to server when entering a groupchat.')], + 'muc_restore_timeout': [opt_int, 60, _('How many minutes back to request logs when a entering a groupchat.')], + 'muc_autorejoin_timeout': [opt_int, 1, _('How many seconds to wait before trying to autorejoin to a conference you are being disconnected from. Set to 0 to disable autorejoining.')], + 'muc_autorejoin_on_kick': [opt_bool, False, 'Should autorejoin be activated when we are being kicked from a conference?'], + 'send_on_ctrl_enter': [opt_bool, False, _('Send message on Ctrl+Enter and with Enter make new line (Mirabilis ICQ Client default behaviour).')], + 'show_roster_on_startup': [opt_bool, True], + 'key_up_lines': [opt_int, 25, _('How many lines to store for Ctrl+KeyUP.')], + 'version': [ opt_str, defs.version ], # which version created the config + 'search_engine': [opt_str, 'http://www.google.com/search?&q=%s&sourceid=gajim'], + 'dictionary_url': [opt_str, 'WIKTIONARY', _("Either custom url with %s in it where %s is the word/phrase or 'WIKTIONARY' which means use wiktionary.")], + 'always_english_wikipedia': [opt_bool, False], + 'always_english_wiktionary': [opt_bool, True], + 'remote_control': [opt_bool, True, _('If checked, Gajim can be controlled remotely using gajim-remote.'), True], + 'networkmanager_support': [opt_bool, True, _('If True, listen to D-Bus signals from NetworkManager and change the status of accounts (provided they do not have listen_to_network_manager set to False and they sync with global status) based upon the status of the network connection.'), True], + 'outgoing_chat_state_notifications': [opt_str, 'all', _('Sent chat state notifications. Can be one of all, composing_only, disabled.')], + 'displayed_chat_state_notifications': [opt_str, 'all', _('Displayed chat state notifications in chat windows. Can be one of all, composing_only, disabled.')], + 'autodetect_browser_mailer': [opt_bool, False, '', True], + 'print_ichat_every_foo_minutes': [opt_int, 5, _('When not printing time for every message (print_time==sometimes), print it every x minutes.')], + 'confirm_close_muc': [opt_bool, True, _('Ask before closing a group chat tab/window.')], + 'confirm_close_muc_rooms': [opt_str, '', _('Always ask before closing group chat tab/window in this space separated list of group chat jids.')], + 'noconfirm_close_muc_rooms': [opt_str, '', _('Never ask before closing group chat tab/window in this space separated list of group chat jids.')], + 'confirm_close_multiple_tabs': [opt_bool, True, _('Ask before closing tabbed chat window if there are control that can loose data (chat, private chat, groupchat that will not be minimized)')], + 'notify_on_file_complete': [opt_bool, True], + 'file_transfers_port': [opt_int, 28011], + 'ft_add_hosts_to_send': [opt_str, '', _('Comma separated list of hosts that we send, in addition of local interfaces, for File Transfer in case of address translation/port forwarding.')], + 'conversation_font': [opt_str, ''], + 'use_kib_mib': [opt_bool, False, _('IEC standard says KiB = 1024 bytes, KB = 1000 bytes.')], + 'notify_on_all_muc_messages': [opt_bool, False], + 'trayicon_notification_on_events': [opt_bool, True, _('Notify of events in the system trayicon.')], + 'last_save_dir': [opt_str, ''], + 'last_send_dir': [opt_str, ''], + 'last_emoticons_dir': [opt_str, ''], + 'last_sounds_dir': [opt_str, ''], + 'tabs_position': [opt_str, 'top'], + 'tabs_always_visible': [opt_bool, False, _('Show tab when only one conversation?')], + 'tabs_border': [opt_bool, False, _('Show tabbed notebook border in chat windows?')], + 'tabs_close_button': [opt_bool, True, _('Show close button in tab?')], + 'esession_modp': [opt_str, '5,14', _('A list of modp groups to use in a Diffie-Hellman, highest preference first, separated by commas. Valid groups are 1, 2, 5, 14, 15, 16, 17 and 18. Higher numbers are more secure, but take longer to calculate when you start a session.')], + 'chat_avatar_width': [opt_int, 52], + 'chat_avatar_height': [opt_int, 52], + 'roster_avatar_width': [opt_int, 32], + 'roster_avatar_height': [opt_int, 32], + 'tooltip_avatar_width': [opt_int, 125], + 'tooltip_avatar_height': [opt_int, 125], + 'vcard_avatar_width': [opt_int, 200], + 'vcard_avatar_height': [opt_int, 200], + 'notification_preview_message': [opt_bool, True, _('Preview new messages in notification popup?')], + 'notification_position_x': [opt_int, -1], + 'notification_position_y': [opt_int, -1], + 'notification_avatar_width': [opt_int, 48], + 'notification_avatar_height': [opt_int, 48], + 'muc_highlight_words': [opt_str, '', _('A semicolon-separated list of words that will be highlighted in group chats.')], + 'quit_on_roster_x_button': [opt_bool, False, _('If True, quits Gajim when X button of Window Manager is clicked. This setting is taken into account only if trayicon is used.')], + 'check_if_gajim_is_default': [opt_bool, True, _('If True, Gajim will check if it\'s the default jabber client on each startup.')], + 'show_unread_tab_icon': [opt_bool, False, _('If True, Gajim will display an icon on each tab containing unread messages. Depending on the theme, this icon may be animated.')], + 'show_status_msgs_in_roster': [opt_bool, True, _('If True, Gajim will display the status message, if not empty, for every contact under the contact name in roster window.'), True], + 'show_avatars_in_roster': [opt_bool, True, '', True], + 'show_mood_in_roster': [opt_bool, True, '', True], + 'show_activity_in_roster': [opt_bool, True, '', True], + 'show_tunes_in_roster': [opt_bool, True, '', True], + 'show_location_in_roster': [opt_bool, True, '', True], + 'avatar_position_in_roster': [opt_str, 'right', _('Define the position of the avatar in roster. Can be left or right'), True], + 'ask_avatars_on_startup': [opt_bool, True, _('If True, Gajim will ask for avatar each contact that did not have an avatar last time or has one cached that is too old.')], + 'print_status_in_chats': [opt_bool, True, _('If False, Gajim will no longer print status line in chats when a contact changes his or her status and/or his or her status message.')], + 'print_status_in_muc': [opt_str, 'in_and_out', _('can be "none", "all" or "in_and_out". If "none", Gajim will no longer print status line in groupchats when a member changes his or her status and/or his or her status message. If "all" Gajim will print all status messages. If "in_and_out", Gajim will only print FOO enters/leaves group chat.')], + 'log_contact_status_changes': [opt_bool, False], + 'just_connected_bg_color': [opt_str, '#adc3c6', _('Background color of contacts when they just signed in.')], + 'just_disconnected_bg_color': [opt_str, '#ab6161', _('Background color of contacts when they just signed out.')], + 'restored_messages_color': [opt_color, '#555753'], + 'restored_messages_small': [opt_bool, True, _('If True, restored messages will use a smaller font than the default one.')], + 'hide_avatar_of_transport': [opt_bool, False, _('Don\'t show avatar for the transport itself.')], + 'roster_window_skip_taskbar': [opt_bool, False, _('Don\'t show roster in the system taskbar.')], + 'use_urgency_hint': [opt_bool, True, _('If True and installed GTK+ and PyGTK versions are at least 2.8, make the window flash (the default behaviour in most Window Managers) when holding pending events.')], + 'notification_timeout': [opt_int, 5], + 'send_sha_in_gc_presence': [opt_bool, True, _('Jabberd1.4 does not like sha info when one join a password protected group chat. Turn this option to False to stop sending sha info in group chat presences.')], + 'one_message_window': [opt_str, 'always', #always, never, peracct, pertype should not be translated - _('Controls the window where new messages are placed.\n\'always\' - All messages are sent to a single window.\n\'always_with_roster\' - Like \'always\' but the messages are in a single window along with the roster.\n\'never\' - All messages get their own window.\n\'peracct\' - Messages for each account are sent to a specific window.\n\'pertype\' - Each message type (e.g., chats vs. groupchats) are sent to a specific window.')], - 'show_avatar_in_chat': [opt_bool, True, _('If False, you will no longer see the avatar in the chat window.')], - 'escape_key_closes': [opt_bool, True, _('If True, pressing the escape key closes a tab/window.')], - 'compact_view': [opt_bool, False, _('Hides the buttons in chat windows.')], - 'hide_groupchat_banner': [opt_bool, False, _('Hides the banner in a group chat window')], - 'hide_chat_banner': [opt_bool, False, _('Hides the banner in two persons chat window')], - 'hide_groupchat_occupants_list': [opt_bool, False, _('Hides the group chat occupants list in group chat window.')], - 'chat_merge_consecutive_nickname': [opt_bool, False, _('In a chat, show the nickname at the beginning of a line only when it\'s not the same person talking than in previous message.')], - 'chat_merge_consecutive_nickname_indent': [opt_str, ' ', _('Indentation when using merge consecutive nickname.')], - 'use_smooth_scrolling': [opt_bool, True, _('Smooth scroll message in conversation window')], - 'gc_nicknames_colors': [ opt_str, '#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000', _('List of colors, separated by ":", that will be used to color nicknames in group chats.'), True ], - 'ctrl_tab_go_to_next_composing': [opt_bool, True, _('Ctrl-Tab go to next composing tab when none is unread.')], - 'confirm_metacontacts': [ opt_str, '', _('Should we show the confirm metacontacts creation dialog or not? Empty string means we never show the dialog.')], - 'confirm_block': [ opt_str, '', _('Should we show the confirm block contact dialog or not? Empty string means we never show the dialog.')], - 'confirm_custom_status': [ opt_str, '', _('Should we show the confirm custom status dialog or not? Empty string means we never show the dialog.')], - 'enable_negative_priority': [ opt_bool, False, _('If True, you will be able to set a negative priority to your account in account modification window. BE CAREFUL, when you are logged in with a negative priority, you will NOT receive any message from your server.')], - 'use_gnomekeyring': [opt_bool, True, _('If True, Gajim will use Gnome Keyring (if available) to store account passwords.')], - 'use_kwalletcli': [opt_bool, True, _('If True, Gajim will use KDE Wallet (if kwalletcli is available) to store account passwords.')], - 'show_contacts_number': [opt_bool, True, _('If True, Gajim will show number of online and total contacts in account and group rows.')], - 'treat_incoming_messages': [ opt_str, '', _('Can be empty, \'chat\' or \'normal\'. If not empty, treat all incoming messages as if they were of this type')], - 'scroll_roster_to_last_message': [opt_bool, True, _('If True, Gajim will scroll and select the contact who sent you the last message, if chat window is not already opened.')], - 'use_latex': [opt_bool, False, _('If True, Gajim will convert string between $$ and $$ to an image using dvips and convert before insterting it in chat window.')], - 'change_status_window_timeout': [opt_int, 15, _('Time of inactivity needed before the change status window closes down.')], - 'max_conversation_lines': [opt_int, 500, _('Maximum number of lines that are printed in conversations. Oldest lines are cleared.')], - 'attach_notifications_to_systray': [opt_bool, False, _('If True, notification windows from notification-daemon will be attached to systray icon.')], - 'check_idle_every_foo_seconds': [opt_int, 2, _('Choose interval between 2 checks of idleness.')], - 'latex_png_dpi': [opt_str, '108',_('Change the value to change the size of latex formulas displayed. The higher is larger.') ], - 'uri_schemes': [opt_str, 'aaa aaas acap cap cid crid data dav dict dns fax file ftp go gopher h323 http https icap im imap info ipp iris iris.beep iris.xpc iris.xpcs iris.lwz ldap mid modem msrp msrps mtqp mupdate news nfs nntp opaquelocktoken pop pres rtsp service shttp sip sips snmp soap.beep soap.beeps tag tel telnet tftp thismessage tip tv urn vemmi xmlrpc.beep xmlrpc.beeps z39.50r z39.50s about cvs daap ed2k feed fish git iax2 irc ircs ldaps magnet mms rsync ssh svn sftp smb webcal', _('Valid uri schemes. Only schemes in this list will be accepted as "real" uri. (mailto and xmpp are handled separately)'), True], - 'ask_offline_status_on_connection': [ opt_bool, False, _('Ask offline status message to all offline contacts when connection to an accoutn is established. WARNING: This causes a lot of requests to be sent!') ], - 'shell_like_completion': [ opt_bool, False, _('If True, completion in groupchats will be like a shell auto-completion')], - 'show_self_contact': [opt_str, 'when_other_resource', _('When is self contact row displayed. Can be "always", "when_other_resource" or "never"'), True], - 'audio_input_device': [opt_str, 'autoaudiosrc ! volume name=gajim_vol'], - 'audio_output_device': [opt_str, 'autoaudiosink'], - 'video_input_device': [opt_str, 'autovideosrc ! videoscale ! ffmpegcolorspace'], - 'video_output_device': [opt_str, 'autovideosink'], - 'audio_input_volume': [opt_int, 50], - 'audio_output_volume': [opt_int, 50], - 'use_stun_server': [opt_bool, True, _('If True, Gajim will try to use a STUN server when using jingle. The one in "stun_server" option, or the one given by the jabber server.')], - 'stun_server': [opt_str, '', _('STUN server to use when using jingle')], - 'show_affiliation_in_groupchat': [opt_bool, True, _('If True, Gajim will show affiliation of groupchat occupants by adding a colored square to the status icon')], - } + _('Controls the window where new messages are placed.\n\'always\' - All messages are sent to a single window.\n\'always_with_roster\' - Like \'always\' but the messages are in a single window along with the roster.\n\'never\' - All messages get their own window.\n\'peracct\' - Messages for each account are sent to a specific window.\n\'pertype\' - Each message type (e.g., chats vs. groupchats) are sent to a specific window.')], + 'show_avatar_in_chat': [opt_bool, True, _('If False, you will no longer see the avatar in the chat window.')], + 'escape_key_closes': [opt_bool, True, _('If True, pressing the escape key closes a tab/window.')], + 'compact_view': [opt_bool, False, _('Hides the buttons in chat windows.')], + 'hide_groupchat_banner': [opt_bool, False, _('Hides the banner in a group chat window')], + 'hide_chat_banner': [opt_bool, False, _('Hides the banner in two persons chat window')], + 'hide_groupchat_occupants_list': [opt_bool, False, _('Hides the group chat occupants list in group chat window.')], + 'chat_merge_consecutive_nickname': [opt_bool, False, _('In a chat, show the nickname at the beginning of a line only when it\'s not the same person talking than in previous message.')], + 'chat_merge_consecutive_nickname_indent': [opt_str, ' ', _('Indentation when using merge consecutive nickname.')], + 'use_smooth_scrolling': [opt_bool, True, _('Smooth scroll message in conversation window')], + 'gc_nicknames_colors': [ opt_str, '#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000', _('List of colors, separated by ":", that will be used to color nicknames in group chats.'), True ], + 'ctrl_tab_go_to_next_composing': [opt_bool, True, _('Ctrl-Tab go to next composing tab when none is unread.')], + 'confirm_metacontacts': [ opt_str, '', _('Should we show the confirm metacontacts creation dialog or not? Empty string means we never show the dialog.')], + 'confirm_block': [ opt_str, '', _('Should we show the confirm block contact dialog or not? Empty string means we never show the dialog.')], + 'confirm_custom_status': [ opt_str, '', _('Should we show the confirm custom status dialog or not? Empty string means we never show the dialog.')], + 'enable_negative_priority': [ opt_bool, False, _('If True, you will be able to set a negative priority to your account in account modification window. BE CAREFUL, when you are logged in with a negative priority, you will NOT receive any message from your server.')], + 'use_gnomekeyring': [opt_bool, True, _('If True, Gajim will use Gnome Keyring (if available) to store account passwords.')], + 'use_kwalletcli': [opt_bool, True, _('If True, Gajim will use KDE Wallet (if kwalletcli is available) to store account passwords.')], + 'show_contacts_number': [opt_bool, True, _('If True, Gajim will show number of online and total contacts in account and group rows.')], + 'treat_incoming_messages': [ opt_str, '', _('Can be empty, \'chat\' or \'normal\'. If not empty, treat all incoming messages as if they were of this type')], + 'scroll_roster_to_last_message': [opt_bool, True, _('If True, Gajim will scroll and select the contact who sent you the last message, if chat window is not already opened.')], + 'use_latex': [opt_bool, False, _('If True, Gajim will convert string between $$ and $$ to an image using dvips and convert before insterting it in chat window.')], + 'change_status_window_timeout': [opt_int, 15, _('Time of inactivity needed before the change status window closes down.')], + 'max_conversation_lines': [opt_int, 500, _('Maximum number of lines that are printed in conversations. Oldest lines are cleared.')], + 'attach_notifications_to_systray': [opt_bool, False, _('If True, notification windows from notification-daemon will be attached to systray icon.')], + 'check_idle_every_foo_seconds': [opt_int, 2, _('Choose interval between 2 checks of idleness.')], + 'latex_png_dpi': [opt_str, '108', _('Change the value to change the size of latex formulas displayed. The higher is larger.') ], + 'uri_schemes': [opt_str, 'aaa aaas acap cap cid crid data dav dict dns fax file ftp go gopher h323 http https icap im imap info ipp iris iris.beep iris.xpc iris.xpcs iris.lwz ldap mid modem msrp msrps mtqp mupdate news nfs nntp opaquelocktoken pop pres rtsp service shttp sip sips snmp soap.beep soap.beeps tag tel telnet tftp thismessage tip tv urn vemmi xmlrpc.beep xmlrpc.beeps z39.50r z39.50s about cvs daap ed2k feed fish git iax2 irc ircs ldaps magnet mms rsync ssh svn sftp smb webcal', _('Valid uri schemes. Only schemes in this list will be accepted as "real" uri. (mailto and xmpp are handled separately)'), True], + 'ask_offline_status_on_connection': [ opt_bool, False, _('Ask offline status message to all offline contacts when connection to an accoutn is established. WARNING: This causes a lot of requests to be sent!') ], + 'shell_like_completion': [ opt_bool, False, _('If True, completion in groupchats will be like a shell auto-completion')], + 'show_self_contact': [opt_str, 'when_other_resource', _('When is self contact row displayed. Can be "always", "when_other_resource" or "never"'), True], + 'audio_input_device': [opt_str, 'autoaudiosrc ! volume name=gajim_vol'], + 'audio_output_device': [opt_str, 'autoaudiosink'], + 'video_input_device': [opt_str, 'autovideosrc ! videoscale ! ffmpegcolorspace'], + 'video_output_device': [opt_str, 'autovideosink'], + 'audio_input_volume': [opt_int, 50], + 'audio_output_volume': [opt_int, 50], + 'use_stun_server': [opt_bool, True, _('If True, Gajim will try to use a STUN server when using jingle. The one in "stun_server" option, or the one given by the jabber server.')], + 'stun_server': [opt_str, '', _('STUN server to use when using jingle')], + 'show_affiliation_in_groupchat': [opt_bool, True, _('If True, Gajim will show affiliation of groupchat occupants by adding a colored square to the status icon')], + } - __options_per_key = { - 'accounts': ({ - 'name': [ opt_str, '', '', True ], - 'hostname': [ opt_str, '', '', True ], - 'anonymous_auth': [ opt_bool, False ], - 'savepass': [ opt_bool, False ], - 'password': [ opt_str, '' ], - 'resource': [ opt_str, 'gajim', '', True ], - 'priority': [ opt_int, 5, '', True ], - 'adjust_priority_with_status': [ opt_bool, True, _('Priority will change automatically according to your status. Priorities are defined in autopriority_* options.') ], - 'autopriority_online': [ opt_int, 50], - 'autopriority_chat': [ opt_int, 50], - 'autopriority_away': [ opt_int, 40], - 'autopriority_xa': [ opt_int, 30], - 'autopriority_dnd': [ opt_int, 20], - 'autopriority_invisible': [ opt_int, 10], - 'autoconnect': [ opt_bool, False, '', True ], - 'autoconnect_as': [ opt_str, 'online', _('Status used to autoconnect as. Can be online, chat, away, xa, dnd, invisible. NOTE: this option is used only if restore_last_status is disabled'), True ], - 'restore_last_status': [ opt_bool, False, _('If enabled, restore the last status that was used.') ], - 'autoreconnect': [ opt_bool, True ], - 'autoauth': [ opt_bool, False, _('If True, Contacts requesting authorization will be automatically accepted.')], - 'active': [ opt_bool, True, _('If False, this account will be disabled and will not appear in roster window.'), True], - 'proxy': [ opt_str, '', '', True ], - 'keyid': [ opt_str, '', '', True ], - 'gpg_sign_presence': [ opt_bool, True, _('If disabled, don\'t sign presences with GPG key, even if GPG is configured.') ], - 'keyname': [ opt_str, '', '', True ], - 'enable_esessions': [opt_bool, True, _('Enable ESessions encryption for this account.')], - 'autonegotiate_esessions': [opt_bool, True, _('Should Gajim automatically start an encrypted session when possible?')], - 'connection_types': [ opt_str, 'tls ssl plain', _('Ordered list (space separated) of connection type to try. Can contain tls, ssl or plain')], - 'warn_when_plaintext_connection': [ opt_bool, True, _('Show a warning dialog before sending password on an plaintext connection.') ], - 'warn_when_insecure_ssl_connection': [ opt_bool, True, _('Show a warning dialog before using standard SSL library.') ], - 'ssl_fingerprint_sha1': [ opt_str, '', '', True ], - 'ignore_ssl_errors': [ opt_str, '', _('Space separated list of ssl errors to ignore.') ], - 'use_srv': [ opt_bool, True, '', True ], - 'use_custom_host': [ opt_bool, False, '', True ], - 'custom_port': [ opt_int, 5222, '', True ], - 'custom_host': [ opt_str, '', '', True ], - 'sync_with_global_status': [ opt_bool, False, ], - 'no_log_for': [ opt_str, '' ], - 'minimized_gc': [ opt_str, '' ], - 'attached_gpg_keys': [ opt_str, '' ], - 'keep_alives_enabled': [ opt_bool, True, _('Whitespace sent after inactivity')], - 'ping_alives_enabled': [ opt_bool, True, _('XMPP ping sent after inactivity')], - # send keepalive every N seconds of inactivity - 'keep_alive_every_foo_secs': [ opt_int, 55 ], - 'ping_alive_every_foo_secs': [ opt_int, 120 ], - 'time_for_ping_alive_answer': [ opt_int, 60, _('How many seconds to wait for the answer of ping alive packet before we try to reconnect.') ], - # try for 1 minutes before giving up (aka. timeout after those seconds) - 'try_connecting_for_foo_secs': [ opt_int, 60 ], - 'http_auth': [opt_str, 'ask'], # yes, no, ask - 'dont_ack_subscription': [opt_bool, False, _('Jabberd2 workaround')], - # proxy65 for FT - 'file_transfer_proxies': [opt_str, 'proxy.eu.jabber.org, proxy.jabber.ru, proxy.jabbim.cz'], - 'use_ft_proxies': [opt_bool, True, _('If checked, Gajim will use your IP and proxies defined in file_transfer_proxies option for file transfer.'), True], - 'msgwin-x-position': [opt_int, -1], # Default is to let the wm decide - 'msgwin-y-position': [opt_int, -1], # Default is to let the wm decide - 'msgwin-width': [opt_int, 480], - 'msgwin-height': [opt_int, 440], - 'listen_to_network_manager' : [opt_bool, True], - 'is_zeroconf': [opt_bool, False], - 'last_status': [opt_str, 'online'], - 'last_status_msg': [opt_str, ''], - 'zeroconf_first_name': [ opt_str, '', '', True ], - 'zeroconf_last_name': [ opt_str, '', '', True ], - 'zeroconf_jabber_id': [ opt_str, '', '', True ], - 'zeroconf_email': [ opt_str, '', '', True ], - 'use_env_http_proxy' : [opt_bool, False], - 'answer_receipts' : [opt_bool, True, _('Answer to receipt requests')], - 'request_receipt' : [opt_bool, True, _('Sent receipt requests')], - 'publish_tune': [opt_bool, False], - 'publish_location': [opt_bool, False], - 'subscribe_mood': [opt_bool, True], - 'subscribe_activity': [opt_bool, True], - 'subscribe_tune': [opt_bool, True], - 'subscribe_nick': [opt_bool, True], - 'subscribe_location': [opt_bool, True], - 'ignore_unknown_contacts': [ opt_bool, False ], - 'send_os_info': [ opt_bool, True ], - 'log_encrypted_sessions': [opt_bool, True, _('When negotiating an encrypted session, should Gajim assume you want your messages to be logged?')], - 'send_idle_time': [ opt_bool, True ], - 'roster_version': [opt_str, ''], - 'subscription_request_msg': [opt_str, '', _('Message that is sent to contacts you want to add')], - }, {}), - 'statusmsg': ({ - 'message': [ opt_str, '' ], - 'activity': [ opt_str, '' ], - 'subactivity': [ opt_str, '' ], - 'activity_text': [ opt_str, '' ], - 'mood': [ opt_str, '' ], - 'mood_text': [ opt_str, '' ], - }, {}), - 'defaultstatusmsg': ({ - 'enabled': [ opt_bool, False ], - 'message': [ opt_str, '' ], - }, {}), - 'soundevents': ({ - 'enabled': [ opt_bool, True ], - 'path': [ opt_str, '' ], - }, {}), - 'proxies': ({ - 'type': [ opt_str, 'http' ], - 'host': [ opt_str, '' ], - 'port': [ opt_int, 3128 ], - 'useauth': [ opt_bool, False ], - 'user': [ opt_str, '' ], - 'pass': [ opt_str, '' ], - 'bosh_uri': [ opt_str, '' ], - 'bosh_useproxy': [ opt_bool, False ], - 'bosh_wait': [ opt_int, 30 ], - 'bosh_hold': [ opt_int, 2 ], - 'bosh_content': [ opt_str, 'text/xml; charset=utf-8' ], - 'bosh_http_pipelining': [ opt_bool, False ], - 'bosh_wait_for_restart_response': [ opt_bool, False ], - }, {}), - 'themes': ({ - 'accounttextcolor': [ opt_color, 'black', '', True ], - 'accountbgcolor': [ opt_color, 'white', '', True ], - 'accountfont': [ opt_str, '', '', True ], - 'accountfontattrs': [ opt_str, 'B', '', True ], - 'grouptextcolor': [ opt_color, 'black', '', True ], - 'groupbgcolor': [ opt_color, 'white', '', True ], - 'groupfont': [ opt_str, '', '', True ], - 'groupfontattrs': [ opt_str, 'I', '', True ], - 'contacttextcolor': [ opt_color, 'black', '', True ], - 'contactbgcolor': [ opt_color, 'white', '', True ], - 'contactfont': [ opt_str, '', '', True ], - 'contactfontattrs': [ opt_str, '', '', True ], - 'bannertextcolor': [ opt_color, 'black', '', True ], - 'bannerbgcolor': [ opt_color, '', '', True ], - 'bannerfont': [ opt_str, '', '', True ], - 'bannerfontattrs': [ opt_str, 'B', '', True ], + __options_per_key = { + 'accounts': ({ + 'name': [ opt_str, '', '', True ], + 'hostname': [ opt_str, '', '', True ], + 'anonymous_auth': [ opt_bool, False ], + 'savepass': [ opt_bool, False ], + 'password': [ opt_str, '' ], + 'resource': [ opt_str, 'gajim', '', True ], + 'priority': [ opt_int, 5, '', True ], + 'adjust_priority_with_status': [ opt_bool, True, _('Priority will change automatically according to your status. Priorities are defined in autopriority_* options.') ], + 'autopriority_online': [ opt_int, 50], + 'autopriority_chat': [ opt_int, 50], + 'autopriority_away': [ opt_int, 40], + 'autopriority_xa': [ opt_int, 30], + 'autopriority_dnd': [ opt_int, 20], + 'autopriority_invisible': [ opt_int, 10], + 'autoconnect': [ opt_bool, False, '', True ], + 'autoconnect_as': [ opt_str, 'online', _('Status used to autoconnect as. Can be online, chat, away, xa, dnd, invisible. NOTE: this option is used only if restore_last_status is disabled'), True ], + 'restore_last_status': [ opt_bool, False, _('If enabled, restore the last status that was used.') ], + 'autoreconnect': [ opt_bool, True ], + 'autoauth': [ opt_bool, False, _('If True, Contacts requesting authorization will be automatically accepted.')], + 'active': [ opt_bool, True, _('If False, this account will be disabled and will not appear in roster window.'), True], + 'proxy': [ opt_str, '', '', True ], + 'keyid': [ opt_str, '', '', True ], + 'gpg_sign_presence': [ opt_bool, True, _('If disabled, don\'t sign presences with GPG key, even if GPG is configured.') ], + 'keyname': [ opt_str, '', '', True ], + 'enable_esessions': [opt_bool, True, _('Enable ESessions encryption for this account.')], + 'autonegotiate_esessions': [opt_bool, True, _('Should Gajim automatically start an encrypted session when possible?')], + 'connection_types': [ opt_str, 'tls ssl plain', _('Ordered list (space separated) of connection type to try. Can contain tls, ssl or plain')], + 'warn_when_plaintext_connection': [ opt_bool, True, _('Show a warning dialog before sending password on an plaintext connection.') ], + 'warn_when_insecure_ssl_connection': [ opt_bool, True, _('Show a warning dialog before using standard SSL library.') ], + 'ssl_fingerprint_sha1': [ opt_str, '', '', True ], + 'ignore_ssl_errors': [ opt_str, '', _('Space separated list of ssl errors to ignore.') ], + 'use_srv': [ opt_bool, True, '', True ], + 'use_custom_host': [ opt_bool, False, '', True ], + 'custom_port': [ opt_int, 5222, '', True ], + 'custom_host': [ opt_str, '', '', True ], + 'sync_with_global_status': [ opt_bool, False, ], + 'no_log_for': [ opt_str, '' ], + 'minimized_gc': [ opt_str, '' ], + 'attached_gpg_keys': [ opt_str, '' ], + 'keep_alives_enabled': [ opt_bool, True, _('Whitespace sent after inactivity')], + 'ping_alives_enabled': [ opt_bool, True, _('XMPP ping sent after inactivity')], + # send keepalive every N seconds of inactivity + 'keep_alive_every_foo_secs': [ opt_int, 55 ], + 'ping_alive_every_foo_secs': [ opt_int, 120 ], + 'time_for_ping_alive_answer': [ opt_int, 60, _('How many seconds to wait for the answer of ping alive packet before we try to reconnect.') ], + # try for 1 minutes before giving up (aka. timeout after those seconds) + 'try_connecting_for_foo_secs': [ opt_int, 60 ], + 'http_auth': [opt_str, 'ask'], # yes, no, ask + 'dont_ack_subscription': [opt_bool, False, _('Jabberd2 workaround')], + # proxy65 for FT + 'file_transfer_proxies': [opt_str, 'proxy.eu.jabber.org, proxy.jabber.ru, proxy.jabbim.cz'], + 'use_ft_proxies': [opt_bool, True, _('If checked, Gajim will use your IP and proxies defined in file_transfer_proxies option for file transfer.'), True], + 'msgwin-x-position': [opt_int, -1], # Default is to let the wm decide + 'msgwin-y-position': [opt_int, -1], # Default is to let the wm decide + 'msgwin-width': [opt_int, 480], + 'msgwin-height': [opt_int, 440], + 'listen_to_network_manager': [opt_bool, True], + 'is_zeroconf': [opt_bool, False], + 'last_status': [opt_str, 'online'], + 'last_status_msg': [opt_str, ''], + 'zeroconf_first_name': [ opt_str, '', '', True ], + 'zeroconf_last_name': [ opt_str, '', '', True ], + 'zeroconf_jabber_id': [ opt_str, '', '', True ], + 'zeroconf_email': [ opt_str, '', '', True ], + 'use_env_http_proxy': [opt_bool, False], + 'answer_receipts': [opt_bool, True, _('Answer to receipt requests')], + 'request_receipt': [opt_bool, True, _('Sent receipt requests')], + 'publish_tune': [opt_bool, False], + 'publish_location': [opt_bool, False], + 'subscribe_mood': [opt_bool, True], + 'subscribe_activity': [opt_bool, True], + 'subscribe_tune': [opt_bool, True], + 'subscribe_nick': [opt_bool, True], + 'subscribe_location': [opt_bool, True], + 'ignore_unknown_contacts': [ opt_bool, False ], + 'send_os_info': [ opt_bool, True ], + 'log_encrypted_sessions': [opt_bool, True, _('When negotiating an encrypted session, should Gajim assume you want your messages to be logged?')], + 'send_idle_time': [ opt_bool, True ], + 'roster_version': [opt_str, ''], + 'subscription_request_msg': [opt_str, '', _('Message that is sent to contacts you want to add')], + }, {}), + 'statusmsg': ({ + 'message': [ opt_str, '' ], + 'activity': [ opt_str, '' ], + 'subactivity': [ opt_str, '' ], + 'activity_text': [ opt_str, '' ], + 'mood': [ opt_str, '' ], + 'mood_text': [ opt_str, '' ], + }, {}), + 'defaultstatusmsg': ({ + 'enabled': [ opt_bool, False ], + 'message': [ opt_str, '' ], + }, {}), + 'soundevents': ({ + 'enabled': [ opt_bool, True ], + 'path': [ opt_str, '' ], + }, {}), + 'proxies': ({ + 'type': [ opt_str, 'http' ], + 'host': [ opt_str, '' ], + 'port': [ opt_int, 3128 ], + 'useauth': [ opt_bool, False ], + 'user': [ opt_str, '' ], + 'pass': [ opt_str, '' ], + 'bosh_uri': [ opt_str, '' ], + 'bosh_useproxy': [ opt_bool, False ], + 'bosh_wait': [ opt_int, 30 ], + 'bosh_hold': [ opt_int, 2 ], + 'bosh_content': [ opt_str, 'text/xml; charset=utf-8' ], + 'bosh_http_pipelining': [ opt_bool, False ], + 'bosh_wait_for_restart_response': [ opt_bool, False ], + }, {}), + 'themes': ({ + 'accounttextcolor': [ opt_color, 'black', '', True ], + 'accountbgcolor': [ opt_color, 'white', '', True ], + 'accountfont': [ opt_str, '', '', True ], + 'accountfontattrs': [ opt_str, 'B', '', True ], + 'grouptextcolor': [ opt_color, 'black', '', True ], + 'groupbgcolor': [ opt_color, 'white', '', True ], + 'groupfont': [ opt_str, '', '', True ], + 'groupfontattrs': [ opt_str, 'I', '', True ], + 'contacttextcolor': [ opt_color, 'black', '', True ], + 'contactbgcolor': [ opt_color, 'white', '', True ], + 'contactfont': [ opt_str, '', '', True ], + 'contactfontattrs': [ opt_str, '', '', True ], + 'bannertextcolor': [ opt_color, 'black', '', True ], + 'bannerbgcolor': [ opt_color, '', '', True ], + 'bannerfont': [ opt_str, '', '', True ], + 'bannerfontattrs': [ opt_str, 'B', '', True ], - # http://www.pitt.edu/~nisg/cis/web/cgi/rgb.html - 'state_inactive_color': [ opt_color, 'grey62' ], - 'state_composing_color': [ opt_color, 'green4' ], - 'state_paused_color': [ opt_color, 'mediumblue' ], - 'state_gone_color': [ opt_color, 'grey' ], + # http://www.pitt.edu/~nisg/cis/web/cgi/rgb.html + 'state_inactive_color': [ opt_color, 'grey62' ], + 'state_composing_color': [ opt_color, 'green4' ], + 'state_paused_color': [ opt_color, 'mediumblue' ], + 'state_gone_color': [ opt_color, 'grey' ], - # MUC chat states - 'state_muc_msg_color': [ opt_color, 'mediumblue' ], - 'state_muc_directed_msg_color': [ opt_color, 'red2' ], - }, {}), - 'contacts': ({ - 'gpg_enabled': [ opt_bool, False, _('Is OpenPGP enabled for this contact?')], - 'autonegotiate_esessions': [opt_bool, True, _('Should Gajim automatically start an encrypted session with this contact when possible?')], - 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')], - }, {}), - 'rooms': ({ - 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')], - }, {}), - 'notifications': ({ - 'event': [opt_str, ''], - 'recipient_type': [opt_str, 'all'], - 'recipients': [opt_str, ''], - 'status': [opt_str, 'all', _('all or space separated status')], - 'tab_opened': [opt_str, 'both', _("'yes', 'no', or 'both'")], - 'sound': [opt_str, '', _("'yes', 'no' or ''")], - 'sound_file': [opt_str, ''], - 'popup': [opt_str, '', _("'yes', 'no' or ''")], - 'auto_open': [opt_str, '', _("'yes', 'no' or ''")], - 'run_command': [opt_bool, False], - 'command': [opt_str, ''], - 'systray': [opt_str, '', _("'yes', 'no' or ''")], - 'roster': [opt_str, '', _("'yes', 'no' or ''")], - 'urgency_hint': [opt_bool, False], - }, {}), - 'plugins': ({ - 'active': [opt_bool, False, _('State whether plugins should be activated on exit (this is saved on Gajim exit). This option SHOULD NOT be used to (de)activate plug-ins. Use GUI instead.')], - },{}), - } + # MUC chat states + 'state_muc_msg_color': [ opt_color, 'mediumblue' ], + 'state_muc_directed_msg_color': [ opt_color, 'red2' ], + }, {}), + 'contacts': ({ + 'gpg_enabled': [ opt_bool, False, _('Is OpenPGP enabled for this contact?')], + 'autonegotiate_esessions': [opt_bool, True, _('Should Gajim automatically start an encrypted session with this contact when possible?')], + 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')], + }, {}), + 'rooms': ({ + 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')], + }, {}), + 'notifications': ({ + 'event': [opt_str, ''], + 'recipient_type': [opt_str, 'all'], + 'recipients': [opt_str, ''], + 'status': [opt_str, 'all', _('all or space separated status')], + 'tab_opened': [opt_str, 'both', _("'yes', 'no', or 'both'")], + 'sound': [opt_str, '', _("'yes', 'no' or ''")], + 'sound_file': [opt_str, ''], + 'popup': [opt_str, '', _("'yes', 'no' or ''")], + 'auto_open': [opt_str, '', _("'yes', 'no' or ''")], + 'run_command': [opt_bool, False], + 'command': [opt_str, ''], + 'systray': [opt_str, '', _("'yes', 'no' or ''")], + 'roster': [opt_str, '', _("'yes', 'no' or ''")], + 'urgency_hint': [opt_bool, False], + }, {}), + 'plugins': ({ + 'active': [opt_bool, False, _('State whether plugins should be activated on exit (this is saved on Gajim exit). This option SHOULD NOT be used to (de)activate plug-ins. Use GUI instead.')], + }, {}), + } - statusmsg_default = { - _('Sleeping'): [ 'ZZZZzzzzzZZZZZ', 'inactive', 'sleeping', '', 'sleepy', '' ], - _('Back soon'): [ _('Back in some minutes.'), '', '', '', '', '' ], - _('Eating'): [ _("I'm eating, so leave me a message."), 'eating', 'other', '', '', '' ], - _('Movie'): [ _("I'm watching a movie."), 'relaxing', 'watching_a_movie', '', '', '' ], - _('Working'): [ _("I'm working."), 'working', 'other', '', '', '' ], - _('Phone'): [ _("I'm on the phone."), 'talking', 'on_the_phone', '', '', '' ], - _('Out'): [ _("I'm out enjoying life."), 'relaxing', 'going_out', '', '', '' ], - '_last_online': ['', '', '', '', '', ''], - '_last_chat': ['', '', '', '', '', ''], - '_last_away': ['', '', '', '', '', ''], - '_last_xa': ['', '', '', '', '', ''], - '_last_dnd': ['', '', '', '', '', ''], - '_last_invisible': ['', '', '', '', '', ''], - '_last_offline': ['', '', '', '', '', ''], - } + statusmsg_default = { + _('Sleeping'): [ 'ZZZZzzzzzZZZZZ', 'inactive', 'sleeping', '', 'sleepy', '' ], + _('Back soon'): [ _('Back in some minutes.'), '', '', '', '', '' ], + _('Eating'): [ _("I'm eating, so leave me a message."), 'eating', 'other', '', '', '' ], + _('Movie'): [ _("I'm watching a movie."), 'relaxing', 'watching_a_movie', '', '', '' ], + _('Working'): [ _("I'm working."), 'working', 'other', '', '', '' ], + _('Phone'): [ _("I'm on the phone."), 'talking', 'on_the_phone', '', '', '' ], + _('Out'): [ _("I'm out enjoying life."), 'relaxing', 'going_out', '', '', '' ], + '_last_online': ['', '', '', '', '', ''], + '_last_chat': ['', '', '', '', '', ''], + '_last_away': ['', '', '', '', '', ''], + '_last_xa': ['', '', '', '', '', ''], + '_last_dnd': ['', '', '', '', '', ''], + '_last_invisible': ['', '', '', '', '', ''], + '_last_offline': ['', '', '', '', '', ''], + } - defaultstatusmsg_default = { - 'online': [ False, _("I'm available.") ], - 'chat': [ False, _("I'm free for chat.") ], - 'away': [ False, _('Be right back.') ], - 'xa': [ False, _("I'm not available.") ], - 'dnd': [ False, _('Do not disturb.') ], - 'invisible': [ False, _('Bye!') ], - 'offline': [ False, _('Bye!') ], - } + defaultstatusmsg_default = { + 'online': [ False, _("I'm available.") ], + 'chat': [ False, _("I'm free for chat.") ], + 'away': [ False, _('Be right back.') ], + 'xa': [ False, _("I'm not available.") ], + 'dnd': [ False, _('Do not disturb.') ], + 'invisible': [ False, _('Bye!') ], + 'offline': [ False, _('Bye!') ], + } - soundevents_default = { - 'first_message_received': [ True, 'message1.wav' ], - 'next_message_received_focused': [ True, 'message2.wav' ], - 'next_message_received_unfocused': [ True, 'message2.wav' ], - 'contact_connected': [ True, 'connected.wav' ], - 'contact_disconnected': [ True, 'disconnected.wav' ], - 'message_sent': [ True, 'sent.wav' ], - 'muc_message_highlight': [ True, 'gc_message1.wav', _('Sound to play when a group chat message contains one of the words in muc_highlight_words, or when a group chat message contains your nickname.')], - 'muc_message_received': [ False, 'gc_message2.wav', _('Sound to play when any MUC message arrives.') ], - 'gmail_received': [ False, 'message1.wav' ], - } + soundevents_default = { + 'first_message_received': [ True, 'message1.wav' ], + 'next_message_received_focused': [ True, 'message2.wav' ], + 'next_message_received_unfocused': [ True, 'message2.wav' ], + 'contact_connected': [ True, 'connected.wav' ], + 'contact_disconnected': [ True, 'disconnected.wav' ], + 'message_sent': [ True, 'sent.wav' ], + 'muc_message_highlight': [ True, 'gc_message1.wav', _('Sound to play when a group chat message contains one of the words in muc_highlight_words, or when a group chat message contains your nickname.')], + 'muc_message_received': [ False, 'gc_message2.wav', _('Sound to play when any MUC message arrives.') ], + 'gmail_received': [ False, 'message1.wav' ], + } - themes_default = { - # sorted alphanum - _('default'): [ '', '', '', 'B', '', '','', 'I', '', '', '', '', '','', - '', 'B' ], + themes_default = { + # sorted alphanum + _('default'): [ '', '', '', 'B', '', '', '', 'I', '', '', '', '', '', '', + '', 'B' ], - _('green'): [ '', '#94aa8c', '', 'B', '#0000ff', '#eff3e7', - '', 'I', '#000000', '', '', '', '', - '#94aa8c', '', 'B' ], + _('green'): [ '', '#94aa8c', '', 'B', '#0000ff', '#eff3e7', + '', 'I', '#000000', '', '', '', '', + '#94aa8c', '', 'B' ], - _('grocery'): [ '', '#6bbe18', '', 'B', '#12125a', '#ceefad', - '', 'I', '#000000', '#efb26b', '', '', '', - '#108abd', '', 'B' ], + _('grocery'): [ '', '#6bbe18', '', 'B', '#12125a', '#ceefad', + '', 'I', '#000000', '#efb26b', '', '', '', + '#108abd', '', 'B' ], - _('human'): [ '', '#996442', '', 'B', '#ab5920', '#e3ca94', - '', 'I', '#000000', '', '', '', '', - '#996442', '', 'B' ], + _('human'): [ '', '#996442', '', 'B', '#ab5920', '#e3ca94', + '', 'I', '#000000', '', '', '', '', + '#996442', '', 'B' ], - _('marine'): [ '', '#918caa', '', 'B', '', '#e9e7f3', - '', 'I', '#000000', '', '', '', '', - '#918caa', '', 'B' ], + _('marine'): [ '', '#918caa', '', 'B', '', '#e9e7f3', + '', 'I', '#000000', '', '', '', '', + '#918caa', '', 'B' ], - } + } - def foreach(self, cb, data = None): - for opt in self.__options: - cb(data, opt, None, self.__options[opt]) - for opt in self.__options_per_key: - cb(data, opt, None, None) - dict_ = self.__options_per_key[opt][1] - for opt2 in dict_.keys(): - cb(data, opt2, [opt], None) - for opt3 in dict_[opt2]: - cb(data, opt3, [opt, opt2], dict_[opt2][opt3]) + def foreach(self, cb, data = None): + for opt in self.__options: + cb(data, opt, None, self.__options[opt]) + for opt in self.__options_per_key: + cb(data, opt, None, None) + dict_ = self.__options_per_key[opt][1] + for opt2 in dict_.keys(): + cb(data, opt2, [opt], None) + for opt3 in dict_[opt2]: + cb(data, opt3, [opt, opt2], dict_[opt2][opt3]) - def get_children(self, node=None): - """ - Tree-like interface - """ - if node is None: - for child, option in self.__options.iteritems(): - yield (child, ), option - for grandparent in self.__options_per_key: - yield (grandparent, ), None - elif len(node) == 1: - grandparent, = node - for parent in self.__options_per_key[grandparent][1]: - yield (grandparent, parent), None - elif len(node) == 2: - grandparent, parent = node - children = self.__options_per_key[grandparent][1][parent] - for child, option in children.iteritems(): - yield (grandparent, parent, child), option - else: - raise ValueError('Invalid node') + def get_children(self, node=None): + """ + Tree-like interface + """ + if node is None: + for child, option in self.__options.iteritems(): + yield (child, ), option + for grandparent in self.__options_per_key: + yield (grandparent, ), None + elif len(node) == 1: + grandparent, = node + for parent in self.__options_per_key[grandparent][1]: + yield (grandparent, parent), None + elif len(node) == 2: + grandparent, parent = node + children = self.__options_per_key[grandparent][1][parent] + for child, option in children.iteritems(): + yield (grandparent, parent, child), option + else: + raise ValueError('Invalid node') - def is_valid_int(self, val): - try: - ival = int(val) - except Exception: - return None - return ival + def is_valid_int(self, val): + try: + ival = int(val) + except Exception: + return None + return ival - def is_valid_bool(self, val): - if val == 'True': - return True - elif val == 'False': - return False - else: - ival = self.is_valid_int(val) - if ival: - return True - elif ival is None: - return None - return False - return None + def is_valid_bool(self, val): + if val == 'True': + return True + elif val == 'False': + return False + else: + ival = self.is_valid_int(val) + if ival: + return True + elif ival is None: + return None + return False + return None - def is_valid_string(self, val): - return val + def is_valid_string(self, val): + return val - def is_valid(self, type_, val): - if not type_: - return None - if type_[0] == 'boolean': - return self.is_valid_bool(val) - elif type_[0] == 'integer': - return self.is_valid_int(val) - elif type_[0] == 'string': - return self.is_valid_string(val) - else: - if re.match(type_[1], val): - return val - else: - return None + def is_valid(self, type_, val): + if not type_: + return None + if type_[0] == 'boolean': + return self.is_valid_bool(val) + elif type_[0] == 'integer': + return self.is_valid_int(val) + elif type_[0] == 'string': + return self.is_valid_string(val) + else: + if re.match(type_[1], val): + return val + else: + return None - def set(self, optname, value): - if optname not in self.__options: -# raise RuntimeError, 'option %s does not exist' % optname - return - opt = self.__options[optname] - value = self.is_valid(opt[OPT_TYPE], value) - if value is None: -# raise RuntimeError, 'value of %s cannot be None' % optname - return + def set(self, optname, value): + if optname not in self.__options: +# raise RuntimeError, 'option %s does not exist' % optname + return + opt = self.__options[optname] + value = self.is_valid(opt[OPT_TYPE], value) + if value is None: +# raise RuntimeError, 'value of %s cannot be None' % optname + return - opt[OPT_VAL] = value + opt[OPT_VAL] = value - def get(self, optname = None): - if not optname: - return self.__options.keys() - if optname not in self.__options: - return None - return self.__options[optname][OPT_VAL] + def get(self, optname = None): + if not optname: + return self.__options.keys() + if optname not in self.__options: + return None + return self.__options[optname][OPT_VAL] - def get_desc(self, optname): - if optname not in self.__options: - return None - if len(self.__options[optname]) > OPT_DESC: - return self.__options[optname][OPT_DESC] + def get_desc(self, optname): + if optname not in self.__options: + return None + if len(self.__options[optname]) > OPT_DESC: + return self.__options[optname][OPT_DESC] - def get_restart(self, optname): - if optname not in self.__options: - return None - if len(self.__options[optname]) > OPT_RESTART: - return self.__options[optname][OPT_RESTART] + def get_restart(self, optname): + if optname not in self.__options: + return None + if len(self.__options[optname]) > OPT_RESTART: + return self.__options[optname][OPT_RESTART] - def add_per(self, typename, name): # per_group_of_option - if typename not in self.__options_per_key: -# raise RuntimeError, 'option %s does not exist' % typename - return + def add_per(self, typename, name): # per_group_of_option + if typename not in self.__options_per_key: +# raise RuntimeError, 'option %s does not exist' % typename + return - opt = self.__options_per_key[typename] - if name in opt[1]: - # we already have added group name before - return 'you already have added %s before' % name - opt[1][name] = copy.deepcopy(opt[0]) + opt = self.__options_per_key[typename] + if name in opt[1]: + # we already have added group name before + return 'you already have added %s before' % name + opt[1][name] = copy.deepcopy(opt[0]) - def del_per(self, typename, name, subname = None): # per_group_of_option - if typename not in self.__options_per_key: -# raise RuntimeError, 'option %s does not exist' % typename - return + def del_per(self, typename, name, subname = None): # per_group_of_option + if typename not in self.__options_per_key: +# raise RuntimeError, 'option %s does not exist' % typename + return - opt = self.__options_per_key[typename] - if subname is None: - del opt[1][name] - # if subname is specified, delete the item in the group. - elif subname in opt[1][name]: - del opt[1][name][subname] + opt = self.__options_per_key[typename] + if subname is None: + del opt[1][name] + # if subname is specified, delete the item in the group. + elif subname in opt[1][name]: + del opt[1][name][subname] - def set_per(self, optname, key, subname, value): # per_group_of_option - if optname not in self.__options_per_key: -# raise RuntimeError, 'option %s does not exist' % optname - return - if not key: - return - dict_ = self.__options_per_key[optname][1] - if key not in dict_: -# raise RuntimeError, '%s is not a key of %s' % (key, dict_) - self.add_per(optname, key) - obj = dict_[key] - if subname not in obj: -# raise RuntimeError, '%s is not a key of %s' % (subname, obj) - return - subobj = obj[subname] - value = self.is_valid(subobj[OPT_TYPE], value) - if value is None: -# raise RuntimeError, '%s of %s cannot be None' % optname - return - subobj[OPT_VAL] = value + def set_per(self, optname, key, subname, value): # per_group_of_option + if optname not in self.__options_per_key: +# raise RuntimeError, 'option %s does not exist' % optname + return + if not key: + return + dict_ = self.__options_per_key[optname][1] + if key not in dict_: +# raise RuntimeError, '%s is not a key of %s' % (key, dict_) + self.add_per(optname, key) + obj = dict_[key] + if subname not in obj: +# raise RuntimeError, '%s is not a key of %s' % (subname, obj) + return + subobj = obj[subname] + value = self.is_valid(subobj[OPT_TYPE], value) + if value is None: +# raise RuntimeError, '%s of %s cannot be None' % optname + return + subobj[OPT_VAL] = value - def get_per(self, optname, key = None, subname = None): # per_group_of_option - if optname not in self.__options_per_key: - return None - dict_ = self.__options_per_key[optname][1] - if not key: - return dict_.keys() - if key not in dict_: - if optname in self.__options_per_key \ - and subname in self.__options_per_key[optname][0]: - return self.__options_per_key \ - [optname][0][subname][1] - return None - obj = dict_[key] - if not subname: - return obj - if subname not in obj: - return None - return obj[subname][OPT_VAL] + def get_per(self, optname, key = None, subname = None): # per_group_of_option + if optname not in self.__options_per_key: + return None + dict_ = self.__options_per_key[optname][1] + if not key: + return dict_.keys() + if key not in dict_: + if optname in self.__options_per_key \ + and subname in self.__options_per_key[optname][0]: + return self.__options_per_key \ + [optname][0][subname][1] + return None + obj = dict_[key] + if not subname: + return obj + if subname not in obj: + return None + return obj[subname][OPT_VAL] - def get_desc_per(self, optname, key = None, subname = None): - if optname not in self.__options_per_key: - return None - dict_ = self.__options_per_key[optname][1] - if not key: - return None - if key not in dict_: - return None - obj = dict_[key] - if not subname: - return None - if subname not in obj: - return None - if len(obj[subname]) > OPT_DESC: - return obj[subname][OPT_DESC] - return None + def get_desc_per(self, optname, key = None, subname = None): + if optname not in self.__options_per_key: + return None + dict_ = self.__options_per_key[optname][1] + if not key: + return None + if key not in dict_: + return None + obj = dict_[key] + if not subname: + return None + if subname not in obj: + return None + if len(obj[subname]) > OPT_DESC: + return obj[subname][OPT_DESC] + return None - def get_restart_per(self, optname, key = None, subname = None): - if optname not in self.__options_per_key: - return False - dict_ = self.__options_per_key[optname][1] - if not key: - return False - if key not in dict_: - return False - obj = dict_[key] - if not subname: - return False - if subname not in obj: - return False - if len(obj[subname]) > OPT_RESTART: - return obj[subname][OPT_RESTART] - return False + def get_restart_per(self, optname, key = None, subname = None): + if optname not in self.__options_per_key: + return False + dict_ = self.__options_per_key[optname][1] + if not key: + return False + if key not in dict_: + return False + obj = dict_[key] + if not subname: + return False + if subname not in obj: + return False + if len(obj[subname]) > OPT_RESTART: + return obj[subname][OPT_RESTART] + return False - def should_log(self, account, jid): - """ - Should conversations between a local account and a remote jid be logged? - """ - no_log_for = self.get_per('accounts', account, 'no_log_for') + def should_log(self, account, jid): + """ + Should conversations between a local account and a remote jid be logged? + """ + no_log_for = self.get_per('accounts', account, 'no_log_for') - if not no_log_for: - no_log_for = '' + if not no_log_for: + no_log_for = '' - no_log_for = no_log_for.split() + no_log_for = no_log_for.split() - return (account not in no_log_for) and (jid not in no_log_for) + return (account not in no_log_for) and (jid not in no_log_for) - def __init__(self): - #init default values - for event in self.soundevents_default: - default = self.soundevents_default[event] - self.add_per('soundevents', event) - self.set_per('soundevents', event, 'enabled', default[0]) - self.set_per('soundevents', event, 'path', default[1]) + def __init__(self): + #init default values + for event in self.soundevents_default: + default = self.soundevents_default[event] + self.add_per('soundevents', event) + self.set_per('soundevents', event, 'enabled', default[0]) + self.set_per('soundevents', event, 'path', default[1]) - for status in self.defaultstatusmsg_default: - default = self.defaultstatusmsg_default[status] - self.add_per('defaultstatusmsg', status) - self.set_per('defaultstatusmsg', status, 'enabled', default[0]) - self.set_per('defaultstatusmsg', status, 'message', default[1]) - -# vim: se ts=3: + for status in self.defaultstatusmsg_default: + default = self.defaultstatusmsg_default[status] + self.add_per('defaultstatusmsg', status) + self.set_per('defaultstatusmsg', status, 'enabled', default[0]) + self.set_per('defaultstatusmsg', status, 'message', default[1]) diff --git a/src/common/configpaths.py b/src/common/configpaths.py index 7ed8472cc..5b542e5a2 100644 --- a/src/common/configpaths.py +++ b/src/common/configpaths.py @@ -28,9 +28,9 @@ import tempfile import defs HAVE_XDG = True try: - import xdg.BaseDirectory + import xdg.BaseDirectory except: - HAVE_XDG = False + HAVE_XDG = False ( TYPE_CONFIG, @@ -58,136 +58,134 @@ TYPE_DATA # not displayed to the user, Unicode is not really necessary here. def fse(s): - """ - Convert from filesystem encoding if not already Unicode - """ - return unicode(s, sys.getfilesystemencoding()) + """ + Convert from filesystem encoding if not already Unicode + """ + return unicode(s, sys.getfilesystemencoding()) def windowsify(s): - if os.name == 'nt': - return s.capitalize() - return s + if os.name == 'nt': + return s.capitalize() + return s class ConfigPaths: - def __init__(self): - # {'name': (type, path), } type can be TYPE_CONFIG, TYPE_CACHE, TYPE_DATA - # or None - self.paths = {} + def __init__(self): + # {'name': (type, path), } type can be TYPE_CONFIG, TYPE_CACHE, TYPE_DATA + # or None + self.paths = {} - if os.name == 'nt': - try: - # Documents and Settings\[User Name]\Application Data\Gajim + if os.name == 'nt': + try: + # Documents and Settings\[User Name]\Application Data\Gajim - # How are we supposed to know what encoding the environment - # variable 'appdata' is in? Assuming it to be in filesystem - # encoding. - self.config_root = self.cache_root = self.data_root = \ - os.path.join(fse(os.environ[u'appdata']), u'Gajim') - except KeyError: - # win9x, in cwd - self.config_root = self.cache_root = self.data_root = u'.' - else: # Unices - # Pass in an Unicode string, and hopefully get one back. - if HAVE_XDG: - self.config_root = xdg.BaseDirectory.load_first_config('gajim') - if not self.config_root: - # Folder doesn't exist yet. - self.config_root = os.path.join(xdg.BaseDirectory.\ - xdg_config_dirs[0], u'gajim') + # How are we supposed to know what encoding the environment + # variable 'appdata' is in? Assuming it to be in filesystem + # encoding. + self.config_root = self.cache_root = self.data_root = \ + os.path.join(fse(os.environ[u'appdata']), u'Gajim') + except KeyError: + # win9x, in cwd + self.config_root = self.cache_root = self.data_root = u'.' + else: # Unices + # Pass in an Unicode string, and hopefully get one back. + if HAVE_XDG: + self.config_root = xdg.BaseDirectory.load_first_config('gajim') + if not self.config_root: + # Folder doesn't exist yet. + self.config_root = os.path.join(xdg.BaseDirectory.\ + xdg_config_dirs[0], u'gajim') - self.cache_root = os.path.join(xdg.BaseDirectory.xdg_cache_home, - u'gajim') + self.cache_root = os.path.join(xdg.BaseDirectory.xdg_cache_home, + u'gajim') - self.data_root = xdg.BaseDirectory.save_data_path('gajim') - if not self.data_root: - self.data_root = os.path.join(xdg.BaseDirectory.\ - xdg_data_dirs[0], u'gajim') - else: - expand = os.path.expanduser - base = os.getenv('XDG_CONFIG_HOME') or expand(u'~/.config') - self.config_root = os.path.join(base, u'gajim') - base = os.getenv('XDG_CACHE_HOME') or expand(u'~/.cache') - self.cache_root = os.path.join(base, u'gajim') - base = os.getenv('XDG_DATA_HOME') or expand(u'~/.local/share') - self.data_root = os.path.join(base, u'gajim') + self.data_root = xdg.BaseDirectory.save_data_path('gajim') + if not self.data_root: + self.data_root = os.path.join(xdg.BaseDirectory.\ + xdg_data_dirs[0], u'gajim') + else: + expand = os.path.expanduser + base = os.getenv('XDG_CONFIG_HOME') or expand(u'~/.config') + self.config_root = os.path.join(base, u'gajim') + base = os.getenv('XDG_CACHE_HOME') or expand(u'~/.cache') + self.cache_root = os.path.join(base, u'gajim') + base = os.getenv('XDG_DATA_HOME') or expand(u'~/.local/share') + self.data_root = os.path.join(base, u'gajim') - def add(self, name, type_, path): - self.paths[name] = (type_, path) + def add(self, name, type_, path): + self.paths[name] = (type_, path) - def __getitem__(self, key): - type_, path = self.paths[key] - if type_ == TYPE_CONFIG: - return os.path.join(self.config_root, path) - elif type_ == TYPE_CACHE: - return os.path.join(self.cache_root, path) - elif type_ == TYPE_DATA: - return os.path.join(self.data_root, path) - return path + def __getitem__(self, key): + type_, path = self.paths[key] + if type_ == TYPE_CONFIG: + return os.path.join(self.config_root, path) + elif type_ == TYPE_CACHE: + return os.path.join(self.cache_root, path) + elif type_ == TYPE_DATA: + return os.path.join(self.data_root, path) + return path - def get(self, key, default=None): - try: - return self[key] - except KeyError: - return default + def get(self, key, default=None): + try: + return self[key] + except KeyError: + return default - def iteritems(self): - for key in self.paths.iterkeys(): - yield (key, self[key]) + def iteritems(self): + for key in self.paths.iterkeys(): + yield (key, self[key]) - def init(self, root=None): - if root is not None: - self.config_root = self.cache_root = self.data_root = root + def init(self, root=None): + if root is not None: + self.config_root = self.cache_root = self.data_root = root - d = {'MY_DATA': '', 'LOG_DB': u'logs.db', 'MY_CACERTS': u'cacerts.pem', - 'MY_EMOTS': u'emoticons', 'MY_ICONSETS': u'iconsets', - 'MY_MOOD_ICONSETS': u'moods', 'MY_ACTIVITY_ICONSETS': u'activities', - 'PLUGINS_USER': u'plugins'} - for name in d: - self.add(name, TYPE_DATA, windowsify(d[name])) + d = {'MY_DATA': '', 'LOG_DB': u'logs.db', 'MY_CACERTS': u'cacerts.pem', + 'MY_EMOTS': u'emoticons', 'MY_ICONSETS': u'iconsets', + 'MY_MOOD_ICONSETS': u'moods', 'MY_ACTIVITY_ICONSETS': u'activities', + 'PLUGINS_USER': u'plugins'} + for name in d: + self.add(name, TYPE_DATA, windowsify(d[name])) - d = {'MY_CACHE': '', 'CACHE_DB': u'cache.db', 'VCARD': u'vcards', - 'AVATAR': u'avatars'} - for name in d: - self.add(name, TYPE_CACHE, windowsify(d[name])) + d = {'MY_CACHE': '', 'CACHE_DB': u'cache.db', 'VCARD': u'vcards', + 'AVATAR': u'avatars'} + for name in d: + self.add(name, TYPE_CACHE, windowsify(d[name])) - self.add('MY_CONFIG', TYPE_CONFIG, '') + self.add('MY_CONFIG', TYPE_CONFIG, '') - basedir = fse(os.environ.get(u'GAJIM_BASEDIR', defs.basedir)) - self.add('DATA', None, os.path.join(basedir, windowsify(u'data'))) - self.add('ICONS', None, os.path.join(basedir, windowsify(u'icons'))) - self.add('HOME', None, fse(os.path.expanduser('~'))) - self.add('PLUGINS_BASE', None, os.path.join(basedir, - windowsify(u'plugins'))) - try: - self.add('TMP', None, fse(tempfile.gettempdir())) - except IOError, e: - print >> sys.stderr, 'Error opening tmp folder: %s\nUsing %s' % ( - str(e), os.path.expanduser('~')) - self.add('TMP', None, fse(os.path.expanduser('~'))) + basedir = fse(os.environ.get(u'GAJIM_BASEDIR', defs.basedir)) + self.add('DATA', None, os.path.join(basedir, windowsify(u'data'))) + self.add('ICONS', None, os.path.join(basedir, windowsify(u'icons'))) + self.add('HOME', None, fse(os.path.expanduser('~'))) + self.add('PLUGINS_BASE', None, os.path.join(basedir, + windowsify(u'plugins'))) + try: + self.add('TMP', None, fse(tempfile.gettempdir())) + except IOError, e: + print >> sys.stderr, 'Error opening tmp folder: %s\nUsing %s' % ( + str(e), os.path.expanduser('~')) + self.add('TMP', None, fse(os.path.expanduser('~'))) - try: - import svn_config - svn_config.configure(self) - except (ImportError, AttributeError): - pass + try: + import svn_config + svn_config.configure(self) + except (ImportError, AttributeError): + pass - def init_profile(self, profile=''): - conffile = windowsify(u'config') - pidfile = windowsify(u'gajim') - secretsfile = windowsify(u'secrets') - pluginsconfdir = windowsify(u'pluginsconfig') + def init_profile(self, profile=''): + conffile = windowsify(u'config') + pidfile = windowsify(u'gajim') + secretsfile = windowsify(u'secrets') + pluginsconfdir = windowsify(u'pluginsconfig') - if len(profile) > 0: - conffile += u'.' + profile - pidfile += u'.' + profile - secretsfile += u'.' + profile - pluginsconfdir += u'.' + profile - pidfile += u'.pid' - self.add('CONFIG_FILE', TYPE_CONFIG, conffile) - self.add('PID_FILE', TYPE_CACHE, pidfile) - self.add('SECRETS_FILE', TYPE_DATA, secretsfile) - self.add('PLUGINS_CONFIG_DIR', TYPE_CONFIG, pluginsconfdir) + if len(profile) > 0: + conffile += u'.' + profile + pidfile += u'.' + profile + secretsfile += u'.' + profile + pluginsconfdir += u'.' + profile + pidfile += u'.pid' + self.add('CONFIG_FILE', TYPE_CONFIG, conffile) + self.add('PID_FILE', TYPE_CACHE, pidfile) + self.add('SECRETS_FILE', TYPE_DATA, secretsfile) + self.add('PLUGINS_CONFIG_DIR', TYPE_CONFIG, pluginsconfdir) gajimpaths = ConfigPaths() - -# vim: se ts=3: diff --git a/src/common/connection.py b/src/common/connection.py index ca8dc19a2..3ba5a2311 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -42,14 +42,14 @@ import locale import hmac try: - randomsource = random.SystemRandom() + randomsource = random.SystemRandom() except Exception: - randomsource = random.Random() - randomsource.seed() + randomsource = random.Random() + randomsource.seed() import signal if os.name != 'nt': - signal.signal(signal.SIGPIPE, signal.SIG_DFL) + signal.signal(signal.SIGPIPE, signal.SIG_DFL) import common.xmpp from common import helpers @@ -100,2193 +100,2191 @@ ssl_error = { } class CommonConnection: - """ - Common connection class, can be derivated for normal connection or zeroconf - connection - """ - - def __init__(self, name): - self.name = name - # self.connected: - # 0=>offline, - # 1=>connection in progress, - # 2=>online - # 3=>free for chat - # ... - self.connected = 0 - self.connection = None # xmpppy ClientCommon instance - self.on_purpose = False - self.is_zeroconf = False - self.password = '' - self.server_resource = self._compute_resource() - self.gpg = None - self.USE_GPG = False - if gajim.HAVE_GPG: - self.USE_GPG = True - self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent')) - self.status = '' - self.old_show = '' - self.priority = gajim.get_priority(name, 'offline') - self.time_to_reconnect = None - self.bookmarks = [] - - self.blocked_list = [] - self.blocked_contacts = [] - self.blocked_groups = [] - self.blocked_all = False - - self.pep_supported = False - self.pep = {} - # Do we continue connection when we get roster (send presence,get vcard..) - self.continue_connect_info = None - - # Remember where we are in the register agent process - self.agent_registrations = {} - # To know the groupchat jid associated with a sranza ID. Useful to - # request vcard or os info... to a real JID but act as if it comes from - # the fake jid - self.groupchat_jids = {} # {ID : groupchat_jid} - - self.privacy_rules_supported = False - self.vcard_supported = False - self.private_storage_supported = False - - self.muc_jid = {} # jid of muc server for each transport type - self._stun_servers = [] # STUN servers of our jabber server - - self.get_config_values_or_default() - - def _compute_resource(self): - resource = gajim.config.get_per('accounts', self.name, 'resource') - # All valid resource substitution strings should be added to this hash. - if resource: - resource = Template(resource).safe_substitute({ - 'hostname': socket.gethostname() - }) - return resource - - def dispatch(self, event, data): - """ - Always passes account name as first param - """ - gajim.ged.raise_event(event, self.name, data) - - def _reconnect(self): - """ - To be implemented by derivated classes - """ - raise NotImplementedError - - def quit(self, kill_core): - if kill_core and gajim.account_is_connected(self.name): - self.disconnect(on_purpose=True) - - def test_gpg_passphrase(self, password): - """ - Returns 'ok', 'bad_pass' or 'expired' - """ - if not self.gpg: - return False - self.gpg.passphrase = password - keyID = gajim.config.get_per('accounts', self.name, 'keyid') - signed = self.gpg.sign('test', keyID) - self.gpg.password = None - if signed == 'KEYEXPIRED': - return 'expired' - elif signed == 'BAD_PASSPHRASE': - return 'bad_pass' - return 'ok' - - def get_signed_msg(self, msg, callback = None): - """ - Returns the signed message if possible or an empty string if gpg is not - used or None if waiting for passphrase - - callback is the function to call when user give the passphrase - """ - signed = '' - keyID = gajim.config.get_per('accounts', self.name, 'keyid') - if keyID and self.USE_GPG: - use_gpg_agent = gajim.config.get('use_gpg_agent') - if self.gpg.passphrase is None and not use_gpg_agent: - # We didn't set a passphrase - return None - if self.gpg.passphrase is not None or use_gpg_agent: - signed = self.gpg.sign(msg, keyID) - if signed == 'BAD_PASSPHRASE': - self.USE_GPG = False - signed = '' - self.dispatch('BAD_PASSPHRASE', ()) - return signed - - def _on_disconnected(self): - """ - Called when a disconnect request has completed successfully - """ - self.disconnect(on_purpose=True) - self.dispatch('STATUS', 'offline') - - def get_status(self): - return gajim.SHOW_LIST[self.connected] - - def check_jid(self, jid): - """ - This function must be implemented by derivated classes. It has to return - the valid jid, or raise a helpers.InvalidFormat exception - """ - raise NotImplementedError - - def _prepare_message(self, jid, msg, keyID, type_='chat', subject='', - chatstate=None, msg_id=None, composing_xep=None, resource=None, - user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None, - original_message=None, delayed=None, callback=None): - if not self.connection or self.connected < 2: - return 1 - try: - jid = self.check_jid(jid) - except helpers.InvalidFormat: - self.dispatch('ERROR', (_('Invalid Jabber ID'), - _('It is not possible to send a message to %s, this JID is not ' - 'valid.') % jid)) - return - - if msg and not xhtml and gajim.config.get( - 'rst_formatting_outgoing_messages'): - from common.rst_xhtml_generator import create_xhtml - xhtml = create_xhtml(msg) - if not msg and chatstate is None and form_node is None: - return - fjid = jid - if resource: - fjid += '/' + resource - msgtxt = msg - msgenc = '' - - if session: - fjid = session.get_to() - - if keyID and self.USE_GPG: - xhtml = None - if keyID == 'UNKNOWN': - error = _('Neither the remote presence is signed, nor a key was ' - 'assigned.') - elif keyID.endswith('MISMATCH'): - error = _('The contact\'s key (%s) does not match the key assigned ' - 'in Gajim.' % keyID[:8]) - else: - def encrypt_thread(msg, keyID, always_trust=False): - # encrypt message. This function returns (msgenc, error) - return self.gpg.encrypt(msg, [keyID], always_trust) - def _on_encrypted(output): - msgenc, error = output - if error == 'NOT_TRUSTED': - def _on_always_trust(answer): - if answer: - gajim.thread_interface(encrypt_thread, [msg, keyID, - True], _on_encrypted, []) - else: - self._message_encrypted_cb(output, type_, msg, msgtxt, - original_message, fjid, resource, jid, xhtml, - subject, chatstate, composing_xep, forward_from, - delayed, session, form_node, user_nick, keyID, - callback) - self.dispatch('GPG_ALWAYS_TRUST', _on_always_trust) - else: - self._message_encrypted_cb(output, type_, msg, msgtxt, - original_message, fjid, resource, jid, xhtml, subject, - chatstate, composing_xep, forward_from, delayed, session, - form_node, user_nick, keyID, callback) - gajim.thread_interface(encrypt_thread, [msg, keyID, False], - _on_encrypted, []) - return - - self._message_encrypted_cb(('', error), type_, msg, msgtxt, - original_message, fjid, resource, jid, xhtml, subject, chatstate, - composing_xep, forward_from, delayed, session, form_node, user_nick, - keyID, callback) - - self._on_continue_message(type_, msg, msgtxt, original_message, fjid, - resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep, - forward_from, delayed, session, form_node, user_nick, callback) - - def _message_encrypted_cb(self, output, type_, msg, msgtxt, original_message, - fjid, resource, jid, xhtml, subject, chatstate, composing_xep, forward_from, - delayed, session, form_node, user_nick, keyID, callback): - msgenc, error = output - - if msgenc and not error: - msgtxt = '[This message is *encrypted* (See :XEP:`27`]' - lang = os.getenv('LANG') - if lang is not None and lang != 'en': # we're not english - # one in locale and one en - msgtxt = _('[This message is *encrypted* (See :XEP:`27`]') + \ - ' (' + msgtxt + ')' - self._on_continue_message(type_, msg, msgtxt, original_message, fjid, - resource, jid, xhtml, subject, msgenc, keyID, chatstate, - composing_xep, forward_from, delayed, session, form_node, user_nick, - callback) - return - # Encryption failed, do not send message - tim = localtime() - self.dispatch('MSGNOTSENT', (jid, error, msgtxt, tim, session)) - - def _on_continue_message(self, type_, msg, msgtxt, original_message, fjid, - resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep, - forward_from, delayed, session, form_node, user_nick, callback): - if type_ == 'chat': - msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ=type_, - xhtml=xhtml) - else: - if subject: - msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal', - subject=subject, xhtml=xhtml) - else: - msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal', - xhtml=xhtml) - if msgenc: - msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc) - - if form_node: - msg_iq.addChild(node=form_node) - - # XEP-0172: user_nickname - if user_nick: - msg_iq.setTag('nick', namespace = common.xmpp.NS_NICK).setData( - user_nick) - - # TODO: We might want to write a function so we don't need to - # reproduce that ugly if somewhere else. - if resource: - contact = gajim.contacts.get_contact(self.name, jid, resource) - else: - contact = gajim.contacts.get_contact_with_highest_priority(self.name, - jid) - - # chatstates - if peer supports xep85 or xep22, send chatstates - # please note that the only valid tag inside a message containing a - # tag is the active event - if chatstate is not None and contact: - if ((composing_xep == 'XEP-0085' or not composing_xep) \ - and composing_xep != 'asked_once') or \ - contact.supports(common.xmpp.NS_CHATSTATES): - # XEP-0085 - msg_iq.setTag(chatstate, namespace=common.xmpp.NS_CHATSTATES) - if composing_xep in ('XEP-0022', 'asked_once') or \ - not composing_xep: - # XEP-0022 - chatstate_node = msg_iq.setTag('x', namespace=common.xmpp.NS_EVENT) - if chatstate is 'composing' or msgtxt: - chatstate_node.addChild(name='composing') - - if forward_from: - addresses = msg_iq.addChild('addresses', - namespace=common.xmpp.NS_ADDRESS) - addresses.addChild('address', attrs = {'type': 'ofrom', - 'jid': forward_from}) - - # XEP-0203 - if delayed: - our_jid = gajim.get_jid_from_account(self.name) + '/' + \ - self.server_resource - timestamp = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(delayed)) - msg_iq.addChild('delay', namespace=common.xmpp.NS_DELAY2, - attrs={'from': our_jid, 'stamp': timestamp}) - - # XEP-0184 - if msgtxt and gajim.config.get_per('accounts', self.name, - 'request_receipt') and contact and contact.supports( - common.xmpp.NS_RECEIPTS): - msg_iq.setTag('request', namespace=common.xmpp.NS_RECEIPTS) - - if session: - # XEP-0201 - session.last_send = time.time() - msg_iq.setThread(session.thread_id) - - # XEP-0200 - if session.enable_encryption: - msg_iq = session.encrypt_stanza(msg_iq) - - if callback: - callback(jid, msg, keyID, forward_from, session, original_message, - subject, type_, msg_iq) - - def log_message(self, jid, msg, forward_from, session, original_message, - subject, type_): - if not forward_from and session and session.is_loggable(): - ji = gajim.get_jid_without_resource(jid) - if gajim.config.should_log(self.name, ji): - log_msg = msg - if original_message is not None: - log_msg = original_message - if subject: - log_msg = _('Subject: %(subject)s\n%(message)s') % \ - {'subject': subject, 'message': log_msg} - if log_msg: - if type_ == 'chat': - kind = 'chat_msg_sent' - else: - kind = 'single_msg_sent' - try: - gajim.logger.write(kind, jid, log_msg) - except exceptions.PysqliteOperationalError, e: - self.dispatch('ERROR', (_('Disk Write Error'), str(e))) - except exceptions.DatabaseMalformed: - pritext = _('Database Error') - sectext = _('The database file (%s) cannot be read. Try to ' - 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)' - ' or remove it (all history will be lost).') % \ - common.logger.LOG_DB_PATH - - def ack_subscribed(self, jid): - """ - To be implemented by derivated classes - """ - raise NotImplementedError - - def ack_unsubscribed(self, jid): - """ - To be implemented by derivated classes - """ - raise NotImplementedError - - def request_subscription(self, jid, msg='', name='', groups=[], - auto_auth=False): - """ - To be implemented by derivated classes - """ - raise NotImplementedError - - def send_authorization(self, jid): - """ - To be implemented by derivated classes - """ - raise NotImplementedError - - def refuse_authorization(self, jid): - """ - To be implemented by derivated classes - """ - raise NotImplementedError - - def unsubscribe(self, jid, remove_auth = True): - """ - To be implemented by derivated classes - """ - raise NotImplementedError - - def unsubscribe_agent(self, agent): - """ - To be implemented by derivated classes - """ - raise NotImplementedError - - def update_contact(self, jid, name, groups): - if self.connection: - self.connection.getRoster().setItem(jid=jid, name=name, groups=groups) - - def update_contacts(self, contacts): - """ - Update multiple roster items - """ - if self.connection: - self.connection.getRoster().setItemMulti(contacts) - - def new_account(self, name, config, sync=False): - """ - To be implemented by derivated classes - """ - raise NotImplementedError - - def _on_new_account(self, con=None, con_type=None): - """ - To be implemented by derivated classes - """ - raise NotImplementedError - - def account_changed(self, new_name): - self.name = new_name - - def request_last_status_time(self, jid, resource): - """ - To be implemented by derivated classes - """ - raise NotImplementedError - - def request_os_info(self, jid, resource): - """ - To be implemented by derivated classes - """ - raise NotImplementedError - - def get_settings(self): - """ - To be implemented by derivated classes - """ - raise NotImplementedError - - def get_bookmarks(self): - """ - To be implemented by derivated classes - """ - raise NotImplementedError - - def store_bookmarks(self): - """ - To be implemented by derivated classes - """ - raise NotImplementedError - - def get_metacontacts(self): - """ - To be implemented by derivated classes - """ - raise NotImplementedError - - def send_agent_status(self, agent, ptype): - """ - To be implemented by derivated classes - """ - raise NotImplementedError - - def gpg_passphrase(self, passphrase): - if self.gpg: - use_gpg_agent = gajim.config.get('use_gpg_agent') - if use_gpg_agent: - self.gpg.passphrase = None - else: - self.gpg.passphrase = passphrase - - def ask_gpg_keys(self): - if self.gpg: - keys = self.gpg.get_keys() - return keys - return None - - def ask_gpg_secrete_keys(self): - if self.gpg: - keys = self.gpg.get_secret_keys() - return keys - return None - - def load_roster_from_db(self): - # Do nothing by default - return - - def _event_dispatcher(self, realm, event, data): - if realm == '': - if event == common.xmpp.transports_nb.DATA_RECEIVED: - self.dispatch('STANZA_ARRIVED', unicode(data, errors='ignore')) - elif event == common.xmpp.transports_nb.DATA_SENT: - self.dispatch('STANZA_SENT', unicode(data)) - - def change_status(self, show, msg, auto=False): - if not msg: - msg = '' - sign_msg = False - if not auto and not show == 'offline': - sign_msg = True - if show != 'invisible': - # We save it only when privacy list is accepted - self.status = msg - if show != 'offline' and self.connected < 1: - # set old_show to requested 'show' in case we need to - # recconect before we auth to server - self.old_show = show - self.on_purpose = False - self.server_resource = self._compute_resource() - if gajim.HAVE_GPG: - self.USE_GPG = True - self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent')) - self.connect_and_init(show, msg, sign_msg) - return - - if show == 'offline': - self.connected = 0 - if self.connection: - p = common.xmpp.Presence(typ = 'unavailable') - p = self.add_sha(p, False) - if msg: - p.setStatus(msg) - - self.connection.RegisterDisconnectHandler(self._on_disconnected) - self.connection.send(p, now=True) - self.connection.start_disconnect() - else: - self._on_disconnected() - return - - if show != 'offline' and self.connected > 0: - # dont'try to connect, when we are in state 'connecting' - if self.connected == 1: - return - if show == 'invisible': - self._change_to_invisible(msg) - return - if show not in ['offline', 'online', 'chat', 'away', 'xa', 'dnd']: - return -1 - was_invisible = self.connected == gajim.SHOW_LIST.index('invisible') - self.connected = gajim.SHOW_LIST.index(show) - if was_invisible: - self._change_from_invisible() - self._update_status(show, msg) + """ + Common connection class, can be derivated for normal connection or zeroconf + connection + """ + + def __init__(self, name): + self.name = name + # self.connected: + # 0=>offline, + # 1=>connection in progress, + # 2=>online + # 3=>free for chat + # ... + self.connected = 0 + self.connection = None # xmpppy ClientCommon instance + self.on_purpose = False + self.is_zeroconf = False + self.password = '' + self.server_resource = self._compute_resource() + self.gpg = None + self.USE_GPG = False + if gajim.HAVE_GPG: + self.USE_GPG = True + self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent')) + self.status = '' + self.old_show = '' + self.priority = gajim.get_priority(name, 'offline') + self.time_to_reconnect = None + self.bookmarks = [] + + self.blocked_list = [] + self.blocked_contacts = [] + self.blocked_groups = [] + self.blocked_all = False + + self.pep_supported = False + self.pep = {} + # Do we continue connection when we get roster (send presence,get vcard..) + self.continue_connect_info = None + + # Remember where we are in the register agent process + self.agent_registrations = {} + # To know the groupchat jid associated with a sranza ID. Useful to + # request vcard or os info... to a real JID but act as if it comes from + # the fake jid + self.groupchat_jids = {} # {ID : groupchat_jid} + + self.privacy_rules_supported = False + self.vcard_supported = False + self.private_storage_supported = False + + self.muc_jid = {} # jid of muc server for each transport type + self._stun_servers = [] # STUN servers of our jabber server + + self.get_config_values_or_default() + + def _compute_resource(self): + resource = gajim.config.get_per('accounts', self.name, 'resource') + # All valid resource substitution strings should be added to this hash. + if resource: + resource = Template(resource).safe_substitute({ + 'hostname': socket.gethostname() + }) + return resource + + def dispatch(self, event, data): + """ + Always passes account name as first param + """ + gajim.ged.raise_event(event, self.name, data) + + def _reconnect(self): + """ + To be implemented by derivated classes + """ + raise NotImplementedError + + def quit(self, kill_core): + if kill_core and gajim.account_is_connected(self.name): + self.disconnect(on_purpose=True) + + def test_gpg_passphrase(self, password): + """ + Returns 'ok', 'bad_pass' or 'expired' + """ + if not self.gpg: + return False + self.gpg.passphrase = password + keyID = gajim.config.get_per('accounts', self.name, 'keyid') + signed = self.gpg.sign('test', keyID) + self.gpg.password = None + if signed == 'KEYEXPIRED': + return 'expired' + elif signed == 'BAD_PASSPHRASE': + return 'bad_pass' + return 'ok' + + def get_signed_msg(self, msg, callback = None): + """ + Returns the signed message if possible or an empty string if gpg is not + used or None if waiting for passphrase + + callback is the function to call when user give the passphrase + """ + signed = '' + keyID = gajim.config.get_per('accounts', self.name, 'keyid') + if keyID and self.USE_GPG: + use_gpg_agent = gajim.config.get('use_gpg_agent') + if self.gpg.passphrase is None and not use_gpg_agent: + # We didn't set a passphrase + return None + if self.gpg.passphrase is not None or use_gpg_agent: + signed = self.gpg.sign(msg, keyID) + if signed == 'BAD_PASSPHRASE': + self.USE_GPG = False + signed = '' + self.dispatch('BAD_PASSPHRASE', ()) + return signed + + def _on_disconnected(self): + """ + Called when a disconnect request has completed successfully + """ + self.disconnect(on_purpose=True) + self.dispatch('STATUS', 'offline') + + def get_status(self): + return gajim.SHOW_LIST[self.connected] + + def check_jid(self, jid): + """ + This function must be implemented by derivated classes. It has to return + the valid jid, or raise a helpers.InvalidFormat exception + """ + raise NotImplementedError + + def _prepare_message(self, jid, msg, keyID, type_='chat', subject='', + chatstate=None, msg_id=None, composing_xep=None, resource=None, + user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None, + original_message=None, delayed=None, callback=None): + if not self.connection or self.connected < 2: + return 1 + try: + jid = self.check_jid(jid) + except helpers.InvalidFormat: + self.dispatch('ERROR', (_('Invalid Jabber ID'), + _('It is not possible to send a message to %s, this JID is not ' + 'valid.') % jid)) + return + + if msg and not xhtml and gajim.config.get( + 'rst_formatting_outgoing_messages'): + from common.rst_xhtml_generator import create_xhtml + xhtml = create_xhtml(msg) + if not msg and chatstate is None and form_node is None: + return + fjid = jid + if resource: + fjid += '/' + resource + msgtxt = msg + msgenc = '' + + if session: + fjid = session.get_to() + + if keyID and self.USE_GPG: + xhtml = None + if keyID == 'UNKNOWN': + error = _('Neither the remote presence is signed, nor a key was ' + 'assigned.') + elif keyID.endswith('MISMATCH'): + error = _('The contact\'s key (%s) does not match the key assigned ' + 'in Gajim.' % keyID[:8]) + else: + def encrypt_thread(msg, keyID, always_trust=False): + # encrypt message. This function returns (msgenc, error) + return self.gpg.encrypt(msg, [keyID], always_trust) + def _on_encrypted(output): + msgenc, error = output + if error == 'NOT_TRUSTED': + def _on_always_trust(answer): + if answer: + gajim.thread_interface(encrypt_thread, [msg, keyID, + True], _on_encrypted, []) + else: + self._message_encrypted_cb(output, type_, msg, msgtxt, + original_message, fjid, resource, jid, xhtml, + subject, chatstate, composing_xep, forward_from, + delayed, session, form_node, user_nick, keyID, + callback) + self.dispatch('GPG_ALWAYS_TRUST', _on_always_trust) + else: + self._message_encrypted_cb(output, type_, msg, msgtxt, + original_message, fjid, resource, jid, xhtml, subject, + chatstate, composing_xep, forward_from, delayed, session, + form_node, user_nick, keyID, callback) + gajim.thread_interface(encrypt_thread, [msg, keyID, False], + _on_encrypted, []) + return + + self._message_encrypted_cb(('', error), type_, msg, msgtxt, + original_message, fjid, resource, jid, xhtml, subject, chatstate, + composing_xep, forward_from, delayed, session, form_node, user_nick, + keyID, callback) + + self._on_continue_message(type_, msg, msgtxt, original_message, fjid, + resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep, + forward_from, delayed, session, form_node, user_nick, callback) + + def _message_encrypted_cb(self, output, type_, msg, msgtxt, original_message, + fjid, resource, jid, xhtml, subject, chatstate, composing_xep, forward_from, + delayed, session, form_node, user_nick, keyID, callback): + msgenc, error = output + + if msgenc and not error: + msgtxt = '[This message is *encrypted* (See :XEP:`27`]' + lang = os.getenv('LANG') + if lang is not None and lang != 'en': # we're not english + # one in locale and one en + msgtxt = _('[This message is *encrypted* (See :XEP:`27`]') + \ + ' (' + msgtxt + ')' + self._on_continue_message(type_, msg, msgtxt, original_message, fjid, + resource, jid, xhtml, subject, msgenc, keyID, chatstate, + composing_xep, forward_from, delayed, session, form_node, user_nick, + callback) + return + # Encryption failed, do not send message + tim = localtime() + self.dispatch('MSGNOTSENT', (jid, error, msgtxt, tim, session)) + + def _on_continue_message(self, type_, msg, msgtxt, original_message, fjid, + resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep, + forward_from, delayed, session, form_node, user_nick, callback): + if type_ == 'chat': + msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ=type_, + xhtml=xhtml) + else: + if subject: + msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal', + subject=subject, xhtml=xhtml) + else: + msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal', + xhtml=xhtml) + if msgenc: + msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc) + + if form_node: + msg_iq.addChild(node=form_node) + + # XEP-0172: user_nickname + if user_nick: + msg_iq.setTag('nick', namespace = common.xmpp.NS_NICK).setData( + user_nick) + + # TODO: We might want to write a function so we don't need to + # reproduce that ugly if somewhere else. + if resource: + contact = gajim.contacts.get_contact(self.name, jid, resource) + else: + contact = gajim.contacts.get_contact_with_highest_priority(self.name, + jid) + + # chatstates - if peer supports xep85 or xep22, send chatstates + # please note that the only valid tag inside a message containing a + # tag is the active event + if chatstate is not None and contact: + if ((composing_xep == 'XEP-0085' or not composing_xep) \ + and composing_xep != 'asked_once') or \ + contact.supports(common.xmpp.NS_CHATSTATES): + # XEP-0085 + msg_iq.setTag(chatstate, namespace=common.xmpp.NS_CHATSTATES) + if composing_xep in ('XEP-0022', 'asked_once') or \ + not composing_xep: + # XEP-0022 + chatstate_node = msg_iq.setTag('x', namespace=common.xmpp.NS_EVENT) + if chatstate is 'composing' or msgtxt: + chatstate_node.addChild(name='composing') + + if forward_from: + addresses = msg_iq.addChild('addresses', + namespace=common.xmpp.NS_ADDRESS) + addresses.addChild('address', attrs = {'type': 'ofrom', + 'jid': forward_from}) + + # XEP-0203 + if delayed: + our_jid = gajim.get_jid_from_account(self.name) + '/' + \ + self.server_resource + timestamp = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(delayed)) + msg_iq.addChild('delay', namespace=common.xmpp.NS_DELAY2, + attrs={'from': our_jid, 'stamp': timestamp}) + + # XEP-0184 + if msgtxt and gajim.config.get_per('accounts', self.name, + 'request_receipt') and contact and contact.supports( + common.xmpp.NS_RECEIPTS): + msg_iq.setTag('request', namespace=common.xmpp.NS_RECEIPTS) + + if session: + # XEP-0201 + session.last_send = time.time() + msg_iq.setThread(session.thread_id) + + # XEP-0200 + if session.enable_encryption: + msg_iq = session.encrypt_stanza(msg_iq) + + if callback: + callback(jid, msg, keyID, forward_from, session, original_message, + subject, type_, msg_iq) + + def log_message(self, jid, msg, forward_from, session, original_message, + subject, type_): + if not forward_from and session and session.is_loggable(): + ji = gajim.get_jid_without_resource(jid) + if gajim.config.should_log(self.name, ji): + log_msg = msg + if original_message is not None: + log_msg = original_message + if subject: + log_msg = _('Subject: %(subject)s\n%(message)s') % \ + {'subject': subject, 'message': log_msg} + if log_msg: + if type_ == 'chat': + kind = 'chat_msg_sent' + else: + kind = 'single_msg_sent' + try: + gajim.logger.write(kind, jid, log_msg) + except exceptions.PysqliteOperationalError, e: + self.dispatch('ERROR', (_('Disk Write Error'), str(e))) + except exceptions.DatabaseMalformed: + pritext = _('Database Error') + sectext = _('The database file (%s) cannot be read. Try to ' + 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)' + ' or remove it (all history will be lost).') % \ + common.logger.LOG_DB_PATH + + def ack_subscribed(self, jid): + """ + To be implemented by derivated classes + """ + raise NotImplementedError + + def ack_unsubscribed(self, jid): + """ + To be implemented by derivated classes + """ + raise NotImplementedError + + def request_subscription(self, jid, msg='', name='', groups=[], + auto_auth=False): + """ + To be implemented by derivated classes + """ + raise NotImplementedError + + def send_authorization(self, jid): + """ + To be implemented by derivated classes + """ + raise NotImplementedError + + def refuse_authorization(self, jid): + """ + To be implemented by derivated classes + """ + raise NotImplementedError + + def unsubscribe(self, jid, remove_auth = True): + """ + To be implemented by derivated classes + """ + raise NotImplementedError + + def unsubscribe_agent(self, agent): + """ + To be implemented by derivated classes + """ + raise NotImplementedError + + def update_contact(self, jid, name, groups): + if self.connection: + self.connection.getRoster().setItem(jid=jid, name=name, groups=groups) + + def update_contacts(self, contacts): + """ + Update multiple roster items + """ + if self.connection: + self.connection.getRoster().setItemMulti(contacts) + + def new_account(self, name, config, sync=False): + """ + To be implemented by derivated classes + """ + raise NotImplementedError + + def _on_new_account(self, con=None, con_type=None): + """ + To be implemented by derivated classes + """ + raise NotImplementedError + + def account_changed(self, new_name): + self.name = new_name + + def request_last_status_time(self, jid, resource): + """ + To be implemented by derivated classes + """ + raise NotImplementedError + + def request_os_info(self, jid, resource): + """ + To be implemented by derivated classes + """ + raise NotImplementedError + + def get_settings(self): + """ + To be implemented by derivated classes + """ + raise NotImplementedError + + def get_bookmarks(self): + """ + To be implemented by derivated classes + """ + raise NotImplementedError + + def store_bookmarks(self): + """ + To be implemented by derivated classes + """ + raise NotImplementedError + + def get_metacontacts(self): + """ + To be implemented by derivated classes + """ + raise NotImplementedError + + def send_agent_status(self, agent, ptype): + """ + To be implemented by derivated classes + """ + raise NotImplementedError + + def gpg_passphrase(self, passphrase): + if self.gpg: + use_gpg_agent = gajim.config.get('use_gpg_agent') + if use_gpg_agent: + self.gpg.passphrase = None + else: + self.gpg.passphrase = passphrase + + def ask_gpg_keys(self): + if self.gpg: + keys = self.gpg.get_keys() + return keys + return None + + def ask_gpg_secrete_keys(self): + if self.gpg: + keys = self.gpg.get_secret_keys() + return keys + return None + + def load_roster_from_db(self): + # Do nothing by default + return + + def _event_dispatcher(self, realm, event, data): + if realm == '': + if event == common.xmpp.transports_nb.DATA_RECEIVED: + self.dispatch('STANZA_ARRIVED', unicode(data, errors='ignore')) + elif event == common.xmpp.transports_nb.DATA_SENT: + self.dispatch('STANZA_SENT', unicode(data)) + + def change_status(self, show, msg, auto=False): + if not msg: + msg = '' + sign_msg = False + if not auto and not show == 'offline': + sign_msg = True + if show != 'invisible': + # We save it only when privacy list is accepted + self.status = msg + if show != 'offline' and self.connected < 1: + # set old_show to requested 'show' in case we need to + # recconect before we auth to server + self.old_show = show + self.on_purpose = False + self.server_resource = self._compute_resource() + if gajim.HAVE_GPG: + self.USE_GPG = True + self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent')) + self.connect_and_init(show, msg, sign_msg) + return + + if show == 'offline': + self.connected = 0 + if self.connection: + p = common.xmpp.Presence(typ = 'unavailable') + p = self.add_sha(p, False) + if msg: + p.setStatus(msg) + + self.connection.RegisterDisconnectHandler(self._on_disconnected) + self.connection.send(p, now=True) + self.connection.start_disconnect() + else: + self._on_disconnected() + return + + if show != 'offline' and self.connected > 0: + # dont'try to connect, when we are in state 'connecting' + if self.connected == 1: + return + if show == 'invisible': + self._change_to_invisible(msg) + return + if show not in ['offline', 'online', 'chat', 'away', 'xa', 'dnd']: + return -1 + was_invisible = self.connected == gajim.SHOW_LIST.index('invisible') + self.connected = gajim.SHOW_LIST.index(show) + if was_invisible: + self._change_from_invisible() + self._update_status(show, msg) class Connection(CommonConnection, ConnectionHandlers): - def __init__(self, name): - CommonConnection.__init__(self, name) - ConnectionHandlers.__init__(self) - # this property is used to prevent double connections - self.last_connection = None # last ClientCommon instance - # If we succeed to connect, remember it so next time we try (after a - # disconnection) we try only this type. - self.last_connection_type = None - self.lang = None - if locale.getdefaultlocale()[0]: - self.lang = locale.getdefaultlocale()[0].split('_')[0] - # increase/decrease default timeout for server responses - self.try_connecting_for_foo_secs = 45 - # holds the actual hostname to which we are connected - self.connected_hostname = None - self.last_time_to_reconnect = None - self.new_account_info = None - self.new_account_form = None - self.annotations = {} - self.last_io = gajim.idlequeue.current_time() - self.last_sent = [] - self.last_history_time = {} - self.password = passwords.get_password(name) - - self.music_track_info = 0 - self.location_info = {} - self.pubsub_supported = False - self.pubsub_publish_options_supported = False - # Do we auto accept insecure connection - self.connection_auto_accepted = False - self.pasword_callback = None - - self.on_connect_success = None - self.on_connect_failure = None - self.retrycount = 0 - self.jids_for_auto_auth = [] # list of jid to auto-authorize - self.available_transports = {} # list of available transports on this - # server {'icq': ['icq.server.com', 'icq2.server.com'], } - self.private_storage_supported = True - self.streamError = '' - self.secret_hmac = str(random.random())[2:] - # END __init__ - - def get_config_values_or_default(self): - if gajim.config.get_per('accounts', self.name, 'keep_alives_enabled'): - self.keepalives = gajim.config.get_per('accounts', self.name, - 'keep_alive_every_foo_secs') - else: - self.keepalives = 0 - if gajim.config.get_per('accounts', self.name, 'ping_alives_enabled'): - self.pingalives = gajim.config.get_per('accounts', self.name, - 'ping_alive_every_foo_secs') - else: - self.pingalives = 0 - - def check_jid(self, jid): - return helpers.parse_jid(jid) - - def _reconnect(self): - # Do not try to reco while we are already trying - self.time_to_reconnect = None - if self.connected < 2: # connection failed - log.debug('reconnect') - self.connected = 1 - self.dispatch('STATUS', 'connecting') - self.retrycount += 1 - self.on_connect_auth = self._discover_server_at_connection - self.connect_and_init(self.old_show, self.status, self.USE_GPG) - else: - # reconnect succeeded - self.time_to_reconnect = None - self.retrycount = 0 - - # We are doing disconnect at so many places, better use one function in all - def disconnect(self, on_purpose=False): - gajim.interface.music_track_changed(None, None, self.name) - self.reset_awaiting_pep() - self.on_purpose = on_purpose - self.connected = 0 - self.time_to_reconnect = None - self.privacy_rules_supported = False - if self.connection: - # make sure previous connection is completely closed - gajim.proxy65_manager.disconnect(self.connection) - self.terminate_sessions() - self.remove_all_transfers() - self.connection.disconnect() - self.last_connection = None - self.connection = None - - def _disconnectedReconnCB(self): - """ - Called when we are disconnected - """ - log.info('disconnectedReconnCB called') - if gajim.account_is_connected(self.name): - # we cannot change our status to offline or connecting - # after we auth to server - self.old_show = gajim.SHOW_LIST[self.connected] - self.connected = 0 - if not self.on_purpose: - self.dispatch('STATUS', 'offline') - self.disconnect() - if gajim.config.get_per('accounts', self.name, 'autoreconnect'): - self.connected = -1 - self.dispatch('STATUS', 'error') - if gajim.status_before_autoaway[self.name]: - # We were auto away. So go back online - self.status = gajim.status_before_autoaway[self.name] - gajim.status_before_autoaway[self.name] = '' - self.old_show = 'online' - # this check has moved from _reconnect method - # do exponential backoff until 15 minutes, - # then small linear increase - if self.retrycount < 2 or self.last_time_to_reconnect is None: - self.last_time_to_reconnect = 5 - if self.last_time_to_reconnect < 800: - self.last_time_to_reconnect *= 1.5 - self.last_time_to_reconnect += randomsource.randint(0, 5) - self.time_to_reconnect = int(self.last_time_to_reconnect) - log.info("Reconnect to %s in %ss", self.name, self.time_to_reconnect) - gajim.idlequeue.set_alarm(self._reconnect_alarm, - self.time_to_reconnect) - elif self.on_connect_failure: - self.on_connect_failure() - self.on_connect_failure = None - else: - # show error dialog - self._connection_lost() - else: - self.disconnect() - self.on_purpose = False - # END disconnectedReconnCB - - def _connection_lost(self): - log.debug('_connection_lost') - self.disconnect(on_purpose = False) - self.dispatch('STATUS', 'offline') - self.dispatch('CONNECTION_LOST', - (_('Connection with account "%s" has been lost') % self.name, - _('Reconnect manually.'))) - - def _event_dispatcher(self, realm, event, data): - CommonConnection._event_dispatcher(self, realm, event, data) - if realm == common.xmpp.NS_REGISTER: - if event == common.xmpp.features_nb.REGISTER_DATA_RECEIVED: - # data is (agent, DataFrom, is_form, error_msg) - if self.new_account_info and \ - self.new_account_info['hostname'] == data[0]: - # it's a new account - if not data[1]: # wrong answer - self.dispatch('ACC_NOT_OK', ( - _('Server %(name)s answered wrongly to register request: ' - '%(error)s') % {'name': data[0], 'error': data[3]})) - return - is_form = data[2] - conf = data[1] - if self.new_account_form: - def _on_register_result(result): - if not common.xmpp.isResultNode(result): - self.dispatch('ACC_NOT_OK', (result.getError())) - return - if gajim.HAVE_GPG: - self.USE_GPG = True - self.gpg = GnuPG.GnuPG(gajim.config.get( - 'use_gpg_agent')) - self.dispatch('ACC_OK', (self.new_account_info)) - self.new_account_info = None - self.new_account_form = None - if self.connection: - self.connection.UnregisterDisconnectHandler( - self._on_new_account) - self.disconnect(on_purpose=True) - # it's the second time we get the form, we have info user - # typed, so send them - if is_form: - #TODO: Check if form has changed - iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to=self._hostname) - iq.setTag('query').addChild(node=self.new_account_form) - self.connection.SendAndCallForResponse(iq, - _on_register_result) - else: - if self.new_account_form.keys().sort() != \ - conf.keys().sort(): - # requested config has changed since first connection - self.dispatch('ACC_NOT_OK', (_( - 'Server %s provided a different registration form')\ - % data[0])) - return - common.xmpp.features_nb.register(self.connection, - self._hostname, self.new_account_form, - _on_register_result) - return - try: - errnum = self.connection.Connection.ssl_errnum - except AttributeError: - errnum = -1 # we don't have an errnum - ssl_msg = '' - if errnum > 0: - ssl_msg = ssl_error.get(errnum, _('Unknown SSL error: %d') % errnum) - ssl_cert = '' - if hasattr(self.connection.Connection, 'ssl_cert_pem'): - ssl_cert = self.connection.Connection.ssl_cert_pem - ssl_fingerprint = '' - if hasattr(self.connection.Connection, 'ssl_fingerprint_sha1'): - ssl_fingerprint = \ - self.connection.Connection.ssl_fingerprint_sha1 - self.dispatch('NEW_ACC_CONNECTED', (conf, is_form, ssl_msg, - errnum, ssl_cert, ssl_fingerprint)) - self.connection.UnregisterDisconnectHandler( - self._on_new_account) - self.disconnect(on_purpose=True) - return - if not data[1]: # wrong answer - self.dispatch('ERROR', (_('Invalid answer'), - _('Transport %(name)s answered wrongly to register request: ' - '%(error)s') % {'name': data[0], 'error': data[3]})) - return - is_form = data[2] - conf = data[1] - self.dispatch('REGISTER_AGENT_INFO', (data[0], conf, is_form)) - elif realm == common.xmpp.NS_PRIVACY: - if event == common.xmpp.features_nb.PRIVACY_LISTS_RECEIVED: - # data is (list) - self.dispatch('PRIVACY_LISTS_RECEIVED', (data)) - elif event == common.xmpp.features_nb.PRIVACY_LIST_RECEIVED: - # data is (resp) - if not data: - return - rules = [] - name = data.getTag('query').getTag('list').getAttr('name') - for child in data.getTag('query').getTag('list').getChildren(): - dict_item = child.getAttrs() - childs = [] - if 'type' in dict_item: - for scnd_child in child.getChildren(): - childs += [scnd_child.getName()] - rules.append({'action':dict_item['action'], - 'type':dict_item['type'], 'order':dict_item['order'], - 'value':dict_item['value'], 'child':childs}) - else: - for scnd_child in child.getChildren(): - childs.append(scnd_child.getName()) - rules.append({'action':dict_item['action'], - 'order':dict_item['order'], 'child':childs}) - self.dispatch('PRIVACY_LIST_RECEIVED', (name, rules)) - elif event == common.xmpp.features_nb.PRIVACY_LISTS_ACTIVE_DEFAULT: - # data is (dict) - self.dispatch('PRIVACY_LISTS_ACTIVE_DEFAULT', (data)) - - def _select_next_host(self, hosts): - """ - Selects the next host according to RFC2782 p.3 based on it's priority. - Chooses between hosts with the same priority randomly, where the - probability of being selected is proportional to the weight of the host - """ - hosts_by_prio = sorted(hosts, key=operator.itemgetter('prio')) - - try: - lowest_prio = hosts_by_prio[0]['prio'] - except IndexError: - raise ValueError("No hosts to choose from!") - - hosts_lowest_prio = [h for h in hosts_by_prio if h['prio'] == lowest_prio] - - if len(hosts_lowest_prio) == 1: - return hosts_lowest_prio[0] - else: - rndint = random.randint(0, sum(h['weight'] for h in hosts_lowest_prio)) - weightsum = 0 - for host in sorted(hosts_lowest_prio, key=operator.itemgetter( - 'weight')): - weightsum += host['weight'] - if weightsum >= rndint: - return host - - def connect(self, data = None): - """ - Start a connection to the Jabber server - - Returns connection, and connection type ('tls', 'ssl', 'plain', '') data - MUST contain hostname, usessl, proxy, use_custom_host, custom_host (if - use_custom_host), custom_port (if use_custom_host) - """ - if self.connection: - return self.connection, '' - - if data: - hostname = data['hostname'] - self.try_connecting_for_foo_secs = 45 - p = data['proxy'] - use_srv = True - use_custom = data['use_custom_host'] - if use_custom: - custom_h = data['custom_host'] - custom_p = data['custom_port'] - else: - hostname = gajim.config.get_per('accounts', self.name, 'hostname') - usessl = gajim.config.get_per('accounts', self.name, 'usessl') - self.try_connecting_for_foo_secs = gajim.config.get_per('accounts', - self.name, 'try_connecting_for_foo_secs') - p = gajim.config.get_per('accounts', self.name, 'proxy') - use_srv = gajim.config.get_per('accounts', self.name, 'use_srv') - use_custom = gajim.config.get_per('accounts', self.name, - 'use_custom_host') - custom_h = gajim.config.get_per('accounts', self.name, 'custom_host') - custom_p = gajim.config.get_per('accounts', self.name, 'custom_port') - - # create connection if it doesn't already exist - self.connected = 1 - if p and p in gajim.config.get_per('proxies'): - proxy = {} - proxyptr = gajim.config.get_per('proxies', p) - for key in proxyptr.keys(): - proxy[key] = proxyptr[key][1] - - elif gajim.config.get_per('accounts', self.name, 'use_env_http_proxy'): - try: - try: - env_http_proxy = os.environ['HTTP_PROXY'] - except Exception: - env_http_proxy = os.environ['http_proxy'] - env_http_proxy = env_http_proxy.strip('"') - # Dispose of the http:// prefix - env_http_proxy = env_http_proxy.split('://') - env_http_proxy = env_http_proxy[len(env_http_proxy)-1] - env_http_proxy = env_http_proxy.split('@') - - if len(env_http_proxy) == 2: - login = env_http_proxy[0].split(':') - addr = env_http_proxy[1].split(':') - else: - login = ['', ''] - addr = env_http_proxy[0].split(':') - - proxy = {'host': addr[0], 'type' : u'http', 'user':login[0]} - - if len(addr) == 2: - proxy['port'] = addr[1] - else: - proxy['port'] = 3128 - - if len(login) == 2: - proxy['password'] = login[1] - else: - proxy['password'] = u'' - - except Exception: - proxy = None - else: - proxy = None - h = hostname - p = 5222 - ssl_p = 5223 -# use_srv = False # wants ssl? disable srv lookup - if use_custom: - h = custom_h - p = custom_p - ssl_p = custom_p - use_srv = False - - # SRV resolver - self._proxy = proxy - self._hosts = [ {'host': h, 'port': p, 'ssl_port': ssl_p, 'prio': 10, - 'weight': 10} ] - self._hostname = hostname - if use_srv: - # add request for srv query to the resolve, on result '_on_resolve' - # will be called - gajim.resolver.resolve('_xmpp-client._tcp.' + helpers.idn_to_ascii(h), - self._on_resolve) - else: - self._on_resolve('', []) - - def _on_resolve(self, host, result_array): - # SRV query returned at least one valid result, we put it in hosts dict - if len(result_array) != 0: - self._hosts = [i for i in result_array] - # Add ssl port - ssl_p = 5223 - if gajim.config.get_per('accounts', self.name, 'use_custom_host'): - ssl_p = gajim.config.get_per('accounts', self.name, 'custom_port') - for i in self._hosts: - i['ssl_port'] = ssl_p - self._connect_to_next_host() - - - def _connect_to_next_host(self, retry=False): - log.debug('Connection to next host') - if len(self._hosts): - # No config option exist when creating a new account - if self.last_connection_type: - self._connection_types = [self.last_connection_type] - elif self.name in gajim.config.get_per('accounts'): - self._connection_types = gajim.config.get_per('accounts', self.name, - 'connection_types').split() - else: - self._connection_types = ['tls', 'ssl', 'plain'] - - if self._proxy and self._proxy['type']=='bosh': - # with BOSH, we can't do TLS negotiation with , we do only "plain" - # connection and TLS with handshake right after TCP connecting ("ssl") - scheme = common.xmpp.transports_nb.urisplit(self._proxy['bosh_uri'])[0] - if scheme=='https': - self._connection_types = ['ssl'] - else: - self._connection_types = ['plain'] - - host = self._select_next_host(self._hosts) - self._current_host = host - self._hosts.remove(host) - self.connect_to_next_type() - - else: - if not retry and self.retrycount == 0: - log.debug("Out of hosts, giving up connecting to %s", self.name) - self.time_to_reconnect = None - if self.on_connect_failure: - self.on_connect_failure() - self.on_connect_failure = None - else: - # shown error dialog - self._connection_lost() - else: - # try reconnect if connection has failed before auth to server - self._disconnectedReconnCB() - - def connect_to_next_type(self, retry=False): - if len(self._connection_types): - self._current_type = self._connection_types.pop(0) - if self.last_connection: - self.last_connection.socket.disconnect() - self.last_connection = None - self.connection = None - - if self._current_type == 'ssl': - # SSL (force TLS on different port than plain) - # If we do TLS over BOSH, port of XMPP server should be the standard one - # and TLS should be negotiated because TLS on 5223 is deprecated - if self._proxy and self._proxy['type']=='bosh': - port = self._current_host['port'] - else: - port = self._current_host['ssl_port'] - elif self._current_type == 'tls': - # TLS - negotiate tls after XMPP stream is estabilished - port = self._current_host['port'] - elif self._current_type == 'plain': - # plain connection on defined port - port = self._current_host['port'] - - cacerts = os.path.join(common.gajim.DATA_DIR, 'other', 'cacerts.pem') - mycerts = common.gajim.MY_CACERTS - secure_tuple = (self._current_type, cacerts, mycerts) - - con = common.xmpp.NonBlockingClient( - domain=self._hostname, - caller=self, - idlequeue=gajim.idlequeue) - - self.last_connection = con - # increase default timeout for server responses - common.xmpp.dispatcher_nb.DEFAULT_TIMEOUT_SECONDS = self.try_connecting_for_foo_secs - # FIXME: this is a hack; need a better way - if self.on_connect_success == self._on_new_account: - con.RegisterDisconnectHandler(self._on_new_account) - - self.log_hosttype_info(port) - con.connect( - hostname=self._current_host['host'], - port=port, - on_connect=self.on_connect_success, - on_proxy_failure=self.on_proxy_failure, - on_connect_failure=self.connect_to_next_type, - proxy=self._proxy, - secure_tuple = secure_tuple) - else: - self._connect_to_next_host(retry) - - def log_hosttype_info(self, port): - msg = '>>>>>> Connecting to %s [%s:%d], type = %s' % (self.name, - self._current_host['host'], port, self._current_type) - log.info(msg) - if self._proxy: - msg = '>>>>>> ' - if self._proxy['type']=='bosh': - msg = '%s over BOSH %s' % (msg, self._proxy['bosh_uri']) - if self._proxy['type'] in ['http','socks5'] or self._proxy['bosh_useproxy']: - msg = '%s over proxy %s:%s' % (msg, self._proxy['host'], self._proxy['port']) - log.info(msg) - - def _connect_failure(self, con_type=None): - if not con_type: - # we are not retrying, and not conecting - if not self.retrycount and self.connected != 0: - self.disconnect(on_purpose = True) - self.dispatch('STATUS', 'offline') - pritxt = _('Could not connect to "%s"') % self._hostname - sectxt = _('Check your connection or try again later.') - if self.streamError: - # show error dialog - key = common.xmpp.NS_XMPP_STREAMS + ' ' + self.streamError - if key in common.xmpp.ERRORS: - sectxt2 = _('Server replied: %s') % common.xmpp.ERRORS[key][2] - self.dispatch('ERROR', (pritxt, '%s\n%s' % (sectxt2, sectxt))) - return - # show popup - self.dispatch('CONNECTION_LOST', (pritxt, sectxt)) - - def on_proxy_failure(self, reason): - log.error('Connection to proxy failed: %s' % reason) - self.time_to_reconnect = None - self.on_connect_failure = None - self.disconnect(on_purpose = True) - self.dispatch('STATUS', 'offline') - self.dispatch('CONNECTION_LOST', - (_('Connection to proxy failed'), reason)) - - def _connect_success(self, con, con_type): - if not self.connected: # We went offline during connecting process - # FIXME - not possible, maybe it was when we used threads - return - _con_type = con_type - if _con_type != self._current_type: - log.info('Connecting to next type beacuse desired is %s and returned is %s' - % (self._current_type, _con_type)) - self.connect_to_next_type() - return - con.RegisterDisconnectHandler(self._on_disconnected) - if _con_type == 'plain' and gajim.config.get_per('accounts', self.name, - 'warn_when_plaintext_connection'): - self.dispatch('PLAIN_CONNECTION', (con,)) - return True - if _con_type in ('tls', 'ssl') and con.Connection.ssl_lib != 'PYOPENSSL' \ - and gajim.config.get_per('accounts', self.name, - 'warn_when_insecure_ssl_connection') and \ - not self.connection_auto_accepted: - # Pyopenssl is not used - self.dispatch('INSECURE_SSL_CONNECTION', (con, _con_type)) - return True - return self.connection_accepted(con, con_type) - - def connection_accepted(self, con, con_type): - if not con or not con.Connection: - self.disconnect(on_purpose=True) - self.dispatch('STATUS', 'offline') - self.dispatch('CONNECTION_LOST', - (_('Could not connect to account %s') % self.name, - _('Connection with account %s has been lost. Retry connecting.') % \ - self.name)) - return - self.hosts = [] - self.connection_auto_accepted = False - self.connected_hostname = self._current_host['host'] - self.on_connect_failure = None - con.UnregisterDisconnectHandler(self._on_disconnected) - con.RegisterDisconnectHandler(self._disconnectedReconnCB) - log.debug('Connected to server %s:%s with %s' % ( - self._current_host['host'], self._current_host['port'], con_type)) - - self.last_connection_type = con_type - if gajim.config.get_per('accounts', self.name, 'anonymous_auth'): - name = None - else: - name = gajim.config.get_per('accounts', self.name, 'name') - hostname = gajim.config.get_per('accounts', self.name, 'hostname') - self.connection = con - try: - errnum = con.Connection.ssl_errnum - except AttributeError: - errnum = -1 # we don't have an errnum - if errnum > 0 and str(errnum) not in gajim.config.get_per('accounts', - self.name, 'ignore_ssl_errors'): - text = _('The authenticity of the %s certificate could be invalid.') %\ - hostname - if errnum in ssl_error: - text += _('\nSSL Error: %s') % ssl_error[errnum] - else: - text += _('\nUnknown SSL error: %d') % errnum - self.dispatch('SSL_ERROR', (text, errnum, con.Connection.ssl_cert_pem, - con.Connection.ssl_fingerprint_sha1)) - return True - if hasattr(con.Connection, 'ssl_fingerprint_sha1'): - saved_fingerprint = gajim.config.get_per('accounts', self.name, 'ssl_fingerprint_sha1') - if saved_fingerprint: - # Check sha1 fingerprint - if con.Connection.ssl_fingerprint_sha1 != saved_fingerprint: - self.dispatch('FINGERPRINT_ERROR', - (con.Connection.ssl_fingerprint_sha1,)) - return True - else: - gajim.config.set_per('accounts', self.name, 'ssl_fingerprint_sha1', - con.Connection.ssl_fingerprint_sha1) - self._register_handlers(con, con_type) - con.auth( - user=name, - password=self.password, - resource=self.server_resource, - sasl=1, - on_auth=self.__on_auth) - - def ssl_certificate_accepted(self): - if not self.connection: - self.disconnect(on_purpose=True) - self.dispatch('STATUS', 'offline') - self.dispatch('CONNECTION_LOST', - (_('Could not connect to account %s') % self.name, - _('Connection with account %s has been lost. Retry connecting.') % \ - self.name)) - return - name = gajim.config.get_per('accounts', self.name, 'name') - self._register_handlers(self.connection, 'ssl') - self.connection.auth(name, self.password, self.server_resource, 1, - self.__on_auth) - - def _register_handlers(self, con, con_type): - self.peerhost = con.get_peerhost() - # notify the gui about con_type - self.dispatch('CON_TYPE', con_type) - ConnectionHandlers._register_handlers(self, con, con_type) - - def __on_auth(self, con, auth): - if not con: - self.disconnect(on_purpose=True) - self.dispatch('STATUS', 'offline') - self.dispatch('CONNECTION_LOST', - (_('Could not connect to "%s"') % self._hostname, - _('Check your connection or try again later'))) - if self.on_connect_auth: - self.on_connect_auth(None) - self.on_connect_auth = None - return - if not self.connected: # We went offline during connecting process - if self.on_connect_auth: - self.on_connect_auth(None) - self.on_connect_auth = None - return - if hasattr(con, 'Resource'): - self.server_resource = con.Resource - if gajim.config.get_per('accounts', self.name, 'anonymous_auth'): - # Get jid given by server - old_jid = gajim.get_jid_from_account(self.name) - gajim.config.set_per('accounts', self.name, 'name', con.User) - new_jid = gajim.get_jid_from_account(self.name) - self.dispatch('NEW_JID', (old_jid, new_jid)) - if auth: - self.last_io = gajim.idlequeue.current_time() - self.connected = 2 - self.retrycount = 0 - if self.on_connect_auth: - self.on_connect_auth(con) - self.on_connect_auth = None - else: - # Forget password, it's wrong - self.password = None - gajim.log.debug("Couldn't authenticate to %s" % self._hostname) - self.disconnect(on_purpose = True) - self.dispatch('STATUS', 'offline') - self.dispatch('ERROR', (_('Authentication failed with "%s"') % \ - self._hostname, - _('Please check your login and password for correctness.'))) - if self.on_connect_auth: - self.on_connect_auth(None) - self.on_connect_auth = None - # END connect - - def add_lang(self, stanza): - if self.lang: - stanza.setAttr('xml:lang', self.lang) - - def get_privacy_lists(self): - if not self.connection: - return - common.xmpp.features_nb.getPrivacyLists(self.connection) - - def send_keepalive(self): - # nothing received for the last foo seconds - if self.connection: - self.connection.send(' ') - - def _on_xmpp_ping_answer(self, iq_obj): - id_ = unicode(iq_obj.getAttr('id')) - assert id_ == self.awaiting_xmpp_ping_id - self.awaiting_xmpp_ping_id = None - - def sendPing(self, pingTo=None): - """ - Send XMPP Ping (XEP-0199) request. If pingTo is not set, ping is sent to - server to detect connection failure at application level - """ - if not self.connection: - return - id_ = self.connection.getAnID() - if pingTo: - to = pingTo.get_full_jid() - self.dispatch('PING_SENT', (pingTo)) - else: - to = gajim.config.get_per('accounts', self.name, 'hostname') - self.awaiting_xmpp_ping_id = id_ - iq = common.xmpp.Iq('get', to=to) - iq.addChild(name = 'ping', namespace = common.xmpp.NS_PING) - iq.setID(id_) - def _on_response(resp): - timePong = time_time() - if not common.xmpp.isResultNode(resp): - self.dispatch('PING_ERROR', (pingTo)) - return - timeDiff = round(timePong - timePing,2) - self.dispatch('PING_REPLY', (pingTo, timeDiff)) - if pingTo: - timePing = time_time() - self.connection.SendAndCallForResponse(iq, _on_response) - else: - self.connection.SendAndCallForResponse(iq, self._on_xmpp_ping_answer) - gajim.idlequeue.set_alarm(self.check_pingalive, gajim.config.get_per( - 'accounts', self.name, 'time_for_ping_alive_answer')) - - def get_active_default_lists(self): - if not self.connection: - return - common.xmpp.features_nb.getActiveAndDefaultPrivacyLists(self.connection) - - def del_privacy_list(self, privacy_list): - if not self.connection: - return - def _on_del_privacy_list_result(result): - if result: - self.dispatch('PRIVACY_LIST_REMOVED', privacy_list) - else: - self.dispatch('ERROR', (_('Error while removing privacy list'), - _('Privacy list %s has not been removed. It is maybe active in ' - 'one of your connected resources. Deactivate it and try ' - 'again.') % privacy_list)) - common.xmpp.features_nb.delPrivacyList(self.connection, privacy_list, - _on_del_privacy_list_result) - - def get_privacy_list(self, title): - if not self.connection: - return - common.xmpp.features_nb.getPrivacyList(self.connection, title) - - def set_privacy_list(self, listname, tags): - if not self.connection: - return - common.xmpp.features_nb.setPrivacyList(self.connection, listname, tags) - - def set_active_list(self, listname): - if not self.connection: - return - common.xmpp.features_nb.setActivePrivacyList(self.connection, listname, 'active') - - def set_default_list(self, listname): - if not self.connection: - return - common.xmpp.features_nb.setDefaultPrivacyList(self.connection, listname) - - def build_privacy_rule(self, name, action, order=1): - """ - Build a Privacy rule stanza for invisibility - """ - iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '') - l = iq.getTag('query').setTag('list', {'name': name}) - i = l.setTag('item', {'action': action, 'order': str(order)}) - i.setTag('presence-out') - return iq - - def build_invisible_rule(self): - iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '') - l = iq.getTag('query').setTag('list', {'name': 'invisible'}) - if self.name in gajim.interface.status_sent_to_groups and \ - len(gajim.interface.status_sent_to_groups[self.name]) > 0: - for group in gajim.interface.status_sent_to_groups[self.name]: - i = l.setTag('item', {'type': 'group', 'value': group, - 'action': 'allow', 'order': '1'}) - i.setTag('presence-out') - if self.name in gajim.interface.status_sent_to_users and \ - len(gajim.interface.status_sent_to_users[self.name]) > 0: - for jid in gajim.interface.status_sent_to_users[self.name]: - i = l.setTag('item', {'type': 'jid', 'value': jid, - 'action': 'allow', 'order': '2'}) - i.setTag('presence-out') - i = l.setTag('item', {'action': 'deny', 'order': '3'}) - i.setTag('presence-out') - return iq - - def set_invisible_rule(self): - if not gajim.account_is_connected(self.name): - return - iq = self.build_invisible_rule() - self.connection.send(iq) - - def activate_privacy_rule(self, name): - """ - Activate a privacy rule - """ - if not self.connection: - return - iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '') - iq.getTag('query').setTag('active', {'name': name}) - self.connection.send(iq) - - def send_invisible_presence(self, msg, signed, initial = False): - if not self.connection: - return - if not self.privacy_rules_supported: - self.dispatch('STATUS', gajim.SHOW_LIST[self.connected]) - self.dispatch('ERROR', (_('Invisibility not supported'), - _('Account %s doesn\'t support invisibility.') % self.name)) - return - # If we are already connected, and privacy rules are supported, send - # offline presence first as it's required by XEP-0126 - if self.connected > 1 and self.privacy_rules_supported: - self.on_purpose = True - p = common.xmpp.Presence(typ = 'unavailable') - p = self.add_sha(p, False) - if msg: - p.setStatus(msg) - self.remove_all_transfers() - self.connection.send(p) - - # try to set the privacy rule - iq = self.build_invisible_rule() - self.connection.SendAndCallForResponse(iq, self._continue_invisible, - {'msg': msg, 'signed': signed, 'initial': initial}) - - def _continue_invisible(self, con, iq_obj, msg, signed, initial): - if iq_obj.getType() == 'error': # server doesn't support privacy lists - return - # active the privacy rule - self.privacy_rules_supported = True - self.activate_privacy_rule('invisible') - self.connected = gajim.SHOW_LIST.index('invisible') - self.status = msg - priority = unicode(gajim.get_priority(self.name, 'invisible')) - p = common.xmpp.Presence(priority = priority) - p = self.add_sha(p, True) - if msg: - p.setStatus(msg) - if signed: - p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed) - self.connection.send(p) - self.priority = priority - self.dispatch('STATUS', 'invisible') - if initial: - # ask our VCard - self.request_vcard(None) - - # Get bookmarks from private namespace - self.get_bookmarks() - - # Get annotations - self.get_annotations() - - # Inform GUI we just signed in - self.dispatch('SIGNED_IN', ()) - - def get_signed_presence(self, msg, callback = None): - if gajim.config.get_per('accounts', self.name, 'gpg_sign_presence'): - return self.get_signed_msg(msg, callback) - return '' - - def connect_and_auth(self): - self.on_connect_success = self._connect_success - self.on_connect_failure = self._connect_failure - self.connect() - - def connect_and_init(self, show, msg, sign_msg): - self.continue_connect_info = [show, msg, sign_msg] - self.on_connect_auth = self._discover_server_at_connection - self.connect_and_auth() - - def _discover_server_at_connection(self, con): - self.connection = con - if not self.connection: - return - self.connection.set_send_timeout(self.keepalives, self.send_keepalive) - self.connection.set_send_timeout2(self.pingalives, self.sendPing) - self.connection.onreceive(None) - self.discoverInfo(gajim.config.get_per('accounts', self.name, 'hostname'), - id_prefix='Gajim_') - self.privacy_rules_requested = False - # Discover Stun server(s) - gajim.resolver.resolve('_stun._udp.' + helpers.idn_to_ascii( - self.connected_hostname), self._on_stun_resolved) - - def _on_stun_resolved(self, host, result_array): - if len(result_array) != 0: - self._stun_servers = self._hosts = [i for i in result_array] - - def _request_privacy(self): - iq = common.xmpp.Iq('get', common.xmpp.NS_PRIVACY, xmlns = '') - id_ = self.connection.getAnID() - iq.setID(id_) - self.awaiting_answers[id_] = (PRIVACY_ARRIVED, ) - self.connection.send(iq) - - def send_custom_status(self, show, msg, jid): - if not show in gajim.SHOW_LIST: - return -1 - if not self.connection: - return - sshow = helpers.get_xmpp_show(show) - if not msg: - msg = '' - if show == 'offline': - p = common.xmpp.Presence(typ = 'unavailable', to = jid) - p = self.add_sha(p, False) - if msg: - p.setStatus(msg) - else: - signed = self.get_signed_presence(msg) - priority = unicode(gajim.get_priority(self.name, sshow)) - p = common.xmpp.Presence(typ = None, priority = priority, show = sshow, - to = jid) - p = self.add_sha(p) - if msg: - p.setStatus(msg) - if signed: - p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed) - self.connection.send(p) - - def _change_to_invisible(self, msg): - signed = self.get_signed_presence(msg) - self.send_invisible_presence(msg, signed) - - def _change_from_invisible(self): - if self.privacy_rules_supported: - iq = self.build_privacy_rule('visible', 'allow') - self.connection.send(iq) - self.activate_privacy_rule('visible') - - def _update_status(self, show, msg): - xmpp_show = helpers.get_xmpp_show(show) - priority = unicode(gajim.get_priority(self.name, xmpp_show)) - p = common.xmpp.Presence(typ=None, priority=priority, show=xmpp_show) - p = self.add_sha(p) - if msg: - p.setStatus(msg) - signed = self.get_signed_presence(msg) - if signed: - p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed) - if self.connection: - self.connection.send(p) - self.priority = priority - self.dispatch('STATUS', show) - - def send_motd(self, jid, subject = '', msg = '', xhtml = None): - if not self.connection: - return - msg_iq = common.xmpp.Message(to = jid, body = msg, subject = subject, - xhtml = xhtml) - - self.connection.send(msg_iq) - - def send_message(self, jid, msg, keyID, type_='chat', subject='', - chatstate=None, msg_id=None, composing_xep=None, resource=None, - user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None, - original_message=None, delayed=None, callback=None, callback_args=[]): - - def cb(jid, msg, keyID, forward_from, session, original_message, subject, - type_, msg_iq): - msg_id = self.connection.send(msg_iq) - jid = helpers.parse_jid(jid) - self.dispatch('MSGSENT', (jid, msg, keyID)) - if callback: - callback(msg_id, *callback_args) - - self.log_message(jid, msg, forward_from, session, original_message, - subject, type_) - - self._prepare_message(jid, msg, keyID, type_=type_, subject=subject, - chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep, - resource=resource, user_nick=user_nick, xhtml=xhtml, session=session, - forward_from=forward_from, form_node=form_node, - original_message=original_message, delayed=delayed, callback=cb) - - def send_contacts(self, contacts, jid): - """ - Send contacts with RosterX (Xep-0144) - """ - if not self.connection: - return - if len(contacts) == 1: - msg = _('Sent contact: "%s" (%s)') % (contacts[0].get_full_jid(), - contacts[0].get_shown_name()) - else: - msg = _('Sent contacts:') - for contact in contacts: - msg += '\n "%s" (%s)' % (contact.get_full_jid(), - contact.get_shown_name()) - msg_iq = common.xmpp.Message(to=jid, body=msg) - x = msg_iq.addChild(name='x', namespace=common.xmpp.NS_ROSTERX) - for contact in contacts: - x.addChild(name='item', attrs={'action': 'add', 'jid': contact.jid, - 'name': contact.get_shown_name()}) - self.connection.send(msg_iq) - - def send_stanza(self, stanza): - """ - Send a stanza untouched - """ - if not self.connection: - return - self.connection.send(stanza) - - def ack_subscribed(self, jid): - if not self.connection: - return - log.debug('ack\'ing subscription complete for %s' % jid) - p = common.xmpp.Presence(jid, 'subscribe') - self.connection.send(p) - - def ack_unsubscribed(self, jid): - if not self.connection: - return - log.debug('ack\'ing unsubscription complete for %s' % jid) - p = common.xmpp.Presence(jid, 'unsubscribe') - self.connection.send(p) - - def request_subscription(self, jid, msg='', name='', groups=[], - auto_auth=False, user_nick=''): - if not self.connection: - return - log.debug('subscription request for %s' % jid) - if auto_auth: - self.jids_for_auto_auth.append(jid) - # RFC 3921 section 8.2 - infos = {'jid': jid} - if name: - infos['name'] = name - iq = common.xmpp.Iq('set', common.xmpp.NS_ROSTER) - q = iq.getTag('query') - item = q.addChild('item', attrs=infos) - for g in groups: - item.addChild('group').setData(g) - self.connection.send(iq) - - p = common.xmpp.Presence(jid, 'subscribe') - if user_nick: - p.setTag('nick', namespace = common.xmpp.NS_NICK).setData(user_nick) - p = self.add_sha(p) - if msg: - p.setStatus(msg) - self.connection.send(p) - - def send_authorization(self, jid): - if not self.connection: - return - p = common.xmpp.Presence(jid, 'subscribed') - p = self.add_sha(p) - self.connection.send(p) - - def refuse_authorization(self, jid): - if not self.connection: - return - p = common.xmpp.Presence(jid, 'unsubscribed') - p = self.add_sha(p) - self.connection.send(p) - - def unsubscribe(self, jid, remove_auth = True): - if not self.connection: - return - if remove_auth: - self.connection.getRoster().delItem(jid) - jid_list = gajim.config.get_per('contacts') - for j in jid_list: - if j.startswith(jid): - gajim.config.del_per('contacts', j) - else: - self.connection.getRoster().Unsubscribe(jid) - self.update_contact(jid, '', []) - - def unsubscribe_agent(self, agent): - if not self.connection: - return - iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to = agent) - iq.getTag('query').setTag('remove') - id_ = self.connection.getAnID() - iq.setID(id_) - self.awaiting_answers[id_] = (AGENT_REMOVED, agent) - self.connection.send(iq) - self.connection.getRoster().delItem(agent) - - def send_new_account_infos(self, form, is_form): - if is_form: - # Get username and password and put them in new_account_info - for field in form.iter_fields(): - if field.var == 'username': - self.new_account_info['name'] = field.value - if field.var == 'password': - self.new_account_info['password'] = field.value - else: - # Get username and password and put them in new_account_info - if 'username' in form: - self.new_account_info['name'] = form['username'] - if 'password' in form: - self.new_account_info['password'] = form['password'] - self.new_account_form = form - self.new_account(self.name, self.new_account_info) - - def new_account(self, name, config, sync=False): - # If a connection already exist we cannot create a new account - if self.connection: - return - self._hostname = config['hostname'] - self.new_account_info = config - self.name = name - self.on_connect_success = self._on_new_account - self.on_connect_failure = self._on_new_account - self.connect(config) - - def _on_new_account(self, con=None, con_type=None): - if not con_type: - if len(self._connection_types) or len(self._hosts): - # There are still other way to try to connect - return - self.dispatch('NEW_ACC_NOT_CONNECTED', - (_('Could not connect to "%s"') % self._hostname)) - return - self.on_connect_failure = None - self.connection = con - common.xmpp.features_nb.getRegInfo(con, self._hostname) - - def request_last_status_time(self, jid, resource, groupchat_jid=None): - """ - groupchat_jid is used when we want to send a request to a real jid and - act as if the answer comes from the groupchat_jid - """ - if not self.connection: - return - to_whom_jid = jid - if resource: - to_whom_jid += '/' + resource - iq = common.xmpp.Iq(to = to_whom_jid, typ = 'get', queryNS =\ - common.xmpp.NS_LAST) - id_ = self.connection.getAnID() - iq.setID(id_) - if groupchat_jid: - self.groupchat_jids[id_] = groupchat_jid - self.last_ids.append(id_) - self.connection.send(iq) - - def request_os_info(self, jid, resource, groupchat_jid=None): - """ - groupchat_jid is used when we want to send a request to a real jid and - act as if the answer comes from the groupchat_jid - """ - if not self.connection: - return - # If we are invisible, do not request - if self.connected == gajim.SHOW_LIST.index('invisible'): - self.dispatch('OS_INFO', (jid, resource, _('Not fetched because of invisible status'), _('Not fetched because of invisible status'))) - return - to_whom_jid = jid - if resource: - to_whom_jid += '/' + resource - iq = common.xmpp.Iq(to=to_whom_jid, typ='get', queryNS=\ - common.xmpp.NS_VERSION) - id_ = self.connection.getAnID() - iq.setID(id_) - if groupchat_jid: - self.groupchat_jids[id_] = groupchat_jid - self.version_ids.append(id_) - self.connection.send(iq) - - def request_entity_time(self, jid, resource, groupchat_jid=None): - """ - groupchat_jid is used when we want to send a request to a real jid and - act as if the answer comes from the groupchat_jid - """ - if not self.connection: - return - # If we are invisible, do not request - if self.connected == gajim.SHOW_LIST.index('invisible'): - self.dispatch('ENTITY_TIME', (jid, resource, _('Not fetched because of invisible status'))) - return - to_whom_jid = jid - if resource: - to_whom_jid += '/' + resource - iq = common.xmpp.Iq(to=to_whom_jid, typ='get') - iq.addChild('time', namespace=common.xmpp.NS_TIME_REVISED) - id_ = self.connection.getAnID() - iq.setID(id_) - if groupchat_jid: - self.groupchat_jids[id_] = groupchat_jid - self.entity_time_ids.append(id_) - self.connection.send(iq) - - def get_settings(self): - """ - Get Gajim settings as described in XEP 0049 - """ - if not self.connection: - return - iq = common.xmpp.Iq(typ='get') - iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) - iq2.addChild(name='gajim', namespace='gajim:prefs') - self.connection.send(iq) - - def _request_bookmarks_xml(self): - if not self.connection: - return - iq = common.xmpp.Iq(typ='get') - iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) - iq2.addChild(name='storage', namespace='storage:bookmarks') - self.connection.send(iq) - - def _check_bookmarks_received(self): - if not self.bookmarks: - self._request_bookmarks_xml() - - def get_bookmarks(self, storage_type=None): - """ - Get Bookmarks from storage or PubSub if supported as described in XEP - 0048 - - storage_type can be set to xml to force request to xml storage - """ - if not self.connection: - return - if self.pubsub_supported and storage_type != 'xml': - self.send_pb_retrieve('', 'storage:bookmarks') - # some server (ejabberd) are so slow to answer that we request via XML - # if we don't get answer in the next 30 seconds - gajim.idlequeue.set_alarm(self._check_bookmarks_received, 30) - else: - self._request_bookmarks_xml() - - def store_bookmarks(self, storage_type=None): - """ - Send bookmarks to the storage namespace or PubSub if supported - - storage_type can be set to 'pubsub' or 'xml' so store in only one method - else it will be stored on both - """ - if not self.connection: - return - iq = common.xmpp.Node(tag='storage', attrs={'xmlns': 'storage:bookmarks'}) - for bm in self.bookmarks: - iq2 = iq.addChild(name = "conference") - iq2.setAttr('jid', bm['jid']) - iq2.setAttr('autojoin', bm['autojoin']) - iq2.setAttr('minimize', bm['minimize']) - iq2.setAttr('name', bm['name']) - # Only add optional elements if not empty - # Note: need to handle both None and '' as empty - # thus shouldn't use "is not None" - if bm.get('nick', None): - iq2.setTagData('nick', bm['nick']) - if bm.get('password', None): - iq2.setTagData('password', bm['password']) - if bm.get('print_status', None): - iq2.setTagData('print_status', bm['print_status']) - - if self.pubsub_supported and storage_type != 'xml': - if self.pubsub_publish_options_supported: - options = common.xmpp.Node(common.xmpp.NS_DATA + ' x', - attrs={'type': 'submit'}) - f = options.addChild('field', attrs={'var': 'FORM_TYPE', - 'type': 'hidden'}) - f.setTagData('value', common.xmpp.NS_PUBSUB_PUBLISH_OPTIONS) - f = options.addChild('field', attrs={'var': 'pubsub#persist_items'}) - f.setTagData('value', 'true') - f = options.addChild('field', attrs={'var': 'pubsub#access_model'}) - f.setTagData('value', 'whitelist') - else: - options = None - self.send_pb_publish('', 'storage:bookmarks', iq, 'current', - options=options) - if storage_type != 'pubsub': - iqA = common.xmpp.Iq(typ='set') - iqB = iqA.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) - iqB.addChild(node=iq) - self.connection.send(iqA) - - def get_annotations(self): - """ - Get Annonations from storage as described in XEP 0048, and XEP 0145 - """ - self.annotations = {} - if not self.connection: - return - iq = common.xmpp.Iq(typ='get') - iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) - iq2.addChild(name='storage', namespace='storage:rosternotes') - self.connection.send(iq) - - def store_annotations(self): - """ - Set Annonations in private storage as described in XEP 0048, and XEP 0145 - """ - if not self.connection: - return - iq = common.xmpp.Iq(typ='set') - iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) - iq3 = iq2.addChild(name='storage', namespace='storage:rosternotes') - for jid in self.annotations.keys(): - if self.annotations[jid]: - iq4 = iq3.addChild(name = "note") - iq4.setAttr('jid', jid) - iq4.setData(self.annotations[jid]) - self.connection.send(iq) - - - def get_metacontacts(self): - """ - Get metacontacts list from storage as described in XEP 0049 - """ - if not self.connection: - return - iq = common.xmpp.Iq(typ='get') - iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) - iq2.addChild(name='storage', namespace='storage:metacontacts') - id_ = self.connection.getAnID() - iq.setID(id_) - self.awaiting_answers[id_] = (METACONTACTS_ARRIVED, ) - self.connection.send(iq) - - def store_metacontacts(self, tags_list): - """ - Send meta contacts to the storage namespace - """ - if not self.connection: - return - iq = common.xmpp.Iq(typ='set') - iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) - iq3 = iq2.addChild(name='storage', namespace='storage:metacontacts') - for tag in tags_list: - for data in tags_list[tag]: - jid = data['jid'] - dict_ = {'jid': jid, 'tag': tag} - if 'order' in data: - dict_['order'] = data['order'] - iq3.addChild(name = 'meta', attrs = dict_) - self.connection.send(iq) - - def send_agent_status(self, agent, ptype): - if not self.connection: - return - show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected]) - p = common.xmpp.Presence(to = agent, typ = ptype, show = show) - p = self.add_sha(p, ptype != 'unavailable') - self.connection.send(p) - - def check_unique_room_id_support(self, server, instance): - if not self.connection: - return - iq = common.xmpp.Iq(typ = 'get', to = server) - iq.setAttr('id', 'unique1') - iq.addChild('unique', namespace=common.xmpp.NS_MUC_UNIQUE) - def _on_response(resp): - if not common.xmpp.isResultNode(resp): - self.dispatch('UNIQUE_ROOM_ID_UNSUPPORTED', (server, instance)) - return - self.dispatch('UNIQUE_ROOM_ID_SUPPORTED', (server, instance, - resp.getTag('unique').getData())) - self.connection.SendAndCallForResponse(iq, _on_response) - - def join_gc(self, nick, room_jid, password, change_nick=False): - # FIXME: This room JID needs to be normalized; see #1364 - if not self.connection: - return - show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected]) - if show == 'invisible': - # Never join a room when invisible - return - - # last date/time in history to avoid duplicate - if room_jid not in self.last_history_time: - # Not in memory, get it from DB - last_log = None - # Do not check if we are not logging for this room - if gajim.config.should_log(self.name, room_jid): - # Check time first in the FAST table - last_log = gajim.logger.get_room_last_message_time(room_jid) - if last_log is None: - # Not in special table, get it from messages DB - last_log = gajim.logger.get_last_date_that_has_logs(room_jid, - is_room=True) - # Create self.last_history_time[room_jid] even if not logging, - # could be used in connection_handlers - if last_log is None: - last_log = 0 - self.last_history_time[room_jid] = last_log - - p = common.xmpp.Presence(to='%s/%s' % (room_jid, nick), - show=show, status=self.status) - h = hmac.new(self.secret_hmac, room_jid).hexdigest()[:6] - id_ = self.connection.getAnID() - id_ = 'gajim_muc_' + id_ + '_' + h - p.setID(id_) - if gajim.config.get('send_sha_in_gc_presence'): - p = self.add_sha(p) - self.add_lang(p) - if not change_nick: - t = p.setTag(common.xmpp.NS_MUC + ' x') - last_date = self.last_history_time[room_jid] - if last_date == 0: - last_date = time.time() - gajim.config.get( - 'muc_restore_timeout') * 60 - else: - last_date = min(last_date, time.time() - gajim.config.get( - 'muc_restore_timeout') * 60) - last_date = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(last_date)) - t.setTag('history', {'maxstanzas': gajim.config.get( - 'muc_restore_lines'), 'since': last_date}) - if password: - t.setTagData('password', password) - self.connection.send(p) - - def send_gc_message(self, jid, msg, xhtml = None): - if not self.connection: - return - if not xhtml and gajim.config.get('rst_formatting_outgoing_messages'): - from common.rst_xhtml_generator import create_xhtml - xhtml = create_xhtml(msg) - msg_iq = common.xmpp.Message(jid, msg, typ = 'groupchat', xhtml = xhtml) - self.connection.send(msg_iq) - self.dispatch('MSGSENT', (jid, msg)) - - def send_gc_subject(self, jid, subject): - if not self.connection: - return - msg_iq = common.xmpp.Message(jid,typ = 'groupchat', subject = subject) - self.connection.send(msg_iq) - - def request_gc_config(self, room_jid): - if not self.connection: - return - iq = common.xmpp.Iq(typ = 'get', queryNS = common.xmpp.NS_MUC_OWNER, - to = room_jid) - self.add_lang(iq) - self.connection.send(iq) - - def destroy_gc_room(self, room_jid, reason = '', jid = ''): - if not self.connection: - return - iq = common.xmpp.Iq(typ = 'set', queryNS = common.xmpp.NS_MUC_OWNER, - to = room_jid) - destroy = iq.getTag('query').setTag('destroy') - if reason: - destroy.setTagData('reason', reason) - if jid: - destroy.setAttr('jid', jid) - self.connection.send(iq) - - def send_gc_status(self, nick, jid, show, status): - if not gajim.account_is_connected(self.name): - return - if show == 'invisible': - show = 'offline' - ptype = None - if show == 'offline': - ptype = 'unavailable' - xmpp_show = helpers.get_xmpp_show(show) - p = common.xmpp.Presence(to = '%s/%s' % (jid, nick), typ = ptype, - show = xmpp_show, status = status) - h = hmac.new(self.secret_hmac, jid).hexdigest()[:6] - id_ = self.connection.getAnID() - id_ = 'gajim_muc_' + id_ + '_' + h - p.setID(id_) - if gajim.config.get('send_sha_in_gc_presence') and show != 'offline': - p = self.add_sha(p, ptype != 'unavailable') - self.add_lang(p) - # send instantly so when we go offline, status is sent to gc before we - # disconnect from jabber server - self.connection.send(p) - - def gc_got_disconnected(self, room_jid): - """ - A groupchat got disconnected. This can be or purpose or not - - Save the time we had last message to avoid duplicate logs AND be faster - than get that date from DB. Save time that we have in mem in a small - table (with fast access) - """ - gajim.logger.set_room_last_message_time(room_jid, self.last_history_time[room_jid]) - - def gc_set_role(self, room_jid, nick, role, reason = ''): - """ - Role is for all the life of the room so it's based on nick - """ - if not self.connection: - return - iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ - common.xmpp.NS_MUC_ADMIN) - item = iq.getTag('query').setTag('item') - item.setAttr('nick', nick) - item.setAttr('role', role) - if reason: - item.addChild(name = 'reason', payload = reason) - self.connection.send(iq) - - def gc_set_affiliation(self, room_jid, jid, affiliation, reason = ''): - """ - Affiliation is for all the life of the room so it's based on jid - """ - if not self.connection: - return - iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ - common.xmpp.NS_MUC_ADMIN) - item = iq.getTag('query').setTag('item') - item.setAttr('jid', jid) - item.setAttr('affiliation', affiliation) - if reason: - item.addChild(name = 'reason', payload = reason) - self.connection.send(iq) - - def send_gc_affiliation_list(self, room_jid, users_dict): - if not self.connection: - return - iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS = \ - common.xmpp.NS_MUC_ADMIN) - item = iq.getTag('query') - for jid in users_dict: - item_tag = item.addChild('item', {'jid': jid, - 'affiliation': users_dict[jid]['affiliation']}) - if 'reason' in users_dict[jid] and users_dict[jid]['reason']: - item_tag.setTagData('reason', users_dict[jid]['reason']) - self.connection.send(iq) - - def get_affiliation_list(self, room_jid, affiliation): - if not self.connection: - return - iq = common.xmpp.Iq(typ = 'get', to = room_jid, queryNS = \ - common.xmpp.NS_MUC_ADMIN) - item = iq.getTag('query').setTag('item') - item.setAttr('affiliation', affiliation) - self.connection.send(iq) - - def send_gc_config(self, room_jid, form): - if not self.connection: - return - iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ - common.xmpp.NS_MUC_OWNER) - query = iq.getTag('query') - form.setAttr('type', 'submit') - query.addChild(node = form) - self.connection.send(iq) - - def change_password(self, password): - if not self.connection: - return - hostname = gajim.config.get_per('accounts', self.name, 'hostname') - username = gajim.config.get_per('accounts', self.name, 'name') - iq = common.xmpp.Iq(typ = 'set', to = hostname) - q = iq.setTag(common.xmpp.NS_REGISTER + ' query') - q.setTagData('username',username) - q.setTagData('password',password) - self.connection.send(iq) - - def get_password(self, callback): - if self.password: - callback(self.password) - return - self.pasword_callback = callback - self.dispatch('PASSWORD_REQUIRED', None) - - def set_password(self, password): - self.password = password - if self.pasword_callback: - self.pasword_callback(password) - self.pasword_callback = None - - def unregister_account(self, on_remove_success): - # no need to write this as a class method and keep the value of - # on_remove_success as a class property as pass it as an argument - def _on_unregister_account_connect(con): - self.on_connect_auth = None - if gajim.account_is_connected(self.name): - hostname = gajim.config.get_per('accounts', self.name, 'hostname') - iq = common.xmpp.Iq(typ = 'set', to = hostname) - iq.setTag(common.xmpp.NS_REGISTER + ' query').setTag('remove') - def _on_answer(result): - if result.getType() == 'result': - on_remove_success(True) - return - self.dispatch('ERROR', (_('Unregister failed'), - _('Unregistration with server %(server)s failed: %(error)s') \ - % {'server': hostname, 'error': result.getErrorMsg()})) - on_remove_success(False) - con.SendAndCallForResponse(iq, _on_answer) - return - on_remove_success(False) - if self.connected == 0: - self.on_connect_auth = _on_unregister_account_connect - self.connect_and_auth() - else: - _on_unregister_account_connect(self.connection) - - def send_invite(self, room, to, reason='', continue_tag=False): - """ - Send invitation - """ - message=common.xmpp.Message(to = room) - c = message.addChild(name = 'x', namespace = common.xmpp.NS_MUC_USER) - c = c.addChild(name = 'invite', attrs={'to' : to}) - if continue_tag: - c.addChild(name = 'continue') - if reason != '': - c.setTagData('reason', reason) - self.connection.send(message) - - def check_pingalive(self): - if self.awaiting_xmpp_ping_id: - # We haven't got the pong in time, disco and reconnect - log.warn("No reply received for keepalive ping. Reconnecting.") - self._disconnectedReconnCB() - - def _reconnect_alarm(self): - if self.time_to_reconnect: - if self.connected < 2: - self._reconnect() - else: - self.time_to_reconnect = None - - def request_search_fields(self, jid): - iq = common.xmpp.Iq(typ = 'get', to = jid, queryNS = \ - common.xmpp.NS_SEARCH) - self.connection.send(iq) - - def send_search_form(self, jid, form, is_form): - iq = common.xmpp.Iq(typ = 'set', to = jid, queryNS = \ - common.xmpp.NS_SEARCH) - item = iq.getTag('query') - if is_form: - item.addChild(node = form) - else: - for i in form.keys(): - item.setTagData(i,form[i]) - def _on_response(resp): - jid = jid = helpers.get_jid_from_iq(resp) - tag = resp.getTag('query', namespace = common.xmpp.NS_SEARCH) - if not tag: - self.dispatch('SEARCH_RESULT', (jid, None, False)) - return - df = tag.getTag('x', namespace = common.xmpp.NS_DATA) - if df: - self.dispatch('SEARCH_RESULT', (jid, df, True)) - return - df = [] - for item in tag.getTags('item'): - # We also show attributes. jid is there - f = item.attrs - for i in item.getPayload(): - f[i.getName()] = i.getData() - df.append(f) - self.dispatch('SEARCH_RESULT', (jid, df, False)) - - self.connection.SendAndCallForResponse(iq, _on_response) - - def load_roster_from_db(self): - roster = gajim.logger.get_roster(gajim.get_jid_from_account(self.name)) - self.dispatch('ROSTER', roster) + def __init__(self, name): + CommonConnection.__init__(self, name) + ConnectionHandlers.__init__(self) + # this property is used to prevent double connections + self.last_connection = None # last ClientCommon instance + # If we succeed to connect, remember it so next time we try (after a + # disconnection) we try only this type. + self.last_connection_type = None + self.lang = None + if locale.getdefaultlocale()[0]: + self.lang = locale.getdefaultlocale()[0].split('_')[0] + # increase/decrease default timeout for server responses + self.try_connecting_for_foo_secs = 45 + # holds the actual hostname to which we are connected + self.connected_hostname = None + self.last_time_to_reconnect = None + self.new_account_info = None + self.new_account_form = None + self.annotations = {} + self.last_io = gajim.idlequeue.current_time() + self.last_sent = [] + self.last_history_time = {} + self.password = passwords.get_password(name) + + self.music_track_info = 0 + self.location_info = {} + self.pubsub_supported = False + self.pubsub_publish_options_supported = False + # Do we auto accept insecure connection + self.connection_auto_accepted = False + self.pasword_callback = None + + self.on_connect_success = None + self.on_connect_failure = None + self.retrycount = 0 + self.jids_for_auto_auth = [] # list of jid to auto-authorize + self.available_transports = {} # list of available transports on this + # server {'icq': ['icq.server.com', 'icq2.server.com'], } + self.private_storage_supported = True + self.streamError = '' + self.secret_hmac = str(random.random())[2:] + # END __init__ + + def get_config_values_or_default(self): + if gajim.config.get_per('accounts', self.name, 'keep_alives_enabled'): + self.keepalives = gajim.config.get_per('accounts', self.name, + 'keep_alive_every_foo_secs') + else: + self.keepalives = 0 + if gajim.config.get_per('accounts', self.name, 'ping_alives_enabled'): + self.pingalives = gajim.config.get_per('accounts', self.name, + 'ping_alive_every_foo_secs') + else: + self.pingalives = 0 + + def check_jid(self, jid): + return helpers.parse_jid(jid) + + def _reconnect(self): + # Do not try to reco while we are already trying + self.time_to_reconnect = None + if self.connected < 2: # connection failed + log.debug('reconnect') + self.connected = 1 + self.dispatch('STATUS', 'connecting') + self.retrycount += 1 + self.on_connect_auth = self._discover_server_at_connection + self.connect_and_init(self.old_show, self.status, self.USE_GPG) + else: + # reconnect succeeded + self.time_to_reconnect = None + self.retrycount = 0 + + # We are doing disconnect at so many places, better use one function in all + def disconnect(self, on_purpose=False): + gajim.interface.music_track_changed(None, None, self.name) + self.reset_awaiting_pep() + self.on_purpose = on_purpose + self.connected = 0 + self.time_to_reconnect = None + self.privacy_rules_supported = False + if self.connection: + # make sure previous connection is completely closed + gajim.proxy65_manager.disconnect(self.connection) + self.terminate_sessions() + self.remove_all_transfers() + self.connection.disconnect() + self.last_connection = None + self.connection = None + + def _disconnectedReconnCB(self): + """ + Called when we are disconnected + """ + log.info('disconnectedReconnCB called') + if gajim.account_is_connected(self.name): + # we cannot change our status to offline or connecting + # after we auth to server + self.old_show = gajim.SHOW_LIST[self.connected] + self.connected = 0 + if not self.on_purpose: + self.dispatch('STATUS', 'offline') + self.disconnect() + if gajim.config.get_per('accounts', self.name, 'autoreconnect'): + self.connected = -1 + self.dispatch('STATUS', 'error') + if gajim.status_before_autoaway[self.name]: + # We were auto away. So go back online + self.status = gajim.status_before_autoaway[self.name] + gajim.status_before_autoaway[self.name] = '' + self.old_show = 'online' + # this check has moved from _reconnect method + # do exponential backoff until 15 minutes, + # then small linear increase + if self.retrycount < 2 or self.last_time_to_reconnect is None: + self.last_time_to_reconnect = 5 + if self.last_time_to_reconnect < 800: + self.last_time_to_reconnect *= 1.5 + self.last_time_to_reconnect += randomsource.randint(0, 5) + self.time_to_reconnect = int(self.last_time_to_reconnect) + log.info("Reconnect to %s in %ss", self.name, self.time_to_reconnect) + gajim.idlequeue.set_alarm(self._reconnect_alarm, + self.time_to_reconnect) + elif self.on_connect_failure: + self.on_connect_failure() + self.on_connect_failure = None + else: + # show error dialog + self._connection_lost() + else: + self.disconnect() + self.on_purpose = False + # END disconnectedReconnCB + + def _connection_lost(self): + log.debug('_connection_lost') + self.disconnect(on_purpose = False) + self.dispatch('STATUS', 'offline') + self.dispatch('CONNECTION_LOST', + (_('Connection with account "%s" has been lost') % self.name, + _('Reconnect manually.'))) + + def _event_dispatcher(self, realm, event, data): + CommonConnection._event_dispatcher(self, realm, event, data) + if realm == common.xmpp.NS_REGISTER: + if event == common.xmpp.features_nb.REGISTER_DATA_RECEIVED: + # data is (agent, DataFrom, is_form, error_msg) + if self.new_account_info and \ + self.new_account_info['hostname'] == data[0]: + # it's a new account + if not data[1]: # wrong answer + self.dispatch('ACC_NOT_OK', ( + _('Server %(name)s answered wrongly to register request: ' + '%(error)s') % {'name': data[0], 'error': data[3]})) + return + is_form = data[2] + conf = data[1] + if self.new_account_form: + def _on_register_result(result): + if not common.xmpp.isResultNode(result): + self.dispatch('ACC_NOT_OK', (result.getError())) + return + if gajim.HAVE_GPG: + self.USE_GPG = True + self.gpg = GnuPG.GnuPG(gajim.config.get( + 'use_gpg_agent')) + self.dispatch('ACC_OK', (self.new_account_info)) + self.new_account_info = None + self.new_account_form = None + if self.connection: + self.connection.UnregisterDisconnectHandler( + self._on_new_account) + self.disconnect(on_purpose=True) + # it's the second time we get the form, we have info user + # typed, so send them + if is_form: + #TODO: Check if form has changed + iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to=self._hostname) + iq.setTag('query').addChild(node=self.new_account_form) + self.connection.SendAndCallForResponse(iq, + _on_register_result) + else: + if self.new_account_form.keys().sort() != \ + conf.keys().sort(): + # requested config has changed since first connection + self.dispatch('ACC_NOT_OK', (_( + 'Server %s provided a different registration form')\ + % data[0])) + return + common.xmpp.features_nb.register(self.connection, + self._hostname, self.new_account_form, + _on_register_result) + return + try: + errnum = self.connection.Connection.ssl_errnum + except AttributeError: + errnum = -1 # we don't have an errnum + ssl_msg = '' + if errnum > 0: + ssl_msg = ssl_error.get(errnum, _('Unknown SSL error: %d') % errnum) + ssl_cert = '' + if hasattr(self.connection.Connection, 'ssl_cert_pem'): + ssl_cert = self.connection.Connection.ssl_cert_pem + ssl_fingerprint = '' + if hasattr(self.connection.Connection, 'ssl_fingerprint_sha1'): + ssl_fingerprint = \ + self.connection.Connection.ssl_fingerprint_sha1 + self.dispatch('NEW_ACC_CONNECTED', (conf, is_form, ssl_msg, + errnum, ssl_cert, ssl_fingerprint)) + self.connection.UnregisterDisconnectHandler( + self._on_new_account) + self.disconnect(on_purpose=True) + return + if not data[1]: # wrong answer + self.dispatch('ERROR', (_('Invalid answer'), + _('Transport %(name)s answered wrongly to register request: ' + '%(error)s') % {'name': data[0], 'error': data[3]})) + return + is_form = data[2] + conf = data[1] + self.dispatch('REGISTER_AGENT_INFO', (data[0], conf, is_form)) + elif realm == common.xmpp.NS_PRIVACY: + if event == common.xmpp.features_nb.PRIVACY_LISTS_RECEIVED: + # data is (list) + self.dispatch('PRIVACY_LISTS_RECEIVED', (data)) + elif event == common.xmpp.features_nb.PRIVACY_LIST_RECEIVED: + # data is (resp) + if not data: + return + rules = [] + name = data.getTag('query').getTag('list').getAttr('name') + for child in data.getTag('query').getTag('list').getChildren(): + dict_item = child.getAttrs() + childs = [] + if 'type' in dict_item: + for scnd_child in child.getChildren(): + childs += [scnd_child.getName()] + rules.append({'action':dict_item['action'], + 'type':dict_item['type'], 'order':dict_item['order'], + 'value':dict_item['value'], 'child':childs}) + else: + for scnd_child in child.getChildren(): + childs.append(scnd_child.getName()) + rules.append({'action':dict_item['action'], + 'order':dict_item['order'], 'child':childs}) + self.dispatch('PRIVACY_LIST_RECEIVED', (name, rules)) + elif event == common.xmpp.features_nb.PRIVACY_LISTS_ACTIVE_DEFAULT: + # data is (dict) + self.dispatch('PRIVACY_LISTS_ACTIVE_DEFAULT', (data)) + + def _select_next_host(self, hosts): + """ + Selects the next host according to RFC2782 p.3 based on it's priority. + Chooses between hosts with the same priority randomly, where the + probability of being selected is proportional to the weight of the host + """ + hosts_by_prio = sorted(hosts, key=operator.itemgetter('prio')) + + try: + lowest_prio = hosts_by_prio[0]['prio'] + except IndexError: + raise ValueError("No hosts to choose from!") + + hosts_lowest_prio = [h for h in hosts_by_prio if h['prio'] == lowest_prio] + + if len(hosts_lowest_prio) == 1: + return hosts_lowest_prio[0] + else: + rndint = random.randint(0, sum(h['weight'] for h in hosts_lowest_prio)) + weightsum = 0 + for host in sorted(hosts_lowest_prio, key=operator.itemgetter( + 'weight')): + weightsum += host['weight'] + if weightsum >= rndint: + return host + + def connect(self, data = None): + """ + Start a connection to the Jabber server + + Returns connection, and connection type ('tls', 'ssl', 'plain', '') data + MUST contain hostname, usessl, proxy, use_custom_host, custom_host (if + use_custom_host), custom_port (if use_custom_host) + """ + if self.connection: + return self.connection, '' + + if data: + hostname = data['hostname'] + self.try_connecting_for_foo_secs = 45 + p = data['proxy'] + use_srv = True + use_custom = data['use_custom_host'] + if use_custom: + custom_h = data['custom_host'] + custom_p = data['custom_port'] + else: + hostname = gajim.config.get_per('accounts', self.name, 'hostname') + usessl = gajim.config.get_per('accounts', self.name, 'usessl') + self.try_connecting_for_foo_secs = gajim.config.get_per('accounts', + self.name, 'try_connecting_for_foo_secs') + p = gajim.config.get_per('accounts', self.name, 'proxy') + use_srv = gajim.config.get_per('accounts', self.name, 'use_srv') + use_custom = gajim.config.get_per('accounts', self.name, + 'use_custom_host') + custom_h = gajim.config.get_per('accounts', self.name, 'custom_host') + custom_p = gajim.config.get_per('accounts', self.name, 'custom_port') + + # create connection if it doesn't already exist + self.connected = 1 + if p and p in gajim.config.get_per('proxies'): + proxy = {} + proxyptr = gajim.config.get_per('proxies', p) + for key in proxyptr.keys(): + proxy[key] = proxyptr[key][1] + + elif gajim.config.get_per('accounts', self.name, 'use_env_http_proxy'): + try: + try: + env_http_proxy = os.environ['HTTP_PROXY'] + except Exception: + env_http_proxy = os.environ['http_proxy'] + env_http_proxy = env_http_proxy.strip('"') + # Dispose of the http:// prefix + env_http_proxy = env_http_proxy.split('://') + env_http_proxy = env_http_proxy[len(env_http_proxy)-1] + env_http_proxy = env_http_proxy.split('@') + + if len(env_http_proxy) == 2: + login = env_http_proxy[0].split(':') + addr = env_http_proxy[1].split(':') + else: + login = ['', ''] + addr = env_http_proxy[0].split(':') + + proxy = {'host': addr[0], 'type' : u'http', 'user':login[0]} + + if len(addr) == 2: + proxy['port'] = addr[1] + else: + proxy['port'] = 3128 + + if len(login) == 2: + proxy['password'] = login[1] + else: + proxy['password'] = u'' + + except Exception: + proxy = None + else: + proxy = None + h = hostname + p = 5222 + ssl_p = 5223 +# use_srv = False # wants ssl? disable srv lookup + if use_custom: + h = custom_h + p = custom_p + ssl_p = custom_p + use_srv = False + + # SRV resolver + self._proxy = proxy + self._hosts = [ {'host': h, 'port': p, 'ssl_port': ssl_p, 'prio': 10, + 'weight': 10} ] + self._hostname = hostname + if use_srv: + # add request for srv query to the resolve, on result '_on_resolve' + # will be called + gajim.resolver.resolve('_xmpp-client._tcp.' + helpers.idn_to_ascii(h), + self._on_resolve) + else: + self._on_resolve('', []) + + def _on_resolve(self, host, result_array): + # SRV query returned at least one valid result, we put it in hosts dict + if len(result_array) != 0: + self._hosts = [i for i in result_array] + # Add ssl port + ssl_p = 5223 + if gajim.config.get_per('accounts', self.name, 'use_custom_host'): + ssl_p = gajim.config.get_per('accounts', self.name, 'custom_port') + for i in self._hosts: + i['ssl_port'] = ssl_p + self._connect_to_next_host() + + + def _connect_to_next_host(self, retry=False): + log.debug('Connection to next host') + if len(self._hosts): + # No config option exist when creating a new account + if self.last_connection_type: + self._connection_types = [self.last_connection_type] + elif self.name in gajim.config.get_per('accounts'): + self._connection_types = gajim.config.get_per('accounts', self.name, + 'connection_types').split() + else: + self._connection_types = ['tls', 'ssl', 'plain'] + + if self._proxy and self._proxy['type']=='bosh': + # with BOSH, we can't do TLS negotiation with , we do only "plain" + # connection and TLS with handshake right after TCP connecting ("ssl") + scheme = common.xmpp.transports_nb.urisplit(self._proxy['bosh_uri'])[0] + if scheme=='https': + self._connection_types = ['ssl'] + else: + self._connection_types = ['plain'] + + host = self._select_next_host(self._hosts) + self._current_host = host + self._hosts.remove(host) + self.connect_to_next_type() + + else: + if not retry and self.retrycount == 0: + log.debug("Out of hosts, giving up connecting to %s", self.name) + self.time_to_reconnect = None + if self.on_connect_failure: + self.on_connect_failure() + self.on_connect_failure = None + else: + # shown error dialog + self._connection_lost() + else: + # try reconnect if connection has failed before auth to server + self._disconnectedReconnCB() + + def connect_to_next_type(self, retry=False): + if len(self._connection_types): + self._current_type = self._connection_types.pop(0) + if self.last_connection: + self.last_connection.socket.disconnect() + self.last_connection = None + self.connection = None + + if self._current_type == 'ssl': + # SSL (force TLS on different port than plain) + # If we do TLS over BOSH, port of XMPP server should be the standard one + # and TLS should be negotiated because TLS on 5223 is deprecated + if self._proxy and self._proxy['type']=='bosh': + port = self._current_host['port'] + else: + port = self._current_host['ssl_port'] + elif self._current_type == 'tls': + # TLS - negotiate tls after XMPP stream is estabilished + port = self._current_host['port'] + elif self._current_type == 'plain': + # plain connection on defined port + port = self._current_host['port'] + + cacerts = os.path.join(common.gajim.DATA_DIR, 'other', 'cacerts.pem') + mycerts = common.gajim.MY_CACERTS + secure_tuple = (self._current_type, cacerts, mycerts) + + con = common.xmpp.NonBlockingClient( + domain=self._hostname, + caller=self, + idlequeue=gajim.idlequeue) + + self.last_connection = con + # increase default timeout for server responses + common.xmpp.dispatcher_nb.DEFAULT_TIMEOUT_SECONDS = self.try_connecting_for_foo_secs + # FIXME: this is a hack; need a better way + if self.on_connect_success == self._on_new_account: + con.RegisterDisconnectHandler(self._on_new_account) + + self.log_hosttype_info(port) + con.connect( + hostname=self._current_host['host'], + port=port, + on_connect=self.on_connect_success, + on_proxy_failure=self.on_proxy_failure, + on_connect_failure=self.connect_to_next_type, + proxy=self._proxy, + secure_tuple = secure_tuple) + else: + self._connect_to_next_host(retry) + + def log_hosttype_info(self, port): + msg = '>>>>>> Connecting to %s [%s:%d], type = %s' % (self.name, + self._current_host['host'], port, self._current_type) + log.info(msg) + if self._proxy: + msg = '>>>>>> ' + if self._proxy['type']=='bosh': + msg = '%s over BOSH %s' % (msg, self._proxy['bosh_uri']) + if self._proxy['type'] in ['http', 'socks5'] or self._proxy['bosh_useproxy']: + msg = '%s over proxy %s:%s' % (msg, self._proxy['host'], self._proxy['port']) + log.info(msg) + + def _connect_failure(self, con_type=None): + if not con_type: + # we are not retrying, and not conecting + if not self.retrycount and self.connected != 0: + self.disconnect(on_purpose = True) + self.dispatch('STATUS', 'offline') + pritxt = _('Could not connect to "%s"') % self._hostname + sectxt = _('Check your connection or try again later.') + if self.streamError: + # show error dialog + key = common.xmpp.NS_XMPP_STREAMS + ' ' + self.streamError + if key in common.xmpp.ERRORS: + sectxt2 = _('Server replied: %s') % common.xmpp.ERRORS[key][2] + self.dispatch('ERROR', (pritxt, '%s\n%s' % (sectxt2, sectxt))) + return + # show popup + self.dispatch('CONNECTION_LOST', (pritxt, sectxt)) + + def on_proxy_failure(self, reason): + log.error('Connection to proxy failed: %s' % reason) + self.time_to_reconnect = None + self.on_connect_failure = None + self.disconnect(on_purpose = True) + self.dispatch('STATUS', 'offline') + self.dispatch('CONNECTION_LOST', + (_('Connection to proxy failed'), reason)) + + def _connect_success(self, con, con_type): + if not self.connected: # We went offline during connecting process + # FIXME - not possible, maybe it was when we used threads + return + _con_type = con_type + if _con_type != self._current_type: + log.info('Connecting to next type beacuse desired is %s and returned is %s' + % (self._current_type, _con_type)) + self.connect_to_next_type() + return + con.RegisterDisconnectHandler(self._on_disconnected) + if _con_type == 'plain' and gajim.config.get_per('accounts', self.name, + 'warn_when_plaintext_connection'): + self.dispatch('PLAIN_CONNECTION', (con,)) + return True + if _con_type in ('tls', 'ssl') and con.Connection.ssl_lib != 'PYOPENSSL' \ + and gajim.config.get_per('accounts', self.name, + 'warn_when_insecure_ssl_connection') and \ + not self.connection_auto_accepted: + # Pyopenssl is not used + self.dispatch('INSECURE_SSL_CONNECTION', (con, _con_type)) + return True + return self.connection_accepted(con, con_type) + + def connection_accepted(self, con, con_type): + if not con or not con.Connection: + self.disconnect(on_purpose=True) + self.dispatch('STATUS', 'offline') + self.dispatch('CONNECTION_LOST', + (_('Could not connect to account %s') % self.name, + _('Connection with account %s has been lost. Retry connecting.') % \ + self.name)) + return + self.hosts = [] + self.connection_auto_accepted = False + self.connected_hostname = self._current_host['host'] + self.on_connect_failure = None + con.UnregisterDisconnectHandler(self._on_disconnected) + con.RegisterDisconnectHandler(self._disconnectedReconnCB) + log.debug('Connected to server %s:%s with %s' % ( + self._current_host['host'], self._current_host['port'], con_type)) + + self.last_connection_type = con_type + if gajim.config.get_per('accounts', self.name, 'anonymous_auth'): + name = None + else: + name = gajim.config.get_per('accounts', self.name, 'name') + hostname = gajim.config.get_per('accounts', self.name, 'hostname') + self.connection = con + try: + errnum = con.Connection.ssl_errnum + except AttributeError: + errnum = -1 # we don't have an errnum + if errnum > 0 and str(errnum) not in gajim.config.get_per('accounts', + self.name, 'ignore_ssl_errors'): + text = _('The authenticity of the %s certificate could be invalid.') %\ + hostname + if errnum in ssl_error: + text += _('\nSSL Error: %s') % ssl_error[errnum] + else: + text += _('\nUnknown SSL error: %d') % errnum + self.dispatch('SSL_ERROR', (text, errnum, con.Connection.ssl_cert_pem, + con.Connection.ssl_fingerprint_sha1)) + return True + if hasattr(con.Connection, 'ssl_fingerprint_sha1'): + saved_fingerprint = gajim.config.get_per('accounts', self.name, 'ssl_fingerprint_sha1') + if saved_fingerprint: + # Check sha1 fingerprint + if con.Connection.ssl_fingerprint_sha1 != saved_fingerprint: + self.dispatch('FINGERPRINT_ERROR', + (con.Connection.ssl_fingerprint_sha1,)) + return True + else: + gajim.config.set_per('accounts', self.name, 'ssl_fingerprint_sha1', + con.Connection.ssl_fingerprint_sha1) + self._register_handlers(con, con_type) + con.auth( + user=name, + password=self.password, + resource=self.server_resource, + sasl=1, + on_auth=self.__on_auth) + + def ssl_certificate_accepted(self): + if not self.connection: + self.disconnect(on_purpose=True) + self.dispatch('STATUS', 'offline') + self.dispatch('CONNECTION_LOST', + (_('Could not connect to account %s') % self.name, + _('Connection with account %s has been lost. Retry connecting.') % \ + self.name)) + return + name = gajim.config.get_per('accounts', self.name, 'name') + self._register_handlers(self.connection, 'ssl') + self.connection.auth(name, self.password, self.server_resource, 1, + self.__on_auth) + + def _register_handlers(self, con, con_type): + self.peerhost = con.get_peerhost() + # notify the gui about con_type + self.dispatch('CON_TYPE', con_type) + ConnectionHandlers._register_handlers(self, con, con_type) + + def __on_auth(self, con, auth): + if not con: + self.disconnect(on_purpose=True) + self.dispatch('STATUS', 'offline') + self.dispatch('CONNECTION_LOST', + (_('Could not connect to "%s"') % self._hostname, + _('Check your connection or try again later'))) + if self.on_connect_auth: + self.on_connect_auth(None) + self.on_connect_auth = None + return + if not self.connected: # We went offline during connecting process + if self.on_connect_auth: + self.on_connect_auth(None) + self.on_connect_auth = None + return + if hasattr(con, 'Resource'): + self.server_resource = con.Resource + if gajim.config.get_per('accounts', self.name, 'anonymous_auth'): + # Get jid given by server + old_jid = gajim.get_jid_from_account(self.name) + gajim.config.set_per('accounts', self.name, 'name', con.User) + new_jid = gajim.get_jid_from_account(self.name) + self.dispatch('NEW_JID', (old_jid, new_jid)) + if auth: + self.last_io = gajim.idlequeue.current_time() + self.connected = 2 + self.retrycount = 0 + if self.on_connect_auth: + self.on_connect_auth(con) + self.on_connect_auth = None + else: + # Forget password, it's wrong + self.password = None + gajim.log.debug("Couldn't authenticate to %s" % self._hostname) + self.disconnect(on_purpose = True) + self.dispatch('STATUS', 'offline') + self.dispatch('ERROR', (_('Authentication failed with "%s"') % \ + self._hostname, + _('Please check your login and password for correctness.'))) + if self.on_connect_auth: + self.on_connect_auth(None) + self.on_connect_auth = None + # END connect + + def add_lang(self, stanza): + if self.lang: + stanza.setAttr('xml:lang', self.lang) + + def get_privacy_lists(self): + if not self.connection: + return + common.xmpp.features_nb.getPrivacyLists(self.connection) + + def send_keepalive(self): + # nothing received for the last foo seconds + if self.connection: + self.connection.send(' ') + + def _on_xmpp_ping_answer(self, iq_obj): + id_ = unicode(iq_obj.getAttr('id')) + assert id_ == self.awaiting_xmpp_ping_id + self.awaiting_xmpp_ping_id = None + + def sendPing(self, pingTo=None): + """ + Send XMPP Ping (XEP-0199) request. If pingTo is not set, ping is sent to + server to detect connection failure at application level + """ + if not self.connection: + return + id_ = self.connection.getAnID() + if pingTo: + to = pingTo.get_full_jid() + self.dispatch('PING_SENT', (pingTo)) + else: + to = gajim.config.get_per('accounts', self.name, 'hostname') + self.awaiting_xmpp_ping_id = id_ + iq = common.xmpp.Iq('get', to=to) + iq.addChild(name = 'ping', namespace = common.xmpp.NS_PING) + iq.setID(id_) + def _on_response(resp): + timePong = time_time() + if not common.xmpp.isResultNode(resp): + self.dispatch('PING_ERROR', (pingTo)) + return + timeDiff = round(timePong - timePing, 2) + self.dispatch('PING_REPLY', (pingTo, timeDiff)) + if pingTo: + timePing = time_time() + self.connection.SendAndCallForResponse(iq, _on_response) + else: + self.connection.SendAndCallForResponse(iq, self._on_xmpp_ping_answer) + gajim.idlequeue.set_alarm(self.check_pingalive, gajim.config.get_per( + 'accounts', self.name, 'time_for_ping_alive_answer')) + + def get_active_default_lists(self): + if not self.connection: + return + common.xmpp.features_nb.getActiveAndDefaultPrivacyLists(self.connection) + + def del_privacy_list(self, privacy_list): + if not self.connection: + return + def _on_del_privacy_list_result(result): + if result: + self.dispatch('PRIVACY_LIST_REMOVED', privacy_list) + else: + self.dispatch('ERROR', (_('Error while removing privacy list'), + _('Privacy list %s has not been removed. It is maybe active in ' + 'one of your connected resources. Deactivate it and try ' + 'again.') % privacy_list)) + common.xmpp.features_nb.delPrivacyList(self.connection, privacy_list, + _on_del_privacy_list_result) + + def get_privacy_list(self, title): + if not self.connection: + return + common.xmpp.features_nb.getPrivacyList(self.connection, title) + + def set_privacy_list(self, listname, tags): + if not self.connection: + return + common.xmpp.features_nb.setPrivacyList(self.connection, listname, tags) + + def set_active_list(self, listname): + if not self.connection: + return + common.xmpp.features_nb.setActivePrivacyList(self.connection, listname, 'active') + + def set_default_list(self, listname): + if not self.connection: + return + common.xmpp.features_nb.setDefaultPrivacyList(self.connection, listname) + + def build_privacy_rule(self, name, action, order=1): + """ + Build a Privacy rule stanza for invisibility + """ + iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '') + l = iq.getTag('query').setTag('list', {'name': name}) + i = l.setTag('item', {'action': action, 'order': str(order)}) + i.setTag('presence-out') + return iq + + def build_invisible_rule(self): + iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '') + l = iq.getTag('query').setTag('list', {'name': 'invisible'}) + if self.name in gajim.interface.status_sent_to_groups and \ + len(gajim.interface.status_sent_to_groups[self.name]) > 0: + for group in gajim.interface.status_sent_to_groups[self.name]: + i = l.setTag('item', {'type': 'group', 'value': group, + 'action': 'allow', 'order': '1'}) + i.setTag('presence-out') + if self.name in gajim.interface.status_sent_to_users and \ + len(gajim.interface.status_sent_to_users[self.name]) > 0: + for jid in gajim.interface.status_sent_to_users[self.name]: + i = l.setTag('item', {'type': 'jid', 'value': jid, + 'action': 'allow', 'order': '2'}) + i.setTag('presence-out') + i = l.setTag('item', {'action': 'deny', 'order': '3'}) + i.setTag('presence-out') + return iq + + def set_invisible_rule(self): + if not gajim.account_is_connected(self.name): + return + iq = self.build_invisible_rule() + self.connection.send(iq) + + def activate_privacy_rule(self, name): + """ + Activate a privacy rule + """ + if not self.connection: + return + iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '') + iq.getTag('query').setTag('active', {'name': name}) + self.connection.send(iq) + + def send_invisible_presence(self, msg, signed, initial = False): + if not self.connection: + return + if not self.privacy_rules_supported: + self.dispatch('STATUS', gajim.SHOW_LIST[self.connected]) + self.dispatch('ERROR', (_('Invisibility not supported'), + _('Account %s doesn\'t support invisibility.') % self.name)) + return + # If we are already connected, and privacy rules are supported, send + # offline presence first as it's required by XEP-0126 + if self.connected > 1 and self.privacy_rules_supported: + self.on_purpose = True + p = common.xmpp.Presence(typ = 'unavailable') + p = self.add_sha(p, False) + if msg: + p.setStatus(msg) + self.remove_all_transfers() + self.connection.send(p) + + # try to set the privacy rule + iq = self.build_invisible_rule() + self.connection.SendAndCallForResponse(iq, self._continue_invisible, + {'msg': msg, 'signed': signed, 'initial': initial}) + + def _continue_invisible(self, con, iq_obj, msg, signed, initial): + if iq_obj.getType() == 'error': # server doesn't support privacy lists + return + # active the privacy rule + self.privacy_rules_supported = True + self.activate_privacy_rule('invisible') + self.connected = gajim.SHOW_LIST.index('invisible') + self.status = msg + priority = unicode(gajim.get_priority(self.name, 'invisible')) + p = common.xmpp.Presence(priority = priority) + p = self.add_sha(p, True) + if msg: + p.setStatus(msg) + if signed: + p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed) + self.connection.send(p) + self.priority = priority + self.dispatch('STATUS', 'invisible') + if initial: + # ask our VCard + self.request_vcard(None) + + # Get bookmarks from private namespace + self.get_bookmarks() + + # Get annotations + self.get_annotations() + + # Inform GUI we just signed in + self.dispatch('SIGNED_IN', ()) + + def get_signed_presence(self, msg, callback = None): + if gajim.config.get_per('accounts', self.name, 'gpg_sign_presence'): + return self.get_signed_msg(msg, callback) + return '' + + def connect_and_auth(self): + self.on_connect_success = self._connect_success + self.on_connect_failure = self._connect_failure + self.connect() + + def connect_and_init(self, show, msg, sign_msg): + self.continue_connect_info = [show, msg, sign_msg] + self.on_connect_auth = self._discover_server_at_connection + self.connect_and_auth() + + def _discover_server_at_connection(self, con): + self.connection = con + if not self.connection: + return + self.connection.set_send_timeout(self.keepalives, self.send_keepalive) + self.connection.set_send_timeout2(self.pingalives, self.sendPing) + self.connection.onreceive(None) + self.discoverInfo(gajim.config.get_per('accounts', self.name, 'hostname'), + id_prefix='Gajim_') + self.privacy_rules_requested = False + # Discover Stun server(s) + gajim.resolver.resolve('_stun._udp.' + helpers.idn_to_ascii( + self.connected_hostname), self._on_stun_resolved) + + def _on_stun_resolved(self, host, result_array): + if len(result_array) != 0: + self._stun_servers = self._hosts = [i for i in result_array] + + def _request_privacy(self): + iq = common.xmpp.Iq('get', common.xmpp.NS_PRIVACY, xmlns = '') + id_ = self.connection.getAnID() + iq.setID(id_) + self.awaiting_answers[id_] = (PRIVACY_ARRIVED, ) + self.connection.send(iq) + + def send_custom_status(self, show, msg, jid): + if not show in gajim.SHOW_LIST: + return -1 + if not self.connection: + return + sshow = helpers.get_xmpp_show(show) + if not msg: + msg = '' + if show == 'offline': + p = common.xmpp.Presence(typ = 'unavailable', to = jid) + p = self.add_sha(p, False) + if msg: + p.setStatus(msg) + else: + signed = self.get_signed_presence(msg) + priority = unicode(gajim.get_priority(self.name, sshow)) + p = common.xmpp.Presence(typ = None, priority = priority, show = sshow, + to = jid) + p = self.add_sha(p) + if msg: + p.setStatus(msg) + if signed: + p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed) + self.connection.send(p) + + def _change_to_invisible(self, msg): + signed = self.get_signed_presence(msg) + self.send_invisible_presence(msg, signed) + + def _change_from_invisible(self): + if self.privacy_rules_supported: + iq = self.build_privacy_rule('visible', 'allow') + self.connection.send(iq) + self.activate_privacy_rule('visible') + + def _update_status(self, show, msg): + xmpp_show = helpers.get_xmpp_show(show) + priority = unicode(gajim.get_priority(self.name, xmpp_show)) + p = common.xmpp.Presence(typ=None, priority=priority, show=xmpp_show) + p = self.add_sha(p) + if msg: + p.setStatus(msg) + signed = self.get_signed_presence(msg) + if signed: + p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed) + if self.connection: + self.connection.send(p) + self.priority = priority + self.dispatch('STATUS', show) + + def send_motd(self, jid, subject = '', msg = '', xhtml = None): + if not self.connection: + return + msg_iq = common.xmpp.Message(to = jid, body = msg, subject = subject, + xhtml = xhtml) + + self.connection.send(msg_iq) + + def send_message(self, jid, msg, keyID, type_='chat', subject='', + chatstate=None, msg_id=None, composing_xep=None, resource=None, + user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None, + original_message=None, delayed=None, callback=None, callback_args=[]): + + def cb(jid, msg, keyID, forward_from, session, original_message, subject, + type_, msg_iq): + msg_id = self.connection.send(msg_iq) + jid = helpers.parse_jid(jid) + self.dispatch('MSGSENT', (jid, msg, keyID)) + if callback: + callback(msg_id, *callback_args) + + self.log_message(jid, msg, forward_from, session, original_message, + subject, type_) + + self._prepare_message(jid, msg, keyID, type_=type_, subject=subject, + chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep, + resource=resource, user_nick=user_nick, xhtml=xhtml, session=session, + forward_from=forward_from, form_node=form_node, + original_message=original_message, delayed=delayed, callback=cb) + + def send_contacts(self, contacts, jid): + """ + Send contacts with RosterX (Xep-0144) + """ + if not self.connection: + return + if len(contacts) == 1: + msg = _('Sent contact: "%s" (%s)') % (contacts[0].get_full_jid(), + contacts[0].get_shown_name()) + else: + msg = _('Sent contacts:') + for contact in contacts: + msg += '\n "%s" (%s)' % (contact.get_full_jid(), + contact.get_shown_name()) + msg_iq = common.xmpp.Message(to=jid, body=msg) + x = msg_iq.addChild(name='x', namespace=common.xmpp.NS_ROSTERX) + for contact in contacts: + x.addChild(name='item', attrs={'action': 'add', 'jid': contact.jid, + 'name': contact.get_shown_name()}) + self.connection.send(msg_iq) + + def send_stanza(self, stanza): + """ + Send a stanza untouched + """ + if not self.connection: + return + self.connection.send(stanza) + + def ack_subscribed(self, jid): + if not self.connection: + return + log.debug('ack\'ing subscription complete for %s' % jid) + p = common.xmpp.Presence(jid, 'subscribe') + self.connection.send(p) + + def ack_unsubscribed(self, jid): + if not self.connection: + return + log.debug('ack\'ing unsubscription complete for %s' % jid) + p = common.xmpp.Presence(jid, 'unsubscribe') + self.connection.send(p) + + def request_subscription(self, jid, msg='', name='', groups=[], + auto_auth=False, user_nick=''): + if not self.connection: + return + log.debug('subscription request for %s' % jid) + if auto_auth: + self.jids_for_auto_auth.append(jid) + # RFC 3921 section 8.2 + infos = {'jid': jid} + if name: + infos['name'] = name + iq = common.xmpp.Iq('set', common.xmpp.NS_ROSTER) + q = iq.getTag('query') + item = q.addChild('item', attrs=infos) + for g in groups: + item.addChild('group').setData(g) + self.connection.send(iq) + + p = common.xmpp.Presence(jid, 'subscribe') + if user_nick: + p.setTag('nick', namespace = common.xmpp.NS_NICK).setData(user_nick) + p = self.add_sha(p) + if msg: + p.setStatus(msg) + self.connection.send(p) + + def send_authorization(self, jid): + if not self.connection: + return + p = common.xmpp.Presence(jid, 'subscribed') + p = self.add_sha(p) + self.connection.send(p) + + def refuse_authorization(self, jid): + if not self.connection: + return + p = common.xmpp.Presence(jid, 'unsubscribed') + p = self.add_sha(p) + self.connection.send(p) + + def unsubscribe(self, jid, remove_auth = True): + if not self.connection: + return + if remove_auth: + self.connection.getRoster().delItem(jid) + jid_list = gajim.config.get_per('contacts') + for j in jid_list: + if j.startswith(jid): + gajim.config.del_per('contacts', j) + else: + self.connection.getRoster().Unsubscribe(jid) + self.update_contact(jid, '', []) + + def unsubscribe_agent(self, agent): + if not self.connection: + return + iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to = agent) + iq.getTag('query').setTag('remove') + id_ = self.connection.getAnID() + iq.setID(id_) + self.awaiting_answers[id_] = (AGENT_REMOVED, agent) + self.connection.send(iq) + self.connection.getRoster().delItem(agent) + + def send_new_account_infos(self, form, is_form): + if is_form: + # Get username and password and put them in new_account_info + for field in form.iter_fields(): + if field.var == 'username': + self.new_account_info['name'] = field.value + if field.var == 'password': + self.new_account_info['password'] = field.value + else: + # Get username and password and put them in new_account_info + if 'username' in form: + self.new_account_info['name'] = form['username'] + if 'password' in form: + self.new_account_info['password'] = form['password'] + self.new_account_form = form + self.new_account(self.name, self.new_account_info) + + def new_account(self, name, config, sync=False): + # If a connection already exist we cannot create a new account + if self.connection: + return + self._hostname = config['hostname'] + self.new_account_info = config + self.name = name + self.on_connect_success = self._on_new_account + self.on_connect_failure = self._on_new_account + self.connect(config) + + def _on_new_account(self, con=None, con_type=None): + if not con_type: + if len(self._connection_types) or len(self._hosts): + # There are still other way to try to connect + return + self.dispatch('NEW_ACC_NOT_CONNECTED', + (_('Could not connect to "%s"') % self._hostname)) + return + self.on_connect_failure = None + self.connection = con + common.xmpp.features_nb.getRegInfo(con, self._hostname) + + def request_last_status_time(self, jid, resource, groupchat_jid=None): + """ + groupchat_jid is used when we want to send a request to a real jid and + act as if the answer comes from the groupchat_jid + """ + if not self.connection: + return + to_whom_jid = jid + if resource: + to_whom_jid += '/' + resource + iq = common.xmpp.Iq(to = to_whom_jid, typ = 'get', queryNS =\ + common.xmpp.NS_LAST) + id_ = self.connection.getAnID() + iq.setID(id_) + if groupchat_jid: + self.groupchat_jids[id_] = groupchat_jid + self.last_ids.append(id_) + self.connection.send(iq) + + def request_os_info(self, jid, resource, groupchat_jid=None): + """ + groupchat_jid is used when we want to send a request to a real jid and + act as if the answer comes from the groupchat_jid + """ + if not self.connection: + return + # If we are invisible, do not request + if self.connected == gajim.SHOW_LIST.index('invisible'): + self.dispatch('OS_INFO', (jid, resource, _('Not fetched because of invisible status'), _('Not fetched because of invisible status'))) + return + to_whom_jid = jid + if resource: + to_whom_jid += '/' + resource + iq = common.xmpp.Iq(to=to_whom_jid, typ='get', queryNS=\ + common.xmpp.NS_VERSION) + id_ = self.connection.getAnID() + iq.setID(id_) + if groupchat_jid: + self.groupchat_jids[id_] = groupchat_jid + self.version_ids.append(id_) + self.connection.send(iq) + + def request_entity_time(self, jid, resource, groupchat_jid=None): + """ + groupchat_jid is used when we want to send a request to a real jid and + act as if the answer comes from the groupchat_jid + """ + if not self.connection: + return + # If we are invisible, do not request + if self.connected == gajim.SHOW_LIST.index('invisible'): + self.dispatch('ENTITY_TIME', (jid, resource, _('Not fetched because of invisible status'))) + return + to_whom_jid = jid + if resource: + to_whom_jid += '/' + resource + iq = common.xmpp.Iq(to=to_whom_jid, typ='get') + iq.addChild('time', namespace=common.xmpp.NS_TIME_REVISED) + id_ = self.connection.getAnID() + iq.setID(id_) + if groupchat_jid: + self.groupchat_jids[id_] = groupchat_jid + self.entity_time_ids.append(id_) + self.connection.send(iq) + + def get_settings(self): + """ + Get Gajim settings as described in XEP 0049 + """ + if not self.connection: + return + iq = common.xmpp.Iq(typ='get') + iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) + iq2.addChild(name='gajim', namespace='gajim:prefs') + self.connection.send(iq) + + def _request_bookmarks_xml(self): + if not self.connection: + return + iq = common.xmpp.Iq(typ='get') + iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) + iq2.addChild(name='storage', namespace='storage:bookmarks') + self.connection.send(iq) + + def _check_bookmarks_received(self): + if not self.bookmarks: + self._request_bookmarks_xml() + + def get_bookmarks(self, storage_type=None): + """ + Get Bookmarks from storage or PubSub if supported as described in XEP + 0048 + + storage_type can be set to xml to force request to xml storage + """ + if not self.connection: + return + if self.pubsub_supported and storage_type != 'xml': + self.send_pb_retrieve('', 'storage:bookmarks') + # some server (ejabberd) are so slow to answer that we request via XML + # if we don't get answer in the next 30 seconds + gajim.idlequeue.set_alarm(self._check_bookmarks_received, 30) + else: + self._request_bookmarks_xml() + + def store_bookmarks(self, storage_type=None): + """ + Send bookmarks to the storage namespace or PubSub if supported + + storage_type can be set to 'pubsub' or 'xml' so store in only one method + else it will be stored on both + """ + if not self.connection: + return + iq = common.xmpp.Node(tag='storage', attrs={'xmlns': 'storage:bookmarks'}) + for bm in self.bookmarks: + iq2 = iq.addChild(name = "conference") + iq2.setAttr('jid', bm['jid']) + iq2.setAttr('autojoin', bm['autojoin']) + iq2.setAttr('minimize', bm['minimize']) + iq2.setAttr('name', bm['name']) + # Only add optional elements if not empty + # Note: need to handle both None and '' as empty + # thus shouldn't use "is not None" + if bm.get('nick', None): + iq2.setTagData('nick', bm['nick']) + if bm.get('password', None): + iq2.setTagData('password', bm['password']) + if bm.get('print_status', None): + iq2.setTagData('print_status', bm['print_status']) + + if self.pubsub_supported and storage_type != 'xml': + if self.pubsub_publish_options_supported: + options = common.xmpp.Node(common.xmpp.NS_DATA + ' x', + attrs={'type': 'submit'}) + f = options.addChild('field', attrs={'var': 'FORM_TYPE', + 'type': 'hidden'}) + f.setTagData('value', common.xmpp.NS_PUBSUB_PUBLISH_OPTIONS) + f = options.addChild('field', attrs={'var': 'pubsub#persist_items'}) + f.setTagData('value', 'true') + f = options.addChild('field', attrs={'var': 'pubsub#access_model'}) + f.setTagData('value', 'whitelist') + else: + options = None + self.send_pb_publish('', 'storage:bookmarks', iq, 'current', + options=options) + if storage_type != 'pubsub': + iqA = common.xmpp.Iq(typ='set') + iqB = iqA.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) + iqB.addChild(node=iq) + self.connection.send(iqA) + + def get_annotations(self): + """ + Get Annonations from storage as described in XEP 0048, and XEP 0145 + """ + self.annotations = {} + if not self.connection: + return + iq = common.xmpp.Iq(typ='get') + iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) + iq2.addChild(name='storage', namespace='storage:rosternotes') + self.connection.send(iq) + + def store_annotations(self): + """ + Set Annonations in private storage as described in XEP 0048, and XEP 0145 + """ + if not self.connection: + return + iq = common.xmpp.Iq(typ='set') + iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) + iq3 = iq2.addChild(name='storage', namespace='storage:rosternotes') + for jid in self.annotations.keys(): + if self.annotations[jid]: + iq4 = iq3.addChild(name = "note") + iq4.setAttr('jid', jid) + iq4.setData(self.annotations[jid]) + self.connection.send(iq) + + + def get_metacontacts(self): + """ + Get metacontacts list from storage as described in XEP 0049 + """ + if not self.connection: + return + iq = common.xmpp.Iq(typ='get') + iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) + iq2.addChild(name='storage', namespace='storage:metacontacts') + id_ = self.connection.getAnID() + iq.setID(id_) + self.awaiting_answers[id_] = (METACONTACTS_ARRIVED, ) + self.connection.send(iq) + + def store_metacontacts(self, tags_list): + """ + Send meta contacts to the storage namespace + """ + if not self.connection: + return + iq = common.xmpp.Iq(typ='set') + iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) + iq3 = iq2.addChild(name='storage', namespace='storage:metacontacts') + for tag in tags_list: + for data in tags_list[tag]: + jid = data['jid'] + dict_ = {'jid': jid, 'tag': tag} + if 'order' in data: + dict_['order'] = data['order'] + iq3.addChild(name = 'meta', attrs = dict_) + self.connection.send(iq) + + def send_agent_status(self, agent, ptype): + if not self.connection: + return + show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected]) + p = common.xmpp.Presence(to = agent, typ = ptype, show = show) + p = self.add_sha(p, ptype != 'unavailable') + self.connection.send(p) + + def check_unique_room_id_support(self, server, instance): + if not self.connection: + return + iq = common.xmpp.Iq(typ = 'get', to = server) + iq.setAttr('id', 'unique1') + iq.addChild('unique', namespace=common.xmpp.NS_MUC_UNIQUE) + def _on_response(resp): + if not common.xmpp.isResultNode(resp): + self.dispatch('UNIQUE_ROOM_ID_UNSUPPORTED', (server, instance)) + return + self.dispatch('UNIQUE_ROOM_ID_SUPPORTED', (server, instance, + resp.getTag('unique').getData())) + self.connection.SendAndCallForResponse(iq, _on_response) + + def join_gc(self, nick, room_jid, password, change_nick=False): + # FIXME: This room JID needs to be normalized; see #1364 + if not self.connection: + return + show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected]) + if show == 'invisible': + # Never join a room when invisible + return + + # last date/time in history to avoid duplicate + if room_jid not in self.last_history_time: + # Not in memory, get it from DB + last_log = None + # Do not check if we are not logging for this room + if gajim.config.should_log(self.name, room_jid): + # Check time first in the FAST table + last_log = gajim.logger.get_room_last_message_time(room_jid) + if last_log is None: + # Not in special table, get it from messages DB + last_log = gajim.logger.get_last_date_that_has_logs(room_jid, + is_room=True) + # Create self.last_history_time[room_jid] even if not logging, + # could be used in connection_handlers + if last_log is None: + last_log = 0 + self.last_history_time[room_jid] = last_log + + p = common.xmpp.Presence(to='%s/%s' % (room_jid, nick), + show=show, status=self.status) + h = hmac.new(self.secret_hmac, room_jid).hexdigest()[:6] + id_ = self.connection.getAnID() + id_ = 'gajim_muc_' + id_ + '_' + h + p.setID(id_) + if gajim.config.get('send_sha_in_gc_presence'): + p = self.add_sha(p) + self.add_lang(p) + if not change_nick: + t = p.setTag(common.xmpp.NS_MUC + ' x') + last_date = self.last_history_time[room_jid] + if last_date == 0: + last_date = time.time() - gajim.config.get( + 'muc_restore_timeout') * 60 + else: + last_date = min(last_date, time.time() - gajim.config.get( + 'muc_restore_timeout') * 60) + last_date = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(last_date)) + t.setTag('history', {'maxstanzas': gajim.config.get( + 'muc_restore_lines'), 'since': last_date}) + if password: + t.setTagData('password', password) + self.connection.send(p) + + def send_gc_message(self, jid, msg, xhtml = None): + if not self.connection: + return + if not xhtml and gajim.config.get('rst_formatting_outgoing_messages'): + from common.rst_xhtml_generator import create_xhtml + xhtml = create_xhtml(msg) + msg_iq = common.xmpp.Message(jid, msg, typ = 'groupchat', xhtml = xhtml) + self.connection.send(msg_iq) + self.dispatch('MSGSENT', (jid, msg)) + + def send_gc_subject(self, jid, subject): + if not self.connection: + return + msg_iq = common.xmpp.Message(jid, typ = 'groupchat', subject = subject) + self.connection.send(msg_iq) + + def request_gc_config(self, room_jid): + if not self.connection: + return + iq = common.xmpp.Iq(typ = 'get', queryNS = common.xmpp.NS_MUC_OWNER, + to = room_jid) + self.add_lang(iq) + self.connection.send(iq) + + def destroy_gc_room(self, room_jid, reason = '', jid = ''): + if not self.connection: + return + iq = common.xmpp.Iq(typ = 'set', queryNS = common.xmpp.NS_MUC_OWNER, + to = room_jid) + destroy = iq.getTag('query').setTag('destroy') + if reason: + destroy.setTagData('reason', reason) + if jid: + destroy.setAttr('jid', jid) + self.connection.send(iq) + + def send_gc_status(self, nick, jid, show, status): + if not gajim.account_is_connected(self.name): + return + if show == 'invisible': + show = 'offline' + ptype = None + if show == 'offline': + ptype = 'unavailable' + xmpp_show = helpers.get_xmpp_show(show) + p = common.xmpp.Presence(to = '%s/%s' % (jid, nick), typ = ptype, + show = xmpp_show, status = status) + h = hmac.new(self.secret_hmac, jid).hexdigest()[:6] + id_ = self.connection.getAnID() + id_ = 'gajim_muc_' + id_ + '_' + h + p.setID(id_) + if gajim.config.get('send_sha_in_gc_presence') and show != 'offline': + p = self.add_sha(p, ptype != 'unavailable') + self.add_lang(p) + # send instantly so when we go offline, status is sent to gc before we + # disconnect from jabber server + self.connection.send(p) + + def gc_got_disconnected(self, room_jid): + """ + A groupchat got disconnected. This can be or purpose or not + + Save the time we had last message to avoid duplicate logs AND be faster + than get that date from DB. Save time that we have in mem in a small + table (with fast access) + """ + gajim.logger.set_room_last_message_time(room_jid, self.last_history_time[room_jid]) + + def gc_set_role(self, room_jid, nick, role, reason = ''): + """ + Role is for all the life of the room so it's based on nick + """ + if not self.connection: + return + iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ + common.xmpp.NS_MUC_ADMIN) + item = iq.getTag('query').setTag('item') + item.setAttr('nick', nick) + item.setAttr('role', role) + if reason: + item.addChild(name = 'reason', payload = reason) + self.connection.send(iq) + + def gc_set_affiliation(self, room_jid, jid, affiliation, reason = ''): + """ + Affiliation is for all the life of the room so it's based on jid + """ + if not self.connection: + return + iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ + common.xmpp.NS_MUC_ADMIN) + item = iq.getTag('query').setTag('item') + item.setAttr('jid', jid) + item.setAttr('affiliation', affiliation) + if reason: + item.addChild(name = 'reason', payload = reason) + self.connection.send(iq) + + def send_gc_affiliation_list(self, room_jid, users_dict): + if not self.connection: + return + iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS = \ + common.xmpp.NS_MUC_ADMIN) + item = iq.getTag('query') + for jid in users_dict: + item_tag = item.addChild('item', {'jid': jid, + 'affiliation': users_dict[jid]['affiliation']}) + if 'reason' in users_dict[jid] and users_dict[jid]['reason']: + item_tag.setTagData('reason', users_dict[jid]['reason']) + self.connection.send(iq) + + def get_affiliation_list(self, room_jid, affiliation): + if not self.connection: + return + iq = common.xmpp.Iq(typ = 'get', to = room_jid, queryNS = \ + common.xmpp.NS_MUC_ADMIN) + item = iq.getTag('query').setTag('item') + item.setAttr('affiliation', affiliation) + self.connection.send(iq) + + def send_gc_config(self, room_jid, form): + if not self.connection: + return + iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ + common.xmpp.NS_MUC_OWNER) + query = iq.getTag('query') + form.setAttr('type', 'submit') + query.addChild(node = form) + self.connection.send(iq) + + def change_password(self, password): + if not self.connection: + return + hostname = gajim.config.get_per('accounts', self.name, 'hostname') + username = gajim.config.get_per('accounts', self.name, 'name') + iq = common.xmpp.Iq(typ = 'set', to = hostname) + q = iq.setTag(common.xmpp.NS_REGISTER + ' query') + q.setTagData('username', username) + q.setTagData('password', password) + self.connection.send(iq) + + def get_password(self, callback): + if self.password: + callback(self.password) + return + self.pasword_callback = callback + self.dispatch('PASSWORD_REQUIRED', None) + + def set_password(self, password): + self.password = password + if self.pasword_callback: + self.pasword_callback(password) + self.pasword_callback = None + + def unregister_account(self, on_remove_success): + # no need to write this as a class method and keep the value of + # on_remove_success as a class property as pass it as an argument + def _on_unregister_account_connect(con): + self.on_connect_auth = None + if gajim.account_is_connected(self.name): + hostname = gajim.config.get_per('accounts', self.name, 'hostname') + iq = common.xmpp.Iq(typ = 'set', to = hostname) + iq.setTag(common.xmpp.NS_REGISTER + ' query').setTag('remove') + def _on_answer(result): + if result.getType() == 'result': + on_remove_success(True) + return + self.dispatch('ERROR', (_('Unregister failed'), + _('Unregistration with server %(server)s failed: %(error)s') \ + % {'server': hostname, 'error': result.getErrorMsg()})) + on_remove_success(False) + con.SendAndCallForResponse(iq, _on_answer) + return + on_remove_success(False) + if self.connected == 0: + self.on_connect_auth = _on_unregister_account_connect + self.connect_and_auth() + else: + _on_unregister_account_connect(self.connection) + + def send_invite(self, room, to, reason='', continue_tag=False): + """ + Send invitation + """ + message=common.xmpp.Message(to = room) + c = message.addChild(name = 'x', namespace = common.xmpp.NS_MUC_USER) + c = c.addChild(name = 'invite', attrs={'to' : to}) + if continue_tag: + c.addChild(name = 'continue') + if reason != '': + c.setTagData('reason', reason) + self.connection.send(message) + + def check_pingalive(self): + if self.awaiting_xmpp_ping_id: + # We haven't got the pong in time, disco and reconnect + log.warn("No reply received for keepalive ping. Reconnecting.") + self._disconnectedReconnCB() + + def _reconnect_alarm(self): + if self.time_to_reconnect: + if self.connected < 2: + self._reconnect() + else: + self.time_to_reconnect = None + + def request_search_fields(self, jid): + iq = common.xmpp.Iq(typ = 'get', to = jid, queryNS = \ + common.xmpp.NS_SEARCH) + self.connection.send(iq) + + def send_search_form(self, jid, form, is_form): + iq = common.xmpp.Iq(typ = 'set', to = jid, queryNS = \ + common.xmpp.NS_SEARCH) + item = iq.getTag('query') + if is_form: + item.addChild(node = form) + else: + for i in form.keys(): + item.setTagData(i, form[i]) + def _on_response(resp): + jid = jid = helpers.get_jid_from_iq(resp) + tag = resp.getTag('query', namespace = common.xmpp.NS_SEARCH) + if not tag: + self.dispatch('SEARCH_RESULT', (jid, None, False)) + return + df = tag.getTag('x', namespace = common.xmpp.NS_DATA) + if df: + self.dispatch('SEARCH_RESULT', (jid, df, True)) + return + df = [] + for item in tag.getTags('item'): + # We also show attributes. jid is there + f = item.attrs + for i in item.getPayload(): + f[i.getName()] = i.getData() + df.append(f) + self.dispatch('SEARCH_RESULT', (jid, df, False)) + + self.connection.SendAndCallForResponse(iq, _on_response) + + def load_roster_from_db(self): + roster = gajim.logger.get_roster(gajim.get_jid_from_account(self.name)) + self.dispatch('ROSTER', roster) # END Connection - -# vim: se ts=3: diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index 4ccb4e618..b0ba56568 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -36,7 +36,7 @@ import hashlib import hmac from time import (altzone, daylight, gmtime, localtime, mktime, strftime, - time as time_time, timezone, tzname) + time as time_time, timezone, tzname) from calendar import timegm import datetime @@ -53,18 +53,18 @@ from common.protocol.bytestream import ConnectionBytestream import common.caps_cache as capscache from common.nec import NetworkEvent if gajim.HAVE_FARSIGHT: - from common.jingle import ConnectionJingle + from common.jingle import ConnectionJingle else: - class ConnectionJingle(): - def __init__(self): - pass - def _JingleCB(self, con, stanza): - pass + class ConnectionJingle(): + def __init__(self): + pass + def _JingleCB(self, con, stanza): + pass from common import dbus_support if dbus_support.supported: - import dbus - from music_track_listener import MusicTrackListener + import dbus + from music_track_listener import MusicTrackListener import logging log = logging.getLogger('gajim.c.connection_handlers') @@ -79,2238 +79,2236 @@ PRIVACY_ARRIVED = 'privacy_arrived' PEP_CONFIG = 'pep_config' HAS_IDLE = True try: -# import idle - import common.sleepy +# import idle + import common.sleepy except Exception: - log.debug(_('Unable to load idle module')) - HAS_IDLE = False + log.debug(_('Unable to load idle module')) + HAS_IDLE = False class ConnectionDisco: - """ - Holds xmpppy handlers and public methods for discover services - """ + """ + Holds xmpppy handlers and public methods for discover services + """ - def discoverItems(self, jid, node = None, id_prefix = None): - """ - According to XEP-0030: - jid is mandatory; - name, node, action is optional. - """ - self._discover(common.xmpp.NS_DISCO_ITEMS, jid, node, id_prefix) + def discoverItems(self, jid, node = None, id_prefix = None): + """ + According to XEP-0030: + jid is mandatory; + name, node, action is optional. + """ + self._discover(common.xmpp.NS_DISCO_ITEMS, jid, node, id_prefix) - def discoverInfo(self, jid, node = None, id_prefix = None): - """ - According to XEP-0030: - For identity: category, type is mandatory, name is optional. - For feature: var is mandatory. - """ - self._discover(common.xmpp.NS_DISCO_INFO, jid, node, id_prefix) + def discoverInfo(self, jid, node = None, id_prefix = None): + """ + According to XEP-0030: + For identity: category, type is mandatory, name is optional. + For feature: var is mandatory. + """ + self._discover(common.xmpp.NS_DISCO_INFO, jid, node, id_prefix) - def request_register_agent_info(self, agent): - if not self.connection or self.connected < 2: - return None - iq = common.xmpp.Iq('get', common.xmpp.NS_REGISTER, to=agent) - id_ = self.connection.getAnID() - iq.setID(id_) - # Wait the answer during 30 secondes - self.awaiting_timeouts[gajim.idlequeue.current_time() + 30] = (id_, - _('Registration information for transport %s has not arrived in time')\ - % agent) - self.connection.SendAndCallForResponse(iq, self._ReceivedRegInfo, - {'agent': agent}) + def request_register_agent_info(self, agent): + if not self.connection or self.connected < 2: + return None + iq = common.xmpp.Iq('get', common.xmpp.NS_REGISTER, to=agent) + id_ = self.connection.getAnID() + iq.setID(id_) + # Wait the answer during 30 secondes + self.awaiting_timeouts[gajim.idlequeue.current_time() + 30] = (id_, + _('Registration information for transport %s has not arrived in time')\ + % agent) + self.connection.SendAndCallForResponse(iq, self._ReceivedRegInfo, + {'agent': agent}) - def _agent_registered_cb(self, con, resp, agent): - if resp.getType() == 'result': - self.dispatch('INFORMATION', (_('Registration succeeded'), - _('Registration with agent %s succeeded') % agent)) - self.request_subscription(agent, auto_auth=True) - self.agent_registrations[agent]['roster_push'] = True - if self.agent_registrations[agent]['sub_received']: - p = common.xmpp.Presence(agent, 'subscribed') - p = self.add_sha(p) - self.connection.send(p) - if resp.getType() == 'error': - self.dispatch('ERROR', (_('Registration failed'), _('Registration with' - ' agent %(agent)s failed with error %(error)s: %(error_msg)s') % { - 'agent': agent, 'error': resp.getError(), - 'error_msg': resp.getErrorMsg()})) + def _agent_registered_cb(self, con, resp, agent): + if resp.getType() == 'result': + self.dispatch('INFORMATION', (_('Registration succeeded'), + _('Registration with agent %s succeeded') % agent)) + self.request_subscription(agent, auto_auth=True) + self.agent_registrations[agent]['roster_push'] = True + if self.agent_registrations[agent]['sub_received']: + p = common.xmpp.Presence(agent, 'subscribed') + p = self.add_sha(p) + self.connection.send(p) + if resp.getType() == 'error': + self.dispatch('ERROR', (_('Registration failed'), _('Registration with' + ' agent %(agent)s failed with error %(error)s: %(error_msg)s') % { + 'agent': agent, 'error': resp.getError(), + 'error_msg': resp.getErrorMsg()})) - def register_agent(self, agent, info, is_form = False): - if not self.connection or self.connected < 2: - return - if is_form: - iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to=agent) - query = iq.getTag('query') - info.setAttr('type', 'submit') - query.addChild(node=info) - self.connection.SendAndCallForResponse(iq, self._agent_registered_cb, - {'agent': agent}) - else: - # fixed: blocking - common.xmpp.features_nb.register(self.connection, agent, info, - self._agent_registered_cb, {'agent': agent}) - self.agent_registrations[agent] = {'roster_push': False, - 'sub_received': False} + def register_agent(self, agent, info, is_form = False): + if not self.connection or self.connected < 2: + return + if is_form: + iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to=agent) + query = iq.getTag('query') + info.setAttr('type', 'submit') + query.addChild(node=info) + self.connection.SendAndCallForResponse(iq, self._agent_registered_cb, + {'agent': agent}) + else: + # fixed: blocking + common.xmpp.features_nb.register(self.connection, agent, info, + self._agent_registered_cb, {'agent': agent}) + self.agent_registrations[agent] = {'roster_push': False, + 'sub_received': False} - def _discover(self, ns, jid, node=None, id_prefix=None): - if not self.connection or self.connected < 2: - return - iq = common.xmpp.Iq(typ='get', to=jid, queryNS=ns) - if id_prefix: - id_ = self.connection.getAnID() - iq.setID('%s%s' % (id_prefix, id_)) - if node: - iq.setQuerynode(node) - self.connection.send(iq) + def _discover(self, ns, jid, node=None, id_prefix=None): + if not self.connection or self.connected < 2: + return + iq = common.xmpp.Iq(typ='get', to=jid, queryNS=ns) + if id_prefix: + id_ = self.connection.getAnID() + iq.setID('%s%s' % (id_prefix, id_)) + if node: + iq.setQuerynode(node) + self.connection.send(iq) - def _ReceivedRegInfo(self, con, resp, agent): - common.xmpp.features_nb._ReceivedRegInfo(con, resp, agent) - self._IqCB(con, resp) + def _ReceivedRegInfo(self, con, resp, agent): + common.xmpp.features_nb._ReceivedRegInfo(con, resp, agent) + self._IqCB(con, resp) - def _discoGetCB(self, con, iq_obj): - """ - Get disco info - """ - if not self.connection or self.connected < 2: - return - frm = helpers.get_full_jid_from_iq(iq_obj) - to = unicode(iq_obj.getAttr('to')) - id_ = unicode(iq_obj.getAttr('id')) - iq = common.xmpp.Iq(to=frm, typ='result', queryNS=common.xmpp.NS_DISCO, - frm=to) - iq.setAttr('id', id_) - query = iq.setTag('query') - query.setAttr('node','http://gajim.org#' + gajim.version.split('-', 1)[0]) - for f in (common.xmpp.NS_BYTESTREAM, common.xmpp.NS_SI, - common.xmpp.NS_FILE, common.xmpp.NS_COMMANDS): - feature = common.xmpp.Node('feature') - feature.setAttr('var', f) - query.addChild(node=feature) + def _discoGetCB(self, con, iq_obj): + """ + Get disco info + """ + if not self.connection or self.connected < 2: + return + frm = helpers.get_full_jid_from_iq(iq_obj) + to = unicode(iq_obj.getAttr('to')) + id_ = unicode(iq_obj.getAttr('id')) + iq = common.xmpp.Iq(to=frm, typ='result', queryNS=common.xmpp.NS_DISCO, + frm=to) + iq.setAttr('id', id_) + query = iq.setTag('query') + query.setAttr('node', 'http://gajim.org#' + gajim.version.split('-', 1)[0]) + for f in (common.xmpp.NS_BYTESTREAM, common.xmpp.NS_SI, + common.xmpp.NS_FILE, common.xmpp.NS_COMMANDS): + feature = common.xmpp.Node('feature') + feature.setAttr('var', f) + query.addChild(node=feature) - self.connection.send(iq) - raise common.xmpp.NodeProcessed + self.connection.send(iq) + raise common.xmpp.NodeProcessed - def _DiscoverItemsErrorCB(self, con, iq_obj): - log.debug('DiscoverItemsErrorCB') - jid = helpers.get_full_jid_from_iq(iq_obj) - self.dispatch('AGENT_ERROR_ITEMS', (jid)) + def _DiscoverItemsErrorCB(self, con, iq_obj): + log.debug('DiscoverItemsErrorCB') + jid = helpers.get_full_jid_from_iq(iq_obj) + self.dispatch('AGENT_ERROR_ITEMS', (jid)) - def _DiscoverItemsCB(self, con, iq_obj): - log.debug('DiscoverItemsCB') - q = iq_obj.getTag('query') - node = q.getAttr('node') - if not node: - node = '' - qp = iq_obj.getQueryPayload() - items = [] - if not qp: - qp = [] - for i in qp: - # CDATA payload is not processed, only nodes - if not isinstance(i, common.xmpp.simplexml.Node): - continue - attr = {} - for key in i.getAttrs(): - attr[key] = i.getAttrs()[key] - if 'jid' not in attr: - continue - try: - attr['jid'] = helpers.parse_jid(attr['jid']) - except common.helpers.InvalidFormat: - # jid is not conform - continue - items.append(attr) - jid = helpers.get_full_jid_from_iq(iq_obj) - hostname = gajim.config.get_per('accounts', self.name, 'hostname') - id_ = iq_obj.getID() - if jid == hostname and id_[:6] == 'Gajim_': - for item in items: - self.discoverInfo(item['jid'], id_prefix='Gajim_') - else: - self.dispatch('AGENT_INFO_ITEMS', (jid, node, items)) + def _DiscoverItemsCB(self, con, iq_obj): + log.debug('DiscoverItemsCB') + q = iq_obj.getTag('query') + node = q.getAttr('node') + if not node: + node = '' + qp = iq_obj.getQueryPayload() + items = [] + if not qp: + qp = [] + for i in qp: + # CDATA payload is not processed, only nodes + if not isinstance(i, common.xmpp.simplexml.Node): + continue + attr = {} + for key in i.getAttrs(): + attr[key] = i.getAttrs()[key] + if 'jid' not in attr: + continue + try: + attr['jid'] = helpers.parse_jid(attr['jid']) + except common.helpers.InvalidFormat: + # jid is not conform + continue + items.append(attr) + jid = helpers.get_full_jid_from_iq(iq_obj) + hostname = gajim.config.get_per('accounts', self.name, 'hostname') + id_ = iq_obj.getID() + if jid == hostname and id_[:6] == 'Gajim_': + for item in items: + self.discoverInfo(item['jid'], id_prefix='Gajim_') + else: + self.dispatch('AGENT_INFO_ITEMS', (jid, node, items)) - def _DiscoverItemsGetCB(self, con, iq_obj): - log.debug('DiscoverItemsGetCB') + def _DiscoverItemsGetCB(self, con, iq_obj): + log.debug('DiscoverItemsGetCB') - if not self.connection or self.connected < 2: - return + if not self.connection or self.connected < 2: + return - if self.commandItemsQuery(con, iq_obj): - raise common.xmpp.NodeProcessed - node = iq_obj.getTagAttr('query', 'node') - if node is None: - result = iq_obj.buildReply('result') - self.connection.send(result) - raise common.xmpp.NodeProcessed - if node==common.xmpp.NS_COMMANDS: - self.commandListQuery(con, iq_obj) - raise common.xmpp.NodeProcessed + if self.commandItemsQuery(con, iq_obj): + raise common.xmpp.NodeProcessed + node = iq_obj.getTagAttr('query', 'node') + if node is None: + result = iq_obj.buildReply('result') + self.connection.send(result) + raise common.xmpp.NodeProcessed + if node==common.xmpp.NS_COMMANDS: + self.commandListQuery(con, iq_obj) + raise common.xmpp.NodeProcessed - def _DiscoverInfoGetCB(self, con, iq_obj): - log.debug('DiscoverInfoGetCB') - if not self.connection or self.connected < 2: - return - q = iq_obj.getTag('query') - node = q.getAttr('node') + def _DiscoverInfoGetCB(self, con, iq_obj): + log.debug('DiscoverInfoGetCB') + if not self.connection or self.connected < 2: + return + q = iq_obj.getTag('query') + node = q.getAttr('node') - if self.commandInfoQuery(con, iq_obj): - raise common.xmpp.NodeProcessed + if self.commandInfoQuery(con, iq_obj): + raise common.xmpp.NodeProcessed - id_ = unicode(iq_obj.getAttr('id')) - if id_[:6] == 'Gajim_': - # We get this request from echo.server - raise common.xmpp.NodeProcessed + id_ = unicode(iq_obj.getAttr('id')) + if id_[:6] == 'Gajim_': + # We get this request from echo.server + raise common.xmpp.NodeProcessed - iq = iq_obj.buildReply('result') - q = iq.getTag('query') - if node: - q.setAttr('node', node) - q.addChild('identity', attrs = gajim.gajim_identity) - client_version = 'http://gajim.org#' + gajim.caps_hash[self.name] + iq = iq_obj.buildReply('result') + q = iq.getTag('query') + if node: + q.setAttr('node', node) + q.addChild('identity', attrs = gajim.gajim_identity) + client_version = 'http://gajim.org#' + gajim.caps_hash[self.name] - if node in (None, client_version): - for f in gajim.gajim_common_features: - q.addChild('feature', attrs = {'var': f}) - for f in gajim.gajim_optional_features[self.name]: - q.addChild('feature', attrs = {'var': f}) + if node in (None, client_version): + for f in gajim.gajim_common_features: + q.addChild('feature', attrs = {'var': f}) + for f in gajim.gajim_optional_features[self.name]: + q.addChild('feature', attrs = {'var': f}) - if q.getChildren(): - self.connection.send(iq) - raise common.xmpp.NodeProcessed + if q.getChildren(): + self.connection.send(iq) + raise common.xmpp.NodeProcessed - def _DiscoverInfoErrorCB(self, con, iq_obj): - log.debug('DiscoverInfoErrorCB') - jid = helpers.get_full_jid_from_iq(iq_obj) - id_ = iq_obj.getID() - if id_[:6] == 'Gajim_': - if not self.privacy_rules_requested: - self.privacy_rules_requested = True - self._request_privacy() - self.dispatch('AGENT_ERROR_INFO', (jid)) + def _DiscoverInfoErrorCB(self, con, iq_obj): + log.debug('DiscoverInfoErrorCB') + jid = helpers.get_full_jid_from_iq(iq_obj) + id_ = iq_obj.getID() + if id_[:6] == 'Gajim_': + if not self.privacy_rules_requested: + self.privacy_rules_requested = True + self._request_privacy() + self.dispatch('AGENT_ERROR_INFO', (jid)) - def _DiscoverInfoCB(self, con, iq_obj): - log.debug('DiscoverInfoCB') - if not self.connection or self.connected < 2: - return - # According to XEP-0030: - # For identity: category, type is mandatory, name is optional. - # For feature: var is mandatory - identities, features, data = [], [], [] - q = iq_obj.getTag('query') - node = q.getAttr('node') - if not node: - node = '' - qc = iq_obj.getQueryChildren() - if not qc: - qc = [] - is_muc = False - transport_type = '' - for i in qc: - if i.getName() == 'identity': - attr = {} - for key in i.getAttrs().keys(): - attr[key] = i.getAttr(key) - if 'category' in attr and \ - attr['category'] in ('gateway', 'headline') and \ - 'type' in attr: - transport_type = attr['type'] - if 'category' in attr and \ - attr['category'] == 'conference' and \ - 'type' in attr and attr['type'] == 'text': - is_muc = True - identities.append(attr) - elif i.getName() == 'feature': - var = i.getAttr('var') - if var: - features.append(var) - elif i.getName() == 'x' and i.getNamespace() == common.xmpp.NS_DATA: - data.append(common.xmpp.DataForm(node=i)) - jid = helpers.get_full_jid_from_iq(iq_obj) - if transport_type and jid not in gajim.transport_type: - gajim.transport_type[jid] = transport_type - gajim.logger.save_transport_type(jid, transport_type) - id_ = iq_obj.getID() - if id_ is None: - log.warn('Invalid IQ received without an ID. Ignoring it: %s' % iq_obj) - return - if not identities: # ejabberd doesn't send identities when we browse online users - #FIXME: see http://www.jabber.ru/bugzilla/show_bug.cgi?id=225 - identities = [{'category': 'server', 'type': 'im', 'name': node}] - if id_[:6] == 'Gajim_': - if jid == gajim.config.get_per('accounts', self.name, 'hostname'): - if features.__contains__(common.xmpp.NS_GMAILNOTIFY): - gajim.gmail_domains.append(jid) - self.request_gmail_notifications() - for identity in identities: - if identity['category'] == 'pubsub' and identity.get('type') == \ - 'pep': - self.pep_supported = True - break - if features.__contains__(common.xmpp.NS_VCARD): - self.vcard_supported = True - if features.__contains__(common.xmpp.NS_PUBSUB): - self.pubsub_supported = True - if features.__contains__(common.xmpp.NS_PUBSUB_PUBLISH_OPTIONS): - self.pubsub_publish_options_supported = True - if features.__contains__(common.xmpp.NS_BYTESTREAM): - our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name) +\ - '/' + self.server_resource) - gajim.proxy65_manager.resolve(jid, self.connection, our_jid, - self.name) - if features.__contains__(common.xmpp.NS_MUC) and is_muc: - type_ = transport_type or 'jabber' - self.muc_jid[type_] = jid - if transport_type: - if transport_type in self.available_transports: - self.available_transports[transport_type].append(jid) - else: - self.available_transports[transport_type] = [jid] - if not self.privacy_rules_requested: - self.privacy_rules_requested = True - self._request_privacy() + def _DiscoverInfoCB(self, con, iq_obj): + log.debug('DiscoverInfoCB') + if not self.connection or self.connected < 2: + return + # According to XEP-0030: + # For identity: category, type is mandatory, name is optional. + # For feature: var is mandatory + identities, features, data = [], [], [] + q = iq_obj.getTag('query') + node = q.getAttr('node') + if not node: + node = '' + qc = iq_obj.getQueryChildren() + if not qc: + qc = [] + is_muc = False + transport_type = '' + for i in qc: + if i.getName() == 'identity': + attr = {} + for key in i.getAttrs().keys(): + attr[key] = i.getAttr(key) + if 'category' in attr and \ + attr['category'] in ('gateway', 'headline') and \ + 'type' in attr: + transport_type = attr['type'] + if 'category' in attr and \ + attr['category'] == 'conference' and \ + 'type' in attr and attr['type'] == 'text': + is_muc = True + identities.append(attr) + elif i.getName() == 'feature': + var = i.getAttr('var') + if var: + features.append(var) + elif i.getName() == 'x' and i.getNamespace() == common.xmpp.NS_DATA: + data.append(common.xmpp.DataForm(node=i)) + jid = helpers.get_full_jid_from_iq(iq_obj) + if transport_type and jid not in gajim.transport_type: + gajim.transport_type[jid] = transport_type + gajim.logger.save_transport_type(jid, transport_type) + id_ = iq_obj.getID() + if id_ is None: + log.warn('Invalid IQ received without an ID. Ignoring it: %s' % iq_obj) + return + if not identities: # ejabberd doesn't send identities when we browse online users + #FIXME: see http://www.jabber.ru/bugzilla/show_bug.cgi?id=225 + identities = [{'category': 'server', 'type': 'im', 'name': node}] + if id_[:6] == 'Gajim_': + if jid == gajim.config.get_per('accounts', self.name, 'hostname'): + if features.__contains__(common.xmpp.NS_GMAILNOTIFY): + gajim.gmail_domains.append(jid) + self.request_gmail_notifications() + for identity in identities: + if identity['category'] == 'pubsub' and identity.get('type') == \ + 'pep': + self.pep_supported = True + break + if features.__contains__(common.xmpp.NS_VCARD): + self.vcard_supported = True + if features.__contains__(common.xmpp.NS_PUBSUB): + self.pubsub_supported = True + if features.__contains__(common.xmpp.NS_PUBSUB_PUBLISH_OPTIONS): + self.pubsub_publish_options_supported = True + if features.__contains__(common.xmpp.NS_BYTESTREAM): + our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name) +\ + '/' + self.server_resource) + gajim.proxy65_manager.resolve(jid, self.connection, our_jid, + self.name) + if features.__contains__(common.xmpp.NS_MUC) and is_muc: + type_ = transport_type or 'jabber' + self.muc_jid[type_] = jid + if transport_type: + if transport_type in self.available_transports: + self.available_transports[transport_type].append(jid) + else: + self.available_transports[transport_type] = [jid] + if not self.privacy_rules_requested: + self.privacy_rules_requested = True + self._request_privacy() - self.dispatch('AGENT_INFO_INFO', (jid, node, identities, - features, data)) - self._capsDiscoCB(jid, node, identities, features, data) + self.dispatch('AGENT_INFO_INFO', (jid, node, identities, + features, data)) + self._capsDiscoCB(jid, node, identities, features, data) class ConnectionVcard: - def __init__(self): - self.vcard_sha = None - self.vcard_shas = {} # sha of contacts - self.room_jids = [] # list of gc jids so that vcard are saved in a folder + def __init__(self): + self.vcard_sha = None + self.vcard_shas = {} # sha of contacts + self.room_jids = [] # list of gc jids so that vcard are saved in a folder - def add_sha(self, p, send_caps = True): - c = p.setTag('x', namespace = common.xmpp.NS_VCARD_UPDATE) - if self.vcard_sha is not None: - c.setTagData('photo', self.vcard_sha) - if send_caps: - return self._add_caps(p) - return p + def add_sha(self, p, send_caps = True): + c = p.setTag('x', namespace = common.xmpp.NS_VCARD_UPDATE) + if self.vcard_sha is not None: + c.setTagData('photo', self.vcard_sha) + if send_caps: + return self._add_caps(p) + return p - def _add_caps(self, p): - ''' advertise our capabilities in presence stanza (xep-0115)''' - c = p.setTag('c', namespace = common.xmpp.NS_CAPS) - c.setAttr('hash', 'sha-1') - c.setAttr('node', 'http://gajim.org') - c.setAttr('ver', gajim.caps_hash[self.name]) - return p + def _add_caps(self, p): + ''' advertise our capabilities in presence stanza (xep-0115)''' + c = p.setTag('c', namespace = common.xmpp.NS_CAPS) + c.setAttr('hash', 'sha-1') + c.setAttr('node', 'http://gajim.org') + c.setAttr('ver', gajim.caps_hash[self.name]) + return p - def _node_to_dict(self, node): - dict_ = {} - for info in node.getChildren(): - name = info.getName() - if name in ('ADR', 'TEL', 'EMAIL'): # we can have several - dict_.setdefault(name, []) - entry = {} - for c in info.getChildren(): - entry[c.getName()] = c.getData() - dict_[name].append(entry) - elif info.getChildren() == []: - dict_[name] = info.getData() - else: - dict_[name] = {} - for c in info.getChildren(): - dict_[name][c.getName()] = c.getData() - return dict_ + def _node_to_dict(self, node): + dict_ = {} + for info in node.getChildren(): + name = info.getName() + if name in ('ADR', 'TEL', 'EMAIL'): # we can have several + dict_.setdefault(name, []) + entry = {} + for c in info.getChildren(): + entry[c.getName()] = c.getData() + dict_[name].append(entry) + elif info.getChildren() == []: + dict_[name] = info.getData() + else: + dict_[name] = {} + for c in info.getChildren(): + dict_[name][c.getName()] = c.getData() + return dict_ - def _save_vcard_to_hd(self, full_jid, card): - jid, nick = gajim.get_room_and_nick_from_fjid(full_jid) - puny_jid = helpers.sanitize_filename(jid) - path = os.path.join(gajim.VCARD_PATH, puny_jid) - if jid in self.room_jids or os.path.isdir(path): - if not nick: - return - # remove room_jid file if needed - if os.path.isfile(path): - os.remove(path) - # create folder if needed - if not os.path.isdir(path): - os.mkdir(path, 0700) - puny_nick = helpers.sanitize_filename(nick) - path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) - else: - path_to_file = path - try: - fil = open(path_to_file, 'w') - fil.write(str(card)) - fil.close() - except IOError, e: - self.dispatch('ERROR', (_('Disk Write Error'), str(e))) + def _save_vcard_to_hd(self, full_jid, card): + jid, nick = gajim.get_room_and_nick_from_fjid(full_jid) + puny_jid = helpers.sanitize_filename(jid) + path = os.path.join(gajim.VCARD_PATH, puny_jid) + if jid in self.room_jids or os.path.isdir(path): + if not nick: + return + # remove room_jid file if needed + if os.path.isfile(path): + os.remove(path) + # create folder if needed + if not os.path.isdir(path): + os.mkdir(path, 0700) + puny_nick = helpers.sanitize_filename(nick) + path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) + else: + path_to_file = path + try: + fil = open(path_to_file, 'w') + fil.write(str(card)) + fil.close() + except IOError, e: + self.dispatch('ERROR', (_('Disk Write Error'), str(e))) - def get_cached_vcard(self, fjid, is_fake_jid=False): - """ - Return the vcard as a dict. - Return {} if vcard was too old. - Return None if we don't have cached vcard. - """ - jid, nick = gajim.get_room_and_nick_from_fjid(fjid) - puny_jid = helpers.sanitize_filename(jid) - if is_fake_jid: - puny_nick = helpers.sanitize_filename(nick) - path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) - else: - path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid) - if not os.path.isfile(path_to_file): - return None - # We have the vcard cached - f = open(path_to_file) - c = f.read() - f.close() - try: - card = common.xmpp.Node(node=c) - except Exception: - # We are unable to parse it. Remove it - os.remove(path_to_file) - return None - vcard = self._node_to_dict(card) - if 'PHOTO' in vcard: - if not isinstance(vcard['PHOTO'], dict): - del vcard['PHOTO'] - elif 'SHA' in vcard['PHOTO']: - cached_sha = vcard['PHOTO']['SHA'] - if jid in self.vcard_shas and self.vcard_shas[jid] != \ - cached_sha: - # user change his vcard so don't use the cached one - return {} - vcard['jid'] = jid - vcard['resource'] = gajim.get_resource_from_jid(fjid) - return vcard + def get_cached_vcard(self, fjid, is_fake_jid=False): + """ + Return the vcard as a dict. + Return {} if vcard was too old. + Return None if we don't have cached vcard. + """ + jid, nick = gajim.get_room_and_nick_from_fjid(fjid) + puny_jid = helpers.sanitize_filename(jid) + if is_fake_jid: + puny_nick = helpers.sanitize_filename(nick) + path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) + else: + path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid) + if not os.path.isfile(path_to_file): + return None + # We have the vcard cached + f = open(path_to_file) + c = f.read() + f.close() + try: + card = common.xmpp.Node(node=c) + except Exception: + # We are unable to parse it. Remove it + os.remove(path_to_file) + return None + vcard = self._node_to_dict(card) + if 'PHOTO' in vcard: + if not isinstance(vcard['PHOTO'], dict): + del vcard['PHOTO'] + elif 'SHA' in vcard['PHOTO']: + cached_sha = vcard['PHOTO']['SHA'] + if jid in self.vcard_shas and self.vcard_shas[jid] != \ + cached_sha: + # user change his vcard so don't use the cached one + return {} + vcard['jid'] = jid + vcard['resource'] = gajim.get_resource_from_jid(fjid) + return vcard - def request_vcard(self, jid=None, groupchat_jid=None): - """ - Request the VCARD + def request_vcard(self, jid=None, groupchat_jid=None): + """ + Request the VCARD - If groupchat_jid is not null, it means we request a vcard to a fake jid, - like in private messages in groupchat. jid can be the real jid of the - contact, but we want to consider it comes from a fake jid - """ - if not self.connection or self.connected < 2: - return - iq = common.xmpp.Iq(typ = 'get') - if jid: - iq.setTo(jid) - iq.setTag(common.xmpp.NS_VCARD + ' vCard') + If groupchat_jid is not null, it means we request a vcard to a fake jid, + like in private messages in groupchat. jid can be the real jid of the + contact, but we want to consider it comes from a fake jid + """ + if not self.connection or self.connected < 2: + return + iq = common.xmpp.Iq(typ = 'get') + if jid: + iq.setTo(jid) + iq.setTag(common.xmpp.NS_VCARD + ' vCard') - id_ = self.connection.getAnID() - iq.setID(id_) - j = jid - if not j: - j = gajim.get_jid_from_account(self.name) - self.awaiting_answers[id_] = (VCARD_ARRIVED, j, groupchat_jid) - if groupchat_jid: - room_jid = gajim.get_room_and_nick_from_fjid(groupchat_jid)[0] - if not room_jid in self.room_jids: - self.room_jids.append(room_jid) - self.groupchat_jids[id_] = groupchat_jid - self.connection.send(iq) + id_ = self.connection.getAnID() + iq.setID(id_) + j = jid + if not j: + j = gajim.get_jid_from_account(self.name) + self.awaiting_answers[id_] = (VCARD_ARRIVED, j, groupchat_jid) + if groupchat_jid: + room_jid = gajim.get_room_and_nick_from_fjid(groupchat_jid)[0] + if not room_jid in self.room_jids: + self.room_jids.append(room_jid) + self.groupchat_jids[id_] = groupchat_jid + self.connection.send(iq) - def send_vcard(self, vcard): - if not self.connection or self.connected < 2: - return - iq = common.xmpp.Iq(typ = 'set') - iq2 = iq.setTag(common.xmpp.NS_VCARD + ' vCard') - for i in vcard: - if i == 'jid': - continue - if isinstance(vcard[i], dict): - iq3 = iq2.addChild(i) - for j in vcard[i]: - iq3.addChild(j).setData(vcard[i][j]) - elif isinstance(vcard[i], list): - for j in vcard[i]: - iq3 = iq2.addChild(i) - for k in j: - iq3.addChild(k).setData(j[k]) - else: - iq2.addChild(i).setData(vcard[i]) + def send_vcard(self, vcard): + if not self.connection or self.connected < 2: + return + iq = common.xmpp.Iq(typ = 'set') + iq2 = iq.setTag(common.xmpp.NS_VCARD + ' vCard') + for i in vcard: + if i == 'jid': + continue + if isinstance(vcard[i], dict): + iq3 = iq2.addChild(i) + for j in vcard[i]: + iq3.addChild(j).setData(vcard[i][j]) + elif isinstance(vcard[i], list): + for j in vcard[i]: + iq3 = iq2.addChild(i) + for k in j: + iq3.addChild(k).setData(j[k]) + else: + iq2.addChild(i).setData(vcard[i]) - id_ = self.connection.getAnID() - iq.setID(id_) - self.connection.send(iq) + id_ = self.connection.getAnID() + iq.setID(id_) + self.connection.send(iq) - our_jid = gajim.get_jid_from_account(self.name) - # Add the sha of the avatar - if 'PHOTO' in vcard and isinstance(vcard['PHOTO'], dict) and \ - 'BINVAL' in vcard['PHOTO']: - photo = vcard['PHOTO']['BINVAL'] - photo_decoded = base64.decodestring(photo) - gajim.interface.save_avatar_files(our_jid, photo_decoded) - avatar_sha = hashlib.sha1(photo_decoded).hexdigest() - iq2.getTag('PHOTO').setTagData('SHA', avatar_sha) - else: - gajim.interface.remove_avatar_files(our_jid) + our_jid = gajim.get_jid_from_account(self.name) + # Add the sha of the avatar + if 'PHOTO' in vcard and isinstance(vcard['PHOTO'], dict) and \ + 'BINVAL' in vcard['PHOTO']: + photo = vcard['PHOTO']['BINVAL'] + photo_decoded = base64.decodestring(photo) + gajim.interface.save_avatar_files(our_jid, photo_decoded) + avatar_sha = hashlib.sha1(photo_decoded).hexdigest() + iq2.getTag('PHOTO').setTagData('SHA', avatar_sha) + else: + gajim.interface.remove_avatar_files(our_jid) - self.awaiting_answers[id_] = (VCARD_PUBLISHED, iq2) + self.awaiting_answers[id_] = (VCARD_PUBLISHED, iq2) - def _IqCB(self, con, iq_obj): - id_ = iq_obj.getID() - - gajim.nec.push_incoming_event(NetworkEvent('raw-iq-received', - conn = con, - xmpp_iq = iq_obj)) + def _IqCB(self, con, iq_obj): + id_ = iq_obj.getID() - # Check if we were waiting a timeout for this id - found_tim = None - for tim in self.awaiting_timeouts: - if id_ == self.awaiting_timeouts[tim][0]: - found_tim = tim - break - if found_tim: - del self.awaiting_timeouts[found_tim] + gajim.nec.push_incoming_event(NetworkEvent('raw-iq-received', + conn = con, + xmpp_iq = iq_obj)) - if id_ not in self.awaiting_answers: - return - if self.awaiting_answers[id_][0] == VCARD_PUBLISHED: - if iq_obj.getType() == 'result': - vcard_iq = self.awaiting_answers[id_][1] - # Save vcard to HD - if vcard_iq.getTag('PHOTO') and vcard_iq.getTag('PHOTO').getTag('SHA'): - new_sha = vcard_iq.getTag('PHOTO').getTagData('SHA') - else: - new_sha = '' + # Check if we were waiting a timeout for this id + found_tim = None + for tim in self.awaiting_timeouts: + if id_ == self.awaiting_timeouts[tim][0]: + found_tim = tim + break + if found_tim: + del self.awaiting_timeouts[found_tim] - # Save it to file - our_jid = gajim.get_jid_from_account(self.name) - self._save_vcard_to_hd(our_jid, vcard_iq) + if id_ not in self.awaiting_answers: + return + if self.awaiting_answers[id_][0] == VCARD_PUBLISHED: + if iq_obj.getType() == 'result': + vcard_iq = self.awaiting_answers[id_][1] + # Save vcard to HD + if vcard_iq.getTag('PHOTO') and vcard_iq.getTag('PHOTO').getTag('SHA'): + new_sha = vcard_iq.getTag('PHOTO').getTagData('SHA') + else: + new_sha = '' - # Send new presence if sha changed and we are not invisible - if self.vcard_sha != new_sha and gajim.SHOW_LIST[self.connected] !=\ - 'invisible': - if not self.connection or self.connected < 2: - return - self.vcard_sha = new_sha - sshow = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected]) - p = common.xmpp.Presence(typ = None, priority = self.priority, - show = sshow, status = self.status) - p = self.add_sha(p) - self.connection.send(p) - self.dispatch('VCARD_PUBLISHED', ()) - elif iq_obj.getType() == 'error': - self.dispatch('VCARD_NOT_PUBLISHED', ()) - elif self.awaiting_answers[id_][0] == VCARD_ARRIVED: - # If vcard is empty, we send to the interface an empty vcard so that - # it knows it arrived - jid = self.awaiting_answers[id_][1] - groupchat_jid = self.awaiting_answers[id_][2] - frm = jid - if groupchat_jid: - # We do as if it comes from the fake_jid - frm = groupchat_jid - our_jid = gajim.get_jid_from_account(self.name) - if not iq_obj.getTag('vCard') or iq_obj.getType() == 'error': - if frm and frm != our_jid: - # Write an empty file - self._save_vcard_to_hd(frm, '') - jid, resource = gajim.get_room_and_nick_from_fjid(frm) - self.dispatch('VCARD', {'jid': jid, 'resource': resource}) - elif frm == our_jid: - self.dispatch('MYVCARD', {'jid': frm}) - elif self.awaiting_answers[id_][0] == AGENT_REMOVED: - jid = self.awaiting_answers[id_][1] - self.dispatch('AGENT_REMOVED', jid) - elif self.awaiting_answers[id_][0] == METACONTACTS_ARRIVED: - if not self.connection: - return - if iq_obj.getType() == 'result': - # Metacontact tags - # http://www.xmpp.org/extensions/xep-0209.html - meta_list = {} - query = iq_obj.getTag('query') - storage = query.getTag('storage') - metas = storage.getTags('meta') - for meta in metas: - try: - jid = helpers.parse_jid(meta.getAttr('jid')) - except common.helpers.InvalidFormat: - continue - tag = meta.getAttr('tag') - data = {'jid': jid} - order = meta.getAttr('order') - try: - order = int(order) - except Exception: - order = 0 - if order is not None: - data['order'] = order - if tag in meta_list: - meta_list[tag].append(data) - else: - meta_list[tag] = [data] - self.dispatch('METACONTACTS', meta_list) - else: - if iq_obj.getErrorCode() not in ('403', '406', '404'): - self.private_storage_supported = False - # We can now continue connection by requesting the roster - version = gajim.config.get_per('accounts', self.name, - 'roster_version') - iq_id = self.connection.initRoster(version=version) - self.awaiting_answers[iq_id] = (ROSTER_ARRIVED, ) - elif self.awaiting_answers[id_][0] == ROSTER_ARRIVED: - if iq_obj.getType() == 'result': - if not iq_obj.getTag('query'): - account_jid = gajim.get_jid_from_account(self.name) - roster_data = gajim.logger.get_roster(account_jid) - roster = self.connection.getRoster(force=True) - roster.setRaw(roster_data) - self._getRoster() - elif self.awaiting_answers[id_][0] == PRIVACY_ARRIVED: - if iq_obj.getType() != 'error': - self.privacy_rules_supported = True - self.get_privacy_list('block') - elif self.continue_connect_info: - if self.continue_connect_info[0] == 'invisible': - # Trying to login as invisible but privacy list not supported - self.disconnect(on_purpose=True) - self.dispatch('STATUS', 'offline') - self.dispatch('ERROR', (_('Invisibility not supported'), - _('Account %s doesn\'t support invisibility.') % self.name)) - return - # Ask metacontacts before roster - self.get_metacontacts() - elif self.awaiting_answers[id_][0] == PEP_CONFIG: - conf = iq_obj.getTag('pubsub').getTag('configure') - node = conf.getAttr('node') - form_tag = conf.getTag('x', namespace=common.xmpp.NS_DATA) - if form_tag: - form = common.dataforms.ExtendForm(node=form_tag) - self.dispatch('PEP_CONFIG', (node, form)) + # Save it to file + our_jid = gajim.get_jid_from_account(self.name) + self._save_vcard_to_hd(our_jid, vcard_iq) - del self.awaiting_answers[id_] + # Send new presence if sha changed and we are not invisible + if self.vcard_sha != new_sha and gajim.SHOW_LIST[self.connected] !=\ + 'invisible': + if not self.connection or self.connected < 2: + return + self.vcard_sha = new_sha + sshow = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected]) + p = common.xmpp.Presence(typ = None, priority = self.priority, + show = sshow, status = self.status) + p = self.add_sha(p) + self.connection.send(p) + self.dispatch('VCARD_PUBLISHED', ()) + elif iq_obj.getType() == 'error': + self.dispatch('VCARD_NOT_PUBLISHED', ()) + elif self.awaiting_answers[id_][0] == VCARD_ARRIVED: + # If vcard is empty, we send to the interface an empty vcard so that + # it knows it arrived + jid = self.awaiting_answers[id_][1] + groupchat_jid = self.awaiting_answers[id_][2] + frm = jid + if groupchat_jid: + # We do as if it comes from the fake_jid + frm = groupchat_jid + our_jid = gajim.get_jid_from_account(self.name) + if not iq_obj.getTag('vCard') or iq_obj.getType() == 'error': + if frm and frm != our_jid: + # Write an empty file + self._save_vcard_to_hd(frm, '') + jid, resource = gajim.get_room_and_nick_from_fjid(frm) + self.dispatch('VCARD', {'jid': jid, 'resource': resource}) + elif frm == our_jid: + self.dispatch('MYVCARD', {'jid': frm}) + elif self.awaiting_answers[id_][0] == AGENT_REMOVED: + jid = self.awaiting_answers[id_][1] + self.dispatch('AGENT_REMOVED', jid) + elif self.awaiting_answers[id_][0] == METACONTACTS_ARRIVED: + if not self.connection: + return + if iq_obj.getType() == 'result': + # Metacontact tags + # http://www.xmpp.org/extensions/xep-0209.html + meta_list = {} + query = iq_obj.getTag('query') + storage = query.getTag('storage') + metas = storage.getTags('meta') + for meta in metas: + try: + jid = helpers.parse_jid(meta.getAttr('jid')) + except common.helpers.InvalidFormat: + continue + tag = meta.getAttr('tag') + data = {'jid': jid} + order = meta.getAttr('order') + try: + order = int(order) + except Exception: + order = 0 + if order is not None: + data['order'] = order + if tag in meta_list: + meta_list[tag].append(data) + else: + meta_list[tag] = [data] + self.dispatch('METACONTACTS', meta_list) + else: + if iq_obj.getErrorCode() not in ('403', '406', '404'): + self.private_storage_supported = False + # We can now continue connection by requesting the roster + version = gajim.config.get_per('accounts', self.name, + 'roster_version') + iq_id = self.connection.initRoster(version=version) + self.awaiting_answers[iq_id] = (ROSTER_ARRIVED, ) + elif self.awaiting_answers[id_][0] == ROSTER_ARRIVED: + if iq_obj.getType() == 'result': + if not iq_obj.getTag('query'): + account_jid = gajim.get_jid_from_account(self.name) + roster_data = gajim.logger.get_roster(account_jid) + roster = self.connection.getRoster(force=True) + roster.setRaw(roster_data) + self._getRoster() + elif self.awaiting_answers[id_][0] == PRIVACY_ARRIVED: + if iq_obj.getType() != 'error': + self.privacy_rules_supported = True + self.get_privacy_list('block') + elif self.continue_connect_info: + if self.continue_connect_info[0] == 'invisible': + # Trying to login as invisible but privacy list not supported + self.disconnect(on_purpose=True) + self.dispatch('STATUS', 'offline') + self.dispatch('ERROR', (_('Invisibility not supported'), + _('Account %s doesn\'t support invisibility.') % self.name)) + return + # Ask metacontacts before roster + self.get_metacontacts() + elif self.awaiting_answers[id_][0] == PEP_CONFIG: + conf = iq_obj.getTag('pubsub').getTag('configure') + node = conf.getAttr('node') + form_tag = conf.getTag('x', namespace=common.xmpp.NS_DATA) + if form_tag: + form = common.dataforms.ExtendForm(node=form_tag) + self.dispatch('PEP_CONFIG', (node, form)) - def _vCardCB(self, con, vc): - """ - Called when we receive a vCard Parse the vCard and send it to plugins - """ - if not vc.getTag('vCard'): - return - if not vc.getTag('vCard').getNamespace() == common.xmpp.NS_VCARD: - return - id_ = vc.getID() - frm_iq = vc.getFrom() - our_jid = gajim.get_jid_from_account(self.name) - resource = '' - if id_ in self.groupchat_jids: - who = self.groupchat_jids[id_] - frm, resource = gajim.get_room_and_nick_from_fjid(who) - del self.groupchat_jids[id_] - elif frm_iq: - who = helpers.get_full_jid_from_iq(vc) - frm, resource = gajim.get_room_and_nick_from_fjid(who) - else: - who = frm = our_jid - card = vc.getChildren()[0] - vcard = self._node_to_dict(card) - photo_decoded = None - if 'PHOTO' in vcard and isinstance(vcard['PHOTO'], dict) and \ - 'BINVAL' in vcard['PHOTO']: - photo = vcard['PHOTO']['BINVAL'] - try: - photo_decoded = base64.decodestring(photo) - avatar_sha = hashlib.sha1(photo_decoded).hexdigest() - except Exception: - avatar_sha = '' - else: - avatar_sha = '' + del self.awaiting_answers[id_] - if avatar_sha: - card.getTag('PHOTO').setTagData('SHA', avatar_sha) + def _vCardCB(self, con, vc): + """ + Called when we receive a vCard Parse the vCard and send it to plugins + """ + if not vc.getTag('vCard'): + return + if not vc.getTag('vCard').getNamespace() == common.xmpp.NS_VCARD: + return + id_ = vc.getID() + frm_iq = vc.getFrom() + our_jid = gajim.get_jid_from_account(self.name) + resource = '' + if id_ in self.groupchat_jids: + who = self.groupchat_jids[id_] + frm, resource = gajim.get_room_and_nick_from_fjid(who) + del self.groupchat_jids[id_] + elif frm_iq: + who = helpers.get_full_jid_from_iq(vc) + frm, resource = gajim.get_room_and_nick_from_fjid(who) + else: + who = frm = our_jid + card = vc.getChildren()[0] + vcard = self._node_to_dict(card) + photo_decoded = None + if 'PHOTO' in vcard and isinstance(vcard['PHOTO'], dict) and \ + 'BINVAL' in vcard['PHOTO']: + photo = vcard['PHOTO']['BINVAL'] + try: + photo_decoded = base64.decodestring(photo) + avatar_sha = hashlib.sha1(photo_decoded).hexdigest() + except Exception: + avatar_sha = '' + else: + avatar_sha = '' - # Save it to file - self._save_vcard_to_hd(who, card) - # Save the decoded avatar to a separate file too, and generate files for dbus notifications - puny_jid = helpers.sanitize_filename(frm) - puny_nick = None - begin_path = os.path.join(gajim.AVATAR_PATH, puny_jid) - frm_jid = frm - if frm in self.room_jids: - puny_nick = helpers.sanitize_filename(resource) - # create folder if needed - if not os.path.isdir(begin_path): - os.mkdir(begin_path, 0700) - begin_path = os.path.join(begin_path, puny_nick) - frm_jid += '/' + resource - if photo_decoded: - avatar_file = begin_path + '_notif_size_colored.png' - if frm_jid == our_jid and avatar_sha != self.vcard_sha: - gajim.interface.save_avatar_files(frm, photo_decoded, puny_nick) - elif frm_jid != our_jid and (not os.path.exists(avatar_file) or \ - frm_jid not in self.vcard_shas or \ - avatar_sha != self.vcard_shas[frm_jid]): - gajim.interface.save_avatar_files(frm, photo_decoded, puny_nick) - if avatar_sha: - self.vcard_shas[frm_jid] = avatar_sha - elif frm in self.vcard_shas: - del self.vcard_shas[frm] - else: - for ext in ('.jpeg', '.png', '_notif_size_bw.png', - '_notif_size_colored.png'): - path = begin_path + ext - if os.path.isfile(path): - os.remove(path) + if avatar_sha: + card.getTag('PHOTO').setTagData('SHA', avatar_sha) - vcard['jid'] = frm - vcard['resource'] = resource - if frm_jid == our_jid: - self.dispatch('MYVCARD', vcard) - # we re-send our presence with sha if has changed and if we are - # not invisible - if self.vcard_sha == avatar_sha: - return - self.vcard_sha = avatar_sha - if gajim.SHOW_LIST[self.connected] == 'invisible': - return - if not self.connection: - return - sshow = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected]) - p = common.xmpp.Presence(typ = None, priority = self.priority, - show = sshow, status = self.status) - p = self.add_sha(p) - self.connection.send(p) - else: - #('VCARD', {entry1: data, entry2: {entry21: data, ...}, ...}) - self.dispatch('VCARD', vcard) + # Save it to file + self._save_vcard_to_hd(who, card) + # Save the decoded avatar to a separate file too, and generate files for dbus notifications + puny_jid = helpers.sanitize_filename(frm) + puny_nick = None + begin_path = os.path.join(gajim.AVATAR_PATH, puny_jid) + frm_jid = frm + if frm in self.room_jids: + puny_nick = helpers.sanitize_filename(resource) + # create folder if needed + if not os.path.isdir(begin_path): + os.mkdir(begin_path, 0700) + begin_path = os.path.join(begin_path, puny_nick) + frm_jid += '/' + resource + if photo_decoded: + avatar_file = begin_path + '_notif_size_colored.png' + if frm_jid == our_jid and avatar_sha != self.vcard_sha: + gajim.interface.save_avatar_files(frm, photo_decoded, puny_nick) + elif frm_jid != our_jid and (not os.path.exists(avatar_file) or \ + frm_jid not in self.vcard_shas or \ + avatar_sha != self.vcard_shas[frm_jid]): + gajim.interface.save_avatar_files(frm, photo_decoded, puny_nick) + if avatar_sha: + self.vcard_shas[frm_jid] = avatar_sha + elif frm in self.vcard_shas: + del self.vcard_shas[frm] + else: + for ext in ('.jpeg', '.png', '_notif_size_bw.png', + '_notif_size_colored.png'): + path = begin_path + ext + if os.path.isfile(path): + os.remove(path) + + vcard['jid'] = frm + vcard['resource'] = resource + if frm_jid == our_jid: + self.dispatch('MYVCARD', vcard) + # we re-send our presence with sha if has changed and if we are + # not invisible + if self.vcard_sha == avatar_sha: + return + self.vcard_sha = avatar_sha + if gajim.SHOW_LIST[self.connected] == 'invisible': + return + if not self.connection: + return + sshow = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected]) + p = common.xmpp.Presence(typ = None, priority = self.priority, + show = sshow, status = self.status) + p = self.add_sha(p) + self.connection.send(p) + else: + #('VCARD', {entry1: data, entry2: {entry21: data, ...}, ...}) + self.dispatch('VCARD', vcard) # basic connection handlers used here and in zeroconf class ConnectionHandlersBase: - def __init__(self): - # List of IDs we are waiting answers for {id: (type_of_request, data), } - self.awaiting_answers = {} - # List of IDs that will produce a timeout is answer doesn't arrive - # {time_of_the_timeout: (id, message to send to gui), } - self.awaiting_timeouts = {} - # keep the jids we auto added (transports contacts) to not send the - # SUBSCRIBED event to gui - self.automatically_added = [] + def __init__(self): + # List of IDs we are waiting answers for {id: (type_of_request, data), } + self.awaiting_answers = {} + # List of IDs that will produce a timeout is answer doesn't arrive + # {time_of_the_timeout: (id, message to send to gui), } + self.awaiting_timeouts = {} + # keep the jids we auto added (transports contacts) to not send the + # SUBSCRIBED event to gui + self.automatically_added = [] - # keep track of sessions this connection has with other JIDs - self.sessions = {} + # keep track of sessions this connection has with other JIDs + self.sessions = {} - def get_sessions(self, jid): - """ - Get all sessions for the given full jid - """ - if not gajim.interface.is_pm_contact(jid, self.name): - jid = gajim.get_jid_without_resource(jid) + def get_sessions(self, jid): + """ + Get all sessions for the given full jid + """ + if not gajim.interface.is_pm_contact(jid, self.name): + jid = gajim.get_jid_without_resource(jid) - try: - return self.sessions[jid].values() - except KeyError: - return [] + try: + return self.sessions[jid].values() + except KeyError: + return [] - def get_or_create_session(self, fjid, thread_id): - """ - Return an existing session between this connection and 'jid', returns a - new one if none exist - """ - pm = True - jid = fjid + def get_or_create_session(self, fjid, thread_id): + """ + Return an existing session between this connection and 'jid', returns a + new one if none exist + """ + pm = True + jid = fjid - if not gajim.interface.is_pm_contact(fjid, self.name): - pm = False - jid = gajim.get_jid_without_resource(fjid) + if not gajim.interface.is_pm_contact(fjid, self.name): + pm = False + jid = gajim.get_jid_without_resource(fjid) - session = self.find_session(jid, thread_id) + session = self.find_session(jid, thread_id) - if session: - return session + if session: + return session - if pm: - return self.make_new_session(fjid, thread_id, type_='pm') - else: - return self.make_new_session(fjid, thread_id) + if pm: + return self.make_new_session(fjid, thread_id, type_='pm') + else: + return self.make_new_session(fjid, thread_id) - def find_session(self, jid, thread_id): - try: - if not thread_id: - return self.find_null_session(jid) - else: - return self.sessions[jid][thread_id] - except KeyError: - return None + def find_session(self, jid, thread_id): + try: + if not thread_id: + return self.find_null_session(jid) + else: + return self.sessions[jid][thread_id] + except KeyError: + return None - def terminate_sessions(self, send_termination=False): - """ - Send termination messages and delete all active sessions - """ - for jid in self.sessions: - for thread_id in self.sessions[jid]: - self.sessions[jid][thread_id].terminate(send_termination) + def terminate_sessions(self, send_termination=False): + """ + Send termination messages and delete all active sessions + """ + for jid in self.sessions: + for thread_id in self.sessions[jid]: + self.sessions[jid][thread_id].terminate(send_termination) - self.sessions = {} + self.sessions = {} - def delete_session(self, jid, thread_id): - if not jid in self.sessions: - jid = gajim.get_jid_without_resource(jid) - if not jid in self.sessions: - return + def delete_session(self, jid, thread_id): + if not jid in self.sessions: + jid = gajim.get_jid_without_resource(jid) + if not jid in self.sessions: + return - del self.sessions[jid][thread_id] + del self.sessions[jid][thread_id] - if not self.sessions[jid]: - del self.sessions[jid] + if not self.sessions[jid]: + del self.sessions[jid] - def find_null_session(self, jid): - """ - Find all of the sessions between us and a remote jid in which we haven't - received a thread_id yet and returns the session that we last sent a - message to - """ - sessions = self.sessions[jid].values() + def find_null_session(self, jid): + """ + Find all of the sessions between us and a remote jid in which we haven't + received a thread_id yet and returns the session that we last sent a + message to + """ + sessions = self.sessions[jid].values() - # sessions that we haven't received a thread ID in - idless = [s for s in sessions if not s.received_thread_id] + # sessions that we haven't received a thread ID in + idless = [s for s in sessions if not s.received_thread_id] - # filter out everything except the default session type - chat_sessions = [s for s in idless if isinstance(s, - gajim.default_session_type)] + # filter out everything except the default session type + chat_sessions = [s for s in idless if isinstance(s, + gajim.default_session_type)] - if chat_sessions: - # return the session that we last sent a message in - return sorted(chat_sessions, key=operator.attrgetter("last_send"))[-1] - else: - return None + if chat_sessions: + # return the session that we last sent a message in + return sorted(chat_sessions, key=operator.attrgetter("last_send"))[-1] + else: + return None - def find_controlless_session(self, jid, resource=None): - """ - Find an active session that doesn't have a control attached - """ - try: - sessions = self.sessions[jid].values() + def find_controlless_session(self, jid, resource=None): + """ + Find an active session that doesn't have a control attached + """ + try: + sessions = self.sessions[jid].values() - # filter out everything except the default session type - chat_sessions = [s for s in sessions if isinstance(s, - gajim.default_session_type)] + # filter out everything except the default session type + chat_sessions = [s for s in sessions if isinstance(s, + gajim.default_session_type)] - orphaned = [s for s in chat_sessions if not s.control] + orphaned = [s for s in chat_sessions if not s.control] - if resource: - orphaned = [s for s in orphaned if s.resource == resource] + if resource: + orphaned = [s for s in orphaned if s.resource == resource] - return orphaned[0] - except (KeyError, IndexError): - return None + return orphaned[0] + except (KeyError, IndexError): + return None - def make_new_session(self, jid, thread_id=None, type_='chat', cls=None): - """ - Create and register a new session + def make_new_session(self, jid, thread_id=None, type_='chat', cls=None): + """ + Create and register a new session - thread_id=None to generate one. - type_ should be 'chat' or 'pm'. - """ - if not cls: - cls = gajim.default_session_type + thread_id=None to generate one. + type_ should be 'chat' or 'pm'. + """ + if not cls: + cls = gajim.default_session_type - sess = cls(self, common.xmpp.JID(jid), thread_id, type_) + sess = cls(self, common.xmpp.JID(jid), thread_id, type_) - # determine if this session is a pm session - # if not, discard the resource so that all sessions are stored bare - if not type_ == 'pm': - jid = gajim.get_jid_without_resource(jid) + # determine if this session is a pm session + # if not, discard the resource so that all sessions are stored bare + if not type_ == 'pm': + jid = gajim.get_jid_without_resource(jid) - if not jid in self.sessions: - self.sessions[jid] = {} + if not jid in self.sessions: + self.sessions[jid] = {} - self.sessions[jid][sess.thread_id] = sess + self.sessions[jid][sess.thread_id] = sess - return sess + return sess class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, - ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionPEP, - ConnectionCaps, ConnectionHandlersBase, ConnectionJingle): - def __init__(self): - global HAS_IDLE - ConnectionVcard.__init__(self) - ConnectionBytestream.__init__(self) - ConnectionCommands.__init__(self) - ConnectionPubSub.__init__(self) - ConnectionPEP.__init__(self, account=self.name, dispatcher=self, - pubsub_connection=self) - ConnectionCaps.__init__(self, account=self.name, - dispatch_event=self.dispatch, capscache=capscache.capscache, - client_caps_factory=capscache.create_suitable_client_caps) - ConnectionJingle.__init__(self) - ConnectionHandlersBase.__init__(self) - self.gmail_url = None - - # keep the latest subscribed event for each jid to prevent loop when we - # acknowledge presences - self.subscribed_events = {} - # IDs of jabber:iq:last requests - self.last_ids = [] - # IDs of jabber:iq:version requests - self.version_ids = [] - # IDs of urn:xmpp:time requests - self.entity_time_ids = [] - # ID of urn:xmpp:ping requests - self.awaiting_xmpp_ping_id = None - self.continue_connect_info = None - - try: - self.sleeper = common.sleepy.Sleepy() -# idle.init() - HAS_IDLE = True - except Exception: - HAS_IDLE = False - - self.gmail_last_tid = None - self.gmail_last_time = None - - def build_http_auth_answer(self, iq_obj, answer): - if not self.connection or self.connected < 2: - return - if answer == 'yes': - self.connection.send(iq_obj.buildReply('result')) - elif answer == 'no': - err = common.xmpp.Error(iq_obj, - common.xmpp.protocol.ERR_NOT_AUTHORIZED) - self.connection.send(err) - - def _HttpAuthCB(self, con, iq_obj): - log.debug('HttpAuthCB') - opt = gajim.config.get_per('accounts', self.name, 'http_auth') - if opt in ('yes', 'no'): - self.build_http_auth_answer(iq_obj, opt) - else: - id_ = iq_obj.getTagAttr('confirm', 'id') - method = iq_obj.getTagAttr('confirm', 'method') - url = iq_obj.getTagAttr('confirm', 'url') - msg = iq_obj.getTagData('body') # In case it's a message with a body - self.dispatch('HTTP_AUTH', (method, url, id_, iq_obj, msg)) - raise common.xmpp.NodeProcessed - - def _ErrorCB(self, con, iq_obj): - log.debug('ErrorCB') - jid_from = helpers.get_full_jid_from_iq(iq_obj) - jid_stripped, resource = gajim.get_room_and_nick_from_fjid(jid_from) - id_ = unicode(iq_obj.getID()) - if id_ in self.version_ids: - self.dispatch('OS_INFO', (jid_stripped, resource, '', '')) - self.version_ids.remove(id_) - return - if id_ in self.last_ids: - self.dispatch('LAST_STATUS_TIME', (jid_stripped, resource, -1, '')) - self.last_ids.remove(id_) - return - if id_ in self.entity_time_ids: - self.dispatch('ENTITY_TIME', (jid_stripped, resource, '')) - self.entity_time_ids.remove(id_) - return - errmsg = iq_obj.getErrorMsg() - errcode = iq_obj.getErrorCode() - self.dispatch('ERROR_ANSWER', (id_, jid_from, errmsg, errcode)) - - def _PrivateCB(self, con, iq_obj): - """ - Private Data (XEP 048 and 049) - """ - log.debug('PrivateCB') - query = iq_obj.getTag('query') - storage = query.getTag('storage') - if storage: - ns = storage.getNamespace() - if ns == 'storage:bookmarks': - self._parse_bookmarks(storage, 'xml') - elif ns == 'gajim:prefs': - # Preferences data - # http://www.xmpp.org/extensions/xep-0049.html - #TODO: implement this - pass - elif ns == 'storage:rosternotes': - # Annotations - # http://www.xmpp.org/extensions/xep-0145.html - notes = storage.getTags('note') - for note in notes: - try: - jid = helpers.parse_jid(note.getAttr('jid')) - except common.helpers.InvalidFormat: - log.warn('Invalid JID: %s, ignoring it' % note.getAttr('jid')) - continue - annotation = note.getData() - self.annotations[jid] = annotation - - def _parse_bookmarks(self, storage, storage_type): - """ - storage_type can be 'pubsub' or 'xml' to tell from where we got bookmarks - """ - # Bookmarked URLs and Conferences - # http://www.xmpp.org/extensions/xep-0048.html - resend_to_pubsub = False - confs = storage.getTags('conference') - for conf in confs: - autojoin_val = conf.getAttr('autojoin') - if autojoin_val is None: # not there (it's optional) - autojoin_val = False - minimize_val = conf.getAttr('minimize') - if minimize_val is None: # not there (it's optional) - minimize_val = False - print_status = conf.getTagData('print_status') - if not print_status: - print_status = conf.getTagData('show_status') - try: - bm = {'name': conf.getAttr('name'), - 'jid': helpers.parse_jid(conf.getAttr('jid')), - 'autojoin': autojoin_val, - 'minimize': minimize_val, - 'password': conf.getTagData('password'), - 'nick': conf.getTagData('nick'), - 'print_status': print_status} - except common.helpers.InvalidFormat: - log.warn('Invalid JID: %s, ignoring it' % conf.getAttr('jid')) - continue - - if bm not in self.bookmarks: - self.bookmarks.append(bm) - if storage_type == 'xml': - # We got a bookmark that was not in pubsub - resend_to_pubsub = True - self.dispatch('BOOKMARKS', self.bookmarks) - if storage_type == 'pubsub': - # We gor bookmarks from pubsub, now get those from xml to merge them - self.get_bookmarks(storage_type='xml') - if self.pubsub_supported and resend_to_pubsub: - self.store_bookmarks('pubsub') - - def _rosterSetCB(self, con, iq_obj): - log.debug('rosterSetCB') - version = iq_obj.getTagAttr('query', 'ver') - for item in iq_obj.getTag('query').getChildren(): - try: - jid = helpers.parse_jid(item.getAttr('jid')) - except common.helpers.InvalidFormat: - log.warn('Invalid JID: %s, ignoring it' % item.getAttr('jid')) - continue - name = item.getAttr('name') - sub = item.getAttr('subscription') - ask = item.getAttr('ask') - groups = [] - for group in item.getTags('group'): - groups.append(group.getData()) - self.dispatch('ROSTER_INFO', (jid, name, sub, ask, groups)) - account_jid = gajim.get_jid_from_account(self.name) - gajim.logger.add_or_update_contact(account_jid, jid, name, sub, ask, - groups) - if version: - gajim.config.set_per('accounts', self.name, 'roster_version', - version) - if not self.connection or self.connected < 2: - raise common.xmpp.NodeProcessed - reply = common.xmpp.Iq(typ='result', attrs={'id': iq_obj.getID()}, - to=iq_obj.getFrom(), frm=iq_obj.getTo(), xmlns=None) - self.connection.send(reply) - raise common.xmpp.NodeProcessed - - def _VersionCB(self, con, iq_obj): - log.debug('VersionCB') - if not self.connection or self.connected < 2: - return - iq_obj = iq_obj.buildReply('result') - qp = iq_obj.getTag('query') - qp.setTagData('name', 'Gajim') - qp.setTagData('version', gajim.version) - send_os = gajim.config.get_per('accounts', self.name, 'send_os_info') - if send_os: - qp.setTagData('os', helpers.get_os_info()) - self.connection.send(iq_obj) - raise common.xmpp.NodeProcessed - - def _LastCB(self, con, iq_obj): - global HAS_IDLE - log.debug('LastCB') - if not self.connection or self.connected < 2: - return - if HAS_IDLE and gajim.config.get_per('accounts', self.name, - 'send_idle_time'): - iq_obj = iq_obj.buildReply('result') - qp = iq_obj.getTag('query') - qp.attrs['seconds'] = int(self.sleeper.getIdleSec()) - else: - iq_obj = iq_obj.buildReply('error') - err = common.xmpp.ErrorNode(name=common.xmpp.NS_STANZAS+' service-unavailable') - iq_obj.addChild(node=err) - - self.connection.send(iq_obj) - raise common.xmpp.NodeProcessed - - def _LastResultCB(self, con, iq_obj): - log.debug('LastResultCB') - qp = iq_obj.getTag('query') - seconds = qp.getAttr('seconds') - status = qp.getData() - try: - seconds = int(seconds) - except Exception: - return - id_ = iq_obj.getID() - if id_ in self.groupchat_jids: - who = self.groupchat_jids[id_] - del self.groupchat_jids[id_] - else: - who = helpers.get_full_jid_from_iq(iq_obj) - if id_ in self.last_ids: - self.last_ids.remove(id_) - jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) - self.dispatch('LAST_STATUS_TIME', (jid_stripped, resource, seconds, status)) - - def _VersionResultCB(self, con, iq_obj): - log.debug('VersionResultCB') - client_info = '' - os_info = '' - qp = iq_obj.getTag('query') - if qp.getTag('name'): - client_info += qp.getTag('name').getData() - if qp.getTag('version'): - client_info += ' ' + qp.getTag('version').getData() - if qp.getTag('os'): - os_info += qp.getTag('os').getData() - id_ = iq_obj.getID() - if id_ in self.groupchat_jids: - who = self.groupchat_jids[id_] - del self.groupchat_jids[id_] - else: - who = helpers.get_full_jid_from_iq(iq_obj) - jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) - if id_ in self.version_ids: - self.version_ids.remove(id_) - self.dispatch('OS_INFO', (jid_stripped, resource, client_info, os_info)) - - def _TimeCB(self, con, iq_obj): - log.debug('TimeCB') - if not self.connection or self.connected < 2: - return - iq_obj = iq_obj.buildReply('result') - qp = iq_obj.getTag('query') - qp.setTagData('utc', strftime('%Y%m%dT%H:%M:%S', gmtime())) - qp.setTagData('tz', helpers.decode_string(tzname[daylight])) - qp.setTagData('display', helpers.decode_string(strftime('%c', - localtime()))) - self.connection.send(iq_obj) - raise common.xmpp.NodeProcessed - - def _TimeRevisedCB(self, con, iq_obj): - log.debug('TimeRevisedCB') - if not self.connection or self.connected < 2: - return - iq_obj = iq_obj.buildReply('result') - qp = iq_obj.setTag('time', - namespace=common.xmpp.NS_TIME_REVISED) - qp.setTagData('utc', strftime('%Y-%m-%dT%H:%M:%SZ', gmtime())) - isdst = localtime().tm_isdst - zone = -(timezone, altzone)[isdst] / 60 - tzo = (zone / 60, abs(zone % 60)) - qp.setTagData('tzo', '%+03d:%02d' % (tzo)) - self.connection.send(iq_obj) - raise common.xmpp.NodeProcessed - - def _TimeRevisedResultCB(self, con, iq_obj): - log.debug('TimeRevisedResultCB') - time_info = '' - qp = iq_obj.getTag('time') - if not qp: - # wrong answer - return - tzo = qp.getTag('tzo').getData() - if tzo.lower() == 'z': - tzo = '0:0' - tzoh, tzom = tzo.split(':') - utc_time = qp.getTag('utc').getData() - ZERO = datetime.timedelta(0) - class UTC(datetime.tzinfo): - def utcoffset(self, dt): - return ZERO - def tzname(self, dt): - return "UTC" - def dst(self, dt): - return ZERO - - class contact_tz(datetime.tzinfo): - def utcoffset(self, dt): - return datetime.timedelta(hours=int(tzoh), minutes=int(tzom)) - def tzname(self, dt): - return "remote timezone" - def dst(self, dt): - return ZERO - - try: - t = datetime.datetime.strptime(utc_time, '%Y-%m-%dT%H:%M:%SZ') - t = t.replace(tzinfo=UTC()) - time_info = t.astimezone(contact_tz()).strftime('%c') - except ValueError, e: - log.info('Wrong time format: %s' % str(e)) - - id_ = iq_obj.getID() - if id_ in self.groupchat_jids: - who = self.groupchat_jids[id_] - del self.groupchat_jids[id_] - else: - who = helpers.get_full_jid_from_iq(iq_obj) - jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) - if id_ in self.entity_time_ids: - self.entity_time_ids.remove(id_) - self.dispatch('ENTITY_TIME', (jid_stripped, resource, time_info)) - - def _gMailNewMailCB(self, con, gm): - """ - Called when we get notified of new mail messages in gmail account - """ - if not self.connection or self.connected < 2: - return - if not gm.getTag('new-mail'): - return - if gm.getTag('new-mail').getNamespace() == common.xmpp.NS_GMAILNOTIFY: - # we'll now ask the server for the exact number of new messages - jid = gajim.get_jid_from_account(self.name) - log.debug('Got notification of new gmail e-mail on %s. Asking the server for more info.' % jid) - iq = common.xmpp.Iq(typ = 'get') - iq.setID(self.connection.getAnID()) - query = iq.setTag('query') - query.setNamespace(common.xmpp.NS_GMAILNOTIFY) - # we want only be notified about newer mails - if self.gmail_last_tid: - query.setAttr('newer-than-tid', self.gmail_last_tid) - if self.gmail_last_time: - query.setAttr('newer-than-time', self.gmail_last_time) - self.connection.send(iq) - raise common.xmpp.NodeProcessed - - def _gMailQueryCB(self, con, gm): - """ - Called when we receive results from Querying the server for mail messages - in gmail account - """ - if not gm.getTag('mailbox'): - return - self.gmail_url = gm.getTag('mailbox').getAttr('url') - if gm.getTag('mailbox').getNamespace() == common.xmpp.NS_GMAILNOTIFY: - newmsgs = gm.getTag('mailbox').getAttr('total-matched') - if newmsgs != '0': - # there are new messages - gmail_messages_list = [] - if gm.getTag('mailbox').getTag('mail-thread-info'): - gmail_messages = gm.getTag('mailbox').getTags('mail-thread-info') - for gmessage in gmail_messages: - unread_senders = [] - for sender in gmessage.getTag('senders').getTags('sender'): - if sender.getAttr('unread') != '1': - continue - if sender.getAttr('name'): - unread_senders.append(sender.getAttr('name') + '< ' + \ - sender.getAttr('address') + '>') - else: - unread_senders.append(sender.getAttr('address')) - - if not unread_senders: - continue - gmail_subject = gmessage.getTag('subject').getData() - gmail_snippet = gmessage.getTag('snippet').getData() - tid = int(gmessage.getAttr('tid')) - if not self.gmail_last_tid or tid > self.gmail_last_tid: - self.gmail_last_tid = tid - gmail_messages_list.append({ \ - 'From': unread_senders, \ - 'Subject': gmail_subject, \ - 'Snippet': gmail_snippet, \ - 'url': gmessage.getAttr('url'), \ - 'participation': gmessage.getAttr('participation'), \ - 'messages': gmessage.getAttr('messages'), \ - 'date': gmessage.getAttr('date')}) - self.gmail_last_time = int(gm.getTag('mailbox').getAttr( - 'result-time')) - - jid = gajim.get_jid_from_account(self.name) - log.debug(('You have %s new gmail e-mails on %s.') % (newmsgs, jid)) - self.dispatch('GMAIL_NOTIFY', (jid, newmsgs, gmail_messages_list)) - raise common.xmpp.NodeProcessed - - def _rosterItemExchangeCB(self, con, msg): - """ - XEP-0144 Roster Item Echange - """ - exchange_items_list = {} - jid_from = helpers.get_full_jid_from_iq(msg) - items_list = msg.getTag('x').getChildren() - if not items_list: - return - action = items_list[0].getAttr('action') - if action == None: - action = 'add' - for item in msg.getTag('x', - namespace=common.xmpp.NS_ROSTERX).getChildren(): - try: - jid = helpers.parse_jid(item.getAttr('jid')) - except common.helpers.InvalidFormat: - log.warn('Invalid JID: %s, ignoring it' % item.getAttr('jid')) - continue - name = item.getAttr('name') - contact = gajim.contacts.get_contact(self.name, jid) - groups = [] - same_groups = True - for group in item.getTags('group'): - groups.append(group.getData()) - # check that all suggested groups are in the groups we have for this - # contact - if not contact or group not in contact.groups: - same_groups = False - if contact: - # check that all groups we have for this contact are in the - # suggested groups - for group in contact.groups: - if group not in groups: - same_groups = False - if contact.sub in ('both', 'to') and same_groups: - continue - exchange_items_list[jid] = [] - exchange_items_list[jid].append(name) - exchange_items_list[jid].append(groups) - if exchange_items_list: - self.dispatch('ROSTERX', (action, exchange_items_list, jid_from)) - raise common.xmpp.NodeProcessed - - def _messageCB(self, con, msg): - """ - Called when we receive a message - """ - log.debug('MessageCB') - - gajim.nec.push_incoming_event(NetworkEvent('raw-message-received', - conn = con, - xmpp_msg = msg, - account = self.name)) - - mtype = msg.getType() - - - # check if the message is a roster item exchange (XEP-0144) - if msg.getTag('x', namespace=common.xmpp.NS_ROSTERX): - self._rosterItemExchangeCB(con, msg) - return - - # check if the message is a XEP-0070 confirmation request - if msg.getTag('confirm', namespace=common.xmpp.NS_HTTP_AUTH): - self._HttpAuthCB(con, msg) - return - - try: - frm = helpers.get_full_jid_from_iq(msg) - jid = helpers.get_jid_from_iq(msg) - except helpers.InvalidFormat: - self.dispatch('ERROR', (_('Invalid Jabber ID'), - _('A message from a non-valid JID arrived, it has been ignored.'))) - return - - addressTag = msg.getTag('addresses', namespace = common.xmpp.NS_ADDRESS) - - # Be sure it comes from one of our resource, else ignore address element - if addressTag and jid == gajim.get_jid_from_account(self.name): - address = addressTag.getTag('address', attrs={'type': 'ofrom'}) - if address: - try: - frm = helpers.parse_jid(address.getAttr('jid')) - except common.helpers.InvalidFormat: - log.warn('Invalid JID: %s, ignoring it' % address.getAttr('jid')) - return - jid = gajim.get_jid_without_resource(frm) - - # invitations - invite = None - encTag = msg.getTag('x', namespace=common.xmpp.NS_ENCRYPTED) - - if not encTag: - invite = msg.getTag('x', namespace = common.xmpp.NS_MUC_USER) - if invite and not invite.getTag('invite'): - invite = None - - # FIXME: Msn transport (CMSN1.2.1 and PyMSN0.10) do NOT RECOMMENDED - # invitation - # stanza (MUC XEP) remove in 2007, as we do not do NOT RECOMMENDED - xtags = msg.getTags('x') - for xtag in xtags: - if xtag.getNamespace() == common.xmpp.NS_CONFERENCE and not invite: - try: - room_jid = helpers.parse_jid(xtag.getAttr('jid')) - except common.helpers.InvalidFormat: - log.warn('Invalid JID: %s, ignoring it' % xtag.getAttr('jid')) - continue - is_continued = False - if xtag.getTag('continue'): - is_continued = True - self.dispatch('GC_INVITATION', (room_jid, frm, '', None, - is_continued)) - return - - thread_id = msg.getThread() - - if not mtype: - mtype = 'normal' - - msgtxt = msg.getBody() - - encrypted = False - xep_200_encrypted = msg.getTag('c', namespace=common.xmpp.NS_STANZA_CRYPTO) - - session = None - if mtype != 'groupchat': - session = self.get_or_create_session(frm, thread_id) - - if thread_id and not session.received_thread_id: - session.received_thread_id = True - - session.last_receive = time_time() - - # check if the message is a XEP-0020 feature negotiation request - if msg.getTag('feature', namespace=common.xmpp.NS_FEATURE): - if gajim.HAVE_PYCRYPTO: - feature = msg.getTag(name='feature', namespace=common.xmpp.NS_FEATURE) - form = common.xmpp.DataForm(node=feature.getTag('x')) - - if form['FORM_TYPE'] == 'urn:xmpp:ssn': - session.handle_negotiation(form) - else: - reply = msg.buildReply() - reply.setType('error') - - reply.addChild(feature) - err = common.xmpp.ErrorNode('service-unavailable', typ='cancel') - reply.addChild(node=err) - - con.send(reply) - - raise common.xmpp.NodeProcessed - - return - - if msg.getTag('init', namespace=common.xmpp.NS_ESESSION_INIT): - init = msg.getTag(name='init', namespace=common.xmpp.NS_ESESSION_INIT) - form = common.xmpp.DataForm(node=init.getTag('x')) - - session.handle_negotiation(form) - - raise common.xmpp.NodeProcessed - - tim = msg.getTimestamp() - tim = helpers.datetime_tuple(tim) - tim = localtime(timegm(tim)) - - if xep_200_encrypted: - encrypted = 'xep200' - - try: - msg = session.decrypt_stanza(msg) - msgtxt = msg.getBody() - except Exception: - self.dispatch('FAILED_DECRYPT', (frm, tim, session)) - - # Receipt requested - # TODO: We shouldn't answer if we're invisible! - contact = gajim.contacts.get_contact(self.name, jid) - nick = gajim.get_room_and_nick_from_fjid(frm)[1] - gc_contact = gajim.contacts.get_gc_contact(self.name, jid, nick) - if msg.getTag('request', namespace=common.xmpp.NS_RECEIPTS) \ - and gajim.config.get_per('accounts', self.name, - 'answer_receipts') and ((contact and contact.sub \ - not in (u'to', u'none')) or gc_contact) and mtype != 'error': - receipt = common.xmpp.Message(to=frm, typ='chat') - receipt.setID(msg.getID()) - receipt.setTag('received', - namespace='urn:xmpp:receipts') - - if thread_id: - receipt.setThread(thread_id) - con.send(receipt) - - # We got our message's receipt - if msg.getTag('received', namespace=common.xmpp.NS_RECEIPTS) and \ - session.control and gajim.config.get_per('accounts', self.name, - 'request_receipt'): - session.control.conv_textview.hide_xep0184_warning(msg.getID()) - - if encTag and self.USE_GPG: - encmsg = encTag.getData() - - keyID = gajim.config.get_per('accounts', self.name, 'keyid') - if keyID: - def decrypt_thread(encmsg, keyID): - decmsg = self.gpg.decrypt(encmsg, keyID) - # \x00 chars are not allowed in C (so in GTK) - msgtxt = helpers.decode_string(decmsg.replace('\x00', '')) - encrypted = 'xep27' - return (msgtxt, encrypted) - gajim.thread_interface(decrypt_thread, [encmsg, keyID], - self._on_message_decrypted, [mtype, msg, session, frm, jid, - invite, tim]) - return - self._on_message_decrypted((msgtxt, encrypted), mtype, msg, session, frm, - jid, invite, tim) - - def _on_message_decrypted(self, output, mtype, msg, session, frm, jid, - invite, tim): - msgtxt, encrypted = output - if mtype == 'error': - self.dispatch_error_message(msg, msgtxt, session, frm, tim) - elif mtype == 'groupchat': - self.dispatch_gc_message(msg, frm, msgtxt, jid, tim) - elif invite is not None: - self.dispatch_invite_message(invite, frm) - else: - if isinstance(session, gajim.default_session_type): - session.received(frm, msgtxt, tim, encrypted, msg) - else: - session.received(msg) - # END messageCB - - # process and dispatch an error message - def dispatch_error_message(self, msg, msgtxt, session, frm, tim): - error_msg = msg.getErrorMsg() - - if not error_msg: - error_msg = msgtxt - msgtxt = None - - subject = msg.getSubject() - - if session.is_loggable(): - try: - gajim.logger.write('error', frm, error_msg, tim=tim, - subject=subject) - except exceptions.PysqliteOperationalError, e: - self.dispatch('ERROR', (_('Disk Write Error'), str(e))) - except exceptions.DatabaseMalformed: - pritext = _('Database Error') - sectext = _('The database file (%s) cannot be read. Try to repair ' - 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove ' - 'it (all history will be lost).') % common.logger.LOG_DB_PATH - self.dispatch('ERROR', (pritext, sectext)) - self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt, - tim, session)) - - # process and dispatch a groupchat message - def dispatch_gc_message(self, msg, frm, msgtxt, jid, tim): - has_timestamp = bool(msg.timestamp) - - subject = msg.getSubject() - - if subject is not None: - self.dispatch('GC_SUBJECT', (frm, subject, msgtxt, has_timestamp)) - return - - statusCode = msg.getStatusCode() - - if not msg.getTag('body'): # no - # It could be a config change. See - # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify - if msg.getTag('x'): - if statusCode != []: - self.dispatch('GC_CONFIG_CHANGE', (jid, statusCode)) - return - - # Ignore message from room in which we are not - if jid not in self.last_history_time: - return - - self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp, msg.getXHTML(), - statusCode)) - - tim_int = int(float(mktime(tim))) - if gajim.config.should_log(self.name, jid) and not \ - tim_int <= self.last_history_time[jid] and msgtxt and frm.find('/') >= 0: - # if frm.find('/') < 0, it means message comes from room itself - # usually it hold description and can be send at each connection - # so don't store it in logs - try: - gajim.logger.write('gc_msg', frm, msgtxt, tim=tim) - # store in memory time of last message logged. - # this will also be saved in rooms_last_message_time table - # when we quit this muc - self.last_history_time[jid] = mktime(tim) - - except exceptions.PysqliteOperationalError, e: - self.dispatch('ERROR', (_('Disk Write Error'), str(e))) - except exceptions.DatabaseMalformed: - pritext = _('Database Error') - sectext = _('The database file (%s) cannot be read. Try to repair ' - 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove ' - 'it (all history will be lost).') % common.logger.LOG_DB_PATH - self.dispatch('ERROR', (pritext, sectext)) - - def dispatch_invite_message(self, invite, frm): - item = invite.getTag('invite') - try: - jid_from = helpers.parse_jid(item.getAttr('from')) - except common.helpers.InvalidFormat: - log.warn('Invalid JID: %s, ignoring it' % item.getAttr('from')) - return - reason = item.getTagData('reason') - item = invite.getTag('password') - password = invite.getTagData('password') - - is_continued = False - if invite.getTag('invite').getTag('continue'): - is_continued = True - self.dispatch('GC_INVITATION',(frm, jid_from, reason, password, - is_continued)) - - def _presenceCB(self, con, prs): - """ - Called when we receive a presence - """ - gajim.nec.push_incoming_event(NetworkEvent('raw-pres-received', - conn=con, xmpp_pres=prs)) - ptype = prs.getType() - if ptype == 'available': - ptype = None - rfc_types = ('unavailable', 'error', 'subscribe', 'subscribed', - 'unsubscribe', 'unsubscribed') - if ptype and not ptype in rfc_types: - ptype = None - log.debug('PresenceCB: %s' % ptype) - if not self.connection or self.connected < 2: - log.debug('account is no more connected') - return - try: - who = helpers.get_full_jid_from_iq(prs) - except Exception: - if prs.getTag('error') and prs.getTag('error').getTag('jid-malformed'): - # wrong jid, we probably tried to change our nick in a room to a non - # valid one - who = str(prs.getFrom()) - jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) - self.dispatch('GC_MSG', (jid_stripped, - _('Nickname not allowed: %s') % resource, None, False, None, [])) - return - jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) - timestamp = None - id_ = prs.getID() - is_gc = False # is it a GC presence ? - sigTag = None - ns_muc_user_x = None - avatar_sha = None - # XEP-0172 User Nickname - user_nick = prs.getTagData('nick') - if not user_nick: - user_nick = '' - contact_nickname = None - transport_auto_auth = False - # XEP-0203 - delay_tag = prs.getTag('delay', namespace=common.xmpp.NS_DELAY2) - if delay_tag: - tim = prs.getTimestamp2() - tim = helpers.datetime_tuple(tim) - timestamp = localtime(timegm(tim)) - xtags = prs.getTags('x') - for x in xtags: - namespace = x.getNamespace() - if namespace.startswith(common.xmpp.NS_MUC): - is_gc = True - if namespace == common.xmpp.NS_MUC_USER and x.getTag('destroy'): - ns_muc_user_x = x - elif namespace == common.xmpp.NS_SIGNED: - sigTag = x - elif namespace == common.xmpp.NS_VCARD_UPDATE: - avatar_sha = x.getTagData('photo') - contact_nickname = x.getTagData('nickname') - elif namespace == common.xmpp.NS_DELAY and not timestamp: - # XEP-0091 - tim = prs.getTimestamp() - tim = helpers.datetime_tuple(tim) - timestamp = localtime(timegm(tim)) - elif namespace == 'http://delx.cjb.net/protocol/roster-subsync': - # see http://trac.gajim.org/ticket/326 - agent = gajim.get_server_from_jid(jid_stripped) - if self.connection.getRoster().getItem(agent): # to be sure it's a transport contact - transport_auto_auth = True - - if not is_gc and id_ and id_.startswith('gajim_muc_') and \ - ptype == 'error': - # Error presences may not include sent stanza, so we don't detect it's - # a muc preence. So detect it by ID - h = hmac.new(self.secret_hmac, jid_stripped).hexdigest()[:6] - if id_.split('_')[-1] == h: - is_gc = True - status = prs.getStatus() or '' - show = prs.getShow() - if show not in ('chat', 'away', 'xa', 'dnd'): - show = '' # We ignore unknown show - if not ptype and not show: - show = 'online' - elif ptype == 'unavailable': - show = 'offline' - - prio = prs.getPriority() - try: - prio = int(prio) - except Exception: - prio = 0 - keyID = '' - if sigTag and self.USE_GPG and ptype != 'error': - # error presences contain our own signature - # verify - sigmsg = sigTag.getData() - keyID = self.gpg.verify(status, sigmsg) - - if is_gc: - if ptype == 'error': - errcon = prs.getError() - errmsg = prs.getErrorMsg() - errcode = prs.getErrorCode() - room_jid, nick = gajim.get_room_and_nick_from_fjid(who) - - gc_control = gajim.interface.msg_win_mgr.get_gc_control(room_jid, - self.name) - - # If gc_control is missing - it may be minimized. Try to get it from - # there. If it's not there - then it's missing anyway and will - # remain set to None. - if gc_control is None: - minimized = gajim.interface.minimized_controls[self.name] - gc_control = minimized.get(room_jid) - - if errcode == '502': - # Internal Timeout: - self.dispatch('NOTIFY', (jid_stripped, 'error', errmsg, resource, - prio, keyID, timestamp, None)) - elif (errcode == '503'): - # maximum user number reached - self.dispatch('ERROR', (_('Unable to join group chat'), - _('Maximum number of users for %s has been reached') % \ - room_jid)) - elif (errcode == '401') or (errcon == 'not-authorized'): - # password required to join - self.dispatch('GC_PASSWORD_REQUIRED', (room_jid, nick)) - elif (errcode == '403') or (errcon == 'forbidden'): - # we are banned - self.dispatch('ERROR', (_('Unable to join group chat'), - _('You are banned from group chat %s.') % room_jid)) - elif (errcode == '404') or (errcon in ('item-not-found', - 'remote-server-not-found')): - if gc_control is None or gc_control.autorejoin is None: - # group chat does not exist - self.dispatch('ERROR', (_('Unable to join group chat'), - _('Group chat %s does not exist.') % room_jid)) - elif (errcode == '405') or (errcon == 'not-allowed'): - self.dispatch('ERROR', (_('Unable to join group chat'), - _('Group chat creation is restricted.'))) - elif (errcode == '406') or (errcon == 'not-acceptable'): - self.dispatch('ERROR', (_('Unable to join group chat'), - _('Your registered nickname must be used in group chat %s.') \ - % room_jid)) - elif (errcode == '407') or (errcon == 'registration-required'): - self.dispatch('ERROR', (_('Unable to join group chat'), - _('You are not in the members list in groupchat %s.') % \ - room_jid)) - elif (errcode == '409') or (errcon == 'conflict'): - # nick conflict - room_jid = gajim.get_room_from_fjid(who) - self.dispatch('ASK_NEW_NICK', (room_jid,)) - else: # print in the window the error - self.dispatch('ERROR_ANSWER', ('', jid_stripped, - errmsg, errcode)) - if not ptype or ptype == 'unavailable': - if gajim.config.get('log_contact_status_changes') and \ - gajim.config.should_log(self.name, jid_stripped): - gc_c = gajim.contacts.get_gc_contact(self.name, jid_stripped, - resource) - st = status or '' - if gc_c: - jid = gc_c.jid - else: - jid = prs.getJid() - if jid: - # we know real jid, save it in db - st += ' (%s)' % jid - try: - gajim.logger.write('gcstatus', who, st, show) - except exceptions.PysqliteOperationalError, e: - self.dispatch('ERROR', (_('Disk Write Error'), str(e))) - except exceptions.DatabaseMalformed: - pritext = _('Database Error') - sectext = _('The database file (%s) cannot be read. Try to ' - 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)' - ' or remove it (all history will be lost).') % \ - common.logger.LOG_DB_PATH - self.dispatch('ERROR', (pritext, sectext)) - if avatar_sha or avatar_sha == '': - if avatar_sha == '': - # contact has no avatar - puny_nick = helpers.sanitize_filename(resource) - gajim.interface.remove_avatar_files(jid_stripped, puny_nick) - # if it's a gc presence, don't ask vcard here. We may ask it to - # real jid in gui part. - if ns_muc_user_x: - # Room has been destroyed. see - # http://www.xmpp.org/extensions/xep-0045.html#destroyroom - reason = _('Room has been destroyed') - destroy = ns_muc_user_x.getTag('destroy') - r = destroy.getTagData('reason') - if r: - reason += ' (%s)' % r - if destroy.getAttr('jid'): - try: - jid = helpers.parse_jid(destroy.getAttr('jid')) - reason += '\n' + _('You can join this room instead: %s') \ - % jid - except common.helpers.InvalidFormat: - pass - statusCode = ['destroyed'] - else: - reason = prs.getReason() - statusCode = prs.getStatusCode() - self.dispatch('GC_NOTIFY', (jid_stripped, show, status, resource, - prs.getRole(), prs.getAffiliation(), prs.getJid(), - reason, prs.getActor(), statusCode, prs.getNewNick(), - avatar_sha)) - return - - if ptype == 'subscribe': - log.debug('subscribe request from %s' % who) - if who.find('@') <= 0 and who in self.agent_registrations: - self.agent_registrations[who]['sub_received'] = True - if not self.agent_registrations[who]['roster_push']: - # We'll reply after roster push result - return - if gajim.config.get_per('accounts', self.name, 'autoauth') or \ - who.find('@') <= 0 or jid_stripped in self.jids_for_auto_auth or \ - transport_auto_auth: - if self.connection: - p = common.xmpp.Presence(who, 'subscribed') - p = self.add_sha(p) - self.connection.send(p) - if who.find('@') <= 0 or transport_auto_auth: - self.dispatch('NOTIFY', (jid_stripped, 'offline', 'offline', - resource, prio, keyID, timestamp, None)) - if transport_auto_auth: - self.automatically_added.append(jid_stripped) - self.request_subscription(jid_stripped, name = user_nick) - else: - if not status: - status = _('I would like to add you to my roster.') - self.dispatch('SUBSCRIBE', (jid_stripped, status, user_nick)) - elif ptype == 'subscribed': - if jid_stripped in self.automatically_added: - self.automatically_added.remove(jid_stripped) - else: - # detect a subscription loop - if jid_stripped not in self.subscribed_events: - self.subscribed_events[jid_stripped] = [] - self.subscribed_events[jid_stripped].append(time_time()) - block = False - if len(self.subscribed_events[jid_stripped]) > 5: - if time_time() - self.subscribed_events[jid_stripped][0] < 5: - block = True - self.subscribed_events[jid_stripped] = self.subscribed_events[jid_stripped][1:] - if block: - gajim.config.set_per('account', self.name, - 'dont_ack_subscription', True) - else: - self.dispatch('SUBSCRIBED', (jid_stripped, resource)) - # BE CAREFUL: no con.updateRosterItem() in a callback - log.debug(_('we are now subscribed to %s') % who) - elif ptype == 'unsubscribe': - log.debug(_('unsubscribe request from %s') % who) - elif ptype == 'unsubscribed': - log.debug(_('we are now unsubscribed from %s') % who) - # detect a unsubscription loop - if jid_stripped not in self.subscribed_events: - self.subscribed_events[jid_stripped] = [] - self.subscribed_events[jid_stripped].append(time_time()) - block = False - if len(self.subscribed_events[jid_stripped]) > 5: - if time_time() - self.subscribed_events[jid_stripped][0] < 5: - block = True - self.subscribed_events[jid_stripped] = self.subscribed_events[jid_stripped][1:] - if block: - gajim.config.set_per('account', self.name, 'dont_ack_subscription', - True) - else: - self.dispatch('UNSUBSCRIBED', jid_stripped) - elif ptype == 'error': - errmsg = prs.getError() - errcode = prs.getErrorCode() - if errcode != '502': # Internal Timeout: - # print in the window the error - self.dispatch('ERROR_ANSWER', ('', jid_stripped, - errmsg, errcode)) - if errcode != '409': # conflict # See #5120 - self.dispatch('NOTIFY', (jid_stripped, 'error', errmsg, resource, - prio, keyID, timestamp, None)) - - if ptype == 'unavailable': - for jid in [jid_stripped, who]: - if jid not in self.sessions: - continue - # automatically terminate sessions that they haven't sent a thread - # ID in, only if other part support thread ID - for sess in self.sessions[jid].values(): - if not sess.received_thread_id: - contact = gajim.contacts.get_contact(self.name, jid) - # FIXME: I don't know if this is the correct behavior here. - # Anyway, it is the old behavior when we assumed that - # not-existing contacts don't support anything - contact_exists = bool(contact) - session_supported = contact_exists and ( - contact.supports(common.xmpp.NS_SSN) or - contact.supports(common.xmpp.NS_ESESSION)) - if session_supported: - sess.terminate() - del self.sessions[jid][sess.thread_id] - - if avatar_sha is not None and ptype != 'error': - if jid_stripped not in self.vcard_shas: - cached_vcard = self.get_cached_vcard(jid_stripped) - if cached_vcard and 'PHOTO' in cached_vcard and \ - 'SHA' in cached_vcard['PHOTO']: - self.vcard_shas[jid_stripped] = cached_vcard['PHOTO']['SHA'] - else: - self.vcard_shas[jid_stripped] = '' - if avatar_sha != self.vcard_shas[jid_stripped]: - # avatar has been updated - self.request_vcard(jid_stripped) - if not ptype or ptype == 'unavailable': - if gajim.config.get('log_contact_status_changes') and \ - gajim.config.should_log(self.name, jid_stripped): - try: - gajim.logger.write('status', jid_stripped, status, show) - except exceptions.PysqliteOperationalError, e: - self.dispatch('ERROR', (_('Disk Write Error'), str(e))) - except exceptions.DatabaseMalformed: - pritext = _('Database Error') - sectext = _('The database file (%s) cannot be read. Try to ' - 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup) ' - 'or remove it (all history will be lost).') % \ - common.logger.LOG_DB_PATH - self.dispatch('ERROR', (pritext, sectext)) - our_jid = gajim.get_jid_from_account(self.name) - if jid_stripped == our_jid and resource == self.server_resource: - # We got our own presence - self.dispatch('STATUS', show) - else: - self.dispatch('NOTIFY', (jid_stripped, show, status, resource, prio, - keyID, timestamp, contact_nickname)) - # END presenceCB - - def _StanzaArrivedCB(self, con, obj): - self.last_io = gajim.idlequeue.current_time() - - def _MucOwnerCB(self, con, iq_obj): - log.debug('MucOwnerCB') - qp = iq_obj.getQueryPayload() - node = None - for q in qp: - if q.getNamespace() == common.xmpp.NS_DATA: - node = q - if not node: - return - self.dispatch('GC_CONFIG', (helpers.get_full_jid_from_iq(iq_obj), node)) - - def _MucAdminCB(self, con, iq_obj): - log.debug('MucAdminCB') - items = iq_obj.getTag('query', namespace=common.xmpp.NS_MUC_ADMIN).\ - getTags('item') - users_dict = {} - for item in items: - if item.has_attr('jid') and item.has_attr('affiliation'): - try: - jid = helpers.parse_jid(item.getAttr('jid')) - except common.helpers.InvalidFormat: - log.warn('Invalid JID: %s, ignoring it' % item.getAttr('jid')) - continue - affiliation = item.getAttr('affiliation') - users_dict[jid] = {'affiliation': affiliation} - if item.has_attr('nick'): - users_dict[jid]['nick'] = item.getAttr('nick') - if item.has_attr('role'): - users_dict[jid]['role'] = item.getAttr('role') - reason = item.getTagData('reason') - if reason: - users_dict[jid]['reason'] = reason - - self.dispatch('GC_AFFILIATION', (helpers.get_full_jid_from_iq(iq_obj), - users_dict)) - - def _MucErrorCB(self, con, iq_obj): - log.debug('MucErrorCB') - jid = helpers.get_full_jid_from_iq(iq_obj) - errmsg = iq_obj.getError() - errcode = iq_obj.getErrorCode() - self.dispatch('MSGERROR', (jid, errcode, errmsg)) - - def _IqPingCB(self, con, iq_obj): - log.debug('IqPingCB') - if not self.connection or self.connected < 2: - return - iq_obj = iq_obj.buildReply('result') - self.connection.send(iq_obj) - raise common.xmpp.NodeProcessed - - def _PrivacySetCB(self, con, iq_obj): - """ - Privacy lists (XEP 016) - - A list has been set. - """ - log.debug('PrivacySetCB') - if not self.connection or self.connected < 2: - return - result = iq_obj.buildReply('result') - q = result.getTag('query') - if q: - result.delChild(q) - self.connection.send(result) - raise common.xmpp.NodeProcessed - - def _getRoster(self): - log.debug('getRosterCB') - if not self.connection: - return - self.connection.getRoster(self._on_roster_set) - self.discoverItems(gajim.config.get_per('accounts', self.name, - 'hostname'), id_prefix='Gajim_') - if gajim.config.get_per('accounts', self.name, 'use_ft_proxies'): - self.discover_ft_proxies() - - def discover_ft_proxies(self): - cfg_proxies = gajim.config.get_per('accounts', self.name, - 'file_transfer_proxies') - our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name) + '/' +\ - self.server_resource) - if cfg_proxies: - proxies = [e.strip() for e in cfg_proxies.split(',')] - for proxy in proxies: - gajim.proxy65_manager.resolve(proxy, self.connection, our_jid) - - def _on_roster_set(self, roster): - roster_version = roster.version - received_from_server = roster.received_from_server - raw_roster = roster.getRaw() - roster = {} - our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name)) - if self.connected > 1 and self.continue_connect_info: - msg = self.continue_connect_info[1] - sign_msg = self.continue_connect_info[2] - signed = '' - send_first_presence = True - if sign_msg: - signed = self.get_signed_presence(msg, self._send_first_presence) - if signed is None: - self.dispatch('GPG_PASSWORD_REQUIRED', - (self._send_first_presence,)) - # _send_first_presence will be called when user enter passphrase - send_first_presence = False - if send_first_presence: - self._send_first_presence(signed) - - for jid in raw_roster: - try: - j = helpers.parse_jid(jid) - except Exception: - print >> sys.stderr, _('JID %s is not RFC compliant. It will not be added to your roster. Use roster management tools such as http://jru.jabberstudio.org/ to remove it') % jid - else: - infos = raw_roster[jid] - if jid != our_jid and (not infos['subscription'] or \ - infos['subscription'] == 'none') and (not infos['ask'] or \ - infos['ask'] == 'none') and not infos['name'] and \ - not infos['groups']: - # remove this useless item, it won't be shown in roster anyway - self.connection.getRoster().delItem(jid) - elif jid != our_jid: # don't add our jid - roster[j] = raw_roster[jid] - if gajim.jid_is_transport(jid) and \ - not gajim.get_transport_name_from_jid(jid): - # we can't determine which iconset to use - self.discoverInfo(jid) - - gajim.logger.replace_roster(self.name, roster_version, roster) - if received_from_server: - for contact in gajim.contacts.iter_contacts(self.name): - if not contact.is_groupchat() and contact.jid not in roster and \ - contact.jid != gajim.get_jid_from_account(self.name): - self.dispatch('ROSTER_INFO', (contact.jid, None, None, None, - ())) - for jid in roster: - self.dispatch('ROSTER_INFO', (jid, roster[jid]['name'], - roster[jid]['subscription'], roster[jid]['ask'], - roster[jid]['groups'])) - - def _send_first_presence(self, signed = ''): - show = self.continue_connect_info[0] - msg = self.continue_connect_info[1] - sign_msg = self.continue_connect_info[2] - if sign_msg and not signed: - signed = self.get_signed_presence(msg) - if signed is None: - self.dispatch('BAD_PASSPHRASE', ()) - self.USE_GPG = False - signed = '' - self.connected = gajim.SHOW_LIST.index(show) - sshow = helpers.get_xmpp_show(show) - # send our presence - if show == 'invisible': - self.send_invisible_presence(msg, signed, True) - return - if show not in ['offline', 'online', 'chat', 'away', 'xa', 'dnd']: - return - priority = gajim.get_priority(self.name, sshow) - our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name)) - vcard = self.get_cached_vcard(our_jid) - if vcard and 'PHOTO' in vcard and 'SHA' in vcard['PHOTO']: - self.vcard_sha = vcard['PHOTO']['SHA'] - p = common.xmpp.Presence(typ = None, priority = priority, show = sshow) - p = self.add_sha(p) - if msg: - p.setStatus(msg) - if signed: - p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed) - - if self.connection: - self.connection.send(p) - self.priority = priority - self.dispatch('STATUS', show) - if self.vcard_supported: - # ask our VCard - self.request_vcard(None) - - # Get bookmarks from private namespace - self.get_bookmarks() - - # Get annotations from private namespace - self.get_annotations() - - # Inform GUI we just signed in - self.dispatch('SIGNED_IN', ()) - self.send_awaiting_pep() - self.continue_connect_info = None - - def request_gmail_notifications(self): - if not self.connection or self.connected < 2: - return - # It's a gmail account, - # inform the server that we want e-mail notifications - our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name)) - log.debug(('%s is a gmail account. Setting option ' - 'to get e-mail notifications on the server.') % (our_jid)) - iq = common.xmpp.Iq(typ = 'set', to = our_jid) - iq.setAttr('id', 'MailNotify') - query = iq.setTag('usersetting') - query.setNamespace(common.xmpp.NS_GTALKSETTING) - query = query.setTag('mailnotifications') - query.setAttr('value', 'true') - self.connection.send(iq) - # Ask how many messages there are now - iq = common.xmpp.Iq(typ = 'get') - iq.setID(self.connection.getAnID()) - query = iq.setTag('query') - query.setNamespace(common.xmpp.NS_GMAILNOTIFY) - self.connection.send(iq) - - - def _search_fields_received(self, con, iq_obj): - jid = jid = helpers.get_jid_from_iq(iq_obj) - tag = iq_obj.getTag('query', namespace = common.xmpp.NS_SEARCH) - if not tag: - self.dispatch('SEARCH_FORM', (jid, None, False)) - return - df = tag.getTag('x', namespace = common.xmpp.NS_DATA) - if df: - self.dispatch('SEARCH_FORM', (jid, df, True)) - return - df = {} - for i in iq_obj.getQueryPayload(): - df[i.getName()] = i.getData() - self.dispatch('SEARCH_FORM', (jid, df, False)) - - def _StreamCB(self, con, obj): - if obj.getTag('conflict'): - # disconnected because of a resource conflict - self.dispatch('RESOURCE_CONFLICT', ()) - - def _register_handlers(self, con, con_type): - # try to find another way to register handlers in each class - # that defines handlers - con.RegisterHandler('message', self._messageCB) - con.RegisterHandler('presence', self._presenceCB) - con.RegisterHandler('presence', self._capsPresenceCB) - # We use makefirst so that this handler is called before _messageCB, and - # can prevent calling it when it's not needed. - # We also don't check for namespace, else it cannot stop _messageCB to be - # called - con.RegisterHandler('message', self._pubsubEventCB, makefirst=True) - con.RegisterHandler('iq', self._vCardCB, 'result', - common.xmpp.NS_VCARD) - con.RegisterHandler('iq', self._rosterSetCB, 'set', - common.xmpp.NS_ROSTER) - con.RegisterHandler('iq', self._siSetCB, 'set', - common.xmpp.NS_SI) - con.RegisterHandler('iq', self._rosterItemExchangeCB, 'set', - common.xmpp.NS_ROSTERX) - con.RegisterHandler('iq', self._siErrorCB, 'error', - common.xmpp.NS_SI) - con.RegisterHandler('iq', self._siResultCB, 'result', - common.xmpp.NS_SI) - con.RegisterHandler('iq', self._discoGetCB, 'get', - common.xmpp.NS_DISCO) - con.RegisterHandler('iq', self._bytestreamSetCB, 'set', - common.xmpp.NS_BYTESTREAM) - con.RegisterHandler('iq', self._bytestreamResultCB, 'result', - common.xmpp.NS_BYTESTREAM) - con.RegisterHandler('iq', self._bytestreamErrorCB, 'error', - common.xmpp.NS_BYTESTREAM) - con.RegisterHandler('iq', self._DiscoverItemsCB, 'result', - common.xmpp.NS_DISCO_ITEMS) - con.RegisterHandler('iq', self._DiscoverItemsErrorCB, 'error', - common.xmpp.NS_DISCO_ITEMS) - con.RegisterHandler('iq', self._DiscoverInfoCB, 'result', - common.xmpp.NS_DISCO_INFO) - con.RegisterHandler('iq', self._DiscoverInfoErrorCB, 'error', - common.xmpp.NS_DISCO_INFO) - con.RegisterHandler('iq', self._VersionCB, 'get', - common.xmpp.NS_VERSION) - con.RegisterHandler('iq', self._TimeCB, 'get', - common.xmpp.NS_TIME) - con.RegisterHandler('iq', self._TimeRevisedCB, 'get', - common.xmpp.NS_TIME_REVISED) - con.RegisterHandler('iq', self._LastCB, 'get', - common.xmpp.NS_LAST) - con.RegisterHandler('iq', self._LastResultCB, 'result', - common.xmpp.NS_LAST) - con.RegisterHandler('iq', self._VersionResultCB, 'result', - common.xmpp.NS_VERSION) - con.RegisterHandler('iq', self._TimeRevisedResultCB, 'result', - common.xmpp.NS_TIME_REVISED) - con.RegisterHandler('iq', self._MucOwnerCB, 'result', - common.xmpp.NS_MUC_OWNER) - con.RegisterHandler('iq', self._MucAdminCB, 'result', - common.xmpp.NS_MUC_ADMIN) - con.RegisterHandler('iq', self._PrivateCB, 'result', - common.xmpp.NS_PRIVATE) - con.RegisterHandler('iq', self._HttpAuthCB, 'get', - common.xmpp.NS_HTTP_AUTH) - con.RegisterHandler('iq', self._CommandExecuteCB, 'set', - common.xmpp.NS_COMMANDS) - con.RegisterHandler('iq', self._gMailNewMailCB, 'set', - common.xmpp.NS_GMAILNOTIFY) - con.RegisterHandler('iq', self._gMailQueryCB, 'result', - common.xmpp.NS_GMAILNOTIFY) - con.RegisterHandler('iq', self._DiscoverInfoGetCB, 'get', - common.xmpp.NS_DISCO_INFO) - con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get', - common.xmpp.NS_DISCO_ITEMS) - con.RegisterHandler('iq', self._IqPingCB, 'get', - common.xmpp.NS_PING) - con.RegisterHandler('iq', self._search_fields_received, 'result', - common.xmpp.NS_SEARCH) - con.RegisterHandler('iq', self._PrivacySetCB, 'set', - common.xmpp.NS_PRIVACY) - con.RegisterHandler('iq', self._PubSubCB, 'result') - con.RegisterHandler('iq', self._PubSubErrorCB, 'error') - con.RegisterHandler('iq', self._JingleCB, 'result') - con.RegisterHandler('iq', self._JingleCB, 'error') - con.RegisterHandler('iq', self._JingleCB, 'set', - common.xmpp.NS_JINGLE) - con.RegisterHandler('iq', self._ErrorCB, 'error') - con.RegisterHandler('iq', self._IqCB) - con.RegisterHandler('iq', self._StanzaArrivedCB) - con.RegisterHandler('iq', self._ResultCB, 'result') - con.RegisterHandler('presence', self._StanzaArrivedCB) - con.RegisterHandler('message', self._StanzaArrivedCB) - con.RegisterHandler('unknown', self._StreamCB, 'urn:ietf:params:xml:ns:xmpp-streams', xmlns='http://etherx.jabber.org/streams') - -# vim: se ts=3: + ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionPEP, + ConnectionCaps, ConnectionHandlersBase, ConnectionJingle): + def __init__(self): + global HAS_IDLE + ConnectionVcard.__init__(self) + ConnectionBytestream.__init__(self) + ConnectionCommands.__init__(self) + ConnectionPubSub.__init__(self) + ConnectionPEP.__init__(self, account=self.name, dispatcher=self, + pubsub_connection=self) + ConnectionCaps.__init__(self, account=self.name, + dispatch_event=self.dispatch, capscache=capscache.capscache, + client_caps_factory=capscache.create_suitable_client_caps) + ConnectionJingle.__init__(self) + ConnectionHandlersBase.__init__(self) + self.gmail_url = None + + # keep the latest subscribed event for each jid to prevent loop when we + # acknowledge presences + self.subscribed_events = {} + # IDs of jabber:iq:last requests + self.last_ids = [] + # IDs of jabber:iq:version requests + self.version_ids = [] + # IDs of urn:xmpp:time requests + self.entity_time_ids = [] + # ID of urn:xmpp:ping requests + self.awaiting_xmpp_ping_id = None + self.continue_connect_info = None + + try: + self.sleeper = common.sleepy.Sleepy() +# idle.init() + HAS_IDLE = True + except Exception: + HAS_IDLE = False + + self.gmail_last_tid = None + self.gmail_last_time = None + + def build_http_auth_answer(self, iq_obj, answer): + if not self.connection or self.connected < 2: + return + if answer == 'yes': + self.connection.send(iq_obj.buildReply('result')) + elif answer == 'no': + err = common.xmpp.Error(iq_obj, + common.xmpp.protocol.ERR_NOT_AUTHORIZED) + self.connection.send(err) + + def _HttpAuthCB(self, con, iq_obj): + log.debug('HttpAuthCB') + opt = gajim.config.get_per('accounts', self.name, 'http_auth') + if opt in ('yes', 'no'): + self.build_http_auth_answer(iq_obj, opt) + else: + id_ = iq_obj.getTagAttr('confirm', 'id') + method = iq_obj.getTagAttr('confirm', 'method') + url = iq_obj.getTagAttr('confirm', 'url') + msg = iq_obj.getTagData('body') # In case it's a message with a body + self.dispatch('HTTP_AUTH', (method, url, id_, iq_obj, msg)) + raise common.xmpp.NodeProcessed + + def _ErrorCB(self, con, iq_obj): + log.debug('ErrorCB') + jid_from = helpers.get_full_jid_from_iq(iq_obj) + jid_stripped, resource = gajim.get_room_and_nick_from_fjid(jid_from) + id_ = unicode(iq_obj.getID()) + if id_ in self.version_ids: + self.dispatch('OS_INFO', (jid_stripped, resource, '', '')) + self.version_ids.remove(id_) + return + if id_ in self.last_ids: + self.dispatch('LAST_STATUS_TIME', (jid_stripped, resource, -1, '')) + self.last_ids.remove(id_) + return + if id_ in self.entity_time_ids: + self.dispatch('ENTITY_TIME', (jid_stripped, resource, '')) + self.entity_time_ids.remove(id_) + return + errmsg = iq_obj.getErrorMsg() + errcode = iq_obj.getErrorCode() + self.dispatch('ERROR_ANSWER', (id_, jid_from, errmsg, errcode)) + + def _PrivateCB(self, con, iq_obj): + """ + Private Data (XEP 048 and 049) + """ + log.debug('PrivateCB') + query = iq_obj.getTag('query') + storage = query.getTag('storage') + if storage: + ns = storage.getNamespace() + if ns == 'storage:bookmarks': + self._parse_bookmarks(storage, 'xml') + elif ns == 'gajim:prefs': + # Preferences data + # http://www.xmpp.org/extensions/xep-0049.html + #TODO: implement this + pass + elif ns == 'storage:rosternotes': + # Annotations + # http://www.xmpp.org/extensions/xep-0145.html + notes = storage.getTags('note') + for note in notes: + try: + jid = helpers.parse_jid(note.getAttr('jid')) + except common.helpers.InvalidFormat: + log.warn('Invalid JID: %s, ignoring it' % note.getAttr('jid')) + continue + annotation = note.getData() + self.annotations[jid] = annotation + + def _parse_bookmarks(self, storage, storage_type): + """ + storage_type can be 'pubsub' or 'xml' to tell from where we got bookmarks + """ + # Bookmarked URLs and Conferences + # http://www.xmpp.org/extensions/xep-0048.html + resend_to_pubsub = False + confs = storage.getTags('conference') + for conf in confs: + autojoin_val = conf.getAttr('autojoin') + if autojoin_val is None: # not there (it's optional) + autojoin_val = False + minimize_val = conf.getAttr('minimize') + if minimize_val is None: # not there (it's optional) + minimize_val = False + print_status = conf.getTagData('print_status') + if not print_status: + print_status = conf.getTagData('show_status') + try: + bm = {'name': conf.getAttr('name'), + 'jid': helpers.parse_jid(conf.getAttr('jid')), + 'autojoin': autojoin_val, + 'minimize': minimize_val, + 'password': conf.getTagData('password'), + 'nick': conf.getTagData('nick'), + 'print_status': print_status} + except common.helpers.InvalidFormat: + log.warn('Invalid JID: %s, ignoring it' % conf.getAttr('jid')) + continue + + if bm not in self.bookmarks: + self.bookmarks.append(bm) + if storage_type == 'xml': + # We got a bookmark that was not in pubsub + resend_to_pubsub = True + self.dispatch('BOOKMARKS', self.bookmarks) + if storage_type == 'pubsub': + # We gor bookmarks from pubsub, now get those from xml to merge them + self.get_bookmarks(storage_type='xml') + if self.pubsub_supported and resend_to_pubsub: + self.store_bookmarks('pubsub') + + def _rosterSetCB(self, con, iq_obj): + log.debug('rosterSetCB') + version = iq_obj.getTagAttr('query', 'ver') + for item in iq_obj.getTag('query').getChildren(): + try: + jid = helpers.parse_jid(item.getAttr('jid')) + except common.helpers.InvalidFormat: + log.warn('Invalid JID: %s, ignoring it' % item.getAttr('jid')) + continue + name = item.getAttr('name') + sub = item.getAttr('subscription') + ask = item.getAttr('ask') + groups = [] + for group in item.getTags('group'): + groups.append(group.getData()) + self.dispatch('ROSTER_INFO', (jid, name, sub, ask, groups)) + account_jid = gajim.get_jid_from_account(self.name) + gajim.logger.add_or_update_contact(account_jid, jid, name, sub, ask, + groups) + if version: + gajim.config.set_per('accounts', self.name, 'roster_version', + version) + if not self.connection or self.connected < 2: + raise common.xmpp.NodeProcessed + reply = common.xmpp.Iq(typ='result', attrs={'id': iq_obj.getID()}, + to=iq_obj.getFrom(), frm=iq_obj.getTo(), xmlns=None) + self.connection.send(reply) + raise common.xmpp.NodeProcessed + + def _VersionCB(self, con, iq_obj): + log.debug('VersionCB') + if not self.connection or self.connected < 2: + return + iq_obj = iq_obj.buildReply('result') + qp = iq_obj.getTag('query') + qp.setTagData('name', 'Gajim') + qp.setTagData('version', gajim.version) + send_os = gajim.config.get_per('accounts', self.name, 'send_os_info') + if send_os: + qp.setTagData('os', helpers.get_os_info()) + self.connection.send(iq_obj) + raise common.xmpp.NodeProcessed + + def _LastCB(self, con, iq_obj): + global HAS_IDLE + log.debug('LastCB') + if not self.connection or self.connected < 2: + return + if HAS_IDLE and gajim.config.get_per('accounts', self.name, + 'send_idle_time'): + iq_obj = iq_obj.buildReply('result') + qp = iq_obj.getTag('query') + qp.attrs['seconds'] = int(self.sleeper.getIdleSec()) + else: + iq_obj = iq_obj.buildReply('error') + err = common.xmpp.ErrorNode(name=common.xmpp.NS_STANZAS+' service-unavailable') + iq_obj.addChild(node=err) + + self.connection.send(iq_obj) + raise common.xmpp.NodeProcessed + + def _LastResultCB(self, con, iq_obj): + log.debug('LastResultCB') + qp = iq_obj.getTag('query') + seconds = qp.getAttr('seconds') + status = qp.getData() + try: + seconds = int(seconds) + except Exception: + return + id_ = iq_obj.getID() + if id_ in self.groupchat_jids: + who = self.groupchat_jids[id_] + del self.groupchat_jids[id_] + else: + who = helpers.get_full_jid_from_iq(iq_obj) + if id_ in self.last_ids: + self.last_ids.remove(id_) + jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) + self.dispatch('LAST_STATUS_TIME', (jid_stripped, resource, seconds, status)) + + def _VersionResultCB(self, con, iq_obj): + log.debug('VersionResultCB') + client_info = '' + os_info = '' + qp = iq_obj.getTag('query') + if qp.getTag('name'): + client_info += qp.getTag('name').getData() + if qp.getTag('version'): + client_info += ' ' + qp.getTag('version').getData() + if qp.getTag('os'): + os_info += qp.getTag('os').getData() + id_ = iq_obj.getID() + if id_ in self.groupchat_jids: + who = self.groupchat_jids[id_] + del self.groupchat_jids[id_] + else: + who = helpers.get_full_jid_from_iq(iq_obj) + jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) + if id_ in self.version_ids: + self.version_ids.remove(id_) + self.dispatch('OS_INFO', (jid_stripped, resource, client_info, os_info)) + + def _TimeCB(self, con, iq_obj): + log.debug('TimeCB') + if not self.connection or self.connected < 2: + return + iq_obj = iq_obj.buildReply('result') + qp = iq_obj.getTag('query') + qp.setTagData('utc', strftime('%Y%m%dT%H:%M:%S', gmtime())) + qp.setTagData('tz', helpers.decode_string(tzname[daylight])) + qp.setTagData('display', helpers.decode_string(strftime('%c', + localtime()))) + self.connection.send(iq_obj) + raise common.xmpp.NodeProcessed + + def _TimeRevisedCB(self, con, iq_obj): + log.debug('TimeRevisedCB') + if not self.connection or self.connected < 2: + return + iq_obj = iq_obj.buildReply('result') + qp = iq_obj.setTag('time', + namespace=common.xmpp.NS_TIME_REVISED) + qp.setTagData('utc', strftime('%Y-%m-%dT%H:%M:%SZ', gmtime())) + isdst = localtime().tm_isdst + zone = -(timezone, altzone)[isdst] / 60 + tzo = (zone / 60, abs(zone % 60)) + qp.setTagData('tzo', '%+03d:%02d' % (tzo)) + self.connection.send(iq_obj) + raise common.xmpp.NodeProcessed + + def _TimeRevisedResultCB(self, con, iq_obj): + log.debug('TimeRevisedResultCB') + time_info = '' + qp = iq_obj.getTag('time') + if not qp: + # wrong answer + return + tzo = qp.getTag('tzo').getData() + if tzo.lower() == 'z': + tzo = '0:0' + tzoh, tzom = tzo.split(':') + utc_time = qp.getTag('utc').getData() + ZERO = datetime.timedelta(0) + class UTC(datetime.tzinfo): + def utcoffset(self, dt): + return ZERO + def tzname(self, dt): + return "UTC" + def dst(self, dt): + return ZERO + + class contact_tz(datetime.tzinfo): + def utcoffset(self, dt): + return datetime.timedelta(hours=int(tzoh), minutes=int(tzom)) + def tzname(self, dt): + return "remote timezone" + def dst(self, dt): + return ZERO + + try: + t = datetime.datetime.strptime(utc_time, '%Y-%m-%dT%H:%M:%SZ') + t = t.replace(tzinfo=UTC()) + time_info = t.astimezone(contact_tz()).strftime('%c') + except ValueError, e: + log.info('Wrong time format: %s' % str(e)) + + id_ = iq_obj.getID() + if id_ in self.groupchat_jids: + who = self.groupchat_jids[id_] + del self.groupchat_jids[id_] + else: + who = helpers.get_full_jid_from_iq(iq_obj) + jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) + if id_ in self.entity_time_ids: + self.entity_time_ids.remove(id_) + self.dispatch('ENTITY_TIME', (jid_stripped, resource, time_info)) + + def _gMailNewMailCB(self, con, gm): + """ + Called when we get notified of new mail messages in gmail account + """ + if not self.connection or self.connected < 2: + return + if not gm.getTag('new-mail'): + return + if gm.getTag('new-mail').getNamespace() == common.xmpp.NS_GMAILNOTIFY: + # we'll now ask the server for the exact number of new messages + jid = gajim.get_jid_from_account(self.name) + log.debug('Got notification of new gmail e-mail on %s. Asking the server for more info.' % jid) + iq = common.xmpp.Iq(typ = 'get') + iq.setID(self.connection.getAnID()) + query = iq.setTag('query') + query.setNamespace(common.xmpp.NS_GMAILNOTIFY) + # we want only be notified about newer mails + if self.gmail_last_tid: + query.setAttr('newer-than-tid', self.gmail_last_tid) + if self.gmail_last_time: + query.setAttr('newer-than-time', self.gmail_last_time) + self.connection.send(iq) + raise common.xmpp.NodeProcessed + + def _gMailQueryCB(self, con, gm): + """ + Called when we receive results from Querying the server for mail messages + in gmail account + """ + if not gm.getTag('mailbox'): + return + self.gmail_url = gm.getTag('mailbox').getAttr('url') + if gm.getTag('mailbox').getNamespace() == common.xmpp.NS_GMAILNOTIFY: + newmsgs = gm.getTag('mailbox').getAttr('total-matched') + if newmsgs != '0': + # there are new messages + gmail_messages_list = [] + if gm.getTag('mailbox').getTag('mail-thread-info'): + gmail_messages = gm.getTag('mailbox').getTags('mail-thread-info') + for gmessage in gmail_messages: + unread_senders = [] + for sender in gmessage.getTag('senders').getTags('sender'): + if sender.getAttr('unread') != '1': + continue + if sender.getAttr('name'): + unread_senders.append(sender.getAttr('name') + '< ' + \ + sender.getAttr('address') + '>') + else: + unread_senders.append(sender.getAttr('address')) + + if not unread_senders: + continue + gmail_subject = gmessage.getTag('subject').getData() + gmail_snippet = gmessage.getTag('snippet').getData() + tid = int(gmessage.getAttr('tid')) + if not self.gmail_last_tid or tid > self.gmail_last_tid: + self.gmail_last_tid = tid + gmail_messages_list.append({ \ + 'From': unread_senders, \ + 'Subject': gmail_subject, \ + 'Snippet': gmail_snippet, \ + 'url': gmessage.getAttr('url'), \ + 'participation': gmessage.getAttr('participation'), \ + 'messages': gmessage.getAttr('messages'), \ + 'date': gmessage.getAttr('date')}) + self.gmail_last_time = int(gm.getTag('mailbox').getAttr( + 'result-time')) + + jid = gajim.get_jid_from_account(self.name) + log.debug(('You have %s new gmail e-mails on %s.') % (newmsgs, jid)) + self.dispatch('GMAIL_NOTIFY', (jid, newmsgs, gmail_messages_list)) + raise common.xmpp.NodeProcessed + + def _rosterItemExchangeCB(self, con, msg): + """ + XEP-0144 Roster Item Echange + """ + exchange_items_list = {} + jid_from = helpers.get_full_jid_from_iq(msg) + items_list = msg.getTag('x').getChildren() + if not items_list: + return + action = items_list[0].getAttr('action') + if action == None: + action = 'add' + for item in msg.getTag('x', + namespace=common.xmpp.NS_ROSTERX).getChildren(): + try: + jid = helpers.parse_jid(item.getAttr('jid')) + except common.helpers.InvalidFormat: + log.warn('Invalid JID: %s, ignoring it' % item.getAttr('jid')) + continue + name = item.getAttr('name') + contact = gajim.contacts.get_contact(self.name, jid) + groups = [] + same_groups = True + for group in item.getTags('group'): + groups.append(group.getData()) + # check that all suggested groups are in the groups we have for this + # contact + if not contact or group not in contact.groups: + same_groups = False + if contact: + # check that all groups we have for this contact are in the + # suggested groups + for group in contact.groups: + if group not in groups: + same_groups = False + if contact.sub in ('both', 'to') and same_groups: + continue + exchange_items_list[jid] = [] + exchange_items_list[jid].append(name) + exchange_items_list[jid].append(groups) + if exchange_items_list: + self.dispatch('ROSTERX', (action, exchange_items_list, jid_from)) + raise common.xmpp.NodeProcessed + + def _messageCB(self, con, msg): + """ + Called when we receive a message + """ + log.debug('MessageCB') + + gajim.nec.push_incoming_event(NetworkEvent('raw-message-received', + conn = con, + xmpp_msg = msg, + account = self.name)) + + mtype = msg.getType() + + + # check if the message is a roster item exchange (XEP-0144) + if msg.getTag('x', namespace=common.xmpp.NS_ROSTERX): + self._rosterItemExchangeCB(con, msg) + return + + # check if the message is a XEP-0070 confirmation request + if msg.getTag('confirm', namespace=common.xmpp.NS_HTTP_AUTH): + self._HttpAuthCB(con, msg) + return + + try: + frm = helpers.get_full_jid_from_iq(msg) + jid = helpers.get_jid_from_iq(msg) + except helpers.InvalidFormat: + self.dispatch('ERROR', (_('Invalid Jabber ID'), + _('A message from a non-valid JID arrived, it has been ignored.'))) + return + + addressTag = msg.getTag('addresses', namespace = common.xmpp.NS_ADDRESS) + + # Be sure it comes from one of our resource, else ignore address element + if addressTag and jid == gajim.get_jid_from_account(self.name): + address = addressTag.getTag('address', attrs={'type': 'ofrom'}) + if address: + try: + frm = helpers.parse_jid(address.getAttr('jid')) + except common.helpers.InvalidFormat: + log.warn('Invalid JID: %s, ignoring it' % address.getAttr('jid')) + return + jid = gajim.get_jid_without_resource(frm) + + # invitations + invite = None + encTag = msg.getTag('x', namespace=common.xmpp.NS_ENCRYPTED) + + if not encTag: + invite = msg.getTag('x', namespace = common.xmpp.NS_MUC_USER) + if invite and not invite.getTag('invite'): + invite = None + + # FIXME: Msn transport (CMSN1.2.1 and PyMSN0.10) do NOT RECOMMENDED + # invitation + # stanza (MUC XEP) remove in 2007, as we do not do NOT RECOMMENDED + xtags = msg.getTags('x') + for xtag in xtags: + if xtag.getNamespace() == common.xmpp.NS_CONFERENCE and not invite: + try: + room_jid = helpers.parse_jid(xtag.getAttr('jid')) + except common.helpers.InvalidFormat: + log.warn('Invalid JID: %s, ignoring it' % xtag.getAttr('jid')) + continue + is_continued = False + if xtag.getTag('continue'): + is_continued = True + self.dispatch('GC_INVITATION', (room_jid, frm, '', None, + is_continued)) + return + + thread_id = msg.getThread() + + if not mtype: + mtype = 'normal' + + msgtxt = msg.getBody() + + encrypted = False + xep_200_encrypted = msg.getTag('c', namespace=common.xmpp.NS_STANZA_CRYPTO) + + session = None + if mtype != 'groupchat': + session = self.get_or_create_session(frm, thread_id) + + if thread_id and not session.received_thread_id: + session.received_thread_id = True + + session.last_receive = time_time() + + # check if the message is a XEP-0020 feature negotiation request + if msg.getTag('feature', namespace=common.xmpp.NS_FEATURE): + if gajim.HAVE_PYCRYPTO: + feature = msg.getTag(name='feature', namespace=common.xmpp.NS_FEATURE) + form = common.xmpp.DataForm(node=feature.getTag('x')) + + if form['FORM_TYPE'] == 'urn:xmpp:ssn': + session.handle_negotiation(form) + else: + reply = msg.buildReply() + reply.setType('error') + + reply.addChild(feature) + err = common.xmpp.ErrorNode('service-unavailable', typ='cancel') + reply.addChild(node=err) + + con.send(reply) + + raise common.xmpp.NodeProcessed + + return + + if msg.getTag('init', namespace=common.xmpp.NS_ESESSION_INIT): + init = msg.getTag(name='init', namespace=common.xmpp.NS_ESESSION_INIT) + form = common.xmpp.DataForm(node=init.getTag('x')) + + session.handle_negotiation(form) + + raise common.xmpp.NodeProcessed + + tim = msg.getTimestamp() + tim = helpers.datetime_tuple(tim) + tim = localtime(timegm(tim)) + + if xep_200_encrypted: + encrypted = 'xep200' + + try: + msg = session.decrypt_stanza(msg) + msgtxt = msg.getBody() + except Exception: + self.dispatch('FAILED_DECRYPT', (frm, tim, session)) + + # Receipt requested + # TODO: We shouldn't answer if we're invisible! + contact = gajim.contacts.get_contact(self.name, jid) + nick = gajim.get_room_and_nick_from_fjid(frm)[1] + gc_contact = gajim.contacts.get_gc_contact(self.name, jid, nick) + if msg.getTag('request', namespace=common.xmpp.NS_RECEIPTS) \ + and gajim.config.get_per('accounts', self.name, + 'answer_receipts') and ((contact and contact.sub \ + not in (u'to', u'none')) or gc_contact) and mtype != 'error': + receipt = common.xmpp.Message(to=frm, typ='chat') + receipt.setID(msg.getID()) + receipt.setTag('received', + namespace='urn:xmpp:receipts') + + if thread_id: + receipt.setThread(thread_id) + con.send(receipt) + + # We got our message's receipt + if msg.getTag('received', namespace=common.xmpp.NS_RECEIPTS) and \ + session.control and gajim.config.get_per('accounts', self.name, + 'request_receipt'): + session.control.conv_textview.hide_xep0184_warning(msg.getID()) + + if encTag and self.USE_GPG: + encmsg = encTag.getData() + + keyID = gajim.config.get_per('accounts', self.name, 'keyid') + if keyID: + def decrypt_thread(encmsg, keyID): + decmsg = self.gpg.decrypt(encmsg, keyID) + # \x00 chars are not allowed in C (so in GTK) + msgtxt = helpers.decode_string(decmsg.replace('\x00', '')) + encrypted = 'xep27' + return (msgtxt, encrypted) + gajim.thread_interface(decrypt_thread, [encmsg, keyID], + self._on_message_decrypted, [mtype, msg, session, frm, jid, + invite, tim]) + return + self._on_message_decrypted((msgtxt, encrypted), mtype, msg, session, frm, + jid, invite, tim) + + def _on_message_decrypted(self, output, mtype, msg, session, frm, jid, + invite, tim): + msgtxt, encrypted = output + if mtype == 'error': + self.dispatch_error_message(msg, msgtxt, session, frm, tim) + elif mtype == 'groupchat': + self.dispatch_gc_message(msg, frm, msgtxt, jid, tim) + elif invite is not None: + self.dispatch_invite_message(invite, frm) + else: + if isinstance(session, gajim.default_session_type): + session.received(frm, msgtxt, tim, encrypted, msg) + else: + session.received(msg) + # END messageCB + + # process and dispatch an error message + def dispatch_error_message(self, msg, msgtxt, session, frm, tim): + error_msg = msg.getErrorMsg() + + if not error_msg: + error_msg = msgtxt + msgtxt = None + + subject = msg.getSubject() + + if session.is_loggable(): + try: + gajim.logger.write('error', frm, error_msg, tim=tim, + subject=subject) + except exceptions.PysqliteOperationalError, e: + self.dispatch('ERROR', (_('Disk Write Error'), str(e))) + except exceptions.DatabaseMalformed: + pritext = _('Database Error') + sectext = _('The database file (%s) cannot be read. Try to repair ' + 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove ' + 'it (all history will be lost).') % common.logger.LOG_DB_PATH + self.dispatch('ERROR', (pritext, sectext)) + self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt, + tim, session)) + + # process and dispatch a groupchat message + def dispatch_gc_message(self, msg, frm, msgtxt, jid, tim): + has_timestamp = bool(msg.timestamp) + + subject = msg.getSubject() + + if subject is not None: + self.dispatch('GC_SUBJECT', (frm, subject, msgtxt, has_timestamp)) + return + + statusCode = msg.getStatusCode() + + if not msg.getTag('body'): # no + # It could be a config change. See + # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify + if msg.getTag('x'): + if statusCode != []: + self.dispatch('GC_CONFIG_CHANGE', (jid, statusCode)) + return + + # Ignore message from room in which we are not + if jid not in self.last_history_time: + return + + self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp, msg.getXHTML(), + statusCode)) + + tim_int = int(float(mktime(tim))) + if gajim.config.should_log(self.name, jid) and not \ + tim_int <= self.last_history_time[jid] and msgtxt and frm.find('/') >= 0: + # if frm.find('/') < 0, it means message comes from room itself + # usually it hold description and can be send at each connection + # so don't store it in logs + try: + gajim.logger.write('gc_msg', frm, msgtxt, tim=tim) + # store in memory time of last message logged. + # this will also be saved in rooms_last_message_time table + # when we quit this muc + self.last_history_time[jid] = mktime(tim) + + except exceptions.PysqliteOperationalError, e: + self.dispatch('ERROR', (_('Disk Write Error'), str(e))) + except exceptions.DatabaseMalformed: + pritext = _('Database Error') + sectext = _('The database file (%s) cannot be read. Try to repair ' + 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove ' + 'it (all history will be lost).') % common.logger.LOG_DB_PATH + self.dispatch('ERROR', (pritext, sectext)) + + def dispatch_invite_message(self, invite, frm): + item = invite.getTag('invite') + try: + jid_from = helpers.parse_jid(item.getAttr('from')) + except common.helpers.InvalidFormat: + log.warn('Invalid JID: %s, ignoring it' % item.getAttr('from')) + return + reason = item.getTagData('reason') + item = invite.getTag('password') + password = invite.getTagData('password') + + is_continued = False + if invite.getTag('invite').getTag('continue'): + is_continued = True + self.dispatch('GC_INVITATION', (frm, jid_from, reason, password, + is_continued)) + + def _presenceCB(self, con, prs): + """ + Called when we receive a presence + """ + gajim.nec.push_incoming_event(NetworkEvent('raw-pres-received', + conn=con, xmpp_pres=prs)) + ptype = prs.getType() + if ptype == 'available': + ptype = None + rfc_types = ('unavailable', 'error', 'subscribe', 'subscribed', + 'unsubscribe', 'unsubscribed') + if ptype and not ptype in rfc_types: + ptype = None + log.debug('PresenceCB: %s' % ptype) + if not self.connection or self.connected < 2: + log.debug('account is no more connected') + return + try: + who = helpers.get_full_jid_from_iq(prs) + except Exception: + if prs.getTag('error') and prs.getTag('error').getTag('jid-malformed'): + # wrong jid, we probably tried to change our nick in a room to a non + # valid one + who = str(prs.getFrom()) + jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) + self.dispatch('GC_MSG', (jid_stripped, + _('Nickname not allowed: %s') % resource, None, False, None, [])) + return + jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) + timestamp = None + id_ = prs.getID() + is_gc = False # is it a GC presence ? + sigTag = None + ns_muc_user_x = None + avatar_sha = None + # XEP-0172 User Nickname + user_nick = prs.getTagData('nick') + if not user_nick: + user_nick = '' + contact_nickname = None + transport_auto_auth = False + # XEP-0203 + delay_tag = prs.getTag('delay', namespace=common.xmpp.NS_DELAY2) + if delay_tag: + tim = prs.getTimestamp2() + tim = helpers.datetime_tuple(tim) + timestamp = localtime(timegm(tim)) + xtags = prs.getTags('x') + for x in xtags: + namespace = x.getNamespace() + if namespace.startswith(common.xmpp.NS_MUC): + is_gc = True + if namespace == common.xmpp.NS_MUC_USER and x.getTag('destroy'): + ns_muc_user_x = x + elif namespace == common.xmpp.NS_SIGNED: + sigTag = x + elif namespace == common.xmpp.NS_VCARD_UPDATE: + avatar_sha = x.getTagData('photo') + contact_nickname = x.getTagData('nickname') + elif namespace == common.xmpp.NS_DELAY and not timestamp: + # XEP-0091 + tim = prs.getTimestamp() + tim = helpers.datetime_tuple(tim) + timestamp = localtime(timegm(tim)) + elif namespace == 'http://delx.cjb.net/protocol/roster-subsync': + # see http://trac.gajim.org/ticket/326 + agent = gajim.get_server_from_jid(jid_stripped) + if self.connection.getRoster().getItem(agent): # to be sure it's a transport contact + transport_auto_auth = True + + if not is_gc and id_ and id_.startswith('gajim_muc_') and \ + ptype == 'error': + # Error presences may not include sent stanza, so we don't detect it's + # a muc preence. So detect it by ID + h = hmac.new(self.secret_hmac, jid_stripped).hexdigest()[:6] + if id_.split('_')[-1] == h: + is_gc = True + status = prs.getStatus() or '' + show = prs.getShow() + if show not in ('chat', 'away', 'xa', 'dnd'): + show = '' # We ignore unknown show + if not ptype and not show: + show = 'online' + elif ptype == 'unavailable': + show = 'offline' + + prio = prs.getPriority() + try: + prio = int(prio) + except Exception: + prio = 0 + keyID = '' + if sigTag and self.USE_GPG and ptype != 'error': + # error presences contain our own signature + # verify + sigmsg = sigTag.getData() + keyID = self.gpg.verify(status, sigmsg) + + if is_gc: + if ptype == 'error': + errcon = prs.getError() + errmsg = prs.getErrorMsg() + errcode = prs.getErrorCode() + room_jid, nick = gajim.get_room_and_nick_from_fjid(who) + + gc_control = gajim.interface.msg_win_mgr.get_gc_control(room_jid, + self.name) + + # If gc_control is missing - it may be minimized. Try to get it from + # there. If it's not there - then it's missing anyway and will + # remain set to None. + if gc_control is None: + minimized = gajim.interface.minimized_controls[self.name] + gc_control = minimized.get(room_jid) + + if errcode == '502': + # Internal Timeout: + self.dispatch('NOTIFY', (jid_stripped, 'error', errmsg, resource, + prio, keyID, timestamp, None)) + elif (errcode == '503'): + # maximum user number reached + self.dispatch('ERROR', (_('Unable to join group chat'), + _('Maximum number of users for %s has been reached') % \ + room_jid)) + elif (errcode == '401') or (errcon == 'not-authorized'): + # password required to join + self.dispatch('GC_PASSWORD_REQUIRED', (room_jid, nick)) + elif (errcode == '403') or (errcon == 'forbidden'): + # we are banned + self.dispatch('ERROR', (_('Unable to join group chat'), + _('You are banned from group chat %s.') % room_jid)) + elif (errcode == '404') or (errcon in ('item-not-found', + 'remote-server-not-found')): + if gc_control is None or gc_control.autorejoin is None: + # group chat does not exist + self.dispatch('ERROR', (_('Unable to join group chat'), + _('Group chat %s does not exist.') % room_jid)) + elif (errcode == '405') or (errcon == 'not-allowed'): + self.dispatch('ERROR', (_('Unable to join group chat'), + _('Group chat creation is restricted.'))) + elif (errcode == '406') or (errcon == 'not-acceptable'): + self.dispatch('ERROR', (_('Unable to join group chat'), + _('Your registered nickname must be used in group chat %s.') \ + % room_jid)) + elif (errcode == '407') or (errcon == 'registration-required'): + self.dispatch('ERROR', (_('Unable to join group chat'), + _('You are not in the members list in groupchat %s.') % \ + room_jid)) + elif (errcode == '409') or (errcon == 'conflict'): + # nick conflict + room_jid = gajim.get_room_from_fjid(who) + self.dispatch('ASK_NEW_NICK', (room_jid,)) + else: # print in the window the error + self.dispatch('ERROR_ANSWER', ('', jid_stripped, + errmsg, errcode)) + if not ptype or ptype == 'unavailable': + if gajim.config.get('log_contact_status_changes') and \ + gajim.config.should_log(self.name, jid_stripped): + gc_c = gajim.contacts.get_gc_contact(self.name, jid_stripped, + resource) + st = status or '' + if gc_c: + jid = gc_c.jid + else: + jid = prs.getJid() + if jid: + # we know real jid, save it in db + st += ' (%s)' % jid + try: + gajim.logger.write('gcstatus', who, st, show) + except exceptions.PysqliteOperationalError, e: + self.dispatch('ERROR', (_('Disk Write Error'), str(e))) + except exceptions.DatabaseMalformed: + pritext = _('Database Error') + sectext = _('The database file (%s) cannot be read. Try to ' + 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)' + ' or remove it (all history will be lost).') % \ + common.logger.LOG_DB_PATH + self.dispatch('ERROR', (pritext, sectext)) + if avatar_sha or avatar_sha == '': + if avatar_sha == '': + # contact has no avatar + puny_nick = helpers.sanitize_filename(resource) + gajim.interface.remove_avatar_files(jid_stripped, puny_nick) + # if it's a gc presence, don't ask vcard here. We may ask it to + # real jid in gui part. + if ns_muc_user_x: + # Room has been destroyed. see + # http://www.xmpp.org/extensions/xep-0045.html#destroyroom + reason = _('Room has been destroyed') + destroy = ns_muc_user_x.getTag('destroy') + r = destroy.getTagData('reason') + if r: + reason += ' (%s)' % r + if destroy.getAttr('jid'): + try: + jid = helpers.parse_jid(destroy.getAttr('jid')) + reason += '\n' + _('You can join this room instead: %s') \ + % jid + except common.helpers.InvalidFormat: + pass + statusCode = ['destroyed'] + else: + reason = prs.getReason() + statusCode = prs.getStatusCode() + self.dispatch('GC_NOTIFY', (jid_stripped, show, status, resource, + prs.getRole(), prs.getAffiliation(), prs.getJid(), + reason, prs.getActor(), statusCode, prs.getNewNick(), + avatar_sha)) + return + + if ptype == 'subscribe': + log.debug('subscribe request from %s' % who) + if who.find('@') <= 0 and who in self.agent_registrations: + self.agent_registrations[who]['sub_received'] = True + if not self.agent_registrations[who]['roster_push']: + # We'll reply after roster push result + return + if gajim.config.get_per('accounts', self.name, 'autoauth') or \ + who.find('@') <= 0 or jid_stripped in self.jids_for_auto_auth or \ + transport_auto_auth: + if self.connection: + p = common.xmpp.Presence(who, 'subscribed') + p = self.add_sha(p) + self.connection.send(p) + if who.find('@') <= 0 or transport_auto_auth: + self.dispatch('NOTIFY', (jid_stripped, 'offline', 'offline', + resource, prio, keyID, timestamp, None)) + if transport_auto_auth: + self.automatically_added.append(jid_stripped) + self.request_subscription(jid_stripped, name = user_nick) + else: + if not status: + status = _('I would like to add you to my roster.') + self.dispatch('SUBSCRIBE', (jid_stripped, status, user_nick)) + elif ptype == 'subscribed': + if jid_stripped in self.automatically_added: + self.automatically_added.remove(jid_stripped) + else: + # detect a subscription loop + if jid_stripped not in self.subscribed_events: + self.subscribed_events[jid_stripped] = [] + self.subscribed_events[jid_stripped].append(time_time()) + block = False + if len(self.subscribed_events[jid_stripped]) > 5: + if time_time() - self.subscribed_events[jid_stripped][0] < 5: + block = True + self.subscribed_events[jid_stripped] = self.subscribed_events[jid_stripped][1:] + if block: + gajim.config.set_per('account', self.name, + 'dont_ack_subscription', True) + else: + self.dispatch('SUBSCRIBED', (jid_stripped, resource)) + # BE CAREFUL: no con.updateRosterItem() in a callback + log.debug(_('we are now subscribed to %s') % who) + elif ptype == 'unsubscribe': + log.debug(_('unsubscribe request from %s') % who) + elif ptype == 'unsubscribed': + log.debug(_('we are now unsubscribed from %s') % who) + # detect a unsubscription loop + if jid_stripped not in self.subscribed_events: + self.subscribed_events[jid_stripped] = [] + self.subscribed_events[jid_stripped].append(time_time()) + block = False + if len(self.subscribed_events[jid_stripped]) > 5: + if time_time() - self.subscribed_events[jid_stripped][0] < 5: + block = True + self.subscribed_events[jid_stripped] = self.subscribed_events[jid_stripped][1:] + if block: + gajim.config.set_per('account', self.name, 'dont_ack_subscription', + True) + else: + self.dispatch('UNSUBSCRIBED', jid_stripped) + elif ptype == 'error': + errmsg = prs.getError() + errcode = prs.getErrorCode() + if errcode != '502': # Internal Timeout: + # print in the window the error + self.dispatch('ERROR_ANSWER', ('', jid_stripped, + errmsg, errcode)) + if errcode != '409': # conflict # See #5120 + self.dispatch('NOTIFY', (jid_stripped, 'error', errmsg, resource, + prio, keyID, timestamp, None)) + + if ptype == 'unavailable': + for jid in [jid_stripped, who]: + if jid not in self.sessions: + continue + # automatically terminate sessions that they haven't sent a thread + # ID in, only if other part support thread ID + for sess in self.sessions[jid].values(): + if not sess.received_thread_id: + contact = gajim.contacts.get_contact(self.name, jid) + # FIXME: I don't know if this is the correct behavior here. + # Anyway, it is the old behavior when we assumed that + # not-existing contacts don't support anything + contact_exists = bool(contact) + session_supported = contact_exists and ( + contact.supports(common.xmpp.NS_SSN) or + contact.supports(common.xmpp.NS_ESESSION)) + if session_supported: + sess.terminate() + del self.sessions[jid][sess.thread_id] + + if avatar_sha is not None and ptype != 'error': + if jid_stripped not in self.vcard_shas: + cached_vcard = self.get_cached_vcard(jid_stripped) + if cached_vcard and 'PHOTO' in cached_vcard and \ + 'SHA' in cached_vcard['PHOTO']: + self.vcard_shas[jid_stripped] = cached_vcard['PHOTO']['SHA'] + else: + self.vcard_shas[jid_stripped] = '' + if avatar_sha != self.vcard_shas[jid_stripped]: + # avatar has been updated + self.request_vcard(jid_stripped) + if not ptype or ptype == 'unavailable': + if gajim.config.get('log_contact_status_changes') and \ + gajim.config.should_log(self.name, jid_stripped): + try: + gajim.logger.write('status', jid_stripped, status, show) + except exceptions.PysqliteOperationalError, e: + self.dispatch('ERROR', (_('Disk Write Error'), str(e))) + except exceptions.DatabaseMalformed: + pritext = _('Database Error') + sectext = _('The database file (%s) cannot be read. Try to ' + 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup) ' + 'or remove it (all history will be lost).') % \ + common.logger.LOG_DB_PATH + self.dispatch('ERROR', (pritext, sectext)) + our_jid = gajim.get_jid_from_account(self.name) + if jid_stripped == our_jid and resource == self.server_resource: + # We got our own presence + self.dispatch('STATUS', show) + else: + self.dispatch('NOTIFY', (jid_stripped, show, status, resource, prio, + keyID, timestamp, contact_nickname)) + # END presenceCB + + def _StanzaArrivedCB(self, con, obj): + self.last_io = gajim.idlequeue.current_time() + + def _MucOwnerCB(self, con, iq_obj): + log.debug('MucOwnerCB') + qp = iq_obj.getQueryPayload() + node = None + for q in qp: + if q.getNamespace() == common.xmpp.NS_DATA: + node = q + if not node: + return + self.dispatch('GC_CONFIG', (helpers.get_full_jid_from_iq(iq_obj), node)) + + def _MucAdminCB(self, con, iq_obj): + log.debug('MucAdminCB') + items = iq_obj.getTag('query', namespace=common.xmpp.NS_MUC_ADMIN).\ + getTags('item') + users_dict = {} + for item in items: + if item.has_attr('jid') and item.has_attr('affiliation'): + try: + jid = helpers.parse_jid(item.getAttr('jid')) + except common.helpers.InvalidFormat: + log.warn('Invalid JID: %s, ignoring it' % item.getAttr('jid')) + continue + affiliation = item.getAttr('affiliation') + users_dict[jid] = {'affiliation': affiliation} + if item.has_attr('nick'): + users_dict[jid]['nick'] = item.getAttr('nick') + if item.has_attr('role'): + users_dict[jid]['role'] = item.getAttr('role') + reason = item.getTagData('reason') + if reason: + users_dict[jid]['reason'] = reason + + self.dispatch('GC_AFFILIATION', (helpers.get_full_jid_from_iq(iq_obj), + users_dict)) + + def _MucErrorCB(self, con, iq_obj): + log.debug('MucErrorCB') + jid = helpers.get_full_jid_from_iq(iq_obj) + errmsg = iq_obj.getError() + errcode = iq_obj.getErrorCode() + self.dispatch('MSGERROR', (jid, errcode, errmsg)) + + def _IqPingCB(self, con, iq_obj): + log.debug('IqPingCB') + if not self.connection or self.connected < 2: + return + iq_obj = iq_obj.buildReply('result') + self.connection.send(iq_obj) + raise common.xmpp.NodeProcessed + + def _PrivacySetCB(self, con, iq_obj): + """ + Privacy lists (XEP 016) + + A list has been set. + """ + log.debug('PrivacySetCB') + if not self.connection or self.connected < 2: + return + result = iq_obj.buildReply('result') + q = result.getTag('query') + if q: + result.delChild(q) + self.connection.send(result) + raise common.xmpp.NodeProcessed + + def _getRoster(self): + log.debug('getRosterCB') + if not self.connection: + return + self.connection.getRoster(self._on_roster_set) + self.discoverItems(gajim.config.get_per('accounts', self.name, + 'hostname'), id_prefix='Gajim_') + if gajim.config.get_per('accounts', self.name, 'use_ft_proxies'): + self.discover_ft_proxies() + + def discover_ft_proxies(self): + cfg_proxies = gajim.config.get_per('accounts', self.name, + 'file_transfer_proxies') + our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name) + '/' +\ + self.server_resource) + if cfg_proxies: + proxies = [e.strip() for e in cfg_proxies.split(',')] + for proxy in proxies: + gajim.proxy65_manager.resolve(proxy, self.connection, our_jid) + + def _on_roster_set(self, roster): + roster_version = roster.version + received_from_server = roster.received_from_server + raw_roster = roster.getRaw() + roster = {} + our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name)) + if self.connected > 1 and self.continue_connect_info: + msg = self.continue_connect_info[1] + sign_msg = self.continue_connect_info[2] + signed = '' + send_first_presence = True + if sign_msg: + signed = self.get_signed_presence(msg, self._send_first_presence) + if signed is None: + self.dispatch('GPG_PASSWORD_REQUIRED', + (self._send_first_presence,)) + # _send_first_presence will be called when user enter passphrase + send_first_presence = False + if send_first_presence: + self._send_first_presence(signed) + + for jid in raw_roster: + try: + j = helpers.parse_jid(jid) + except Exception: + print >> sys.stderr, _('JID %s is not RFC compliant. It will not be added to your roster. Use roster management tools such as http://jru.jabberstudio.org/ to remove it') % jid + else: + infos = raw_roster[jid] + if jid != our_jid and (not infos['subscription'] or \ + infos['subscription'] == 'none') and (not infos['ask'] or \ + infos['ask'] == 'none') and not infos['name'] and \ + not infos['groups']: + # remove this useless item, it won't be shown in roster anyway + self.connection.getRoster().delItem(jid) + elif jid != our_jid: # don't add our jid + roster[j] = raw_roster[jid] + if gajim.jid_is_transport(jid) and \ + not gajim.get_transport_name_from_jid(jid): + # we can't determine which iconset to use + self.discoverInfo(jid) + + gajim.logger.replace_roster(self.name, roster_version, roster) + if received_from_server: + for contact in gajim.contacts.iter_contacts(self.name): + if not contact.is_groupchat() and contact.jid not in roster and \ + contact.jid != gajim.get_jid_from_account(self.name): + self.dispatch('ROSTER_INFO', (contact.jid, None, None, None, + ())) + for jid in roster: + self.dispatch('ROSTER_INFO', (jid, roster[jid]['name'], + roster[jid]['subscription'], roster[jid]['ask'], + roster[jid]['groups'])) + + def _send_first_presence(self, signed = ''): + show = self.continue_connect_info[0] + msg = self.continue_connect_info[1] + sign_msg = self.continue_connect_info[2] + if sign_msg and not signed: + signed = self.get_signed_presence(msg) + if signed is None: + self.dispatch('BAD_PASSPHRASE', ()) + self.USE_GPG = False + signed = '' + self.connected = gajim.SHOW_LIST.index(show) + sshow = helpers.get_xmpp_show(show) + # send our presence + if show == 'invisible': + self.send_invisible_presence(msg, signed, True) + return + if show not in ['offline', 'online', 'chat', 'away', 'xa', 'dnd']: + return + priority = gajim.get_priority(self.name, sshow) + our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name)) + vcard = self.get_cached_vcard(our_jid) + if vcard and 'PHOTO' in vcard and 'SHA' in vcard['PHOTO']: + self.vcard_sha = vcard['PHOTO']['SHA'] + p = common.xmpp.Presence(typ = None, priority = priority, show = sshow) + p = self.add_sha(p) + if msg: + p.setStatus(msg) + if signed: + p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed) + + if self.connection: + self.connection.send(p) + self.priority = priority + self.dispatch('STATUS', show) + if self.vcard_supported: + # ask our VCard + self.request_vcard(None) + + # Get bookmarks from private namespace + self.get_bookmarks() + + # Get annotations from private namespace + self.get_annotations() + + # Inform GUI we just signed in + self.dispatch('SIGNED_IN', ()) + self.send_awaiting_pep() + self.continue_connect_info = None + + def request_gmail_notifications(self): + if not self.connection or self.connected < 2: + return + # It's a gmail account, + # inform the server that we want e-mail notifications + our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name)) + log.debug(('%s is a gmail account. Setting option ' + 'to get e-mail notifications on the server.') % (our_jid)) + iq = common.xmpp.Iq(typ = 'set', to = our_jid) + iq.setAttr('id', 'MailNotify') + query = iq.setTag('usersetting') + query.setNamespace(common.xmpp.NS_GTALKSETTING) + query = query.setTag('mailnotifications') + query.setAttr('value', 'true') + self.connection.send(iq) + # Ask how many messages there are now + iq = common.xmpp.Iq(typ = 'get') + iq.setID(self.connection.getAnID()) + query = iq.setTag('query') + query.setNamespace(common.xmpp.NS_GMAILNOTIFY) + self.connection.send(iq) + + + def _search_fields_received(self, con, iq_obj): + jid = jid = helpers.get_jid_from_iq(iq_obj) + tag = iq_obj.getTag('query', namespace = common.xmpp.NS_SEARCH) + if not tag: + self.dispatch('SEARCH_FORM', (jid, None, False)) + return + df = tag.getTag('x', namespace = common.xmpp.NS_DATA) + if df: + self.dispatch('SEARCH_FORM', (jid, df, True)) + return + df = {} + for i in iq_obj.getQueryPayload(): + df[i.getName()] = i.getData() + self.dispatch('SEARCH_FORM', (jid, df, False)) + + def _StreamCB(self, con, obj): + if obj.getTag('conflict'): + # disconnected because of a resource conflict + self.dispatch('RESOURCE_CONFLICT', ()) + + def _register_handlers(self, con, con_type): + # try to find another way to register handlers in each class + # that defines handlers + con.RegisterHandler('message', self._messageCB) + con.RegisterHandler('presence', self._presenceCB) + con.RegisterHandler('presence', self._capsPresenceCB) + # We use makefirst so that this handler is called before _messageCB, and + # can prevent calling it when it's not needed. + # We also don't check for namespace, else it cannot stop _messageCB to be + # called + con.RegisterHandler('message', self._pubsubEventCB, makefirst=True) + con.RegisterHandler('iq', self._vCardCB, 'result', + common.xmpp.NS_VCARD) + con.RegisterHandler('iq', self._rosterSetCB, 'set', + common.xmpp.NS_ROSTER) + con.RegisterHandler('iq', self._siSetCB, 'set', + common.xmpp.NS_SI) + con.RegisterHandler('iq', self._rosterItemExchangeCB, 'set', + common.xmpp.NS_ROSTERX) + con.RegisterHandler('iq', self._siErrorCB, 'error', + common.xmpp.NS_SI) + con.RegisterHandler('iq', self._siResultCB, 'result', + common.xmpp.NS_SI) + con.RegisterHandler('iq', self._discoGetCB, 'get', + common.xmpp.NS_DISCO) + con.RegisterHandler('iq', self._bytestreamSetCB, 'set', + common.xmpp.NS_BYTESTREAM) + con.RegisterHandler('iq', self._bytestreamResultCB, 'result', + common.xmpp.NS_BYTESTREAM) + con.RegisterHandler('iq', self._bytestreamErrorCB, 'error', + common.xmpp.NS_BYTESTREAM) + con.RegisterHandler('iq', self._DiscoverItemsCB, 'result', + common.xmpp.NS_DISCO_ITEMS) + con.RegisterHandler('iq', self._DiscoverItemsErrorCB, 'error', + common.xmpp.NS_DISCO_ITEMS) + con.RegisterHandler('iq', self._DiscoverInfoCB, 'result', + common.xmpp.NS_DISCO_INFO) + con.RegisterHandler('iq', self._DiscoverInfoErrorCB, 'error', + common.xmpp.NS_DISCO_INFO) + con.RegisterHandler('iq', self._VersionCB, 'get', + common.xmpp.NS_VERSION) + con.RegisterHandler('iq', self._TimeCB, 'get', + common.xmpp.NS_TIME) + con.RegisterHandler('iq', self._TimeRevisedCB, 'get', + common.xmpp.NS_TIME_REVISED) + con.RegisterHandler('iq', self._LastCB, 'get', + common.xmpp.NS_LAST) + con.RegisterHandler('iq', self._LastResultCB, 'result', + common.xmpp.NS_LAST) + con.RegisterHandler('iq', self._VersionResultCB, 'result', + common.xmpp.NS_VERSION) + con.RegisterHandler('iq', self._TimeRevisedResultCB, 'result', + common.xmpp.NS_TIME_REVISED) + con.RegisterHandler('iq', self._MucOwnerCB, 'result', + common.xmpp.NS_MUC_OWNER) + con.RegisterHandler('iq', self._MucAdminCB, 'result', + common.xmpp.NS_MUC_ADMIN) + con.RegisterHandler('iq', self._PrivateCB, 'result', + common.xmpp.NS_PRIVATE) + con.RegisterHandler('iq', self._HttpAuthCB, 'get', + common.xmpp.NS_HTTP_AUTH) + con.RegisterHandler('iq', self._CommandExecuteCB, 'set', + common.xmpp.NS_COMMANDS) + con.RegisterHandler('iq', self._gMailNewMailCB, 'set', + common.xmpp.NS_GMAILNOTIFY) + con.RegisterHandler('iq', self._gMailQueryCB, 'result', + common.xmpp.NS_GMAILNOTIFY) + con.RegisterHandler('iq', self._DiscoverInfoGetCB, 'get', + common.xmpp.NS_DISCO_INFO) + con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get', + common.xmpp.NS_DISCO_ITEMS) + con.RegisterHandler('iq', self._IqPingCB, 'get', + common.xmpp.NS_PING) + con.RegisterHandler('iq', self._search_fields_received, 'result', + common.xmpp.NS_SEARCH) + con.RegisterHandler('iq', self._PrivacySetCB, 'set', + common.xmpp.NS_PRIVACY) + con.RegisterHandler('iq', self._PubSubCB, 'result') + con.RegisterHandler('iq', self._PubSubErrorCB, 'error') + con.RegisterHandler('iq', self._JingleCB, 'result') + con.RegisterHandler('iq', self._JingleCB, 'error') + con.RegisterHandler('iq', self._JingleCB, 'set', + common.xmpp.NS_JINGLE) + con.RegisterHandler('iq', self._ErrorCB, 'error') + con.RegisterHandler('iq', self._IqCB) + con.RegisterHandler('iq', self._StanzaArrivedCB) + con.RegisterHandler('iq', self._ResultCB, 'result') + con.RegisterHandler('presence', self._StanzaArrivedCB) + con.RegisterHandler('message', self._StanzaArrivedCB) + con.RegisterHandler('unknown', self._StreamCB, 'urn:ietf:params:xml:ns:xmpp-streams', xmlns='http://etherx.jabber.org/streams') diff --git a/src/common/contacts.py b/src/common/contacts.py index 7ce83dba4..edd9c22e3 100644 --- a/src/common/contacts.py +++ b/src/common/contacts.py @@ -34,809 +34,807 @@ from common.account import Account import common.gajim class XMPPEntity(object): - """ - Base representation of entities in XMPP - """ + """ + Base representation of entities in XMPP + """ - def __init__(self, jid, account, resource): - self.jid = jid - self.resource = resource - self.account = account + def __init__(self, jid, account, resource): + self.jid = jid + self.resource = resource + self.account = account class CommonContact(XMPPEntity): - def __init__(self, jid, account, resource, show, status, name, - our_chatstate, composing_xep, chatstate, client_caps=None): + def __init__(self, jid, account, resource, show, status, name, + our_chatstate, composing_xep, chatstate, client_caps=None): - XMPPEntity.__init__(self, jid, account, resource) + XMPPEntity.__init__(self, jid, account, resource) - self.show = show - self.status = status - self.name = name + self.show = show + self.status = status + self.name = name - self.client_caps = client_caps or caps_cache.NullClientCaps() + self.client_caps = client_caps or caps_cache.NullClientCaps() - # please read xep-85 http://www.xmpp.org/extensions/xep-0085.html - # we keep track of xep85 support with the peer by three extra states: - # None, False and 'ask' - # None if no info about peer - # False if peer does not support xep85 - # 'ask' if we sent the first 'active' chatstate and are waiting for reply - # this holds what WE SEND to contact (our current chatstate) - self.our_chatstate = our_chatstate - # tell which XEP we're using for composing state - # None = have to ask, XEP-0022 = use this xep, - # XEP-0085 = use this xep, False = no composing support - self.composing_xep = composing_xep - # this is contact's chatstate - self.chatstate = chatstate + # please read xep-85 http://www.xmpp.org/extensions/xep-0085.html + # we keep track of xep85 support with the peer by three extra states: + # None, False and 'ask' + # None if no info about peer + # False if peer does not support xep85 + # 'ask' if we sent the first 'active' chatstate and are waiting for reply + # this holds what WE SEND to contact (our current chatstate) + self.our_chatstate = our_chatstate + # tell which XEP we're using for composing state + # None = have to ask, XEP-0022 = use this xep, + # XEP-0085 = use this xep, False = no composing support + self.composing_xep = composing_xep + # this is contact's chatstate + self.chatstate = chatstate - def get_full_jid(self): - raise NotImplementedError + def get_full_jid(self): + raise NotImplementedError - def get_shown_name(self): - raise NotImplementedError + def get_shown_name(self): + raise NotImplementedError - def supports(self, requested_feature): - """ - Return True if the contact has advertised to support the feature - identified by the given namespace. False otherwise. - """ - if self.show == 'offline': - # Unfortunately, if all resources are offline, the contact - # includes the last resource that was online. Check for its - # show, so we can be sure it's existant. Otherwise, we still - # return caps for a contact that has no resources left. - return False - else: - return caps_cache.client_supports(self.client_caps, requested_feature) + def supports(self, requested_feature): + """ + Return True if the contact has advertised to support the feature + identified by the given namespace. False otherwise. + """ + if self.show == 'offline': + # Unfortunately, if all resources are offline, the contact + # includes the last resource that was online. Check for its + # show, so we can be sure it's existant. Otherwise, we still + # return caps for a contact that has no resources left. + return False + else: + return caps_cache.client_supports(self.client_caps, requested_feature) class Contact(CommonContact): - """ - Information concerning a contact - """ - def __init__(self, jid, account, name='', groups=[], show='', status='', - sub='', ask='', resource='', priority=0, keyID='', client_caps=None, - our_chatstate=None, chatstate=None, last_status_time=None, msg_id= - None, composing_xep=None, last_activity_time=None): + """ + Information concerning a contact + """ + def __init__(self, jid, account, name='', groups=[], show='', status='', + sub='', ask='', resource='', priority=0, keyID='', client_caps=None, + our_chatstate=None, chatstate=None, last_status_time=None, msg_id= + None, composing_xep=None, last_activity_time=None): - CommonContact.__init__(self, jid, account, resource, show, status, name, - our_chatstate, composing_xep, chatstate, client_caps=client_caps) + CommonContact.__init__(self, jid, account, resource, show, status, name, + our_chatstate, composing_xep, chatstate, client_caps=client_caps) - self.contact_name = '' # nick choosen by contact - self.groups = [i for i in set(groups)] # filter duplicate values + self.contact_name = '' # nick choosen by contact + self.groups = [i for i in set(groups)] # filter duplicate values - self.sub = sub - self.ask = ask + self.sub = sub + self.ask = ask - self.priority = priority - self.keyID = keyID - self.msg_id = msg_id - self.last_status_time = last_status_time - self.last_activity_time = last_activity_time + self.priority = priority + self.keyID = keyID + self.msg_id = msg_id + self.last_status_time = last_status_time + self.last_activity_time = last_activity_time - self.pep = {} + self.pep = {} - def get_full_jid(self): - if self.resource: - return self.jid + '/' + self.resource - return self.jid + def get_full_jid(self): + if self.resource: + return self.jid + '/' + self.resource + return self.jid - def get_shown_name(self): - if self.name: - return self.name - if self.contact_name: - return self.contact_name - return self.jid.split('@')[0] + def get_shown_name(self): + if self.name: + return self.name + if self.contact_name: + return self.contact_name + return self.jid.split('@')[0] - def get_shown_groups(self): - if self.is_observer(): - return [_('Observers')] - elif self.is_groupchat(): - return [_('Groupchats')] - elif self.is_transport(): - return [_('Transports')] - elif not self.groups: - return [_('General')] - else: - return self.groups + def get_shown_groups(self): + if self.is_observer(): + return [_('Observers')] + elif self.is_groupchat(): + return [_('Groupchats')] + elif self.is_transport(): + return [_('Transports')] + elif not self.groups: + return [_('General')] + else: + return self.groups - def is_hidden_from_roster(self): - """ - If contact should not be visible in roster - """ - # XEP-0162: http://www.xmpp.org/extensions/xep-0162.html - if self.is_transport(): - return False - if self.sub in ('both', 'to'): - return False - if self.sub in ('none', 'from') and self.ask == 'subscribe': - return False - if self.sub in ('none', 'from') and (self.name or len(self.groups)): - return False - if _('Not in Roster') in self.groups: - return False - return True + def is_hidden_from_roster(self): + """ + If contact should not be visible in roster + """ + # XEP-0162: http://www.xmpp.org/extensions/xep-0162.html + if self.is_transport(): + return False + if self.sub in ('both', 'to'): + return False + if self.sub in ('none', 'from') and self.ask == 'subscribe': + return False + if self.sub in ('none', 'from') and (self.name or len(self.groups)): + return False + if _('Not in Roster') in self.groups: + return False + return True - def is_observer(self): - # XEP-0162: http://www.xmpp.org/extensions/xep-0162.html - is_observer = False - if self.sub == 'from' and not self.is_transport()\ - and self.is_hidden_from_roster(): - is_observer = True - return is_observer + def is_observer(self): + # XEP-0162: http://www.xmpp.org/extensions/xep-0162.html + is_observer = False + if self.sub == 'from' and not self.is_transport()\ + and self.is_hidden_from_roster(): + is_observer = True + return is_observer - def is_groupchat(self): - for account in common.gajim.gc_connected: - if self.jid in common.gajim.gc_connected[account]: - return True - return False + def is_groupchat(self): + for account in common.gajim.gc_connected: + if self.jid in common.gajim.gc_connected[account]: + return True + return False - def is_transport(self): - # if not '@' or '@' starts the jid then contact is transport - return self.jid.find('@') <= 0 + def is_transport(self): + # if not '@' or '@' starts the jid then contact is transport + return self.jid.find('@') <= 0 class GC_Contact(CommonContact): - """ - Information concerning each groupchat contact - """ + """ + Information concerning each groupchat contact + """ - def __init__(self, room_jid, account, name='', show='', status='', role='', - affiliation='', jid='', resource='', our_chatstate=None, - composing_xep=None, chatstate=None): + def __init__(self, room_jid, account, name='', show='', status='', role='', + affiliation='', jid='', resource='', our_chatstate=None, + composing_xep=None, chatstate=None): - CommonContact.__init__(self, jid, account, resource, show, status, name, - our_chatstate, composing_xep, chatstate) + CommonContact.__init__(self, jid, account, resource, show, status, name, + our_chatstate, composing_xep, chatstate) - self.room_jid = room_jid - self.role = role - self.affiliation = affiliation + self.room_jid = room_jid + self.role = role + self.affiliation = affiliation - def get_full_jid(self): - return self.room_jid + '/' + self.name + def get_full_jid(self): + return self.room_jid + '/' + self.name - def get_shown_name(self): - return self.name + def get_shown_name(self): + return self.name - def as_contact(self): - """ - Create a Contact instance from this GC_Contact instance - """ - return Contact(jid=self.get_full_jid(), account=self.account, - name=self.name, groups=[], show=self.show, status=self.status, - sub='none', client_caps=self.client_caps) + def as_contact(self): + """ + Create a Contact instance from this GC_Contact instance + """ + return Contact(jid=self.get_full_jid(), account=self.account, + name=self.name, groups=[], show=self.show, status=self.status, + sub='none', client_caps=self.client_caps) class LegacyContactsAPI: - """ - This is a GOD class for accessing contact and groupchat information. - The API has several flaws: - - * it mixes concerns because it deals with contacts, groupchats, - groupchat contacts and metacontacts - * some methods like get_contact() may return None. This leads to - a lot of duplication all over Gajim because it is not sure - if we receive a proper contact or just None. - - It is a long way to cleanup this API. Therefore just stick with it - and use it as before. We will try to figure out a migration path. - """ - def __init__(self): - self._metacontact_manager = MetacontactManager(self) - self._accounts = {} + """ + This is a GOD class for accessing contact and groupchat information. + The API has several flaws: - def change_account_name(self, old_name, new_name): - self._accounts[new_name] = self._accounts[old_name] - self._accounts[new_name].name = new_name - del self._accounts[old_name] + * it mixes concerns because it deals with contacts, groupchats, + groupchat contacts and metacontacts + * some methods like get_contact() may return None. This leads to + a lot of duplication all over Gajim because it is not sure + if we receive a proper contact or just None. - self._metacontact_manager.change_account_name(old_name, new_name) + It is a long way to cleanup this API. Therefore just stick with it + and use it as before. We will try to figure out a migration path. + """ + def __init__(self): + self._metacontact_manager = MetacontactManager(self) + self._accounts = {} - def add_account(self, account_name): - self._accounts[account_name] = Account(account_name, Contacts(), - GC_Contacts()) - self._metacontact_manager.add_account(account_name) + def change_account_name(self, old_name, new_name): + self._accounts[new_name] = self._accounts[old_name] + self._accounts[new_name].name = new_name + del self._accounts[old_name] - def get_accounts(self): - return self._accounts.keys() + self._metacontact_manager.change_account_name(old_name, new_name) - def remove_account(self, account): - del self._accounts[account] - self._metacontact_manager.remove_account(account) + def add_account(self, account_name): + self._accounts[account_name] = Account(account_name, Contacts(), + GC_Contacts()) + self._metacontact_manager.add_account(account_name) - def create_contact(self, jid, account, name='', groups=[], show='', - status='', sub='', ask='', resource='', priority=0, keyID='', - client_caps=None, our_chatstate=None, chatstate=None, last_status_time=None, - composing_xep=None, last_activity_time=None): - # Use Account object if available - account = self._accounts.get(account, account) - return Contact(jid=jid, account=account, name=name, groups=groups, - show=show, status=status, sub=sub, ask=ask, resource=resource, - priority=priority, keyID=keyID, client_caps=client_caps, - our_chatstate=our_chatstate, chatstate=chatstate, - last_status_time=last_status_time, composing_xep=composing_xep, - last_activity_time=last_activity_time) + def get_accounts(self): + return self._accounts.keys() - def create_self_contact(self, jid, account, resource, show, status, priority, - name='', keyID=''): - conn = common.gajim.connections[account] - nick = name or common.gajim.nicks[account] - account = self._accounts.get(account, account) # Use Account object if available - self_contact = self.create_contact(jid=jid, account=account, - name=nick, groups=['self_contact'], show=show, status=status, - sub='both', ask='none', priority=priority, keyID=keyID, - resource=resource) - self_contact.pep = conn.pep - return self_contact + def remove_account(self, account): + del self._accounts[account] + self._metacontact_manager.remove_account(account) - def create_not_in_roster_contact(self, jid, account, resource='', name='', keyID=''): - account = self._accounts.get(account, account) # Use Account object if available - return self.create_contact(jid=jid, account=account, resource=resource, - name=name, groups=[_('Not in Roster')], show='not in roster', - status='', sub='none', keyID=keyID) + def create_contact(self, jid, account, name='', groups=[], show='', + status='', sub='', ask='', resource='', priority=0, keyID='', + client_caps=None, our_chatstate=None, chatstate=None, last_status_time=None, + composing_xep=None, last_activity_time=None): + # Use Account object if available + account = self._accounts.get(account, account) + return Contact(jid=jid, account=account, name=name, groups=groups, + show=show, status=status, sub=sub, ask=ask, resource=resource, + priority=priority, keyID=keyID, client_caps=client_caps, + our_chatstate=our_chatstate, chatstate=chatstate, + last_status_time=last_status_time, composing_xep=composing_xep, + last_activity_time=last_activity_time) - def copy_contact(self, contact): - return self.create_contact(contact.jid, contact.account, - name=contact.name, groups=contact.groups, show=contact.show, - status=contact.status, sub=contact.sub, ask=contact.ask, - resource=contact.resource, priority=contact.priority, - keyID=contact.keyID, client_caps=contact.client_caps, - our_chatstate=contact.our_chatstate, chatstate=contact.chatstate, - last_status_time=contact.last_status_time, - composing_xep=contact.composing_xep, - last_activity_time=contact.last_activity_time) + def create_self_contact(self, jid, account, resource, show, status, priority, + name='', keyID=''): + conn = common.gajim.connections[account] + nick = name or common.gajim.nicks[account] + account = self._accounts.get(account, account) # Use Account object if available + self_contact = self.create_contact(jid=jid, account=account, + name=nick, groups=['self_contact'], show=show, status=status, + sub='both', ask='none', priority=priority, keyID=keyID, + resource=resource) + self_contact.pep = conn.pep + return self_contact - def add_contact(self, account, contact): - if account not in self._accounts: - self.add_account(account) - return self._accounts[account].contacts.add_contact(contact) + def create_not_in_roster_contact(self, jid, account, resource='', name='', keyID=''): + account = self._accounts.get(account, account) # Use Account object if available + return self.create_contact(jid=jid, account=account, resource=resource, + name=name, groups=[_('Not in Roster')], show='not in roster', + status='', sub='none', keyID=keyID) - def remove_contact(self, account, contact): - if account not in self._accounts: - return - return self._accounts[account].contacts.remove_contact(contact) + def copy_contact(self, contact): + return self.create_contact(contact.jid, contact.account, + name=contact.name, groups=contact.groups, show=contact.show, + status=contact.status, sub=contact.sub, ask=contact.ask, + resource=contact.resource, priority=contact.priority, + keyID=contact.keyID, client_caps=contact.client_caps, + our_chatstate=contact.our_chatstate, chatstate=contact.chatstate, + last_status_time=contact.last_status_time, + composing_xep=contact.composing_xep, + last_activity_time=contact.last_activity_time) - def remove_jid(self, account, jid, remove_meta=True): - self._accounts[account].contacts.remove_jid(jid) - if remove_meta: - self._metacontact_manager.remove_metacontact(account, jid) + def add_contact(self, account, contact): + if account not in self._accounts: + self.add_account(account) + return self._accounts[account].contacts.add_contact(contact) - def get_contacts(self, account, jid): - return self._accounts[account].contacts.get_contacts(jid) + def remove_contact(self, account, contact): + if account not in self._accounts: + return + return self._accounts[account].contacts.remove_contact(contact) - def get_contact(self, account, jid, resource=None): - return self._accounts[account].contacts.get_contact(jid, resource=resource) + def remove_jid(self, account, jid, remove_meta=True): + self._accounts[account].contacts.remove_jid(jid) + if remove_meta: + self._metacontact_manager.remove_metacontact(account, jid) - def iter_contacts(self, account): - for contact in self._accounts[account].contacts.iter_contacts(): - yield contact + def get_contacts(self, account, jid): + return self._accounts[account].contacts.get_contacts(jid) - def get_contact_from_full_jid(self, account, fjid): - return self._accounts[account].contacts.get_contact_from_full_jid(fjid) + def get_contact(self, account, jid, resource=None): + return self._accounts[account].contacts.get_contact(jid, resource=resource) - def get_first_contact_from_jid(self, account, jid): - return self._accounts[account].contacts.get_first_contact_from_jid(jid) + def iter_contacts(self, account): + for contact in self._accounts[account].contacts.iter_contacts(): + yield contact - def get_contacts_from_group(self, account, group): - return self._accounts[account].contacts.get_contacts_from_group(group) + def get_contact_from_full_jid(self, account, fjid): + return self._accounts[account].contacts.get_contact_from_full_jid(fjid) - def get_contacts_jid_list(self, account): - return self._accounts[account].contacts.get_contacts_jid_list() + def get_first_contact_from_jid(self, account, jid): + return self._accounts[account].contacts.get_first_contact_from_jid(jid) - def get_jid_list(self, account): - return self._accounts[account].contacts.get_jid_list() + def get_contacts_from_group(self, account, group): + return self._accounts[account].contacts.get_contacts_from_group(group) - def change_contact_jid(self, old_jid, new_jid, account): - return self._accounts[account].change_contact_jid(old_jid, new_jid) + def get_contacts_jid_list(self, account): + return self._accounts[account].contacts.get_contacts_jid_list() - def get_highest_prio_contact_from_contacts(self, contacts): - if not contacts: - return None - prim_contact = contacts[0] - for contact in contacts[1:]: - if int(contact.priority) > int(prim_contact.priority): - prim_contact = contact - return prim_contact + def get_jid_list(self, account): + return self._accounts[account].contacts.get_jid_list() - def get_contact_with_highest_priority(self, account, jid): - contacts = self.get_contacts(account, jid) - if not contacts and '/' in jid: - # jid may be a fake jid, try it - room, nick = jid.split('/', 1) - contact = self.get_gc_contact(account, room, nick) - return contact - return self.get_highest_prio_contact_from_contacts(contacts) + def change_contact_jid(self, old_jid, new_jid, account): + return self._accounts[account].change_contact_jid(old_jid, new_jid) - def get_nb_online_total_contacts(self, accounts=[], groups=[]): - """ - Return the number of online contacts and the total number of contacts - """ - if accounts == []: - accounts = self.get_accounts() - nbr_online = 0 - nbr_total = 0 - for account in accounts: - our_jid = common.gajim.get_jid_from_account(account) - for jid in self.get_jid_list(account): - if jid == our_jid: - continue - if common.gajim.jid_is_transport(jid) and not \ - _('Transports') in groups: - # do not count transports - continue - if self.has_brother(account, jid, accounts) and not \ - self.is_big_brother(account, jid, accounts): - # count metacontacts only once - continue - contact = self.get_contact_with_highest_priority(account, jid) - if _('Not in roster') in contact.groups: - continue - in_groups = False - if groups == []: - in_groups = True - else: - for group in groups: - if group in contact.get_shown_groups(): - in_groups = True - break + def get_highest_prio_contact_from_contacts(self, contacts): + if not contacts: + return None + prim_contact = contacts[0] + for contact in contacts[1:]: + if int(contact.priority) > int(prim_contact.priority): + prim_contact = contact + return prim_contact - if in_groups: - if contact.show not in ('offline', 'error'): - nbr_online += 1 - nbr_total += 1 - return nbr_online, nbr_total + def get_contact_with_highest_priority(self, account, jid): + contacts = self.get_contacts(account, jid) + if not contacts and '/' in jid: + # jid may be a fake jid, try it + room, nick = jid.split('/', 1) + contact = self.get_gc_contact(account, room, nick) + return contact + return self.get_highest_prio_contact_from_contacts(contacts) - def __getattr__(self, attr_name): - # Only called if self has no attr_name - if hasattr(self._metacontact_manager, attr_name): - return getattr(self._metacontact_manager, attr_name) - else: - raise AttributeError(attr_name) + def get_nb_online_total_contacts(self, accounts=[], groups=[]): + """ + Return the number of online contacts and the total number of contacts + """ + if accounts == []: + accounts = self.get_accounts() + nbr_online = 0 + nbr_total = 0 + for account in accounts: + our_jid = common.gajim.get_jid_from_account(account) + for jid in self.get_jid_list(account): + if jid == our_jid: + continue + if common.gajim.jid_is_transport(jid) and not \ + _('Transports') in groups: + # do not count transports + continue + if self.has_brother(account, jid, accounts) and not \ + self.is_big_brother(account, jid, accounts): + # count metacontacts only once + continue + contact = self.get_contact_with_highest_priority(account, jid) + if _('Not in roster') in contact.groups: + continue + in_groups = False + if groups == []: + in_groups = True + else: + for group in groups: + if group in contact.get_shown_groups(): + in_groups = True + break - def create_gc_contact(self, room_jid, account, name='', show='', status='', - role='', affiliation='', jid='', resource=''): - account = self._accounts.get(account, account) # Use Account object if available - return GC_Contact(room_jid, account, name, show, status, role, affiliation, jid, - resource) + if in_groups: + if contact.show not in ('offline', 'error'): + nbr_online += 1 + nbr_total += 1 + return nbr_online, nbr_total - def add_gc_contact(self, account, gc_contact): - return self._accounts[account].gc_contacts.add_gc_contact(gc_contact) + def __getattr__(self, attr_name): + # Only called if self has no attr_name + if hasattr(self._metacontact_manager, attr_name): + return getattr(self._metacontact_manager, attr_name) + else: + raise AttributeError(attr_name) - def remove_gc_contact(self, account, gc_contact): - return self._accounts[account].gc_contacts.remove_gc_contact(gc_contact) + def create_gc_contact(self, room_jid, account, name='', show='', status='', + role='', affiliation='', jid='', resource=''): + account = self._accounts.get(account, account) # Use Account object if available + return GC_Contact(room_jid, account, name, show, status, role, affiliation, jid, + resource) - def remove_room(self, account, room_jid): - return self._accounts[account].gc_contacts.remove_room(room_jid) + def add_gc_contact(self, account, gc_contact): + return self._accounts[account].gc_contacts.add_gc_contact(gc_contact) - def get_gc_list(self, account): - return self._accounts[account].gc_contacts.get_gc_list() + def remove_gc_contact(self, account, gc_contact): + return self._accounts[account].gc_contacts.remove_gc_contact(gc_contact) - def get_nick_list(self, account, room_jid): - return self._accounts[account].gc_contacts.get_nick_list(room_jid) + def remove_room(self, account, room_jid): + return self._accounts[account].gc_contacts.remove_room(room_jid) - def get_gc_contact(self, account, room_jid, nick): - return self._accounts[account].gc_contacts.get_gc_contact(room_jid, nick) + def get_gc_list(self, account): + return self._accounts[account].gc_contacts.get_gc_list() - def get_nb_role_total_gc_contacts(self, account, room_jid, role): - return self._accounts[account].gc_contacts.get_nb_role_total_gc_contacts(room_jid, role) + def get_nick_list(self, account, room_jid): + return self._accounts[account].gc_contacts.get_nick_list(room_jid) + + def get_gc_contact(self, account, room_jid, nick): + return self._accounts[account].gc_contacts.get_gc_contact(room_jid, nick) + + def get_nb_role_total_gc_contacts(self, account, room_jid, role): + return self._accounts[account].gc_contacts.get_nb_role_total_gc_contacts(room_jid, role) class Contacts(): - """ - This is a breakout of the contact related behavior of the old - Contacts class (which is not called LegacyContactsAPI) - """ - def __init__(self): - # list of contacts {jid1: [C1, C2]}, } one Contact per resource - self._contacts = {} + """ + This is a breakout of the contact related behavior of the old + Contacts class (which is not called LegacyContactsAPI) + """ + def __init__(self): + # list of contacts {jid1: [C1, C2]}, } one Contact per resource + self._contacts = {} - def add_contact(self, contact): - if contact.jid not in self._contacts: - self._contacts[contact.jid] = [contact] - return - contacts = self._contacts[contact.jid] - # We had only one that was offline, remove it - if len(contacts) == 1 and contacts[0].show == 'offline': - # Do not use self.remove_contact: it deteles - # self._contacts[account][contact.jid] - contacts.remove(contacts[0]) - # If same JID with same resource already exists, use the new one - for c in contacts: - if c.resource == contact.resource: - self.remove_contact(c) - break - contacts.append(contact) + def add_contact(self, contact): + if contact.jid not in self._contacts: + self._contacts[contact.jid] = [contact] + return + contacts = self._contacts[contact.jid] + # We had only one that was offline, remove it + if len(contacts) == 1 and contacts[0].show == 'offline': + # Do not use self.remove_contact: it deteles + # self._contacts[account][contact.jid] + contacts.remove(contacts[0]) + # If same JID with same resource already exists, use the new one + for c in contacts: + if c.resource == contact.resource: + self.remove_contact(c) + break + contacts.append(contact) - def remove_contact(self, contact): - if contact.jid not in self._contacts: - return - if contact in self._contacts[contact.jid]: - self._contacts[contact.jid].remove(contact) - if len(self._contacts[contact.jid]) == 0: - del self._contacts[contact.jid] + def remove_contact(self, contact): + if contact.jid not in self._contacts: + return + if contact in self._contacts[contact.jid]: + self._contacts[contact.jid].remove(contact) + if len(self._contacts[contact.jid]) == 0: + del self._contacts[contact.jid] - def remove_jid(self, jid): - """ - Remove all contacts for a given jid - """ - if jid in self._contacts: - del self._contacts[jid] + def remove_jid(self, jid): + """ + Remove all contacts for a given jid + """ + if jid in self._contacts: + del self._contacts[jid] - def get_contacts(self, jid): - """ - Return the list of contact instances for this jid - """ - return self._contacts.get(jid, []) + def get_contacts(self, jid): + """ + Return the list of contact instances for this jid + """ + return self._contacts.get(jid, []) - def get_contact(self, jid, resource=None): - ### WARNING ### - # This function returns a *RANDOM* resource if resource = None! - # Do *NOT* use if you need to get the contact to which you - # send a message for example, as a bare JID in Jabber means - # highest available resource, which this function ignores! - """ - Return the contact instance for the given resource if it's given else the - first contact is no resource is given or None if there is not - """ - if jid in self._contacts: - if not resource: - return self._contacts[jid][0] - for c in self._contacts[jid]: - if c.resource == resource: - return c + def get_contact(self, jid, resource=None): + ### WARNING ### + # This function returns a *RANDOM* resource if resource = None! + # Do *NOT* use if you need to get the contact to which you + # send a message for example, as a bare JID in Jabber means + # highest available resource, which this function ignores! + """ + Return the contact instance for the given resource if it's given else the + first contact is no resource is given or None if there is not + """ + if jid in self._contacts: + if not resource: + return self._contacts[jid][0] + for c in self._contacts[jid]: + if c.resource == resource: + return c - def iter_contacts(self): - for jid in self._contacts.keys(): - for contact in self._contacts[jid][:]: - yield contact + def iter_contacts(self): + for jid in self._contacts.keys(): + for contact in self._contacts[jid][:]: + yield contact - def get_jid_list(self): - return self._contacts.keys() + def get_jid_list(self): + return self._contacts.keys() - def get_contacts_jid_list(self): - contacts = self._contacts.keys() - for jid in self._contacts.keys(): - if self._contacts[jid][0].is_groupchat(): - contacts.remove(jid) - return contacts + def get_contacts_jid_list(self): + contacts = self._contacts.keys() + for jid in self._contacts.keys(): + if self._contacts[jid][0].is_groupchat(): + contacts.remove(jid) + return contacts - def get_contact_from_full_jid(self, fjid): - """ - Get Contact object for specific resource of given jid - """ - barejid, resource = common.gajim.get_room_and_nick_from_fjid(fjid) - return self.get_contact(barejid, resource) + def get_contact_from_full_jid(self, fjid): + """ + Get Contact object for specific resource of given jid + """ + barejid, resource = common.gajim.get_room_and_nick_from_fjid(fjid) + return self.get_contact(barejid, resource) - def get_first_contact_from_jid(self, jid): - if jid in self._contacts: - return self._contacts[jid][0] + def get_first_contact_from_jid(self, jid): + if jid in self._contacts: + return self._contacts[jid][0] - def get_contacts_from_group(self, group): - """ - Return all contacts in the given group - """ - group_contacts = [] - for jid in self._contacts: - contacts = self.get_contacts(jid) - if group in contacts[0].groups: - group_contacts += contacts - return group_contacts + def get_contacts_from_group(self, group): + """ + Return all contacts in the given group + """ + group_contacts = [] + for jid in self._contacts: + contacts = self.get_contacts(jid) + if group in contacts[0].groups: + group_contacts += contacts + return group_contacts - def change_contact_jid(self, old_jid, new_jid): - if old_jid not in self._contacts: - return - self._contacts[new_jid] = [] - for _contact in self._contacts[old_jid]: - _contact.jid = new_jid - self._contacts[new_jid].append(_contact) - del self._contacts[old_jid] + def change_contact_jid(self, old_jid, new_jid): + if old_jid not in self._contacts: + return + self._contacts[new_jid] = [] + for _contact in self._contacts[old_jid]: + _contact.jid = new_jid + self._contacts[new_jid].append(_contact) + del self._contacts[old_jid] class GC_Contacts(): - def __init__(self): - # list of contacts that are in gc {room_jid: {nick: C}}} - self._rooms = {} + def __init__(self): + # list of contacts that are in gc {room_jid: {nick: C}}} + self._rooms = {} - def add_gc_contact(self, gc_contact): - if gc_contact.room_jid not in self._rooms: - self._rooms[gc_contact.room_jid] = {gc_contact.name: gc_contact} - else: - self._rooms[gc_contact.room_jid][gc_contact.name] = gc_contact + def add_gc_contact(self, gc_contact): + if gc_contact.room_jid not in self._rooms: + self._rooms[gc_contact.room_jid] = {gc_contact.name: gc_contact} + else: + self._rooms[gc_contact.room_jid][gc_contact.name] = gc_contact - def remove_gc_contact(self, gc_contact): - if gc_contact.room_jid not in self._rooms: - return - if gc_contact.name not in self._rooms[gc_contact.room_jid]: - return - del self._rooms[gc_contact.room_jid][gc_contact.name] - # It was the last nick in room ? - if not len(self._rooms[gc_contact.room_jid]): - del self._rooms[gc_contact.room_jid] + def remove_gc_contact(self, gc_contact): + if gc_contact.room_jid not in self._rooms: + return + if gc_contact.name not in self._rooms[gc_contact.room_jid]: + return + del self._rooms[gc_contact.room_jid][gc_contact.name] + # It was the last nick in room ? + if not len(self._rooms[gc_contact.room_jid]): + del self._rooms[gc_contact.room_jid] - def remove_room(self, room_jid): - if room_jid in self._rooms: - del self._rooms[room_jid] + def remove_room(self, room_jid): + if room_jid in self._rooms: + del self._rooms[room_jid] - def get_gc_list(self): - return self._rooms.keys() + def get_gc_list(self): + return self._rooms.keys() - def get_nick_list(self, room_jid): - gc_list = self.get_gc_list() - if not room_jid in gc_list: - return [] - return self._rooms[room_jid].keys() + def get_nick_list(self, room_jid): + gc_list = self.get_gc_list() + if not room_jid in gc_list: + return [] + return self._rooms[room_jid].keys() - def get_gc_contact(self, room_jid, nick): - nick_list = self.get_nick_list(room_jid) - if not nick in nick_list: - return None - return self._rooms[room_jid][nick] + def get_gc_contact(self, room_jid, nick): + nick_list = self.get_nick_list(room_jid) + if not nick in nick_list: + return None + return self._rooms[room_jid][nick] - def get_nb_role_total_gc_contacts(self, room_jid, role): - """ - Return the number of group chat contacts for the given role and the total - number of group chat contacts - """ - if room_jid not in self._rooms: - return 0, 0 - nb_role = nb_total = 0 - for nick in self._rooms[room_jid]: - if self._rooms[room_jid][nick].role == role: - nb_role += 1 - nb_total += 1 - return nb_role, nb_total + def get_nb_role_total_gc_contacts(self, room_jid, role): + """ + Return the number of group chat contacts for the given role and the total + number of group chat contacts + """ + if room_jid not in self._rooms: + return 0, 0 + nb_role = nb_total = 0 + for nick in self._rooms[room_jid]: + if self._rooms[room_jid][nick].role == role: + nb_role += 1 + nb_total += 1 + return nb_role, nb_total class MetacontactManager(): - def __init__(self, contacts): - self._metacontacts_tags = {} - self._contacts = contacts + def __init__(self, contacts): + self._metacontacts_tags = {} + self._contacts = contacts - def change_account_name(self, old_name, new_name): - self._metacontacts_tags[new_name] = self._metacontacts_tags[old_name] - del self._metacontacts_tags[old_name] + def change_account_name(self, old_name, new_name): + self._metacontacts_tags[new_name] = self._metacontacts_tags[old_name] + del self._metacontacts_tags[old_name] - def add_account(self, account): - if account not in self._metacontacts_tags: - self._metacontacts_tags[account] = {} + def add_account(self, account): + if account not in self._metacontacts_tags: + self._metacontacts_tags[account] = {} - def remove_account(self, account): - del self._metacontacts_tags[account] + def remove_account(self, account): + del self._metacontacts_tags[account] - def define_metacontacts(self, account, tags_list): - self._metacontacts_tags[account] = tags_list + def define_metacontacts(self, account, tags_list): + self._metacontacts_tags[account] = tags_list - def _get_new_metacontacts_tag(self, jid): - if not jid in self._metacontacts_tags: - return jid - #FIXME: can this append ? - assert False + def _get_new_metacontacts_tag(self, jid): + if not jid in self._metacontacts_tags: + return jid + #FIXME: can this append ? + assert False - def iter_metacontacts_families(self, account): - for tag in self._metacontacts_tags[account]: - family = self._get_metacontacts_family_from_tag(account, tag) - yield family + def iter_metacontacts_families(self, account): + for tag in self._metacontacts_tags[account]: + family = self._get_metacontacts_family_from_tag(account, tag) + yield family - def _get_metacontacts_tag(self, account, jid): - """ - Return the tag of a jid - """ - if not account in self._metacontacts_tags: - return None - for tag in self._metacontacts_tags[account]: - for data in self._metacontacts_tags[account][tag]: - if data['jid'] == jid: - return tag - return None + def _get_metacontacts_tag(self, account, jid): + """ + Return the tag of a jid + """ + if not account in self._metacontacts_tags: + return None + for tag in self._metacontacts_tags[account]: + for data in self._metacontacts_tags[account][tag]: + if data['jid'] == jid: + return tag + return None - def add_metacontact(self, brother_account, brother_jid, account, jid, order=None): - tag = self._get_metacontacts_tag(brother_account, brother_jid) - if not tag: - tag = self._get_new_metacontacts_tag(brother_jid) - self._metacontacts_tags[brother_account][tag] = [{'jid': brother_jid, - 'tag': tag}] - if brother_account != account: - common.gajim.connections[brother_account].store_metacontacts( - self._metacontacts_tags[brother_account]) - # be sure jid has no other tag - old_tag = self._get_metacontacts_tag(account, jid) - while old_tag: - self.remove_metacontact(account, jid) - old_tag = self._get_metacontacts_tag(account, jid) - if tag not in self._metacontacts_tags[account]: - self._metacontacts_tags[account][tag] = [{'jid': jid, 'tag': tag}] - else: - if order: - self._metacontacts_tags[account][tag].append({'jid': jid, - 'tag': tag, 'order': order}) - else: - self._metacontacts_tags[account][tag].append({'jid': jid, - 'tag': tag}) - common.gajim.connections[account].store_metacontacts( - self._metacontacts_tags[account]) + def add_metacontact(self, brother_account, brother_jid, account, jid, order=None): + tag = self._get_metacontacts_tag(brother_account, brother_jid) + if not tag: + tag = self._get_new_metacontacts_tag(brother_jid) + self._metacontacts_tags[brother_account][tag] = [{'jid': brother_jid, + 'tag': tag}] + if brother_account != account: + common.gajim.connections[brother_account].store_metacontacts( + self._metacontacts_tags[brother_account]) + # be sure jid has no other tag + old_tag = self._get_metacontacts_tag(account, jid) + while old_tag: + self.remove_metacontact(account, jid) + old_tag = self._get_metacontacts_tag(account, jid) + if tag not in self._metacontacts_tags[account]: + self._metacontacts_tags[account][tag] = [{'jid': jid, 'tag': tag}] + else: + if order: + self._metacontacts_tags[account][tag].append({'jid': jid, + 'tag': tag, 'order': order}) + else: + self._metacontacts_tags[account][tag].append({'jid': jid, + 'tag': tag}) + common.gajim.connections[account].store_metacontacts( + self._metacontacts_tags[account]) - def remove_metacontact(self, account, jid): - if not account in self._metacontacts_tags: - return + def remove_metacontact(self, account, jid): + if not account in self._metacontacts_tags: + return - found = None - for tag in self._metacontacts_tags[account]: - for data in self._metacontacts_tags[account][tag]: - if data['jid'] == jid: - found = data - break - if found: - self._metacontacts_tags[account][tag].remove(found) - common.gajim.connections[account].store_metacontacts( - self._metacontacts_tags[account]) - break + found = None + for tag in self._metacontacts_tags[account]: + for data in self._metacontacts_tags[account][tag]: + if data['jid'] == jid: + found = data + break + if found: + self._metacontacts_tags[account][tag].remove(found) + common.gajim.connections[account].store_metacontacts( + self._metacontacts_tags[account]) + break - def has_brother(self, account, jid, accounts): - tag = self._get_metacontacts_tag(account, jid) - if not tag: - return False - meta_jids = self._get_metacontacts_jids(tag, accounts) - return len(meta_jids) > 1 or len(meta_jids[account]) > 1 + def has_brother(self, account, jid, accounts): + tag = self._get_metacontacts_tag(account, jid) + if not tag: + return False + meta_jids = self._get_metacontacts_jids(tag, accounts) + return len(meta_jids) > 1 or len(meta_jids[account]) > 1 - def is_big_brother(self, account, jid, accounts): - family = self.get_metacontacts_family(account, jid) - if family: - nearby_family = [data for data in family - if account in accounts] - bb_data = self._get_metacontacts_big_brother(nearby_family) - if bb_data['jid'] == jid and bb_data['account'] == account: - return True - return False + def is_big_brother(self, account, jid, accounts): + family = self.get_metacontacts_family(account, jid) + if family: + nearby_family = [data for data in family + if account in accounts] + bb_data = self._get_metacontacts_big_brother(nearby_family) + if bb_data['jid'] == jid and bb_data['account'] == account: + return True + return False - def _get_metacontacts_jids(self, tag, accounts): - """ - Return all jid for the given tag in the form {acct: [jid1, jid2],.} - """ - answers = {} - for account in self._metacontacts_tags: - if tag in self._metacontacts_tags[account]: - if account not in accounts: - continue - answers[account] = [] - for data in self._metacontacts_tags[account][tag]: - answers[account].append(data['jid']) - return answers + def _get_metacontacts_jids(self, tag, accounts): + """ + Return all jid for the given tag in the form {acct: [jid1, jid2],.} + """ + answers = {} + for account in self._metacontacts_tags: + if tag in self._metacontacts_tags[account]: + if account not in accounts: + continue + answers[account] = [] + for data in self._metacontacts_tags[account][tag]: + answers[account].append(data['jid']) + return answers - def get_metacontacts_family(self, account, jid): - """ - Return the family of the given jid, including jid in the form: - [{'account': acct, 'jid': jid, 'order': order}, ] 'order' is optional - """ - tag = self._get_metacontacts_tag(account, jid) - return self._get_metacontacts_family_from_tag(account, tag) + def get_metacontacts_family(self, account, jid): + """ + Return the family of the given jid, including jid in the form: + [{'account': acct, 'jid': jid, 'order': order}, ] 'order' is optional + """ + tag = self._get_metacontacts_tag(account, jid) + return self._get_metacontacts_family_from_tag(account, tag) - def _get_metacontacts_family_from_tag(self, account, tag): - if not tag: - return [] - answers = [] - for account in self._metacontacts_tags: - if tag in self._metacontacts_tags[account]: - for data in self._metacontacts_tags[account][tag]: - data['account'] = account - answers.append(data) - return answers + def _get_metacontacts_family_from_tag(self, account, tag): + if not tag: + return [] + answers = [] + for account in self._metacontacts_tags: + if tag in self._metacontacts_tags[account]: + for data in self._metacontacts_tags[account][tag]: + data['account'] = account + answers.append(data) + return answers - def _compare_metacontacts(self, data1, data2): - """ - Compare 2 metacontacts + def _compare_metacontacts(self, data1, data2): + """ + Compare 2 metacontacts - Data is {'jid': jid, 'account': account, 'order': order} order is - optional - """ - jid1 = data1['jid'] - jid2 = data2['jid'] - account1 = data1['account'] - account2 = data2['account'] - contact1 = self._contacts.get_contact_with_highest_priority(account1, jid1) - contact2 = self._contacts.get_contact_with_highest_priority(account2, jid2) - show_list = ['not in roster', 'error', 'offline', 'invisible', 'dnd', - 'xa', 'away', 'chat', 'online', 'requested', 'message'] - # contact can be null when a jid listed in the metacontact data - # is not in our roster - if not contact1: - if contact2: - return -1 # prefer the known contact - else: - show1 = 0 - priority1 = 0 - else: - show1 = show_list.index(contact1.show) - priority1 = contact1.priority - if not contact2: - if contact1: - return 1 # prefer the known contact - else: - show2 = 0 - priority2 = 0 - else: - show2 = show_list.index(contact2.show) - priority2 = contact2.priority - # If only one is offline, it's always second - if show1 > 2 and show2 < 3: - return 1 - if show2 > 2 and show1 < 3: - return -1 - if 'order' in data1 and 'order' in data2: - if data1['order'] > data2['order']: - return 1 - if data1['order'] < data2['order']: - return -1 - if 'order' in data1: - return 1 - if 'order' in data2: - return -1 - transport1 = common.gajim.get_transport_name_from_jid(jid1) - transport2 = common.gajim.get_transport_name_from_jid(jid2) - if transport2 and not transport1: - return 1 - if transport1 and not transport2: - return -1 - if show1 > show2: - return 1 - if show2 > show1: - return -1 - if priority1 > priority2: - return 1 - if priority2 > priority1: - return -1 - server1 = common.gajim.get_server_from_jid(jid1) - server2 = common.gajim.get_server_from_jid(jid2) - myserver1 = common.gajim.config.get_per('accounts', account1, 'hostname') - myserver2 = common.gajim.config.get_per('accounts', account2, 'hostname') - if server1 == myserver1: - if server2 != myserver2: - return 1 - elif server2 == myserver2: - return -1 - if jid1 > jid2: - return 1 - if jid2 > jid1: - return -1 - # If all is the same, compare accounts, they can't be the same - if account1 > account2: - return 1 - if account2 > account1: - return -1 - return 0 + Data is {'jid': jid, 'account': account, 'order': order} order is + optional + """ + jid1 = data1['jid'] + jid2 = data2['jid'] + account1 = data1['account'] + account2 = data2['account'] + contact1 = self._contacts.get_contact_with_highest_priority(account1, jid1) + contact2 = self._contacts.get_contact_with_highest_priority(account2, jid2) + show_list = ['not in roster', 'error', 'offline', 'invisible', 'dnd', + 'xa', 'away', 'chat', 'online', 'requested', 'message'] + # contact can be null when a jid listed in the metacontact data + # is not in our roster + if not contact1: + if contact2: + return -1 # prefer the known contact + else: + show1 = 0 + priority1 = 0 + else: + show1 = show_list.index(contact1.show) + priority1 = contact1.priority + if not contact2: + if contact1: + return 1 # prefer the known contact + else: + show2 = 0 + priority2 = 0 + else: + show2 = show_list.index(contact2.show) + priority2 = contact2.priority + # If only one is offline, it's always second + if show1 > 2 and show2 < 3: + return 1 + if show2 > 2 and show1 < 3: + return -1 + if 'order' in data1 and 'order' in data2: + if data1['order'] > data2['order']: + return 1 + if data1['order'] < data2['order']: + return -1 + if 'order' in data1: + return 1 + if 'order' in data2: + return -1 + transport1 = common.gajim.get_transport_name_from_jid(jid1) + transport2 = common.gajim.get_transport_name_from_jid(jid2) + if transport2 and not transport1: + return 1 + if transport1 and not transport2: + return -1 + if show1 > show2: + return 1 + if show2 > show1: + return -1 + if priority1 > priority2: + return 1 + if priority2 > priority1: + return -1 + server1 = common.gajim.get_server_from_jid(jid1) + server2 = common.gajim.get_server_from_jid(jid2) + myserver1 = common.gajim.config.get_per('accounts', account1, 'hostname') + myserver2 = common.gajim.config.get_per('accounts', account2, 'hostname') + if server1 == myserver1: + if server2 != myserver2: + return 1 + elif server2 == myserver2: + return -1 + if jid1 > jid2: + return 1 + if jid2 > jid1: + return -1 + # If all is the same, compare accounts, they can't be the same + if account1 > account2: + return 1 + if account2 > account1: + return -1 + return 0 - def get_nearby_family_and_big_brother(self, family, account): - """ - Return the nearby family and its Big Brother + def get_nearby_family_and_big_brother(self, family, account): + """ + Return the nearby family and its Big Brother - Nearby family is the part of the family that is grouped with the - metacontact. A metacontact may be over different accounts. If accounts - are not merged then the given family is split account wise. + Nearby family is the part of the family that is grouped with the + metacontact. A metacontact may be over different accounts. If accounts + are not merged then the given family is split account wise. - (nearby_family, big_brother_jid, big_brother_account) - """ - if common.gajim.config.get('mergeaccounts'): - # group all together - nearby_family = family - else: - # we want one nearby_family per account - nearby_family = [data for data in family if account == data['account']] + (nearby_family, big_brother_jid, big_brother_account) + """ + if common.gajim.config.get('mergeaccounts'): + # group all together + nearby_family = family + else: + # we want one nearby_family per account + nearby_family = [data for data in family if account == data['account']] - big_brother_data = self._get_metacontacts_big_brother(nearby_family) - big_brother_jid = big_brother_data['jid'] - big_brother_account = big_brother_data['account'] + big_brother_data = self._get_metacontacts_big_brother(nearby_family) + big_brother_jid = big_brother_data['jid'] + big_brother_account = big_brother_data['account'] - return (nearby_family, big_brother_jid, big_brother_account) + return (nearby_family, big_brother_jid, big_brother_account) - def _get_metacontacts_big_brother(self, family): - """ - Which of the family will be the big brother under wich all others will be - ? - """ - family.sort(cmp=self._compare_metacontacts) - return family[-1] - -# vim: se ts=3: + def _get_metacontacts_big_brother(self, family): + """ + Which of the family will be the big brother under wich all others will be + ? + """ + family.sort(cmp=self._compare_metacontacts) + return family[-1] diff --git a/src/common/crypto.py b/src/common/crypto.py index 785b753bb..c787df6aa 100644 --- a/src/common/crypto.py +++ b/src/common/crypto.py @@ -26,82 +26,80 @@ from hashlib import sha256 as SHA256 # convert a large integer to a big-endian bitstring def encode_mpi(n): - if n >= 256: - return encode_mpi(n / 256) + chr(n % 256) - else: - return chr(n) + if n >= 256: + return encode_mpi(n / 256) + chr(n % 256) + else: + return chr(n) # convert a large integer to a big-endian bitstring, padded with \x00s to # a multiple of 16 bytes def encode_mpi_with_padding(n): - return pad_to_multiple(encode_mpi(n), 16, '\x00', True) + return pad_to_multiple(encode_mpi(n), 16, '\x00', True) # pad 'string' to a multiple of 'multiple_of' with 'char'. # pad on the left if 'left', otherwise pad on the right. def pad_to_multiple(string, multiple_of, char, left): - mod = len(string) % multiple_of - if mod == 0: - return string - else: - padding = (multiple_of - mod) * char + mod = len(string) % multiple_of + if mod == 0: + return string + else: + padding = (multiple_of - mod) * char - if left: - return padding + string - else: - return string + padding + if left: + return padding + string + else: + return string + padding # convert a big-endian bitstring to an integer def decode_mpi(s): - if len(s) == 0: - return 0 - else: - return 256 * decode_mpi(s[:-1]) + ord(s[-1]) + if len(s) == 0: + return 0 + else: + return 256 * decode_mpi(s[:-1]) + ord(s[-1]) def sha256(string): - sh = SHA256() - sh.update(string) - return sh.digest() + sh = SHA256() + sh.update(string) + return sh.digest() base28_chr = "acdefghikmopqruvwxy123456789" def sas_28x5(m_a, form_b): - sha = sha256(m_a + form_b + 'Short Authentication String') - lsb24 = decode_mpi(sha[-3:]) - return base28(lsb24) + sha = sha256(m_a + form_b + 'Short Authentication String') + lsb24 = decode_mpi(sha[-3:]) + return base28(lsb24) def base28(n): - if n >= 28: - return base28(n / 28) + base28_chr[n % 28] - else: - return base28_chr[n] + if n >= 28: + return base28(n / 28) + base28_chr[n % 28] + else: + return base28_chr[n] def random_bytes(bytes_): - return os.urandom(bytes_) + return os.urandom(bytes_) def generate_nonce(): - return random_bytes(8) + return random_bytes(8) # generate a random number between 'bottom' and 'top' def srand(bottom, top): - # minimum number of bytes needed to represent that range - bytes = int(math.ceil(math.log(top - bottom, 256))) + # minimum number of bytes needed to represent that range + bytes = int(math.ceil(math.log(top - bottom, 256))) - # in retrospect, this is horribly inadequate. - return (decode_mpi(random_bytes(bytes)) % (top - bottom)) + bottom + # in retrospect, this is horribly inadequate. + return (decode_mpi(random_bytes(bytes)) % (top - bottom)) + bottom # a faster version of (base ** exp) % mod -# taken from +# taken from def powmod(base, exp, mod): - square = base % mod - result = 1 + square = base % mod + result = 1 - while exp > 0: - if exp & 1: # exponent is odd - result = (result * square) % mod + while exp > 0: + if exp & 1: # exponent is odd + result = (result * square) % mod - square = (square * square) % mod - exp /= 2 + square = (square * square) % mod + exp /= 2 - return result - -# vim: se ts=3: + return result diff --git a/src/common/dataforms.py b/src/common/dataforms.py index 048077a74..4620556c4 100644 --- a/src/common/dataforms.py +++ b/src/common/dataforms.py @@ -38,583 +38,580 @@ class WrongFieldValue(Error): pass # helper class to change class of already existing object class ExtendedNode(xmpp.Node, object): - @classmethod - def __new__(cls, *a, **b): - if 'extend' not in b.keys() or not b['extend']: - return object.__new__(cls) + @classmethod + def __new__(cls, *a, **b): + if 'extend' not in b.keys() or not b['extend']: + return object.__new__(cls) - extend = b['extend'] - assert issubclass(cls, extend.__class__) - extend.__class__ = cls - return extend + extend = b['extend'] + assert issubclass(cls, extend.__class__) + extend.__class__ = cls + return extend # helper decorator to create properties in cleaner way def nested_property(f): - ret = f() - p = {'doc': f.__doc__} - for v in ('fget', 'fset', 'fdel', 'doc'): - if v in ret.keys(): p[v]=ret[v] - return property(**p) + ret = f() + p = {'doc': f.__doc__} + for v in ('fget', 'fset', 'fdel', 'doc'): + if v in ret.keys(): p[v]=ret[v] + return property(**p) # helper to create fields from scratch def Field(typ, **attrs): - ''' Helper function to create a field of given type. ''' - f = { - 'boolean': BooleanField, - 'fixed': StringField, - 'hidden': StringField, - 'text-private': StringField, - 'text-single': StringField, - 'jid-multi': ListMultiField, - 'jid-single': ListSingleField, - 'list-multi': ListMultiField, - 'list-single': ListSingleField, - 'text-multi': TextMultiField, - }[typ](typ=typ, **attrs) - return f + ''' Helper function to create a field of given type. ''' + f = { + 'boolean': BooleanField, + 'fixed': StringField, + 'hidden': StringField, + 'text-private': StringField, + 'text-single': StringField, + 'jid-multi': ListMultiField, + 'jid-single': ListSingleField, + 'list-multi': ListMultiField, + 'list-single': ListSingleField, + 'text-multi': TextMultiField, + }[typ](typ=typ, **attrs) + return f def ExtendField(node): - """ - Helper function to extend a node to field of appropriate type - """ - # when validation (XEP-122) will go in, we could have another classes - # like DateTimeField - so that dicts in Field() and ExtendField() will - # be different... - typ=node.getAttr('type') - f = { - 'boolean': BooleanField, - 'fixed': StringField, - 'hidden': StringField, - 'text-private': StringField, - 'text-single': StringField, - 'jid-multi': ListMultiField, - 'jid-single': ListSingleField, - 'list-multi': ListMultiField, - 'list-single': ListSingleField, - 'text-multi': TextMultiField, - } - if typ not in f: - typ = 'text-single' - return f[typ](extend=node) + """ + Helper function to extend a node to field of appropriate type + """ + # when validation (XEP-122) will go in, we could have another classes + # like DateTimeField - so that dicts in Field() and ExtendField() will + # be different... + typ=node.getAttr('type') + f = { + 'boolean': BooleanField, + 'fixed': StringField, + 'hidden': StringField, + 'text-private': StringField, + 'text-single': StringField, + 'jid-multi': ListMultiField, + 'jid-single': ListSingleField, + 'list-multi': ListMultiField, + 'list-single': ListSingleField, + 'text-multi': TextMultiField, + } + if typ not in f: + typ = 'text-single' + return f[typ](extend=node) def ExtendForm(node): - """ - Helper function to extend a node to form of appropriate type - """ - if node.getTag('reported') is not None: - return MultipleDataForm(extend=node) - else: - return SimpleDataForm(extend=node) + """ + Helper function to extend a node to form of appropriate type + """ + if node.getTag('reported') is not None: + return MultipleDataForm(extend=node) + else: + return SimpleDataForm(extend=node) class DataField(ExtendedNode): - """ - Keeps data about one field - var, field type, labels, instructions... Base - class for different kinds of fields. Use Field() function to construct one - of these - """ + """ + Keeps data about one field - var, field type, labels, instructions... Base + class for different kinds of fields. Use Field() function to construct one + of these + """ - def __init__(self, typ=None, var=None, value=None, label=None, desc=None, - required=False, options=None, extend=None): + def __init__(self, typ=None, var=None, value=None, label=None, desc=None, + required=False, options=None, extend=None): - if extend is None: - ExtendedNode.__init__(self, 'field') + if extend is None: + ExtendedNode.__init__(self, 'field') - self.type = typ - self.var = var - if value is not None: - self.value = value - if label is not None: - self.label = label - if desc is not None: - self.desc = desc - self.required = required - self.options = options + self.type = typ + self.var = var + if value is not None: + self.value = value + if label is not None: + self.label = label + if desc is not None: + self.desc = desc + self.required = required + self.options = options - @nested_property - def type(): - """ - Type of field. Recognized values are: 'boolean', 'fixed', 'hidden', - 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi', - 'text-private', 'text-single'. If you set this to something different, - DataField will store given name, but treat all data as text-single - """ - def fget(self): - t = self.getAttr('type') - if t is None: - return 'text-single' - return t + @nested_property + def type(): + """ + Type of field. Recognized values are: 'boolean', 'fixed', 'hidden', + 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi', + 'text-private', 'text-single'. If you set this to something different, + DataField will store given name, but treat all data as text-single + """ + def fget(self): + t = self.getAttr('type') + if t is None: + return 'text-single' + return t - def fset(self, value): - assert isinstance(value, basestring) - self.setAttr('type', value) + def fset(self, value): + assert isinstance(value, basestring) + self.setAttr('type', value) - return locals() + return locals() - @nested_property - def var(): - """ - Field identifier - """ - def fget(self): - return self.getAttr('var') + @nested_property + def var(): + """ + Field identifier + """ + def fget(self): + return self.getAttr('var') - def fset(self, value): - assert isinstance(value, basestring) - self.setAttr('var', value) + def fset(self, value): + assert isinstance(value, basestring) + self.setAttr('var', value) - def fdel(self): - self.delAttr('var') + def fdel(self): + self.delAttr('var') - return locals() + return locals() - @nested_property - def label(): - """ - Human-readable field name - """ - def fget(self): - l = self.getAttr('label') - if not l: - l = self.var - return l + @nested_property + def label(): + """ + Human-readable field name + """ + def fget(self): + l = self.getAttr('label') + if not l: + l = self.var + return l - def fset(self, value): - assert isinstance(value, basestring) - self.setAttr('label', value) + def fset(self, value): + assert isinstance(value, basestring) + self.setAttr('label', value) - def fdel(self): - if self.getAttr('label'): - self.delAttr('label') + def fdel(self): + if self.getAttr('label'): + self.delAttr('label') - return locals() + return locals() - @nested_property - def description(): - """ - Human-readable description of field meaning - """ - def fget(self): - return self.getTagData('desc') or u'' + @nested_property + def description(): + """ + Human-readable description of field meaning + """ + def fget(self): + return self.getTagData('desc') or u'' - def fset(self, value): - assert isinstance(value, basestring) - if value == '': - fdel(self) - else: - self.setTagData('desc', value) + def fset(self, value): + assert isinstance(value, basestring) + if value == '': + fdel(self) + else: + self.setTagData('desc', value) - def fdel(self): - t = self.getTag('desc') - if t is not None: - self.delChild(t) + def fdel(self): + t = self.getTag('desc') + if t is not None: + self.delChild(t) - return locals() + return locals() - @nested_property - def required(): - """ - Controls whether this field required to fill. Boolean - """ - def fget(self): - return bool(self.getTag('required')) + @nested_property + def required(): + """ + Controls whether this field required to fill. Boolean + """ + def fget(self): + return bool(self.getTag('required')) - def fset(self, value): - t = self.getTag('required') - if t and not value: - self.delChild(t) - elif not t and value: - self.addChild('required') + def fset(self, value): + t = self.getTag('required') + if t and not value: + self.delChild(t) + elif not t and value: + self.addChild('required') - return locals() + return locals() class BooleanField(DataField): - @nested_property - def value(): - """ - Value of field. May contain True, False or None - """ - def fget(self): - v = self.getTagData('value') - if v in ('0', 'false'): - return False - if v in ('1', 'true'): - return True - if v is None: - return False # default value is False - raise WrongFieldValue + @nested_property + def value(): + """ + Value of field. May contain True, False or None + """ + def fget(self): + v = self.getTagData('value') + if v in ('0', 'false'): + return False + if v in ('1', 'true'): + return True + if v is None: + return False # default value is False + raise WrongFieldValue - def fset(self, value): - self.setTagData('value', value and '1' or '0') + def fset(self, value): + self.setTagData('value', value and '1' or '0') - def fdel(self, value): - t = self.getTag('value') - if t is not None: - self.delChild(t) + def fdel(self, value): + t = self.getTag('value') + if t is not None: + self.delChild(t) - return locals() + return locals() class StringField(DataField): - """ - Covers fields of types: fixed, hidden, text-private, text-single - """ + """ + Covers fields of types: fixed, hidden, text-private, text-single + """ - @nested_property - def value(): - """ - Value of field. May be any unicode string - """ - def fget(self): - return self.getTagData('value') or u'' + @nested_property + def value(): + """ + Value of field. May be any unicode string + """ + def fget(self): + return self.getTagData('value') or u'' - def fset(self, value): - assert isinstance(value, basestring) - if value == '': - return fdel(self) - self.setTagData('value', value) + def fset(self, value): + assert isinstance(value, basestring) + if value == '': + return fdel(self) + self.setTagData('value', value) - def fdel(self): - try: - self.delChild(self.getTag('value')) - except ValueError: # if there already were no value tag - pass + def fdel(self): + try: + self.delChild(self.getTag('value')) + except ValueError: # if there already were no value tag + pass - return locals() + return locals() class ListField(DataField): - """ - Covers fields of types: jid-multi, jid-single, list-multi, list-single - """ + """ + Covers fields of types: jid-multi, jid-single, list-multi, list-single + """ - @nested_property - def options(): - """ - Options - """ - def fget(self): - options = [] - for element in self.getTags('option'): - v = element.getTagData('value') - if v is None: - raise WrongFieldValue - l = element.getAttr('label') - if not l: - l = v - options.append((l, v)) - return options + @nested_property + def options(): + """ + Options + """ + def fget(self): + options = [] + for element in self.getTags('option'): + v = element.getTagData('value') + if v is None: + raise WrongFieldValue + l = element.getAttr('label') + if not l: + l = v + options.append((l, v)) + return options - def fset(self, values): - fdel(self) - for value, label in values: - self.addChild('option', {'label': label}).setTagData('value', value) + def fset(self, values): + fdel(self) + for value, label in values: + self.addChild('option', {'label': label}).setTagData('value', value) - def fdel(self): - for element in self.getTags('option'): - self.delChild(element) + def fdel(self): + for element in self.getTags('option'): + self.delChild(element) - return locals() + return locals() - def iter_options(self): - for element in self.iterTags('option'): - v = element.getTagData('value') - if v is None: - raise WrongFieldValue - l = element.getAttr('label') - if not l: - l = v - yield (v, l) + def iter_options(self): + for element in self.iterTags('option'): + v = element.getTagData('value') + if v is None: + raise WrongFieldValue + l = element.getAttr('label') + if not l: + l = v + yield (v, l) class ListSingleField(ListField, StringField): - """ - Covers list-single and jid-single fields - """ - pass + """ + Covers list-single and jid-single fields + """ + pass class ListMultiField(ListField): - """ - Covers list-multi and jid-multi fields - """ + """ + Covers list-multi and jid-multi fields + """ - @nested_property - def values(): - """ - Values held in field - """ - def fget(self): - values = [] - for element in self.getTags('value'): - values.append(element.getData()) - return values + @nested_property + def values(): + """ + Values held in field + """ + def fget(self): + values = [] + for element in self.getTags('value'): + values.append(element.getData()) + return values - def fset(self, values): - fdel(self) - for value in values: - self.addChild('value').setData(value) + def fset(self, values): + fdel(self) + for value in values: + self.addChild('value').setData(value) - def fdel(self): - for element in self.getTags('value'): - self.delChild(element) + def fdel(self): + for element in self.getTags('value'): + self.delChild(element) - return locals() + return locals() - def iter_values(self): - for element in self.getTags('value'): - yield element.getData() + def iter_values(self): + for element in self.getTags('value'): + yield element.getData() class TextMultiField(DataField): - @nested_property - def value(): - """ - Value held in field - """ - def fget(self): - value = u'' - for element in self.iterTags('value'): - value += '\n' + element.getData() - return value[1:] + @nested_property + def value(): + """ + Value held in field + """ + def fget(self): + value = u'' + for element in self.iterTags('value'): + value += '\n' + element.getData() + return value[1:] - def fset(self, value): - fdel(self) - if value == '': - return - for line in value.split('\n'): - self.addChild('value').setData(line) + def fset(self, value): + fdel(self) + if value == '': + return + for line in value.split('\n'): + self.addChild('value').setData(line) - def fdel(self): - for element in self.getTags('value'): - self.delChild(element) + def fdel(self): + for element in self.getTags('value'): + self.delChild(element) - return locals() + return locals() class DataRecord(ExtendedNode): - """ - The container for data fields - an xml element which has DataField elements - as children - """ - def __init__(self, fields=None, associated=None, extend=None): - self.associated = associated - self.vars = {} - if extend is None: - # we have to build this object from scratch - xmpp.Node.__init__(self) + """ + The container for data fields - an xml element which has DataField elements + as children + """ + def __init__(self, fields=None, associated=None, extend=None): + self.associated = associated + self.vars = {} + if extend is None: + # we have to build this object from scratch + xmpp.Node.__init__(self) - if fields is not None: - self.fields = fields - else: - # we already have xmpp.Node inside - try to convert all - # fields into DataField objects - if fields is None: - for field in self.iterTags('field'): - if not isinstance(field, DataField): - ExtendField(field) - self.vars[field.var] = field - else: - for field in self.getTags('field'): - self.delChild(field) - self.fields = fields + if fields is not None: + self.fields = fields + else: + # we already have xmpp.Node inside - try to convert all + # fields into DataField objects + if fields is None: + for field in self.iterTags('field'): + if not isinstance(field, DataField): + ExtendField(field) + self.vars[field.var] = field + else: + for field in self.getTags('field'): + self.delChild(field) + self.fields = fields - @nested_property - def fields(): - """ - List of fields in this record - """ - def fget(self): - return self.getTags('field') + @nested_property + def fields(): + """ + List of fields in this record + """ + def fget(self): + return self.getTags('field') - def fset(self, fields): - fdel(self) - for field in fields: - if not isinstance(field, DataField): - ExtendField(extend=field) - self.addChild(node=field) + def fset(self, fields): + fdel(self) + for field in fields: + if not isinstance(field, DataField): + ExtendField(extend=field) + self.addChild(node=field) - def fdel(self): - for element in self.getTags('field'): - self.delChild(element) + def fdel(self): + for element in self.getTags('field'): + self.delChild(element) - return locals() + return locals() - def iter_fields(self): - """ - Iterate over fields in this record. Do not take associated into account - """ - for field in self.iterTags('field'): - yield field + def iter_fields(self): + """ + Iterate over fields in this record. Do not take associated into account + """ + for field in self.iterTags('field'): + yield field - def iter_with_associated(self): - """ - Iterate over associated, yielding both our field and associated one - together - """ - for field in self.associated.iter_fields(): - yield self[field.var], field + def iter_with_associated(self): + """ + Iterate over associated, yielding both our field and associated one + together + """ + for field in self.associated.iter_fields(): + yield self[field.var], field - def __getitem__(self, item): - return self.vars[item] + def __getitem__(self, item): + return self.vars[item] class DataForm(ExtendedNode): - def __init__(self, type_=None, title=None, instructions=None, extend=None): - if extend is None: - # we have to build form from scratch - xmpp.Node.__init__(self, 'x', attrs={'xmlns': xmpp.NS_DATA}) + def __init__(self, type_=None, title=None, instructions=None, extend=None): + if extend is None: + # we have to build form from scratch + xmpp.Node.__init__(self, 'x', attrs={'xmlns': xmpp.NS_DATA}) - if type_ is not None: - self.type_=type_ - if title is not None: - self.title=title - if instructions is not None: - self.instructions=instructions + if type_ is not None: + self.type_=type_ + if title is not None: + self.title=title + if instructions is not None: + self.instructions=instructions - @nested_property - def type(): - """ - Type of the form. Must be one of: 'form', 'submit', 'cancel', 'result'. - 'form' - this form is to be filled in; you will be able soon to do: - filledform = DataForm(replyto=thisform) - """ - def fget(self): - return self.getAttr('type') + @nested_property + def type(): + """ + Type of the form. Must be one of: 'form', 'submit', 'cancel', 'result'. + 'form' - this form is to be filled in; you will be able soon to do: + filledform = DataForm(replyto=thisform) + """ + def fget(self): + return self.getAttr('type') - def fset(self, type_): - assert type_ in ('form', 'submit', 'cancel', 'result') - self.setAttr('type', type_) + def fset(self, type_): + assert type_ in ('form', 'submit', 'cancel', 'result') + self.setAttr('type', type_) - return locals() + return locals() - @nested_property - def title(): - """ - Title of the form + @nested_property + def title(): + """ + Title of the form - Human-readable, should not contain any \\r\\n. - """ - def fget(self): - return self.getTagData('title') + Human-readable, should not contain any \\r\\n. + """ + def fget(self): + return self.getTagData('title') - def fset(self, title): - self.setTagData('title', title) + def fset(self, title): + self.setTagData('title', title) - def fdel(self): - try: - self.delChild('title') - except ValueError: - pass + def fdel(self): + try: + self.delChild('title') + except ValueError: + pass - return locals() + return locals() - @nested_property - def instructions(): - """ - Instructions for this form + @nested_property + def instructions(): + """ + Instructions for this form - Human-readable, may contain \\r\\n. - """ - # TODO: the same code is in TextMultiField. join them - def fget(self): - value = u'' - for valuenode in self.getTags('instructions'): - value += '\n' + valuenode.getData() - return value[1:] + Human-readable, may contain \\r\\n. + """ + # TODO: the same code is in TextMultiField. join them + def fget(self): + value = u'' + for valuenode in self.getTags('instructions'): + value += '\n' + valuenode.getData() + return value[1:] - def fset(self, value): - fdel(self) - if value == '': return - for line in value.split('\n'): - self.addChild('instructions').setData(line) + def fset(self, value): + fdel(self) + if value == '': return + for line in value.split('\n'): + self.addChild('instructions').setData(line) - def fdel(self): - for value in self.getTags('instructions'): - self.delChild(value) + def fdel(self): + for value in self.getTags('instructions'): + self.delChild(value) - return locals() + return locals() class SimpleDataForm(DataForm, DataRecord): - def __init__(self, type_=None, title=None, instructions=None, fields=None, \ - extend=None): - DataForm.__init__(self, type_=type_, title=title, - instructions=instructions, extend=extend) - DataRecord.__init__(self, fields=fields, extend=self, associated=self) + def __init__(self, type_=None, title=None, instructions=None, fields=None, \ + extend=None): + DataForm.__init__(self, type_=type_, title=title, + instructions=instructions, extend=extend) + DataRecord.__init__(self, fields=fields, extend=self, associated=self) - def get_purged(self): - c = SimpleDataForm(extend=self) - del c.title - c.instructions = '' - to_be_removed = [] - for f in c.iter_fields(): - if f.required: - # Keep all required fields - continue - if (hasattr(f, 'value') and not f.value) or (hasattr(f, 'values') and \ - len(f.values) == 0): - to_be_removed.append(f) - else: - del f.label - del f.description - for f in to_be_removed: - c.delChild(f) - return c + def get_purged(self): + c = SimpleDataForm(extend=self) + del c.title + c.instructions = '' + to_be_removed = [] + for f in c.iter_fields(): + if f.required: + # Keep all required fields + continue + if (hasattr(f, 'value') and not f.value) or (hasattr(f, 'values') and \ + len(f.values) == 0): + to_be_removed.append(f) + else: + del f.label + del f.description + for f in to_be_removed: + c.delChild(f) + return c class MultipleDataForm(DataForm): - def __init__(self, type_=None, title=None, instructions=None, items=None, - extend=None): - DataForm.__init__(self, type_=type_, title=title, - instructions=instructions, extend=extend) - # all records, recorded into DataRecords - if extend is None: - if items is not None: - self.items = items - else: - # we already have xmpp.Node inside - try to convert all - # fields into DataField objects - if items is None: - self.items = list(self.iterTags('item')) - else: - for item in self.getTags('item'): - self.delChild(item) - self.items = items - reported_tag = self.getTag('reported') - self.reported = DataRecord(extend=reported_tag) + def __init__(self, type_=None, title=None, instructions=None, items=None, + extend=None): + DataForm.__init__(self, type_=type_, title=title, + instructions=instructions, extend=extend) + # all records, recorded into DataRecords + if extend is None: + if items is not None: + self.items = items + else: + # we already have xmpp.Node inside - try to convert all + # fields into DataField objects + if items is None: + self.items = list(self.iterTags('item')) + else: + for item in self.getTags('item'): + self.delChild(item) + self.items = items + reported_tag = self.getTag('reported') + self.reported = DataRecord(extend=reported_tag) - @nested_property - def items(): - """ - A list of all records - """ - def fget(self): - return list(self.iter_records()) + @nested_property + def items(): + """ + A list of all records + """ + def fget(self): + return list(self.iter_records()) - def fset(self, records): - fdel(self) - for record in records: - if not isinstance(record, DataRecord): - DataRecord(extend=record) - self.addChild(node=record) + def fset(self, records): + fdel(self) + for record in records: + if not isinstance(record, DataRecord): + DataRecord(extend=record) + self.addChild(node=record) - def fdel(self): - for record in self.getTags('item'): - self.delChild(record) + def fdel(self): + for record in self.getTags('item'): + self.delChild(record) - return locals() + return locals() - def iter_records(self): - for record in self.getTags('item'): - yield record + def iter_records(self): + for record in self.getTags('item'): + yield record -# @nested_property -# def reported(): -# """ -# DataRecord that contains descriptions of fields in records -# """ -# def fget(self): -# return self.getTag('reported') -# def fset(self, record): -# try: -# self.delChild('reported') -# except: -# pass +# @nested_property +# def reported(): +# """ +# DataRecord that contains descriptions of fields in records +# """ +# def fget(self): +# return self.getTag('reported') +# def fset(self, record): +# try: +# self.delChild('reported') +# except: +# pass # -# record.setName('reported') -# self.addChild(node=record) -# return locals() - - -# vim: se ts=3: +# record.setName('reported') +# self.addChild(node=record) +# return locals() diff --git a/src/common/dbus_support.py b/src/common/dbus_support.py index 4762b474d..d1e8fe381 100644 --- a/src/common/dbus_support.py +++ b/src/common/dbus_support.py @@ -32,152 +32,150 @@ from common import exceptions _GAJIM_ERROR_IFACE = 'org.gajim.dbus.Error' try: - import dbus - import dbus.glib + import dbus + import dbus.glib except ImportError: - supported = False - if not os.name == 'nt': # only say that to non Windows users - print _('D-Bus python bindings are missing in this computer') - print _('D-Bus capabilities of Gajim cannot be used') + supported = False + if not os.name == 'nt': # only say that to non Windows users + print _('D-Bus python bindings are missing in this computer') + print _('D-Bus capabilities of Gajim cannot be used') else: - try: - # test if dbus-x11 is installed - bus = dbus.SessionBus() - supported = True # does user have D-Bus bindings? - except dbus.DBusException: - supported = False - if not os.name == 'nt': # only say that to non Windows users - print _('D-Bus does not run correctly on this machine') - print _('D-Bus capabilities of Gajim cannot be used') + try: + # test if dbus-x11 is installed + bus = dbus.SessionBus() + supported = True # does user have D-Bus bindings? + except dbus.DBusException: + supported = False + if not os.name == 'nt': # only say that to non Windows users + print _('D-Bus does not run correctly on this machine') + print _('D-Bus capabilities of Gajim cannot be used') class SystemBus: - """ - A Singleton for the DBus SystemBus - """ + """ + A Singleton for the DBus SystemBus + """ - def __init__(self): - self.system_bus = None + def __init__(self): + self.system_bus = None - def SystemBus(self): - if not supported: - raise exceptions.DbusNotSupported + def SystemBus(self): + if not supported: + raise exceptions.DbusNotSupported - if not self.present(): - raise exceptions.SystemBusNotPresent - return self.system_bus + if not self.present(): + raise exceptions.SystemBusNotPresent + return self.system_bus - def bus(self): - return self.SystemBus() + def bus(self): + return self.SystemBus() - def present(self): - if not supported: - return False - if self.system_bus is None: - try: - self.system_bus = dbus.SystemBus() - except dbus.DBusException: - self.system_bus = None - return False - if self.system_bus is None: - return False - # Don't exit Gajim when dbus is stopped - self.system_bus.set_exit_on_disconnect(False) - return True + def present(self): + if not supported: + return False + if self.system_bus is None: + try: + self.system_bus = dbus.SystemBus() + except dbus.DBusException: + self.system_bus = None + return False + if self.system_bus is None: + return False + # Don't exit Gajim when dbus is stopped + self.system_bus.set_exit_on_disconnect(False) + return True system_bus = SystemBus() class SessionBus: - """ - A Singleton for the D-Bus SessionBus - """ + """ + A Singleton for the D-Bus SessionBus + """ - def __init__(self): - self.session_bus = None + def __init__(self): + self.session_bus = None - def SessionBus(self): - if not supported: - raise exceptions.DbusNotSupported + def SessionBus(self): + if not supported: + raise exceptions.DbusNotSupported - if not self.present(): - raise exceptions.SessionBusNotPresent - return self.session_bus + if not self.present(): + raise exceptions.SessionBusNotPresent + return self.session_bus - def bus(self): - return self.SessionBus() + def bus(self): + return self.SessionBus() - def present(self): - if not supported: - return False - if self.session_bus is None: - try: - self.session_bus = dbus.SessionBus() - except dbus.DBusException: - self.session_bus = None - return False - if self.session_bus is None: - return False - return True + def present(self): + if not supported: + return False + if self.session_bus is None: + try: + self.session_bus = dbus.SessionBus() + except dbus.DBusException: + self.session_bus = None + return False + if self.session_bus is None: + return False + return True session_bus = SessionBus() def get_interface(interface, path, start_service=True): - """ - Get an interface on the current SessionBus. If the interface isn't running, - try to start it first - """ - if not supported: - return None - if session_bus.present(): - bus = session_bus.SessionBus() - else: - return None - try: - obj = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus') - dbus_iface = dbus.Interface(obj, 'org.freedesktop.DBus') - running_services = dbus_iface.ListNames() - started = True - if interface not in running_services: - # try to start the service - if start_service and dbus_iface.StartServiceByName(interface, dbus.UInt32(0)) == 1: - started = True - else: - started = False - if not started: - return None - obj = bus.get_object(interface, path) - return dbus.Interface(obj, interface) - except Exception, e: - gajim.log.debug(str(e)) - return None + """ + Get an interface on the current SessionBus. If the interface isn't running, + try to start it first + """ + if not supported: + return None + if session_bus.present(): + bus = session_bus.SessionBus() + else: + return None + try: + obj = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus') + dbus_iface = dbus.Interface(obj, 'org.freedesktop.DBus') + running_services = dbus_iface.ListNames() + started = True + if interface not in running_services: + # try to start the service + if start_service and dbus_iface.StartServiceByName(interface, dbus.UInt32(0)) == 1: + started = True + else: + started = False + if not started: + return None + obj = bus.get_object(interface, path) + return dbus.Interface(obj, interface) + except Exception, e: + gajim.log.debug(str(e)) + return None def get_notifications_interface(notif=None): - """ - Get the notifications interface + """ + Get the notifications interface - :param notif: DesktopNotification instance - """ - # try to see if KDE notifications are available - iface = get_interface('org.kde.VisualNotifications', '/VisualNotifications', - start_service=False) - if iface != None: - if notif != None: - notif.kde_notifications = True - return iface - # KDE notifications don't seem to be available, falling back to - # notification-daemon - else: - if notif != None: - notif.kde_notifications = False - return get_interface('org.freedesktop.Notifications', - '/org/freedesktop/Notifications') + :param notif: DesktopNotification instance + """ + # try to see if KDE notifications are available + iface = get_interface('org.kde.VisualNotifications', '/VisualNotifications', + start_service=False) + if iface != None: + if notif != None: + notif.kde_notifications = True + return iface + # KDE notifications don't seem to be available, falling back to + # notification-daemon + else: + if notif != None: + notif.kde_notifications = False + return get_interface('org.freedesktop.Notifications', + '/org/freedesktop/Notifications') if supported: - class MissingArgument(dbus.DBusException): - _dbus_error_name = _GAJIM_ERROR_IFACE + '.MissingArgument' + class MissingArgument(dbus.DBusException): + _dbus_error_name = _GAJIM_ERROR_IFACE + '.MissingArgument' - class InvalidArgument(dbus.DBusException): - '''Raised when one of the provided arguments is invalid.''' - _dbus_error_name = _GAJIM_ERROR_IFACE + '.InvalidArgument' - -# vim: se ts=3: + class InvalidArgument(dbus.DBusException): + '''Raised when one of the provided arguments is invalid.''' + _dbus_error_name = _GAJIM_ERROR_IFACE + '.InvalidArgument' diff --git a/src/common/defs.py b/src/common/defs.py index 615c825b2..348010bff 100644 --- a/src/common/defs.py +++ b/src/common/defs.py @@ -31,6 +31,4 @@ version = '0.13.10.2-dev' import sys, os.path for base in ('.', 'common'): - sys.path.append(os.path.join(base, '.libs')) - -# vim: se ts=3: + sys.path.append(os.path.join(base, '.libs')) diff --git a/src/common/dh.py b/src/common/dh.py index b13cdefc0..29f59a083 100644 --- a/src/common/dh.py +++ b/src/common/dh.py @@ -28,28 +28,27 @@ These constants have been obtained from RFC2409 and RFC3526. import string -generators = [ None, # one to get the right offset - 2, - 2, - None, - None, - 2, - None, - None, - None, - None, - None, - None, - None, - None, - 2, # group 14 - 2, - 2, - 2, - 2, - ] +generators = [None, # one to get the right offset + 2, + 2, + None, + None, + 2, + None, + None, + None, + None, + None, + None, + None, + None, + 2, # group 14 + 2, + 2, + 2, + 2] -hex_primes = [ None, +hex_primes = [None, # group 1 '''FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 @@ -222,11 +221,9 @@ B1D510BD 7EE74D73 FAF36BC3 1ECFA268 359046F4 EB879F92 all_ascii = ''.join(map(chr, range(256))) def hex_to_decimal(stripee): - if not stripee: - return None + if not stripee: + return None - return int(stripee.translate(all_ascii, string.whitespace), 16) + return int(stripee.translate(all_ascii, string.whitespace), 16) primes = map(hex_to_decimal, hex_primes) - -# vim: se ts=3: diff --git a/src/common/events.py b/src/common/events.py index ccf5744f1..9e8ae059f 100644 --- a/src/common/events.py +++ b/src/common/events.py @@ -27,310 +27,308 @@ import time class Event: - """ - Information concerning each event - """ + """ + Information concerning each event + """ - def __init__(self, type_, time_, parameters, show_in_roster=False, - show_in_systray=True): - """ - type_ in chat, normal, file-request, file-error, file-completed, - file-request-error, file-send-error, file-stopped, gc_msg, pm, - printed_chat, printed_gc_msg, printed_marked_gc_msg, printed_pm, - gc-invitation, subscription_request, unsubscribedm jingle-incoming + def __init__(self, type_, time_, parameters, show_in_roster=False, + show_in_systray=True): + """ + type_ in chat, normal, file-request, file-error, file-completed, + file-request-error, file-send-error, file-stopped, gc_msg, pm, + printed_chat, printed_gc_msg, printed_marked_gc_msg, printed_pm, + gc-invitation, subscription_request, unsubscribedm jingle-incoming - parameters is (per type_): - chat, normal, pm: [message, subject, kind, time, encrypted, resource, - msg_id] - where kind in error, incoming - file-*: file_props - gc_msg: None - printed_chat: control - printed_*: None - messages that are already printed in chat, but not read - gc-invitation: [room_jid, reason, password, is_continued] - subscription_request: [text, nick] - unsubscribed: contact - jingle-incoming: (fulljid, sessionid, content_types) - """ - self.type_ = type_ - self.time_ = time_ - self.parameters = parameters - self.show_in_roster = show_in_roster - self.show_in_systray = show_in_systray - # Set when adding the event - self.jid = None - self.account = None + parameters is (per type_): + chat, normal, pm: [message, subject, kind, time, encrypted, resource, + msg_id] + where kind in error, incoming + file-*: file_props + gc_msg: None + printed_chat: control + printed_*: None + messages that are already printed in chat, but not read + gc-invitation: [room_jid, reason, password, is_continued] + subscription_request: [text, nick] + unsubscribed: contact + jingle-incoming: (fulljid, sessionid, content_types) + """ + self.type_ = type_ + self.time_ = time_ + self.parameters = parameters + self.show_in_roster = show_in_roster + self.show_in_systray = show_in_systray + # Set when adding the event + self.jid = None + self.account = None class Events: - """ - Information concerning all events - """ + """ + Information concerning all events + """ - def __init__(self): - self._events = {} # list of events {acct: {jid1: [E1, E2]}, } - self._event_added_listeners = [] - self._event_removed_listeners = [] + def __init__(self): + self._events = {} # list of events {acct: {jid1: [E1, E2]}, } + self._event_added_listeners = [] + self._event_removed_listeners = [] - def event_added_subscribe(self, listener): - """ - Add a listener when an event is added to the queue - """ - if not listener in self._event_added_listeners: - self._event_added_listeners.append(listener) + def event_added_subscribe(self, listener): + """ + Add a listener when an event is added to the queue + """ + if not listener in self._event_added_listeners: + self._event_added_listeners.append(listener) - def event_added_unsubscribe(self, listener): - """ - Remove a listener when an event is added to the queue - """ - if listener in self._event_added_listeners: - self._event_added_listeners.remove(listener) + def event_added_unsubscribe(self, listener): + """ + Remove a listener when an event is added to the queue + """ + if listener in self._event_added_listeners: + self._event_added_listeners.remove(listener) - def event_removed_subscribe(self, listener): - """ - Add a listener when an event is removed from the queue - """ - if not listener in self._event_removed_listeners: - self._event_removed_listeners.append(listener) + def event_removed_subscribe(self, listener): + """ + Add a listener when an event is removed from the queue + """ + if not listener in self._event_removed_listeners: + self._event_removed_listeners.append(listener) - def event_removed_unsubscribe(self, listener): - """ - Remove a listener when an event is removed from the queue - """ - if listener in self._event_removed_listeners: - self._event_removed_listeners.remove(listener) + def event_removed_unsubscribe(self, listener): + """ + Remove a listener when an event is removed from the queue + """ + if listener in self._event_removed_listeners: + self._event_removed_listeners.remove(listener) - def fire_event_added(self, event): - for listener in self._event_added_listeners: - listener(event) + def fire_event_added(self, event): + for listener in self._event_added_listeners: + listener(event) - def fire_event_removed(self, event_list): - for listener in self._event_removed_listeners: - listener(event_list) + def fire_event_removed(self, event_list): + for listener in self._event_removed_listeners: + listener(event_list) - def change_account_name(self, old_name, new_name): - if old_name in self._events: - self._events[new_name] = self._events[old_name] - del self._events[old_name] + def change_account_name(self, old_name, new_name): + if old_name in self._events: + self._events[new_name] = self._events[old_name] + del self._events[old_name] - def add_account(self, account): - self._events[account] = {} + def add_account(self, account): + self._events[account] = {} - def get_accounts(self): - return self._events.keys() + def get_accounts(self): + return self._events.keys() - def remove_account(self, account): - del self._events[account] + def remove_account(self, account): + del self._events[account] - def create_event(self, type_, parameters, time_ = time.time(), - show_in_roster = False, show_in_systray = True): - return Event(type_, time_, parameters, show_in_roster, - show_in_systray) + def create_event(self, type_, parameters, time_ = time.time(), + show_in_roster = False, show_in_systray = True): + return Event(type_, time_, parameters, show_in_roster, + show_in_systray) - def add_event(self, account, jid, event): - # No such account before ? - if account not in self._events: - self._events[account] = {jid: [event]} - # no such jid before ? - elif jid not in self._events[account]: - self._events[account][jid] = [event] - else: - self._events[account][jid].append(event) - event.jid = jid - event.account = account - self.fire_event_added(event) + def add_event(self, account, jid, event): + # No such account before ? + if account not in self._events: + self._events[account] = {jid: [event]} + # no such jid before ? + elif jid not in self._events[account]: + self._events[account][jid] = [event] + else: + self._events[account][jid].append(event) + event.jid = jid + event.account = account + self.fire_event_added(event) - def remove_events(self, account, jid, event = None, types = []): - """ - If event is not specified, remove all events from this jid, optionally - only from given type return True if no such event found - """ - if account not in self._events: - return True - if jid not in self._events[account]: - return True - if event: # remove only one event - if event in self._events[account][jid]: - if len(self._events[account][jid]) == 1: - del self._events[account][jid] - else: - self._events[account][jid].remove(event) - self.fire_event_removed([event]) - return - else: - return True - if types: - new_list = [] # list of events to keep - removed_list = [] # list of removed events - for ev in self._events[account][jid]: - if ev.type_ not in types: - new_list.append(ev) - else: - removed_list.append(ev) - if len(new_list) == len(self._events[account][jid]): - return True - if new_list: - self._events[account][jid] = new_list - else: - del self._events[account][jid] - self.fire_event_removed(removed_list) - return - # no event nor type given, remove them all - self.fire_event_removed(self._events[account][jid]) - del self._events[account][jid] + def remove_events(self, account, jid, event = None, types = []): + """ + If event is not specified, remove all events from this jid, optionally + only from given type return True if no such event found + """ + if account not in self._events: + return True + if jid not in self._events[account]: + return True + if event: # remove only one event + if event in self._events[account][jid]: + if len(self._events[account][jid]) == 1: + del self._events[account][jid] + else: + self._events[account][jid].remove(event) + self.fire_event_removed([event]) + return + else: + return True + if types: + new_list = [] # list of events to keep + removed_list = [] # list of removed events + for ev in self._events[account][jid]: + if ev.type_ not in types: + new_list.append(ev) + else: + removed_list.append(ev) + if len(new_list) == len(self._events[account][jid]): + return True + if new_list: + self._events[account][jid] = new_list + else: + del self._events[account][jid] + self.fire_event_removed(removed_list) + return + # no event nor type given, remove them all + self.fire_event_removed(self._events[account][jid]) + del self._events[account][jid] - def change_jid(self, account, old_jid, new_jid): - if account not in self._events: - return - if old_jid not in self._events[account]: - return - if new_jid in self._events[account]: - self._events[account][new_jid] += self._events[account][old_jid] - else: - self._events[account][new_jid] = self._events[account][old_jid] - del self._events[account][old_jid] + def change_jid(self, account, old_jid, new_jid): + if account not in self._events: + return + if old_jid not in self._events[account]: + return + if new_jid in self._events[account]: + self._events[account][new_jid] += self._events[account][old_jid] + else: + self._events[account][new_jid] = self._events[account][old_jid] + del self._events[account][old_jid] - def get_nb_events(self, types = [], account = None): - return self._get_nb_events(types = types, account = account) + def get_nb_events(self, types = [], account = None): + return self._get_nb_events(types = types, account = account) - def get_events(self, account, jid = None, types = []): - """ - Return all events from the given account of the form {jid1: [], jid2: - []}. If jid is given, returns all events from the given jid in a list: [] - optionally only from given type - """ - if account not in self._events: - return [] - if not jid: - events_list = {} # list of events - for jid_ in self._events[account]: - events = [] - for ev in self._events[account][jid_]: - if not types or ev.type_ in types: - events.append(ev) - if events: - events_list[jid_] = events - return events_list - if jid not in self._events[account]: - return [] - events_list = [] # list of events - for ev in self._events[account][jid]: - if not types or ev.type_ in types: - events_list.append(ev) - return events_list + def get_events(self, account, jid = None, types = []): + """ + Return all events from the given account of the form {jid1: [], jid2: + []}. If jid is given, returns all events from the given jid in a list: [] + optionally only from given type + """ + if account not in self._events: + return [] + if not jid: + events_list = {} # list of events + for jid_ in self._events[account]: + events = [] + for ev in self._events[account][jid_]: + if not types or ev.type_ in types: + events.append(ev) + if events: + events_list[jid_] = events + return events_list + if jid not in self._events[account]: + return [] + events_list = [] # list of events + for ev in self._events[account][jid]: + if not types or ev.type_ in types: + events_list.append(ev) + return events_list - def get_first_event(self, account, jid = None, type_ = None): - """ - Return the first event of type type_ if given - """ - events_list = self.get_events(account, jid, type_) - # be sure it's bigger than latest event - first_event_time = time.time() + 1 - first_event = None - for event in events_list: - if event.time_ < first_event_time: - first_event_time = event.time_ - first_event = event - return first_event + def get_first_event(self, account, jid = None, type_ = None): + """ + Return the first event of type type_ if given + """ + events_list = self.get_events(account, jid, type_) + # be sure it's bigger than latest event + first_event_time = time.time() + 1 + first_event = None + for event in events_list: + if event.time_ < first_event_time: + first_event_time = event.time_ + first_event = event + return first_event - def _get_nb_events(self, account = None, jid = None, attribute = None, types - = []): - """ - Return the number of pending events - """ - nb = 0 - if account: - accounts = [account] - else: - accounts = self._events.keys() - for acct in accounts: - if acct not in self._events: - continue - if jid: - jids = [jid] - else: - jids = self._events[acct].keys() - for j in jids: - if j not in self._events[acct]: - continue - for event in self._events[acct][j]: - if types and event.type_ not in types: - continue - if not attribute or \ - attribute == 'systray' and event.show_in_systray or \ - attribute == 'roster' and event.show_in_roster: - nb += 1 - return nb + def _get_nb_events(self, account = None, jid = None, attribute = None, types + = []): + """ + Return the number of pending events + """ + nb = 0 + if account: + accounts = [account] + else: + accounts = self._events.keys() + for acct in accounts: + if acct not in self._events: + continue + if jid: + jids = [jid] + else: + jids = self._events[acct].keys() + for j in jids: + if j not in self._events[acct]: + continue + for event in self._events[acct][j]: + if types and event.type_ not in types: + continue + if not attribute or \ + attribute == 'systray' and event.show_in_systray or \ + attribute == 'roster' and event.show_in_roster: + nb += 1 + return nb - def _get_some_events(self, attribute): - """ - Attribute in systray, roster - """ - events = {} - for account in self._events: - events[account] = {} - for jid in self._events[account]: - events[account][jid] = [] - for event in self._events[account][jid]: - if attribute == 'systray' and event.show_in_systray or \ - attribute == 'roster' and event.show_in_roster: - events[account][jid].append(event) - if not events[account][jid]: - del events[account][jid] - if not events[account]: - del events[account] - return events + def _get_some_events(self, attribute): + """ + Attribute in systray, roster + """ + events = {} + for account in self._events: + events[account] = {} + for jid in self._events[account]: + events[account][jid] = [] + for event in self._events[account][jid]: + if attribute == 'systray' and event.show_in_systray or \ + attribute == 'roster' and event.show_in_roster: + events[account][jid].append(event) + if not events[account][jid]: + del events[account][jid] + if not events[account]: + del events[account] + return events - def _get_first_event_with_attribute(self, events): - """ - Get the first event + def _get_first_event_with_attribute(self, events): + """ + Get the first event - events is in the form {account1: {jid1: [ev1, ev2], },. } - """ - # be sure it's bigger than latest event - first_event_time = time.time() + 1 - first_account = None - first_jid = None - first_event = None - for account in events: - for jid in events[account]: - for event in events[account][jid]: - if event.time_ < first_event_time: - first_event_time = event.time_ - first_account = account - first_jid = jid - first_event = event - return first_account, first_jid, first_event + events is in the form {account1: {jid1: [ev1, ev2], },. } + """ + # be sure it's bigger than latest event + first_event_time = time.time() + 1 + first_account = None + first_jid = None + first_event = None + for account in events: + for jid in events[account]: + for event in events[account][jid]: + if event.time_ < first_event_time: + first_event_time = event.time_ + first_account = account + first_jid = jid + first_event = event + return first_account, first_jid, first_event - def get_nb_systray_events(self, types = []): - """ - Return the number of events displayed in roster - """ - return self._get_nb_events(attribute = 'systray', types = types) + def get_nb_systray_events(self, types = []): + """ + Return the number of events displayed in roster + """ + return self._get_nb_events(attribute = 'systray', types = types) - def get_systray_events(self): - """ - Return all events that must be displayed in systray: - {account1: {jid1: [ev1, ev2], },. } - """ - return self._get_some_events('systray') + def get_systray_events(self): + """ + Return all events that must be displayed in systray: + {account1: {jid1: [ev1, ev2], },. } + """ + return self._get_some_events('systray') - def get_first_systray_event(self): - events = self.get_systray_events() - return self._get_first_event_with_attribute(events) + def get_first_systray_event(self): + events = self.get_systray_events() + return self._get_first_event_with_attribute(events) - def get_nb_roster_events(self, account = None, jid = None, types = []): - """ - Return the number of events displayed in roster - """ - return self._get_nb_events(attribute = 'roster', account = account, - jid = jid, types = types) + def get_nb_roster_events(self, account = None, jid = None, types = []): + """ + Return the number of events displayed in roster + """ + return self._get_nb_events(attribute = 'roster', account = account, + jid = jid, types = types) - def get_roster_events(self): - """ - Return all events that must be displayed in roster: - {account1: {jid1: [ev1, ev2], },. } - """ - return self._get_some_events('roster') - -# vim: se ts=3: + def get_roster_events(self): + """ + Return all events that must be displayed in roster: + {account1: {jid1: [ev1, ev2], },. } + """ + return self._get_some_events('roster') diff --git a/src/common/exceptions.py b/src/common/exceptions.py index 090255768..6e1df2e36 100644 --- a/src/common/exceptions.py +++ b/src/common/exceptions.py @@ -22,114 +22,112 @@ ## class PysqliteOperationalError(Exception): - """ - Sqlite2 raised pysqlite2.dbapi2.OperationalError - """ + """ + Sqlite2 raised pysqlite2.dbapi2.OperationalError + """ - def __init__(self, text=''): - Exception.__init__(self) - self.text = text + def __init__(self, text=''): + Exception.__init__(self) + self.text = text - def __str__(self): - return self.text + def __str__(self): + return self.text class DatabaseMalformed(Exception): - """ - The databas can't be read - """ + """ + The databas can't be read + """ - def __init__(self): - Exception.__init__(self) + def __init__(self): + Exception.__init__(self) - def __str__(self): - return _('Database cannot be read.') + def __str__(self): + return _('Database cannot be read.') class ServiceNotAvailable(Exception): - """ - This exception is raised when we cannot use Gajim remotely' - """ + """ + This exception is raised when we cannot use Gajim remotely' + """ - def __init__(self): - Exception.__init__(self) + def __init__(self): + Exception.__init__(self) - def __str__(self): - return _('Service not available: Gajim is not running, or remote_control is False') + def __str__(self): + return _('Service not available: Gajim is not running, or remote_control is False') class DbusNotSupported(Exception): - """ - D-Bus is not installed or python bindings are missing - """ + """ + D-Bus is not installed or python bindings are missing + """ - def __init__(self): - Exception.__init__(self) + def __init__(self): + Exception.__init__(self) - def __str__(self): - return _('D-Bus is not present on this machine or python module is missing') + def __str__(self): + return _('D-Bus is not present on this machine or python module is missing') class SessionBusNotPresent(Exception): - """ - This exception indicates that there is no session daemon - """ + """ + This exception indicates that there is no session daemon + """ - def __init__(self): - Exception.__init__(self) + def __init__(self): + Exception.__init__(self) - def __str__(self): - return _('Session bus is not available.\nTry reading %(url)s') % \ - {'url': 'http://trac.gajim.org/wiki/GajimDBus'} + def __str__(self): + return _('Session bus is not available.\nTry reading %(url)s') % \ + {'url': 'http://trac.gajim.org/wiki/GajimDBus'} class SystemBusNotPresent(Exception): - """ - This exception indicates that there is no session daemon - """ + """ + This exception indicates that there is no session daemon + """ - def __init__(self): - Exception.__init__(self) + def __init__(self): + Exception.__init__(self) - def __str__(self): - return _('System bus is not available.\nTry reading %(url)s') % \ - {'url': 'http://trac.gajim.org/wiki/GajimDBus'} + def __str__(self): + return _('System bus is not available.\nTry reading %(url)s') % \ + {'url': 'http://trac.gajim.org/wiki/GajimDBus'} class NegotiationError(Exception): - """ - A session negotiation failed - """ - pass + """ + A session negotiation failed + """ + pass class DecryptionError(Exception): - """ - A message couldn't be decrypted into usable XML - """ - pass + """ + A message couldn't be decrypted into usable XML + """ + pass class Cancelled(Exception): - """ - The user cancelled an operation - """ - pass + """ + The user cancelled an operation + """ + pass class LatexError(Exception): - """ - LaTeX processing failed for some reason - """ + """ + LaTeX processing failed for some reason + """ - def __init__(self, text=''): - Exception.__init__(self) - self.text = text + def __init__(self, text=''): + Exception.__init__(self) + self.text = text - def __str__(self): - return self.text + def __str__(self): + return self.text class GajimGeneralException(Exception): - """ - This exception is our general exception - """ + """ + This exception is our general exception + """ - def __init__(self, text=''): - Exception.__init__(self) - self.text = text + def __init__(self, text=''): + Exception.__init__(self) + self.text = text - def __str__(self): - return self.text - -# vim: se ts=3: + def __str__(self): + return self.text diff --git a/src/common/fuzzyclock.py b/src/common/fuzzyclock.py index 3adad66d1..f84d63c3d 100755 --- a/src/common/fuzzyclock.py +++ b/src/common/fuzzyclock.py @@ -35,39 +35,37 @@ So most of the credit goes to this guys, thanks :-) import time class FuzzyClock: - HOUR_NAMES = [ _('twelve'), _('one'), _('two'), _('three'), _('four'), - _('five'), _('six'), _('seven'), _('eight'), _('nine'), _('ten'), - _('eleven') ] + HOUR_NAMES = [ _('twelve'), _('one'), _('two'), _('three'), _('four'), + _('five'), _('six'), _('seven'), _('eight'), _('nine'), _('ten'), + _('eleven') ] - #Strings to use for the output. %(0)s will be replaced with the preceding hour - #(e.g. "x PAST %(0)s"), %(1)s with the coming hour (e.g. "x TO %(0)s"). ''' - FUZZY_TIME = [ _("%(0)s o'clock"), _('five past %(0)s'), _('ten past %(0)s'), - _('quarter past %(0)s'), _('twenty past %(0)s'), _('twenty five past %(0)s'), - _('half past %(0)s'), _('twenty five to %(1)s'), _('twenty to %(1)s'), - _('quarter to %(1)s'), _('ten to %(1)s'), _('five to %(1)s'), _("%(1)s o'clock") ] + #Strings to use for the output. %(0)s will be replaced with the preceding hour + #(e.g. "x PAST %(0)s"), %(1)s with the coming hour (e.g. "x TO %(0)s"). ''' + FUZZY_TIME = [ _("%(0)s o'clock"), _('five past %(0)s'), _('ten past %(0)s'), + _('quarter past %(0)s'), _('twenty past %(0)s'), _('twenty five past %(0)s'), + _('half past %(0)s'), _('twenty five to %(1)s'), _('twenty to %(1)s'), + _('quarter to %(1)s'), _('ten to %(1)s'), _('five to %(1)s'), _("%(1)s o'clock") ] - FUZZY_DAYTIME = [ _('Night'), _('Early morning'), _('Morning'), - _('Almost noon'), _('Noon'), _('Afternoon'), _('Evening'), - _('Late evening'), _('Night') ] + FUZZY_DAYTIME = [ _('Night'), _('Early morning'), _('Morning'), + _('Almost noon'), _('Noon'), _('Afternoon'), _('Evening'), + _('Late evening'), _('Night') ] - FUZZY_WEEK = [ _('Start of week'), _('Middle of week'), _('Middle of week'), - _('Middle of week'), _('End of week'), _('Weekend!'), _('Weekend!') ] + FUZZY_WEEK = [ _('Start of week'), _('Middle of week'), _('Middle of week'), + _('Middle of week'), _('End of week'), _('Weekend!'), _('Weekend!') ] - def fuzzy_time(self, fuzzyness, now): - if fuzzyness == 1 or fuzzyness == 2: - if fuzzyness == 1: - sector = int(round(now.tm_min / 5.0)) - else: - sector = int(round(now.tm_min / 15.0)) * 3 + def fuzzy_time(self, fuzzyness, now): + if fuzzyness == 1 or fuzzyness == 2: + if fuzzyness == 1: + sector = int(round(now.tm_min / 5.0)) + else: + sector = int(round(now.tm_min / 15.0)) * 3 - return self.FUZZY_TIME[sector] % { - '0': self.HOUR_NAMES[now.tm_hour % 12], - '1': self.HOUR_NAMES[(now.tm_hour + 1) % 12]} + return self.FUZZY_TIME[sector] % { + '0': self.HOUR_NAMES[now.tm_hour % 12], + '1': self.HOUR_NAMES[(now.tm_hour + 1) % 12]} - elif fuzzyness == 3: - return self.FUZZY_DAYTIME[int(round(now.tm_hour / 3.0))] + elif fuzzyness == 3: + return self.FUZZY_DAYTIME[int(round(now.tm_hour / 3.0))] - else: - return self.FUZZY_WEEK[now.tm_wday] - -# vim: se ts=3: + else: + return self.FUZZY_WEEK[now.tm_wday] diff --git a/src/common/gajim.py b/src/common/gajim.py index 864da2ceb..c8646aec3 100644 --- a/src/common/gajim.py +++ b/src/common/gajim.py @@ -35,30 +35,30 @@ import config import xmpp try: - import defs + import defs except ImportError: - print >> sys.stderr, '''defs.py is missing! + print >> sys.stderr, '''defs.py is missing! If you start gajim from svn: - * Make sure you have GNU autotools installed. - This includes the following packages: - automake >= 1.8 - autoconf >= 2.59 - intltool-0.35 - libtool - * Run - $ sh autogen.sh - * Optionally, install gajim - $ make - $ sudo make install +* Make sure you have GNU autotools installed. +This includes the following packages: +automake >= 1.8 +autoconf >= 2.59 +intltool-0.35 +libtool +* Run +$ sh autogen.sh +* Optionally, install gajim +$ make +$ sudo make install **** Note for translators **** - You can get the latest string updates, by running: - $ cd po/ - $ make update-po +You can get the latest string updates, by running: +$ cd po/ +$ make update-po ''' - sys.exit(1) + sys.exit(1) interface = None # The actual interface (the gtk one for the moment) thread_interface = None # Interface to run a thread and then a callback @@ -91,18 +91,18 @@ DATA_DIR = gajimpaths['DATA'] ICONS_DIR = gajimpaths['ICONS'] HOME_DIR = gajimpaths['HOME'] PLUGINS_DIRS = [gajimpaths['PLUGINS_BASE'], - gajimpaths['PLUGINS_USER']] + gajimpaths['PLUGINS_USER']] PLUGINS_CONFIG_DIR = gajimpaths['PLUGINS_CONFIG_DIR'] try: - LANG = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc.. + LANG = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc.. except (ValueError, locale.Error): - # unknown locale, use en is better than fail - LANG = None + # unknown locale, use en is better than fail + LANG = None if LANG is None: - LANG = 'en' + LANG = 'en' else: - LANG = LANG[:2] # en, fr, el etc.. + LANG = LANG[:2] # en, fr, el etc.. os_info = None # used to cache os information @@ -114,7 +114,7 @@ gmail_domains = ['gmail.com', 'googlemail.com'] transport_type = {} # list the type of transport last_message_time = {} # list of time of the latest incomming message - # {acct1: {jid1: time1, jid2: time2}, } + # {acct1: {jid1: time1, jid2: time2}, } encrypted_chats = {} # list of encrypted chats {acct1: [jid1, jid2], ..} contacts = LegacyContactsAPI() @@ -152,35 +152,35 @@ transport_avatar = {} # {transport_jid: [jid_list]} # Is Gnome configured to activate on single click ? single_click = False SHOW_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd', - 'invisible', 'error'] + 'invisible', 'error'] # zeroconf account name ZEROCONF_ACC_NAME = 'Local' HAVE_ZEROCONF = True try: - import avahi + import avahi except ImportError: - try: - import pybonjour - except Exception: # Linux raises ImportError, Windows raises WindowsError - HAVE_ZEROCONF = False + try: + import pybonjour + except Exception: # Linux raises ImportError, Windows raises WindowsError + HAVE_ZEROCONF = False HAVE_PYCRYPTO = True try: - import Crypto + import Crypto except ImportError: - HAVE_PYCRYPTO = False + HAVE_PYCRYPTO = False HAVE_GPG = True try: - import GnuPGInterface + import GnuPGInterface except ImportError: - HAVE_GPG = False + HAVE_GPG = False else: - from os import system - if system('gpg -h >/dev/null 2>&1'): - HAVE_GPG = False + from os import system + if system('gpg -h >/dev/null 2>&1'): + HAVE_GPG = False # Depends on use_latex option. Will be correctly set after we config options are # read. @@ -188,23 +188,23 @@ HAVE_LATEX = False HAVE_INDICATOR = True try: - import indicate + import indicate except ImportError: - HAVE_INDICATOR = False + HAVE_INDICATOR = False HAVE_FARSIGHT = True try: - import farsight, gst + import farsight, gst except ImportError: - HAVE_FARSIGHT = False + HAVE_FARSIGHT = False gajim_identity = {'type': 'pc', 'category': 'client', 'name': 'Gajim'} gajim_common_features = [xmpp.NS_BYTESTREAM, xmpp.NS_SI, xmpp.NS_FILE, - xmpp.NS_MUC, xmpp.NS_MUC_USER, xmpp.NS_MUC_ADMIN, xmpp.NS_MUC_OWNER, - xmpp.NS_MUC_CONFIG, xmpp.NS_COMMANDS, xmpp.NS_DISCO_INFO, 'ipv6', - 'jabber:iq:gateway', xmpp.NS_LAST, xmpp.NS_PRIVACY, xmpp.NS_PRIVATE, - xmpp.NS_REGISTER, xmpp.NS_VERSION, xmpp.NS_DATA, xmpp.NS_ENCRYPTED, 'msglog', - 'sslc2s', 'stringprep', xmpp.NS_PING, xmpp.NS_TIME_REVISED, xmpp.NS_SSN, - xmpp.NS_MOOD, xmpp.NS_ACTIVITY, xmpp.NS_NICK, xmpp.NS_ROSTERX] + xmpp.NS_MUC, xmpp.NS_MUC_USER, xmpp.NS_MUC_ADMIN, xmpp.NS_MUC_OWNER, + xmpp.NS_MUC_CONFIG, xmpp.NS_COMMANDS, xmpp.NS_DISCO_INFO, 'ipv6', + 'jabber:iq:gateway', xmpp.NS_LAST, xmpp.NS_PRIVACY, xmpp.NS_PRIVATE, + xmpp.NS_REGISTER, xmpp.NS_VERSION, xmpp.NS_DATA, xmpp.NS_ENCRYPTED, 'msglog', + 'sslc2s', 'stringprep', xmpp.NS_PING, xmpp.NS_TIME_REVISED, xmpp.NS_SSN, + xmpp.NS_MOOD, xmpp.NS_ACTIVITY, xmpp.NS_NICK, xmpp.NS_ROSTERX] # Optional features gajim supports per account gajim_optional_features = {} @@ -216,224 +216,222 @@ import caps_cache caps_cache.initialize(logger) def get_nick_from_jid(jid): - pos = jid.find('@') - return jid[:pos] + pos = jid.find('@') + return jid[:pos] def get_server_from_jid(jid): - pos = jid.find('@') + 1 # after @ - return jid[pos:] + pos = jid.find('@') + 1 # after @ + return jid[pos:] def get_name_and_server_from_jid(jid): - name = get_nick_from_jid(jid) - server = get_server_from_jid(jid) - return name, server + name = get_nick_from_jid(jid) + server = get_server_from_jid(jid) + return name, server def get_room_and_nick_from_fjid(jid): - # fake jid is the jid for a contact in a room - # gaim@conference.jabber.no/nick/nick-continued - # return ('gaim@conference.jabber.no', 'nick/nick-continued') - l = jid.split('/', 1) - if len(l) == 1: # No nick - l.append('') - return l + # fake jid is the jid for a contact in a room + # gaim@conference.jabber.no/nick/nick-continued + # return ('gaim@conference.jabber.no', 'nick/nick-continued') + l = jid.split('/', 1) + if len(l) == 1: # No nick + l.append('') + return l def get_real_jid_from_fjid(account, fjid): - """ - Return real jid or returns None, if we don't know the real jid - """ - room_jid, nick = get_room_and_nick_from_fjid(fjid) - if not nick: # It's not a fake_jid, it is a real jid - return fjid # we return the real jid - real_jid = fjid - if interface.msg_win_mgr.get_gc_control(room_jid, account): - # It's a pm, so if we have real jid it's in contact.jid - gc_contact = contacts.get_gc_contact(account, room_jid, nick) - if not gc_contact: - return - # gc_contact.jid is None when it's not a real jid (we don't know real jid) - real_jid = gc_contact.jid - return real_jid + """ + Return real jid or returns None, if we don't know the real jid + """ + room_jid, nick = get_room_and_nick_from_fjid(fjid) + if not nick: # It's not a fake_jid, it is a real jid + return fjid # we return the real jid + real_jid = fjid + if interface.msg_win_mgr.get_gc_control(room_jid, account): + # It's a pm, so if we have real jid it's in contact.jid + gc_contact = contacts.get_gc_contact(account, room_jid, nick) + if not gc_contact: + return + # gc_contact.jid is None when it's not a real jid (we don't know real jid) + real_jid = gc_contact.jid + return real_jid def get_room_from_fjid(jid): - return get_room_and_nick_from_fjid(jid)[0] + return get_room_and_nick_from_fjid(jid)[0] def get_contact_name_from_jid(account, jid): - c = contacts.get_first_contact_from_jid(account, jid) - return c.name + c = contacts.get_first_contact_from_jid(account, jid) + return c.name def get_jid_without_resource(jid): - return jid.split('/')[0] + return jid.split('/')[0] def construct_fjid(room_jid, nick): - """ - Nick is in UTF-8 (taken from treeview); room_jid is in unicode - """ - # fake jid is the jid for a contact in a room - # gaim@conference.jabber.org/nick - if isinstance(nick, str): - nick = unicode(nick, 'utf-8') - return room_jid + '/' + nick + """ + Nick is in UTF-8 (taken from treeview); room_jid is in unicode + """ + # fake jid is the jid for a contact in a room + # gaim@conference.jabber.org/nick + if isinstance(nick, str): + nick = unicode(nick, 'utf-8') + return room_jid + '/' + nick def get_resource_from_jid(jid): - jids = jid.split('/', 1) - if len(jids) > 1: - return jids[1] # abc@doremi.org/res/res-continued - else: - return '' + jids = jid.split('/', 1) + if len(jids) > 1: + return jids[1] # abc@doremi.org/res/res-continued + else: + return '' def get_number_of_accounts(): - """ - Return the number of ALL accounts - """ - return len(connections.keys()) + """ + Return the number of ALL accounts + """ + return len(connections.keys()) def get_number_of_connected_accounts(accounts_list = None): - """ - Returns the number of CONNECTED accounts. Uou can optionally pass an - accounts_list and if you do those will be checked, else all will be checked - """ - connected_accounts = 0 - if accounts_list is None: - accounts = connections.keys() - else: - accounts = accounts_list - for account in accounts: - if account_is_connected(account): - connected_accounts = connected_accounts + 1 - return connected_accounts + """ + Returns the number of CONNECTED accounts. Uou can optionally pass an + accounts_list and if you do those will be checked, else all will be checked + """ + connected_accounts = 0 + if accounts_list is None: + accounts = connections.keys() + else: + accounts = accounts_list + for account in accounts: + if account_is_connected(account): + connected_accounts = connected_accounts + 1 + return connected_accounts def account_is_connected(account): - if account not in connections: - return False - if connections[account].connected > 1: # 0 is offline, 1 is connecting - return True - else: - return False + if account not in connections: + return False + if connections[account].connected > 1: # 0 is offline, 1 is connecting + return True + else: + return False def account_is_disconnected(account): - return not account_is_connected(account) + return not account_is_connected(account) def zeroconf_is_connected(): - return account_is_connected(ZEROCONF_ACC_NAME) and \ - config.get_per('accounts', ZEROCONF_ACC_NAME, 'is_zeroconf') + return account_is_connected(ZEROCONF_ACC_NAME) and \ + config.get_per('accounts', ZEROCONF_ACC_NAME, 'is_zeroconf') def get_number_of_securely_connected_accounts(): - """ - Return the number of the accounts that are SSL/TLS connected - """ - num_of_secured = 0 - for account in connections.keys(): - if account_is_securely_connected(account): - num_of_secured += 1 - return num_of_secured + """ + Return the number of the accounts that are SSL/TLS connected + """ + num_of_secured = 0 + for account in connections.keys(): + if account_is_securely_connected(account): + num_of_secured += 1 + return num_of_secured def account_is_securely_connected(account): - if account_is_connected(account) and \ - account in con_types and con_types[account] in ('tls', 'ssl'): - return True - else: - return False + if account_is_connected(account) and \ + account in con_types and con_types[account] in ('tls', 'ssl'): + return True + else: + return False def get_transport_name_from_jid(jid, use_config_setting = True): - """ - Returns 'aim', 'gg', 'irc' etc + """ + Returns 'aim', 'gg', 'irc' etc - If JID is not from transport returns None. - """ - #FIXME: jid can be None! one TB I saw had this problem: - # in the code block # it is a groupchat presence in handle_event_notify - # jid was None. Yann why? - if not jid or (use_config_setting and not config.get('use_transports_iconsets')): - return + If JID is not from transport returns None. + """ + #FIXME: jid can be None! one TB I saw had this problem: + # in the code block # it is a groupchat presence in handle_event_notify + # jid was None. Yann why? + if not jid or (use_config_setting and not config.get('use_transports_iconsets')): + return - host = get_server_from_jid(jid) - if host in transport_type: - return transport_type[host] + host = get_server_from_jid(jid) + if host in transport_type: + return transport_type[host] - # host is now f.e. icq.foo.org or just icq (sometimes on hacky transports) - host_splitted = host.split('.') - if len(host_splitted) != 0: - # now we support both 'icq.' and 'icq' but not icqsucks.org - host = host_splitted[0] + # host is now f.e. icq.foo.org or just icq (sometimes on hacky transports) + host_splitted = host.split('.') + if len(host_splitted) != 0: + # now we support both 'icq.' and 'icq' but not icqsucks.org + host = host_splitted[0] - if host in ('aim', 'irc', 'icq', 'msn', 'sms', 'tlen', 'weather', 'yahoo', - 'mrim', 'facebook'): - return host - elif host == 'gg': - return 'gadu-gadu' - elif host == 'jit': - return 'icq' - elif host == 'facebook': - return 'facebook' - else: - return None + if host in ('aim', 'irc', 'icq', 'msn', 'sms', 'tlen', 'weather', 'yahoo', + 'mrim', 'facebook'): + return host + elif host == 'gg': + return 'gadu-gadu' + elif host == 'jit': + return 'icq' + elif host == 'facebook': + return 'facebook' + else: + return None def jid_is_transport(jid): - # if not '@' or '@' starts the jid then it is transport - if jid.find('@') <= 0: - return True - return False + # if not '@' or '@' starts the jid then it is transport + if jid.find('@') <= 0: + return True + return False def get_jid_from_account(account_name): - """ - Return the jid we use in the given account - """ - name = config.get_per('accounts', account_name, 'name') - hostname = config.get_per('accounts', account_name, 'hostname') - jid = name + '@' + hostname - return jid + """ + Return the jid we use in the given account + """ + name = config.get_per('accounts', account_name, 'name') + hostname = config.get_per('accounts', account_name, 'hostname') + jid = name + '@' + hostname + return jid def get_our_jids(): - """ - Returns a list of the jids we use in our accounts - """ - our_jids = list() - for account in contacts.get_accounts(): - our_jids.append(get_jid_from_account(account)) - return our_jids + """ + Returns a list of the jids we use in our accounts + """ + our_jids = list() + for account in contacts.get_accounts(): + our_jids.append(get_jid_from_account(account)) + return our_jids def get_hostname_from_account(account_name, use_srv = False): - """ - Returns hostname (if custom hostname is used, that is returned) - """ - if use_srv and connections[account_name].connected_hostname: - return connections[account_name].connected_hostname - if config.get_per('accounts', account_name, 'use_custom_host'): - return config.get_per('accounts', account_name, 'custom_host') - return config.get_per('accounts', account_name, 'hostname') + """ + Returns hostname (if custom hostname is used, that is returned) + """ + if use_srv and connections[account_name].connected_hostname: + return connections[account_name].connected_hostname + if config.get_per('accounts', account_name, 'use_custom_host'): + return config.get_per('accounts', account_name, 'custom_host') + return config.get_per('accounts', account_name, 'hostname') def get_notification_image_prefix(jid): - """ - Returns the prefix for the notification images - """ - transport_name = get_transport_name_from_jid(jid) - if transport_name in ('aim', 'icq', 'msn', 'yahoo', 'facebook'): - prefix = transport_name - else: - prefix = 'jabber' - return prefix + """ + Returns the prefix for the notification images + """ + transport_name = get_transport_name_from_jid(jid) + if transport_name in ('aim', 'icq', 'msn', 'yahoo', 'facebook'): + prefix = transport_name + else: + prefix = 'jabber' + return prefix def get_name_from_jid(account, jid): - """ - Return from JID's shown name and if no contact returns jids - """ - contact = contacts.get_first_contact_from_jid(account, jid) - if contact: - actor = contact.get_shown_name() - else: - actor = jid - return actor + """ + Return from JID's shown name and if no contact returns jids + """ + contact = contacts.get_first_contact_from_jid(account, jid) + if contact: + actor = contact.get_shown_name() + else: + actor = jid + return actor def get_priority(account, show): - """ - Return the priority an account must have - """ - if not show: - show = 'online' + """ + Return the priority an account must have + """ + if not show: + show = 'online' - if show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible') and \ - config.get_per('accounts', account, 'adjust_priority_with_status'): - return config.get_per('accounts', account, 'autopriority_' + show) - return config.get_per('accounts', account, 'priority') - -# vim: se ts=3: + if show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible') and \ + config.get_per('accounts', account, 'adjust_priority_with_status'): + return config.get_per('accounts', account, 'autopriority_' + show) + return config.get_per('accounts', account, 'priority') diff --git a/src/common/ged.py b/src/common/ged.py index 775fad8c3..92c54a268 100644 --- a/src/common/ged.py +++ b/src/common/ged.py @@ -33,33 +33,32 @@ POSTCORE = 50 class GlobalEventsDispatcher(object): - def __init__(self): - self.handlers = {} + def __init__(self): + self.handlers = {} - def register_event_handler(self, event_name, priority, handler): - if event_name in self.handlers: - handlers_list = self.handlers[event_name] - i = 0 - for i,h in enumerate(handlers_list): - if priority < h[0]: - break + def register_event_handler(self, event_name, priority, handler): + if event_name in self.handlers: + handlers_list = self.handlers[event_name] + i = 0 + for i, h in enumerate(handlers_list): + if priority < h[0]: + break - handlers_list.insert(i, (priority, handler)) - else: - self.handlers[event_name] = [(priority, handler)] + handlers_list.insert(i, (priority, handler)) + else: + self.handlers[event_name] = [(priority, handler)] - def remove_event_handler(self, event_name, priority, handler): - if event_name in self.handlers: - try: - self.handlers[event_name].remove((priority, handler)) - except ValueError, error: - log.warn('''Function (%s) with priority "%s" never registered - as handler of event "%s". Couldn\'t remove. Error: %s''' - %(handler, priority, event_name, error)) + def remove_event_handler(self, event_name, priority, handler): + if event_name in self.handlers: + try: + self.handlers[event_name].remove((priority, handler)) + except ValueError, error: + log.warn('''Function (%s) with priority "%s" never registered + as handler of event "%s". Couldn\'t remove. Error: %s''' + %(handler, priority, event_name, error)) - def raise_event(self, event_name, *args, **kwargs): - log.debug('%s\nArgs: %s'%(event_name, str(args))) - if event_name in self.handlers: - for priority, handler in self.handlers[event_name]: - handler(*args, **kwargs) - \ No newline at end of file + def raise_event(self, event_name, *args, **kwargs): + log.debug('%s\nArgs: %s'%(event_name, str(args))) + if event_name in self.handlers: + for priority, handler in self.handlers[event_name]: + handler(*args, **kwargs) diff --git a/src/common/helpers.py b/src/common/helpers.py index d3caa4266..51b6f49ad 100644 --- a/src/common/helpers.py +++ b/src/common/helpers.py @@ -47,1311 +47,1310 @@ from i18n import Q_ from i18n import ngettext try: - import winsound # windows-only built-in module for playing wav - import win32api - import win32con + import winsound # windows-only built-in module for playing wav + import win32api + import win32con except Exception: - pass + pass special_groups = (_('Transports'), _('Not in Roster'), _('Observers'), _('Groupchats')) class InvalidFormat(Exception): - pass + pass def decompose_jid(jidstring): - user = None - server = None - resource = None + user = None + server = None + resource = None - # Search for delimiters - user_sep = jidstring.find('@') - res_sep = jidstring.find('/') + # Search for delimiters + user_sep = jidstring.find('@') + res_sep = jidstring.find('/') - if user_sep == -1: - if res_sep == -1: - # host - server = jidstring - else: - # host/resource - server = jidstring[0:res_sep] - resource = jidstring[res_sep + 1:] or None - else: - if res_sep == -1: - # user@host - user = jidstring[0:user_sep] or None - server = jidstring[user_sep + 1:] - else: - if user_sep < res_sep: - # user@host/resource - user = jidstring[0:user_sep] or None - server = jidstring[user_sep + 1:user_sep + (res_sep - user_sep)] - resource = jidstring[res_sep + 1:] or None - else: - # server/resource (with an @ in resource) - server = jidstring[0:res_sep] - resource = jidstring[res_sep + 1:] or None - return user, server, resource + if user_sep == -1: + if res_sep == -1: + # host + server = jidstring + else: + # host/resource + server = jidstring[0:res_sep] + resource = jidstring[res_sep + 1:] or None + else: + if res_sep == -1: + # user@host + user = jidstring[0:user_sep] or None + server = jidstring[user_sep + 1:] + else: + if user_sep < res_sep: + # user@host/resource + user = jidstring[0:user_sep] or None + server = jidstring[user_sep + 1:user_sep + (res_sep - user_sep)] + resource = jidstring[res_sep + 1:] or None + else: + # server/resource (with an @ in resource) + server = jidstring[0:res_sep] + resource = jidstring[res_sep + 1:] or None + return user, server, resource def parse_jid(jidstring): - """ - Perform stringprep on all JID fragments from a string and return the full - jid - """ - # This function comes from http://svn.twistedmatrix.com/cvs/trunk/twisted/words/protocols/jabber/jid.py + """ + Perform stringprep on all JID fragments from a string and return the full + jid + """ + # This function comes from http://svn.twistedmatrix.com/cvs/trunk/twisted/words/protocols/jabber/jid.py - return prep(*decompose_jid(jidstring)) + return prep(*decompose_jid(jidstring)) def idn_to_ascii(host): - """ - Convert IDN (Internationalized Domain Names) to ACE (ASCII-compatible - encoding) - """ - from encodings import idna - labels = idna.dots.split(host) - converted_labels = [] - for label in labels: - converted_labels.append(idna.ToASCII(label)) - return ".".join(converted_labels) + """ + Convert IDN (Internationalized Domain Names) to ACE (ASCII-compatible + encoding) + """ + from encodings import idna + labels = idna.dots.split(host) + converted_labels = [] + for label in labels: + converted_labels.append(idna.ToASCII(label)) + return ".".join(converted_labels) def ascii_to_idn(host): - """ - Convert ACE (ASCII-compatible encoding) to IDN (Internationalized Domain - Names) - """ - from encodings import idna - labels = idna.dots.split(host) - converted_labels = [] - for label in labels: - converted_labels.append(idna.ToUnicode(label)) - return ".".join(converted_labels) + """ + Convert ACE (ASCII-compatible encoding) to IDN (Internationalized Domain + Names) + """ + from encodings import idna + labels = idna.dots.split(host) + converted_labels = [] + for label in labels: + converted_labels.append(idna.ToUnicode(label)) + return ".".join(converted_labels) def parse_resource(resource): - """ - Perform stringprep on resource and return it - """ - if resource: - try: - from xmpp.stringprepare import resourceprep - return resourceprep.prepare(unicode(resource)) - except UnicodeError: - raise InvalidFormat, 'Invalid character in resource.' + """ + Perform stringprep on resource and return it + """ + if resource: + try: + from xmpp.stringprepare import resourceprep + return resourceprep.prepare(unicode(resource)) + except UnicodeError: + raise InvalidFormat, 'Invalid character in resource.' def prep(user, server, resource): - """ - Perform stringprep on all JID fragments and return the full jid - """ - # This function comes from - #http://svn.twistedmatrix.com/cvs/trunk/twisted/words/protocols/jabber/jid.py - if user: - try: - from xmpp.stringprepare import nodeprep - user = nodeprep.prepare(unicode(user)) - except UnicodeError: - raise InvalidFormat, _('Invalid character in username.') - else: - user = None + """ + Perform stringprep on all JID fragments and return the full jid + """ + # This function comes from + #http://svn.twistedmatrix.com/cvs/trunk/twisted/words/protocols/jabber/jid.py + if user: + try: + from xmpp.stringprepare import nodeprep + user = nodeprep.prepare(unicode(user)) + except UnicodeError: + raise InvalidFormat, _('Invalid character in username.') + else: + user = None - if not server: - raise InvalidFormat, _('Server address required.') - else: - try: - from xmpp.stringprepare import nameprep - server = nameprep.prepare(unicode(server)) - except UnicodeError: - raise InvalidFormat, _('Invalid character in hostname.') + if not server: + raise InvalidFormat, _('Server address required.') + else: + try: + from xmpp.stringprepare import nameprep + server = nameprep.prepare(unicode(server)) + except UnicodeError: + raise InvalidFormat, _('Invalid character in hostname.') - if resource: - try: - from xmpp.stringprepare import resourceprep - resource = resourceprep.prepare(unicode(resource)) - except UnicodeError: - raise InvalidFormat, _('Invalid character in resource.') - else: - resource = None + if resource: + try: + from xmpp.stringprepare import resourceprep + resource = resourceprep.prepare(unicode(resource)) + except UnicodeError: + raise InvalidFormat, _('Invalid character in resource.') + else: + resource = None - if user: - if resource: - return '%s@%s/%s' % (user, server, resource) - else: - return '%s@%s' % (user, server) - else: - if resource: - return '%s/%s' % (server, resource) - else: - return server + if user: + if resource: + return '%s@%s/%s' % (user, server, resource) + else: + return '%s@%s' % (user, server) + else: + if resource: + return '%s/%s' % (server, resource) + else: + return server def windowsify(s): - if os.name == 'nt': - return s.capitalize() - return s + if os.name == 'nt': + return s.capitalize() + return s def temp_failure_retry(func, *args, **kwargs): - while True: - try: - return func(*args, **kwargs) - except (os.error, IOError, select.error), ex: - if ex.errno == errno.EINTR: - continue - else: - raise + while True: + try: + return func(*args, **kwargs) + except (os.error, IOError, select.error), ex: + if ex.errno == errno.EINTR: + continue + else: + raise def get_uf_show(show, use_mnemonic = False): - """ - Return a userfriendly string for dnd/xa/chat and make all strings - translatable + """ + Return a userfriendly string for dnd/xa/chat and make all strings + translatable - If use_mnemonic is True, it adds _ so GUI should call with True for - accessibility issues - """ - if show == 'dnd': - if use_mnemonic: - uf_show = _('_Busy') - else: - uf_show = _('Busy') - elif show == 'xa': - if use_mnemonic: - uf_show = _('_Not Available') - else: - uf_show = _('Not Available') - elif show == 'chat': - if use_mnemonic: - uf_show = _('_Free for Chat') - else: - uf_show = _('Free for Chat') - elif show == 'online': - if use_mnemonic: - uf_show = _('_Available') - else: - uf_show = _('Available') - elif show == 'connecting': - uf_show = _('Connecting') - elif show == 'away': - if use_mnemonic: - uf_show = _('A_way') - else: - uf_show = _('Away') - elif show == 'offline': - if use_mnemonic: - uf_show = _('_Offline') - else: - uf_show = _('Offline') - elif show == 'invisible': - if use_mnemonic: - uf_show = _('_Invisible') - else: - uf_show = _('Invisible') - elif show == 'not in roster': - uf_show = _('Not in Roster') - elif show == 'requested': - uf_show = Q_('?contact has status:Unknown') - else: - uf_show = Q_('?contact has status:Has errors') - return unicode(uf_show) + If use_mnemonic is True, it adds _ so GUI should call with True for + accessibility issues + """ + if show == 'dnd': + if use_mnemonic: + uf_show = _('_Busy') + else: + uf_show = _('Busy') + elif show == 'xa': + if use_mnemonic: + uf_show = _('_Not Available') + else: + uf_show = _('Not Available') + elif show == 'chat': + if use_mnemonic: + uf_show = _('_Free for Chat') + else: + uf_show = _('Free for Chat') + elif show == 'online': + if use_mnemonic: + uf_show = _('_Available') + else: + uf_show = _('Available') + elif show == 'connecting': + uf_show = _('Connecting') + elif show == 'away': + if use_mnemonic: + uf_show = _('A_way') + else: + uf_show = _('Away') + elif show == 'offline': + if use_mnemonic: + uf_show = _('_Offline') + else: + uf_show = _('Offline') + elif show == 'invisible': + if use_mnemonic: + uf_show = _('_Invisible') + else: + uf_show = _('Invisible') + elif show == 'not in roster': + uf_show = _('Not in Roster') + elif show == 'requested': + uf_show = Q_('?contact has status:Unknown') + else: + uf_show = Q_('?contact has status:Has errors') + return unicode(uf_show) def get_uf_sub(sub): - if sub == 'none': - uf_sub = Q_('?Subscription we already have:None') - elif sub == 'to': - uf_sub = _('To') - elif sub == 'from': - uf_sub = _('From') - elif sub == 'both': - uf_sub = _('Both') - else: - uf_sub = sub + if sub == 'none': + uf_sub = Q_('?Subscription we already have:None') + elif sub == 'to': + uf_sub = _('To') + elif sub == 'from': + uf_sub = _('From') + elif sub == 'both': + uf_sub = _('Both') + else: + uf_sub = sub - return unicode(uf_sub) + return unicode(uf_sub) def get_uf_ask(ask): - if ask is None: - uf_ask = Q_('?Ask (for Subscription):None') - elif ask == 'subscribe': - uf_ask = _('Subscribe') - else: - uf_ask = ask + if ask is None: + uf_ask = Q_('?Ask (for Subscription):None') + elif ask == 'subscribe': + uf_ask = _('Subscribe') + else: + uf_ask = ask - return unicode(uf_ask) + return unicode(uf_ask) def get_uf_role(role, plural = False): - ''' plural determines if you get Moderators or Moderator''' - if role == 'none': - role_name = Q_('?Group Chat Contact Role:None') - elif role == 'moderator': - if plural: - role_name = _('Moderators') - else: - role_name = _('Moderator') - elif role == 'participant': - if plural: - role_name = _('Participants') - else: - role_name = _('Participant') - elif role == 'visitor': - if plural: - role_name = _('Visitors') - else: - role_name = _('Visitor') - return role_name + ''' plural determines if you get Moderators or Moderator''' + if role == 'none': + role_name = Q_('?Group Chat Contact Role:None') + elif role == 'moderator': + if plural: + role_name = _('Moderators') + else: + role_name = _('Moderator') + elif role == 'participant': + if plural: + role_name = _('Participants') + else: + role_name = _('Participant') + elif role == 'visitor': + if plural: + role_name = _('Visitors') + else: + role_name = _('Visitor') + return role_name def get_uf_affiliation(affiliation): - '''Get a nice and translated affilition for muc''' - if affiliation == 'none': - affiliation_name = Q_('?Group Chat Contact Affiliation:None') - elif affiliation == 'owner': - affiliation_name = _('Owner') - elif affiliation == 'admin': - affiliation_name = _('Administrator') - elif affiliation == 'member': - affiliation_name = _('Member') - else: # Argl ! An unknown affiliation ! - affiliation_name = affiliation.capitalize() - return affiliation_name + '''Get a nice and translated affilition for muc''' + if affiliation == 'none': + affiliation_name = Q_('?Group Chat Contact Affiliation:None') + elif affiliation == 'owner': + affiliation_name = _('Owner') + elif affiliation == 'admin': + affiliation_name = _('Administrator') + elif affiliation == 'member': + affiliation_name = _('Member') + else: # Argl ! An unknown affiliation ! + affiliation_name = affiliation.capitalize() + return affiliation_name def get_sorted_keys(adict): - keys = sorted(adict.keys()) - return keys + keys = sorted(adict.keys()) + return keys def to_one_line(msg): - msg = msg.replace('\\', '\\\\') - msg = msg.replace('\n', '\\n') - # s1 = 'test\ntest\\ntest' - # s11 = s1.replace('\\', '\\\\') - # s12 = s11.replace('\n', '\\n') - # s12 - # 'test\\ntest\\\\ntest' - return msg + msg = msg.replace('\\', '\\\\') + msg = msg.replace('\n', '\\n') + # s1 = 'test\ntest\\ntest' + # s11 = s1.replace('\\', '\\\\') + # s12 = s11.replace('\n', '\\n') + # s12 + # 'test\\ntest\\\\ntest' + return msg def from_one_line(msg): - # (? 48: - hash = hashlib.md5(filename) - filename = base64.b64encode(hash.digest()) + """ + Make sure the filename we will write does contain only acceptable and latin + characters, and is not too long (in that case hash it) + """ + # 48 is the limit + if len(filename) > 48: + hash = hashlib.md5(filename) + filename = base64.b64encode(hash.digest()) - filename = punycode_encode(filename) # make it latin chars only - filename = filename.replace('/', '_') - if os.name == 'nt': - filename = filename.replace('?', '_').replace(':', '_')\ - .replace('\\', '_').replace('"', "'").replace('|', '_')\ - .replace('*', '_').replace('<', '_').replace('>', '_') + filename = punycode_encode(filename) # make it latin chars only + filename = filename.replace('/', '_') + if os.name == 'nt': + filename = filename.replace('?', '_').replace(':', '_')\ + .replace('\\', '_').replace('"', "'").replace('|', '_')\ + .replace('*', '_').replace('<', '_').replace('>', '_') - return filename + return filename def reduce_chars_newlines(text, max_chars = 0, max_lines = 0): - """ - Cut the chars after 'max_chars' on each line and show only the first - 'max_lines' + """ + Cut the chars after 'max_chars' on each line and show only the first + 'max_lines' - If any of the params is not present (None or 0) the action on it is not - performed - """ - def _cut_if_long(string): - if len(string) > max_chars: - string = string[:max_chars - 3] + '...' - return string + If any of the params is not present (None or 0) the action on it is not + performed + """ + def _cut_if_long(string): + if len(string) > max_chars: + string = string[:max_chars - 3] + '...' + return string - if isinstance(text, str): - text = text.decode('utf-8') + if isinstance(text, str): + text = text.decode('utf-8') - if max_lines == 0: - lines = text.split('\n') - else: - lines = text.split('\n', max_lines)[:max_lines] - if max_chars > 0: - if lines: - lines = [_cut_if_long(e) for e in lines] - if lines: - reduced_text = '\n'.join(lines) - if reduced_text != text: - reduced_text += '...' - else: - reduced_text = '' - return reduced_text + if max_lines == 0: + lines = text.split('\n') + else: + lines = text.split('\n', max_lines)[:max_lines] + if max_chars > 0: + if lines: + lines = [_cut_if_long(e) for e in lines] + if lines: + reduced_text = '\n'.join(lines) + if reduced_text != text: + reduced_text += '...' + else: + reduced_text = '' + return reduced_text def get_account_status(account): - status = reduce_chars_newlines(account['status_line'], 100, 1) - return status + status = reduce_chars_newlines(account['status_line'], 100, 1) + return status def get_avatar_path(prefix): - """ - Return the filename of the avatar, distinguishes between user- and contact- - provided one. Return None if no avatar was found at all. prefix is the path - to the requested avatar just before the ".png" or ".jpeg" - """ - # First, scan for a local, user-set avatar - for type_ in ('jpeg', 'png'): - file_ = prefix + '_local.' + type_ - if os.path.exists(file_): - return file_ - # If none available, scan for a contact-provided avatar - for type_ in ('jpeg', 'png'): - file_ = prefix + '.' + type_ - if os.path.exists(file_): - return file_ - return None + """ + Return the filename of the avatar, distinguishes between user- and contact- + provided one. Return None if no avatar was found at all. prefix is the path + to the requested avatar just before the ".png" or ".jpeg" + """ + # First, scan for a local, user-set avatar + for type_ in ('jpeg', 'png'): + file_ = prefix + '_local.' + type_ + if os.path.exists(file_): + return file_ + # If none available, scan for a contact-provided avatar + for type_ in ('jpeg', 'png'): + file_ = prefix + '.' + type_ + if os.path.exists(file_): + return file_ + return None def datetime_tuple(timestamp): - """ - Convert timestamp using strptime and the format: %Y%m%dT%H:%M:%S + """ + Convert timestamp using strptime and the format: %Y%m%dT%H:%M:%S - Because of various datetime formats are used the following exceptions - are handled: - - Optional milliseconds appened to the string are removed - - Optional Z (that means UTC) appened to the string are removed - - XEP-082 datetime strings have all '-' cahrs removed to meet - the above format. - """ - timestamp = timestamp.split('.')[0] - timestamp = timestamp.replace('-', '') - timestamp = timestamp.replace('z', '') - timestamp = timestamp.replace('Z', '') - from time import strptime - return strptime(timestamp, '%Y%m%dT%H:%M:%S') + Because of various datetime formats are used the following exceptions + are handled: + - Optional milliseconds appened to the string are removed + - Optional Z (that means UTC) appened to the string are removed + - XEP-082 datetime strings have all '-' cahrs removed to meet + the above format. + """ + timestamp = timestamp.split('.')[0] + timestamp = timestamp.replace('-', '') + timestamp = timestamp.replace('z', '') + timestamp = timestamp.replace('Z', '') + from time import strptime + return strptime(timestamp, '%Y%m%dT%H:%M:%S') # import gajim only when needed (after decode_string is defined) see #4764 import gajim def convert_bytes(string): - suffix = '' - # IEC standard says KiB = 1024 bytes KB = 1000 bytes - # but do we use the standard? - use_kib_mib = gajim.config.get('use_kib_mib') - align = 1024. - bytes = float(string) - if bytes >= align: - bytes = round(bytes/align, 1) - if bytes >= align: - bytes = round(bytes/align, 1) - if bytes >= align: - bytes = round(bytes/align, 1) - if use_kib_mib: - #GiB means gibibyte - suffix = _('%s GiB') - else: - #GB means gigabyte - suffix = _('%s GB') - else: - if use_kib_mib: - #MiB means mibibyte - suffix = _('%s MiB') - else: - #MB means megabyte - suffix = _('%s MB') - else: - if use_kib_mib: - #KiB means kibibyte - suffix = _('%s KiB') - else: - #KB means kilo bytes - suffix = _('%s KB') - else: - #B means bytes - suffix = _('%s B') - return suffix % unicode(bytes) + suffix = '' + # IEC standard says KiB = 1024 bytes KB = 1000 bytes + # but do we use the standard? + use_kib_mib = gajim.config.get('use_kib_mib') + align = 1024. + bytes = float(string) + if bytes >= align: + bytes = round(bytes/align, 1) + if bytes >= align: + bytes = round(bytes/align, 1) + if bytes >= align: + bytes = round(bytes/align, 1) + if use_kib_mib: + #GiB means gibibyte + suffix = _('%s GiB') + else: + #GB means gigabyte + suffix = _('%s GB') + else: + if use_kib_mib: + #MiB means mibibyte + suffix = _('%s MiB') + else: + #MB means megabyte + suffix = _('%s MB') + else: + if use_kib_mib: + #KiB means kibibyte + suffix = _('%s KiB') + else: + #KB means kilo bytes + suffix = _('%s KB') + else: + #B means bytes + suffix = _('%s B') + return suffix % unicode(bytes) def get_contact_dict_for_account(account): - """ - Create a dict of jid, nick -> contact with all contacts of account. + """ + Create a dict of jid, nick -> contact with all contacts of account. - Can be used for completion lists - """ - contacts_dict = {} - for jid in gajim.contacts.get_jid_list(account): - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - contacts_dict[jid] = contact - name = contact.name - if name in contacts_dict: - contact1 = contacts_dict[name] - del contacts_dict[name] - contacts_dict['%s (%s)' % (name, contact1.jid)] = contact1 - contacts_dict['%s (%s)' % (name, jid)] = contact - else: - if contact.name == gajim.get_nick_from_jid(jid): - del contacts_dict[jid] - contacts_dict[name] = contact - return contacts_dict + Can be used for completion lists + """ + contacts_dict = {} + for jid in gajim.contacts.get_jid_list(account): + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + contacts_dict[jid] = contact + name = contact.name + if name in contacts_dict: + contact1 = contacts_dict[name] + del contacts_dict[name] + contacts_dict['%s (%s)' % (name, contact1.jid)] = contact1 + contacts_dict['%s (%s)' % (name, jid)] = contact + else: + if contact.name == gajim.get_nick_from_jid(jid): + del contacts_dict[jid] + contacts_dict[name] = contact + return contacts_dict def launch_browser_mailer(kind, uri): - #kind = 'url' or 'mail' - if os.name == 'nt': - try: - os.startfile(uri) # if pywin32 is installed we open - except Exception: - pass + #kind = 'url' or 'mail' + if os.name == 'nt': + try: + os.startfile(uri) # if pywin32 is installed we open + except Exception: + pass - else: - if kind in ('mail', 'sth_at_sth') and not uri.startswith('mailto:'): - uri = 'mailto:' + uri + else: + if kind in ('mail', 'sth_at_sth') and not uri.startswith('mailto:'): + uri = 'mailto:' + uri - if kind == 'url' and uri.startswith('www.'): - uri = 'http://' + uri + if kind == 'url' and uri.startswith('www.'): + uri = 'http://' + uri - if gajim.config.get('openwith') == 'gnome-open': - command = 'gnome-open' - elif gajim.config.get('openwith') == 'kfmclient exec': - command = 'kfmclient exec' - elif gajim.config.get('openwith') == 'exo-open': - command = 'exo-open' - elif gajim.config.get('openwith') == 'custom': - if kind == 'url': - command = gajim.config.get('custombrowser') - elif kind in ('mail', 'sth_at_sth'): - command = gajim.config.get('custommailapp') - if command == '': # if no app is configured - return + if gajim.config.get('openwith') == 'gnome-open': + command = 'gnome-open' + elif gajim.config.get('openwith') == 'kfmclient exec': + command = 'kfmclient exec' + elif gajim.config.get('openwith') == 'exo-open': + command = 'exo-open' + elif gajim.config.get('openwith') == 'custom': + if kind == 'url': + command = gajim.config.get('custombrowser') + elif kind in ('mail', 'sth_at_sth'): + command = gajim.config.get('custommailapp') + if command == '': # if no app is configured + return - command = build_command(command, uri) - try: - exec_command(command) - except Exception: - pass + command = build_command(command, uri) + try: + exec_command(command) + except Exception: + pass def launch_file_manager(path_to_open): - if os.name == 'nt': - try: - os.startfile(path_to_open) # if pywin32 is installed we open - except Exception: - pass - else: - if gajim.config.get('openwith') == 'gnome-open': - command = 'gnome-open' - elif gajim.config.get('openwith') == 'kfmclient exec': - command = 'kfmclient exec' - elif gajim.config.get('openwith') == 'exo-open': - command = 'exo-open' - elif gajim.config.get('openwith') == 'custom': - command = gajim.config.get('custom_file_manager') - if command == '': # if no app is configured - return - command = build_command(command, path_to_open) - try: - exec_command(command) - except Exception: - pass + if os.name == 'nt': + try: + os.startfile(path_to_open) # if pywin32 is installed we open + except Exception: + pass + else: + if gajim.config.get('openwith') == 'gnome-open': + command = 'gnome-open' + elif gajim.config.get('openwith') == 'kfmclient exec': + command = 'kfmclient exec' + elif gajim.config.get('openwith') == 'exo-open': + command = 'exo-open' + elif gajim.config.get('openwith') == 'custom': + command = gajim.config.get('custom_file_manager') + if command == '': # if no app is configured + return + command = build_command(command, path_to_open) + try: + exec_command(command) + except Exception: + pass def play_sound(event): - if not gajim.config.get('sounds_on'): - return - path_to_soundfile = gajim.config.get_per('soundevents', event, 'path') - play_sound_file(path_to_soundfile) + if not gajim.config.get('sounds_on'): + return + path_to_soundfile = gajim.config.get_per('soundevents', event, 'path') + play_sound_file(path_to_soundfile) def check_soundfile_path(file, dirs=(gajim.gajimpaths.data_root, gajim.DATA_DIR)): - """ - Check if the sound file exists + """ + Check if the sound file exists - :param file: the file to check, absolute or relative to 'dirs' path - :param dirs: list of knows paths to fallback if the file doesn't exists - (eg: ~/.gajim/sounds/, DATADIR/sounds...). - :return the path to file or None if it doesn't exists. - """ - if not file: - return None - elif os.path.exists(file): - return file + :param file: the file to check, absolute or relative to 'dirs' path + :param dirs: list of knows paths to fallback if the file doesn't exists + (eg: ~/.gajim/sounds/, DATADIR/sounds...). + :return the path to file or None if it doesn't exists. + """ + if not file: + return None + elif os.path.exists(file): + return file - for d in dirs: - d = os.path.join(d, 'sounds', file) - if os.path.exists(d): - return d - return None + for d in dirs: + d = os.path.join(d, 'sounds', file) + if os.path.exists(d): + return d + return None def strip_soundfile_path(file, dirs=(gajim.gajimpaths.data_root, gajim.DATA_DIR), abs=True): - """ - Remove knowns paths from a sound file + """ + Remove knowns paths from a sound file - Filechooser returns absolute path. If path is a known fallback path, we remove it. - So config have no hardcoded path to DATA_DIR and text in textfield is shorther. - param: file: the filename to strip. - param: dirs: list of knowns paths from which the filename should be stripped. - param: abs: force absolute path on dirs - """ - if not file: - return None + Filechooser returns absolute path. If path is a known fallback path, we remove it. + So config have no hardcoded path to DATA_DIR and text in textfield is shorther. + param: file: the filename to strip. + param: dirs: list of knowns paths from which the filename should be stripped. + param: abs: force absolute path on dirs + """ + if not file: + return None - name = os.path.basename(file) - for d in dirs: - d = os.path.join(d, 'sounds', name) - if abs: - d = os.path.abspath(d) - if file == d: - return name - return file + name = os.path.basename(file) + for d in dirs: + d = os.path.join(d, 'sounds', name) + if abs: + d = os.path.abspath(d) + if file == d: + return name + return file def play_sound_file(path_to_soundfile): - if path_to_soundfile == 'beep': - exec_command('beep') - return - path_to_soundfile = check_soundfile_path(path_to_soundfile) - if path_to_soundfile is None: - return - elif os.name == 'nt': - try: - winsound.PlaySound(path_to_soundfile, - winsound.SND_FILENAME|winsound.SND_ASYNC) - except Exception: - pass - elif os.name == 'posix': - if gajim.config.get('soundplayer') == '': - return - player = gajim.config.get('soundplayer') - command = build_command(player, path_to_soundfile) - exec_command(command) + if path_to_soundfile == 'beep': + exec_command('beep') + return + path_to_soundfile = check_soundfile_path(path_to_soundfile) + if path_to_soundfile is None: + return + elif os.name == 'nt': + try: + winsound.PlaySound(path_to_soundfile, + winsound.SND_FILENAME|winsound.SND_ASYNC) + except Exception: + pass + elif os.name == 'posix': + if gajim.config.get('soundplayer') == '': + return + player = gajim.config.get('soundplayer') + command = build_command(player, path_to_soundfile) + exec_command(command) def get_global_show(): - maxi = 0 - for account in gajim.connections: - if not gajim.config.get_per('accounts', account, - 'sync_with_global_status'): - continue - connected = gajim.connections[account].connected - if connected > maxi: - maxi = connected - return gajim.SHOW_LIST[maxi] + maxi = 0 + for account in gajim.connections: + if not gajim.config.get_per('accounts', account, + 'sync_with_global_status'): + continue + connected = gajim.connections[account].connected + if connected > maxi: + maxi = connected + return gajim.SHOW_LIST[maxi] def get_global_status(): - maxi = 0 - for account in gajim.connections: - if not gajim.config.get_per('accounts', account, - 'sync_with_global_status'): - continue - connected = gajim.connections[account].connected - if connected > maxi: - maxi = connected - status = gajim.connections[account].status - return status + maxi = 0 + for account in gajim.connections: + if not gajim.config.get_per('accounts', account, + 'sync_with_global_status'): + continue + connected = gajim.connections[account].connected + if connected > maxi: + maxi = connected + status = gajim.connections[account].status + return status def statuses_unified(): - """ - Test if all statuses are the same - """ - reference = None - for account in gajim.connections: - if not gajim.config.get_per('accounts', account, - 'sync_with_global_status'): - continue - if reference is None: - reference = gajim.connections[account].connected - elif reference != gajim.connections[account].connected: - return False - return True + """ + Test if all statuses are the same + """ + reference = None + for account in gajim.connections: + if not gajim.config.get_per('accounts', account, + 'sync_with_global_status'): + continue + if reference is None: + reference = gajim.connections[account].connected + elif reference != gajim.connections[account].connected: + return False + return True def get_icon_name_to_show(contact, account = None): - """ - Get the icon name to show in online, away, requested, etc - """ - if account and gajim.events.get_nb_roster_events(account, contact.jid): - return 'event' - if account and gajim.events.get_nb_roster_events(account, - contact.get_full_jid()): - return 'event' - if account and account in gajim.interface.minimized_controls and \ - contact.jid in gajim.interface.minimized_controls[account] and gajim.interface.\ - minimized_controls[account][contact.jid].get_nb_unread_pm() > 0: - return 'event' - if account and contact.jid in gajim.gc_connected[account]: - if gajim.gc_connected[account][contact.jid]: - return 'muc_active' - else: - return 'muc_inactive' - if contact.jid.find('@') <= 0: # if not '@' or '@' starts the jid ==> agent - return contact.show - if contact.sub in ('both', 'to'): - return contact.show - if contact.ask == 'subscribe': - return 'requested' - transport = gajim.get_transport_name_from_jid(contact.jid) - if transport: - return contact.show - if contact.show in gajim.SHOW_LIST: - return contact.show - return 'not in roster' + """ + Get the icon name to show in online, away, requested, etc + """ + if account and gajim.events.get_nb_roster_events(account, contact.jid): + return 'event' + if account and gajim.events.get_nb_roster_events(account, + contact.get_full_jid()): + return 'event' + if account and account in gajim.interface.minimized_controls and \ + contact.jid in gajim.interface.minimized_controls[account] and gajim.interface.\ + minimized_controls[account][contact.jid].get_nb_unread_pm() > 0: + return 'event' + if account and contact.jid in gajim.gc_connected[account]: + if gajim.gc_connected[account][contact.jid]: + return 'muc_active' + else: + return 'muc_inactive' + if contact.jid.find('@') <= 0: # if not '@' or '@' starts the jid ==> agent + return contact.show + if contact.sub in ('both', 'to'): + return contact.show + if contact.ask == 'subscribe': + return 'requested' + transport = gajim.get_transport_name_from_jid(contact.jid) + if transport: + return contact.show + if contact.show in gajim.SHOW_LIST: + return contact.show + return 'not in roster' def get_full_jid_from_iq(iq_obj): - """ - Return the full jid (with resource) from an iq as unicode - """ - return parse_jid(str(iq_obj.getFrom())) + """ + Return the full jid (with resource) from an iq as unicode + """ + return parse_jid(str(iq_obj.getFrom())) def get_jid_from_iq(iq_obj): - """ - Return the jid (without resource) from an iq as unicode - """ - jid = get_full_jid_from_iq(iq_obj) - return gajim.get_jid_without_resource(jid) + """ + Return the jid (without resource) from an iq as unicode + """ + jid = get_full_jid_from_iq(iq_obj) + return gajim.get_jid_without_resource(jid) def get_auth_sha(sid, initiator, target): - """ - Return sha of sid + initiator + target used for proxy auth - """ - return hashlib.sha1("%s%s%s" % (sid, initiator, target)).hexdigest() + """ + Return sha of sid + initiator + target used for proxy auth + """ + return hashlib.sha1("%s%s%s" % (sid, initiator, target)).hexdigest() def remove_invalid_xml_chars(string): - if string: - string = re.sub(gajim.interface.invalid_XML_chars_re, '', string) - return string + if string: + string = re.sub(gajim.interface.invalid_XML_chars_re, '', string) + return string distro_info = { - 'Arch Linux': '/etc/arch-release', - 'Aurox Linux': '/etc/aurox-release', - 'Conectiva Linux': '/etc/conectiva-release', - 'CRUX': '/usr/bin/crux', - 'Debian GNU/Linux': '/etc/debian_release', - 'Debian GNU/Linux': '/etc/debian_version', - 'Fedora Linux': '/etc/fedora-release', - 'Gentoo Linux': '/etc/gentoo-release', - 'Linux from Scratch': '/etc/lfs-release', - 'Mandrake Linux': '/etc/mandrake-release', - 'Slackware Linux': '/etc/slackware-release', - 'Slackware Linux': '/etc/slackware-version', - 'Solaris/Sparc': '/etc/release', - 'Source Mage': '/etc/sourcemage_version', - 'SUSE Linux': '/etc/SuSE-release', - 'Sun JDS': '/etc/sun-release', - 'PLD Linux': '/etc/pld-release', - 'Yellow Dog Linux': '/etc/yellowdog-release', - # many distros use the /etc/redhat-release for compatibility - # so Redhat is the last - 'Redhat Linux': '/etc/redhat-release' + 'Arch Linux': '/etc/arch-release', + 'Aurox Linux': '/etc/aurox-release', + 'Conectiva Linux': '/etc/conectiva-release', + 'CRUX': '/usr/bin/crux', + 'Debian GNU/Linux': '/etc/debian_release', + 'Debian GNU/Linux': '/etc/debian_version', + 'Fedora Linux': '/etc/fedora-release', + 'Gentoo Linux': '/etc/gentoo-release', + 'Linux from Scratch': '/etc/lfs-release', + 'Mandrake Linux': '/etc/mandrake-release', + 'Slackware Linux': '/etc/slackware-release', + 'Slackware Linux': '/etc/slackware-version', + 'Solaris/Sparc': '/etc/release', + 'Source Mage': '/etc/sourcemage_version', + 'SUSE Linux': '/etc/SuSE-release', + 'Sun JDS': '/etc/sun-release', + 'PLD Linux': '/etc/pld-release', + 'Yellow Dog Linux': '/etc/yellowdog-release', + # many distros use the /etc/redhat-release for compatibility + # so Redhat is the last + 'Redhat Linux': '/etc/redhat-release' } def get_random_string_16(): - """ - Create random string of length 16 - """ - rng = range(65, 90) - rng.extend(range(48, 57)) - char_sequence = [chr(e) for e in rng] - from random import sample - return ''.join(sample(char_sequence, 16)) + """ + Create random string of length 16 + """ + rng = range(65, 90) + rng.extend(range(48, 57)) + char_sequence = [chr(e) for e in rng] + from random import sample + return ''.join(sample(char_sequence, 16)) def get_os_info(): - if gajim.os_info: - return gajim.os_info - if os.name == 'nt': - # platform.release() seems to return the name of the windows - ver = sys.getwindowsversion() - ver_format = ver[3], ver[0], ver[1] - win_version = { - (1, 4, 0): '95', - (1, 4, 10): '98', - (1, 4, 90): 'ME', - (2, 4, 0): 'NT', - (2, 5, 0): '2000', - (2, 5, 1): 'XP', - (2, 5, 2): '2003', - (2, 6, 0): 'Vista', - (2, 6, 1): '7', - } - if ver_format in win_version: - os_info = 'Windows' + ' ' + win_version[ver_format] - else: - os_info = 'Windows' - gajim.os_info = os_info - return os_info - elif os.name == 'posix': - executable = 'lsb_release' - params = ' --description --codename --release --short' - full_path_to_executable = is_in_path(executable, return_abs_path = True) - if full_path_to_executable: - command = executable + params - p = subprocess.Popen([command], shell=True, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, close_fds=True) - p.wait() - output = temp_failure_retry(p.stdout.readline).strip() - # some distros put n/a in places, so remove those - output = output.replace('n/a', '').replace('N/A', '') - gajim.os_info = output - return output + if gajim.os_info: + return gajim.os_info + if os.name == 'nt': + # platform.release() seems to return the name of the windows + ver = sys.getwindowsversion() + ver_format = ver[3], ver[0], ver[1] + win_version = { + (1, 4, 0): '95', + (1, 4, 10): '98', + (1, 4, 90): 'ME', + (2, 4, 0): 'NT', + (2, 5, 0): '2000', + (2, 5, 1): 'XP', + (2, 5, 2): '2003', + (2, 6, 0): 'Vista', + (2, 6, 1): '7', + } + if ver_format in win_version: + os_info = 'Windows' + ' ' + win_version[ver_format] + else: + os_info = 'Windows' + gajim.os_info = os_info + return os_info + elif os.name == 'posix': + executable = 'lsb_release' + params = ' --description --codename --release --short' + full_path_to_executable = is_in_path(executable, return_abs_path = True) + if full_path_to_executable: + command = executable + params + p = subprocess.Popen([command], shell=True, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, close_fds=True) + p.wait() + output = temp_failure_retry(p.stdout.readline).strip() + # some distros put n/a in places, so remove those + output = output.replace('n/a', '').replace('N/A', '') + gajim.os_info = output + return output - # lsb_release executable not available, so parse files - for distro_name in distro_info: - path_to_file = distro_info[distro_name] - if os.path.exists(path_to_file): - if os.access(path_to_file, os.X_OK): - # the file is executable (f.e. CRUX) - # yes, then run it and get the first line of output. - text = get_output_of_command(path_to_file)[0] - else: - fd = open(path_to_file) - text = fd.readline().strip() # get only first line - fd.close() - if path_to_file.endswith('version'): - # sourcemage_version and slackware-version files - # have all the info we need (name and version of distro) - if not os.path.basename(path_to_file).startswith( - 'sourcemage') or not\ - os.path.basename(path_to_file).startswith('slackware'): - text = distro_name + ' ' + text - elif path_to_file.endswith('aurox-release') or \ - path_to_file.endswith('arch-release'): - # file doesn't have version - text = distro_name - elif path_to_file.endswith('lfs-release'): # file just has version - text = distro_name + ' ' + text - os_info = text.replace('\n', '') - gajim.os_info = os_info - return os_info + # lsb_release executable not available, so parse files + for distro_name in distro_info: + path_to_file = distro_info[distro_name] + if os.path.exists(path_to_file): + if os.access(path_to_file, os.X_OK): + # the file is executable (f.e. CRUX) + # yes, then run it and get the first line of output. + text = get_output_of_command(path_to_file)[0] + else: + fd = open(path_to_file) + text = fd.readline().strip() # get only first line + fd.close() + if path_to_file.endswith('version'): + # sourcemage_version and slackware-version files + # have all the info we need (name and version of distro) + if not os.path.basename(path_to_file).startswith( + 'sourcemage') or not\ + os.path.basename(path_to_file).startswith('slackware'): + text = distro_name + ' ' + text + elif path_to_file.endswith('aurox-release') or \ + path_to_file.endswith('arch-release'): + # file doesn't have version + text = distro_name + elif path_to_file.endswith('lfs-release'): # file just has version + text = distro_name + ' ' + text + os_info = text.replace('\n', '') + gajim.os_info = os_info + return os_info - # our last chance, ask uname and strip it - uname_output = get_output_of_command('uname -sr') - if uname_output is not None: - os_info = uname_output[0] # only first line - gajim.os_info = os_info - return os_info - os_info = 'N/A' - gajim.os_info = os_info - return os_info + # our last chance, ask uname and strip it + uname_output = get_output_of_command('uname -sr') + if uname_output is not None: + os_info = uname_output[0] # only first line + gajim.os_info = os_info + return os_info + os_info = 'N/A' + gajim.os_info = os_info + return os_info def allow_showing_notification(account, type_ = 'notify_on_new_message', - advanced_notif_num = None, is_first_message = True): - """ - Is it allowed to show nofication? + advanced_notif_num = None, is_first_message = True): + """ + Is it allowed to show nofication? - Check OUR status and if we allow notifications for that status type is the - option that need to be True e.g.: notify_on_signing is_first_message: set it - to false when it's not the first message - """ - if advanced_notif_num is not None: - popup = gajim.config.get_per('notifications', str(advanced_notif_num), - 'popup') - if popup == 'yes': - return True - if popup == 'no': - return False - if type_ and (not gajim.config.get(type_) or not is_first_message): - return False - if gajim.config.get('autopopupaway'): # always show notification - return True - if gajim.connections[account].connected in (2, 3): # we're online or chat - return True - return False + Check OUR status and if we allow notifications for that status type is the + option that need to be True e.g.: notify_on_signing is_first_message: set it + to false when it's not the first message + """ + if advanced_notif_num is not None: + popup = gajim.config.get_per('notifications', str(advanced_notif_num), + 'popup') + if popup == 'yes': + return True + if popup == 'no': + return False + if type_ and (not gajim.config.get(type_) or not is_first_message): + return False + if gajim.config.get('autopopupaway'): # always show notification + return True + if gajim.connections[account].connected in (2, 3): # we're online or chat + return True + return False def allow_popup_window(account, advanced_notif_num = None): - """ - Is it allowed to popup windows? - """ - if advanced_notif_num is not None: - popup = gajim.config.get_per('notifications', str(advanced_notif_num), - 'auto_open') - if popup == 'yes': - return True - if popup == 'no': - return False - autopopup = gajim.config.get('autopopup') - autopopupaway = gajim.config.get('autopopupaway') - if autopopup and (autopopupaway or \ - gajim.connections[account].connected in (2, 3)): # we're online or chat - return True - return False + """ + Is it allowed to popup windows? + """ + if advanced_notif_num is not None: + popup = gajim.config.get_per('notifications', str(advanced_notif_num), + 'auto_open') + if popup == 'yes': + return True + if popup == 'no': + return False + autopopup = gajim.config.get('autopopup') + autopopupaway = gajim.config.get('autopopupaway') + if autopopup and (autopopupaway or \ + gajim.connections[account].connected in (2, 3)): # we're online or chat + return True + return False def allow_sound_notification(account, sound_event, advanced_notif_num=None): - if advanced_notif_num is not None: - sound = gajim.config.get_per('notifications', str(advanced_notif_num), - 'sound') - if sound == 'yes': - return True - if sound == 'no': - return False - if gajim.config.get('sounddnd') or gajim.connections[account].connected != \ - gajim.SHOW_LIST.index('dnd') and gajim.config.get_per('soundevents', - sound_event, 'enabled'): - return True - return False + if advanced_notif_num is not None: + sound = gajim.config.get_per('notifications', str(advanced_notif_num), + 'sound') + if sound == 'yes': + return True + if sound == 'no': + return False + if gajim.config.get('sounddnd') or gajim.connections[account].connected != \ + gajim.SHOW_LIST.index('dnd') and gajim.config.get_per('soundevents', + sound_event, 'enabled'): + return True + return False def get_chat_control(account, contact): - full_jid_with_resource = contact.jid - if contact.resource: - full_jid_with_resource += '/' + contact.resource - highest_contact = gajim.contacts.get_contact_with_highest_priority( - account, contact.jid) + full_jid_with_resource = contact.jid + if contact.resource: + full_jid_with_resource += '/' + contact.resource + highest_contact = gajim.contacts.get_contact_with_highest_priority( + account, contact.jid) - # Look for a chat control that has the given resource, or default to - # one without resource - ctrl = gajim.interface.msg_win_mgr.get_control(full_jid_with_resource, - account) + # Look for a chat control that has the given resource, or default to + # one without resource + ctrl = gajim.interface.msg_win_mgr.get_control(full_jid_with_resource, + account) - if ctrl: - return ctrl - elif highest_contact and highest_contact.resource and \ - contact.resource != highest_contact.resource: - return None - else: - # unknown contact or offline message - return gajim.interface.msg_win_mgr.get_control(contact.jid, account) + if ctrl: + return ctrl + elif highest_contact and highest_contact.resource and \ + contact.resource != highest_contact.resource: + return None + else: + # unknown contact or offline message + return gajim.interface.msg_win_mgr.get_control(contact.jid, account) def get_notification_icon_tooltip_dict(): - """ - Return a dict of the form {acct: {'show': show, 'message': message, - 'event_lines': [list of text lines to show in tooltip]} - """ - # How many events must there be before they're shown summarized, not per-user - max_ungrouped_events = 10 + """ + Return a dict of the form {acct: {'show': show, 'message': message, + 'event_lines': [list of text lines to show in tooltip]} + """ + # How many events must there be before they're shown summarized, not per-user + max_ungrouped_events = 10 - accounts = get_accounts_info() + accounts = get_accounts_info() - # Gather events. (With accounts, when there are more.) - for account in accounts: - account_name = account['name'] - account['event_lines'] = [] - # Gather events per-account - pending_events = gajim.events.get_events(account = account_name) - messages, non_messages, total_messages, total_non_messages = {}, {}, 0, 0 - for jid in pending_events: - for event in pending_events[jid]: - if event.type_.count('file') > 0: - # This is a non-messagee event. - messages[jid] = non_messages.get(jid, 0) + 1 - total_non_messages = total_non_messages + 1 - else: - # This is a message. - messages[jid] = messages.get(jid, 0) + 1 - total_messages = total_messages + 1 - # Display unread messages numbers, if any - if total_messages > 0: - if total_messages > max_ungrouped_events: - text = ngettext( - '%d message pending', - '%d messages pending', - total_messages, total_messages, total_messages) - account['event_lines'].append(text) - else: - for jid in messages.keys(): - text = ngettext( - '%d message pending', - '%d messages pending', - messages[jid], messages[jid], messages[jid]) - contact = gajim.contacts.get_first_contact_from_jid( - account['name'], jid) - if jid in gajim.gc_connected[account['name']]: - text += _(' from room %s') % (jid) - elif contact: - name = contact.get_shown_name() - text += _(' from user %s') % (name) - else: - text += _(' from %s') % (jid) - account['event_lines'].append(text) + # Gather events. (With accounts, when there are more.) + for account in accounts: + account_name = account['name'] + account['event_lines'] = [] + # Gather events per-account + pending_events = gajim.events.get_events(account = account_name) + messages, non_messages, total_messages, total_non_messages = {}, {}, 0, 0 + for jid in pending_events: + for event in pending_events[jid]: + if event.type_.count('file') > 0: + # This is a non-messagee event. + messages[jid] = non_messages.get(jid, 0) + 1 + total_non_messages = total_non_messages + 1 + else: + # This is a message. + messages[jid] = messages.get(jid, 0) + 1 + total_messages = total_messages + 1 + # Display unread messages numbers, if any + if total_messages > 0: + if total_messages > max_ungrouped_events: + text = ngettext( + '%d message pending', + '%d messages pending', + total_messages, total_messages, total_messages) + account['event_lines'].append(text) + else: + for jid in messages.keys(): + text = ngettext( + '%d message pending', + '%d messages pending', + messages[jid], messages[jid], messages[jid]) + contact = gajim.contacts.get_first_contact_from_jid( + account['name'], jid) + if jid in gajim.gc_connected[account['name']]: + text += _(' from room %s') % (jid) + elif contact: + name = contact.get_shown_name() + text += _(' from user %s') % (name) + else: + text += _(' from %s') % (jid) + account['event_lines'].append(text) - # Display unseen events numbers, if any - if total_non_messages > 0: - if total_non_messages > max_ungrouped_events: - text = ngettext( - '%d event pending', - '%d events pending', - total_non_messages, total_non_messages, total_non_messages) - account['event_lines'].append(text) - else: - for jid in non_messages.keys(): - text = ngettext( - '%d event pending', - '%d events pending', - non_messages[jid], non_messages[jid], non_messages[jid]) - text += _(' from user %s') % (jid) - account[account]['event_lines'].append(text) + # Display unseen events numbers, if any + if total_non_messages > 0: + if total_non_messages > max_ungrouped_events: + text = ngettext( + '%d event pending', + '%d events pending', + total_non_messages, total_non_messages, total_non_messages) + account['event_lines'].append(text) + else: + for jid in non_messages.keys(): + text = ngettext( + '%d event pending', + '%d events pending', + non_messages[jid], non_messages[jid], non_messages[jid]) + text += _(' from user %s') % (jid) + account[account]['event_lines'].append(text) - return accounts + return accounts def get_notification_icon_tooltip_text(): - text = None - # How many events must there be before they're shown summarized, not per-user - # max_ungrouped_events = 10 - # Character which should be used to indent in the tooltip. - indent_with = ' ' + text = None + # How many events must there be before they're shown summarized, not per-user + # max_ungrouped_events = 10 + # Character which should be used to indent in the tooltip. + indent_with = ' ' - accounts = get_notification_icon_tooltip_dict() + accounts = get_notification_icon_tooltip_dict() - if len(accounts) == 0: - # No configured account - return _('Gajim') + if len(accounts) == 0: + # No configured account + return _('Gajim') - # at least one account present + # at least one account present - # Is there more that one account? - if len(accounts) == 1: - show_more_accounts = False - else: - show_more_accounts = True + # Is there more that one account? + if len(accounts) == 1: + show_more_accounts = False + else: + show_more_accounts = True - # If there is only one account, its status is shown on the first line. - if show_more_accounts: - text = _('Gajim') - else: - text = _('Gajim - %s') % (get_account_status(accounts[0])) + # If there is only one account, its status is shown on the first line. + if show_more_accounts: + text = _('Gajim') + else: + text = _('Gajim - %s') % (get_account_status(accounts[0])) - # Gather and display events. (With accounts, when there are more.) - for account in accounts: - account_name = account['name'] - # Set account status, if not set above - if (show_more_accounts): - message = '\n' + indent_with + ' %s - %s' - text += message % (account_name, get_account_status(account)) - # Account list shown, messages need to be indented more - indent_how = 2 - else: - # If no account list is shown, messages could have default indenting. - indent_how = 1 - for line in account['event_lines']: - text += '\n' + indent_with * indent_how + ' ' - text += line - return text + # Gather and display events. (With accounts, when there are more.) + for account in accounts: + account_name = account['name'] + # Set account status, if not set above + if (show_more_accounts): + message = '\n' + indent_with + ' %s - %s' + text += message % (account_name, get_account_status(account)) + # Account list shown, messages need to be indented more + indent_how = 2 + else: + # If no account list is shown, messages could have default indenting. + indent_how = 1 + for line in account['event_lines']: + text += '\n' + indent_with * indent_how + ' ' + text += line + return text def get_accounts_info(): - """ - Helper for notification icon tooltip - """ - accounts = [] - accounts_list = sorted(gajim.contacts.get_accounts()) - for account in accounts_list: - status_idx = gajim.connections[account].connected - # uncomment the following to hide offline accounts - # if status_idx == 0: continue - status = gajim.SHOW_LIST[status_idx] - message = gajim.connections[account].status - single_line = get_uf_show(status) - if message is None: - message = '' - else: - message = message.strip() - if message != '': - single_line += ': ' + message - accounts.append({'name': account, 'status_line': single_line, - 'show': status, 'message': message}) - return accounts + """ + Helper for notification icon tooltip + """ + accounts = [] + accounts_list = sorted(gajim.contacts.get_accounts()) + for account in accounts_list: + status_idx = gajim.connections[account].connected + # uncomment the following to hide offline accounts + # if status_idx == 0: continue + status = gajim.SHOW_LIST[status_idx] + message = gajim.connections[account].status + single_line = get_uf_show(status) + if message is None: + message = '' + else: + message = message.strip() + if message != '': + single_line += ': ' + message + accounts.append({'name': account, 'status_line': single_line, + 'show': status, 'message': message}) + return accounts def get_iconset_path(iconset): - if os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', iconset)): - return os.path.join(gajim.DATA_DIR, 'iconsets', iconset) - elif os.path.isdir(os.path.join(gajim.MY_ICONSETS_PATH, iconset)): - return os.path.join(gajim.MY_ICONSETS_PATH, iconset) + if os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', iconset)): + return os.path.join(gajim.DATA_DIR, 'iconsets', iconset) + elif os.path.isdir(os.path.join(gajim.MY_ICONSETS_PATH, iconset)): + return os.path.join(gajim.MY_ICONSETS_PATH, iconset) def get_mood_iconset_path(iconset): - if os.path.isdir(os.path.join(gajim.DATA_DIR, 'moods', iconset)): - return os.path.join(gajim.DATA_DIR, 'moods', iconset) - elif os.path.isdir(os.path.join(gajim.MY_MOOD_ICONSETS_PATH, iconset)): - return os.path.join(gajim.MY_MOOD_ICONSETS_PATH, iconset) + if os.path.isdir(os.path.join(gajim.DATA_DIR, 'moods', iconset)): + return os.path.join(gajim.DATA_DIR, 'moods', iconset) + elif os.path.isdir(os.path.join(gajim.MY_MOOD_ICONSETS_PATH, iconset)): + return os.path.join(gajim.MY_MOOD_ICONSETS_PATH, iconset) def get_activity_iconset_path(iconset): - if os.path.isdir(os.path.join(gajim.DATA_DIR, 'activities', iconset)): - return os.path.join(gajim.DATA_DIR, 'activities', iconset) - elif os.path.isdir(os.path.join(gajim.MY_ACTIVITY_ICONSETS_PATH, - iconset)): - return os.path.join(gajim.MY_ACTIVITY_ICONSETS_PATH, iconset) + if os.path.isdir(os.path.join(gajim.DATA_DIR, 'activities', iconset)): + return os.path.join(gajim.DATA_DIR, 'activities', iconset) + elif os.path.isdir(os.path.join(gajim.MY_ACTIVITY_ICONSETS_PATH, + iconset)): + return os.path.join(gajim.MY_ACTIVITY_ICONSETS_PATH, iconset) def get_transport_path(transport): - if os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', 'transports', - transport)): - return os.path.join(gajim.DATA_DIR, 'iconsets', 'transports', transport) - elif os.path.isdir(os.path.join(gajim.MY_ICONSETS_PATH, 'transports', - transport)): - return os.path.join(gajim.MY_ICONSETS_PATH, 'transports', transport) - # No transport folder found, use default jabber one - return get_iconset_path(gajim.config.get('iconset')) + if os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', 'transports', + transport)): + return os.path.join(gajim.DATA_DIR, 'iconsets', 'transports', transport) + elif os.path.isdir(os.path.join(gajim.MY_ICONSETS_PATH, 'transports', + transport)): + return os.path.join(gajim.MY_ICONSETS_PATH, 'transports', transport) + # No transport folder found, use default jabber one + return get_iconset_path(gajim.config.get('iconset')) def prepare_and_validate_gpg_keyID(account, jid, keyID): - """ - Return an eight char long keyID that can be used with for GPG encryption - with this contact + """ + Return an eight char long keyID that can be used with for GPG encryption + with this contact - If the given keyID is None, return UNKNOWN; if the key does not match the - assigned key XXXXXXXXMISMATCH is returned. If the key is trusted and not yet - assigned, assign it. - """ - if gajim.connections[account].USE_GPG: - if keyID and len(keyID) == 16: - keyID = keyID[8:] + If the given keyID is None, return UNKNOWN; if the key does not match the + assigned key XXXXXXXXMISMATCH is returned. If the key is trusted and not yet + assigned, assign it. + """ + if gajim.connections[account].USE_GPG: + if keyID and len(keyID) == 16: + keyID = keyID[8:] - attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() + attached_keys = gajim.config.get_per('accounts', account, + 'attached_gpg_keys').split() - if jid in attached_keys and keyID: - attachedkeyID = attached_keys[attached_keys.index(jid) + 1] - if attachedkeyID != keyID: - # Mismatch! Another gpg key was expected - keyID += 'MISMATCH' - elif jid in attached_keys: - # An unsigned presence, just use the assigned key - keyID = attached_keys[attached_keys.index(jid) + 1] - elif keyID: - public_keys = gajim.connections[account].ask_gpg_keys() - # Assign the corresponding key, if we have it in our keyring - if keyID in public_keys: - for u in gajim.contacts.get_contacts(account, jid): - u.keyID = keyID - keys_str = gajim.config.get_per('accounts', account, 'attached_gpg_keys') - keys_str += jid + ' ' + keyID + ' ' - gajim.config.set_per('accounts', account, 'attached_gpg_keys', keys_str) - elif keyID is None: - keyID = 'UNKNOWN' - return keyID + if jid in attached_keys and keyID: + attachedkeyID = attached_keys[attached_keys.index(jid) + 1] + if attachedkeyID != keyID: + # Mismatch! Another gpg key was expected + keyID += 'MISMATCH' + elif jid in attached_keys: + # An unsigned presence, just use the assigned key + keyID = attached_keys[attached_keys.index(jid) + 1] + elif keyID: + public_keys = gajim.connections[account].ask_gpg_keys() + # Assign the corresponding key, if we have it in our keyring + if keyID in public_keys: + for u in gajim.contacts.get_contacts(account, jid): + u.keyID = keyID + keys_str = gajim.config.get_per('accounts', account, 'attached_gpg_keys') + keys_str += jid + ' ' + keyID + ' ' + gajim.config.set_per('accounts', account, 'attached_gpg_keys', keys_str) + elif keyID is None: + keyID = 'UNKNOWN' + return keyID def update_optional_features(account = None): - import xmpp - if account: - accounts = [account] - else: - accounts = [a for a in gajim.connections] - for a in accounts: - gajim.gajim_optional_features[a] = [] - if gajim.config.get_per('accounts', a, 'subscribe_mood'): - gajim.gajim_optional_features[a].append(xmpp.NS_MOOD + '+notify') - if gajim.config.get_per('accounts', a, 'subscribe_activity'): - gajim.gajim_optional_features[a].append(xmpp.NS_ACTIVITY + '+notify') - if gajim.config.get_per('accounts', a, 'publish_tune'): - gajim.gajim_optional_features[a].append(xmpp.NS_TUNE) - if gajim.config.get_per('accounts', a, 'publish_location'): - gajim.gajim_optional_features[a].append(xmpp.NS_LOCATION) - if gajim.config.get_per('accounts', a, 'subscribe_tune'): - gajim.gajim_optional_features[a].append(xmpp.NS_TUNE + '+notify') - if gajim.config.get_per('accounts', a, 'subscribe_nick'): - gajim.gajim_optional_features[a].append(xmpp.NS_NICK + '+notify') - if gajim.config.get_per('accounts', a, 'subscribe_location'): - gajim.gajim_optional_features[a].append(xmpp.NS_LOCATION + '+notify') - if gajim.config.get('outgoing_chat_state_notifactions') != 'disabled': - gajim.gajim_optional_features[a].append(xmpp.NS_CHATSTATES) - if not gajim.config.get('ignore_incoming_xhtml'): - gajim.gajim_optional_features[a].append(xmpp.NS_XHTML_IM) - if gajim.HAVE_PYCRYPTO \ - and gajim.config.get_per('accounts', a, 'enable_esessions'): - gajim.gajim_optional_features[a].append(xmpp.NS_ESESSION) - if gajim.config.get_per('accounts', a, 'answer_receipts'): - gajim.gajim_optional_features[a].append(xmpp.NS_RECEIPTS) - if gajim.HAVE_FARSIGHT: - gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE) - gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP) - gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_AUDIO) - gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_VIDEO) - gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_ICE_UDP) - gajim.caps_hash[a] = caps_cache.compute_caps_hash([gajim.gajim_identity], - gajim.gajim_common_features + gajim.gajim_optional_features[a]) - # re-send presence with new hash - connected = gajim.connections[a].connected - if connected > 1 and gajim.SHOW_LIST[connected] != 'invisible': - gajim.connections[a].change_status(gajim.SHOW_LIST[connected], - gajim.connections[a].status) + import xmpp + if account: + accounts = [account] + else: + accounts = [a for a in gajim.connections] + for a in accounts: + gajim.gajim_optional_features[a] = [] + if gajim.config.get_per('accounts', a, 'subscribe_mood'): + gajim.gajim_optional_features[a].append(xmpp.NS_MOOD + '+notify') + if gajim.config.get_per('accounts', a, 'subscribe_activity'): + gajim.gajim_optional_features[a].append(xmpp.NS_ACTIVITY + '+notify') + if gajim.config.get_per('accounts', a, 'publish_tune'): + gajim.gajim_optional_features[a].append(xmpp.NS_TUNE) + if gajim.config.get_per('accounts', a, 'publish_location'): + gajim.gajim_optional_features[a].append(xmpp.NS_LOCATION) + if gajim.config.get_per('accounts', a, 'subscribe_tune'): + gajim.gajim_optional_features[a].append(xmpp.NS_TUNE + '+notify') + if gajim.config.get_per('accounts', a, 'subscribe_nick'): + gajim.gajim_optional_features[a].append(xmpp.NS_NICK + '+notify') + if gajim.config.get_per('accounts', a, 'subscribe_location'): + gajim.gajim_optional_features[a].append(xmpp.NS_LOCATION + '+notify') + if gajim.config.get('outgoing_chat_state_notifactions') != 'disabled': + gajim.gajim_optional_features[a].append(xmpp.NS_CHATSTATES) + if not gajim.config.get('ignore_incoming_xhtml'): + gajim.gajim_optional_features[a].append(xmpp.NS_XHTML_IM) + if gajim.HAVE_PYCRYPTO \ + and gajim.config.get_per('accounts', a, 'enable_esessions'): + gajim.gajim_optional_features[a].append(xmpp.NS_ESESSION) + if gajim.config.get_per('accounts', a, 'answer_receipts'): + gajim.gajim_optional_features[a].append(xmpp.NS_RECEIPTS) + if gajim.HAVE_FARSIGHT: + gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE) + gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP) + gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_AUDIO) + gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_VIDEO) + gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_ICE_UDP) + gajim.caps_hash[a] = caps_cache.compute_caps_hash([gajim.gajim_identity], + gajim.gajim_common_features + gajim.gajim_optional_features[a]) + # re-send presence with new hash + connected = gajim.connections[a].connected + if connected > 1 and gajim.SHOW_LIST[connected] != 'invisible': + gajim.connections[a].change_status(gajim.SHOW_LIST[connected], + gajim.connections[a].status) def jid_is_blocked(account, jid): - return ((jid in gajim.connections[account].blocked_contacts) or \ - gajim.connections[account].blocked_all) + return ((jid in gajim.connections[account].blocked_contacts) or \ + gajim.connections[account].blocked_all) def group_is_blocked(account, group): - return ((group in gajim.connections[account].blocked_groups) or \ - gajim.connections[account].blocked_all) + return ((group in gajim.connections[account].blocked_groups) or \ + gajim.connections[account].blocked_all) def get_subscription_request_msg(account=None): - s = gajim.config.get_per('accounts', account, 'subscription_request_msg') - if s: - return s - s = _('I would like to add you to my contact list.') - if account: - s = _('Hello, I am $name.') + ' ' + s - our_jid = gajim.get_jid_from_account(account) - vcard = gajim.connections[account].get_cached_vcard(our_jid) - name = '' - if 'N' in vcard: - if 'GIVEN' in vcard['N'] and 'FAMILY' in vcard['N']: - name = vcard['N']['GIVEN'] + ' ' + vcard['N']['FAMILY'] - if not name: - if 'FN' in vcard: - name = vcard['FN'] - nick = gajim.nicks[account] - if name and nick: - name += ' (%s)' % nick - elif nick: - name = nick - s = Template(s).safe_substitute({'name': name}) - return s -# vim: se ts=3: + s = gajim.config.get_per('accounts', account, 'subscription_request_msg') + if s: + return s + s = _('I would like to add you to my contact list.') + if account: + s = _('Hello, I am $name.') + ' ' + s + our_jid = gajim.get_jid_from_account(account) + vcard = gajim.connections[account].get_cached_vcard(our_jid) + name = '' + if 'N' in vcard: + if 'GIVEN' in vcard['N'] and 'FAMILY' in vcard['N']: + name = vcard['N']['GIVEN'] + ' ' + vcard['N']['FAMILY'] + if not name: + if 'FN' in vcard: + name = vcard['FN'] + nick = gajim.nicks[account] + if name and nick: + name += ' (%s)' % nick + elif nick: + name = nick + s = Template(s).safe_substitute({'name': name}) + return s diff --git a/src/common/i18n.py b/src/common/i18n.py index 7b9e8dab3..4497a56f0 100644 --- a/src/common/i18n.py +++ b/src/common/i18n.py @@ -29,20 +29,20 @@ import defs import unicodedata def paragraph_direction_mark(text): - """ - Determine paragraph writing direction according to - http://www.unicode.org/reports/tr9/#The_Paragraph_Level + """ + Determine paragraph writing direction according to + http://www.unicode.org/reports/tr9/#The_Paragraph_Level - Returns either Unicode LTR mark or RTL mark. - """ - for char in text: - bidi = unicodedata.bidirectional(char) - if bidi == 'L': - return u'\u200E' - elif bidi == 'AL' or bidi == 'R': - return u'\u200F' + Returns either Unicode LTR mark or RTL mark. + """ + for char in text: + bidi = unicodedata.bidirectional(char) + if bidi == 'L': + return u'\u200E' + elif bidi == 'AL' or bidi == 'R': + return u'\u200F' - return u'\u200E' + return u'\u200E' APP = 'gajim' DIR = defs.localedir @@ -53,48 +53,46 @@ locale.setlocale(locale.LC_ALL, '') ## For windows: set, if needed, a value in LANG environmental variable ## if os.name == 'nt': - lang = os.getenv('LANG') - if lang is None: - default_lang = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc.. - if default_lang: - lang = default_lang + lang = os.getenv('LANG') + if lang is None: + default_lang = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc.. + if default_lang: + lang = default_lang - if lang: - os.environ['LANG'] = lang + if lang: + os.environ['LANG'] = lang gettext.install(APP, DIR, unicode = True) if gettext._translations: - _translation = gettext._translations.values()[0] + _translation = gettext._translations.values()[0] else: - _translation = gettext.NullTranslations() + _translation = gettext.NullTranslations() def Q_(s): - # Qualified translatable strings - # Some strings are too ambiguous to be easily translated. - # so we must use as: - # s = Q_('?vcard:Unknown') - # widget.set_text(s) - # Q_() removes the ?vcard: - # but gettext while parsing the file detects ?vcard:Unknown as a whole string. - # translator can either put the ?vcard: part or no (easier for him or her to no) - # nothing fails - s = _(s) - if s[0] == '?': - s = s[s.find(':')+1:] # remove ?abc: part - return s + # Qualified translatable strings + # Some strings are too ambiguous to be easily translated. + # so we must use as: + # s = Q_('?vcard:Unknown') + # widget.set_text(s) + # Q_() removes the ?vcard: + # but gettext while parsing the file detects ?vcard:Unknown as a whole string. + # translator can either put the ?vcard: part or no (easier for him or her to no) + # nothing fails + s = _(s) + if s[0] == '?': + s = s[s.find(':')+1:] # remove ?abc: part + return s def ngettext(s_sing, s_plural, n, replace_sing = None, replace_plural = None): - """ - Use as: - i18n.ngettext('leave room %s', 'leave rooms %s', len(rooms), 'a', 'a, b, c') + """ + Use as: + i18n.ngettext('leave room %s', 'leave rooms %s', len(rooms), 'a', 'a, b, c') - In other words this is a hack to ngettext() to support %s %d etc.. - """ - text = _translation.ungettext(s_sing, s_plural, n) - if n == 1 and replace_sing is not None: - text = text % replace_sing - elif n > 1 and replace_plural is not None: - text = text % replace_plural - return text - -# vim: se ts=3: + In other words this is a hack to ngettext() to support %s %d etc.. + """ + text = _translation.ungettext(s_sing, s_plural, n) + if n == 1 and replace_sing is not None: + text = text % replace_sing + elif n > 1 and replace_plural is not None: + text = text % replace_plural + return text diff --git a/src/common/idle.py b/src/common/idle.py index d3ff4dff7..5e1bf4c0f 100644 --- a/src/common/idle.py +++ b/src/common/idle.py @@ -20,14 +20,14 @@ import ctypes import ctypes.util class XScreenSaverInfo(ctypes.Structure): - _fields_ = [ - ('window', ctypes.c_ulong), - ('state', ctypes.c_int), - ('kind', ctypes.c_int), - ('til_or_since', ctypes.c_ulong), - ('idle', ctypes.c_ulong), - ('eventMask', ctypes.c_ulong) - ] + _fields_ = [ + ('window', ctypes.c_ulong), + ('state', ctypes.c_int), + ('kind', ctypes.c_int), + ('til_or_since', ctypes.c_ulong), + ('idle', ctypes.c_ulong), + ('eventMask', ctypes.c_ulong) + ] XScreenSaverInfo_p = ctypes.POINTER(XScreenSaverInfo) display_p = ctypes.c_void_p @@ -35,65 +35,65 @@ xid = ctypes.c_ulong c_int_p = ctypes.POINTER(ctypes.c_int) try: - libX11path = ctypes.util.find_library('X11') - if libX11path == None: - raise OSError('libX11 could not be found.') - libX11 = ctypes.cdll.LoadLibrary(libX11path) - libX11.XOpenDisplay.restype = display_p - libX11.XOpenDisplay.argtypes = ctypes.c_char_p, - libX11.XDefaultRootWindow.restype = xid - libX11.XDefaultRootWindow.argtypes = display_p, + libX11path = ctypes.util.find_library('X11') + if libX11path == None: + raise OSError('libX11 could not be found.') + libX11 = ctypes.cdll.LoadLibrary(libX11path) + libX11.XOpenDisplay.restype = display_p + libX11.XOpenDisplay.argtypes = ctypes.c_char_p, + libX11.XDefaultRootWindow.restype = xid + libX11.XDefaultRootWindow.argtypes = display_p, - libXsspath = ctypes.util.find_library('Xss') - if libXsspath == None: - raise OSError('libXss could not be found.') - libXss = ctypes.cdll.LoadLibrary(libXsspath) - libXss.XScreenSaverQueryExtension.argtypes = display_p, c_int_p, c_int_p - libXss.XScreenSaverAllocInfo.restype = XScreenSaverInfo_p - libXss.XScreenSaverQueryInfo.argtypes = (display_p, xid, XScreenSaverInfo_p) + libXsspath = ctypes.util.find_library('Xss') + if libXsspath == None: + raise OSError('libXss could not be found.') + libXss = ctypes.cdll.LoadLibrary(libXsspath) + libXss.XScreenSaverQueryExtension.argtypes = display_p, c_int_p, c_int_p + libXss.XScreenSaverAllocInfo.restype = XScreenSaverInfo_p + libXss.XScreenSaverQueryInfo.argtypes = (display_p, xid, XScreenSaverInfo_p) - dpy_p = libX11.XOpenDisplay(None) - if dpy_p == None: - raise OSError('Could not open X Display.') + dpy_p = libX11.XOpenDisplay(None) + if dpy_p == None: + raise OSError('Could not open X Display.') - _event_basep = ctypes.c_int() - _error_basep = ctypes.c_int() - if libXss.XScreenSaverQueryExtension(dpy_p, ctypes.byref(_event_basep), - ctypes.byref(_error_basep)) == 0: - raise OSError('XScreenSaver Extension not available on display.') + _event_basep = ctypes.c_int() + _error_basep = ctypes.c_int() + if libXss.XScreenSaverQueryExtension(dpy_p, ctypes.byref(_event_basep), + ctypes.byref(_error_basep)) == 0: + raise OSError('XScreenSaver Extension not available on display.') - xss_info_p = libXss.XScreenSaverAllocInfo() - if xss_info_p == None: - raise OSError('XScreenSaverAllocInfo: Out of Memory.') + xss_info_p = libXss.XScreenSaverAllocInfo() + if xss_info_p == None: + raise OSError('XScreenSaverAllocInfo: Out of Memory.') - rootwindow = libX11.XDefaultRootWindow(dpy_p) - xss_available = True + rootwindow = libX11.XDefaultRootWindow(dpy_p) + xss_available = True except OSError, e: - # Logging? - xss_available = False + # Logging? + xss_available = False def getIdleSec(): - global xss_available - """ - Return the idle time in seconds - """ - if not xss_available: - return 0 - if libXss.XScreenSaverQueryInfo(dpy_p, rootwindow, xss_info_p) == 0: - return 0 - else: - return int(xss_info_p.contents.idle) / 1000 + global xss_available + """ + Return the idle time in seconds + """ + if not xss_available: + return 0 + if libXss.XScreenSaverQueryInfo(dpy_p, rootwindow, xss_info_p) == 0: + return 0 + else: + return int(xss_info_p.contents.idle) / 1000 def close(): - global xss_available - if xss_available: - libX11.XFree(xss_info_p) - libX11.XCloseDisplay(dpy_p) - xss_available = False + global xss_available + if xss_available: + libX11.XFree(xss_info_p) + libX11.XCloseDisplay(dpy_p) + xss_available = False if __name__ == '__main__': - import time - time.sleep(2.1) - print getIdleSec() - close() - print getIdleSec() + import time + time.sleep(2.1) + print getIdleSec() + close() + print getIdleSec() diff --git a/src/common/jingle.py b/src/common/jingle.py index 73745592a..8f87504c8 100644 --- a/src/common/jingle.py +++ b/src/common/jingle.py @@ -38,111 +38,109 @@ from jingle_rtp import JingleAudio, JingleVideo class ConnectionJingle(object): - """ - This object depends on that it is a part of Connection class. - """ + """ + This object depends on that it is a part of Connection class. + """ - def __init__(self): - # dictionary: (jid, sessionid) => JingleSession object - self.__sessions = {} + def __init__(self): + # dictionary: (jid, sessionid) => JingleSession object + self.__sessions = {} - # dictionary: (jid, iq stanza id) => JingleSession object, - # one time callbacks - self.__iq_responses = {} + # dictionary: (jid, iq stanza id) => JingleSession object, + # one time callbacks + self.__iq_responses = {} - def add_jingle(self, jingle): - """ - Add a jingle session to a jingle stanza dispatcher + def add_jingle(self, jingle): + """ + Add a jingle session to a jingle stanza dispatcher - jingle - a JingleSession object. - """ - self.__sessions[(jingle.peerjid, jingle.sid)] = jingle + jingle - a JingleSession object. + """ + self.__sessions[(jingle.peerjid, jingle.sid)] = jingle - def delete_jingle_session(self, peerjid, sid): - """ - Remove a jingle session from a jingle stanza dispatcher - """ - key = (peerjid, sid) - if key in self.__sessions: - #FIXME: Move this elsewhere? - for content in self.__sessions[key].contents.values(): - content.destroy() - self.__sessions[key].callbacks = [] - del self.__sessions[key] + def delete_jingle_session(self, peerjid, sid): + """ + Remove a jingle session from a jingle stanza dispatcher + """ + key = (peerjid, sid) + if key in self.__sessions: + #FIXME: Move this elsewhere? + for content in self.__sessions[key].contents.values(): + content.destroy() + self.__sessions[key].callbacks = [] + del self.__sessions[key] - def _JingleCB(self, con, stanza): - """ - The jingle stanza dispatcher + def _JingleCB(self, con, stanza): + """ + The jingle stanza dispatcher - Route jingle stanza to proper JingleSession object, or create one if it - is a new session. + Route jingle stanza to proper JingleSession object, or create one if it + is a new session. - TODO: Also check if the stanza isn't an error stanza, if so route it - adequatelly. - """ - # get data - jid = helpers.get_full_jid_from_iq(stanza) - id = stanza.getID() + TODO: Also check if the stanza isn't an error stanza, if so route it + adequatelly. + """ + # get data + jid = helpers.get_full_jid_from_iq(stanza) + id = stanza.getID() - if (jid, id) in self.__iq_responses.keys(): - self.__iq_responses[(jid, id)].on_stanza(stanza) - del self.__iq_responses[(jid, id)] - raise xmpp.NodeProcessed + if (jid, id) in self.__iq_responses.keys(): + self.__iq_responses[(jid, id)].on_stanza(stanza) + del self.__iq_responses[(jid, id)] + raise xmpp.NodeProcessed - jingle = stanza.getTag('jingle') - if not jingle: return - sid = jingle.getAttr('sid') + jingle = stanza.getTag('jingle') + if not jingle: return + sid = jingle.getAttr('sid') - # do we need to create a new jingle object - if (jid, sid) not in self.__sessions: - #TODO: tie-breaking and other things... - newjingle = JingleSession(con=self, weinitiate=False, jid=jid, sid=sid) - self.add_jingle(newjingle) + # do we need to create a new jingle object + if (jid, sid) not in self.__sessions: + #TODO: tie-breaking and other things... + newjingle = JingleSession(con=self, weinitiate=False, jid=jid, sid=sid) + self.add_jingle(newjingle) - # we already have such session in dispatcher... - self.__sessions[(jid, sid)].on_stanza(stanza) + # we already have such session in dispatcher... + self.__sessions[(jid, sid)].on_stanza(stanza) - raise xmpp.NodeProcessed + raise xmpp.NodeProcessed - def start_audio(self, jid): - if self.get_jingle_session(jid, media='audio'): - return self.get_jingle_session(jid, media='audio').sid - jingle = self.get_jingle_session(jid, media='video') - if jingle: - jingle.add_content('voice', JingleAudio(jingle)) - else: - jingle = JingleSession(self, weinitiate=True, jid=jid) - self.add_jingle(jingle) - jingle.add_content('voice', JingleAudio(jingle)) - jingle.start_session() - return jingle.sid + def start_audio(self, jid): + if self.get_jingle_session(jid, media='audio'): + return self.get_jingle_session(jid, media='audio').sid + jingle = self.get_jingle_session(jid, media='video') + if jingle: + jingle.add_content('voice', JingleAudio(jingle)) + else: + jingle = JingleSession(self, weinitiate=True, jid=jid) + self.add_jingle(jingle) + jingle.add_content('voice', JingleAudio(jingle)) + jingle.start_session() + return jingle.sid - def start_video(self, jid): - if self.get_jingle_session(jid, media='video'): - return self.get_jingle_session(jid, media='video').sid - jingle = self.get_jingle_session(jid, media='audio') - if jingle: - jingle.add_content('video', JingleVideo(jingle)) - else: - jingle = JingleSession(self, weinitiate=True, jid=jid) - self.add_jingle(jingle) - jingle.add_content('video', JingleVideo(jingle)) - jingle.start_session() - return jingle.sid + def start_video(self, jid): + if self.get_jingle_session(jid, media='video'): + return self.get_jingle_session(jid, media='video').sid + jingle = self.get_jingle_session(jid, media='audio') + if jingle: + jingle.add_content('video', JingleVideo(jingle)) + else: + jingle = JingleSession(self, weinitiate=True, jid=jid) + self.add_jingle(jingle) + jingle.add_content('video', JingleVideo(jingle)) + jingle.start_session() + return jingle.sid - def get_jingle_session(self, jid, sid=None, media=None): - if sid: - if (jid, sid) in self.__sessions: - return self.__sessions[(jid, sid)] - else: - return None - elif media: - if media not in ('audio', 'video'): - return None - for session in self.__sessions.values(): - if session.peerjid == jid and session.get_content(media): - return session + def get_jingle_session(self, jid, sid=None, media=None): + if sid: + if (jid, sid) in self.__sessions: + return self.__sessions[(jid, sid)] + else: + return None + elif media: + if media not in ('audio', 'video'): + return None + for session in self.__sessions.values(): + if session.peerjid == jid and session.get_content(media): + return session - return None - -# vim: se ts=3: \ No newline at end of file + return None diff --git a/src/common/jingle_content.py b/src/common/jingle_content.py index 7b0e3f8fe..f4022cc8e 100644 --- a/src/common/jingle_content.py +++ b/src/common/jingle_content.py @@ -20,123 +20,121 @@ import xmpp contents = {} def get_jingle_content(node): - namespace = node.getNamespace() - if namespace in contents: - return contents[namespace](node) + namespace = node.getNamespace() + if namespace in contents: + return contents[namespace](node) class JingleContentSetupException(Exception): - """ - Exception that should be raised when a content fails to setup. - """ + """ + Exception that should be raised when a content fails to setup. + """ class JingleContent(object): - """ - An abstraction of content in Jingle sessions - """ + """ + An abstraction of content in Jingle sessions + """ - def __init__(self, session, transport): - self.session = session - self.transport = transport - # will be filled by JingleSession.add_content() - # don't uncomment these lines, we will catch more buggy code then - # (a JingleContent not added to session shouldn't send anything) - #self.creator = None - #self.name = None - self.accepted = False - self.sent = False - self.negotiated = False + def __init__(self, session, transport): + self.session = session + self.transport = transport + # will be filled by JingleSession.add_content() + # don't uncomment these lines, we will catch more buggy code then + # (a JingleContent not added to session shouldn't send anything) + #self.creator = None + #self.name = None + self.accepted = False + self.sent = False + self.negotiated = False - self.media = None + self.media = None - self.senders = 'both' #FIXME - self.allow_sending = True # Used for stream direction, attribute 'senders' + self.senders = 'both' #FIXME + self.allow_sending = True # Used for stream direction, attribute 'senders' - self.callbacks = { - # these are called when *we* get stanzas - 'content-accept': [self.__on_transport_info], - 'content-add': [self.__on_transport_info], - 'content-modify': [], - 'content-reject': [], - 'content-remove': [], - 'description-info': [], - 'security-info': [], - 'session-accept': [self.__on_transport_info], - 'session-info': [], - 'session-initiate': [self.__on_transport_info], - 'session-terminate': [], - 'transport-info': [self.__on_transport_info], - 'transport-replace': [], - 'transport-accept': [], - 'transport-reject': [], - 'iq-result': [], - 'iq-error': [], - # these are called when *we* sent these stanzas - 'content-accept-sent': [self.__fill_jingle_stanza], - 'content-add-sent': [self.__fill_jingle_stanza], - 'session-initiate-sent': [self.__fill_jingle_stanza], - 'session-accept-sent': [self.__fill_jingle_stanza], - 'session-terminate-sent': [], - } + self.callbacks = { + # these are called when *we* get stanzas + 'content-accept': [self.__on_transport_info], + 'content-add': [self.__on_transport_info], + 'content-modify': [], + 'content-reject': [], + 'content-remove': [], + 'description-info': [], + 'security-info': [], + 'session-accept': [self.__on_transport_info], + 'session-info': [], + 'session-initiate': [self.__on_transport_info], + 'session-terminate': [], + 'transport-info': [self.__on_transport_info], + 'transport-replace': [], + 'transport-accept': [], + 'transport-reject': [], + 'iq-result': [], + 'iq-error': [], + # these are called when *we* sent these stanzas + 'content-accept-sent': [self.__fill_jingle_stanza], + 'content-add-sent': [self.__fill_jingle_stanza], + 'session-initiate-sent': [self.__fill_jingle_stanza], + 'session-accept-sent': [self.__fill_jingle_stanza], + 'session-terminate-sent': [], + } - def is_ready(self): - return self.accepted and not self.sent + def is_ready(self): + return self.accepted and not self.sent - def on_negotiated(self): - if self.accepted: - self.negotiated = True - self.session.content_negotiated(self.media) + def on_negotiated(self): + if self.accepted: + self.negotiated = True + self.session.content_negotiated(self.media) - def add_remote_candidates(self, candidates): - """ - Add a list of candidates to the list of remote candidates - """ - pass + def add_remote_candidates(self, candidates): + """ + Add a list of candidates to the list of remote candidates + """ + pass - def on_stanza(self, stanza, content, error, action): - """ - Called when something related to our content was sent by peer - """ - if action in self.callbacks: - for callback in self.callbacks[action]: - callback(stanza, content, error, action) + def on_stanza(self, stanza, content, error, action): + """ + Called when something related to our content was sent by peer + """ + if action in self.callbacks: + for callback in self.callbacks[action]: + callback(stanza, content, error, action) - def __on_transport_info(self, stanza, content, error, action): - """ - Got a new transport candidate - """ - candidates = self.transport.parse_transport_stanza( - content.getTag('transport')) - if candidates: - self.add_remote_candidates(candidates) + def __on_transport_info(self, stanza, content, error, action): + """ + Got a new transport candidate + """ + candidates = self.transport.parse_transport_stanza( + content.getTag('transport')) + if candidates: + self.add_remote_candidates(candidates) - def __content(self, payload=[]): - """ - Build a XML content-wrapper for our data - """ - return xmpp.Node('content', - attrs={'name': self.name, 'creator': self.creator}, - payload=payload) + def __content(self, payload=[]): + """ + Build a XML content-wrapper for our data + """ + return xmpp.Node('content', + attrs={'name': self.name, 'creator': self.creator}, + payload=payload) - def send_candidate(self, candidate): - """ - Send a transport candidate for a previously defined transport. - """ - content = self.__content() - content.addChild(node=self.transport.make_transport([candidate])) - self.session.send_transport_info(content) + def send_candidate(self, candidate): + """ + Send a transport candidate for a previously defined transport. + """ + content = self.__content() + content.addChild(node=self.transport.make_transport([candidate])) + self.session.send_transport_info(content) - def __fill_jingle_stanza(self, stanza, content, error, action): - """ - Add our things to session-initiate stanza - """ - self._fill_content(content) - self.sent = True - content.addChild(node=self.transport.make_transport()) + def __fill_jingle_stanza(self, stanza, content, error, action): + """ + Add our things to session-initiate stanza + """ + self._fill_content(content) + self.sent = True + content.addChild(node=self.transport.make_transport()) - def destroy(self): - self.callbacks = None - del self.session.contents[(self.creator, self.name)] - -# vim: se ts=3: + def destroy(self): + self.callbacks = None + del self.session.contents[(self.creator, self.name)] diff --git a/src/common/jingle_rtp.py b/src/common/jingle_rtp.py index ce37370f4..32d9398a9 100644 --- a/src/common/jingle_rtp.py +++ b/src/common/jingle_rtp.py @@ -29,325 +29,323 @@ from jingle_content import contents, JingleContent, JingleContentSetupException class JingleRTPContent(JingleContent): - def __init__(self, session, media, transport=None): - if transport is None: - transport = JingleTransportICEUDP() - JingleContent.__init__(self, session, transport) - self.media = media - self._dtmf_running = False - self.farsight_media = {'audio': farsight.MEDIA_TYPE_AUDIO, - 'video': farsight.MEDIA_TYPE_VIDEO}[media] + def __init__(self, session, media, transport=None): + if transport is None: + transport = JingleTransportICEUDP() + JingleContent.__init__(self, session, transport) + self.media = media + self._dtmf_running = False + self.farsight_media = {'audio': farsight.MEDIA_TYPE_AUDIO, + 'video': farsight.MEDIA_TYPE_VIDEO}[media] - self.candidates_ready = False # True when local candidates are prepared + self.candidates_ready = False # True when local candidates are prepared - self.callbacks['session-initiate'] += [self.__on_remote_codecs] - self.callbacks['content-add'] += [self.__on_remote_codecs] - self.callbacks['description-info'] += [self.__on_remote_codecs] - self.callbacks['content-accept'] += [self.__on_remote_codecs, - self.__on_content_accept] - self.callbacks['session-accept'] += [self.__on_remote_codecs, - self.__on_content_accept] - self.callbacks['session-accept-sent'] += [self.__on_content_accept] - self.callbacks['content-accept-sent'] += [self.__on_content_accept] - self.callbacks['session-terminate'] += [self.__stop] - self.callbacks['session-terminate-sent'] += [self.__stop] + self.callbacks['session-initiate'] += [self.__on_remote_codecs] + self.callbacks['content-add'] += [self.__on_remote_codecs] + self.callbacks['description-info'] += [self.__on_remote_codecs] + self.callbacks['content-accept'] += [self.__on_remote_codecs, + self.__on_content_accept] + self.callbacks['session-accept'] += [self.__on_remote_codecs, + self.__on_content_accept] + self.callbacks['session-accept-sent'] += [self.__on_content_accept] + self.callbacks['content-accept-sent'] += [self.__on_content_accept] + self.callbacks['session-terminate'] += [self.__stop] + self.callbacks['session-terminate-sent'] += [self.__stop] - def setup_stream(self): - # pipeline and bus - self.pipeline = gst.Pipeline() - bus = self.pipeline.get_bus() - bus.add_signal_watch() - bus.connect('message', self._on_gst_message) + def setup_stream(self): + # pipeline and bus + self.pipeline = gst.Pipeline() + bus = self.pipeline.get_bus() + bus.add_signal_watch() + bus.connect('message', self._on_gst_message) - # conference - self.conference = gst.element_factory_make('fsrtpconference') - self.conference.set_property('sdes-cname', self.session.ourjid) - self.pipeline.add(self.conference) - self.funnel = None + # conference + self.conference = gst.element_factory_make('fsrtpconference') + self.conference.set_property('sdes-cname', self.session.ourjid) + self.pipeline.add(self.conference) + self.funnel = None - self.p2psession = self.conference.new_session(self.farsight_media) + self.p2psession = self.conference.new_session(self.farsight_media) - participant = self.conference.new_participant(self.session.peerjid) - # FIXME: Consider a workaround, here... - # pidgin and telepathy-gabble don't follow the XEP, and it won't work - # due to bad controlling-mode - params = {'controlling-mode': self.session.weinitiate, 'debug': False} - if gajim.config.get('use_stun_server'): - stun_server = gajim.config.get('stun_server') - if not stun_server and self.session.connection._stun_servers: - stun_server = self.session.connection._stun_servers[0]['host'] - if stun_server: - try: - ip = socket.getaddrinfo(stun_server, 0, socket.AF_UNSPEC, - socket.SOCK_STREAM)[0][4][0] - except socket.gaierror, (errnum, errstr): - log.warn('Lookup of stun ip failed: %s' % errstr) - else: - params['stun-ip'] = ip + participant = self.conference.new_participant(self.session.peerjid) + # FIXME: Consider a workaround, here... + # pidgin and telepathy-gabble don't follow the XEP, and it won't work + # due to bad controlling-mode + params = {'controlling-mode': self.session.weinitiate, 'debug': False} + if gajim.config.get('use_stun_server'): + stun_server = gajim.config.get('stun_server') + if not stun_server and self.session.connection._stun_servers: + stun_server = self.session.connection._stun_servers[0]['host'] + if stun_server: + try: + ip = socket.getaddrinfo(stun_server, 0, socket.AF_UNSPEC, + socket.SOCK_STREAM)[0][4][0] + except socket.gaierror, (errnum, errstr): + log.warn('Lookup of stun ip failed: %s' % errstr) + else: + params['stun-ip'] = ip - self.p2pstream = self.p2psession.new_stream(participant, - farsight.DIRECTION_RECV, 'nice', params) + self.p2pstream = self.p2psession.new_stream(participant, + farsight.DIRECTION_RECV, 'nice', params) - def is_ready(self): - return (JingleContent.is_ready(self) and self.candidates_ready - and self.p2psession.get_property('codecs-ready')) + def is_ready(self): + return (JingleContent.is_ready(self) and self.candidates_ready + and self.p2psession.get_property('codecs-ready')) - def make_bin_from_config(self, config_key, pipeline, text): - pipeline = pipeline % gajim.config.get(config_key) - try: - bin = gst.parse_bin_from_description(pipeline, True) - return bin - except GError, error_str: - self.session.connection.dispatch('ERROR', - (_("%s configuration error") % text.capitalize(), - _("Couldn't setup %s. Check your configuration.\n\n" - "Pipeline was:\n%s\n\n" - "Error was:\n%s") % (text, pipeline, error_str))) - raise JingleContentSetupException + def make_bin_from_config(self, config_key, pipeline, text): + pipeline = pipeline % gajim.config.get(config_key) + try: + bin = gst.parse_bin_from_description(pipeline, True) + return bin + except GError, error_str: + self.session.connection.dispatch('ERROR', + (_("%s configuration error") % text.capitalize(), + _("Couldn't setup %s. Check your configuration.\n\n" + "Pipeline was:\n%s\n\n" + "Error was:\n%s") % (text, pipeline, error_str))) + raise JingleContentSetupException - def add_remote_candidates(self, candidates): - JingleContent.add_remote_candidates(self, candidates) - # FIXME: connectivity should not be etablished yet - # Instead, it should be etablished after session-accept! - if self.sent: - self.p2pstream.set_remote_candidates(candidates) + def add_remote_candidates(self, candidates): + JingleContent.add_remote_candidates(self, candidates) + # FIXME: connectivity should not be etablished yet + # Instead, it should be etablished after session-accept! + if self.sent: + self.p2pstream.set_remote_candidates(candidates) - def batch_dtmf(self, events): - """ - Send several DTMF tones - """ - if self._dtmf_running: - raise Exception # TODO: Proper exception - self._dtmf_running = True - self._start_dtmf(events.pop(0)) - gobject.timeout_add(500, self._next_dtmf, events) + def batch_dtmf(self, events): + """ + Send several DTMF tones + """ + if self._dtmf_running: + raise Exception # TODO: Proper exception + self._dtmf_running = True + self._start_dtmf(events.pop(0)) + gobject.timeout_add(500, self._next_dtmf, events) - def _next_dtmf(self, events): - self._stop_dtmf() - if events: - self._start_dtmf(events.pop(0)) - gobject.timeout_add(500, self._next_dtmf, events) - else: - self._dtmf_running = False + def _next_dtmf(self, events): + self._stop_dtmf() + if events: + self._start_dtmf(events.pop(0)) + gobject.timeout_add(500, self._next_dtmf, events) + else: + self._dtmf_running = False - def _start_dtmf(self, event): - if event in ('*', '#'): - event = {'*': farsight.DTMF_EVENT_STAR, - '#': farsight.DTMF_EVENT_POUND}[event] - else: - event = int(event) - self.p2psession.start_telephony_event(event, 2, - farsight.DTMF_METHOD_RTP_RFC4733) + def _start_dtmf(self, event): + if event in ('*', '#'): + event = {'*': farsight.DTMF_EVENT_STAR, + '#': farsight.DTMF_EVENT_POUND}[event] + else: + event = int(event) + self.p2psession.start_telephony_event(event, 2, + farsight.DTMF_METHOD_RTP_RFC4733) - def _stop_dtmf(self): - self.p2psession.stop_telephony_event(farsight.DTMF_METHOD_RTP_RFC4733) + def _stop_dtmf(self): + self.p2psession.stop_telephony_event(farsight.DTMF_METHOD_RTP_RFC4733) - def _fill_content(self, content): - content.addChild(xmpp.NS_JINGLE_RTP + ' description', - attrs={'media': self.media}, payload=self.iter_codecs()) + def _fill_content(self, content): + content.addChild(xmpp.NS_JINGLE_RTP + ' description', + attrs={'media': self.media}, payload=self.iter_codecs()) - def _setup_funnel(self): - self.funnel = gst.element_factory_make('fsfunnel') - self.pipeline.add(self.funnel) - self.funnel.set_state(gst.STATE_PLAYING) - self.sink.set_state(gst.STATE_PLAYING) - self.funnel.link(self.sink) + def _setup_funnel(self): + self.funnel = gst.element_factory_make('fsfunnel') + self.pipeline.add(self.funnel) + self.funnel.set_state(gst.STATE_PLAYING) + self.sink.set_state(gst.STATE_PLAYING) + self.funnel.link(self.sink) - def _on_src_pad_added(self, stream, pad, codec): - if not self.funnel: - self._setup_funnel() - pad.link(self.funnel.get_pad('sink%d')) + def _on_src_pad_added(self, stream, pad, codec): + if not self.funnel: + self._setup_funnel() + pad.link(self.funnel.get_pad('sink%d')) - def _on_gst_message(self, bus, message): - if message.type == gst.MESSAGE_ELEMENT: - name = message.structure.get_name() - if name == 'farsight-new-active-candidate-pair': - pass - elif name == 'farsight-recv-codecs-changed': - pass - elif name == 'farsight-codecs-changed': - if self.is_ready(): - self.session.on_session_state_changed(self) - # TODO: description-info - elif name == 'farsight-local-candidates-prepared': - self.candidates_ready = True - if self.is_ready(): - self.session.on_session_state_changed(self) - elif name == 'farsight-new-local-candidate': - candidate = message.structure['candidate'] - self.transport.candidates.append(candidate) - if self.candidates_ready: - # FIXME: Is this case even possible? - self.send_candidate(candidate) - elif name == 'farsight-component-state-changed': - state = message.structure['state'] - print message.structure['component'], state - if state == farsight.STREAM_STATE_FAILED: - reason = xmpp.Node('reason') - reason.setTag('failed-transport') - self.session._session_terminate(reason) - elif name == 'farsight-error': - print 'Farsight error #%d!' % message.structure['error-no'] - print 'Message: %s' % message.structure['error-msg'] - print 'Debug: %s' % message.structure['debug-msg'] - else: - print name + def _on_gst_message(self, bus, message): + if message.type == gst.MESSAGE_ELEMENT: + name = message.structure.get_name() + if name == 'farsight-new-active-candidate-pair': + pass + elif name == 'farsight-recv-codecs-changed': + pass + elif name == 'farsight-codecs-changed': + if self.is_ready(): + self.session.on_session_state_changed(self) + # TODO: description-info + elif name == 'farsight-local-candidates-prepared': + self.candidates_ready = True + if self.is_ready(): + self.session.on_session_state_changed(self) + elif name == 'farsight-new-local-candidate': + candidate = message.structure['candidate'] + self.transport.candidates.append(candidate) + if self.candidates_ready: + # FIXME: Is this case even possible? + self.send_candidate(candidate) + elif name == 'farsight-component-state-changed': + state = message.structure['state'] + print message.structure['component'], state + if state == farsight.STREAM_STATE_FAILED: + reason = xmpp.Node('reason') + reason.setTag('failed-transport') + self.session._session_terminate(reason) + elif name == 'farsight-error': + print 'Farsight error #%d!' % message.structure['error-no'] + print 'Message: %s' % message.structure['error-msg'] + print 'Debug: %s' % message.structure['debug-msg'] + else: + print name - def __on_content_accept(self, stanza, content, error, action): - if self.accepted: - if self.transport.remote_candidates: - self.p2pstream.set_remote_candidates(self.transport.remote_candidates) - self.transport.remote_candidates = [] - # TODO: farsight.DIRECTION_BOTH only if senders='both' - self.p2pstream.set_property('direction', farsight.DIRECTION_BOTH) - self.on_negotiated() + def __on_content_accept(self, stanza, content, error, action): + if self.accepted: + if self.transport.remote_candidates: + self.p2pstream.set_remote_candidates(self.transport.remote_candidates) + self.transport.remote_candidates = [] + # TODO: farsight.DIRECTION_BOTH only if senders='both' + self.p2pstream.set_property('direction', farsight.DIRECTION_BOTH) + self.on_negotiated() - def __on_remote_codecs(self, stanza, content, error, action): - """ - Get peer codecs from what we get from peer - """ + def __on_remote_codecs(self, stanza, content, error, action): + """ + Get peer codecs from what we get from peer + """ - codecs = [] - for codec in content.getTag('description').iterTags('payload-type'): - c = farsight.Codec(int(codec['id']), codec['name'], - self.farsight_media, int(codec['clockrate'])) - if 'channels' in codec: - c.channels = int(codec['channels']) - else: - c.channels = 1 - c.optional_params = [(str(p['name']), str(p['value'])) for p in \ - codec.iterTags('parameter')] - codecs.append(c) + codecs = [] + for codec in content.getTag('description').iterTags('payload-type'): + c = farsight.Codec(int(codec['id']), codec['name'], + self.farsight_media, int(codec['clockrate'])) + if 'channels' in codec: + c.channels = int(codec['channels']) + else: + c.channels = 1 + c.optional_params = [(str(p['name']), str(p['value'])) for p in \ + codec.iterTags('parameter')] + codecs.append(c) - if codecs: - # FIXME: Handle this case: - # glib.GError: There was no intersection between the remote codecs and - # the local ones - self.p2pstream.set_remote_codecs(codecs) + if codecs: + # FIXME: Handle this case: + # glib.GError: There was no intersection between the remote codecs and + # the local ones + self.p2pstream.set_remote_codecs(codecs) - def iter_codecs(self): - codecs = self.p2psession.get_property('codecs') - for codec in codecs: - attrs = {'name': codec.encoding_name, - 'id': codec.id, - 'channels': codec.channels} - if codec.clock_rate: - attrs['clockrate'] = codec.clock_rate - if codec.optional_params: - payload = (xmpp.Node('parameter', {'name': name, 'value': value}) - for name, value in codec.optional_params) - else: - payload = () - yield xmpp.Node('payload-type', attrs, payload) + def iter_codecs(self): + codecs = self.p2psession.get_property('codecs') + for codec in codecs: + attrs = {'name': codec.encoding_name, + 'id': codec.id, + 'channels': codec.channels} + if codec.clock_rate: + attrs['clockrate'] = codec.clock_rate + if codec.optional_params: + payload = (xmpp.Node('parameter', {'name': name, 'value': value}) + for name, value in codec.optional_params) + else: + payload = () + yield xmpp.Node('payload-type', attrs, payload) - def __stop(self, *things): - self.pipeline.set_state(gst.STATE_NULL) + def __stop(self, *things): + self.pipeline.set_state(gst.STATE_NULL) - def __del__(self): - self.__stop() + def __del__(self): + self.__stop() - def destroy(self): - JingleContent.destroy(self) - self.p2pstream.disconnect_by_func(self._on_src_pad_added) - self.pipeline.get_bus().disconnect_by_func(self._on_gst_message) + def destroy(self): + JingleContent.destroy(self) + self.p2pstream.disconnect_by_func(self._on_src_pad_added) + self.pipeline.get_bus().disconnect_by_func(self._on_gst_message) class JingleAudio(JingleRTPContent): - """ - Jingle VoIP sessions consist of audio content transported over an ICE UDP - protocol - """ + """ + Jingle VoIP sessions consist of audio content transported over an ICE UDP + protocol + """ - def __init__(self, session, transport=None): - JingleRTPContent.__init__(self, session, 'audio', transport) - self.setup_stream() + def __init__(self, session, transport=None): + JingleRTPContent.__init__(self, session, 'audio', transport) + self.setup_stream() - def set_mic_volume(self, vol): - """ - vol must be between 0 ans 1 - """ - self.mic_volume.set_property('volume', vol) + def set_mic_volume(self, vol): + """ + vol must be between 0 ans 1 + """ + self.mic_volume.set_property('volume', vol) - def set_out_volume(self, vol): - """ - vol must be between 0 ans 1 - """ - self.out_volume.set_property('volume', vol) + def set_out_volume(self, vol): + """ + vol must be between 0 ans 1 + """ + self.out_volume.set_property('volume', vol) - def setup_stream(self): - JingleRTPContent.setup_stream(self) + def setup_stream(self): + JingleRTPContent.setup_stream(self) - # Configure SPEEX - # Workaround for psi (not needed since rev - # 147aedcea39b43402fe64c533d1866a25449888a): - # place 16kHz before 8kHz, as buggy psi versions will take in - # account only the first codec + # Configure SPEEX + # Workaround for psi (not needed since rev + # 147aedcea39b43402fe64c533d1866a25449888a): + # place 16kHz before 8kHz, as buggy psi versions will take in + # account only the first codec - codecs = [farsight.Codec(farsight.CODEC_ID_ANY, 'SPEEX', - farsight.MEDIA_TYPE_AUDIO, 16000), - farsight.Codec(farsight.CODEC_ID_ANY, 'SPEEX', - farsight.MEDIA_TYPE_AUDIO, 8000)] - self.p2psession.set_codec_preferences(codecs) + codecs = [farsight.Codec(farsight.CODEC_ID_ANY, 'SPEEX', + farsight.MEDIA_TYPE_AUDIO, 16000), + farsight.Codec(farsight.CODEC_ID_ANY, 'SPEEX', + farsight.MEDIA_TYPE_AUDIO, 8000)] + self.p2psession.set_codec_preferences(codecs) - # the local parts - # TODO: Add queues? - src_bin = self.make_bin_from_config('audio_input_device', - '%s ! audioconvert', _("audio input")) + # the local parts + # TODO: Add queues? + src_bin = self.make_bin_from_config('audio_input_device', + '%s ! audioconvert', _("audio input")) - self.sink = self.make_bin_from_config('audio_output_device', - 'audioconvert ! volume name=gajim_out_vol ! %s', _("audio output")) + self.sink = self.make_bin_from_config('audio_output_device', + 'audioconvert ! volume name=gajim_out_vol ! %s', _("audio output")) - self.mic_volume = src_bin.get_by_name('gajim_vol') - self.out_volume = self.sink.get_by_name('gajim_out_vol') + self.mic_volume = src_bin.get_by_name('gajim_vol') + self.out_volume = self.sink.get_by_name('gajim_out_vol') - # link gst elements - self.pipeline.add(self.sink, src_bin) + # link gst elements + self.pipeline.add(self.sink, src_bin) - src_bin.get_pad('src').link(self.p2psession.get_property( - 'sink-pad')) - self.p2pstream.connect('src-pad-added', self._on_src_pad_added) + src_bin.get_pad('src').link(self.p2psession.get_property( + 'sink-pad')) + self.p2pstream.connect('src-pad-added', self._on_src_pad_added) - # The following is needed for farsight to process ICE requests: - self.pipeline.set_state(gst.STATE_PLAYING) + # The following is needed for farsight to process ICE requests: + self.pipeline.set_state(gst.STATE_PLAYING) class JingleVideo(JingleRTPContent): - def __init__(self, session, transport=None): - JingleRTPContent.__init__(self, session, 'video', transport) - self.setup_stream() + def __init__(self, session, transport=None): + JingleRTPContent.__init__(self, session, 'video', transport) + self.setup_stream() - def setup_stream(self): - # TODO: Everything is not working properly: - # sometimes, one window won't show up, - # sometimes it'll freeze... - JingleRTPContent.setup_stream(self) + def setup_stream(self): + # TODO: Everything is not working properly: + # sometimes, one window won't show up, + # sometimes it'll freeze... + JingleRTPContent.setup_stream(self) - # the local parts - src_bin = self.make_bin_from_config('video_input_device', - '%s ! videoscale ! ffmpegcolorspace', _("video input")) - #caps = gst.element_factory_make('capsfilter') - #caps.set_property('caps', gst.caps_from_string('video/x-raw-yuv, width=320, height=240')) + # the local parts + src_bin = self.make_bin_from_config('video_input_device', + '%s ! videoscale ! ffmpegcolorspace', _("video input")) + #caps = gst.element_factory_make('capsfilter') + #caps.set_property('caps', gst.caps_from_string('video/x-raw-yuv, width=320, height=240')) - self.pipeline.add(src_bin)#, caps) - #src_bin.link(caps) + self.pipeline.add(src_bin)#, caps) + #src_bin.link(caps) - self.sink = self.make_bin_from_config('video_output_device', - 'videoscale ! ffmpegcolorspace ! %s', _("video output")) - self.pipeline.add(self.sink) + self.sink = self.make_bin_from_config('video_output_device', + 'videoscale ! ffmpegcolorspace ! %s', _("video output")) + self.pipeline.add(self.sink) - src_bin.get_pad('src').link(self.p2psession.get_property('sink-pad')) - self.p2pstream.connect('src-pad-added', self._on_src_pad_added) + src_bin.get_pad('src').link(self.p2psession.get_property('sink-pad')) + self.p2pstream.connect('src-pad-added', self._on_src_pad_added) - # The following is needed for farsight to process ICE requests: - self.pipeline.set_state(gst.STATE_PLAYING) + # The following is needed for farsight to process ICE requests: + self.pipeline.set_state(gst.STATE_PLAYING) def get_content(desc): - if desc['media'] == 'audio': - return JingleAudio - elif desc['media'] == 'video': - return JingleVideo + if desc['media'] == 'audio': + return JingleAudio + elif desc['media'] == 'video': + return JingleVideo contents[xmpp.NS_JINGLE_RTP] = get_content - -# vim: se ts=3: diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index 3e2392739..fd5fec1ef 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -33,624 +33,622 @@ from jingle_content import get_jingle_content, JingleContentSetupException # FIXME: Move it to JingleSession.States? class JingleStates(object): - """ - States in which jingle session may exist - """ - ended = 0 - pending = 1 - active = 2 + """ + States in which jingle session may exist + """ + ended = 0 + pending = 1 + active = 2 class OutOfOrder(Exception): - """ - Exception that should be raised when an action is received when in the wrong - state - """ + """ + Exception that should be raised when an action is received when in the wrong + state + """ class TieBreak(Exception): - """ - Exception that should be raised in case of a tie, when we overrule the other - action - """ + """ + Exception that should be raised in case of a tie, when we overrule the other + action + """ class JingleSession(object): - """ - This represents one jingle session, that is, one or more content types - negotiated between an initiator and a responder. - """ - - def __init__(self, con, weinitiate, jid, sid=None): - """ - con -- connection object, - weinitiate -- boolean, are we the initiator? - jid - jid of the other entity - """ - self.contents = {} # negotiated contents - self.connection = con # connection to use - # our full jid - #FIXME: Get rid of gajim here? - self.ourjid = gajim.get_jid_from_account(self.connection.name) + '/' + \ - con.server_resource - self.peerjid = jid # jid we connect to - # jid we use as the initiator - self.initiator = weinitiate and self.ourjid or self.peerjid - # jid we use as the responder - self.responder = weinitiate and self.peerjid or self.ourjid - # are we an initiator? - self.weinitiate = weinitiate - # what state is session in? (one from JingleStates) - self.state = JingleStates.ended - if not sid: - sid = con.connection.getAnID() - self.sid = sid # sessionid - - self.accepted = True # is this session accepted by user - - # callbacks to call on proper contents - # use .prepend() to add new callbacks, especially when you're going - # to send error instead of ack - self.callbacks = { - 'content-accept': [self.__on_content_accept, self.__broadcast, - self.__ack], - 'content-add': [self.__on_content_add, self.__broadcast, - self.__ack], #TODO - 'content-modify': [self.__ack], #TODO - 'content-reject': [self.__ack, self.__on_content_remove], #TODO - 'content-remove': [self.__ack, self.__on_content_remove], - 'description-info': [self.__broadcast, self.__ack], #TODO - 'security-info': [self.__ack], #TODO - 'session-accept': [self.__on_session_accept, self.__on_content_accept, - self.__broadcast, self.__ack], - 'session-info': [self.__broadcast, self.__on_session_info, self.__ack], - 'session-initiate': [self.__on_session_initiate, self.__broadcast, - self.__ack], - 'session-terminate': [self.__on_session_terminate, self.__broadcast_all, - self.__ack], - 'transport-info': [self.__broadcast, self.__ack], - 'transport-replace': [self.__broadcast, self.__on_transport_replace], #TODO - 'transport-accept': [self.__ack], #TODO - 'transport-reject': [self.__ack], #TODO - 'iq-result': [], - 'iq-error': [self.__on_error], - } - - def approve_session(self): - """ - Called when user accepts session in UI (when we aren't the initiator) - """ - self.accept_session() - - def decline_session(self): - """ - Called when user declines session in UI (when we aren't the initiator) - """ - reason = xmpp.Node('reason') - reason.addChild('decline') - self._session_terminate(reason) - - def approve_content(self, media): - content = self.get_content(media) - if content: - content.accepted = True - self.on_session_state_changed(content) - - def reject_content(self, media): - content = self.get_content(media) - if content: - if self.state == JingleStates.active: - self.__content_reject(content) - content.destroy() - self.on_session_state_changed() - - def end_session(self): - """ - Called when user stops or cancel session in UI - """ - reason = xmpp.Node('reason') - if self.state == JingleStates.active: - reason.addChild('success') - else: - reason.addChild('cancel') - self._session_terminate(reason) - - def get_content(self, media=None): - if media is None: - return - - for content in self.contents.values(): - if content.media == media: - return content - - def add_content(self, name, content, creator='we'): - """ - Add new content to session. If the session is active, this will send - proper stanza to update session - - Creator must be one of ('we', 'peer', 'initiator', 'responder') - """ - assert creator in ('we', 'peer', 'initiator', 'responder') - - if (creator == 'we' and self.weinitiate) or (creator == 'peer' and \ - not self.weinitiate): - creator = 'initiator' - elif (creator == 'peer' and self.weinitiate) or (creator == 'we' and \ - not self.weinitiate): - creator = 'responder' - content.creator = creator - content.name = name - self.contents[(creator, name)] = content - - if (creator == 'initiator') == self.weinitiate: - # The content is from us, accept it - content.accepted = True - - def remove_content(self, creator, name): - """ - We do not need this now - """ - #TODO: - if (creator, name) in self.contents: - content = self.contents[(creator, name)] - if len(self.contents) > 1: - self.__content_remove(content) - self.contents[(creator, name)].destroy() - if len(self.contents) == 0: - self.end_session() - - def modify_content(self, creator, name, *someother): - """ - We do not need this now - """ - pass - - def on_session_state_changed(self, content=None): - if self.state == JingleStates.ended: - # Session not yet started, only one action possible: session-initiate - if self.is_ready() and self.weinitiate: - self.__session_initiate() - elif self.state == JingleStates.pending: - # We can either send a session-accept or a content-add - if self.is_ready() and not self.weinitiate: - self.__session_accept() - elif content and (content.creator == 'initiator') == self.weinitiate: - self.__content_add(content) - elif content and self.weinitiate: - self.__content_accept(content) - elif self.state == JingleStates.active: - # We can either send a content-add or a content-accept - if not content: - return - if (content.creator == 'initiator') == self.weinitiate: - # We initiated this content. It's a pending content-add. - self.__content_add(content) - else: - # The other side created this content, we accept it. - self.__content_accept(content) - - def is_ready(self): - """ - Return True when all codecs and candidates are ready (for all contents) - """ - return (all((content.is_ready() for content in self.contents.itervalues())) - and self.accepted) - - def accept_session(self): - """ - Mark the session as accepted - """ - self.accepted = True - self.on_session_state_changed() - - def start_session(self): - """ - Mark the session as ready to be started - """ - self.accepted = True - self.on_session_state_changed() - - def send_session_info(self): - pass - - def send_content_accept(self, content): - assert self.state != JingleStates.ended - stanza, jingle = self.__make_jingle('content-accept') - jingle.addChild(node=content) - self.connection.connection.send(stanza) - - def send_transport_info(self, content): - assert self.state != JingleStates.ended - stanza, jingle = self.__make_jingle('transport-info') - jingle.addChild(node=content) - self.connection.connection.send(stanza) - - def on_stanza(self, stanza): - """ - A callback for ConnectionJingle. It gets stanza, then tries to send it to - all internally registered callbacks. First one to raise - xmpp.NodeProcessed breaks function - """ - jingle = stanza.getTag('jingle') - error = stanza.getTag('error') - if error: - # it's an iq-error stanza - action = 'iq-error' - elif jingle: - # it's a jingle action - action = jingle.getAttr('action') - if action not in self.callbacks: - self.__send_error(stanza, 'bad_request') - return - # FIXME: If we aren't initiated and it's not a session-initiate... - if action != 'session-initiate' and self.state == JingleStates.ended: - self.__send_error(stanza, 'item-not-found', 'unknown-session') - return - else: - # it's an iq-result (ack) stanza - action = 'iq-result' - - callables = self.callbacks[action] - - try: - for callable in callables: - callable(stanza=stanza, jingle=jingle, error=error, action=action) - except xmpp.NodeProcessed: - pass - except TieBreak: - self.__send_error(stanza, 'conflict', 'tiebreak') - except OutOfOrder: - # FIXME - self.__send_error(stanza, 'unexpected-request', 'out-of-order') - - def __ack(self, stanza, jingle, error, action): - """ - Default callback for action stanzas -- simple ack and stop processing - """ - response = stanza.buildReply('result') - self.connection.connection.send(response) - - def __on_error(self, stanza, jingle, error, action): - # FIXME - text = error.getTagData('text') - jingle_error = None - xmpp_error = None - for child in error.getChildren(): - if child.getNamespace() == xmpp.NS_JINGLE_ERRORS: - jingle_error = child.getName() - elif child.getNamespace() == xmpp.NS_STANZAS: - xmpp_error = child.getName() - self.__dispatch_error(xmpp_error, jingle_error, text) - # FIXME: Not sure when we would want to do that... - if xmpp_error == 'item-not-found': - self.connection.delete_jingle_session(self.peerjid, self.sid) - - def __on_transport_replace(self, stanza, jingle, error, action): - for content in jingle.iterTags('content'): - creator = content['creator'] - name = content['name'] - if (creator, name) in self.contents: - transport_ns = content.getTag('transport').getNamespace() - if transport_ns == xmpp.JINGLE_ICE_UDP: - # FIXME: We don't manage anything else than ICE-UDP now... - # What was the previous transport?!? - # Anyway, content's transport is not modifiable yet - pass - else: - stanza, jingle = self.__make_jingle('transport-reject') - content = jingle.setTag('content', attrs={'creator': creator, - 'name': name}) - content.setTag('transport', namespace=transport_ns) - self.connection.connection.send(stanza) - raise xmpp.NodeProcessed - else: - # FIXME: This ressource is unknown to us, what should we do? - # For now, reject the transport - stanza, jingle = self.__make_jingle('transport-reject') - c = jingle.setTag('content', attrs={'creator': creator, - 'name': name}) - c.setTag('transport', namespace=transport_ns) - self.connection.connection.send(stanza) - raise xmpp.NodeProcessed - - def __on_session_info(self, stanza, jingle, error, action): - # TODO: ringing, active, (un)hold, (un)mute - payload = jingle.getPayload() - if payload: - self.__send_error(stanza, 'feature-not-implemented', 'unsupported-info') - raise xmpp.NodeProcessed - - def __on_content_remove(self, stanza, jingle, error, action): - for content in jingle.iterTags('content'): - creator = content['creator'] - name = content['name'] - if (creator, name) in self.contents: - content = self.contents[(creator, name)] - # TODO: this will fail if content is not an RTP content - self.connection.dispatch('JINGLE_DISCONNECTED', - (self.peerjid, self.sid, content.media, 'removed')) - content.destroy() - if not self.contents: - reason = xmpp.Node('reason') - reason.setTag('success') - self._session_terminate(reason) - - def __on_session_accept(self, stanza, jingle, error, action): - # FIXME - if self.state != JingleStates.pending: - raise OutOfOrder - self.state = JingleStates.active - - def __on_content_accept(self, stanza, jingle, error, action): - """ - Called when we get content-accept stanza or equivalent one (like - session-accept) - """ - # check which contents are accepted - for content in jingle.iterTags('content'): - creator = content['creator'] - # TODO - name = content['name'] - - def __on_content_add(self, stanza, jingle, error, action): - if self.state == JingleStates.ended: - raise OutOfOrder - - parse_result = self.__parse_contents(jingle) - contents = parse_result[0] - rejected_contents = parse_result[1] - - for name, creator in rejected_contents: - # TODO - content = JingleContent() - self.add_content(name, content, creator) - self.__content_reject(content) - self.contents[(content.creator, content.name)].destroy() - - self.connection.dispatch('JINGLE_INCOMING', (self.peerjid, self.sid, - contents)) - - def __on_session_initiate(self, stanza, jingle, error, action): - """ - We got a jingle session request from other entity, therefore we are the - receiver... Unpack the data, inform the user - """ - if self.state != JingleStates.ended: - raise OutOfOrder - - self.initiator = jingle['initiator'] - self.responder = self.ourjid - self.peerjid = self.initiator - self.accepted = False # user did not accept this session yet - - # TODO: If the initiator is unknown to the receiver (e.g., via presence - # subscription) and the receiver has a policy of not communicating via - # Jingle with unknown entities, it SHOULD return a - # error. - - # Lets check what kind of jingle session does the peer want - contents, contents_rejected, reason = self.__parse_contents(jingle) - - # If there's no content we understand... - if not contents: - # TODO: http://xmpp.org/extensions/xep-0166.html#session-terminate - reason = xmpp.Node('reason') - reason.setTag(reason) - self.__ack(stanza, jingle, error, action) - self._session_terminate(reason) - raise xmpp.NodeProcessed - - self.state = JingleStates.pending - - # Send event about starting a session - self.connection.dispatch('JINGLE_INCOMING', (self.peerjid, self.sid, - contents)) - - def __broadcast(self, stanza, jingle, error, action): - """ - Broadcast the stanza contents to proper content handlers - """ - for content in jingle.iterTags('content'): - name = content['name'] - creator = content['creator'] - cn = self.contents[(creator, name)] - cn.on_stanza(stanza, content, error, action) - - def __on_session_terminate(self, stanza, jingle, error, action): - self.connection.delete_jingle_session(self.peerjid, self.sid) - reason, text = self.__reason_from_stanza(jingle) - if reason not in ('success', 'cancel', 'decline'): - self.__dispatch_error(reason, reason, text) - if text: - text = '%s (%s)' % (reason, text) - else: - # TODO - text = reason - self.connection.dispatch('JINGLE_DISCONNECTED', - (self.peerjid, self.sid, None, text)) - - def __broadcast_all(self, stanza, jingle, error, action): - """ - Broadcast the stanza to all content handlers - """ - for content in self.contents.itervalues(): - content.on_stanza(stanza, None, error, action) - - def __parse_contents(self, jingle): - # TODO: Needs some reworking - contents = [] - contents_rejected = [] - reasons = set() - - for element in jingle.iterTags('content'): - transport = get_jingle_transport(element.getTag('transport')) - content_type = get_jingle_content(element.getTag('description')) - if content_type: - try: - if transport: - content = content_type(self, transport) - self.add_content(element['name'], - content, 'peer') - contents.append((content.media,)) - else: - reasons.add('unsupported-transports') - contents_rejected.append((element['name'], 'peer')) - except JingleContentSetupException: - reasons.add('failed-application') - else: - contents_rejected.append((element['name'], 'peer')) - failed.add('unsupported-applications') - - failure_reason = None - - # Store the first reason of failure - for reason in ('failed-application', 'unsupported-transports', - 'unsupported-applications'): - if reason in reasons: - failure_reason = reason - break - - return (contents, contents_rejected, failure_reason) - - def __dispatch_error(self, error, jingle_error=None, text=None): - if jingle_error: - error = jingle_error - if text: - text = '%s (%s)' % (error, text) - else: - text = error - self.connection.dispatch('JINGLE_ERROR', (self.peerjid, self.sid, text)) - - def __reason_from_stanza(self, stanza): - reason = 'success' - reasons = ['success', 'busy', 'cancel', 'connectivity-error', - 'decline', 'expired', 'failed-application', 'failed-transport', - 'general-error', 'gone', 'incompatible-parameters', 'media-error', - 'security-error', 'timeout', 'unsupported-applications', - 'unsupported-transports'] - tag = stanza.getTag('reason') - if tag: - text = tag.getTagData('text') - for r in reasons: - if tag.getTag(r): - reason = r - break - return (reason, text) - - def __make_jingle(self, action): - stanza = xmpp.Iq(typ='set', to=xmpp.JID(self.peerjid)) - attrs = {'action': action, - 'sid': self.sid} - if action == 'session-initiate': - attrs['initiator'] = self.initiator - elif action == 'session-accept': - attrs['responder'] = self.responder - jingle = stanza.addChild('jingle', attrs=attrs, namespace=xmpp.NS_JINGLE) - return stanza, jingle - - def __send_error(self, stanza, error, jingle_error=None, text=None): - err = xmpp.Error(stanza, '%s %s' % (xmpp.NS_STANZAS, error)) - if jingle_error: - err.setTag(jingle_error, namespace=xmpp.NS_JINGLE_ERRORS) - if text: - err.setTagData('text', text) - self.connection.connection.send(err) - self.__dispatch_error(error, jingle_error, text) - - def __append_content(self, jingle, content): - """ - Append element to element, with (full=True) or - without (full=False) children - """ - jingle.addChild('content', - attrs={'name': content.name, 'creator': content.creator}) - - def __append_contents(self, jingle): - """ - Append all elements to - """ - # TODO: integrate with __appendContent? - # TODO: parameters 'name', 'content'? - for content in self.contents.values(): - self.__append_content(jingle, content) - - def __session_initiate(self): - assert self.state == JingleStates.ended - stanza, jingle = self.__make_jingle('session-initiate') - self.__append_contents(jingle) - self.__broadcast(stanza, jingle, None, 'session-initiate-sent') - self.connection.connection.send(stanza) - self.state = JingleStates.pending - - def __session_accept(self): - assert self.state == JingleStates.pending - stanza, jingle = self.__make_jingle('session-accept') - self.__append_contents(jingle) - self.__broadcast(stanza, jingle, None, 'session-accept-sent') - self.connection.connection.send(stanza) - self.state = JingleStates.active - - def __session_info(self, payload=None): - assert self.state != JingleStates.ended - stanza, jingle = self.__make_jingle('session-info') - if payload: - jingle.addChild(node=payload) - self.connection.connection.send(stanza) - - def _session_terminate(self, reason=None): - assert self.state != JingleStates.ended - stanza, jingle = self.__make_jingle('session-terminate') - if reason is not None: - jingle.addChild(node=reason) - self.__broadcast_all(stanza, jingle, None, 'session-terminate-sent') - self.connection.connection.send(stanza) - reason, text = self.__reason_from_stanza(jingle) - if reason not in ('success', 'cancel', 'decline'): - self.__dispatch_error(reason, reason, text) - if text: - text = '%s (%s)' % (reason, text) - else: - text = reason - self.connection.delete_jingle_session(self.peerjid, self.sid) - self.connection.dispatch('JINGLE_DISCONNECTED', - (self.peerjid, self.sid, None, text)) - - def __content_add(self, content): - # TODO: test - assert self.state != JingleStates.ended - stanza, jingle = self.__make_jingle('content-add') - self.__append_content(jingle, content) - self.__broadcast(stanza, jingle, None, 'content-add-sent') - self.connection.connection.send(stanza) - - def __content_accept(self, content): - # TODO: test - assert self.state != JingleStates.ended - stanza, jingle = self.__make_jingle('content-accept') - self.__append_content(jingle, content) - self.__broadcast(stanza, jingle, None, 'content-accept-sent') - self.connection.connection.send(stanza) - - def __content_reject(self, content): - assert self.state != JingleStates.ended - stanza, jingle = self.__make_jingle('content-reject') - self.__append_content(jingle, content) - self.connection.connection.send(stanza) - # TODO: this will fail if content is not an RTP content - self.connection.dispatch('JINGLE_DISCONNECTED', - (self.peerjid, self.sid, content.media, 'rejected')) - - def __content_modify(self): - assert self.state != JingleStates.ended - - def __content_remove(self, content): - assert self.state != JingleStates.ended - stanza, jingle = self.__make_jingle('content-remove') - self.__append_content(jingle, content) - self.connection.connection.send(stanza) - # TODO: this will fail if content is not an RTP content - self.connection.dispatch('JINGLE_DISCONNECTED', - (self.peerjid, self.sid, content.media, 'removed')) - - def content_negotiated(self, media): - self.connection.dispatch('JINGLE_CONNECTED', (self.peerjid, self.sid, - media)) - -# vim: se ts=3: + """ + This represents one jingle session, that is, one or more content types + negotiated between an initiator and a responder. + """ + + def __init__(self, con, weinitiate, jid, sid=None): + """ + con -- connection object, + weinitiate -- boolean, are we the initiator? + jid - jid of the other entity + """ + self.contents = {} # negotiated contents + self.connection = con # connection to use + # our full jid + #FIXME: Get rid of gajim here? + self.ourjid = gajim.get_jid_from_account(self.connection.name) + '/' + \ + con.server_resource + self.peerjid = jid # jid we connect to + # jid we use as the initiator + self.initiator = weinitiate and self.ourjid or self.peerjid + # jid we use as the responder + self.responder = weinitiate and self.peerjid or self.ourjid + # are we an initiator? + self.weinitiate = weinitiate + # what state is session in? (one from JingleStates) + self.state = JingleStates.ended + if not sid: + sid = con.connection.getAnID() + self.sid = sid # sessionid + + self.accepted = True # is this session accepted by user + + # callbacks to call on proper contents + # use .prepend() to add new callbacks, especially when you're going + # to send error instead of ack + self.callbacks = { + 'content-accept': [self.__on_content_accept, self.__broadcast, + self.__ack], + 'content-add': [self.__on_content_add, self.__broadcast, + self.__ack], #TODO + 'content-modify': [self.__ack], #TODO + 'content-reject': [self.__ack, self.__on_content_remove], #TODO + 'content-remove': [self.__ack, self.__on_content_remove], + 'description-info': [self.__broadcast, self.__ack], #TODO + 'security-info': [self.__ack], #TODO + 'session-accept': [self.__on_session_accept, self.__on_content_accept, + self.__broadcast, self.__ack], + 'session-info': [self.__broadcast, self.__on_session_info, self.__ack], + 'session-initiate': [self.__on_session_initiate, self.__broadcast, + self.__ack], + 'session-terminate': [self.__on_session_terminate, self.__broadcast_all, + self.__ack], + 'transport-info': [self.__broadcast, self.__ack], + 'transport-replace': [self.__broadcast, self.__on_transport_replace], #TODO + 'transport-accept': [self.__ack], #TODO + 'transport-reject': [self.__ack], #TODO + 'iq-result': [], + 'iq-error': [self.__on_error], + } + + def approve_session(self): + """ + Called when user accepts session in UI (when we aren't the initiator) + """ + self.accept_session() + + def decline_session(self): + """ + Called when user declines session in UI (when we aren't the initiator) + """ + reason = xmpp.Node('reason') + reason.addChild('decline') + self._session_terminate(reason) + + def approve_content(self, media): + content = self.get_content(media) + if content: + content.accepted = True + self.on_session_state_changed(content) + + def reject_content(self, media): + content = self.get_content(media) + if content: + if self.state == JingleStates.active: + self.__content_reject(content) + content.destroy() + self.on_session_state_changed() + + def end_session(self): + """ + Called when user stops or cancel session in UI + """ + reason = xmpp.Node('reason') + if self.state == JingleStates.active: + reason.addChild('success') + else: + reason.addChild('cancel') + self._session_terminate(reason) + + def get_content(self, media=None): + if media is None: + return + + for content in self.contents.values(): + if content.media == media: + return content + + def add_content(self, name, content, creator='we'): + """ + Add new content to session. If the session is active, this will send + proper stanza to update session + + Creator must be one of ('we', 'peer', 'initiator', 'responder') + """ + assert creator in ('we', 'peer', 'initiator', 'responder') + + if (creator == 'we' and self.weinitiate) or (creator == 'peer' and \ + not self.weinitiate): + creator = 'initiator' + elif (creator == 'peer' and self.weinitiate) or (creator == 'we' and \ + not self.weinitiate): + creator = 'responder' + content.creator = creator + content.name = name + self.contents[(creator, name)] = content + + if (creator == 'initiator') == self.weinitiate: + # The content is from us, accept it + content.accepted = True + + def remove_content(self, creator, name): + """ + We do not need this now + """ + #TODO: + if (creator, name) in self.contents: + content = self.contents[(creator, name)] + if len(self.contents) > 1: + self.__content_remove(content) + self.contents[(creator, name)].destroy() + if len(self.contents) == 0: + self.end_session() + + def modify_content(self, creator, name, *someother): + """ + We do not need this now + """ + pass + + def on_session_state_changed(self, content=None): + if self.state == JingleStates.ended: + # Session not yet started, only one action possible: session-initiate + if self.is_ready() and self.weinitiate: + self.__session_initiate() + elif self.state == JingleStates.pending: + # We can either send a session-accept or a content-add + if self.is_ready() and not self.weinitiate: + self.__session_accept() + elif content and (content.creator == 'initiator') == self.weinitiate: + self.__content_add(content) + elif content and self.weinitiate: + self.__content_accept(content) + elif self.state == JingleStates.active: + # We can either send a content-add or a content-accept + if not content: + return + if (content.creator == 'initiator') == self.weinitiate: + # We initiated this content. It's a pending content-add. + self.__content_add(content) + else: + # The other side created this content, we accept it. + self.__content_accept(content) + + def is_ready(self): + """ + Return True when all codecs and candidates are ready (for all contents) + """ + return (all((content.is_ready() for content in self.contents.itervalues())) + and self.accepted) + + def accept_session(self): + """ + Mark the session as accepted + """ + self.accepted = True + self.on_session_state_changed() + + def start_session(self): + """ + Mark the session as ready to be started + """ + self.accepted = True + self.on_session_state_changed() + + def send_session_info(self): + pass + + def send_content_accept(self, content): + assert self.state != JingleStates.ended + stanza, jingle = self.__make_jingle('content-accept') + jingle.addChild(node=content) + self.connection.connection.send(stanza) + + def send_transport_info(self, content): + assert self.state != JingleStates.ended + stanza, jingle = self.__make_jingle('transport-info') + jingle.addChild(node=content) + self.connection.connection.send(stanza) + + def on_stanza(self, stanza): + """ + A callback for ConnectionJingle. It gets stanza, then tries to send it to + all internally registered callbacks. First one to raise + xmpp.NodeProcessed breaks function + """ + jingle = stanza.getTag('jingle') + error = stanza.getTag('error') + if error: + # it's an iq-error stanza + action = 'iq-error' + elif jingle: + # it's a jingle action + action = jingle.getAttr('action') + if action not in self.callbacks: + self.__send_error(stanza, 'bad_request') + return + # FIXME: If we aren't initiated and it's not a session-initiate... + if action != 'session-initiate' and self.state == JingleStates.ended: + self.__send_error(stanza, 'item-not-found', 'unknown-session') + return + else: + # it's an iq-result (ack) stanza + action = 'iq-result' + + callables = self.callbacks[action] + + try: + for callable in callables: + callable(stanza=stanza, jingle=jingle, error=error, action=action) + except xmpp.NodeProcessed: + pass + except TieBreak: + self.__send_error(stanza, 'conflict', 'tiebreak') + except OutOfOrder: + # FIXME + self.__send_error(stanza, 'unexpected-request', 'out-of-order') + + def __ack(self, stanza, jingle, error, action): + """ + Default callback for action stanzas -- simple ack and stop processing + """ + response = stanza.buildReply('result') + self.connection.connection.send(response) + + def __on_error(self, stanza, jingle, error, action): + # FIXME + text = error.getTagData('text') + jingle_error = None + xmpp_error = None + for child in error.getChildren(): + if child.getNamespace() == xmpp.NS_JINGLE_ERRORS: + jingle_error = child.getName() + elif child.getNamespace() == xmpp.NS_STANZAS: + xmpp_error = child.getName() + self.__dispatch_error(xmpp_error, jingle_error, text) + # FIXME: Not sure when we would want to do that... + if xmpp_error == 'item-not-found': + self.connection.delete_jingle_session(self.peerjid, self.sid) + + def __on_transport_replace(self, stanza, jingle, error, action): + for content in jingle.iterTags('content'): + creator = content['creator'] + name = content['name'] + if (creator, name) in self.contents: + transport_ns = content.getTag('transport').getNamespace() + if transport_ns == xmpp.JINGLE_ICE_UDP: + # FIXME: We don't manage anything else than ICE-UDP now... + # What was the previous transport?!? + # Anyway, content's transport is not modifiable yet + pass + else: + stanza, jingle = self.__make_jingle('transport-reject') + content = jingle.setTag('content', attrs={'creator': creator, + 'name': name}) + content.setTag('transport', namespace=transport_ns) + self.connection.connection.send(stanza) + raise xmpp.NodeProcessed + else: + # FIXME: This ressource is unknown to us, what should we do? + # For now, reject the transport + stanza, jingle = self.__make_jingle('transport-reject') + c = jingle.setTag('content', attrs={'creator': creator, + 'name': name}) + c.setTag('transport', namespace=transport_ns) + self.connection.connection.send(stanza) + raise xmpp.NodeProcessed + + def __on_session_info(self, stanza, jingle, error, action): + # TODO: ringing, active, (un)hold, (un)mute + payload = jingle.getPayload() + if payload: + self.__send_error(stanza, 'feature-not-implemented', 'unsupported-info') + raise xmpp.NodeProcessed + + def __on_content_remove(self, stanza, jingle, error, action): + for content in jingle.iterTags('content'): + creator = content['creator'] + name = content['name'] + if (creator, name) in self.contents: + content = self.contents[(creator, name)] + # TODO: this will fail if content is not an RTP content + self.connection.dispatch('JINGLE_DISCONNECTED', + (self.peerjid, self.sid, content.media, 'removed')) + content.destroy() + if not self.contents: + reason = xmpp.Node('reason') + reason.setTag('success') + self._session_terminate(reason) + + def __on_session_accept(self, stanza, jingle, error, action): + # FIXME + if self.state != JingleStates.pending: + raise OutOfOrder + self.state = JingleStates.active + + def __on_content_accept(self, stanza, jingle, error, action): + """ + Called when we get content-accept stanza or equivalent one (like + session-accept) + """ + # check which contents are accepted + for content in jingle.iterTags('content'): + creator = content['creator'] + # TODO + name = content['name'] + + def __on_content_add(self, stanza, jingle, error, action): + if self.state == JingleStates.ended: + raise OutOfOrder + + parse_result = self.__parse_contents(jingle) + contents = parse_result[0] + rejected_contents = parse_result[1] + + for name, creator in rejected_contents: + # TODO + content = JingleContent() + self.add_content(name, content, creator) + self.__content_reject(content) + self.contents[(content.creator, content.name)].destroy() + + self.connection.dispatch('JINGLE_INCOMING', (self.peerjid, self.sid, + contents)) + + def __on_session_initiate(self, stanza, jingle, error, action): + """ + We got a jingle session request from other entity, therefore we are the + receiver... Unpack the data, inform the user + """ + if self.state != JingleStates.ended: + raise OutOfOrder + + self.initiator = jingle['initiator'] + self.responder = self.ourjid + self.peerjid = self.initiator + self.accepted = False # user did not accept this session yet + + # TODO: If the initiator is unknown to the receiver (e.g., via presence + # subscription) and the receiver has a policy of not communicating via + # Jingle with unknown entities, it SHOULD return a + # error. + + # Lets check what kind of jingle session does the peer want + contents, contents_rejected, reason = self.__parse_contents(jingle) + + # If there's no content we understand... + if not contents: + # TODO: http://xmpp.org/extensions/xep-0166.html#session-terminate + reason = xmpp.Node('reason') + reason.setTag(reason) + self.__ack(stanza, jingle, error, action) + self._session_terminate(reason) + raise xmpp.NodeProcessed + + self.state = JingleStates.pending + + # Send event about starting a session + self.connection.dispatch('JINGLE_INCOMING', (self.peerjid, self.sid, + contents)) + + def __broadcast(self, stanza, jingle, error, action): + """ + Broadcast the stanza contents to proper content handlers + """ + for content in jingle.iterTags('content'): + name = content['name'] + creator = content['creator'] + cn = self.contents[(creator, name)] + cn.on_stanza(stanza, content, error, action) + + def __on_session_terminate(self, stanza, jingle, error, action): + self.connection.delete_jingle_session(self.peerjid, self.sid) + reason, text = self.__reason_from_stanza(jingle) + if reason not in ('success', 'cancel', 'decline'): + self.__dispatch_error(reason, reason, text) + if text: + text = '%s (%s)' % (reason, text) + else: + # TODO + text = reason + self.connection.dispatch('JINGLE_DISCONNECTED', + (self.peerjid, self.sid, None, text)) + + def __broadcast_all(self, stanza, jingle, error, action): + """ + Broadcast the stanza to all content handlers + """ + for content in self.contents.itervalues(): + content.on_stanza(stanza, None, error, action) + + def __parse_contents(self, jingle): + # TODO: Needs some reworking + contents = [] + contents_rejected = [] + reasons = set() + + for element in jingle.iterTags('content'): + transport = get_jingle_transport(element.getTag('transport')) + content_type = get_jingle_content(element.getTag('description')) + if content_type: + try: + if transport: + content = content_type(self, transport) + self.add_content(element['name'], + content, 'peer') + contents.append((content.media,)) + else: + reasons.add('unsupported-transports') + contents_rejected.append((element['name'], 'peer')) + except JingleContentSetupException: + reasons.add('failed-application') + else: + contents_rejected.append((element['name'], 'peer')) + failed.add('unsupported-applications') + + failure_reason = None + + # Store the first reason of failure + for reason in ('failed-application', 'unsupported-transports', + 'unsupported-applications'): + if reason in reasons: + failure_reason = reason + break + + return (contents, contents_rejected, failure_reason) + + def __dispatch_error(self, error, jingle_error=None, text=None): + if jingle_error: + error = jingle_error + if text: + text = '%s (%s)' % (error, text) + else: + text = error + self.connection.dispatch('JINGLE_ERROR', (self.peerjid, self.sid, text)) + + def __reason_from_stanza(self, stanza): + reason = 'success' + reasons = ['success', 'busy', 'cancel', 'connectivity-error', + 'decline', 'expired', 'failed-application', 'failed-transport', + 'general-error', 'gone', 'incompatible-parameters', 'media-error', + 'security-error', 'timeout', 'unsupported-applications', + 'unsupported-transports'] + tag = stanza.getTag('reason') + if tag: + text = tag.getTagData('text') + for r in reasons: + if tag.getTag(r): + reason = r + break + return (reason, text) + + def __make_jingle(self, action): + stanza = xmpp.Iq(typ='set', to=xmpp.JID(self.peerjid)) + attrs = {'action': action, + 'sid': self.sid} + if action == 'session-initiate': + attrs['initiator'] = self.initiator + elif action == 'session-accept': + attrs['responder'] = self.responder + jingle = stanza.addChild('jingle', attrs=attrs, namespace=xmpp.NS_JINGLE) + return stanza, jingle + + def __send_error(self, stanza, error, jingle_error=None, text=None): + err = xmpp.Error(stanza, '%s %s' % (xmpp.NS_STANZAS, error)) + if jingle_error: + err.setTag(jingle_error, namespace=xmpp.NS_JINGLE_ERRORS) + if text: + err.setTagData('text', text) + self.connection.connection.send(err) + self.__dispatch_error(error, jingle_error, text) + + def __append_content(self, jingle, content): + """ + Append element to element, with (full=True) or + without (full=False) children + """ + jingle.addChild('content', + attrs={'name': content.name, 'creator': content.creator}) + + def __append_contents(self, jingle): + """ + Append all elements to + """ + # TODO: integrate with __appendContent? + # TODO: parameters 'name', 'content'? + for content in self.contents.values(): + self.__append_content(jingle, content) + + def __session_initiate(self): + assert self.state == JingleStates.ended + stanza, jingle = self.__make_jingle('session-initiate') + self.__append_contents(jingle) + self.__broadcast(stanza, jingle, None, 'session-initiate-sent') + self.connection.connection.send(stanza) + self.state = JingleStates.pending + + def __session_accept(self): + assert self.state == JingleStates.pending + stanza, jingle = self.__make_jingle('session-accept') + self.__append_contents(jingle) + self.__broadcast(stanza, jingle, None, 'session-accept-sent') + self.connection.connection.send(stanza) + self.state = JingleStates.active + + def __session_info(self, payload=None): + assert self.state != JingleStates.ended + stanza, jingle = self.__make_jingle('session-info') + if payload: + jingle.addChild(node=payload) + self.connection.connection.send(stanza) + + def _session_terminate(self, reason=None): + assert self.state != JingleStates.ended + stanza, jingle = self.__make_jingle('session-terminate') + if reason is not None: + jingle.addChild(node=reason) + self.__broadcast_all(stanza, jingle, None, 'session-terminate-sent') + self.connection.connection.send(stanza) + reason, text = self.__reason_from_stanza(jingle) + if reason not in ('success', 'cancel', 'decline'): + self.__dispatch_error(reason, reason, text) + if text: + text = '%s (%s)' % (reason, text) + else: + text = reason + self.connection.delete_jingle_session(self.peerjid, self.sid) + self.connection.dispatch('JINGLE_DISCONNECTED', + (self.peerjid, self.sid, None, text)) + + def __content_add(self, content): + # TODO: test + assert self.state != JingleStates.ended + stanza, jingle = self.__make_jingle('content-add') + self.__append_content(jingle, content) + self.__broadcast(stanza, jingle, None, 'content-add-sent') + self.connection.connection.send(stanza) + + def __content_accept(self, content): + # TODO: test + assert self.state != JingleStates.ended + stanza, jingle = self.__make_jingle('content-accept') + self.__append_content(jingle, content) + self.__broadcast(stanza, jingle, None, 'content-accept-sent') + self.connection.connection.send(stanza) + + def __content_reject(self, content): + assert self.state != JingleStates.ended + stanza, jingle = self.__make_jingle('content-reject') + self.__append_content(jingle, content) + self.connection.connection.send(stanza) + # TODO: this will fail if content is not an RTP content + self.connection.dispatch('JINGLE_DISCONNECTED', + (self.peerjid, self.sid, content.media, 'rejected')) + + def __content_modify(self): + assert self.state != JingleStates.ended + + def __content_remove(self, content): + assert self.state != JingleStates.ended + stanza, jingle = self.__make_jingle('content-remove') + self.__append_content(jingle, content) + self.connection.connection.send(stanza) + # TODO: this will fail if content is not an RTP content + self.connection.dispatch('JINGLE_DISCONNECTED', + (self.peerjid, self.sid, content.media, 'removed')) + + def content_negotiated(self, media): + self.connection.dispatch('JINGLE_CONNECTED', (self.peerjid, self.sid, + media)) diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py index 82a0cb987..521564e87 100644 --- a/src/common/jingle_transport.py +++ b/src/common/jingle_transport.py @@ -20,131 +20,129 @@ import xmpp transports = {} def get_jingle_transport(node): - namespace = node.getNamespace() - if namespace in transports: - return transports[namespace]() + namespace = node.getNamespace() + if namespace in transports: + return transports[namespace]() class TransportType(object): - """ - Possible types of a JingleTransport - """ - datagram = 1 - streaming = 2 + """ + Possible types of a JingleTransport + """ + datagram = 1 + streaming = 2 class JingleTransport(object): - """ - An abstraction of a transport in Jingle sessions - """ + """ + An abstraction of a transport in Jingle sessions + """ - def __init__(self, type_): - self.type = type_ - self.candidates = [] - self.remote_candidates = [] + def __init__(self, type_): + self.type = type_ + self.candidates = [] + self.remote_candidates = [] - def _iter_candidates(self): - for candidate in self.candidates: - yield self.make_candidate(candidate) + def _iter_candidates(self): + for candidate in self.candidates: + yield self.make_candidate(candidate) - def make_candidate(self, candidate): - """ - Build a candidate stanza for the given candidate - """ - pass + def make_candidate(self, candidate): + """ + Build a candidate stanza for the given candidate + """ + pass - def make_transport(self, candidates=None): - """ - Build a transport stanza with the given candidates (or self.candidates if - candidates is None) - """ - if not candidates: - candidates = self._iter_candidates() - else: - candidates = (self.make_candidate(candidate) for candidate in candidates) - transport = xmpp.Node('transport', payload=candidates) - return transport + def make_transport(self, candidates=None): + """ + Build a transport stanza with the given candidates (or self.candidates if + candidates is None) + """ + if not candidates: + candidates = self._iter_candidates() + else: + candidates = (self.make_candidate(candidate) for candidate in candidates) + transport = xmpp.Node('transport', payload=candidates) + return transport - def parse_transport_stanza(self, transport): - """ - Return the list of transport candidates from a transport stanza - """ - return [] + def parse_transport_stanza(self, transport): + """ + Return the list of transport candidates from a transport stanza + """ + return [] import farsight class JingleTransportICEUDP(JingleTransport): - def __init__(self): - JingleTransport.__init__(self, TransportType.datagram) + def __init__(self): + JingleTransport.__init__(self, TransportType.datagram) - def make_candidate(self, candidate): - types = {farsight.CANDIDATE_TYPE_HOST: 'host', - farsight.CANDIDATE_TYPE_SRFLX: 'srflx', - farsight.CANDIDATE_TYPE_PRFLX: 'prflx', - farsight.CANDIDATE_TYPE_RELAY: 'relay', - farsight.CANDIDATE_TYPE_MULTICAST: 'multicast'} - attrs = { - 'component': candidate.component_id, - 'foundation': '1', # hack - 'generation': '0', - 'ip': candidate.ip, - 'network': '0', - 'port': candidate.port, - 'priority': int(candidate.priority), # hack - } - if candidate.type in types: - attrs['type'] = types[candidate.type] - if candidate.proto == farsight.NETWORK_PROTOCOL_UDP: - attrs['protocol'] = 'udp' - else: - # we actually don't handle properly different tcp options in jingle - attrs['protocol'] = 'tcp' - return xmpp.Node('candidate', attrs=attrs) + def make_candidate(self, candidate): + types = {farsight.CANDIDATE_TYPE_HOST: 'host', + farsight.CANDIDATE_TYPE_SRFLX: 'srflx', + farsight.CANDIDATE_TYPE_PRFLX: 'prflx', + farsight.CANDIDATE_TYPE_RELAY: 'relay', + farsight.CANDIDATE_TYPE_MULTICAST: 'multicast'} + attrs = { + 'component': candidate.component_id, + 'foundation': '1', # hack + 'generation': '0', + 'ip': candidate.ip, + 'network': '0', + 'port': candidate.port, + 'priority': int(candidate.priority), # hack + } + if candidate.type in types: + attrs['type'] = types[candidate.type] + if candidate.proto == farsight.NETWORK_PROTOCOL_UDP: + attrs['protocol'] = 'udp' + else: + # we actually don't handle properly different tcp options in jingle + attrs['protocol'] = 'tcp' + return xmpp.Node('candidate', attrs=attrs) - def make_transport(self, candidates=None): - transport = JingleTransport.make_transport(self, candidates) - transport.setNamespace(xmpp.NS_JINGLE_ICE_UDP) - if self.candidates and self.candidates[0].username and \ - self.candidates[0].password: - transport.setAttr('ufrag', self.candidates[0].username) - transport.setAttr('pwd', self.candidates[0].password) - return transport + def make_transport(self, candidates=None): + transport = JingleTransport.make_transport(self, candidates) + transport.setNamespace(xmpp.NS_JINGLE_ICE_UDP) + if self.candidates and self.candidates[0].username and \ + self.candidates[0].password: + transport.setAttr('ufrag', self.candidates[0].username) + transport.setAttr('pwd', self.candidates[0].password) + return transport - def parse_transport_stanza(self, transport): - candidates = [] - for candidate in transport.iterTags('candidate'): - cand = farsight.Candidate() - cand.component_id = int(candidate['component']) - cand.ip = str(candidate['ip']) - cand.port = int(candidate['port']) - cand.foundation = str(candidate['foundation']) - #cand.type = farsight.CANDIDATE_TYPE_LOCAL - cand.priority = int(candidate['priority']) + def parse_transport_stanza(self, transport): + candidates = [] + for candidate in transport.iterTags('candidate'): + cand = farsight.Candidate() + cand.component_id = int(candidate['component']) + cand.ip = str(candidate['ip']) + cand.port = int(candidate['port']) + cand.foundation = str(candidate['foundation']) + #cand.type = farsight.CANDIDATE_TYPE_LOCAL + cand.priority = int(candidate['priority']) - if candidate['protocol'] == 'udp': - cand.proto = farsight.NETWORK_PROTOCOL_UDP - else: - # we actually don't handle properly different tcp options in jingle - cand.proto = farsight.NETWORK_PROTOCOL_TCP + if candidate['protocol'] == 'udp': + cand.proto = farsight.NETWORK_PROTOCOL_UDP + else: + # we actually don't handle properly different tcp options in jingle + cand.proto = farsight.NETWORK_PROTOCOL_TCP - cand.username = str(transport['ufrag']) - cand.password = str(transport['pwd']) + cand.username = str(transport['ufrag']) + cand.password = str(transport['pwd']) - #FIXME: huh? - types = {'host': farsight.CANDIDATE_TYPE_HOST, - 'srflx': farsight.CANDIDATE_TYPE_SRFLX, - 'prflx': farsight.CANDIDATE_TYPE_PRFLX, - 'relay': farsight.CANDIDATE_TYPE_RELAY, - 'multicast': farsight.CANDIDATE_TYPE_MULTICAST} - if 'type' in candidate and candidate['type'] in types: - cand.type = types[candidate['type']] - else: - print 'Unknown type %s', candidate['type'] - candidates.append(cand) - self.remote_candidates.extend(candidates) - return candidates + #FIXME: huh? + types = {'host': farsight.CANDIDATE_TYPE_HOST, + 'srflx': farsight.CANDIDATE_TYPE_SRFLX, + 'prflx': farsight.CANDIDATE_TYPE_PRFLX, + 'relay': farsight.CANDIDATE_TYPE_RELAY, + 'multicast': farsight.CANDIDATE_TYPE_MULTICAST} + if 'type' in candidate and candidate['type'] in types: + cand.type = types[candidate['type']] + else: + print 'Unknown type %s', candidate['type'] + candidates.append(cand) + self.remote_candidates.extend(candidates) + return candidates transports[xmpp.NS_JINGLE_ICE_UDP] = JingleTransportICEUDP - -# vim: se ts=3: diff --git a/src/common/kwalletbinding.py b/src/common/kwalletbinding.py index 842aff8b9..af320c2f8 100644 --- a/src/common/kwalletbinding.py +++ b/src/common/kwalletbinding.py @@ -25,56 +25,56 @@ import subprocess def kwallet_available(): - """ - Return True if kwalletcli can be run, False otherwise - """ - try: - p = subprocess.Popen(["kwalletcli", "-qV"]) - except Exception: - return False - p.communicate() - if p.returncode == 0: - return True - return False + """ + Return True if kwalletcli can be run, False otherwise + """ + try: + p = subprocess.Popen(["kwalletcli", "-qV"]) + except Exception: + return False + p.communicate() + if p.returncode == 0: + return True + return False def kwallet_get(folder, entry): - """ - Retrieve a passphrase from the KDE Wallet via kwalletcli + """ + Retrieve a passphrase from the KDE Wallet via kwalletcli - Arguments: - • folder: The top-level category to use (normally the programme name) - • entry: The key of the entry to retrieve + Arguments: + • folder: The top-level category to use (normally the programme name) + • entry: The key of the entry to retrieve - Returns the passphrase as unicode, False if it cannot be found, - or None if an error occured. - """ - p = subprocess.Popen(["kwalletcli", "-q", "-f", folder.encode('utf-8'), - "-e", entry.encode('utf-8')], stdout=subprocess.PIPE) - pw = p.communicate()[0] - if p.returncode == 0: - return unicode(pw.decode('utf-8')) - if p.returncode == 1 or p.returncode == 4: - # ENOENT - return False - # error - return None + Returns the passphrase as unicode, False if it cannot be found, + or None if an error occured. + """ + p = subprocess.Popen(["kwalletcli", "-q", "-f", folder.encode('utf-8'), + "-e", entry.encode('utf-8')], stdout=subprocess.PIPE) + pw = p.communicate()[0] + if p.returncode == 0: + return unicode(pw.decode('utf-8')) + if p.returncode == 1 or p.returncode == 4: + # ENOENT + return False + # error + return None def kwallet_put(folder, entry, passphrase): - """ - Store a passphrase into the KDE Wallet via kwalletcli + """ + Store a passphrase into the KDE Wallet via kwalletcli - Arguments: - • folder: The top-level category to use (normally the programme name) - • entry: The key of the entry to store - • passphrase: The value to store + Arguments: + • folder: The top-level category to use (normally the programme name) + • entry: The key of the entry to store + • passphrase: The value to store - Returns True on success, False otherwise. - """ - p = subprocess.Popen(["kwalletcli", "-q", "-f", folder.encode('utf-8'), - "-e", entry.encode('utf-8'), "-P"], stdin=subprocess.PIPE) - p.communicate(passphrase.encode('utf-8')) - if p.returncode == 0: - return True - return False + Returns True on success, False otherwise. + """ + p = subprocess.Popen(["kwalletcli", "-q", "-f", folder.encode('utf-8'), + "-e", entry.encode('utf-8'), "-P"], stdin=subprocess.PIPE) + p.communicate(passphrase.encode('utf-8')) + if p.returncode == 0: + return True + return False diff --git a/src/common/latex.py b/src/common/latex.py index 492f04c39..a95f1f9f7 100644 --- a/src/common/latex.py +++ b/src/common/latex.py @@ -41,121 +41,119 @@ import helpers # some latex commands are really bad blacklist = ['\\def', '\\let', '\\futurelet', - '\\newcommand', '\\renewcomment', '\\else', '\\fi', '\\write', - '\\input', '\\include', '\\chardef', '\\catcode', '\\makeatletter', - '\\noexpand', '\\toksdef', '\\every', '\\errhelp', '\\errorstopmode', - '\\scrollmode', '\\nonstopmode', '\\batchmode', '\\read', '\\csname', - '\\newhelp', '\\relax', '\\afterground', '\\afterassignment', - '\\expandafter', '\\noexpand', '\\special', '\\command', '\\loop', - '\\repeat', '\\toks', '\\output', '\\line', '\\mathcode', '\\name', - '\\item', '\\section', '\\mbox', '\\DeclareRobustCommand', '\\[', - '\\]'] + '\\newcommand', '\\renewcomment', '\\else', '\\fi', '\\write', + '\\input', '\\include', '\\chardef', '\\catcode', '\\makeatletter', + '\\noexpand', '\\toksdef', '\\every', '\\errhelp', '\\errorstopmode', + '\\scrollmode', '\\nonstopmode', '\\batchmode', '\\read', '\\csname', + '\\newhelp', '\\relax', '\\afterground', '\\afterassignment', + '\\expandafter', '\\noexpand', '\\special', '\\command', '\\loop', + '\\repeat', '\\toks', '\\output', '\\line', '\\mathcode', '\\name', + '\\item', '\\section', '\\mbox', '\\DeclareRobustCommand', '\\[', + '\\]'] # True if the string matches the blacklist def check_blacklist(str_): - for word in blacklist: - if word in str_: - return True - return False + for word in blacklist: + if word in str_: + return True + return False def get_tmpfile_name(): - random.seed() - int_ = random.randint(0, 100) - return os.path.join(gettempdir(), 'gajimtex_' + int_.__str__()) + random.seed() + int_ = random.randint(0, 100) + return os.path.join(gettempdir(), 'gajimtex_' + int_.__str__()) def write_latex(filename, str_): - texstr = '\\documentclass[12pt]{article}\\usepackage[dvips]{graphicx}' - texstr += '\\usepackage{amsmath}\\usepackage{amssymb}' - texstr += '\\pagestyle{empty}' - texstr += '\\begin{document}\\begin{large}\\begin{gather*}' - texstr += str_ - texstr += '\\end{gather*}\\end{large}\\end{document}' + texstr = '\\documentclass[12pt]{article}\\usepackage[dvips]{graphicx}' + texstr += '\\usepackage{amsmath}\\usepackage{amssymb}' + texstr += '\\pagestyle{empty}' + texstr += '\\begin{document}\\begin{large}\\begin{gather*}' + texstr += str_ + texstr += '\\end{gather*}\\end{large}\\end{document}' - file_ = open(filename, "w+") - file_.write(texstr) - file_.flush() - file_.close() + file_ = open(filename, "w+") + file_.write(texstr) + file_.flush() + file_.close() # a wrapper for Popen so that no window gets opened on Windows # (i think this is the reason we're using Popen rather than just system()) # stdout goes to a pipe so that it can be read def popen_nt_friendly(command): - if os.name == 'nt': - # CREATE_NO_WINDOW - return Popen(command, creationflags=0x08000000, cwd=gettempdir(), stdout=PIPE) - else: - return Popen(command, cwd=gettempdir(), stdout=PIPE) + if os.name == 'nt': + # CREATE_NO_WINDOW + return Popen(command, creationflags=0x08000000, cwd=gettempdir(), stdout=PIPE) + else: + return Popen(command, cwd=gettempdir(), stdout=PIPE) def check_for_latex_support(): - """ - Check if latex is available and if it can create a picture - """ - try: - filename = latex_to_image("test") - if filename: - # we have a file, conversion succeeded - os.remove(filename) - return True - return False - except LatexError: - return False + """ + Check if latex is available and if it can create a picture + """ + try: + filename = latex_to_image("test") + if filename: + # we have a file, conversion succeeded + os.remove(filename) + return True + return False + except LatexError: + return False def try_run(argv): - try: - p = popen_nt_friendly(argv) - out = p.communicate()[0] - log.info(out) - return p.wait() - except Exception, e: - return _('Error executing "%(command)s": %(error)s') % { - 'command': " ".join(argv), - 'error': helpers.decode_string(str(e))} + try: + p = popen_nt_friendly(argv) + out = p.communicate()[0] + log.info(out) + return p.wait() + except Exception, e: + return _('Error executing "%(command)s": %(error)s') % { + 'command': " ".join(argv), + 'error': helpers.decode_string(str(e))} def latex_to_image(str_): - result = None - exitcode = 0 + result = None + exitcode = 0 - try: - bg_str, fg_str = gajim.interface.get_bg_fg_colors() - except: - # interface may not be available when we test latext at startup - bg_str, fg_str = 'rgb 1.0 1.0 1.0', 'rgb 0.0 0.0 0.0' + try: + bg_str, fg_str = gajim.interface.get_bg_fg_colors() + except: + # interface may not be available when we test latext at startup + bg_str, fg_str = 'rgb 1.0 1.0 1.0', 'rgb 0.0 0.0 0.0' - # filter latex code with bad commands - if check_blacklist(str_): - # we triggered the blacklist, immediately return None - return None + # filter latex code with bad commands + if check_blacklist(str_): + # we triggered the blacklist, immediately return None + return None - tmpfile = get_tmpfile_name() + tmpfile = get_tmpfile_name() - # build latex string - write_latex(os.path.join(tmpfile + '.tex'), str_) + # build latex string + write_latex(os.path.join(tmpfile + '.tex'), str_) - # convert TeX to dvi - exitcode = try_run(['latex', '--interaction=nonstopmode', - tmpfile + '.tex']) + # convert TeX to dvi + exitcode = try_run(['latex', '--interaction=nonstopmode', + tmpfile + '.tex']) - if exitcode == 0: - # convert dvi to png - latex_png_dpi = gajim.config.get('latex_png_dpi') - exitcode = try_run(['dvipng', '-bg', bg_str, '-fg', fg_str, '-T', - 'tight', '-D', latex_png_dpi, tmpfile + '.dvi', '-o', - tmpfile + '.png']) + if exitcode == 0: + # convert dvi to png + latex_png_dpi = gajim.config.get('latex_png_dpi') + exitcode = try_run(['dvipng', '-bg', bg_str, '-fg', fg_str, '-T', + 'tight', '-D', latex_png_dpi, tmpfile + '.dvi', '-o', + tmpfile + '.png']) - # remove temp files created by us and TeX - extensions = ['.tex', '.log', '.aux', '.dvi'] - for ext in extensions: - try: - os.remove(tmpfile + ext) - except Exception: - pass + # remove temp files created by us and TeX + extensions = ['.tex', '.log', '.aux', '.dvi'] + for ext in extensions: + try: + os.remove(tmpfile + ext) + except Exception: + pass - if isinstance(exitcode, (unicode, str)): - raise LatexError(exitcode) + if isinstance(exitcode, (unicode, str)): + raise LatexError(exitcode) - if exitcode == 0: - result = tmpfile + '.png' + if exitcode == 0: + result = tmpfile + '.png' - return result - -# vim: se ts=3: + return result diff --git a/src/common/location_listener.py b/src/common/location_listener.py index f1ba5b714..37a2e4612 100644 --- a/src/common/location_listener.py +++ b/src/common/location_listener.py @@ -22,116 +22,116 @@ from common import gajim from common import pep from common import dbus_support if dbus_support.supported: - import dbus - import dbus.glib + import dbus + import dbus.glib class LocationListener: - _instance = None - @classmethod - def get(cls): - if cls._instance is None: - cls._instance = cls() - return cls._instance + _instance = None + @classmethod + def get(cls): + if cls._instance is None: + cls._instance = cls() + return cls._instance - def __init__(self): - self._data = {} + def __init__(self): + self._data = {} - def get_data(self): - self._get_address() - self._get_position() + def get_data(self): + self._get_address() + self._get_position() - def _get_address(self): - bus = dbus.SessionBus() - if 'org.freedesktop.Geoclue.Master' not in bus.list_names(): - self._on_geoclue_address_changed() - return - obj = bus.get_object('org.freedesktop.Geoclue.Master', - '/org/freedesktop/Geoclue/Master') - # get MasterClient path - path = obj.Create() - # get MasterClient - cli = bus.get_object('org.freedesktop.Geoclue.Master', path) - cli.AddressStart() - # Check that there is a provider - name, description, service, path = cli.GetAddressProvider() - if path: - timestamp, address, accuracy = cli.GetAddress() - self._on_geoclue_address_changed(timestamp, address, accuracy) + def _get_address(self): + bus = dbus.SessionBus() + if 'org.freedesktop.Geoclue.Master' not in bus.list_names(): + self._on_geoclue_address_changed() + return + obj = bus.get_object('org.freedesktop.Geoclue.Master', + '/org/freedesktop/Geoclue/Master') + # get MasterClient path + path = obj.Create() + # get MasterClient + cli = bus.get_object('org.freedesktop.Geoclue.Master', path) + cli.AddressStart() + # Check that there is a provider + name, description, service, path = cli.GetAddressProvider() + if path: + timestamp, address, accuracy = cli.GetAddress() + self._on_geoclue_address_changed(timestamp, address, accuracy) - def _get_position(self): - bus = dbus.SessionBus() - if 'org.freedesktop.Geoclue.Master' not in bus.list_names(): - self._on_geoclue_position_changed() - return - obj = bus.get_object('org.freedesktop.Geoclue.Master', - '/org/freedesktop/Geoclue/Master') - # get MasterClient path - path = obj.Create() - # get MasterClient - cli = bus.get_object('org.freedesktop.Geoclue.Master', path) - cli.PositionStart() - # Check that there is a provider - name, description, service, path = cli.GetPositionProvider() - if path: - fields, timestamp, lat, lon, alt, accuray = cli.GetPosition() - self._on_geoclue_position_changed(fields, timestamp, lat, lon, alt, - accuracy) + def _get_position(self): + bus = dbus.SessionBus() + if 'org.freedesktop.Geoclue.Master' not in bus.list_names(): + self._on_geoclue_position_changed() + return + obj = bus.get_object('org.freedesktop.Geoclue.Master', + '/org/freedesktop/Geoclue/Master') + # get MasterClient path + path = obj.Create() + # get MasterClient + cli = bus.get_object('org.freedesktop.Geoclue.Master', path) + cli.PositionStart() + # Check that there is a provider + name, description, service, path = cli.GetPositionProvider() + if path: + fields, timestamp, lat, lon, alt, accuray = cli.GetPosition() + self._on_geoclue_position_changed(fields, timestamp, lat, lon, alt, + accuracy) - def start(self): - self.get_data() - bus = dbus.SessionBus() - # Geoclue - bus.add_signal_receiver(self._on_geoclue_address_changed, - 'AddressChanged', 'org.freedesktop.Geoclue.Address') - bus.add_signal_receiver(self._on_geoclue_position_changed, - 'PositionChanged', 'org.freedesktop.Geoclue.Position') + def start(self): + self.get_data() + bus = dbus.SessionBus() + # Geoclue + bus.add_signal_receiver(self._on_geoclue_address_changed, + 'AddressChanged', 'org.freedesktop.Geoclue.Address') + bus.add_signal_receiver(self._on_geoclue_position_changed, + 'PositionChanged', 'org.freedesktop.Geoclue.Position') - def shut_down(self): - pass + def shut_down(self): + pass - def _on_geoclue_address_changed(self, timestamp=None, address={}, - accuracy=None): - # update data with info we just received - for field in ['country', 'countrycode', 'locality', 'postalcode', - 'region', 'street']: - self._data[field] = address.get(field, None) - if timestamp: - self._data['timestamp'] = timestamp - if accuracy: - # in PEP it's horizontal accuracy - self._data['accuracy'] = accuracy[1] - self._send_location() + def _on_geoclue_address_changed(self, timestamp=None, address={}, + accuracy=None): + # update data with info we just received + for field in ['country', 'countrycode', 'locality', 'postalcode', + 'region', 'street']: + self._data[field] = address.get(field, None) + if timestamp: + self._data['timestamp'] = timestamp + if accuracy: + # in PEP it's horizontal accuracy + self._data['accuracy'] = accuracy[1] + self._send_location() - def _on_geoclue_position_changed(self, fields=[], timestamp=None, lat=None, - lon=None, alt=None, accuracy=None): - # update data with info we just received - _dict = {'lat': lat, 'lon': lon, 'alt': alt} - for field in _dict: - if _dict[field] is not None: - self._data[field] = _dict[field] - if timestamp: - self._data['timestamp'] = timestamp - if accuracy: - # in PEP it's horizontal accuracy - self._data['accuracy'] = accuracy[1] - self._send_location() + def _on_geoclue_position_changed(self, fields=[], timestamp=None, lat=None, + lon=None, alt=None, accuracy=None): + # update data with info we just received + _dict = {'lat': lat, 'lon': lon, 'alt': alt} + for field in _dict: + if _dict[field] is not None: + self._data[field] = _dict[field] + if timestamp: + self._data['timestamp'] = timestamp + if accuracy: + # in PEP it's horizontal accuracy + self._data['accuracy'] = accuracy[1] + self._send_location() - def _send_location(self): - accounts = gajim.connections.keys() - for acct in accounts: - if not gajim.account_is_connected(acct): - continue - if not gajim.config.get_per('accounts', acct, 'publish_location'): - continue - if gajim.connections[acct].location_info == self._data: - continue - gajim.connections[acct].send_location(self._data) - gajim.connections[acct].location_info = self._data + def _send_location(self): + accounts = gajim.connections.keys() + for acct in accounts: + if not gajim.account_is_connected(acct): + continue + if not gajim.config.get_per('accounts', acct, 'publish_location'): + continue + if gajim.connections[acct].location_info == self._data: + continue + gajim.connections[acct].send_location(self._data) + gajim.connections[acct].location_info = self._data def enable(): - listener = LocationListener.get() - listener.start() + listener = LocationListener.get() + listener.start() def disable(): - listener = LocationListener.get() - listener.shut_down() + listener = LocationListener.get() + listener.shut_down() diff --git a/src/common/logger.py b/src/common/logger.py index a9297c463..b1079690a 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -46,1009 +46,1007 @@ LOG_DB_FOLDER, LOG_DB_FILE = os.path.split(LOG_DB_PATH) CACHE_DB_PATH = configpaths.gajimpaths['CACHE_DB'] class Constants: - def __init__(self): - ( - self.JID_NORMAL_TYPE, - self.JID_ROOM_TYPE - ) = range(2) + def __init__(self): + ( + self.JID_NORMAL_TYPE, + self.JID_ROOM_TYPE + ) = range(2) - ( - self.KIND_STATUS, - self.KIND_GCSTATUS, - self.KIND_GC_MSG, - self.KIND_SINGLE_MSG_RECV, - self.KIND_CHAT_MSG_RECV, - self.KIND_SINGLE_MSG_SENT, - self.KIND_CHAT_MSG_SENT, - self.KIND_ERROR - ) = range(8) + ( + self.KIND_STATUS, + self.KIND_GCSTATUS, + self.KIND_GC_MSG, + self.KIND_SINGLE_MSG_RECV, + self.KIND_CHAT_MSG_RECV, + self.KIND_SINGLE_MSG_SENT, + self.KIND_CHAT_MSG_SENT, + self.KIND_ERROR + ) = range(8) - ( - self.SHOW_ONLINE, - self.SHOW_CHAT, - self.SHOW_AWAY, - self.SHOW_XA, - self.SHOW_DND, - self.SHOW_OFFLINE - ) = range(6) + ( + self.SHOW_ONLINE, + self.SHOW_CHAT, + self.SHOW_AWAY, + self.SHOW_XA, + self.SHOW_DND, + self.SHOW_OFFLINE + ) = range(6) - ( - self.TYPE_AIM, - self.TYPE_GG, - self.TYPE_HTTP_WS, - self.TYPE_ICQ, - self.TYPE_MSN, - self.TYPE_QQ, - self.TYPE_SMS, - self.TYPE_SMTP, - self.TYPE_TLEN, - self.TYPE_YAHOO, - self.TYPE_NEWMAIL, - self.TYPE_RSS, - self.TYPE_WEATHER, - self.TYPE_MRIM, - ) = range(14) + ( + self.TYPE_AIM, + self.TYPE_GG, + self.TYPE_HTTP_WS, + self.TYPE_ICQ, + self.TYPE_MSN, + self.TYPE_QQ, + self.TYPE_SMS, + self.TYPE_SMTP, + self.TYPE_TLEN, + self.TYPE_YAHOO, + self.TYPE_NEWMAIL, + self.TYPE_RSS, + self.TYPE_WEATHER, + self.TYPE_MRIM, + ) = range(14) - ( - self.SUBSCRIPTION_NONE, - self.SUBSCRIPTION_TO, - self.SUBSCRIPTION_FROM, - self.SUBSCRIPTION_BOTH, - ) = range(4) + ( + self.SUBSCRIPTION_NONE, + self.SUBSCRIPTION_TO, + self.SUBSCRIPTION_FROM, + self.SUBSCRIPTION_BOTH, + ) = range(4) constants = Constants() class Logger: - def __init__(self): - self.jids_already_in = [] # holds jids that we already have in DB - self.con = None - - if not os.path.exists(LOG_DB_PATH): - # this can happen only the first time (the time we create the db) - # db is not created here but in src/common/checks_paths.py - return - self.init_vars() - if not os.path.exists(CACHE_DB_PATH): - # this can happen cache database is not present when gajim is launched - # db will be created in src/common/checks_paths.py - return - self.attach_cache_database() - - def close_db(self): - if self.con: - self.con.close() - self.con = None - self.cur = None - - def open_db(self): - self.close_db() - - # FIXME: sqlite3_open wants UTF8 strings. So a path with - # non-ascii chars doesn't work. See #2812 and - # http://lists.initd.org/pipermail/pysqlite/2005-August/000134.html - back = os.getcwd() - os.chdir(LOG_DB_FOLDER) - - # if locked, wait up to 20 sec to unlock - # before raise (hopefully should be enough) - - self.con = sqlite.connect(LOG_DB_FILE, timeout=20.0, - isolation_level='IMMEDIATE') - os.chdir(back) - self.cur = self.con.cursor() - self.set_synchronous(False) - - def attach_cache_database(self): - try: - self.cur.execute("ATTACH DATABASE '%s' AS cache" % CACHE_DB_PATH) - except sqlite.Error, e: - gajim.log.debug("Failed to attach cache database: %s" % str(e)) - - def set_synchronous(self, sync): - try: - if sync: - self.cur.execute("PRAGMA synchronous = NORMAL") - else: - self.cur.execute("PRAGMA synchronous = OFF") - except sqlite.Error, e: - gajim.log.debug("Failed to set_synchronous(%s): %s" % (sync, str(e))) - - def init_vars(self): - self.open_db() - self.get_jids_already_in_db() - - def simple_commit(self, sql_to_commit): - """ - Helper to commit - """ - self.cur.execute(sql_to_commit) - try: - self.con.commit() - except sqlite.OperationalError, e: - print >> sys.stderr, str(e) - - def get_jids_already_in_db(self): - try: - self.cur.execute('SELECT jid FROM jids') - # list of tupples: [(u'aaa@bbb',), (u'cc@dd',)] - rows = self.cur.fetchall() - except sqlite.DatabaseError: - raise exceptions.DatabaseMalformed - self.jids_already_in = [] - for row in rows: - # row[0] is first item of row (the only result here, the jid) - if row[0] == '': - # malformed jid, ignore line - pass - else: - self.jids_already_in.append(row[0]) - - def get_jids_in_db(self): - return self.jids_already_in - - def jid_is_from_pm(self, jid): - """ - If jid is gajim@conf/nkour it's likely a pm one, how we know gajim@conf - is not a normal guy and nkour is not his resource? we ask if gajim@conf - is already in jids (with type room jid) this fails if user disables - logging for room and only enables for pm (so higly unlikely) and if we - fail we do not go chaos (user will see the first pm as if it was message - in room's public chat) and after that all okay - """ - if jid.find('/') > -1: - possible_room_jid = jid.split('/', 1)[0] - return self.jid_is_room_jid(possible_room_jid) - else: - # it's not a full jid, so it's not a pm one - return False - - def jid_is_room_jid(self, jid): - self.cur.execute('SELECT jid_id FROM jids WHERE jid=? AND type=?', - (jid, constants.JID_ROOM_TYPE)) - row = self.cur.fetchone() - if row is None: - return False - else: - return True - - def get_jid_id(self, jid, typestr=None): - """ - jids table has jid and jid_id logs table has log_id, jid_id, - contact_name, time, kind, show, message so to ask logs we need jid_id - that matches our jid in jids table this method wants jid and returns the - jid_id for later sql-ing on logs typestr can be 'ROOM' or anything else - depending on the type of JID and is only needed to be specified when the - JID is new in DB - """ - if jid.find('/') != -1: # if it has a / - jid_is_from_pm = self.jid_is_from_pm(jid) - if not jid_is_from_pm: # it's normal jid with resource - jid = jid.split('/', 1)[0] # remove the resource - if jid in self.jids_already_in: # we already have jids in DB - self.cur.execute('SELECT jid_id FROM jids WHERE jid=?', [jid]) - row = self.cur.fetchone() - if row: - return row[0] - # oh! a new jid :), we add it now - if typestr == 'ROOM': - typ = constants.JID_ROOM_TYPE - else: - typ = constants.JID_NORMAL_TYPE - try: - self.cur.execute('INSERT INTO jids (jid, type) VALUES (?, ?)', (jid, - typ)) - self.con.commit() - except sqlite.IntegrityError, e: - # Jid already in DB, maybe added by another instance. re-read DB - self.get_jids_already_in_db() - return self.get_jid_id(jid, typestr) - except sqlite.OperationalError, e: - raise exceptions.PysqliteOperationalError(str(e)) - jid_id = self.cur.lastrowid - self.jids_already_in.append(jid) - return jid_id - - def convert_human_values_to_db_api_values(self, kind, show): - """ - Convert from string style to constant ints for db - """ - if kind == 'status': - kind_col = constants.KIND_STATUS - elif kind == 'gcstatus': - kind_col = constants.KIND_GCSTATUS - elif kind == 'gc_msg': - kind_col = constants.KIND_GC_MSG - elif kind == 'single_msg_recv': - kind_col = constants.KIND_SINGLE_MSG_RECV - elif kind == 'single_msg_sent': - kind_col = constants.KIND_SINGLE_MSG_SENT - elif kind == 'chat_msg_recv': - kind_col = constants.KIND_CHAT_MSG_RECV - elif kind == 'chat_msg_sent': - kind_col = constants.KIND_CHAT_MSG_SENT - elif kind == 'error': - kind_col = constants.KIND_ERROR - - if show == 'online': - show_col = constants.SHOW_ONLINE - elif show == 'chat': - show_col = constants.SHOW_CHAT - elif show == 'away': - show_col = constants.SHOW_AWAY - elif show == 'xa': - show_col = constants.SHOW_XA - elif show == 'dnd': - show_col = constants.SHOW_DND - elif show == 'offline': - show_col = constants.SHOW_OFFLINE - elif show is None: - show_col = None - else: # invisible in GC when someone goes invisible - # it's a RFC violation .... but we should not crash - show_col = 'UNKNOWN' - - return kind_col, show_col - - def convert_human_transport_type_to_db_api_values(self, type_): - """ - Convert from string style to constant ints for db - """ - if type_ == 'aim': - return constants.TYPE_AIM - if type_ == 'gadu-gadu': - return constants.TYPE_GG - if type_ == 'http-ws': - return constants.TYPE_HTTP_WS - if type_ == 'icq': - return constants.TYPE_ICQ - if type_ == 'msn': - return constants.TYPE_MSN - if type_ == 'qq': - return constants.TYPE_QQ - if type_ == 'sms': - return constants.TYPE_SMS - if type_ == 'smtp': - return constants.TYPE_SMTP - if type_ in ('tlen', 'x-tlen'): - return constants.TYPE_TLEN - if type_ == 'yahoo': - return constants.TYPE_YAHOO - if type_ == 'newmail': - return constants.TYPE_NEWMAIL - if type_ == 'rss': - return constants.TYPE_RSS - if type_ == 'weather': - return constants.TYPE_WEATHER - if type_ == 'mrim': - return constants.TYPE_MRIM - return None - - def convert_api_values_to_human_transport_type(self, type_id): - """ - Convert from constant ints for db to string style - """ - if type_id == constants.TYPE_AIM: - return 'aim' - if type_id == constants.TYPE_GG: - return 'gadu-gadu' - if type_id == constants.TYPE_HTTP_WS: - return 'http-ws' - if type_id == constants.TYPE_ICQ: - return 'icq' - if type_id == constants.TYPE_MSN: - return 'msn' - if type_id == constants.TYPE_QQ: - return 'qq' - if type_id == constants.TYPE_SMS: - return 'sms' - if type_id == constants.TYPE_SMTP: - return 'smtp' - if type_id == constants.TYPE_TLEN: - return 'tlen' - if type_id == constants.TYPE_YAHOO: - return 'yahoo' - if type_id == constants.TYPE_NEWMAIL: - return 'newmail' - if type_id == constants.TYPE_RSS: - return 'rss' - if type_id == constants.TYPE_WEATHER: - return 'weather' - if type_id == constants.TYPE_MRIM: - return 'mrim' - - def convert_human_subscription_values_to_db_api_values(self, sub): - """ - Convert from string style to constant ints for db - """ - if sub == 'none': - return constants.SUBSCRIPTION_NONE - if sub == 'to': - return constants.SUBSCRIPTION_TO - if sub == 'from': - return constants.SUBSCRIPTION_FROM - if sub == 'both': - return constants.SUBSCRIPTION_BOTH - - def convert_db_api_values_to_human_subscription_values(self, sub): - """ - Convert from constant ints for db to string style - """ - if sub == constants.SUBSCRIPTION_NONE: - return 'none' - if sub == constants.SUBSCRIPTION_TO: - return 'to' - if sub == constants.SUBSCRIPTION_FROM: - return 'from' - if sub == constants.SUBSCRIPTION_BOTH: - return 'both' - - def commit_to_db(self, values, write_unread=False): - sql = '''INSERT INTO logs (jid_id, contact_name, time, kind, show, - message, subject) VALUES (?, ?, ?, ?, ?, ?, ?)''' - try: - self.cur.execute(sql, values) - except sqlite.DatabaseError: - raise exceptions.DatabaseMalformed - except sqlite.OperationalError, e: - raise exceptions.PysqliteOperationalError(str(e)) - message_id = None - try: - self.con.commit() - if write_unread: - message_id = self.cur.lastrowid - except sqlite.OperationalError, e: - print >> sys.stderr, str(e) - if message_id: - self.insert_unread_events(message_id, values[0]) - return message_id - - def insert_unread_events(self, message_id, jid_id): - """ - Add unread message with id: message_id - """ - sql = 'INSERT INTO unread_messages VALUES (%d, %d, 0)' % (message_id, - jid_id) - self.simple_commit(sql) - - def set_read_messages(self, message_ids): - """ - Mark all messages with ids in message_ids as read - """ - ids = ','.join([str(i) for i in message_ids]) - sql = 'DELETE FROM unread_messages WHERE message_id IN (%s)' % ids - self.simple_commit(sql) - - def set_shown_unread_msgs(self, msg_id): - """ - Mark unread message as shown un GUI - """ - sql = 'UPDATE unread_messages SET shown = 1 where message_id = %s' % \ - msg_id - self.simple_commit(sql) - - def reset_shown_unread_messages(self): - """ - Set shown field to False in unread_messages table - """ - sql = 'UPDATE unread_messages SET shown = 0' - self.simple_commit(sql) - - def get_unread_msgs(self): - """ - Get all unread messages - """ - all_messages = [] - try: - self.cur.execute( - 'SELECT message_id, shown from unread_messages') - results = self.cur.fetchall() - except Exception: - pass - for message in results: - msg_id = message[0] - shown = message[1] - # here we get infos for that message, and related jid from jids table - # do NOT change order of SELECTed things, unless you change function(s) - # that called this function - self.cur.execute(''' - SELECT logs.log_line_id, logs.message, logs.time, logs.subject, - jids.jid - FROM logs, jids - WHERE logs.log_line_id = %d AND logs.jid_id = jids.jid_id - ''' % msg_id - ) - results = self.cur.fetchall() - if len(results) == 0: - # Log line is no more in logs table. remove it from unread_messages - self.set_read_messages([msg_id]) - continue - all_messages.append(results[0] + (shown,)) - return all_messages - - def write(self, kind, jid, message=None, show=None, tim=None, subject=None): - """ - Write a row (status, gcstatus, message etc) to logs database - - kind can be status, gcstatus, gc_msg, (we only recv for those 3), - single_msg_recv, chat_msg_recv, chat_msg_sent, single_msg_sent we cannot - know if it is pm or normal chat message, we try to guess see - jid_is_from_pm() - - We analyze jid and store it as follows: - jids.jid text column will hold JID if TC-related, room_jid if GC-related, - ROOM_JID/nick if pm-related. - """ - - if self.jids_already_in == []: # only happens if we just created the db - self.open_db() - - contact_name_col = None # holds nickname for kinds gcstatus, gc_msg - # message holds the message unless kind is status or gcstatus, - # then it holds status message - message_col = message - subject_col = subject - if tim: - time_col = int(float(time.mktime(tim))) - else: - time_col = int(float(time.time())) - - kind_col, show_col = self.convert_human_values_to_db_api_values(kind, - show) - - write_unread = False - - # now we may have need to do extra care for some values in columns - if kind == 'status': # we store (not None) time, jid, show, msg - # status for roster items - try: - jid_id = self.get_jid_id(jid) - except exceptions.PysqliteOperationalError, e: - raise exceptions.PysqliteOperationalError(str(e)) - if show is None: # show is None (xmpp), but we say that 'online' - show_col = constants.SHOW_ONLINE - - elif kind == 'gcstatus': - # status in ROOM (for pm status see status) - if show is None: # show is None (xmpp), but we say that 'online' - show_col = constants.SHOW_ONLINE - jid, nick = jid.split('/', 1) - try: - # re-get jid_id for the new jid - jid_id = self.get_jid_id(jid, 'ROOM') - except exceptions.PysqliteOperationalError, e: - raise exceptions.PysqliteOperationalError(str(e)) - contact_name_col = nick - - elif kind == 'gc_msg': - if jid.find('/') != -1: # if it has a / - jid, nick = jid.split('/', 1) - else: - # it's server message f.e. error message - # when user tries to ban someone but he's not allowed to - nick = None - try: - # re-get jid_id for the new jid - jid_id = self.get_jid_id(jid, 'ROOM') - except exceptions.PysqliteOperationalError, e: - raise exceptions.PysqliteOperationalError(str(e)) - contact_name_col = nick - else: - try: - jid_id = self.get_jid_id(jid) - except exceptions.PysqliteOperationalError, e: - raise exceptions.PysqliteOperationalError(str(e)) - if kind == 'chat_msg_recv': - if not self.jid_is_from_pm(jid): - # Save in unread table only if it's not a pm - write_unread = True - - if show_col == 'UNKNOWN': # unknown show, do not log - return - - values = (jid_id, contact_name_col, time_col, kind_col, show_col, - message_col, subject_col) - return self.commit_to_db(values, write_unread) - - def get_last_conversation_lines(self, jid, restore_how_many_rows, - pending_how_many, timeout, account): - """ - Accept how many rows to restore and when to time them out (in minutes) - (mark them as too old) and number of messages that are in queue and are - already logged but pending to be viewed, returns a list of tupples - containg time, kind, message, list with empty tupple if nothing found to - meet our demands - """ - try: - self.get_jid_id(jid) - except exceptions.PysqliteOperationalError, e: - # Error trying to create a new jid_id. This means there is no log - return [] - where_sql = self._build_contact_where(account, jid) - - now = int(float(time.time())) - timed_out = now - (timeout * 60) # before that they are too old - # so if we ask last 5 lines and we have 2 pending we get - # 3 - 8 (we avoid the last 2 lines but we still return 5 asked) - try: - self.cur.execute(''' - SELECT time, kind, message FROM logs - WHERE (%s) AND kind IN (%d, %d, %d, %d, %d) AND time > %d - ORDER BY time DESC LIMIT %d OFFSET %d - ''' % (where_sql, constants.KIND_SINGLE_MSG_RECV, - constants.KIND_CHAT_MSG_RECV, constants.KIND_SINGLE_MSG_SENT, - constants.KIND_CHAT_MSG_SENT, constants.KIND_ERROR, - timed_out, restore_how_many_rows, pending_how_many) - ) - - results = self.cur.fetchall() - except sqlite.DatabaseError: - raise exceptions.DatabaseMalformed - results.reverse() - return results - - def get_unix_time_from_date(self, year, month, day): - # year (fe 2005), month (fe 11), day (fe 25) - # returns time in seconds for the second that starts that date since epoch - # gimme unixtime from year month day: - d = datetime.date(year, month, day) - local_time = d.timetuple() # time tupple (compat with time.localtime()) - # we have time since epoch baby :) - start_of_day = int(time.mktime(local_time)) - return start_of_day - - def get_conversation_for_date(self, jid, year, month, day, account): - """ - Return contact_name, time, kind, show, message, subject - - For each row in a list of tupples, returns list with empty tupple if we - found nothing to meet our demands - """ - try: - self.get_jid_id(jid) - except exceptions.PysqliteOperationalError, e: - # Error trying to create a new jid_id. This means there is no log - return [] - where_sql = self._build_contact_where(account, jid) - - start_of_day = self.get_unix_time_from_date(year, month, day) - seconds_in_a_day = 86400 # 60 * 60 * 24 - last_second_of_day = start_of_day + seconds_in_a_day - 1 - - self.cur.execute(''' - SELECT contact_name, time, kind, show, message, subject FROM logs - WHERE (%s) - AND time BETWEEN %d AND %d - ORDER BY time - ''' % (where_sql, start_of_day, last_second_of_day)) - - results = self.cur.fetchall() - return results - - def get_search_results_for_query(self, jid, query, account): - """ - Returns contact_name, time, kind, show, message - - For each row in a list of tupples, returns list with empty tupple if we - found nothing to meet our demands - """ - try: - self.get_jid_id(jid) - except exceptions.PysqliteOperationalError, e: - # Error trying to create a new jid_id. This means there is no log - return [] - - if False: # query.startswith('SELECT '): # it's SQL query (FIXME) - try: - self.cur.execute(query) - except sqlite.OperationalError, e: - results = [('', '', '', '', str(e))] - return results - - else: # user just typed something, we search in message column - where_sql = self._build_contact_where(account, jid) - like_sql = '%' + query.replace("'", "''") + '%' - self.cur.execute(''' - SELECT contact_name, time, kind, show, message, subject FROM logs - WHERE (%s) AND message LIKE '%s' - ORDER BY time - ''' % (where_sql, like_sql)) - - results = self.cur.fetchall() - return results - - def get_days_with_logs(self, jid, year, month, max_day, account): - """ - Return the list of days that have logs (not status messages) - """ - try: - self.get_jid_id(jid) - except exceptions.PysqliteOperationalError, e: - # Error trying to create a new jid_id. This means there is no log - return [] - days_with_logs = [] - where_sql = self._build_contact_where(account, jid) - - # First select all date of month whith logs we want - start_of_month = self.get_unix_time_from_date(year, month, 1) - seconds_in_a_day = 86400 # 60 * 60 * 24 - last_second_of_month = start_of_month + (seconds_in_a_day * max_day) - 1 - - # Select times and 'floor' them to time 0:00 - # (by dividing, they are integers) - # and take only one of the same values (distinct) - # Now we have timestamps of time 0:00 of every day with logs - self.cur.execute(''' - SELECT DISTINCT time/(86400)*86400 FROM logs - WHERE (%s) - AND time BETWEEN %d AND %d - AND kind NOT IN (%d, %d) - ORDER BY time - ''' % (where_sql, start_of_month, last_second_of_month, - constants.KIND_STATUS, constants.KIND_GCSTATUS)) - result = self.cur.fetchall() - - # convert timestamps to day of month - for line in result: - days_with_logs[0:0]=[time.gmtime(line[0])[2]] - - return days_with_logs - - def get_last_date_that_has_logs(self, jid, account=None, is_room=False): - """ - Return last time (in seconds since EPOCH) for which we had logs - (excluding statuses) - """ - where_sql = '' - if not is_room: - where_sql = self._build_contact_where(account, jid) - else: - try: - jid_id = self.get_jid_id(jid, 'ROOM') - except exceptions.PysqliteOperationalError, e: - # Error trying to create a new jid_id. This means there is no log - return None - where_sql = 'jid_id = %s' % jid_id - self.cur.execute(''' - SELECT MAX(time) FROM logs - WHERE (%s) - AND kind NOT IN (%d, %d) - ''' % (where_sql, constants.KIND_STATUS, constants.KIND_GCSTATUS)) - - results = self.cur.fetchone() - if results is not None: - result = results[0] - else: - result = None - return result - - def get_room_last_message_time(self, jid): - """ - Return FASTLY last time (in seconds since EPOCH) for which we had logs - for that room from rooms_last_message_time table - """ - try: - jid_id = self.get_jid_id(jid, 'ROOM') - except exceptions.PysqliteOperationalError, e: - # Error trying to create a new jid_id. This means there is no log - return None - where_sql = 'jid_id = %s' % jid_id - self.cur.execute(''' - SELECT time FROM rooms_last_message_time - WHERE (%s) - ''' % (where_sql)) - - results = self.cur.fetchone() - if results is not None: - result = results[0] - else: - result = None - return result - - def set_room_last_message_time(self, jid, time): - """ - Set last time (in seconds since EPOCH) for which we had logs for that - room in rooms_last_message_time table - """ - jid_id = self.get_jid_id(jid, 'ROOM') - # jid_id is unique in this table, create or update : - sql = 'REPLACE INTO rooms_last_message_time VALUES (%d, %d)' % \ - (jid_id, time) - self.simple_commit(sql) - - def _build_contact_where(self, account, jid): - """ - Build the where clause for a jid, including metacontacts jid(s) if any - """ - where_sql = '' - # will return empty list if jid is not associated with - # any metacontacts - family = gajim.contacts.get_metacontacts_family(account, jid) - if family: - for user in family: - try: - jid_id = self.get_jid_id(user['jid']) - except exceptions.PysqliteOperationalError, e: - continue - where_sql += 'jid_id = %s' % jid_id - if user != family[-1]: - where_sql += ' OR ' - else: # if jid was not associated with metacontacts - jid_id = self.get_jid_id(jid) - where_sql = 'jid_id = %s' % jid_id - return where_sql - - def save_transport_type(self, jid, type_): - """ - Save the type of the transport in DB - """ - type_id = self.convert_human_transport_type_to_db_api_values(type_) - if not type_id: - # unknown type - return - self.cur.execute( - 'SELECT type from transports_cache WHERE transport = "%s"' % jid) - results = self.cur.fetchall() - if results: - result = results[0][0] - if result == type_id: - return - sql = 'UPDATE transports_cache SET type = %d WHERE transport = "%s"' %\ - (type_id, jid) - self.simple_commit(sql) - return - sql = 'INSERT INTO transports_cache VALUES ("%s", %d)' % (jid, type_id) - self.simple_commit(sql) - - def get_transports_type(self): - """ - Return all the type of the transports in DB - """ - self.cur.execute( - 'SELECT * from transports_cache') - results = self.cur.fetchall() - if not results: - return {} - answer = {} - for result in results: - answer[result[0]] = self.convert_api_values_to_human_transport_type( - result[1]) - return answer - - # A longer note here: - # The database contains a blob field. Pysqlite seems to need special care for - # such fields. - # When storing, we need to convert string into buffer object (1). - # When retrieving, we need to convert it back to a string to decompress it. - # (2) - # GzipFile needs a file-like object, StringIO emulates file for plain strings - def iter_caps_data(self): - """ - Iterate over caps cache data stored in the database - - The iterator values are pairs of (node, ver, ext, identities, features): - identities == {'category':'foo', 'type':'bar', 'name':'boo'}, - features being a list of feature namespaces. - """ - # get data from table - # the data field contains binary object (gzipped data), this is a hack - # to get that data without trying to convert it to unicode - try: - self.cur.execute('SELECT hash_method, hash, data FROM caps_cache;') - except sqlite.OperationalError: - # might happen when there's no caps_cache table yet - # -- there's no data to read anyway then - return - - # list of corrupted entries that will be removed - to_be_removed = [] - for hash_method, hash_, data in self.cur: - # for each row: unpack the data field - # (format: (category, type, name, category, type, name, ... - # ..., 'FEAT', feature1, feature2, ...).join(' ')) - # NOTE: if there's a need to do more gzip, put that to a function - try: - data = GzipFile(fileobj=StringIO(str(data))).read().decode( - 'utf-8').split('\0') - except IOError: - # This data is corrupted. It probably contains non-ascii chars - to_be_removed.append((hash_method, hash_)) - continue - i = 0 - identities = list() - features = list() - while i < (len(data) - 3) and data[i] != 'FEAT': - category = data[i] - type_ = data[i + 1] - lang = data[i + 2] - name = data[i + 3] - identities.append({'category': category, 'type': type_, - 'xml:lang': lang, 'name': name}) - i += 4 - i+=1 - while i < len(data): - features.append(data[i]) - i += 1 - - # yield the row - yield hash_method, hash_, identities, features - for hash_method, hash_ in to_be_removed: - sql = '''DELETE FROM caps_cache WHERE hash_method = "%s" AND - hash = "%s"''' % (hash_method, hash_) - self.simple_commit(sql) - - def add_caps_entry(self, hash_method, hash_, identities, features): - data = [] - for identity in identities: - # there is no FEAT category - if identity['category'] == 'FEAT': - return - data.extend((identity.get('category'), identity.get('type', ''), - identity.get('xml:lang', ''), identity.get('name', ''))) - data.append('FEAT') - data.extend(features) - data = '\0'.join(data) - # if there's a need to do more gzip, put that to a function - string = StringIO() - gzip = GzipFile(fileobj=string, mode='w') - data = data.encode('utf-8') # the gzip module can't handle unicode objects - gzip.write(data) - gzip.close() - data = string.getvalue() - self.cur.execute(''' - INSERT INTO caps_cache ( hash_method, hash, data, last_seen ) - VALUES (?, ?, ?, ?); - ''', (hash_method, hash_, buffer(data), int(time.time()))) - # (1) -- note above - try: - self.con.commit() - except sqlite.OperationalError, e: - print >> sys.stderr, str(e) - - def update_caps_time(self, method, hash_): - sql = '''UPDATE caps_cache SET last_seen = %d - WHERE hash_method = "%s" and hash = "%s"''' % \ - (int(time.time()), method, hash_) - self.simple_commit(sql) - - def clean_caps_table(self): - """ - Remove caps which was not seen for 3 months - """ - sql = '''DELETE FROM caps_cache WHERE last_seen < %d''' % \ - int(time.time() - 3*30*24*3600) - self.simple_commit(sql) - - def replace_roster(self, account_name, roster_version, roster): - """ - Replace current roster in DB by a new one - - accout_name is the name of the account to change. - roster_version is the version of the new roster. - roster is the new version. - """ - # First we must reset roster_version value to ensure that the server - # sends back all the roster at the next connexion if the replacement - # didn't work properly. - gajim.config.set_per('accounts', account_name, 'roster_version', '') - - account_jid = gajim.get_jid_from_account(account_name) - account_jid_id = self.get_jid_id(account_jid) - - # Delete old roster - self.remove_roster(account_jid) - - # Fill roster tables with the new roster - for jid in roster: - self.add_or_update_contact(account_jid, jid, roster[jid]['name'], - roster[jid]['subscription'], roster[jid]['ask'], - roster[jid]['groups']) - - # At this point, we are sure the replacement works properly so we can - # set the new roster_version value. - gajim.config.set_per('accounts', account_name, 'roster_version', - roster_version) - - def del_contact(self, account_jid, jid): - """ - Remove jid from account_jid roster - """ - try: - account_jid_id = self.get_jid_id(account_jid) - jid_id = self.get_jid_id(jid) - except exceptions.PysqliteOperationalError, e: - raise exceptions.PysqliteOperationalError(str(e)) - self.cur.execute( - 'DELETE FROM roster_group WHERE account_jid_id=? AND jid_id=?', - (account_jid_id, jid_id)) - self.cur.execute( - 'DELETE FROM roster_entry WHERE account_jid_id=? AND jid_id=?', - (account_jid_id, jid_id)) - self.con.commit() - - def add_or_update_contact(self, account_jid, jid, name, sub, ask, groups): - """ - Add or update a contact from account_jid roster - """ - if sub == 'remove': - self.del_contact(account_jid, jid) - return - - try: - account_jid_id = self.get_jid_id(account_jid) - jid_id = self.get_jid_id(jid) - except exceptions.PysqliteOperationalError, e: - raise exceptions.PysqliteOperationalError(str(e)) - - # Update groups information - # First we delete all previous groups information - self.cur.execute( - 'DELETE FROM roster_group WHERE account_jid_id=? AND jid_id=?', - (account_jid_id, jid_id)) - # Then we add all new groups information - for group in groups: - self.cur.execute('INSERT INTO roster_group VALUES(?, ?, ?)', - (account_jid_id, jid_id, group)) - - if name is None: - name = '' - - self.cur.execute('REPLACE INTO roster_entry VALUES(?, ?, ?, ?, ?)', - (account_jid_id, jid_id, name, - self.convert_human_subscription_values_to_db_api_values(sub), - bool(ask))) - self.con.commit() - - def get_roster(self, account_jid): - """ - Return the accound_jid roster in NonBlockingRoster format - """ - data = {} - account_jid_id = self.get_jid_id(account_jid) - - # First we fill data with roster_entry informations - self.cur.execute(''' - SELECT j.jid, re.jid_id, re.name, re.subscription, re.ask - FROM roster_entry re, jids j - WHERE re.account_jid_id=? AND j.jid_id=re.jid_id''', (account_jid_id,)) - for jid, jid_id, name, subscription, ask in self.cur: - data[jid] = {} - if name: - data[jid]['name'] = name - else: - data[jid]['name'] = None - data[jid]['subscription'] = \ - self.convert_db_api_values_to_human_subscription_values( - subscription) - data[jid]['groups'] = [] - data[jid]['resources'] = {} - if ask: - data[jid]['ask'] = 'subscribe' - else: - data[jid]['ask'] = None - data[jid]['id'] = jid_id - - # Then we add group for roster entries - for jid in data: - self.cur.execute(''' - SELECT group_name FROM roster_group - WHERE account_jid_id=? AND jid_id=?''', - (account_jid_id, data[jid]['id'])) - for (group_name,) in self.cur: - data[jid]['groups'].append(group_name) - del data[jid]['id'] - - return data - - def remove_roster(self, account_jid): - """ - Remove all entry from account_jid roster - """ - account_jid_id = self.get_jid_id(account_jid) - - self.cur.execute('DELETE FROM roster_entry WHERE account_jid_id=?', - (account_jid_id,)) - self.cur.execute('DELETE FROM roster_group WHERE account_jid_id=?', - (account_jid_id,)) - self.con.commit() - -# vim: se ts=3: + def __init__(self): + self.jids_already_in = [] # holds jids that we already have in DB + self.con = None + + if not os.path.exists(LOG_DB_PATH): + # this can happen only the first time (the time we create the db) + # db is not created here but in src/common/checks_paths.py + return + self.init_vars() + if not os.path.exists(CACHE_DB_PATH): + # this can happen cache database is not present when gajim is launched + # db will be created in src/common/checks_paths.py + return + self.attach_cache_database() + + def close_db(self): + if self.con: + self.con.close() + self.con = None + self.cur = None + + def open_db(self): + self.close_db() + + # FIXME: sqlite3_open wants UTF8 strings. So a path with + # non-ascii chars doesn't work. See #2812 and + # http://lists.initd.org/pipermail/pysqlite/2005-August/000134.html + back = os.getcwd() + os.chdir(LOG_DB_FOLDER) + + # if locked, wait up to 20 sec to unlock + # before raise (hopefully should be enough) + + self.con = sqlite.connect(LOG_DB_FILE, timeout=20.0, + isolation_level='IMMEDIATE') + os.chdir(back) + self.cur = self.con.cursor() + self.set_synchronous(False) + + def attach_cache_database(self): + try: + self.cur.execute("ATTACH DATABASE '%s' AS cache" % CACHE_DB_PATH) + except sqlite.Error, e: + gajim.log.debug("Failed to attach cache database: %s" % str(e)) + + def set_synchronous(self, sync): + try: + if sync: + self.cur.execute("PRAGMA synchronous = NORMAL") + else: + self.cur.execute("PRAGMA synchronous = OFF") + except sqlite.Error, e: + gajim.log.debug("Failed to set_synchronous(%s): %s" % (sync, str(e))) + + def init_vars(self): + self.open_db() + self.get_jids_already_in_db() + + def simple_commit(self, sql_to_commit): + """ + Helper to commit + """ + self.cur.execute(sql_to_commit) + try: + self.con.commit() + except sqlite.OperationalError, e: + print >> sys.stderr, str(e) + + def get_jids_already_in_db(self): + try: + self.cur.execute('SELECT jid FROM jids') + # list of tupples: [(u'aaa@bbb',), (u'cc@dd',)] + rows = self.cur.fetchall() + except sqlite.DatabaseError: + raise exceptions.DatabaseMalformed + self.jids_already_in = [] + for row in rows: + # row[0] is first item of row (the only result here, the jid) + if row[0] == '': + # malformed jid, ignore line + pass + else: + self.jids_already_in.append(row[0]) + + def get_jids_in_db(self): + return self.jids_already_in + + def jid_is_from_pm(self, jid): + """ + If jid is gajim@conf/nkour it's likely a pm one, how we know gajim@conf + is not a normal guy and nkour is not his resource? we ask if gajim@conf + is already in jids (with type room jid) this fails if user disables + logging for room and only enables for pm (so higly unlikely) and if we + fail we do not go chaos (user will see the first pm as if it was message + in room's public chat) and after that all okay + """ + if jid.find('/') > -1: + possible_room_jid = jid.split('/', 1)[0] + return self.jid_is_room_jid(possible_room_jid) + else: + # it's not a full jid, so it's not a pm one + return False + + def jid_is_room_jid(self, jid): + self.cur.execute('SELECT jid_id FROM jids WHERE jid=? AND type=?', + (jid, constants.JID_ROOM_TYPE)) + row = self.cur.fetchone() + if row is None: + return False + else: + return True + + def get_jid_id(self, jid, typestr=None): + """ + jids table has jid and jid_id logs table has log_id, jid_id, + contact_name, time, kind, show, message so to ask logs we need jid_id + that matches our jid in jids table this method wants jid and returns the + jid_id for later sql-ing on logs typestr can be 'ROOM' or anything else + depending on the type of JID and is only needed to be specified when the + JID is new in DB + """ + if jid.find('/') != -1: # if it has a / + jid_is_from_pm = self.jid_is_from_pm(jid) + if not jid_is_from_pm: # it's normal jid with resource + jid = jid.split('/', 1)[0] # remove the resource + if jid in self.jids_already_in: # we already have jids in DB + self.cur.execute('SELECT jid_id FROM jids WHERE jid=?', [jid]) + row = self.cur.fetchone() + if row: + return row[0] + # oh! a new jid :), we add it now + if typestr == 'ROOM': + typ = constants.JID_ROOM_TYPE + else: + typ = constants.JID_NORMAL_TYPE + try: + self.cur.execute('INSERT INTO jids (jid, type) VALUES (?, ?)', (jid, + typ)) + self.con.commit() + except sqlite.IntegrityError, e: + # Jid already in DB, maybe added by another instance. re-read DB + self.get_jids_already_in_db() + return self.get_jid_id(jid, typestr) + except sqlite.OperationalError, e: + raise exceptions.PysqliteOperationalError(str(e)) + jid_id = self.cur.lastrowid + self.jids_already_in.append(jid) + return jid_id + + def convert_human_values_to_db_api_values(self, kind, show): + """ + Convert from string style to constant ints for db + """ + if kind == 'status': + kind_col = constants.KIND_STATUS + elif kind == 'gcstatus': + kind_col = constants.KIND_GCSTATUS + elif kind == 'gc_msg': + kind_col = constants.KIND_GC_MSG + elif kind == 'single_msg_recv': + kind_col = constants.KIND_SINGLE_MSG_RECV + elif kind == 'single_msg_sent': + kind_col = constants.KIND_SINGLE_MSG_SENT + elif kind == 'chat_msg_recv': + kind_col = constants.KIND_CHAT_MSG_RECV + elif kind == 'chat_msg_sent': + kind_col = constants.KIND_CHAT_MSG_SENT + elif kind == 'error': + kind_col = constants.KIND_ERROR + + if show == 'online': + show_col = constants.SHOW_ONLINE + elif show == 'chat': + show_col = constants.SHOW_CHAT + elif show == 'away': + show_col = constants.SHOW_AWAY + elif show == 'xa': + show_col = constants.SHOW_XA + elif show == 'dnd': + show_col = constants.SHOW_DND + elif show == 'offline': + show_col = constants.SHOW_OFFLINE + elif show is None: + show_col = None + else: # invisible in GC when someone goes invisible + # it's a RFC violation .... but we should not crash + show_col = 'UNKNOWN' + + return kind_col, show_col + + def convert_human_transport_type_to_db_api_values(self, type_): + """ + Convert from string style to constant ints for db + """ + if type_ == 'aim': + return constants.TYPE_AIM + if type_ == 'gadu-gadu': + return constants.TYPE_GG + if type_ == 'http-ws': + return constants.TYPE_HTTP_WS + if type_ == 'icq': + return constants.TYPE_ICQ + if type_ == 'msn': + return constants.TYPE_MSN + if type_ == 'qq': + return constants.TYPE_QQ + if type_ == 'sms': + return constants.TYPE_SMS + if type_ == 'smtp': + return constants.TYPE_SMTP + if type_ in ('tlen', 'x-tlen'): + return constants.TYPE_TLEN + if type_ == 'yahoo': + return constants.TYPE_YAHOO + if type_ == 'newmail': + return constants.TYPE_NEWMAIL + if type_ == 'rss': + return constants.TYPE_RSS + if type_ == 'weather': + return constants.TYPE_WEATHER + if type_ == 'mrim': + return constants.TYPE_MRIM + return None + + def convert_api_values_to_human_transport_type(self, type_id): + """ + Convert from constant ints for db to string style + """ + if type_id == constants.TYPE_AIM: + return 'aim' + if type_id == constants.TYPE_GG: + return 'gadu-gadu' + if type_id == constants.TYPE_HTTP_WS: + return 'http-ws' + if type_id == constants.TYPE_ICQ: + return 'icq' + if type_id == constants.TYPE_MSN: + return 'msn' + if type_id == constants.TYPE_QQ: + return 'qq' + if type_id == constants.TYPE_SMS: + return 'sms' + if type_id == constants.TYPE_SMTP: + return 'smtp' + if type_id == constants.TYPE_TLEN: + return 'tlen' + if type_id == constants.TYPE_YAHOO: + return 'yahoo' + if type_id == constants.TYPE_NEWMAIL: + return 'newmail' + if type_id == constants.TYPE_RSS: + return 'rss' + if type_id == constants.TYPE_WEATHER: + return 'weather' + if type_id == constants.TYPE_MRIM: + return 'mrim' + + def convert_human_subscription_values_to_db_api_values(self, sub): + """ + Convert from string style to constant ints for db + """ + if sub == 'none': + return constants.SUBSCRIPTION_NONE + if sub == 'to': + return constants.SUBSCRIPTION_TO + if sub == 'from': + return constants.SUBSCRIPTION_FROM + if sub == 'both': + return constants.SUBSCRIPTION_BOTH + + def convert_db_api_values_to_human_subscription_values(self, sub): + """ + Convert from constant ints for db to string style + """ + if sub == constants.SUBSCRIPTION_NONE: + return 'none' + if sub == constants.SUBSCRIPTION_TO: + return 'to' + if sub == constants.SUBSCRIPTION_FROM: + return 'from' + if sub == constants.SUBSCRIPTION_BOTH: + return 'both' + + def commit_to_db(self, values, write_unread=False): + sql = '''INSERT INTO logs (jid_id, contact_name, time, kind, show, + message, subject) VALUES (?, ?, ?, ?, ?, ?, ?)''' + try: + self.cur.execute(sql, values) + except sqlite.DatabaseError: + raise exceptions.DatabaseMalformed + except sqlite.OperationalError, e: + raise exceptions.PysqliteOperationalError(str(e)) + message_id = None + try: + self.con.commit() + if write_unread: + message_id = self.cur.lastrowid + except sqlite.OperationalError, e: + print >> sys.stderr, str(e) + if message_id: + self.insert_unread_events(message_id, values[0]) + return message_id + + def insert_unread_events(self, message_id, jid_id): + """ + Add unread message with id: message_id + """ + sql = 'INSERT INTO unread_messages VALUES (%d, %d, 0)' % (message_id, + jid_id) + self.simple_commit(sql) + + def set_read_messages(self, message_ids): + """ + Mark all messages with ids in message_ids as read + """ + ids = ','.join([str(i) for i in message_ids]) + sql = 'DELETE FROM unread_messages WHERE message_id IN (%s)' % ids + self.simple_commit(sql) + + def set_shown_unread_msgs(self, msg_id): + """ + Mark unread message as shown un GUI + """ + sql = 'UPDATE unread_messages SET shown = 1 where message_id = %s' % \ + msg_id + self.simple_commit(sql) + + def reset_shown_unread_messages(self): + """ + Set shown field to False in unread_messages table + """ + sql = 'UPDATE unread_messages SET shown = 0' + self.simple_commit(sql) + + def get_unread_msgs(self): + """ + Get all unread messages + """ + all_messages = [] + try: + self.cur.execute( + 'SELECT message_id, shown from unread_messages') + results = self.cur.fetchall() + except Exception: + pass + for message in results: + msg_id = message[0] + shown = message[1] + # here we get infos for that message, and related jid from jids table + # do NOT change order of SELECTed things, unless you change function(s) + # that called this function + self.cur.execute(''' + SELECT logs.log_line_id, logs.message, logs.time, logs.subject, + jids.jid + FROM logs, jids + WHERE logs.log_line_id = %d AND logs.jid_id = jids.jid_id + ''' % msg_id + ) + results = self.cur.fetchall() + if len(results) == 0: + # Log line is no more in logs table. remove it from unread_messages + self.set_read_messages([msg_id]) + continue + all_messages.append(results[0] + (shown,)) + return all_messages + + def write(self, kind, jid, message=None, show=None, tim=None, subject=None): + """ + Write a row (status, gcstatus, message etc) to logs database + + kind can be status, gcstatus, gc_msg, (we only recv for those 3), + single_msg_recv, chat_msg_recv, chat_msg_sent, single_msg_sent we cannot + know if it is pm or normal chat message, we try to guess see + jid_is_from_pm() + + We analyze jid and store it as follows: + jids.jid text column will hold JID if TC-related, room_jid if GC-related, + ROOM_JID/nick if pm-related. + """ + + if self.jids_already_in == []: # only happens if we just created the db + self.open_db() + + contact_name_col = None # holds nickname for kinds gcstatus, gc_msg + # message holds the message unless kind is status or gcstatus, + # then it holds status message + message_col = message + subject_col = subject + if tim: + time_col = int(float(time.mktime(tim))) + else: + time_col = int(float(time.time())) + + kind_col, show_col = self.convert_human_values_to_db_api_values(kind, + show) + + write_unread = False + + # now we may have need to do extra care for some values in columns + if kind == 'status': # we store (not None) time, jid, show, msg + # status for roster items + try: + jid_id = self.get_jid_id(jid) + except exceptions.PysqliteOperationalError, e: + raise exceptions.PysqliteOperationalError(str(e)) + if show is None: # show is None (xmpp), but we say that 'online' + show_col = constants.SHOW_ONLINE + + elif kind == 'gcstatus': + # status in ROOM (for pm status see status) + if show is None: # show is None (xmpp), but we say that 'online' + show_col = constants.SHOW_ONLINE + jid, nick = jid.split('/', 1) + try: + # re-get jid_id for the new jid + jid_id = self.get_jid_id(jid, 'ROOM') + except exceptions.PysqliteOperationalError, e: + raise exceptions.PysqliteOperationalError(str(e)) + contact_name_col = nick + + elif kind == 'gc_msg': + if jid.find('/') != -1: # if it has a / + jid, nick = jid.split('/', 1) + else: + # it's server message f.e. error message + # when user tries to ban someone but he's not allowed to + nick = None + try: + # re-get jid_id for the new jid + jid_id = self.get_jid_id(jid, 'ROOM') + except exceptions.PysqliteOperationalError, e: + raise exceptions.PysqliteOperationalError(str(e)) + contact_name_col = nick + else: + try: + jid_id = self.get_jid_id(jid) + except exceptions.PysqliteOperationalError, e: + raise exceptions.PysqliteOperationalError(str(e)) + if kind == 'chat_msg_recv': + if not self.jid_is_from_pm(jid): + # Save in unread table only if it's not a pm + write_unread = True + + if show_col == 'UNKNOWN': # unknown show, do not log + return + + values = (jid_id, contact_name_col, time_col, kind_col, show_col, + message_col, subject_col) + return self.commit_to_db(values, write_unread) + + def get_last_conversation_lines(self, jid, restore_how_many_rows, + pending_how_many, timeout, account): + """ + Accept how many rows to restore and when to time them out (in minutes) + (mark them as too old) and number of messages that are in queue and are + already logged but pending to be viewed, returns a list of tupples + containg time, kind, message, list with empty tupple if nothing found to + meet our demands + """ + try: + self.get_jid_id(jid) + except exceptions.PysqliteOperationalError, e: + # Error trying to create a new jid_id. This means there is no log + return [] + where_sql = self._build_contact_where(account, jid) + + now = int(float(time.time())) + timed_out = now - (timeout * 60) # before that they are too old + # so if we ask last 5 lines and we have 2 pending we get + # 3 - 8 (we avoid the last 2 lines but we still return 5 asked) + try: + self.cur.execute(''' + SELECT time, kind, message FROM logs + WHERE (%s) AND kind IN (%d, %d, %d, %d, %d) AND time > %d + ORDER BY time DESC LIMIT %d OFFSET %d + ''' % (where_sql, constants.KIND_SINGLE_MSG_RECV, + constants.KIND_CHAT_MSG_RECV, constants.KIND_SINGLE_MSG_SENT, + constants.KIND_CHAT_MSG_SENT, constants.KIND_ERROR, + timed_out, restore_how_many_rows, pending_how_many) + ) + + results = self.cur.fetchall() + except sqlite.DatabaseError: + raise exceptions.DatabaseMalformed + results.reverse() + return results + + def get_unix_time_from_date(self, year, month, day): + # year (fe 2005), month (fe 11), day (fe 25) + # returns time in seconds for the second that starts that date since epoch + # gimme unixtime from year month day: + d = datetime.date(year, month, day) + local_time = d.timetuple() # time tupple (compat with time.localtime()) + # we have time since epoch baby :) + start_of_day = int(time.mktime(local_time)) + return start_of_day + + def get_conversation_for_date(self, jid, year, month, day, account): + """ + Return contact_name, time, kind, show, message, subject + + For each row in a list of tupples, returns list with empty tupple if we + found nothing to meet our demands + """ + try: + self.get_jid_id(jid) + except exceptions.PysqliteOperationalError, e: + # Error trying to create a new jid_id. This means there is no log + return [] + where_sql = self._build_contact_where(account, jid) + + start_of_day = self.get_unix_time_from_date(year, month, day) + seconds_in_a_day = 86400 # 60 * 60 * 24 + last_second_of_day = start_of_day + seconds_in_a_day - 1 + + self.cur.execute(''' + SELECT contact_name, time, kind, show, message, subject FROM logs + WHERE (%s) + AND time BETWEEN %d AND %d + ORDER BY time + ''' % (where_sql, start_of_day, last_second_of_day)) + + results = self.cur.fetchall() + return results + + def get_search_results_for_query(self, jid, query, account): + """ + Returns contact_name, time, kind, show, message + + For each row in a list of tupples, returns list with empty tupple if we + found nothing to meet our demands + """ + try: + self.get_jid_id(jid) + except exceptions.PysqliteOperationalError, e: + # Error trying to create a new jid_id. This means there is no log + return [] + + if False: # query.startswith('SELECT '): # it's SQL query (FIXME) + try: + self.cur.execute(query) + except sqlite.OperationalError, e: + results = [('', '', '', '', str(e))] + return results + + else: # user just typed something, we search in message column + where_sql = self._build_contact_where(account, jid) + like_sql = '%' + query.replace("'", "''") + '%' + self.cur.execute(''' + SELECT contact_name, time, kind, show, message, subject FROM logs + WHERE (%s) AND message LIKE '%s' + ORDER BY time + ''' % (where_sql, like_sql)) + + results = self.cur.fetchall() + return results + + def get_days_with_logs(self, jid, year, month, max_day, account): + """ + Return the list of days that have logs (not status messages) + """ + try: + self.get_jid_id(jid) + except exceptions.PysqliteOperationalError, e: + # Error trying to create a new jid_id. This means there is no log + return [] + days_with_logs = [] + where_sql = self._build_contact_where(account, jid) + + # First select all date of month whith logs we want + start_of_month = self.get_unix_time_from_date(year, month, 1) + seconds_in_a_day = 86400 # 60 * 60 * 24 + last_second_of_month = start_of_month + (seconds_in_a_day * max_day) - 1 + + # Select times and 'floor' them to time 0:00 + # (by dividing, they are integers) + # and take only one of the same values (distinct) + # Now we have timestamps of time 0:00 of every day with logs + self.cur.execute(''' + SELECT DISTINCT time/(86400)*86400 FROM logs + WHERE (%s) + AND time BETWEEN %d AND %d + AND kind NOT IN (%d, %d) + ORDER BY time + ''' % (where_sql, start_of_month, last_second_of_month, + constants.KIND_STATUS, constants.KIND_GCSTATUS)) + result = self.cur.fetchall() + + # convert timestamps to day of month + for line in result: + days_with_logs[0:0]=[time.gmtime(line[0])[2]] + + return days_with_logs + + def get_last_date_that_has_logs(self, jid, account=None, is_room=False): + """ + Return last time (in seconds since EPOCH) for which we had logs + (excluding statuses) + """ + where_sql = '' + if not is_room: + where_sql = self._build_contact_where(account, jid) + else: + try: + jid_id = self.get_jid_id(jid, 'ROOM') + except exceptions.PysqliteOperationalError, e: + # Error trying to create a new jid_id. This means there is no log + return None + where_sql = 'jid_id = %s' % jid_id + self.cur.execute(''' + SELECT MAX(time) FROM logs + WHERE (%s) + AND kind NOT IN (%d, %d) + ''' % (where_sql, constants.KIND_STATUS, constants.KIND_GCSTATUS)) + + results = self.cur.fetchone() + if results is not None: + result = results[0] + else: + result = None + return result + + def get_room_last_message_time(self, jid): + """ + Return FASTLY last time (in seconds since EPOCH) for which we had logs + for that room from rooms_last_message_time table + """ + try: + jid_id = self.get_jid_id(jid, 'ROOM') + except exceptions.PysqliteOperationalError, e: + # Error trying to create a new jid_id. This means there is no log + return None + where_sql = 'jid_id = %s' % jid_id + self.cur.execute(''' + SELECT time FROM rooms_last_message_time + WHERE (%s) + ''' % (where_sql)) + + results = self.cur.fetchone() + if results is not None: + result = results[0] + else: + result = None + return result + + def set_room_last_message_time(self, jid, time): + """ + Set last time (in seconds since EPOCH) for which we had logs for that + room in rooms_last_message_time table + """ + jid_id = self.get_jid_id(jid, 'ROOM') + # jid_id is unique in this table, create or update : + sql = 'REPLACE INTO rooms_last_message_time VALUES (%d, %d)' % \ + (jid_id, time) + self.simple_commit(sql) + + def _build_contact_where(self, account, jid): + """ + Build the where clause for a jid, including metacontacts jid(s) if any + """ + where_sql = '' + # will return empty list if jid is not associated with + # any metacontacts + family = gajim.contacts.get_metacontacts_family(account, jid) + if family: + for user in family: + try: + jid_id = self.get_jid_id(user['jid']) + except exceptions.PysqliteOperationalError, e: + continue + where_sql += 'jid_id = %s' % jid_id + if user != family[-1]: + where_sql += ' OR ' + else: # if jid was not associated with metacontacts + jid_id = self.get_jid_id(jid) + where_sql = 'jid_id = %s' % jid_id + return where_sql + + def save_transport_type(self, jid, type_): + """ + Save the type of the transport in DB + """ + type_id = self.convert_human_transport_type_to_db_api_values(type_) + if not type_id: + # unknown type + return + self.cur.execute( + 'SELECT type from transports_cache WHERE transport = "%s"' % jid) + results = self.cur.fetchall() + if results: + result = results[0][0] + if result == type_id: + return + sql = 'UPDATE transports_cache SET type = %d WHERE transport = "%s"' %\ + (type_id, jid) + self.simple_commit(sql) + return + sql = 'INSERT INTO transports_cache VALUES ("%s", %d)' % (jid, type_id) + self.simple_commit(sql) + + def get_transports_type(self): + """ + Return all the type of the transports in DB + """ + self.cur.execute( + 'SELECT * from transports_cache') + results = self.cur.fetchall() + if not results: + return {} + answer = {} + for result in results: + answer[result[0]] = self.convert_api_values_to_human_transport_type( + result[1]) + return answer + + # A longer note here: + # The database contains a blob field. Pysqlite seems to need special care for + # such fields. + # When storing, we need to convert string into buffer object (1). + # When retrieving, we need to convert it back to a string to decompress it. + # (2) + # GzipFile needs a file-like object, StringIO emulates file for plain strings + def iter_caps_data(self): + """ + Iterate over caps cache data stored in the database + + The iterator values are pairs of (node, ver, ext, identities, features): + identities == {'category':'foo', 'type':'bar', 'name':'boo'}, + features being a list of feature namespaces. + """ + # get data from table + # the data field contains binary object (gzipped data), this is a hack + # to get that data without trying to convert it to unicode + try: + self.cur.execute('SELECT hash_method, hash, data FROM caps_cache;') + except sqlite.OperationalError: + # might happen when there's no caps_cache table yet + # -- there's no data to read anyway then + return + + # list of corrupted entries that will be removed + to_be_removed = [] + for hash_method, hash_, data in self.cur: + # for each row: unpack the data field + # (format: (category, type, name, category, type, name, ... + # ..., 'FEAT', feature1, feature2, ...).join(' ')) + # NOTE: if there's a need to do more gzip, put that to a function + try: + data = GzipFile(fileobj=StringIO(str(data))).read().decode( + 'utf-8').split('\0') + except IOError: + # This data is corrupted. It probably contains non-ascii chars + to_be_removed.append((hash_method, hash_)) + continue + i = 0 + identities = list() + features = list() + while i < (len(data) - 3) and data[i] != 'FEAT': + category = data[i] + type_ = data[i + 1] + lang = data[i + 2] + name = data[i + 3] + identities.append({'category': category, 'type': type_, + 'xml:lang': lang, 'name': name}) + i += 4 + i+=1 + while i < len(data): + features.append(data[i]) + i += 1 + + # yield the row + yield hash_method, hash_, identities, features + for hash_method, hash_ in to_be_removed: + sql = '''DELETE FROM caps_cache WHERE hash_method = "%s" AND + hash = "%s"''' % (hash_method, hash_) + self.simple_commit(sql) + + def add_caps_entry(self, hash_method, hash_, identities, features): + data = [] + for identity in identities: + # there is no FEAT category + if identity['category'] == 'FEAT': + return + data.extend((identity.get('category'), identity.get('type', ''), + identity.get('xml:lang', ''), identity.get('name', ''))) + data.append('FEAT') + data.extend(features) + data = '\0'.join(data) + # if there's a need to do more gzip, put that to a function + string = StringIO() + gzip = GzipFile(fileobj=string, mode='w') + data = data.encode('utf-8') # the gzip module can't handle unicode objects + gzip.write(data) + gzip.close() + data = string.getvalue() + self.cur.execute(''' + INSERT INTO caps_cache ( hash_method, hash, data, last_seen ) + VALUES (?, ?, ?, ?); + ''', (hash_method, hash_, buffer(data), int(time.time()))) + # (1) -- note above + try: + self.con.commit() + except sqlite.OperationalError, e: + print >> sys.stderr, str(e) + + def update_caps_time(self, method, hash_): + sql = '''UPDATE caps_cache SET last_seen = %d + WHERE hash_method = "%s" and hash = "%s"''' % \ + (int(time.time()), method, hash_) + self.simple_commit(sql) + + def clean_caps_table(self): + """ + Remove caps which was not seen for 3 months + """ + sql = '''DELETE FROM caps_cache WHERE last_seen < %d''' % \ + int(time.time() - 3*30*24*3600) + self.simple_commit(sql) + + def replace_roster(self, account_name, roster_version, roster): + """ + Replace current roster in DB by a new one + + accout_name is the name of the account to change. + roster_version is the version of the new roster. + roster is the new version. + """ + # First we must reset roster_version value to ensure that the server + # sends back all the roster at the next connexion if the replacement + # didn't work properly. + gajim.config.set_per('accounts', account_name, 'roster_version', '') + + account_jid = gajim.get_jid_from_account(account_name) + account_jid_id = self.get_jid_id(account_jid) + + # Delete old roster + self.remove_roster(account_jid) + + # Fill roster tables with the new roster + for jid in roster: + self.add_or_update_contact(account_jid, jid, roster[jid]['name'], + roster[jid]['subscription'], roster[jid]['ask'], + roster[jid]['groups']) + + # At this point, we are sure the replacement works properly so we can + # set the new roster_version value. + gajim.config.set_per('accounts', account_name, 'roster_version', + roster_version) + + def del_contact(self, account_jid, jid): + """ + Remove jid from account_jid roster + """ + try: + account_jid_id = self.get_jid_id(account_jid) + jid_id = self.get_jid_id(jid) + except exceptions.PysqliteOperationalError, e: + raise exceptions.PysqliteOperationalError(str(e)) + self.cur.execute( + 'DELETE FROM roster_group WHERE account_jid_id=? AND jid_id=?', + (account_jid_id, jid_id)) + self.cur.execute( + 'DELETE FROM roster_entry WHERE account_jid_id=? AND jid_id=?', + (account_jid_id, jid_id)) + self.con.commit() + + def add_or_update_contact(self, account_jid, jid, name, sub, ask, groups): + """ + Add or update a contact from account_jid roster + """ + if sub == 'remove': + self.del_contact(account_jid, jid) + return + + try: + account_jid_id = self.get_jid_id(account_jid) + jid_id = self.get_jid_id(jid) + except exceptions.PysqliteOperationalError, e: + raise exceptions.PysqliteOperationalError(str(e)) + + # Update groups information + # First we delete all previous groups information + self.cur.execute( + 'DELETE FROM roster_group WHERE account_jid_id=? AND jid_id=?', + (account_jid_id, jid_id)) + # Then we add all new groups information + for group in groups: + self.cur.execute('INSERT INTO roster_group VALUES(?, ?, ?)', + (account_jid_id, jid_id, group)) + + if name is None: + name = '' + + self.cur.execute('REPLACE INTO roster_entry VALUES(?, ?, ?, ?, ?)', + (account_jid_id, jid_id, name, + self.convert_human_subscription_values_to_db_api_values(sub), + bool(ask))) + self.con.commit() + + def get_roster(self, account_jid): + """ + Return the accound_jid roster in NonBlockingRoster format + """ + data = {} + account_jid_id = self.get_jid_id(account_jid) + + # First we fill data with roster_entry informations + self.cur.execute(''' + SELECT j.jid, re.jid_id, re.name, re.subscription, re.ask + FROM roster_entry re, jids j + WHERE re.account_jid_id=? AND j.jid_id=re.jid_id''', (account_jid_id,)) + for jid, jid_id, name, subscription, ask in self.cur: + data[jid] = {} + if name: + data[jid]['name'] = name + else: + data[jid]['name'] = None + data[jid]['subscription'] = \ + self.convert_db_api_values_to_human_subscription_values( + subscription) + data[jid]['groups'] = [] + data[jid]['resources'] = {} + if ask: + data[jid]['ask'] = 'subscribe' + else: + data[jid]['ask'] = None + data[jid]['id'] = jid_id + + # Then we add group for roster entries + for jid in data: + self.cur.execute(''' + SELECT group_name FROM roster_group + WHERE account_jid_id=? AND jid_id=?''', + (account_jid_id, data[jid]['id'])) + for (group_name,) in self.cur: + data[jid]['groups'].append(group_name) + del data[jid]['id'] + + return data + + def remove_roster(self, account_jid): + """ + Remove all entry from account_jid roster + """ + account_jid_id = self.get_jid_id(account_jid) + + self.cur.execute('DELETE FROM roster_entry WHERE account_jid_id=?', + (account_jid_id,)) + self.cur.execute('DELETE FROM roster_group WHERE account_jid_id=?', + (account_jid_id,)) + self.con.commit() diff --git a/src/common/logging_helpers.py b/src/common/logging_helpers.py index 733958b2e..d04b08d9f 100644 --- a/src/common/logging_helpers.py +++ b/src/common/logging_helpers.py @@ -22,163 +22,161 @@ import logging import i18n def parseLogLevel(arg): - """ - Eiter numeric value or level name from logging module - """ - if arg.isdigit(): - return int(arg) - elif arg.isupper(): - return getattr(logging, arg) - else: - raise ValueError(_('%s is not a valid loglevel'), repr(arg)) + """ + Eiter numeric value or level name from logging module + """ + if arg.isdigit(): + return int(arg) + elif arg.isupper(): + return getattr(logging, arg) + else: + raise ValueError(_('%s is not a valid loglevel'), repr(arg)) def parseLogTarget(arg): - """ - [gajim.]c.x.y -> gajim.c.x.y - .other_logger -> other_logger - -> gajim - """ - arg = arg.lower() - if not arg: - return 'gajim' - elif arg.startswith('.'): - return arg[1:] - elif arg.startswith('gajim'): - return arg - else: - return 'gajim.' + arg + """ + [gajim.]c.x.y -> gajim.c.x.y + .other_logger -> other_logger + -> gajim + """ + arg = arg.lower() + if not arg: + return 'gajim' + elif arg.startswith('.'): + return arg[1:] + elif arg.startswith('gajim'): + return arg + else: + return 'gajim.' + arg def parseAndSetLogLevels(arg): - """ - [=]LOGLEVEL -> gajim=LOGLEVEL - gajim=LOGLEVEL -> gajim=LOGLEVEL - .other=10 -> other=10 - .=10 -> - c.x.y=c.z=20 -> gajim.c.x.y=20 - gajim.c.z=20 - gajim=10,c.x=20 -> gajim=10 - gajim.c.x=20 - """ - for directive in arg.split(','): - directive = directive.strip() - if not directive: - continue - if '=' not in directive: - directive = '=' + directive - targets, level = directive.rsplit('=', 1) - level = parseLogLevel(level.strip()) - for target in targets.split('='): - target = parseLogTarget(target.strip()) - if target: - logging.getLogger(target).setLevel(level) - print "Logger %s level set to %d" % (target, level) + """ + [=]LOGLEVEL -> gajim=LOGLEVEL + gajim=LOGLEVEL -> gajim=LOGLEVEL + .other=10 -> other=10 + .=10 -> + c.x.y=c.z=20 -> gajim.c.x.y=20 + gajim.c.z=20 + gajim=10,c.x=20 -> gajim=10 + gajim.c.x=20 + """ + for directive in arg.split(','): + directive = directive.strip() + if not directive: + continue + if '=' not in directive: + directive = '=' + directive + targets, level = directive.rsplit('=', 1) + level = parseLogLevel(level.strip()) + for target in targets.split('='): + target = parseLogTarget(target.strip()) + if target: + logging.getLogger(target).setLevel(level) + print "Logger %s level set to %d" % (target, level) class colors: - NONE = chr(27) + "[0m" - BLACk = chr(27) + "[30m" - RED = chr(27) + "[31m" - GREEN = chr(27) + "[32m" - BROWN = chr(27) + "[33m" - BLUE = chr(27) + "[34m" - MAGENTA = chr(27) + "[35m" - CYAN = chr(27) + "[36m" - LIGHT_GRAY = chr(27) + "[37m" - DARK_GRAY = chr(27) + "[30;1m" - BRIGHT_RED = chr(27) + "[31;1m" - BRIGHT_GREEN = chr(27) + "[32;1m" - YELLOW = chr(27) + "[33;1m" - BRIGHT_BLUE = chr(27) + "[34;1m" - PURPLE = chr(27) + "[35;1m" - BRIGHT_CYAN = chr(27) + "[36;1m" - WHITE = chr(27) + "[37;1m" + NONE = chr(27) + "[0m" + BLACk = chr(27) + "[30m" + RED = chr(27) + "[31m" + GREEN = chr(27) + "[32m" + BROWN = chr(27) + "[33m" + BLUE = chr(27) + "[34m" + MAGENTA = chr(27) + "[35m" + CYAN = chr(27) + "[36m" + LIGHT_GRAY = chr(27) + "[37m" + DARK_GRAY = chr(27) + "[30;1m" + BRIGHT_RED = chr(27) + "[31;1m" + BRIGHT_GREEN = chr(27) + "[32;1m" + YELLOW = chr(27) + "[33;1m" + BRIGHT_BLUE = chr(27) + "[34;1m" + PURPLE = chr(27) + "[35;1m" + BRIGHT_CYAN = chr(27) + "[36;1m" + WHITE = chr(27) + "[37;1m" def colorize(text, color): - return color + text + colors.NONE + return color + text + colors.NONE class FancyFormatter(logging.Formatter): - """ - An eye-candy formatter with colors - """ - colors_mapping = { - 'DEBUG': colors.BLUE, - 'INFO' : colors.GREEN, - 'WARNING': colors.BROWN, - 'ERROR': colors.RED, - 'CRITICAL': colors.BRIGHT_RED, - } + """ + An eye-candy formatter with colors + """ + colors_mapping = { + 'DEBUG': colors.BLUE, + 'INFO': colors.GREEN, + 'WARNING': colors.BROWN, + 'ERROR': colors.RED, + 'CRITICAL': colors.BRIGHT_RED, + } - def __init__(self, fmt, datefmt=None, use_color=False): - logging.Formatter.__init__(self, fmt, datefmt) - self.use_color = use_color + def __init__(self, fmt, datefmt=None, use_color=False): + logging.Formatter.__init__(self, fmt, datefmt) + self.use_color = use_color - def formatTime(self, record, datefmt=None): - f = logging.Formatter.formatTime(self, record, datefmt) - if self.use_color: - f = colorize(f, colors.DARK_GRAY) - return f + def formatTime(self, record, datefmt=None): + f = logging.Formatter.formatTime(self, record, datefmt) + if self.use_color: + f = colorize(f, colors.DARK_GRAY) + return f - def format(self, record): - level = record.levelname - record.levelname = '(%s)' % level[0] + def format(self, record): + level = record.levelname + record.levelname = '(%s)' % level[0] - if self.use_color: - c = FancyFormatter.colors_mapping.get(level, '') - record.levelname = colorize(record.levelname, c) - record.name = colorize(record.name, colors.CYAN) - else: - record.name += ':' + if self.use_color: + c = FancyFormatter.colors_mapping.get(level, '') + record.levelname = colorize(record.levelname, c) + record.name = colorize(record.name, colors.CYAN) + else: + record.name += ':' - return logging.Formatter.format(self, record) + return logging.Formatter.format(self, record) def init(use_color=False): - """ - Iinitialize the logging system - """ - consoleloghandler = logging.StreamHandler() - consoleloghandler.setFormatter( - FancyFormatter( - '%(asctime)s %(levelname)s %(name)s %(message)s', - '%H:%M:%S', - use_color - ) - ) + """ + Iinitialize the logging system + """ + consoleloghandler = logging.StreamHandler() + consoleloghandler.setFormatter( + FancyFormatter( + '%(asctime)s %(levelname)s %(name)s %(message)s', + '%H:%M:%S', + use_color + ) + ) - # fake the root logger so we have 'gajim' root name instead of 'root' - root_log = logging.getLogger('gajim') - root_log.setLevel(logging.WARNING) - root_log.addHandler(consoleloghandler) - root_log.propagate = False + # fake the root logger so we have 'gajim' root name instead of 'root' + root_log = logging.getLogger('gajim') + root_log.setLevel(logging.WARNING) + root_log.addHandler(consoleloghandler) + root_log.propagate = False def set_loglevels(loglevels_string): - parseAndSetLogLevels(loglevels_string) + parseAndSetLogLevels(loglevels_string) def set_verbose(): - parseAndSetLogLevels('gajim=1') + parseAndSetLogLevels('gajim=1') def set_quiet(): - parseAndSetLogLevels('gajim=CRITICAL') + parseAndSetLogLevels('gajim=CRITICAL') # tests if __name__ == '__main__': - init(use_color=True) + init(use_color=True) - set_loglevels('gajim.c=DEBUG,INFO') + set_loglevels('gajim.c=DEBUG,INFO') - log = logging.getLogger('gajim') - log.debug('debug') - log.info('info') - log.warn('warn') - log.error('error') - log.critical('critical') + log = logging.getLogger('gajim') + log.debug('debug') + log.info('info') + log.warn('warn') + log.error('error') + log.critical('critical') - log = logging.getLogger('gajim.c.x.dispatcher') - log.debug('debug') - log.info('info') - log.warn('warn') - log.error('error') - log.critical('critical') - -# vim: se ts=3: + log = logging.getLogger('gajim.c.x.dispatcher') + log.debug('debug') + log.info('info') + log.warn('warn') + log.error('error') + log.critical('critical') diff --git a/src/common/multimedia_helpers.py b/src/common/multimedia_helpers.py index 4a2a4fb2e..1707e1961 100644 --- a/src/common/multimedia_helpers.py +++ b/src/common/multimedia_helpers.py @@ -15,87 +15,85 @@ import gst class DeviceManager(object): - def __init__(self): - self.devices = {} + def __init__(self): + self.devices = {} - def detect(self): - self.devices = {} + def detect(self): + self.devices = {} - def get_devices(self): - if not self.devices: - self.detect() - return self.devices + def get_devices(self): + if not self.devices: + self.detect() + return self.devices - def detect_element(self, name, text, pipe='%s'): - try: - element = gst.element_factory_make(name, '%spresencetest' % name) - if isinstance(element, gst.interfaces.PropertyProbe): - element.set_state(gst.STATE_READY) - devices = element.probe_get_values_name('device') - if devices: - self.devices[text % _(' Default device')] = pipe % name - for device in devices: - element.set_property('device', device) - device_name = element.get_property('device-name') - self.devices[text % device_name] = pipe % '%s device=%s' % (name, device) - element.set_state(gst.STATE_NULL) - else: - self.devices[text] = pipe % name - except gst.ElementNotFoundError: - print 'element \'%s\' not found' % name + def detect_element(self, name, text, pipe='%s'): + try: + element = gst.element_factory_make(name, '%spresencetest' % name) + if isinstance(element, gst.interfaces.PropertyProbe): + element.set_state(gst.STATE_READY) + devices = element.probe_get_values_name('device') + if devices: + self.devices[text % _(' Default device')] = pipe % name + for device in devices: + element.set_property('device', device) + device_name = element.get_property('device-name') + self.devices[text % device_name] = pipe % '%s device=%s' % (name, device) + element.set_state(gst.STATE_NULL) + else: + self.devices[text] = pipe % name + except gst.ElementNotFoundError: + print 'element \'%s\' not found' % name class AudioInputManager(DeviceManager): - def detect(self): - self.devices = {} - # Test src - self.detect_element('audiotestsrc', _('Audio test'), - '%s is-live=true name=gajim_vol') - # Auto src - self.detect_element('autoaudiosrc', _('Autodetect'), - '%s ! volume name=gajim_vol') - # Alsa src - self.detect_element('alsasrc', _('ALSA: %s'), - '%s ! volume name=gajim_vol') + def detect(self): + self.devices = {} + # Test src + self.detect_element('audiotestsrc', _('Audio test'), + '%s is-live=true name=gajim_vol') + # Auto src + self.detect_element('autoaudiosrc', _('Autodetect'), + '%s ! volume name=gajim_vol') + # Alsa src + self.detect_element('alsasrc', _('ALSA: %s'), + '%s ! volume name=gajim_vol') class AudioOutputManager(DeviceManager): - def detect(self): - self.devices = {} - # Fake sink - self.detect_element('fakesink', _('Fake audio output')) - # Auto sink - self.detect_element('autoaudiosink', _('Autodetect')) - # Alsa sink - self.detect_element('alsasink', _('ALSA: %s'), - '%s sync=false') + def detect(self): + self.devices = {} + # Fake sink + self.detect_element('fakesink', _('Fake audio output')) + # Auto sink + self.detect_element('autoaudiosink', _('Autodetect')) + # Alsa sink + self.detect_element('alsasink', _('ALSA: %s'), + '%s sync=false') class VideoInputManager(DeviceManager): - def detect(self): - self.devices = {} - # Test src - self.detect_element('videotestsrc', _('Video test'), - '%s is-live=true') - # Auto src - self.detect_element('autovideosrc', _('Autodetect')) - # V4L2 src ; TODO: Figure out why it doesn't work - self.detect_element('v4l2src', _('V4L2: %s')) - # Funny things, just to test... - # self.devices['GOOM'] = 'audiotestsrc ! goom' - # self.devices['screen'] = 'ximagesrc' + def detect(self): + self.devices = {} + # Test src + self.detect_element('videotestsrc', _('Video test'), + '%s is-live=true') + # Auto src + self.detect_element('autovideosrc', _('Autodetect')) + # V4L2 src ; TODO: Figure out why it doesn't work + self.detect_element('v4l2src', _('V4L2: %s')) + # Funny things, just to test... + # self.devices['GOOM'] = 'audiotestsrc ! goom' + # self.devices['screen'] = 'ximagesrc' class VideoOutputManager(DeviceManager): - def detect(self): - self.devices = {} - # Fake video output - self.detect_element('fakesink', _('Fake audio output')) - # Auto sink - self.detect_element('autovideosink', _('Autodetect')) - # xvimage sink - self.detect_element('xvimagesink', _('X Window System (X11/XShm/Xv): %s')) - # ximagesink - self.detect_element('ximagesink', _('X Window System (without Xv)')) - - + def detect(self): + self.devices = {} + # Fake video output + self.detect_element('fakesink', _('Fake audio output')) + # Auto sink + self.detect_element('autovideosink', _('Autodetect')) + # xvimage sink + self.detect_element('xvimagesink', _('X Window System (X11/XShm/Xv): %s')) + # ximagesink + self.detect_element('ximagesink', _('X Window System (without Xv)')) diff --git a/src/common/nec.py b/src/common/nec.py index 6f433ca3c..07b1a674e 100644 --- a/src/common/nec.py +++ b/src/common/nec.py @@ -30,106 +30,105 @@ from pprint import pformat from common import gajim class NetworkEventsController(object): - - def __init__(self): - self.incoming_events_generators = {} - ''' - Keys: names of events - Values: list of class objects that are subclasses - of `NetworkIncomingEvent` - ''' - - def register_incoming_event(self, event_class): - for base_event_name in event_class.base_network_events: - self.incoming_events_generators.setdefault(base_event_name,[]).append(event_class) - - def unregister_incoming_event(self, event_class): - for base_event_name in event_class.base_network_events: - if base_event_name in self.incoming_events_generators: - self.incoming_events_generators[base_event_name].remove(event_class) - - def register_outgoing_event(self, event_class): - pass - - def unregister_outgoing_event(self, event_class): - pass - - def push_incoming_event(self, event_object): - if self._generate_events_based_on_incoming_event(event_object): - gajim.ged.raise_event(event_object.name, event_object) - - def push_outgoing_event(self, event_object): - pass - - def _generate_events_based_on_incoming_event(self, event_object): - ''' - :return: True if even_object should be dispatched through Global - Events Dispatcher, False otherwise. This can be used to replace - base events with those that more data computed (easier to use - by handlers). - :note: replacing mechanism is not implemented currently, but will be - based on attribute in new network events object. - ''' - base_event_name = event_object.name - if base_event_name in self.incoming_events_generators: - for new_event_class in self.incoming_events_generators[base_event_name]: - new_event_object = new_event_class(None, base_event=event_object) - if new_event_object.generate(): - if self._generate_events_based_on_incoming_event(new_event_object): - gajim.ged.raise_event(new_event_object.name, new_event_object) - return True - -class NetworkEvent(object): - name = '' - def __init__(self, new_name, **kwargs): - if new_name: - self.name = new_name - - self._set_kwargs_as_attributes(**kwargs) - - self.init() - - def init(self): - pass - - def _set_kwargs_as_attributes(self, **kwargs): - for k,v in kwargs.iteritems(): - setattr(self, k, v) - - def __str__(self): - return ' Attributes: %s'%(pformat(self.__dict__)) - - def __repr__(self): - return ' Attributes: %s'%(pformat(self.__dict__)) - + def __init__(self): + self.incoming_events_generators = {} + ''' + Keys: names of events + Values: list of class objects that are subclasses + of `NetworkIncomingEvent` + ''' + + def register_incoming_event(self, event_class): + for base_event_name in event_class.base_network_events: + self.incoming_events_generators.setdefault(base_event_name, []).append(event_class) + + def unregister_incoming_event(self, event_class): + for base_event_name in event_class.base_network_events: + if base_event_name in self.incoming_events_generators: + self.incoming_events_generators[base_event_name].remove(event_class) + + def register_outgoing_event(self, event_class): + pass + + def unregister_outgoing_event(self, event_class): + pass + + def push_incoming_event(self, event_object): + if self._generate_events_based_on_incoming_event(event_object): + gajim.ged.raise_event(event_object.name, event_object) + + def push_outgoing_event(self, event_object): + pass + + def _generate_events_based_on_incoming_event(self, event_object): + ''' + :return: True if even_object should be dispatched through Global + Events Dispatcher, False otherwise. This can be used to replace + base events with those that more data computed (easier to use + by handlers). + :note: replacing mechanism is not implemented currently, but will be + based on attribute in new network events object. + ''' + base_event_name = event_object.name + if base_event_name in self.incoming_events_generators: + for new_event_class in self.incoming_events_generators[base_event_name]: + new_event_object = new_event_class(None, base_event=event_object) + if new_event_object.generate(): + if self._generate_events_based_on_incoming_event(new_event_object): + gajim.ged.raise_event(new_event_object.name, new_event_object) + return True + +class NetworkEvent(object): + name = '' + + def __init__(self, new_name, **kwargs): + if new_name: + self.name = new_name + + self._set_kwargs_as_attributes(**kwargs) + + self.init() + + def init(self): + pass + + def _set_kwargs_as_attributes(self, **kwargs): + for k, v in kwargs.iteritems(): + setattr(self, k, v) + + def __str__(self): + return ' Attributes: %s'%(pformat(self.__dict__)) + + def __repr__(self): + return ' Attributes: %s'%(pformat(self.__dict__)) + class NetworkIncomingEvent(NetworkEvent): - base_network_events = [] - ''' - Names of base network events that new event is going to be generated on. - ''' - - def init(self): - pass - - def generate(self): - ''' - Generates new event (sets it's attributes) based on event object. - - Base event object name is one of those in `base_network_events`. - - Reference to base event object is stored in `self.base_event` attribute. - - Note that this is a reference, so modifications to that event object - are possible before dispatching to Global Events Dispatcher. - - :return: True if generated event should be dispatched, False otherwise. - ''' - pass - + base_network_events = [] + ''' + Names of base network events that new event is going to be generated on. + ''' + + def init(self): + pass + + def generate(self): + ''' + Generates new event (sets it's attributes) based on event object. + + Base event object name is one of those in `base_network_events`. + + Reference to base event object is stored in `self.base_event` attribute. + + Note that this is a reference, so modifications to that event object + are possible before dispatching to Global Events Dispatcher. + + :return: True if generated event should be dispatched, False otherwise. + ''' + pass + class NetworkOutgoingEvent(NetworkEvent): - - def init(self): - pass - \ No newline at end of file + + def init(self): + pass diff --git a/src/common/optparser.py b/src/common/optparser.py index b14bdf69e..b90304b4d 100644 --- a/src/common/optparser.py +++ b/src/common/optparser.py @@ -39,847 +39,845 @@ import sqlite3 as sqlite import logger class OptionsParser: - def __init__(self, filename): - self.__filename = filename - self.old_values = {} # values that are saved in the file and maybe - # no longer valid - - def read(self): - try: - fd = open(self.__filename) - except Exception: - if os.path.exists(self.__filename): - #we talk about a file - print _('error: cannot open %s for reading') % self.__filename - return False - - new_version = gajim.config.get('version') - new_version = new_version.split('-', 1)[0] - seen = set() - regex = re.compile(r"(?P[^.]+)(?:(?:\.(?P.+))?\.(?P[^.]+))?\s=\s(?P.*)") - - for line in fd: - try: - line = line.decode('utf-8') - except UnicodeDecodeError: - line = line.decode(locale.getpreferredencoding()) - optname, key, subname, value = regex.match(line).groups() - if key is None: - self.old_values[optname] = value - gajim.config.set(optname, value) - else: - if (optname, key) not in seen: - gajim.config.add_per(optname, key) - seen.add((optname, key)) - gajim.config.set_per(optname, key, subname, value) - - old_version = gajim.config.get('version') - old_version = old_version.split('-', 1)[0] - - self.update_config(old_version, new_version) - self.old_values = {} # clean mem - - fd.close() - return True - - def write_line(self, fd, opt, parents, value): - if value is None: - return - value = value[1] - # convert to utf8 before writing to file if needed - if isinstance(value, unicode): - value = value.encode('utf-8') - else: - value = str(value) - if isinstance(opt, unicode): - opt = opt.encode('utf-8') - s = '' - if parents: - if len(parents) == 1: - return - for p in parents: - if isinstance(p, unicode): - p = p.encode('utf-8') - s += p + '.' - s += opt - fd.write(s + ' = ' + value + '\n') - - def write(self): - (base_dir, filename) = os.path.split(self.__filename) - self.__tempfile = os.path.join(base_dir, '.' + filename) - try: - f = open(self.__tempfile, 'w') - except IOError, e: - return str(e) - try: - gajim.config.foreach(self.write_line, f) - except IOError, e: - return str(e) - f.close() - if os.path.exists(self.__filename): - # win32 needs this - try: - os.remove(self.__filename) - except Exception: - pass - try: - os.rename(self.__tempfile, self.__filename) - except IOError, e: - return str(e) - os.chmod(self.__filename, 0600) - - def update_config(self, old_version, new_version): - old_version_list = old_version.split('.') # convert '0.x.y' to (0, x, y) - old = [] - while len(old_version_list): - old.append(int(old_version_list.pop(0))) - new_version_list = new_version.split('.') - new = [] - while len(new_version_list): - new.append(int(new_version_list.pop(0))) - - if old < [0, 9] and new >= [0, 9]: - self.update_config_x_to_09() - if old < [0, 10] and new >= [0, 10]: - self.update_config_09_to_010() - if old < [0, 10, 1, 1] and new >= [0, 10, 1, 1]: - self.update_config_to_01011() - if old < [0, 10, 1, 2] and new >= [0, 10, 1, 2]: - self.update_config_to_01012() - if old < [0, 10, 1, 3] and new >= [0, 10, 1, 3]: - self.update_config_to_01013() - if old < [0, 10, 1, 4] and new >= [0, 10, 1, 4]: - self.update_config_to_01014() - if old < [0, 10, 1, 5] and new >= [0, 10, 1, 5]: - self.update_config_to_01015() - if old < [0, 10, 1, 6] and new >= [0, 10, 1, 6]: - self.update_config_to_01016() - if old < [0, 10, 1, 7] and new >= [0, 10, 1, 7]: - self.update_config_to_01017() - if old < [0, 10, 1, 8] and new >= [0, 10, 1, 8]: - self.update_config_to_01018() - if old < [0, 11, 0, 1] and new >= [0, 11, 0, 1]: - self.update_config_to_01101() - if old < [0, 11, 0, 2] and new >= [0, 11, 0, 2]: - self.update_config_to_01102() - if old < [0, 11, 1, 1] and new >= [0, 11, 1, 1]: - self.update_config_to_01111() - if old < [0, 11, 1, 2] and new >= [0, 11, 1, 2]: - self.update_config_to_01112() - if old < [0, 11, 1, 3] and new >= [0, 11, 1, 3]: - self.update_config_to_01113() - if old < [0, 11, 1, 4] and new >= [0, 11, 1, 4]: - self.update_config_to_01114() - if old < [0, 11, 1, 5] and new >= [0, 11, 1, 5]: - self.update_config_to_01115() - if old < [0, 11, 2, 1] and new >= [0, 11, 2, 1]: - self.update_config_to_01121() - if old < [0, 11, 4, 1] and new >= [0, 11, 4, 1]: - self.update_config_to_01141() - if old < [0, 11, 4, 2] and new >= [0, 11, 4, 2]: - self.update_config_to_01142() - if old < [0, 11, 4, 3] and new >= [0, 11, 4, 3]: - self.update_config_to_01143() - if old < [0, 11, 4, 4] and new >= [0, 11, 4, 4]: - self.update_config_to_01144() - if old < [0, 12, 0, 1] and new >= [0, 12, 0, 1]: - self.update_config_to_01201() - if old < [0, 12, 1, 1] and new >= [0, 12, 1, 1]: - self.update_config_to_01211() - if old < [0, 12, 1, 2] and new >= [0, 12, 1, 2]: - self.update_config_to_01212() - if old < [0, 12, 1, 3] and new >= [0, 12, 1, 3]: - self.update_config_to_01213() - if old < [0, 12, 1, 4] and new >= [0, 12, 1, 4]: - self.update_config_to_01214() - if old < [0, 12, 1, 5] and new >= [0, 12, 1, 5]: - self.update_config_to_01215() - if old < [0, 12, 3, 1] and new >= [0, 12, 3, 1]: - self.update_config_to_01231() - if old < [0, 12, 5, 1] and new >= [0, 12, 5, 1]: - self.update_config_from_0125() - self.update_config_to_01251() - if old < [0, 12, 5, 2] and new >= [0, 12, 5, 2]: - self.update_config_to_01252() - if old < [0, 12, 5, 3] and new >= [0, 12, 5, 3]: - self.update_config_to_01253() - if old < [0, 12, 5, 4] and new >= [0, 12, 5, 4]: - self.update_config_to_01254() - if old < [0, 12, 5, 5] and new >= [0, 12, 5, 5]: - self.update_config_to_01255() - if old < [0, 12, 5, 6] and new >= [0, 12, 5, 6]: - self.update_config_to_01256() - if old < [0, 12, 5, 7] and new >= [0, 12, 5, 7]: - self.update_config_to_01257() - if old < [0, 12, 5, 8] and new >= [0, 12, 5, 8]: - self.update_config_to_01258() - if old < [0, 13, 10, 0] and new >= [0, 13, 10, 0]: - self.update_config_to_013100() - if old < [0, 13, 10, 1] and new >= [0, 13, 10, 1]: - self.update_config_to_013101() - - gajim.logger.init_vars() - gajim.logger.attach_cache_database() - gajim.config.set('version', new_version) - - caps_cache.capscache.initialize_from_db() - - def assert_unread_msgs_table_exists(self): - """ - Create table unread_messages if there is no such table - """ - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - try: - cur.executescript( - ''' - CREATE TABLE unread_messages ( - message_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, - jid_id INTEGER - ); - ''' - ) - con.commit() - gajim.logger.init_vars() - except sqlite.OperationalError: - pass - con.close() - - def update_ft_proxies(self, to_remove=[], to_add=[]): - for account in gajim.config.get_per('accounts'): - proxies_str = gajim.config.get_per('accounts', account, - 'file_transfer_proxies') - proxies = [p.strip() for p in proxies_str.split(',')] - for wrong_proxy in to_remove: - if wrong_proxy in proxies: - proxies.remove(wrong_proxy) - for new_proxy in to_add: - if new_proxy not in proxies: - proxies.append(new_proxy) - proxies_str = ', '.join(proxies) - gajim.config.set_per('accounts', account, 'file_transfer_proxies', - proxies_str) - - def update_config_x_to_09(self): - # Var name that changed: - # avatar_width /height -> chat_avatar_width / height - if 'avatar_width' in self.old_values: - gajim.config.set('chat_avatar_width', self.old_values['avatar_width']) - if 'avatar_height' in self.old_values: - gajim.config.set('chat_avatar_height', self.old_values['avatar_height']) - if 'use_dbus' in self.old_values: - gajim.config.set('remote_control', self.old_values['use_dbus']) - # always_compact_view -> always_compact_view_chat / _gc - if 'always_compact_view' in self.old_values: - gajim.config.set('always_compact_view_chat', - self.old_values['always_compact_view']) - gajim.config.set('always_compact_view_gc', - self.old_values['always_compact_view']) - # new theme: grocery, plain - d = ['accounttextcolor', 'accountbgcolor', 'accountfont', - 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont', - 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', 'contactfont', - 'contactfontattrs', 'bannertextcolor', 'bannerbgcolor', 'bannerfont', - 'bannerfontattrs'] - for theme_name in (_('grocery'), _('default')): - if theme_name not in gajim.config.get_per('themes'): - gajim.config.add_per('themes', theme_name) - theme = gajim.config.themes_default[theme_name] - for o in d: - gajim.config.set_per('themes', theme_name, o, theme[d.index(o)]) - # Remove cyan theme if it's not the current theme - if 'cyan' in gajim.config.get_per('themes'): - gajim.config.del_per('themes', 'cyan') - if _('cyan') in gajim.config.get_per('themes'): - gajim.config.del_per('themes', _('cyan')) - # If we removed our roster_theme, choose the default green one or another - # one if doesn't exists in config - if gajim.config.get('roster_theme') not in gajim.config.get_per('themes'): - theme = _('green') - if theme not in gajim.config.get_per('themes'): - theme = gajim.config.get_per('themes')[0] - gajim.config.set('roster_theme', theme) - # new proxies in accounts.name.file_transfer_proxies - self.update_ft_proxies(to_add=['proxy.netlab.cz']) - - gajim.config.set('version', '0.9') - - def update_config_09_to_010(self): - if 'usetabbedchat' in self.old_values and not \ - self.old_values['usetabbedchat']: - gajim.config.set('one_message_window', 'never') - if 'autodetect_browser_mailer' in self.old_values and \ - self.old_values['autodetect_browser_mailer'] is True: - gajim.config.set('autodetect_browser_mailer', False) - if 'useemoticons' in self.old_values and \ - not self.old_values['useemoticons']: - gajim.config.set('emoticons_theme', '') - if 'always_compact_view_chat' in self.old_values and \ - self.old_values['always_compact_view_chat'] != 'False': - gajim.config.set('always_hide_chat_buttons', True) - if 'always_compact_view_gc' in self.old_values and \ - self.old_values['always_compact_view_gc'] != 'False': - gajim.config.set('always_hide_groupchat_buttons', True) - - self.update_ft_proxies(to_remove=['proxy65.jabber.autocom.pl', - 'proxy65.jabber.ccc.de'], to_add=['transfer.jabber.freenet.de']) - # create unread_messages table if needed - self.assert_unread_msgs_table_exists() - - gajim.config.set('version', '0.10') - - def update_config_to_01011(self): - if 'print_status_in_muc' in self.old_values and \ - self.old_values['print_status_in_muc'] in (True, False): - gajim.config.set('print_status_in_muc', 'in_and_out') - gajim.config.set('version', '0.10.1.1') - - def update_config_to_01012(self): - # See [6456] - if 'emoticons_theme' in self.old_values and \ - self.old_values['emoticons_theme'] == 'Disabled': - gajim.config.set('emoticons_theme', '') - gajim.config.set('version', '0.10.1.2') - - def update_config_to_01013(self): - """ - Create table transports_cache if there is no such table - """ - # FIXME see #2812 - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - try: - cur.executescript( - ''' - CREATE TABLE transports_cache ( - transport TEXT UNIQUE, - type INTEGER - ); - ''' - ) - con.commit() - except sqlite.OperationalError: - pass - con.close() - gajim.config.set('version', '0.10.1.3') - - def update_config_to_01014(self): - """ - Apply indeces to the logs database - """ - print _('migrating logs database to indices') - # FIXME see #2812 - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - # apply indeces - try: - cur.executescript( - ''' - CREATE INDEX idx_logs_jid_id_kind ON logs (jid_id, kind); - CREATE INDEX idx_unread_messages_jid_id ON unread_messages (jid_id); - ''' - ) - - con.commit() - except Exception: - pass - con.close() - gajim.config.set('version', '0.10.1.4') - - def update_config_to_01015(self): - """ - Clean show values in logs database - """ - #FIXME see #2812 - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - status = dict((i[5:].lower(), logger.constants.__dict__[i]) for i in \ - logger.constants.__dict__.keys() if i.startswith('SHOW_')) - for show in status: - cur.execute('update logs set show = ? where show = ?;', (status[show], - show)) - cur.execute('update logs set show = NULL where show not in (0, 1, 2, 3, 4, 5);') - con.commit() - cur.close() # remove this in 2007 [pysqlite old versions need this] - con.close() - gajim.config.set('version', '0.10.1.5') - - def update_config_to_01016(self): - """ - #2494 : Now we play gc_received_message sound even if - notify_on_all_muc_messages is false. Keep precedent behaviour - """ - if 'notify_on_all_muc_messages' in self.old_values and \ - self.old_values['notify_on_all_muc_messages'] == 'False' and \ - gajim.config.get_per('soundevents', 'muc_message_received', 'enabled'): - gajim.config.set_per('soundevents',\ - 'muc_message_received', 'enabled', False) - gajim.config.set('version', '0.10.1.6') - - def update_config_to_01017(self): - """ - trayicon_notification_on_new_messages -> trayicon_notification_on_events - """ - if 'trayicon_notification_on_new_messages' in self.old_values: - gajim.config.set('trayicon_notification_on_events', - self.old_values['trayicon_notification_on_new_messages']) - gajim.config.set('version', '0.10.1.7') - - def update_config_to_01018(self): - """ - chat_state_notifications -> outgoing_chat_state_notifications - """ - if 'chat_state_notifications' in self.old_values: - gajim.config.set('outgoing_chat_state_notifications', - self.old_values['chat_state_notifications']) - gajim.config.set('version', '0.10.1.8') - - def update_config_to_01101(self): - """ - Fill time_stamp from before_time and after_time - """ - if 'before_time' in self.old_values: - gajim.config.set('time_stamp', '%s%%X%s ' % ( - self.old_values['before_time'], self.old_values['after_time'])) - gajim.config.set('version', '0.11.0.1') - - def update_config_to_01102(self): - """ - Fill time_stamp from before_time and after_time - """ - if 'ft_override_host_to_send' in self.old_values: - gajim.config.set('ft_add_hosts_to_send', - self.old_values['ft_override_host_to_send']) - gajim.config.set('version', '0.11.0.2') - - def update_config_to_01111(self): - """ - Always_hide_chatbuttons -> compact_view - """ - if 'always_hide_groupchat_buttons' in self.old_values and \ - 'always_hide_chat_buttons' in self.old_values: - gajim.config.set('compact_view', self.old_values['always_hide_groupchat_buttons'] and \ - self.old_values['always_hide_chat_buttons']) - gajim.config.set('version', '0.11.1.1') - - def update_config_to_01112(self): - """ - GTK+ theme is renamed to default - """ - if 'roster_theme' in self.old_values and \ - self.old_values['roster_theme'] == 'gtk+': - gajim.config.set('roster_theme', _('default')) - gajim.config.set('version', '0.11.1.2') - - def update_config_to_01113(self): - # copy&pasted from update_config_to_01013, possibly 'FIXME see #2812' applies too - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - try: - cur.executescript( - ''' - CREATE TABLE caps_cache ( - node TEXT, - ver TEXT, - ext TEXT, - data BLOB - ); - ''' - ) - con.commit() - except sqlite.OperationalError: - pass - con.close() - gajim.config.set('version', '0.11.1.3') - - def update_config_to_01114(self): - # add default theme if it doesn't exist - d = ['accounttextcolor', 'accountbgcolor', 'accountfont', - 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont', - 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', 'contactfont', - 'contactfontattrs', 'bannertextcolor', 'bannerbgcolor', 'bannerfont', - 'bannerfontattrs'] - theme_name = _('default') - if theme_name not in gajim.config.get_per('themes'): - gajim.config.add_per('themes', theme_name) - if gajim.config.get_per('themes', 'gtk+'): - # copy from old gtk+ theme - for o in d: - val = gajim.config.get_per('themes', 'gtk+', o) - gajim.config.set_per('themes', theme_name, o, val) - gajim.config.del_per('themes', 'gtk+') - else: - # copy from default theme - theme = gajim.config.themes_default[theme_name] - for o in d: - gajim.config.set_per('themes', theme_name, o, theme[d.index(o)]) - gajim.config.set('version', '0.11.1.4') - - def update_config_to_01115(self): - # copy&pasted from update_config_to_01013, possibly 'FIXME see #2812' applies too - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - try: - cur.executescript( - ''' - DELETE FROM caps_cache; - ''' - ) - con.commit() - except sqlite.OperationalError: - pass - con.close() - gajim.config.set('version', '0.11.1.5') - - def update_config_to_01121(self): - # remove old unencrypted secrets file - from common.configpaths import gajimpaths - - new_file = gajimpaths['SECRETS_FILE'] - - old_file = os.path.dirname(new_file) + '/secrets' - - if os.path.exists(old_file): - os.remove(old_file) - - gajim.config.set('version', '0.11.2.1') - - def update_config_to_01141(self): - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - try: - cur.executescript( - ''' - CREATE TABLE IF NOT EXISTS caps_cache ( - node TEXT, - ver TEXT, - ext TEXT, - data BLOB - ); - ''' - ) - con.commit() - except sqlite.OperationalError: - pass - con.close() - gajim.config.set('version', '0.11.4.1') - - def update_config_to_01142(self): - """ - next_message_received sound event is splittedin 2 events - """ - gajim.config.add_per('soundevents', 'next_message_received_focused') - gajim.config.add_per('soundevents', 'next_message_received_unfocused') - if gajim.config.get_per('soundevents', 'next_message_received'): - enabled = gajim.config.get_per('soundevents', 'next_message_received', - 'enabled') - path = gajim.config.get_per('soundevents', 'next_message_received', - 'path') - gajim.config.del_per('soundevents', 'next_message_received') - gajim.config.set_per('soundevents', 'next_message_received_focused', - 'enabled', enabled) - gajim.config.set_per('soundevents', 'next_message_received_focused', - 'path', path) - gajim.config.set('version', '0.11.1.2') - - def update_config_to_01143(self): - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - try: - cur.executescript( - ''' - CREATE TABLE IF NOT EXISTS rooms_last_message_time( - jid_id INTEGER PRIMARY KEY UNIQUE, - time INTEGER - ); - ''' - ) - con.commit() - except sqlite.OperationalError: - pass - con.close() - gajim.config.set('version', '0.11.4.3') - - def update_config_to_01144(self): - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - try: - cur.executescript('DROP TABLE caps_cache;') - con.commit() - except sqlite.OperationalError: - pass - try: - cur.executescript( - ''' - CREATE TABLE caps_cache ( - hash_method TEXT, - hash TEXT, - data BLOB - ); - ''' - ) - con.commit() - except sqlite.OperationalError, e: - pass - con.close() - gajim.config.set('version', '0.11.4.4') - - def update_config_to_01201(self): - if 'uri_schemes' in self.old_values: - new_values = self.old_values['uri_schemes'].replace(' mailto', '').\ - replace(' xmpp', '') - gajim.config.set('uri_schemes', new_values) - gajim.config.set('version', '0.12.0.1') - - def update_config_to_01211(self): - if 'trayicon' in self.old_values: - if self.old_values['trayicon'] == 'False': - gajim.config.set('trayicon', 'never') - else: - gajim.config.set('trayicon', 'always') - gajim.config.set('version', '0.12.1.1') - - def update_config_to_01212(self): - for opt in ('ignore_unknown_contacts', 'send_os_info', - 'log_encrypted_sessions'): - if opt in self.old_values: - val = self.old_values[opt] - for account in gajim.config.get_per('accounts'): - gajim.config.set_per('accounts', account, opt, val) - gajim.config.set('version', '0.12.1.2') - - def update_config_to_01213(self): - msgs = gajim.config.statusmsg_default - for msg_name in gajim.config.get_per('statusmsg'): - if msg_name in msgs: - gajim.config.set_per('statusmsg', msg_name, 'activity', - msgs[msg_name][1]) - gajim.config.set_per('statusmsg', msg_name, 'subactivity', - msgs[msg_name][2]) - gajim.config.set_per('statusmsg', msg_name, 'activity_text', - msgs[msg_name][3]) - gajim.config.set_per('statusmsg', msg_name, 'mood', - msgs[msg_name][4]) - gajim.config.set_per('statusmsg', msg_name, 'mood_text', - msgs[msg_name][5]) - gajim.config.set('version', '0.12.1.3') - - def update_config_to_01214(self): - for status in ['online', 'chat', 'away', 'xa', 'dnd', 'invisible', - 'offline']: - if 'last_status_msg_' + status in self.old_values: - gajim.config.add_per('statusmsg', '_last_' + status) - gajim.config.set_per('statusmsg', '_last_' + status, 'message', - self.old_values['last_status_msg_' + status]) - gajim.config.set('version', '0.12.1.4') - - def update_config_to_01215(self): - """ - Remove hardcoded ../data/sounds from config - """ - dirs = ('../data', gajim.gajimpaths.root, gajim.DATA_DIR) - for evt in gajim.config.get_per('soundevents'): - path = gajim.config.get_per('soundevents', evt ,'path') - # absolute and relative passes are necessary - path = helpers.strip_soundfile_path(path, dirs, abs=False) - path = helpers.strip_soundfile_path(path, dirs, abs=True) - gajim.config.set_per('soundevents', evt, 'path', path) - gajim.config.set('version', '0.12.1.5') - - def update_config_to_01231(self): - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - try: - cur.executescript( - ''' - CREATE TABLE IF NOT EXISTS roster_entry( - account_jid_id INTEGER, - jid_id INTEGER, - name TEXT, - subscription INTEGER, - ask BOOLEAN, - PRIMARY KEY (account_jid_id, jid_id) - ); - - CREATE TABLE IF NOT EXISTS roster_group( - account_jid_id INTEGER, - jid_id INTEGER, - group_name TEXT, - PRIMARY KEY (account_jid_id, jid_id, group_name) - ); - ''' - ) - con.commit() - except sqlite.OperationalError: - pass - con.close() - gajim.config.set('version', '0.12.3.1') - - def update_config_from_0125(self): - # All those functions need to be called for 0.12.5 to 0.13 transition - self.update_config_to_01211() - self.update_config_to_01213() - self.update_config_to_01214() - self.update_config_to_01215() - self.update_config_to_01231() - - def update_config_to_01251(self): - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - try: - cur.executescript( - ''' - ALTER TABLE unread_messages - ADD shown BOOLEAN default 0; - ''' - ) - con.commit() - except sqlite.OperationalError: - pass - con.close() - gajim.config.set('version', '0.12.5.1') - - def update_config_to_01252(self): - if 'alwaysauth' in self.old_values: - val = self.old_values['alwaysauth'] - for account in gajim.config.get_per('accounts'): - gajim.config.set_per('accounts', account, 'autoauth', val) - gajim.config.set('version', '0.12.5.2') - - def update_config_to_01253(self): - if 'enable_zeroconf' in self.old_values: - val = self.old_values['enable_zeroconf'] - for account in gajim.config.get_per('accounts'): - if gajim.config.get_per('accounts', account, 'is_zeroconf'): - gajim.config.set_per('accounts', account, 'active', val) - else: - gajim.config.set_per('accounts', account, 'active', True) - gajim.config.set('version', '0.12.5.3') - - def update_config_to_01254(self): - vals = {'inmsgcolor': ['#a34526', '#a40000'], - 'outmsgcolor': ['#164e6f', '#3465a4'], - 'restored_messages_color': ['grey', '#555753'], - 'statusmsgcolor': ['#1eaa1e', '#73d216'], - 'urlmsgcolor': ['#0000ff', '#204a87'], - 'gc_nicknames_colors': ['#a34526:#c000ff:#0012ff:#388a99:#045723:#7c7c7c:#ff8a00:#94452d:#244b5a:#32645a', '#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000']} - for c in vals: - if c not in self.old_values: - continue - val = self.old_values[c] - if val == vals[c][0]: - # We didn't change default value, so update it with new default - gajim.config.set(c, vals[c][1]) - gajim.config.set('version', '0.12.5.4') - - def update_config_to_01255(self): - vals = {'statusmsgcolor': ['#73d216', '#4e9a06'], - 'outmsgtxtcolor': ['#a2a2a2', '#555753']} - for c in vals: - if c not in self.old_values: - continue - val = self.old_values[c] - if val == vals[c][0]: - # We didn't change default value, so update it with new default - gajim.config.set(c, vals[c][1]) - gajim.config.set('version', '0.12.5.5') - - def update_config_to_01256(self): - vals = {'gc_nicknames_colors': ['#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000', '#f57900:#ce5c00:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000']} - for c in vals: - if c not in self.old_values: - continue - val = self.old_values[c] - if val == vals[c][0]: - # We didn't change default value, so update it with new default - gajim.config.set(c, vals[c][1]) - gajim.config.set('version', '0.12.5.6') - - def update_config_to_01257(self): - if 'iconset' in self.old_values: - if self.old_values['iconset'] in ('nuvola', 'crystal', 'gossip', - 'simplebulb', 'stellar'): - gajim.config.set('iconset', gajim.config.DEFAULT_ICONSET) - gajim.config.set('version', '0.12.5.7') - - def update_config_to_01258(self): - self.update_ft_proxies(to_remove=['proxy65.talkonaut.com', - 'proxy.jabber.org', 'proxy.netlab.cz', 'transfer.jabber.freenet.de', - 'proxy.jabber.cd.chalmers.se'], to_add=['proxy.eu.jabber.org', - 'proxy.jabber.ru', 'proxy.jabbim.cz']) - gajim.config.set('version', '0.12.5.8') - - def update_config_to_013100(self): - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - try: - cur.executescript( - ''' - ALTER TABLE caps_cache - ADD last_seen INTEGER default %d; - ''' % int(time()) - ) - con.commit() - except sqlite.OperationalError: - pass - con.close() - gajim.config.set('version', '0.13.10.0') - - def update_config_to_013101(self): - back = os.getcwd() - os.chdir(logger.LOG_DB_FOLDER) - con = sqlite.connect(logger.LOG_DB_FILE) - os.chdir(back) - cur = con.cursor() - try: - cur.executescript( - ''' - DROP INDEX IF EXISTS idx_logs_jid_id_kind; - - CREATE INDEX IF NOT EXISTS - idx_logs_jid_id_time ON logs (jid_id, time DESC); - ''' - ) - con.commit() - except sqlite.OperationalError: - pass - con.close() - gajim.config.set('version', '0.13.10.1') - -# vim: se ts=3: + def __init__(self, filename): + self.__filename = filename + self.old_values = {} # values that are saved in the file and maybe + # no longer valid + + def read(self): + try: + fd = open(self.__filename) + except Exception: + if os.path.exists(self.__filename): + #we talk about a file + print _('error: cannot open %s for reading') % self.__filename + return False + + new_version = gajim.config.get('version') + new_version = new_version.split('-', 1)[0] + seen = set() + regex = re.compile(r"(?P[^.]+)(?:(?:\.(?P.+))?\.(?P[^.]+))?\s=\s(?P.*)") + + for line in fd: + try: + line = line.decode('utf-8') + except UnicodeDecodeError: + line = line.decode(locale.getpreferredencoding()) + optname, key, subname, value = regex.match(line).groups() + if key is None: + self.old_values[optname] = value + gajim.config.set(optname, value) + else: + if (optname, key) not in seen: + gajim.config.add_per(optname, key) + seen.add((optname, key)) + gajim.config.set_per(optname, key, subname, value) + + old_version = gajim.config.get('version') + old_version = old_version.split('-', 1)[0] + + self.update_config(old_version, new_version) + self.old_values = {} # clean mem + + fd.close() + return True + + def write_line(self, fd, opt, parents, value): + if value is None: + return + value = value[1] + # convert to utf8 before writing to file if needed + if isinstance(value, unicode): + value = value.encode('utf-8') + else: + value = str(value) + if isinstance(opt, unicode): + opt = opt.encode('utf-8') + s = '' + if parents: + if len(parents) == 1: + return + for p in parents: + if isinstance(p, unicode): + p = p.encode('utf-8') + s += p + '.' + s += opt + fd.write(s + ' = ' + value + '\n') + + def write(self): + (base_dir, filename) = os.path.split(self.__filename) + self.__tempfile = os.path.join(base_dir, '.' + filename) + try: + f = open(self.__tempfile, 'w') + except IOError, e: + return str(e) + try: + gajim.config.foreach(self.write_line, f) + except IOError, e: + return str(e) + f.close() + if os.path.exists(self.__filename): + # win32 needs this + try: + os.remove(self.__filename) + except Exception: + pass + try: + os.rename(self.__tempfile, self.__filename) + except IOError, e: + return str(e) + os.chmod(self.__filename, 0600) + + def update_config(self, old_version, new_version): + old_version_list = old_version.split('.') # convert '0.x.y' to (0, x, y) + old = [] + while len(old_version_list): + old.append(int(old_version_list.pop(0))) + new_version_list = new_version.split('.') + new = [] + while len(new_version_list): + new.append(int(new_version_list.pop(0))) + + if old < [0, 9] and new >= [0, 9]: + self.update_config_x_to_09() + if old < [0, 10] and new >= [0, 10]: + self.update_config_09_to_010() + if old < [0, 10, 1, 1] and new >= [0, 10, 1, 1]: + self.update_config_to_01011() + if old < [0, 10, 1, 2] and new >= [0, 10, 1, 2]: + self.update_config_to_01012() + if old < [0, 10, 1, 3] and new >= [0, 10, 1, 3]: + self.update_config_to_01013() + if old < [0, 10, 1, 4] and new >= [0, 10, 1, 4]: + self.update_config_to_01014() + if old < [0, 10, 1, 5] and new >= [0, 10, 1, 5]: + self.update_config_to_01015() + if old < [0, 10, 1, 6] and new >= [0, 10, 1, 6]: + self.update_config_to_01016() + if old < [0, 10, 1, 7] and new >= [0, 10, 1, 7]: + self.update_config_to_01017() + if old < [0, 10, 1, 8] and new >= [0, 10, 1, 8]: + self.update_config_to_01018() + if old < [0, 11, 0, 1] and new >= [0, 11, 0, 1]: + self.update_config_to_01101() + if old < [0, 11, 0, 2] and new >= [0, 11, 0, 2]: + self.update_config_to_01102() + if old < [0, 11, 1, 1] and new >= [0, 11, 1, 1]: + self.update_config_to_01111() + if old < [0, 11, 1, 2] and new >= [0, 11, 1, 2]: + self.update_config_to_01112() + if old < [0, 11, 1, 3] and new >= [0, 11, 1, 3]: + self.update_config_to_01113() + if old < [0, 11, 1, 4] and new >= [0, 11, 1, 4]: + self.update_config_to_01114() + if old < [0, 11, 1, 5] and new >= [0, 11, 1, 5]: + self.update_config_to_01115() + if old < [0, 11, 2, 1] and new >= [0, 11, 2, 1]: + self.update_config_to_01121() + if old < [0, 11, 4, 1] and new >= [0, 11, 4, 1]: + self.update_config_to_01141() + if old < [0, 11, 4, 2] and new >= [0, 11, 4, 2]: + self.update_config_to_01142() + if old < [0, 11, 4, 3] and new >= [0, 11, 4, 3]: + self.update_config_to_01143() + if old < [0, 11, 4, 4] and new >= [0, 11, 4, 4]: + self.update_config_to_01144() + if old < [0, 12, 0, 1] and new >= [0, 12, 0, 1]: + self.update_config_to_01201() + if old < [0, 12, 1, 1] and new >= [0, 12, 1, 1]: + self.update_config_to_01211() + if old < [0, 12, 1, 2] and new >= [0, 12, 1, 2]: + self.update_config_to_01212() + if old < [0, 12, 1, 3] and new >= [0, 12, 1, 3]: + self.update_config_to_01213() + if old < [0, 12, 1, 4] and new >= [0, 12, 1, 4]: + self.update_config_to_01214() + if old < [0, 12, 1, 5] and new >= [0, 12, 1, 5]: + self.update_config_to_01215() + if old < [0, 12, 3, 1] and new >= [0, 12, 3, 1]: + self.update_config_to_01231() + if old < [0, 12, 5, 1] and new >= [0, 12, 5, 1]: + self.update_config_from_0125() + self.update_config_to_01251() + if old < [0, 12, 5, 2] and new >= [0, 12, 5, 2]: + self.update_config_to_01252() + if old < [0, 12, 5, 3] and new >= [0, 12, 5, 3]: + self.update_config_to_01253() + if old < [0, 12, 5, 4] and new >= [0, 12, 5, 4]: + self.update_config_to_01254() + if old < [0, 12, 5, 5] and new >= [0, 12, 5, 5]: + self.update_config_to_01255() + if old < [0, 12, 5, 6] and new >= [0, 12, 5, 6]: + self.update_config_to_01256() + if old < [0, 12, 5, 7] and new >= [0, 12, 5, 7]: + self.update_config_to_01257() + if old < [0, 12, 5, 8] and new >= [0, 12, 5, 8]: + self.update_config_to_01258() + if old < [0, 13, 10, 0] and new >= [0, 13, 10, 0]: + self.update_config_to_013100() + if old < [0, 13, 10, 1] and new >= [0, 13, 10, 1]: + self.update_config_to_013101() + + gajim.logger.init_vars() + gajim.logger.attach_cache_database() + gajim.config.set('version', new_version) + + caps_cache.capscache.initialize_from_db() + + def assert_unread_msgs_table_exists(self): + """ + Create table unread_messages if there is no such table + """ + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + try: + cur.executescript( + ''' + CREATE TABLE unread_messages ( + message_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, + jid_id INTEGER + ); + ''' + ) + con.commit() + gajim.logger.init_vars() + except sqlite.OperationalError: + pass + con.close() + + def update_ft_proxies(self, to_remove=[], to_add=[]): + for account in gajim.config.get_per('accounts'): + proxies_str = gajim.config.get_per('accounts', account, + 'file_transfer_proxies') + proxies = [p.strip() for p in proxies_str.split(',')] + for wrong_proxy in to_remove: + if wrong_proxy in proxies: + proxies.remove(wrong_proxy) + for new_proxy in to_add: + if new_proxy not in proxies: + proxies.append(new_proxy) + proxies_str = ', '.join(proxies) + gajim.config.set_per('accounts', account, 'file_transfer_proxies', + proxies_str) + + def update_config_x_to_09(self): + # Var name that changed: + # avatar_width /height -> chat_avatar_width / height + if 'avatar_width' in self.old_values: + gajim.config.set('chat_avatar_width', self.old_values['avatar_width']) + if 'avatar_height' in self.old_values: + gajim.config.set('chat_avatar_height', self.old_values['avatar_height']) + if 'use_dbus' in self.old_values: + gajim.config.set('remote_control', self.old_values['use_dbus']) + # always_compact_view -> always_compact_view_chat / _gc + if 'always_compact_view' in self.old_values: + gajim.config.set('always_compact_view_chat', + self.old_values['always_compact_view']) + gajim.config.set('always_compact_view_gc', + self.old_values['always_compact_view']) + # new theme: grocery, plain + d = ['accounttextcolor', 'accountbgcolor', 'accountfont', + 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont', + 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', 'contactfont', + 'contactfontattrs', 'bannertextcolor', 'bannerbgcolor', 'bannerfont', + 'bannerfontattrs'] + for theme_name in (_('grocery'), _('default')): + if theme_name not in gajim.config.get_per('themes'): + gajim.config.add_per('themes', theme_name) + theme = gajim.config.themes_default[theme_name] + for o in d: + gajim.config.set_per('themes', theme_name, o, theme[d.index(o)]) + # Remove cyan theme if it's not the current theme + if 'cyan' in gajim.config.get_per('themes'): + gajim.config.del_per('themes', 'cyan') + if _('cyan') in gajim.config.get_per('themes'): + gajim.config.del_per('themes', _('cyan')) + # If we removed our roster_theme, choose the default green one or another + # one if doesn't exists in config + if gajim.config.get('roster_theme') not in gajim.config.get_per('themes'): + theme = _('green') + if theme not in gajim.config.get_per('themes'): + theme = gajim.config.get_per('themes')[0] + gajim.config.set('roster_theme', theme) + # new proxies in accounts.name.file_transfer_proxies + self.update_ft_proxies(to_add=['proxy.netlab.cz']) + + gajim.config.set('version', '0.9') + + def update_config_09_to_010(self): + if 'usetabbedchat' in self.old_values and not \ + self.old_values['usetabbedchat']: + gajim.config.set('one_message_window', 'never') + if 'autodetect_browser_mailer' in self.old_values and \ + self.old_values['autodetect_browser_mailer'] is True: + gajim.config.set('autodetect_browser_mailer', False) + if 'useemoticons' in self.old_values and \ + not self.old_values['useemoticons']: + gajim.config.set('emoticons_theme', '') + if 'always_compact_view_chat' in self.old_values and \ + self.old_values['always_compact_view_chat'] != 'False': + gajim.config.set('always_hide_chat_buttons', True) + if 'always_compact_view_gc' in self.old_values and \ + self.old_values['always_compact_view_gc'] != 'False': + gajim.config.set('always_hide_groupchat_buttons', True) + + self.update_ft_proxies(to_remove=['proxy65.jabber.autocom.pl', + 'proxy65.jabber.ccc.de'], to_add=['transfer.jabber.freenet.de']) + # create unread_messages table if needed + self.assert_unread_msgs_table_exists() + + gajim.config.set('version', '0.10') + + def update_config_to_01011(self): + if 'print_status_in_muc' in self.old_values and \ + self.old_values['print_status_in_muc'] in (True, False): + gajim.config.set('print_status_in_muc', 'in_and_out') + gajim.config.set('version', '0.10.1.1') + + def update_config_to_01012(self): + # See [6456] + if 'emoticons_theme' in self.old_values and \ + self.old_values['emoticons_theme'] == 'Disabled': + gajim.config.set('emoticons_theme', '') + gajim.config.set('version', '0.10.1.2') + + def update_config_to_01013(self): + """ + Create table transports_cache if there is no such table + """ + # FIXME see #2812 + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + try: + cur.executescript( + ''' + CREATE TABLE transports_cache ( + transport TEXT UNIQUE, + type INTEGER + ); + ''' + ) + con.commit() + except sqlite.OperationalError: + pass + con.close() + gajim.config.set('version', '0.10.1.3') + + def update_config_to_01014(self): + """ + Apply indeces to the logs database + """ + print _('migrating logs database to indices') + # FIXME see #2812 + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + # apply indeces + try: + cur.executescript( + ''' + CREATE INDEX idx_logs_jid_id_kind ON logs (jid_id, kind); + CREATE INDEX idx_unread_messages_jid_id ON unread_messages (jid_id); + ''' + ) + + con.commit() + except Exception: + pass + con.close() + gajim.config.set('version', '0.10.1.4') + + def update_config_to_01015(self): + """ + Clean show values in logs database + """ + #FIXME see #2812 + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + status = dict((i[5:].lower(), logger.constants.__dict__[i]) for i in \ + logger.constants.__dict__.keys() if i.startswith('SHOW_')) + for show in status: + cur.execute('update logs set show = ? where show = ?;', (status[show], + show)) + cur.execute('update logs set show = NULL where show not in (0, 1, 2, 3, 4, 5);') + con.commit() + cur.close() # remove this in 2007 [pysqlite old versions need this] + con.close() + gajim.config.set('version', '0.10.1.5') + + def update_config_to_01016(self): + """ + #2494 : Now we play gc_received_message sound even if + notify_on_all_muc_messages is false. Keep precedent behaviour + """ + if 'notify_on_all_muc_messages' in self.old_values and \ + self.old_values['notify_on_all_muc_messages'] == 'False' and \ + gajim.config.get_per('soundevents', 'muc_message_received', 'enabled'): + gajim.config.set_per('soundevents',\ + 'muc_message_received', 'enabled', False) + gajim.config.set('version', '0.10.1.6') + + def update_config_to_01017(self): + """ + trayicon_notification_on_new_messages -> trayicon_notification_on_events + """ + if 'trayicon_notification_on_new_messages' in self.old_values: + gajim.config.set('trayicon_notification_on_events', + self.old_values['trayicon_notification_on_new_messages']) + gajim.config.set('version', '0.10.1.7') + + def update_config_to_01018(self): + """ + chat_state_notifications -> outgoing_chat_state_notifications + """ + if 'chat_state_notifications' in self.old_values: + gajim.config.set('outgoing_chat_state_notifications', + self.old_values['chat_state_notifications']) + gajim.config.set('version', '0.10.1.8') + + def update_config_to_01101(self): + """ + Fill time_stamp from before_time and after_time + """ + if 'before_time' in self.old_values: + gajim.config.set('time_stamp', '%s%%X%s ' % ( + self.old_values['before_time'], self.old_values['after_time'])) + gajim.config.set('version', '0.11.0.1') + + def update_config_to_01102(self): + """ + Fill time_stamp from before_time and after_time + """ + if 'ft_override_host_to_send' in self.old_values: + gajim.config.set('ft_add_hosts_to_send', + self.old_values['ft_override_host_to_send']) + gajim.config.set('version', '0.11.0.2') + + def update_config_to_01111(self): + """ + Always_hide_chatbuttons -> compact_view + """ + if 'always_hide_groupchat_buttons' in self.old_values and \ + 'always_hide_chat_buttons' in self.old_values: + gajim.config.set('compact_view', self.old_values['always_hide_groupchat_buttons'] and \ + self.old_values['always_hide_chat_buttons']) + gajim.config.set('version', '0.11.1.1') + + def update_config_to_01112(self): + """ + GTK+ theme is renamed to default + """ + if 'roster_theme' in self.old_values and \ + self.old_values['roster_theme'] == 'gtk+': + gajim.config.set('roster_theme', _('default')) + gajim.config.set('version', '0.11.1.2') + + def update_config_to_01113(self): + # copy&pasted from update_config_to_01013, possibly 'FIXME see #2812' applies too + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + try: + cur.executescript( + ''' + CREATE TABLE caps_cache ( + node TEXT, + ver TEXT, + ext TEXT, + data BLOB + ); + ''' + ) + con.commit() + except sqlite.OperationalError: + pass + con.close() + gajim.config.set('version', '0.11.1.3') + + def update_config_to_01114(self): + # add default theme if it doesn't exist + d = ['accounttextcolor', 'accountbgcolor', 'accountfont', + 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont', + 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', 'contactfont', + 'contactfontattrs', 'bannertextcolor', 'bannerbgcolor', 'bannerfont', + 'bannerfontattrs'] + theme_name = _('default') + if theme_name not in gajim.config.get_per('themes'): + gajim.config.add_per('themes', theme_name) + if gajim.config.get_per('themes', 'gtk+'): + # copy from old gtk+ theme + for o in d: + val = gajim.config.get_per('themes', 'gtk+', o) + gajim.config.set_per('themes', theme_name, o, val) + gajim.config.del_per('themes', 'gtk+') + else: + # copy from default theme + theme = gajim.config.themes_default[theme_name] + for o in d: + gajim.config.set_per('themes', theme_name, o, theme[d.index(o)]) + gajim.config.set('version', '0.11.1.4') + + def update_config_to_01115(self): + # copy&pasted from update_config_to_01013, possibly 'FIXME see #2812' applies too + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + try: + cur.executescript( + ''' + DELETE FROM caps_cache; + ''' + ) + con.commit() + except sqlite.OperationalError: + pass + con.close() + gajim.config.set('version', '0.11.1.5') + + def update_config_to_01121(self): + # remove old unencrypted secrets file + from common.configpaths import gajimpaths + + new_file = gajimpaths['SECRETS_FILE'] + + old_file = os.path.dirname(new_file) + '/secrets' + + if os.path.exists(old_file): + os.remove(old_file) + + gajim.config.set('version', '0.11.2.1') + + def update_config_to_01141(self): + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + try: + cur.executescript( + ''' + CREATE TABLE IF NOT EXISTS caps_cache ( + node TEXT, + ver TEXT, + ext TEXT, + data BLOB + ); + ''' + ) + con.commit() + except sqlite.OperationalError: + pass + con.close() + gajim.config.set('version', '0.11.4.1') + + def update_config_to_01142(self): + """ + next_message_received sound event is splittedin 2 events + """ + gajim.config.add_per('soundevents', 'next_message_received_focused') + gajim.config.add_per('soundevents', 'next_message_received_unfocused') + if gajim.config.get_per('soundevents', 'next_message_received'): + enabled = gajim.config.get_per('soundevents', 'next_message_received', + 'enabled') + path = gajim.config.get_per('soundevents', 'next_message_received', + 'path') + gajim.config.del_per('soundevents', 'next_message_received') + gajim.config.set_per('soundevents', 'next_message_received_focused', + 'enabled', enabled) + gajim.config.set_per('soundevents', 'next_message_received_focused', + 'path', path) + gajim.config.set('version', '0.11.1.2') + + def update_config_to_01143(self): + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + try: + cur.executescript( + ''' + CREATE TABLE IF NOT EXISTS rooms_last_message_time( + jid_id INTEGER PRIMARY KEY UNIQUE, + time INTEGER + ); + ''' + ) + con.commit() + except sqlite.OperationalError: + pass + con.close() + gajim.config.set('version', '0.11.4.3') + + def update_config_to_01144(self): + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + try: + cur.executescript('DROP TABLE caps_cache;') + con.commit() + except sqlite.OperationalError: + pass + try: + cur.executescript( + ''' + CREATE TABLE caps_cache ( + hash_method TEXT, + hash TEXT, + data BLOB + ); + ''' + ) + con.commit() + except sqlite.OperationalError, e: + pass + con.close() + gajim.config.set('version', '0.11.4.4') + + def update_config_to_01201(self): + if 'uri_schemes' in self.old_values: + new_values = self.old_values['uri_schemes'].replace(' mailto', '').\ + replace(' xmpp', '') + gajim.config.set('uri_schemes', new_values) + gajim.config.set('version', '0.12.0.1') + + def update_config_to_01211(self): + if 'trayicon' in self.old_values: + if self.old_values['trayicon'] == 'False': + gajim.config.set('trayicon', 'never') + else: + gajim.config.set('trayicon', 'always') + gajim.config.set('version', '0.12.1.1') + + def update_config_to_01212(self): + for opt in ('ignore_unknown_contacts', 'send_os_info', + 'log_encrypted_sessions'): + if opt in self.old_values: + val = self.old_values[opt] + for account in gajim.config.get_per('accounts'): + gajim.config.set_per('accounts', account, opt, val) + gajim.config.set('version', '0.12.1.2') + + def update_config_to_01213(self): + msgs = gajim.config.statusmsg_default + for msg_name in gajim.config.get_per('statusmsg'): + if msg_name in msgs: + gajim.config.set_per('statusmsg', msg_name, 'activity', + msgs[msg_name][1]) + gajim.config.set_per('statusmsg', msg_name, 'subactivity', + msgs[msg_name][2]) + gajim.config.set_per('statusmsg', msg_name, 'activity_text', + msgs[msg_name][3]) + gajim.config.set_per('statusmsg', msg_name, 'mood', + msgs[msg_name][4]) + gajim.config.set_per('statusmsg', msg_name, 'mood_text', + msgs[msg_name][5]) + gajim.config.set('version', '0.12.1.3') + + def update_config_to_01214(self): + for status in ['online', 'chat', 'away', 'xa', 'dnd', 'invisible', + 'offline']: + if 'last_status_msg_' + status in self.old_values: + gajim.config.add_per('statusmsg', '_last_' + status) + gajim.config.set_per('statusmsg', '_last_' + status, 'message', + self.old_values['last_status_msg_' + status]) + gajim.config.set('version', '0.12.1.4') + + def update_config_to_01215(self): + """ + Remove hardcoded ../data/sounds from config + """ + dirs = ('../data', gajim.gajimpaths.root, gajim.DATA_DIR) + for evt in gajim.config.get_per('soundevents'): + path = gajim.config.get_per('soundevents', evt, 'path') + # absolute and relative passes are necessary + path = helpers.strip_soundfile_path(path, dirs, abs=False) + path = helpers.strip_soundfile_path(path, dirs, abs=True) + gajim.config.set_per('soundevents', evt, 'path', path) + gajim.config.set('version', '0.12.1.5') + + def update_config_to_01231(self): + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + try: + cur.executescript( + ''' + CREATE TABLE IF NOT EXISTS roster_entry( + account_jid_id INTEGER, + jid_id INTEGER, + name TEXT, + subscription INTEGER, + ask BOOLEAN, + PRIMARY KEY (account_jid_id, jid_id) + ); + + CREATE TABLE IF NOT EXISTS roster_group( + account_jid_id INTEGER, + jid_id INTEGER, + group_name TEXT, + PRIMARY KEY (account_jid_id, jid_id, group_name) + ); + ''' + ) + con.commit() + except sqlite.OperationalError: + pass + con.close() + gajim.config.set('version', '0.12.3.1') + + def update_config_from_0125(self): + # All those functions need to be called for 0.12.5 to 0.13 transition + self.update_config_to_01211() + self.update_config_to_01213() + self.update_config_to_01214() + self.update_config_to_01215() + self.update_config_to_01231() + + def update_config_to_01251(self): + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + try: + cur.executescript( + ''' + ALTER TABLE unread_messages + ADD shown BOOLEAN default 0; + ''' + ) + con.commit() + except sqlite.OperationalError: + pass + con.close() + gajim.config.set('version', '0.12.5.1') + + def update_config_to_01252(self): + if 'alwaysauth' in self.old_values: + val = self.old_values['alwaysauth'] + for account in gajim.config.get_per('accounts'): + gajim.config.set_per('accounts', account, 'autoauth', val) + gajim.config.set('version', '0.12.5.2') + + def update_config_to_01253(self): + if 'enable_zeroconf' in self.old_values: + val = self.old_values['enable_zeroconf'] + for account in gajim.config.get_per('accounts'): + if gajim.config.get_per('accounts', account, 'is_zeroconf'): + gajim.config.set_per('accounts', account, 'active', val) + else: + gajim.config.set_per('accounts', account, 'active', True) + gajim.config.set('version', '0.12.5.3') + + def update_config_to_01254(self): + vals = {'inmsgcolor': ['#a34526', '#a40000'], + 'outmsgcolor': ['#164e6f', '#3465a4'], + 'restored_messages_color': ['grey', '#555753'], + 'statusmsgcolor': ['#1eaa1e', '#73d216'], + 'urlmsgcolor': ['#0000ff', '#204a87'], + 'gc_nicknames_colors': ['#a34526:#c000ff:#0012ff:#388a99:#045723:#7c7c7c:#ff8a00:#94452d:#244b5a:#32645a', '#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000']} + for c in vals: + if c not in self.old_values: + continue + val = self.old_values[c] + if val == vals[c][0]: + # We didn't change default value, so update it with new default + gajim.config.set(c, vals[c][1]) + gajim.config.set('version', '0.12.5.4') + + def update_config_to_01255(self): + vals = {'statusmsgcolor': ['#73d216', '#4e9a06'], + 'outmsgtxtcolor': ['#a2a2a2', '#555753']} + for c in vals: + if c not in self.old_values: + continue + val = self.old_values[c] + if val == vals[c][0]: + # We didn't change default value, so update it with new default + gajim.config.set(c, vals[c][1]) + gajim.config.set('version', '0.12.5.5') + + def update_config_to_01256(self): + vals = {'gc_nicknames_colors': ['#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000', '#f57900:#ce5c00:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000']} + for c in vals: + if c not in self.old_values: + continue + val = self.old_values[c] + if val == vals[c][0]: + # We didn't change default value, so update it with new default + gajim.config.set(c, vals[c][1]) + gajim.config.set('version', '0.12.5.6') + + def update_config_to_01257(self): + if 'iconset' in self.old_values: + if self.old_values['iconset'] in ('nuvola', 'crystal', 'gossip', + 'simplebulb', 'stellar'): + gajim.config.set('iconset', gajim.config.DEFAULT_ICONSET) + gajim.config.set('version', '0.12.5.7') + + def update_config_to_01258(self): + self.update_ft_proxies(to_remove=['proxy65.talkonaut.com', + 'proxy.jabber.org', 'proxy.netlab.cz', 'transfer.jabber.freenet.de', + 'proxy.jabber.cd.chalmers.se'], to_add=['proxy.eu.jabber.org', + 'proxy.jabber.ru', 'proxy.jabbim.cz']) + gajim.config.set('version', '0.12.5.8') + + def update_config_to_013100(self): + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + try: + cur.executescript( + ''' + ALTER TABLE caps_cache + ADD last_seen INTEGER default %d; + ''' % int(time()) + ) + con.commit() + except sqlite.OperationalError: + pass + con.close() + gajim.config.set('version', '0.13.10.0') + + def update_config_to_013101(self): + back = os.getcwd() + os.chdir(logger.LOG_DB_FOLDER) + con = sqlite.connect(logger.LOG_DB_FILE) + os.chdir(back) + cur = con.cursor() + try: + cur.executescript( + ''' + DROP INDEX IF EXISTS idx_logs_jid_id_kind; + + CREATE INDEX IF NOT EXISTS + idx_logs_jid_id_time ON logs (jid_id, time DESC); + ''' + ) + con.commit() + except sqlite.OperationalError: + pass + con.close() + gajim.config.set('version', '0.13.10.1') diff --git a/src/common/passwords.py b/src/common/passwords.py index 9000085c4..15607976d 100644 --- a/src/common/passwords.py +++ b/src/common/passwords.py @@ -36,189 +36,187 @@ USER_HAS_KWALLETCLI = False gnomekeyring = None class PasswordStorage(object): - def get_password(self, account_name): - raise NotImplementedError - def save_password(self, account_name, password): - raise NotImplementedError + def get_password(self, account_name): + raise NotImplementedError + def save_password(self, account_name, password): + raise NotImplementedError class SimplePasswordStorage(PasswordStorage): - def get_password(self, account_name): - passwd = gajim.config.get_per('accounts', account_name, 'password') - if passwd and (passwd.startswith('gnomekeyring:') or \ - passwd == ''): - # this is not a real password, it's either a gnome - # keyring token or stored in the KDE wallet - return None - else: - return passwd + def get_password(self, account_name): + passwd = gajim.config.get_per('accounts', account_name, 'password') + if passwd and (passwd.startswith('gnomekeyring:') or \ + passwd == ''): + # this is not a real password, it's either a gnome + # keyring token or stored in the KDE wallet + return None + else: + return passwd - def save_password(self, account_name, password): - gajim.config.set_per('accounts', account_name, 'password', password) - if account_name in gajim.connections: - gajim.connections[account_name].password = password + def save_password(self, account_name, password): + gajim.config.set_per('accounts', account_name, 'password', password) + if account_name in gajim.connections: + gajim.connections[account_name].password = password class GnomePasswordStorage(PasswordStorage): - def __init__(self): - self.keyring = gnomekeyring.get_default_keyring_sync() - if self.keyring is None: - self.keyring = 'default' - try: - gnomekeyring.create_sync(self.keyring, None) - except gnomekeyring.AlreadyExistsError: - pass + def __init__(self): + self.keyring = gnomekeyring.get_default_keyring_sync() + if self.keyring is None: + self.keyring = 'default' + try: + gnomekeyring.create_sync(self.keyring, None) + except gnomekeyring.AlreadyExistsError: + pass - def get_password(self, account_name): - conf = gajim.config.get_per('accounts', account_name, 'password') - if conf is None or conf == '': - return None - if not conf.startswith('gnomekeyring:'): - password = conf - ## migrate the password over to keyring - try: - self.save_password(account_name, password, update=False) - except gnomekeyring.NoKeyringDaemonError: - ## no keyring daemon: in the future, stop using it - set_storage(SimplePasswordStorage()) - return password - try: - server = gajim.config.get_per('accounts', account_name, 'hostname') - user = gajim.config.get_per('accounts', account_name, 'name') - attributes1 = dict(server=str(server), user=str(user), protocol='xmpp') - attributes2 = dict(account_name=str(account_name), gajim=1) - try: - items = gnomekeyring.find_items_sync( - gnomekeyring.ITEM_NETWORK_PASSWORD, attributes1) - except gnomekeyring.Error: - try: - items = gnomekeyring.find_items_sync( - gnomekeyring.ITEM_GENERIC_SECRET, attributes2) - if items: - # We found an old item, move it to new way of storing - password = items[0].secret - self.save_password(account_name, password) - gnomekeyring.item_delete_sync(items[0].keyring, - int(items[0].item_id)) - except gnomekeyring.Error: - items = [] - if len(items) > 1: - warnings.warn("multiple gnome keyring items found for account %s;" - " trying to use the first one..." - % account_name) - if items: - return items[0].secret - else: - return None - except gnomekeyring.DeniedError: - return None - except gnomekeyring.NoKeyringDaemonError: - ## no keyring daemon: in the future, stop using it - set_storage(SimplePasswordStorage()) - return None + def get_password(self, account_name): + conf = gajim.config.get_per('accounts', account_name, 'password') + if conf is None or conf == '': + return None + if not conf.startswith('gnomekeyring:'): + password = conf + ## migrate the password over to keyring + try: + self.save_password(account_name, password, update=False) + except gnomekeyring.NoKeyringDaemonError: + ## no keyring daemon: in the future, stop using it + set_storage(SimplePasswordStorage()) + return password + try: + server = gajim.config.get_per('accounts', account_name, 'hostname') + user = gajim.config.get_per('accounts', account_name, 'name') + attributes1 = dict(server=str(server), user=str(user), protocol='xmpp') + attributes2 = dict(account_name=str(account_name), gajim=1) + try: + items = gnomekeyring.find_items_sync( + gnomekeyring.ITEM_NETWORK_PASSWORD, attributes1) + except gnomekeyring.Error: + try: + items = gnomekeyring.find_items_sync( + gnomekeyring.ITEM_GENERIC_SECRET, attributes2) + if items: + # We found an old item, move it to new way of storing + password = items[0].secret + self.save_password(account_name, password) + gnomekeyring.item_delete_sync(items[0].keyring, + int(items[0].item_id)) + except gnomekeyring.Error: + items = [] + if len(items) > 1: + warnings.warn("multiple gnome keyring items found for account %s;" + " trying to use the first one..." + % account_name) + if items: + return items[0].secret + else: + return None + except gnomekeyring.DeniedError: + return None + except gnomekeyring.NoKeyringDaemonError: + ## no keyring daemon: in the future, stop using it + set_storage(SimplePasswordStorage()) + return None - def save_password(self, account_name, password, update=True): - server = gajim.config.get_per('accounts', account_name, 'hostname') - user = gajim.config.get_per('accounts', account_name, 'name') - display_name = _('XMPP account %s@%s') % (user, server) - attributes1 = dict(server=str(server), user=str(user), protocol='xmpp') - if password is None: - password = str() - try: - auth_token = gnomekeyring.item_create_sync( - self.keyring, gnomekeyring.ITEM_NETWORK_PASSWORD, - display_name, attributes1, password, update) - except gnomekeyring.DeniedError: - set_storage(SimplePasswordStorage()) - storage.save_password(account_name, password) - return - gajim.config.set_per('accounts', account_name, 'password', - 'gnomekeyring:') - if account_name in gajim.connections: - gajim.connections[account_name].password = password + def save_password(self, account_name, password, update=True): + server = gajim.config.get_per('accounts', account_name, 'hostname') + user = gajim.config.get_per('accounts', account_name, 'name') + display_name = _('XMPP account %s@%s') % (user, server) + attributes1 = dict(server=str(server), user=str(user), protocol='xmpp') + if password is None: + password = str() + try: + auth_token = gnomekeyring.item_create_sync( + self.keyring, gnomekeyring.ITEM_NETWORK_PASSWORD, + display_name, attributes1, password, update) + except gnomekeyring.DeniedError: + set_storage(SimplePasswordStorage()) + storage.save_password(account_name, password) + return + gajim.config.set_per('accounts', account_name, 'password', + 'gnomekeyring:') + if account_name in gajim.connections: + gajim.connections[account_name].password = password class KWalletPasswordStorage(PasswordStorage): - def get_password(self, account_name): - pw = gajim.config.get_per('accounts', account_name, 'password') - if not pw or pw.startswith('gnomekeyring:'): - # unset, empty or not ours - return None - if pw != '': - # migrate the password - if kwalletbinding.kwallet_put('gajim', account_name, pw): - gajim.config.set_per('accounts', account_name, 'password', - '') - else: - # stop using the KDE Wallet - set_storage(SimplePasswordStorage()) - return pw - pw = kwalletbinding.kwallet_get('gajim', account_name) - if pw is None: - # stop using the KDE Wallet - set_storage(SimplePasswordStorage()) - if not pw: - # False, None, or the empty string - return None - return pw + def get_password(self, account_name): + pw = gajim.config.get_per('accounts', account_name, 'password') + if not pw or pw.startswith('gnomekeyring:'): + # unset, empty or not ours + return None + if pw != '': + # migrate the password + if kwalletbinding.kwallet_put('gajim', account_name, pw): + gajim.config.set_per('accounts', account_name, 'password', + '') + else: + # stop using the KDE Wallet + set_storage(SimplePasswordStorage()) + return pw + pw = kwalletbinding.kwallet_get('gajim', account_name) + if pw is None: + # stop using the KDE Wallet + set_storage(SimplePasswordStorage()) + if not pw: + # False, None, or the empty string + return None + return pw - def save_password(self, account_name, password): - if not kwalletbinding.kwallet_put('gajim', account_name, password): - # stop using the KDE Wallet - set_storage(SimplePasswordStorage()) - storage.save_password(account_name, password) - return - pwtoken = '' - if not password: - # no sense in looking up the empty string in the KWallet - pwtoken = '' - gajim.config.set_per('accounts', account_name, 'password', pwtoken) - if account_name in gajim.connections: - gajim.connections[account_name].password = password + def save_password(self, account_name, password): + if not kwalletbinding.kwallet_put('gajim', account_name, password): + # stop using the KDE Wallet + set_storage(SimplePasswordStorage()) + storage.save_password(account_name, password) + return + pwtoken = '' + if not password: + # no sense in looking up the empty string in the KWallet + pwtoken = '' + gajim.config.set_per('accounts', account_name, 'password', pwtoken) + if account_name in gajim.connections: + gajim.connections[account_name].password = password storage = None def get_storage(): - global storage - if storage is None: # None is only in first time get_storage is called - if gajim.config.get('use_gnomekeyring'): - global gnomekeyring - try: - import gnomekeyring - except ImportError: - pass - else: - global USER_HAS_GNOMEKEYRING - global USER_USES_GNOMEKEYRING - USER_HAS_GNOMEKEYRING = True - if gnomekeyring.is_available(): - USER_USES_GNOMEKEYRING = True - else: - USER_USES_GNOMEKEYRING = False - if USER_USES_GNOMEKEYRING: - try: - storage = GnomePasswordStorage() - except (gnomekeyring.NoKeyringDaemonError, gnomekeyring.DeniedError): - storage = None - if storage is None: - if gajim.config.get('use_kwalletcli'): - global USER_HAS_KWALLETCLI - if kwalletbinding.kwallet_available(): - USER_HAS_KWALLETCLI = True - if USER_HAS_KWALLETCLI: - storage = KWalletPasswordStorage() - if storage is None: - storage = SimplePasswordStorage() - return storage + global storage + if storage is None: # None is only in first time get_storage is called + if gajim.config.get('use_gnomekeyring'): + global gnomekeyring + try: + import gnomekeyring + except ImportError: + pass + else: + global USER_HAS_GNOMEKEYRING + global USER_USES_GNOMEKEYRING + USER_HAS_GNOMEKEYRING = True + if gnomekeyring.is_available(): + USER_USES_GNOMEKEYRING = True + else: + USER_USES_GNOMEKEYRING = False + if USER_USES_GNOMEKEYRING: + try: + storage = GnomePasswordStorage() + except (gnomekeyring.NoKeyringDaemonError, gnomekeyring.DeniedError): + storage = None + if storage is None: + if gajim.config.get('use_kwalletcli'): + global USER_HAS_KWALLETCLI + if kwalletbinding.kwallet_available(): + USER_HAS_KWALLETCLI = True + if USER_HAS_KWALLETCLI: + storage = KWalletPasswordStorage() + if storage is None: + storage = SimplePasswordStorage() + return storage def set_storage(storage_): - global storage - storage = storage_ + global storage + storage = storage_ def get_password(account_name): - return get_storage().get_password(account_name) + return get_storage().get_password(account_name) def save_password(account_name, password): - return get_storage().save_password(account_name, password) - -# vim: se ts=3: + return get_storage().save_password(account_name, password) diff --git a/src/common/pep.py b/src/common/pep.py index 0457f6667..b417b77e0 100644 --- a/src/common/pep.py +++ b/src/common/pep.py @@ -24,177 +24,177 @@ ## MOODS = { - 'afraid': _('Afraid'), - 'amazed': _('Amazed'), - 'amorous': _('Amorous'), - 'angry': _('Angry'), - 'annoyed': _('Annoyed'), - 'anxious': _('Anxious'), - 'aroused': _('Aroused'), - 'ashamed': _('Ashamed'), - 'bored': _('Bored'), - 'brave': _('Brave'), - 'calm': _('Calm'), - 'cautious': _('Cautious'), - 'cold': _('Cold'), - 'confident': _('Confident'), - 'confused': _('Confused'), - 'contemplative': _('Contemplative'), - 'contented': _('Contented'), - 'cranky': _('Cranky'), - 'crazy': _('Crazy'), - 'creative': _('Creative'), - 'curious': _('Curious'), - 'dejected': _('Dejected'), - 'depressed': _('Depressed'), - 'disappointed': _('Disappointed'), - 'disgusted': _('Disgusted'), - 'dismayed': _('Dismayed'), - 'distracted': _('Distracted'), - 'embarrassed': _('Embarrassed'), - 'envious': _('Envious'), - 'excited': _('Excited'), - 'flirtatious': _('Flirtatious'), - 'frustrated': _('Frustrated'), - 'grateful': _('Grateful'), - 'grieving': _('Grieving'), - 'grumpy': _('Grumpy'), - 'guilty': _('Guilty'), - 'happy': _('Happy'), - 'hopeful': _('Hopeful'), - 'hot': _('Hot'), - 'humbled': _('Humbled'), - 'humiliated': _('Humiliated'), - 'hungry': _('Hungry'), - 'hurt': _('Hurt'), - 'impressed': _('Impressed'), - 'in_awe': _('In Awe'), - 'in_love': _('In Love'), - 'indignant': _('Indignant'), - 'interested': _('Interested'), - 'intoxicated': _('Intoxicated'), - 'invincible': _('Invincible'), - 'jealous': _('Jealous'), - 'lonely': _('Lonely'), - 'lost': _('Lost'), - 'lucky': _('Lucky'), - 'mean': _('Mean'), - 'moody': _('Moody'), - 'nervous': _('Nervous'), - 'neutral': _('Neutral'), - 'offended': _('Offended'), - 'outraged': _('Outraged'), - 'playful': _('Playful'), - 'proud': _('Proud'), - 'relaxed': _('Relaxed'), - 'relieved': _('Relieved'), - 'remorseful': _('Remorseful'), - 'restless': _('Restless'), - 'sad': _('Sad'), - 'sarcastic': _('Sarcastic'), - 'satisfied': _('Satisfied'), - 'serious': _('Serious'), - 'shocked': _('Shocked'), - 'shy': _('Shy'), - 'sick': _('Sick'), - 'sleepy': _('Sleepy'), - 'spontaneous': _('Spontaneous'), - 'stressed': _('Stressed'), - 'strong': _('Strong'), - 'surprised': _('Surprised'), - 'thankful': _('Thankful'), - 'thirsty': _('Thirsty'), - 'tired': _('Tired'), - 'undefined': _('Undefined'), - 'weak': _('Weak'), - 'worried': _('Worried')} + 'afraid': _('Afraid'), + 'amazed': _('Amazed'), + 'amorous': _('Amorous'), + 'angry': _('Angry'), + 'annoyed': _('Annoyed'), + 'anxious': _('Anxious'), + 'aroused': _('Aroused'), + 'ashamed': _('Ashamed'), + 'bored': _('Bored'), + 'brave': _('Brave'), + 'calm': _('Calm'), + 'cautious': _('Cautious'), + 'cold': _('Cold'), + 'confident': _('Confident'), + 'confused': _('Confused'), + 'contemplative': _('Contemplative'), + 'contented': _('Contented'), + 'cranky': _('Cranky'), + 'crazy': _('Crazy'), + 'creative': _('Creative'), + 'curious': _('Curious'), + 'dejected': _('Dejected'), + 'depressed': _('Depressed'), + 'disappointed': _('Disappointed'), + 'disgusted': _('Disgusted'), + 'dismayed': _('Dismayed'), + 'distracted': _('Distracted'), + 'embarrassed': _('Embarrassed'), + 'envious': _('Envious'), + 'excited': _('Excited'), + 'flirtatious': _('Flirtatious'), + 'frustrated': _('Frustrated'), + 'grateful': _('Grateful'), + 'grieving': _('Grieving'), + 'grumpy': _('Grumpy'), + 'guilty': _('Guilty'), + 'happy': _('Happy'), + 'hopeful': _('Hopeful'), + 'hot': _('Hot'), + 'humbled': _('Humbled'), + 'humiliated': _('Humiliated'), + 'hungry': _('Hungry'), + 'hurt': _('Hurt'), + 'impressed': _('Impressed'), + 'in_awe': _('In Awe'), + 'in_love': _('In Love'), + 'indignant': _('Indignant'), + 'interested': _('Interested'), + 'intoxicated': _('Intoxicated'), + 'invincible': _('Invincible'), + 'jealous': _('Jealous'), + 'lonely': _('Lonely'), + 'lost': _('Lost'), + 'lucky': _('Lucky'), + 'mean': _('Mean'), + 'moody': _('Moody'), + 'nervous': _('Nervous'), + 'neutral': _('Neutral'), + 'offended': _('Offended'), + 'outraged': _('Outraged'), + 'playful': _('Playful'), + 'proud': _('Proud'), + 'relaxed': _('Relaxed'), + 'relieved': _('Relieved'), + 'remorseful': _('Remorseful'), + 'restless': _('Restless'), + 'sad': _('Sad'), + 'sarcastic': _('Sarcastic'), + 'satisfied': _('Satisfied'), + 'serious': _('Serious'), + 'shocked': _('Shocked'), + 'shy': _('Shy'), + 'sick': _('Sick'), + 'sleepy': _('Sleepy'), + 'spontaneous': _('Spontaneous'), + 'stressed': _('Stressed'), + 'strong': _('Strong'), + 'surprised': _('Surprised'), + 'thankful': _('Thankful'), + 'thirsty': _('Thirsty'), + 'tired': _('Tired'), + 'undefined': _('Undefined'), + 'weak': _('Weak'), + 'worried': _('Worried')} ACTIVITIES = { - 'doing_chores': {'category': _('Doing Chores'), - 'buying_groceries': _('Buying Groceries'), - 'cleaning': _('Cleaning'), - 'cooking': _('Cooking'), - 'doing_maintenance': _('Doing Maintenance'), - 'doing_the_dishes': _('Doing the Dishes'), - 'doing_the_laundry': _('Doing the Laundry'), - 'gardening': _('Gardening'), - 'running_an_errand': _('Running an Errand'), - 'walking_the_dog': _('Walking the Dog')}, - 'drinking': {'category': _('Drinking'), - 'having_a_beer': _('Having a Beer'), - 'having_coffee': _('Having Coffee'), - 'having_tea': _('Having Tea')}, - 'eating': {'category': _('Eating'), - 'having_a_snack': _('Having a Snack'), - 'having_breakfast': _('Having Breakfast'), - 'having_dinner': _('Having Dinner'), - 'having_lunch': _('Having Lunch')}, - 'exercising': {'category': _('Exercising'), - 'cycling': _('Cycling'), - 'dancing': _('Dancing'), - 'hiking': _('Hiking'), - 'jogging': _('Jogging'), - 'playing_sports': _('Playing Sports'), - 'running': _('Running'), - 'skiing': _('Skiing'), - 'swimming': _('Swimming'), - 'working_out': _('Working out')}, - 'grooming': {'category': _('Grooming'), - 'at_the_spa': _('At the Spa'), - 'brushing_teeth': _('Brushing Teeth'), - 'getting_a_haircut': _('Getting a Haircut'), - 'shaving': _('Shaving'), - 'taking_a_bath': _('Taking a Bath'), - 'taking_a_shower': _('Taking a Shower')}, - 'having_appointment': {'category': _('Having an Appointment')}, - 'inactive': {'category': _('Inactive'), - 'day_off': _('Day Off'), - 'hanging_out': _('Hanging out'), - 'hiding': _('Hiding'), - 'on_vacation': _('On Vacation'), - 'praying': _('Praying'), - 'scheduled_holiday': _('Scheduled Holiday'), - 'sleeping': _('Sleeping'), - 'thinking': _('Thinking')}, - 'relaxing': {'category': _('Relaxing'), - 'fishing': _('Fishing'), - 'gaming': _('Gaming'), - 'going_out': _('Going out'), - 'partying': _('Partying'), - 'reading': _('Reading'), - 'rehearsing': _('Rehearsing'), - 'shopping': _('Shopping'), - 'smoking': _('Smoking'), - 'socializing': _('Socializing'), - 'sunbathing': _('Sunbathing'), - 'watching_tv': _('Watching TV'), - 'watching_a_movie': _('Watching a Movie')}, - 'talking': {'category': _('Talking'), - 'in_real_life': _('In Real Life'), - 'on_the_phone': _('On the Phone'), - 'on_video_phone': _('On Video Phone')}, - 'traveling': {'category': _('Traveling'), - 'commuting': _('Commuting'), - 'cycling': _('Cycling'), - 'driving': _('Driving'), - 'in_a_car': _('In a Car'), - 'on_a_bus': _('On a Bus'), - 'on_a_plane': _('On a Plane'), - 'on_a_train': _('On a Train'), - 'on_a_trip': _('On a Trip'), - 'walking': _('Walking')}, - 'working': {'category': _('Working'), - 'coding': _('Coding'), - 'in_a_meeting': _('In a Meeting'), - 'studying': _('Studying'), - 'writing': _('Writing')}} + 'doing_chores': {'category': _('Doing Chores'), + 'buying_groceries': _('Buying Groceries'), + 'cleaning': _('Cleaning'), + 'cooking': _('Cooking'), + 'doing_maintenance': _('Doing Maintenance'), + 'doing_the_dishes': _('Doing the Dishes'), + 'doing_the_laundry': _('Doing the Laundry'), + 'gardening': _('Gardening'), + 'running_an_errand': _('Running an Errand'), + 'walking_the_dog': _('Walking the Dog')}, + 'drinking': {'category': _('Drinking'), + 'having_a_beer': _('Having a Beer'), + 'having_coffee': _('Having Coffee'), + 'having_tea': _('Having Tea')}, + 'eating': {'category': _('Eating'), + 'having_a_snack': _('Having a Snack'), + 'having_breakfast': _('Having Breakfast'), + 'having_dinner': _('Having Dinner'), + 'having_lunch': _('Having Lunch')}, + 'exercising': {'category': _('Exercising'), + 'cycling': _('Cycling'), + 'dancing': _('Dancing'), + 'hiking': _('Hiking'), + 'jogging': _('Jogging'), + 'playing_sports': _('Playing Sports'), + 'running': _('Running'), + 'skiing': _('Skiing'), + 'swimming': _('Swimming'), + 'working_out': _('Working out')}, + 'grooming': {'category': _('Grooming'), + 'at_the_spa': _('At the Spa'), + 'brushing_teeth': _('Brushing Teeth'), + 'getting_a_haircut': _('Getting a Haircut'), + 'shaving': _('Shaving'), + 'taking_a_bath': _('Taking a Bath'), + 'taking_a_shower': _('Taking a Shower')}, + 'having_appointment': {'category': _('Having an Appointment')}, + 'inactive': {'category': _('Inactive'), + 'day_off': _('Day Off'), + 'hanging_out': _('Hanging out'), + 'hiding': _('Hiding'), + 'on_vacation': _('On Vacation'), + 'praying': _('Praying'), + 'scheduled_holiday': _('Scheduled Holiday'), + 'sleeping': _('Sleeping'), + 'thinking': _('Thinking')}, + 'relaxing': {'category': _('Relaxing'), + 'fishing': _('Fishing'), + 'gaming': _('Gaming'), + 'going_out': _('Going out'), + 'partying': _('Partying'), + 'reading': _('Reading'), + 'rehearsing': _('Rehearsing'), + 'shopping': _('Shopping'), + 'smoking': _('Smoking'), + 'socializing': _('Socializing'), + 'sunbathing': _('Sunbathing'), + 'watching_tv': _('Watching TV'), + 'watching_a_movie': _('Watching a Movie')}, + 'talking': {'category': _('Talking'), + 'in_real_life': _('In Real Life'), + 'on_the_phone': _('On the Phone'), + 'on_video_phone': _('On Video Phone')}, + 'traveling': {'category': _('Traveling'), + 'commuting': _('Commuting'), + 'cycling': _('Cycling'), + 'driving': _('Driving'), + 'in_a_car': _('In a Car'), + 'on_a_bus': _('On a Bus'), + 'on_a_plane': _('On a Plane'), + 'on_a_train': _('On a Train'), + 'on_a_trip': _('On a Trip'), + 'walking': _('Walking')}, + 'working': {'category': _('Working'), + 'coding': _('Coding'), + 'in_a_meeting': _('In a Meeting'), + 'studying': _('Studying'), + 'writing': _('Writing')}} TUNE_DATA = ['artist', 'title', 'source', 'track', 'length'] LOCATION_DATA = ['accuracy', 'alt', 'area', 'bearing', 'building', 'country', - 'countrycode', 'datum', 'description', 'error', 'floor', 'lat', - 'locality', 'lon', 'postalcode', 'region', 'room', 'speed', 'street', - 'text', 'timestamp', 'uri'] + 'countrycode', 'datum', 'description', 'error', 'floor', 'lat', + 'locality', 'lon', 'postalcode', 'region', 'room', 'speed', 'street', + 'text', 'timestamp', 'uri'] import gobject import gtk @@ -212,470 +212,468 @@ import gtkgui_helpers class AbstractPEP(object): - type = '' - namespace = '' + type = '' + namespace = '' - @classmethod - def get_tag_as_PEP(cls, jid, account, event_tag): - items = event_tag.getTag('items', {'node': cls.namespace}) - if items: - log.debug("Received PEP 'user %s' from %s" % (cls.type, jid)) - return cls(jid, account, items) - else: - return None + @classmethod + def get_tag_as_PEP(cls, jid, account, event_tag): + items = event_tag.getTag('items', {'node': cls.namespace}) + if items: + log.debug("Received PEP 'user %s' from %s" % (cls.type, jid)) + return cls(jid, account, items) + else: + return None - def __init__(self, jid, account, items): - self._pep_specific_data, self._retracted = self._extract_info(items) + def __init__(self, jid, account, items): + self._pep_specific_data, self._retracted = self._extract_info(items) - self._update_contacts(jid, account) - if jid == gajim.get_jid_from_account(account): - self._update_account(account) + self._update_contacts(jid, account) + if jid == gajim.get_jid_from_account(account): + self._update_account(account) - def _extract_info(self, items): - '''To be implemented by subclasses''' - raise NotImplementedError + def _extract_info(self, items): + '''To be implemented by subclasses''' + raise NotImplementedError - def _update_contacts(self, jid, account): - for contact in gajim.contacts.get_contacts(account, jid): - if self._retracted: - if self.type in contact.pep: - del contact.pep[self.type] - else: - contact.pep[self.type] = self + def _update_contacts(self, jid, account): + for contact in gajim.contacts.get_contacts(account, jid): + if self._retracted: + if self.type in contact.pep: + del contact.pep[self.type] + else: + contact.pep[self.type] = self - def _update_account(self, account): - acc = gajim.connections[account] - if self._retracted: - if self.type in acc.pep: - del acc.pep[self.type] - else: - acc.pep[self.type] = self + def _update_account(self, account): + acc = gajim.connections[account] + if self._retracted: + if self.type in acc.pep: + del acc.pep[self.type] + else: + acc.pep[self.type] = self - def asPixbufIcon(self): - '''SHOULD be implemented by subclasses''' - return None + def asPixbufIcon(self): + '''SHOULD be implemented by subclasses''' + return None - def asMarkupText(self): - '''SHOULD be implemented by subclasses''' - return '' + def asMarkupText(self): + '''SHOULD be implemented by subclasses''' + return '' class UserMoodPEP(AbstractPEP): - '''XEP-0107: User Mood''' + '''XEP-0107: User Mood''' - type = 'mood' - namespace = xmpp.NS_MOOD + type = 'mood' + namespace = xmpp.NS_MOOD - def _extract_info(self, items): - mood_dict = {} + def _extract_info(self, items): + mood_dict = {} - for item in items.getTags('item'): - mood_tag = item.getTag('mood') - if mood_tag: - for child in mood_tag.getChildren(): - name = child.getName().strip() - if name == 'text': - mood_dict['text'] = child.getData() - else: - mood_dict['mood'] = name + for item in items.getTags('item'): + mood_tag = item.getTag('mood') + if mood_tag: + for child in mood_tag.getChildren(): + name = child.getName().strip() + if name == 'text': + mood_dict['text'] = child.getData() + else: + mood_dict['mood'] = name - retracted = items.getTag('retract') or not 'mood' in mood_dict - return (mood_dict, retracted) + retracted = items.getTag('retract') or not 'mood' in mood_dict + return (mood_dict, retracted) - def asPixbufIcon(self): - assert not self._retracted - received_mood = self._pep_specific_data['mood'] - mood = received_mood if received_mood in MOODS else 'unknown' - pixbuf = gtkgui_helpers.load_mood_icon(mood).get_pixbuf() - return pixbuf + def asPixbufIcon(self): + assert not self._retracted + received_mood = self._pep_specific_data['mood'] + mood = received_mood if received_mood in MOODS else 'unknown' + pixbuf = gtkgui_helpers.load_mood_icon(mood).get_pixbuf() + return pixbuf - def asMarkupText(self): - assert not self._retracted - untranslated_mood = self._pep_specific_data['mood'] - mood = self._translate_mood(untranslated_mood) - markuptext = '%s' % gobject.markup_escape_text(mood) - if 'text' in self._pep_specific_data: - text = self._pep_specific_data['text'] - markuptext += ' (%s)' % gobject.markup_escape_text(text) - return markuptext + def asMarkupText(self): + assert not self._retracted + untranslated_mood = self._pep_specific_data['mood'] + mood = self._translate_mood(untranslated_mood) + markuptext = '%s' % gobject.markup_escape_text(mood) + if 'text' in self._pep_specific_data: + text = self._pep_specific_data['text'] + markuptext += ' (%s)' % gobject.markup_escape_text(text) + return markuptext - def _translate_mood(self, mood): - if mood in MOODS: - return MOODS[mood] - else: - return mood + def _translate_mood(self, mood): + if mood in MOODS: + return MOODS[mood] + else: + return mood class UserTunePEP(AbstractPEP): - '''XEP-0118: User Tune''' + '''XEP-0118: User Tune''' - type = 'tune' - namespace = xmpp.NS_TUNE + type = 'tune' + namespace = xmpp.NS_TUNE - def _extract_info(self, items): - tune_dict = {} + def _extract_info(self, items): + tune_dict = {} - for item in items.getTags('item'): - tune_tag = item.getTag('tune') - if tune_tag: - for child in tune_tag.getChildren(): - name = child.getName().strip() - data = child.getData().strip() - if child.getName() in TUNE_DATA: - tune_dict[name] = data + for item in items.getTags('item'): + tune_tag = item.getTag('tune') + if tune_tag: + for child in tune_tag.getChildren(): + name = child.getName().strip() + data = child.getData().strip() + if child.getName() in TUNE_DATA: + tune_dict[name] = data - retracted = items.getTag('retract') or not ('artist' in tune_dict or - 'title' in tune_dict) - return (tune_dict, retracted) + retracted = items.getTag('retract') or not ('artist' in tune_dict or + 'title' in tune_dict) + return (tune_dict, retracted) - def asPixbufIcon(self): - import os - path = os.path.join(gajim.DATA_DIR, 'emoticons', 'static', 'music.png') - return gtk.gdk.pixbuf_new_from_file(path) + def asPixbufIcon(self): + import os + path = os.path.join(gajim.DATA_DIR, 'emoticons', 'static', 'music.png') + return gtk.gdk.pixbuf_new_from_file(path) - def asMarkupText(self): - assert not self._retracted - tune = self._pep_specific_data + def asMarkupText(self): + assert not self._retracted + tune = self._pep_specific_data - artist = tune.get('artist', _('Unknown Artist')) - artist = gobject.markup_escape_text(artist) + artist = tune.get('artist', _('Unknown Artist')) + artist = gobject.markup_escape_text(artist) - title = tune.get('title', _('Unknown Title')) - title = gobject.markup_escape_text(title) + title = tune.get('title', _('Unknown Title')) + title = gobject.markup_escape_text(title) - source = tune.get('source', _('Unknown Source')) - source = gobject.markup_escape_text(source) + source = tune.get('source', _('Unknown Source')) + source = gobject.markup_escape_text(source) - tune_string = _('"%(title)s" by %(artist)s\n' - 'from %(source)s') % {'title': title, - 'artist': artist, 'source': source} - return tune_string + tune_string = _('"%(title)s" by %(artist)s\n' + 'from %(source)s') % {'title': title, + 'artist': artist, 'source': source} + return tune_string class UserActivityPEP(AbstractPEP): - '''XEP-0108: User Activity''' + '''XEP-0108: User Activity''' - type = 'activity' - namespace = xmpp.NS_ACTIVITY + type = 'activity' + namespace = xmpp.NS_ACTIVITY - def _extract_info(self, items): - activity_dict = {} + def _extract_info(self, items): + activity_dict = {} - for item in items.getTags('item'): - activity_tag = item.getTag('activity') - if activity_tag: - for child in activity_tag.getChildren(): - name = child.getName().strip() - data = child.getData().strip() - if name == 'text': - activity_dict['text'] = data - else: - activity_dict['activity'] = name - for subactivity in child.getChildren(): - subactivity_name = subactivity.getName().strip() - activity_dict['subactivity'] = subactivity_name + for item in items.getTags('item'): + activity_tag = item.getTag('activity') + if activity_tag: + for child in activity_tag.getChildren(): + name = child.getName().strip() + data = child.getData().strip() + if name == 'text': + activity_dict['text'] = data + else: + activity_dict['activity'] = name + for subactivity in child.getChildren(): + subactivity_name = subactivity.getName().strip() + activity_dict['subactivity'] = subactivity_name - retracted = items.getTag('retract') or not 'activity' in activity_dict - return (activity_dict, retracted) + retracted = items.getTag('retract') or not 'activity' in activity_dict + return (activity_dict, retracted) - def asPixbufIcon(self): - assert not self._retracted - pep = self._pep_specific_data - activity = pep['activity'] + def asPixbufIcon(self): + assert not self._retracted + pep = self._pep_specific_data + activity = pep['activity'] - has_known_activity = activity in ACTIVITIES - has_known_subactivity = (has_known_activity and ('subactivity' in pep) - and (pep['subactivity'] in ACTIVITIES[activity])) + has_known_activity = activity in ACTIVITIES + has_known_subactivity = (has_known_activity and ('subactivity' in pep) + and (pep['subactivity'] in ACTIVITIES[activity])) - if has_known_activity: - if has_known_subactivity: - subactivity = pep['subactivity'] - return gtkgui_helpers.load_activity_icon(activity, subactivity).get_pixbuf() - else: - return gtkgui_helpers.load_activity_icon(activity).get_pixbuf() - else: - return gtkgui_helpers.load_activity_icon('unknown').get_pixbuf() + if has_known_activity: + if has_known_subactivity: + subactivity = pep['subactivity'] + return gtkgui_helpers.load_activity_icon(activity, subactivity).get_pixbuf() + else: + return gtkgui_helpers.load_activity_icon(activity).get_pixbuf() + else: + return gtkgui_helpers.load_activity_icon('unknown').get_pixbuf() - def asMarkupText(self): - assert not self._retracted - pep = self._pep_specific_data - activity = pep['activity'] - subactivity = pep['subactivity'] if 'subactivity' in pep else None - text = pep['text'] if 'text' in pep else None + def asMarkupText(self): + assert not self._retracted + pep = self._pep_specific_data + activity = pep['activity'] + subactivity = pep['subactivity'] if 'subactivity' in pep else None + text = pep['text'] if 'text' in pep else None - if activity in ACTIVITIES: - # Translate standard activities - if subactivity in ACTIVITIES[activity]: - subactivity = ACTIVITIES[activity][subactivity] - activity = ACTIVITIES[activity]['category'] + if activity in ACTIVITIES: + # Translate standard activities + if subactivity in ACTIVITIES[activity]: + subactivity = ACTIVITIES[activity][subactivity] + activity = ACTIVITIES[activity]['category'] - markuptext = '' + gobject.markup_escape_text(activity) - if subactivity: - markuptext += ': ' + gobject.markup_escape_text(subactivity) - markuptext += '' - if text: - markuptext += ' (%s)' % gobject.markup_escape_text(text) - return markuptext + markuptext = '' + gobject.markup_escape_text(activity) + if subactivity: + markuptext += ': ' + gobject.markup_escape_text(subactivity) + markuptext += '' + if text: + markuptext += ' (%s)' % gobject.markup_escape_text(text) + return markuptext class UserNicknamePEP(AbstractPEP): - '''XEP-0172: User Nickname''' + '''XEP-0172: User Nickname''' - type = 'nickname' - namespace = xmpp.NS_NICK + type = 'nickname' + namespace = xmpp.NS_NICK - def _extract_info(self, items): - nick = '' - for item in items.getTags('item'): - child = item.getTag('nick') - if child: - nick = child.getData() - break + def _extract_info(self, items): + nick = '' + for item in items.getTags('item'): + child = item.getTag('nick') + if child: + nick = child.getData() + break - retracted = items.getTag('retract') or not nick - return (nick, retracted) + retracted = items.getTag('retract') or not nick + return (nick, retracted) - def _update_contacts(self, jid, account): - nick = '' if self._retracted else self._pep_specific_data - for contact in gajim.contacts.get_contacts(account, jid): - contact.contact_name = nick + def _update_contacts(self, jid, account): + nick = '' if self._retracted else self._pep_specific_data + for contact in gajim.contacts.get_contacts(account, jid): + contact.contact_name = nick - def _update_account(self, account): - if self._retracted: - gajim.nicks[account] = gajim.config.get_per('accounts', account, 'name') - else: - gajim.nicks[account] = self._pep_specific_data + def _update_account(self, account): + if self._retracted: + gajim.nicks[account] = gajim.config.get_per('accounts', account, 'name') + else: + gajim.nicks[account] = self._pep_specific_data class UserLocationPEP(AbstractPEP): - '''XEP-0080: User Location''' + '''XEP-0080: User Location''' - type = 'location' - namespace = xmpp.NS_LOCATION + type = 'location' + namespace = xmpp.NS_LOCATION - def _extract_info(self, items): - location_dict = {} + def _extract_info(self, items): + location_dict = {} - for item in items.getTags('item'): - location_tag = item.getTag('geoloc') - if location_tag: - for child in location_tag.getChildren(): - name = child.getName().strip() - data = child.getData().strip() - if child.getName() in LOCATION_DATA: - location_dict[name] = data + for item in items.getTags('item'): + location_tag = item.getTag('geoloc') + if location_tag: + for child in location_tag.getChildren(): + name = child.getName().strip() + data = child.getData().strip() + if child.getName() in LOCATION_DATA: + location_dict[name] = data - retracted = items.getTag('retract') or not location_dict - return (location_dict, retracted) + retracted = items.getTag('retract') or not location_dict + return (location_dict, retracted) - def _update_account(self, account): - AbstractPEP._update_account(self, account) - con = gajim.connections[account].location_info = \ - self._pep_specific_data + def _update_account(self, account): + AbstractPEP._update_account(self, account) + con = gajim.connections[account].location_info = \ + self._pep_specific_data - def asPixbufIcon(self): - path = gtkgui_helpers.get_icon_path('gajim-earth') - return gtk.gdk.pixbuf_new_from_file(path) + def asPixbufIcon(self): + path = gtkgui_helpers.get_icon_path('gajim-earth') + return gtk.gdk.pixbuf_new_from_file(path) - def asMarkupText(self): - assert not self._retracted - location = self._pep_specific_data - location_string = '' + def asMarkupText(self): + assert not self._retracted + location = self._pep_specific_data + location_string = '' - for entry in location.keys(): - text = location[entry] - text = gobject.markup_escape_text(text) - location_string += '\n%(tag)s: %(text)s' % \ - {'tag': entry.capitalize(), 'text': text} + for entry in location.keys(): + text = location[entry] + text = gobject.markup_escape_text(text) + location_string += '\n%(tag)s: %(text)s' % \ + {'tag': entry.capitalize(), 'text': text} - return location_string.strip() + return location_string.strip() SUPPORTED_PERSONAL_USER_EVENTS = [UserMoodPEP, UserTunePEP, UserActivityPEP, - UserNicknamePEP, UserLocationPEP] + UserNicknamePEP, UserLocationPEP] class ConnectionPEP(object): - def __init__(self, account, dispatcher, pubsub_connection): - self._account = account - self._dispatcher = dispatcher - self._pubsub_connection = pubsub_connection - self.reset_awaiting_pep() + def __init__(self, account, dispatcher, pubsub_connection): + self._account = account + self._dispatcher = dispatcher + self._pubsub_connection = pubsub_connection + self.reset_awaiting_pep() - def reset_awaiting_pep(self): - self.to_be_sent_activity = None - self.to_be_sent_mood = None - self.to_be_sent_tune = None - self.to_be_sent_nick = None - self.to_be_sent_location = None + def reset_awaiting_pep(self): + self.to_be_sent_activity = None + self.to_be_sent_mood = None + self.to_be_sent_tune = None + self.to_be_sent_nick = None + self.to_be_sent_location = None - def send_awaiting_pep(self): - """ - Send pep info that were waiting for connection - """ - if self.to_be_sent_activity: - self.send_activity(*self.to_be_sent_activity) - if self.to_be_sent_mood: - self.send_mood(*self.to_be_sent_mood) - if self.to_be_sent_tune: - self.send_tune(*self.to_be_sent_tune) - if self.to_be_sent_nick: - self.send_nick(self.to_be_sent_nick) - if self.to_be_sent_location: - self.send_location(self.to_be_sent_location) - self.reset_awaiting_pep() + def send_awaiting_pep(self): + """ + Send pep info that were waiting for connection + """ + if self.to_be_sent_activity: + self.send_activity(*self.to_be_sent_activity) + if self.to_be_sent_mood: + self.send_mood(*self.to_be_sent_mood) + if self.to_be_sent_tune: + self.send_tune(*self.to_be_sent_tune) + if self.to_be_sent_nick: + self.send_nick(self.to_be_sent_nick) + if self.to_be_sent_location: + self.send_location(self.to_be_sent_location) + self.reset_awaiting_pep() - def _pubsubEventCB(self, xmpp_dispatcher, msg): - ''' Called when we receive with pubsub event. ''' - if not msg.getTag('event'): - return - if msg.getTag('error'): - log.debug('PubsubEventCB received error stanza. Ignoring') - raise xmpp.NodeProcessed + def _pubsubEventCB(self, xmpp_dispatcher, msg): + ''' Called when we receive with pubsub event. ''' + if not msg.getTag('event'): + return + if msg.getTag('error'): + log.debug('PubsubEventCB received error stanza. Ignoring') + raise xmpp.NodeProcessed - jid = helpers.get_full_jid_from_iq(msg) - event_tag = msg.getTag('event') + jid = helpers.get_full_jid_from_iq(msg) + event_tag = msg.getTag('event') - for pep_class in SUPPORTED_PERSONAL_USER_EVENTS: - pep = pep_class.get_tag_as_PEP(jid, self._account, event_tag) - if pep: - self._dispatcher.dispatch('PEP_RECEIVED', (jid, pep.type)) + for pep_class in SUPPORTED_PERSONAL_USER_EVENTS: + pep = pep_class.get_tag_as_PEP(jid, self._account, event_tag) + if pep: + self._dispatcher.dispatch('PEP_RECEIVED', (jid, pep.type)) - items = event_tag.getTag('items') - if items: - for item in items.getTags('item'): - entry = item.getTag('entry') - if entry: - # for each entry in feed (there shouldn't be more than one, - # but to be sure... - self._dispatcher.dispatch('ATOM_ENTRY', - (atom.OldEntry(node=entry),)) + items = event_tag.getTag('items') + if items: + for item in items.getTags('item'): + entry = item.getTag('entry') + if entry: + # for each entry in feed (there shouldn't be more than one, + # but to be sure... + self._dispatcher.dispatch('ATOM_ENTRY', + (atom.OldEntry(node=entry),)) - raise xmpp.NodeProcessed + raise xmpp.NodeProcessed - def send_activity(self, activity, subactivity=None, message=None): - if self.connected == 1: - # We are connecting, keep activity in mem and send it when we'll be - # connected - self.to_be_sent_activity = (activity, subactivity, message) - return - if not self.pep_supported: - return - item = xmpp.Node('activity', {'xmlns': xmpp.NS_ACTIVITY}) - if activity: - i = item.addChild(activity) - if subactivity: - i.addChild(subactivity) - if message: - i = item.addChild('text') - i.addData(message) - self._pubsub_connection.send_pb_publish('', xmpp.NS_ACTIVITY, item, '0') + def send_activity(self, activity, subactivity=None, message=None): + if self.connected == 1: + # We are connecting, keep activity in mem and send it when we'll be + # connected + self.to_be_sent_activity = (activity, subactivity, message) + return + if not self.pep_supported: + return + item = xmpp.Node('activity', {'xmlns': xmpp.NS_ACTIVITY}) + if activity: + i = item.addChild(activity) + if subactivity: + i.addChild(subactivity) + if message: + i = item.addChild('text') + i.addData(message) + self._pubsub_connection.send_pb_publish('', xmpp.NS_ACTIVITY, item, '0') - def retract_activity(self): - if not self.pep_supported: - return - self.send_activity(None) - # not all client support new XEP, so we still retract - self._pubsub_connection.send_pb_retract('', xmpp.NS_ACTIVITY, '0') + def retract_activity(self): + if not self.pep_supported: + return + self.send_activity(None) + # not all client support new XEP, so we still retract + self._pubsub_connection.send_pb_retract('', xmpp.NS_ACTIVITY, '0') - def send_mood(self, mood, message=None): - if self.connected == 1: - # We are connecting, keep mood in mem and send it when we'll be - # connected - self.to_be_sent_mood = (mood, message) - return - if not self.pep_supported: - return - item = xmpp.Node('mood', {'xmlns': xmpp.NS_MOOD}) - if mood: - item.addChild(mood) - if message: - i = item.addChild('text') - i.addData(message) - self._pubsub_connection.send_pb_publish('', xmpp.NS_MOOD, item, '0') + def send_mood(self, mood, message=None): + if self.connected == 1: + # We are connecting, keep mood in mem and send it when we'll be + # connected + self.to_be_sent_mood = (mood, message) + return + if not self.pep_supported: + return + item = xmpp.Node('mood', {'xmlns': xmpp.NS_MOOD}) + if mood: + item.addChild(mood) + if message: + i = item.addChild('text') + i.addData(message) + self._pubsub_connection.send_pb_publish('', xmpp.NS_MOOD, item, '0') - def retract_mood(self): - if not self.pep_supported: - return - self.send_mood(None) - # not all client support new XEP, so we still retract - self._pubsub_connection.send_pb_retract('', xmpp.NS_MOOD, '0') + def retract_mood(self): + if not self.pep_supported: + return + self.send_mood(None) + # not all client support new XEP, so we still retract + self._pubsub_connection.send_pb_retract('', xmpp.NS_MOOD, '0') - def send_tune(self, artist='', title='', source='', track=0, length=0, - items=None): - if self.connected == 1: - # We are connecting, keep tune in mem and send it when we'll be - # connected - self.to_be_sent_tune = (artist, title, source, track, length, items) - return - if not self.pep_supported: - return - item = xmpp.Node('tune', {'xmlns': xmpp.NS_TUNE}) - if artist: - i = item.addChild('artist') - i.addData(artist) - if title: - i = item.addChild('title') - i.addData(title) - if source: - i = item.addChild('source') - i.addData(source) - if track: - i = item.addChild('track') - i.addData(track) - if length: - i = item.addChild('length') - i.addData(length) - if items: - item.addChild(payload=items) - self._pubsub_connection.send_pb_publish('', xmpp.NS_TUNE, item, '0') + def send_tune(self, artist='', title='', source='', track=0, length=0, + items=None): + if self.connected == 1: + # We are connecting, keep tune in mem and send it when we'll be + # connected + self.to_be_sent_tune = (artist, title, source, track, length, items) + return + if not self.pep_supported: + return + item = xmpp.Node('tune', {'xmlns': xmpp.NS_TUNE}) + if artist: + i = item.addChild('artist') + i.addData(artist) + if title: + i = item.addChild('title') + i.addData(title) + if source: + i = item.addChild('source') + i.addData(source) + if track: + i = item.addChild('track') + i.addData(track) + if length: + i = item.addChild('length') + i.addData(length) + if items: + item.addChild(payload=items) + self._pubsub_connection.send_pb_publish('', xmpp.NS_TUNE, item, '0') - def retract_tune(self): - if not self.pep_supported: - return - self.send_tune(None) - # not all client support new XEP, so we still retract - self._pubsub_connection.send_pb_retract('', xmpp.NS_TUNE, '0') + def retract_tune(self): + if not self.pep_supported: + return + self.send_tune(None) + # not all client support new XEP, so we still retract + self._pubsub_connection.send_pb_retract('', xmpp.NS_TUNE, '0') - def send_nickname(self, nick): - if self.connected == 1: - # We are connecting, keep nick in mem and send it when we'll be - # connected - self.to_be_sent_nick = nick - return - if not self.pep_supported: - return - item = xmpp.Node('nick', {'xmlns': xmpp.NS_NICK}) - item.addData(nick) - self._pubsub_connection.send_pb_publish('', xmpp.NS_NICK, item, '0') + def send_nickname(self, nick): + if self.connected == 1: + # We are connecting, keep nick in mem and send it when we'll be + # connected + self.to_be_sent_nick = nick + return + if not self.pep_supported: + return + item = xmpp.Node('nick', {'xmlns': xmpp.NS_NICK}) + item.addData(nick) + self._pubsub_connection.send_pb_publish('', xmpp.NS_NICK, item, '0') - def retract_nickname(self): - if not self.pep_supported: - return - self.send_nickname(None) - # not all client support new XEP, so we still retract - self._pubsub_connection.send_pb_retract('', xmpp.NS_NICK, '0') + def retract_nickname(self): + if not self.pep_supported: + return + self.send_nickname(None) + # not all client support new XEP, so we still retract + self._pubsub_connection.send_pb_retract('', xmpp.NS_NICK, '0') - def send_location(self, info): - if self.connected == 1: - # We are connecting, keep location in mem and send it when we'll be - # connected - self.to_be_sent_location = info - return - if not self.pep_supported: - return - item = xmpp.Node('geoloc', {'xmlns': xmpp.NS_LOCATION}) - for field in LOCATION_DATA: - if info.get(field, None): - i = item.addChild(field) - i.addData(info[field]) - self._pubsub_connection.send_pb_publish('', xmpp.NS_LOCATION, item, '0') + def send_location(self, info): + if self.connected == 1: + # We are connecting, keep location in mem and send it when we'll be + # connected + self.to_be_sent_location = info + return + if not self.pep_supported: + return + item = xmpp.Node('geoloc', {'xmlns': xmpp.NS_LOCATION}) + for field in LOCATION_DATA: + if info.get(field, None): + i = item.addChild(field) + i.addData(info[field]) + self._pubsub_connection.send_pb_publish('', xmpp.NS_LOCATION, item, '0') - def retract_location(self): - if not self.pep_supported: - return - self.send_location({}) - # not all client support new XEP, so we still retract - self._pubsub_connection.send_pb_retract('', xmpp.NS_LOCATION, '0') - -# vim: se ts=3: + def retract_location(self): + if not self.pep_supported: + return + self.send_location({}) + # not all client support new XEP, so we still retract + self._pubsub_connection.send_pb_retract('', xmpp.NS_LOCATION, '0') diff --git a/src/common/protocol/__init__.py b/src/common/protocol/__init__.py index f50b2cd01..2d167f344 100644 --- a/src/common/protocol/__init__.py +++ b/src/common/protocol/__init__.py @@ -1,3 +1,3 @@ """ Implementations of specific XMPP protocols and XEPs -""" \ No newline at end of file +""" diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py index ce0086b19..f70b50aa5 100644 --- a/src/common/protocol/bytestream.py +++ b/src/common/protocol/bytestream.py @@ -39,612 +39,610 @@ from common.socks5 import Socks5Receiver def is_transfer_paused(file_props): - if 'stopped' in file_props and file_props['stopped']: - return False - if 'completed' in file_props and file_props['completed']: - return False - if 'disconnect_cb' not in file_props: - return False - return file_props['paused'] + if 'stopped' in file_props and file_props['stopped']: + return False + if 'completed' in file_props and file_props['completed']: + return False + if 'disconnect_cb' not in file_props: + return False + return file_props['paused'] def is_transfer_active(file_props): - if 'stopped' in file_props and file_props['stopped']: - return False - if 'completed' in file_props and file_props['completed']: - return False - if 'started' not in file_props or not file_props['started']: - return False - if 'paused' not in file_props: - return True - return not file_props['paused'] + if 'stopped' in file_props and file_props['stopped']: + return False + if 'completed' in file_props and file_props['completed']: + return False + if 'started' not in file_props or not file_props['started']: + return False + if 'paused' not in file_props: + return True + return not file_props['paused'] def is_transfer_stopped(file_props): - if 'error' in file_props and file_props['error'] != 0: - return True - if 'completed' in file_props and file_props['completed']: - return True - if 'connected' in file_props and file_props['connected'] == False: - return True - if 'stopped' not in file_props or not file_props['stopped']: - return False - return True + if 'error' in file_props and file_props['error'] != 0: + return True + if 'completed' in file_props and file_props['completed']: + return True + if 'connected' in file_props and file_props['connected'] == False: + return True + if 'stopped' not in file_props or not file_props['stopped']: + return False + return True class ConnectionBytestream: - def __init__(self): - self.files_props = {} + def __init__(self): + self.files_props = {} - def send_success_connect_reply(self, streamhost): - """ - Send reply to the initiator of FT that we made a connection - """ - if not self.connection or self.connected < 2: - return - if streamhost is None: - return None - iq = xmpp.Iq(to=streamhost['initiator'], typ='result', - frm=streamhost['target']) - iq.setAttr('id', streamhost['id']) - query = iq.setTag('query', namespace=xmpp.NS_BYTESTREAM) - stream_tag = query.setTag('streamhost-used') - stream_tag.setAttr('jid', streamhost['jid']) - self.connection.send(iq) + def send_success_connect_reply(self, streamhost): + """ + Send reply to the initiator of FT that we made a connection + """ + if not self.connection or self.connected < 2: + return + if streamhost is None: + return None + iq = xmpp.Iq(to=streamhost['initiator'], typ='result', + frm=streamhost['target']) + iq.setAttr('id', streamhost['id']) + query = iq.setTag('query', namespace=xmpp.NS_BYTESTREAM) + stream_tag = query.setTag('streamhost-used') + stream_tag.setAttr('jid', streamhost['jid']) + self.connection.send(iq) - def stop_all_active_file_transfers(self, contact): - """ - Stop all active transfer to or from the given contact - """ - for file_props in self.files_props.values(): - if is_transfer_stopped(file_props): - continue - receiver_jid = unicode(file_props['receiver']) - if contact.get_full_jid() == receiver_jid: - file_props['error'] = -5 - self.remove_transfer(file_props) - self.dispatch('FILE_REQUEST_ERROR', (contact.jid, file_props, '')) - sender_jid = unicode(file_props['sender']) - if contact.get_full_jid() == sender_jid: - file_props['error'] = -3 - self.remove_transfer(file_props) + def stop_all_active_file_transfers(self, contact): + """ + Stop all active transfer to or from the given contact + """ + for file_props in self.files_props.values(): + if is_transfer_stopped(file_props): + continue + receiver_jid = unicode(file_props['receiver']) + if contact.get_full_jid() == receiver_jid: + file_props['error'] = -5 + self.remove_transfer(file_props) + self.dispatch('FILE_REQUEST_ERROR', (contact.jid, file_props, '')) + sender_jid = unicode(file_props['sender']) + if contact.get_full_jid() == sender_jid: + file_props['error'] = -3 + self.remove_transfer(file_props) - def remove_all_transfers(self): - """ - Stop and remove all active connections from the socks5 pool - """ - for file_props in self.files_props.values(): - self.remove_transfer(file_props, remove_from_list=False) - self.files_props = {} + def remove_all_transfers(self): + """ + Stop and remove all active connections from the socks5 pool + """ + for file_props in self.files_props.values(): + self.remove_transfer(file_props, remove_from_list=False) + self.files_props = {} - def remove_transfer(self, file_props, remove_from_list=True): - if file_props is None: - return - self.disconnect_transfer(file_props) - sid = file_props['sid'] - gajim.socks5queue.remove_file_props(self.name, sid) + def remove_transfer(self, file_props, remove_from_list=True): + if file_props is None: + return + self.disconnect_transfer(file_props) + sid = file_props['sid'] + gajim.socks5queue.remove_file_props(self.name, sid) - if remove_from_list: - if 'sid' in self.files_props: - del(self.files_props['sid']) + if remove_from_list: + if 'sid' in self.files_props: + del(self.files_props['sid']) - def disconnect_transfer(self, file_props): - if file_props is None: - return - if 'hash' in file_props: - gajim.socks5queue.remove_sender(file_props['hash']) + def disconnect_transfer(self, file_props): + if file_props is None: + return + if 'hash' in file_props: + gajim.socks5queue.remove_sender(file_props['hash']) - if 'streamhosts' in file_props: - for host in file_props['streamhosts']: - if 'idx' in host and host['idx'] > 0: - gajim.socks5queue.remove_receiver(host['idx']) - gajim.socks5queue.remove_sender(host['idx']) + if 'streamhosts' in file_props: + for host in file_props['streamhosts']: + if 'idx' in host and host['idx'] > 0: + gajim.socks5queue.remove_receiver(host['idx']) + gajim.socks5queue.remove_sender(host['idx']) - def _send_socks5_info(self, file_props): - """ - Send iq for the present streamhosts and proxies - """ - if not self.connection or self.connected < 2: - return - receiver = file_props['receiver'] - sender = file_props['sender'] + def _send_socks5_info(self, file_props): + """ + Send iq for the present streamhosts and proxies + """ + if not self.connection or self.connected < 2: + return + receiver = file_props['receiver'] + sender = file_props['sender'] - sha_str = helpers.get_auth_sha(file_props['sid'], sender, receiver) - file_props['sha_str'] = sha_str + sha_str = helpers.get_auth_sha(file_props['sid'], sender, receiver) + file_props['sha_str'] = sha_str - port = gajim.config.get('file_transfers_port') - listener = gajim.socks5queue.start_listener(port, sha_str, - self._result_socks5_sid, file_props['sid']) - if not listener: - file_props['error'] = -5 - self.dispatch('FILE_REQUEST_ERROR', (unicode(receiver), file_props, '')) - self._connect_error(unicode(receiver), file_props['sid'], - file_props['sid'], code=406) - else: - iq = xmpp.Iq(to=unicode(receiver), typ='set') - file_props['request-id'] = 'id_' + file_props['sid'] - iq.setID(file_props['request-id']) - query = iq.setTag('query', namespace=xmpp.NS_BYTESTREAM) - query.setAttr('mode', 'plain') - query.setAttr('sid', file_props['sid']) + port = gajim.config.get('file_transfers_port') + listener = gajim.socks5queue.start_listener(port, sha_str, + self._result_socks5_sid, file_props['sid']) + if not listener: + file_props['error'] = -5 + self.dispatch('FILE_REQUEST_ERROR', (unicode(receiver), file_props, '')) + self._connect_error(unicode(receiver), file_props['sid'], + file_props['sid'], code=406) + else: + iq = xmpp.Iq(to=unicode(receiver), typ='set') + file_props['request-id'] = 'id_' + file_props['sid'] + iq.setID(file_props['request-id']) + query = iq.setTag('query', namespace=xmpp.NS_BYTESTREAM) + query.setAttr('mode', 'plain') + query.setAttr('sid', file_props['sid']) - self._add_addiditional_streamhosts_to_query(query, file_props) - self._add_local_ips_as_streamhosts_to_query(query, file_props) - self._add_proxy_streamhosts_to_query(query, file_props) + self._add_addiditional_streamhosts_to_query(query, file_props) + self._add_local_ips_as_streamhosts_to_query(query, file_props) + self._add_proxy_streamhosts_to_query(query, file_props) - self.connection.send(iq) + self.connection.send(iq) - def _add_streamhosts_to_query(self, query, sender, port, hosts): - for host in hosts: - streamhost = xmpp.Node(tag='streamhost') - query.addChild(node=streamhost) - streamhost.setAttr('port', unicode(port)) - streamhost.setAttr('host', host) - streamhost.setAttr('jid', sender) + def _add_streamhosts_to_query(self, query, sender, port, hosts): + for host in hosts: + streamhost = xmpp.Node(tag='streamhost') + query.addChild(node=streamhost) + streamhost.setAttr('port', unicode(port)) + streamhost.setAttr('host', host) + streamhost.setAttr('jid', sender) - def _add_local_ips_as_streamhosts_to_query(self, query, file_props): - try: - my_ips = [self.peerhost[0]] # The ip we're connected to server with - # all IPs from local DNS - for addr in socket.getaddrinfo(socket.gethostname(), None): - if not addr[4][0] in my_ips and not addr[4][0].startswith('127'): - my_ips.append(addr[4][0]) + def _add_local_ips_as_streamhosts_to_query(self, query, file_props): + try: + my_ips = [self.peerhost[0]] # The ip we're connected to server with + # all IPs from local DNS + for addr in socket.getaddrinfo(socket.gethostname(), None): + if not addr[4][0] in my_ips and not addr[4][0].startswith('127'): + my_ips.append(addr[4][0]) - sender = file_props['sender'] - port = gajim.config.get('file_transfers_port') - self._add_streamhosts_to_query(query, sender, port, my_ips) - except socket.gaierror: - self.dispatch('ERROR', (_('Wrong host'), - _('Invalid local address? :-O'))) + sender = file_props['sender'] + port = gajim.config.get('file_transfers_port') + self._add_streamhosts_to_query(query, sender, port, my_ips) + except socket.gaierror: + self.dispatch('ERROR', (_('Wrong host'), + _('Invalid local address? :-O'))) - def _add_addiditional_streamhosts_to_query(self, query, file_props): - sender = file_props['sender'] - port = gajim.config.get('file_transfers_port') - ft_add_hosts_to_send = gajim.config.get('ft_add_hosts_to_send') - additional_hosts = [] - if ft_add_hosts_to_send: - additional_hosts = [e.strip() for e in ft_add_hosts_to_send.split(',')] - else: - additional_hosts = [] - self._add_streamhosts_to_query(query, sender, port, additional_hosts) + def _add_addiditional_streamhosts_to_query(self, query, file_props): + sender = file_props['sender'] + port = gajim.config.get('file_transfers_port') + ft_add_hosts_to_send = gajim.config.get('ft_add_hosts_to_send') + additional_hosts = [] + if ft_add_hosts_to_send: + additional_hosts = [e.strip() for e in ft_add_hosts_to_send.split(',')] + else: + additional_hosts = [] + self._add_streamhosts_to_query(query, sender, port, additional_hosts) - def _add_proxy_streamhosts_to_query(self, query, file_props): - proxyhosts = self._get_file_transfer_proxies_from_config(file_props) - if proxyhosts: - file_props['proxy_receiver'] = unicode(file_props['receiver']) - file_props['proxy_sender'] = unicode(file_props['sender']) - file_props['proxyhosts'] = proxyhosts + def _add_proxy_streamhosts_to_query(self, query, file_props): + proxyhosts = self._get_file_transfer_proxies_from_config(file_props) + if proxyhosts: + file_props['proxy_receiver'] = unicode(file_props['receiver']) + file_props['proxy_sender'] = unicode(file_props['sender']) + file_props['proxyhosts'] = proxyhosts - for proxyhost in proxyhosts: - self._add_streamhosts_to_query(query, proxyhost['jid'], - proxyhost['port'], [proxyhost['host']]) + for proxyhost in proxyhosts: + self._add_streamhosts_to_query(query, proxyhost['jid'], + proxyhost['port'], [proxyhost['host']]) - def _get_file_transfer_proxies_from_config(self, file_props): - configured_proxies = gajim.config.get_per('accounts', self.name, - 'file_transfer_proxies') - shall_use_proxies = gajim.config.get_per('accounts', self.name, - 'use_ft_proxies') - if shall_use_proxies and configured_proxies: - proxyhost_dicts = [] - proxies = [item.strip() for item in configured_proxies.split(',')] - default_proxy = gajim.proxy65_manager.get_default_for_name(self.name) - if default_proxy: - # add/move default proxy at top of the others - if default_proxy in proxies: - proxies.remove(default_proxy) - proxies.insert(0, default_proxy) + def _get_file_transfer_proxies_from_config(self, file_props): + configured_proxies = gajim.config.get_per('accounts', self.name, + 'file_transfer_proxies') + shall_use_proxies = gajim.config.get_per('accounts', self.name, + 'use_ft_proxies') + if shall_use_proxies and configured_proxies: + proxyhost_dicts = [] + proxies = [item.strip() for item in configured_proxies.split(',')] + default_proxy = gajim.proxy65_manager.get_default_for_name(self.name) + if default_proxy: + # add/move default proxy at top of the others + if default_proxy in proxies: + proxies.remove(default_proxy) + proxies.insert(0, default_proxy) - for proxy in proxies: - (host, _port, jid) = gajim.proxy65_manager.get_proxy(proxy, self.name) - if not host: - continue - host_dict = { - 'state': 0, - 'target': unicode(file_props['receiver']), - 'id': file_props['sid'], - 'sid': file_props['sid'], - 'initiator': proxy, - 'host': host, - 'port': unicode(_port), - 'jid': jid - } - proxyhost_dicts.append(host_dict) - return proxyhost_dicts - else: - return [] + for proxy in proxies: + (host, _port, jid) = gajim.proxy65_manager.get_proxy(proxy, self.name) + if not host: + continue + host_dict = { + 'state': 0, + 'target': unicode(file_props['receiver']), + 'id': file_props['sid'], + 'sid': file_props['sid'], + 'initiator': proxy, + 'host': host, + 'port': unicode(_port), + 'jid': jid + } + proxyhost_dicts.append(host_dict) + return proxyhost_dicts + else: + return [] - def send_file_rejection(self, file_props, code='403', typ=None): - """ - Inform sender that we refuse to download the file + def send_file_rejection(self, file_props, code='403', typ=None): + """ + Inform sender that we refuse to download the file - typ is used when code = '400', in this case typ can be 'strean' for - invalid stream or 'profile' for invalid profile - """ - # user response to ConfirmationDialog may come after we've disconneted - if not self.connection or self.connected < 2: - return - iq = xmpp.Iq(to=unicode(file_props['sender']), typ='error') - iq.setAttr('id', file_props['request-id']) - if code == '400' and typ in ('stream', 'profile'): - name = 'bad-request' - text = '' - else: - name = 'forbidden' - text = 'Offer Declined' - err = xmpp.ErrorNode(code=code, typ='cancel', name=name, text=text) - if code == '400' and typ in ('stream', 'profile'): - if typ == 'stream': - err.setTag('no-valid-streams', namespace=xmpp.NS_SI) - else: - err.setTag('bad-profile', namespace=xmpp.NS_SI) - iq.addChild(node=err) - self.connection.send(iq) + typ is used when code = '400', in this case typ can be 'strean' for + invalid stream or 'profile' for invalid profile + """ + # user response to ConfirmationDialog may come after we've disconneted + if not self.connection or self.connected < 2: + return + iq = xmpp.Iq(to=unicode(file_props['sender']), typ='error') + iq.setAttr('id', file_props['request-id']) + if code == '400' and typ in ('stream', 'profile'): + name = 'bad-request' + text = '' + else: + name = 'forbidden' + text = 'Offer Declined' + err = xmpp.ErrorNode(code=code, typ='cancel', name=name, text=text) + if code == '400' and typ in ('stream', 'profile'): + if typ == 'stream': + err.setTag('no-valid-streams', namespace=xmpp.NS_SI) + else: + err.setTag('bad-profile', namespace=xmpp.NS_SI) + iq.addChild(node=err) + self.connection.send(iq) - def send_file_approval(self, file_props): - """ - Send iq, confirming that we want to download the file - """ - # user response to ConfirmationDialog may come after we've disconneted - if not self.connection or self.connected < 2: - return - iq = xmpp.Iq(to=unicode(file_props['sender']), typ='result') - iq.setAttr('id', file_props['request-id']) - si = iq.setTag('si', namespace=xmpp.NS_SI) - if 'offset' in file_props and file_props['offset']: - file_tag = si.setTag('file', namespace=xmpp.NS_FILE) - range_tag = file_tag.setTag('range') - range_tag.setAttr('offset', file_props['offset']) - feature = si.setTag('feature', namespace=xmpp.NS_FEATURE) - _feature = xmpp.DataForm(typ='submit') - feature.addChild(node=_feature) - field = _feature.setField('stream-method') - field.delAttr('type') - field.setValue(xmpp.NS_BYTESTREAM) - self.connection.send(iq) + def send_file_approval(self, file_props): + """ + Send iq, confirming that we want to download the file + """ + # user response to ConfirmationDialog may come after we've disconneted + if not self.connection or self.connected < 2: + return + iq = xmpp.Iq(to=unicode(file_props['sender']), typ='result') + iq.setAttr('id', file_props['request-id']) + si = iq.setTag('si', namespace=xmpp.NS_SI) + if 'offset' in file_props and file_props['offset']: + file_tag = si.setTag('file', namespace=xmpp.NS_FILE) + range_tag = file_tag.setTag('range') + range_tag.setAttr('offset', file_props['offset']) + feature = si.setTag('feature', namespace=xmpp.NS_FEATURE) + _feature = xmpp.DataForm(typ='submit') + feature.addChild(node=_feature) + field = _feature.setField('stream-method') + field.delAttr('type') + field.setValue(xmpp.NS_BYTESTREAM) + self.connection.send(iq) - def _ft_get_our_jid(self): - our_jid = gajim.get_jid_from_account(self.name) - resource = self.server_resource - return our_jid + '/' + resource + def _ft_get_our_jid(self): + our_jid = gajim.get_jid_from_account(self.name) + resource = self.server_resource + return our_jid + '/' + resource - def _ft_get_receiver_jid(self, file_props): - return file_props['receiver'].jid + '/' + file_props['receiver'].resource + def _ft_get_receiver_jid(self, file_props): + return file_props['receiver'].jid + '/' + file_props['receiver'].resource - def send_file_request(self, file_props): - """ - Send iq for new FT request - """ - if not self.connection or self.connected < 2: - return - file_props['sender'] = self._ft_get_our_jid() - fjid = self._ft_get_receiver_jid(file_props) - iq = xmpp.Iq(to=fjid, typ='set') - iq.setID(file_props['sid']) - self.files_props[file_props['sid']] = file_props - si = iq.setTag('si', namespace=xmpp.NS_SI) - si.setAttr('profile', xmpp.NS_FILE) - si.setAttr('id', file_props['sid']) - file_tag = si.setTag('file', namespace=xmpp.NS_FILE) - file_tag.setAttr('name', file_props['name']) - file_tag.setAttr('size', file_props['size']) - desc = file_tag.setTag('desc') - if 'desc' in file_props: - desc.setData(file_props['desc']) - file_tag.setTag('range') - feature = si.setTag('feature', namespace=xmpp.NS_FEATURE) - _feature = xmpp.DataForm(typ='form') - feature.addChild(node=_feature) - field = _feature.setField('stream-method') - field.setAttr('type', 'list-single') - field.addOption(xmpp.NS_BYTESTREAM) - self.connection.send(iq) + def send_file_request(self, file_props): + """ + Send iq for new FT request + """ + if not self.connection or self.connected < 2: + return + file_props['sender'] = self._ft_get_our_jid() + fjid = self._ft_get_receiver_jid(file_props) + iq = xmpp.Iq(to=fjid, typ='set') + iq.setID(file_props['sid']) + self.files_props[file_props['sid']] = file_props + si = iq.setTag('si', namespace=xmpp.NS_SI) + si.setAttr('profile', xmpp.NS_FILE) + si.setAttr('id', file_props['sid']) + file_tag = si.setTag('file', namespace=xmpp.NS_FILE) + file_tag.setAttr('name', file_props['name']) + file_tag.setAttr('size', file_props['size']) + desc = file_tag.setTag('desc') + if 'desc' in file_props: + desc.setData(file_props['desc']) + file_tag.setTag('range') + feature = si.setTag('feature', namespace=xmpp.NS_FEATURE) + _feature = xmpp.DataForm(typ='form') + feature.addChild(node=_feature) + field = _feature.setField('stream-method') + field.setAttr('type', 'list-single') + field.addOption(xmpp.NS_BYTESTREAM) + self.connection.send(iq) - def _result_socks5_sid(self, sid, hash_id): - """ - Store the result of SHA message from auth - """ - if sid not in self.files_props: - return - file_props = self.files_props[sid] - file_props['hash'] = hash_id - return + def _result_socks5_sid(self, sid, hash_id): + """ + Store the result of SHA message from auth + """ + if sid not in self.files_props: + return + file_props = self.files_props[sid] + file_props['hash'] = hash_id + return - def _connect_error(self, to, _id, sid, code=404): - """ - Called when there is an error establishing BS connection, or when - connection is rejected - """ - if not self.connection or self.connected < 2: - return - msg_dict = { - 404: 'Could not connect to given hosts', - 405: 'Cancel', - 406: 'Not acceptable', - } - msg = msg_dict[code] - iq = xmpp.Iq(to=to, typ='error') - iq.setAttr('id', _id) - err = iq.setTag('error') - err.setAttr('code', unicode(code)) - err.setData(msg) - self.connection.send(iq) - if code == 404: - file_props = gajim.socks5queue.get_file_props(self.name, sid) - if file_props is not None: - self.disconnect_transfer(file_props) - file_props['error'] = -3 - self.dispatch('FILE_REQUEST_ERROR', (to, file_props, msg)) + def _connect_error(self, to, _id, sid, code=404): + """ + Called when there is an error establishing BS connection, or when + connection is rejected + """ + if not self.connection or self.connected < 2: + return + msg_dict = { + 404: 'Could not connect to given hosts', + 405: 'Cancel', + 406: 'Not acceptable', + } + msg = msg_dict[code] + iq = xmpp.Iq(to=to, typ='error') + iq.setAttr('id', _id) + err = iq.setTag('error') + err.setAttr('code', unicode(code)) + err.setData(msg) + self.connection.send(iq) + if code == 404: + file_props = gajim.socks5queue.get_file_props(self.name, sid) + if file_props is not None: + self.disconnect_transfer(file_props) + file_props['error'] = -3 + self.dispatch('FILE_REQUEST_ERROR', (to, file_props, msg)) - def _proxy_auth_ok(self, proxy): - """ - Called after authentication to proxy server - """ - if not self.connection or self.connected < 2: - return - file_props = self.files_props[proxy['sid']] - iq = xmpp.Iq(to=proxy['initiator'], typ='set') - auth_id = "au_" + proxy['sid'] - iq.setID(auth_id) - query = iq.setTag('query', namespace=xmpp.NS_BYTESTREAM) - query.setAttr('sid', proxy['sid']) - activate = query.setTag('activate') - activate.setData(file_props['proxy_receiver']) - iq.setID(auth_id) - self.connection.send(iq) + def _proxy_auth_ok(self, proxy): + """ + Called after authentication to proxy server + """ + if not self.connection or self.connected < 2: + return + file_props = self.files_props[proxy['sid']] + iq = xmpp.Iq(to=proxy['initiator'], typ='set') + auth_id = "au_" + proxy['sid'] + iq.setID(auth_id) + query = iq.setTag('query', namespace=xmpp.NS_BYTESTREAM) + query.setAttr('sid', proxy['sid']) + activate = query.setTag('activate') + activate.setData(file_props['proxy_receiver']) + iq.setID(auth_id) + self.connection.send(iq) - # register xmpppy handlers for bytestream and FT stanzas - def _bytestreamErrorCB(self, con, iq_obj): - id_ = unicode(iq_obj.getAttr('id')) - frm = helpers.get_full_jid_from_iq(iq_obj) - query = iq_obj.getTag('query') - gajim.proxy65_manager.error_cb(frm, query) - jid = helpers.get_jid_from_iq(iq_obj) - id_ = id_[3:] - if id_ not in self.files_props: - return - file_props = self.files_props[id_] - file_props['error'] = -4 - self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, '')) - raise xmpp.NodeProcessed + # register xmpppy handlers for bytestream and FT stanzas + def _bytestreamErrorCB(self, con, iq_obj): + id_ = unicode(iq_obj.getAttr('id')) + frm = helpers.get_full_jid_from_iq(iq_obj) + query = iq_obj.getTag('query') + gajim.proxy65_manager.error_cb(frm, query) + jid = helpers.get_jid_from_iq(iq_obj) + id_ = id_[3:] + if id_ not in self.files_props: + return + file_props = self.files_props[id_] + file_props['error'] = -4 + self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, '')) + raise xmpp.NodeProcessed - def _ft_get_from(self, iq_obj): - return helpers.get_full_jid_from_iq(iq_obj) + def _ft_get_from(self, iq_obj): + return helpers.get_full_jid_from_iq(iq_obj) - def _bytestreamSetCB(self, con, iq_obj): - target = unicode(iq_obj.getAttr('to')) - id_ = unicode(iq_obj.getAttr('id')) - query = iq_obj.getTag('query') - sid = unicode(query.getAttr('sid')) - file_props = gajim.socks5queue.get_file_props(self.name, sid) - streamhosts = [] - for item in query.getChildren(): - if item.getName() == 'streamhost': - host_dict = { - 'state': 0, - 'target': target, - 'id': id_, - 'sid': sid, - 'initiator': self._ft_get_from(iq_obj) - } - for attr in item.getAttrs(): - host_dict[attr] = item.getAttr(attr) - streamhosts.append(host_dict) - if file_props is None: - if sid in self.files_props: - file_props = self.files_props[sid] - file_props['fast'] = streamhosts - if file_props['type'] == 's': # FIXME: remove fast xmlns - # only psi do this - if 'streamhosts' in file_props: - file_props['streamhosts'].extend(streamhosts) - else: - file_props['streamhosts'] = streamhosts - if not gajim.socks5queue.get_file_props(self.name, sid): - gajim.socks5queue.add_file_props(self.name, file_props) - gajim.socks5queue.connect_to_hosts(self.name, sid, - self.send_success_connect_reply, None) - raise xmpp.NodeProcessed + def _bytestreamSetCB(self, con, iq_obj): + target = unicode(iq_obj.getAttr('to')) + id_ = unicode(iq_obj.getAttr('id')) + query = iq_obj.getTag('query') + sid = unicode(query.getAttr('sid')) + file_props = gajim.socks5queue.get_file_props(self.name, sid) + streamhosts = [] + for item in query.getChildren(): + if item.getName() == 'streamhost': + host_dict = { + 'state': 0, + 'target': target, + 'id': id_, + 'sid': sid, + 'initiator': self._ft_get_from(iq_obj) + } + for attr in item.getAttrs(): + host_dict[attr] = item.getAttr(attr) + streamhosts.append(host_dict) + if file_props is None: + if sid in self.files_props: + file_props = self.files_props[sid] + file_props['fast'] = streamhosts + if file_props['type'] == 's': # FIXME: remove fast xmlns + # only psi do this + if 'streamhosts' in file_props: + file_props['streamhosts'].extend(streamhosts) + else: + file_props['streamhosts'] = streamhosts + if not gajim.socks5queue.get_file_props(self.name, sid): + gajim.socks5queue.add_file_props(self.name, file_props) + gajim.socks5queue.connect_to_hosts(self.name, sid, + self.send_success_connect_reply, None) + raise xmpp.NodeProcessed - file_props['streamhosts'] = streamhosts - if file_props['type'] == 'r': - gajim.socks5queue.connect_to_hosts(self.name, sid, - self.send_success_connect_reply, self._connect_error) - raise xmpp.NodeProcessed + file_props['streamhosts'] = streamhosts + if file_props['type'] == 'r': + gajim.socks5queue.connect_to_hosts(self.name, sid, + self.send_success_connect_reply, self._connect_error) + raise xmpp.NodeProcessed - def _ResultCB(self, con, iq_obj): - # if we want to respect xep-0065 we have to check for proxy - # activation result in any result iq - real_id = unicode(iq_obj.getAttr('id')) - if not real_id.startswith('au_'): - return - frm = self._ft_get_from(iq_obj) - id_ = real_id[3:] - if id_ in self.files_props: - file_props = self.files_props[id_] - if file_props['streamhost-used']: - for host in file_props['proxyhosts']: - if host['initiator'] == frm and 'idx' in host: - gajim.socks5queue.activate_proxy(host['idx']) - raise xmpp.NodeProcessed + def _ResultCB(self, con, iq_obj): + # if we want to respect xep-0065 we have to check for proxy + # activation result in any result iq + real_id = unicode(iq_obj.getAttr('id')) + if not real_id.startswith('au_'): + return + frm = self._ft_get_from(iq_obj) + id_ = real_id[3:] + if id_ in self.files_props: + file_props = self.files_props[id_] + if file_props['streamhost-used']: + for host in file_props['proxyhosts']: + if host['initiator'] == frm and 'idx' in host: + gajim.socks5queue.activate_proxy(host['idx']) + raise xmpp.NodeProcessed - def _ft_get_streamhost_jid_attr(self, streamhost): - return helpers.parse_jid(streamhost.getAttr('jid')) + def _ft_get_streamhost_jid_attr(self, streamhost): + return helpers.parse_jid(streamhost.getAttr('jid')) - def _bytestreamResultCB(self, con, iq_obj): - frm = self._ft_get_from(iq_obj) - real_id = unicode(iq_obj.getAttr('id')) - query = iq_obj.getTag('query') - gajim.proxy65_manager.resolve_result(frm, query) + def _bytestreamResultCB(self, con, iq_obj): + frm = self._ft_get_from(iq_obj) + real_id = unicode(iq_obj.getAttr('id')) + query = iq_obj.getTag('query') + gajim.proxy65_manager.resolve_result(frm, query) - try: - streamhost = query.getTag('streamhost-used') - except Exception: # this bytestream result is not what we need - pass - id_ = real_id[3:] - if id_ in self.files_props: - file_props = self.files_props[id_] - else: - raise xmpp.NodeProcessed - if streamhost is None: - # proxy approves the activate query - if real_id.startswith('au_'): - if 'streamhost-used' not in file_props or \ - file_props['streamhost-used'] is False: - raise xmpp.NodeProcessed - if 'proxyhosts' not in file_props: - raise xmpp.NodeProcessed - for host in file_props['proxyhosts']: - if host['initiator'] == frm and \ - unicode(query.getAttr('sid')) == file_props['sid']: - gajim.socks5queue.activate_proxy(host['idx']) - break - raise xmpp.NodeProcessed - jid = self._ft_get_streamhost_jid_attr(streamhost) - if 'streamhost-used' in file_props and \ - file_props['streamhost-used'] is True: - raise xmpp.NodeProcessed + try: + streamhost = query.getTag('streamhost-used') + except Exception: # this bytestream result is not what we need + pass + id_ = real_id[3:] + if id_ in self.files_props: + file_props = self.files_props[id_] + else: + raise xmpp.NodeProcessed + if streamhost is None: + # proxy approves the activate query + if real_id.startswith('au_'): + if 'streamhost-used' not in file_props or \ + file_props['streamhost-used'] is False: + raise xmpp.NodeProcessed + if 'proxyhosts' not in file_props: + raise xmpp.NodeProcessed + for host in file_props['proxyhosts']: + if host['initiator'] == frm and \ + unicode(query.getAttr('sid')) == file_props['sid']: + gajim.socks5queue.activate_proxy(host['idx']) + break + raise xmpp.NodeProcessed + jid = self._ft_get_streamhost_jid_attr(streamhost) + if 'streamhost-used' in file_props and \ + file_props['streamhost-used'] is True: + raise xmpp.NodeProcessed - if real_id.startswith('au_'): - if 'stopped' in file and file_props['stopped']: - self.remove_transfer(file_props) - else: - gajim.socks5queue.send_file(file_props, self.name) - raise xmpp.NodeProcessed + if real_id.startswith('au_'): + if 'stopped' in file and file_props['stopped']: + self.remove_transfer(file_props) + else: + gajim.socks5queue.send_file(file_props, self.name) + raise xmpp.NodeProcessed - proxy = None - if 'proxyhosts' in file_props: - for proxyhost in file_props['proxyhosts']: - if proxyhost['jid'] == jid: - proxy = proxyhost + proxy = None + if 'proxyhosts' in file_props: + for proxyhost in file_props['proxyhosts']: + if proxyhost['jid'] == jid: + proxy = proxyhost - if proxy is not None: - file_props['streamhost-used'] = True - if 'streamhosts' not in file_props: - file_props['streamhosts'] = [] - file_props['streamhosts'].append(proxy) - file_props['is_a_proxy'] = True - receiver = Socks5Receiver(gajim.idlequeue, proxy, - file_props['sid'], file_props) - gajim.socks5queue.add_receiver(self.name, receiver) - proxy['idx'] = receiver.queue_idx - gajim.socks5queue.on_success = self._proxy_auth_ok - raise xmpp.NodeProcessed + if proxy is not None: + file_props['streamhost-used'] = True + if 'streamhosts' not in file_props: + file_props['streamhosts'] = [] + file_props['streamhosts'].append(proxy) + file_props['is_a_proxy'] = True + receiver = Socks5Receiver(gajim.idlequeue, proxy, + file_props['sid'], file_props) + gajim.socks5queue.add_receiver(self.name, receiver) + proxy['idx'] = receiver.queue_idx + gajim.socks5queue.on_success = self._proxy_auth_ok + raise xmpp.NodeProcessed - else: - if 'stopped' in file_props and file_props['stopped']: - self.remove_transfer(file_props) - else: - gajim.socks5queue.send_file(file_props, self.name) - if 'fast' in file_props: - fasts = file_props['fast'] - if len(fasts) > 0: - self._connect_error(frm, fasts[0]['id'], file_props['sid'], - code=406) + else: + if 'stopped' in file_props and file_props['stopped']: + self.remove_transfer(file_props) + else: + gajim.socks5queue.send_file(file_props, self.name) + if 'fast' in file_props: + fasts = file_props['fast'] + if len(fasts) > 0: + self._connect_error(frm, fasts[0]['id'], file_props['sid'], + code=406) - raise xmpp.NodeProcessed + raise xmpp.NodeProcessed - def _siResultCB(self, con, iq_obj): - file_props = self.files_props.get(iq_obj.getAttr('id')) - if not file_props: - return - if 'request-id' in file_props: - # we have already sent streamhosts info - return - file_props['receiver'] = self._ft_get_from(iq_obj) - si = iq_obj.getTag('si') - file_tag = si.getTag('file') - range_tag = None - if file_tag: - range_tag = file_tag.getTag('range') - if range_tag: - offset = range_tag.getAttr('offset') - if offset: - file_props['offset'] = int(offset) - length = range_tag.getAttr('length') - if length: - file_props['length'] = int(length) - feature = si.setTag('feature') - if feature.getNamespace() != xmpp.NS_FEATURE: - return - form_tag = feature.getTag('x') - form = xmpp.DataForm(node=form_tag) - field = form.getField('stream-method') - if field.getValue() != xmpp.NS_BYTESTREAM: - return - self._send_socks5_info(file_props) - raise xmpp.NodeProcessed + def _siResultCB(self, con, iq_obj): + file_props = self.files_props.get(iq_obj.getAttr('id')) + if not file_props: + return + if 'request-id' in file_props: + # we have already sent streamhosts info + return + file_props['receiver'] = self._ft_get_from(iq_obj) + si = iq_obj.getTag('si') + file_tag = si.getTag('file') + range_tag = None + if file_tag: + range_tag = file_tag.getTag('range') + if range_tag: + offset = range_tag.getAttr('offset') + if offset: + file_props['offset'] = int(offset) + length = range_tag.getAttr('length') + if length: + file_props['length'] = int(length) + feature = si.setTag('feature') + if feature.getNamespace() != xmpp.NS_FEATURE: + return + form_tag = feature.getTag('x') + form = xmpp.DataForm(node=form_tag) + field = form.getField('stream-method') + if field.getValue() != xmpp.NS_BYTESTREAM: + return + self._send_socks5_info(file_props) + raise xmpp.NodeProcessed - def _siSetCB(self, con, iq_obj): - jid = self._ft_get_from(iq_obj) - file_props = {'type': 'r'} - file_props['sender'] = jid - file_props['request-id'] = unicode(iq_obj.getAttr('id')) - si = iq_obj.getTag('si') - profile = si.getAttr('profile') - mime_type = si.getAttr('mime-type') - if profile != xmpp.NS_FILE: - self.send_file_rejection(file_props, code='400', typ='profile') - raise xmpp.NodeProcessed - feature_tag = si.getTag('feature', namespace=xmpp.NS_FEATURE) - if not feature_tag: - return - form_tag = feature_tag.getTag('x', namespace=xmpp.NS_DATA) - if not form_tag: - return - form = dataforms.ExtendForm(node=form_tag) - for f in form.iter_fields(): - if f.var == 'stream-method' and f.type == 'list-single': - values = [o[1] for o in f.options] - if xmpp.NS_BYTESTREAM in values: - break - else: - self.send_file_rejection(file_props, code='400', typ='stream') - raise xmpp.NodeProcessed - file_tag = si.getTag('file') - for attribute in file_tag.getAttrs(): - if attribute in ('name', 'size', 'hash', 'date'): - val = file_tag.getAttr(attribute) - if val is None: - continue - file_props[attribute] = val - file_desc_tag = file_tag.getTag('desc') - if file_desc_tag is not None: - file_props['desc'] = file_desc_tag.getData() + def _siSetCB(self, con, iq_obj): + jid = self._ft_get_from(iq_obj) + file_props = {'type': 'r'} + file_props['sender'] = jid + file_props['request-id'] = unicode(iq_obj.getAttr('id')) + si = iq_obj.getTag('si') + profile = si.getAttr('profile') + mime_type = si.getAttr('mime-type') + if profile != xmpp.NS_FILE: + self.send_file_rejection(file_props, code='400', typ='profile') + raise xmpp.NodeProcessed + feature_tag = si.getTag('feature', namespace=xmpp.NS_FEATURE) + if not feature_tag: + return + form_tag = feature_tag.getTag('x', namespace=xmpp.NS_DATA) + if not form_tag: + return + form = dataforms.ExtendForm(node=form_tag) + for f in form.iter_fields(): + if f.var == 'stream-method' and f.type == 'list-single': + values = [o[1] for o in f.options] + if xmpp.NS_BYTESTREAM in values: + break + else: + self.send_file_rejection(file_props, code='400', typ='stream') + raise xmpp.NodeProcessed + file_tag = si.getTag('file') + for attribute in file_tag.getAttrs(): + if attribute in ('name', 'size', 'hash', 'date'): + val = file_tag.getAttr(attribute) + if val is None: + continue + file_props[attribute] = val + file_desc_tag = file_tag.getTag('desc') + if file_desc_tag is not None: + file_props['desc'] = file_desc_tag.getData() - if mime_type is not None: - file_props['mime-type'] = mime_type - file_props['receiver'] = self._ft_get_our_jid() - file_props['sid'] = unicode(si.getAttr('id')) - file_props['transfered_size'] = [] - gajim.socks5queue.add_file_props(self.name, file_props) - self.dispatch('FILE_REQUEST', (jid, file_props)) - raise xmpp.NodeProcessed + if mime_type is not None: + file_props['mime-type'] = mime_type + file_props['receiver'] = self._ft_get_our_jid() + file_props['sid'] = unicode(si.getAttr('id')) + file_props['transfered_size'] = [] + gajim.socks5queue.add_file_props(self.name, file_props) + self.dispatch('FILE_REQUEST', (jid, file_props)) + raise xmpp.NodeProcessed - def _siErrorCB(self, con, iq_obj): - si = iq_obj.getTag('si') - profile = si.getAttr('profile') - if profile != xmpp.NS_FILE: - return - file_props = self.files_props.get(iq_obj.getAttr('id')) - if not file_props: - return - jid = self._ft_get_from(iq_obj) - file_props['error'] = -3 - self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, '')) - raise xmpp.NodeProcessed + def _siErrorCB(self, con, iq_obj): + si = iq_obj.getTag('si') + profile = si.getAttr('profile') + if profile != xmpp.NS_FILE: + return + file_props = self.files_props.get(iq_obj.getAttr('id')) + if not file_props: + return + jid = self._ft_get_from(iq_obj) + file_props['error'] = -3 + self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, '')) + raise xmpp.NodeProcessed class ConnectionBytestreamZeroconf(ConnectionBytestream): - def _ft_get_from(self, iq_obj): - return unicode(iq_obj.getFrom()) + def _ft_get_from(self, iq_obj): + return unicode(iq_obj.getFrom()) - def _ft_get_our_jid(self): - return gajim.get_jid_from_account(self.name) + def _ft_get_our_jid(self): + return gajim.get_jid_from_account(self.name) - def _ft_get_receiver_jid(self, file_props): - return file_props['receiver'].jid + def _ft_get_receiver_jid(self, file_props): + return file_props['receiver'].jid - def _ft_get_streamhost_jid_attr(self, streamhost): - return streamhost.getAttr('jid') - -# vim: se ts=3: + def _ft_get_streamhost_jid_attr(self, streamhost): + return streamhost.getAttr('jid') diff --git a/src/common/protocol/caps.py b/src/common/protocol/caps.py index 3fd6c1386..a80ca527a 100644 --- a/src/common/protocol/caps.py +++ b/src/common/protocol/caps.py @@ -32,80 +32,78 @@ from common import helpers class ConnectionCaps(object): - def __init__(self, account, dispatch_event, capscache, client_caps_factory): - self._account = account - self._dispatch_event = dispatch_event - self._capscache = capscache - self._create_suitable_client_caps = client_caps_factory + def __init__(self, account, dispatch_event, capscache, client_caps_factory): + self._account = account + self._dispatch_event = dispatch_event + self._capscache = capscache + self._create_suitable_client_caps = client_caps_factory - def _capsPresenceCB(self, con, presence): - """ - XMMPPY callback method to handle retrieved caps info - """ - try: - jid = helpers.get_full_jid_from_iq(presence) - except: - log.info("Ignoring invalid JID in caps presenceCB") - return + def _capsPresenceCB(self, con, presence): + """ + XMMPPY callback method to handle retrieved caps info + """ + try: + jid = helpers.get_full_jid_from_iq(presence) + except: + log.info("Ignoring invalid JID in caps presenceCB") + return - client_caps = self._extract_client_caps_from_presence(presence) - self._capscache.query_client_of_jid_if_unknown(self, jid, client_caps) - self._update_client_caps_of_contact(jid, client_caps) + client_caps = self._extract_client_caps_from_presence(presence) + self._capscache.query_client_of_jid_if_unknown(self, jid, client_caps) + self._update_client_caps_of_contact(jid, client_caps) - self._dispatch_event('CAPS_RECEIVED', (jid,)) + self._dispatch_event('CAPS_RECEIVED', (jid,)) - def _extract_client_caps_from_presence(self, presence): - caps_tag = presence.getTag('c', namespace=NS_CAPS) - if caps_tag: - hash_method, node, caps_hash = caps_tag['hash'], caps_tag['node'], caps_tag['ver'] - else: - hash_method = node = caps_hash = None - return self._create_suitable_client_caps(node, caps_hash, hash_method) + def _extract_client_caps_from_presence(self, presence): + caps_tag = presence.getTag('c', namespace=NS_CAPS) + if caps_tag: + hash_method, node, caps_hash = caps_tag['hash'], caps_tag['node'], caps_tag['ver'] + else: + hash_method = node = caps_hash = None + return self._create_suitable_client_caps(node, caps_hash, hash_method) - def _update_client_caps_of_contact(self, jid, client_caps): - contact = self._get_contact_or_gc_contact_for_jid(jid) - if contact: - contact.client_caps = client_caps - else: - log.info("Received Caps from unknown contact %s" % jid) + def _update_client_caps_of_contact(self, jid, client_caps): + contact = self._get_contact_or_gc_contact_for_jid(jid) + if contact: + contact.client_caps = client_caps + else: + log.info("Received Caps from unknown contact %s" % jid) - def _get_contact_or_gc_contact_for_jid(self, jid): - contact = gajim.contacts.get_contact_from_full_jid(self._account, jid) - if contact is None: - room_jid, nick = gajim.get_room_and_nick_from_fjid(jid) - contact = gajim.contacts.get_gc_contact(self._account, room_jid, nick) - return contact + def _get_contact_or_gc_contact_for_jid(self, jid): + contact = gajim.contacts.get_contact_from_full_jid(self._account, jid) + if contact is None: + room_jid, nick = gajim.get_room_and_nick_from_fjid(jid) + contact = gajim.contacts.get_gc_contact(self._account, room_jid, nick) + return contact - def _capsDiscoCB(self, jid, node, identities, features, dataforms): - """ - XMMPPY callback to update our caps cache with queried information after - we have retrieved an unknown caps hash and issued a disco - """ - contact = self._get_contact_or_gc_contact_for_jid(jid) - if not contact: - log.info("Received Disco from unknown contact %s" % jid) - return + def _capsDiscoCB(self, jid, node, identities, features, dataforms): + """ + XMMPPY callback to update our caps cache with queried information after + we have retrieved an unknown caps hash and issued a disco + """ + contact = self._get_contact_or_gc_contact_for_jid(jid) + if not contact: + log.info("Received Disco from unknown contact %s" % jid) + return - lookup = contact.client_caps.get_cache_lookup_strategy() - cache_item = lookup(self._capscache) + lookup = contact.client_caps.get_cache_lookup_strategy() + cache_item = lookup(self._capscache) - if cache_item.is_valid(): - # we already know that the hash is fine and have already cached - # the identities and features - return - else: - validate = contact.client_caps.get_hash_validation_strategy() - hash_is_valid = validate(identities, features, dataforms) + if cache_item.is_valid(): + # we already know that the hash is fine and have already cached + # the identities and features + return + else: + validate = contact.client_caps.get_hash_validation_strategy() + hash_is_valid = validate(identities, features, dataforms) - if hash_is_valid: - cache_item.set_and_store(identities, features) - else: - node = caps_hash = hash_method = None - contact.client_caps = self._create_suitable_client_caps(node, - caps_hash, hash_method) - log.warn("Computed and retrieved caps hash differ." + - "Ignoring caps of contact %s" % contact.get_full_jid()) + if hash_is_valid: + cache_item.set_and_store(identities, features) + else: + node = caps_hash = hash_method = None + contact.client_caps = self._create_suitable_client_caps(node, + caps_hash, hash_method) + log.warn("Computed and retrieved caps hash differ." + + "Ignoring caps of contact %s" % contact.get_full_jid()) - self._dispatch_event('CAPS_RECEIVED', (jid,)) - -# vim: se ts=3: + self._dispatch_event('CAPS_RECEIVED', (jid,)) diff --git a/src/common/proxy65_manager.py b/src/common/proxy65_manager.py index 5c4420839..e30af586d 100644 --- a/src/common/proxy65_manager.py +++ b/src/common/proxy65_manager.py @@ -41,429 +41,427 @@ S_FINISHED = 4 CONNECT_TIMEOUT = 20 class Proxy65Manager: - """ - Keep records for file transfer proxies. Each time account establishes a - connection to its server call proxy65manger.resolve(proxy) for every proxy - that is convigured within the account. The class takes care to resolve and - test each proxy only once - """ + """ + Keep records for file transfer proxies. Each time account establishes a + connection to its server call proxy65manger.resolve(proxy) for every proxy + that is convigured within the account. The class takes care to resolve and + test each proxy only once + """ - def __init__(self, idlequeue): - # dict {proxy: proxy properties} - self.idlequeue = idlequeue - self.proxies = {} - # dict {account: proxy} default proxy for account - self.default_proxies = {} + def __init__(self, idlequeue): + # dict {proxy: proxy properties} + self.idlequeue = idlequeue + self.proxies = {} + # dict {account: proxy} default proxy for account + self.default_proxies = {} - def resolve(self, proxy, connection, sender_jid, default=None): - """ - Start - """ - if proxy in self.proxies: - resolver = self.proxies[proxy] - else: - # proxy is being ressolved for the first time - resolver = ProxyResolver(proxy, sender_jid) - self.proxies[proxy] = resolver - resolver.add_connection(connection) - if default: - # add this proxy as default for account - self.default_proxies[default] = proxy + def resolve(self, proxy, connection, sender_jid, default=None): + """ + Start + """ + if proxy in self.proxies: + resolver = self.proxies[proxy] + else: + # proxy is being ressolved for the first time + resolver = ProxyResolver(proxy, sender_jid) + self.proxies[proxy] = resolver + resolver.add_connection(connection) + if default: + # add this proxy as default for account + self.default_proxies[default] = proxy - def disconnect(self, connection): - for resolver in self.proxies.values(): - resolver.disconnect(connection) + def disconnect(self, connection): + for resolver in self.proxies.values(): + resolver.disconnect(connection) - def resolve_result(self, proxy, query): - if proxy not in self.proxies: - return - jid = None - for item in query.getChildren(): - if item.getName() == 'streamhost': - host = item.getAttr('host') - port = item.getAttr('port') - jid = item.getAttr('jid') - self.proxies[proxy].resolve_result(host, port, jid) - # we can have only one streamhost - raise common.xmpp.NodeProcessed + def resolve_result(self, proxy, query): + if proxy not in self.proxies: + return + jid = None + for item in query.getChildren(): + if item.getName() == 'streamhost': + host = item.getAttr('host') + port = item.getAttr('port') + jid = item.getAttr('jid') + self.proxies[proxy].resolve_result(host, port, jid) + # we can have only one streamhost + raise common.xmpp.NodeProcessed - def error_cb(self, proxy, query): - sid = query.getAttr('sid') - for resolver in self.proxies.values(): - if resolver.sid == sid: - resolver.keep_conf() - break + def error_cb(self, proxy, query): + sid = query.getAttr('sid') + for resolver in self.proxies.values(): + if resolver.sid == sid: + resolver.keep_conf() + break - def get_default_for_name(self, account): - if account in self.default_proxies: - return self.default_proxies[account] + def get_default_for_name(self, account): + if account in self.default_proxies: + return self.default_proxies[account] - def get_proxy(self, proxy, account): - if proxy in self.proxies: - resolver = self.proxies[proxy] - if resolver.state == S_FINISHED: - return (resolver.host, resolver.port, resolver.jid) - return (None, 0, None) + def get_proxy(self, proxy, account): + if proxy in self.proxies: + resolver = self.proxies[proxy] + if resolver.state == S_FINISHED: + return (resolver.host, resolver.port, resolver.jid) + return (None, 0, None) class ProxyResolver: - def resolve_result(self, host, port, jid): - """ - Test if host has a real proxy65 listening on port - """ - self.host = str(host) - self.port = int(port) - self.jid = unicode(jid) - self.state = S_RESOLVED - #FIXME: re-enable proxy testing - log.info('start resolving %s:%s' % (self.host, self.port)) - self.receiver_tester = ReceiverTester(self.host, self.port, self.jid, - self.sid, self.sender_jid, self._on_receiver_success, - self._on_connect_failure) - self.receiver_tester.connect() + def resolve_result(self, host, port, jid): + """ + Test if host has a real proxy65 listening on port + """ + self.host = str(host) + self.port = int(port) + self.jid = unicode(jid) + self.state = S_RESOLVED + #FIXME: re-enable proxy testing + log.info('start resolving %s:%s' % (self.host, self.port)) + self.receiver_tester = ReceiverTester(self.host, self.port, self.jid, + self.sid, self.sender_jid, self._on_receiver_success, + self._on_connect_failure) + self.receiver_tester.connect() - def _on_receiver_success(self): - log.debug('Receiver successfully connected %s:%s' % (self.host, - self.port)) - self.host_tester = HostTester(self.host, self.port, self.jid, - self.sid, self.sender_jid, self._on_connect_success, - self._on_connect_failure) - self.host_tester.connect() + def _on_receiver_success(self): + log.debug('Receiver successfully connected %s:%s' % (self.host, + self.port)) + self.host_tester = HostTester(self.host, self.port, self.jid, + self.sid, self.sender_jid, self._on_connect_success, + self._on_connect_failure) + self.host_tester.connect() - def _on_connect_success(self): - log.debug('Host successfully connected %s:%s' % (self.host, self.port)) - iq = common.xmpp.Protocol(name='iq', to=self.jid, typ='set') - query = iq.setTag('query') - query.setNamespace(common.xmpp.NS_BYTESTREAM) - query.setAttr('sid', self.sid) + def _on_connect_success(self): + log.debug('Host successfully connected %s:%s' % (self.host, self.port)) + iq = common.xmpp.Protocol(name='iq', to=self.jid, typ='set') + query = iq.setTag('query') + query.setNamespace(common.xmpp.NS_BYTESTREAM) + query.setAttr('sid', self.sid) - activate = query.setTag('activate') - activate.setData('test@gajim.org/test2') + activate = query.setTag('activate') + activate.setData('test@gajim.org/test2') - if self.active_connection: - log.debug('Activating bytestream on %s:%s' % (self.host, self.port)) - self.active_connection.SendAndCallForResponse(iq, - self._result_received) - self.state = S_ACTIVATED - else: - self.state = S_INITIAL + if self.active_connection: + log.debug('Activating bytestream on %s:%s' % (self.host, self.port)) + self.active_connection.SendAndCallForResponse(iq, + self._result_received) + self.state = S_ACTIVATED + else: + self.state = S_INITIAL - def _result_received(self, data): - self.disconnect(self.active_connection) - if data.getType() == 'result': - self.keep_conf() - else: - self._on_connect_failure() + def _result_received(self, data): + self.disconnect(self.active_connection) + if data.getType() == 'result': + self.keep_conf() + else: + self._on_connect_failure() - def keep_conf(self): - log.debug('Bytestream activated %s:%s' % (self.host, self.port)) - self.state = S_FINISHED + def keep_conf(self): + log.debug('Bytestream activated %s:%s' % (self.host, self.port)) + self.state = S_FINISHED - def _on_connect_failure(self): - self.state = S_FINISHED - self.host = None - self.port = 0 - self.jid = None + def _on_connect_failure(self): + self.state = S_FINISHED + self.host = None + self.port = 0 + self.jid = None - def disconnect(self, connection): - if self.host_tester: - self.host_tester.disconnect() - self.host_tester = None - if self.receiver_tester: - self.receiver_tester.disconnect() - self.receiver_tester = None - try: - self.connections.remove(connection) - except ValueError: - pass - if connection == self.active_connection: - self.active_connection = None - if self.state != S_FINISHED: - self.state = S_INITIAL - self.try_next_connection() + def disconnect(self, connection): + if self.host_tester: + self.host_tester.disconnect() + self.host_tester = None + if self.receiver_tester: + self.receiver_tester.disconnect() + self.receiver_tester = None + try: + self.connections.remove(connection) + except ValueError: + pass + if connection == self.active_connection: + self.active_connection = None + if self.state != S_FINISHED: + self.state = S_INITIAL + self.try_next_connection() - def try_next_connection(self): - """ - Try to resolve proxy with the next possible connection - """ - if self.connections: - connection = self.connections.pop(0) - self.start_resolve(connection) + def try_next_connection(self): + """ + Try to resolve proxy with the next possible connection + """ + if self.connections: + connection = self.connections.pop(0) + self.start_resolve(connection) - def add_connection(self, connection): - """ - Add a new connection in case the first fails - """ - self.connections.append(connection) - if self.state == S_INITIAL: - self.start_resolve(connection) + def add_connection(self, connection): + """ + Add a new connection in case the first fails + """ + self.connections.append(connection) + if self.state == S_INITIAL: + self.start_resolve(connection) - def start_resolve(self, connection): - """ - Request network address from proxy - """ - self.state = S_STARTED - self.active_connection = connection - iq = common.xmpp.Protocol(name='iq', to=self.proxy, typ='get') - query = iq.setTag('query') - query.setNamespace(common.xmpp.NS_BYTESTREAM) - connection.send(iq) + def start_resolve(self, connection): + """ + Request network address from proxy + """ + self.state = S_STARTED + self.active_connection = connection + iq = common.xmpp.Protocol(name='iq', to=self.proxy, typ='get') + query = iq.setTag('query') + query.setNamespace(common.xmpp.NS_BYTESTREAM) + connection.send(iq) - def __init__(self, proxy, sender_jid): - self.proxy = proxy - self.state = S_INITIAL - self.active_connection = None - self.connections = [] - self.host_tester = None - self.receiver_tester = None - self.jid = None - self.host = None - self.port = None - self.sid = helpers.get_random_string_16() - self.sender_jid = sender_jid + def __init__(self, proxy, sender_jid): + self.proxy = proxy + self.state = S_INITIAL + self.active_connection = None + self.connections = [] + self.host_tester = None + self.receiver_tester = None + self.jid = None + self.host = None + self.port = None + self.sid = helpers.get_random_string_16() + self.sender_jid = sender_jid class HostTester(Socks5, IdleObject): - """ - Fake proxy tester - """ + """ + Fake proxy tester + """ - def __init__(self, host, port, jid, sid, sender_jid, on_success, on_failure): - """ - Try to establish and auth to proxy at (host, port) + def __init__(self, host, port, jid, sid, sender_jid, on_success, on_failure): + """ + Try to establish and auth to proxy at (host, port) - Calls on_success, or on_failure according to the result. - """ - self.host = host - self.port = port - self.jid = jid - self.on_success = on_success - self.on_failure = on_failure - self._sock = None - self.file_props = {'is_a_proxy': True, - 'proxy_sender': sender_jid, - 'proxy_receiver': 'test@gajim.org/test2'} - Socks5.__init__(self, gajim.idlequeue, host, port, None, None, None) - self.sid = sid + Calls on_success, or on_failure according to the result. + """ + self.host = host + self.port = port + self.jid = jid + self.on_success = on_success + self.on_failure = on_failure + self._sock = None + self.file_props = {'is_a_proxy': True, + 'proxy_sender': sender_jid, + 'proxy_receiver': 'test@gajim.org/test2'} + Socks5.__init__(self, gajim.idlequeue, host, port, None, None, None) + self.sid = sid - def connect(self): - """ - Create the socket and plug it to the idlequeue - """ - if self.host is None: - self.on_failure() - return None - self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self._sock.setblocking(False) - self.fd = self._sock.fileno() - self.state = 0 # about to be connected - gajim.idlequeue.plug_idle(self, True, False) - self.do_connect() - self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - return None + def connect(self): + """ + Create the socket and plug it to the idlequeue + """ + if self.host is None: + self.on_failure() + return None + self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._sock.setblocking(False) + self.fd = self._sock.fileno() + self.state = 0 # about to be connected + gajim.idlequeue.plug_idle(self, True, False) + self.do_connect() + self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + return None - def read_timeout(self): - self.idlequeue.remove_timeout(self.fd) - self.pollend() + def read_timeout(self): + self.idlequeue.remove_timeout(self.fd) + self.pollend() - def pollend(self): - self.disconnect() - self.on_failure() + def pollend(self): + self.disconnect() + self.on_failure() - def pollout(self): - self.idlequeue.remove_timeout(self.fd) - if self.state == 0: - self.do_connect() - return - elif self.state == 1: # send initially: version and auth types - data = self._get_auth_buff() - self.send_raw(data) - else: - return - self.state += 1 - # unplug and plug for reading - gajim.idlequeue.plug_idle(self, False, True) - gajim.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + def pollout(self): + self.idlequeue.remove_timeout(self.fd) + if self.state == 0: + self.do_connect() + return + elif self.state == 1: # send initially: version and auth types + data = self._get_auth_buff() + self.send_raw(data) + else: + return + self.state += 1 + # unplug and plug for reading + gajim.idlequeue.plug_idle(self, False, True) + gajim.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - def pollin(self): - self.idlequeue.remove_timeout(self.fd) - if self.state == 2: - self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - # begin negotiation. on success 'address' != 0 - buff = self.receive() - if buff == '': - # end connection - self.pollend() - return - # read auth response - if buff is None or len(buff) != 2: - return None - version, method = struct.unpack('!BB', buff[:2]) - if version != 0x05 or method == 0xff: - self.pollend() - return - data = self._get_request_buff(self._get_sha1_auth()) - self.send_raw(data) - self.state += 1 - log.debug('Host authenticating to %s:%s' % (self.host, self.port)) - elif self.state == 3: - log.debug('Host authenticated to %s:%s' % (self.host, self.port)) - self.on_success() - self.disconnect() - self.state += 1 - else: - assert False, 'unexpected state: %d' % self.state + def pollin(self): + self.idlequeue.remove_timeout(self.fd) + if self.state == 2: + self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + # begin negotiation. on success 'address' != 0 + buff = self.receive() + if buff == '': + # end connection + self.pollend() + return + # read auth response + if buff is None or len(buff) != 2: + return None + version, method = struct.unpack('!BB', buff[:2]) + if version != 0x05 or method == 0xff: + self.pollend() + return + data = self._get_request_buff(self._get_sha1_auth()) + self.send_raw(data) + self.state += 1 + log.debug('Host authenticating to %s:%s' % (self.host, self.port)) + elif self.state == 3: + log.debug('Host authenticated to %s:%s' % (self.host, self.port)) + self.on_success() + self.disconnect() + self.state += 1 + else: + assert False, 'unexpected state: %d' % self.state - def do_connect(self): - try: - self._sock.connect((self.host, self.port)) - self._sock.setblocking(False) - log.debug('Host Connecting to %s:%s' % (self.host, self.port)) - self._send = self._sock.send - self._recv = self._sock.recv - except Exception, ee: - errnum = ee[0] - # 56 is for freebsd - if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): - # still trying to connect - return - # win32 needs this - if errnum not in (0, 10056, errno.EISCONN): - # connection failed - self.on_failure() - return - # socket is already connected - self._sock.setblocking(False) - self._send = self._sock.send - self._recv = self._sock.recv - self.buff = '' - self.state = 1 # connected - log.debug('Host connected to %s:%s' % (self.host, self.port)) - self.idlequeue.plug_idle(self, True, False) - return + def do_connect(self): + try: + self._sock.connect((self.host, self.port)) + self._sock.setblocking(False) + log.debug('Host Connecting to %s:%s' % (self.host, self.port)) + self._send = self._sock.send + self._recv = self._sock.recv + except Exception, ee: + errnum = ee[0] + # 56 is for freebsd + if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): + # still trying to connect + return + # win32 needs this + if errnum not in (0, 10056, errno.EISCONN): + # connection failed + self.on_failure() + return + # socket is already connected + self._sock.setblocking(False) + self._send = self._sock.send + self._recv = self._sock.recv + self.buff = '' + self.state = 1 # connected + log.debug('Host connected to %s:%s' % (self.host, self.port)) + self.idlequeue.plug_idle(self, True, False) + return class ReceiverTester(Socks5, IdleObject): - """ - Fake proxy tester - """ + """ + Fake proxy tester + """ - def __init__(self, host, port, jid, sid, sender_jid, on_success, on_failure): - """ - Try to establish and auth to proxy at (host, port) + def __init__(self, host, port, jid, sid, sender_jid, on_success, on_failure): + """ + Try to establish and auth to proxy at (host, port) - Call on_success, or on_failure according to the result. - """ - self.host = host - self.port = port - self.jid = jid - self.on_success = on_success - self.on_failure = on_failure - self._sock = None - self.file_props = {'is_a_proxy': True, - 'proxy_sender': sender_jid, - 'proxy_receiver': 'test@gajim.org/test2'} - Socks5.__init__(self, gajim.idlequeue, host, port, None, None, None) - self.sid = sid + Call on_success, or on_failure according to the result. + """ + self.host = host + self.port = port + self.jid = jid + self.on_success = on_success + self.on_failure = on_failure + self._sock = None + self.file_props = {'is_a_proxy': True, + 'proxy_sender': sender_jid, + 'proxy_receiver': 'test@gajim.org/test2'} + Socks5.__init__(self, gajim.idlequeue, host, port, None, None, None) + self.sid = sid - def connect(self): - """ - Create the socket and plug it to the idlequeue - """ - if self.host is None: - self.on_failure() - return None - self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self._sock.setblocking(False) - self.fd = self._sock.fileno() - self.state = 0 # about to be connected - gajim.idlequeue.plug_idle(self, True, False) - self.do_connect() - self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - return None + def connect(self): + """ + Create the socket and plug it to the idlequeue + """ + if self.host is None: + self.on_failure() + return None + self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._sock.setblocking(False) + self.fd = self._sock.fileno() + self.state = 0 # about to be connected + gajim.idlequeue.plug_idle(self, True, False) + self.do_connect() + self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + return None - def read_timeout(self): - self.idlequeue.remove_timeout(self.fd) - self.pollend() + def read_timeout(self): + self.idlequeue.remove_timeout(self.fd) + self.pollend() - def pollend(self): - self.disconnect() - self.on_failure() + def pollend(self): + self.disconnect() + self.on_failure() - def pollout(self): - self.idlequeue.remove_timeout(self.fd) - if self.state == 0: - self.do_connect() - return - elif self.state == 1: # send initially: version and auth types - data = self._get_auth_buff() - self.send_raw(data) - else: - return - self.state += 1 - # unplug and plug for reading - gajim.idlequeue.plug_idle(self, False, True) - gajim.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + def pollout(self): + self.idlequeue.remove_timeout(self.fd) + if self.state == 0: + self.do_connect() + return + elif self.state == 1: # send initially: version and auth types + data = self._get_auth_buff() + self.send_raw(data) + else: + return + self.state += 1 + # unplug and plug for reading + gajim.idlequeue.plug_idle(self, False, True) + gajim.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - def pollin(self): - self.idlequeue.remove_timeout(self.fd) - if self.state in (2, 3): - self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - # begin negotiation. on success 'address' != 0 - buff = self.receive() - if buff == '': - # end connection - self.pollend() - return - if self.state == 2: - # read auth response - if buff is None or len(buff) != 2: - return None - version, method = struct.unpack('!BB', buff[:2]) - if version != 0x05 or method == 0xff: - self.pollend() - return - log.debug('Receiver authenticating to %s:%s' % (self.host, self.port)) - data = self._get_request_buff(self._get_sha1_auth()) - self.send_raw(data) - self.state += 1 - elif self.state == 3: - # read connect response - if buff is None or len(buff) < 2: - return None - version, reply = struct.unpack('!BB', buff[:2]) - if version != 0x05 or reply != 0x00: - self.pollend() - return - log.debug('Receiver authenticated to %s:%s' % (self.host, self.port)) - self.on_success() - self.disconnect() - self.state += 1 - else: - assert False, 'unexpected state: %d' % self.state + def pollin(self): + self.idlequeue.remove_timeout(self.fd) + if self.state in (2, 3): + self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + # begin negotiation. on success 'address' != 0 + buff = self.receive() + if buff == '': + # end connection + self.pollend() + return + if self.state == 2: + # read auth response + if buff is None or len(buff) != 2: + return None + version, method = struct.unpack('!BB', buff[:2]) + if version != 0x05 or method == 0xff: + self.pollend() + return + log.debug('Receiver authenticating to %s:%s' % (self.host, self.port)) + data = self._get_request_buff(self._get_sha1_auth()) + self.send_raw(data) + self.state += 1 + elif self.state == 3: + # read connect response + if buff is None or len(buff) < 2: + return None + version, reply = struct.unpack('!BB', buff[:2]) + if version != 0x05 or reply != 0x00: + self.pollend() + return + log.debug('Receiver authenticated to %s:%s' % (self.host, self.port)) + self.on_success() + self.disconnect() + self.state += 1 + else: + assert False, 'unexpected state: %d' % self.state - def do_connect(self): - try: - self._sock.setblocking(False) - self._sock.connect((self.host, self.port)) - log.debug('Receiver Connecting to %s:%s' % (self.host, self.port)) - self._send = self._sock.send - self._recv = self._sock.recv - except Exception, ee: - errnum = ee[0] - # 56 is for freebsd - if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): - # still trying to connect - return - # win32 needs this - if errnum not in (0, 10056, errno.EISCONN): - # connection failed - self.on_failure() - return - # socket is already connected - self._sock.setblocking(False) - self._send = self._sock.send - self._recv = self._sock.recv - self.buff = '' - self.state = 1 # connected - log.debug('Receiver connected to %s:%s' % (self.host, self.port)) - self.idlequeue.plug_idle(self, True, False) - -# vim: se ts=3: + def do_connect(self): + try: + self._sock.setblocking(False) + self._sock.connect((self.host, self.port)) + log.debug('Receiver Connecting to %s:%s' % (self.host, self.port)) + self._send = self._sock.send + self._recv = self._sock.recv + except Exception, ee: + errnum = ee[0] + # 56 is for freebsd + if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): + # still trying to connect + return + # win32 needs this + if errnum not in (0, 10056, errno.EISCONN): + # connection failed + self.on_failure() + return + # socket is already connected + self._sock.setblocking(False) + self._send = self._sock.send + self._recv = self._sock.recv + self.buff = '' + self.state = 1 # connected + log.debug('Receiver connected to %s:%s' % (self.host, self.port)) + self.idlequeue.plug_idle(self, True, False) diff --git a/src/common/pubsub.py b/src/common/pubsub.py index d2dbf88e1..2a75d34a9 100644 --- a/src/common/pubsub.py +++ b/src/common/pubsub.py @@ -28,177 +28,175 @@ import logging log = logging.getLogger('gajim.c.pubsub') class ConnectionPubSub: - def __init__(self): - self.__callbacks={} + def __init__(self): + self.__callbacks={} - def send_pb_subscription_query(self, jid, cb, *args, **kwargs): - if not self.connection or self.connected < 2: - return - query = xmpp.Iq('get', to=jid) - pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) - pb.addChild('subscriptions') + def send_pb_subscription_query(self, jid, cb, *args, **kwargs): + if not self.connection or self.connected < 2: + return + query = xmpp.Iq('get', to=jid) + pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) + pb.addChild('subscriptions') - id_ = self.connection.send(query) + id_ = self.connection.send(query) - self.__callbacks[id_]=(cb, args, kwargs) + self.__callbacks[id_]=(cb, args, kwargs) - def send_pb_subscribe(self, jid, node, cb, *args, **kwargs): - if not self.connection or self.connected < 2: - return - our_jid = gajim.get_jid_from_account(self.name) - query = xmpp.Iq('set', to=jid) - pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) - pb.addChild('subscribe', {'node': node, 'jid': our_jid}) + def send_pb_subscribe(self, jid, node, cb, *args, **kwargs): + if not self.connection or self.connected < 2: + return + our_jid = gajim.get_jid_from_account(self.name) + query = xmpp.Iq('set', to=jid) + pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) + pb.addChild('subscribe', {'node': node, 'jid': our_jid}) - id_ = self.connection.send(query) + id_ = self.connection.send(query) - self.__callbacks[id_]=(cb, args, kwargs) + self.__callbacks[id_]=(cb, args, kwargs) - def send_pb_unsubscribe(self, jid, node, cb, *args, **kwargs): - if not self.connection or self.connected < 2: - return - our_jid = gajim.get_jid_from_account(self.name) - query = xmpp.Iq('set', to=jid) - pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) - pb.addChild('unsubscribe', {'node': node, 'jid': our_jid}) + def send_pb_unsubscribe(self, jid, node, cb, *args, **kwargs): + if not self.connection or self.connected < 2: + return + our_jid = gajim.get_jid_from_account(self.name) + query = xmpp.Iq('set', to=jid) + pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) + pb.addChild('unsubscribe', {'node': node, 'jid': our_jid}) - id_ = self.connection.send(query) + id_ = self.connection.send(query) - self.__callbacks[id_]=(cb, args, kwargs) + self.__callbacks[id_]=(cb, args, kwargs) - def send_pb_publish(self, jid, node, item, id_, options=None): - """ - Publish item to a node - """ - if not self.connection or self.connected < 2: - return - query = xmpp.Iq('set', to=jid) - e = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) - p = e.addChild('publish', {'node': node}) - p.addChild('item', {'id': id_}, [item]) - if options: - p = e.addChild('publish-options') - p.addChild(node=options) + def send_pb_publish(self, jid, node, item, id_, options=None): + """ + Publish item to a node + """ + if not self.connection or self.connected < 2: + return + query = xmpp.Iq('set', to=jid) + e = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) + p = e.addChild('publish', {'node': node}) + p.addChild('item', {'id': id_}, [item]) + if options: + p = e.addChild('publish-options') + p.addChild(node=options) - self.connection.send(query) + self.connection.send(query) - def send_pb_retrieve(self, jid, node, cb=None, *args, **kwargs): - """ - Get items from a node - """ - if not self.connection or self.connected < 2: - return - query = xmpp.Iq('get', to=jid) - r = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) - r = r.addChild('items', {'node': node}) - id_ = self.connection.send(query) + def send_pb_retrieve(self, jid, node, cb=None, *args, **kwargs): + """ + Get items from a node + """ + if not self.connection or self.connected < 2: + return + query = xmpp.Iq('get', to=jid) + r = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) + r = r.addChild('items', {'node': node}) + id_ = self.connection.send(query) - if cb: - self.__callbacks[id_]=(cb, args, kwargs) + if cb: + self.__callbacks[id_]=(cb, args, kwargs) - def send_pb_retract(self, jid, node, id_): - """ - Delete item from a node - """ - if not self.connection or self.connected < 2: - return - query = xmpp.Iq('set', to=jid) - r = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) - r = r.addChild('retract', {'node': node, 'notify': '1'}) - r = r.addChild('item', {'id': id_}) + def send_pb_retract(self, jid, node, id_): + """ + Delete item from a node + """ + if not self.connection or self.connected < 2: + return + query = xmpp.Iq('set', to=jid) + r = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) + r = r.addChild('retract', {'node': node, 'notify': '1'}) + r = r.addChild('item', {'id': id_}) - self.connection.send(query) + self.connection.send(query) - def send_pb_delete(self, jid, node): - """ - Delete node - """ - if not self.connection or self.connected < 2: - return - query = xmpp.Iq('set', to=jid) - d = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) - d = d.addChild('delete', {'node': node}) + def send_pb_delete(self, jid, node): + """ + Delete node + """ + if not self.connection or self.connected < 2: + return + query = xmpp.Iq('set', to=jid) + d = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) + d = d.addChild('delete', {'node': node}) - def response(con, resp, jid, node): - if resp.getType() == 'result': - self.dispatch('PUBSUB_NODE_REMOVED', (jid, node)) - else: - msg = resp.getErrorMsg() - self.dispatch('PUBSUB_NODE_NOT_REMOVED', (jid, node, msg)) + def response(con, resp, jid, node): + if resp.getType() == 'result': + self.dispatch('PUBSUB_NODE_REMOVED', (jid, node)) + else: + msg = resp.getErrorMsg() + self.dispatch('PUBSUB_NODE_NOT_REMOVED', (jid, node, msg)) - self.connection.SendAndCallForResponse(query, response, {'jid': jid, - 'node': node}) + self.connection.SendAndCallForResponse(query, response, {'jid': jid, + 'node': node}) - def send_pb_create(self, jid, node, configure = False, configure_form = None): - """ - Create a new node - """ - if not self.connection or self.connected < 2: - return - query = xmpp.Iq('set', to=jid) - c = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) - c = c.addChild('create', {'node': node}) - if configure: - conf = c.addChild('configure') - if configure_form is not None: - conf.addChild(node=configure_form) + def send_pb_create(self, jid, node, configure = False, configure_form = None): + """ + Create a new node + """ + if not self.connection or self.connected < 2: + return + query = xmpp.Iq('set', to=jid) + c = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) + c = c.addChild('create', {'node': node}) + if configure: + conf = c.addChild('configure') + if configure_form is not None: + conf.addChild(node=configure_form) - self.connection.send(query) + self.connection.send(query) - def send_pb_configure(self, jid, node, form): - if not self.connection or self.connected < 2: - return - query = xmpp.Iq('set', to=jid) - c = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB_OWNER) - c = c.addChild('configure', {'node': node}) - c.addChild(node=form) + def send_pb_configure(self, jid, node, form): + if not self.connection or self.connected < 2: + return + query = xmpp.Iq('set', to=jid) + c = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB_OWNER) + c = c.addChild('configure', {'node': node}) + c.addChild(node=form) - self.connection.send(query) + self.connection.send(query) - def _PubSubCB(self, conn, stanza): - log.debug('_PubsubCB') - try: - cb, args, kwargs = self.__callbacks.pop(stanza.getID()) - cb(conn, stanza, *args, **kwargs) - except Exception: - pass + def _PubSubCB(self, conn, stanza): + log.debug('_PubsubCB') + try: + cb, args, kwargs = self.__callbacks.pop(stanza.getID()) + cb(conn, stanza, *args, **kwargs) + except Exception: + pass - pubsub = stanza.getTag('pubsub') - if not pubsub: - return - items = pubsub.getTag('items') - if not items: - return - item = items.getTag('item') - if not item: - return - storage = item.getTag('storage') - if storage: - ns = storage.getNamespace() - if ns == 'storage:bookmarks': - self._parse_bookmarks(storage, 'pubsub') + pubsub = stanza.getTag('pubsub') + if not pubsub: + return + items = pubsub.getTag('items') + if not items: + return + item = items.getTag('item') + if not item: + return + storage = item.getTag('storage') + if storage: + ns = storage.getNamespace() + if ns == 'storage:bookmarks': + self._parse_bookmarks(storage, 'pubsub') - def _PubSubErrorCB(self, conn, stanza): - log.debug('_PubsubErrorCB') - pubsub = stanza.getTag('pubsub') - if not pubsub: - return - items = pubsub.getTag('items') - if not items: - return - if items.getAttr('node') == 'storage:bookmarks': - # Receiving bookmarks from pubsub failed, so take them from xml - self.get_bookmarks(storage_type='xml') + def _PubSubErrorCB(self, conn, stanza): + log.debug('_PubsubErrorCB') + pubsub = stanza.getTag('pubsub') + if not pubsub: + return + items = pubsub.getTag('items') + if not items: + return + if items.getAttr('node') == 'storage:bookmarks': + # Receiving bookmarks from pubsub failed, so take them from xml + self.get_bookmarks(storage_type='xml') - def request_pb_configuration(self, jid, node): - if not self.connection or self.connected < 2: - return - query = xmpp.Iq('get', to=jid) - e = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB_OWNER) - e = e.addChild('configure', {'node': node}) - id_ = self.connection.getAnID() - query.setID(id_) - self.awaiting_answers[id_] = (connection_handlers.PEP_CONFIG,) - self.connection.send(query) - -# vim: se ts=3: + def request_pb_configuration(self, jid, node): + if not self.connection or self.connected < 2: + return + query = xmpp.Iq('get', to=jid) + e = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB_OWNER) + e = e.addChild('configure', {'node': node}) + id_ = self.connection.getAnID() + query.setID(id_) + self.awaiting_answers[id_] = (connection_handlers.PEP_CONFIG,) + self.connection.send(query) diff --git a/src/common/resolver.py b/src/common/resolver.py index 8ed31dd13..482779ca0 100644 --- a/src/common/resolver.py +++ b/src/common/resolver.py @@ -1,4 +1,4 @@ -## common/resolver.py +## common/resolver.py ## ## Copyright (C) 2006 Dimitur Kirov ## @@ -24,10 +24,10 @@ import logging log = logging.getLogger('gajim.c.resolver') if __name__ == '__main__': - sys.path.append('..') - from common import i18n - import common.configpaths - common.configpaths.gajimpaths.init(None) + sys.path.append('..') + from common import i18n + import common.configpaths + common.configpaths.gajimpaths.init(None) from common import helpers from common.xmpp.idlequeue import IdleCommand @@ -39,309 +39,307 @@ ns_type_pattern = re.compile('^[a-z]+$') host_pattern = re.compile('^[a-z0-9\-._]*[a-z0-9]\.[a-z]{2,}$') try: - #raise ImportError("Manually disabled libasync") - import libasyncns - USE_LIBASYNCNS = True - log.info("libasyncns-python loaded") + #raise ImportError("Manually disabled libasync") + import libasyncns + USE_LIBASYNCNS = True + log.info("libasyncns-python loaded") except ImportError: - USE_LIBASYNCNS = False - log.debug("Import of libasyncns-python failed, getaddrinfo will block", exc_info=True) + USE_LIBASYNCNS = False + log.debug("Import of libasyncns-python failed, getaddrinfo will block", exc_info=True) def get_resolver(idlequeue): - if USE_LIBASYNCNS: - return LibAsyncNSResolver() - else: - return NSLookupResolver(idlequeue) + if USE_LIBASYNCNS: + return LibAsyncNSResolver() + else: + return NSLookupResolver(idlequeue) class CommonResolver(): - def __init__(self): - # dict {"host+type" : list of records} - self.resolved_hosts = {} - # dict {"host+type" : list of callbacks} - self.handlers = {} + def __init__(self): + # dict {"host+type" : list of records} + self.resolved_hosts = {} + # dict {"host+type" : list of callbacks} + self.handlers = {} - def resolve(self, host, on_ready, type='srv'): - log.debug('resolve %s type=%s' % (host, type)) - assert(type in ['srv', 'txt']) - if not host: - # empty host, return empty list of srv records - on_ready([]) - return - if self.resolved_hosts.has_key(host+type): - # host is already resolved, return cached values - log.debug('%s already resolved: %s') - on_ready(host, self.resolved_hosts[host+type]) - return - if self.handlers.has_key(host+type): - # host is about to be resolved by another connection, - # attach our callback - log.debug('already resolving %s' % host) - self.handlers[host+type].append(on_ready) - else: - # host has never been resolved, start now - log.debug('Starting to resolve %s using %s' % (host, self)) - self.handlers[host+type] = [on_ready] - self.start_resolve(host, type) + def resolve(self, host, on_ready, type='srv'): + log.debug('resolve %s type=%s' % (host, type)) + assert(type in ['srv', 'txt']) + if not host: + # empty host, return empty list of srv records + on_ready([]) + return + if self.resolved_hosts.has_key(host+type): + # host is already resolved, return cached values + log.debug('%s already resolved: %s') + on_ready(host, self.resolved_hosts[host+type]) + return + if self.handlers.has_key(host+type): + # host is about to be resolved by another connection, + # attach our callback + log.debug('already resolving %s' % host) + self.handlers[host+type].append(on_ready) + else: + # host has never been resolved, start now + log.debug('Starting to resolve %s using %s' % (host, self)) + self.handlers[host+type] = [on_ready] + self.start_resolve(host, type) - def _on_ready(self, host, type, result_list): - # practically it is impossible to be the opposite, but who knows :) - log.debug('Resolving result for %s: %s' % (host, result_list)) - if not self.resolved_hosts.has_key(host+type): - self.resolved_hosts[host+type] = result_list - if self.handlers.has_key(host+type): - for callback in self.handlers[host+type]: - callback(host, result_list) - del(self.handlers[host+type]) + def _on_ready(self, host, type, result_list): + # practically it is impossible to be the opposite, but who knows :) + log.debug('Resolving result for %s: %s' % (host, result_list)) + if not self.resolved_hosts.has_key(host+type): + self.resolved_hosts[host+type] = result_list + if self.handlers.has_key(host+type): + for callback in self.handlers[host+type]: + callback(host, result_list) + del(self.handlers[host+type]) - def start_resolve(self, host, type): - pass + def start_resolve(self, host, type): + pass # FIXME: API usage is not consistent! This one requires that process is called class LibAsyncNSResolver(CommonResolver): - """ - Asynchronous resolver using libasyncns-python. process() method has to be - called in order to proceed the pending requests. Based on patch submitted by - Damien Thebault. - """ + """ + Asynchronous resolver using libasyncns-python. process() method has to be + called in order to proceed the pending requests. Based on patch submitted by + Damien Thebault. + """ - def __init__(self): - self.asyncns = libasyncns.Asyncns() - CommonResolver.__init__(self) + def __init__(self): + self.asyncns = libasyncns.Asyncns() + CommonResolver.__init__(self) - def start_resolve(self, host, type): - type = libasyncns.ns_t_srv - if type == 'txt': type = libasyncns.ns_t_txt - resq = self.asyncns.res_query(host, libasyncns.ns_c_in, type) - resq.userdata = {'host':host, 'type':type} + def start_resolve(self, host, type): + type = libasyncns.ns_t_srv + if type == 'txt': type = libasyncns.ns_t_txt + resq = self.asyncns.res_query(host, libasyncns.ns_c_in, type) + resq.userdata = {'host':host, 'type':type} - # getaddrinfo to be done - #def resolve_name(self, dname, callback): - #resq = self.asyncns.getaddrinfo(dname) - #resq.userdata = {'callback':callback, 'dname':dname} + # getaddrinfo to be done + #def resolve_name(self, dname, callback): + #resq = self.asyncns.getaddrinfo(dname) + #resq.userdata = {'callback':callback, 'dname':dname} - def _on_ready(self, host, type, result_list): - if type == libasyncns.ns_t_srv: type = 'srv' - elif type == libasyncns.ns_t_txt: type = 'txt' + def _on_ready(self, host, type, result_list): + if type == libasyncns.ns_t_srv: type = 'srv' + elif type == libasyncns.ns_t_txt: type = 'txt' - CommonResolver._on_ready(self, host, type, result_list) + CommonResolver._on_ready(self, host, type, result_list) - def process(self): - try: - self.asyncns.wait(False) - resq = self.asyncns.get_next() - except: - return True - if type(resq) == libasyncns.ResQuery: - # TXT or SRV result - while resq is not None: - try: - rl = resq.get_done() - except Exception: - rl = [] - hosts = [] - requested_type = resq.userdata['type'] - requested_host = resq.userdata['host'] - if rl: - for r in rl: - if r['type'] != requested_type: - # Answer doesn't contain valid SRV data - continue - r['prio'] = r['pref'] - hosts.append(r) - self._on_ready(host=requested_host, type=requested_type, - result_list=hosts) - try: - resq = self.asyncns.get_next() - except Exception: - resq = None - elif type(resq) == libasyncns.AddrInfoQuery: - # getaddrinfo result (A or AAAA) - rl = resq.get_done() - resq.userdata['callback'](resq.userdata['dname'], rl) - return True + def process(self): + try: + self.asyncns.wait(False) + resq = self.asyncns.get_next() + except: + return True + if type(resq) == libasyncns.ResQuery: + # TXT or SRV result + while resq is not None: + try: + rl = resq.get_done() + except Exception: + rl = [] + hosts = [] + requested_type = resq.userdata['type'] + requested_host = resq.userdata['host'] + if rl: + for r in rl: + if r['type'] != requested_type: + # Answer doesn't contain valid SRV data + continue + r['prio'] = r['pref'] + hosts.append(r) + self._on_ready(host=requested_host, type=requested_type, + result_list=hosts) + try: + resq = self.asyncns.get_next() + except Exception: + resq = None + elif type(resq) == libasyncns.AddrInfoQuery: + # getaddrinfo result (A or AAAA) + rl = resq.get_done() + resq.userdata['callback'](resq.userdata['dname'], rl) + return True class NSLookupResolver(CommonResolver): - """ - Asynchronous DNS resolver calling nslookup. Processing of pending requests - is invoked from idlequeue which is watching file descriptor of pipe of - stdout of nslookup process. - """ + """ + Asynchronous DNS resolver calling nslookup. Processing of pending requests + is invoked from idlequeue which is watching file descriptor of pipe of + stdout of nslookup process. + """ - def __init__(self, idlequeue): - self.idlequeue = idlequeue - self.process = False - CommonResolver.__init__(self) + def __init__(self, idlequeue): + self.idlequeue = idlequeue + self.process = False + CommonResolver.__init__(self) - def parse_srv_result(self, fqdn, result): - """ - Parse the output of nslookup command and return list of properties: - 'host', 'port','weight', 'priority' corresponding to the found srv hosts - """ - if os.name == 'nt': - return self._parse_srv_result_nt(fqdn, result) - elif os.name == 'posix': - return self._parse_srv_result_posix(fqdn, result) + def parse_srv_result(self, fqdn, result): + """ + Parse the output of nslookup command and return list of properties: + 'host', 'port','weight', 'priority' corresponding to the found srv hosts + """ + if os.name == 'nt': + return self._parse_srv_result_nt(fqdn, result) + elif os.name == 'posix': + return self._parse_srv_result_posix(fqdn, result) - def _parse_srv_result_nt(self, fqdn, result): - # output from win32 nslookup command - if not result: - return [] - hosts = [] - lines = result.replace('\r','').split('\n') - current_host = None - for line in lines: - line = line.lstrip() - if line == '': - continue - if line.startswith(fqdn): - rest = line[len(fqdn):] - if rest.find('service') > -1: - current_host = {} - elif isinstance(current_host, dict): - res = line.strip().split('=') - if len(res) != 2: - if len(current_host) == 4: - hosts.append(current_host) - current_host = None - continue - prop_type = res[0].strip() - prop_value = res[1].strip() - if prop_type.find('prio') > -1: - try: - current_host['prio'] = int(prop_value) - except ValueError: - continue - elif prop_type.find('weight') > -1: - try: - current_host['weight'] = int(prop_value) - except ValueError: - continue - elif prop_type.find('port') > -1: - try: - current_host['port'] = int(prop_value) - except ValueError: - continue - elif prop_type.find('host') > -1: - # strip '.' at the end of hostname - if prop_value[-1] == '.': - prop_value = prop_value[:-1] - current_host['host'] = prop_value - if len(current_host) == 4: - hosts.append(current_host) - current_host = None - return hosts + def _parse_srv_result_nt(self, fqdn, result): + # output from win32 nslookup command + if not result: + return [] + hosts = [] + lines = result.replace('\r', '').split('\n') + current_host = None + for line in lines: + line = line.lstrip() + if line == '': + continue + if line.startswith(fqdn): + rest = line[len(fqdn):] + if rest.find('service') > -1: + current_host = {} + elif isinstance(current_host, dict): + res = line.strip().split('=') + if len(res) != 2: + if len(current_host) == 4: + hosts.append(current_host) + current_host = None + continue + prop_type = res[0].strip() + prop_value = res[1].strip() + if prop_type.find('prio') > -1: + try: + current_host['prio'] = int(prop_value) + except ValueError: + continue + elif prop_type.find('weight') > -1: + try: + current_host['weight'] = int(prop_value) + except ValueError: + continue + elif prop_type.find('port') > -1: + try: + current_host['port'] = int(prop_value) + except ValueError: + continue + elif prop_type.find('host') > -1: + # strip '.' at the end of hostname + if prop_value[-1] == '.': + prop_value = prop_value[:-1] + current_host['host'] = prop_value + if len(current_host) == 4: + hosts.append(current_host) + current_host = None + return hosts - def _parse_srv_result_posix(self, fqdn, result): - # typical output of bind-tools nslookup command: - # _xmpp-client._tcp.jabber.org service = 30 30 5222 jabber.org. - if not result: - return [] - ufqdn = helpers.ascii_to_idn(fqdn) # Unicode domain name - hosts = [] - lines = result.split('\n') - for line in lines: - if line == '': - continue - domain = None - if line.startswith(fqdn): - domain = fqdn # For nslookup 9.5 - elif helpers.decode_string(line).startswith(ufqdn): - line = helpers.decode_string(line) - domain = ufqdn # For nslookup 9.6 - if domain: - rest = line[len(domain):].split('=') - if len(rest) != 2: - continue - answer_type, props_str = rest - if answer_type.strip() != 'service': - continue - props = props_str.strip().split(' ') - if len(props) < 4: - continue - prio, weight, port, host = props[-4:] - if host[-1] == '.': - host = host[:-1] - try: - prio = int(prio) - weight = int(weight) - port = int(port) - except ValueError: - continue - hosts.append({'host': host, 'port': port, 'weight': weight, - 'prio': prio}) - return hosts + def _parse_srv_result_posix(self, fqdn, result): + # typical output of bind-tools nslookup command: + # _xmpp-client._tcp.jabber.org service = 30 30 5222 jabber.org. + if not result: + return [] + ufqdn = helpers.ascii_to_idn(fqdn) # Unicode domain name + hosts = [] + lines = result.split('\n') + for line in lines: + if line == '': + continue + domain = None + if line.startswith(fqdn): + domain = fqdn # For nslookup 9.5 + elif helpers.decode_string(line).startswith(ufqdn): + line = helpers.decode_string(line) + domain = ufqdn # For nslookup 9.6 + if domain: + rest = line[len(domain):].split('=') + if len(rest) != 2: + continue + answer_type, props_str = rest + if answer_type.strip() != 'service': + continue + props = props_str.strip().split(' ') + if len(props) < 4: + continue + prio, weight, port, host = props[-4:] + if host[-1] == '.': + host = host[:-1] + try: + prio = int(prio) + weight = int(weight) + port = int(port) + except ValueError: + continue + hosts.append({'host': host, 'port': port, 'weight': weight, + 'prio': prio}) + return hosts - def _on_ready(self, host, type, result): - # nslookup finished, parse the result and call the handlers - result_list = self.parse_srv_result(host, result) - CommonResolver._on_ready(self, host, type, result_list) + def _on_ready(self, host, type, result): + # nslookup finished, parse the result and call the handlers + result_list = self.parse_srv_result(host, result) + CommonResolver._on_ready(self, host, type, result_list) - def start_resolve(self, host, type): - """ - Spawn new nslookup process and start waiting for results - """ - ns = NsLookup(self._on_ready, host, type) - ns.set_idlequeue(self.idlequeue) - ns.commandtimeout = 20 - ns.start() + def start_resolve(self, host, type): + """ + Spawn new nslookup process and start waiting for results + """ + ns = NsLookup(self._on_ready, host, type) + ns.set_idlequeue(self.idlequeue) + ns.commandtimeout = 20 + ns.start() class NsLookup(IdleCommand): - def __init__(self, on_result, host='_xmpp-client', type='srv'): - IdleCommand.__init__(self, on_result) - self.commandtimeout = 10 - self.host = host.lower() - self.type = type.lower() - if not host_pattern.match(self.host): - # invalid host name - log.error('Invalid host: %s' % self.host) - self.canexecute = False - return - if not ns_type_pattern.match(self.type): - log.error('Invalid querytype: %s' % self.type) - self.canexecute = False - return + def __init__(self, on_result, host='_xmpp-client', type='srv'): + IdleCommand.__init__(self, on_result) + self.commandtimeout = 10 + self.host = host.lower() + self.type = type.lower() + if not host_pattern.match(self.host): + # invalid host name + log.error('Invalid host: %s' % self.host) + self.canexecute = False + return + if not ns_type_pattern.match(self.type): + log.error('Invalid querytype: %s' % self.type) + self.canexecute = False + return - def _compose_command_args(self): - return ['nslookup', '-type=' + self.type , self.host] + def _compose_command_args(self): + return ['nslookup', '-type=' + self.type, self.host] - def _return_result(self): - if self.result_handler: - self.result_handler(self.host, self.type, self.result) - self.result_handler = None + def _return_result(self): + if self.result_handler: + self.result_handler(self.host, self.type, self.result) + self.result_handler = None # below lines is on how to use API and assist in testing if __name__ == '__main__': - import gobject - import gtk - from xmpp import idlequeue + import gobject + import gtk + from xmpp import idlequeue - idlequeue = idlequeue.get_idlequeue() - resolver = get_resolver(idlequeue) + idlequeue = idlequeue.get_idlequeue() + resolver = get_resolver(idlequeue) - def clicked(widget): - global resolver - host = text_view.get_text() - def on_result(host, result_array): - print 'Result:\n' + repr(result_array) - resolver.resolve(host, on_result) - win = gtk.Window() - win.set_border_width(6) - text_view = gtk.Entry() - text_view.set_text('_xmpp-client._tcp.jabber.org') - hbox = gtk.HBox() - hbox.set_spacing(3) - but = gtk.Button(' Lookup SRV ') - hbox.pack_start(text_view, 5) - hbox.pack_start(but, 0) - but.connect('clicked', clicked) - win.add(hbox) - win.show_all() - gobject.timeout_add(200, idlequeue.process) - if USE_LIBASYNCNS: - gobject.timeout_add(200, resolver.process) - gtk.main() - -# vim: se ts=3: + def clicked(widget): + global resolver + host = text_view.get_text() + def on_result(host, result_array): + print 'Result:\n' + repr(result_array) + resolver.resolve(host, on_result) + win = gtk.Window() + win.set_border_width(6) + text_view = gtk.Entry() + text_view.set_text('_xmpp-client._tcp.jabber.org') + hbox = gtk.HBox() + hbox.set_spacing(3) + but = gtk.Button(' Lookup SRV ') + hbox.pack_start(text_view, 5) + hbox.pack_start(but, 0) + but.connect('clicked', clicked) + win.add(hbox) + win.show_all() + gobject.timeout_add(200, idlequeue.process) + if USE_LIBASYNCNS: + gobject.timeout_add(200, resolver.process) + gtk.main() diff --git a/src/common/rst_xhtml_generator.py b/src/common/rst_xhtml_generator.py index 7211e8a1d..09f28a504 100644 --- a/src/common/rst_xhtml_generator.py +++ b/src/common/rst_xhtml_generator.py @@ -22,132 +22,132 @@ ## try: - from docutils import io - from docutils.core import Publisher - from docutils.parsers.rst import roles - from docutils import nodes,utils - from docutils.parsers.rst.roles import set_classes + from docutils import io + from docutils.core import Publisher + from docutils.parsers.rst import roles + from docutils import nodes, utils + from docutils.parsers.rst.roles import set_classes except ImportError: - print "Requires docutils 0.4 for set_classes to be available" - def create_xhtml(text): - return None + print "Requires docutils 0.4 for set_classes to be available" + def create_xhtml(text): + return None else: - def pos_int_validator(text): - """ - Validates that text can be evaluated as a positive integer - """ - result = int(text) - if result < 0: - raise ValueError("Error: value '%(text)s' " - "must be a positive integer") - return result + def pos_int_validator(text): + """ + Validates that text can be evaluated as a positive integer + """ + result = int(text) + if result < 0: + raise ValueError("Error: value '%(text)s' " + "must be a positive integer") + return result - def generate_uri_role( role_name, aliases, anchor_text, base_url, - interpret_url, validator): - """ - Create and register a uri based "interpreted role" + def generate_uri_role( role_name, aliases, anchor_text, base_url, + interpret_url, validator): + """ + Create and register a uri based "interpreted role" - Those are similar to the RFC, and PEP ones, and take - role_name: - name that will be registered - aliases: - list of alternate names - anchor_text: - text that will be used, together with the role - base_url: - base url for the link - interpret_url: - this, modulo the validated text, will be added to it - validator: - should return the validated text, or raise ValueError - """ - def uri_reference_role(role, rawtext, text, lineno, inliner, - options={}, content=[]): - try: - valid_text = validator(text) - except ValueError, e: - msg = inliner.reporter.error( e.message % dict(text=text), line=lineno) - prb = inliner.problematic(rawtext, rawtext, msg) - return [prb], [msg] - ref = base_url + interpret_url % valid_text - set_classes(options) - node = nodes.reference(rawtext, anchor_text + utils.unescape(text), refuri=ref, - **options) - return [node], [] + Those are similar to the RFC, and PEP ones, and take + role_name: + name that will be registered + aliases: + list of alternate names + anchor_text: + text that will be used, together with the role + base_url: + base url for the link + interpret_url: + this, modulo the validated text, will be added to it + validator: + should return the validated text, or raise ValueError + """ + def uri_reference_role(role, rawtext, text, lineno, inliner, + options={}, content=[]): + try: + valid_text = validator(text) + except ValueError, e: + msg = inliner.reporter.error( e.message % dict(text=text), line=lineno) + prb = inliner.problematic(rawtext, rawtext, msg) + return [prb], [msg] + ref = base_url + interpret_url % valid_text + set_classes(options) + node = nodes.reference(rawtext, anchor_text + utils.unescape(text), refuri=ref, + **options) + return [node], [] - uri_reference_role.__doc__ = """Role to make handy references to URIs. + uri_reference_role.__doc__ = """Role to make handy references to URIs. - Use as :%(role_name)s:`71` (or any of %(aliases)s). - It will use %(base_url)s+%(interpret_url)s - validator should throw a ValueError, containing optionally - a %%(text)s format, if the interpreted text is not valid. - """ % locals() - roles.register_canonical_role(role_name, uri_reference_role) - from docutils.parsers.rst.languages import en - en.roles[role_name] = role_name - for alias in aliases: - en.roles[alias] = role_name + Use as :%(role_name)s:`71` (or any of %(aliases)s). + It will use %(base_url)s+%(interpret_url)s + validator should throw a ValueError, containing optionally + a %%(text)s format, if the interpreted text is not valid. + """ % locals() + roles.register_canonical_role(role_name, uri_reference_role) + from docutils.parsers.rst.languages import en + en.roles[role_name] = role_name + for alias in aliases: + en.roles[alias] = role_name - generate_uri_role('xep-reference', ('jep', 'xep'), - 'XEP #', 'http://www.xmpp.org/extensions/', 'xep-%04d.html', - pos_int_validator) - generate_uri_role('gajim-ticket-reference', ('ticket','gtrack'), - 'Gajim Ticket #', 'http://trac.gajim.org/ticket/', '%d', - pos_int_validator) + generate_uri_role('xep-reference', ('jep', 'xep'), + 'XEP #', 'http://www.xmpp.org/extensions/', 'xep-%04d.html', + pos_int_validator) + generate_uri_role('gajim-ticket-reference', ('ticket', 'gtrack'), + 'Gajim Ticket #', 'http://trac.gajim.org/ticket/', '%d', + pos_int_validator) - class HTMLGenerator: - """ - Really simple HTMLGenerator starting from publish_parts + class HTMLGenerator: + """ + Really simple HTMLGenerator starting from publish_parts - It reuses the docutils.core.Publisher class, which means it is *not* - threadsafe. - """ - def __init__(self, settings_spec=None, - settings_overrides=dict(report_level=5, halt_level=5), - config_section='general'): - self.pub = Publisher(reader=None, parser=None, writer=None, - settings=None, - source_class=io.StringInput, - destination_class=io.StringOutput) - self.pub.set_components(reader_name='standalone', - parser_name='restructuredtext', - writer_name='html') - # hack: JEP-0071 does not allow HTML char entities, so we hack our way - # out of it. - # — == u"\u2014" - # a setting to only emit charater entities in the writer would be nice - # FIXME: several   are emitted, and they are explicitly forbidden - # in the JEP - #   == u"\u00a0" - self.pub.writer.translator_class.attribution_formats['dash'] = ( - u'\u2014', '') - self.pub.process_programmatic_settings(settings_spec, - settings_overrides, - config_section) + It reuses the docutils.core.Publisher class, which means it is *not* + threadsafe. + """ + def __init__(self, settings_spec=None, + settings_overrides=dict(report_level=5, halt_level=5), + config_section='general'): + self.pub = Publisher(reader=None, parser=None, writer=None, + settings=None, + source_class=io.StringInput, + destination_class=io.StringOutput) + self.pub.set_components(reader_name='standalone', + parser_name='restructuredtext', + writer_name='html') + # hack: JEP-0071 does not allow HTML char entities, so we hack our way + # out of it. + # — == u"\u2014" + # a setting to only emit charater entities in the writer would be nice + # FIXME: several   are emitted, and they are explicitly forbidden + # in the JEP + #   == u"\u00a0" + self.pub.writer.translator_class.attribution_formats['dash'] = ( + u'\u2014', '') + self.pub.process_programmatic_settings(settings_spec, + settings_overrides, + config_section) - def create_xhtml(self, text, destination=None, destination_path=None, - enable_exit_status=None): - """ - Create xhtml for a fragment of IM dialog. We can use the source_name - to store info about the message - """ - self.pub.set_source(text, None) - self.pub.set_destination(destination, destination_path) - output = self.pub.publish(enable_exit_status=enable_exit_status) - # kludge until we can get docutils to stop generating (rare)   - # entities - return u'\u00a0'.join(self.pub.writer.parts['fragment'].strip().split( - ' ')) + def create_xhtml(self, text, destination=None, destination_path=None, + enable_exit_status=None): + """ + Create xhtml for a fragment of IM dialog. We can use the source_name + to store info about the message + """ + self.pub.set_source(text, None) + self.pub.set_destination(destination, destination_path) + output = self.pub.publish(enable_exit_status=enable_exit_status) + # kludge until we can get docutils to stop generating (rare)   + # entities + return u'\u00a0'.join(self.pub.writer.parts['fragment'].strip().split( + ' ')) - Generator = HTMLGenerator() + Generator = HTMLGenerator() - def create_xhtml(text): - return Generator.create_xhtml(text) + def create_xhtml(text): + return Generator.create_xhtml(text) if __name__ == '__main__': - print "test 1\n", Generator.create_xhtml(""" + print "test 1\n", Generator.create_xhtml(""" test:: >>> print 1 @@ -158,11 +158,9 @@ test:: this `` should trigger`` should trigger the   problem. """) - print "test 2\n", Generator.create_xhtml(""" + print "test 2\n", Generator.create_xhtml(""" *test1 test2_ """) - print "test 3\n", Generator.create_xhtml(""":ticket:`316` implements :xep:`71`""") - -# vim: se ts=3: + print "test 3\n", Generator.create_xhtml(""":ticket:`316` implements :xep:`71`""") diff --git a/src/common/sleepy.py b/src/common/sleepy.py index 0116faefb..7f115da21 100644 --- a/src/common/sleepy.py +++ b/src/common/sleepy.py @@ -28,119 +28,117 @@ import os, sys STATE_UNKNOWN = 'OS probably not supported' STATE_XA = 'extended away' STATE_AWAY = 'away' -STATE_AWAKE = 'awake' +STATE_AWAKE = 'awake' SUPPORTED = True try: - if os.name == 'nt': - import ctypes + if os.name == 'nt': + import ctypes - GetTickCount = ctypes.windll.kernel32.GetTickCount - GetLastInputInfo = ctypes.windll.user32.GetLastInputInfo + GetTickCount = ctypes.windll.kernel32.GetTickCount + GetLastInputInfo = ctypes.windll.user32.GetLastInputInfo - class LASTINPUTINFO(ctypes.Structure): - _fields_ = [('cbSize', ctypes.c_uint), ('dwTime', ctypes.c_uint)] + class LASTINPUTINFO(ctypes.Structure): + _fields_ = [('cbSize', ctypes.c_uint), ('dwTime', ctypes.c_uint)] - lastInputInfo = LASTINPUTINFO() - lastInputInfo.cbSize = ctypes.sizeof(lastInputInfo) + lastInputInfo = LASTINPUTINFO() + lastInputInfo.cbSize = ctypes.sizeof(lastInputInfo) - # one or more of these may not be supported before XP. - OpenInputDesktop = ctypes.windll.user32.OpenInputDesktop - CloseDesktop = ctypes.windll.user32.CloseDesktop - SystemParametersInfo = ctypes.windll.user32.SystemParametersInfoW - else: # unix - from common import idle + # one or more of these may not be supported before XP. + OpenInputDesktop = ctypes.windll.user32.OpenInputDesktop + CloseDesktop = ctypes.windll.user32.CloseDesktop + SystemParametersInfo = ctypes.windll.user32.SystemParametersInfoW + else: # unix + from common import idle except Exception: - gajim.log.debug('Unable to load idle module') - SUPPORTED = False + gajim.log.debug('Unable to load idle module') + SUPPORTED = False class SleepyWindows: - def __init__(self, away_interval = 60, xa_interval = 120): - self.away_interval = away_interval - self.xa_interval = xa_interval - self.state = STATE_AWAKE # assume we are awake + def __init__(self, away_interval = 60, xa_interval = 120): + self.away_interval = away_interval + self.xa_interval = xa_interval + self.state = STATE_AWAKE # assume we are awake - def getIdleSec(self): - GetLastInputInfo(ctypes.byref(lastInputInfo)) - idleDelta = float(GetTickCount() - lastInputInfo.dwTime) / 1000 - return idleDelta + def getIdleSec(self): + GetLastInputInfo(ctypes.byref(lastInputInfo)) + idleDelta = float(GetTickCount() - lastInputInfo.dwTime) / 1000 + return idleDelta - def poll(self): - """ - Check to see if we should change state - """ - if not SUPPORTED: - return False + def poll(self): + """ + Check to see if we should change state + """ + if not SUPPORTED: + return False - # screen saver, in windows >= XP - saver_runing = ctypes.c_int(0) - # 0x72 is SPI_GETSCREENSAVERRUNNING - if SystemParametersInfo(0x72, 0, ctypes.byref(saver_runing), 0) and \ - saver_runing.value: - self.state = STATE_XA - return True + # screen saver, in windows >= XP + saver_runing = ctypes.c_int(0) + # 0x72 is SPI_GETSCREENSAVERRUNNING + if SystemParametersInfo(0x72, 0, ctypes.byref(saver_runing), 0) and \ + saver_runing.value: + self.state = STATE_XA + return True - desk = OpenInputDesktop(0, False, 0) - if not desk: - # Screen locked - self.state = STATE_XA - return True - CloseDesktop(desk) + desk = OpenInputDesktop(0, False, 0) + if not desk: + # Screen locked + self.state = STATE_XA + return True + CloseDesktop(desk) - idleTime = self.getIdleSec() + idleTime = self.getIdleSec() - # xa is stronger than away so check for xa first - if idleTime > self.xa_interval: - self.state = STATE_XA - elif idleTime > self.away_interval: - self.state = STATE_AWAY - else: - self.state = STATE_AWAKE - return True + # xa is stronger than away so check for xa first + if idleTime > self.xa_interval: + self.state = STATE_XA + elif idleTime > self.away_interval: + self.state = STATE_AWAY + else: + self.state = STATE_AWAKE + return True - def getState(self): - return self.state + def getState(self): + return self.state - def setState(self, val): - self.state = val + def setState(self, val): + self.state = val class SleepyUnix: - def __init__(self, away_interval = 60, xa_interval = 120): - global SUPPORTED - self.away_interval = away_interval - self.xa_interval = xa_interval - self.state = STATE_AWAKE # assume we are awake + def __init__(self, away_interval = 60, xa_interval = 120): + global SUPPORTED + self.away_interval = away_interval + self.xa_interval = xa_interval + self.state = STATE_AWAKE # assume we are awake - def getIdleSec(self): - return idle.getIdleSec() + def getIdleSec(self): + return idle.getIdleSec() - def poll(self): - """ - Check to see if we should change state - """ - if not SUPPORTED: - return False + def poll(self): + """ + Check to see if we should change state + """ + if not SUPPORTED: + return False - idleTime = self.getIdleSec() + idleTime = self.getIdleSec() - # xa is stronger than away so check for xa first - if idleTime > self.xa_interval: - self.state = STATE_XA - elif idleTime > self.away_interval: - self.state = STATE_AWAY - else: - self.state = STATE_AWAKE - return True + # xa is stronger than away so check for xa first + if idleTime > self.xa_interval: + self.state = STATE_XA + elif idleTime > self.away_interval: + self.state = STATE_AWAY + else: + self.state = STATE_AWAKE + return True - def getState(self): - return self.state + def getState(self): + return self.state - def setState(self, val): - self.state = val + def setState(self, val): + self.state = val if os.name == 'nt': - Sleepy = SleepyWindows + Sleepy = SleepyWindows else: - Sleepy = SleepyUnix - -# vim: se ts=3: + Sleepy = SleepyUnix diff --git a/src/common/socks5.py b/src/common/socks5.py index 59d7e4b5a..bfeda862e 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -53,1114 +53,1112 @@ READ_TIMEOUT = 180 SEND_TIMEOUT = 180 class SocksQueue: - """ - Queue for all file requests objects - """ + """ + Queue for all file requests objects + """ - def __init__(self, idlequeue, complete_transfer_cb=None, - progress_transfer_cb=None, error_cb=None): - self.connected = 0 - self.readers = {} - self.files_props = {} - self.senders = {} - self.idx = 1 - self.listener = None - self.sha_handlers = {} - # handle all io events in the global idle queue, instead of processing - # each foo seconds - self.idlequeue = idlequeue - self.complete_transfer_cb = complete_transfer_cb - self.progress_transfer_cb = progress_transfer_cb - self.error_cb = error_cb - self.on_success = None - self.on_failure = None + def __init__(self, idlequeue, complete_transfer_cb=None, + progress_transfer_cb=None, error_cb=None): + self.connected = 0 + self.readers = {} + self.files_props = {} + self.senders = {} + self.idx = 1 + self.listener = None + self.sha_handlers = {} + # handle all io events in the global idle queue, instead of processing + # each foo seconds + self.idlequeue = idlequeue + self.complete_transfer_cb = complete_transfer_cb + self.progress_transfer_cb = progress_transfer_cb + self.error_cb = error_cb + self.on_success = None + self.on_failure = None - def start_listener(self, port, sha_str, sha_handler, sid): - """ - Start waiting for incomming connections on (host, port) and do a socks5 - authentication using sid for generated SHA - """ - self.sha_handlers[sha_str] = (sha_handler, sid) - if self.listener is None: - self.listener = Socks5Listener(self.idlequeue, port) - self.listener.queue = self - self.listener.bind() - if self.listener.started is False: - self.listener = None - # We cannot bind port, call error callback and fail - self.error_cb(_('Unable to bind to port %s.') % port, - _('Maybe you have another running instance of Gajim. File ' - 'Transfer will be cancelled.')) - return None - self.connected += 1 - return self.listener + def start_listener(self, port, sha_str, sha_handler, sid): + """ + Start waiting for incomming connections on (host, port) and do a socks5 + authentication using sid for generated SHA + """ + self.sha_handlers[sha_str] = (sha_handler, sid) + if self.listener is None: + self.listener = Socks5Listener(self.idlequeue, port) + self.listener.queue = self + self.listener.bind() + if self.listener.started is False: + self.listener = None + # We cannot bind port, call error callback and fail + self.error_cb(_('Unable to bind to port %s.') % port, + _('Maybe you have another running instance of Gajim. File ' + 'Transfer will be cancelled.')) + return None + self.connected += 1 + return self.listener - def send_success_reply(self, file_props, streamhost): - if 'streamhost-used' in file_props and \ - file_props['streamhost-used'] is True: - if 'proxyhosts' in file_props: - for proxy in file_props['proxyhosts']: - if proxy == streamhost: - self.on_success(streamhost) - return 2 - return 0 - if 'streamhosts' in file_props: - for host in file_props['streamhosts']: - if streamhost['state'] == 1: - return 0 - streamhost['state'] = 1 - self.on_success(streamhost) - return 1 - return 0 + def send_success_reply(self, file_props, streamhost): + if 'streamhost-used' in file_props and \ + file_props['streamhost-used'] is True: + if 'proxyhosts' in file_props: + for proxy in file_props['proxyhosts']: + if proxy == streamhost: + self.on_success(streamhost) + return 2 + return 0 + if 'streamhosts' in file_props: + for host in file_props['streamhosts']: + if streamhost['state'] == 1: + return 0 + streamhost['state'] = 1 + self.on_success(streamhost) + return 1 + return 0 - def connect_to_hosts(self, account, sid, on_success=None, on_failure=None): - self.on_success = on_success - self.on_failure = on_failure - file_props = self.files_props[account][sid] - file_props['failure_cb'] = on_failure + def connect_to_hosts(self, account, sid, on_success=None, on_failure=None): + self.on_success = on_success + self.on_failure = on_failure + file_props = self.files_props[account][sid] + file_props['failure_cb'] = on_failure - # add streamhosts to the queue - for streamhost in file_props['streamhosts']: - receiver = Socks5Receiver(self.idlequeue, streamhost, sid, file_props) - self.add_receiver(account, receiver) - streamhost['idx'] = receiver.queue_idx + # add streamhosts to the queue + for streamhost in file_props['streamhosts']: + receiver = Socks5Receiver(self.idlequeue, streamhost, sid, file_props) + self.add_receiver(account, receiver) + streamhost['idx'] = receiver.queue_idx - def _socket_connected(self, streamhost, file_props): - """ - Called when there is a host connected to one of the senders's - streamhosts. Stop othere attempts for connections - """ - for host in file_props['streamhosts']: - if host != streamhost and 'idx' in host: - if host['state'] == 1: - # remove current - self.remove_receiver(streamhost['idx']) - return - # set state -2, meaning that this streamhost is stopped, - # but it may be connectected later - if host['state'] >= 0: - self.remove_receiver(host['idx']) - host['idx'] = -1 - host['state'] = -2 + def _socket_connected(self, streamhost, file_props): + """ + Called when there is a host connected to one of the senders's + streamhosts. Stop othere attempts for connections + """ + for host in file_props['streamhosts']: + if host != streamhost and 'idx' in host: + if host['state'] == 1: + # remove current + self.remove_receiver(streamhost['idx']) + return + # set state -2, meaning that this streamhost is stopped, + # but it may be connectected later + if host['state'] >= 0: + self.remove_receiver(host['idx']) + host['idx'] = -1 + host['state'] = -2 - def reconnect_receiver(self, receiver, streamhost): - """ - Check the state of all streamhosts and if all has failed, then emit - connection failure cb. If there are some which are still not connected - try to establish connection to one of them - """ - self.idlequeue.remove_timeout(receiver.fd) - self.idlequeue.unplug_idle(receiver.fd) - file_props = receiver.file_props - streamhost['state'] = -1 - # boolean, indicates that there are hosts, which are not tested yet - unused_hosts = False - for host in file_props['streamhosts']: - if 'idx' in host: - if host['state'] >= 0: - return - elif host['state'] == -2: - unused_hosts = True - if unused_hosts: - for host in file_props['streamhosts']: - if host['state'] == -2: - host['state'] = 0 - receiver = Socks5Receiver(self.idlequeue, host, host['sid'], - file_props) - self.add_receiver(receiver.account, receiver) - host['idx'] = receiver.queue_idx - # we still have chances to connect - return - if 'received-len' not in file_props or file_props['received-len'] == 0: - # there are no other streamhosts and transfer hasn't started - self._connection_refused(streamhost, file_props, receiver.queue_idx) - else: - # transfer stopped, it is most likely stopped from sender - receiver.disconnect() - file_props['error'] = -1 - self.process_result(-1, receiver) + def reconnect_receiver(self, receiver, streamhost): + """ + Check the state of all streamhosts and if all has failed, then emit + connection failure cb. If there are some which are still not connected + try to establish connection to one of them + """ + self.idlequeue.remove_timeout(receiver.fd) + self.idlequeue.unplug_idle(receiver.fd) + file_props = receiver.file_props + streamhost['state'] = -1 + # boolean, indicates that there are hosts, which are not tested yet + unused_hosts = False + for host in file_props['streamhosts']: + if 'idx' in host: + if host['state'] >= 0: + return + elif host['state'] == -2: + unused_hosts = True + if unused_hosts: + for host in file_props['streamhosts']: + if host['state'] == -2: + host['state'] = 0 + receiver = Socks5Receiver(self.idlequeue, host, host['sid'], + file_props) + self.add_receiver(receiver.account, receiver) + host['idx'] = receiver.queue_idx + # we still have chances to connect + return + if 'received-len' not in file_props or file_props['received-len'] == 0: + # there are no other streamhosts and transfer hasn't started + self._connection_refused(streamhost, file_props, receiver.queue_idx) + else: + # transfer stopped, it is most likely stopped from sender + receiver.disconnect() + file_props['error'] = -1 + self.process_result(-1, receiver) - def _connection_refused(self, streamhost, file_props, idx): - """ - Called when we loose connection during transfer - """ - if file_props is None: - return - streamhost['state'] = -1 - self.remove_receiver(idx, False) - if 'streamhosts' in file_props: - for host in file_props['streamhosts']: - if host['state'] != -1: - return - # failure_cb exists - this means that it has never been called - if 'failure_cb' in file_props and file_props['failure_cb']: - file_props['failure_cb'](streamhost['initiator'], streamhost['id'], - file_props['sid'], code = 404) - del(file_props['failure_cb']) + def _connection_refused(self, streamhost, file_props, idx): + """ + Called when we loose connection during transfer + """ + if file_props is None: + return + streamhost['state'] = -1 + self.remove_receiver(idx, False) + if 'streamhosts' in file_props: + for host in file_props['streamhosts']: + if host['state'] != -1: + return + # failure_cb exists - this means that it has never been called + if 'failure_cb' in file_props and file_props['failure_cb']: + file_props['failure_cb'](streamhost['initiator'], streamhost['id'], + file_props['sid'], code = 404) + del(file_props['failure_cb']) - def add_receiver(self, account, sock5_receiver): - """ - Add new file request - """ - self.readers[self.idx] = sock5_receiver - sock5_receiver.queue_idx = self.idx - sock5_receiver.queue = self - sock5_receiver.account = account - self.idx += 1 - result = sock5_receiver.connect() - self.connected += 1 - if result is not None: - result = sock5_receiver.main() - self.process_result(result, sock5_receiver) - return 1 - return None + def add_receiver(self, account, sock5_receiver): + """ + Add new file request + """ + self.readers[self.idx] = sock5_receiver + sock5_receiver.queue_idx = self.idx + sock5_receiver.queue = self + sock5_receiver.account = account + self.idx += 1 + result = sock5_receiver.connect() + self.connected += 1 + if result is not None: + result = sock5_receiver.main() + self.process_result(result, sock5_receiver) + return 1 + return None - def get_file_from_sender(self, file_props, account): - if file_props is None: - return - if 'hash' in file_props and file_props['hash'] in self.senders: - sender = self.senders[file_props['hash']] - sender.account = account - result = self.get_file_contents(0) - self.process_result(result, sender) + def get_file_from_sender(self, file_props, account): + if file_props is None: + return + if 'hash' in file_props and file_props['hash'] in self.senders: + sender = self.senders[file_props['hash']] + sender.account = account + result = self.get_file_contents(0) + self.process_result(result, sender) - def result_sha(self, sha_str, idx): - if sha_str in self.sha_handlers: - props = self.sha_handlers[sha_str] - props[0](props[1], idx) + def result_sha(self, sha_str, idx): + if sha_str in self.sha_handlers: + props = self.sha_handlers[sha_str] + props[0](props[1], idx) - def activate_proxy(self, idx): - if idx not in self.readers: - return - reader = self.readers[idx] - if reader.file_props['type'] != 's': - return - if reader.state != 5: - return - reader.state = 6 - if reader.connected: - reader.file_props['error'] = 0 - reader.file_props['disconnect_cb'] = reader.disconnect - reader.file_props['started'] = True - reader.file_props['completed'] = False - reader.file_props['paused'] = False - reader.file_props['stalled'] = False - reader.file_props['elapsed-time'] = 0 - reader.file_props['last-time'] = self.idlequeue.current_time() - reader.file_props['received-len'] = 0 - reader.pauses = 0 - # start sending file to proxy - self.idlequeue.set_read_timeout(reader.fd, STALLED_TIMEOUT) - self.idlequeue.plug_idle(reader, True, False) - result = reader.write_next() - self.process_result(result, reader) + def activate_proxy(self, idx): + if idx not in self.readers: + return + reader = self.readers[idx] + if reader.file_props['type'] != 's': + return + if reader.state != 5: + return + reader.state = 6 + if reader.connected: + reader.file_props['error'] = 0 + reader.file_props['disconnect_cb'] = reader.disconnect + reader.file_props['started'] = True + reader.file_props['completed'] = False + reader.file_props['paused'] = False + reader.file_props['stalled'] = False + reader.file_props['elapsed-time'] = 0 + reader.file_props['last-time'] = self.idlequeue.current_time() + reader.file_props['received-len'] = 0 + reader.pauses = 0 + # start sending file to proxy + self.idlequeue.set_read_timeout(reader.fd, STALLED_TIMEOUT) + self.idlequeue.plug_idle(reader, True, False) + result = reader.write_next() + self.process_result(result, reader) - def send_file(self, file_props, account): - if 'hash' in file_props and file_props['hash'] in self.senders: - sender = self.senders[file_props['hash']] - file_props['streamhost-used'] = True - sender.account = account - if file_props['type'] == 's': - sender.file_props = file_props - result = sender.send_file() - self.process_result(result, sender) - else: - file_props['elapsed-time'] = 0 - file_props['last-time'] = self.idlequeue.current_time() - file_props['received-len'] = 0 - sender.file_props = file_props + def send_file(self, file_props, account): + if 'hash' in file_props and file_props['hash'] in self.senders: + sender = self.senders[file_props['hash']] + file_props['streamhost-used'] = True + sender.account = account + if file_props['type'] == 's': + sender.file_props = file_props + result = sender.send_file() + self.process_result(result, sender) + else: + file_props['elapsed-time'] = 0 + file_props['last-time'] = self.idlequeue.current_time() + file_props['received-len'] = 0 + sender.file_props = file_props - def add_file_props(self, account, file_props): - """ - File_prop to the dict of current file_props. It is identified by account - name and sid - """ - if file_props is None or ('sid' in file_props) is False: - return - _id = file_props['sid'] - if account not in self.files_props: - self.files_props[account] = {} - self.files_props[account][_id] = file_props + def add_file_props(self, account, file_props): + """ + File_prop to the dict of current file_props. It is identified by account + name and sid + """ + if file_props is None or ('sid' in file_props) is False: + return + _id = file_props['sid'] + if account not in self.files_props: + self.files_props[account] = {} + self.files_props[account][_id] = file_props - def remove_file_props(self, account, sid): - if account in self.files_props: - fl_props = self.files_props[account] - if sid in fl_props: - del(fl_props[sid]) + def remove_file_props(self, account, sid): + if account in self.files_props: + fl_props = self.files_props[account] + if sid in fl_props: + del(fl_props[sid]) - if len(self.files_props) == 0: - self.connected = 0 + if len(self.files_props) == 0: + self.connected = 0 - def get_file_props(self, account, sid): - """ - Get fil_prop by account name and session id - """ - if account in self.files_props: - fl_props = self.files_props[account] - if sid in fl_props: - return fl_props[sid] - return None + def get_file_props(self, account, sid): + """ + Get fil_prop by account name and session id + """ + if account in self.files_props: + fl_props = self.files_props[account] + if sid in fl_props: + return fl_props[sid] + return None - def on_connection_accepted(self, sock): - sock_hash = sock.__hash__() - if sock_hash not in self.senders: - self.senders[sock_hash] = Socks5Sender(self.idlequeue, sock_hash, self, - sock[0], sock[1][0], sock[1][1]) - self.connected += 1 + def on_connection_accepted(self, sock): + sock_hash = sock.__hash__() + if sock_hash not in self.senders: + self.senders[sock_hash] = Socks5Sender(self.idlequeue, sock_hash, self, + sock[0], sock[1][0], sock[1][1]) + self.connected += 1 - def process_result(self, result, actor): - """ - Take appropriate actions upon the result: - [ 0, - 1 ] complete/end transfer - [ > 0 ] send progress message - [ None ] do nothing - """ - if result is None: - return - if result in (0, -1) and self.complete_transfer_cb is not None: - account = actor.account - if account is None and 'tt_account' in actor.file_props: - account = actor.file_props['tt_account'] - self.complete_transfer_cb(account, actor.file_props) - elif self.progress_transfer_cb is not None: - self.progress_transfer_cb(actor.account, actor.file_props) + def process_result(self, result, actor): + """ + Take appropriate actions upon the result: + [ 0, - 1 ] complete/end transfer + [ > 0 ] send progress message + [ None ] do nothing + """ + if result is None: + return + if result in (0, -1) and self.complete_transfer_cb is not None: + account = actor.account + if account is None and 'tt_account' in actor.file_props: + account = actor.file_props['tt_account'] + self.complete_transfer_cb(account, actor.file_props) + elif self.progress_transfer_cb is not None: + self.progress_transfer_cb(actor.account, actor.file_props) - def remove_receiver(self, idx, do_disconnect=True): - """ - Remove reciver from the list and decrease the number of active - connections with 1 - """ - if idx != -1: - if idx in self.readers: - reader = self.readers[idx] - self.idlequeue.unplug_idle(reader.fd) - self.idlequeue.remove_timeout(reader.fd) - if do_disconnect: - reader.disconnect() - else: - if reader.streamhost is not None: - reader.streamhost['state'] = -1 - del(self.readers[idx]) + def remove_receiver(self, idx, do_disconnect=True): + """ + Remove reciver from the list and decrease the number of active + connections with 1 + """ + if idx != -1: + if idx in self.readers: + reader = self.readers[idx] + self.idlequeue.unplug_idle(reader.fd) + self.idlequeue.remove_timeout(reader.fd) + if do_disconnect: + reader.disconnect() + else: + if reader.streamhost is not None: + reader.streamhost['state'] = -1 + del(self.readers[idx]) - def remove_sender(self, idx, do_disconnect=True): - """ - Remove sender from the list of senders and decrease the number of active - connections with 1 - """ - if idx != -1: - if idx in self.senders: - if do_disconnect: - self.senders[idx].disconnect() - return - else: - del(self.senders[idx]) - if self.connected > 0: - self.connected -= 1 - if len(self.senders) == 0 and self.listener is not None: - self.listener.disconnect() - self.listener = None - self.connected -= 1 + def remove_sender(self, idx, do_disconnect=True): + """ + Remove sender from the list of senders and decrease the number of active + connections with 1 + """ + if idx != -1: + if idx in self.senders: + if do_disconnect: + self.senders[idx].disconnect() + return + else: + del(self.senders[idx]) + if self.connected > 0: + self.connected -= 1 + if len(self.senders) == 0 and self.listener is not None: + self.listener.disconnect() + self.listener = None + self.connected -= 1 class Socks5: - def __init__(self, idlequeue, host, port, initiator, target, sid): - if host is not None: - try: - self.host = host - self.ais = socket.getaddrinfo(host, port, socket.AF_UNSPEC, - socket.SOCK_STREAM) - except socket.gaierror: - self.ais = None - self.idlequeue = idlequeue - self.fd = -1 - self.port = port - self.initiator = initiator - self.target = target - self.sid = sid - self._sock = None - self.account = None - self.state = 0 # not connected - self.pauses = 0 - self.size = 0 - self.remaining_buff = '' - self.file = None + def __init__(self, idlequeue, host, port, initiator, target, sid): + if host is not None: + try: + self.host = host + self.ais = socket.getaddrinfo(host, port, socket.AF_UNSPEC, + socket.SOCK_STREAM) + except socket.gaierror: + self.ais = None + self.idlequeue = idlequeue + self.fd = -1 + self.port = port + self.initiator = initiator + self.target = target + self.sid = sid + self._sock = None + self.account = None + self.state = 0 # not connected + self.pauses = 0 + self.size = 0 + self.remaining_buff = '' + self.file = None - def open_file_for_reading(self): - if self.file is None: - try: - self.file = open(self.file_props['file-name'],'rb') - if 'offset' in self.file_props and self.file_props['offset']: - self.size = self.file_props['offset'] - self.file.seek(self.size) - self.file_props['received-len'] = self.size - except IOError, e: - self.close_file() - raise IOError, e + def open_file_for_reading(self): + if self.file is None: + try: + self.file = open(self.file_props['file-name'], 'rb') + if 'offset' in self.file_props and self.file_props['offset']: + self.size = self.file_props['offset'] + self.file.seek(self.size) + self.file_props['received-len'] = self.size + except IOError, e: + self.close_file() + raise IOError, e - def close_file(self): - if self.file: - if not self.file.closed: - try: - self.file.close() - except Exception: - pass - self.file = None + def close_file(self): + if self.file: + if not self.file.closed: + try: + self.file.close() + except Exception: + pass + self.file = None - def get_fd(self): - """ - Test if file is already open and return its fd, or just open the file and - return the fd - """ - if 'fd' in self.file_props: - fd = self.file_props['fd'] - else: - offset = 0 - opt = 'wb' - if 'offset' in self.file_props and self.file_props['offset']: - offset = self.file_props['offset'] - opt = 'ab' - fd = open(self.file_props['file-name'], opt) - self.file_props['fd'] = fd - self.file_props['elapsed-time'] = 0 - self.file_props['last-time'] = self.idlequeue.current_time() - self.file_props['received-len'] = offset - return fd + def get_fd(self): + """ + Test if file is already open and return its fd, or just open the file and + return the fd + """ + if 'fd' in self.file_props: + fd = self.file_props['fd'] + else: + offset = 0 + opt = 'wb' + if 'offset' in self.file_props and self.file_props['offset']: + offset = self.file_props['offset'] + opt = 'ab' + fd = open(self.file_props['file-name'], opt) + self.file_props['fd'] = fd + self.file_props['elapsed-time'] = 0 + self.file_props['last-time'] = self.idlequeue.current_time() + self.file_props['received-len'] = offset + return fd - def rem_fd(self, fd): - if 'fd' in self.file_props: - del(self.file_props['fd']) - try: - fd.close() - except Exception: - pass + def rem_fd(self, fd): + if 'fd' in self.file_props: + del(self.file_props['fd']) + try: + fd.close() + except Exception: + pass - def receive(self): - """ - Read small chunks of data. Call owner's disconnected() method if - appropriate - """ - received = '' - try: - add = self._recv(64) - except Exception: - add = '' - received += add - if len(add) == 0: - self.disconnect() - return add + def receive(self): + """ + Read small chunks of data. Call owner's disconnected() method if + appropriate + """ + received = '' + try: + add = self._recv(64) + except Exception: + add = '' + received += add + if len(add) == 0: + self.disconnect() + return add - def send_raw(self,raw_data): - """ - Write raw outgoing data - """ - try: - self._send(raw_data) - except Exception: - self.disconnect() - return len(raw_data) + def send_raw(self, raw_data): + """ + Write raw outgoing data + """ + try: + self._send(raw_data) + except Exception: + self.disconnect() + return len(raw_data) - def write_next(self): - if self.remaining_buff != '': - buff = self.remaining_buff - self.remaining_buff = '' - else: - try: - self.open_file_for_reading() - except IOError, e: - self.state = 8 # end connection - self.disconnect() - self.file_props['error'] = -7 # unable to read from file - return -1 - buff = self.file.read(MAX_BUFF_LEN) - if len(buff) > 0: - lenn = 0 - try: - lenn = self._send(buff) - except Exception, e: - if e.args[0] not in (EINTR, ENOBUFS, EWOULDBLOCK): - # peer stopped reading - self.state = 8 # end connection - self.disconnect() - self.file_props['error'] = -1 - return -1 - self.size += lenn - current_time = self.idlequeue.current_time() - self.file_props['elapsed-time'] += current_time - \ - self.file_props['last-time'] - self.file_props['last-time'] = current_time - self.file_props['received-len'] = self.size - if self.size >= int(self.file_props['size']): - self.state = 8 # end connection - self.file_props['error'] = 0 - self.disconnect() - return -1 - if lenn != len(buff): - self.remaining_buff = buff[lenn:] - else: - self.remaining_buff = '' - self.state = 7 # continue to write in the socket - if lenn == 0: - return None - self.file_props['stalled'] = False - return lenn - else: - self.state = 8 # end connection - self.disconnect() - return -1 + def write_next(self): + if self.remaining_buff != '': + buff = self.remaining_buff + self.remaining_buff = '' + else: + try: + self.open_file_for_reading() + except IOError, e: + self.state = 8 # end connection + self.disconnect() + self.file_props['error'] = -7 # unable to read from file + return -1 + buff = self.file.read(MAX_BUFF_LEN) + if len(buff) > 0: + lenn = 0 + try: + lenn = self._send(buff) + except Exception, e: + if e.args[0] not in (EINTR, ENOBUFS, EWOULDBLOCK): + # peer stopped reading + self.state = 8 # end connection + self.disconnect() + self.file_props['error'] = -1 + return -1 + self.size += lenn + current_time = self.idlequeue.current_time() + self.file_props['elapsed-time'] += current_time - \ + self.file_props['last-time'] + self.file_props['last-time'] = current_time + self.file_props['received-len'] = self.size + if self.size >= int(self.file_props['size']): + self.state = 8 # end connection + self.file_props['error'] = 0 + self.disconnect() + return -1 + if lenn != len(buff): + self.remaining_buff = buff[lenn:] + else: + self.remaining_buff = '' + self.state = 7 # continue to write in the socket + if lenn == 0: + return None + self.file_props['stalled'] = False + return lenn + else: + self.state = 8 # end connection + self.disconnect() + return -1 - def get_file_contents(self, timeout): - """ - Read file contents from socket and write them to file - """ - if self.file_props is None or ('file-name' in self.file_props) is False: - self.file_props['error'] = -2 - return None - fd = None - if self.remaining_buff != '': - try: - fd = self.get_fd() - except IOError, e: - self.disconnect(False) - self.file_props['error'] = -6 # file system error - return 0 - fd.write(self.remaining_buff) - lenn = len(self.remaining_buff) - current_time = self.idlequeue.current_time() - self.file_props['elapsed-time'] += current_time - \ - self.file_props['last-time'] - self.file_props['last-time'] = current_time - self.file_props['received-len'] += lenn - self.remaining_buff = '' - if self.file_props['received-len'] == int(self.file_props['size']): - self.rem_fd(fd) - self.disconnect() - self.file_props['error'] = 0 - self.file_props['completed'] = True - return 0 - else: - try: - fd = self.get_fd() - except IOError, e: - self.disconnect(False) - self.file_props['error'] = -6 # file system error - return 0 - try: - buff = self._recv(MAX_BUFF_LEN) - except Exception: - buff = '' - current_time = self.idlequeue.current_time() - self.file_props['elapsed-time'] += current_time - \ - self.file_props['last-time'] - self.file_props['last-time'] = current_time - self.file_props['received-len'] += len(buff) - if len(buff) == 0: - # Transfer stopped somehow: - # reset, paused or network error - self.rem_fd(fd) - self.disconnect(False) - self.file_props['error'] = -1 - return 0 - try: - fd.write(buff) - except IOError, e: - self.rem_fd(fd) - self.disconnect(False) - self.file_props['error'] = -6 # file system error - return 0 - if self.file_props['received-len'] >= int(self.file_props['size']): - # transfer completed - self.rem_fd(fd) - self.disconnect() - self.file_props['error'] = 0 - self.file_props['completed'] = True - return 0 - # return number of read bytes. It can be used in progressbar - if fd is not None: - self.file_props['stalled'] = False - if fd is None and self.file_props['stalled'] is False: - return None - if 'received-len' in self.file_props: - if self.file_props['received-len'] != 0: - return self.file_props['received-len'] - return None + def get_file_contents(self, timeout): + """ + Read file contents from socket and write them to file + """ + if self.file_props is None or ('file-name' in self.file_props) is False: + self.file_props['error'] = -2 + return None + fd = None + if self.remaining_buff != '': + try: + fd = self.get_fd() + except IOError, e: + self.disconnect(False) + self.file_props['error'] = -6 # file system error + return 0 + fd.write(self.remaining_buff) + lenn = len(self.remaining_buff) + current_time = self.idlequeue.current_time() + self.file_props['elapsed-time'] += current_time - \ + self.file_props['last-time'] + self.file_props['last-time'] = current_time + self.file_props['received-len'] += lenn + self.remaining_buff = '' + if self.file_props['received-len'] == int(self.file_props['size']): + self.rem_fd(fd) + self.disconnect() + self.file_props['error'] = 0 + self.file_props['completed'] = True + return 0 + else: + try: + fd = self.get_fd() + except IOError, e: + self.disconnect(False) + self.file_props['error'] = -6 # file system error + return 0 + try: + buff = self._recv(MAX_BUFF_LEN) + except Exception: + buff = '' + current_time = self.idlequeue.current_time() + self.file_props['elapsed-time'] += current_time - \ + self.file_props['last-time'] + self.file_props['last-time'] = current_time + self.file_props['received-len'] += len(buff) + if len(buff) == 0: + # Transfer stopped somehow: + # reset, paused or network error + self.rem_fd(fd) + self.disconnect(False) + self.file_props['error'] = -1 + return 0 + try: + fd.write(buff) + except IOError, e: + self.rem_fd(fd) + self.disconnect(False) + self.file_props['error'] = -6 # file system error + return 0 + if self.file_props['received-len'] >= int(self.file_props['size']): + # transfer completed + self.rem_fd(fd) + self.disconnect() + self.file_props['error'] = 0 + self.file_props['completed'] = True + return 0 + # return number of read bytes. It can be used in progressbar + if fd is not None: + self.file_props['stalled'] = False + if fd is None and self.file_props['stalled'] is False: + return None + if 'received-len' in self.file_props: + if self.file_props['received-len'] != 0: + return self.file_props['received-len'] + return None - def disconnect(self): - """ - Close open descriptors and remover socket descr. from idleque - """ - # be sure that we don't leave open file - self.close_file() - self.idlequeue.remove_timeout(self.fd) - self.idlequeue.unplug_idle(self.fd) - try: - self._sock.shutdown(socket.SHUT_RDWR) - self._sock.close() - except Exception: - # socket is already closed - pass - self.connected = False - self.fd = -1 - self.state = -1 + def disconnect(self): + """ + Close open descriptors and remover socket descr. from idleque + """ + # be sure that we don't leave open file + self.close_file() + self.idlequeue.remove_timeout(self.fd) + self.idlequeue.unplug_idle(self.fd) + try: + self._sock.shutdown(socket.SHUT_RDWR) + self._sock.close() + except Exception: + # socket is already closed + pass + self.connected = False + self.fd = -1 + self.state = -1 - def _get_auth_buff(self): - """ - Message, that we support 1 one auth mechanism: the 'no auth' mechanism - """ - return struct.pack('!BBB', 0x05, 0x01, 0x00) + def _get_auth_buff(self): + """ + Message, that we support 1 one auth mechanism: the 'no auth' mechanism + """ + return struct.pack('!BBB', 0x05, 0x01, 0x00) - def _parse_auth_buff(self, buff): - """ - Parse the initial message and create a list of auth mechanisms - """ - auth_mechanisms = [] - try: - num_auth = struct.unpack('!xB', buff[:2])[0] - for i in xrange(num_auth): - mechanism, = struct.unpack('!B', buff[1 + i]) - auth_mechanisms.append(mechanism) - except Exception: - return None - return auth_mechanisms + def _parse_auth_buff(self, buff): + """ + Parse the initial message and create a list of auth mechanisms + """ + auth_mechanisms = [] + try: + num_auth = struct.unpack('!xB', buff[:2])[0] + for i in xrange(num_auth): + mechanism, = struct.unpack('!B', buff[1 + i]) + auth_mechanisms.append(mechanism) + except Exception: + return None + return auth_mechanisms - def _get_auth_response(self): - """ - Socks version(5), number of extra auth methods (we send 0x00 - no auth) - """ - return struct.pack('!BB', 0x05, 0x00) + def _get_auth_response(self): + """ + Socks version(5), number of extra auth methods (we send 0x00 - no auth) + """ + return struct.pack('!BB', 0x05, 0x00) - def _get_connect_buff(self): - ''' Connect request by domain name ''' - buff = struct.pack('!BBBBB%dsBB' % len(self.host), - 0x05, 0x01, 0x00, 0x03, len(self.host), self.host, - self.port >> 8, self.port & 0xff) - return buff + def _get_connect_buff(self): + ''' Connect request by domain name ''' + buff = struct.pack('!BBBBB%dsBB' % len(self.host), + 0x05, 0x01, 0x00, 0x03, len(self.host), self.host, + self.port >> 8, self.port & 0xff) + return buff - def _get_request_buff(self, msg, command = 0x01): - """ - Connect request by domain name, sid sha, instead of domain name (jep - 0096) - """ - buff = struct.pack('!BBBBB%dsBB' % len(msg), - 0x05, command, 0x00, 0x03, len(msg), msg, 0, 0) - return buff + def _get_request_buff(self, msg, command = 0x01): + """ + Connect request by domain name, sid sha, instead of domain name (jep + 0096) + """ + buff = struct.pack('!BBBBB%dsBB' % len(msg), + 0x05, command, 0x00, 0x03, len(msg), msg, 0, 0) + return buff - def _parse_request_buff(self, buff): - try: # don't trust on what comes from the outside - req_type, host_type, = struct.unpack('!xBxB', buff[:4]) - if host_type == 0x01: - host_arr = struct.unpack('!iiii', buff[4:8]) - host, = '.'.join(str(s) for s in host_arr) - host_len = len(host) - elif host_type == 0x03: - host_len, = struct.unpack('!B' , buff[4]) - host, = struct.unpack('!%ds' % host_len, buff[5:5 + host_len]) - portlen = len(buff[host_len + 5:]) - if portlen == 1: - port, = struct.unpack('!B', buff[host_len + 5]) - elif portlen == 2: - port, = struct.unpack('!H', buff[host_len + 5:]) - # file data, comes with auth message (Gaim bug) - else: - port, = struct.unpack('!H', buff[host_len + 5: host_len + 7]) - self.remaining_buff = buff[host_len + 7:] - except Exception: - return (None, None, None) - return (req_type, host, port) + def _parse_request_buff(self, buff): + try: # don't trust on what comes from the outside + req_type, host_type, = struct.unpack('!xBxB', buff[:4]) + if host_type == 0x01: + host_arr = struct.unpack('!iiii', buff[4:8]) + host, = '.'.join(str(s) for s in host_arr) + host_len = len(host) + elif host_type == 0x03: + host_len, = struct.unpack('!B', buff[4]) + host, = struct.unpack('!%ds' % host_len, buff[5:5 + host_len]) + portlen = len(buff[host_len + 5:]) + if portlen == 1: + port, = struct.unpack('!B', buff[host_len + 5]) + elif portlen == 2: + port, = struct.unpack('!H', buff[host_len + 5:]) + # file data, comes with auth message (Gaim bug) + else: + port, = struct.unpack('!H', buff[host_len + 5: host_len + 7]) + self.remaining_buff = buff[host_len + 7:] + except Exception: + return (None, None, None) + return (req_type, host, port) - def read_connect(self): - """ - Connect response: version, auth method - """ - buff = self._recv() - try: - version, method = struct.unpack('!BB', buff) - except Exception: - version, method = None, None - if version != 0x05 or method == 0xff: - self.disconnect() + def read_connect(self): + """ + Connect response: version, auth method + """ + buff = self._recv() + try: + version, method = struct.unpack('!BB', buff) + except Exception: + version, method = None, None + if version != 0x05 or method == 0xff: + self.disconnect() - def continue_paused_transfer(self): - if self.state < 5: - return - if self.file_props['type'] == 'r': - self.idlequeue.plug_idle(self, False, True) - else: - self.idlequeue.plug_idle(self, True, False) + def continue_paused_transfer(self): + if self.state < 5: + return + if self.file_props['type'] == 'r': + self.idlequeue.plug_idle(self, False, True) + else: + self.idlequeue.plug_idle(self, True, False) - def _get_sha1_auth(self): - """ - Get sha of sid + Initiator jid + Target jid - """ - if 'is_a_proxy' in self.file_props: - del(self.file_props['is_a_proxy']) - return hashlib.sha1('%s%s%s' % (self.sid, - self.file_props['proxy_sender'], - self.file_props['proxy_receiver'])).hexdigest() - return hashlib.sha1('%s%s%s' % (self.sid, self.initiator, self.target)).\ - hexdigest() + def _get_sha1_auth(self): + """ + Get sha of sid + Initiator jid + Target jid + """ + if 'is_a_proxy' in self.file_props: + del(self.file_props['is_a_proxy']) + return hashlib.sha1('%s%s%s' % (self.sid, + self.file_props['proxy_sender'], + self.file_props['proxy_receiver'])).hexdigest() + return hashlib.sha1('%s%s%s' % (self.sid, self.initiator, self.target)).\ + hexdigest() class Socks5Sender(Socks5, IdleObject): - """ - Class for sending file to socket over socks5 - """ + """ + Class for sending file to socket over socks5 + """ - def __init__(self, idlequeue, sock_hash, parent, _sock, host=None, - port=None): - self.queue_idx = sock_hash - self.queue = parent - Socks5.__init__(self, idlequeue, host, port, None, None, None) - self._sock = _sock - self._sock.setblocking(False) - self.fd = _sock.fileno() - self._recv = _sock.recv - self._send = _sock.send - self.connected = True - self.state = 1 # waiting for first bytes - self.file_props = None - # start waiting for data - self.idlequeue.plug_idle(self, False, True) + def __init__(self, idlequeue, sock_hash, parent, _sock, host=None, + port=None): + self.queue_idx = sock_hash + self.queue = parent + Socks5.__init__(self, idlequeue, host, port, None, None, None) + self._sock = _sock + self._sock.setblocking(False) + self.fd = _sock.fileno() + self._recv = _sock.recv + self._send = _sock.send + self.connected = True + self.state = 1 # waiting for first bytes + self.file_props = None + # start waiting for data + self.idlequeue.plug_idle(self, False, True) - def read_timeout(self): - self.idlequeue.remove_timeout(self.fd) - if self.state > 5: - # no activity for foo seconds - if self.file_props['stalled'] == False: - self.file_props['stalled'] = True - self.queue.process_result(-1, self) - if SEND_TIMEOUT > 0: - self.idlequeue.set_read_timeout(self.fd, SEND_TIMEOUT) - else: - # stop transfer, there is no error code for this - self.pollend() + def read_timeout(self): + self.idlequeue.remove_timeout(self.fd) + if self.state > 5: + # no activity for foo seconds + if self.file_props['stalled'] == False: + self.file_props['stalled'] = True + self.queue.process_result(-1, self) + if SEND_TIMEOUT > 0: + self.idlequeue.set_read_timeout(self.fd, SEND_TIMEOUT) + else: + # stop transfer, there is no error code for this + self.pollend() - def pollout(self): - if not self.connected: - self.disconnect() - return - self.idlequeue.remove_timeout(self.fd) - if self.state == 2: # send reply with desired auth type - self.send_raw(self._get_auth_response()) - elif self.state == 4: # send positive response to the 'connect' - self.send_raw(self._get_request_buff(self.sha_msg, 0x00)) - elif self.state == 7: - if self.file_props['paused']: - self.file_props['continue_cb'] = self.continue_paused_transfer - self.idlequeue.plug_idle(self, False, False) - return - result = self.write_next() - self.queue.process_result(result, self) - if result is None or result <= 0: - self.disconnect() - return - self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) - elif self.state == 8: - self.disconnect() - return - else: - self.disconnect() - if self.state < 5: - self.state += 1 - # unplug and plug this time for reading - self.idlequeue.plug_idle(self, False, True) + def pollout(self): + if not self.connected: + self.disconnect() + return + self.idlequeue.remove_timeout(self.fd) + if self.state == 2: # send reply with desired auth type + self.send_raw(self._get_auth_response()) + elif self.state == 4: # send positive response to the 'connect' + self.send_raw(self._get_request_buff(self.sha_msg, 0x00)) + elif self.state == 7: + if self.file_props['paused']: + self.file_props['continue_cb'] = self.continue_paused_transfer + self.idlequeue.plug_idle(self, False, False) + return + result = self.write_next() + self.queue.process_result(result, self) + if result is None or result <= 0: + self.disconnect() + return + self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) + elif self.state == 8: + self.disconnect() + return + else: + self.disconnect() + if self.state < 5: + self.state += 1 + # unplug and plug this time for reading + self.idlequeue.plug_idle(self, False, True) - def pollend(self): - self.state = 8 # end connection - self.disconnect() - self.file_props['error'] = -1 - self.queue.process_result(-1, self) + def pollend(self): + self.state = 8 # end connection + self.disconnect() + self.file_props['error'] = -1 + self.queue.process_result(-1, self) - def pollin(self): - if self.connected: - if self.state < 5: - result = self.main() - if self.state == 4: - self.queue.result_sha(self.sha_msg, self.queue_idx) - if result == -1: - self.disconnect() + def pollin(self): + if self.connected: + if self.state < 5: + result = self.main() + if self.state == 4: + self.queue.result_sha(self.sha_msg, self.queue_idx) + if result == -1: + self.disconnect() - elif self.state == 5: - if self.file_props is not None and self.file_props['type'] == 'r': - result = self.get_file_contents(0) - self.queue.process_result(result, self) - else: - self.disconnect() + elif self.state == 5: + if self.file_props is not None and self.file_props['type'] == 'r': + result = self.get_file_contents(0) + self.queue.process_result(result, self) + else: + self.disconnect() - def send_file(self): - """ - Start sending the file over verified connection - """ - if self.file_props['started']: - return - self.file_props['error'] = 0 - self.file_props['disconnect_cb'] = self.disconnect - self.file_props['started'] = True - self.file_props['completed'] = False - self.file_props['paused'] = False - self.file_props['continue_cb'] = self.continue_paused_transfer - self.file_props['stalled'] = False - self.file_props['connected'] = True - self.file_props['elapsed-time'] = 0 - self.file_props['last-time'] = self.idlequeue.current_time() - self.file_props['received-len'] = 0 - self.pauses = 0 - self.state = 7 - # plug for writing - self.idlequeue.plug_idle(self, True, False) - return self.write_next() # initial for nl byte + def send_file(self): + """ + Start sending the file over verified connection + """ + if self.file_props['started']: + return + self.file_props['error'] = 0 + self.file_props['disconnect_cb'] = self.disconnect + self.file_props['started'] = True + self.file_props['completed'] = False + self.file_props['paused'] = False + self.file_props['continue_cb'] = self.continue_paused_transfer + self.file_props['stalled'] = False + self.file_props['connected'] = True + self.file_props['elapsed-time'] = 0 + self.file_props['last-time'] = self.idlequeue.current_time() + self.file_props['received-len'] = 0 + self.pauses = 0 + self.state = 7 + # plug for writing + self.idlequeue.plug_idle(self, True, False) + return self.write_next() # initial for nl byte - def main(self): - """ - Initial requests for verifying the connection - """ - if self.state == 1: # initial read - buff = self.receive() - if not self.connected: - return -1 - mechs = self._parse_auth_buff(buff) - if mechs is None: - return -1 # invalid auth methods received - elif self.state == 3: # get next request - buff = self.receive() - req_type, self.sha_msg = self._parse_request_buff(buff)[:2] - if req_type != 0x01: - return -1 # request is not of type 'connect' - self.state += 1 # go to the next step - # unplug & plug for writing - self.idlequeue.plug_idle(self, True, False) - return None + def main(self): + """ + Initial requests for verifying the connection + """ + if self.state == 1: # initial read + buff = self.receive() + if not self.connected: + return -1 + mechs = self._parse_auth_buff(buff) + if mechs is None: + return -1 # invalid auth methods received + elif self.state == 3: # get next request + buff = self.receive() + req_type, self.sha_msg = self._parse_request_buff(buff)[:2] + if req_type != 0x01: + return -1 # request is not of type 'connect' + self.state += 1 # go to the next step + # unplug & plug for writing + self.idlequeue.plug_idle(self, True, False) + return None - def disconnect(self, cb=True): - """ - Close the socket - """ - # close connection and remove us from the queue - Socks5.disconnect(self) - if self.file_props is not None: - self.file_props['connected'] = False - self.file_props['disconnect_cb'] = None - if self.queue is not None: - self.queue.remove_sender(self.queue_idx, False) + def disconnect(self, cb=True): + """ + Close the socket + """ + # close connection and remove us from the queue + Socks5.disconnect(self) + if self.file_props is not None: + self.file_props['connected'] = False + self.file_props['disconnect_cb'] = None + if self.queue is not None: + self.queue.remove_sender(self.queue_idx, False) class Socks5Listener(IdleObject): - def __init__(self, idlequeue, port): - """ - Handle all incomming connections on (0.0.0.0, port) + def __init__(self, idlequeue, port): + """ + Handle all incomming connections on (0.0.0.0, port) - This class implements IdleObject, but we will expect - only pollin events though - """ - self.port = port - self.ais = socket.getaddrinfo(None, port, socket.AF_UNSPEC, - socket.SOCK_STREAM, socket.SOL_TCP, socket.AI_PASSIVE) - self.ais.sort(reverse=True) # Try IPv6 first - self.queue_idx = -1 - self.idlequeue = idlequeue - self.queue = None - self.started = False - self._sock = None - self.fd = -1 + This class implements IdleObject, but we will expect + only pollin events though + """ + self.port = port + self.ais = socket.getaddrinfo(None, port, socket.AF_UNSPEC, + socket.SOCK_STREAM, socket.SOL_TCP, socket.AI_PASSIVE) + self.ais.sort(reverse=True) # Try IPv6 first + self.queue_idx = -1 + self.idlequeue = idlequeue + self.queue = None + self.started = False + self._sock = None + self.fd = -1 - def bind(self): - for ai in self.ais: - # try the different possibilities (ipv6, ipv4, etc.) - try: - self._serv = socket.socket(*ai[:3]) - except socket.error, e: - if e.args[0] == EAFNOSUPPORT: - self.ai = None - continue - raise - self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) - self._serv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - # Under windows Vista, we need that to listen on ipv6 AND ipv4 - # Doesn't work under windows XP - if os.name == 'nt': - ver = os.sys.getwindowsversion() - if (ver[3], ver[0], ver[1]) == (2, 6, 0): - # 27 is socket.IPV6_V6ONLY under windows, but not defined ... - self._serv.setsockopt(socket.IPPROTO_IPV6, 27, 1) - # will fail when port as busy, or we don't have rights to bind - try: - self._serv.bind(ai[4]) - self.ai = ai - break - except Exception: - self.ai = None - continue - if not self.ai: - # unable to bind, show error dialog - return None - self._serv.listen(socket.SOMAXCONN) - self._serv.setblocking(False) - self.fd = self._serv.fileno() - self.idlequeue.plug_idle(self, False, True) - self.started = True + def bind(self): + for ai in self.ais: + # try the different possibilities (ipv6, ipv4, etc.) + try: + self._serv = socket.socket(*ai[:3]) + except socket.error, e: + if e.args[0] == EAFNOSUPPORT: + self.ai = None + continue + raise + self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + self._serv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + # Under windows Vista, we need that to listen on ipv6 AND ipv4 + # Doesn't work under windows XP + if os.name == 'nt': + ver = os.sys.getwindowsversion() + if (ver[3], ver[0], ver[1]) == (2, 6, 0): + # 27 is socket.IPV6_V6ONLY under windows, but not defined ... + self._serv.setsockopt(socket.IPPROTO_IPV6, 27, 1) + # will fail when port as busy, or we don't have rights to bind + try: + self._serv.bind(ai[4]) + self.ai = ai + break + except Exception: + self.ai = None + continue + if not self.ai: + # unable to bind, show error dialog + return None + self._serv.listen(socket.SOMAXCONN) + self._serv.setblocking(False) + self.fd = self._serv.fileno() + self.idlequeue.plug_idle(self, False, True) + self.started = True - def pollend(self): - """ - Called when we stop listening on (host, port) - """ - self.disconnect() + def pollend(self): + """ + Called when we stop listening on (host, port) + """ + self.disconnect() - def pollin(self): - """ - Accept a new incomming connection and notify queue - """ - sock = self.accept_conn() - self.queue.on_connection_accepted(sock) + def pollin(self): + """ + Accept a new incomming connection and notify queue + """ + sock = self.accept_conn() + self.queue.on_connection_accepted(sock) - def disconnect(self): - """ - Free all resources, we are not listening anymore - """ - self.idlequeue.remove_timeout(self.fd) - self.idlequeue.unplug_idle(self.fd) - self.fd = -1 - self.state = -1 - self.started = False - try: - self._serv.close() - except Exception: - pass + def disconnect(self): + """ + Free all resources, we are not listening anymore + """ + self.idlequeue.remove_timeout(self.fd) + self.idlequeue.unplug_idle(self.fd) + self.fd = -1 + self.state = -1 + self.started = False + try: + self._serv.close() + except Exception: + pass - def accept_conn(self): - """ - Accept a new incomming connection - """ - _sock = self._serv.accept() - _sock[0].setblocking(False) - return _sock + def accept_conn(self): + """ + Accept a new incomming connection + """ + _sock = self._serv.accept() + _sock[0].setblocking(False) + return _sock class Socks5Receiver(Socks5, IdleObject): - def __init__(self, idlequeue, streamhost, sid, file_props = None): - self.queue_idx = -1 - self.streamhost = streamhost - self.queue = None - self.file_props = file_props - self.connect_timeout = 0 - self.connected = False - self.pauses = 0 - if not self.file_props: - self.file_props = {} - self.file_props['disconnect_cb'] = self.disconnect - self.file_props['error'] = 0 - self.file_props['started'] = True - self.file_props['completed'] = False - self.file_props['paused'] = False - self.file_props['continue_cb'] = self.continue_paused_transfer - self.file_props['stalled'] = False - Socks5.__init__(self, idlequeue, streamhost['host'], - int(streamhost['port']), streamhost['initiator'], streamhost['target'], - sid) + def __init__(self, idlequeue, streamhost, sid, file_props = None): + self.queue_idx = -1 + self.streamhost = streamhost + self.queue = None + self.file_props = file_props + self.connect_timeout = 0 + self.connected = False + self.pauses = 0 + if not self.file_props: + self.file_props = {} + self.file_props['disconnect_cb'] = self.disconnect + self.file_props['error'] = 0 + self.file_props['started'] = True + self.file_props['completed'] = False + self.file_props['paused'] = False + self.file_props['continue_cb'] = self.continue_paused_transfer + self.file_props['stalled'] = False + Socks5.__init__(self, idlequeue, streamhost['host'], + int(streamhost['port']), streamhost['initiator'], streamhost['target'], + sid) - def read_timeout(self): - self.idlequeue.remove_timeout(self.fd) - if self.state > 5: - # no activity for foo seconds - if self.file_props['stalled'] == False: - self.file_props['stalled'] = True - if 'received-len' not in self.file_props: - self.file_props['received-len'] = 0 - self.queue.process_result(-1, self) - if READ_TIMEOUT > 0: - self.idlequeue.set_read_timeout(self.fd, READ_TIMEOUT) - else: - # stop transfer, there is no error code for this - self.pollend() - else: - self.queue.reconnect_receiver(self, self.streamhost) + def read_timeout(self): + self.idlequeue.remove_timeout(self.fd) + if self.state > 5: + # no activity for foo seconds + if self.file_props['stalled'] == False: + self.file_props['stalled'] = True + if 'received-len' not in self.file_props: + self.file_props['received-len'] = 0 + self.queue.process_result(-1, self) + if READ_TIMEOUT > 0: + self.idlequeue.set_read_timeout(self.fd, READ_TIMEOUT) + else: + # stop transfer, there is no error code for this + self.pollend() + else: + self.queue.reconnect_receiver(self, self.streamhost) - def connect(self): - """ - Create the socket and plug it to the idlequeue - """ - if self.ais is None: - return None + def connect(self): + """ + Create the socket and plug it to the idlequeue + """ + if self.ais is None: + return None - for ai in self.ais: - try: - self._sock = socket.socket(*ai[:3]) - # this will not block the GUI - self._sock.setblocking(False) - self._server = ai[4] - break - except socket.error, e: - if not isinstance(e, basestring) and e[0] == EINPROGRESS: - break - # for all other errors, we try other addresses - continue - self.fd = self._sock.fileno() - self.state = 0 # about to be connected - self.idlequeue.plug_idle(self, True, False) - self.do_connect() - self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - return None + for ai in self.ais: + try: + self._sock = socket.socket(*ai[:3]) + # this will not block the GUI + self._sock.setblocking(False) + self._server = ai[4] + break + except socket.error, e: + if not isinstance(e, basestring) and e[0] == EINPROGRESS: + break + # for all other errors, we try other addresses + continue + self.fd = self._sock.fileno() + self.state = 0 # about to be connected + self.idlequeue.plug_idle(self, True, False) + self.do_connect() + self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + return None - def _is_connected(self): - if self.state < 5: - return False - return True + def _is_connected(self): + if self.state < 5: + return False + return True - def pollout(self): - self.idlequeue.remove_timeout(self.fd) - if self.state == 0: - self.do_connect() - return - elif self.state == 1: # send initially: version and auth types - self.send_raw(self._get_auth_buff()) - elif self.state == 3: # send 'connect' request - self.send_raw(self._get_request_buff(self._get_sha1_auth())) - elif self.file_props['type'] != 'r': - if self.file_props['paused']: - self.idlequeue.plug_idle(self, False, False) - return - result = self.write_next() - self.queue.process_result(result, self) - return - self.state += 1 - # unplug and plug for reading - self.idlequeue.plug_idle(self, False, True) - self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + def pollout(self): + self.idlequeue.remove_timeout(self.fd) + if self.state == 0: + self.do_connect() + return + elif self.state == 1: # send initially: version and auth types + self.send_raw(self._get_auth_buff()) + elif self.state == 3: # send 'connect' request + self.send_raw(self._get_request_buff(self._get_sha1_auth())) + elif self.file_props['type'] != 'r': + if self.file_props['paused']: + self.idlequeue.plug_idle(self, False, False) + return + result = self.write_next() + self.queue.process_result(result, self) + return + self.state += 1 + # unplug and plug for reading + self.idlequeue.plug_idle(self, False, True) + self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - def pollend(self): - if self.state >= 5: - # error during transfer - self.disconnect() - self.file_props['error'] = -1 - self.queue.process_result(-1, self) - else: - self.queue.reconnect_receiver(self, self.streamhost) + def pollend(self): + if self.state >= 5: + # error during transfer + self.disconnect() + self.file_props['error'] = -1 + self.queue.process_result(-1, self) + else: + self.queue.reconnect_receiver(self, self.streamhost) - def pollin(self): - self.idlequeue.remove_timeout(self.fd) - if self.connected: - if self.file_props['paused']: - self.idlequeue.plug_idle(self, False, False) - return - if self.state < 5: - self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) - result = self.main(0) - self.queue.process_result(result, self) - elif self.state == 5: # wait for proxy reply - pass - elif self.file_props['type'] == 'r': - self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) - result = self.get_file_contents(0) - self.queue.process_result(result, self) - else: - self.disconnect() + def pollin(self): + self.idlequeue.remove_timeout(self.fd) + if self.connected: + if self.file_props['paused']: + self.idlequeue.plug_idle(self, False, False) + return + if self.state < 5: + self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT) + result = self.main(0) + self.queue.process_result(result, self) + elif self.state == 5: # wait for proxy reply + pass + elif self.file_props['type'] == 'r': + self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) + result = self.get_file_contents(0) + self.queue.process_result(result, self) + else: + self.disconnect() - def do_connect(self): - try: - self._sock.connect(self._server) - self._sock.setblocking(False) - self._send=self._sock.send - self._recv=self._sock.recv - except Exception, ee: - errnum = ee[0] - self.connect_timeout += 1 - if errnum == 111 or self.connect_timeout > 1000: - self.queue._connection_refused(self.streamhost, - self.file_props, self.queue_idx) - return None - # win32 needs this - elif errnum not in (10056, EISCONN) or self.state != 0: - return None - else: # socket is already connected - self._sock.setblocking(False) - self._send=self._sock.send - self._recv=self._sock.recv - self.buff = '' - self.connected = True - self.file_props['connected'] = True - self.file_props['disconnect_cb'] = self.disconnect - self.state = 1 # connected + def do_connect(self): + try: + self._sock.connect(self._server) + self._sock.setblocking(False) + self._send=self._sock.send + self._recv=self._sock.recv + except Exception, ee: + errnum = ee[0] + self.connect_timeout += 1 + if errnum == 111 or self.connect_timeout > 1000: + self.queue._connection_refused(self.streamhost, + self.file_props, self.queue_idx) + return None + # win32 needs this + elif errnum not in (10056, EISCONN) or self.state != 0: + return None + else: # socket is already connected + self._sock.setblocking(False) + self._send=self._sock.send + self._recv=self._sock.recv + self.buff = '' + self.connected = True + self.file_props['connected'] = True + self.file_props['disconnect_cb'] = self.disconnect + self.state = 1 # connected - # stop all others connections to sender's streamhosts - self.queue._socket_connected(self.streamhost, self.file_props) - self.idlequeue.plug_idle(self, True, False) - return 1 # we are connected + # stop all others connections to sender's streamhosts + self.queue._socket_connected(self.streamhost, self.file_props) + self.idlequeue.plug_idle(self, True, False) + return 1 # we are connected - def main(self, timeout=0): - """ - Begin negotiation. on success 'address' != 0 - """ - result = 1 - buff = self.receive() - if buff == '': - # end connection - self.pollend() - return + def main(self, timeout=0): + """ + Begin negotiation. on success 'address' != 0 + """ + result = 1 + buff = self.receive() + if buff == '': + # end connection + self.pollend() + return - if self.state == 2: # read auth response - if buff is None or len(buff) != 2: - return None - version, method = struct.unpack('!BB', buff[:2]) - if version != 0x05 or method == 0xff: - self.disconnect() - elif self.state == 4: # get approve of our request - if buff is None: - return None - sub_buff = buff[:4] - if len(sub_buff) < 4: - return None - version, address_type = struct.unpack('!BxxB', buff[:4]) - addrlen = 0 - if address_type == 0x03: - addrlen = ord(buff[4]) - address = struct.unpack('!%ds' % addrlen, buff[5:addrlen + 5]) - portlen = len(buff[addrlen + 5:]) - if portlen == 1: - port, = struct.unpack('!B', buff[addrlen + 5]) - elif portlen == 2: - port, = struct.unpack('!H', buff[addrlen + 5:]) - else: # Gaim bug :) - port, = struct.unpack('!H', buff[addrlen + 5:addrlen + 7]) - self.remaining_buff = buff[addrlen + 7:] - self.state = 5 # for senders: init file_props and send '\n' - if self.queue.on_success: - result = self.queue.send_success_reply(self.file_props, - self.streamhost) - if result == 0: - self.state = 8 - self.disconnect() + if self.state == 2: # read auth response + if buff is None or len(buff) != 2: + return None + version, method = struct.unpack('!BB', buff[:2]) + if version != 0x05 or method == 0xff: + self.disconnect() + elif self.state == 4: # get approve of our request + if buff is None: + return None + sub_buff = buff[:4] + if len(sub_buff) < 4: + return None + version, address_type = struct.unpack('!BxxB', buff[:4]) + addrlen = 0 + if address_type == 0x03: + addrlen = ord(buff[4]) + address = struct.unpack('!%ds' % addrlen, buff[5:addrlen + 5]) + portlen = len(buff[addrlen + 5:]) + if portlen == 1: + port, = struct.unpack('!B', buff[addrlen + 5]) + elif portlen == 2: + port, = struct.unpack('!H', buff[addrlen + 5:]) + else: # Gaim bug :) + port, = struct.unpack('!H', buff[addrlen + 5:addrlen + 7]) + self.remaining_buff = buff[addrlen + 7:] + self.state = 5 # for senders: init file_props and send '\n' + if self.queue.on_success: + result = self.queue.send_success_reply(self.file_props, + self.streamhost) + if result == 0: + self.state = 8 + self.disconnect() - # for senders: init file_props - if result == 1 and self.state == 5: - if self.file_props['type'] == 's': - self.file_props['error'] = 0 - self.file_props['disconnect_cb'] = self.disconnect - self.file_props['started'] = True - self.file_props['completed'] = False - self.file_props['paused'] = False - self.file_props['stalled'] = False - self.file_props['elapsed-time'] = 0 - self.file_props['last-time'] = self.idlequeue.current_time() - self.file_props['received-len'] = 0 - self.pauses = 0 - # start sending file contents to socket - self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) - self.idlequeue.plug_idle(self, True, False) - else: - # receiving file contents from socket - self.idlequeue.plug_idle(self, False, True) - self.file_props['continue_cb'] = self.continue_paused_transfer - # we have set up the connection, next - retrieve file - self.state = 6 - if self.state < 5: - self.idlequeue.plug_idle(self, True, False) - self.state += 1 - return None + # for senders: init file_props + if result == 1 and self.state == 5: + if self.file_props['type'] == 's': + self.file_props['error'] = 0 + self.file_props['disconnect_cb'] = self.disconnect + self.file_props['started'] = True + self.file_props['completed'] = False + self.file_props['paused'] = False + self.file_props['stalled'] = False + self.file_props['elapsed-time'] = 0 + self.file_props['last-time'] = self.idlequeue.current_time() + self.file_props['received-len'] = 0 + self.pauses = 0 + # start sending file contents to socket + self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT) + self.idlequeue.plug_idle(self, True, False) + else: + # receiving file contents from socket + self.idlequeue.plug_idle(self, False, True) + self.file_props['continue_cb'] = self.continue_paused_transfer + # we have set up the connection, next - retrieve file + self.state = 6 + if self.state < 5: + self.idlequeue.plug_idle(self, True, False) + self.state += 1 + return None - def disconnect(self, cb=True): - """ - Close the socket. Remove self from queue if cb is True - """ - # close connection - Socks5.disconnect(self) - if cb is True: - self.file_props['disconnect_cb'] = None - if self.queue is not None: - self.queue.remove_receiver(self.queue_idx, False) - -# vim: se ts=3: + def disconnect(self, cb=True): + """ + Close the socket. Remove self from queue if cb is True + """ + # close connection + Socks5.disconnect(self) + if cb is True: + self.file_props['disconnect_cb'] = None + if self.queue is not None: + self.queue.remove_receiver(self.queue_idx, False) diff --git a/src/common/stanza_session.py b/src/common/stanza_session.py index a85155efd..129f60ea3 100644 --- a/src/common/stanza_session.py +++ b/src/common/stanza_session.py @@ -38,1030 +38,1028 @@ from hmac import HMAC from common import crypto if gajim.HAVE_PYCRYPTO: - from Crypto.Cipher import AES - from Crypto.PublicKey import RSA + from Crypto.Cipher import AES + from Crypto.PublicKey import RSA - from common import dh - import secrets + from common import dh + import secrets XmlDsig = 'http://www.w3.org/2000/09/xmldsig#' class StanzaSession(object): - ''' - ''' - def __init__(self, conn, jid, thread_id, type_): - ''' - ''' - self.conn = conn - self.jid = jid - self.type = type_ - self.resource = None + ''' + ''' + def __init__(self, conn, jid, thread_id, type_): + ''' + ''' + self.conn = conn + self.jid = jid + self.type = type_ + self.resource = None - if thread_id: - self.received_thread_id = True - self.thread_id = thread_id - else: - self.received_thread_id = False - if type_ == 'normal': - self.thread_id = None - else: - self.thread_id = self.generate_thread_id() + if thread_id: + self.received_thread_id = True + self.thread_id = thread_id + else: + self.received_thread_id = False + if type_ == 'normal': + self.thread_id = None + else: + self.thread_id = self.generate_thread_id() - self.loggable = True + self.loggable = True - self.last_send = 0 - self.last_receive = 0 - self.status = None - self.negotiated = {} + self.last_send = 0 + self.last_receive = 0 + self.status = None + self.negotiated = {} - def is_loggable(self): - return self.loggable and gajim.config.should_log(self.conn.name, self.jid) + def is_loggable(self): + return self.loggable and gajim.config.should_log(self.conn.name, self.jid) - def get_to(self): - to = str(self.jid) - if self.resource and not to.endswith(self.resource): - to += '/' + self.resource - return to + def get_to(self): + to = str(self.jid) + if self.resource and not to.endswith(self.resource): + to += '/' + self.resource + return to - def remove_events(self, types): - """ - Remove events associated with this session from the queue + def remove_events(self, types): + """ + Remove events associated with this session from the queue - Returns True if any events were removed (unlike events.py remove_events) - """ - any_removed = False + Returns True if any events were removed (unlike events.py remove_events) + """ + any_removed = False - for j in (self.jid, self.jid.getStripped()): - for event in gajim.events.get_events(self.conn.name, j, types=types): - # the event wasn't in this session - if (event.type_ == 'chat' and event.parameters[8] != self) or \ - (event.type_ == 'printed_chat' and event.parameters[0].session != \ - self): - continue + for j in (self.jid, self.jid.getStripped()): + for event in gajim.events.get_events(self.conn.name, j, types=types): + # the event wasn't in this session + if (event.type_ == 'chat' and event.parameters[8] != self) or \ + (event.type_ == 'printed_chat' and event.parameters[0].session != \ + self): + continue - # events.remove_events returns True when there were no events - # for some reason - r = gajim.events.remove_events(self.conn.name, j, event) + # events.remove_events returns True when there were no events + # for some reason + r = gajim.events.remove_events(self.conn.name, j, event) - if not r: - any_removed = True + if not r: + any_removed = True - return any_removed + return any_removed - def generate_thread_id(self): - return ''.join([f(string.ascii_letters) for f in itertools.repeat( - random.choice, 32)]) + def generate_thread_id(self): + return ''.join([f(string.ascii_letters) for f in itertools.repeat( + random.choice, 32)]) - def send(self, msg): - if self.thread_id: - msg.NT.thread = self.thread_id + def send(self, msg): + if self.thread_id: + msg.NT.thread = self.thread_id - msg.setAttr('to', self.get_to()) - self.conn.send_stanza(msg) + msg.setAttr('to', self.get_to()) + self.conn.send_stanza(msg) - if isinstance(msg, xmpp.Message): - self.last_send = time.time() + if isinstance(msg, xmpp.Message): + self.last_send = time.time() - def reject_negotiation(self, body=None): - msg = xmpp.Message() - feature = msg.NT.feature - feature.setNamespace(xmpp.NS_FEATURE) + def reject_negotiation(self, body=None): + msg = xmpp.Message() + feature = msg.NT.feature + feature.setNamespace(xmpp.NS_FEATURE) - x = xmpp.DataForm(typ='submit') - x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn')) - x.addChild(node=xmpp.DataField(name='accept', value='0')) + x = xmpp.DataForm(typ='submit') + x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn')) + x.addChild(node=xmpp.DataField(name='accept', value='0')) - feature.addChild(node=x) + feature.addChild(node=x) - if body: - msg.setBody(body) + if body: + msg.setBody(body) - self.send(msg) + self.send(msg) - self.cancelled_negotiation() + self.cancelled_negotiation() - def cancelled_negotiation(self): - """ - A negotiation has been cancelled, so reset this session to its default - state - """ - if self.control: - self.control.on_cancel_session_negotiation() + def cancelled_negotiation(self): + """ + A negotiation has been cancelled, so reset this session to its default + state + """ + if self.control: + self.control.on_cancel_session_negotiation() - self.status = None - self.negotiated = {} + self.status = None + self.negotiated = {} - def terminate(self, send_termination = True): - # only send termination message if we've sent a message and think they - # have XEP-0201 support - if send_termination and self.last_send > 0 and \ - (self.received_thread_id or self.last_receive == 0): - msg = xmpp.Message() - feature = msg.NT.feature - feature.setNamespace(xmpp.NS_FEATURE) + def terminate(self, send_termination = True): + # only send termination message if we've sent a message and think they + # have XEP-0201 support + if send_termination and self.last_send > 0 and \ + (self.received_thread_id or self.last_receive == 0): + msg = xmpp.Message() + feature = msg.NT.feature + feature.setNamespace(xmpp.NS_FEATURE) - x = xmpp.DataForm(typ='submit') - x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn')) - x.addChild(node=xmpp.DataField(name='terminate', value='1')) + x = xmpp.DataForm(typ='submit') + x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn')) + x.addChild(node=xmpp.DataField(name='terminate', value='1')) - feature.addChild(node=x) + feature.addChild(node=x) - self.send(msg) + self.send(msg) - self.status = None + self.status = None - def acknowledge_termination(self): - # we could send an acknowledgement message to the remote client here - self.status = None + def acknowledge_termination(self): + # we could send an acknowledgement message to the remote client here + self.status = None class EncryptedStanzaSession(StanzaSession): - """ - An encrypted stanza negotiation has several states. They arerepresented as - the following values in the 'status' attribute of the session object: - - 1. None: - default state - 2. 'requested-e2e': - this client has initiated an esession negotiation and is waiting - for a response - 3. 'responded-e2e': - this client has responded to an esession negotiation request and - is waiting for the initiator to identify itself and complete the - negotiation - 4. 'identified-alice': - this client identified itself and is waiting for the responder to - identify itself and complete the negotiation - 5. 'active': - an encrypted session has been successfully negotiated. messages - of any of the types listed in 'encryptable_stanzas' should be - encrypted before they're sent. - - The transition between these states is handled in gajim.py's - handle_session_negotiation method. - """ - - def __init__(self, conn, jid, thread_id, type_='chat'): - StanzaSession.__init__(self, conn, jid, thread_id, type_='chat') - - self.xes = {} - self.es = {} - self.n = 128 - self.enable_encryption = False - - # _s denotes 'self' (ie. this client) - self._kc_s = None - # _o denotes 'other' (ie. the client at the other end of the session) - self._kc_o = None - - # has the remote contact's identity ever been verified? - self.verified_identity = False - - def _get_contact(self): - c = gajim.contacts.get_contact(self.conn.name, self.jid, self.resource) - if not c: - c = gajim.contacts.get_contact(self.conn.name, self.jid) - return c - - def _is_buggy_gajim(self): - c = self._get_contact() - if c and c.supports(xmpp.NS_ROSTERX): - return False - return True - - def set_kc_s(self, value): - """ - Keep the encrypter updated with my latest cipher key - """ - self._kc_s = value - self.encrypter = self.cipher.new(self._kc_s, self.cipher.MODE_CTR, - counter=self.encryptcounter) - - def get_kc_s(self): - return self._kc_s - - def set_kc_o(self, value): - """ - Keep the decrypter updated with the other party's latest cipher key - """ - self._kc_o = value - self.decrypter = self.cipher.new(self._kc_o, self.cipher.MODE_CTR, - counter=self.decryptcounter) - - def get_kc_o(self): - return self._kc_o - - kc_s = property(get_kc_s, set_kc_s) - kc_o = property(get_kc_o, set_kc_o) - - def encryptcounter(self): - self.c_s = (self.c_s + 1) % (2 ** self.n) - return crypto.encode_mpi_with_padding(self.c_s) - - def decryptcounter(self): - self.c_o = (self.c_o + 1) % (2 ** self.n) - return crypto.encode_mpi_with_padding(self.c_o) - - def sign(self, string): - if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'): - hash_ = crypto.sha256(string) - return crypto.encode_mpi(gajim.pubkey.sign(hash_, '')[0]) - - def encrypt_stanza(self, stanza): - encryptable = [x for x in stanza.getChildren() if x.getName() not in - ('error', 'amp', 'thread')] - - # FIXME can also encrypt contents of elements in stanzas @type = - # 'error' - # (except for child elements) - - old_en_counter = self.c_s - - for element in encryptable: - stanza.delChild(element) - - plaintext = ''.join(map(str, encryptable)) - - m_compressed = self.compress(plaintext) - m_final = self.encrypt(m_compressed) - - c = stanza.NT.c - c.setNamespace('http://www.xmpp.org/extensions/xep-0200.html#ns') - c.NT.data = base64.b64encode(m_final) - - # FIXME check for rekey request, handle elements - - m_content = ''.join(map(str, c.getChildren())) - c.NT.mac = base64.b64encode(self.hmac(self.km_s, m_content + \ - crypto.encode_mpi(old_en_counter))) + """ + An encrypted stanza negotiation has several states. They arerepresented as + the following values in the 'status' attribute of the session object: + + 1. None: + default state + 2. 'requested-e2e': + this client has initiated an esession negotiation and is waiting + for a response + 3. 'responded-e2e': + this client has responded to an esession negotiation request and + is waiting for the initiator to identify itself and complete the + negotiation + 4. 'identified-alice': + this client identified itself and is waiting for the responder to + identify itself and complete the negotiation + 5. 'active': + an encrypted session has been successfully negotiated. messages + of any of the types listed in 'encryptable_stanzas' should be + encrypted before they're sent. + + The transition between these states is handled in gajim.py's + handle_session_negotiation method. + """ + + def __init__(self, conn, jid, thread_id, type_='chat'): + StanzaSession.__init__(self, conn, jid, thread_id, type_='chat') + + self.xes = {} + self.es = {} + self.n = 128 + self.enable_encryption = False + + # _s denotes 'self' (ie. this client) + self._kc_s = None + # _o denotes 'other' (ie. the client at the other end of the session) + self._kc_o = None + + # has the remote contact's identity ever been verified? + self.verified_identity = False + + def _get_contact(self): + c = gajim.contacts.get_contact(self.conn.name, self.jid, self.resource) + if not c: + c = gajim.contacts.get_contact(self.conn.name, self.jid) + return c + + def _is_buggy_gajim(self): + c = self._get_contact() + if c and c.supports(xmpp.NS_ROSTERX): + return False + return True + + def set_kc_s(self, value): + """ + Keep the encrypter updated with my latest cipher key + """ + self._kc_s = value + self.encrypter = self.cipher.new(self._kc_s, self.cipher.MODE_CTR, + counter=self.encryptcounter) + + def get_kc_s(self): + return self._kc_s + + def set_kc_o(self, value): + """ + Keep the decrypter updated with the other party's latest cipher key + """ + self._kc_o = value + self.decrypter = self.cipher.new(self._kc_o, self.cipher.MODE_CTR, + counter=self.decryptcounter) + + def get_kc_o(self): + return self._kc_o + + kc_s = property(get_kc_s, set_kc_s) + kc_o = property(get_kc_o, set_kc_o) + + def encryptcounter(self): + self.c_s = (self.c_s + 1) % (2 ** self.n) + return crypto.encode_mpi_with_padding(self.c_s) + + def decryptcounter(self): + self.c_o = (self.c_o + 1) % (2 ** self.n) + return crypto.encode_mpi_with_padding(self.c_o) + + def sign(self, string): + if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'): + hash_ = crypto.sha256(string) + return crypto.encode_mpi(gajim.pubkey.sign(hash_, '')[0]) + + def encrypt_stanza(self, stanza): + encryptable = [x for x in stanza.getChildren() if x.getName() not in + ('error', 'amp', 'thread')] + + # FIXME can also encrypt contents of elements in stanzas @type = + # 'error' + # (except for child elements) + + old_en_counter = self.c_s + + for element in encryptable: + stanza.delChild(element) + + plaintext = ''.join(map(str, encryptable)) + + m_compressed = self.compress(plaintext) + m_final = self.encrypt(m_compressed) + + c = stanza.NT.c + c.setNamespace('http://www.xmpp.org/extensions/xep-0200.html#ns') + c.NT.data = base64.b64encode(m_final) + + # FIXME check for rekey request, handle elements + + m_content = ''.join(map(str, c.getChildren())) + c.NT.mac = base64.b64encode(self.hmac(self.km_s, m_content + \ + crypto.encode_mpi(old_en_counter))) - msgtxt = '[This is part of an encrypted session. ' \ - 'If you see this message, something went wrong.]' - lang = os.getenv('LANG') - if lang is not None and lang != 'en': # we're not english - msgtxt = _('[This is part of an encrypted session. ' - 'If you see this message, something went wrong.]') + ' (' + \ - msgtxt + ')' - stanza.setBody(msgtxt) + msgtxt = '[This is part of an encrypted session. ' \ + 'If you see this message, something went wrong.]' + lang = os.getenv('LANG') + if lang is not None and lang != 'en': # we're not english + msgtxt = _('[This is part of an encrypted session. ' + 'If you see this message, something went wrong.]') + ' (' + \ + msgtxt + ')' + stanza.setBody(msgtxt) - return stanza + return stanza - def is_xep_200_encrypted(self, msg): - msg.getTag('c', namespace=xmpp.NS_STANZA_CRYPTO) + def is_xep_200_encrypted(self, msg): + msg.getTag('c', namespace=xmpp.NS_STANZA_CRYPTO) - def hmac(self, key, content): - return HMAC(key, content, self.hash_alg).digest() + def hmac(self, key, content): + return HMAC(key, content, self.hash_alg).digest() - def generate_initiator_keys(self, k): - return (self.hmac(k, 'Initiator Cipher Key'), - self.hmac(k, 'Initiator MAC Key'), - self.hmac(k, 'Initiator SIGMA Key')) - - def generate_responder_keys(self, k): - return (self.hmac(k, 'Responder Cipher Key'), - self.hmac(k, 'Responder MAC Key'), - self.hmac(k, 'Responder SIGMA Key')) + def generate_initiator_keys(self, k): + return (self.hmac(k, 'Initiator Cipher Key'), + self.hmac(k, 'Initiator MAC Key'), + self.hmac(k, 'Initiator SIGMA Key')) + + def generate_responder_keys(self, k): + return (self.hmac(k, 'Responder Cipher Key'), + self.hmac(k, 'Responder MAC Key'), + self.hmac(k, 'Responder SIGMA Key')) - def compress(self, plaintext): - if self.compression is None: - return plaintext + def compress(self, plaintext): + if self.compression is None: + return plaintext - def decompress(self, compressed): - if self.compression is None: - return compressed + def decompress(self, compressed): + if self.compression is None: + return compressed - def encrypt(self, encryptable): - padded = crypto.pad_to_multiple(encryptable, 16, ' ', False) + def encrypt(self, encryptable): + padded = crypto.pad_to_multiple(encryptable, 16, ' ', False) - return self.encrypter.encrypt(padded) + return self.encrypter.encrypt(padded) - def decrypt_stanza(self, stanza): - """ - Delete the unencrypted explanation body, if it exists - """ - orig_body = stanza.getTag('body') - if orig_body: - stanza.delChild(orig_body) + def decrypt_stanza(self, stanza): + """ + Delete the unencrypted explanation body, if it exists + """ + orig_body = stanza.getTag('body') + if orig_body: + stanza.delChild(orig_body) - c = stanza.getTag(name='c', - namespace='http://www.xmpp.org/extensions/xep-0200.html#ns') + c = stanza.getTag(name='c', + namespace='http://www.xmpp.org/extensions/xep-0200.html#ns') - stanza.delChild(c) + stanza.delChild(c) - # contents of , minus , minus whitespace - macable = ''.join(str(x) for x in c.getChildren() if x.getName() != 'mac') + # contents of , minus , minus whitespace + macable = ''.join(str(x) for x in c.getChildren() if x.getName() != 'mac') - received_mac = base64.b64decode(c.getTagData('mac')) - calculated_mac = self.hmac(self.km_o, macable + \ - crypto.encode_mpi_with_padding(self.c_o)) + received_mac = base64.b64decode(c.getTagData('mac')) + calculated_mac = self.hmac(self.km_o, macable + \ + crypto.encode_mpi_with_padding(self.c_o)) - if not calculated_mac == received_mac: - raise DecryptionError('bad signature') + if not calculated_mac == received_mac: + raise DecryptionError('bad signature') - m_final = base64.b64decode(c.getTagData('data')) - m_compressed = self.decrypt(m_final) - plaintext = self.decompress(m_compressed) + m_final = base64.b64decode(c.getTagData('data')) + m_compressed = self.decrypt(m_final) + plaintext = self.decompress(m_compressed) - try: - parsed = xmpp.Node(node='' + plaintext + '') - except Exception: - raise DecryptionError('decrypted not parseable as XML') + try: + parsed = xmpp.Node(node='' + plaintext + '') + except Exception: + raise DecryptionError('decrypted not parseable as XML') - for child in parsed.getChildren(): - stanza.addChild(node=child) + for child in parsed.getChildren(): + stanza.addChild(node=child) - return stanza + return stanza - def decrypt(self, ciphertext): - return self.decrypter.decrypt(ciphertext) + def decrypt(self, ciphertext): + return self.decrypter.decrypt(ciphertext) - def logging_preference(self): - if gajim.config.get_per('accounts', self.conn.name, - 'log_encrypted_sessions'): - return ['may', 'mustnot'] - else: - return ['mustnot', 'may'] + def logging_preference(self): + if gajim.config.get_per('accounts', self.conn.name, + 'log_encrypted_sessions'): + return ['may', 'mustnot'] + else: + return ['mustnot', 'may'] - def get_shared_secret(self, e, y, p): - if (not 1 < e < (p - 1)): - raise NegotiationError('invalid DH value') + def get_shared_secret(self, e, y, p): + if (not 1 < e < (p - 1)): + raise NegotiationError('invalid DH value') - return crypto.sha256(crypto.encode_mpi(crypto.powmod(e, y, p))) + return crypto.sha256(crypto.encode_mpi(crypto.powmod(e, y, p))) - def c7lize_mac_id(self, form): - kids = form.getChildren() - macable = [x for x in kids if x.getVar() not in ('mac', 'identity')] - return ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el in \ - macable) + def c7lize_mac_id(self, form): + kids = form.getChildren() + macable = [x for x in kids if x.getVar() not in ('mac', 'identity')] + return ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el in \ + macable) - def verify_identity(self, form, dh_i, sigmai, i_o): - m_o = base64.b64decode(form['mac']) - id_o = base64.b64decode(form['identity']) + def verify_identity(self, form, dh_i, sigmai, i_o): + m_o = base64.b64decode(form['mac']) + id_o = base64.b64decode(form['identity']) - m_o_calculated = self.hmac(self.km_o, crypto.encode_mpi(self.c_o) + id_o) + m_o_calculated = self.hmac(self.km_o, crypto.encode_mpi(self.c_o) + id_o) - if m_o_calculated != m_o: - raise NegotiationError('calculated m_%s differs from received m_%s' % - (i_o, i_o)) + if m_o_calculated != m_o: + raise NegotiationError('calculated m_%s differs from received m_%s' % + (i_o, i_o)) - if i_o == 'a' and self.sas_algs == 'sas28x5': - # we don't need to calculate this if there's a verified retained secret - # (but we do anyways) - self.sas = crypto.sas_28x5(m_o, self.form_s) + if i_o == 'a' and self.sas_algs == 'sas28x5': + # we don't need to calculate this if there's a verified retained secret + # (but we do anyways) + self.sas = crypto.sas_28x5(m_o, self.form_s) - if self.negotiated['recv_pubkey']: - plaintext = self.decrypt(id_o) - parsed = xmpp.Node(node='' + plaintext + '') + if self.negotiated['recv_pubkey']: + plaintext = self.decrypt(id_o) + parsed = xmpp.Node(node='' + plaintext + '') - if self.negotiated['recv_pubkey'] == 'hash': - # fingerprint = parsed.getTagData('fingerprint') - # FIXME find stored pubkey or terminate session - raise NotImplementedError() - else: - if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'): - keyvalue = parsed.getTag(name='RSAKeyValue', namespace=XmlDsig) + if self.negotiated['recv_pubkey'] == 'hash': + # fingerprint = parsed.getTagData('fingerprint') + # FIXME find stored pubkey or terminate session + raise NotImplementedError() + else: + if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'): + keyvalue = parsed.getTag(name='RSAKeyValue', namespace=XmlDsig) - n, e = (crypto.decode_mpi(base64.b64decode( - keyvalue.getTagData(x))) for x in ('Modulus', 'Exponent')) - eir_pubkey = RSA.construct((n,long(e))) + n, e = (crypto.decode_mpi(base64.b64decode( + keyvalue.getTagData(x))) for x in ('Modulus', 'Exponent')) + eir_pubkey = RSA.construct((n, long(e))) - pubkey_o = xmpp.c14n.c14n(keyvalue, self._is_buggy_gajim()) - else: - # FIXME DSA, etc. - raise NotImplementedError() + pubkey_o = xmpp.c14n.c14n(keyvalue, self._is_buggy_gajim()) + else: + # FIXME DSA, etc. + raise NotImplementedError() - enc_sig = parsed.getTag(name='SignatureValue', - namespace=XmlDsig).getData() - signature = (crypto.decode_mpi(base64.b64decode(enc_sig)), ) - else: - mac_o = self.decrypt(id_o) - pubkey_o = '' + enc_sig = parsed.getTag(name='SignatureValue', + namespace=XmlDsig).getData() + signature = (crypto.decode_mpi(base64.b64decode(enc_sig)), ) + else: + mac_o = self.decrypt(id_o) + pubkey_o = '' - c7l_form = self.c7lize_mac_id(form) + c7l_form = self.c7lize_mac_id(form) - content = self.n_s + self.n_o + crypto.encode_mpi(dh_i) + pubkey_o + content = self.n_s + self.n_o + crypto.encode_mpi(dh_i) + pubkey_o - if sigmai: - self.form_o = c7l_form - content += self.form_o - else: - form_o2 = c7l_form - content += self.form_o + form_o2 + if sigmai: + self.form_o = c7l_form + content += self.form_o + else: + form_o2 = c7l_form + content += self.form_o + form_o2 - mac_o_calculated = self.hmac(self.ks_o, content) + mac_o_calculated = self.hmac(self.ks_o, content) - if self.negotiated['recv_pubkey']: - hash_ = crypto.sha256(mac_o_calculated) + if self.negotiated['recv_pubkey']: + hash_ = crypto.sha256(mac_o_calculated) - if not eir_pubkey.verify(hash_, signature): - raise NegotiationError('public key signature verification failed!') + if not eir_pubkey.verify(hash_, signature): + raise NegotiationError('public key signature verification failed!') - elif mac_o_calculated != mac_o: - raise NegotiationError('calculated mac_%s differs from received mac_%s' - % (i_o, i_o)) + elif mac_o_calculated != mac_o: + raise NegotiationError('calculated mac_%s differs from received mac_%s' + % (i_o, i_o)) - def make_identity(self, form, dh_i): - if self.negotiated['send_pubkey']: - if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'): - pubkey = secrets.secrets().my_pubkey(self.conn.name) - fields = (pubkey.n, pubkey.e) + def make_identity(self, form, dh_i): + if self.negotiated['send_pubkey']: + if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'): + pubkey = secrets.secrets().my_pubkey(self.conn.name) + fields = (pubkey.n, pubkey.e) - cb_fields = [base64.b64encode(crypto.encode_mpi(f)) for f in - fields] + cb_fields = [base64.b64encode(crypto.encode_mpi(f)) for f in + fields] - pubkey_s = '%s%s' % \ - tuple(cb_fields) - else: - pubkey_s = '' + pubkey_s = '%s%s' % \ + tuple(cb_fields) + else: + pubkey_s = '' - form_s2 = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el in \ - form.getChildren()) + form_s2 = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el in \ + form.getChildren()) - old_c_s = self.c_s - content = self.n_o + self.n_s + crypto.encode_mpi(dh_i) + pubkey_s + \ - self.form_s + form_s2 + old_c_s = self.c_s + content = self.n_o + self.n_s + crypto.encode_mpi(dh_i) + pubkey_s + \ + self.form_s + form_s2 - mac_s = self.hmac(self.ks_s, content) + mac_s = self.hmac(self.ks_s, content) - if self.negotiated['send_pubkey']: - signature = self.sign(mac_s) + if self.negotiated['send_pubkey']: + signature = self.sign(mac_s) - sign_s = '' - '%s' % base64.b64encode(signature) + sign_s = '' + '%s' % base64.b64encode(signature) - if self.negotiated['send_pubkey'] == 'hash': - b64ed = base64.b64encode(self.hash(pubkey_s)) - pubkey_s = '%s' % b64ed + if self.negotiated['send_pubkey'] == 'hash': + b64ed = base64.b64encode(self.hash(pubkey_s)) + pubkey_s = '%s' % b64ed - id_s = self.encrypt(pubkey_s + sign_s) - else: - id_s = self.encrypt(mac_s) + id_s = self.encrypt(pubkey_s + sign_s) + else: + id_s = self.encrypt(mac_s) - m_s = self.hmac(self.km_s, crypto.encode_mpi(old_c_s) + id_s) + m_s = self.hmac(self.km_s, crypto.encode_mpi(old_c_s) + id_s) - if self.status == 'requested-e2e' and self.sas_algs == 'sas28x5': - # we're alice; check for a retained secret - # if none exists, prompt the user with the SAS - self.sas = crypto.sas_28x5(m_s, self.form_o) + if self.status == 'requested-e2e' and self.sas_algs == 'sas28x5': + # we're alice; check for a retained secret + # if none exists, prompt the user with the SAS + self.sas = crypto.sas_28x5(m_s, self.form_o) - if self.sigmai: - # FIXME save retained secret? - self.check_identity(tuple) + if self.sigmai: + # FIXME save retained secret? + self.check_identity(tuple) - return (xmpp.DataField(name='identity', value=base64.b64encode(id_s)), - xmpp.DataField(name='mac', value=base64.b64encode(m_s))) + return (xmpp.DataField(name='identity', value=base64.b64encode(id_s)), + xmpp.DataField(name='mac', value=base64.b64encode(m_s))) - def negotiate_e2e(self, sigmai): - self.negotiated = {} + def negotiate_e2e(self, sigmai): + self.negotiated = {} - request = xmpp.Message() - feature = request.NT.feature - feature.setNamespace(xmpp.NS_FEATURE) + request = xmpp.Message() + feature = request.NT.feature + feature.setNamespace(xmpp.NS_FEATURE) - x = xmpp.DataForm(typ='form') + x = xmpp.DataForm(typ='form') - x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn', - typ='hidden')) - x.addChild(node=xmpp.DataField(name='accept', value='1', typ='boolean', - required=True)) + x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn', + typ='hidden')) + x.addChild(node=xmpp.DataField(name='accept', value='1', typ='boolean', + required=True)) - # this field is incorrectly called 'otr' in XEPs 0116 and 0217 - x.addChild(node=xmpp.DataField(name='logging', typ='list-single', - options=self.logging_preference(), required=True)) + # this field is incorrectly called 'otr' in XEPs 0116 and 0217 + x.addChild(node=xmpp.DataField(name='logging', typ='list-single', + options=self.logging_preference(), required=True)) - # unsupported options: 'disabled', 'enabled' - x.addChild(node=xmpp.DataField(name='disclosure', typ='list-single', - options=['never'], required=True)) - x.addChild(node=xmpp.DataField(name='security', typ='list-single', - options=['e2e'], required=True)) - x.addChild(node=xmpp.DataField(name='crypt_algs', value='aes128-ctr', - typ='hidden')) - x.addChild(node=xmpp.DataField(name='hash_algs', value='sha256', - typ='hidden')) - x.addChild(node=xmpp.DataField(name='compress', value='none', - typ='hidden')) - - # unsupported options: 'iq', 'presence' - x.addChild(node=xmpp.DataField(name='stanzas', typ='list-multi', - options=['message'])) - - x.addChild(node=xmpp.DataField(name='init_pubkey', options=['none', 'key', - 'hash'], typ='list-single')) - - # FIXME store key, use hash - x.addChild(node=xmpp.DataField(name='resp_pubkey', options=['none', - 'key'], typ='list-single')) - - x.addChild(node=xmpp.DataField(name='ver', value='1.0', typ='hidden')) - - x.addChild(node=xmpp.DataField(name='rekey_freq', value='4294967295', - typ='hidden')) - - x.addChild(node=xmpp.DataField(name='sas_algs', value='sas28x5', - typ='hidden')) - x.addChild(node=xmpp.DataField(name='sign_algs', - value='http://www.w3.org/2000/09/xmldsig#rsa-sha256', typ='hidden')) - - self.n_s = crypto.generate_nonce() - - x.addChild(node=xmpp.DataField(name='my_nonce', - value=base64.b64encode(self.n_s), typ='hidden')) - - modp_options = [ int(g) for g in gajim.config.get('esession_modp').split( - ',') ] - - x.addChild(node=xmpp.DataField(name='modp', typ='list-single', - options=[[None, y] for y in modp_options])) - - x.addChild(node=self.make_dhfield(modp_options, sigmai)) - self.sigmai = sigmai - - self.form_s = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \ - in x.getChildren()) - - feature.addChild(node=x) - - self.status = 'requested-e2e' - - self.send(request) - - def verify_options_bob(self, form): - """ - 4.3 esession response (bob) - """ - negotiated = {'recv_pubkey': None, 'send_pubkey': None} - not_acceptable = [] - ask_user = {} - - fixed = { 'disclosure': 'never', 'security': 'e2e', - 'crypt_algs': 'aes128-ctr', 'hash_algs': 'sha256', 'compress': 'none', - 'stanzas': 'message', 'init_pubkey': 'none', 'resp_pubkey': 'none', - 'ver': '1.0', 'sas_algs': 'sas28x5' } - - self.encryptable_stanzas = ['message'] - - self.sas_algs = 'sas28x5' - self.cipher = AES - self.hash_alg = sha256 - self.compression = None - - for name in form.asDict(): - field = form.getField(name) - options = [x[1] for x in field.getOptions()] - values = field.getValues() - - if not field.getType() in ('list-single', 'list-multi'): - options = values - - if name in fixed: - if fixed[name] in options: - negotiated[name] = fixed[name] - else: - not_acceptable.append(name) - elif name == 'rekey_freq': - preferred = int(options[0]) - negotiated['rekey_freq'] = preferred - self.rekey_freq = preferred - elif name == 'logging': - my_prefs = self.logging_preference() - - if my_prefs[0] in options: # our first choice is offered, select it - pref = my_prefs[0] - negotiated['logging'] = pref - else: # see if other acceptable choices are offered - for pref in my_prefs: - if pref in options: - ask_user['logging'] = pref - break - - if not 'logging' in ask_user: - not_acceptable.append(name) - elif name == 'init_pubkey': - for x in ('key'): - if x in options: - negotiated['recv_pubkey'] = x - break - elif name == 'resp_pubkey': - for x in ('hash', 'key'): - if x in options: - negotiated['send_pubkey'] = x - break - elif name == 'sign_algs': - if (XmlDsig + 'rsa-sha256') in options: - negotiated['sign_algs'] = XmlDsig + 'rsa-sha256' - else: - # FIXME some things are handled elsewhere, some things are - # not-implemented - pass - - return (negotiated, not_acceptable, ask_user) - - def respond_e2e_bob(self, form, negotiated, not_acceptable): - """ - 4.3 esession response (bob) - """ - response = xmpp.Message() - feature = response.NT.feature - feature.setNamespace(xmpp.NS_FEATURE) - - x = xmpp.DataForm(typ='submit') - - x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn')) - x.addChild(node=xmpp.DataField(name='accept', value='true')) - - for name in negotiated: - # some fields are internal and should not be sent - if not name in ('send_pubkey', 'recv_pubkey'): - x.addChild(node=xmpp.DataField(name=name, value=negotiated[name])) - - self.negotiated = negotiated - - # the offset of the group we chose (need it to match up with the dhhash) - group_order = 0 - self.modp = int(form.getField('modp').getOptions()[group_order][1]) - x.addChild(node=xmpp.DataField(name='modp', value=self.modp)) - - g = dh.generators[self.modp] - p = dh.primes[self.modp] - - self.n_o = base64.b64decode(form['my_nonce']) - - dhhashes = form.getField('dhhashes').getValues() - self.negotiated['He'] = base64.b64decode(dhhashes[group_order].encode( - 'utf8')) - - bytes = int(self.n / 8) - - self.n_s = crypto.generate_nonce() - - # n-bit random number - self.c_o = crypto.decode_mpi(crypto.random_bytes(bytes)) - self.c_s = self.c_o ^ (2 ** (self.n - 1)) - - self.y = crypto.srand(2 ** (2 * self.n - 1), p - 1) - self.d = crypto.powmod(g, self.y, p) - - to_add = {'my_nonce': self.n_s, - 'dhkeys': crypto.encode_mpi(self.d), - 'counter': crypto.encode_mpi(self.c_o), - 'nonce': self.n_o} - - for name in to_add: - b64ed = base64.b64encode(to_add[name]) - x.addChild(node=xmpp.DataField(name=name, value=b64ed)) - - self.form_o = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \ - in form.getChildren()) - self.form_s = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \ - in x.getChildren()) - - self.status = 'responded-e2e' - - feature.addChild(node=x) - - if not_acceptable: - response = xmpp.Error(response, xmpp.ERR_NOT_ACCEPTABLE) - - feature = xmpp.Node(xmpp.NS_FEATURE + ' feature') - - for f in not_acceptable: - n = xmpp.Node('field') - n['var'] = f - feature.addChild(node=n) - - response.T.error.addChild(node=feature) - - self.send(response) - - def verify_options_alice(self, form): - """ - 'Alice Accepts' - """ - negotiated = {} - ask_user = {} - not_acceptable = [] - - if not form['logging'] in self.logging_preference(): - not_acceptable.append(form['logging']) - elif form['logging'] != self.logging_preference()[0]: - ask_user['logging'] = form['logging'] - else: - negotiated['logging'] = self.logging_preference()[0] + # unsupported options: 'disabled', 'enabled' + x.addChild(node=xmpp.DataField(name='disclosure', typ='list-single', + options=['never'], required=True)) + x.addChild(node=xmpp.DataField(name='security', typ='list-single', + options=['e2e'], required=True)) + x.addChild(node=xmpp.DataField(name='crypt_algs', value='aes128-ctr', + typ='hidden')) + x.addChild(node=xmpp.DataField(name='hash_algs', value='sha256', + typ='hidden')) + x.addChild(node=xmpp.DataField(name='compress', value='none', + typ='hidden')) + + # unsupported options: 'iq', 'presence' + x.addChild(node=xmpp.DataField(name='stanzas', typ='list-multi', + options=['message'])) + + x.addChild(node=xmpp.DataField(name='init_pubkey', options=['none', 'key', + 'hash'], typ='list-single')) + + # FIXME store key, use hash + x.addChild(node=xmpp.DataField(name='resp_pubkey', options=['none', + 'key'], typ='list-single')) + + x.addChild(node=xmpp.DataField(name='ver', value='1.0', typ='hidden')) + + x.addChild(node=xmpp.DataField(name='rekey_freq', value='4294967295', + typ='hidden')) + + x.addChild(node=xmpp.DataField(name='sas_algs', value='sas28x5', + typ='hidden')) + x.addChild(node=xmpp.DataField(name='sign_algs', + value='http://www.w3.org/2000/09/xmldsig#rsa-sha256', typ='hidden')) + + self.n_s = crypto.generate_nonce() + + x.addChild(node=xmpp.DataField(name='my_nonce', + value=base64.b64encode(self.n_s), typ='hidden')) + + modp_options = [ int(g) for g in gajim.config.get('esession_modp').split( + ',') ] + + x.addChild(node=xmpp.DataField(name='modp', typ='list-single', + options=[[None, y] for y in modp_options])) + + x.addChild(node=self.make_dhfield(modp_options, sigmai)) + self.sigmai = sigmai + + self.form_s = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \ + in x.getChildren()) + + feature.addChild(node=x) + + self.status = 'requested-e2e' + + self.send(request) + + def verify_options_bob(self, form): + """ + 4.3 esession response (bob) + """ + negotiated = {'recv_pubkey': None, 'send_pubkey': None} + not_acceptable = [] + ask_user = {} + + fixed = { 'disclosure': 'never', 'security': 'e2e', + 'crypt_algs': 'aes128-ctr', 'hash_algs': 'sha256', 'compress': 'none', + 'stanzas': 'message', 'init_pubkey': 'none', 'resp_pubkey': 'none', + 'ver': '1.0', 'sas_algs': 'sas28x5' } + + self.encryptable_stanzas = ['message'] + + self.sas_algs = 'sas28x5' + self.cipher = AES + self.hash_alg = sha256 + self.compression = None + + for name in form.asDict(): + field = form.getField(name) + options = [x[1] for x in field.getOptions()] + values = field.getValues() + + if not field.getType() in ('list-single', 'list-multi'): + options = values + + if name in fixed: + if fixed[name] in options: + negotiated[name] = fixed[name] + else: + not_acceptable.append(name) + elif name == 'rekey_freq': + preferred = int(options[0]) + negotiated['rekey_freq'] = preferred + self.rekey_freq = preferred + elif name == 'logging': + my_prefs = self.logging_preference() + + if my_prefs[0] in options: # our first choice is offered, select it + pref = my_prefs[0] + negotiated['logging'] = pref + else: # see if other acceptable choices are offered + for pref in my_prefs: + if pref in options: + ask_user['logging'] = pref + break + + if not 'logging' in ask_user: + not_acceptable.append(name) + elif name == 'init_pubkey': + for x in ('key'): + if x in options: + negotiated['recv_pubkey'] = x + break + elif name == 'resp_pubkey': + for x in ('hash', 'key'): + if x in options: + negotiated['send_pubkey'] = x + break + elif name == 'sign_algs': + if (XmlDsig + 'rsa-sha256') in options: + negotiated['sign_algs'] = XmlDsig + 'rsa-sha256' + else: + # FIXME some things are handled elsewhere, some things are + # not-implemented + pass + + return (negotiated, not_acceptable, ask_user) + + def respond_e2e_bob(self, form, negotiated, not_acceptable): + """ + 4.3 esession response (bob) + """ + response = xmpp.Message() + feature = response.NT.feature + feature.setNamespace(xmpp.NS_FEATURE) + + x = xmpp.DataForm(typ='submit') + + x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn')) + x.addChild(node=xmpp.DataField(name='accept', value='true')) + + for name in negotiated: + # some fields are internal and should not be sent + if not name in ('send_pubkey', 'recv_pubkey'): + x.addChild(node=xmpp.DataField(name=name, value=negotiated[name])) + + self.negotiated = negotiated + + # the offset of the group we chose (need it to match up with the dhhash) + group_order = 0 + self.modp = int(form.getField('modp').getOptions()[group_order][1]) + x.addChild(node=xmpp.DataField(name='modp', value=self.modp)) + + g = dh.generators[self.modp] + p = dh.primes[self.modp] + + self.n_o = base64.b64decode(form['my_nonce']) + + dhhashes = form.getField('dhhashes').getValues() + self.negotiated['He'] = base64.b64decode(dhhashes[group_order].encode( + 'utf8')) + + bytes = int(self.n / 8) + + self.n_s = crypto.generate_nonce() + + # n-bit random number + self.c_o = crypto.decode_mpi(crypto.random_bytes(bytes)) + self.c_s = self.c_o ^ (2 ** (self.n - 1)) + + self.y = crypto.srand(2 ** (2 * self.n - 1), p - 1) + self.d = crypto.powmod(g, self.y, p) + + to_add = {'my_nonce': self.n_s, + 'dhkeys': crypto.encode_mpi(self.d), + 'counter': crypto.encode_mpi(self.c_o), + 'nonce': self.n_o} + + for name in to_add: + b64ed = base64.b64encode(to_add[name]) + x.addChild(node=xmpp.DataField(name=name, value=b64ed)) + + self.form_o = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \ + in form.getChildren()) + self.form_s = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \ + in x.getChildren()) + + self.status = 'responded-e2e' + + feature.addChild(node=x) + + if not_acceptable: + response = xmpp.Error(response, xmpp.ERR_NOT_ACCEPTABLE) + + feature = xmpp.Node(xmpp.NS_FEATURE + ' feature') + + for f in not_acceptable: + n = xmpp.Node('field') + n['var'] = f + feature.addChild(node=n) + + response.T.error.addChild(node=feature) + + self.send(response) + + def verify_options_alice(self, form): + """ + 'Alice Accepts' + """ + negotiated = {} + ask_user = {} + not_acceptable = [] + + if not form['logging'] in self.logging_preference(): + not_acceptable.append(form['logging']) + elif form['logging'] != self.logging_preference()[0]: + ask_user['logging'] = form['logging'] + else: + negotiated['logging'] = self.logging_preference()[0] - for r,a in (('recv_pubkey', 'resp_pubkey'), ('send_pubkey', - 'init_pubkey')): - negotiated[r] = None + for r, a in (('recv_pubkey', 'resp_pubkey'), ('send_pubkey', + 'init_pubkey')): + negotiated[r] = None - if a in form.asDict() and form[a] in ('key', 'hash'): - negotiated[r] = form[a] + if a in form.asDict() and form[a] in ('key', 'hash'): + negotiated[r] = form[a] - if 'sign_algs' in form.asDict(): - if form['sign_algs'] in (XmlDsig + 'rsa-sha256', ): - negotiated['sign_algs'] = form['sign_algs'] - else: - not_acceptable.append(form['sign_algs']) + if 'sign_algs' in form.asDict(): + if form['sign_algs'] in (XmlDsig + 'rsa-sha256', ): + negotiated['sign_algs'] = form['sign_algs'] + else: + not_acceptable.append(form['sign_algs']) - return (negotiated, not_acceptable, ask_user) + return (negotiated, not_acceptable, ask_user) - def accept_e2e_alice(self, form, negotiated): - """ - 'Alice Accepts', continued - """ - self.encryptable_stanzas = ['message'] - self.sas_algs = 'sas28x5' - self.cipher = AES - self.hash_alg = sha256 - self.compression = None + def accept_e2e_alice(self, form, negotiated): + """ + 'Alice Accepts', continued + """ + self.encryptable_stanzas = ['message'] + self.sas_algs = 'sas28x5' + self.cipher = AES + self.hash_alg = sha256 + self.compression = None - self.negotiated = negotiated + self.negotiated = negotiated - accept = xmpp.Message() - feature = accept.NT.feature - feature.setNamespace(xmpp.NS_FEATURE) + accept = xmpp.Message() + feature = accept.NT.feature + feature.setNamespace(xmpp.NS_FEATURE) - result = xmpp.DataForm(typ='result') + result = xmpp.DataForm(typ='result') - self.c_s = crypto.decode_mpi(base64.b64decode(form['counter'])) - self.c_o = self.c_s ^ (2 ** (self.n - 1)) - self.n_o = base64.b64decode(form['my_nonce']) + self.c_s = crypto.decode_mpi(base64.b64decode(form['counter'])) + self.c_o = self.c_s ^ (2 ** (self.n - 1)) + self.n_o = base64.b64decode(form['my_nonce']) - mod_p = int(form['modp']) - p = dh.primes[mod_p] - x = self.xes[mod_p] - e = self.es[mod_p] + mod_p = int(form['modp']) + p = dh.primes[mod_p] + x = self.xes[mod_p] + e = self.es[mod_p] - self.d = crypto.decode_mpi(base64.b64decode(form['dhkeys'])) - self.k = self.get_shared_secret(self.d, x, p) + self.d = crypto.decode_mpi(base64.b64decode(form['dhkeys'])) + self.k = self.get_shared_secret(self.d, x, p) - result.addChild(node=xmpp.DataField(name='FORM_TYPE', - value='urn:xmpp:ssn')) - result.addChild(node=xmpp.DataField(name='accept', value='1')) - result.addChild(node=xmpp.DataField(name='nonce', - value=base64.b64encode(self.n_o))) - - self.kc_s, self.km_s, self.ks_s = self.generate_initiator_keys(self.k) + result.addChild(node=xmpp.DataField(name='FORM_TYPE', + value='urn:xmpp:ssn')) + result.addChild(node=xmpp.DataField(name='accept', value='1')) + result.addChild(node=xmpp.DataField(name='nonce', + value=base64.b64encode(self.n_o))) + + self.kc_s, self.km_s, self.ks_s = self.generate_initiator_keys(self.k) - if self.sigmai: - self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(self.k) - self.verify_identity(form, self.d, True, 'b') - else: - srses = secrets.secrets().retained_secrets(self.conn.name, - self.jid.getStripped()) - rshashes = [self.hmac(self.n_s, rs[0]) for rs in srses] + if self.sigmai: + self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(self.k) + self.verify_identity(form, self.d, True, 'b') + else: + srses = secrets.secrets().retained_secrets(self.conn.name, + self.jid.getStripped()) + rshashes = [self.hmac(self.n_s, rs[0]) for rs in srses] - if not rshashes: - # we've never spoken before, but we'll pretend we have - rshash_size = self.hash_alg().digest_size - rshashes.append(crypto.random_bytes(rshash_size)) + if not rshashes: + # we've never spoken before, but we'll pretend we have + rshash_size = self.hash_alg().digest_size + rshashes.append(crypto.random_bytes(rshash_size)) - rshashes = [base64.b64encode(rshash) for rshash in rshashes] - result.addChild(node=xmpp.DataField(name='rshashes', value=rshashes)) - result.addChild(node=xmpp.DataField(name='dhkeys', - value=base64.b64encode(crypto.encode_mpi(e)))) + rshashes = [base64.b64encode(rshash) for rshash in rshashes] + result.addChild(node=xmpp.DataField(name='rshashes', value=rshashes)) + result.addChild(node=xmpp.DataField(name='dhkeys', + value=base64.b64encode(crypto.encode_mpi(e)))) - self.form_o = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for \ - el in form.getChildren()) + self.form_o = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for \ + el in form.getChildren()) - # MUST securely destroy K unless it will be used later to generate the - # final shared secret + # MUST securely destroy K unless it will be used later to generate the + # final shared secret - for datafield in self.make_identity(result, e): - result.addChild(node=datafield) + for datafield in self.make_identity(result, e): + result.addChild(node=datafield) - feature.addChild(node=result) - self.send(accept) + feature.addChild(node=result) + self.send(accept) - if self.sigmai: - self.status = 'active' - self.enable_encryption = True - else: - self.status = 'identified-alice' - - def accept_e2e_bob(self, form): - """ - 4.5 esession accept (bob) - """ - response = xmpp.Message() + if self.sigmai: + self.status = 'active' + self.enable_encryption = True + else: + self.status = 'identified-alice' + + def accept_e2e_bob(self, form): + """ + 4.5 esession accept (bob) + """ + response = xmpp.Message() - init = response.NT.init - init.setNamespace(xmpp.NS_ESESSION_INIT) + init = response.NT.init + init.setNamespace(xmpp.NS_ESESSION_INIT) - x = xmpp.DataForm(typ='result') + x = xmpp.DataForm(typ='result') - for field in ('nonce', 'dhkeys', 'rshashes', 'identity', 'mac'): - # FIXME: will do nothing in real world... - assert field in form.asDict(), "alice's form didn't have a %s field" \ - % field + for field in ('nonce', 'dhkeys', 'rshashes', 'identity', 'mac'): + # FIXME: will do nothing in real world... + assert field in form.asDict(), "alice's form didn't have a %s field" \ + % field - # 4.5.1 generating provisory session keys - e = crypto.decode_mpi(base64.b64decode(form['dhkeys'])) - p = dh.primes[self.modp] + # 4.5.1 generating provisory session keys + e = crypto.decode_mpi(base64.b64decode(form['dhkeys'])) + p = dh.primes[self.modp] - if crypto.sha256(crypto.encode_mpi(e)) != self.negotiated['He']: - raise NegotiationError('SHA256(e) != He') + if crypto.sha256(crypto.encode_mpi(e)) != self.negotiated['He']: + raise NegotiationError('SHA256(e) != He') - k = self.get_shared_secret(e, self.y, p) - self.kc_o, self.km_o, self.ks_o = self.generate_initiator_keys(k) + k = self.get_shared_secret(e, self.y, p) + self.kc_o, self.km_o, self.ks_o = self.generate_initiator_keys(k) - # 4.5.2 verifying alice's identity - self.verify_identity(form, e, False, 'a') + # 4.5.2 verifying alice's identity + self.verify_identity(form, e, False, 'a') - # 4.5.4 generating bob's final session keys - srs = '' + # 4.5.4 generating bob's final session keys + srs = '' - srses = secrets.secrets().retained_secrets(self.conn.name, - self.jid.getStripped()) - rshashes = [base64.b64decode(rshash) for rshash in form.getField( - 'rshashes').getValues()] + srses = secrets.secrets().retained_secrets(self.conn.name, + self.jid.getStripped()) + rshashes = [base64.b64decode(rshash) for rshash in form.getField( + 'rshashes').getValues()] - for s in srses: - secret = s[0] - if self.hmac(self.n_o, secret) in rshashes: - srs = secret - break + for s in srses: + secret = s[0] + if self.hmac(self.n_o, secret) in rshashes: + srs = secret + break - # other shared secret - # (we're not using one) - oss = '' - - k = crypto.sha256(k + srs + oss) - - self.kc_s, self.km_s, self.ks_s = self.generate_responder_keys(k) - self.kc_o, self.km_o, self.ks_o = self.generate_initiator_keys(k) - - # 4.5.5 - if srs: - srshash = self.hmac(srs, 'Shared Retained Secret') - else: - srshash = crypto.random_bytes(32) - - x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn')) - x.addChild(node=xmpp.DataField(name='nonce', value=base64.b64encode( - self.n_o))) - x.addChild(node=xmpp.DataField(name='srshash', value=base64.b64encode( - srshash))) - - for datafield in self.make_identity(x, self.d): - x.addChild(node=datafield) + # other shared secret + # (we're not using one) + oss = '' + + k = crypto.sha256(k + srs + oss) + + self.kc_s, self.km_s, self.ks_s = self.generate_responder_keys(k) + self.kc_o, self.km_o, self.ks_o = self.generate_initiator_keys(k) + + # 4.5.5 + if srs: + srshash = self.hmac(srs, 'Shared Retained Secret') + else: + srshash = crypto.random_bytes(32) + + x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn')) + x.addChild(node=xmpp.DataField(name='nonce', value=base64.b64encode( + self.n_o))) + x.addChild(node=xmpp.DataField(name='srshash', value=base64.b64encode( + srshash))) + + for datafield in self.make_identity(x, self.d): + x.addChild(node=datafield) - init.addChild(node=x) + init.addChild(node=x) - self.send(response) + self.send(response) - self.do_retained_secret(k, srs) + self.do_retained_secret(k, srs) - if self.negotiated['logging'] == 'mustnot': - self.loggable = False + if self.negotiated['logging'] == 'mustnot': + self.loggable = False - self.status = 'active' - self.enable_encryption = True + self.status = 'active' + self.enable_encryption = True - if self.control: - self.control.print_esession_details() + if self.control: + self.control.print_esession_details() - def final_steps_alice(self, form): - srs = '' - srses = secrets.secrets().retained_secrets(self.conn.name, - self.jid.getStripped()) + def final_steps_alice(self, form): + srs = '' + srses = secrets.secrets().retained_secrets(self.conn.name, + self.jid.getStripped()) - try: - srshash = base64.b64decode(form['srshash']) - except IndexError: - return + try: + srshash = base64.b64decode(form['srshash']) + except IndexError: + return - for s in srses: - secret = s[0] - if self.hmac(secret, 'Shared Retained Secret') == srshash: - srs = secret - break + for s in srses: + secret = s[0] + if self.hmac(secret, 'Shared Retained Secret') == srshash: + srs = secret + break - oss = '' - k = crypto.sha256(self.k + srs + oss) - del self.k + oss = '' + k = crypto.sha256(self.k + srs + oss) + del self.k - self.do_retained_secret(k, srs) + self.do_retained_secret(k, srs) - # ks_s doesn't need to be calculated here - self.kc_s, self.km_s, self.ks_s = self.generate_initiator_keys(k) - self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(k) + # ks_s doesn't need to be calculated here + self.kc_s, self.km_s, self.ks_s = self.generate_initiator_keys(k) + self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(k) - # 4.6.2 Verifying Bob's Identity - self.verify_identity(form, self.d, False, 'b') - # Note: If Alice discovers an error then she SHOULD ignore any encrypted - # content she received in the stanza. + # 4.6.2 Verifying Bob's Identity + self.verify_identity(form, self.d, False, 'b') + # Note: If Alice discovers an error then she SHOULD ignore any encrypted + # content she received in the stanza. - if self.negotiated['logging'] == 'mustnot': - self.loggable = False + if self.negotiated['logging'] == 'mustnot': + self.loggable = False - self.status = 'active' - self.enable_encryption = True + self.status = 'active' + self.enable_encryption = True - if self.control: - self.control.print_esession_details() + if self.control: + self.control.print_esession_details() - def do_retained_secret(self, k, old_srs): - """ - Calculate the new retained secret. determine if the user needs to check - the remote party's identity. Set up callbacks for when the identity has - been verified - """ - new_srs = self.hmac(k, 'New Retained Secret') - self.srs = new_srs + def do_retained_secret(self, k, old_srs): + """ + Calculate the new retained secret. determine if the user needs to check + the remote party's identity. Set up callbacks for when the identity has + been verified + """ + new_srs = self.hmac(k, 'New Retained Secret') + self.srs = new_srs - account = self.conn.name - bjid = self.jid.getStripped() + account = self.conn.name + bjid = self.jid.getStripped() - self.verified_identity = False + self.verified_identity = False - if old_srs: - if secrets.secrets().srs_verified(account, bjid, old_srs): - # already had a stored secret verified by the user. - secrets.secrets().replace_srs(account, bjid, old_srs, new_srs, True) - # continue without warning. - self.verified_identity = True - else: - # had a secret, but it wasn't verified. - secrets.secrets().replace_srs(account, bjid, old_srs, new_srs, - False) - else: - # we don't even have an SRS - secrets.secrets().save_new_srs(account, bjid, new_srs, False) + if old_srs: + if secrets.secrets().srs_verified(account, bjid, old_srs): + # already had a stored secret verified by the user. + secrets.secrets().replace_srs(account, bjid, old_srs, new_srs, True) + # continue without warning. + self.verified_identity = True + else: + # had a secret, but it wasn't verified. + secrets.secrets().replace_srs(account, bjid, old_srs, new_srs, + False) + else: + # we don't even have an SRS + secrets.secrets().save_new_srs(account, bjid, new_srs, False) - def _verified_srs_cb(self): - secrets.secrets().replace_srs(self.conn.name, self.jid.getStripped(), - self.srs, self.srs, True) + def _verified_srs_cb(self): + secrets.secrets().replace_srs(self.conn.name, self.jid.getStripped(), + self.srs, self.srs, True) - def _unverified_srs_cb(self): - secrets.secrets().replace_srs(self.conn.name, self.jid.getStripped(), - self.srs, self.srs, False) + def _unverified_srs_cb(self): + secrets.secrets().replace_srs(self.conn.name, self.jid.getStripped(), + self.srs, self.srs, False) - def make_dhfield(self, modp_options, sigmai): - dhs = [] + def make_dhfield(self, modp_options, sigmai): + dhs = [] - for modp in modp_options: - p = dh.primes[modp] - g = dh.generators[modp] + for modp in modp_options: + p = dh.primes[modp] + g = dh.generators[modp] - x = crypto.srand(2 ** (2 * self.n - 1), p - 1) + x = crypto.srand(2 ** (2 * self.n - 1), p - 1) - # FIXME this may be a source of performance issues - e = crypto.powmod(g, x, p) + # FIXME this may be a source of performance issues + e = crypto.powmod(g, x, p) - self.xes[modp] = x - self.es[modp] = e - - if sigmai: - dhs.append(base64.b64encode(crypto.encode_mpi(e))) - name = 'dhkeys' - else: - He = crypto.sha256(crypto.encode_mpi(e)) - dhs.append(base64.b64encode(He)) - name = 'dhhashes' - - return xmpp.DataField(name=name, typ='hidden', value=dhs) - - def terminate_e2e(self): - self.terminate() - self.enable_encryption = False - - def acknowledge_termination(self): - StanzaSession.acknowledge_termination(self) - self.enable_encryption = False - - def fail_bad_negotiation(self, reason, fields=None): - """ - Send an error and cancels everything - - If fields is None, the remote party has given us a bad cryptographic - value of some kind. Otherwise, list the fields we haven't implemented. - """ - err = xmpp.Error(xmpp.Message(), xmpp.ERR_FEATURE_NOT_IMPLEMENTED) - err.T.error.T.text.setData(reason) - - if fields: - feature = xmpp.Node(xmpp.NS_FEATURE + ' feature') - - for field in fields: - fn = xmpp.Node('field') - fn['var'] = field - feature.addChild(node=feature) - - err.addChild(node=feature) - - self.send(err) - - self.status = None - self.enable_encryption = False - - # this prevents the MAC check on decryption from succeeding, - # preventing falsified messages from going through. - self.km_o = '' - - def cancelled_negotiation(self): - StanzaSession.cancelled_negotiation(self) - self.enable_encryption = False - self.km_o = '' - -# vim: se ts=3: + self.xes[modp] = x + self.es[modp] = e + + if sigmai: + dhs.append(base64.b64encode(crypto.encode_mpi(e))) + name = 'dhkeys' + else: + He = crypto.sha256(crypto.encode_mpi(e)) + dhs.append(base64.b64encode(He)) + name = 'dhhashes' + + return xmpp.DataField(name=name, typ='hidden', value=dhs) + + def terminate_e2e(self): + self.terminate() + self.enable_encryption = False + + def acknowledge_termination(self): + StanzaSession.acknowledge_termination(self) + self.enable_encryption = False + + def fail_bad_negotiation(self, reason, fields=None): + """ + Send an error and cancels everything + + If fields is None, the remote party has given us a bad cryptographic + value of some kind. Otherwise, list the fields we haven't implemented. + """ + err = xmpp.Error(xmpp.Message(), xmpp.ERR_FEATURE_NOT_IMPLEMENTED) + err.T.error.T.text.setData(reason) + + if fields: + feature = xmpp.Node(xmpp.NS_FEATURE + ' feature') + + for field in fields: + fn = xmpp.Node('field') + fn['var'] = field + feature.addChild(node=feature) + + err.addChild(node=feature) + + self.send(err) + + self.status = None + self.enable_encryption = False + + # this prevents the MAC check on decryption from succeeding, + # preventing falsified messages from going through. + self.km_o = '' + + def cancelled_negotiation(self): + StanzaSession.cancelled_negotiation(self) + self.enable_encryption = False + self.km_o = '' diff --git a/src/common/xmpp/__init__.py b/src/common/xmpp/__init__.py index 8f0fb3d48..4ebc4cfa3 100644 --- a/src/common/xmpp/__init__.py +++ b/src/common/xmpp/__init__.py @@ -15,5 +15,3 @@ import simplexml, protocol, auth_nb, transports_nb, roster_nb import dispatcher_nb, features_nb, idlequeue, bosh, tls_nb, proxy_connectors from client_nb import NonBlockingClient from plugin import PlugIn - -# vim: se ts=3: diff --git a/src/common/xmpp/auth_nb.py b/src/common/xmpp/auth_nb.py index 9872f651b..2b982d091 100644 --- a/src/common/xmpp/auth_nb.py +++ b/src/common/xmpp/auth_nb.py @@ -38,10 +38,10 @@ def H(some): return hashlib.md5(some).digest() def C(some): return ':'.join(some) try: - import kerberos - have_kerberos = True + import kerberos + have_kerberos = True except ImportError: - have_kerberos = False + have_kerberos = False GSS_STATE_STEP = 0 GSS_STATE_WRAP = 1 @@ -51,518 +51,516 @@ SASL_UNSUPPORTED = 'not-supported' SASL_IN_PROCESS = 'in-process' def challenge_splitter(data): - """ - Helper function that creates a dict from challenge string + """ + Helper function that creates a dict from challenge string - Sample challenge string: - username="example.org",realm="somerealm",\ - nonce="OA6MG9tEQGm2hh",cnonce="OA6MHXh6VqTrRk",\ - nc=00000001,qop="auth,auth-int,auth-conf",charset=utf-8 + Sample challenge string: + username="example.org",realm="somerealm",\ + nonce="OA6MG9tEQGm2hh",cnonce="OA6MHXh6VqTrRk",\ + nc=00000001,qop="auth,auth-int,auth-conf",charset=utf-8 - Expected result for challan: - dict['qop'] = ('auth','auth-int','auth-conf') - dict['realm'] = 'somerealm' - """ - X_KEYWORD, X_VALUE, X_END = 0, 1, 2 - quotes_open = False - keyword, value = '', '' - dict_ = {} - arr = None + Expected result for challan: + dict['qop'] = ('auth','auth-int','auth-conf') + dict['realm'] = 'somerealm' + """ + X_KEYWORD, X_VALUE, X_END = 0, 1, 2 + quotes_open = False + keyword, value = '', '' + dict_ = {} + arr = None - expecting = X_KEYWORD - for iter_ in range(len(data) + 1): - end = False - if iter_ == len(data): - expecting = X_END - end = True - else: - char = data[iter_] - if expecting == X_KEYWORD: - if char == '=': - expecting = X_VALUE - elif char in (',', ' ', '\t'): - pass - else: - keyword = '%s%c' % (keyword, char) - elif expecting == X_VALUE: - if char == '"': - if quotes_open: - end = True - else: - quotes_open = True - elif char in (',', ' ', '\t'): - if quotes_open: - if not arr: - arr = [value] - else: - arr.append(value) - value = "" - else: - end = True - else: - value = '%s%c' % (value, char) - if end: - if arr: - arr.append(value) - dict_[keyword] = arr - arr = None - else: - dict_[keyword] = value - value, keyword = '', '' - expecting = X_KEYWORD - quotes_open = False - return dict_ + expecting = X_KEYWORD + for iter_ in range(len(data) + 1): + end = False + if iter_ == len(data): + expecting = X_END + end = True + else: + char = data[iter_] + if expecting == X_KEYWORD: + if char == '=': + expecting = X_VALUE + elif char in (',', ' ', '\t'): + pass + else: + keyword = '%s%c' % (keyword, char) + elif expecting == X_VALUE: + if char == '"': + if quotes_open: + end = True + else: + quotes_open = True + elif char in (',', ' ', '\t'): + if quotes_open: + if not arr: + arr = [value] + else: + arr.append(value) + value = "" + else: + end = True + else: + value = '%s%c' % (value, char) + if end: + if arr: + arr.append(value) + dict_[keyword] = arr + arr = None + else: + dict_[keyword] = value + value, keyword = '', '' + expecting = X_KEYWORD + quotes_open = False + return dict_ class SASL(PlugIn): - """ - Implements SASL authentication. Can be plugged into NonBlockingClient - to start authentication - """ + """ + Implements SASL authentication. Can be plugged into NonBlockingClient + to start authentication + """ - def __init__(self, username, password, on_sasl): - """ - :param user: XMPP username - :param password: XMPP password - :param on_sasl: Callback, will be called after each SASL auth-step. - """ - PlugIn.__init__(self) - self.username = username - self.password = password - self.on_sasl = on_sasl - self.realm = None + def __init__(self, username, password, on_sasl): + """ + :param user: XMPP username + :param password: XMPP password + :param on_sasl: Callback, will be called after each SASL auth-step. + """ + PlugIn.__init__(self) + self.username = username + self.password = password + self.on_sasl = on_sasl + self.realm = None - def plugin(self, owner): - if 'version' not in self._owner.Dispatcher.Stream._document_attrs: - self.startsasl = SASL_UNSUPPORTED - elif self._owner.Dispatcher.Stream.features: - try: - self.FeaturesHandler(self._owner.Dispatcher, - self._owner.Dispatcher.Stream.features) - except NodeProcessed: - pass - else: - self.startsasl = None + def plugin(self, owner): + if 'version' not in self._owner.Dispatcher.Stream._document_attrs: + self.startsasl = SASL_UNSUPPORTED + elif self._owner.Dispatcher.Stream.features: + try: + self.FeaturesHandler(self._owner.Dispatcher, + self._owner.Dispatcher.Stream.features) + except NodeProcessed: + pass + else: + self.startsasl = None - def plugout(self): - """ - Remove SASL handlers from owner's dispatcher. Used internally - """ - if 'features' in self._owner.__dict__: - self._owner.UnregisterHandler('features', self.FeaturesHandler, - xmlns=NS_STREAMS) - if 'challenge' in self._owner.__dict__: - self._owner.UnregisterHandler('challenge', self.SASLHandler, - xmlns=NS_SASL) - if 'failure' in self._owner.__dict__: - self._owner.UnregisterHandler('failure', self.SASLHandler, - xmlns=NS_SASL) - if 'success' in self._owner.__dict__: - self._owner.UnregisterHandler('success', self.SASLHandler, - xmlns=NS_SASL) + def plugout(self): + """ + Remove SASL handlers from owner's dispatcher. Used internally + """ + if 'features' in self._owner.__dict__: + self._owner.UnregisterHandler('features', self.FeaturesHandler, + xmlns=NS_STREAMS) + if 'challenge' in self._owner.__dict__: + self._owner.UnregisterHandler('challenge', self.SASLHandler, + xmlns=NS_SASL) + if 'failure' in self._owner.__dict__: + self._owner.UnregisterHandler('failure', self.SASLHandler, + xmlns=NS_SASL) + if 'success' in self._owner.__dict__: + self._owner.UnregisterHandler('success', self.SASLHandler, + xmlns=NS_SASL) - def auth(self): - """ - Start authentication. Result can be obtained via "SASL.startsasl" - attribute and will be either SASL_SUCCESS or SASL_FAILURE + def auth(self): + """ + Start authentication. Result can be obtained via "SASL.startsasl" + attribute and will be either SASL_SUCCESS or SASL_FAILURE - Note that successfull auth will take at least two Dispatcher.Process() - calls. - """ - if self.startsasl: - pass - elif self._owner.Dispatcher.Stream.features: - try: - self.FeaturesHandler(self._owner.Dispatcher, - self._owner.Dispatcher.Stream.features) - except NodeProcessed: - pass - else: - self._owner.RegisterHandler('features', - self.FeaturesHandler, xmlns=NS_STREAMS) + Note that successfull auth will take at least two Dispatcher.Process() + calls. + """ + if self.startsasl: + pass + elif self._owner.Dispatcher.Stream.features: + try: + self.FeaturesHandler(self._owner.Dispatcher, + self._owner.Dispatcher.Stream.features) + except NodeProcessed: + pass + else: + self._owner.RegisterHandler('features', + self.FeaturesHandler, xmlns=NS_STREAMS) - def FeaturesHandler(self, conn, feats): - """ - Used to determine if server supports SASL auth. Used internally - """ - if not feats.getTag('mechanisms', namespace=NS_SASL): - self.startsasl='not-supported' - log.error('SASL not supported by server') - return - self.mecs = [] - for mec in feats.getTag('mechanisms', namespace=NS_SASL).getTags( - 'mechanism'): - self.mecs.append(mec.getData()) + def FeaturesHandler(self, conn, feats): + """ + Used to determine if server supports SASL auth. Used internally + """ + if not feats.getTag('mechanisms', namespace=NS_SASL): + self.startsasl='not-supported' + log.error('SASL not supported by server') + return + self.mecs = [] + for mec in feats.getTag('mechanisms', namespace=NS_SASL).getTags( + 'mechanism'): + self.mecs.append(mec.getData()) - self._owner.RegisterHandler('challenge', self.SASLHandler, xmlns=NS_SASL) - self._owner.RegisterHandler('failure', self.SASLHandler, xmlns=NS_SASL) - self._owner.RegisterHandler('success', self.SASLHandler, xmlns=NS_SASL) - self.MechanismHandler() + self._owner.RegisterHandler('challenge', self.SASLHandler, xmlns=NS_SASL) + self._owner.RegisterHandler('failure', self.SASLHandler, xmlns=NS_SASL) + self._owner.RegisterHandler('success', self.SASLHandler, xmlns=NS_SASL) + self.MechanismHandler() - def MechanismHandler(self): - if 'ANONYMOUS' in self.mecs and self.username is None: - self.mecs.remove('ANONYMOUS') - node = Node('auth',attrs={'xmlns': NS_SASL, 'mechanism': 'ANONYMOUS'}) - self.mechanism = 'ANONYMOUS' - self.startsasl = SASL_IN_PROCESS - self._owner.send(str(node)) - raise NodeProcessed - if "EXTERNAL" in self.mecs: - self.mecs.remove('EXTERNAL') - node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'EXTERNAL'}, - payload=[base64.encodestring('%s@%s' % (self.username, - self._owner.Server)).replace('\n', '')]) - self.startsasl = SASL_IN_PROCESS - self._owner.send(str(node)) - raise NodeProcessed - if 'GSSAPI' in self.mecs and have_kerberos: - self.mecs.remove('GSSAPI') - try: - self.gss_vc = kerberos.authGSSClientInit('xmpp@' + \ - self._owner.xmpp_hostname)[1] - kerberos.authGSSClientStep(self.gss_vc, '') - response = kerberos.authGSSClientResponse(self.gss_vc) - node=Node('auth',attrs={'xmlns': NS_SASL, 'mechanism': 'GSSAPI'}, - payload=(response or '')) - self.mechanism = 'GSSAPI' - self.gss_step = GSS_STATE_STEP - self.startsasl = SASL_IN_PROCESS - self._owner.send(str(node)) - raise NodeProcessed - except kerberos.GSSError, e: - log.info('GSSAPI authentication failed: %s' % str(e)) - if 'DIGEST-MD5' in self.mecs: - self.mecs.remove('DIGEST-MD5') - node = Node('auth',attrs={'xmlns': NS_SASL, 'mechanism': 'DIGEST-MD5'}) - self.mechanism = 'DIGEST-MD5' - self.startsasl = SASL_IN_PROCESS - self._owner.send(str(node)) - raise NodeProcessed - if 'PLAIN' in self.mecs: - self.mecs.remove('PLAIN') - self.mechanism = 'PLAIN' - self._owner._caller.get_password(self.set_password) - self.startsasl = SASL_IN_PROCESS - raise NodeProcessed - self.startsasl = SASL_FAILURE - log.error('I can only use EXTERNAL, DIGEST-MD5, GSSAPI and PLAIN ' - 'mecanisms.') - if self.on_sasl: - self.on_sasl() - return + def MechanismHandler(self): + if 'ANONYMOUS' in self.mecs and self.username is None: + self.mecs.remove('ANONYMOUS') + node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'ANONYMOUS'}) + self.mechanism = 'ANONYMOUS' + self.startsasl = SASL_IN_PROCESS + self._owner.send(str(node)) + raise NodeProcessed + if "EXTERNAL" in self.mecs: + self.mecs.remove('EXTERNAL') + node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'EXTERNAL'}, + payload=[base64.encodestring('%s@%s' % (self.username, + self._owner.Server)).replace('\n', '')]) + self.startsasl = SASL_IN_PROCESS + self._owner.send(str(node)) + raise NodeProcessed + if 'GSSAPI' in self.mecs and have_kerberos: + self.mecs.remove('GSSAPI') + try: + self.gss_vc = kerberos.authGSSClientInit('xmpp@' + \ + self._owner.xmpp_hostname)[1] + kerberos.authGSSClientStep(self.gss_vc, '') + response = kerberos.authGSSClientResponse(self.gss_vc) + node=Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'GSSAPI'}, + payload=(response or '')) + self.mechanism = 'GSSAPI' + self.gss_step = GSS_STATE_STEP + self.startsasl = SASL_IN_PROCESS + self._owner.send(str(node)) + raise NodeProcessed + except kerberos.GSSError, e: + log.info('GSSAPI authentication failed: %s' % str(e)) + if 'DIGEST-MD5' in self.mecs: + self.mecs.remove('DIGEST-MD5') + node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'DIGEST-MD5'}) + self.mechanism = 'DIGEST-MD5' + self.startsasl = SASL_IN_PROCESS + self._owner.send(str(node)) + raise NodeProcessed + if 'PLAIN' in self.mecs: + self.mecs.remove('PLAIN') + self.mechanism = 'PLAIN' + self._owner._caller.get_password(self.set_password) + self.startsasl = SASL_IN_PROCESS + raise NodeProcessed + self.startsasl = SASL_FAILURE + log.error('I can only use EXTERNAL, DIGEST-MD5, GSSAPI and PLAIN ' + 'mecanisms.') + if self.on_sasl: + self.on_sasl() + return - def SASLHandler(self, conn, challenge): - """ - Perform next SASL auth step. Used internally - """ - if challenge.getNamespace() != NS_SASL: - return - ### Handle Auth result - if challenge.getName() == 'failure': - self.startsasl = SASL_FAILURE - try: - reason = challenge.getChildren()[0] - except Exception: - reason = challenge - log.error('Failed SASL authentification: %s' % reason) - if len(self.mecs) > 0: - # There are other mechanisms to test - self.MechanismHandler() - raise NodeProcessed - if self.on_sasl: - self.on_sasl() - raise NodeProcessed - elif challenge.getName() == 'success': - self.startsasl = SASL_SUCCESS - log.info('Successfully authenticated with remote server.') - handlers = self._owner.Dispatcher.dumpHandlers() + def SASLHandler(self, conn, challenge): + """ + Perform next SASL auth step. Used internally + """ + if challenge.getNamespace() != NS_SASL: + return + ### Handle Auth result + if challenge.getName() == 'failure': + self.startsasl = SASL_FAILURE + try: + reason = challenge.getChildren()[0] + except Exception: + reason = challenge + log.error('Failed SASL authentification: %s' % reason) + if len(self.mecs) > 0: + # There are other mechanisms to test + self.MechanismHandler() + raise NodeProcessed + if self.on_sasl: + self.on_sasl() + raise NodeProcessed + elif challenge.getName() == 'success': + self.startsasl = SASL_SUCCESS + log.info('Successfully authenticated with remote server.') + handlers = self._owner.Dispatcher.dumpHandlers() - # Bosh specific dispatcher replugging - # save old features. They will be used in case we won't get response on - # stream restart after SASL auth (happens with XMPP over BOSH with - # Openfire) - old_features = self._owner.Dispatcher.Stream.features - self._owner.Dispatcher.PlugOut() - dispatcher_nb.Dispatcher.get_instance().PlugIn(self._owner, - after_SASL=True, old_features=old_features) - self._owner.Dispatcher.restoreHandlers(handlers) - self._owner.User = self.username + # Bosh specific dispatcher replugging + # save old features. They will be used in case we won't get response on + # stream restart after SASL auth (happens with XMPP over BOSH with + # Openfire) + old_features = self._owner.Dispatcher.Stream.features + self._owner.Dispatcher.PlugOut() + dispatcher_nb.Dispatcher.get_instance().PlugIn(self._owner, + after_SASL=True, old_features=old_features) + self._owner.Dispatcher.restoreHandlers(handlers) + self._owner.User = self.username - if self.on_sasl: - self.on_sasl() - raise NodeProcessed + if self.on_sasl: + self.on_sasl() + raise NodeProcessed - ### Perform auth step - incoming_data = challenge.getData() - data=base64.decodestring(incoming_data) - log.info('Got challenge:' + data) + ### Perform auth step + incoming_data = challenge.getData() + data=base64.decodestring(incoming_data) + log.info('Got challenge:' + data) - if self.mechanism == 'GSSAPI': - if self.gss_step == GSS_STATE_STEP: - rc = kerberos.authGSSClientStep(self.gss_vc, incoming_data) - if rc != kerberos.AUTH_GSS_CONTINUE: - self.gss_step = GSS_STATE_WRAP - elif self.gss_step == GSS_STATE_WRAP: - rc = kerberos.authGSSClientUnwrap(self.gss_vc, incoming_data) - response = kerberos.authGSSClientResponse(self.gss_vc) - rc = kerberos.authGSSClientWrap(self.gss_vc, response, - kerberos.authGSSClientUserName(self.gss_vc)) - response = kerberos.authGSSClientResponse(self.gss_vc) - if not response: - response = '' - self._owner.send(Node('response', attrs={'xmlns':NS_SASL}, - payload=response).__str__()) - raise NodeProcessed + if self.mechanism == 'GSSAPI': + if self.gss_step == GSS_STATE_STEP: + rc = kerberos.authGSSClientStep(self.gss_vc, incoming_data) + if rc != kerberos.AUTH_GSS_CONTINUE: + self.gss_step = GSS_STATE_WRAP + elif self.gss_step == GSS_STATE_WRAP: + rc = kerberos.authGSSClientUnwrap(self.gss_vc, incoming_data) + response = kerberos.authGSSClientResponse(self.gss_vc) + rc = kerberos.authGSSClientWrap(self.gss_vc, response, + kerberos.authGSSClientUserName(self.gss_vc)) + response = kerberos.authGSSClientResponse(self.gss_vc) + if not response: + response = '' + self._owner.send(Node('response', attrs={'xmlns':NS_SASL}, + payload=response).__str__()) + raise NodeProcessed - # magic foo... - chal = challenge_splitter(data) - if not self.realm and 'realm' in chal: - self.realm = chal['realm'] - if 'qop' in chal and ((isinstance(chal['qop'], str) and \ - chal['qop'] =='auth') or (isinstance(chal['qop'], list) and 'auth' in \ - chal['qop'])): - self.resp = {} - self.resp['username'] = self.username - if self.realm: - self.resp['realm'] = self.realm - else: - self.resp['realm'] = self._owner.Server - self.resp['nonce'] = chal['nonce'] - self.resp['cnonce'] = ''.join("%x" % randint(0, 2**28) for randint in - itertools.repeat(random.randint, 7)) - self.resp['nc'] = ('00000001') - self.resp['qop'] = 'auth' - self.resp['digest-uri'] = 'xmpp/' + self._owner.Server - self.resp['charset'] = 'utf-8' - # Password is now required - self._owner._caller.get_password(self.set_password) - elif 'rspauth' in chal: - self._owner.send(str(Node('response', attrs={'xmlns':NS_SASL}))) - else: - self.startsasl = SASL_FAILURE - log.error('Failed SASL authentification: unknown challenge') - if self.on_sasl: - self.on_sasl() - raise NodeProcessed + # magic foo... + chal = challenge_splitter(data) + if not self.realm and 'realm' in chal: + self.realm = chal['realm'] + if 'qop' in chal and ((isinstance(chal['qop'], str) and \ + chal['qop'] =='auth') or (isinstance(chal['qop'], list) and 'auth' in \ + chal['qop'])): + self.resp = {} + self.resp['username'] = self.username + if self.realm: + self.resp['realm'] = self.realm + else: + self.resp['realm'] = self._owner.Server + self.resp['nonce'] = chal['nonce'] + self.resp['cnonce'] = ''.join("%x" % randint(0, 2**28) for randint in + itertools.repeat(random.randint, 7)) + self.resp['nc'] = ('00000001') + self.resp['qop'] = 'auth' + self.resp['digest-uri'] = 'xmpp/' + self._owner.Server + self.resp['charset'] = 'utf-8' + # Password is now required + self._owner._caller.get_password(self.set_password) + elif 'rspauth' in chal: + self._owner.send(str(Node('response', attrs={'xmlns':NS_SASL}))) + else: + self.startsasl = SASL_FAILURE + log.error('Failed SASL authentification: unknown challenge') + if self.on_sasl: + self.on_sasl() + raise NodeProcessed - def set_password(self, password): - if password is None: - self.password = '' - else: - self.password = password - if self.mechanism == 'DIGEST-MD5': - def convert_to_iso88591(string): - try: - string = string.decode('utf-8').encode('iso-8859-1') - except UnicodeEncodeError: - pass - return string - hash_username = convert_to_iso88591(self.resp['username']) - hash_realm = convert_to_iso88591(self.resp['realm']) - hash_password = convert_to_iso88591(self.password) - A1 = C([H(C([hash_username, hash_realm, hash_password])), - self.resp['nonce'], self.resp['cnonce']]) - A2 = C(['AUTHENTICATE', self.resp['digest-uri']]) - response= HH(C([HH(A1), self.resp['nonce'], self.resp['nc'], - self.resp['cnonce'], self.resp['qop'], HH(A2)])) - self.resp['response'] = response - sasl_data = u'' - for key in ('charset', 'username', 'realm', 'nonce', 'nc', 'cnonce', - 'digest-uri', 'response', 'qop'): - if key in ('nc','qop','response','charset'): - sasl_data += u"%s=%s," % (key, self.resp[key]) - else: - sasl_data += u'%s="%s",' % (key, self.resp[key]) - sasl_data = sasl_data[:-1].encode('utf-8').encode('base64').replace( - '\r', '').replace('\n', '') - node = Node('response', attrs={'xmlns':NS_SASL}, payload=[sasl_data]) - elif self.mechanism == 'PLAIN': - sasl_data = u'%s\x00%s\x00%s' % (self.username + '@' + \ - self._owner.Server, self.username, self.password) - sasl_data = sasl_data.encode('utf-8').encode('base64').replace( - '\n', '') - node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'PLAIN'}, - payload=[sasl_data]) - self._owner.send(str(node)) + def set_password(self, password): + if password is None: + self.password = '' + else: + self.password = password + if self.mechanism == 'DIGEST-MD5': + def convert_to_iso88591(string): + try: + string = string.decode('utf-8').encode('iso-8859-1') + except UnicodeEncodeError: + pass + return string + hash_username = convert_to_iso88591(self.resp['username']) + hash_realm = convert_to_iso88591(self.resp['realm']) + hash_password = convert_to_iso88591(self.password) + A1 = C([H(C([hash_username, hash_realm, hash_password])), + self.resp['nonce'], self.resp['cnonce']]) + A2 = C(['AUTHENTICATE', self.resp['digest-uri']]) + response= HH(C([HH(A1), self.resp['nonce'], self.resp['nc'], + self.resp['cnonce'], self.resp['qop'], HH(A2)])) + self.resp['response'] = response + sasl_data = u'' + for key in ('charset', 'username', 'realm', 'nonce', 'nc', 'cnonce', + 'digest-uri', 'response', 'qop'): + if key in ('nc', 'qop', 'response', 'charset'): + sasl_data += u"%s=%s," % (key, self.resp[key]) + else: + sasl_data += u'%s="%s",' % (key, self.resp[key]) + sasl_data = sasl_data[:-1].encode('utf-8').encode('base64').replace( + '\r', '').replace('\n', '') + node = Node('response', attrs={'xmlns':NS_SASL}, payload=[sasl_data]) + elif self.mechanism == 'PLAIN': + sasl_data = u'%s\x00%s\x00%s' % (self.username + '@' + \ + self._owner.Server, self.username, self.password) + sasl_data = sasl_data.encode('utf-8').encode('base64').replace( + '\n', '') + node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'PLAIN'}, + payload=[sasl_data]) + self._owner.send(str(node)) class NonBlockingNonSASL(PlugIn): - """ - Implements old Non-SASL (JEP-0078) authentication used in jabberd1.4 and - transport authentication - """ + """ + Implements old Non-SASL (JEP-0078) authentication used in jabberd1.4 and + transport authentication + """ - def __init__(self, user, password, resource, on_auth): - """ - Caches username, password and resource for auth - """ - PlugIn.__init__(self) - self.user = user - if password is None: - self.password = '' - else: - self.password = password - self.resource = resource - self.on_auth = on_auth + def __init__(self, user, password, resource, on_auth): + """ + Caches username, password and resource for auth + """ + PlugIn.__init__(self) + self.user = user + if password is None: + self.password = '' + else: + self.password = password + self.resource = resource + self.on_auth = on_auth - def plugin(self, owner): - """ - Determine the best auth method (digest/0k/plain) and use it for auth. - Returns used method name on success. Used internally - """ - log.info('Querying server about possible auth methods') - self.owner = owner + def plugin(self, owner): + """ + Determine the best auth method (digest/0k/plain) and use it for auth. + Returns used method name on success. Used internally + """ + log.info('Querying server about possible auth methods') + self.owner = owner - owner.Dispatcher.SendAndWaitForResponse( - Iq('get', NS_AUTH, payload=[Node('username', payload=[self.user])]), - func=self._on_username) + owner.Dispatcher.SendAndWaitForResponse( + Iq('get', NS_AUTH, payload=[Node('username', payload=[self.user])]), + func=self._on_username) - def _on_username(self, resp): - if not isResultNode(resp): - log.error('No result node arrived! Aborting...') - return self.on_auth(None) + def _on_username(self, resp): + if not isResultNode(resp): + log.error('No result node arrived! Aborting...') + return self.on_auth(None) - iq=Iq(typ='set',node=resp) - query = iq.getTag('query') - query.setTagData('username',self.user) - query.setTagData('resource',self.resource) + iq=Iq(typ='set', node=resp) + query = iq.getTag('query') + query.setTagData('username', self.user) + query.setTagData('resource', self.resource) - if query.getTag('digest'): - log.info("Performing digest authentication") - query.setTagData('digest', - hashlib.sha1(self.owner.Dispatcher.Stream._document_attrs['id'] - + self.password).hexdigest()) - if query.getTag('password'): - query.delChild('password') - self._method = 'digest' - elif query.getTag('token'): - token = query.getTagData('token') - seq = query.getTagData('sequence') - log.info("Performing zero-k authentication") + if query.getTag('digest'): + log.info("Performing digest authentication") + query.setTagData('digest', + hashlib.sha1(self.owner.Dispatcher.Stream._document_attrs['id'] + + self.password).hexdigest()) + if query.getTag('password'): + query.delChild('password') + self._method = 'digest' + elif query.getTag('token'): + token = query.getTagData('token') + seq = query.getTagData('sequence') + log.info("Performing zero-k authentication") - def hasher(s): - return hashlib.sha1(s).hexdigest() + def hasher(s): + return hashlib.sha1(s).hexdigest() - def hash_n_times(s, count): - return count and hasher(hash_n_times(s, count-1)) or s + def hash_n_times(s, count): + return count and hasher(hash_n_times(s, count-1)) or s - hash_ = hash_n_times(hasher(hasher(self.password) + token), int(seq)) - query.setTagData('hash', hash_) - self._method='0k' - else: - log.warn("Sequre methods unsupported, performing plain text \ - authentication") - query.setTagData('password', self.password) - self._method = 'plain' - resp = self.owner.Dispatcher.SendAndWaitForResponse(iq,func=self._on_auth) + hash_ = hash_n_times(hasher(hasher(self.password) + token), int(seq)) + query.setTagData('hash', hash_) + self._method='0k' + else: + log.warn("Sequre methods unsupported, performing plain text \ + authentication") + query.setTagData('password', self.password) + self._method = 'plain' + resp = self.owner.Dispatcher.SendAndWaitForResponse(iq, func=self._on_auth) - def _on_auth(self, resp): - if isResultNode(resp): - log.info('Sucessfully authenticated with remote host.') - self.owner.User = self.user - self.owner.Resource = self.resource - self.owner._registered_name = self.owner.User+'@'+self.owner.Server+\ - '/'+self.owner.Resource - return self.on_auth(self._method) - log.error('Authentication failed!') - return self.on_auth(None) + def _on_auth(self, resp): + if isResultNode(resp): + log.info('Sucessfully authenticated with remote host.') + self.owner.User = self.user + self.owner.Resource = self.resource + self.owner._registered_name = self.owner.User+'@'+self.owner.Server+\ + '/'+self.owner.Resource + return self.on_auth(self._method) + log.error('Authentication failed!') + return self.on_auth(None) class NonBlockingBind(PlugIn): - """ - Bind some JID to the current connection to allow router know of our - location. Must be plugged after successful SASL auth - """ + """ + Bind some JID to the current connection to allow router know of our + location. Must be plugged after successful SASL auth + """ - def __init__(self): - PlugIn.__init__(self) - self.bound = None + def __init__(self): + PlugIn.__init__(self) + self.bound = None - def plugin(self, owner): - ''' Start resource binding, if allowed at this time. Used internally. ''' - if self._owner.Dispatcher.Stream.features: - try: - self.FeaturesHandler(self._owner.Dispatcher, - self._owner.Dispatcher.Stream.features) - except NodeProcessed: - pass - else: - self._owner.RegisterHandler('features', self.FeaturesHandler, - xmlns=NS_STREAMS) + def plugin(self, owner): + ''' Start resource binding, if allowed at this time. Used internally. ''' + if self._owner.Dispatcher.Stream.features: + try: + self.FeaturesHandler(self._owner.Dispatcher, + self._owner.Dispatcher.Stream.features) + except NodeProcessed: + pass + else: + self._owner.RegisterHandler('features', self.FeaturesHandler, + xmlns=NS_STREAMS) - def FeaturesHandler(self, conn, feats): - """ - Determine if server supports resource binding and set some internal - attributes accordingly - """ - if not feats.getTag('bind', namespace=NS_BIND): - log.error('Server does not requested binding.') - # we try to bind resource anyway - #self.bound='failure' - self.bound = [] - return - if feats.getTag('session', namespace=NS_SESSION): - self.session = 1 - else: - self.session = -1 - self.bound = [] + def FeaturesHandler(self, conn, feats): + """ + Determine if server supports resource binding and set some internal + attributes accordingly + """ + if not feats.getTag('bind', namespace=NS_BIND): + log.error('Server does not requested binding.') + # we try to bind resource anyway + #self.bound='failure' + self.bound = [] + return + if feats.getTag('session', namespace=NS_SESSION): + self.session = 1 + else: + self.session = -1 + self.bound = [] - def plugout(self): - """ - Remove Bind handler from owner's dispatcher. Used internally - """ - self._owner.UnregisterHandler('features', self.FeaturesHandler, - xmlns=NS_STREAMS) + def plugout(self): + """ + Remove Bind handler from owner's dispatcher. Used internally + """ + self._owner.UnregisterHandler('features', self.FeaturesHandler, + xmlns=NS_STREAMS) - def NonBlockingBind(self, resource=None, on_bound=None): - """ - Perform binding. Use provided resource name or random (if not provided). - """ - self.on_bound = on_bound - self._resource = resource - if self._resource: - self._resource = [Node('resource', payload=[self._resource])] - else: - self._resource = [] + def NonBlockingBind(self, resource=None, on_bound=None): + """ + Perform binding. Use provided resource name or random (if not provided). + """ + self.on_bound = on_bound + self._resource = resource + if self._resource: + self._resource = [Node('resource', payload=[self._resource])] + else: + self._resource = [] - self._owner.onreceive(None) - self._owner.Dispatcher.SendAndWaitForResponse( - Protocol('iq',typ='set', payload=[Node('bind', attrs={'xmlns':NS_BIND}, - payload=self._resource)]), func=self._on_bound) + self._owner.onreceive(None) + self._owner.Dispatcher.SendAndWaitForResponse( + Protocol('iq', typ='set', payload=[Node('bind', attrs={'xmlns':NS_BIND}, + payload=self._resource)]), func=self._on_bound) - def _on_bound(self, resp): - if isResultNode(resp): - if resp.getTag('bind') and resp.getTag('bind').getTagData('jid'): - self.bound.append(resp.getTag('bind').getTagData('jid')) - log.info('Successfully bound %s.' % self.bound[-1]) - jid = JID(resp.getTag('bind').getTagData('jid')) - self._owner.User = jid.getNode() - self._owner.Resource = jid.getResource() - if hasattr(self, 'session') and self.session == -1: - # Server don't want us to initialize a session - log.info('No session required.') - self.on_bound('ok') - else: - self._owner.SendAndWaitForResponse(Protocol('iq', typ='set', - payload=[Node('session', attrs={'xmlns':NS_SESSION})]), - func=self._on_session) - return - if resp: - log.error('Binding failed: %s.' % resp.getTag('error')) - self.on_bound(None) - else: - log.error('Binding failed: timeout expired.') - self.on_bound(None) + def _on_bound(self, resp): + if isResultNode(resp): + if resp.getTag('bind') and resp.getTag('bind').getTagData('jid'): + self.bound.append(resp.getTag('bind').getTagData('jid')) + log.info('Successfully bound %s.' % self.bound[-1]) + jid = JID(resp.getTag('bind').getTagData('jid')) + self._owner.User = jid.getNode() + self._owner.Resource = jid.getResource() + if hasattr(self, 'session') and self.session == -1: + # Server don't want us to initialize a session + log.info('No session required.') + self.on_bound('ok') + else: + self._owner.SendAndWaitForResponse(Protocol('iq', typ='set', + payload=[Node('session', attrs={'xmlns':NS_SESSION})]), + func=self._on_session) + return + if resp: + log.error('Binding failed: %s.' % resp.getTag('error')) + self.on_bound(None) + else: + log.error('Binding failed: timeout expired.') + self.on_bound(None) - def _on_session(self, resp): - self._owner.onreceive(None) - if isResultNode(resp): - log.info('Successfully opened session.') - self.session = 1 - self.on_bound('ok') - else: - log.error('Session open failed.') - self.session = 0 - self.on_bound(None) - -# vim: se ts=3: + def _on_session(self, resp): + self._owner.onreceive(None) + if isResultNode(resp): + log.info('Successfully opened session.') + self.session = 1 + self.on_bound('ok') + else: + log.error('Session open failed.') + self.session = 0 + self.on_bound(None) diff --git a/src/common/xmpp/bosh.py b/src/common/xmpp/bosh.py index 2658668ba..08796c4fe 100644 --- a/src/common/xmpp/bosh.py +++ b/src/common/xmpp/bosh.py @@ -21,8 +21,8 @@ import locale, random from hashlib import sha1 from transports_nb import NonBlockingTransport, NonBlockingHTTPBOSH,\ - CONNECTED, CONNECTING, DISCONNECTED, DISCONNECTING,\ - urisplit, DISCONNECT_TIMEOUT_SECONDS + CONNECTED, CONNECTING, DISCONNECTED, DISCONNECTING,\ + urisplit, DISCONNECT_TIMEOUT_SECONDS from protocol import BOSHBody from simplexml import Node @@ -37,540 +37,540 @@ FAKE_DESCRIPTOR = -1337 class NonBlockingBOSH(NonBlockingTransport): - def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, certs, - xmpp_server, domain, bosh_dict, proxy_creds): - NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue, - estabilish_tls, certs) - - self.bosh_sid = None - if locale.getdefaultlocale()[0]: - self.bosh_xml_lang = locale.getdefaultlocale()[0].split('_')[0] - else: - self.bosh_xml_lang = 'en' - - self.http_version = 'HTTP/1.1' - self.http_persistent = True - self.http_pipelining = bosh_dict['bosh_http_pipelining'] - self.bosh_to = domain - - self.route_host, self.route_port = xmpp_server - - self.bosh_wait = bosh_dict['bosh_wait'] - if not self.http_pipelining: - self.bosh_hold = 1 - else: - self.bosh_hold = bosh_dict['bosh_hold'] - self.bosh_requests = self.bosh_hold - self.bosh_uri = bosh_dict['bosh_uri'] - self.bosh_content = bosh_dict['bosh_content'] - self.over_proxy = bosh_dict['bosh_useproxy'] - if estabilish_tls: - self.bosh_secure = 'true' - else: - self.bosh_secure = 'false' - self.use_proxy_auth = bosh_dict['useauth'] - self.proxy_creds = proxy_creds - self.wait_cb_time = None - self.http_socks = [] - self.stanza_buffer = [] - self.prio_bosh_stanzas = [] - self.current_recv_handler = None - self.current_recv_socket = None - self.key_stack = None - self.ack_checker = None - self.after_init = False - self.proxy_dict = {} - if self.over_proxy and self.estabilish_tls: - self.proxy_dict['type'] = 'http' - # with SSL over proxy, we do HTTP CONNECT to proxy to open a channel to - # BOSH Connection Manager - host, port = urisplit(self.bosh_uri)[1:3] - self.proxy_dict['xmpp_server'] = (host, port) - self.proxy_dict['credentials'] = self.proxy_creds - - - def connect(self, conn_5tuple, on_connect, on_connect_failure): - NonBlockingTransport.connect(self, conn_5tuple, on_connect, on_connect_failure) - - global FAKE_DESCRIPTOR - FAKE_DESCRIPTOR = FAKE_DESCRIPTOR - 1 - self.fd = FAKE_DESCRIPTOR - - self.stanza_buffer = [] - self.prio_bosh_stanzas = [] - - self.key_stack = KeyStack(KEY_COUNT) - self.ack_checker = AckChecker() - self.after_init = True - - self.http_socks.append(self.get_new_http_socket()) - self._tcp_connecting_started() - - self.http_socks[0].connect( - conn_5tuple = conn_5tuple, - on_connect = self._on_connect, - on_connect_failure = self._on_connect_failure) - - def _on_connect(self): - self.peerhost = self.http_socks[0].peerhost - self.ssl_lib = self.http_socks[0].ssl_lib - NonBlockingTransport._on_connect(self) - - - - def set_timeout(self, timeout): - if self.get_state() != DISCONNECTED and self.fd != -1: - NonBlockingTransport.set_timeout(self, timeout) - else: - log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % (self.get_state(), self.fd)) - - def on_http_request_possible(self): - """ - Called when HTTP request it's possible to send a HTTP request. It can be when - socket is connected or when HTTP response arrived - - There should be always one pending request to BOSH CM. - """ - log.debug('on_http_req possible, state:\n%s' % self.get_current_state()) - if self.get_state()==DISCONNECTED: return - - #Hack for making the non-secure warning dialog work - if self._owner.got_features: - if (hasattr(self._owner, 'NonBlockingNonSASL') or hasattr(self._owner, 'SASL')): - self.send_BOSH(None) - else: - # If we already got features and no auth module was plugged yet, we are - # probably waiting for confirmation of the "not-secure-connection" dialog. - # We don't send HTTP request in that case. - # see http://lists.jabber.ru/pipermail/ejabberd/2008-August/004027.html - return - else: - self.send_BOSH(None) - - - - def get_socket_in(self, state): - """ - Get sockets in desired state - """ - for s in self.http_socks: - if s.get_state()==state: return s - return None - - - def get_free_socket(self): - """ - Select and returns socket eligible for sending a data to - """ - if self.http_pipelining: - return self.get_socket_in(CONNECTED) - else: - last_recv_time, tmpsock = 0, None - for s in self.http_socks: - # we're interested only in CONNECTED socket with no requests pending - if s.get_state()==CONNECTED and s.pending_requests==0: - # if there's more of them, we want the one with the least recent data receive - # (lowest last_recv_time) - if (last_recv_time==0) or (s.last_recv_time < last_recv_time): - last_recv_time = s.last_recv_time - tmpsock = s - if tmpsock: - return tmpsock - else: - return None - - - def send_BOSH(self, payload): - """ - Tries to send a stanza in payload by appeding it to a buffer and plugging a - free socket for writing. - """ - total_pending_reqs = sum([s.pending_requests for s in self.http_socks]) - - # when called after HTTP response (Payload=None) and when there are already - # some pending requests and no data to send, or when the socket is - # disconnected, we do nothing - if payload is None and \ - total_pending_reqs > 0 and \ - self.stanza_buffer == [] and \ - self.prio_bosh_stanzas == [] or \ - self.get_state()==DISCONNECTED: - return - - # now the payload is put to buffer and will be sent at some point - self.append_stanza(payload) - - # if we're about to make more requests than allowed, we don't send - stanzas will be - # sent after HTTP response from CM, exception is when we're disconnecting - then we - # send anyway - if total_pending_reqs >= self.bosh_requests and self.get_state()!=DISCONNECTING: - log.warn('attemp to make more requests than allowed by Connection Manager:\n%s' % - self.get_current_state()) - return - - # when there's free CONNECTED socket, we plug it for write and the data will - # be sent when write is possible - if self.get_free_socket(): - self.plug_socket() - return - - # if there is a connecting socket, we just wait for when it connects, - # payload will be sent in a sec when the socket connects - if self.get_socket_in(CONNECTING): return - - # being here means there are either DISCONNECTED sockets or all sockets are - # CONNECTED with too many pending requests - s = self.get_socket_in(DISCONNECTED) - - # if we have DISCONNECTED socket, lets connect it and plug for send - if s: - self.connect_and_flush(s) - else: - # otherwise create and connect a new one - ss = self.get_new_http_socket() - self.http_socks.append(ss) - self.connect_and_flush(ss) - return - - def plug_socket(self): - stanza = None - s = self.get_free_socket() - if s: - s._plug_idle(writable=True, readable=True) - else: - log.error('=====!!!!!!!!====> Couldn\'t get free socket in plug_socket())') - - def build_stanza(self, socket): - """ - Build a BOSH body tag from data in buffers and adds key, rid and ack - attributes to it - - This method is called from _do_send() of underlying transport. This is to - ensure rid and keys will be processed in correct order. If I generate - them before plugging a socket for write (and did it for two sockets/HTTP - connections) in parallel, they might be sent in wrong order, which - results in violating the BOSH session and server-side disconnect. - """ - if self.prio_bosh_stanzas: - stanza, add_payload = self.prio_bosh_stanzas.pop(0) - if add_payload: - stanza.setPayload(self.stanza_buffer) - self.stanza_buffer = [] - else: - stanza = self.boshify_stanzas(self.stanza_buffer) - self.stanza_buffer = [] - - stanza = self.ack_checker.backup_stanza(stanza, socket) - - key, newkey = self.key_stack.get() - if key: - stanza.setAttr('key', key) - if newkey: - stanza.setAttr('newkey', newkey) - - - log.info('sending msg with rid=%s to sock %s' % (stanza.getAttr('rid'), id(socket))) - self.renew_bosh_wait_timeout(self.bosh_wait + 3) - return stanza - - - def on_bosh_wait_timeout(self): - log.error('Connection Manager didn\'t respond within %s + 3 seconds --> forcing disconnect' % self.bosh_wait) - self.disconnect() - - - def renew_bosh_wait_timeout(self, timeout): - if self.wait_cb_time is not None: - self.remove_bosh_wait_timeout() - sched_time = self.idlequeue.set_alarm(self.on_bosh_wait_timeout, timeout) - self.wait_cb_time = sched_time - - def remove_bosh_wait_timeout(self): - self.idlequeue.remove_alarm( - self.on_bosh_wait_timeout, - self.wait_cb_time) - - def on_persistent_fallback(self, socket): - """ - Called from underlying transport when server closes TCP connection - - :param socket: disconnected transport object - """ - if socket.http_persistent: - log.warn('Fallback to nonpersistent HTTP (no pipelining as well)') - socket.http_persistent = False - self.http_persistent = False - self.http_pipelining = False - socket.disconnect(do_callback=False) - self.connect_and_flush(socket) - else: - socket.disconnect() - - - - def handle_body_attrs(self, stanza_attrs): - """ - Called for each incoming body stanza from dispatcher. Checks body - attributes. - """ - self.remove_bosh_wait_timeout() - - if self.after_init: - if stanza_attrs.has_key('sid'): - # session ID should be only in init response - self.bosh_sid = stanza_attrs['sid'] - - if stanza_attrs.has_key('requests'): - self.bosh_requests = int(stanza_attrs['requests']) - - if stanza_attrs.has_key('wait'): - self.bosh_wait = int(stanza_attrs['wait']) - self.after_init = False - - ack = None - if stanza_attrs.has_key('ack'): - ack = stanza_attrs['ack'] - self.ack_checker.process_incoming_ack(ack=ack, - socket=self.current_recv_socket) - - if stanza_attrs.has_key('type'): - if stanza_attrs['type'] in ['terminate', 'terminal']: - condition = 'n/a' - if stanza_attrs.has_key('condition'): - condition = stanza_attrs['condition'] - if condition == 'n/a': - log.info('Received sesion-ending terminating stanza') - else: - log.error('Received terminating stanza: %s - %s' % (condition, - bosh_errors[condition])) - self.disconnect() - return - - if stanza_attrs['type'] == 'error': - # recoverable error - pass - return - - - def append_stanza(self, stanza): - """ - Append stanza to a buffer to send - """ - if stanza: - if isinstance(stanza, tuple): - # stanza is tuple of BOSH stanza and bool value for whether to add payload - self.prio_bosh_stanzas.append(stanza) - else: - # stanza is XMPP stanza. Will be boshified before send. - self.stanza_buffer.append(stanza) - - - def send(self, stanza, now=False): - self.send_BOSH(stanza) - - - - def get_current_state(self): - t = '------ SOCKET_ID\tSOCKET_STATE\tPENDING_REQS\n' - for s in self.http_socks: - t = '%s------ %s\t%s\t%s\n' % (t,id(s), s.get_state(), s.pending_requests) - t = '%s------ prio stanzas: %s, queued XMPP stanzas: %s, not_acked stanzas: %s' \ - % (t, self.prio_bosh_stanzas, self.stanza_buffer, - self.ack_checker.get_not_acked_rids()) - return t - - - def connect_and_flush(self, socket): - socket.connect( - conn_5tuple = self.conn_5tuple, - on_connect = self.on_http_request_possible, - on_connect_failure = self.disconnect) - - - def boshify_stanzas(self, stanzas=[], body_attrs=None): - """ - Wraps zero to many stanzas by body tag with xmlns and sid - """ - log.debug('boshify_staza - type is: %s, stanza is %s' % (type(stanzas), stanzas)) - tag = BOSHBody(attrs={'sid': self.bosh_sid}) - tag.setPayload(stanzas) - return tag - - - def send_init(self, after_SASL=False): - if after_SASL: - t = BOSHBody( - attrs={ 'to': self.bosh_to, - 'sid': self.bosh_sid, - 'xml:lang': self.bosh_xml_lang, - 'xmpp:restart': 'true', - 'secure': self.bosh_secure, - 'xmlns:xmpp': 'urn:xmpp:xbosh'}) - else: - t = BOSHBody( - attrs={ 'content': self.bosh_content, - 'hold': str(self.bosh_hold), - 'route': '%s:%s' % (self.route_host, self.route_port), - 'to': self.bosh_to, - 'wait': str(self.bosh_wait), - 'xml:lang': self.bosh_xml_lang, - 'xmpp:version': '1.0', - 'ver': '1.6', - 'xmlns:xmpp': 'urn:xmpp:xbosh'}) - self.send_BOSH((t,True)) - - def start_disconnect(self): - NonBlockingTransport.start_disconnect(self) - self.renew_bosh_wait_timeout(DISCONNECT_TIMEOUT_SECONDS) - self.send_BOSH( - (BOSHBody(attrs={'sid': self.bosh_sid, 'type': 'terminate'}), True)) - - - def get_new_http_socket(self): - http_dict = {'http_uri': self.bosh_uri, - 'http_version': self.http_version, - 'http_persistent': self.http_persistent, - 'add_proxy_headers': self.over_proxy and not self.estabilish_tls} - if self.use_proxy_auth: - http_dict['proxy_user'], http_dict['proxy_pass'] = self.proxy_creds - - s = NonBlockingHTTPBOSH( - raise_event=self.raise_event, - on_disconnect=self.disconnect, - idlequeue = self.idlequeue, - estabilish_tls = self.estabilish_tls, - certs = self.certs, - on_http_request_possible = self.on_http_request_possible, - http_dict = http_dict, - proxy_dict = self.proxy_dict, - on_persistent_fallback = self.on_persistent_fallback) - - s.onreceive(self.on_received_http) - s.set_stanza_build_cb(self.build_stanza) - return s - - - def onreceive(self, recv_handler): - if recv_handler is None: - recv_handler = self._owner.Dispatcher.ProcessNonBlocking - self.current_recv_handler = recv_handler - - - def on_received_http(self, data, socket): - self.current_recv_socket = socket - self.current_recv_handler(data) - - - def disconnect(self, do_callback=True): - self.remove_bosh_wait_timeout() - if self.get_state() == DISCONNECTED: return - self.fd = -1 - for s in self.http_socks: - s.disconnect(do_callback=False) - NonBlockingTransport.disconnect(self, do_callback) + def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, certs, + xmpp_server, domain, bosh_dict, proxy_creds): + NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue, + estabilish_tls, certs) + + self.bosh_sid = None + if locale.getdefaultlocale()[0]: + self.bosh_xml_lang = locale.getdefaultlocale()[0].split('_')[0] + else: + self.bosh_xml_lang = 'en' + + self.http_version = 'HTTP/1.1' + self.http_persistent = True + self.http_pipelining = bosh_dict['bosh_http_pipelining'] + self.bosh_to = domain + + self.route_host, self.route_port = xmpp_server + + self.bosh_wait = bosh_dict['bosh_wait'] + if not self.http_pipelining: + self.bosh_hold = 1 + else: + self.bosh_hold = bosh_dict['bosh_hold'] + self.bosh_requests = self.bosh_hold + self.bosh_uri = bosh_dict['bosh_uri'] + self.bosh_content = bosh_dict['bosh_content'] + self.over_proxy = bosh_dict['bosh_useproxy'] + if estabilish_tls: + self.bosh_secure = 'true' + else: + self.bosh_secure = 'false' + self.use_proxy_auth = bosh_dict['useauth'] + self.proxy_creds = proxy_creds + self.wait_cb_time = None + self.http_socks = [] + self.stanza_buffer = [] + self.prio_bosh_stanzas = [] + self.current_recv_handler = None + self.current_recv_socket = None + self.key_stack = None + self.ack_checker = None + self.after_init = False + self.proxy_dict = {} + if self.over_proxy and self.estabilish_tls: + self.proxy_dict['type'] = 'http' + # with SSL over proxy, we do HTTP CONNECT to proxy to open a channel to + # BOSH Connection Manager + host, port = urisplit(self.bosh_uri)[1:3] + self.proxy_dict['xmpp_server'] = (host, port) + self.proxy_dict['credentials'] = self.proxy_creds + + + def connect(self, conn_5tuple, on_connect, on_connect_failure): + NonBlockingTransport.connect(self, conn_5tuple, on_connect, on_connect_failure) + + global FAKE_DESCRIPTOR + FAKE_DESCRIPTOR = FAKE_DESCRIPTOR - 1 + self.fd = FAKE_DESCRIPTOR + + self.stanza_buffer = [] + self.prio_bosh_stanzas = [] + + self.key_stack = KeyStack(KEY_COUNT) + self.ack_checker = AckChecker() + self.after_init = True + + self.http_socks.append(self.get_new_http_socket()) + self._tcp_connecting_started() + + self.http_socks[0].connect( + conn_5tuple = conn_5tuple, + on_connect = self._on_connect, + on_connect_failure = self._on_connect_failure) + + def _on_connect(self): + self.peerhost = self.http_socks[0].peerhost + self.ssl_lib = self.http_socks[0].ssl_lib + NonBlockingTransport._on_connect(self) + + + + def set_timeout(self, timeout): + if self.get_state() != DISCONNECTED and self.fd != -1: + NonBlockingTransport.set_timeout(self, timeout) + else: + log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % (self.get_state(), self.fd)) + + def on_http_request_possible(self): + """ + Called when HTTP request it's possible to send a HTTP request. It can be when + socket is connected or when HTTP response arrived + + There should be always one pending request to BOSH CM. + """ + log.debug('on_http_req possible, state:\n%s' % self.get_current_state()) + if self.get_state()==DISCONNECTED: return + + #Hack for making the non-secure warning dialog work + if self._owner.got_features: + if (hasattr(self._owner, 'NonBlockingNonSASL') or hasattr(self._owner, 'SASL')): + self.send_BOSH(None) + else: + # If we already got features and no auth module was plugged yet, we are + # probably waiting for confirmation of the "not-secure-connection" dialog. + # We don't send HTTP request in that case. + # see http://lists.jabber.ru/pipermail/ejabberd/2008-August/004027.html + return + else: + self.send_BOSH(None) + + + + def get_socket_in(self, state): + """ + Get sockets in desired state + """ + for s in self.http_socks: + if s.get_state()==state: return s + return None + + + def get_free_socket(self): + """ + Select and returns socket eligible for sending a data to + """ + if self.http_pipelining: + return self.get_socket_in(CONNECTED) + else: + last_recv_time, tmpsock = 0, None + for s in self.http_socks: + # we're interested only in CONNECTED socket with no requests pending + if s.get_state()==CONNECTED and s.pending_requests==0: + # if there's more of them, we want the one with the least recent data receive + # (lowest last_recv_time) + if (last_recv_time==0) or (s.last_recv_time < last_recv_time): + last_recv_time = s.last_recv_time + tmpsock = s + if tmpsock: + return tmpsock + else: + return None + + + def send_BOSH(self, payload): + """ + Tries to send a stanza in payload by appeding it to a buffer and plugging a + free socket for writing. + """ + total_pending_reqs = sum([s.pending_requests for s in self.http_socks]) + + # when called after HTTP response (Payload=None) and when there are already + # some pending requests and no data to send, or when the socket is + # disconnected, we do nothing + if payload is None and \ + total_pending_reqs > 0 and \ + self.stanza_buffer == [] and \ + self.prio_bosh_stanzas == [] or \ + self.get_state()==DISCONNECTED: + return + + # now the payload is put to buffer and will be sent at some point + self.append_stanza(payload) + + # if we're about to make more requests than allowed, we don't send - stanzas will be + # sent after HTTP response from CM, exception is when we're disconnecting - then we + # send anyway + if total_pending_reqs >= self.bosh_requests and self.get_state()!=DISCONNECTING: + log.warn('attemp to make more requests than allowed by Connection Manager:\n%s' % + self.get_current_state()) + return + + # when there's free CONNECTED socket, we plug it for write and the data will + # be sent when write is possible + if self.get_free_socket(): + self.plug_socket() + return + + # if there is a connecting socket, we just wait for when it connects, + # payload will be sent in a sec when the socket connects + if self.get_socket_in(CONNECTING): return + + # being here means there are either DISCONNECTED sockets or all sockets are + # CONNECTED with too many pending requests + s = self.get_socket_in(DISCONNECTED) + + # if we have DISCONNECTED socket, lets connect it and plug for send + if s: + self.connect_and_flush(s) + else: + # otherwise create and connect a new one + ss = self.get_new_http_socket() + self.http_socks.append(ss) + self.connect_and_flush(ss) + return + + def plug_socket(self): + stanza = None + s = self.get_free_socket() + if s: + s._plug_idle(writable=True, readable=True) + else: + log.error('=====!!!!!!!!====> Couldn\'t get free socket in plug_socket())') + + def build_stanza(self, socket): + """ + Build a BOSH body tag from data in buffers and adds key, rid and ack + attributes to it + + This method is called from _do_send() of underlying transport. This is to + ensure rid and keys will be processed in correct order. If I generate + them before plugging a socket for write (and did it for two sockets/HTTP + connections) in parallel, they might be sent in wrong order, which + results in violating the BOSH session and server-side disconnect. + """ + if self.prio_bosh_stanzas: + stanza, add_payload = self.prio_bosh_stanzas.pop(0) + if add_payload: + stanza.setPayload(self.stanza_buffer) + self.stanza_buffer = [] + else: + stanza = self.boshify_stanzas(self.stanza_buffer) + self.stanza_buffer = [] + + stanza = self.ack_checker.backup_stanza(stanza, socket) + + key, newkey = self.key_stack.get() + if key: + stanza.setAttr('key', key) + if newkey: + stanza.setAttr('newkey', newkey) + + + log.info('sending msg with rid=%s to sock %s' % (stanza.getAttr('rid'), id(socket))) + self.renew_bosh_wait_timeout(self.bosh_wait + 3) + return stanza + + + def on_bosh_wait_timeout(self): + log.error('Connection Manager didn\'t respond within %s + 3 seconds --> forcing disconnect' % self.bosh_wait) + self.disconnect() + + + def renew_bosh_wait_timeout(self, timeout): + if self.wait_cb_time is not None: + self.remove_bosh_wait_timeout() + sched_time = self.idlequeue.set_alarm(self.on_bosh_wait_timeout, timeout) + self.wait_cb_time = sched_time + + def remove_bosh_wait_timeout(self): + self.idlequeue.remove_alarm( + self.on_bosh_wait_timeout, + self.wait_cb_time) + + def on_persistent_fallback(self, socket): + """ + Called from underlying transport when server closes TCP connection + + :param socket: disconnected transport object + """ + if socket.http_persistent: + log.warn('Fallback to nonpersistent HTTP (no pipelining as well)') + socket.http_persistent = False + self.http_persistent = False + self.http_pipelining = False + socket.disconnect(do_callback=False) + self.connect_and_flush(socket) + else: + socket.disconnect() + + + + def handle_body_attrs(self, stanza_attrs): + """ + Called for each incoming body stanza from dispatcher. Checks body + attributes. + """ + self.remove_bosh_wait_timeout() + + if self.after_init: + if stanza_attrs.has_key('sid'): + # session ID should be only in init response + self.bosh_sid = stanza_attrs['sid'] + + if stanza_attrs.has_key('requests'): + self.bosh_requests = int(stanza_attrs['requests']) + + if stanza_attrs.has_key('wait'): + self.bosh_wait = int(stanza_attrs['wait']) + self.after_init = False + + ack = None + if stanza_attrs.has_key('ack'): + ack = stanza_attrs['ack'] + self.ack_checker.process_incoming_ack(ack=ack, + socket=self.current_recv_socket) + + if stanza_attrs.has_key('type'): + if stanza_attrs['type'] in ['terminate', 'terminal']: + condition = 'n/a' + if stanza_attrs.has_key('condition'): + condition = stanza_attrs['condition'] + if condition == 'n/a': + log.info('Received sesion-ending terminating stanza') + else: + log.error('Received terminating stanza: %s - %s' % (condition, + bosh_errors[condition])) + self.disconnect() + return + + if stanza_attrs['type'] == 'error': + # recoverable error + pass + return + + + def append_stanza(self, stanza): + """ + Append stanza to a buffer to send + """ + if stanza: + if isinstance(stanza, tuple): + # stanza is tuple of BOSH stanza and bool value for whether to add payload + self.prio_bosh_stanzas.append(stanza) + else: + # stanza is XMPP stanza. Will be boshified before send. + self.stanza_buffer.append(stanza) + + + def send(self, stanza, now=False): + self.send_BOSH(stanza) + + + + def get_current_state(self): + t = '------ SOCKET_ID\tSOCKET_STATE\tPENDING_REQS\n' + for s in self.http_socks: + t = '%s------ %s\t%s\t%s\n' % (t, id(s), s.get_state(), s.pending_requests) + t = '%s------ prio stanzas: %s, queued XMPP stanzas: %s, not_acked stanzas: %s' \ + % (t, self.prio_bosh_stanzas, self.stanza_buffer, + self.ack_checker.get_not_acked_rids()) + return t + + + def connect_and_flush(self, socket): + socket.connect( + conn_5tuple = self.conn_5tuple, + on_connect = self.on_http_request_possible, + on_connect_failure = self.disconnect) + + + def boshify_stanzas(self, stanzas=[], body_attrs=None): + """ + Wraps zero to many stanzas by body tag with xmlns and sid + """ + log.debug('boshify_staza - type is: %s, stanza is %s' % (type(stanzas), stanzas)) + tag = BOSHBody(attrs={'sid': self.bosh_sid}) + tag.setPayload(stanzas) + return tag + + + def send_init(self, after_SASL=False): + if after_SASL: + t = BOSHBody( + attrs={ 'to': self.bosh_to, + 'sid': self.bosh_sid, + 'xml:lang': self.bosh_xml_lang, + 'xmpp:restart': 'true', + 'secure': self.bosh_secure, + 'xmlns:xmpp': 'urn:xmpp:xbosh'}) + else: + t = BOSHBody( + attrs={ 'content': self.bosh_content, + 'hold': str(self.bosh_hold), + 'route': '%s:%s' % (self.route_host, self.route_port), + 'to': self.bosh_to, + 'wait': str(self.bosh_wait), + 'xml:lang': self.bosh_xml_lang, + 'xmpp:version': '1.0', + 'ver': '1.6', + 'xmlns:xmpp': 'urn:xmpp:xbosh'}) + self.send_BOSH((t, True)) + + def start_disconnect(self): + NonBlockingTransport.start_disconnect(self) + self.renew_bosh_wait_timeout(DISCONNECT_TIMEOUT_SECONDS) + self.send_BOSH( + (BOSHBody(attrs={'sid': self.bosh_sid, 'type': 'terminate'}), True)) + + + def get_new_http_socket(self): + http_dict = {'http_uri': self.bosh_uri, + 'http_version': self.http_version, + 'http_persistent': self.http_persistent, + 'add_proxy_headers': self.over_proxy and not self.estabilish_tls} + if self.use_proxy_auth: + http_dict['proxy_user'], http_dict['proxy_pass'] = self.proxy_creds + + s = NonBlockingHTTPBOSH( + raise_event=self.raise_event, + on_disconnect=self.disconnect, + idlequeue = self.idlequeue, + estabilish_tls = self.estabilish_tls, + certs = self.certs, + on_http_request_possible = self.on_http_request_possible, + http_dict = http_dict, + proxy_dict = self.proxy_dict, + on_persistent_fallback = self.on_persistent_fallback) + + s.onreceive(self.on_received_http) + s.set_stanza_build_cb(self.build_stanza) + return s + + + def onreceive(self, recv_handler): + if recv_handler is None: + recv_handler = self._owner.Dispatcher.ProcessNonBlocking + self.current_recv_handler = recv_handler + + + def on_received_http(self, data, socket): + self.current_recv_socket = socket + self.current_recv_handler(data) + + + def disconnect(self, do_callback=True): + self.remove_bosh_wait_timeout() + if self.get_state() == DISCONNECTED: return + self.fd = -1 + for s in self.http_socks: + s.disconnect(do_callback=False) + NonBlockingTransport.disconnect(self, do_callback) def get_rand_number(): - # with 50-bit random initial rid, session would have to go up - # to 7881299347898368 messages to raise rid over 2**53 - # (see http://www.xmpp.org/extensions/xep-0124.html#rids) - # it's also used for sequence key initialization - r = random.Random() - r.seed() - return r.getrandbits(50) + # with 50-bit random initial rid, session would have to go up + # to 7881299347898368 messages to raise rid over 2**53 + # (see http://www.xmpp.org/extensions/xep-0124.html#rids) + # it's also used for sequence key initialization + r = random.Random() + r.seed() + return r.getrandbits(50) class AckChecker(): - """ - Class for generating rids and generating and checking acknowledgements in - BOSH messages - """ - def __init__(self): - self.rid = get_rand_number() - self.ack = 1 - self.last_rids = {} - self.not_acked = [] + """ + Class for generating rids and generating and checking acknowledgements in + BOSH messages + """ + def __init__(self): + self.rid = get_rand_number() + self.ack = 1 + self.last_rids = {} + self.not_acked = [] - def get_not_acked_rids(self): return [rid for rid, st in self.not_acked] + def get_not_acked_rids(self): return [rid for rid, st in self.not_acked] - def backup_stanza(self, stanza, socket): - socket.pending_requests += 1 - rid = self.get_rid() - self.not_acked.append((rid, stanza)) - stanza.setAttr('rid', str(rid)) - self.last_rids[socket]=rid + def backup_stanza(self, stanza, socket): + socket.pending_requests += 1 + rid = self.get_rid() + self.not_acked.append((rid, stanza)) + stanza.setAttr('rid', str(rid)) + self.last_rids[socket]=rid - if self.rid != self.ack + 1: - stanza.setAttr('ack', str(self.ack)) - return stanza + if self.rid != self.ack + 1: + stanza.setAttr('ack', str(self.ack)) + return stanza - def process_incoming_ack(self, socket, ack=None): - socket.pending_requests -= 1 - if ack: - ack = int(ack) - else: - ack = self.last_rids[socket] + def process_incoming_ack(self, socket, ack=None): + socket.pending_requests -= 1 + if ack: + ack = int(ack) + else: + ack = self.last_rids[socket] - i = len([rid for rid, st in self.not_acked if ack >= rid]) - self.not_acked = self.not_acked[i:] + i = len([rid for rid, st in self.not_acked if ack >= rid]) + self.not_acked = self.not_acked[i:] - self.ack = ack + self.ack = ack - def get_rid(self): - self.rid = self.rid + 1 - return self.rid + def get_rid(self): + self.rid = self.rid + 1 + return self.rid class KeyStack(): - """ - Class implementing key sequences for BOSH messages - """ - def __init__(self, count): - self.count = count - self.keys = [] - self.reset() - self.first_call = True + """ + Class implementing key sequences for BOSH messages + """ + def __init__(self, count): + self.count = count + self.keys = [] + self.reset() + self.first_call = True - def reset(self): - seed = str(get_rand_number()) - self.keys = [sha1(seed).hexdigest()] - for i in range(self.count-1): - curr_seed = self.keys[i] - self.keys.append(sha1(curr_seed).hexdigest()) + def reset(self): + seed = str(get_rand_number()) + self.keys = [sha1(seed).hexdigest()] + for i in range(self.count-1): + curr_seed = self.keys[i] + self.keys.append(sha1(curr_seed).hexdigest()) - def get(self): - if self.first_call: - self.first_call = False - return (None, self.keys.pop()) + def get(self): + if self.first_call: + self.first_call = False + return (None, self.keys.pop()) - if len(self.keys)>1: - return (self.keys.pop(), None) - else: - last_key = self.keys.pop() - self.reset() - new_key = self.keys.pop() - return (last_key, new_key) + if len(self.keys)>1: + return (self.keys.pop(), None) + else: + last_key = self.keys.pop() + self.reset() + new_key = self.keys.pop() + return (last_key, new_key) # http://www.xmpp.org/extensions/xep-0124.html#errorstatus-terminal bosh_errors = { - 'n/a': 'none or unknown condition in terminating body stanza', - 'bad-request': 'The format of an HTTP header or binding element received from the client is unacceptable (e.g., syntax error), or Script Syntax is not supported.', - 'host-gone': 'The target domain specified in the "to" attribute or the target host or port specified in the "route" attribute is no longer serviced by the connection manager.', - 'host-unknown': 'The target domain specified in the "to" attribute or the target host or port specified in the "route" attribute is unknown to the connection manager.', - 'improper-addressing': 'The initialization element lacks a "to" or "route" attribute (or the attribute has no value) but the connection manager requires one.', - 'internal-server-error': 'The connection manager has experienced an internal error that prevents it from servicing the request.', - 'item-not-found': '(1) "sid" is not valid, (2) "stream" is not valid, (3) "rid" is larger than the upper limit of the expected window, (4) connection manager is unable to resend response, (5) "key" sequence is invalid', - 'other-request': 'Another request being processed at the same time as this request caused the session to terminate.', - 'policy-violation': 'The client has broken the session rules (polling too frequently, requesting too frequently, too many simultaneous requests).', - 'remote-connection-failed': 'The connection manager was unable to connect to, or unable to connect securely to, or has lost its connection to, the server.', - 'remote-stream-error': 'Encapsulates an error in the protocol being transported.', - 'see-other-uri': 'The connection manager does not operate at this URI (e.g., the connection manager accepts only SSL or TLS connections at some https: URI rather than the http: URI requested by the client). The client may try POSTing to the URI in the content of the child element.', - 'system-shutdown': 'The connection manager is being shut down. All active HTTP sessions are being terminated. No new sessions can be created.', - 'undefined-condition': 'The error is not one of those defined herein; the connection manager SHOULD include application-specific information in the content of the wrapper.' + 'n/a': 'none or unknown condition in terminating body stanza', + 'bad-request': 'The format of an HTTP header or binding element received from the client is unacceptable (e.g., syntax error), or Script Syntax is not supported.', + 'host-gone': 'The target domain specified in the "to" attribute or the target host or port specified in the "route" attribute is no longer serviced by the connection manager.', + 'host-unknown': 'The target domain specified in the "to" attribute or the target host or port specified in the "route" attribute is unknown to the connection manager.', + 'improper-addressing': 'The initialization element lacks a "to" or "route" attribute (or the attribute has no value) but the connection manager requires one.', + 'internal-server-error': 'The connection manager has experienced an internal error that prevents it from servicing the request.', + 'item-not-found': '(1) "sid" is not valid, (2) "stream" is not valid, (3) "rid" is larger than the upper limit of the expected window, (4) connection manager is unable to resend response, (5) "key" sequence is invalid', + 'other-request': 'Another request being processed at the same time as this request caused the session to terminate.', + 'policy-violation': 'The client has broken the session rules (polling too frequently, requesting too frequently, too many simultaneous requests).', + 'remote-connection-failed': 'The connection manager was unable to connect to, or unable to connect securely to, or has lost its connection to, the server.', + 'remote-stream-error': 'Encapsulates an error in the protocol being transported.', + 'see-other-uri': 'The connection manager does not operate at this URI (e.g., the connection manager accepts only SSL or TLS connections at some https: URI rather than the http: URI requested by the client). The client may try POSTing to the URI in the content of the child element.', + 'system-shutdown': 'The connection manager is being shut down. All active HTTP sessions are being terminated. No new sessions can be created.', + 'undefined-condition': 'The error is not one of those defined herein; the connection manager SHOULD include application-specific information in the content of the wrapper.' } diff --git a/src/common/xmpp/c14n.py b/src/common/xmpp/c14n.py index 521f3144b..0d318a352 100644 --- a/src/common/xmpp/c14n.py +++ b/src/common/xmpp/c14n.py @@ -25,38 +25,35 @@ XML canonicalisation methods (for XEP-0116) from simplexml import ustr def c14n(node, is_buggy): - s = "<" + node.name - if node.namespace: - if not node.parent or node.parent.namespace != node.namespace: - s = s + ' xmlns="%s"' % node.namespace + s = "<" + node.name + if node.namespace: + if not node.parent or node.parent.namespace != node.namespace: + s = s + ' xmlns="%s"' % node.namespace - sorted_attrs = sorted(node.attrs.keys()) - for key in sorted_attrs: - if not is_buggy and key == 'xmlns': - continue - val = ustr(node.attrs[key]) - # like XMLescape() but with whitespace and without > - s = s + ' %s="%s"' % ( key, normalise_attr(val) ) - s = s + ">" - cnt = 0 - if node.kids: - for a in node.kids: - if (len(node.data)-1) >= cnt: - s = s + normalise_text(node.data[cnt]) - s = s + c14n(a, is_buggy) - cnt=cnt+1 - if (len(node.data)-1) >= cnt: s = s + normalise_text(node.data[cnt]) - if not node.kids and s.endswith('>'): - s=s[:-1]+' />' - else: - s = s + "" - return s.encode('utf-8') + sorted_attrs = sorted(node.attrs.keys()) + for key in sorted_attrs: + if not is_buggy and key == 'xmlns': + continue + val = ustr(node.attrs[key]) + # like XMLescape() but with whitespace and without > + s = s + ' %s="%s"' % ( key, normalise_attr(val) ) + s = s + ">" + cnt = 0 + if node.kids: + for a in node.kids: + if (len(node.data)-1) >= cnt: + s = s + normalise_text(node.data[cnt]) + s = s + c14n(a, is_buggy) + cnt=cnt+1 + if (len(node.data)-1) >= cnt: s = s + normalise_text(node.data[cnt]) + if not node.kids and s.endswith('>'): + s=s[:-1]+' />' + else: + s = s + "" + return s.encode('utf-8') def normalise_attr(val): - return val.replace('&', '&').replace('<', '<').replace('"', '"').replace('\t', ' ').replace('\n', ' ').replace('\r', ' ') + return val.replace('&', '&').replace('<', '<').replace('"', '"').replace('\t', ' ').replace('\n', ' ').replace('\r', ' ') def normalise_text(val): - return val.replace('&', '&').replace('<', '<').replace('>', '>').replace('\r', ' ') - - -# vim: se ts=3: + return val.replace('&', '&').replace('<', '<').replace('>', '>').replace('\r', ' ') diff --git a/src/common/xmpp/client_nb.py b/src/common/xmpp/client_nb.py index b2b1d3b35..ea0e43fb1 100644 --- a/src/common/xmpp/client_nb.py +++ b/src/common/xmpp/client_nb.py @@ -1,8 +1,8 @@ ## client_nb.py -## based on client.py, changes backported up to revision 1.60 +## based on client.py, changes backported up to revision 1.60 ## ## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov -## modified by Dimitur Kirov +## modified by Dimitur Kirov ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -29,568 +29,566 @@ log = logging.getLogger('gajim.c.x.client_nb') class NonBlockingClient: - """ - Client class is XMPP connection mountpoint. Objects for authentication, - network communication, roster, xml parsing ... are plugged to client object. - Client implements the abstract behavior - mostly negotioation and callbacks - handling, whereas underlying modules take care of feature-specific logic - """ + """ + Client class is XMPP connection mountpoint. Objects for authentication, + network communication, roster, xml parsing ... are plugged to client object. + Client implements the abstract behavior - mostly negotioation and callbacks + handling, whereas underlying modules take care of feature-specific logic + """ - def __init__(self, domain, idlequeue, caller=None): - """ - Caches connection data + def __init__(self, domain, idlequeue, caller=None): + """ + Caches connection data - :param domain: domain - for to: attribute (from account info) - :param idlequeue: processing idlequeue - :param caller: calling object - it has to implement methods - _event_dispatcher which is called from dispatcher instance - """ - self.Namespace = protocol.NS_CLIENT - self.defaultNamespace = self.Namespace + :param domain: domain - for to: attribute (from account info) + :param idlequeue: processing idlequeue + :param caller: calling object - it has to implement methods + _event_dispatcher which is called from dispatcher instance + """ + self.Namespace = protocol.NS_CLIENT + self.defaultNamespace = self.Namespace - self.idlequeue = idlequeue - self.disconnect_handlers = [] + self.idlequeue = idlequeue + self.disconnect_handlers = [] - self.Server = domain - self.xmpp_hostname = None # FQDN hostname to connect to + self.Server = domain + self.xmpp_hostname = None # FQDN hostname to connect to - # caller is who initiated this client, it is in needed to register - # the EventDispatcher - self._caller = caller - self._owner = self - self._registered_name = None # our full jid, set after successful auth - self.connected = '' - self.socket = None - self.on_connect = None - self.on_proxy_failure = None - self.on_connect_failure = None - self.proxy = None - self.got_features = False - self.stream_started = False - self.disconnecting = False - self.protocol_type = 'XMPP' + # caller is who initiated this client, it is in needed to register + # the EventDispatcher + self._caller = caller + self._owner = self + self._registered_name = None # our full jid, set after successful auth + self.connected = '' + self.socket = None + self.on_connect = None + self.on_proxy_failure = None + self.on_connect_failure = None + self.proxy = None + self.got_features = False + self.stream_started = False + self.disconnecting = False + self.protocol_type = 'XMPP' - def disconnect(self, message=''): - """ - Called on disconnection - disconnect callback is picked based on state of - the client. - """ - # to avoid recursive calls - if self.disconnecting: return + def disconnect(self, message=''): + """ + Called on disconnection - disconnect callback is picked based on state of + the client. + """ + # to avoid recursive calls + if self.disconnecting: return - log.info('Disconnecting NBClient: %s' % message) + log.info('Disconnecting NBClient: %s' % message) - if 'NonBlockingRoster' in self.__dict__: - self.NonBlockingRoster.PlugOut() - if 'NonBlockingBind' in self.__dict__: - self.NonBlockingBind.PlugOut() - if 'NonBlockingNonSASL' in self.__dict__: - self.NonBlockingNonSASL.PlugOut() - if 'SASL' in self.__dict__: - self.SASL.PlugOut() - if 'NonBlockingTCP' in self.__dict__: - self.NonBlockingTCP.PlugOut() - if 'NonBlockingHTTP' in self.__dict__: - self.NonBlockingHTTP.PlugOut() - if 'NonBlockingBOSH' in self.__dict__: - self.NonBlockingBOSH.PlugOut() - # FIXME: we never unplug dispatcher, only on next connect - # See _xmpp_connect_machine and SASLHandler + if 'NonBlockingRoster' in self.__dict__: + self.NonBlockingRoster.PlugOut() + if 'NonBlockingBind' in self.__dict__: + self.NonBlockingBind.PlugOut() + if 'NonBlockingNonSASL' in self.__dict__: + self.NonBlockingNonSASL.PlugOut() + if 'SASL' in self.__dict__: + self.SASL.PlugOut() + if 'NonBlockingTCP' in self.__dict__: + self.NonBlockingTCP.PlugOut() + if 'NonBlockingHTTP' in self.__dict__: + self.NonBlockingHTTP.PlugOut() + if 'NonBlockingBOSH' in self.__dict__: + self.NonBlockingBOSH.PlugOut() + # FIXME: we never unplug dispatcher, only on next connect + # See _xmpp_connect_machine and SASLHandler - connected = self.connected - stream_started = self.stream_started + connected = self.connected + stream_started = self.stream_started - self.connected = '' - self.stream_started = False + self.connected = '' + self.stream_started = False - self.disconnecting = True + self.disconnecting = True - log.debug('Client disconnected..') - if connected == '': - # if we're disconnecting before connection to XMPP sever is opened, - # we don't call disconnect handlers but on_connect_failure callback - if self.proxy: - # with proxy, we have different failure callback - log.debug('calling on_proxy_failure cb') - self.on_proxy_failure(reason=message) - else: - log.debug('calling on_connect_failure cb') - self.on_connect_failure() - else: - # we are connected to XMPP server - if not stream_started: - # if error occur before XML stream was opened, e.g. no response on - # init request, we call the on_connect_failure callback because - # proper connection is not established yet and it's not a proxy - # issue - log.debug('calling on_connect_failure cb') - self._caller.streamError = message - self.on_connect_failure() - else: - # with open connection, we are calling the disconnect handlers - for i in reversed(self.disconnect_handlers): - log.debug('Calling disconnect handler %s' % i) - i() - self.disconnecting = False + log.debug('Client disconnected..') + if connected == '': + # if we're disconnecting before connection to XMPP sever is opened, + # we don't call disconnect handlers but on_connect_failure callback + if self.proxy: + # with proxy, we have different failure callback + log.debug('calling on_proxy_failure cb') + self.on_proxy_failure(reason=message) + else: + log.debug('calling on_connect_failure cb') + self.on_connect_failure() + else: + # we are connected to XMPP server + if not stream_started: + # if error occur before XML stream was opened, e.g. no response on + # init request, we call the on_connect_failure callback because + # proper connection is not established yet and it's not a proxy + # issue + log.debug('calling on_connect_failure cb') + self._caller.streamError = message + self.on_connect_failure() + else: + # with open connection, we are calling the disconnect handlers + for i in reversed(self.disconnect_handlers): + log.debug('Calling disconnect handler %s' % i) + i() + self.disconnecting = False - def connect(self, on_connect, on_connect_failure, hostname=None, port=5222, - on_proxy_failure=None, proxy=None, secure_tuple=('plain', None, - None)): - """ - Open XMPP connection (open XML streams in both directions) + def connect(self, on_connect, on_connect_failure, hostname=None, port=5222, + on_proxy_failure=None, proxy=None, secure_tuple=('plain', None, + None)): + """ + Open XMPP connection (open XML streams in both directions) - :param on_connect: called after stream is successfully opened - :param on_connect_failure: called when error occures during connection - :param hostname: hostname of XMPP server from SRV request - :param port: port number of XMPP server - :param on_proxy_failure: called if error occurres during TCP connection to - proxy server or during proxy connecting process - :param proxy: dictionary with proxy data. It should contain at least - values for keys 'host' and 'port' - connection details for proxy serve - and optionally keys 'user' and 'pass' as proxy credentials - :param secure_tuple: tuple of (desired connection type, cacerts, mycerts) - connection type can be 'ssl' - TLS established after TCP connection, - 'tls' - TLS established after negotiation with starttls, or 'plain'. - cacerts, mycerts - see tls_nb.NonBlockingTLS constructor for more - details - """ - self.on_connect = on_connect - self.on_connect_failure=on_connect_failure - self.on_proxy_failure = on_proxy_failure - self.desired_security, self.cacerts, self.mycerts = secure_tuple - self.Connection = None - self.Port = port - self.proxy = proxy + :param on_connect: called after stream is successfully opened + :param on_connect_failure: called when error occures during connection + :param hostname: hostname of XMPP server from SRV request + :param port: port number of XMPP server + :param on_proxy_failure: called if error occurres during TCP connection to + proxy server or during proxy connecting process + :param proxy: dictionary with proxy data. It should contain at least + values for keys 'host' and 'port' - connection details for proxy serve + and optionally keys 'user' and 'pass' as proxy credentials + :param secure_tuple: tuple of (desired connection type, cacerts, mycerts) + connection type can be 'ssl' - TLS established after TCP connection, + 'tls' - TLS established after negotiation with starttls, or 'plain'. + cacerts, mycerts - see tls_nb.NonBlockingTLS constructor for more + details + """ + self.on_connect = on_connect + self.on_connect_failure=on_connect_failure + self.on_proxy_failure = on_proxy_failure + self.desired_security, self.cacerts, self.mycerts = secure_tuple + self.Connection = None + self.Port = port + self.proxy = proxy - if hostname: - self.xmpp_hostname = hostname - else: - self.xmpp_hostname = self.Server + if hostname: + self.xmpp_hostname = hostname + else: + self.xmpp_hostname = self.Server - # We only check for SSL here as for TLS we will first have to start a - # PLAIN connection and negotiate TLS afterwards. - # establish_tls will instruct transport to start secure connection - # directly - establish_tls = self.desired_security == 'ssl' - certs = (self.cacerts, self.mycerts) + # We only check for SSL here as for TLS we will first have to start a + # PLAIN connection and negotiate TLS afterwards. + # establish_tls will instruct transport to start secure connection + # directly + establish_tls = self.desired_security == 'ssl' + certs = (self.cacerts, self.mycerts) - proxy_dict = {} - tcp_host = self.xmpp_hostname - tcp_port = self.Port + proxy_dict = {} + tcp_host = self.xmpp_hostname + tcp_port = self.Port - if proxy: - # with proxies, client connects to proxy instead of directly to - # XMPP server ((hostname, port)) - # tcp_host is hostname of machine used for socket connection - # (DNS request will be done for proxy or BOSH CM hostname) - tcp_host, tcp_port, proxy_user, proxy_pass = \ - transports_nb.get_proxy_data_from_dict(proxy) + if proxy: + # with proxies, client connects to proxy instead of directly to + # XMPP server ((hostname, port)) + # tcp_host is hostname of machine used for socket connection + # (DNS request will be done for proxy or BOSH CM hostname) + tcp_host, tcp_port, proxy_user, proxy_pass = \ + transports_nb.get_proxy_data_from_dict(proxy) - if proxy['type'] == 'bosh': - # Setup BOSH transport - self.socket = bosh.NonBlockingBOSH.get_instance( - on_disconnect=self.disconnect, - raise_event=self.raise_event, - idlequeue=self.idlequeue, - estabilish_tls=establish_tls, - certs=certs, - proxy_creds=(proxy_user, proxy_pass), - xmpp_server=(self.xmpp_hostname, self.Port), - domain=self.Server, - bosh_dict=proxy) - self.protocol_type = 'BOSH' - self.wait_for_restart_response = \ - proxy['bosh_wait_for_restart_response'] - else: - # http proxy - proxy_dict['type'] = proxy['type'] - proxy_dict['xmpp_server'] = (self.xmpp_hostname, self.Port) - proxy_dict['credentials'] = (proxy_user, proxy_pass) + if proxy['type'] == 'bosh': + # Setup BOSH transport + self.socket = bosh.NonBlockingBOSH.get_instance( + on_disconnect=self.disconnect, + raise_event=self.raise_event, + idlequeue=self.idlequeue, + estabilish_tls=establish_tls, + certs=certs, + proxy_creds=(proxy_user, proxy_pass), + xmpp_server=(self.xmpp_hostname, self.Port), + domain=self.Server, + bosh_dict=proxy) + self.protocol_type = 'BOSH' + self.wait_for_restart_response = \ + proxy['bosh_wait_for_restart_response'] + else: + # http proxy + proxy_dict['type'] = proxy['type'] + proxy_dict['xmpp_server'] = (self.xmpp_hostname, self.Port) + proxy_dict['credentials'] = (proxy_user, proxy_pass) - if not proxy or proxy['type'] != 'bosh': - # Setup ordinary TCP transport - self.socket = transports_nb.NonBlockingTCP.get_instance( - on_disconnect=self.disconnect, - raise_event=self.raise_event, - idlequeue=self.idlequeue, - estabilish_tls=establish_tls, - certs=certs, - proxy_dict=proxy_dict) + if not proxy or proxy['type'] != 'bosh': + # Setup ordinary TCP transport + self.socket = transports_nb.NonBlockingTCP.get_instance( + on_disconnect=self.disconnect, + raise_event=self.raise_event, + idlequeue=self.idlequeue, + estabilish_tls=establish_tls, + certs=certs, + proxy_dict=proxy_dict) - # plug transport into client as self.Connection - self.socket.PlugIn(self) + # plug transport into client as self.Connection + self.socket.PlugIn(self) - self._resolve_hostname( - hostname=tcp_host, - port=tcp_port, - on_success=self._try_next_ip) + self._resolve_hostname( + hostname=tcp_host, + port=tcp_port, + on_success=self._try_next_ip) - def _resolve_hostname(self, hostname, port, on_success): - """ - Wrapper for getaddinfo call + def _resolve_hostname(self, hostname, port, on_success): + """ + Wrapper for getaddinfo call - FIXME: getaddinfo blocks - """ - try: - self.ip_addresses = socket.getaddrinfo(hostname, port, - socket.AF_UNSPEC, socket.SOCK_STREAM) - except socket.gaierror, (errnum, errstr): - self.disconnect(message='Lookup failure for %s:%s, hostname: %s - %s' % - (self.Server, self.Port, hostname, errstr)) - else: - on_success() + FIXME: getaddinfo blocks + """ + try: + self.ip_addresses = socket.getaddrinfo(hostname, port, + socket.AF_UNSPEC, socket.SOCK_STREAM) + except socket.gaierror, (errnum, errstr): + self.disconnect(message='Lookup failure for %s:%s, hostname: %s - %s' % + (self.Server, self.Port, hostname, errstr)) + else: + on_success() - def _try_next_ip(self, err_message=None): - """ - Iterate over IP addresses tries to connect to it - """ - if err_message: - log.debug('While looping over DNS A records: %s' % err_message) - if self.ip_addresses == []: - msg = 'Run out of hosts for name %s:%s.' % (self.Server, self.Port) - msg = msg + ' Error for last IP: %s' % err_message - self.disconnect(msg) - else: - self.current_ip = self.ip_addresses.pop(0) - self.socket.connect( - conn_5tuple=self.current_ip, - on_connect=lambda: self._xmpp_connect(), - on_connect_failure=self._try_next_ip) + def _try_next_ip(self, err_message=None): + """ + Iterate over IP addresses tries to connect to it + """ + if err_message: + log.debug('While looping over DNS A records: %s' % err_message) + if self.ip_addresses == []: + msg = 'Run out of hosts for name %s:%s.' % (self.Server, self.Port) + msg = msg + ' Error for last IP: %s' % err_message + self.disconnect(msg) + else: + self.current_ip = self.ip_addresses.pop(0) + self.socket.connect( + conn_5tuple=self.current_ip, + on_connect=lambda: self._xmpp_connect(), + on_connect_failure=self._try_next_ip) - def incoming_stream_version(self): - """ - Get version of xml stream - """ - if 'version' in self.Dispatcher.Stream._document_attrs: - return self.Dispatcher.Stream._document_attrs['version'] - else: - return None + def incoming_stream_version(self): + """ + Get version of xml stream + """ + if 'version' in self.Dispatcher.Stream._document_attrs: + return self.Dispatcher.Stream._document_attrs['version'] + else: + return None - def _xmpp_connect(self, socket_type=None): - """ - Start XMPP connecting process - open the XML stream. Is called after TCP - connection is established or after switch to TLS when successfully - negotiated with . - """ - # socket_type contains info which transport connection was established - if not socket_type: - if self.Connection.ssl_lib: - # When ssl_lib is set we connected via SSL - socket_type = 'ssl' - else: - # PLAIN is default - socket_type = 'plain' - self.connected = socket_type - self._xmpp_connect_machine() + def _xmpp_connect(self, socket_type=None): + """ + Start XMPP connecting process - open the XML stream. Is called after TCP + connection is established or after switch to TLS when successfully + negotiated with . + """ + # socket_type contains info which transport connection was established + if not socket_type: + if self.Connection.ssl_lib: + # When ssl_lib is set we connected via SSL + socket_type = 'ssl' + else: + # PLAIN is default + socket_type = 'plain' + self.connected = socket_type + self._xmpp_connect_machine() - def _xmpp_connect_machine(self, mode=None, data=None): - """ - Finite automaton taking care of stream opening and features tag handling. - Calls _on_stream_start when stream is started, and disconnect() on - failure. - """ - log.info('-------------xmpp_connect_machine() >> mode: %s, data: %s...' % - (mode, str(data)[:20])) + def _xmpp_connect_machine(self, mode=None, data=None): + """ + Finite automaton taking care of stream opening and features tag handling. + Calls _on_stream_start when stream is started, and disconnect() on + failure. + """ + log.info('-------------xmpp_connect_machine() >> mode: %s, data: %s...' % + (mode, str(data)[:20])) - def on_next_receive(mode): - """ - Set desired on_receive callback on transport based on the state of - connect_machine. - """ - log.info('setting %s on next receive' % mode) - if mode is None: - self.onreceive(None) # switch to Dispatcher.ProcessNonBlocking - else: - self.onreceive(lambda _data:self._xmpp_connect_machine(mode, _data)) + def on_next_receive(mode): + """ + Set desired on_receive callback on transport based on the state of + connect_machine. + """ + log.info('setting %s on next receive' % mode) + if mode is None: + self.onreceive(None) # switch to Dispatcher.ProcessNonBlocking + else: + self.onreceive(lambda _data:self._xmpp_connect_machine(mode, _data)) - if not mode: - # starting state - if self.__dict__.has_key('Dispatcher'): - self.Dispatcher.PlugOut() - self.got_features = False - dispatcher_nb.Dispatcher.get_instance().PlugIn(self) - on_next_receive('RECEIVE_DOCUMENT_ATTRIBUTES') + if not mode: + # starting state + if self.__dict__.has_key('Dispatcher'): + self.Dispatcher.PlugOut() + self.got_features = False + dispatcher_nb.Dispatcher.get_instance().PlugIn(self) + on_next_receive('RECEIVE_DOCUMENT_ATTRIBUTES') - elif mode == 'FAILURE': - self.disconnect('During XMPP connect: %s' % data) + elif mode == 'FAILURE': + self.disconnect('During XMPP connect: %s' % data) - elif mode == 'RECEIVE_DOCUMENT_ATTRIBUTES': - if data: - self.Dispatcher.ProcessNonBlocking(data) - if not hasattr(self, 'Dispatcher') or \ - self.Dispatcher.Stream._document_attrs is None: - self._xmpp_connect_machine( - mode='FAILURE', - data='Error on stream open') - return + elif mode == 'RECEIVE_DOCUMENT_ATTRIBUTES': + if data: + self.Dispatcher.ProcessNonBlocking(data) + if not hasattr(self, 'Dispatcher') or \ + self.Dispatcher.Stream._document_attrs is None: + self._xmpp_connect_machine( + mode='FAILURE', + data='Error on stream open') + return - # if terminating stanza was received after init request then client gets - # disconnected from bosh transport plugin and we have to end the stream - # negotiating process straight away. - # fixes #4657 - if not self.connected: return + # if terminating stanza was received after init request then client gets + # disconnected from bosh transport plugin and we have to end the stream + # negotiating process straight away. + # fixes #4657 + if not self.connected: return - if self.incoming_stream_version() == '1.0': - if not self.got_features: - on_next_receive('RECEIVE_STREAM_FEATURES') - else: - log.info('got STREAM FEATURES in first recv') - self._xmpp_connect_machine(mode='STREAM_STARTED') - else: - log.info('incoming stream version less than 1.0') - self._xmpp_connect_machine(mode='STREAM_STARTED') + if self.incoming_stream_version() == '1.0': + if not self.got_features: + on_next_receive('RECEIVE_STREAM_FEATURES') + else: + log.info('got STREAM FEATURES in first recv') + self._xmpp_connect_machine(mode='STREAM_STARTED') + else: + log.info('incoming stream version less than 1.0') + self._xmpp_connect_machine(mode='STREAM_STARTED') - elif mode == 'RECEIVE_STREAM_FEATURES': - if data: - # sometimes are received together with document - # attributes and sometimes on next receive... - self.Dispatcher.ProcessNonBlocking(data) - if not self.got_features: - self._xmpp_connect_machine( - mode='FAILURE', - data='Missing in 1.0 stream') - else: - log.info('got STREAM FEATURES in second recv') - self._xmpp_connect_machine(mode='STREAM_STARTED') + elif mode == 'RECEIVE_STREAM_FEATURES': + if data: + # sometimes are received together with document + # attributes and sometimes on next receive... + self.Dispatcher.ProcessNonBlocking(data) + if not self.got_features: + self._xmpp_connect_machine( + mode='FAILURE', + data='Missing in 1.0 stream') + else: + log.info('got STREAM FEATURES in second recv') + self._xmpp_connect_machine(mode='STREAM_STARTED') - elif mode == 'STREAM_STARTED': - self._on_stream_start() + elif mode == 'STREAM_STARTED': + self._on_stream_start() - def _on_stream_start(self): - """ - Called after XMPP stream is opened. TLS negotiation may follow if - supported and desired. - """ - self.stream_started = True - self.onreceive(None) + def _on_stream_start(self): + """ + Called after XMPP stream is opened. TLS negotiation may follow if + supported and desired. + """ + self.stream_started = True + self.onreceive(None) - if self.connected == 'plain': - if self.desired_security == 'plain': - # if we want and have plain connection, we're done now - self._on_connect() - else: - # try to negotiate TLS - if self.incoming_stream_version() != '1.0': - # if stream version is less than 1.0, we can't do more - log.warn('While connecting with type = "tls": stream version ' + - 'is less than 1.0') - self._on_connect() - return - if self.Dispatcher.Stream.features.getTag('starttls'): - # Server advertises TLS support, start negotiation - self.stream_started = False - log.info('TLS supported by remote server. Requesting TLS start.') - self._tls_negotiation_handler() - else: - log.warn('While connecting with type = "tls": TLS unsupported ' + - 'by remote server') - self._on_connect() + if self.connected == 'plain': + if self.desired_security == 'plain': + # if we want and have plain connection, we're done now + self._on_connect() + else: + # try to negotiate TLS + if self.incoming_stream_version() != '1.0': + # if stream version is less than 1.0, we can't do more + log.warn('While connecting with type = "tls": stream version ' + + 'is less than 1.0') + self._on_connect() + return + if self.Dispatcher.Stream.features.getTag('starttls'): + # Server advertises TLS support, start negotiation + self.stream_started = False + log.info('TLS supported by remote server. Requesting TLS start.') + self._tls_negotiation_handler() + else: + log.warn('While connecting with type = "tls": TLS unsupported ' + + 'by remote server') + self._on_connect() - elif self.connected in ['ssl', 'tls']: - self._on_connect() - else: - assert False, 'Stream opened for unsupported connection' + elif self.connected in ['ssl', 'tls']: + self._on_connect() + else: + assert False, 'Stream opened for unsupported connection' - def _tls_negotiation_handler(self, con=None, tag=None): - """ - Take care of TLS negotioation with - """ - log.info('-------------tls_negotiaton_handler() >> tag: %s' % tag) - if not con and not tag: - # starting state when we send the - self.RegisterHandlerOnce('proceed', self._tls_negotiation_handler, - xmlns=NS_TLS) - self.RegisterHandlerOnce('failure', self._tls_negotiation_handler, - xmlns=NS_TLS) - self.send('' % NS_TLS) - else: - # we got or - if tag.getNamespace() != NS_TLS: - self.disconnect('Unknown namespace: %s' % tag.getNamespace()) - return - tagname = tag.getName() - if tagname == 'failure': - self.disconnect('TLS received: %s' % tag) - return - log.info('Got starttls proceed response. Switching to TLS/SSL...') - # following call wouldn't work for BOSH transport but it doesn't matter - # because negotiation with BOSH is forbidden - self.Connection.tls_init( - on_succ = lambda: self._xmpp_connect(socket_type='tls'), - on_fail = lambda: self.disconnect('error while etabilishing TLS')) + def _tls_negotiation_handler(self, con=None, tag=None): + """ + Take care of TLS negotioation with + """ + log.info('-------------tls_negotiaton_handler() >> tag: %s' % tag) + if not con and not tag: + # starting state when we send the + self.RegisterHandlerOnce('proceed', self._tls_negotiation_handler, + xmlns=NS_TLS) + self.RegisterHandlerOnce('failure', self._tls_negotiation_handler, + xmlns=NS_TLS) + self.send('' % NS_TLS) + else: + # we got or + if tag.getNamespace() != NS_TLS: + self.disconnect('Unknown namespace: %s' % tag.getNamespace()) + return + tagname = tag.getName() + if tagname == 'failure': + self.disconnect('TLS received: %s' % tag) + return + log.info('Got starttls proceed response. Switching to TLS/SSL...') + # following call wouldn't work for BOSH transport but it doesn't matter + # because negotiation with BOSH is forbidden + self.Connection.tls_init( + on_succ = lambda: self._xmpp_connect(socket_type='tls'), + on_fail = lambda: self.disconnect('error while etabilishing TLS')) - def _on_connect(self): - """ - Preceed call of on_connect callback - """ - self.onreceive(None) - self.on_connect(self, self.connected) + def _on_connect(self): + """ + Preceed call of on_connect callback + """ + self.onreceive(None) + self.on_connect(self, self.connected) - def raise_event(self, event_type, data): - """ - Raise event to connection instance. DATA_SENT and DATA_RECIVED events - are used in XML console to show XMPP traffic - """ - log.info('raising event from transport: :::::%s::::\n_____________\n%s\n_____________\n' % (event_type,data)) - if hasattr(self, 'Dispatcher'): - self.Dispatcher.Event('', event_type, data) + def raise_event(self, event_type, data): + """ + Raise event to connection instance. DATA_SENT and DATA_RECIVED events + are used in XML console to show XMPP traffic + """ + log.info('raising event from transport: :::::%s::::\n_____________\n%s\n_____________\n' % (event_type, data)) + if hasattr(self, 'Dispatcher'): + self.Dispatcher.Event('', event_type, data) ############################################################################### ### follows code for authentication, resource bind, session and roster download ############################################################################### - def auth(self, user, password, resource='', sasl=True, on_auth=None): - """ - Authenticate connnection and bind resource. If resource is not provided - random one or library name used + def auth(self, user, password, resource='', sasl=True, on_auth=None): + """ + Authenticate connnection and bind resource. If resource is not provided + random one or library name used - :param user: XMPP username - :param password: XMPP password - :param resource: resource that shall be used for auth/connecting - :param sasl: Boolean indicating if SASL shall be used. (default: True) - :param on_auth: Callback, called after auth. On auth failure, argument - is None. - """ - self._User, self._Password = user, password - self._Resource, self._sasl = resource, sasl - self.on_auth = on_auth - self._on_doc_attrs() - return + :param user: XMPP username + :param password: XMPP password + :param resource: resource that shall be used for auth/connecting + :param sasl: Boolean indicating if SASL shall be used. (default: True) + :param on_auth: Callback, called after auth. On auth failure, argument + is None. + """ + self._User, self._Password = user, password + self._Resource, self._sasl = resource, sasl + self.on_auth = on_auth + self._on_doc_attrs() + return - def _on_old_auth(self, res): - """ - Callback used by NON-SASL auth. On auth failure, res is None - """ - if res: - self.connected += '+old_auth' - self.on_auth(self, 'old_auth') - else: - self.on_auth(self, None) + def _on_old_auth(self, res): + """ + Callback used by NON-SASL auth. On auth failure, res is None + """ + if res: + self.connected += '+old_auth' + self.on_auth(self, 'old_auth') + else: + self.on_auth(self, None) - def _on_sasl_auth(self, res): - """ - Used internally. On auth failure, res is None - """ - self.onreceive(None) - if res: - self.connected += '+sasl' - self.on_auth(self, 'sasl') - else: - self.on_auth(self, None) + def _on_sasl_auth(self, res): + """ + Used internally. On auth failure, res is None + """ + self.onreceive(None) + if res: + self.connected += '+sasl' + self.on_auth(self, 'sasl') + else: + self.on_auth(self, None) - def _on_doc_attrs(self): - """ - Plug authentication objects and start auth - """ - if self._sasl: - auth_nb.SASL.get_instance(self._User, self._Password, - self._on_start_sasl).PlugIn(self) - if not self._sasl or self.SASL.startsasl == 'not-supported': - if not self._Resource: - self._Resource = 'xmpppy' - auth_nb.NonBlockingNonSASL.get_instance(self._User, self._Password, - self._Resource, self._on_old_auth).PlugIn(self) - return - self.SASL.auth() - return True + def _on_doc_attrs(self): + """ + Plug authentication objects and start auth + """ + if self._sasl: + auth_nb.SASL.get_instance(self._User, self._Password, + self._on_start_sasl).PlugIn(self) + if not self._sasl or self.SASL.startsasl == 'not-supported': + if not self._Resource: + self._Resource = 'xmpppy' + auth_nb.NonBlockingNonSASL.get_instance(self._User, self._Password, + self._Resource, self._on_old_auth).PlugIn(self) + return + self.SASL.auth() + return True - def _on_start_sasl(self, data=None): - """ - Callback used by SASL, called on each auth step - """ - if data: - self.Dispatcher.ProcessNonBlocking(data) - if not 'SASL' in self.__dict__: - # SASL is pluged out, possible disconnect - return - if self.SASL.startsasl == 'in-process': - return - self.onreceive(None) - if self.SASL.startsasl == 'failure': - # wrong user/pass, stop auth - if 'SASL' in self.__dict__: - self.SASL.PlugOut() - self.connected = None # FIXME: is this intended? We use ''elsewhere - self._on_sasl_auth(None) - elif self.SASL.startsasl == 'success': - auth_nb.NonBlockingBind.get_instance().PlugIn(self) - self.onreceive(self._on_auth_bind) - return True + def _on_start_sasl(self, data=None): + """ + Callback used by SASL, called on each auth step + """ + if data: + self.Dispatcher.ProcessNonBlocking(data) + if not 'SASL' in self.__dict__: + # SASL is pluged out, possible disconnect + return + if self.SASL.startsasl == 'in-process': + return + self.onreceive(None) + if self.SASL.startsasl == 'failure': + # wrong user/pass, stop auth + if 'SASL' in self.__dict__: + self.SASL.PlugOut() + self.connected = None # FIXME: is this intended? We use ''elsewhere + self._on_sasl_auth(None) + elif self.SASL.startsasl == 'success': + auth_nb.NonBlockingBind.get_instance().PlugIn(self) + self.onreceive(self._on_auth_bind) + return True - def _on_auth_bind(self, data): - # FIXME: Why use this callback and not bind directly? - if data: - self.Dispatcher.ProcessNonBlocking(data) - if self.NonBlockingBind.bound is None: - return - self.NonBlockingBind.NonBlockingBind(self._Resource, self._on_sasl_auth) - return True + def _on_auth_bind(self, data): + # FIXME: Why use this callback and not bind directly? + if data: + self.Dispatcher.ProcessNonBlocking(data) + if self.NonBlockingBind.bound is None: + return + self.NonBlockingBind.NonBlockingBind(self._Resource, self._on_sasl_auth) + return True - def initRoster(self, version=''): - """ - Plug in the roster - """ - if not self.__dict__.has_key('NonBlockingRoster'): - return roster_nb.NonBlockingRoster.get_instance(version=version).PlugIn(self) + def initRoster(self, version=''): + """ + Plug in the roster + """ + if not self.__dict__.has_key('NonBlockingRoster'): + return roster_nb.NonBlockingRoster.get_instance(version=version).PlugIn(self) - def getRoster(self, on_ready=None, force=False): - """ - Return the Roster instance, previously plugging it in and requesting - roster from server if needed - """ - if self.__dict__.has_key('NonBlockingRoster'): - return self.NonBlockingRoster.getRoster(on_ready, force) - return None + def getRoster(self, on_ready=None, force=False): + """ + Return the Roster instance, previously plugging it in and requesting + roster from server if needed + """ + if self.__dict__.has_key('NonBlockingRoster'): + return self.NonBlockingRoster.getRoster(on_ready, force) + return None - def sendPresence(self, jid=None, typ=None, requestRoster=0): - """ - Send some specific presence state. Can also request roster from server if - according agrument is set - """ - if requestRoster: - # FIXME: used somewhere? - roster_nb.NonBlockingRoster.get_instance().PlugIn(self) - self.send(dispatcher_nb.Presence(to=jid, typ=typ)) + def sendPresence(self, jid=None, typ=None, requestRoster=0): + """ + Send some specific presence state. Can also request roster from server if + according agrument is set + """ + if requestRoster: + # FIXME: used somewhere? + roster_nb.NonBlockingRoster.get_instance().PlugIn(self) + self.send(dispatcher_nb.Presence(to=jid, typ=typ)) ############################################################################### ### following methods are moved from blocking client class of xmpppy ############################################################################### - def RegisterDisconnectHandler(self,handler): - """ - Register handler that will be called on disconnect - """ - self.disconnect_handlers.append(handler) + def RegisterDisconnectHandler(self, handler): + """ + Register handler that will be called on disconnect + """ + self.disconnect_handlers.append(handler) - def UnregisterDisconnectHandler(self,handler): - """ - Unregister handler that is called on disconnect - """ - self.disconnect_handlers.remove(handler) + def UnregisterDisconnectHandler(self, handler): + """ + Unregister handler that is called on disconnect + """ + self.disconnect_handlers.remove(handler) - def DisconnectHandler(self): - """ - Default disconnect handler. Just raises an IOError. If you choosed to use - this class in your production client, override this method or at least - unregister it. - """ - raise IOError('Disconnected from server.') + def DisconnectHandler(self): + """ + Default disconnect handler. Just raises an IOError. If you choosed to use + this class in your production client, override this method or at least + unregister it. + """ + raise IOError('Disconnected from server.') - def get_connect_type(self): - """ - Return connection state. F.e.: None / 'tls' / 'plain+non_sasl' - """ - return self.connected + def get_connect_type(self): + """ + Return connection state. F.e.: None / 'tls' / 'plain+non_sasl' + """ + return self.connected - def get_peerhost(self): - """ - Gets the ip address of the account, from which is made connection to the - server (e.g. IP and port of gajim's socket) + def get_peerhost(self): + """ + Gets the ip address of the account, from which is made connection to the + server (e.g. IP and port of gajim's socket) - We will create listening socket on the same ip - """ - # FIXME: tuple (ip, port) is expected (and checked for) but port num is - # useless - return self.socket.peerhost - -# vim: se ts=3: + We will create listening socket on the same ip + """ + # FIXME: tuple (ip, port) is expected (and checked for) but port num is + # useless + return self.socket.peerhost diff --git a/src/common/xmpp/dispatcher_nb.py b/src/common/xmpp/dispatcher_nb.py index 469c47f4a..f5972e3ce 100644 --- a/src/common/xmpp/dispatcher_nb.py +++ b/src/common/xmpp/dispatcher_nb.py @@ -24,7 +24,7 @@ import simplexml, sys, locale from xml.parsers.expat import ExpatError from plugin import PlugIn from protocol import (NS_STREAMS, NS_XMPP_STREAMS, NS_HTTP_BIND, Iq, Presence, - Message, Protocol, Node, Error, ERR_FEATURE_NOT_IMPLEMENTED, StreamError) + Message, Protocol, Node, Error, ERR_FEATURE_NOT_IMPLEMENTED, StreamError) import logging log = logging.getLogger('gajim.c.x.dispatcher_nb') @@ -36,566 +36,564 @@ XML_DECLARATION = '' # FIXME: ugly class Dispatcher(): - """ - Why is this here - I needed to redefine Dispatcher for BOSH and easiest way - was to inherit original Dispatcher (now renamed to XMPPDispatcher). Trouble - is that reference used to access dispatcher instance is in Client attribute - named by __class__.__name__ of the dispatcher instance .. long story short: + """ + Why is this here - I needed to redefine Dispatcher for BOSH and easiest way + was to inherit original Dispatcher (now renamed to XMPPDispatcher). Trouble + is that reference used to access dispatcher instance is in Client attribute + named by __class__.__name__ of the dispatcher instance .. long story short: - I wrote following to avoid changing each client.Dispatcher.whatever() in xmpp + I wrote following to avoid changing each client.Dispatcher.whatever() in xmpp - If having two kinds of dispatcher will go well, I will rewrite the dispatcher - references in other scripts - """ + If having two kinds of dispatcher will go well, I will rewrite the dispatcher + references in other scripts + """ - def PlugIn(self, client_obj, after_SASL=False, old_features=None): - if client_obj.protocol_type == 'XMPP': - XMPPDispatcher().PlugIn(client_obj) - elif client_obj.protocol_type == 'BOSH': - BOSHDispatcher().PlugIn(client_obj, after_SASL, old_features) - else: - assert False # should never be reached + def PlugIn(self, client_obj, after_SASL=False, old_features=None): + if client_obj.protocol_type == 'XMPP': + XMPPDispatcher().PlugIn(client_obj) + elif client_obj.protocol_type == 'BOSH': + BOSHDispatcher().PlugIn(client_obj, after_SASL, old_features) + else: + assert False # should never be reached - @classmethod - def get_instance(cls, *args, **kwargs): - """ - Factory Method for object creation + @classmethod + def get_instance(cls, *args, **kwargs): + """ + Factory Method for object creation - Use this instead of directly initializing the class in order to make - unit testing much easier. - """ - return cls(*args, **kwargs) + Use this instead of directly initializing the class in order to make + unit testing much easier. + """ + return cls(*args, **kwargs) class XMPPDispatcher(PlugIn): - """ - Handles XMPP stream and is the first who takes control over a fresh stanza + """ + Handles XMPP stream and is the first who takes control over a fresh stanza - Is plugged into NonBlockingClient but can be replugged to restart handled - stream headers (used by SASL f.e.). - """ + Is plugged into NonBlockingClient but can be replugged to restart handled + stream headers (used by SASL f.e.). + """ - def __init__(self): - PlugIn.__init__(self) - self.handlers = {} - self._expected = {} - self._defaultHandler = None - self._pendingExceptions = [] - self._eventHandler = None - self._cycleHandlers = [] - self._exported_methods=[self.RegisterHandler, self.RegisterDefaultHandler, - self.RegisterEventHandler, self.UnregisterCycleHandler, - self.RegisterCycleHandler, self.RegisterHandlerOnce, - self.UnregisterHandler, self.RegisterProtocol, - self.SendAndWaitForResponse, self.SendAndCallForResponse, - self.getAnID, self.Event, self.send] + def __init__(self): + PlugIn.__init__(self) + self.handlers = {} + self._expected = {} + self._defaultHandler = None + self._pendingExceptions = [] + self._eventHandler = None + self._cycleHandlers = [] + self._exported_methods=[self.RegisterHandler, self.RegisterDefaultHandler, + self.RegisterEventHandler, self.UnregisterCycleHandler, + self.RegisterCycleHandler, self.RegisterHandlerOnce, + self.UnregisterHandler, self.RegisterProtocol, + self.SendAndWaitForResponse, self.SendAndCallForResponse, + self.getAnID, self.Event, self.send] - def getAnID(self): - global outgoingID - outgoingID += 1 - return repr(outgoingID) + def getAnID(self): + global outgoingID + outgoingID += 1 + return repr(outgoingID) - def dumpHandlers(self): - """ - Return set of user-registered callbacks in it's internal format. Used - within the library to carry user handlers set over Dispatcher replugins - """ - return self.handlers + def dumpHandlers(self): + """ + Return set of user-registered callbacks in it's internal format. Used + within the library to carry user handlers set over Dispatcher replugins + """ + return self.handlers - def restoreHandlers(self, handlers): - """ - Restore user-registered callbacks structure from dump previously obtained - via dumpHandlers. Used within the library to carry user handlers set over - Dispatcher replugins. - """ - self.handlers = handlers + def restoreHandlers(self, handlers): + """ + Restore user-registered callbacks structure from dump previously obtained + via dumpHandlers. Used within the library to carry user handlers set over + Dispatcher replugins. + """ + self.handlers = handlers - def _init(self): - """ - Register default namespaces/protocols/handlers. Used internally - """ - # FIXME: inject dependencies, do not rely that they are defined by our - # owner - self.RegisterNamespace('unknown') - self.RegisterNamespace(NS_STREAMS) - self.RegisterNamespace(self._owner.defaultNamespace) - self.RegisterProtocol('iq', Iq) - self.RegisterProtocol('presence', Presence) - self.RegisterProtocol('message', Message) - self.RegisterDefaultHandler(self.returnStanzaHandler) - self.RegisterEventHandler(self._owner._caller._event_dispatcher) - self.on_responses = {} + def _init(self): + """ + Register default namespaces/protocols/handlers. Used internally + """ + # FIXME: inject dependencies, do not rely that they are defined by our + # owner + self.RegisterNamespace('unknown') + self.RegisterNamespace(NS_STREAMS) + self.RegisterNamespace(self._owner.defaultNamespace) + self.RegisterProtocol('iq', Iq) + self.RegisterProtocol('presence', Presence) + self.RegisterProtocol('message', Message) + self.RegisterDefaultHandler(self.returnStanzaHandler) + self.RegisterEventHandler(self._owner._caller._event_dispatcher) + self.on_responses = {} - def plugin(self, owner): - """ - Plug the Dispatcher instance into Client class instance and send initial - stream header. Used internally - """ - self._init() - self._owner.lastErrNode = None - self._owner.lastErr = None - self._owner.lastErrCode = None - if hasattr(self._owner, 'StreamInit'): - self._owner.StreamInit() - else: - self.StreamInit() + def plugin(self, owner): + """ + Plug the Dispatcher instance into Client class instance and send initial + stream header. Used internally + """ + self._init() + self._owner.lastErrNode = None + self._owner.lastErr = None + self._owner.lastErrCode = None + if hasattr(self._owner, 'StreamInit'): + self._owner.StreamInit() + else: + self.StreamInit() - def plugout(self): - """ - Prepare instance to be destructed - """ - self.Stream.dispatch = None - self.Stream.features = None - self.Stream.destroy() - self._owner = None - self.Stream = None + def plugout(self): + """ + Prepare instance to be destructed + """ + self.Stream.dispatch = None + self.Stream.features = None + self.Stream.destroy() + self._owner = None + self.Stream = None - def StreamInit(self): - """ - Send an initial stream header - """ - self.Stream = simplexml.NodeBuilder() - self.Stream.dispatch = self.dispatch - self.Stream._dispatch_depth = 2 - self.Stream.stream_header_received = self._check_stream_start - self.Stream.features = None - self._metastream = Node('stream:stream') - self._metastream.setNamespace(self._owner.Namespace) - self._metastream.setAttr('version', '1.0') - self._metastream.setAttr('xmlns:stream', NS_STREAMS) - self._metastream.setAttr('to', self._owner.Server) - if locale.getdefaultlocale()[0]: - self._metastream.setAttr('xml:lang', - locale.getdefaultlocale()[0].split('_')[0]) - self._owner.send("%s%s>" % (XML_DECLARATION, str(self._metastream)[:-2])) + def StreamInit(self): + """ + Send an initial stream header + """ + self.Stream = simplexml.NodeBuilder() + self.Stream.dispatch = self.dispatch + self.Stream._dispatch_depth = 2 + self.Stream.stream_header_received = self._check_stream_start + self.Stream.features = None + self._metastream = Node('stream:stream') + self._metastream.setNamespace(self._owner.Namespace) + self._metastream.setAttr('version', '1.0') + self._metastream.setAttr('xmlns:stream', NS_STREAMS) + self._metastream.setAttr('to', self._owner.Server) + if locale.getdefaultlocale()[0]: + self._metastream.setAttr('xml:lang', + locale.getdefaultlocale()[0].split('_')[0]) + self._owner.send("%s%s>" % (XML_DECLARATION, str(self._metastream)[:-2])) - def _check_stream_start(self, ns, tag, attrs): - if ns != NS_STREAMS or tag!='stream': - raise ValueError('Incorrect stream start: (%s,%s). Terminating.' - % (tag, ns)) + def _check_stream_start(self, ns, tag, attrs): + if ns != NS_STREAMS or tag!='stream': + raise ValueError('Incorrect stream start: (%s,%s). Terminating.' + % (tag, ns)) - def ProcessNonBlocking(self, data): - """ - Check incoming stream for data waiting + def ProcessNonBlocking(self, data): + """ + Check incoming stream for data waiting - :param data: data received from transports/IO sockets - :return: - 1) length of processed data if some data were processed; - 2) '0' string if no data were processed but link is alive; - 3) 0 (zero) if underlying connection is closed. - """ - # FIXME: - # When an error occurs we disconnect the transport directly. Client's - # disconnect method will never be called. - # Is this intended? - # also look at transports start_disconnect() - for handler in self._cycleHandlers: - handler(self) - if len(self._pendingExceptions) > 0: - _pendingException = self._pendingExceptions.pop() - raise _pendingException[0], _pendingException[1], _pendingException[2] - try: - self.Stream.Parse(data) - # end stream:stream tag received - if self.Stream and self.Stream.has_received_endtag(): - self._owner.disconnect(self.Stream.streamError) - return 0 - except ExpatError: - log.error('Invalid XML received from server. Forcing disconnect.') - self._owner.Connection.disconnect() - return 0 - except ValueError, e: - log.debug('ValueError: %s' % str(e)) - self._owner.Connection.pollend() - return 0 - if len(self._pendingExceptions) > 0: - _pendingException = self._pendingExceptions.pop() - raise _pendingException[0], _pendingException[1], _pendingException[2] - if len(data) == 0: - return '0' - return len(data) + :param data: data received from transports/IO sockets + :return: + 1) length of processed data if some data were processed; + 2) '0' string if no data were processed but link is alive; + 3) 0 (zero) if underlying connection is closed. + """ + # FIXME: + # When an error occurs we disconnect the transport directly. Client's + # disconnect method will never be called. + # Is this intended? + # also look at transports start_disconnect() + for handler in self._cycleHandlers: + handler(self) + if len(self._pendingExceptions) > 0: + _pendingException = self._pendingExceptions.pop() + raise _pendingException[0], _pendingException[1], _pendingException[2] + try: + self.Stream.Parse(data) + # end stream:stream tag received + if self.Stream and self.Stream.has_received_endtag(): + self._owner.disconnect(self.Stream.streamError) + return 0 + except ExpatError: + log.error('Invalid XML received from server. Forcing disconnect.') + self._owner.Connection.disconnect() + return 0 + except ValueError, e: + log.debug('ValueError: %s' % str(e)) + self._owner.Connection.pollend() + return 0 + if len(self._pendingExceptions) > 0: + _pendingException = self._pendingExceptions.pop() + raise _pendingException[0], _pendingException[1], _pendingException[2] + if len(data) == 0: + return '0' + return len(data) - def RegisterNamespace(self, xmlns, order='info'): - """ - Create internal structures for newly registered namespace + def RegisterNamespace(self, xmlns, order='info'): + """ + Create internal structures for newly registered namespace - You can register handlers for this namespace afterwards. By default - one namespace is already registered - (jabber:client or jabber:component:accept depending on context. - """ - log.debug('Registering namespace "%s"' % xmlns) - self.handlers[xmlns] = {} - self.RegisterProtocol('unknown', Protocol, xmlns=xmlns) - self.RegisterProtocol('default', Protocol, xmlns=xmlns) + You can register handlers for this namespace afterwards. By default + one namespace is already registered + (jabber:client or jabber:component:accept depending on context. + """ + log.debug('Registering namespace "%s"' % xmlns) + self.handlers[xmlns] = {} + self.RegisterProtocol('unknown', Protocol, xmlns=xmlns) + self.RegisterProtocol('default', Protocol, xmlns=xmlns) - def RegisterProtocol(self, tag_name, Proto, xmlns=None, order='info'): - """ - Used to declare some top-level stanza name to dispatcher + def RegisterProtocol(self, tag_name, Proto, xmlns=None, order='info'): + """ + Used to declare some top-level stanza name to dispatcher - Needed to start registering handlers for such stanzas. Iq, message and - presence protocols are registered by default. - """ - if not xmlns: - xmlns=self._owner.defaultNamespace - log.debug('Registering protocol "%s" as %s(%s)' %(tag_name, Proto, xmlns)) - self.handlers[xmlns][tag_name] = {type:Proto, 'default':[]} + Needed to start registering handlers for such stanzas. Iq, message and + presence protocols are registered by default. + """ + if not xmlns: + xmlns=self._owner.defaultNamespace + log.debug('Registering protocol "%s" as %s(%s)' %(tag_name, Proto, xmlns)) + self.handlers[xmlns][tag_name] = {type:Proto, 'default':[]} - def RegisterNamespaceHandler(self, xmlns, handler, typ='', ns='', - makefirst=0, system=0): - """ - Register handler for processing all stanzas for specified namespace - """ - self.RegisterHandler('default', handler, typ, ns, xmlns, makefirst, - system) + def RegisterNamespaceHandler(self, xmlns, handler, typ='', ns='', + makefirst=0, system=0): + """ + Register handler for processing all stanzas for specified namespace + """ + self.RegisterHandler('default', handler, typ, ns, xmlns, makefirst, + system) - def RegisterHandler(self, name, handler, typ='', ns='', xmlns=None, - makefirst=False, system=False): - """ - Register user callback as stanzas handler of declared type + def RegisterHandler(self, name, handler, typ='', ns='', xmlns=None, + makefirst=False, system=False): + """ + Register user callback as stanzas handler of declared type - Callback arguments: - dispatcher instance (for replying), incoming return of previous handlers. - The callback must raise xmpp.NodeProcessed just before return if it wants - to prevent other callbacks to be called with the same stanza as argument - _and_, more importantly library from returning stanza to sender with error set. + Callback arguments: + dispatcher instance (for replying), incoming return of previous handlers. + The callback must raise xmpp.NodeProcessed just before return if it wants + to prevent other callbacks to be called with the same stanza as argument + _and_, more importantly library from returning stanza to sender with error set. - :param name: name of stanza. F.e. "iq". - :param handler: user callback. - :param typ: value of stanza's "type" attribute. If not specified any - value will match - :param ns: namespace of child that stanza must contain. - :param makefirst: insert handler in the beginning of handlers list instead - of adding it to the end. Note that more common handlers i.e. w/o "typ" - and " will be called first nevertheless. - :param system: call handler even if NodeProcessed Exception were raised - already. - """ - if not xmlns: - xmlns=self._owner.defaultNamespace - log.debug('Registering handler %s for "%s" type->%s ns->%s(%s)' % - (handler, name, typ, ns, xmlns)) - if not typ and not ns: - typ='default' - if xmlns not in self.handlers: - self.RegisterNamespace(xmlns,'warn') - if name not in self.handlers[xmlns]: - self.RegisterProtocol(name,Protocol,xmlns,'warn') - if typ+ns not in self.handlers[xmlns][name]: - self.handlers[xmlns][name][typ+ns]=[] - if makefirst: - self.handlers[xmlns][name][typ+ns].insert(0,{'func':handler, - 'system':system}) - else: - self.handlers[xmlns][name][typ+ns].append({'func':handler, - 'system':system}) + :param name: name of stanza. F.e. "iq". + :param handler: user callback. + :param typ: value of stanza's "type" attribute. If not specified any + value will match + :param ns: namespace of child that stanza must contain. + :param makefirst: insert handler in the beginning of handlers list instead + of adding it to the end. Note that more common handlers i.e. w/o "typ" + and " will be called first nevertheless. + :param system: call handler even if NodeProcessed Exception were raised + already. + """ + if not xmlns: + xmlns=self._owner.defaultNamespace + log.debug('Registering handler %s for "%s" type->%s ns->%s(%s)' % + (handler, name, typ, ns, xmlns)) + if not typ and not ns: + typ='default' + if xmlns not in self.handlers: + self.RegisterNamespace(xmlns, 'warn') + if name not in self.handlers[xmlns]: + self.RegisterProtocol(name, Protocol, xmlns, 'warn') + if typ+ns not in self.handlers[xmlns][name]: + self.handlers[xmlns][name][typ+ns]=[] + if makefirst: + self.handlers[xmlns][name][typ+ns].insert(0, {'func':handler, + 'system':system}) + else: + self.handlers[xmlns][name][typ+ns].append({'func':handler, + 'system':system}) - def RegisterHandlerOnce(self, name, handler, typ='', ns='', xmlns=None, - makefirst=0, system=0): - """ - Unregister handler after first call (not implemented yet) - """ - # FIXME Drop or implement - if not xmlns: - xmlns = self._owner.defaultNamespace - self.RegisterHandler(name, handler, typ, ns, xmlns, makefirst, system) + def RegisterHandlerOnce(self, name, handler, typ='', ns='', xmlns=None, + makefirst=0, system=0): + """ + Unregister handler after first call (not implemented yet) + """ + # FIXME Drop or implement + if not xmlns: + xmlns = self._owner.defaultNamespace + self.RegisterHandler(name, handler, typ, ns, xmlns, makefirst, system) - def UnregisterHandler(self, name, handler, typ='', ns='', xmlns=None): - """ - Unregister handler. "typ" and "ns" must be specified exactly the same as - with registering. - """ - if not xmlns: - xmlns = self._owner.defaultNamespace - if not typ and not ns: - typ='default' - if xmlns not in self.handlers: - return - if name not in self.handlers[xmlns]: - return - if typ+ns not in self.handlers[xmlns][name]: - return - for pack in self.handlers[xmlns][name][typ+ns]: - if pack['func'] == handler: - try: - self.handlers[xmlns][name][typ+ns].remove(pack) - except ValueError: - pass + def UnregisterHandler(self, name, handler, typ='', ns='', xmlns=None): + """ + Unregister handler. "typ" and "ns" must be specified exactly the same as + with registering. + """ + if not xmlns: + xmlns = self._owner.defaultNamespace + if not typ and not ns: + typ='default' + if xmlns not in self.handlers: + return + if name not in self.handlers[xmlns]: + return + if typ+ns not in self.handlers[xmlns][name]: + return + for pack in self.handlers[xmlns][name][typ+ns]: + if pack['func'] == handler: + try: + self.handlers[xmlns][name][typ+ns].remove(pack) + except ValueError: + pass - def RegisterDefaultHandler(self, handler): - """ - Specify the handler that will be used if no NodeProcessed exception were - raised. This is returnStanzaHandler by default. - """ - self._defaultHandler = handler + def RegisterDefaultHandler(self, handler): + """ + Specify the handler that will be used if no NodeProcessed exception were + raised. This is returnStanzaHandler by default. + """ + self._defaultHandler = handler - def RegisterEventHandler(self, handler): - """ - Register handler that will process events. F.e. "FILERECEIVED" event. See - common/connection: _event_dispatcher() - """ - self._eventHandler = handler + def RegisterEventHandler(self, handler): + """ + Register handler that will process events. F.e. "FILERECEIVED" event. See + common/connection: _event_dispatcher() + """ + self._eventHandler = handler - def returnStanzaHandler(self, conn, stanza): - """ - Return stanza back to the sender with error - set - """ - if stanza.getType() in ('get','set'): - conn._owner.send(Error(stanza, ERR_FEATURE_NOT_IMPLEMENTED)) + def returnStanzaHandler(self, conn, stanza): + """ + Return stanza back to the sender with error + set + """ + if stanza.getType() in ('get', 'set'): + conn._owner.send(Error(stanza, ERR_FEATURE_NOT_IMPLEMENTED)) - def RegisterCycleHandler(self, handler): - """ - Register handler that will be called on every Dispatcher.Process() call - """ - if handler not in self._cycleHandlers: - self._cycleHandlers.append(handler) + def RegisterCycleHandler(self, handler): + """ + Register handler that will be called on every Dispatcher.Process() call + """ + if handler not in self._cycleHandlers: + self._cycleHandlers.append(handler) - def UnregisterCycleHandler(self, handler): - """ - Unregister handler that will is called on every Dispatcher.Process() call - """ - if handler in self._cycleHandlers: - self._cycleHandlers.remove(handler) + def UnregisterCycleHandler(self, handler): + """ + Unregister handler that will is called on every Dispatcher.Process() call + """ + if handler in self._cycleHandlers: + self._cycleHandlers.remove(handler) - def Event(self, realm, event, data): - """ - Raise some event + def Event(self, realm, event, data): + """ + Raise some event - :param realm: scope of event. Usually a namespace. - :param event: the event itself. F.e. "SUCCESSFUL SEND". - :param data: data that comes along with event. Depends on event. - """ - if self._eventHandler: - self._eventHandler(realm, event, data) - else: - log.warning('Received unhandled event: %s' % event) + :param realm: scope of event. Usually a namespace. + :param event: the event itself. F.e. "SUCCESSFUL SEND". + :param data: data that comes along with event. Depends on event. + """ + if self._eventHandler: + self._eventHandler(realm, event, data) + else: + log.warning('Received unhandled event: %s' % event) - def dispatch(self, stanza, session=None, direct=0): - """ - Main procedure that performs XMPP stanza recognition and calling - apppropriate handlers for it. Called by simplexml - """ - # FIXME: Where do we set session and direct. Why? What are those intended - # to do? + def dispatch(self, stanza, session=None, direct=0): + """ + Main procedure that performs XMPP stanza recognition and calling + apppropriate handlers for it. Called by simplexml + """ + # FIXME: Where do we set session and direct. Why? What are those intended + # to do? - #log.info('dispatch called: stanza = %s, session = %s, direct= %s' - # % (stanza, session, direct)) - if not session: - session = self - session.Stream._mini_dom = None - name = stanza.getName() + #log.info('dispatch called: stanza = %s, session = %s, direct= %s' + # % (stanza, session, direct)) + if not session: + session = self + session.Stream._mini_dom = None + name = stanza.getName() - if name == 'features': - self._owner.got_features = True - session.Stream.features = stanza + if name == 'features': + self._owner.got_features = True + session.Stream.features = stanza - xmlns = stanza.getNamespace() + xmlns = stanza.getNamespace() - # log.info('in dispatch, getting ns for %s, and the ns is %s' - # % (stanza, xmlns)) - if xmlns not in self.handlers: - log.warn("Unknown namespace: " + xmlns) - xmlns = 'unknown' - # features stanza has been handled before - if name not in self.handlers[xmlns]: - if name != 'features': - log.warn("Unknown stanza: " + name) - else: - log.debug("Got %s/%s stanza" % (xmlns, name)) - name='unknown' - else: - log.debug("Got %s/%s stanza" % (xmlns, name)) + # log.info('in dispatch, getting ns for %s, and the ns is %s' + # % (stanza, xmlns)) + if xmlns not in self.handlers: + log.warn("Unknown namespace: " + xmlns) + xmlns = 'unknown' + # features stanza has been handled before + if name not in self.handlers[xmlns]: + if name != 'features': + log.warn("Unknown stanza: " + name) + else: + log.debug("Got %s/%s stanza" % (xmlns, name)) + name='unknown' + else: + log.debug("Got %s/%s stanza" % (xmlns, name)) - if stanza.__class__.__name__ == 'Node': - # FIXME: this cannot work - stanza=self.handlers[xmlns][name][type](node=stanza) + if stanza.__class__.__name__ == 'Node': + # FIXME: this cannot work + stanza=self.handlers[xmlns][name][type](node=stanza) - typ = stanza.getType() - if not typ: - typ = '' - stanza.props = stanza.getProperties() - ID = stanza.getID() + typ = stanza.getType() + if not typ: + typ = '' + stanza.props = stanza.getProperties() + ID = stanza.getID() - list_ = ['default'] # we will use all handlers: - if typ in self.handlers[xmlns][name]: - list_.append(typ) # from very common... - for prop in stanza.props: - if prop in self.handlers[xmlns][name]: - list_.append(prop) - if typ and typ+prop in self.handlers[xmlns][name]: - list_.append(typ+prop) # ...to very particular + list_ = ['default'] # we will use all handlers: + if typ in self.handlers[xmlns][name]: + list_.append(typ) # from very common... + for prop in stanza.props: + if prop in self.handlers[xmlns][name]: + list_.append(prop) + if typ and typ+prop in self.handlers[xmlns][name]: + list_.append(typ+prop) # ...to very particular - chain = self.handlers[xmlns]['default']['default'] - for key in list_: - if key: - chain = chain + self.handlers[xmlns][name][key] + chain = self.handlers[xmlns]['default']['default'] + for key in list_: + if key: + chain = chain + self.handlers[xmlns][name][key] - if ID in session._expected: - user = 0 - if isinstance(session._expected[ID], tuple): - cb, args = session._expected[ID] - log.debug("Expected stanza arrived. Callback %s(%s) found!" % - (cb, args)) - try: - cb(session,stanza,**args) - except Exception, typ: - if typ.__class__.__name__ != 'NodeProcessed': - raise - else: - log.debug("Expected stanza arrived!") - session._expected[ID] = stanza - else: - user = 1 - for handler in chain: - if user or handler['system']: - try: - handler['func'](session, stanza) - except Exception, typ: - if typ.__class__.__name__ != 'NodeProcessed': - self._pendingExceptions.insert(0, sys.exc_info()) - return - user=0 - if user and self._defaultHandler: - self._defaultHandler(session, stanza) + if ID in session._expected: + user = 0 + if isinstance(session._expected[ID], tuple): + cb, args = session._expected[ID] + log.debug("Expected stanza arrived. Callback %s(%s) found!" % + (cb, args)) + try: + cb(session,stanza,**args) + except Exception, typ: + if typ.__class__.__name__ != 'NodeProcessed': + raise + else: + log.debug("Expected stanza arrived!") + session._expected[ID] = stanza + else: + user = 1 + for handler in chain: + if user or handler['system']: + try: + handler['func'](session, stanza) + except Exception, typ: + if typ.__class__.__name__ != 'NodeProcessed': + self._pendingExceptions.insert(0, sys.exc_info()) + return + user=0 + if user and self._defaultHandler: + self._defaultHandler(session, stanza) - def _WaitForData(self, data): - """ - Internal wrapper around ProcessNonBlocking. Will check for - """ - if data is None: - return - res = self.ProcessNonBlocking(data) - # 0 result indicates that we have closed the connection, e.g. - # we have released dispatcher, so self._owner has no methods - if not res: - return - if 'remove_timeout' in self._owner.__dict__: - # When we receive data after we started disconnecting, Transport may - # already be plugged out - self._owner.remove_timeout() - for (_id, _iq) in self._expected.items(): - if _iq is None: - # If the expected Stanza would have arrived, ProcessNonBlocking - # would have placed the reply stanza in there - continue - if _id in self.on_responses: - if len(self._expected) == 1: - self._owner.onreceive(None) - resp, args = self.on_responses[_id] - del self.on_responses[_id] - if args is None: - resp(_iq) - else: - resp(self._owner, _iq, **args) - del self._expected[_id] + def _WaitForData(self, data): + """ + Internal wrapper around ProcessNonBlocking. Will check for + """ + if data is None: + return + res = self.ProcessNonBlocking(data) + # 0 result indicates that we have closed the connection, e.g. + # we have released dispatcher, so self._owner has no methods + if not res: + return + if 'remove_timeout' in self._owner.__dict__: + # When we receive data after we started disconnecting, Transport may + # already be plugged out + self._owner.remove_timeout() + for (_id, _iq) in self._expected.items(): + if _iq is None: + # If the expected Stanza would have arrived, ProcessNonBlocking + # would have placed the reply stanza in there + continue + if _id in self.on_responses: + if len(self._expected) == 1: + self._owner.onreceive(None) + resp, args = self.on_responses[_id] + del self.on_responses[_id] + if args is None: + resp(_iq) + else: + resp(self._owner, _iq, **args) + del self._expected[_id] - def SendAndWaitForResponse(self, stanza, timeout=None, func=None, args=None): - """ - Send stanza and wait for recipient's response to it. Will call transports - on_timeout callback if response is not retrieved in time + def SendAndWaitForResponse(self, stanza, timeout=None, func=None, args=None): + """ + Send stanza and wait for recipient's response to it. Will call transports + on_timeout callback if response is not retrieved in time - Be aware: Only timeout of latest call of SendAndWait is active. - """ - if timeout is None: - timeout = DEFAULT_TIMEOUT_SECONDS - _waitid = self.send(stanza) - if func: - self.on_responses[_waitid] = (func, args) - if timeout: - self._owner.set_timeout(timeout) - self._owner.onreceive(self._WaitForData) - self._expected[_waitid] = None - return _waitid + Be aware: Only timeout of latest call of SendAndWait is active. + """ + if timeout is None: + timeout = DEFAULT_TIMEOUT_SECONDS + _waitid = self.send(stanza) + if func: + self.on_responses[_waitid] = (func, args) + if timeout: + self._owner.set_timeout(timeout) + self._owner.onreceive(self._WaitForData) + self._expected[_waitid] = None + return _waitid - def SendAndCallForResponse(self, stanza, func=None, args=None): - """ - Put stanza on the wire and call back when recipient replies. Additional - callback arguments can be specified in args - """ - self.SendAndWaitForResponse(stanza, 0, func, args) + def SendAndCallForResponse(self, stanza, func=None, args=None): + """ + Put stanza on the wire and call back when recipient replies. Additional + callback arguments can be specified in args + """ + self.SendAndWaitForResponse(stanza, 0, func, args) - def send(self, stanza, now=False): - """ - Wrap transports send method when plugged into NonBlockingClient. Makes - sure stanzas get ID and from tag. - """ - ID = None - if type(stanza) not in [type(''), type(u'')]: - if isinstance(stanza, Protocol): - ID = stanza.getID() - if ID is None: - stanza.setID(self.getAnID()) - ID = stanza.getID() - if self._owner._registered_name and not stanza.getAttr('from'): - stanza.setAttr('from', self._owner._registered_name) - self._owner.Connection.send(stanza, now) - return ID + def send(self, stanza, now=False): + """ + Wrap transports send method when plugged into NonBlockingClient. Makes + sure stanzas get ID and from tag. + """ + ID = None + if type(stanza) not in [type(''), type(u'')]: + if isinstance(stanza, Protocol): + ID = stanza.getID() + if ID is None: + stanza.setID(self.getAnID()) + ID = stanza.getID() + if self._owner._registered_name and not stanza.getAttr('from'): + stanza.setAttr('from', self._owner._registered_name) + self._owner.Connection.send(stanza, now) + return ID class BOSHDispatcher(XMPPDispatcher): - def PlugIn(self, owner, after_SASL=False, old_features=None): - self.old_features = old_features - self.after_SASL = after_SASL - XMPPDispatcher.PlugIn(self, owner) + def PlugIn(self, owner, after_SASL=False, old_features=None): + self.old_features = old_features + self.after_SASL = after_SASL + XMPPDispatcher.PlugIn(self, owner) - def StreamInit(self): - """ - Send an initial stream header - """ - self.Stream = simplexml.NodeBuilder() - self.Stream.dispatch = self.dispatch - self.Stream._dispatch_depth = 2 - self.Stream.stream_header_received = self._check_stream_start - self.Stream.features = self.old_features + def StreamInit(self): + """ + Send an initial stream header + """ + self.Stream = simplexml.NodeBuilder() + self.Stream.dispatch = self.dispatch + self.Stream._dispatch_depth = 2 + self.Stream.stream_header_received = self._check_stream_start + self.Stream.features = self.old_features - self._metastream = Node('stream:stream') - self._metastream.setNamespace(self._owner.Namespace) - self._metastream.setAttr('version', '1.0') - self._metastream.setAttr('xmlns:stream', NS_STREAMS) - self._metastream.setAttr('to', self._owner.Server) - if locale.getdefaultlocale()[0]: - self._metastream.setAttr('xml:lang', - locale.getdefaultlocale()[0].split('_')[0]) + self._metastream = Node('stream:stream') + self._metastream.setNamespace(self._owner.Namespace) + self._metastream.setAttr('version', '1.0') + self._metastream.setAttr('xmlns:stream', NS_STREAMS) + self._metastream.setAttr('to', self._owner.Server) + if locale.getdefaultlocale()[0]: + self._metastream.setAttr('xml:lang', + locale.getdefaultlocale()[0].split('_')[0]) - self.restart = True - self._owner.Connection.send_init(after_SASL=self.after_SASL) + self.restart = True + self._owner.Connection.send_init(after_SASL=self.after_SASL) - def StreamTerminate(self): - """ - Send a stream terminator - """ - self._owner.Connection.send_terminator() + def StreamTerminate(self): + """ + Send a stream terminator + """ + self._owner.Connection.send_terminator() - def ProcessNonBlocking(self, data=None): - if self.restart: - fromstream = self._metastream - fromstream.setAttr('from', fromstream.getAttr('to')) - fromstream.delAttr('to') - data = '%s%s>%s' % (XML_DECLARATION,str(fromstream)[:-2] ,data) - self.restart = False - return XMPPDispatcher.ProcessNonBlocking(self, data) + def ProcessNonBlocking(self, data=None): + if self.restart: + fromstream = self._metastream + fromstream.setAttr('from', fromstream.getAttr('to')) + fromstream.delAttr('to') + data = '%s%s>%s' % (XML_DECLARATION, str(fromstream)[:-2], data) + self.restart = False + return XMPPDispatcher.ProcessNonBlocking(self, data) - def dispatch(self, stanza, session=None, direct=0): - if stanza.getName() == 'body' and stanza.getNamespace() == NS_HTTP_BIND: + def dispatch(self, stanza, session=None, direct=0): + if stanza.getName() == 'body' and stanza.getNamespace() == NS_HTTP_BIND: - stanza_attrs = stanza.getAttrs() - if 'authid' in stanza_attrs: - # should be only in init response - # auth module expects id of stream in document attributes - self.Stream._document_attrs['id'] = stanza_attrs['authid'] - self._owner.Connection.handle_body_attrs(stanza_attrs) + stanza_attrs = stanza.getAttrs() + if 'authid' in stanza_attrs: + # should be only in init response + # auth module expects id of stream in document attributes + self.Stream._document_attrs['id'] = stanza_attrs['authid'] + self._owner.Connection.handle_body_attrs(stanza_attrs) - children = stanza.getChildren() - if children: - for child in children: - # if child doesn't have any ns specified, simplexml (or expat) - # thinks it's of parent's (BOSH body) namespace, so we have to - # rewrite it to jabber:client - if child.getNamespace() == NS_HTTP_BIND: - child.setNamespace(self._owner.defaultNamespace) - XMPPDispatcher.dispatch(self, child, session, direct) - else: - XMPPDispatcher.dispatch(self, stanza, session, direct) - -# vim: se ts=3: + children = stanza.getChildren() + if children: + for child in children: + # if child doesn't have any ns specified, simplexml (or expat) + # thinks it's of parent's (BOSH body) namespace, so we have to + # rewrite it to jabber:client + if child.getNamespace() == NS_HTTP_BIND: + child.setNamespace(self._owner.defaultNamespace) + XMPPDispatcher.dispatch(self, child, session, direct) + else: + XMPPDispatcher.dispatch(self, stanza, session, direct) diff --git a/src/common/xmpp/features_nb.py b/src/common/xmpp/features_nb.py index a2eb669ca..19d216015 100644 --- a/src/common/xmpp/features_nb.py +++ b/src/common/xmpp/features_nb.py @@ -23,13 +23,13 @@ Different stuff that wasn't worth separating it into modules from protocol import NS_REGISTER, NS_PRIVACY, NS_DATA, Iq, isResultNode, Node def _on_default_response(disp, iq, cb): - def _on_response(resp): - if isResultNode(resp): - if cb: - cb(True) - elif cb: - cb(False) - disp.SendAndCallForResponse(iq, _on_response) + def _on_response(resp): + if isResultNode(resp): + if cb: + cb(True) + elif cb: + cb(False) + disp.SendAndCallForResponse(iq, _on_response) ############################################################################### ### Registration @@ -38,75 +38,75 @@ def _on_default_response(disp, iq, cb): REGISTER_DATA_RECEIVED = 'REGISTER DATA RECEIVED' def getRegInfo(disp, host, info={}, sync=True): - """ - Get registration form from remote host. Info dict can be prefilled - :param disp: plugged dispatcher instance - :param info: dict, like {'username':'joey'}. + """ + Get registration form from remote host. Info dict can be prefilled + :param disp: plugged dispatcher instance + :param info: dict, like {'username':'joey'}. - See JEP-0077 for details. - """ - iq=Iq('get',NS_REGISTER,to=host) - for i in info.keys(): - iq.setTagData(i,info[i]) - if sync: - disp.SendAndCallForResponse(iq, lambda resp: - _ReceivedRegInfo(disp.Dispatcher, resp, host)) - else: - disp.SendAndCallForResponse(iq, _ReceivedRegInfo, {'agent': host }) + See JEP-0077 for details. + """ + iq=Iq('get', NS_REGISTER, to=host) + for i in info.keys(): + iq.setTagData(i, info[i]) + if sync: + disp.SendAndCallForResponse(iq, lambda resp: + _ReceivedRegInfo(disp.Dispatcher, resp, host)) + else: + disp.SendAndCallForResponse(iq, _ReceivedRegInfo, {'agent': host }) def _ReceivedRegInfo(con, resp, agent): - Iq('get',NS_REGISTER,to=agent) - if not isResultNode(resp): - error_msg = resp.getErrorMsg() - con.Event(NS_REGISTER,REGISTER_DATA_RECEIVED,(agent,None,False,error_msg)) - return - tag=resp.getTag('query',namespace=NS_REGISTER) - if not tag: - error_msg = resp.getErrorMsg() - con.Event(NS_REGISTER,REGISTER_DATA_RECEIVED,(agent,None,False,error_msg)) - return - df=tag.getTag('x',namespace=NS_DATA) - if df: - con.Event(NS_REGISTER,REGISTER_DATA_RECEIVED,(agent,df,True,'')) - return - df={} - for i in resp.getQueryPayload(): - if not isinstance(i, Node): - continue - df[i.getName()] = i.getData() - con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent,df,False,'')) + Iq('get', NS_REGISTER, to=agent) + if not isResultNode(resp): + error_msg = resp.getErrorMsg() + con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent, None, False, error_msg)) + return + tag=resp.getTag('query', namespace=NS_REGISTER) + if not tag: + error_msg = resp.getErrorMsg() + con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent, None, False, error_msg)) + return + df=tag.getTag('x', namespace=NS_DATA) + if df: + con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent, df, True, '')) + return + df={} + for i in resp.getQueryPayload(): + if not isinstance(i, Node): + continue + df[i.getName()] = i.getData() + con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent, df, False, '')) def register(disp, host, info, cb, args=None): - """ - Perform registration on remote server with provided info + """ + Perform registration on remote server with provided info - If registration fails you can get additional info from the dispatcher's - owner attributes lastErrNode, lastErr and lastErrCode. - """ - iq=Iq('set', NS_REGISTER, to=host) - if not isinstance(info, dict): - info=info.asDict() - for i in info.keys(): - iq.setTag('query').setTagData(i,info[i]) - disp.SendAndCallForResponse(iq, cb, args) + If registration fails you can get additional info from the dispatcher's + owner attributes lastErrNode, lastErr and lastErrCode. + """ + iq=Iq('set', NS_REGISTER, to=host) + if not isinstance(info, dict): + info=info.asDict() + for i in info.keys(): + iq.setTag('query').setTagData(i, info[i]) + disp.SendAndCallForResponse(iq, cb, args) def unregister(disp, host, cb): - """ - Unregisters with host (permanently removes account). Returns true on success - """ - iq = Iq('set', NS_REGISTER, to=host, payload=[Node('remove')]) - _on_default_response(disp, iq, cb) + """ + Unregisters with host (permanently removes account). Returns true on success + """ + iq = Iq('set', NS_REGISTER, to=host, payload=[Node('remove')]) + _on_default_response(disp, iq, cb) def changePasswordTo(disp, newpassword, host=None, cb = None): - """ - Changes password on specified or current (if not specified) server. Returns - true on success. - """ - if not host: - host = disp._owner.Server - iq = Iq('set',NS_REGISTER,to=host, payload=[Node('username', - payload=[disp._owner.Server]),Node('password',payload=[newpassword])]) - _on_default_response(disp, iq, cb) + """ + Changes password on specified or current (if not specified) server. Returns + true on success. + """ + if not host: + host = disp._owner.Server + iq = Iq('set', NS_REGISTER, to=host, payload=[Node('username', + payload=[disp._owner.Server]), Node('password', payload=[newpassword])]) + _on_default_response(disp, iq, cb) ############################################################################### ### Privacy List @@ -123,97 +123,95 @@ PRIVACY_LIST_RECEIVED = 'PRIVACY LIST RECEIVED' PRIVACY_LISTS_ACTIVE_DEFAULT = 'PRIVACY LISTS ACTIVE DEFAULT' def getPrivacyLists(disp): - """ - Request privacy lists from connected server. Returns dictionary of existing - lists on success. - """ - iq = Iq('get', NS_PRIVACY) - def _on_response(resp): - dict_ = {'lists': []} - if not isResultNode(resp): - disp.Event(NS_PRIVACY, PRIVACY_LISTS_RECEIVED, (False)) - return - for list_ in resp.getQueryPayload(): - if list_.getName()=='list': - dict_['lists'].append(list_.getAttr('name')) - else: - dict_[list_.getName()]=list_.getAttr('name') - disp.Event(NS_PRIVACY, PRIVACY_LISTS_RECEIVED, (dict_)) - disp.SendAndCallForResponse(iq, _on_response) + """ + Request privacy lists from connected server. Returns dictionary of existing + lists on success. + """ + iq = Iq('get', NS_PRIVACY) + def _on_response(resp): + dict_ = {'lists': []} + if not isResultNode(resp): + disp.Event(NS_PRIVACY, PRIVACY_LISTS_RECEIVED, (False)) + return + for list_ in resp.getQueryPayload(): + if list_.getName()=='list': + dict_['lists'].append(list_.getAttr('name')) + else: + dict_[list_.getName()]=list_.getAttr('name') + disp.Event(NS_PRIVACY, PRIVACY_LISTS_RECEIVED, (dict_)) + disp.SendAndCallForResponse(iq, _on_response) def getActiveAndDefaultPrivacyLists(disp): - iq = Iq('get', NS_PRIVACY) - def _on_response(resp): - dict_ = {'active': '', 'default': ''} - if not isResultNode(resp): - disp.Event(NS_PRIVACY, PRIVACY_LISTS_ACTIVE_DEFAULT, (False)) - return - for list_ in resp.getQueryPayload(): - if list_.getName() == 'active': - dict_['active'] = list_.getAttr('name') - elif list_.getName() == 'default': - dict_['default'] = list_.getAttr('name') - disp.Event(NS_PRIVACY, PRIVACY_LISTS_ACTIVE_DEFAULT, (dict_)) - disp.SendAndCallForResponse(iq, _on_response) + iq = Iq('get', NS_PRIVACY) + def _on_response(resp): + dict_ = {'active': '', 'default': ''} + if not isResultNode(resp): + disp.Event(NS_PRIVACY, PRIVACY_LISTS_ACTIVE_DEFAULT, (False)) + return + for list_ in resp.getQueryPayload(): + if list_.getName() == 'active': + dict_['active'] = list_.getAttr('name') + elif list_.getName() == 'default': + dict_['default'] = list_.getAttr('name') + disp.Event(NS_PRIVACY, PRIVACY_LISTS_ACTIVE_DEFAULT, (dict_)) + disp.SendAndCallForResponse(iq, _on_response) def getPrivacyList(disp, listname): - """ - Request specific privacy list listname. Returns list of XML nodes (rules) - taken from the server responce. - """ - def _on_response(resp): - if not isResultNode(resp): - disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (False)) - return - disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (resp)) - iq = Iq('get', NS_PRIVACY, payload=[Node('list', {'name': listname})]) - disp.SendAndCallForResponse(iq, _on_response) + """ + Request specific privacy list listname. Returns list of XML nodes (rules) + taken from the server responce. + """ + def _on_response(resp): + if not isResultNode(resp): + disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (False)) + return + disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (resp)) + iq = Iq('get', NS_PRIVACY, payload=[Node('list', {'name': listname})]) + disp.SendAndCallForResponse(iq, _on_response) def setActivePrivacyList(disp, listname=None, typ='active', cb=None): - """ - Switch privacy list 'listname' to specified type. By default the type is - 'active'. Returns true on success. - """ - if listname: - attrs={'name':listname} - else: - attrs={} - iq = Iq('set',NS_PRIVACY,payload=[Node(typ,attrs)]) - _on_default_response(disp, iq, cb) + """ + Switch privacy list 'listname' to specified type. By default the type is + 'active'. Returns true on success. + """ + if listname: + attrs={'name':listname} + else: + attrs={} + iq = Iq('set', NS_PRIVACY, payload=[Node(typ, attrs)]) + _on_default_response(disp, iq, cb) def setDefaultPrivacyList(disp, listname=None): - """ - Set the default privacy list as 'listname'. Returns true on success - """ - return setActivePrivacyList(disp, listname,'default') + """ + Set the default privacy list as 'listname'. Returns true on success + """ + return setActivePrivacyList(disp, listname, 'default') def setPrivacyList(disp, listname, tags): - """ - Set the ruleset + """ + Set the ruleset - 'list' should be the simpleXML node formatted according to RFC 3921 - (XMPP-IM) I.e. Node('list',{'name':listname},payload=[...]). + 'list' should be the simpleXML node formatted according to RFC 3921 + (XMPP-IM) I.e. Node('list',{'name':listname},payload=[...]). - Returns true on success. - """ - iq = Iq('set', NS_PRIVACY, xmlns = '') - list_query = iq.getTag('query').setTag('list', {'name': listname}) - for item in tags: - if 'type' in item and 'value' in item: - item_tag = list_query.setTag('item', {'action': item['action'], - 'order': item['order'], 'type': item['type'], - 'value': item['value']}) - else: - item_tag = list_query.setTag('item', {'action': item['action'], - 'order': item['order']}) - if 'child' in item: - for child_tag in item['child']: - item_tag.setTag(child_tag) - _on_default_response(disp, iq, None) + Returns true on success. + """ + iq = Iq('set', NS_PRIVACY, xmlns = '') + list_query = iq.getTag('query').setTag('list', {'name': listname}) + for item in tags: + if 'type' in item and 'value' in item: + item_tag = list_query.setTag('item', {'action': item['action'], + 'order': item['order'], 'type': item['type'], + 'value': item['value']}) + else: + item_tag = list_query.setTag('item', {'action': item['action'], + 'order': item['order']}) + if 'child' in item: + for child_tag in item['child']: + item_tag.setTag(child_tag) + _on_default_response(disp, iq, None) def delPrivacyList(disp, listname, cb=None): - ''' Deletes privacy list 'listname'. Returns true on success. ''' - iq = Iq('set',NS_PRIVACY,payload=[Node('list',{'name':listname})]) - _on_default_response(disp, iq, cb) - -# vim: se ts=3: + ''' Deletes privacy list 'listname'. Returns true on success. ''' + iq = Iq('set', NS_PRIVACY, payload=[Node('list', {'name':listname})]) + _on_default_response(disp, iq, cb) diff --git a/src/common/xmpp/idlequeue.py b/src/common/xmpp/idlequeue.py index 8d61159da..5284ad8c7 100644 --- a/src/common/xmpp/idlequeue.py +++ b/src/common/xmpp/idlequeue.py @@ -25,524 +25,521 @@ log = logging.getLogger('gajim.c.x.idlequeue') # needed for get_idleqeue try: - import gobject - HAVE_GOBJECT = True + import gobject + HAVE_GOBJECT = True except ImportError: - HAVE_GOBJECT = False + HAVE_GOBJECT = False # needed for idlecommand if os.name == 'nt': - from subprocess import * # python24 only. we ask this for Windows + from subprocess import * # python24 only. we ask this for Windows elif os.name == 'posix': - import fcntl + import fcntl -FLAG_WRITE = 20 # write only -FLAG_READ = 19 # read only -FLAG_READ_WRITE = 23 # read and write -FLAG_CLOSE = 16 # wait for close +FLAG_WRITE = 20 # write only +FLAG_READ = 19 # read only +FLAG_READ_WRITE = 23 # read and write +FLAG_CLOSE = 16 # wait for close -PENDING_READ = 3 # waiting read event -PENDING_WRITE = 4 # waiting write event -IS_CLOSED = 16 # channel closed +PENDING_READ = 3 # waiting read event +PENDING_WRITE = 4 # waiting write event +IS_CLOSED = 16 # channel closed def get_idlequeue(): - """ - Get an appropriate idlequeue - """ - if os.name == 'nt': - # gobject.io_add_watch does not work on windows - return SelectIdleQueue() - else: - if HAVE_GOBJECT: - # Gajim's default Idlequeue - return GlibIdleQueue() - else: - # GUI less implementation - return SelectIdleQueue() + """ + Get an appropriate idlequeue + """ + if os.name == 'nt': + # gobject.io_add_watch does not work on windows + return SelectIdleQueue() + else: + if HAVE_GOBJECT: + # Gajim's default Idlequeue + return GlibIdleQueue() + else: + # GUI less implementation + return SelectIdleQueue() class IdleObject: - """ - Idle listener interface. Listed methods are called by IdleQueue. - """ + """ + Idle listener interface. Listed methods are called by IdleQueue. + """ - def __init__(self): - self.fd = -1 #: filedescriptor, must be unique for each IdleObject + def __init__(self): + self.fd = -1 #: filedescriptor, must be unique for each IdleObject - def pollend(self): - """ - Called on stream failure - """ - pass + def pollend(self): + """ + Called on stream failure + """ + pass - def pollin(self): - """ - Called on new read event - """ - pass + def pollin(self): + """ + Called on new read event + """ + pass - def pollout(self): - """ - Called on new write event (connect in sockets is a pollout) - """ - pass + def pollout(self): + """ + Called on new write event (connect in sockets is a pollout) + """ + pass - def read_timeout(self): - """ - Called when timeout happened - """ - pass + def read_timeout(self): + """ + Called when timeout happened + """ + pass class IdleCommand(IdleObject): - """ - Can be subclassed to execute commands asynchronously by the idlequeue. - Result will be optained via file descriptor of created pipe - """ + """ + Can be subclassed to execute commands asynchronously by the idlequeue. + Result will be optained via file descriptor of created pipe + """ - def __init__(self, on_result): - IdleObject.__init__(self) - # how long (sec.) to wait for result ( 0 - forever ) - # it is a class var, instead of a constant and we can override it. - self.commandtimeout = 0 - # when we have some kind of result (valid, ot not) we call this handler - self.result_handler = on_result - # if it is True, we can safetely execute the command - self.canexecute = True - self.idlequeue = None - self.result ='' + def __init__(self, on_result): + IdleObject.__init__(self) + # how long (sec.) to wait for result ( 0 - forever ) + # it is a class var, instead of a constant and we can override it. + self.commandtimeout = 0 + # when we have some kind of result (valid, ot not) we call this handler + self.result_handler = on_result + # if it is True, we can safetely execute the command + self.canexecute = True + self.idlequeue = None + self.result ='' - def set_idlequeue(self, idlequeue): - self.idlequeue = idlequeue + def set_idlequeue(self, idlequeue): + self.idlequeue = idlequeue - def _return_result(self): - if self.result_handler: - self.result_handler(self.result) - self.result_handler = None + def _return_result(self): + if self.result_handler: + self.result_handler(self.result) + self.result_handler = None - def _compose_command_args(self): - return ['echo', 'da'] + def _compose_command_args(self): + return ['echo', 'da'] - def _compose_command_line(self): - """ - Return one line representation of command and its arguments - """ - return reduce(lambda left, right: left + ' ' + right, - self._compose_command_args()) + def _compose_command_line(self): + """ + Return one line representation of command and its arguments + """ + return reduce(lambda left, right: left + ' ' + right, + self._compose_command_args()) - def wait_child(self): - if self.pipe.poll() is None: - # result timeout - if self.endtime < self.idlequeue.current_time(): - self._return_result() - self.pipe.stdout.close() - self.pipe.stdin.close() - else: - # child is still active, continue to wait - self.idlequeue.set_alarm(self.wait_child, 0.1) - else: - # child has quit - self.result = self.pipe.stdout.read() - self._return_result() - self.pipe.stdout.close() - self.pipe.stdin.close() + def wait_child(self): + if self.pipe.poll() is None: + # result timeout + if self.endtime < self.idlequeue.current_time(): + self._return_result() + self.pipe.stdout.close() + self.pipe.stdin.close() + else: + # child is still active, continue to wait + self.idlequeue.set_alarm(self.wait_child, 0.1) + else: + # child has quit + self.result = self.pipe.stdout.read() + self._return_result() + self.pipe.stdout.close() + self.pipe.stdin.close() - def start(self): - if not self.canexecute: - self.result = '' - self._return_result() - return - if os.name == 'nt': - self._start_nt() - elif os.name == 'posix': - self._start_posix() + def start(self): + if not self.canexecute: + self.result = '' + self._return_result() + return + if os.name == 'nt': + self._start_nt() + elif os.name == 'posix': + self._start_posix() - def _start_nt(self): - # if gajim is started from noninteraactive shells stdin is closed and - # cannot be forwarded, so we have to keep it open - self.pipe = Popen(self._compose_command_args(), stdout=PIPE, - bufsize = 1024, shell = True, stderr = STDOUT, stdin = PIPE) - if self.commandtimeout >= 0: - self.endtime = self.idlequeue.current_time() + self.commandtimeout - self.idlequeue.set_alarm(self.wait_child, 0.1) + def _start_nt(self): + # if gajim is started from noninteraactive shells stdin is closed and + # cannot be forwarded, so we have to keep it open + self.pipe = Popen(self._compose_command_args(), stdout=PIPE, + bufsize = 1024, shell = True, stderr = STDOUT, stdin = PIPE) + if self.commandtimeout >= 0: + self.endtime = self.idlequeue.current_time() + self.commandtimeout + self.idlequeue.set_alarm(self.wait_child, 0.1) - def _start_posix(self): - self.pipe = os.popen(self._compose_command_line()) - self.fd = self.pipe.fileno() - fcntl.fcntl(self.pipe, fcntl.F_SETFL, os.O_NONBLOCK) - self.idlequeue.plug_idle(self, False, True) - if self.commandtimeout >= 0: - self.idlequeue.set_read_timeout(self.fd, self.commandtimeout) + def _start_posix(self): + self.pipe = os.popen(self._compose_command_line()) + self.fd = self.pipe.fileno() + fcntl.fcntl(self.pipe, fcntl.F_SETFL, os.O_NONBLOCK) + self.idlequeue.plug_idle(self, False, True) + if self.commandtimeout >= 0: + self.idlequeue.set_read_timeout(self.fd, self.commandtimeout) - def end(self): - self.idlequeue.unplug_idle(self.fd) - try: - self.pipe.close() - except: - pass + def end(self): + self.idlequeue.unplug_idle(self.fd) + try: + self.pipe.close() + except: + pass - def pollend(self): - self.idlequeue.remove_timeout(self.fd) - self.end() - self._return_result() + def pollend(self): + self.idlequeue.remove_timeout(self.fd) + self.end() + self._return_result() - def pollin(self): - try: - res = self.pipe.read() - except Exception, e: - res = '' - if res == '': - return self.pollend() - else: - self.result += res + def pollin(self): + try: + res = self.pipe.read() + except Exception, e: + res = '' + if res == '': + return self.pollend() + else: + self.result += res - def read_timeout(self): - self.end() - self._return_result() + def read_timeout(self): + self.end() + self._return_result() class IdleQueue: - """ - IdleQueue provide three distinct time based features. Uses select.poll() + """ + IdleQueue provide three distinct time based features. Uses select.poll() - 1. Alarm timeout: Execute a callback after foo seconds - 2. Timeout event: Call read_timeout() of an plugged object if a timeout - has been set, but not removed in time. - 3. Check file descriptor of plugged objects for read, write and error - events - """ + 1. Alarm timeout: Execute a callback after foo seconds + 2. Timeout event: Call read_timeout() of an plugged object if a timeout + has been set, but not removed in time. + 3. Check file descriptor of plugged objects for read, write and error + events + """ - # (timeout, boolean) - # Boolean is True if timeout is specified in seconds, False means miliseconds - PROCESS_TIMEOUT = (200, False) + # (timeout, boolean) + # Boolean is True if timeout is specified in seconds, False means miliseconds + PROCESS_TIMEOUT = (200, False) - def __init__(self): - self.queue = {} + def __init__(self): + self.queue = {} - # when there is a timeout it executes obj.read_timeout() - # timeout is not removed automatically! - # {fd1: {timeout1: func1, timeout2: func2}} - # timout are unique (timeout1 must be != timeout2) - # If func1 is None, read_time function is called - self.read_timeouts = {} + # when there is a timeout it executes obj.read_timeout() + # timeout is not removed automatically! + # {fd1: {timeout1: func1, timeout2: func2}} + # timout are unique (timeout1 must be != timeout2) + # If func1 is None, read_time function is called + self.read_timeouts = {} - # cb, which are executed after XX sec., alarms are removed automatically - self.alarms = {} - self._init_idle() + # cb, which are executed after XX sec., alarms are removed automatically + self.alarms = {} + self._init_idle() - def _init_idle(self): - """ - Hook method for subclassed. Will be called by __init__ - """ - self.selector = select.poll() + def _init_idle(self): + """ + Hook method for subclassed. Will be called by __init__ + """ + self.selector = select.poll() - def set_alarm(self, alarm_cb, seconds): - """ - Set up a new alarm. alarm_cb will be called after specified seconds. - """ - alarm_time = self.current_time() + seconds - # almost impossible, but in case we have another alarm_cb at this time - if alarm_time in self.alarms: - self.alarms[alarm_time].append(alarm_cb) - else: - self.alarms[alarm_time] = [alarm_cb] - return alarm_time + def set_alarm(self, alarm_cb, seconds): + """ + Set up a new alarm. alarm_cb will be called after specified seconds. + """ + alarm_time = self.current_time() + seconds + # almost impossible, but in case we have another alarm_cb at this time + if alarm_time in self.alarms: + self.alarms[alarm_time].append(alarm_cb) + else: + self.alarms[alarm_time] = [alarm_cb] + return alarm_time - def remove_alarm(self, alarm_cb, alarm_time): - """ - Remove alarm callback alarm_cb scheduled on alarm_time. Returns True if - it was removed sucessfully, otherwise False - """ - if not alarm_time in self.alarms: - return False - i = -1 - for i in range(len(self.alarms[alarm_time])): - # let's not modify the list inside the loop - if self.alarms[alarm_time][i] is alarm_cb: - break - if i != -1: - del self.alarms[alarm_time][i] - if self.alarms[alarm_time] == []: - del self.alarms[alarm_time] - return True - else: - return False + def remove_alarm(self, alarm_cb, alarm_time): + """ + Remove alarm callback alarm_cb scheduled on alarm_time. Returns True if + it was removed sucessfully, otherwise False + """ + if not alarm_time in self.alarms: + return False + i = -1 + for i in range(len(self.alarms[alarm_time])): + # let's not modify the list inside the loop + if self.alarms[alarm_time][i] is alarm_cb: + break + if i != -1: + del self.alarms[alarm_time][i] + if self.alarms[alarm_time] == []: + del self.alarms[alarm_time] + return True + else: + return False - def remove_timeout(self, fd, timeout=None): - """ - Remove the read timeout - """ - log.info('read timeout removed for fd %s' % fd) - if fd in self.read_timeouts: - if timeout: - if timeout in self.read_timeouts[fd]: - del(self.read_timeouts[fd][timeout]) - if len(self.read_timeouts[fd]) == 0: - del(self.read_timeouts[fd]) - else: - del(self.read_timeouts[fd]) + def remove_timeout(self, fd, timeout=None): + """ + Remove the read timeout + """ + log.info('read timeout removed for fd %s' % fd) + if fd in self.read_timeouts: + if timeout: + if timeout in self.read_timeouts[fd]: + del(self.read_timeouts[fd][timeout]) + if len(self.read_timeouts[fd]) == 0: + del(self.read_timeouts[fd]) + else: + del(self.read_timeouts[fd]) - def set_read_timeout(self, fd, seconds, func=None): - """ - Seta a new timeout. If it is not removed after specified seconds, - func or obj.read_timeout() will be called + def set_read_timeout(self, fd, seconds, func=None): + """ + Seta a new timeout. If it is not removed after specified seconds, + func or obj.read_timeout() will be called - A filedescriptor fd can have several timeouts. - """ - log_txt = 'read timeout set for fd %s on %s seconds' % (fd, seconds) - if func: - log_txt += ' with function ' + str(func) - log.info(log_txt) - timeout = self.current_time() + seconds - if fd in self.read_timeouts: - self.read_timeouts[fd][timeout] = func - else: - self.read_timeouts[fd] = {timeout: func} + A filedescriptor fd can have several timeouts. + """ + log_txt = 'read timeout set for fd %s on %s seconds' % (fd, seconds) + if func: + log_txt += ' with function ' + str(func) + log.info(log_txt) + timeout = self.current_time() + seconds + if fd in self.read_timeouts: + self.read_timeouts[fd][timeout] = func + else: + self.read_timeouts[fd] = {timeout: func} - def _check_time_events(self): - """ - Execute and remove alarm callbacks and execute func() or read_timeout() - for plugged objects if specified time has ellapsed - """ - current_time = self.current_time() + def _check_time_events(self): + """ + Execute and remove alarm callbacks and execute func() or read_timeout() + for plugged objects if specified time has ellapsed + """ + current_time = self.current_time() - for fd, timeouts in self.read_timeouts.items(): - if fd not in self.queue: - self.remove_timeout(fd) - continue - for timeout, func in timeouts.items(): - if timeout > current_time: - continue - if func: - log.debug('Calling %s for fd %s' % (func, fd)) - func() - else: - log.debug('Calling read_timeout for fd %s' % fd) - self.queue[fd].read_timeout() - self.remove_timeout(fd, timeout) + for fd, timeouts in self.read_timeouts.items(): + if fd not in self.queue: + self.remove_timeout(fd) + continue + for timeout, func in timeouts.items(): + if timeout > current_time: + continue + if func: + log.debug('Calling %s for fd %s' % (func, fd)) + func() + else: + log.debug('Calling read_timeout for fd %s' % fd) + self.queue[fd].read_timeout() + self.remove_timeout(fd, timeout) - times = self.alarms.keys() - for alarm_time in times: - if alarm_time > current_time: - continue - if alarm_time in self.alarms: - for callback in self.alarms[alarm_time]: - callback() - if alarm_time in self.alarms: - del(self.alarms[alarm_time]) + times = self.alarms.keys() + for alarm_time in times: + if alarm_time > current_time: + continue + if alarm_time in self.alarms: + for callback in self.alarms[alarm_time]: + callback() + if alarm_time in self.alarms: + del(self.alarms[alarm_time]) - def plug_idle(self, obj, writable=True, readable=True): - """ - Plug an IdleObject into idlequeue. Filedescriptor fd must be set + def plug_idle(self, obj, writable=True, readable=True): + """ + Plug an IdleObject into idlequeue. Filedescriptor fd must be set - :param obj: the IdleObject - :param writable: True if obj has data to sent - :param readable: True if obj expects data to be reiceived - """ - if obj.fd == -1: - return - if obj.fd in self.queue: - self.unplug_idle(obj.fd) - self.queue[obj.fd] = obj - if writable: - if not readable: - flags = FLAG_WRITE - else: - flags = FLAG_READ_WRITE - else: - if readable: - flags = FLAG_READ - else: - # when we paused a FT, we expect only a close event - flags = FLAG_CLOSE - self._add_idle(obj.fd, flags) + :param obj: the IdleObject + :param writable: True if obj has data to sent + :param readable: True if obj expects data to be reiceived + """ + if obj.fd == -1: + return + if obj.fd in self.queue: + self.unplug_idle(obj.fd) + self.queue[obj.fd] = obj + if writable: + if not readable: + flags = FLAG_WRITE + else: + flags = FLAG_READ_WRITE + else: + if readable: + flags = FLAG_READ + else: + # when we paused a FT, we expect only a close event + flags = FLAG_CLOSE + self._add_idle(obj.fd, flags) - def _add_idle(self, fd, flags): - """ - Hook method for subclasses, called by plug_idle - """ - self.selector.register(fd, flags) + def _add_idle(self, fd, flags): + """ + Hook method for subclasses, called by plug_idle + """ + self.selector.register(fd, flags) - def unplug_idle(self, fd): - """ - Remove plugged IdleObject, specified by filedescriptor fd - """ - if fd in self.queue: - del(self.queue[fd]) - self._remove_idle(fd) + def unplug_idle(self, fd): + """ + Remove plugged IdleObject, specified by filedescriptor fd + """ + if fd in self.queue: + del(self.queue[fd]) + self._remove_idle(fd) - def current_time(self): - from time import time - return time() + def current_time(self): + from time import time + return time() - def _remove_idle(self, fd): - """ - Hook method for subclassed, called by unplug_idle - """ - self.selector.unregister(fd) + def _remove_idle(self, fd): + """ + Hook method for subclassed, called by unplug_idle + """ + self.selector.unregister(fd) - def _process_events(self, fd, flags): - obj = self.queue.get(fd) - if obj is None: - self.unplug_idle(fd) - return False + def _process_events(self, fd, flags): + obj = self.queue.get(fd) + if obj is None: + self.unplug_idle(fd) + return False - if flags & PENDING_READ: - #print 'waiting read on %d, flags are %d' % (fd, flags) - obj.pollin() - return True + if flags & PENDING_READ: + #print 'waiting read on %d, flags are %d' % (fd, flags) + obj.pollin() + return True - elif flags & PENDING_WRITE: - obj.pollout() - return True + elif flags & PENDING_WRITE: + obj.pollout() + return True - elif flags & IS_CLOSED: - # io error, don't expect more events - self.remove_timeout(obj.fd) - self.unplug_idle(obj.fd) - obj.pollend() - return False + elif flags & IS_CLOSED: + # io error, don't expect more events + self.remove_timeout(obj.fd) + self.unplug_idle(obj.fd) + obj.pollend() + return False - def process(self): - """ - Process idlequeue. Check for any pending timeout or alarm events. Call - IdleObjects on possible and requested read, write and error events on - their file descriptors + def process(self): + """ + Process idlequeue. Check for any pending timeout or alarm events. Call + IdleObjects on possible and requested read, write and error events on + their file descriptors - Call this in regular intervals. - """ - if not self.queue: - # check for timeouts/alert also when there are no active fds - self._check_time_events() - return True - try: - waiting_descriptors = self.selector.poll(0) - except select.error, e: - waiting_descriptors = [] - if e[0] != 4: # interrupt - raise - for fd, flags in waiting_descriptors: - self._process_events(fd, flags) - self._check_time_events() - return True + Call this in regular intervals. + """ + if not self.queue: + # check for timeouts/alert also when there are no active fds + self._check_time_events() + return True + try: + waiting_descriptors = self.selector.poll(0) + except select.error, e: + waiting_descriptors = [] + if e[0] != 4: # interrupt + raise + for fd, flags in waiting_descriptors: + self._process_events(fd, flags) + self._check_time_events() + return True class SelectIdleQueue(IdleQueue): - """ - Extends IdleQueue to use select.select() for polling + """ + Extends IdleQueue to use select.select() for polling - This class exisists for the sake of gtk2.8 on windows, which doesn't seem to - support io_add_watch properly (yet) - """ + This class exisists for the sake of gtk2.8 on windows, which doesn't seem to + support io_add_watch properly (yet) + """ - def _init_idle(self): - """ - Create a dict, which maps file/pipe/sock descriptor to glib event id - """ - self.read_fds = {} - self.write_fds = {} - self.error_fds = {} + def _init_idle(self): + """ + Create a dict, which maps file/pipe/sock descriptor to glib event id + """ + self.read_fds = {} + self.write_fds = {} + self.error_fds = {} - def _add_idle(self, fd, flags): - """ - This method is called when we plug a new idle object. Remove descriptor - to read/write/error lists, according flags - """ - if flags & 3: - self.read_fds[fd] = fd - if flags & 4: - self.write_fds[fd] = fd - self.error_fds[fd] = fd + def _add_idle(self, fd, flags): + """ + This method is called when we plug a new idle object. Remove descriptor + to read/write/error lists, according flags + """ + if flags & 3: + self.read_fds[fd] = fd + if flags & 4: + self.write_fds[fd] = fd + self.error_fds[fd] = fd - def _remove_idle(self, fd): - """ - This method is called when we unplug a new idle object. Remove descriptor - from read/write/error lists - """ - if fd in self.read_fds: - del(self.read_fds[fd]) - if fd in self.write_fds: - del(self.write_fds[fd]) - if fd in self.error_fds: - del(self.error_fds[fd]) + def _remove_idle(self, fd): + """ + This method is called when we unplug a new idle object. Remove descriptor + from read/write/error lists + """ + if fd in self.read_fds: + del(self.read_fds[fd]) + if fd in self.write_fds: + del(self.write_fds[fd]) + if fd in self.error_fds: + del(self.error_fds[fd]) - def process(self): - if not self.write_fds and not self.read_fds: - self._check_time_events() - return True - try: - waiting_descriptors = select.select(self.read_fds.keys(), - self.write_fds.keys(), self.error_fds.keys(), 0) - except select.error, e: - waiting_descriptors = ((),(),()) - if e[0] != 4: # interrupt - raise - for fd in waiting_descriptors[0]: - q = self.queue.get(fd) - if q: - q.pollin() - for fd in waiting_descriptors[1]: - q = self.queue.get(fd) - if q: - q.pollout() - for fd in waiting_descriptors[2]: - q = self.queue.get(fd) - if q: - q.pollend() - self._check_time_events() - return True + def process(self): + if not self.write_fds and not self.read_fds: + self._check_time_events() + return True + try: + waiting_descriptors = select.select(self.read_fds.keys(), + self.write_fds.keys(), self.error_fds.keys(), 0) + except select.error, e: + waiting_descriptors = ((), (), ()) + if e[0] != 4: # interrupt + raise + for fd in waiting_descriptors[0]: + q = self.queue.get(fd) + if q: + q.pollin() + for fd in waiting_descriptors[1]: + q = self.queue.get(fd) + if q: + q.pollout() + for fd in waiting_descriptors[2]: + q = self.queue.get(fd) + if q: + q.pollend() + self._check_time_events() + return True class GlibIdleQueue(IdleQueue): - """ - Extends IdleQueue to use glib io_add_wath, instead of select/poll In another - 'non gui' implementation of Gajim IdleQueue can be used safetly - """ + """ + Extends IdleQueue to use glib io_add_wath, instead of select/poll In another + 'non gui' implementation of Gajim IdleQueue can be used safetly + """ - # (timeout, boolean) - # Boolean is True if timeout is specified in seconds, False means miliseconds - PROCESS_TIMEOUT = (2, True) + # (timeout, boolean) + # Boolean is True if timeout is specified in seconds, False means miliseconds + PROCESS_TIMEOUT = (2, True) - def _init_idle(self): - """ - Creates a dict, which maps file/pipe/sock descriptor to glib event id - """ - self.events = {} - # time() is already called in glib, we just get the last value - # overrides IdleQueue.current_time() - self.current_time = gobject.get_current_time + def _init_idle(self): + """ + Creates a dict, which maps file/pipe/sock descriptor to glib event id + """ + self.events = {} + # time() is already called in glib, we just get the last value + # overrides IdleQueue.current_time() + self.current_time = gobject.get_current_time - def _add_idle(self, fd, flags): - """ - This method is called when we plug a new idle object. Start listening for - events from fd - """ - res = gobject.io_add_watch(fd, flags, self._process_events, - priority=gobject.PRIORITY_LOW) - # store the id of the watch, so that we can remove it on unplug - self.events[fd] = res + def _add_idle(self, fd, flags): + """ + This method is called when we plug a new idle object. Start listening for + events from fd + """ + res = gobject.io_add_watch(fd, flags, self._process_events, + priority=gobject.PRIORITY_LOW) + # store the id of the watch, so that we can remove it on unplug + self.events[fd] = res - def _process_events(self, fd, flags): - try: - return IdleQueue._process_events(self, fd, flags) - except Exception: - self._remove_idle(fd) - self._add_idle(fd, flags) - raise + def _process_events(self, fd, flags): + try: + return IdleQueue._process_events(self, fd, flags) + except Exception: + self._remove_idle(fd) + self._add_idle(fd, flags) + raise - def _remove_idle(self, fd): - """ - This method is called when we unplug a new idle object. Stop listening - for events from fd - """ - if not fd in self.events: - return - gobject.source_remove(self.events[fd]) - del(self.events[fd]) + def _remove_idle(self, fd): + """ + This method is called when we unplug a new idle object. Stop listening + for events from fd + """ + if not fd in self.events: + return + gobject.source_remove(self.events[fd]) + del(self.events[fd]) - def process(self): - self._check_time_events() - - -# vim: se ts=3: + def process(self): + self._check_time_events() diff --git a/src/common/xmpp/plugin.py b/src/common/xmpp/plugin.py index 2e4368eb2..5ef82c931 100644 --- a/src/common/xmpp/plugin.py +++ b/src/common/xmpp/plugin.py @@ -22,77 +22,75 @@ import logging log = logging.getLogger('gajim.c.x.plugin') class PlugIn: - """ - Abstract xmpppy plugin infrastructure code, providing plugging in/out and - debugging functionality + """ + Abstract xmpppy plugin infrastructure code, providing plugging in/out and + debugging functionality - Inherit to develop pluggable objects. No code change on the owner class - required (the object where we plug into) + Inherit to develop pluggable objects. No code change on the owner class + required (the object where we plug into) - For every instance of PlugIn class the 'owner' is the class in what the plug - was plugged. - """ + For every instance of PlugIn class the 'owner' is the class in what the plug + was plugged. + """ - def __init__(self): - self._exported_methods=[] + def __init__(self): + self._exported_methods=[] - def PlugIn(self, owner): - """ - Attach to owner and register ourself and our _exported_methods in it. - If defined by a subclass, call self.plugin(owner) to execute hook - code after plugging - """ - self._owner=owner - log.info('Plugging %s __INTO__ %s' % (self, self._owner)) - if self.__class__.__name__ in owner.__dict__: - log.debug('Plugging ignored: another instance already plugged.') - return - self._old_owners_methods=[] - for method in self._exported_methods: - if method.__name__ in owner.__dict__: - self._old_owners_methods.append(owner.__dict__[method.__name__]) - owner.__dict__[method.__name__]=method - if self.__class__.__name__.endswith('Dispatcher'): - # FIXME: I need BOSHDispatcher or XMPPDispatcher on .Dispatcher - # there must be a better way.. - owner.__dict__['Dispatcher']=self - else: - owner.__dict__[self.__class__.__name__]=self + def PlugIn(self, owner): + """ + Attach to owner and register ourself and our _exported_methods in it. + If defined by a subclass, call self.plugin(owner) to execute hook + code after plugging + """ + self._owner=owner + log.info('Plugging %s __INTO__ %s' % (self, self._owner)) + if self.__class__.__name__ in owner.__dict__: + log.debug('Plugging ignored: another instance already plugged.') + return + self._old_owners_methods=[] + for method in self._exported_methods: + if method.__name__ in owner.__dict__: + self._old_owners_methods.append(owner.__dict__[method.__name__]) + owner.__dict__[method.__name__]=method + if self.__class__.__name__.endswith('Dispatcher'): + # FIXME: I need BOSHDispatcher or XMPPDispatcher on .Dispatcher + # there must be a better way.. + owner.__dict__['Dispatcher']=self + else: + owner.__dict__[self.__class__.__name__]=self - # Execute hook - if hasattr(self,'plugin'): - return self.plugin(owner) + # Execute hook + if hasattr(self, 'plugin'): + return self.plugin(owner) - def PlugOut(self): - """ - Unregister our _exported_methods from owner and detach from it. - If defined by a subclass, call self.plugout() after unplugging to execute - hook code - """ - log.info('Plugging %s __OUT__ of %s.' % (self, self._owner)) - for method in self._exported_methods: - del self._owner.__dict__[method.__name__] - for method in self._old_owners_methods: - self._owner.__dict__[method.__name__]=method - # FIXME: Dispatcher workaround - if self.__class__.__name__.endswith('Dispatcher'): - del self._owner.__dict__['Dispatcher'] - else: - del self._owner.__dict__[self.__class__.__name__] - # Execute hook - if hasattr(self,'plugout'): - return self.plugout() - del self._owner + def PlugOut(self): + """ + Unregister our _exported_methods from owner and detach from it. + If defined by a subclass, call self.plugout() after unplugging to execute + hook code + """ + log.info('Plugging %s __OUT__ of %s.' % (self, self._owner)) + for method in self._exported_methods: + del self._owner.__dict__[method.__name__] + for method in self._old_owners_methods: + self._owner.__dict__[method.__name__]=method + # FIXME: Dispatcher workaround + if self.__class__.__name__.endswith('Dispatcher'): + del self._owner.__dict__['Dispatcher'] + else: + del self._owner.__dict__[self.__class__.__name__] + # Execute hook + if hasattr(self, 'plugout'): + return self.plugout() + del self._owner - @classmethod - def get_instance(cls, *args, **kwargs): - """ - Factory Method for object creation + @classmethod + def get_instance(cls, *args, **kwargs): + """ + Factory Method for object creation - Use this instead of directly initializing the class in order to make - unit testing easier. For testing, this method can be patched to inject - mock objects. - """ - return cls(*args, **kwargs) - -# vim: se ts=3: + Use this instead of directly initializing the class in order to make + unit testing easier. For testing, this method can be patched to inject + mock objects. + """ + return cls(*args, **kwargs) diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py index 16b7ff058..26f795966 100644 --- a/src/common/xmpp/protocol.py +++ b/src/common/xmpp/protocol.py @@ -23,47 +23,47 @@ sub- stanzas) handling routines from simplexml import Node, NodeBuilder import time -NS_ACTIVITY ='http://jabber.org/protocol/activity' # XEP-0108 -NS_ADDRESS ='http://jabber.org/protocol/address' # XEP-0033 -NS_AGENTS ='jabber:iq:agents' -NS_AMP ='http://jabber.org/protocol/amp' +NS_ACTIVITY ='http://jabber.org/protocol/activity' # XEP-0108 +NS_ADDRESS ='http://jabber.org/protocol/address' # XEP-0033 +NS_AGENTS ='jabber:iq:agents' +NS_AMP ='http://jabber.org/protocol/amp' NS_AMP_ERRORS =NS_AMP+'#errors' -NS_AUTH ='jabber:iq:auth' -NS_AVATAR ='http://www.xmpp.org/extensions/xep-0084.html#ns-metadata' -NS_BIND ='urn:ietf:params:xml:ns:xmpp-bind' -NS_BROWSE ='jabber:iq:browse' -NS_BROWSING ='http://jabber.org/protocol/browsing' # XEP-0195 -NS_BYTESTREAM ='http://jabber.org/protocol/bytestreams' # JEP-0065 -NS_CAPS ='http://jabber.org/protocol/caps' # JEP-0115 -NS_CHATSTATES ='http://jabber.org/protocol/chatstates' # JEP-0085 -NS_CHATTING ='http://jabber.org/protocol/chatting' # XEP-0194 -NS_CLIENT ='jabber:client' -NS_COMMANDS ='http://jabber.org/protocol/commands' +NS_AUTH ='jabber:iq:auth' +NS_AVATAR ='http://www.xmpp.org/extensions/xep-0084.html#ns-metadata' +NS_BIND ='urn:ietf:params:xml:ns:xmpp-bind' +NS_BROWSE ='jabber:iq:browse' +NS_BROWSING ='http://jabber.org/protocol/browsing' # XEP-0195 +NS_BYTESTREAM ='http://jabber.org/protocol/bytestreams' # JEP-0065 +NS_CAPS ='http://jabber.org/protocol/caps' # JEP-0115 +NS_CHATSTATES ='http://jabber.org/protocol/chatstates' # JEP-0085 +NS_CHATTING ='http://jabber.org/protocol/chatting' # XEP-0194 +NS_CLIENT ='jabber:client' +NS_COMMANDS ='http://jabber.org/protocol/commands' NS_COMPONENT_ACCEPT='jabber:component:accept' NS_COMPONENT_1 ='http://jabberd.jabberstudio.org/ns/component/1.0' -NS_COMPRESS ='http://jabber.org/protocol/compress' # XEP-0138 +NS_COMPRESS ='http://jabber.org/protocol/compress' # XEP-0138 NS_CONFERENCE ='jabber:x:conference' -NS_DATA ='jabber:x:data' # XEP-0004 -NS_DELAY ='jabber:x:delay' -NS_DELAY2 ='urn:xmpp:delay' -NS_DIALBACK ='jabber:server:dialback' -NS_DISCO ='http://jabber.org/protocol/disco' +NS_DATA ='jabber:x:data' # XEP-0004 +NS_DELAY ='jabber:x:delay' +NS_DELAY2 ='urn:xmpp:delay' +NS_DIALBACK ='jabber:server:dialback' +NS_DISCO ='http://jabber.org/protocol/disco' NS_DISCO_INFO =NS_DISCO+'#info' NS_DISCO_ITEMS =NS_DISCO+'#items' -NS_ENCRYPTED ='jabber:x:encrypted' # XEP-0027 -NS_ESESSION ='http://www.xmpp.org/extensions/xep-0116.html#ns' +NS_ENCRYPTED ='jabber:x:encrypted' # XEP-0027 +NS_ESESSION ='http://www.xmpp.org/extensions/xep-0116.html#ns' NS_ESESSION_INIT='http://www.xmpp.org/extensions/xep-0116.html#ns-init' # XEP-0116 -NS_EVENT ='jabber:x:event' # XEP-0022 -NS_FEATURE ='http://jabber.org/protocol/feature-neg' -NS_FILE ='http://jabber.org/protocol/si/profile/file-transfer' # JEP-0096 -NS_GAMING ='http://jabber.org/protocol/gaming' # XEP-0196 -NS_GEOLOC ='http://jabber.org/protocol/geoloc' # JEP-0080 -NS_GROUPCHAT ='gc-1.0' -NS_HTTP_AUTH ='http://jabber.org/protocol/http-auth' # XEP-0070 -NS_HTTP_BIND ='http://jabber.org/protocol/httpbind' # XEP-0124 -NS_IBB ='http://jabber.org/protocol/ibb' -NS_INVISIBLE ='presence-invisible' # Jabberd2 -NS_IQ ='iq' # Jabberd2 +NS_EVENT ='jabber:x:event' # XEP-0022 +NS_FEATURE ='http://jabber.org/protocol/feature-neg' +NS_FILE ='http://jabber.org/protocol/si/profile/file-transfer' # JEP-0096 +NS_GAMING ='http://jabber.org/protocol/gaming' # XEP-0196 +NS_GEOLOC ='http://jabber.org/protocol/geoloc' # JEP-0080 +NS_GROUPCHAT ='gc-1.0' +NS_HTTP_AUTH ='http://jabber.org/protocol/http-auth' # XEP-0070 +NS_HTTP_BIND ='http://jabber.org/protocol/httpbind' # XEP-0124 +NS_IBB ='http://jabber.org/protocol/ibb' +NS_INVISIBLE ='presence-invisible' # Jabberd2 +NS_IQ ='iq' # Jabberd2 NS_JINGLE ='urn:xmpp:jingle:1' # XEP-0166 NS_JINGLE_ERRORS='urn:xmpp:jingle:errors:1' # XEP-0166 NS_JINGLE_RTP ='urn:xmpp:jingle:apps:rtp:1' # XEP-0167 @@ -71,62 +71,62 @@ NS_JINGLE_RTP_AUDIO='urn:xmpp:jingle:apps:rtp:audio' # XEP-01 NS_JINGLE_RTP_VIDEO='urn:xmpp:jingle:apps:rtp:video' # XEP-0167 NS_JINGLE_RAW_UDP='urn:xmpp:jingle:transports:raw-udp:1' # XEP-0177 NS_JINGLE_ICE_UDP='urn:xmpp:jingle:transports:ice-udp:1' # XEP-0176 -NS_LAST ='jabber:iq:last' -NS_LOCATION ='http://jabber.org/protocol/geoloc' # XEP-0080 -NS_MESSAGE ='message' # Jabberd2 -NS_MOOD ='http://jabber.org/protocol/mood' # XEP-0107 -NS_MUC ='http://jabber.org/protocol/muc' -NS_MUC_USER =NS_MUC+'#user' -NS_MUC_ADMIN =NS_MUC+'#admin' -NS_MUC_OWNER =NS_MUC+'#owner' +NS_LAST ='jabber:iq:last' +NS_LOCATION ='http://jabber.org/protocol/geoloc' # XEP-0080 +NS_MESSAGE ='message' # Jabberd2 +NS_MOOD ='http://jabber.org/protocol/mood' # XEP-0107 +NS_MUC ='http://jabber.org/protocol/muc' +NS_MUC_USER =NS_MUC+'#user' +NS_MUC_ADMIN =NS_MUC+'#admin' +NS_MUC_OWNER =NS_MUC+'#owner' NS_MUC_UNIQUE =NS_MUC+'#unique' NS_MUC_CONFIG =NS_MUC+'#roomconfig' -NS_NICK ='http://jabber.org/protocol/nick' # XEP-0172 -NS_OFFLINE ='http://www.jabber.org/jeps/jep-0030.html' # XEP-0013 -NS_PHYSLOC ='http://jabber.org/protocol/physloc' # XEP-0112 -NS_PING ='urn:xmpp:ping' # SEP-0199 -NS_PRESENCE ='presence' # Jabberd2 -NS_PRIVACY ='jabber:iq:privacy' -NS_PRIVATE ='jabber:iq:private' -NS_PROFILE ='http://jabber.org/protocol/profile' # XEP-0154 -NS_PUBSUB ='http://jabber.org/protocol/pubsub' # XEP-0060 +NS_NICK ='http://jabber.org/protocol/nick' # XEP-0172 +NS_OFFLINE ='http://www.jabber.org/jeps/jep-0030.html' # XEP-0013 +NS_PHYSLOC ='http://jabber.org/protocol/physloc' # XEP-0112 +NS_PING ='urn:xmpp:ping' # SEP-0199 +NS_PRESENCE ='presence' # Jabberd2 +NS_PRIVACY ='jabber:iq:privacy' +NS_PRIVATE ='jabber:iq:private' +NS_PROFILE ='http://jabber.org/protocol/profile' # XEP-0154 +NS_PUBSUB ='http://jabber.org/protocol/pubsub' # XEP-0060 NS_PUBSUB_EVENT = 'http://jabber.org/protocol/pubsub#event' -NS_PUBSUB_PUBLISH_OPTIONS = NS_PUBSUB + '#publish-options' # XEP-0060 -NS_PUBSUB_OWNER ='http://jabber.org/protocol/pubsub#owner' # JEP-0060 -NS_REGISTER ='jabber:iq:register' -NS_ROSTER ='jabber:iq:roster' -NS_ROSTERX ='http://jabber.org/protocol/rosterx' # XEP-0144 -NS_RPC ='jabber:iq:rpc' # XEP-0009 -NS_SASL ='urn:ietf:params:xml:ns:xmpp-sasl' -NS_SEARCH ='jabber:iq:search' -NS_SERVER ='jabber:server' -NS_SESSION ='urn:ietf:params:xml:ns:xmpp-session' -NS_SI ='http://jabber.org/protocol/si' # XEP-0096 -NS_SI_PUB ='http://jabber.org/protocol/sipub' # XEP-0137 -NS_SIGNED ='jabber:x:signed' # XEP-0027 -NS_SSN ='urn:xmpp:ssn' # XEP-0155 -NS_STANZA_CRYPTO='http://www.xmpp.org/extensions/xep-0200.html#ns' # XEP-0200 -NS_STANZAS ='urn:ietf:params:xml:ns:xmpp-stanzas' -NS_STREAM ='http://affinix.com/jabber/stream' -NS_STREAMS ='http://etherx.jabber.org/streams' -NS_TIME ='jabber:iq:time' # XEP-0900 -NS_TIME_REVISED ='urn:xmpp:time' # XEP-0202 -NS_TLS ='urn:ietf:params:xml:ns:xmpp-tls' -NS_TUNE ='http://jabber.org/protocol/tune' # XEP-0118 -NS_VACATION ='http://jabber.org/protocol/vacation' -NS_VCARD ='vcard-temp' +NS_PUBSUB_PUBLISH_OPTIONS = NS_PUBSUB + '#publish-options' # XEP-0060 +NS_PUBSUB_OWNER ='http://jabber.org/protocol/pubsub#owner' # JEP-0060 +NS_REGISTER ='jabber:iq:register' +NS_ROSTER ='jabber:iq:roster' +NS_ROSTERX ='http://jabber.org/protocol/rosterx' # XEP-0144 +NS_RPC ='jabber:iq:rpc' # XEP-0009 +NS_SASL ='urn:ietf:params:xml:ns:xmpp-sasl' +NS_SEARCH ='jabber:iq:search' +NS_SERVER ='jabber:server' +NS_SESSION ='urn:ietf:params:xml:ns:xmpp-session' +NS_SI ='http://jabber.org/protocol/si' # XEP-0096 +NS_SI_PUB ='http://jabber.org/protocol/sipub' # XEP-0137 +NS_SIGNED ='jabber:x:signed' # XEP-0027 +NS_SSN ='urn:xmpp:ssn' # XEP-0155 +NS_STANZA_CRYPTO='http://www.xmpp.org/extensions/xep-0200.html#ns' # XEP-0200 +NS_STANZAS ='urn:ietf:params:xml:ns:xmpp-stanzas' +NS_STREAM ='http://affinix.com/jabber/stream' +NS_STREAMS ='http://etherx.jabber.org/streams' +NS_TIME ='jabber:iq:time' # XEP-0900 +NS_TIME_REVISED ='urn:xmpp:time' # XEP-0202 +NS_TLS ='urn:ietf:params:xml:ns:xmpp-tls' +NS_TUNE ='http://jabber.org/protocol/tune' # XEP-0118 +NS_VACATION ='http://jabber.org/protocol/vacation' +NS_VCARD ='vcard-temp' NS_GMAILNOTIFY ='google:mail:notify' NS_GTALKSETTING ='google:setting' NS_VCARD_UPDATE =NS_VCARD+':x:update' -NS_VERSION ='jabber:iq:version' -NS_VIEWING ='http://jabber.org/protocol/viewing' # XEP--197 -NS_WAITINGLIST ='http://jabber.org/protocol/waitinglist' # XEP-0130 -NS_XHTML_IM ='http://jabber.org/protocol/xhtml-im' # XEP-0071 -NS_XHTML = 'http://www.w3.org/1999/xhtml' # " -NS_DATA_LAYOUT ='http://jabber.org/protocol/xdata-layout' # XEP-0141 -NS_DATA_VALIDATE='http://jabber.org/protocol/xdata-validate' # XEP-0122 +NS_VERSION ='jabber:iq:version' +NS_VIEWING ='http://jabber.org/protocol/viewing' # XEP--197 +NS_WAITINGLIST ='http://jabber.org/protocol/waitinglist' # XEP-0130 +NS_XHTML_IM ='http://jabber.org/protocol/xhtml-im' # XEP-0071 +NS_XHTML = 'http://www.w3.org/1999/xhtml' # " +NS_DATA_LAYOUT ='http://jabber.org/protocol/xdata-layout' # XEP-0141 +NS_DATA_VALIDATE='http://jabber.org/protocol/xdata-validate' # XEP-0122 NS_XMPP_STREAMS ='urn:ietf:params:xml:ns:xmpp-streams' -NS_RECEIPTS ='urn:xmpp:receipts' +NS_RECEIPTS ='urn:xmpp:receipts' xmpp_stream_error_conditions = ''' bad-format -- -- -- The entity has sent XML that cannot be processed. @@ -189,1118 +189,1116 @@ temporary-auth-failure -- -- -- The authentication failed because of a tempora ERRORS, _errorcodes = {}, {} for ns, errname, errpool in ((NS_XMPP_STREAMS, 'STREAM', xmpp_stream_error_conditions), - (NS_STANZAS ,'ERR' ,xmpp_stanza_error_conditions), - (NS_SASL ,'SASL' ,sasl_error_conditions)): - for err in errpool.split('\n')[1:]: - cond, code, typ, text = err.split(' -- ') - name = errname + '_' + cond.upper().replace('-', '_') - locals()[name] = ns + ' ' + cond - ERRORS[ns + ' ' + cond] = [code, typ, text] - if code: - _errorcodes[code] = cond + (NS_STANZAS, 'ERR', xmpp_stanza_error_conditions), + (NS_SASL, 'SASL', sasl_error_conditions)): + for err in errpool.split('\n')[1:]: + cond, code, typ, text = err.split(' -- ') + name = errname + '_' + cond.upper().replace('-', '_') + locals()[name] = ns + ' ' + cond + ERRORS[ns + ' ' + cond] = [code, typ, text] + if code: + _errorcodes[code] = cond del ns, errname, errpool, err, cond, code, typ, text def isResultNode(node): - """ - Return true if the node is a positive reply - """ - return node and node.getType() == 'result' + """ + Return true if the node is a positive reply + """ + return node and node.getType() == 'result' def isErrorNode(node): - """ - Return true if the node is a negative reply - """ - return node and node.getType() == 'error' + """ + Return true if the node is a negative reply + """ + return node and node.getType() == 'error' class NodeProcessed(Exception): - """ - Exception that should be raised by handler when the handling should be - stopped - """ - pass + """ + Exception that should be raised by handler when the handling should be + stopped + """ + pass class StreamError(Exception): - """ - Base exception class for stream errors - """ - pass + """ + Base exception class for stream errors + """ + pass class BadFormat(StreamError): - pass + pass class BadNamespacePrefix(StreamError): - pass + pass class Conflict(StreamError): - pass + pass class ConnectionTimeout(StreamError): - pass + pass class HostGone(StreamError): - pass + pass class HostUnknown(StreamError): - pass + pass class ImproperAddressing(StreamError): - pass + pass class InternalServerError(StreamError): - pass + pass class InvalidFrom(StreamError): - pass + pass class InvalidID(StreamError): - pass + pass class InvalidNamespace(StreamError): - pass + pass class InvalidXML(StreamError): - pass + pass class NotAuthorized(StreamError): - pass + pass class PolicyViolation(StreamError): - pass + pass class RemoteConnectionFailed(StreamError): - pass + pass class ResourceConstraint(StreamError): - pass + pass class RestrictedXML(StreamError): - pass + pass class SeeOtherHost(StreamError): - pass + pass class SystemShutdown(StreamError): - pass + pass class UndefinedCondition(StreamError): - pass + pass class UnsupportedEncoding(StreamError): - pass + pass class UnsupportedStanzaType(StreamError): - pass + pass class UnsupportedVersion(StreamError): - pass + pass class XMLNotWellFormed(StreamError): - pass + pass stream_exceptions = {'bad-format': BadFormat, - 'bad-namespace-prefix': BadNamespacePrefix, - 'conflict': Conflict, - 'connection-timeout': ConnectionTimeout, - 'host-gone': HostGone, - 'host-unknown': HostUnknown, - 'improper-addressing': ImproperAddressing, - 'internal-server-error': InternalServerError, - 'invalid-from': InvalidFrom, - 'invalid-id': InvalidID, - 'invalid-namespace': InvalidNamespace, - 'invalid-xml': InvalidXML, - 'not-authorized': NotAuthorized, - 'policy-violation': PolicyViolation, - 'remote-connection-failed': RemoteConnectionFailed, - 'resource-constraint': ResourceConstraint, - 'restricted-xml': RestrictedXML, - 'see-other-host': SeeOtherHost, - 'system-shutdown': SystemShutdown, - 'undefined-condition': UndefinedCondition, - 'unsupported-encoding': UnsupportedEncoding, - 'unsupported-stanza-type': UnsupportedStanzaType, - 'unsupported-version': UnsupportedVersion, - 'xml-not-well-formed': XMLNotWellFormed} + 'bad-namespace-prefix': BadNamespacePrefix, + 'conflict': Conflict, + 'connection-timeout': ConnectionTimeout, + 'host-gone': HostGone, + 'host-unknown': HostUnknown, + 'improper-addressing': ImproperAddressing, + 'internal-server-error': InternalServerError, + 'invalid-from': InvalidFrom, + 'invalid-id': InvalidID, + 'invalid-namespace': InvalidNamespace, + 'invalid-xml': InvalidXML, + 'not-authorized': NotAuthorized, + 'policy-violation': PolicyViolation, + 'remote-connection-failed': RemoteConnectionFailed, + 'resource-constraint': ResourceConstraint, + 'restricted-xml': RestrictedXML, + 'see-other-host': SeeOtherHost, + 'system-shutdown': SystemShutdown, + 'undefined-condition': UndefinedCondition, + 'unsupported-encoding': UnsupportedEncoding, + 'unsupported-stanza-type': UnsupportedStanzaType, + 'unsupported-version': UnsupportedVersion, + 'xml-not-well-formed': XMLNotWellFormed} class JID: - """ - JID can be built from string, modified, compared, serialised into string - """ + """ + JID can be built from string, modified, compared, serialised into string + """ - def __init__(self, jid=None, node='', domain='', resource=''): - """ - JID can be specified as string (jid argument) or as separate parts + def __init__(self, jid=None, node='', domain='', resource=''): + """ + JID can be specified as string (jid argument) or as separate parts - Examples: - JID('node@domain/resource') - JID(node='node',domain='domain.org') - """ - if not jid and not domain: - raise ValueError('JID must contain at least domain name') - elif type(jid) == type(self): - self.node, self.domain, self.resource = jid.node, jid.domain, jid.resource - elif domain: - self.node, self.domain, self.resource = node, domain, resource - else: - if jid.find('@') + 1: - self.node,jid = jid.split('@', 1) - else: - self.node = '' - if jid.find('/')+1: - self.domain, self.resource = jid.split('/',1) - else: - self.domain, self.resource = jid, '' + Examples: + JID('node@domain/resource') + JID(node='node',domain='domain.org') + """ + if not jid and not domain: + raise ValueError('JID must contain at least domain name') + elif type(jid) == type(self): + self.node, self.domain, self.resource = jid.node, jid.domain, jid.resource + elif domain: + self.node, self.domain, self.resource = node, domain, resource + else: + if jid.find('@') + 1: + self.node, jid = jid.split('@', 1) + else: + self.node = '' + if jid.find('/')+1: + self.domain, self.resource = jid.split('/', 1) + else: + self.domain, self.resource = jid, '' - def getNode(self): - """ - Return the node part of the JID - """ - return self.node + def getNode(self): + """ + Return the node part of the JID + """ + return self.node - def setNode(self, node): - """ - Set the node part of the JID to new value. Specify None to remove the node part - """ - self.node = node.lower() + def setNode(self, node): + """ + Set the node part of the JID to new value. Specify None to remove the node part + """ + self.node = node.lower() - def getDomain(self): - """ - Return the domain part of the JID - """ - return self.domain + def getDomain(self): + """ + Return the domain part of the JID + """ + return self.domain - def setDomain(self, domain): - """ - Set the domain part of the JID to new value - """ - self.domain = domain.lower() + def setDomain(self, domain): + """ + Set the domain part of the JID to new value + """ + self.domain = domain.lower() - def getResource(self): - """ - Return the resource part of the JID - """ - return self.resource + def getResource(self): + """ + Return the resource part of the JID + """ + return self.resource - def setResource(self, resource): - """ - Set the resource part of the JID to new value. Specify None to remove the - resource part - """ - self.resource = resource + def setResource(self, resource): + """ + Set the resource part of the JID to new value. Specify None to remove the + resource part + """ + self.resource = resource - def getStripped(self): - """ - Return the bare representation of JID. I.e. string value w/o resource - """ - return self.__str__(0) + def getStripped(self): + """ + Return the bare representation of JID. I.e. string value w/o resource + """ + return self.__str__(0) - def __eq__(self, other): - """ - Compare the JID to another instance or to string for equality - """ - try: - other = JID(other) - except ValueError: - return 0 - return self.resource == other.resource and self.__str__(0) == other.__str__(0) + def __eq__(self, other): + """ + Compare the JID to another instance or to string for equality + """ + try: + other = JID(other) + except ValueError: + return 0 + return self.resource == other.resource and self.__str__(0) == other.__str__(0) - def __ne__(self, other): - """ - Compare the JID to another instance or to string for non-equality - """ - return not self.__eq__(other) + def __ne__(self, other): + """ + Compare the JID to another instance or to string for non-equality + """ + return not self.__eq__(other) - def bareMatch(self, other): - """ - Compare the node and domain parts of the JID's for equality - """ - return self.__str__(0) == JID(other).__str__(0) + def bareMatch(self, other): + """ + Compare the node and domain parts of the JID's for equality + """ + return self.__str__(0) == JID(other).__str__(0) - def __str__(self, wresource=1): - """ - Serialise JID into string - """ - if self.node: - jid = self.node + '@' + self.domain - else: - jid = self.domain - if wresource and self.resource: - return jid + '/' + self.resource - return jid + def __str__(self, wresource=1): + """ + Serialise JID into string + """ + if self.node: + jid = self.node + '@' + self.domain + else: + jid = self.domain + if wresource and self.resource: + return jid + '/' + self.resource + return jid - def __hash__(self): - """ - Produce hash of the JID, Allows to use JID objects as keys of the dictionary - """ - return hash(str(self)) + def __hash__(self): + """ + Produce hash of the JID, Allows to use JID objects as keys of the dictionary + """ + return hash(str(self)) class BOSHBody(Node): - """ - tag that wraps usual XMPP stanzas in XMPP over BOSH - """ + """ + tag that wraps usual XMPP stanzas in XMPP over BOSH + """ - def __init__(self, attrs={}, payload=[], node=None): - Node.__init__(self, tag='body', attrs=attrs, payload=payload, node=node) - self.setNamespace(NS_HTTP_BIND) + def __init__(self, attrs={}, payload=[], node=None): + Node.__init__(self, tag='body', attrs=attrs, payload=payload, node=node) + self.setNamespace(NS_HTTP_BIND) class Protocol(Node): - """ - A "stanza" object class. Contains methods that are common for presences, iqs - and messages - """ + """ + A "stanza" object class. Contains methods that are common for presences, iqs + and messages + """ - def __init__(self, name=None, to=None, typ=None, frm=None, attrs={}, - payload=[], timestamp=None, xmlns=None, node=None): - """ - Constructor, name is the name of the stanza i.e. 'message' or 'presence' or 'iq' + def __init__(self, name=None, to=None, typ=None, frm=None, attrs={}, + payload=[], timestamp=None, xmlns=None, node=None): + """ + Constructor, name is the name of the stanza i.e. 'message' or 'presence' or 'iq' - to is the value of 'to' attribure, 'typ' - 'type' attribute - frn - from attribure, attrs - other attributes mapping, - payload - same meaning as for simplexml payload definition - timestamp - the time value that needs to be stamped over stanza - xmlns - namespace of top stanza node - node - parsed or unparsed stana to be taken as prototype. - """ - if not attrs: - attrs = {} - if to: - attrs['to'] = to - if frm: - attrs['from'] = frm - if typ: - attrs['type'] = typ - Node.__init__(self, tag=name, attrs=attrs, payload=payload, node=node) - if not node and xmlns: - self.setNamespace(xmlns) - if self['to']: - self.setTo(self['to']) - if self['from']: - self.setFrom(self['from']) - if node and type(self) == type(node) and self.__class__ == node.__class__ and self.attrs.has_key('id'): - del self.attrs['id'] - self.timestamp = None - for d in self.getTags('delay', namespace=NS_DELAY2): - try: - if d.getAttr('stamp') < self.getTimestamp2(): - self.setTimestamp(d.getAttr('stamp')) - except Exception: - pass - if not self.timestamp: - for x in self.getTags('x', namespace=NS_DELAY): - try: - if x.getAttr('stamp') < self.getTimestamp(): - self.setTimestamp(x.getAttr('stamp')) - except Exception: - pass - if timestamp is not None: - self.setTimestamp(timestamp) # To auto-timestamp stanza just pass timestamp='' + to is the value of 'to' attribure, 'typ' - 'type' attribute + frn - from attribure, attrs - other attributes mapping, + payload - same meaning as for simplexml payload definition + timestamp - the time value that needs to be stamped over stanza + xmlns - namespace of top stanza node + node - parsed or unparsed stana to be taken as prototype. + """ + if not attrs: + attrs = {} + if to: + attrs['to'] = to + if frm: + attrs['from'] = frm + if typ: + attrs['type'] = typ + Node.__init__(self, tag=name, attrs=attrs, payload=payload, node=node) + if not node and xmlns: + self.setNamespace(xmlns) + if self['to']: + self.setTo(self['to']) + if self['from']: + self.setFrom(self['from']) + if node and type(self) == type(node) and self.__class__ == node.__class__ and self.attrs.has_key('id'): + del self.attrs['id'] + self.timestamp = None + for d in self.getTags('delay', namespace=NS_DELAY2): + try: + if d.getAttr('stamp') < self.getTimestamp2(): + self.setTimestamp(d.getAttr('stamp')) + except Exception: + pass + if not self.timestamp: + for x in self.getTags('x', namespace=NS_DELAY): + try: + if x.getAttr('stamp') < self.getTimestamp(): + self.setTimestamp(x.getAttr('stamp')) + except Exception: + pass + if timestamp is not None: + self.setTimestamp(timestamp) # To auto-timestamp stanza just pass timestamp='' - def getTo(self): - """ - Return value of the 'to' attribute - """ - try: - return self['to'] - except: - return None + def getTo(self): + """ + Return value of the 'to' attribute + """ + try: + return self['to'] + except: + return None - def getFrom(self): - """ - Return value of the 'from' attribute - """ - try: - return self['from'] - except: - return None + def getFrom(self): + """ + Return value of the 'from' attribute + """ + try: + return self['from'] + except: + return None - def getTimestamp(self): - """ - Return the timestamp in the 'yyyymmddThhmmss' format - """ - if self.timestamp: - return self.timestamp - return time.strftime('%Y%m%dT%H:%M:%S', time.gmtime()) + def getTimestamp(self): + """ + Return the timestamp in the 'yyyymmddThhmmss' format + """ + if self.timestamp: + return self.timestamp + return time.strftime('%Y%m%dT%H:%M:%S', time.gmtime()) - def getTimestamp2(self): - """ - Return the timestamp in the 'yyyymmddThhmmss' format - """ - if self.timestamp: - return self.timestamp - return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) + def getTimestamp2(self): + """ + Return the timestamp in the 'yyyymmddThhmmss' format + """ + if self.timestamp: + return self.timestamp + return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) - def getID(self): - """ - Return the value of the 'id' attribute - """ - return self.getAttr('id') + def getID(self): + """ + Return the value of the 'id' attribute + """ + return self.getAttr('id') - def setTo(self, val): - """ - Set the value of the 'to' attribute - """ - self.setAttr('to', JID(val)) + def setTo(self, val): + """ + Set the value of the 'to' attribute + """ + self.setAttr('to', JID(val)) - def getType(self): - """ - Return the value of the 'type' attribute - """ - return self.getAttr('type') + def getType(self): + """ + Return the value of the 'type' attribute + """ + return self.getAttr('type') - def setFrom(self, val): - """ - Set the value of the 'from' attribute - """ - self.setAttr('from', JID(val)) + def setFrom(self, val): + """ + Set the value of the 'from' attribute + """ + self.setAttr('from', JID(val)) - def setType(self, val): - """ - Set the value of the 'type' attribute - """ - self.setAttr('type', val) + def setType(self, val): + """ + Set the value of the 'type' attribute + """ + self.setAttr('type', val) - def setID(self, val): - """ - Set the value of the 'id' attribute - """ - self.setAttr('id', val) + def setID(self, val): + """ + Set the value of the 'id' attribute + """ + self.setAttr('id', val) - def getError(self): - """ - Return the error-condition (if present) or the textual description of the - error (otherwise) - """ - errtag = self.getTag('error') - if errtag: - for tag in errtag.getChildren(): - if tag.getName() <> 'text': - return tag.getName() - return errtag.getData() + def getError(self): + """ + Return the error-condition (if present) or the textual description of the + error (otherwise) + """ + errtag = self.getTag('error') + if errtag: + for tag in errtag.getChildren(): + if tag.getName() <> 'text': + return tag.getName() + return errtag.getData() - def getErrorMsg(self): - """ - Return the textual description of the error (if present) or the error condition - """ - errtag = self.getTag('error') - if errtag: - for tag in errtag.getChildren(): - if tag.getName() == 'text': - return tag.getData() - return self.getError() + def getErrorMsg(self): + """ + Return the textual description of the error (if present) or the error condition + """ + errtag = self.getTag('error') + if errtag: + for tag in errtag.getChildren(): + if tag.getName() == 'text': + return tag.getData() + return self.getError() - def getErrorCode(self): - """ - Return the error code. Obsolete. - """ - return self.getTagAttr('error','code') + def getErrorCode(self): + """ + Return the error code. Obsolete. + """ + return self.getTagAttr('error', 'code') - def setError(self,error,code=None): - """ - Set the error code. Obsolete. Use error-conditions instead - """ - if code: - if str(code) in _errorcodes.keys(): - error = ErrorNode(_errorcodes[str(code)], text=error) - else: - error = ErrorNode(ERR_UNDEFINED_CONDITION, code=code, typ='cancel', text=error) - elif type(error) in [type(''),type(u'')]: - error=ErrorNode(error) - self.setType('error') - self.addChild(node=error) + def setError(self,error,code=None): + """ + Set the error code. Obsolete. Use error-conditions instead + """ + if code: + if str(code) in _errorcodes.keys(): + error = ErrorNode(_errorcodes[str(code)], text=error) + else: + error = ErrorNode(ERR_UNDEFINED_CONDITION, code=code, typ='cancel', text=error) + elif type(error) in [type(''), type(u'')]: + error=ErrorNode(error) + self.setType('error') + self.addChild(node=error) - def setTimestamp(self, val=None): - """ - Set the timestamp. timestamp should be the yyyymmddThhmmss string - """ - if not val: - val = time.strftime('%Y%m%dT%H:%M:%S', time.gmtime()) - self.timestamp=val - self.setTag('x', {'stamp': self.timestamp}, namespace=NS_DELAY) + def setTimestamp(self, val=None): + """ + Set the timestamp. timestamp should be the yyyymmddThhmmss string + """ + if not val: + val = time.strftime('%Y%m%dT%H:%M:%S', time.gmtime()) + self.timestamp=val + self.setTag('x', {'stamp': self.timestamp}, namespace=NS_DELAY) - def getProperties(self): - """ - Return the list of namespaces to which belongs the direct childs of element - """ - props = [] - for child in self.getChildren(): - prop = child.getNamespace() - if prop not in props: - props.append(prop) - return props + def getProperties(self): + """ + Return the list of namespaces to which belongs the direct childs of element + """ + props = [] + for child in self.getChildren(): + prop = child.getNamespace() + if prop not in props: + props.append(prop) + return props - def __setitem__(self, item, val): - """ - Set the item 'item' to the value 'val' - """ - if item in ['to','from']: - val = JID(val) - return self.setAttr(item, val) + def __setitem__(self, item, val): + """ + Set the item 'item' to the value 'val' + """ + if item in ['to', 'from']: + val = JID(val) + return self.setAttr(item, val) class Message(Protocol): - """ - XMPP Message stanza - "push" mechanism - """ + """ + XMPP Message stanza - "push" mechanism + """ - def __init__(self, to=None, body=None, xhtml=None, typ=None, subject=None, - attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, - node=None): - """ - You can specify recipient, text of message, type of message any - additional attributes, sender of the message, any additional payload - (f.e. jabber:x:delay element) and namespace in one go. + def __init__(self, to=None, body=None, xhtml=None, typ=None, subject=None, + attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, + node=None): + """ + You can specify recipient, text of message, type of message any + additional attributes, sender of the message, any additional payload + (f.e. jabber:x:delay element) and namespace in one go. - Alternatively you can pass in the other XML object as the 'node' - parameted to replicate it as message - """ - Protocol.__init__(self, 'message', to=to, typ=typ, attrs=attrs, frm=frm, - payload=payload, timestamp=timestamp, xmlns=xmlns, node=node) - if body: - self.setBody(body) - if xhtml: - self.setXHTML(xhtml) - if subject is not None: - self.setSubject(subject) + Alternatively you can pass in the other XML object as the 'node' + parameted to replicate it as message + """ + Protocol.__init__(self, 'message', to=to, typ=typ, attrs=attrs, frm=frm, + payload=payload, timestamp=timestamp, xmlns=xmlns, node=node) + if body: + self.setBody(body) + if xhtml: + self.setXHTML(xhtml) + if subject is not None: + self.setSubject(subject) - def getBody(self): - """ - Return text of the message - """ - return self.getTagData('body') + def getBody(self): + """ + Return text of the message + """ + return self.getTagData('body') - def getXHTML(self, xmllang=None): - """ - Return serialized xhtml-im element text of the message + def getXHTML(self, xmllang=None): + """ + Return serialized xhtml-im element text of the message - TODO: Returning a DOM could make rendering faster. - """ - xhtml = self.getTag('html') - if xhtml: - if xmllang: - body = xhtml.getTag('body', attrs={'xml:lang': xmllang}) - else: - body = xhtml.getTag('body') - return str(body) - return None + TODO: Returning a DOM could make rendering faster. + """ + xhtml = self.getTag('html') + if xhtml: + if xmllang: + body = xhtml.getTag('body', attrs={'xml:lang': xmllang}) + else: + body = xhtml.getTag('body') + return str(body) + return None - def getSubject(self): - """ - Return subject of the message - """ - return self.getTagData('subject') + def getSubject(self): + """ + Return subject of the message + """ + return self.getTagData('subject') - def getThread(self): - """ - Return thread of the message - """ - return self.getTagData('thread') + def getThread(self): + """ + Return thread of the message + """ + return self.getTagData('thread') - def setBody(self, val): - """ - Set the text of the message""" - self.setTagData('body', val) + def setBody(self, val): + """ + Set the text of the message""" + self.setTagData('body', val) - def setXHTML(self, val, xmllang=None): - """ - Sets the xhtml text of the message (XEP-0071). The parameter is the - "inner html" to the body. - """ - try: - if xmllang: - dom = NodeBuilder('%s' % (NS_XHTML, xmllang, val)).getDom() - else: - dom = NodeBuilder('%s, 0' % (NS_XHTM, val)).getDom() - if self.getTag('html'): - self.getTag('html').addChild(node=dom) - else: - self.setTag('html', namespace=NS_XHTML_IM).addChild(node=dom) - except Exception, e: - print "Error", e - # FIXME: log. we could not set xhtml (parse error, whatever) + def setXHTML(self, val, xmllang=None): + """ + Sets the xhtml text of the message (XEP-0071). The parameter is the + "inner html" to the body. + """ + try: + if xmllang: + dom = NodeBuilder('%s' % (NS_XHTML, xmllang, val)).getDom() + else: + dom = NodeBuilder('%s, 0' % (NS_XHTM, val)).getDom() + if self.getTag('html'): + self.getTag('html').addChild(node=dom) + else: + self.setTag('html', namespace=NS_XHTML_IM).addChild(node=dom) + except Exception, e: + print "Error", e + # FIXME: log. we could not set xhtml (parse error, whatever) - def setSubject(self, val): - """ - Set the subject of the message - """ - self.setTagData('subject', val) + def setSubject(self, val): + """ + Set the subject of the message + """ + self.setTagData('subject', val) - def setThread(self, val): - """ - Set the thread of the message - """ - self.setTagData('thread', val) + def setThread(self, val): + """ + Set the thread of the message + """ + self.setTagData('thread', val) - def buildReply(self, text=None): - """ - Builds and returns another message object with specified text. The to, - from and thread properties of new message are pre-set as reply to this - message - """ - m = Message(to=self.getFrom(), frm=self.getTo(), body=text) - th = self.getThread() - if th: - m.setThread(th) - return m + def buildReply(self, text=None): + """ + Builds and returns another message object with specified text. The to, + from and thread properties of new message are pre-set as reply to this + message + """ + m = Message(to=self.getFrom(), frm=self.getTo(), body=text) + th = self.getThread() + if th: + m.setThread(th) + return m - def getStatusCode(self): - """ - Return the status code of the message (for groupchat config change) - """ - attrs = [] - for xtag in self.getTags('x'): - for child in xtag.getTags('status'): - attrs.append(child.getAttr('code')) - return attrs + def getStatusCode(self): + """ + Return the status code of the message (for groupchat config change) + """ + attrs = [] + for xtag in self.getTags('x'): + for child in xtag.getTags('status'): + attrs.append(child.getAttr('code')) + return attrs class Presence(Protocol): - def __init__(self, to=None, typ=None, priority=None, show=None, status=None, - attrs={}, frm=None, timestamp=None, payload=[], xmlns=NS_CLIENT, - node=None): - """ - You can specify recipient, type of message, priority, show and status - values any additional attributes, sender of the presence, timestamp, any - additional payload (f.e. jabber:x:delay element) and namespace in one go. - Alternatively you can pass in the other XML object as the 'node' - parameted to replicate it as presence - """ - Protocol.__init__(self, 'presence', to=to, typ=typ, attrs=attrs, frm=frm, - payload=payload, timestamp=timestamp, xmlns=xmlns, node=node) - if priority: - self.setPriority(priority) - if show: - self.setShow(show) - if status: - self.setStatus(status) + def __init__(self, to=None, typ=None, priority=None, show=None, status=None, + attrs={}, frm=None, timestamp=None, payload=[], xmlns=NS_CLIENT, + node=None): + """ + You can specify recipient, type of message, priority, show and status + values any additional attributes, sender of the presence, timestamp, any + additional payload (f.e. jabber:x:delay element) and namespace in one go. + Alternatively you can pass in the other XML object as the 'node' + parameted to replicate it as presence + """ + Protocol.__init__(self, 'presence', to=to, typ=typ, attrs=attrs, frm=frm, + payload=payload, timestamp=timestamp, xmlns=xmlns, node=node) + if priority: + self.setPriority(priority) + if show: + self.setShow(show) + if status: + self.setStatus(status) - def getPriority(self): - """ - Return the priority of the message - """ - return self.getTagData('priority') + def getPriority(self): + """ + Return the priority of the message + """ + return self.getTagData('priority') - def getShow(self): - """ - Return the show value of the message - """ - return self.getTagData('show') + def getShow(self): + """ + Return the show value of the message + """ + return self.getTagData('show') - def getStatus(self): - """ - Return the status string of the message - """ - return self.getTagData('status') + def getStatus(self): + """ + Return the status string of the message + """ + return self.getTagData('status') - def setPriority(self, val): - """ - Set the priority of the message - """ - self.setTagData('priority', val) + def setPriority(self, val): + """ + Set the priority of the message + """ + self.setTagData('priority', val) - def setShow(self, val): - """ - Set the show value of the message - """ - self.setTagData('show', val) + def setShow(self, val): + """ + Set the show value of the message + """ + self.setTagData('show', val) - def setStatus(self, val): - """ - Set the status string of the message - """ - self.setTagData('status', val) + def setStatus(self, val): + """ + Set the status string of the message + """ + self.setTagData('status', val) - def _muc_getItemAttr(self, tag, attr): - for xtag in self.getTags('x'): - if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN): - continue - for child in xtag.getTags(tag): - return child.getAttr(attr) + def _muc_getItemAttr(self, tag, attr): + for xtag in self.getTags('x'): + if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN): + continue + for child in xtag.getTags(tag): + return child.getAttr(attr) - def _muc_getSubTagDataAttr(self, tag, attr): - for xtag in self.getTags('x'): - if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN): - continue - for child in xtag.getTags('item'): - for cchild in child.getTags(tag): - return cchild.getData(), cchild.getAttr(attr) - return None, None + def _muc_getSubTagDataAttr(self, tag, attr): + for xtag in self.getTags('x'): + if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN): + continue + for child in xtag.getTags('item'): + for cchild in child.getTags(tag): + return cchild.getData(), cchild.getAttr(attr) + return None, None - def getRole(self): - """ - Return the presence role (for groupchat) - """ - return self._muc_getItemAttr('item', 'role') - def getAffiliation(self): - """ - Return the presence affiliation (for groupchat) - """ - return self._muc_getItemAttr('item', 'affiliation') + def getRole(self): + """ + Return the presence role (for groupchat) + """ + return self._muc_getItemAttr('item', 'role') + def getAffiliation(self): + """ + Return the presence affiliation (for groupchat) + """ + return self._muc_getItemAttr('item', 'affiliation') - def getNewNick(self): - """ - Return the status code of the presence (for groupchat) - """ - return self._muc_getItemAttr('item', 'nick') + def getNewNick(self): + """ + Return the status code of the presence (for groupchat) + """ + return self._muc_getItemAttr('item', 'nick') - def getJid(self): - """ - Return the presence jid (for groupchat) - """ - return self._muc_getItemAttr('item', 'jid') + def getJid(self): + """ + Return the presence jid (for groupchat) + """ + return self._muc_getItemAttr('item', 'jid') - def getReason(self): - """ - Returns the reason of the presence (for groupchat) - """ - return self._muc_getSubTagDataAttr('reason', '')[0] + def getReason(self): + """ + Returns the reason of the presence (for groupchat) + """ + return self._muc_getSubTagDataAttr('reason', '')[0] - def getActor(self): - """ - Return the reason of the presence (for groupchat) - """ - return self._muc_getSubTagDataAttr('actor', 'jid')[1] + def getActor(self): + """ + Return the reason of the presence (for groupchat) + """ + return self._muc_getSubTagDataAttr('actor', 'jid')[1] - def getStatusCode(self): - """ - Return the status code of the presence (for groupchat) - """ - attrs = [] - for xtag in self.getTags('x'): - for child in xtag.getTags('status'): - attrs.append(child.getAttr('code')) - return attrs + def getStatusCode(self): + """ + Return the status code of the presence (for groupchat) + """ + attrs = [] + for xtag in self.getTags('x'): + for child in xtag.getTags('status'): + attrs.append(child.getAttr('code')) + return attrs class Iq(Protocol): - """ - XMPP Iq object - get/set dialog mechanism - """ + """ + XMPP Iq object - get/set dialog mechanism + """ - def __init__(self, typ=None, queryNS=None, attrs={}, to=None, frm=None, - payload=[], xmlns=NS_CLIENT, node=None): - """ - You can specify type, query namespace any additional attributes, - recipient of the iq, sender of the iq, any additional payload (f.e. - jabber:x:data node) and namespace in one go. + def __init__(self, typ=None, queryNS=None, attrs={}, to=None, frm=None, + payload=[], xmlns=NS_CLIENT, node=None): + """ + You can specify type, query namespace any additional attributes, + recipient of the iq, sender of the iq, any additional payload (f.e. + jabber:x:data node) and namespace in one go. - Alternatively you can pass in the other XML object as the 'node' - parameted to replicate it as an iq - """ - Protocol.__init__(self, 'iq', to=to, typ=typ, attrs=attrs, frm=frm, xmlns=xmlns, node=node) - if payload: - self.setQueryPayload(payload) - if queryNS: - self.setQueryNS(queryNS) + Alternatively you can pass in the other XML object as the 'node' + parameted to replicate it as an iq + """ + Protocol.__init__(self, 'iq', to=to, typ=typ, attrs=attrs, frm=frm, xmlns=xmlns, node=node) + if payload: + self.setQueryPayload(payload) + if queryNS: + self.setQueryNS(queryNS) - def getQueryNS(self): - """ - Return the namespace of the 'query' child element - """ - tag = self.getTag('query') - if tag: - return tag.getNamespace() + def getQueryNS(self): + """ + Return the namespace of the 'query' child element + """ + tag = self.getTag('query') + if tag: + return tag.getNamespace() - def getQuerynode(self): - """ - Return the 'node' attribute value of the 'query' child element - """ - return self.getTagAttr('query', 'node') + def getQuerynode(self): + """ + Return the 'node' attribute value of the 'query' child element + """ + return self.getTagAttr('query', 'node') - def getQueryPayload(self): - """ - Return the 'query' child element payload - """ - tag = self.getTag('query') - if tag: - return tag.getPayload() + def getQueryPayload(self): + """ + Return the 'query' child element payload + """ + tag = self.getTag('query') + if tag: + return tag.getPayload() - def getQueryChildren(self): - """ - Return the 'query' child element child nodes - """ - tag = self.getTag('query') - if tag: - return tag.getChildren() + def getQueryChildren(self): + """ + Return the 'query' child element child nodes + """ + tag = self.getTag('query') + if tag: + return tag.getChildren() - def setQueryNS(self, namespace): - """ - Set the namespace of the 'query' child element - """ - self.setTag('query').setNamespace(namespace) + def setQueryNS(self, namespace): + """ + Set the namespace of the 'query' child element + """ + self.setTag('query').setNamespace(namespace) - def setQueryPayload(self, payload): - """ - Set the 'query' child element payload - """ - self.setTag('query').setPayload(payload) + def setQueryPayload(self, payload): + """ + Set the 'query' child element payload + """ + self.setTag('query').setPayload(payload) - def setQuerynode(self, node): - """ - Set the 'node' attribute value of the 'query' child element - """ - self.setTagAttr('query', 'node', node) + def setQuerynode(self, node): + """ + Set the 'node' attribute value of the 'query' child element + """ + self.setTagAttr('query', 'node', node) - def buildReply(self, typ): - """ - Build and return another Iq object of specified type. The to, from and - query child node of new Iq are pre-set as reply to this Iq. - """ - iq = Iq(typ, to=self.getFrom(), frm=self.getTo(), attrs={'id': self.getID()}) - if self.getTag('query'): - iq.setQueryNS(self.getQueryNS()) - return iq + def buildReply(self, typ): + """ + Build and return another Iq object of specified type. The to, from and + query child node of new Iq are pre-set as reply to this Iq. + """ + iq = Iq(typ, to=self.getFrom(), frm=self.getTo(), attrs={'id': self.getID()}) + if self.getTag('query'): + iq.setQueryNS(self.getQueryNS()) + return iq class ErrorNode(Node): - """ - XMPP-style error element + """ + XMPP-style error element - In the case of stanza error should be attached to XMPP stanza. - In the case of stream-level errors should be used separately. - """ + In the case of stanza error should be attached to XMPP stanza. + In the case of stream-level errors should be used separately. + """ - def __init__(self, name, code=None, typ=None, text=None): - """ - Mandatory parameter: name - name of error condition. - Optional parameters: code, typ, text. Used for backwards compartibility with older jabber protocol. - """ - if name in ERRORS: - cod, type_, txt = ERRORS[name] - ns = name.split()[0] - else: - cod, ns, type_, txt = '500', NS_STANZAS, 'cancel', '' - if typ: - type_ = typ - if code: - cod = code - if text: - txt = text - Node.__init__(self,'error', {}, [Node(name)]) - if type_: - self.setAttr('type', type_) - if not cod: - self.setName('stream:error') - if txt: - self.addChild(node=Node(ns + ' text', {}, [txt])) - if cod: - self.setAttr('code', cod) + def __init__(self, name, code=None, typ=None, text=None): + """ + Mandatory parameter: name - name of error condition. + Optional parameters: code, typ, text. Used for backwards compartibility with older jabber protocol. + """ + if name in ERRORS: + cod, type_, txt = ERRORS[name] + ns = name.split()[0] + else: + cod, ns, type_, txt = '500', NS_STANZAS, 'cancel', '' + if typ: + type_ = typ + if code: + cod = code + if text: + txt = text + Node.__init__(self, 'error', {}, [Node(name)]) + if type_: + self.setAttr('type', type_) + if not cod: + self.setName('stream:error') + if txt: + self.addChild(node=Node(ns + ' text', {}, [txt])) + if cod: + self.setAttr('code', cod) class Error(Protocol): - """ - Used to quickly transform received stanza into error reply - """ + """ + Used to quickly transform received stanza into error reply + """ - def __init__(self, node, error, reply=1): - """ - Create error reply basing on the received 'node' stanza and the 'error' - error condition + def __init__(self, node, error, reply=1): + """ + Create error reply basing on the received 'node' stanza and the 'error' + error condition - If the 'node' is not the received stanza but locally created ('to' and - 'from' fields needs not swapping) specify the 'reply' argument as false. - """ - if reply: - Protocol.__init__(self, to=node.getFrom(), frm=node.getTo(), node=node) - else: - Protocol.__init__(self, node=node) - self.setError(error) - if node.getType() == 'error': - self.__str__ = self.__dupstr__ + If the 'node' is not the received stanza but locally created ('to' and + 'from' fields needs not swapping) specify the 'reply' argument as false. + """ + if reply: + Protocol.__init__(self, to=node.getFrom(), frm=node.getTo(), node=node) + else: + Protocol.__init__(self, node=node) + self.setError(error) + if node.getType() == 'error': + self.__str__ = self.__dupstr__ - def __dupstr__(self, dup1=None, dup2=None): - """ - Dummy function used as preventor of creating error node in reply to error - node. I.e. you will not be able to serialise "double" error into string. - """ - return '' + def __dupstr__(self, dup1=None, dup2=None): + """ + Dummy function used as preventor of creating error node in reply to error + node. I.e. you will not be able to serialise "double" error into string. + """ + return '' class DataField(Node): - """ - This class is used in the DataForm class to describe the single data item + """ + This class is used in the DataForm class to describe the single data item - If you are working with jabber:x:data (XEP-0004, XEP-0068, XEP-0122) then - you will need to work with instances of this class. - """ + If you are working with jabber:x:data (XEP-0004, XEP-0068, XEP-0122) then + you will need to work with instances of this class. + """ - def __init__(self, name=None, value=None, typ=None, required=0, desc=None, - options=[], node=None): - """ - Create new data field of specified name,value and type + def __init__(self, name=None, value=None, typ=None, required=0, desc=None, + options=[], node=None): + """ + Create new data field of specified name,value and type - Also 'required','desc' and 'options' fields can be set. Alternatively - other XML object can be passed in as the 'node' parameted to replicate it - as a new datafiled. - """ - Node.__init__(self, 'field', node=node) - if name: - self.setVar(name) - if isinstance(value, (list, tuple)): - self.setValues(value) - elif value: - self.setValue(value) - if typ: - self.setType(typ) - elif not typ and not node: - self.setType('text-single') - if required: - self.setRequired(required) - if desc: - self.setDesc(desc) - if options: - self.setOptions(options) + Also 'required','desc' and 'options' fields can be set. Alternatively + other XML object can be passed in as the 'node' parameted to replicate it + as a new datafiled. + """ + Node.__init__(self, 'field', node=node) + if name: + self.setVar(name) + if isinstance(value, (list, tuple)): + self.setValues(value) + elif value: + self.setValue(value) + if typ: + self.setType(typ) + elif not typ and not node: + self.setType('text-single') + if required: + self.setRequired(required) + if desc: + self.setDesc(desc) + if options: + self.setOptions(options) - def setRequired(self, req=1): - """ - Change the state of the 'required' flag - """ - if req: - self.setTag('required') - else: - try: - self.delChild('required') - except ValueError: - return + def setRequired(self, req=1): + """ + Change the state of the 'required' flag + """ + if req: + self.setTag('required') + else: + try: + self.delChild('required') + except ValueError: + return - def isRequired(self): - """ - Return in this field a required one - """ - return self.getTag('required') + def isRequired(self): + """ + Return in this field a required one + """ + return self.getTag('required') - def setDesc(self, desc): - """ - Set the description of this field - """ - self.setTagData('desc', desc) + def setDesc(self, desc): + """ + Set the description of this field + """ + self.setTagData('desc', desc) - def getDesc(self): - """ - Return the description of this field - """ - return self.getTagData('desc') + def getDesc(self): + """ + Return the description of this field + """ + return self.getTagData('desc') - def setValue(self, val): - """ - Set the value of this field - """ - self.setTagData('value', val) + def setValue(self, val): + """ + Set the value of this field + """ + self.setTagData('value', val) - def getValue(self): - return self.getTagData('value') + def getValue(self): + return self.getTagData('value') - def setValues(self, lst): - """ - Set the values of this field as values-list. Replaces all previous filed - values! If you need to just add a value - use addValue method - """ - while self.getTag('value'): - self.delChild('value') - for val in lst: - self.addValue(val) + def setValues(self, lst): + """ + Set the values of this field as values-list. Replaces all previous filed + values! If you need to just add a value - use addValue method + """ + while self.getTag('value'): + self.delChild('value') + for val in lst: + self.addValue(val) - def addValue(self, val): - """ - Add one more value to this field. Used in 'get' iq's or such - """ - self.addChild('value', {}, [val]) + def addValue(self, val): + """ + Add one more value to this field. Used in 'get' iq's or such + """ + self.addChild('value', {}, [val]) - def getValues(self): - """ - Return the list of values associated with this field - """ - ret = [] - for tag in self.getTags('value'): - ret.append(tag.getData()) - return ret + def getValues(self): + """ + Return the list of values associated with this field + """ + ret = [] + for tag in self.getTags('value'): + ret.append(tag.getData()) + return ret - def getOptions(self): - """ - Return label-option pairs list associated with this field - """ - ret = [] - for tag in self.getTags('option'): - ret.append([tag.getAttr('label'), tag.getTagData('value')]) - return ret + def getOptions(self): + """ + Return label-option pairs list associated with this field + """ + ret = [] + for tag in self.getTags('option'): + ret.append([tag.getAttr('label'), tag.getTagData('value')]) + return ret - def setOptions(self, lst): - """ - Set label-option pairs list associated with this field - """ - while self.getTag('option'): - self.delChild('option') - for opt in lst: - self.addOption(opt) + def setOptions(self, lst): + """ + Set label-option pairs list associated with this field + """ + while self.getTag('option'): + self.delChild('option') + for opt in lst: + self.addOption(opt) - def addOption(self, opt): - """ - Add one more label-option pair to this field - """ - if isinstance(opt, basestring): - self.addChild('option').setTagData('value', opt) - else: - self.addChild('option', {'label': opt[0]}).setTagData('value', opt[1]) + def addOption(self, opt): + """ + Add one more label-option pair to this field + """ + if isinstance(opt, basestring): + self.addChild('option').setTagData('value', opt) + else: + self.addChild('option', {'label': opt[0]}).setTagData('value', opt[1]) - def getType(self): - """ - Get type of this field - """ - return self.getAttr('type') + def getType(self): + """ + Get type of this field + """ + return self.getAttr('type') - def setType(self, val): - """ - Set type of this field - """ - return self.setAttr('type', val) + def setType(self, val): + """ + Set type of this field + """ + return self.setAttr('type', val) - def getVar(self): - """ - Get 'var' attribute value of this field - """ - return self.getAttr('var') + def getVar(self): + """ + Get 'var' attribute value of this field + """ + return self.getAttr('var') - def setVar(self, val): - """ - Set 'var' attribute value of this field - """ - return self.setAttr('var',val) + def setVar(self, val): + """ + Set 'var' attribute value of this field + """ + return self.setAttr('var', val) class DataForm(Node): - """ - Used for manipulating dataforms in XMPP + """ + Used for manipulating dataforms in XMPP - Relevant XEPs: 0004, 0068, 0122. Can be used in disco, pub-sub and many - other applications. - """ - def __init__(self, typ=None, data=[], title=None, node=None): - """ - Create new dataform of type 'typ'. 'data' is the list of DataField - instances that this dataform contains, 'title' - the title string. You - can specify the 'node' argument as the other node to be used as base for - constructing this dataform + Relevant XEPs: 0004, 0068, 0122. Can be used in disco, pub-sub and many + other applications. + """ + def __init__(self, typ=None, data=[], title=None, node=None): + """ + Create new dataform of type 'typ'. 'data' is the list of DataField + instances that this dataform contains, 'title' - the title string. You + can specify the 'node' argument as the other node to be used as base for + constructing this dataform - title and instructions is optional and SHOULD NOT contain newlines. - Several instructions MAY be present. - 'typ' can be one of ('form' | 'submit' | 'cancel' | 'result' ) - 'typ' of reply iq can be ( 'result' | 'set' | 'set' | 'result' ) respectively. - 'cancel' form can not contain any fields. All other forms contains AT LEAST one field. - 'title' MAY be included in forms of type "form" and "result" - """ - Node.__init__(self, 'x', node=node) - if node: - newkids = [] - for n in self.getChildren(): - if n.getName() == 'field': - newkids.append(DataField(node=n)) - else: - newkids.append(n) - self.kids = newkids - if typ: - self.setType(typ) - self.setNamespace(NS_DATA) - if title: - self.setTitle(title) - if isinstance(data, dict): - newdata = [] - for name in data.keys(): - newdata.append(DataField(name, data[name])) - data = newdata - for child in data: - if isinstance(child, basestring): - self.addInstructions(child) - elif child.__class__.__name__ == 'DataField': - self.kids.append(child) - else: - self.kids.append(DataField(node=child)) + title and instructions is optional and SHOULD NOT contain newlines. + Several instructions MAY be present. + 'typ' can be one of ('form' | 'submit' | 'cancel' | 'result' ) + 'typ' of reply iq can be ( 'result' | 'set' | 'set' | 'result' ) respectively. + 'cancel' form can not contain any fields. All other forms contains AT LEAST one field. + 'title' MAY be included in forms of type "form" and "result" + """ + Node.__init__(self, 'x', node=node) + if node: + newkids = [] + for n in self.getChildren(): + if n.getName() == 'field': + newkids.append(DataField(node=n)) + else: + newkids.append(n) + self.kids = newkids + if typ: + self.setType(typ) + self.setNamespace(NS_DATA) + if title: + self.setTitle(title) + if isinstance(data, dict): + newdata = [] + for name in data.keys(): + newdata.append(DataField(name, data[name])) + data = newdata + for child in data: + if isinstance(child, basestring): + self.addInstructions(child) + elif child.__class__.__name__ == 'DataField': + self.kids.append(child) + else: + self.kids.append(DataField(node=child)) - def getType(self): - """ - Return the type of dataform - """ - return self.getAttr('type') + def getType(self): + """ + Return the type of dataform + """ + return self.getAttr('type') - def setType(self, typ): - """ - Set the type of dataform - """ - self.setAttr('type', typ) + def setType(self, typ): + """ + Set the type of dataform + """ + self.setAttr('type', typ) - def getTitle(self): - """ - Return the title of dataform - """ - return self.getTagData('title') + def getTitle(self): + """ + Return the title of dataform + """ + return self.getTagData('title') - def setTitle(self, text): - """ - Set the title of dataform - """ - self.setTagData('title', text) + def setTitle(self, text): + """ + Set the title of dataform + """ + self.setTagData('title', text) - def getInstructions(self): - """ - Return the instructions of dataform - """ - return self.getTagData('instructions') + def getInstructions(self): + """ + Return the instructions of dataform + """ + return self.getTagData('instructions') - def setInstructions(self, text): - """ - Set the instructions of dataform - """ - self.setTagData('instructions', text) + def setInstructions(self, text): + """ + Set the instructions of dataform + """ + self.setTagData('instructions', text) - def addInstructions(self, text): - """ - Add one more instruction to the dataform - """ - self.addChild('instructions', {}, [text]) + def addInstructions(self, text): + """ + Add one more instruction to the dataform + """ + self.addChild('instructions', {}, [text]) - def getField(self, name): - """ - Return the datafield object with name 'name' (if exists) - """ - return self.getTag('field', attrs={'var': name}) + def getField(self, name): + """ + Return the datafield object with name 'name' (if exists) + """ + return self.getTag('field', attrs={'var': name}) - def setField(self, name): - """ - Create if nessessary or get the existing datafield object with name - 'name' and return it - """ - f = self.getField(name) - if f: - return f - return self.addChild(node=DataField(name)) + def setField(self, name): + """ + Create if nessessary or get the existing datafield object with name + 'name' and return it + """ + f = self.getField(name) + if f: + return f + return self.addChild(node=DataField(name)) - def asDict(self): - """ - Represent dataform as simple dictionary mapping of datafield names to - their values - """ - ret = {} - for field in self.getTags('field'): - name = field.getAttr('var') - typ = field.getType() - if isinstance(typ, basestring) and typ.endswith('-multi'): - val = [] - for i in field.getTags('value'): - val.append(i.getData()) - else: - val = field.getTagData('value') - ret[name] = val - if self.getTag('instructions'): - ret['instructions'] = self.getInstructions() - return ret + def asDict(self): + """ + Represent dataform as simple dictionary mapping of datafield names to + their values + """ + ret = {} + for field in self.getTags('field'): + name = field.getAttr('var') + typ = field.getType() + if isinstance(typ, basestring) and typ.endswith('-multi'): + val = [] + for i in field.getTags('value'): + val.append(i.getData()) + else: + val = field.getTagData('value') + ret[name] = val + if self.getTag('instructions'): + ret['instructions'] = self.getInstructions() + return ret - def __getitem__(self, name): - """ - Simple dictionary interface for getting datafields values by their names - """ - item = self.getField(name) - if item: - return item.getValue() - raise IndexError('No such field') + def __getitem__(self, name): + """ + Simple dictionary interface for getting datafields values by their names + """ + item = self.getField(name) + if item: + return item.getValue() + raise IndexError('No such field') - def __setitem__(self, name, val): - """ - Simple dictionary interface for setting datafields values by their names - """ - return self.setField(name).setValue(val) - -# vim: se ts=3: + def __setitem__(self, name, val): + """ + Simple dictionary interface for setting datafields values by their names + """ + return self.setField(name).setValue(val) diff --git a/src/common/xmpp/proxy_connectors.py b/src/common/xmpp/proxy_connectors.py index b4e7acfcd..2e020013e 100644 --- a/src/common/xmpp/proxy_connectors.py +++ b/src/common/xmpp/proxy_connectors.py @@ -27,214 +27,212 @@ import logging log = logging.getLogger('gajim.c.x.proxy_connectors') class ProxyConnector: - """ - Interface for proxy-connecting object - when tunnneling XMPP over proxies, - some connecting process usually has to be done before opening stream. Proxy - connectors are used right after TCP connection is estabilished - """ + """ + Interface for proxy-connecting object - when tunnneling XMPP over proxies, + some connecting process usually has to be done before opening stream. Proxy + connectors are used right after TCP connection is estabilished + """ - def __init__(self, send_method, onreceive, old_on_receive, on_success, - on_failure, xmpp_server, proxy_creds=(None,None)): - """ - Creates proxy connector, starts connecting immediately and gives control - back to transport afterwards + def __init__(self, send_method, onreceive, old_on_receive, on_success, + on_failure, xmpp_server, proxy_creds=(None, None)): + """ + Creates proxy connector, starts connecting immediately and gives control + back to transport afterwards - :param send_method: transport send method - :param onreceive: method to set on_receive callbacks - :param old_on_receive: on_receive callback that should be set when - proxy connection was successful - :param on_success: called after proxy connection was successfully opened - :param on_failure: called when errors occured while connecting - :param xmpp_server: tuple of (hostname, port) - :param proxy_creds: tuple of (proxy_user, proxy_credentials) - """ - self.send = send_method - self.onreceive = onreceive - self.old_on_receive = old_on_receive - self.on_success = on_success - self.on_failure = on_failure - self.xmpp_server = xmpp_server - self.proxy_user, self.proxy_pass = proxy_creds - self.old_on_receive = old_on_receive + :param send_method: transport send method + :param onreceive: method to set on_receive callbacks + :param old_on_receive: on_receive callback that should be set when + proxy connection was successful + :param on_success: called after proxy connection was successfully opened + :param on_failure: called when errors occured while connecting + :param xmpp_server: tuple of (hostname, port) + :param proxy_creds: tuple of (proxy_user, proxy_credentials) + """ + self.send = send_method + self.onreceive = onreceive + self.old_on_receive = old_on_receive + self.on_success = on_success + self.on_failure = on_failure + self.xmpp_server = xmpp_server + self.proxy_user, self.proxy_pass = proxy_creds + self.old_on_receive = old_on_receive - self.start_connecting() + self.start_connecting() - @classmethod - def get_instance(cls, *args, **kwargs): - """ - Factory Method for object creation + @classmethod + def get_instance(cls, *args, **kwargs): + """ + Factory Method for object creation - Use this instead of directly initializing the class in order to make unit - testing much easier. - """ - return cls(*args, **kwargs) + Use this instead of directly initializing the class in order to make unit + testing much easier. + """ + return cls(*args, **kwargs) - def start_connecting(self): - raise NotImplementedError + def start_connecting(self): + raise NotImplementedError - def connecting_over(self): - self.onreceive(self.old_on_receive) - self.on_success() + def connecting_over(self): + self.onreceive(self.old_on_receive) + self.on_success() class HTTPCONNECTConnector(ProxyConnector): - def start_connecting(self): - """ - Connect to a proxy, supply login and password to it (if were specified - while creating instance). Instruct proxy to make connection to the target - server. - """ - log.info('Proxy server contacted, performing authentification') - connector = ['CONNECT %s:%s HTTP/1.1' % self.xmpp_server, - 'Proxy-Connection: Keep-Alive', - 'Pragma: no-cache', - 'Host: %s:%s' % self.xmpp_server, - 'User-Agent: Gajim'] - if self.proxy_user and self.proxy_pass: - credentials = '%s:%s' % (self.proxy_user, self.proxy_pass) - credentials = base64.encodestring(credentials).strip() - connector.append('Proxy-Authorization: Basic '+credentials) - connector.append('\r\n') - self.onreceive(self._on_headers_sent) - self.send('\r\n'.join(connector)) + def start_connecting(self): + """ + Connect to a proxy, supply login and password to it (if were specified + while creating instance). Instruct proxy to make connection to the target + server. + """ + log.info('Proxy server contacted, performing authentification') + connector = ['CONNECT %s:%s HTTP/1.1' % self.xmpp_server, + 'Proxy-Connection: Keep-Alive', + 'Pragma: no-cache', + 'Host: %s:%s' % self.xmpp_server, + 'User-Agent: Gajim'] + if self.proxy_user and self.proxy_pass: + credentials = '%s:%s' % (self.proxy_user, self.proxy_pass) + credentials = base64.encodestring(credentials).strip() + connector.append('Proxy-Authorization: Basic '+credentials) + connector.append('\r\n') + self.onreceive(self._on_headers_sent) + self.send('\r\n'.join(connector)) - def _on_headers_sent(self, reply): - if reply is None: - return - self.reply = reply.replace('\r', '') - try: - proto, code, desc = reply.split('\n')[0].split(' ', 2) - except: - log.error("_on_headers_sent:", exc_info=True) - #traceback.print_exc() - self.on_failure('Invalid proxy reply') - return - if code <> '200': - log.error('Invalid proxy reply: %s %s %s' % (proto, code, desc)) - self.on_failure('Invalid proxy reply') - return - if len(reply) != 2: - pass - self.connecting_over() + def _on_headers_sent(self, reply): + if reply is None: + return + self.reply = reply.replace('\r', '') + try: + proto, code, desc = reply.split('\n')[0].split(' ', 2) + except: + log.error("_on_headers_sent:", exc_info=True) + #traceback.print_exc() + self.on_failure('Invalid proxy reply') + return + if code <> '200': + log.error('Invalid proxy reply: %s %s %s' % (proto, code, desc)) + self.on_failure('Invalid proxy reply') + return + if len(reply) != 2: + pass + self.connecting_over() class SOCKS5Connector(ProxyConnector): - """ - SOCKS5 proxy connection class. Allows to use SOCKS5 proxies with - (optionally) simple authentication (only USERNAME/PASSWORD auth) - """ + """ + SOCKS5 proxy connection class. Allows to use SOCKS5 proxies with + (optionally) simple authentication (only USERNAME/PASSWORD auth) + """ - def start_connecting(self): - log.info('Proxy server contacted, performing authentification') - if self.proxy_user and self.proxy_pass: - to_send = '\x05\x02\x00\x02' - else: - to_send = '\x05\x01\x00' - self.onreceive(self._on_greeting_sent) - self.send(to_send) + def start_connecting(self): + log.info('Proxy server contacted, performing authentification') + if self.proxy_user and self.proxy_pass: + to_send = '\x05\x02\x00\x02' + else: + to_send = '\x05\x01\x00' + self.onreceive(self._on_greeting_sent) + self.send(to_send) - def _on_greeting_sent(self, reply): - if reply is None: - return - if len(reply) != 2: - self.on_failure('Invalid proxy reply') - return - if reply[0] != '\x05': - log.info('Invalid proxy reply') - self.on_failure('Invalid proxy reply') - return - if reply[1] == '\x00': - return self._on_proxy_auth('\x01\x00') - elif reply[1] == '\x02': - to_send = '\x01' + chr(len(self.proxy_user)) + self.proxy_user +\ - chr(len(self.proxy_pass)) + self.proxy_pass - self.onreceive(self._on_proxy_auth) - self.send(to_send) - else: - if reply[1] == '\xff': - log.error('Authentification to proxy impossible: no acceptable ' - 'auth method') - self.on_failure('Authentification to proxy impossible: no ' - 'acceptable authentification method') - return - log.error('Invalid proxy reply') - self.on_failure('Invalid proxy reply') - return + def _on_greeting_sent(self, reply): + if reply is None: + return + if len(reply) != 2: + self.on_failure('Invalid proxy reply') + return + if reply[0] != '\x05': + log.info('Invalid proxy reply') + self.on_failure('Invalid proxy reply') + return + if reply[1] == '\x00': + return self._on_proxy_auth('\x01\x00') + elif reply[1] == '\x02': + to_send = '\x01' + chr(len(self.proxy_user)) + self.proxy_user +\ + chr(len(self.proxy_pass)) + self.proxy_pass + self.onreceive(self._on_proxy_auth) + self.send(to_send) + else: + if reply[1] == '\xff': + log.error('Authentification to proxy impossible: no acceptable ' + 'auth method') + self.on_failure('Authentification to proxy impossible: no ' + 'acceptable authentification method') + return + log.error('Invalid proxy reply') + self.on_failure('Invalid proxy reply') + return - def _on_proxy_auth(self, reply): - if reply is None: - return - if len(reply) != 2: - log.error('Invalid proxy reply') - self.on_failure('Invalid proxy reply') - return - if reply[0] != '\x01': - log.error('Invalid proxy reply') - self.on_failure('Invalid proxy reply') - return - if reply[1] != '\x00': - log.error('Authentification to proxy failed') - self.on_failure('Authentification to proxy failed') - return - log.info('Authentification successfull. Jabber server contacted.') - # Request connection - req = "\x05\x01\x00" - # If the given destination address is an IP address, we'll - # use the IPv4 address request even if remote resolving was specified. - try: - self.ipaddr = socket.inet_aton(self.xmpp_server[0]) - req = req + "\x01" + self.ipaddr - except socket.error: - # Well it's not an IP number, so it's probably a DNS name. -# if self.__proxy[3]==True: - # Resolve remotely - self.ipaddr = None - req = req + "\x03" + chr(len(self.xmpp_server[0])) + self.xmpp_server[0] -# else: -# # Resolve locally -# self.ipaddr = socket.inet_aton(socket.gethostbyname(self.xmpp_server[0])) -# req = req + "\x01" + ipaddr - req = req + struct.pack(">H", self.xmpp_server[1]) - self.onreceive(self._on_req_sent) - self.send(req) + def _on_proxy_auth(self, reply): + if reply is None: + return + if len(reply) != 2: + log.error('Invalid proxy reply') + self.on_failure('Invalid proxy reply') + return + if reply[0] != '\x01': + log.error('Invalid proxy reply') + self.on_failure('Invalid proxy reply') + return + if reply[1] != '\x00': + log.error('Authentification to proxy failed') + self.on_failure('Authentification to proxy failed') + return + log.info('Authentification successfull. Jabber server contacted.') + # Request connection + req = "\x05\x01\x00" + # If the given destination address is an IP address, we'll + # use the IPv4 address request even if remote resolving was specified. + try: + self.ipaddr = socket.inet_aton(self.xmpp_server[0]) + req = req + "\x01" + self.ipaddr + except socket.error: + # Well it's not an IP number, so it's probably a DNS name. +# if self.__proxy[3]==True: + # Resolve remotely + self.ipaddr = None + req = req + "\x03" + chr(len(self.xmpp_server[0])) + self.xmpp_server[0] +# else: +# # Resolve locally +# self.ipaddr = socket.inet_aton(socket.gethostbyname(self.xmpp_server[0])) +# req = req + "\x01" + ipaddr + req = req + struct.pack(">H", self.xmpp_server[1]) + self.onreceive(self._on_req_sent) + self.send(req) - def _on_req_sent(self, reply): - if reply is None: - return - if len(reply) < 10: - log.error('Invalid proxy reply') - self.on_failure('Invalid proxy reply') - return - if reply[0] != '\x05': - log.error('Invalid proxy reply') - self.on_failure('Invalid proxy reply') - return - if reply[1] != "\x00": - # Connection failed - if ord(reply[1])<9: - errors = ['general SOCKS server failure', - 'connection not allowed by ruleset', - 'Network unreachable', - 'Host unreachable', - 'Connection refused', - 'TTL expired', - 'Command not supported', - 'Address type not supported' - ] - txt = errors[ord(reply[1])-1] - else: - txt = 'Invalid proxy reply' - log.error(txt) - self.on_failure(txt) - return - # Get the bound address/port - elif reply[3] == "\x01": - begin, end = 3, 7 - elif reply[3] == "\x03": - begin, end = 4, 4 + reply[4] - else: - log.error('Invalid proxy reply') - self.on_failure('Invalid proxy reply') - return - self.connecting_over() - -# vim: se ts=3: + def _on_req_sent(self, reply): + if reply is None: + return + if len(reply) < 10: + log.error('Invalid proxy reply') + self.on_failure('Invalid proxy reply') + return + if reply[0] != '\x05': + log.error('Invalid proxy reply') + self.on_failure('Invalid proxy reply') + return + if reply[1] != "\x00": + # Connection failed + if ord(reply[1])<9: + errors = ['general SOCKS server failure', + 'connection not allowed by ruleset', + 'Network unreachable', + 'Host unreachable', + 'Connection refused', + 'TTL expired', + 'Command not supported', + 'Address type not supported' + ] + txt = errors[ord(reply[1])-1] + else: + txt = 'Invalid proxy reply' + log.error(txt) + self.on_failure(txt) + return + # Get the bound address/port + elif reply[3] == "\x01": + begin, end = 3, 7 + elif reply[3] == "\x03": + begin, end = 4, 4 + reply[4] + else: + log.error('Invalid proxy reply') + self.on_failure('Invalid proxy reply') + return + self.connecting_over() diff --git a/src/common/xmpp/roster_nb.py b/src/common/xmpp/roster_nb.py index f343c8341..e90a1820a 100644 --- a/src/common/xmpp/roster_nb.py +++ b/src/common/xmpp/roster_nb.py @@ -1,8 +1,8 @@ ## roster_nb.py -## based on roster.py +## based on roster.py ## ## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov -## modified by Dimitur Kirov +## modified by Dimitur Kirov ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -30,337 +30,335 @@ log = logging.getLogger('gajim.c.x.roster_nb') class NonBlockingRoster(PlugIn): - """ - Defines a plenty of methods that will allow you to manage roster. Also - automatically track presences from remote JIDs taking into account that - every JID can have multiple resources connected. Does not currently support - 'error' presences. You can also use mapping interface for access to the - internal representation of contacts in roster - """ + """ + Defines a plenty of methods that will allow you to manage roster. Also + automatically track presences from remote JIDs taking into account that + every JID can have multiple resources connected. Does not currently support + 'error' presences. You can also use mapping interface for access to the + internal representation of contacts in roster + """ - def __init__(self, version=''): - """ - Init internal variables - """ - PlugIn.__init__(self) - self.version = version - self._data = {} - self.set=None - self._exported_methods=[self.getRoster] - self.received_from_server = False + def __init__(self, version=''): + """ + Init internal variables + """ + PlugIn.__init__(self) + self.version = version + self._data = {} + self.set=None + self._exported_methods=[self.getRoster] + self.received_from_server = False - def Request(self, force=0): - """ - Request roster from server if it were not yet requested (or if the - 'force' argument is set) - """ - if self.set is None: - self.set = 0 - elif not force: - return + def Request(self, force=0): + """ + Request roster from server if it were not yet requested (or if the + 'force' argument is set) + """ + if self.set is None: + self.set = 0 + elif not force: + return - iq = Iq('get',NS_ROSTER) - iq.setTagAttr('query', 'ver', self.version) - id_ = self._owner.getAnID() - iq.setID(id_) - self._owner.send(iq) - log.info('Roster requested from server') - return id_ + iq = Iq('get', NS_ROSTER) + iq.setTagAttr('query', 'ver', self.version) + id_ = self._owner.getAnID() + iq.setID(id_) + self._owner.send(iq) + log.info('Roster requested from server') + return id_ - def RosterIqHandler(self, dis, stanza): - """ - Subscription tracker. Used internally for setting items state in internal - roster representation - """ - sender = stanza.getAttr('from') - if not sender is None and not sender.bareMatch( - self._owner.User + '@' + self._owner.Server): - return - query = stanza.getTag('query') - if query: - self.received_from_server = True - self.version = stanza.getTagAttr('query', 'ver') - if self.version is None: - self.version = '' - for item in query.getTags('item'): - jid=item.getAttr('jid') - if item.getAttr('subscription')=='remove': - if self._data.has_key(jid): del self._data[jid] - # Looks like we have a workaround - # raise NodeProcessed # a MUST - log.info('Setting roster item %s...' % jid) - if not self._data.has_key(jid): self._data[jid]={} - self._data[jid]['name']=item.getAttr('name') - self._data[jid]['ask']=item.getAttr('ask') - self._data[jid]['subscription']=item.getAttr('subscription') - self._data[jid]['groups']=[] - if not self._data[jid].has_key('resources'): self._data[jid]['resources']={} - for group in item.getTags('group'): - if group.getData() not in self._data[jid]['groups']: - self._data[jid]['groups'].append(group.getData()) - self._data[self._owner.User+'@'+self._owner.Server]={'resources':{},'name':None,'ask':None,'subscription':None,'groups':None,} - self.set=1 - # Looks like we have a workaround - # raise NodeProcessed # a MUST. Otherwise you'll get back an + def RosterIqHandler(self, dis, stanza): + """ + Subscription tracker. Used internally for setting items state in internal + roster representation + """ + sender = stanza.getAttr('from') + if not sender is None and not sender.bareMatch( + self._owner.User + '@' + self._owner.Server): + return + query = stanza.getTag('query') + if query: + self.received_from_server = True + self.version = stanza.getTagAttr('query', 'ver') + if self.version is None: + self.version = '' + for item in query.getTags('item'): + jid=item.getAttr('jid') + if item.getAttr('subscription')=='remove': + if self._data.has_key(jid): del self._data[jid] + # Looks like we have a workaround + # raise NodeProcessed # a MUST + log.info('Setting roster item %s...' % jid) + if not self._data.has_key(jid): self._data[jid]={} + self._data[jid]['name']=item.getAttr('name') + self._data[jid]['ask']=item.getAttr('ask') + self._data[jid]['subscription']=item.getAttr('subscription') + self._data[jid]['groups']=[] + if not self._data[jid].has_key('resources'): self._data[jid]['resources']={} + for group in item.getTags('group'): + if group.getData() not in self._data[jid]['groups']: + self._data[jid]['groups'].append(group.getData()) + self._data[self._owner.User+'@'+self._owner.Server]={'resources': {}, 'name': None, 'ask': None, 'subscription': None, 'groups': None,} + self.set=1 + # Looks like we have a workaround + # raise NodeProcessed # a MUST. Otherwise you'll get back an - def PresenceHandler(self, dis, pres): - """ - Presence tracker. Used internally for setting items' resources state in - internal roster representation - """ - if pres.getTag('x', namespace=NS_MUC_USER): - return - jid=pres.getFrom() - if not jid: - # If no from attribue, it's from server - jid=self._owner.Server - jid=JID(jid) - if not self._data.has_key(jid.getStripped()): self._data[jid.getStripped()]={'name':None,'ask':None,'subscription':'none','groups':['Not in roster'],'resources':{}} - if type(self._data[jid.getStripped()]['resources'])!=type(dict()): - self._data[jid.getStripped()]['resources']={} - item=self._data[jid.getStripped()] - typ=pres.getType() + def PresenceHandler(self, dis, pres): + """ + Presence tracker. Used internally for setting items' resources state in + internal roster representation + """ + if pres.getTag('x', namespace=NS_MUC_USER): + return + jid=pres.getFrom() + if not jid: + # If no from attribue, it's from server + jid=self._owner.Server + jid=JID(jid) + if not self._data.has_key(jid.getStripped()): self._data[jid.getStripped()]={'name':None,'ask':None,'subscription':'none','groups':['Not in roster'],'resources':{}} + if type(self._data[jid.getStripped()]['resources'])!=type(dict()): + self._data[jid.getStripped()]['resources']={} + item=self._data[jid.getStripped()] + typ=pres.getType() - if not typ: - log.info('Setting roster item %s for resource %s...'%(jid.getStripped(),jid.getResource())) - item['resources'][jid.getResource()]=res={'show':None,'status':None,'priority':'0','timestamp':None} - if pres.getTag('show'): res['show']=pres.getShow() - if pres.getTag('status'): res['status']=pres.getStatus() - if pres.getTag('priority'): res['priority']=pres.getPriority() - if not pres.getTimestamp(): pres.setTimestamp() - res['timestamp']=pres.getTimestamp() - elif typ=='unavailable' and item['resources'].has_key(jid.getResource()): del item['resources'][jid.getResource()] - # Need to handle type='error' also + if not typ: + log.info('Setting roster item %s for resource %s...'%(jid.getStripped(), jid.getResource())) + item['resources'][jid.getResource()]=res={'show':None,'status':None,'priority':'0','timestamp':None} + if pres.getTag('show'): res['show']=pres.getShow() + if pres.getTag('status'): res['status']=pres.getStatus() + if pres.getTag('priority'): res['priority']=pres.getPriority() + if not pres.getTimestamp(): pres.setTimestamp() + res['timestamp']=pres.getTimestamp() + elif typ=='unavailable' and item['resources'].has_key(jid.getResource()): del item['resources'][jid.getResource()] + # Need to handle type='error' also - def _getItemData(self, jid, dataname): - """ - Return specific jid's representation in internal format. Used internally - """ - jid = jid[:(jid+'/').find('/')] - return self._data[jid][dataname] + def _getItemData(self, jid, dataname): + """ + Return specific jid's representation in internal format. Used internally + """ + jid = jid[:(jid+'/').find('/')] + return self._data[jid][dataname] - def _getResourceData(self, jid, dataname): - """ - Return specific jid's resource representation in internal format. Used - internally - """ - if jid.find('/') + 1: - jid, resource = jid.split('/', 1) - if self._data[jid]['resources'].has_key(resource): - return self._data[jid]['resources'][resource][dataname] - elif self._data[jid]['resources'].keys(): - lastpri = -129 - for r in self._data[jid]['resources'].keys(): - if int(self._data[jid]['resources'][r]['priority']) > lastpri: - resource,lastpri=r,int(self._data[jid]['resources'][r]['priority']) - return self._data[jid]['resources'][resource][dataname] + def _getResourceData(self, jid, dataname): + """ + Return specific jid's resource representation in internal format. Used + internally + """ + if jid.find('/') + 1: + jid, resource = jid.split('/', 1) + if self._data[jid]['resources'].has_key(resource): + return self._data[jid]['resources'][resource][dataname] + elif self._data[jid]['resources'].keys(): + lastpri = -129 + for r in self._data[jid]['resources'].keys(): + if int(self._data[jid]['resources'][r]['priority']) > lastpri: + resource, lastpri=r, int(self._data[jid]['resources'][r]['priority']) + return self._data[jid]['resources'][resource][dataname] - def delItem(self, jid): - """ - Delete contact 'jid' from roster - """ - self._owner.send(Iq('set', NS_ROSTER, payload=[Node('item', {'jid': jid, 'subscription': 'remove'})])) + def delItem(self, jid): + """ + Delete contact 'jid' from roster + """ + self._owner.send(Iq('set', NS_ROSTER, payload=[Node('item', {'jid': jid, 'subscription': 'remove'})])) - def getAsk(self, jid): - """ - Return 'ask' value of contact 'jid' - """ - return self._getItemData(jid, 'ask') + def getAsk(self, jid): + """ + Return 'ask' value of contact 'jid' + """ + return self._getItemData(jid, 'ask') - def getGroups(self, jid): - """ - Return groups list that contact 'jid' belongs to - """ - return self._getItemData(jid, 'groups') + def getGroups(self, jid): + """ + Return groups list that contact 'jid' belongs to + """ + return self._getItemData(jid, 'groups') - def getName(self, jid): - """ - Return name of contact 'jid' - """ - return self._getItemData(jid, 'name') + def getName(self, jid): + """ + Return name of contact 'jid' + """ + return self._getItemData(jid, 'name') - def getPriority(self, jid): - """ - Return priority of contact 'jid'. 'jid' should be a full (not bare) JID - """ - return self._getResourceData(jid, 'priority') + def getPriority(self, jid): + """ + Return priority of contact 'jid'. 'jid' should be a full (not bare) JID + """ + return self._getResourceData(jid, 'priority') - def getRawRoster(self): - """ - Return roster representation in internal format - """ - return self._data + def getRawRoster(self): + """ + Return roster representation in internal format + """ + return self._data - def getRawItem(self, jid): - """ - Return roster item 'jid' representation in internal format - """ - return self._data[jid[:(jid+'/').find('/')]] + def getRawItem(self, jid): + """ + Return roster item 'jid' representation in internal format + """ + return self._data[jid[:(jid+'/').find('/')]] - def getShow(self, jid): - """ - Return 'show' value of contact 'jid'. 'jid' should be a full (not bare) - JID - """ - return self._getResourceData(jid, 'show') + def getShow(self, jid): + """ + Return 'show' value of contact 'jid'. 'jid' should be a full (not bare) + JID + """ + return self._getResourceData(jid, 'show') - def getStatus(self, jid): - """ - Return 'status' value of contact 'jid'. 'jid' should be a full (not bare) - JID - """ - return self._getResourceData(jid, 'status') + def getStatus(self, jid): + """ + Return 'status' value of contact 'jid'. 'jid' should be a full (not bare) + JID + """ + return self._getResourceData(jid, 'status') - def getSubscription(self, jid): - """ - Return 'subscription' value of contact 'jid' - """ - return self._getItemData(jid, 'subscription') + def getSubscription(self, jid): + """ + Return 'subscription' value of contact 'jid' + """ + return self._getItemData(jid, 'subscription') - def getResources(self, jid): - """ - Return list of connected resources of contact 'jid' - """ - return self._data[jid[:(jid+'/').find('/')]]['resources'].keys() + def getResources(self, jid): + """ + Return list of connected resources of contact 'jid' + """ + return self._data[jid[:(jid+'/').find('/')]]['resources'].keys() - def setItem(self, jid, name=None, groups=[]): - """ - Rename contact 'jid' and sets the groups list that it now belongs to - """ - iq = Iq('set',NS_ROSTER) - query = iq.getTag('query') - attrs = {'jid': jid} - if name: - attrs['name'] = name - item = query.setTag('item' ,attrs) - for group in groups: - item.addChild(node=Node('group', payload=[group])) - self._owner.send(iq) + def setItem(self, jid, name=None, groups=[]): + """ + Rename contact 'jid' and sets the groups list that it now belongs to + """ + iq = Iq('set', NS_ROSTER) + query = iq.getTag('query') + attrs = {'jid': jid} + if name: + attrs['name'] = name + item = query.setTag('item', attrs) + for group in groups: + item.addChild(node=Node('group', payload=[group])) + self._owner.send(iq) - def setItemMulti(self, items): - """ - Rename multiple contacts and sets their group lists - """ - iq = Iq('set', NS_ROSTER) - query = iq.getTag('query') - for i in items: - attrs = {'jid': i['jid']} - if i['name']: - attrs['name'] = i['name'] - item = query.setTag('item', attrs) - for group in i['groups']: - item.addChild(node=Node('group', payload=[group])) - self._owner.send(iq) + def setItemMulti(self, items): + """ + Rename multiple contacts and sets their group lists + """ + iq = Iq('set', NS_ROSTER) + query = iq.getTag('query') + for i in items: + attrs = {'jid': i['jid']} + if i['name']: + attrs['name'] = i['name'] + item = query.setTag('item', attrs) + for group in i['groups']: + item.addChild(node=Node('group', payload=[group])) + self._owner.send(iq) - def getItems(self): - """ - Return list of all [bare] JIDs that the roster is currently tracks - """ - return self._data.keys() + def getItems(self): + """ + Return list of all [bare] JIDs that the roster is currently tracks + """ + return self._data.keys() - def keys(self): - """ - Same as getItems. Provided for the sake of dictionary interface - """ - return self._data.keys() + def keys(self): + """ + Same as getItems. Provided for the sake of dictionary interface + """ + return self._data.keys() - def __getitem__(self, item): - """ - Get the contact in the internal format. Raises KeyError if JID 'item' is - not in roster - """ - return self._data[item] + def __getitem__(self, item): + """ + Get the contact in the internal format. Raises KeyError if JID 'item' is + not in roster + """ + return self._data[item] - def getItem(self,item): - """ - Get the contact in the internal format (or None if JID 'item' is not in - roster) - """ - if self._data.has_key(item): - return self._data[item] + def getItem(self, item): + """ + Get the contact in the internal format (or None if JID 'item' is not in + roster) + """ + if self._data.has_key(item): + return self._data[item] - def Subscribe(self, jid): - """ - Send subscription request to JID 'jid' - """ - self._owner.send(Presence(jid, 'subscribe')) + def Subscribe(self, jid): + """ + Send subscription request to JID 'jid' + """ + self._owner.send(Presence(jid, 'subscribe')) - def Unsubscribe(self,jid): - """ - Ask for removing our subscription for JID 'jid' - """ - self._owner.send(Presence(jid, 'unsubscribe')) + def Unsubscribe(self, jid): + """ + Ask for removing our subscription for JID 'jid' + """ + self._owner.send(Presence(jid, 'unsubscribe')) - def Authorize(self, jid): - """ - Authorize JID 'jid'. Works only if these JID requested auth previously - """ - self._owner.send(Presence(jid, 'subscribed')) + def Authorize(self, jid): + """ + Authorize JID 'jid'. Works only if these JID requested auth previously + """ + self._owner.send(Presence(jid, 'subscribed')) - def Unauthorize(self, jid): - """ - Unauthorise JID 'jid'. Use for declining authorisation request or for - removing existing authorization - """ - self._owner.send(Presence(jid, 'unsubscribed')) + def Unauthorize(self, jid): + """ + Unauthorise JID 'jid'. Use for declining authorisation request or for + removing existing authorization + """ + self._owner.send(Presence(jid, 'unsubscribed')) - def getRaw(self): - """ - Return the internal data representation of the roster - """ - return self._data + def getRaw(self): + """ + Return the internal data representation of the roster + """ + return self._data - def setRaw(self, data): - """ - Return the internal data representation of the roster - """ - self._data = data - self._data[self._owner.User + '@' + self._owner.Server] = { - 'resources': {}, - 'name': None, - 'ask': None, - 'subscription': None, - 'groups': None - } - self.set = 1 + def setRaw(self, data): + """ + Return the internal data representation of the roster + """ + self._data = data + self._data[self._owner.User + '@' + self._owner.Server] = { + 'resources': {}, + 'name': None, + 'ask': None, + 'subscription': None, + 'groups': None + } + self.set = 1 - def plugin(self, owner, request=1): - """ - Register presence and subscription trackers in the owner's dispatcher. - Also request roster from server if the 'request' argument is set. Used - internally - """ - self._owner.RegisterHandler('iq', self.RosterIqHandler, 'result', NS_ROSTER, makefirst = 1) - self._owner.RegisterHandler('iq', self.RosterIqHandler, 'set', NS_ROSTER) - self._owner.RegisterHandler('presence', self.PresenceHandler) - if request: - return self.Request() + def plugin(self, owner, request=1): + """ + Register presence and subscription trackers in the owner's dispatcher. + Also request roster from server if the 'request' argument is set. Used + internally + """ + self._owner.RegisterHandler('iq', self.RosterIqHandler, 'result', NS_ROSTER, makefirst = 1) + self._owner.RegisterHandler('iq', self.RosterIqHandler, 'set', NS_ROSTER) + self._owner.RegisterHandler('presence', self.PresenceHandler) + if request: + return self.Request() - def _on_roster_set(self, data): - if data: - self._owner.Dispatcher.ProcessNonBlocking(data) - if not self.set: - return - self._owner.onreceive(None) - if self.on_ready: - self.on_ready(self) - self.on_ready = None - return True + def _on_roster_set(self, data): + if data: + self._owner.Dispatcher.ProcessNonBlocking(data) + if not self.set: + return + self._owner.onreceive(None) + if self.on_ready: + self.on_ready(self) + self.on_ready = None + return True - def getRoster(self, on_ready=None, force=False): - """ - Request roster from server if neccessary and returns self - """ - return_self = True - if not self.set: - self.on_ready = on_ready - self._owner.onreceive(self._on_roster_set) - return_self = False - elif on_ready: - on_ready(self) - return_self = False - if return_self or force: - return self - return None - -# vim: se ts=3: + def getRoster(self, on_ready=None, force=False): + """ + Request roster from server if neccessary and returns self + """ + return_self = True + if not self.set: + self.on_ready = on_ready + self._owner.onreceive(self._on_roster_set) + return_self = False + elif on_ready: + on_ready(self) + return_self = False + if return_self or force: + return self + return None diff --git a/src/common/xmpp/simplexml.py b/src/common/xmpp/simplexml.py index 2ace6657a..4a11cda7d 100644 --- a/src/common/xmpp/simplexml.py +++ b/src/common/xmpp/simplexml.py @@ -25,670 +25,668 @@ import logging log = logging.getLogger('gajim.c.x.simplexml') def XMLescape(txt): - """ - Return provided string with symbols & < > " replaced by their respective XML - entities - """ - # replace also FORM FEED and ESC, because they are not valid XML chars - return txt.replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """).replace(u'\x0C', "").replace(u'\x1B', "") + """ + Return provided string with symbols & < > " replaced by their respective XML + entities + """ + # replace also FORM FEED and ESC, because they are not valid XML chars + return txt.replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """).replace(u'\x0C', "").replace(u'\x1B', "") ENCODING='utf-8' def ustr(what): - """ - Converts object "what" to unicode string using it's own __str__ method if - accessible or unicode method otherwise - """ - if isinstance(what, unicode): - return what - try: - r = what.__str__() - except AttributeError: - r = str(what) - if not isinstance(r, unicode): - return unicode(r, ENCODING) - return r + """ + Converts object "what" to unicode string using it's own __str__ method if + accessible or unicode method otherwise + """ + if isinstance(what, unicode): + return what + try: + r = what.__str__() + except AttributeError: + r = str(what) + if not isinstance(r, unicode): + return unicode(r, ENCODING) + return r class Node(object): - """ - Node class describes syntax of separate XML Node. It have a constructor that - permits node creation from set of "namespace name", attributes and payload - of text strings and other nodes. It does not natively support building node - from text string and uses NodeBuilder class for that purpose. After - creation node can be mangled in many ways so it can be completely changed. - Also node can be serialised into string in one of two modes: default (where - the textual representation of node describes it exactly) and "fancy" - with - whitespace added to make indentation and thus make result more readable by - human. + """ + Node class describes syntax of separate XML Node. It have a constructor that + permits node creation from set of "namespace name", attributes and payload + of text strings and other nodes. It does not natively support building node + from text string and uses NodeBuilder class for that purpose. After + creation node can be mangled in many ways so it can be completely changed. + Also node can be serialised into string in one of two modes: default (where + the textual representation of node describes it exactly) and "fancy" - with + whitespace added to make indentation and thus make result more readable by + human. - Node class have attribute FORCE_NODE_RECREATION that is defaults to False - thus enabling fast node replication from the some other node. The drawback - of the fast way is that new node shares some info with the "original" node - that is changing the one node may influence the other. Though it is rarely - needed (in xmpppy it is never needed at all since I'm usually never using - original node after replication (and using replication only to move upwards - on the classes tree). - """ + Node class have attribute FORCE_NODE_RECREATION that is defaults to False + thus enabling fast node replication from the some other node. The drawback + of the fast way is that new node shares some info with the "original" node + that is changing the one node may influence the other. Though it is rarely + needed (in xmpppy it is never needed at all since I'm usually never using + original node after replication (and using replication only to move upwards + on the classes tree). + """ - FORCE_NODE_RECREATION = 0 + FORCE_NODE_RECREATION = 0 - def __init__(self, tag=None, attrs={}, payload=[], parent=None, nsp=None, - node_built=False, node=None): - """ - Takes "tag" argument as the name of node (prepended by namespace, if - needed and separated from it by a space), attrs dictionary as the set of - arguments, payload list as the set of textual strings and child nodes - that this node carries within itself and "parent" argument that is - another node that this one will be the child of. Also the __init__ can be - provided with "node" argument that is either a text string containing - exactly one node or another Node instance to begin with. If both "node" - and other arguments is provided then the node initially created as - replica of "node" provided and then modified to be compliant with other - arguments. - """ - if node: - if self.FORCE_NODE_RECREATION and isinstance(node, Node): - node = str(node) - if not isinstance(node, Node): - node = NodeBuilder(node,self) - node_built = True - else: - self.name, self.namespace, self.attrs, self.data, self.kids, self.parent, self.nsd = node.name, node.namespace, {}, [], [], node.parent, {} - for key in node.attrs.keys(): - self.attrs[key] = node.attrs[key] - for data in node.data: - self.data.append(data) - for kid in node.kids: - self.kids.append(kid) - for k,v in node.nsd.items(): - self.nsd[k] = v - else: - self.name, self.namespace, self.attrs, self.data, self.kids, self.parent, self.nsd = 'tag', '', {}, [], [], None, {} - if parent: - self.parent = parent - self.nsp_cache = {} - if nsp: - for k,v in nsp.items(): self.nsp_cache[k] = v - for attr,val in attrs.items(): - if attr == 'xmlns': - self.nsd[u''] = val - elif attr.startswith('xmlns:'): - self.nsd[attr[6:]] = val - self.attrs[attr]=attrs[attr] - if tag: - if node_built: - pfx,self.name = (['']+tag.split(':'))[-2:] - self.namespace = self.lookup_nsp(pfx) - else: - if ' ' in tag: - self.namespace,self.name = tag.split() - else: - self.name = tag - if isinstance(payload, basestring): payload=[payload] - for i in payload: - if isinstance(i, Node): - self.addChild(node=i) - else: - self.data.append(ustr(i)) + def __init__(self, tag=None, attrs={}, payload=[], parent=None, nsp=None, + node_built=False, node=None): + """ + Takes "tag" argument as the name of node (prepended by namespace, if + needed and separated from it by a space), attrs dictionary as the set of + arguments, payload list as the set of textual strings and child nodes + that this node carries within itself and "parent" argument that is + another node that this one will be the child of. Also the __init__ can be + provided with "node" argument that is either a text string containing + exactly one node or another Node instance to begin with. If both "node" + and other arguments is provided then the node initially created as + replica of "node" provided and then modified to be compliant with other + arguments. + """ + if node: + if self.FORCE_NODE_RECREATION and isinstance(node, Node): + node = str(node) + if not isinstance(node, Node): + node = NodeBuilder(node, self) + node_built = True + else: + self.name, self.namespace, self.attrs, self.data, self.kids, self.parent, self.nsd = node.name, node.namespace, {}, [], [], node.parent, {} + for key in node.attrs.keys(): + self.attrs[key] = node.attrs[key] + for data in node.data: + self.data.append(data) + for kid in node.kids: + self.kids.append(kid) + for k, v in node.nsd.items(): + self.nsd[k] = v + else: + self.name, self.namespace, self.attrs, self.data, self.kids, self.parent, self.nsd = 'tag', '', {}, [], [], None, {} + if parent: + self.parent = parent + self.nsp_cache = {} + if nsp: + for k, v in nsp.items(): self.nsp_cache[k] = v + for attr, val in attrs.items(): + if attr == 'xmlns': + self.nsd[u''] = val + elif attr.startswith('xmlns:'): + self.nsd[attr[6:]] = val + self.attrs[attr]=attrs[attr] + if tag: + if node_built: + pfx, self.name = (['']+tag.split(':'))[-2:] + self.namespace = self.lookup_nsp(pfx) + else: + if ' ' in tag: + self.namespace, self.name = tag.split() + else: + self.name = tag + if isinstance(payload, basestring): payload=[payload] + for i in payload: + if isinstance(i, Node): + self.addChild(node=i) + else: + self.data.append(ustr(i)) - def lookup_nsp(self, pfx=''): - ns = self.nsd.get(pfx,None) - if ns is None: - ns = self.nsp_cache.get(pfx,None) - if ns is None: - if self.parent: - ns = self.parent.lookup_nsp(pfx) - self.nsp_cache[pfx] = ns - else: - return 'http://www.gajim.org/xmlns/undeclared' - return ns + def lookup_nsp(self, pfx=''): + ns = self.nsd.get(pfx, None) + if ns is None: + ns = self.nsp_cache.get(pfx, None) + if ns is None: + if self.parent: + ns = self.parent.lookup_nsp(pfx) + self.nsp_cache[pfx] = ns + else: + return 'http://www.gajim.org/xmlns/undeclared' + return ns - def __str__(self, fancy=0): - """ - Method used to dump node into textual representation. If "fancy" argument - is set to True produces indented output for readability - """ - s = (fancy-1) * 2 * ' ' + "<" + self.name - if self.namespace: - if not self.parent or self.parent.namespace!=self.namespace: - if 'xmlns' not in self.attrs: - s = s + ' xmlns="%s"'%self.namespace - for key in self.attrs.keys(): - val = ustr(self.attrs[key]) - s = s + ' %s="%s"' % ( key, XMLescape(val) ) - s = s + ">" - cnt = 0 - if self.kids: - if fancy: s = s + "\n" - for a in self.kids: - if not fancy and (len(self.data)-1)>=cnt: s=s+XMLescape(self.data[cnt]) - elif (len(self.data)-1)>=cnt: s=s+XMLescape(self.data[cnt].strip()) - if isinstance(a, str) or isinstance(a, unicode): - s = s + a.__str__() - else: - s = s + a.__str__(fancy and fancy+1) - cnt=cnt+1 - if not fancy and (len(self.data)-1) >= cnt: s = s + XMLescape(self.data[cnt]) - elif (len(self.data)-1) >= cnt: s = s + XMLescape(self.data[cnt].strip()) - if not self.kids and s.endswith('>'): - s=s[:-1]+' />' - if fancy: s = s + "\n" - else: - if fancy and not self.data: s = s + (fancy-1) * 2 * ' ' - s = s + "" - if fancy: s = s + "\n" - return s + def __str__(self, fancy=0): + """ + Method used to dump node into textual representation. If "fancy" argument + is set to True produces indented output for readability + """ + s = (fancy-1) * 2 * ' ' + "<" + self.name + if self.namespace: + if not self.parent or self.parent.namespace!=self.namespace: + if 'xmlns' not in self.attrs: + s = s + ' xmlns="%s"'%self.namespace + for key in self.attrs.keys(): + val = ustr(self.attrs[key]) + s = s + ' %s="%s"' % ( key, XMLescape(val) ) + s = s + ">" + cnt = 0 + if self.kids: + if fancy: s = s + "\n" + for a in self.kids: + if not fancy and (len(self.data)-1)>=cnt: s=s+XMLescape(self.data[cnt]) + elif (len(self.data)-1)>=cnt: s=s+XMLescape(self.data[cnt].strip()) + if isinstance(a, str) or isinstance(a, unicode): + s = s + a.__str__() + else: + s = s + a.__str__(fancy and fancy+1) + cnt=cnt+1 + if not fancy and (len(self.data)-1) >= cnt: s = s + XMLescape(self.data[cnt]) + elif (len(self.data)-1) >= cnt: s = s + XMLescape(self.data[cnt].strip()) + if not self.kids and s.endswith('>'): + s=s[:-1]+' />' + if fancy: s = s + "\n" + else: + if fancy and not self.data: s = s + (fancy-1) * 2 * ' ' + s = s + "" + if fancy: s = s + "\n" + return s - def addChild(self, name=None, attrs={}, payload=[], namespace=None, node=None): - """ - If "node" argument is provided, adds it as child node. Else creates new - node from the other arguments' values and adds it as well - """ - if 'xmlns' in attrs: - raise AttributeError("Use namespace=x instead of attrs={'xmlns':x}") - if node: - newnode=node - node.parent = self - else: newnode=Node(tag=name, parent=self, attrs=attrs, payload=payload) - if namespace: - newnode.setNamespace(namespace) - self.kids.append(newnode) - return newnode + def addChild(self, name=None, attrs={}, payload=[], namespace=None, node=None): + """ + If "node" argument is provided, adds it as child node. Else creates new + node from the other arguments' values and adds it as well + """ + if 'xmlns' in attrs: + raise AttributeError("Use namespace=x instead of attrs={'xmlns':x}") + if node: + newnode=node + node.parent = self + else: newnode=Node(tag=name, parent=self, attrs=attrs, payload=payload) + if namespace: + newnode.setNamespace(namespace) + self.kids.append(newnode) + return newnode - def addData(self, data): - """ - Add some CDATA to node - """ - self.data.append(ustr(data)) + def addData(self, data): + """ + Add some CDATA to node + """ + self.data.append(ustr(data)) - def clearData(self): - """ - Remove all CDATA from the node - """ - self.data = [] + def clearData(self): + """ + Remove all CDATA from the node + """ + self.data = [] - def delAttr(self, key): - """ - Delete an attribute "key" - """ - del self.attrs[key] + def delAttr(self, key): + """ + Delete an attribute "key" + """ + del self.attrs[key] - def delChild(self, node, attrs={}): - """ - Delete the "node" from the node's childs list, if "node" is an instance. - Else delete the first node that have specified name and (optionally) - attributes - """ - if not isinstance(node, Node): - node = self.getTag(node,attrs) - self.kids.remove(node) - return node + def delChild(self, node, attrs={}): + """ + Delete the "node" from the node's childs list, if "node" is an instance. + Else delete the first node that have specified name and (optionally) + attributes + """ + if not isinstance(node, Node): + node = self.getTag(node, attrs) + self.kids.remove(node) + return node - def getAttrs(self): - """ - Return all node's attributes as dictionary - """ - return self.attrs + def getAttrs(self): + """ + Return all node's attributes as dictionary + """ + return self.attrs - def getAttr(self, key): - """ - Return value of specified attribute - """ - return self.attrs.get(key) + def getAttr(self, key): + """ + Return value of specified attribute + """ + return self.attrs.get(key) - def getChildren(self): - """ - Return all node's child nodes as list - """ - return self.kids + def getChildren(self): + """ + Return all node's child nodes as list + """ + return self.kids - def getData(self): - """ - Return all node CDATA as string (concatenated) - """ - return ''.join(self.data) + def getData(self): + """ + Return all node CDATA as string (concatenated) + """ + return ''.join(self.data) - def getName(self): - """ - Return the name of node - """ - return self.name + def getName(self): + """ + Return the name of node + """ + return self.name - def getNamespace(self): - """ - Return the namespace of node - """ - return self.namespace + def getNamespace(self): + """ + Return the namespace of node + """ + return self.namespace - def getParent(self): - """ - Returns the parent of node (if present) - """ - return self.parent + def getParent(self): + """ + Returns the parent of node (if present) + """ + return self.parent - def getPayload(self): - """ - Return the payload of node i.e. list of child nodes and CDATA entries. - F.e. for "text1 text2" will be returned - list: ['text1', , , ' text2'] - """ - ret = [] - for i in range(len(self.kids)+len(self.data)+1): - try: - if self.data[i]: - ret.append(self.data[i]) - except IndexError: - pass - try: - ret.append(self.kids[i]) - except IndexError: - pass - return ret + def getPayload(self): + """ + Return the payload of node i.e. list of child nodes and CDATA entries. + F.e. for "text1 text2" will be returned + list: ['text1', , , ' text2'] + """ + ret = [] + for i in range(len(self.kids)+len(self.data)+1): + try: + if self.data[i]: + ret.append(self.data[i]) + except IndexError: + pass + try: + ret.append(self.kids[i]) + except IndexError: + pass + return ret - def getTag(self, name, attrs={}, namespace=None): - """ - Filter all child nodes using specified arguments as filter. Return the - first found or None if not found - """ - return self.getTags(name, attrs, namespace, one=1) + def getTag(self, name, attrs={}, namespace=None): + """ + Filter all child nodes using specified arguments as filter. Return the + first found or None if not found + """ + return self.getTags(name, attrs, namespace, one=1) - def getTagAttr(self, tag, attr): - """ - Return attribute value of the child with specified name (or None if no - such attribute) - """ - try: - return self.getTag(tag).attrs[attr] - except: - return None + def getTagAttr(self, tag, attr): + """ + Return attribute value of the child with specified name (or None if no + such attribute) + """ + try: + return self.getTag(tag).attrs[attr] + except: + return None - def getTagData(self,tag): - """ - Return cocatenated CDATA of the child with specified name - """ - try: - return self.getTag(tag).getData() - except Exception: - return None + def getTagData(self, tag): + """ + Return cocatenated CDATA of the child with specified name + """ + try: + return self.getTag(tag).getData() + except Exception: + return None - def getTags(self, name, attrs={}, namespace=None, one=0): - """ - Filter all child nodes using specified arguments as filter. Returns the - list of nodes found - """ - nodes = [] - for node in self.kids: - if namespace and namespace != node.getNamespace(): - continue - if node.getName() == name: - for key in attrs.keys(): - if key not in node.attrs or node.attrs[key]!=attrs[key]: - break - else: - nodes.append(node) - if one and nodes: - return nodes[0] - if not one: - return nodes + def getTags(self, name, attrs={}, namespace=None, one=0): + """ + Filter all child nodes using specified arguments as filter. Returns the + list of nodes found + """ + nodes = [] + for node in self.kids: + if namespace and namespace != node.getNamespace(): + continue + if node.getName() == name: + for key in attrs.keys(): + if key not in node.attrs or node.attrs[key]!=attrs[key]: + break + else: + nodes.append(node) + if one and nodes: + return nodes[0] + if not one: + return nodes - def iterTags(self, name, attrs={}, namespace=None): - """ - Iterate over all children using specified arguments as filter - """ - for node in self.kids: - if namespace is not None and namespace != node.getNamespace(): - continue - if node.getName() == name: - for key in attrs.keys(): - if key not in node.attrs or \ - node.attrs[key]!=attrs[key]: - break - else: - yield node + def iterTags(self, name, attrs={}, namespace=None): + """ + Iterate over all children using specified arguments as filter + """ + for node in self.kids: + if namespace is not None and namespace != node.getNamespace(): + continue + if node.getName() == name: + for key in attrs.keys(): + if key not in node.attrs or \ + node.attrs[key]!=attrs[key]: + break + else: + yield node - def setAttr(self, key, val): - """ - Set attribute "key" with the value "val" - """ - self.attrs[key] = val + def setAttr(self, key, val): + """ + Set attribute "key" with the value "val" + """ + self.attrs[key] = val - def setData(self, data): - """ - Set node's CDATA to provided string. Resets all previous CDATA! - """ - self.data = [ustr(data)] + def setData(self, data): + """ + Set node's CDATA to provided string. Resets all previous CDATA! + """ + self.data = [ustr(data)] - def setName(self, val): - """ - Change the node name - """ - self.name = val + def setName(self, val): + """ + Change the node name + """ + self.name = val - def setNamespace(self, namespace): - """ - Changes the node namespace - """ - self.namespace = namespace + def setNamespace(self, namespace): + """ + Changes the node namespace + """ + self.namespace = namespace - def setParent(self, node): - """ - Set node's parent to "node". WARNING: do not checks if the parent already - present and not removes the node from the list of childs of previous - parent - """ - self.parent = node + def setParent(self, node): + """ + Set node's parent to "node". WARNING: do not checks if the parent already + present and not removes the node from the list of childs of previous + parent + """ + self.parent = node - def setPayload(self, payload, add=0): - """ - Set node payload according to the list specified. WARNING: completely - replaces all node's previous content. If you wish just to add child or - CDATA - use addData or addChild methods - """ - if isinstance(payload, basestring): - payload = [payload] - if add: - self.kids += payload - else: - self.kids = payload + def setPayload(self, payload, add=0): + """ + Set node payload according to the list specified. WARNING: completely + replaces all node's previous content. If you wish just to add child or + CDATA - use addData or addChild methods + """ + if isinstance(payload, basestring): + payload = [payload] + if add: + self.kids += payload + else: + self.kids = payload - def setTag(self, name, attrs={}, namespace=None): - """ - Same as getTag but if the node with specified namespace/attributes not - found, creates such node and returns it - """ - node = self.getTags(name, attrs, namespace=namespace, one=1) - if node: - return node - else: - return self.addChild(name, attrs, namespace=namespace) + def setTag(self, name, attrs={}, namespace=None): + """ + Same as getTag but if the node with specified namespace/attributes not + found, creates such node and returns it + """ + node = self.getTags(name, attrs, namespace=namespace, one=1) + if node: + return node + else: + return self.addChild(name, attrs, namespace=namespace) - def setTagAttr(self, tag, attr, val): - """ - Create new node (if not already present) with name "tag" and set it's - attribute "attr" to value "val" - """ - try: - self.getTag(tag).attrs[attr] = val - except Exception: - self.addChild(tag, attrs={attr: val}) + def setTagAttr(self, tag, attr, val): + """ + Create new node (if not already present) with name "tag" and set it's + attribute "attr" to value "val" + """ + try: + self.getTag(tag).attrs[attr] = val + except Exception: + self.addChild(tag, attrs={attr: val}) - def setTagData(self, tag, val, attrs={}): - """ - Creates new node (if not already present) with name "tag" and - (optionally) attributes "attrs" and sets it's CDATA to string "val" - """ - try: - self.getTag(tag,attrs).setData(ustr(val)) - except Exception: - self.addChild(tag,attrs,payload = [ustr(val)]) + def setTagData(self, tag, val, attrs={}): + """ + Creates new node (if not already present) with name "tag" and + (optionally) attributes "attrs" and sets it's CDATA to string "val" + """ + try: + self.getTag(tag, attrs).setData(ustr(val)) + except Exception: + self.addChild(tag, attrs, payload = [ustr(val)]) - def has_attr(self, key): - """ - Check if node have attribute "key" - """ - return key in self.attrs + def has_attr(self, key): + """ + Check if node have attribute "key" + """ + return key in self.attrs - def __getitem__(self, item): - """ - Return node's attribute "item" value - """ - return self.getAttr(item) + def __getitem__(self, item): + """ + Return node's attribute "item" value + """ + return self.getAttr(item) - def __setitem__(self, item, val): - """ - Set node's attribute "item" value - """ - return self.setAttr(item, val) + def __setitem__(self, item, val): + """ + Set node's attribute "item" value + """ + return self.setAttr(item, val) - def __delitem__(self, item): - """ - Delete node's attribute "item" - """ - return self.delAttr(item) + def __delitem__(self, item): + """ + Delete node's attribute "item" + """ + return self.delAttr(item) - def __contains__(self, item): - """ - Check if node has attribute "item" - """ - return self.has_attr(item) + def __contains__(self, item): + """ + Check if node has attribute "item" + """ + return self.has_attr(item) - def __getattr__(self, attr): - """ - Reduce memory usage caused by T/NT classes - use memory only when needed - """ - if attr == 'T': - self.T = T(self) - return self.T - if attr == 'NT': - self.NT = NT(self) - return self.NT - raise AttributeError + def __getattr__(self, attr): + """ + Reduce memory usage caused by T/NT classes - use memory only when needed + """ + if attr == 'T': + self.T = T(self) + return self.T + if attr == 'NT': + self.NT = NT(self) + return self.NT + raise AttributeError class T: - """ - Auxiliary class used to quick access to node's child nodes - """ + """ + Auxiliary class used to quick access to node's child nodes + """ - def __init__(self, node): - self.__dict__['node'] = node + def __init__(self, node): + self.__dict__['node'] = node - def __getattr__(self, attr): - return self.node.setTag(attr) + def __getattr__(self, attr): + return self.node.setTag(attr) - def __setattr__(self, attr, val): - if isinstance(val,Node): - Node.__init__(self.node.setTag(attr), node=val) - else: - return self.node.setTagData(attr, val) + def __setattr__(self, attr, val): + if isinstance(val, Node): + Node.__init__(self.node.setTag(attr), node=val) + else: + return self.node.setTagData(attr, val) - def __delattr__(self, attr): - return self.node.delChild(attr) + def __delattr__(self, attr): + return self.node.delChild(attr) class NT(T): - """ - Auxiliary class used to quick create node's child nodes - """ + """ + Auxiliary class used to quick create node's child nodes + """ - def __getattr__(self, attr): - return self.node.addChild(attr) + def __getattr__(self, attr): + return self.node.addChild(attr) - def __setattr__(self, attr, val): - if isinstance(val,Node): - self.node.addChild(attr,node=val) - else: - return self.node.addChild(attr, payload=[val]) + def __setattr__(self, attr, val): + if isinstance(val, Node): + self.node.addChild(attr, node=val) + else: + return self.node.addChild(attr, payload=[val]) class NodeBuilder: - """ - Builds a Node class minidom from data parsed to it. This class used for two - purposes: - 1. Creation an XML Node from a textual representation. F.e. reading a - config file. See an XML2Node method. - 2. Handling an incoming XML stream. This is done by mangling the - __dispatch_depth parameter and redefining the dispatch method. + """ + Builds a Node class minidom from data parsed to it. This class used for two + purposes: + 1. Creation an XML Node from a textual representation. F.e. reading a + config file. See an XML2Node method. + 2. Handling an incoming XML stream. This is done by mangling the + __dispatch_depth parameter and redefining the dispatch method. - You do not need to use this class directly if you do not designing your own - XML handler - """ + You do not need to use this class directly if you do not designing your own + XML handler + """ - def __init__(self, data=None, initial_node=None): - """ - Take two optional parameters: "data" and "initial_node" + def __init__(self, data=None, initial_node=None): + """ + Take two optional parameters: "data" and "initial_node" - By default class initialised with empty Node class instance. Though, if - "initial_node" is provided it used as "starting point". You can think - about it as of "node upgrade". "data" (if provided) feeded to parser - immidiatedly after instance init. - """ - log.debug("Preparing to handle incoming XML stream.") - self._parser = xml.parsers.expat.ParserCreate() - self._parser.StartElementHandler = self.starttag - self._parser.EndElementHandler = self.endtag - self._parser.StartNamespaceDeclHandler = self.handle_namespace_start - self._parser.CharacterDataHandler = self.handle_cdata - self._parser.buffer_text = True - self.Parse = self._parser.Parse + By default class initialised with empty Node class instance. Though, if + "initial_node" is provided it used as "starting point". You can think + about it as of "node upgrade". "data" (if provided) feeded to parser + immidiatedly after instance init. + """ + log.debug("Preparing to handle incoming XML stream.") + self._parser = xml.parsers.expat.ParserCreate() + self._parser.StartElementHandler = self.starttag + self._parser.EndElementHandler = self.endtag + self._parser.StartNamespaceDeclHandler = self.handle_namespace_start + self._parser.CharacterDataHandler = self.handle_cdata + self._parser.buffer_text = True + self.Parse = self._parser.Parse - self.__depth = 0 - self.__last_depth = 0 - self.__max_depth = 0 - self._dispatch_depth = 1 - self._document_attrs = None - self._document_nsp = None - self._mini_dom=initial_node - self.last_is_data = 1 - self._ptr=None - self.data_buffer = None - self.streamError = '' - if data: - self._parser.Parse(data,1) + self.__depth = 0 + self.__last_depth = 0 + self.__max_depth = 0 + self._dispatch_depth = 1 + self._document_attrs = None + self._document_nsp = None + self._mini_dom=initial_node + self.last_is_data = 1 + self._ptr=None + self.data_buffer = None + self.streamError = '' + if data: + self._parser.Parse(data, 1) - def check_data_buffer(self): - if self.data_buffer: - self._ptr.data.append(''.join(self.data_buffer)) - del self.data_buffer[:] - self.data_buffer = None + def check_data_buffer(self): + if self.data_buffer: + self._ptr.data.append(''.join(self.data_buffer)) + del self.data_buffer[:] + self.data_buffer = None - def destroy(self): - """ - Method used to allow class instance to be garbage-collected - """ - self.check_data_buffer() - self._parser.StartElementHandler = None - self._parser.EndElementHandler = None - self._parser.CharacterDataHandler = None - self._parser.StartNamespaceDeclHandler = None + def destroy(self): + """ + Method used to allow class instance to be garbage-collected + """ + self.check_data_buffer() + self._parser.StartElementHandler = None + self._parser.EndElementHandler = None + self._parser.CharacterDataHandler = None + self._parser.StartNamespaceDeclHandler = None - def starttag(self, tag, attrs): - """ - XML Parser callback. Used internally - """ - self.check_data_buffer() - self._inc_depth() - log.info("STARTTAG.. DEPTH -> %i , tag -> %s, attrs -> %s" % (self.__depth, tag, `attrs`)) - if self.__depth == self._dispatch_depth: - if not self._mini_dom : - self._mini_dom = Node(tag=tag, attrs=attrs, nsp = self._document_nsp, node_built=True) - else: - Node.__init__(self._mini_dom,tag=tag, attrs=attrs, nsp = self._document_nsp, node_built=True) - self._ptr = self._mini_dom - elif self.__depth > self._dispatch_depth: - self._ptr.kids.append(Node(tag=tag,parent=self._ptr,attrs=attrs, node_built=True)) - self._ptr = self._ptr.kids[-1] - if self.__depth == 1: - self._document_attrs = {} - self._document_nsp = {} - nsp, name = (['']+tag.split(':'))[-2:] - for attr,val in attrs.items(): - if attr == 'xmlns': - self._document_nsp[u''] = val - elif attr.startswith('xmlns:'): - self._document_nsp[attr[6:]] = val - else: - self._document_attrs[attr] = val - ns = self._document_nsp.get(nsp, 'http://www.gajim.org/xmlns/undeclared-root') - try: - self.stream_header_received(ns, name, attrs) - except ValueError, e: - self._document_attrs = None - raise ValueError(str(e)) - if not self.last_is_data and self._ptr.parent: - self._ptr.parent.data.append('') - self.last_is_data = 0 + def starttag(self, tag, attrs): + """ + XML Parser callback. Used internally + """ + self.check_data_buffer() + self._inc_depth() + log.info("STARTTAG.. DEPTH -> %i , tag -> %s, attrs -> %s" % (self.__depth, tag, `attrs`)) + if self.__depth == self._dispatch_depth: + if not self._mini_dom : + self._mini_dom = Node(tag=tag, attrs=attrs, nsp = self._document_nsp, node_built=True) + else: + Node.__init__(self._mini_dom, tag=tag, attrs=attrs, nsp = self._document_nsp, node_built=True) + self._ptr = self._mini_dom + elif self.__depth > self._dispatch_depth: + self._ptr.kids.append(Node(tag=tag, parent=self._ptr, attrs=attrs, node_built=True)) + self._ptr = self._ptr.kids[-1] + if self.__depth == 1: + self._document_attrs = {} + self._document_nsp = {} + nsp, name = (['']+tag.split(':'))[-2:] + for attr, val in attrs.items(): + if attr == 'xmlns': + self._document_nsp[u''] = val + elif attr.startswith('xmlns:'): + self._document_nsp[attr[6:]] = val + else: + self._document_attrs[attr] = val + ns = self._document_nsp.get(nsp, 'http://www.gajim.org/xmlns/undeclared-root') + try: + self.stream_header_received(ns, name, attrs) + except ValueError, e: + self._document_attrs = None + raise ValueError(str(e)) + if not self.last_is_data and self._ptr.parent: + self._ptr.parent.data.append('') + self.last_is_data = 0 - def endtag(self, tag ): - """ - XML Parser callback. Used internally - """ - log.info("DEPTH -> %i , tag -> %s" % (self.__depth, tag)) - self.check_data_buffer() - if self.__depth == self._dispatch_depth: - if self._mini_dom.getName() == 'error': - children = self._mini_dom.getChildren() - if children: - self.streamError = children[0].getName() - else: - self.streamError = self._mini_dom.getData() - self.dispatch(self._mini_dom) - elif self.__depth > self._dispatch_depth: - self._ptr = self._ptr.parent - else: - log.info("Got higher than dispatch level. Stream terminated?") - self._dec_depth() - self.last_is_data = 0 - if self.__depth == 0: self.stream_footer_received() + def endtag(self, tag ): + """ + XML Parser callback. Used internally + """ + log.info("DEPTH -> %i , tag -> %s" % (self.__depth, tag)) + self.check_data_buffer() + if self.__depth == self._dispatch_depth: + if self._mini_dom.getName() == 'error': + children = self._mini_dom.getChildren() + if children: + self.streamError = children[0].getName() + else: + self.streamError = self._mini_dom.getData() + self.dispatch(self._mini_dom) + elif self.__depth > self._dispatch_depth: + self._ptr = self._ptr.parent + else: + log.info("Got higher than dispatch level. Stream terminated?") + self._dec_depth() + self.last_is_data = 0 + if self.__depth == 0: self.stream_footer_received() - def handle_cdata(self, data): - if self.last_is_data: - if self.data_buffer: - self.data_buffer.append(data) - elif self._ptr: - self.data_buffer = [data] - self.last_is_data = 1 + def handle_cdata(self, data): + if self.last_is_data: + if self.data_buffer: + self.data_buffer.append(data) + elif self._ptr: + self.data_buffer = [data] + self.last_is_data = 1 - def handle_namespace_start(self, prefix, uri): - """ - XML Parser callback. Used internally - """ - self.check_data_buffer() + def handle_namespace_start(self, prefix, uri): + """ + XML Parser callback. Used internally + """ + self.check_data_buffer() - def getDom(self): - """ - Return just built Node - """ - self.check_data_buffer() - return self._mini_dom + def getDom(self): + """ + Return just built Node + """ + self.check_data_buffer() + return self._mini_dom - def dispatch(self, stanza): - """ - Get called when the NodeBuilder reaches some level of depth on it's way - up with the built node as argument. Can be redefined to convert incoming - XML stanzas to program events - """ - pass + def dispatch(self, stanza): + """ + Get called when the NodeBuilder reaches some level of depth on it's way + up with the built node as argument. Can be redefined to convert incoming + XML stanzas to program events + """ + pass - def stream_header_received(self, ns, tag, attrs): - """ - Method called when stream just opened - """ - self.check_data_buffer() + def stream_header_received(self, ns, tag, attrs): + """ + Method called when stream just opened + """ + self.check_data_buffer() - def stream_footer_received(self): - """ - Method called when stream just closed - """ - self.check_data_buffer() + def stream_footer_received(self): + """ + Method called when stream just closed + """ + self.check_data_buffer() - def has_received_endtag(self, level=0): - """ - Return True if at least one end tag was seen (at level) - """ - return self.__depth <= level and self.__max_depth > level + def has_received_endtag(self, level=0): + """ + Return True if at least one end tag was seen (at level) + """ + return self.__depth <= level and self.__max_depth > level - def _inc_depth(self): - self.__last_depth = self.__depth - self.__depth += 1 - self.__max_depth = max(self.__depth, self.__max_depth) + def _inc_depth(self): + self.__last_depth = self.__depth + self.__depth += 1 + self.__max_depth = max(self.__depth, self.__max_depth) - def _dec_depth(self): - self.__last_depth = self.__depth - self.__depth -= 1 + def _dec_depth(self): + self.__last_depth = self.__depth + self.__depth -= 1 def XML2Node(xml): - """ - Convert supplied textual string into XML node. Handy f.e. for reading - configuration file. Raises xml.parser.expat.parsererror if provided string - is not well-formed XML - """ - return NodeBuilder(xml).getDom() + """ + Convert supplied textual string into XML node. Handy f.e. for reading + configuration file. Raises xml.parser.expat.parsererror if provided string + is not well-formed XML + """ + return NodeBuilder(xml).getDom() def BadXML2Node(xml): - """ - Convert supplied textual string into XML node. Survives if xml data is - cutted half way round. I.e. "some text
some more text". Will raise - xml.parser.expat.parsererror on misplaced tags though. F.e. "some text -
some more text
" will not work - """ - return NodeBuilder(xml).getDom() - -# vim: se ts=3: + """ + Convert supplied textual string into XML node. Survives if xml data is + cutted half way round. I.e. "some text
some more text". Will raise + xml.parser.expat.parsererror on misplaced tags though. F.e. "some text +
some more text
" will not work + """ + return NodeBuilder(xml).getDom() diff --git a/src/common/xmpp/stringprepare.py b/src/common/xmpp/stringprepare.py index 39759a513..5d0610d4b 100644 --- a/src/common/xmpp/stringprepare.py +++ b/src/common/xmpp/stringprepare.py @@ -26,187 +26,187 @@ import unicodedata from encodings import idna class ILookupTable: - """ - Interface for character lookup classes - """ + """ + Interface for character lookup classes + """ - def lookup(self, c): - """ - Return whether character is in this table - """ - pass + def lookup(self, c): + """ + Return whether character is in this table + """ + pass class IMappingTable: - """ - Interface for character mapping classes - """ + """ + Interface for character mapping classes + """ - def map(self, c): - """ - Return mapping for character - """ - pass + def map(self, c): + """ + Return mapping for character + """ + pass class LookupTableFromFunction: - __implements__ = ILookupTable + __implements__ = ILookupTable - def __init__(self, in_table_function): - self.lookup = in_table_function + def __init__(self, in_table_function): + self.lookup = in_table_function class LookupTable: - __implements__ = ILookupTable + __implements__ = ILookupTable - def __init__(self, table): - self._table = table + def __init__(self, table): + self._table = table - def lookup(self, c): - return c in self._table + def lookup(self, c): + return c in self._table class MappingTableFromFunction: - __implements__ = IMappingTable + __implements__ = IMappingTable - def __init__(self, map_table_function): - self.map = map_table_function + def __init__(self, map_table_function): + self.map = map_table_function class EmptyMappingTable: - __implements__ = IMappingTable + __implements__ = IMappingTable - def __init__(self, in_table_function): - self._in_table_function = in_table_function + def __init__(self, in_table_function): + self._in_table_function = in_table_function - def map(self, c): - if self._in_table_function(c): - return None - else: - return c + def map(self, c): + if self._in_table_function(c): + return None + else: + return c class Profile: - def __init__(self, mappings=[], normalize=True, prohibiteds=[], - check_unassigneds=True, check_bidi=True): - self.mappings = mappings - self.normalize = normalize - self.prohibiteds = prohibiteds - self.do_check_unassigneds = check_unassigneds - self.do_check_bidi = check_bidi + def __init__(self, mappings=[], normalize=True, prohibiteds=[], + check_unassigneds=True, check_bidi=True): + self.mappings = mappings + self.normalize = normalize + self.prohibiteds = prohibiteds + self.do_check_unassigneds = check_unassigneds + self.do_check_bidi = check_bidi - def prepare(self, string): - result = self.map(string) - if self.normalize: - result = unicodedata.normalize("NFKC", result) - self.check_prohibiteds(result) - if self.do_check_unassigneds: - self.check_unassigneds(result) - if self.do_check_bidi: - self.check_bidirectionals(result) - return result + def prepare(self, string): + result = self.map(string) + if self.normalize: + result = unicodedata.normalize("NFKC", result) + self.check_prohibiteds(result) + if self.do_check_unassigneds: + self.check_unassigneds(result) + if self.do_check_bidi: + self.check_bidirectionals(result) + return result - def map(self, string): - result = [] + def map(self, string): + result = [] - for c in string: - result_c = c + for c in string: + result_c = c - for mapping in self.mappings: - result_c = mapping.map(c) - if result_c != c: - break + for mapping in self.mappings: + result_c = mapping.map(c) + if result_c != c: + break - if result_c is not None: - result.append(result_c) + if result_c is not None: + result.append(result_c) - return u"".join(result) + return u"".join(result) - def check_prohibiteds(self, string): - for c in string: - for table in self.prohibiteds: - if table.lookup(c): - raise UnicodeError, "Invalid character %s" % repr(c) + def check_prohibiteds(self, string): + for c in string: + for table in self.prohibiteds: + if table.lookup(c): + raise UnicodeError, "Invalid character %s" % repr(c) - def check_unassigneds(self, string): - for c in string: - if stringprep.in_table_a1(c): - raise UnicodeError, "Unassigned code point %s" % repr(c) + def check_unassigneds(self, string): + for c in string: + if stringprep.in_table_a1(c): + raise UnicodeError, "Unassigned code point %s" % repr(c) - def check_bidirectionals(self, string): - found_LCat = False - found_RandALCat = False + def check_bidirectionals(self, string): + found_LCat = False + found_RandALCat = False - for c in string: - if stringprep.in_table_d1(c): - found_RandALCat = True - if stringprep.in_table_d2(c): - found_LCat = True + for c in string: + if stringprep.in_table_d1(c): + found_RandALCat = True + if stringprep.in_table_d2(c): + found_LCat = True - if found_LCat and found_RandALCat: - raise UnicodeError, "Violation of BIDI Requirement 2" + if found_LCat and found_RandALCat: + raise UnicodeError, "Violation of BIDI Requirement 2" - if found_RandALCat and not (stringprep.in_table_d1(string[0]) and - stringprep.in_table_d1(string[-1])): - raise UnicodeError, "Violation of BIDI Requirement 3" + if found_RandALCat and not (stringprep.in_table_d1(string[0]) and + stringprep.in_table_d1(string[-1])): + raise UnicodeError, "Violation of BIDI Requirement 3" class NamePrep: - """ - Implements preparation of internationalized domain names + """ + Implements preparation of internationalized domain names - This class implements preparing internationalized domain names using the - rules defined in RFC 3491, section 4 (Conversion operations). + This class implements preparing internationalized domain names using the + rules defined in RFC 3491, section 4 (Conversion operations). - We do not perform step 4 since we deal with unicode representations of - domain names and do not convert from or to ASCII representations using - punycode encoding. When such a conversion is needed, the L{idna} standard - library provides the C{ToUnicode()} and C{ToASCII()} functions. Note that - L{idna} itself assumes UseSTD3ASCIIRules to be false. + We do not perform step 4 since we deal with unicode representations of + domain names and do not convert from or to ASCII representations using + punycode encoding. When such a conversion is needed, the L{idna} standard + library provides the C{ToUnicode()} and C{ToASCII()} functions. Note that + L{idna} itself assumes UseSTD3ASCIIRules to be false. - The following steps are performed by C{prepare()}: + The following steps are performed by C{prepare()}: - * Split the domain name in labels at the dots (RFC 3490, 3.1) - * Apply nameprep proper on each label (RFC 3491) - * Enforce the restrictions on ASCII characters in host names by - assuming STD3ASCIIRules to be true. (STD 3) - * Rejoin the labels using the label separator U+002E (full stop). - """ + * Split the domain name in labels at the dots (RFC 3490, 3.1) + * Apply nameprep proper on each label (RFC 3491) + * Enforce the restrictions on ASCII characters in host names by + assuming STD3ASCIIRules to be true. (STD 3) + * Rejoin the labels using the label separator U+002E (full stop). + """ - # Prohibited characters. - prohibiteds = [unichr(n) for n in range(0x00, 0x2c + 1) + - range(0x2e, 0x2f + 1) + - range(0x3a, 0x40 + 1) + - range(0x5b, 0x60 + 1) + - range(0x7b, 0x7f + 1) ] + # Prohibited characters. + prohibiteds = [unichr(n) for n in range(0x00, 0x2c + 1) + + range(0x2e, 0x2f + 1) + + range(0x3a, 0x40 + 1) + + range(0x5b, 0x60 + 1) + + range(0x7b, 0x7f + 1) ] - def prepare(self, string): - result = [] + def prepare(self, string): + result = [] - labels = idna.dots.split(string) + labels = idna.dots.split(string) - if labels and len(labels[-1]) == 0: - trailing_dot = '.' - del labels[-1] - else: - trailing_dot = '' + if labels and len(labels[-1]) == 0: + trailing_dot = '.' + del labels[-1] + else: + trailing_dot = '' - for label in labels: - result.append(self.nameprep(label)) + for label in labels: + result.append(self.nameprep(label)) - return ".".join(result)+trailing_dot + return ".".join(result)+trailing_dot - def check_prohibiteds(self, string): - for c in string: - if c in self.prohibiteds: - raise UnicodeError, "Invalid character %s" % repr(c) + def check_prohibiteds(self, string): + for c in string: + if c in self.prohibiteds: + raise UnicodeError, "Invalid character %s" % repr(c) - def nameprep(self, label): - label = idna.nameprep(label) - self.check_prohibiteds(label) - if label[0] == '-': - raise UnicodeError, "Invalid leading hyphen-minus" - if label[-1] == '-': - raise UnicodeError, "Invalid trailing hyphen-minus" - return label + def nameprep(self, label): + label = idna.nameprep(label) + self.check_prohibiteds(label) + if label[0] == '-': + raise UnicodeError, "Invalid leading hyphen-minus" + if label[-1] == '-': + raise UnicodeError, "Invalid trailing hyphen-minus" + return label C_11 = LookupTableFromFunction(stringprep.in_table_c11) C_12 = LookupTableFromFunction(stringprep.in_table_c12) @@ -224,15 +224,13 @@ B_1 = EmptyMappingTable(stringprep.in_table_b1) B_2 = MappingTableFromFunction(stringprep.map_table_b2) nodeprep = Profile(mappings=[B_1, B_2], - prohibiteds=[C_11, C_12, C_21, C_22, - C_3, C_4, C_5, C_6, C_7, C_8, C_9, - LookupTable([u'"', u'&', u"'", u'/', - u':', u'<', u'>', u'@'])]) + prohibiteds=[C_11, C_12, C_21, C_22, + C_3, C_4, C_5, C_6, C_7, C_8, C_9, + LookupTable([u'"', u'&', u"'", u'/', + u':', u'<', u'>', u'@'])]) resourceprep = Profile(mappings=[B_1,], - prohibiteds=[C_12, C_21, C_22, - C_3, C_4, C_5, C_6, C_7, C_8, C_9]) + prohibiteds=[C_12, C_21, C_22, + C_3, C_4, C_5, C_6, C_7, C_8, C_9]) nameprep = NamePrep() - -# vim: se ts=3: diff --git a/src/common/xmpp/tls_nb.py b/src/common/xmpp/tls_nb.py index 8706e7fb7..9c54eb07d 100644 --- a/src/common/xmpp/tls_nb.py +++ b/src/common/xmpp/tls_nb.py @@ -33,390 +33,388 @@ PYOPENSSL = 'PYOPENSSL' PYSTDLIB = 'PYSTDLIB' try: - #raise ImportError("Manually disabled PyOpenSSL") - import OpenSSL.SSL - import OpenSSL.crypto - USE_PYOPENSSL = True - log.info("PyOpenSSL loaded") + #raise ImportError("Manually disabled PyOpenSSL") + import OpenSSL.SSL + import OpenSSL.crypto + USE_PYOPENSSL = True + log.info("PyOpenSSL loaded") except ImportError: - log.debug("Import of PyOpenSSL failed:", exc_info=True) + log.debug("Import of PyOpenSSL failed:", exc_info=True) - # FIXME: Remove these prints before release, replace with a warning dialog. - print >> sys.stderr, "=" * 79 - print >> sys.stderr, "PyOpenSSL not found, falling back to Python builtin SSL objects (insecure)." - print >> sys.stderr, "=" * 79 + # FIXME: Remove these prints before release, replace with a warning dialog. + print >> sys.stderr, "=" * 79 + print >> sys.stderr, "PyOpenSSL not found, falling back to Python builtin SSL objects (insecure)." + print >> sys.stderr, "=" * 79 def gattr(obj, attr, default=None): - try: - return getattr(obj, attr) - except AttributeError: - return default + try: + return getattr(obj, attr) + except AttributeError: + return default class SSLWrapper: - """ - Abstract SSLWrapper base class - """ + """ + Abstract SSLWrapper base class + """ - class Error(IOError): - """ - Generic SSL Error Wrapper - """ + class Error(IOError): + """ + Generic SSL Error Wrapper + """ - def __init__(self, sock=None, exc=None, errno=None, strerror=None, - peer=None): - self.parent = IOError + def __init__(self, sock=None, exc=None, errno=None, strerror=None, + peer=None): + self.parent = IOError - errno = errno or gattr(exc, 'errno') or exc[0] - strerror = strerror or gattr(exc, 'strerror') or gattr(exc, 'args') - if not isinstance(strerror, basestring): - strerror = repr(strerror) + errno = errno or gattr(exc, 'errno') or exc[0] + strerror = strerror or gattr(exc, 'strerror') or gattr(exc, 'args') + if not isinstance(strerror, basestring): + strerror = repr(strerror) - self.sock = sock - self.exc = exc - self.peer = peer - self.exc_name = None - self.exc_args = None - self.exc_str = None - self.exc_repr = None + self.sock = sock + self.exc = exc + self.peer = peer + self.exc_name = None + self.exc_args = None + self.exc_str = None + self.exc_repr = None - if self.exc is not None: - self.exc_name = str(self.exc.__class__) - self.exc_args = gattr(self.exc, 'args') - self.exc_str = str(self.exc) - self.exc_repr = repr(self.exc) - if not errno: - try: - if isinstance(exc, OpenSSL.SSL.SysCallError): - if self.exc_args[0] > 0: - errno = self.exc_args[0] - strerror = self.exc_args[1] - except: pass + if self.exc is not None: + self.exc_name = str(self.exc.__class__) + self.exc_args = gattr(self.exc, 'args') + self.exc_str = str(self.exc) + self.exc_repr = repr(self.exc) + if not errno: + try: + if isinstance(exc, OpenSSL.SSL.SysCallError): + if self.exc_args[0] > 0: + errno = self.exc_args[0] + strerror = self.exc_args[1] + except: pass - self.parent.__init__(self, errno, strerror) + self.parent.__init__(self, errno, strerror) - if self.peer is None and sock is not None: - try: - ppeer = self.sock.getpeername() - if len(ppeer) == 2 and isinstance(ppeer[0], basestring) \ - and isinstance(ppeer[1], int): - self.peer = ppeer - except: - pass + if self.peer is None and sock is not None: + try: + ppeer = self.sock.getpeername() + if len(ppeer) == 2 and isinstance(ppeer[0], basestring) \ + and isinstance(ppeer[1], int): + self.peer = ppeer + except: + pass - def __str__(self): - s = str(self.__class__) - if self.peer: - s += " for %s:%d" % self.peer - if self.errno is not None: - s += ": [Errno: %d]" % self.errno - if self.strerror: - s += " (%s)" % self.strerror - if self.exc_name: - s += ", Caused by %s" % self.exc_name - if self.exc_str: - if self.strerror: - s += "(%s)" % self.exc_str - else: s += "(%s)" % str(self.exc_args) - return s + def __str__(self): + s = str(self.__class__) + if self.peer: + s += " for %s:%d" % self.peer + if self.errno is not None: + s += ": [Errno: %d]" % self.errno + if self.strerror: + s += " (%s)" % self.strerror + if self.exc_name: + s += ", Caused by %s" % self.exc_name + if self.exc_str: + if self.strerror: + s += "(%s)" % self.exc_str + else: s += "(%s)" % str(self.exc_args) + return s - def __init__(self, sslobj, sock=None): - self.sslobj = sslobj - self.sock = sock - log.debug("%s.__init__ called with %s", self.__class__, sslobj) + def __init__(self, sslobj, sock=None): + self.sslobj = sslobj + self.sock = sock + log.debug("%s.__init__ called with %s", self.__class__, sslobj) - def recv(self, data, flags=None): - """ - Receive wrapper for SSL object + def recv(self, data, flags=None): + """ + Receive wrapper for SSL object - We can return None out of this function to signal that no data is - available right now. Better than an exception, which differs - depending on which SSL lib we're using. Unfortunately returning '' - can indicate that the socket has been closed, so to be sure, we avoid - this by returning None. - """ - raise NotImplementedError + We can return None out of this function to signal that no data is + available right now. Better than an exception, which differs + depending on which SSL lib we're using. Unfortunately returning '' + can indicate that the socket has been closed, so to be sure, we avoid + this by returning None. + """ + raise NotImplementedError - def send(self, data, flags=None, now=False): - """ - Send wrapper for SSL object - """ - raise NotImplementedError + def send(self, data, flags=None, now=False): + """ + Send wrapper for SSL object + """ + raise NotImplementedError class PyOpenSSLWrapper(SSLWrapper): - """ - Wrapper class for PyOpenSSL's recv() and send() methods - """ + """ + Wrapper class for PyOpenSSL's recv() and send() methods + """ - def __init__(self, *args): - self.parent = SSLWrapper - self.parent.__init__(self, *args) + def __init__(self, *args): + self.parent = SSLWrapper + self.parent.__init__(self, *args) - def is_numtoolarge(self, e): - ''' Magic methods don't need documentation ''' - t = ('asn1 encoding routines', 'a2d_ASN1_OBJECT', 'first num too large') - return (isinstance(e.args, (list, tuple)) and len(e.args) == 1 and - isinstance(e.args[0], (list, tuple)) and len(e.args[0]) == 2 and - e.args[0][0] == e.args[0][1] == t) + def is_numtoolarge(self, e): + ''' Magic methods don't need documentation ''' + t = ('asn1 encoding routines', 'a2d_ASN1_OBJECT', 'first num too large') + return (isinstance(e.args, (list, tuple)) and len(e.args) == 1 and + isinstance(e.args[0], (list, tuple)) and len(e.args[0]) == 2 and + e.args[0][0] == e.args[0][1] == t) - def recv(self, bufsize, flags=None): - retval = None - try: - if flags is None: - retval = self.sslobj.recv(bufsize) - else: - retval = self.sslobj.recv(bufsize, flags) - except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e: - log.debug("Recv: Want-error: " + repr(e)) - except OpenSSL.SSL.SysCallError, e: - log.debug("Recv: Got OpenSSL.SSL.SysCallError: " + repr(e), - exc_info=True) - raise SSLWrapper.Error(self.sock or self.sslobj, e) - except OpenSSL.SSL.ZeroReturnError, e: - # end-of-connection raises ZeroReturnError instead of having the - # connection's .recv() method return a zero-sized result. - raise SSLWrapper.Error(self.sock or self.sslobj, e, -1) - except OpenSSL.SSL.Error, e: - if self.is_numtoolarge(e): - # warn, but ignore this exception - log.warning("Recv: OpenSSL: asn1enc: first num too large (ignored)") - else: - log.debug("Recv: Caught OpenSSL.SSL.Error:", exc_info=True) - raise SSLWrapper.Error(self.sock or self.sslobj, e) - return retval + def recv(self, bufsize, flags=None): + retval = None + try: + if flags is None: + retval = self.sslobj.recv(bufsize) + else: + retval = self.sslobj.recv(bufsize, flags) + except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e: + log.debug("Recv: Want-error: " + repr(e)) + except OpenSSL.SSL.SysCallError, e: + log.debug("Recv: Got OpenSSL.SSL.SysCallError: " + repr(e), + exc_info=True) + raise SSLWrapper.Error(self.sock or self.sslobj, e) + except OpenSSL.SSL.ZeroReturnError, e: + # end-of-connection raises ZeroReturnError instead of having the + # connection's .recv() method return a zero-sized result. + raise SSLWrapper.Error(self.sock or self.sslobj, e, -1) + except OpenSSL.SSL.Error, e: + if self.is_numtoolarge(e): + # warn, but ignore this exception + log.warning("Recv: OpenSSL: asn1enc: first num too large (ignored)") + else: + log.debug("Recv: Caught OpenSSL.SSL.Error:", exc_info=True) + raise SSLWrapper.Error(self.sock or self.sslobj, e) + return retval - def send(self, data, flags=None, now=False): - try: - if flags is None: - return self.sslobj.send(data) - else: - return self.sslobj.send(data, flags) - except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e: - #log.debug("Send: " + repr(e)) - time.sleep(0.1) # prevent 100% CPU usage - except OpenSSL.SSL.SysCallError, e: - log.error("Send: Got OpenSSL.SSL.SysCallError: " + repr(e), - exc_info=True) - raise SSLWrapper.Error(self.sock or self.sslobj, e) - except OpenSSL.SSL.Error, e: - if self.is_numtoolarge(e): - # warn, but ignore this exception - log.warning("Send: OpenSSL: asn1enc: first num too large (ignored)") - else: - log.error("Send: Caught OpenSSL.SSL.Error:", exc_info=True) - raise SSLWrapper.Error(self.sock or self.sslobj, e) - return 0 + def send(self, data, flags=None, now=False): + try: + if flags is None: + return self.sslobj.send(data) + else: + return self.sslobj.send(data, flags) + except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e: + #log.debug("Send: " + repr(e)) + time.sleep(0.1) # prevent 100% CPU usage + except OpenSSL.SSL.SysCallError, e: + log.error("Send: Got OpenSSL.SSL.SysCallError: " + repr(e), + exc_info=True) + raise SSLWrapper.Error(self.sock or self.sslobj, e) + except OpenSSL.SSL.Error, e: + if self.is_numtoolarge(e): + # warn, but ignore this exception + log.warning("Send: OpenSSL: asn1enc: first num too large (ignored)") + else: + log.error("Send: Caught OpenSSL.SSL.Error:", exc_info=True) + raise SSLWrapper.Error(self.sock or self.sslobj, e) + return 0 class StdlibSSLWrapper(SSLWrapper): - """ - Wrapper class for Python socket.ssl read() and write() methods - """ + """ + Wrapper class for Python socket.ssl read() and write() methods + """ - def __init__(self, *args): - self.parent = SSLWrapper - self.parent.__init__(self, *args) + def __init__(self, *args): + self.parent = SSLWrapper + self.parent.__init__(self, *args) - def recv(self, bufsize, flags=None): - # we simply ignore flags since ssl object doesn't support it - try: - return self.sslobj.read(bufsize) - except socket.sslerror, e: - log.debug("Recv: Caught socket.sslerror: " + repr(e), exc_info=True) - if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE): - raise SSLWrapper.Error(self.sock or self.sslobj, e) - return None + def recv(self, bufsize, flags=None): + # we simply ignore flags since ssl object doesn't support it + try: + return self.sslobj.read(bufsize) + except socket.sslerror, e: + log.debug("Recv: Caught socket.sslerror: " + repr(e), exc_info=True) + if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE): + raise SSLWrapper.Error(self.sock or self.sslobj, e) + return None - def send(self, data, flags=None, now=False): - # we simply ignore flags since ssl object doesn't support it - try: - return self.sslobj.write(data) - except socket.sslerror, e: - log.debug("Send: Caught socket.sslerror:", exc_info=True) - if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE): - raise SSLWrapper.Error(self.sock or self.sslobj, e) - return 0 + def send(self, data, flags=None, now=False): + # we simply ignore flags since ssl object doesn't support it + try: + return self.sslobj.write(data) + except socket.sslerror, e: + log.debug("Send: Caught socket.sslerror:", exc_info=True) + if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE): + raise SSLWrapper.Error(self.sock or self.sslobj, e) + return 0 class NonBlockingTLS(PlugIn): - """ - TLS connection used to encrypts already estabilished tcp connection + """ + TLS connection used to encrypts already estabilished tcp connection - Can be plugged into NonBlockingTCP and will make use of StdlibSSLWrapper or - PyOpenSSLWrapper. - """ + Can be plugged into NonBlockingTCP and will make use of StdlibSSLWrapper or + PyOpenSSLWrapper. + """ - def __init__(self, cacerts, mycerts): - """ - :param cacerts: path to pem file with certificates of known XMPP servers - :param mycerts: path to pem file with certificates of user trusted servers - """ - PlugIn.__init__(self) - self.cacerts = cacerts - self.mycerts = mycerts + def __init__(self, cacerts, mycerts): + """ + :param cacerts: path to pem file with certificates of known XMPP servers + :param mycerts: path to pem file with certificates of user trusted servers + """ + PlugIn.__init__(self) + self.cacerts = cacerts + self.mycerts = mycerts - # from ssl.h (partial extract) - ssl_h_bits = { "SSL_ST_CONNECT": 0x1000, "SSL_ST_ACCEPT": 0x2000, - "SSL_CB_LOOP": 0x01, "SSL_CB_EXIT": 0x02, - "SSL_CB_READ": 0x04, "SSL_CB_WRITE": 0x08, - "SSL_CB_ALERT": 0x4000, - "SSL_CB_HANDSHAKE_START": 0x10, "SSL_CB_HANDSHAKE_DONE": 0x20} + # from ssl.h (partial extract) + ssl_h_bits = { "SSL_ST_CONNECT": 0x1000, "SSL_ST_ACCEPT": 0x2000, + "SSL_CB_LOOP": 0x01, "SSL_CB_EXIT": 0x02, + "SSL_CB_READ": 0x04, "SSL_CB_WRITE": 0x08, + "SSL_CB_ALERT": 0x4000, + "SSL_CB_HANDSHAKE_START": 0x10, "SSL_CB_HANDSHAKE_DONE": 0x20} - def plugin(self, owner): - """ - Use to PlugIn TLS into transport and start establishing immediately. - Returns True if TLS/SSL was established correctly, otherwise False - """ - log.info('Starting TLS estabilishing') - try: - res = self._startSSL() - except Exception, e: - log.error("PlugIn: while trying _startSSL():", exc_info=True) - return False - return res + def plugin(self, owner): + """ + Use to PlugIn TLS into transport and start establishing immediately. + Returns True if TLS/SSL was established correctly, otherwise False + """ + log.info('Starting TLS estabilishing') + try: + res = self._startSSL() + except Exception, e: + log.error("PlugIn: while trying _startSSL():", exc_info=True) + return False + return res - def _dumpX509(self, cert, stream=sys.stderr): - print >> stream, "Digest (SHA-1):", cert.digest("sha1") - print >> stream, "Digest (MD5):", cert.digest("md5") - print >> stream, "Serial #:", cert.get_serial_number() - print >> stream, "Version:", cert.get_version() - print >> stream, "Expired:", ("Yes" if cert.has_expired() else "No") - print >> stream, "Subject:" - self._dumpX509Name(cert.get_subject(), stream) - print >> stream, "Issuer:" - self._dumpX509Name(cert.get_issuer(), stream) - self._dumpPKey(cert.get_pubkey(), stream) + def _dumpX509(self, cert, stream=sys.stderr): + print >> stream, "Digest (SHA-1):", cert.digest("sha1") + print >> stream, "Digest (MD5):", cert.digest("md5") + print >> stream, "Serial #:", cert.get_serial_number() + print >> stream, "Version:", cert.get_version() + print >> stream, "Expired:", ("Yes" if cert.has_expired() else "No") + print >> stream, "Subject:" + self._dumpX509Name(cert.get_subject(), stream) + print >> stream, "Issuer:" + self._dumpX509Name(cert.get_issuer(), stream) + self._dumpPKey(cert.get_pubkey(), stream) - def _dumpX509Name(self, name, stream=sys.stderr): - print >> stream, "X509Name:", str(name) + def _dumpX509Name(self, name, stream=sys.stderr): + print >> stream, "X509Name:", str(name) - def _dumpPKey(self, pkey, stream=sys.stderr): - typedict = {OpenSSL.crypto.TYPE_RSA: "RSA", - OpenSSL.crypto.TYPE_DSA: "DSA"} - print >> stream, "PKey bits:", pkey.bits() - print >> stream, "PKey type: %s (%d)" % (typedict.get(pkey.type(), - "Unknown"), pkey.type()) + def _dumpPKey(self, pkey, stream=sys.stderr): + typedict = {OpenSSL.crypto.TYPE_RSA: "RSA", + OpenSSL.crypto.TYPE_DSA: "DSA"} + print >> stream, "PKey bits:", pkey.bits() + print >> stream, "PKey type: %s (%d)" % (typedict.get(pkey.type(), + "Unknown"), pkey.type()) - def _startSSL(self): - """ - Immediatedly switch socket to TLS mode. Used internally - """ - log.debug("_startSSL called") + def _startSSL(self): + """ + Immediatedly switch socket to TLS mode. Used internally + """ + log.debug("_startSSL called") - if USE_PYOPENSSL: - result = self._startSSL_pyOpenSSL() - else: - result = self._startSSL_stdlib() + if USE_PYOPENSSL: + result = self._startSSL_pyOpenSSL() + else: + result = self._startSSL_stdlib() - if result: - log.debug('Synchronous handshake completed') - return True - else: - return False + if result: + log.debug('Synchronous handshake completed') + return True + else: + return False - def _load_cert_file(self, cert_path, cert_store, logg=True): - if not os.path.isfile(cert_path): - return - try: - f = open(cert_path) - except IOError, e: - log.warning('Unable to open certificate file %s: %s' % \ - (cert_path, str(e))) - return - lines = f.readlines() - i = 0 - begin = -1 - for line in lines: - if 'BEGIN CERTIFICATE' in line: - begin = i - elif 'END CERTIFICATE' in line and begin > -1: - cert = ''.join(lines[begin:i+2]) - try: - x509cert = OpenSSL.crypto.load_certificate( - OpenSSL.crypto.FILETYPE_PEM, cert) - cert_store.add_cert(x509cert) - except OpenSSL.crypto.Error, exception_obj: - if logg: - log.warning('Unable to load a certificate from file %s: %s' %\ - (cert_path, exception_obj.args[0][0][2])) - except: - log.warning('Unknown error while loading certificate from file ' - '%s' % cert_path) - begin = -1 - i += 1 + def _load_cert_file(self, cert_path, cert_store, logg=True): + if not os.path.isfile(cert_path): + return + try: + f = open(cert_path) + except IOError, e: + log.warning('Unable to open certificate file %s: %s' % \ + (cert_path, str(e))) + return + lines = f.readlines() + i = 0 + begin = -1 + for line in lines: + if 'BEGIN CERTIFICATE' in line: + begin = i + elif 'END CERTIFICATE' in line and begin > -1: + cert = ''.join(lines[begin:i+2]) + try: + x509cert = OpenSSL.crypto.load_certificate( + OpenSSL.crypto.FILETYPE_PEM, cert) + cert_store.add_cert(x509cert) + except OpenSSL.crypto.Error, exception_obj: + if logg: + log.warning('Unable to load a certificate from file %s: %s' %\ + (cert_path, exception_obj.args[0][0][2])) + except: + log.warning('Unknown error while loading certificate from file ' + '%s' % cert_path) + begin = -1 + i += 1 - def _startSSL_pyOpenSSL(self): - log.debug("_startSSL_pyOpenSSL called") - tcpsock = self._owner - # See http://docs.python.org/dev/library/ssl.html - tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) - tcpsock.ssl_errnum = 0 - tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER, - self._ssl_verify_callback) - try: - tcpsock._sslContext.load_verify_locations(self.cacerts) - except: - log.warning('Unable to load SSL certificates from file %s' % \ - os.path.abspath(self.cacerts)) - store = tcpsock._sslContext.get_cert_store() - self._load_cert_file(self.mycerts, store) - if os.path.isdir('/etc/ssl/certs'): - for f in os.listdir('/etc/ssl/certs'): - # We don't logg because there is a lot a duplicated certs in this - # folder - self._load_cert_file(os.path.join('/etc/ssl/certs', f), store, - logg=False) + def _startSSL_pyOpenSSL(self): + log.debug("_startSSL_pyOpenSSL called") + tcpsock = self._owner + # See http://docs.python.org/dev/library/ssl.html + tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) + tcpsock.ssl_errnum = 0 + tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER, + self._ssl_verify_callback) + try: + tcpsock._sslContext.load_verify_locations(self.cacerts) + except: + log.warning('Unable to load SSL certificates from file %s' % \ + os.path.abspath(self.cacerts)) + store = tcpsock._sslContext.get_cert_store() + self._load_cert_file(self.mycerts, store) + if os.path.isdir('/etc/ssl/certs'): + for f in os.listdir('/etc/ssl/certs'): + # We don't logg because there is a lot a duplicated certs in this + # folder + self._load_cert_file(os.path.join('/etc/ssl/certs', f), store, + logg=False) - tcpsock._sslObj = OpenSSL.SSL.Connection(tcpsock._sslContext, - tcpsock._sock) - tcpsock._sslObj.set_connect_state() # set to client mode - wrapper = PyOpenSSLWrapper(tcpsock._sslObj) - tcpsock._recv = wrapper.recv - tcpsock._send = wrapper.send + tcpsock._sslObj = OpenSSL.SSL.Connection(tcpsock._sslContext, + tcpsock._sock) + tcpsock._sslObj.set_connect_state() # set to client mode + wrapper = PyOpenSSLWrapper(tcpsock._sslObj) + tcpsock._recv = wrapper.recv + tcpsock._send = wrapper.send - log.debug("Initiating handshake...") - tcpsock._sslObj.setblocking(True) - try: - tcpsock._sslObj.do_handshake() - except: - log.error('Error while TLS handshake: ', exc_info=True) - return False - tcpsock._sslObj.setblocking(False) - self._owner.ssl_lib = PYOPENSSL - return True + log.debug("Initiating handshake...") + tcpsock._sslObj.setblocking(True) + try: + tcpsock._sslObj.do_handshake() + except: + log.error('Error while TLS handshake: ', exc_info=True) + return False + tcpsock._sslObj.setblocking(False) + self._owner.ssl_lib = PYOPENSSL + return True - def _startSSL_stdlib(self): - log.debug("_startSSL_stdlib called") - tcpsock=self._owner - try: - tcpsock._sock.setblocking(True) - tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None) - tcpsock._sock.setblocking(False) - tcpsock._sslIssuer = tcpsock._sslObj.issuer() - tcpsock._sslServer = tcpsock._sslObj.server() - wrapper = StdlibSSLWrapper(tcpsock._sslObj, tcpsock._sock) - tcpsock._recv = wrapper.recv - tcpsock._send = wrapper.send - except: - log.error("Exception caught in _startSSL_stdlib:", exc_info=True) - return False - self._owner.ssl_lib = PYSTDLIB - return True + def _startSSL_stdlib(self): + log.debug("_startSSL_stdlib called") + tcpsock=self._owner + try: + tcpsock._sock.setblocking(True) + tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None) + tcpsock._sock.setblocking(False) + tcpsock._sslIssuer = tcpsock._sslObj.issuer() + tcpsock._sslServer = tcpsock._sslObj.server() + wrapper = StdlibSSLWrapper(tcpsock._sslObj, tcpsock._sock) + tcpsock._recv = wrapper.recv + tcpsock._send = wrapper.send + except: + log.error("Exception caught in _startSSL_stdlib:", exc_info=True) + return False + self._owner.ssl_lib = PYSTDLIB + return True - def _ssl_verify_callback(self, sslconn, cert, errnum, depth, ok): - # Exceptions can't propagate up through this callback, so print them here. - try: - self._owner.ssl_fingerprint_sha1 = cert.digest('sha1') - if errnum == 0: - return True - self._owner.ssl_errnum = errnum - self._owner.ssl_cert_pem = OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_PEM, cert) - return True - except: - log.error("Exception caught in _ssl_info_callback:", exc_info=True) - # Make sure something is printed, even if log is disabled. - traceback.print_exc() - -# vim: se ts=3: + def _ssl_verify_callback(self, sslconn, cert, errnum, depth, ok): + # Exceptions can't propagate up through this callback, so print them here. + try: + self._owner.ssl_fingerprint_sha1 = cert.digest('sha1') + if errnum == 0: + return True + self._owner.ssl_errnum = errnum + self._owner.ssl_cert_pem = OpenSSL.crypto.dump_certificate( + OpenSSL.crypto.FILETYPE_PEM, cert) + return True + except: + log.error("Exception caught in _ssl_info_callback:", exc_info=True) + # Make sure something is printed, even if log is disabled. + traceback.print_exc() diff --git a/src/common/xmpp/transports_nb.py b/src/common/xmpp/transports_nb.py index dfe9870c6..8d0368abc 100644 --- a/src/common/xmpp/transports_nb.py +++ b/src/common/xmpp/transports_nb.py @@ -41,40 +41,40 @@ import logging log = logging.getLogger('gajim.c.x.transports_nb') def urisplit(uri): - """ - Function for splitting URI string to tuple (protocol, host, port, path). - e.g. urisplit('http://httpcm.jabber.org:123/webclient') returns ('http', - 'httpcm.jabber.org', 123, '/webclient') return 443 as default port if proto - is https else 80 - """ - splitted = urlparse.urlsplit(uri) - proto, host, path = splitted.scheme, splitted.hostname, splitted.path - try: - port = splitted.port - except ValueError: - log.warn('port cannot be extracted from BOSH URL %s, using default port' \ - % uri) - port = '' - if not port: - if proto == 'https': - port = 443 - else: - port = 80 - return proto, host, port, path + """ + Function for splitting URI string to tuple (protocol, host, port, path). + e.g. urisplit('http://httpcm.jabber.org:123/webclient') returns ('http', + 'httpcm.jabber.org', 123, '/webclient') return 443 as default port if proto + is https else 80 + """ + splitted = urlparse.urlsplit(uri) + proto, host, path = splitted.scheme, splitted.hostname, splitted.path + try: + port = splitted.port + except ValueError: + log.warn('port cannot be extracted from BOSH URL %s, using default port' \ + % uri) + port = '' + if not port: + if proto == 'https': + port = 443 + else: + port = 80 + return proto, host, port, path def get_proxy_data_from_dict(proxy): - tcp_host, tcp_port, proxy_user, proxy_pass = None, None, None, None - proxy_type = proxy['type'] - if proxy_type == 'bosh' and not proxy['bosh_useproxy']: - # with BOSH not over proxy we have to parse the hostname from BOSH URI - proto, tcp_host, tcp_port, path = urisplit(proxy['bosh_uri']) - else: - # with proxy!=bosh or with bosh over HTTP proxy we're connecting to proxy - # machine - tcp_host, tcp_port = proxy['host'], proxy['port'] - if proxy.get('useauth', False): - proxy_user, proxy_pass = proxy['user'], proxy['pass'] - return tcp_host, tcp_port, proxy_user, proxy_pass + tcp_host, tcp_port, proxy_user, proxy_pass = None, None, None, None + proxy_type = proxy['type'] + if proxy_type == 'bosh' and not proxy['bosh_useproxy']: + # with BOSH not over proxy we have to parse the hostname from BOSH URI + proto, tcp_host, tcp_port, path = urisplit(proxy['bosh_uri']) + else: + # with proxy!=bosh or with bosh over HTTP proxy we're connecting to proxy + # machine + tcp_host, tcp_port = proxy['host'], proxy['port'] + if proxy.get('useauth', False): + proxy_user, proxy_pass = proxy['user'], proxy['pass'] + return tcp_host, tcp_port, proxy_user, proxy_pass #: timeout to connect to the server socket, it doesn't include auth CONNECT_TIMEOUT_SECONDS = 30 @@ -102,686 +102,684 @@ CONNECTED = 'CONNECTED' STATES = (DISCONNECTED, CONNECTING, PROXY_CONNECTING, CONNECTED, DISCONNECTING) class NonBlockingTransport(PlugIn): - """ - Abstract class representing a transport + """ + Abstract class representing a transport - Subclasses CAN have different constructor signature but connect method SHOULD - be the same. - """ + Subclasses CAN have different constructor signature but connect method SHOULD + be the same. + """ - def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, - certs): - """ - Each trasport class can have different constructor but it has to have at - least all the arguments of NonBlockingTransport constructor + def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, + certs): + """ + Each trasport class can have different constructor but it has to have at + least all the arguments of NonBlockingTransport constructor - :param raise_event: callback for monitoring of sent and received data - :param on_disconnect: callback called on disconnection during runtime - :param idlequeue: processing idlequeue - :param estabilish_tls: boolean whether to estabilish TLS connection after - TCP connection is done - :param certs: tuple of (cacerts, mycerts) see constructor of - tls_nb.NonBlockingTLS for more details - """ - PlugIn.__init__(self) - self.raise_event = raise_event - self.on_disconnect = on_disconnect - self.on_connect = None - self.on_connect_failure = None - self.idlequeue = idlequeue - self.on_receive = None - self.server = None - self.port = None - self.conn_5tuple = None - self.set_state(DISCONNECTED) - self.estabilish_tls = estabilish_tls - self.certs = certs - # type of used ssl lib (if any) will be assigned to this member var - self.ssl_lib = None - self._exported_methods=[self.onreceive, self.set_send_timeout, - self.set_send_timeout2, self.set_timeout, self.remove_timeout, - self.start_disconnect] + :param raise_event: callback for monitoring of sent and received data + :param on_disconnect: callback called on disconnection during runtime + :param idlequeue: processing idlequeue + :param estabilish_tls: boolean whether to estabilish TLS connection after + TCP connection is done + :param certs: tuple of (cacerts, mycerts) see constructor of + tls_nb.NonBlockingTLS for more details + """ + PlugIn.__init__(self) + self.raise_event = raise_event + self.on_disconnect = on_disconnect + self.on_connect = None + self.on_connect_failure = None + self.idlequeue = idlequeue + self.on_receive = None + self.server = None + self.port = None + self.conn_5tuple = None + self.set_state(DISCONNECTED) + self.estabilish_tls = estabilish_tls + self.certs = certs + # type of used ssl lib (if any) will be assigned to this member var + self.ssl_lib = None + self._exported_methods=[self.onreceive, self.set_send_timeout, + self.set_send_timeout2, self.set_timeout, self.remove_timeout, + self.start_disconnect] - # time to wait for SOME stanza to come and then send keepalive - self.sendtimeout = 0 + # time to wait for SOME stanza to come and then send keepalive + self.sendtimeout = 0 - # in case we want to something different than sending keepalives - self.on_timeout = None - self.on_timeout2 = None + # in case we want to something different than sending keepalives + self.on_timeout = None + self.on_timeout2 = None - def plugin(self, owner): - owner.Connection = self + def plugin(self, owner): + owner.Connection = self - def plugout(self): - self._owner.Connection = None - self._owner = None - self.disconnect(do_callback=False) + def plugout(self): + self._owner.Connection = None + self._owner = None + self.disconnect(do_callback=False) - def connect(self, conn_5tuple, on_connect, on_connect_failure): - """ - Creates and connects transport to server and port defined in conn_5tuple - which should be item from list returned from getaddrinfo + def connect(self, conn_5tuple, on_connect, on_connect_failure): + """ + Creates and connects transport to server and port defined in conn_5tuple + which should be item from list returned from getaddrinfo - :param conn_5tuple: 5-tuple returned from getaddrinfo - :param on_connect: callback called on successful connect to the server - :param on_connect_failure: callback called on failure when connecting - """ - self.on_connect = on_connect - self.on_connect_failure = on_connect_failure - self.server, self.port = conn_5tuple[4][:2] - self.conn_5tuple = conn_5tuple + :param conn_5tuple: 5-tuple returned from getaddrinfo + :param on_connect: callback called on successful connect to the server + :param on_connect_failure: callback called on failure when connecting + """ + self.on_connect = on_connect + self.on_connect_failure = on_connect_failure + self.server, self.port = conn_5tuple[4][:2] + self.conn_5tuple = conn_5tuple - def set_state(self, newstate): - assert(newstate in STATES) - self.state = newstate + def set_state(self, newstate): + assert(newstate in STATES) + self.state = newstate - def get_state(self): - return self.state + def get_state(self): + return self.state - def _on_connect(self): - """ - Preceeds call of on_connect callback - """ - # data is reference to socket wrapper instance. We don't need it in client - # because - self.set_state(CONNECTED) - self.on_connect() + def _on_connect(self): + """ + Preceeds call of on_connect callback + """ + # data is reference to socket wrapper instance. We don't need it in client + # because + self.set_state(CONNECTED) + self.on_connect() - def _on_connect_failure(self, err_message): - """ - Preceeds call of on_connect_failure callback - """ - # In case of error while connecting we need to disconnect transport - # but we don't want to call DisconnectHandlers from client, - # thus the do_callback=False - self.disconnect(do_callback=False) - self.on_connect_failure(err_message=err_message) + def _on_connect_failure(self, err_message): + """ + Preceeds call of on_connect_failure callback + """ + # In case of error while connecting we need to disconnect transport + # but we don't want to call DisconnectHandlers from client, + # thus the do_callback=False + self.disconnect(do_callback=False) + self.on_connect_failure(err_message=err_message) - def send(self, raw_data, now=False): - if self.get_state() == DISCONNECTED: - log.error('Unable to send %s \n because state is %s.' % - (raw_data, self.get_state())) + def send(self, raw_data, now=False): + if self.get_state() == DISCONNECTED: + log.error('Unable to send %s \n because state is %s.' % + (raw_data, self.get_state())) - def disconnect(self, do_callback=True): - self.set_state(DISCONNECTED) - if do_callback: - # invoke callback given in __init__ - self.on_disconnect() + def disconnect(self, do_callback=True): + self.set_state(DISCONNECTED) + if do_callback: + # invoke callback given in __init__ + self.on_disconnect() - def onreceive(self, recv_handler): - """ - Set the on_receive callback. + def onreceive(self, recv_handler): + """ + Set the on_receive callback. - onreceive(None) sets callback to Dispatcher.ProcessNonBlocking which is - the default one that will decide what to do with received stanza based on - its tag name and namespace. + onreceive(None) sets callback to Dispatcher.ProcessNonBlocking which is + the default one that will decide what to do with received stanza based on + its tag name and namespace. - Do not confuse it with on_receive() method, which is the callback - itself. - """ - if not recv_handler: - if hasattr(self, '_owner') and hasattr(self._owner, 'Dispatcher'): - self.on_receive = self._owner.Dispatcher.ProcessNonBlocking - else: - log.warning('No Dispatcher plugged. Received data will not be processed') - self.on_receive = None - return - self.on_receive = recv_handler + Do not confuse it with on_receive() method, which is the callback + itself. + """ + if not recv_handler: + if hasattr(self, '_owner') and hasattr(self._owner, 'Dispatcher'): + self.on_receive = self._owner.Dispatcher.ProcessNonBlocking + else: + log.warning('No Dispatcher plugged. Received data will not be processed') + self.on_receive = None + return + self.on_receive = recv_handler - def _tcp_connecting_started(self): - self.set_state(CONNECTING) + def _tcp_connecting_started(self): + self.set_state(CONNECTING) - def read_timeout(self): - """ - Called when there's no response from server in defined timeout - """ - if self.on_timeout: - self.on_timeout() - self.renew_send_timeout() + def read_timeout(self): + """ + Called when there's no response from server in defined timeout + """ + if self.on_timeout: + self.on_timeout() + self.renew_send_timeout() - def read_timeout2(self): - """ - called when there's no response from server in defined timeout - """ - if self.on_timeout2: - self.on_timeout2() - self.renew_send_timeout2() + def read_timeout2(self): + """ + called when there's no response from server in defined timeout + """ + if self.on_timeout2: + self.on_timeout2() + self.renew_send_timeout2() - def renew_send_timeout(self): - if self.on_timeout and self.sendtimeout > 0: - self.set_timeout(self.sendtimeout) + def renew_send_timeout(self): + if self.on_timeout and self.sendtimeout > 0: + self.set_timeout(self.sendtimeout) - def renew_send_timeout2(self): - if self.on_timeout2 and self.sendtimeout2 > 0: - self.set_timeout2(self.sendtimeout2) + def renew_send_timeout2(self): + if self.on_timeout2 and self.sendtimeout2 > 0: + self.set_timeout2(self.sendtimeout2) - def set_timeout(self, timeout): - self.idlequeue.set_read_timeout(self.fd, timeout) + def set_timeout(self, timeout): + self.idlequeue.set_read_timeout(self.fd, timeout) - def set_timeout2(self, timeout2): - self.idlequeue.set_read_timeout(self.fd, timeout2, self.read_timeout2) + def set_timeout2(self, timeout2): + self.idlequeue.set_read_timeout(self.fd, timeout2, self.read_timeout2) - def get_fd(self): - pass + def get_fd(self): + pass - def remove_timeout(self): - self.idlequeue.remove_timeout(self.fd) + def remove_timeout(self): + self.idlequeue.remove_timeout(self.fd) - def set_send_timeout(self, timeout, on_timeout): - self.sendtimeout = timeout - if self.sendtimeout > 0: - self.on_timeout = on_timeout - else: - self.on_timeout = None + def set_send_timeout(self, timeout, on_timeout): + self.sendtimeout = timeout + if self.sendtimeout > 0: + self.on_timeout = on_timeout + else: + self.on_timeout = None - def set_send_timeout2(self, timeout2, on_timeout2): - self.sendtimeout2 = timeout2 - if self.sendtimeout2 > 0: - self.on_timeout2 = on_timeout2 - else: - self.on_timeout2 = None + def set_send_timeout2(self, timeout2, on_timeout2): + self.sendtimeout2 = timeout2 + if self.sendtimeout2 > 0: + self.on_timeout2 = on_timeout2 + else: + self.on_timeout2 = None - # FIXME: where and why does this need to be called - def start_disconnect(self): - self.set_state(DISCONNECTING) + # FIXME: where and why does this need to be called + def start_disconnect(self): + self.set_state(DISCONNECTING) class NonBlockingTCP(NonBlockingTransport, IdleObject): - """ - Non-blocking TCP socket wrapper + """ + Non-blocking TCP socket wrapper - It is used for simple XMPP connection. Can be connected via proxy and can - estabilish TLS connection. - """ - def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, - certs, proxy_dict=None): - """ - :param proxy_dict: dictionary with proxy data as loaded from config file - """ - NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue, - estabilish_tls, certs) - IdleObject.__init__(self) + It is used for simple XMPP connection. Can be connected via proxy and can + estabilish TLS connection. + """ + def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, + certs, proxy_dict=None): + """ + :param proxy_dict: dictionary with proxy data as loaded from config file + """ + NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue, + estabilish_tls, certs) + IdleObject.__init__(self) - # queue with messages to be send - self.sendqueue = [] + # queue with messages to be send + self.sendqueue = [] - # bytes remained from the last send message - self.sendbuff = '' + # bytes remained from the last send message + self.sendbuff = '' - self.proxy_dict = proxy_dict - self.on_remote_disconnect = self.disconnect + self.proxy_dict = proxy_dict + self.on_remote_disconnect = self.disconnect - # FIXME: transport should not be aware xmpp - def start_disconnect(self): - NonBlockingTransport.start_disconnect(self) - self.send('', now=True) - self.disconnect() + # FIXME: transport should not be aware xmpp + def start_disconnect(self): + NonBlockingTransport.start_disconnect(self) + self.send('', now=True) + self.disconnect() - def connect(self, conn_5tuple, on_connect, on_connect_failure): - NonBlockingTransport.connect(self, conn_5tuple, on_connect, - on_connect_failure) - log.info('NonBlockingTCP Connect :: About to connect to %s:%s' % - (self.server, self.port)) + def connect(self, conn_5tuple, on_connect, on_connect_failure): + NonBlockingTransport.connect(self, conn_5tuple, on_connect, + on_connect_failure) + log.info('NonBlockingTCP Connect :: About to connect to %s:%s' % + (self.server, self.port)) - try: - self._sock = socket.socket(*conn_5tuple[:3]) - except socket.error, (errnum, errstr): - self._on_connect_failure('NonBlockingTCP Connect: Error while creating\ - socket: %s %s' % (errnum, errstr)) - return + try: + self._sock = socket.socket(*conn_5tuple[:3]) + except socket.error, (errnum, errstr): + self._on_connect_failure('NonBlockingTCP Connect: Error while creating\ + socket: %s %s' % (errnum, errstr)) + return - self._send = self._sock.send - self._recv = self._sock.recv - self.fd = self._sock.fileno() + self._send = self._sock.send + self._recv = self._sock.recv + self.fd = self._sock.fileno() - # we want to be notified when send is possible to connected socket because - # it means the TCP connection is estabilished - self._plug_idle(writable=True, readable=False) - self.peerhost = None + # we want to be notified when send is possible to connected socket because + # it means the TCP connection is estabilished + self._plug_idle(writable=True, readable=False) + self.peerhost = None - # variable for errno symbol that will be found from exception raised - # from connect() - errnum = 0 + # variable for errno symbol that will be found from exception raised + # from connect() + errnum = 0 - # set timeout for TCP connecting - if nonblocking connect() fails, pollend - # is called. If if succeeds pollout is called. - self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT_SECONDS) + # set timeout for TCP connecting - if nonblocking connect() fails, pollend + # is called. If if succeeds pollout is called. + self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT_SECONDS) - try: - self._sock.setblocking(False) - self._sock.connect((self.server, self.port)) - except Exception, (errnum, errstr): - pass + try: + self._sock.setblocking(False) + self._sock.connect((self.server, self.port)) + except Exception, (errnum, errstr): + pass - if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): - # connecting in progress - log.info('After NB connect() of %s. "%s" raised => CONNECTING' % - (id(self), errstr)) - self._tcp_connecting_started() - return + if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): + # connecting in progress + log.info('After NB connect() of %s. "%s" raised => CONNECTING' % + (id(self), errstr)) + self._tcp_connecting_started() + return - # if there was some other exception, call failure callback and unplug - # transport which will also remove read_timeouts for descriptor - self._on_connect_failure('Exception while connecting to %s:%s - %s %s' % - (self.server, self.port, errnum, errstr)) + # if there was some other exception, call failure callback and unplug + # transport which will also remove read_timeouts for descriptor + self._on_connect_failure('Exception while connecting to %s:%s - %s %s' % + (self.server, self.port, errnum, errstr)) - def _connect_to_proxy(self): - self.set_state(PROXY_CONNECTING) - if self.proxy_dict['type'] == 'socks5': - proxyclass = proxy_connectors.SOCKS5Connector - elif self.proxy_dict['type'] == 'http' : - proxyclass = proxy_connectors.HTTPCONNECTConnector - proxyclass.get_instance( - send_method=self.send, - onreceive=self.onreceive, - old_on_receive=self.on_receive, - on_success=self._on_connect, - on_failure=self._on_connect_failure, - xmpp_server=self.proxy_dict['xmpp_server'], - proxy_creds=self.proxy_dict['credentials']) + def _connect_to_proxy(self): + self.set_state(PROXY_CONNECTING) + if self.proxy_dict['type'] == 'socks5': + proxyclass = proxy_connectors.SOCKS5Connector + elif self.proxy_dict['type'] == 'http' : + proxyclass = proxy_connectors.HTTPCONNECTConnector + proxyclass.get_instance( + send_method=self.send, + onreceive=self.onreceive, + old_on_receive=self.on_receive, + on_success=self._on_connect, + on_failure=self._on_connect_failure, + xmpp_server=self.proxy_dict['xmpp_server'], + proxy_creds=self.proxy_dict['credentials']) - def _on_connect(self): - """ - Preceed invoking of on_connect callback. TCP connection is already - estabilished by this time - """ - if self.estabilish_tls: - self.tls_init( - on_succ = lambda: NonBlockingTransport._on_connect(self), - on_fail = lambda: self._on_connect_failure( - 'error while estabilishing TLS')) - else: - NonBlockingTransport._on_connect(self) + def _on_connect(self): + """ + Preceed invoking of on_connect callback. TCP connection is already + estabilished by this time + """ + if self.estabilish_tls: + self.tls_init( + on_succ = lambda: NonBlockingTransport._on_connect(self), + on_fail = lambda: self._on_connect_failure( + 'error while estabilishing TLS')) + else: + NonBlockingTransport._on_connect(self) - def tls_init(self, on_succ, on_fail): - """ - Estabilishes TLS/SSL using this TCP connection by plugging a - NonBlockingTLS module - """ - cacerts, mycerts = self.certs - result = tls_nb.NonBlockingTLS.get_instance(cacerts, mycerts).PlugIn(self) - if result: - on_succ() - else: - on_fail() + def tls_init(self, on_succ, on_fail): + """ + Estabilishes TLS/SSL using this TCP connection by plugging a + NonBlockingTLS module + """ + cacerts, mycerts = self.certs + result = tls_nb.NonBlockingTLS.get_instance(cacerts, mycerts).PlugIn(self) + if result: + on_succ() + else: + on_fail() - def pollin(self): - """ - Called by idlequeu when receive on plugged socket is possible - """ - log.info('pollin called, state == %s' % self.get_state()) - self._do_receive() + def pollin(self): + """ + Called by idlequeu when receive on plugged socket is possible + """ + log.info('pollin called, state == %s' % self.get_state()) + self._do_receive() - def pollout(self): - """ - Called by idlequeu when send to plugged socket is possible - """ - log.info('pollout called, state == %s' % self.get_state()) + def pollout(self): + """ + Called by idlequeu when send to plugged socket is possible + """ + log.info('pollout called, state == %s' % self.get_state()) - if self.get_state() == CONNECTING: - log.info('%s socket wrapper connected' % id(self)) - self.idlequeue.remove_timeout(self.fd) - self._plug_idle(writable=False, readable=False) - self.peerhost = self._sock.getsockname() - if self.proxy_dict: - self._connect_to_proxy() - else: - self._on_connect() - else: - self._do_send() + if self.get_state() == CONNECTING: + log.info('%s socket wrapper connected' % id(self)) + self.idlequeue.remove_timeout(self.fd) + self._plug_idle(writable=False, readable=False) + self.peerhost = self._sock.getsockname() + if self.proxy_dict: + self._connect_to_proxy() + else: + self._on_connect() + else: + self._do_send() - def pollend(self): - """ - Called by idlequeue on TCP connection errors - """ - log.info('pollend called, state == %s' % self.get_state()) + def pollend(self): + """ + Called by idlequeue on TCP connection errors + """ + log.info('pollend called, state == %s' % self.get_state()) - if self.get_state() == CONNECTING: - self._on_connect_failure('Error during connect to %s:%s' % - (self.server, self.port)) - else: - self.disconnect() + if self.get_state() == CONNECTING: + self._on_connect_failure('Error during connect to %s:%s' % + (self.server, self.port)) + else: + self.disconnect() - def disconnect(self, do_callback=True): - if self.get_state() == DISCONNECTED: - return - self.set_state(DISCONNECTED) - self.idlequeue.unplug_idle(self.fd) - if 'NonBlockingTLS' in self.__dict__: - self.NonBlockingTLS.PlugOut() - try: - self._sock.shutdown(socket.SHUT_RDWR) - self._sock.close() - except socket.error, (errnum, errstr): - log.error('Error while disconnecting socket: %s' % errstr) - self.fd = -1 - NonBlockingTransport.disconnect(self, do_callback) + def disconnect(self, do_callback=True): + if self.get_state() == DISCONNECTED: + return + self.set_state(DISCONNECTED) + self.idlequeue.unplug_idle(self.fd) + if 'NonBlockingTLS' in self.__dict__: + self.NonBlockingTLS.PlugOut() + try: + self._sock.shutdown(socket.SHUT_RDWR) + self._sock.close() + except socket.error, (errnum, errstr): + log.error('Error while disconnecting socket: %s' % errstr) + self.fd = -1 + NonBlockingTransport.disconnect(self, do_callback) - def read_timeout(self): - log.info('read_timeout called, state == %s' % self.get_state()) - if self.get_state() == CONNECTING: - # if read_timeout is called during connecting, connect() didn't end yet - # thus we have to call the tcp failure callback - self._on_connect_failure('Error during connect to %s:%s' % - (self.server, self.port)) - else: - NonBlockingTransport.read_timeout(self) + def read_timeout(self): + log.info('read_timeout called, state == %s' % self.get_state()) + if self.get_state() == CONNECTING: + # if read_timeout is called during connecting, connect() didn't end yet + # thus we have to call the tcp failure callback + self._on_connect_failure('Error during connect to %s:%s' % + (self.server, self.port)) + else: + NonBlockingTransport.read_timeout(self) - def set_timeout(self, timeout): - if self.get_state() != DISCONNECTED and self.fd != -1: - NonBlockingTransport.set_timeout(self, timeout) - else: - log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % - (self.get_state(), self.fd)) + def set_timeout(self, timeout): + if self.get_state() != DISCONNECTED and self.fd != -1: + NonBlockingTransport.set_timeout(self, timeout) + else: + log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % + (self.get_state(), self.fd)) - def remove_timeout(self): - if self.fd: - NonBlockingTransport.remove_timeout(self) - else: - log.warn('remove_timeout: no self.fd state is %s' % self.get_state()) + def remove_timeout(self): + if self.fd: + NonBlockingTransport.remove_timeout(self) + else: + log.warn('remove_timeout: no self.fd state is %s' % self.get_state()) - def send(self, raw_data, now=False): - """ - Append raw_data to the queue of messages to be send. If supplied data is - unicode string, encode it to utf-8. - """ - NonBlockingTransport.send(self, raw_data, now) + def send(self, raw_data, now=False): + """ + Append raw_data to the queue of messages to be send. If supplied data is + unicode string, encode it to utf-8. + """ + NonBlockingTransport.send(self, raw_data, now) - r = self.encode_stanza(raw_data) + r = self.encode_stanza(raw_data) - if now: - self.sendqueue.insert(0, r) - self._do_send() - else: - self.sendqueue.append(r) + if now: + self.sendqueue.insert(0, r) + self._do_send() + else: + self.sendqueue.append(r) - self._plug_idle(writable=True, readable=True) + self._plug_idle(writable=True, readable=True) - def encode_stanza(self, stanza): - """ - Encode str or unicode to utf-8 - """ - if isinstance(stanza, unicode): - stanza = stanza.encode('utf-8') - elif not isinstance(stanza, str): - stanza = ustr(stanza).encode('utf-8') - return stanza + def encode_stanza(self, stanza): + """ + Encode str or unicode to utf-8 + """ + if isinstance(stanza, unicode): + stanza = stanza.encode('utf-8') + elif not isinstance(stanza, str): + stanza = ustr(stanza).encode('utf-8') + return stanza - def _plug_idle(self, writable, readable): - """ - Plug file descriptor of socket to Idlequeue + def _plug_idle(self, writable, readable): + """ + Plug file descriptor of socket to Idlequeue - Plugged socket will be watched for "send possible" or/and "recv possible" - events. pollin() callback is invoked on "recv possible", pollout() on - "send_possible". + Plugged socket will be watched for "send possible" or/and "recv possible" + events. pollin() callback is invoked on "recv possible", pollout() on + "send_possible". - Plugged socket will always be watched for "error" event - in that case, - pollend() is called. - """ - log.info('Plugging fd %d, W:%s, R:%s' % (self.fd, writable, readable)) - self.idlequeue.plug_idle(self, writable, readable) + Plugged socket will always be watched for "error" event - in that case, + pollend() is called. + """ + log.info('Plugging fd %d, W:%s, R:%s' % (self.fd, writable, readable)) + self.idlequeue.plug_idle(self, writable, readable) - def _do_send(self): - """ - Called when send() to connected socket is possible. First message from - sendqueue will be sent - """ - if not self.sendbuff: - if not self.sendqueue: - log.warn('calling send on empty buffer and queue') - self._plug_idle(writable=False, readable=True) - return None - self.sendbuff = self.sendqueue.pop(0) - try: - send_count = self._send(self.sendbuff) - if send_count: - sent_data = self.sendbuff[:send_count] - self.sendbuff = self.sendbuff[send_count:] - self._plug_idle( - writable=((self.sendqueue!=[]) or (self.sendbuff!='')), - readable=True) - self.raise_event(DATA_SENT, sent_data) + def _do_send(self): + """ + Called when send() to connected socket is possible. First message from + sendqueue will be sent + """ + if not self.sendbuff: + if not self.sendqueue: + log.warn('calling send on empty buffer and queue') + self._plug_idle(writable=False, readable=True) + return None + self.sendbuff = self.sendqueue.pop(0) + try: + send_count = self._send(self.sendbuff) + if send_count: + sent_data = self.sendbuff[:send_count] + self.sendbuff = self.sendbuff[send_count:] + self._plug_idle( + writable=((self.sendqueue!=[]) or (self.sendbuff!='')), + readable=True) + self.raise_event(DATA_SENT, sent_data) - except socket.error, e: - log.error('_do_send:', exc_info=True) - traceback.print_exc() - self.disconnect() + except socket.error, e: + log.error('_do_send:', exc_info=True) + traceback.print_exc() + self.disconnect() - def _do_receive(self): - """ - Reads all pending incoming data. Will call owner's disconnected() method - if appropriate - """ - received = None - errnum = 0 - errstr = 'No Error Set' + def _do_receive(self): + """ + Reads all pending incoming data. Will call owner's disconnected() method + if appropriate + """ + received = None + errnum = 0 + errstr = 'No Error Set' - try: - # get as many bites, as possible, but not more than RECV_BUFSIZE - received = self._recv(RECV_BUFSIZE) - except socket.error, (errnum, errstr): - log.info("_do_receive: got %s:" % received , exc_info=True) - except tls_nb.SSLWrapper.Error, e: - log.info("_do_receive, caught SSL error, got %s:" % received, - exc_info=True) - errnum, errstr = e.errno, e.strerror + try: + # get as many bites, as possible, but not more than RECV_BUFSIZE + received = self._recv(RECV_BUFSIZE) + except socket.error, (errnum, errstr): + log.info("_do_receive: got %s:" % received, exc_info=True) + except tls_nb.SSLWrapper.Error, e: + log.info("_do_receive, caught SSL error, got %s:" % received, + exc_info=True) + errnum, errstr = e.errno, e.strerror - if received == '': - errstr = 'zero bytes on recv' + if received == '': + errstr = 'zero bytes on recv' - if (self.ssl_lib is None and received == '') or \ - (self.ssl_lib == tls_nb.PYSTDLIB and errnum == 8 ) or \ - (self.ssl_lib == tls_nb.PYOPENSSL and errnum == -1 ): - # 8 in stdlib: errstr == EOF occured in violation of protocol - # -1 in pyopenssl: errstr == Unexpected EOF - log.info("Disconnected by remote server: #%s, %s" % (errnum, errstr)) - self.on_remote_disconnect() - return + if (self.ssl_lib is None and received == '') or \ + (self.ssl_lib == tls_nb.PYSTDLIB and errnum == 8 ) or \ + (self.ssl_lib == tls_nb.PYOPENSSL and errnum == -1 ): + # 8 in stdlib: errstr == EOF occured in violation of protocol + # -1 in pyopenssl: errstr == Unexpected EOF + log.info("Disconnected by remote server: #%s, %s" % (errnum, errstr)) + self.on_remote_disconnect() + return - if errnum: - log.info("Connection to %s:%s lost: %s %s" % (self.server, self.port, - errnum, errstr), exc_info=True) - self.disconnect() - return + if errnum: + log.info("Connection to %s:%s lost: %s %s" % (self.server, self.port, + errnum, errstr), exc_info=True) + self.disconnect() + return - # this branch is for case of non-fatal SSL errors - None is returned from - # recv() but no errnum is set - if received is None: - return + # this branch is for case of non-fatal SSL errors - None is returned from + # recv() but no errnum is set + if received is None: + return - # we have received some bytes, stop the timeout! - self.remove_timeout() - self.renew_send_timeout() - self.renew_send_timeout2() - # pass received data to owner - if self.on_receive: - self.raise_event(DATA_RECEIVED, received) - self._on_receive(received) - else: - # This should never happen, so we need the debug. - # (If there is no handler on receive specified, data is passed to - # Dispatcher.ProcessNonBlocking) - log.error('SOCKET %s Unhandled data received: %s' % (id(self), - received)) - self.disconnect() + # we have received some bytes, stop the timeout! + self.remove_timeout() + self.renew_send_timeout() + self.renew_send_timeout2() + # pass received data to owner + if self.on_receive: + self.raise_event(DATA_RECEIVED, received) + self._on_receive(received) + else: + # This should never happen, so we need the debug. + # (If there is no handler on receive specified, data is passed to + # Dispatcher.ProcessNonBlocking) + log.error('SOCKET %s Unhandled data received: %s' % (id(self), + received)) + self.disconnect() - def _on_receive(self, data): - """ - Preceeds on_receive callback. It peels off and checks HTTP headers in - HTTP classes, in here it just calls the callback - """ - self.on_receive(data) + def _on_receive(self, data): + """ + Preceeds on_receive callback. It peels off and checks HTTP headers in + HTTP classes, in here it just calls the callback + """ + self.on_receive(data) class NonBlockingHTTP(NonBlockingTCP): - """ - Socket wrapper that creates HTTP message out of sent data and peels-off HTTP - headers from incoming messages - """ + """ + Socket wrapper that creates HTTP message out of sent data and peels-off HTTP + headers from incoming messages + """ - def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, - certs, on_http_request_possible, on_persistent_fallback, http_dict, - proxy_dict=None): - """ - :param on_http_request_possible: method to call when HTTP request to - socket owned by transport is possible. - :param on_persistent_fallback: callback called when server ends TCP - connection. It doesn't have to be fatal for HTTP session. - :param http_dict: dictionary with data for HTTP request and headers - """ - NonBlockingTCP.__init__(self, raise_event, on_disconnect, idlequeue, - estabilish_tls, certs, proxy_dict) + def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, + certs, on_http_request_possible, on_persistent_fallback, http_dict, + proxy_dict=None): + """ + :param on_http_request_possible: method to call when HTTP request to + socket owned by transport is possible. + :param on_persistent_fallback: callback called when server ends TCP + connection. It doesn't have to be fatal for HTTP session. + :param http_dict: dictionary with data for HTTP request and headers + """ + NonBlockingTCP.__init__(self, raise_event, on_disconnect, idlequeue, + estabilish_tls, certs, proxy_dict) - self.http_protocol, self.http_host, self.http_port, self.http_path = \ - urisplit(http_dict['http_uri']) - self.http_protocol = self.http_protocol or 'http' - self.http_path = self.http_path or '/' - self.http_version = http_dict['http_version'] - self.http_persistent = http_dict['http_persistent'] - self.add_proxy_headers = http_dict['add_proxy_headers'] + self.http_protocol, self.http_host, self.http_port, self.http_path = \ + urisplit(http_dict['http_uri']) + self.http_protocol = self.http_protocol or 'http' + self.http_path = self.http_path or '/' + self.http_version = http_dict['http_version'] + self.http_persistent = http_dict['http_persistent'] + self.add_proxy_headers = http_dict['add_proxy_headers'] - if 'proxy_user' in http_dict and 'proxy_pass' in http_dict: - self.proxy_user, self.proxy_pass = http_dict['proxy_user'], http_dict[ - 'proxy_pass'] - else: - self.proxy_user, self.proxy_pass = None, None + if 'proxy_user' in http_dict and 'proxy_pass' in http_dict: + self.proxy_user, self.proxy_pass = http_dict['proxy_user'], http_dict[ + 'proxy_pass'] + else: + self.proxy_user, self.proxy_pass = None, None - # buffer for partial responses - self.recvbuff = '' - self.expected_length = 0 - self.pending_requests = 0 - self.on_http_request_possible = on_http_request_possible - self.last_recv_time = 0 - self.close_current_connection = False - self.on_remote_disconnect = lambda: on_persistent_fallback(self) + # buffer for partial responses + self.recvbuff = '' + self.expected_length = 0 + self.pending_requests = 0 + self.on_http_request_possible = on_http_request_possible + self.last_recv_time = 0 + self.close_current_connection = False + self.on_remote_disconnect = lambda: on_persistent_fallback(self) - def http_send(self, raw_data, now=False): - self.send(self.build_http_message(raw_data), now) + def http_send(self, raw_data, now=False): + self.send(self.build_http_message(raw_data), now) - def _on_receive(self, data): - """ - Preceeds passing received data to owner class. Gets rid of HTTP headers - and checks them. - """ - if self.get_state() == PROXY_CONNECTING: - NonBlockingTCP._on_receive(self, data) - return + def _on_receive(self, data): + """ + Preceeds passing received data to owner class. Gets rid of HTTP headers + and checks them. + """ + if self.get_state() == PROXY_CONNECTING: + NonBlockingTCP._on_receive(self, data) + return - # append currently received data to HTTP msg in buffer - self.recvbuff = '%s%s' % (self.recvbuff or '', data) - statusline, headers, httpbody, buffer_rest = self.parse_http_message( - self.recvbuff) + # append currently received data to HTTP msg in buffer + self.recvbuff = '%s%s' % (self.recvbuff or '', data) + statusline, headers, httpbody, buffer_rest = self.parse_http_message( + self.recvbuff) - if not (statusline and headers and httpbody): - log.debug('Received incomplete HTTP response') - return + if not (statusline and headers and httpbody): + log.debug('Received incomplete HTTP response') + return - if statusline[1] != '200': - log.error('HTTP Error: %s %s' % (statusline[1], statusline[2])) - self.disconnect() - return - self.expected_length = int(headers['Content-Length']) - if 'Connection' in headers and headers['Connection'].strip()=='close': - self.close_current_connection = True + if statusline[1] != '200': + log.error('HTTP Error: %s %s' % (statusline[1], statusline[2])) + self.disconnect() + return + self.expected_length = int(headers['Content-Length']) + if 'Connection' in headers and headers['Connection'].strip()=='close': + self.close_current_connection = True - if self.expected_length > len(httpbody): - # If we haven't received the whole HTTP mess yet, let's end the thread. - # It will be finnished from one of following recvs on plugged socket. - log.info('not enough bytes in HTTP response - %d expected, got %d' % - (self.expected_length, len(httpbody))) - else: - # First part of buffer has been extraced and is going to be handled, - # remove it from buffer - self.recvbuff = buffer_rest + if self.expected_length > len(httpbody): + # If we haven't received the whole HTTP mess yet, let's end the thread. + # It will be finnished from one of following recvs on plugged socket. + log.info('not enough bytes in HTTP response - %d expected, got %d' % + (self.expected_length, len(httpbody))) + else: + # First part of buffer has been extraced and is going to be handled, + # remove it from buffer + self.recvbuff = buffer_rest - # everything was received - self.expected_length = 0 + # everything was received + self.expected_length = 0 - if not self.http_persistent or self.close_current_connection: - # not-persistent connections disconnect after response - self.disconnect(do_callback=False) - self.close_current_connection = False - self.last_recv_time = time.time() - self.on_receive(data=httpbody, socket=self) - self.on_http_request_possible() + if not self.http_persistent or self.close_current_connection: + # not-persistent connections disconnect after response + self.disconnect(do_callback=False) + self.close_current_connection = False + self.last_recv_time = time.time() + self.on_receive(data=httpbody, socket=self) + self.on_http_request_possible() - def build_http_message(self, httpbody, method='POST'): - """ - Builds http message with given body. Values for headers and status line - fields are taken from class variables - """ - absolute_uri = '%s://%s:%s%s' % (self.http_protocol, self.http_host, - self.http_port, self.http_path) - headers = ['%s %s %s' % (method, absolute_uri, self.http_version), - 'Host: %s:%s' % (self.http_host, self.http_port), - 'User-Agent: Gajim', - 'Content-Type: text/xml; charset=utf-8', - 'Content-Length: %s' % len(str(httpbody))] - if self.add_proxy_headers: - headers.append('Proxy-Connection: keep-alive') - headers.append('Pragma: no-cache') - if self.proxy_user and self.proxy_pass: - credentials = '%s:%s' % (self.proxy_user, self.proxy_pass) - credentials = base64.encodestring(credentials).strip() - headers.append('Proxy-Authorization: Basic %s' % credentials) - else: - headers.append('Connection: Keep-Alive') - headers.append('\r\n') - headers = '\r\n'.join(headers) - return('%s%s' % (headers, httpbody)) + def build_http_message(self, httpbody, method='POST'): + """ + Builds http message with given body. Values for headers and status line + fields are taken from class variables + """ + absolute_uri = '%s://%s:%s%s' % (self.http_protocol, self.http_host, + self.http_port, self.http_path) + headers = ['%s %s %s' % (method, absolute_uri, self.http_version), + 'Host: %s:%s' % (self.http_host, self.http_port), + 'User-Agent: Gajim', + 'Content-Type: text/xml; charset=utf-8', + 'Content-Length: %s' % len(str(httpbody))] + if self.add_proxy_headers: + headers.append('Proxy-Connection: keep-alive') + headers.append('Pragma: no-cache') + if self.proxy_user and self.proxy_pass: + credentials = '%s:%s' % (self.proxy_user, self.proxy_pass) + credentials = base64.encodestring(credentials).strip() + headers.append('Proxy-Authorization: Basic %s' % credentials) + else: + headers.append('Connection: Keep-Alive') + headers.append('\r\n') + headers = '\r\n'.join(headers) + return('%s%s' % (headers, httpbody)) - def parse_http_message(self, message): - """ - Split http message into a tuple: - (statusline - list of e.g. ['HTTP/1.1', '200', 'OK'], - headers - dictionary of headers e.g. {'Content-Length': '604', - 'Content-Type': 'text/xml; charset=utf-8'}, - httpbody - string with http body) - http_rest - what is left in the message after a full HTTP header + body - """ - message = message.replace('\r','') - message = message.lstrip('\n') - splitted = message.split('\n\n') - if len(splitted) < 2: - # no complete http message. Keep filling the buffer until we find one - buffer_rest = message - return ('', '', '', buffer_rest) - else: - (header, httpbody) = splitted[:2] - header = header.split('\n') - statusline = header[0].split(' ', 2) - header = header[1:] - headers = {} - for dummy in header: - row = dummy.split(' ', 1) - headers[row[0][:-1]] = row[1] - body_size = headers['Content-Length'] - rest_splitted = splitted[2:] - while (len(httpbody) < body_size) and rest_splitted: - # Complete httpbody until it has the announced size - httpbody = '\n\n'.join([httpbody, rest_splitted.pop(0)]) - buffer_rest = "\n\n".join(rest_splitted) - return (statusline, headers, httpbody, buffer_rest) + def parse_http_message(self, message): + """ + Split http message into a tuple: + (statusline - list of e.g. ['HTTP/1.1', '200', 'OK'], + headers - dictionary of headers e.g. {'Content-Length': '604', + 'Content-Type': 'text/xml; charset=utf-8'}, + httpbody - string with http body) + http_rest - what is left in the message after a full HTTP header + body + """ + message = message.replace('\r', '') + message = message.lstrip('\n') + splitted = message.split('\n\n') + if len(splitted) < 2: + # no complete http message. Keep filling the buffer until we find one + buffer_rest = message + return ('', '', '', buffer_rest) + else: + (header, httpbody) = splitted[:2] + header = header.split('\n') + statusline = header[0].split(' ', 2) + header = header[1:] + headers = {} + for dummy in header: + row = dummy.split(' ', 1) + headers[row[0][:-1]] = row[1] + body_size = headers['Content-Length'] + rest_splitted = splitted[2:] + while (len(httpbody) < body_size) and rest_splitted: + # Complete httpbody until it has the announced size + httpbody = '\n\n'.join([httpbody, rest_splitted.pop(0)]) + buffer_rest = "\n\n".join(rest_splitted) + return (statusline, headers, httpbody, buffer_rest) class NonBlockingHTTPBOSH(NonBlockingHTTP): - """ - Class for BOSH HTTP connections. Slightly redefines HTTP transport by - calling bosh bodytag generating callback before putting data on wire - """ + """ + Class for BOSH HTTP connections. Slightly redefines HTTP transport by + calling bosh bodytag generating callback before putting data on wire + """ - def set_stanza_build_cb(self, build_cb): - self.build_cb = build_cb + def set_stanza_build_cb(self, build_cb): + self.build_cb = build_cb - def _do_send(self): - if self.state == PROXY_CONNECTING: - NonBlockingTCP._do_send(self) - return - if not self.sendbuff: - stanza = self.build_cb(socket=self) - stanza = self.encode_stanza(stanza) - stanza = self.build_http_message(httpbody=stanza) - self.sendbuff = stanza - NonBlockingTCP._do_send(self) - -# vim: se ts=3: + def _do_send(self): + if self.state == PROXY_CONNECTING: + NonBlockingTCP._do_send(self) + return + if not self.sendbuff: + stanza = self.build_cb(socket=self) + stanza = self.encode_stanza(stanza) + stanza = self.build_http_message(httpbody=stanza) + self.sendbuff = stanza + NonBlockingTCP._do_send(self) diff --git a/src/common/zeroconf/__init__.py b/src/common/zeroconf/__init__.py index bbd45f43d..e69de29bb 100644 --- a/src/common/zeroconf/__init__.py +++ b/src/common/zeroconf/__init__.py @@ -1,2 +0,0 @@ - -# vim: se ts=3: \ No newline at end of file diff --git a/src/common/zeroconf/client_zeroconf.py b/src/common/zeroconf/client_zeroconf.py index 96cbd7436..adc3f45d0 100644 --- a/src/common/zeroconf/client_zeroconf.py +++ b/src/common/zeroconf/client_zeroconf.py @@ -1,7 +1,7 @@ ## common/zeroconf/client_zeroconf.py ## ## Copyright (C) 2006 Stefan Bethge -## 2006 Dimitur Kirov +## 2006 Dimitur Kirov ## ## This file is part of Gajim. ## @@ -46,762 +46,760 @@ CONNECT_TIMEOUT_SECONDS = 10 ACTIVITY_TIMEOUT_SECONDS = 30 class ZeroconfListener(IdleObject): - def __init__(self, port, conn_holder): - """ - Handle all incomming connections on ('0.0.0.0', port) - """ - self.port = port - self.queue_idx = -1 - #~ self.queue = None - self.started = False - self._sock = None - self.fd = -1 - self.caller = conn_holder.caller - self.conn_holder = conn_holder + def __init__(self, port, conn_holder): + """ + Handle all incomming connections on ('0.0.0.0', port) + """ + self.port = port + self.queue_idx = -1 + #~ self.queue = None + self.started = False + self._sock = None + self.fd = -1 + self.caller = conn_holder.caller + self.conn_holder = conn_holder - def bind(self): - flags = socket.AI_PASSIVE - if hasattr(socket, 'AI_ADDRCONFIG'): - flags |= socket.AI_ADDRCONFIG - ai = socket.getaddrinfo(None, self.port, socket.AF_UNSPEC, - socket.SOCK_STREAM, 0, flags)[0] - self._serv = socket.socket(ai[0], ai[1]) - self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) - self._serv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - # will fail when port is busy, or we don't have rights to bind - try: - self._serv.bind((ai[4][0], self.port)) - except Exception: - # unable to bind, show error dialog - return None - self._serv.listen(socket.SOMAXCONN) - self._serv.setblocking(False) - self.fd = self._serv.fileno() - gajim.idlequeue.plug_idle(self, False, True) - self.started = True + def bind(self): + flags = socket.AI_PASSIVE + if hasattr(socket, 'AI_ADDRCONFIG'): + flags |= socket.AI_ADDRCONFIG + ai = socket.getaddrinfo(None, self.port, socket.AF_UNSPEC, + socket.SOCK_STREAM, 0, flags)[0] + self._serv = socket.socket(ai[0], ai[1]) + self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + self._serv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + # will fail when port is busy, or we don't have rights to bind + try: + self._serv.bind((ai[4][0], self.port)) + except Exception: + # unable to bind, show error dialog + return None + self._serv.listen(socket.SOMAXCONN) + self._serv.setblocking(False) + self.fd = self._serv.fileno() + gajim.idlequeue.plug_idle(self, False, True) + self.started = True - def pollend(self): - """ - Called when we stop listening on (host, port) - """ - self.disconnect() + def pollend(self): + """ + Called when we stop listening on (host, port) + """ + self.disconnect() - def pollin(self): - """ - Accept a new incomming connection and notify queue - """ - sock = self.accept_conn() - # loop through roster to find who has connected to us - from_jid = None - ipaddr = sock[1][0] - for jid in self.conn_holder.getRoster().keys(): - entry = self.conn_holder.getRoster().getItem(jid) - if (entry['address'] == ipaddr): - from_jid = jid - break - P2PClient(sock[0], ipaddr, sock[1][1], self.conn_holder, [], from_jid) + def pollin(self): + """ + Accept a new incomming connection and notify queue + """ + sock = self.accept_conn() + # loop through roster to find who has connected to us + from_jid = None + ipaddr = sock[1][0] + for jid in self.conn_holder.getRoster().keys(): + entry = self.conn_holder.getRoster().getItem(jid) + if (entry['address'] == ipaddr): + from_jid = jid + break + P2PClient(sock[0], ipaddr, sock[1][1], self.conn_holder, [], from_jid) - def disconnect(self, message=''): - """ - Free all resources, we are not listening anymore - """ - log.info('Disconnecting ZeroconfListener: %s' % message) - gajim.idlequeue.remove_timeout(self.fd) - gajim.idlequeue.unplug_idle(self.fd) - self.fd = -1 - self.started = False - try: - self._serv.close() - except socket.error: - pass - self.conn_holder.kill_all_connections() + def disconnect(self, message=''): + """ + Free all resources, we are not listening anymore + """ + log.info('Disconnecting ZeroconfListener: %s' % message) + gajim.idlequeue.remove_timeout(self.fd) + gajim.idlequeue.unplug_idle(self.fd) + self.fd = -1 + self.started = False + try: + self._serv.close() + except socket.error: + pass + self.conn_holder.kill_all_connections() - def accept_conn(self): - """ - Accept a new incoming connection - """ - _sock = self._serv.accept() - _sock[0].setblocking(False) - return _sock + def accept_conn(self): + """ + Accept a new incoming connection + """ + _sock = self._serv.accept() + _sock[0].setblocking(False) + return _sock class P2PClient(IdleObject): - def __init__(self, _sock, host, port, conn_holder, stanzaqueue=[], to=None, - on_ok=None, on_not_ok=None): - self._owner = self - self.Namespace = 'jabber:client' - self.protocol_type = 'XMPP' - self.defaultNamespace = self.Namespace - self._component = 0 - self._registered_name = None - self._caller = conn_holder.caller - self.conn_holder = conn_holder - self.stanzaqueue = stanzaqueue - self.to = to - self.Server = host - self.on_ok = on_ok - self.on_not_ok = on_not_ok - self.Connection = None - self.sock_hash = None - if _sock: - self.sock_type = TYPE_SERVER - else: - self.sock_type = TYPE_CLIENT - self.fd = -1 - conn = P2PConnection('', _sock, host, port, self._caller, self.on_connect, - self) - if not self.conn_holder: - # An error occured, disconnect() has been called - if on_not_ok: - on_not_ok('Connection to host could not be established.') - return - self.sock_hash = conn._sock.__hash__ - self.fd = conn.fd - self.conn_holder.add_connection(self, self.Server, port, self.to) - # count messages in queue - for val in self.stanzaqueue: - stanza, is_message = val - if is_message: - if self.fd == -1: - if on_not_ok: - on_not_ok('Connection to host could not be established.') - return - thread_id = stanza.getThread() - id_ = stanza.getID() - if not id_: - id_ = self.Dispatcher.getAnID() - if self.conn_holder.ids_of_awaiting_messages.has_key(self.fd): - self.conn_holder.ids_of_awaiting_messages[self.fd].append((id_, - thread_id)) - else: - self.conn_holder.ids_of_awaiting_messages[self.fd] = [(id_, - thread_id)] + def __init__(self, _sock, host, port, conn_holder, stanzaqueue=[], to=None, + on_ok=None, on_not_ok=None): + self._owner = self + self.Namespace = 'jabber:client' + self.protocol_type = 'XMPP' + self.defaultNamespace = self.Namespace + self._component = 0 + self._registered_name = None + self._caller = conn_holder.caller + self.conn_holder = conn_holder + self.stanzaqueue = stanzaqueue + self.to = to + self.Server = host + self.on_ok = on_ok + self.on_not_ok = on_not_ok + self.Connection = None + self.sock_hash = None + if _sock: + self.sock_type = TYPE_SERVER + else: + self.sock_type = TYPE_CLIENT + self.fd = -1 + conn = P2PConnection('', _sock, host, port, self._caller, self.on_connect, + self) + if not self.conn_holder: + # An error occured, disconnect() has been called + if on_not_ok: + on_not_ok('Connection to host could not be established.') + return + self.sock_hash = conn._sock.__hash__ + self.fd = conn.fd + self.conn_holder.add_connection(self, self.Server, port, self.to) + # count messages in queue + for val in self.stanzaqueue: + stanza, is_message = val + if is_message: + if self.fd == -1: + if on_not_ok: + on_not_ok('Connection to host could not be established.') + return + thread_id = stanza.getThread() + id_ = stanza.getID() + if not id_: + id_ = self.Dispatcher.getAnID() + if self.conn_holder.ids_of_awaiting_messages.has_key(self.fd): + self.conn_holder.ids_of_awaiting_messages[self.fd].append((id_, + thread_id)) + else: + self.conn_holder.ids_of_awaiting_messages[self.fd] = [(id_, + thread_id)] - self.on_responses = {} + self.on_responses = {} - def add_stanza(self, stanza, is_message=False): - if self.Connection: - if self.Connection.state == -1: - return False - self.send(stanza, is_message) - else: - self.stanzaqueue.append((stanza, is_message)) + def add_stanza(self, stanza, is_message=False): + if self.Connection: + if self.Connection.state == -1: + return False + self.send(stanza, is_message) + else: + self.stanzaqueue.append((stanza, is_message)) - if is_message: - thread_id = stanza.getThread() - id_ = stanza.getID() - if not id_: - id_ = self.Dispatcher.getAnID() - if self.conn_holder.ids_of_awaiting_messages.has_key(self.fd): - self.conn_holder.ids_of_awaiting_messages[self.fd].append((id_, - thread_id)) - else: - self.conn_holder.ids_of_awaiting_messages[self.fd] = [(id_, - thread_id)] + if is_message: + thread_id = stanza.getThread() + id_ = stanza.getID() + if not id_: + id_ = self.Dispatcher.getAnID() + if self.conn_holder.ids_of_awaiting_messages.has_key(self.fd): + self.conn_holder.ids_of_awaiting_messages[self.fd].append((id_, + thread_id)) + else: + self.conn_holder.ids_of_awaiting_messages[self.fd] = [(id_, + thread_id)] - return True + return True - def on_message_sent(self, connection_id): - id_, thread_id = \ - self.conn_holder.ids_of_awaiting_messages[connection_id].pop(0) - if self.on_ok: - self.on_ok(id_) - # use on_ok only on first message. For others it's called in - # ClientZeroconf - self.on_ok = None + def on_message_sent(self, connection_id): + id_, thread_id = \ + self.conn_holder.ids_of_awaiting_messages[connection_id].pop(0) + if self.on_ok: + self.on_ok(id_) + # use on_ok only on first message. For others it's called in + # ClientZeroconf + self.on_ok = None - def on_connect(self, conn): - self.Connection = conn - self.Connection.PlugIn(self) - dispatcher_nb.Dispatcher().PlugIn(self) - self._register_handlers() + def on_connect(self, conn): + self.Connection = conn + self.Connection.PlugIn(self) + dispatcher_nb.Dispatcher().PlugIn(self) + self._register_handlers() - def StreamInit(self): - """ - Send an initial stream header - """ - self.Dispatcher.Stream = simplexml.NodeBuilder() - self.Dispatcher.Stream._dispatch_depth = 2 - self.Dispatcher.Stream.dispatch = self.Dispatcher.dispatch - self.Dispatcher.Stream.stream_header_received = self._check_stream_start - self.Dispatcher.Stream.features = None - if self.sock_type == TYPE_CLIENT: - self.send_stream_header() + def StreamInit(self): + """ + Send an initial stream header + """ + self.Dispatcher.Stream = simplexml.NodeBuilder() + self.Dispatcher.Stream._dispatch_depth = 2 + self.Dispatcher.Stream.dispatch = self.Dispatcher.dispatch + self.Dispatcher.Stream.stream_header_received = self._check_stream_start + self.Dispatcher.Stream.features = None + if self.sock_type == TYPE_CLIENT: + self.send_stream_header() - def send_stream_header(self): - self.Dispatcher._metastream = Node('stream:stream') - self.Dispatcher._metastream.setNamespace(self.Namespace) - self.Dispatcher._metastream.setAttr('version', '1.0') - self.Dispatcher._metastream.setAttr('xmlns:stream', NS_STREAMS) - self.Dispatcher._metastream.setAttr('from', self.conn_holder.zeroconf.name) - if self.to: - self.Dispatcher._metastream.setAttr('to', self.to) - self.Dispatcher.send("%s>" % str( - self.Dispatcher._metastream)[:-2]) + def send_stream_header(self): + self.Dispatcher._metastream = Node('stream:stream') + self.Dispatcher._metastream.setNamespace(self.Namespace) + self.Dispatcher._metastream.setAttr('version', '1.0') + self.Dispatcher._metastream.setAttr('xmlns:stream', NS_STREAMS) + self.Dispatcher._metastream.setAttr('from', self.conn_holder.zeroconf.name) + if self.to: + self.Dispatcher._metastream.setAttr('to', self.to) + self.Dispatcher.send("%s>" % str( + self.Dispatcher._metastream)[:-2]) - def _check_stream_start(self, ns, tag, attrs): - if ns != NS_STREAMS or tag != 'stream': - log.error('Incorrect stream start: (%s,%s).Terminating!' % (tag, ns), 'error') - self.Connection.disconnect() - if self.on_not_ok: - self.on_not_ok('Connection to host could not be established: Incorrect answer from server.') - return - if self.sock_type == TYPE_SERVER: - if attrs.has_key('from'): - self.to = attrs['from'] - self.send_stream_header() - if attrs.has_key('version') and attrs['version'] == '1.0': - # other part supports stream features - features = Node('stream:features') - self.Dispatcher.send(features) - while self.stanzaqueue: - stanza, is_message = self.stanzaqueue.pop(0) - self.send(stanza, is_message) - elif self.sock_type == TYPE_CLIENT: - while self.stanzaqueue: - stanza, is_message = self.stanzaqueue.pop(0) - self.send(stanza, is_message) + def _check_stream_start(self, ns, tag, attrs): + if ns != NS_STREAMS or tag != 'stream': + log.error('Incorrect stream start: (%s,%s).Terminating!' % (tag, ns), 'error') + self.Connection.disconnect() + if self.on_not_ok: + self.on_not_ok('Connection to host could not be established: Incorrect answer from server.') + return + if self.sock_type == TYPE_SERVER: + if attrs.has_key('from'): + self.to = attrs['from'] + self.send_stream_header() + if attrs.has_key('version') and attrs['version'] == '1.0': + # other part supports stream features + features = Node('stream:features') + self.Dispatcher.send(features) + while self.stanzaqueue: + stanza, is_message = self.stanzaqueue.pop(0) + self.send(stanza, is_message) + elif self.sock_type == TYPE_CLIENT: + while self.stanzaqueue: + stanza, is_message = self.stanzaqueue.pop(0) + self.send(stanza, is_message) - def on_disconnect(self): - if self.conn_holder: - if self.conn_holder.ids_of_awaiting_messages.has_key(self.fd): - del self.conn_holder.ids_of_awaiting_messages[self.fd] - self.conn_holder.remove_connection(self.sock_hash) - if self.__dict__.has_key('Dispatcher'): - self.Dispatcher.PlugOut() - if self.__dict__.has_key('P2PConnection'): - self.P2PConnection.PlugOut() - self.Connection = None - self._caller = None - self.conn_holder = None + def on_disconnect(self): + if self.conn_holder: + if self.conn_holder.ids_of_awaiting_messages.has_key(self.fd): + del self.conn_holder.ids_of_awaiting_messages[self.fd] + self.conn_holder.remove_connection(self.sock_hash) + if self.__dict__.has_key('Dispatcher'): + self.Dispatcher.PlugOut() + if self.__dict__.has_key('P2PConnection'): + self.P2PConnection.PlugOut() + self.Connection = None + self._caller = None + self.conn_holder = None - def force_disconnect(self): - if self.Connection: - self.disconnect() - else: - self.on_disconnect() + def force_disconnect(self): + if self.Connection: + self.disconnect() + else: + self.on_disconnect() - def _on_receive_document_attrs(self, data): - if data: - self.Dispatcher.ProcessNonBlocking(data) - if not hasattr(self, 'Dispatcher') or \ - self.Dispatcher.Stream._document_attrs is None: - return - self.onreceive(None) - if self.Dispatcher.Stream._document_attrs.has_key('version') and \ - self.Dispatcher.Stream._document_attrs['version'] == '1.0': - #~ self.onreceive(self._on_receive_stream_features) - #XXX continue with TLS - return - self.onreceive(None) - return True + def _on_receive_document_attrs(self, data): + if data: + self.Dispatcher.ProcessNonBlocking(data) + if not hasattr(self, 'Dispatcher') or \ + self.Dispatcher.Stream._document_attrs is None: + return + self.onreceive(None) + if self.Dispatcher.Stream._document_attrs.has_key('version') and \ + self.Dispatcher.Stream._document_attrs['version'] == '1.0': + #~ self.onreceive(self._on_receive_stream_features) + #XXX continue with TLS + return + self.onreceive(None) + return True - def remove_timeout(self): - pass + def remove_timeout(self): + pass - def _register_handlers(self): - self._caller.peerhost = self.Connection._sock.getsockname() - self.RegisterHandler('message', lambda conn, data:self._caller._messageCB( - self.Server, conn, data)) - self.RegisterHandler('iq', self._caller._siSetCB, 'set', - common.xmpp.NS_SI) - self.RegisterHandler('iq', self._caller._siErrorCB, 'error', - common.xmpp.NS_SI) - self.RegisterHandler('iq', self._caller._siResultCB, 'result', - common.xmpp.NS_SI) - self.RegisterHandler('iq', self._caller._bytestreamSetCB, 'set', - common.xmpp.NS_BYTESTREAM) - self.RegisterHandler('iq', self._caller._bytestreamResultCB, 'result', - common.xmpp.NS_BYTESTREAM) - self.RegisterHandler('iq', self._caller._bytestreamErrorCB, 'error', - common.xmpp.NS_BYTESTREAM) - self.RegisterHandler('iq', self._caller._DiscoverItemsGetCB, 'get', - common.xmpp.NS_DISCO_ITEMS) + def _register_handlers(self): + self._caller.peerhost = self.Connection._sock.getsockname() + self.RegisterHandler('message', lambda conn, data:self._caller._messageCB( + self.Server, conn, data)) + self.RegisterHandler('iq', self._caller._siSetCB, 'set', + common.xmpp.NS_SI) + self.RegisterHandler('iq', self._caller._siErrorCB, 'error', + common.xmpp.NS_SI) + self.RegisterHandler('iq', self._caller._siResultCB, 'result', + common.xmpp.NS_SI) + self.RegisterHandler('iq', self._caller._bytestreamSetCB, 'set', + common.xmpp.NS_BYTESTREAM) + self.RegisterHandler('iq', self._caller._bytestreamResultCB, 'result', + common.xmpp.NS_BYTESTREAM) + self.RegisterHandler('iq', self._caller._bytestreamErrorCB, 'error', + common.xmpp.NS_BYTESTREAM) + self.RegisterHandler('iq', self._caller._DiscoverItemsGetCB, 'get', + common.xmpp.NS_DISCO_ITEMS) class P2PConnection(IdleObject, PlugIn): - def __init__(self, sock_hash, _sock, host=None, port=None, caller=None, - on_connect=None, client=None): - IdleObject.__init__(self) - self._owner = client - PlugIn.__init__(self) - self.sendqueue = [] - self.sendbuff = None - self.buff_is_message = False - self._sock = _sock - self.sock_hash = None - self.host, self.port = host, port - self.on_connect = on_connect - self.client = client - self.writable = False - self.readable = False - self._exported_methods = [self.send, self.disconnect, self.onreceive] - self.on_receive = None - if _sock: - self._sock = _sock - self.state = 1 - self._sock.setblocking(False) - self.fd = self._sock.fileno() - self.on_connect(self) - else: - self.state = 0 - try: - self.ais = socket.getaddrinfo(host, port, socket.AF_UNSPEC, - socket.SOCK_STREAM) - except socket.gaierror, e: - log.info('Lookup failure for %s: %s[%s]', host, e[1], repr(e[0]), - exc_info=True) - else: - self.connect_to_next_ip() + def __init__(self, sock_hash, _sock, host=None, port=None, caller=None, + on_connect=None, client=None): + IdleObject.__init__(self) + self._owner = client + PlugIn.__init__(self) + self.sendqueue = [] + self.sendbuff = None + self.buff_is_message = False + self._sock = _sock + self.sock_hash = None + self.host, self.port = host, port + self.on_connect = on_connect + self.client = client + self.writable = False + self.readable = False + self._exported_methods = [self.send, self.disconnect, self.onreceive] + self.on_receive = None + if _sock: + self._sock = _sock + self.state = 1 + self._sock.setblocking(False) + self.fd = self._sock.fileno() + self.on_connect(self) + else: + self.state = 0 + try: + self.ais = socket.getaddrinfo(host, port, socket.AF_UNSPEC, + socket.SOCK_STREAM) + except socket.gaierror, e: + log.info('Lookup failure for %s: %s[%s]', host, e[1], repr(e[0]), + exc_info=True) + else: + self.connect_to_next_ip() - def connect_to_next_ip(self): - if len(self.ais) == 0: - log.error('Connection failure to %s', self.host, exc_info=True) - self.disconnect() - return - ai = self.ais.pop(0) - log.info('Trying to connect to %s through %s:%s', self.host, ai[4][0], - ai[4][1], exc_info=True) - try: - self._sock = socket.socket(*ai[:3]) - self._sock.setblocking(False) - self._server = ai[4] - except socket.error: - if sys.exc_value[0] != errno.EINPROGRESS: - # for all errors, we try other addresses - self.connect_to_next_ip() - return - self.fd = self._sock.fileno() - gajim.idlequeue.plug_idle(self, True, False) - self.set_timeout(CONNECT_TIMEOUT_SECONDS) - self.do_connect() + def connect_to_next_ip(self): + if len(self.ais) == 0: + log.error('Connection failure to %s', self.host, exc_info=True) + self.disconnect() + return + ai = self.ais.pop(0) + log.info('Trying to connect to %s through %s:%s', self.host, ai[4][0], + ai[4][1], exc_info=True) + try: + self._sock = socket.socket(*ai[:3]) + self._sock.setblocking(False) + self._server = ai[4] + except socket.error: + if sys.exc_value[0] != errno.EINPROGRESS: + # for all errors, we try other addresses + self.connect_to_next_ip() + return + self.fd = self._sock.fileno() + gajim.idlequeue.plug_idle(self, True, False) + self.set_timeout(CONNECT_TIMEOUT_SECONDS) + self.do_connect() - def set_timeout(self, timeout): - gajim.idlequeue.remove_timeout(self.fd) - if self.state >= 0: - gajim.idlequeue.set_read_timeout(self.fd, timeout) + def set_timeout(self, timeout): + gajim.idlequeue.remove_timeout(self.fd) + if self.state >= 0: + gajim.idlequeue.set_read_timeout(self.fd, timeout) - def plugin(self, owner): - self.onreceive(owner._on_receive_document_attrs) - self._plug_idle() - return True + def plugin(self, owner): + self.onreceive(owner._on_receive_document_attrs) + self._plug_idle() + return True - def plugout(self): - """ - Disconnect from the remote server and unregister self.disconnected method - from the owner's dispatcher - """ - self.disconnect() - self._owner = None + def plugout(self): + """ + Disconnect from the remote server and unregister self.disconnected method + from the owner's dispatcher + """ + self.disconnect() + self._owner = None - def onreceive(self, recv_handler): - if not recv_handler: - if hasattr(self._owner, 'Dispatcher'): - self.on_receive = self._owner.Dispatcher.ProcessNonBlocking - else: - self.on_receive = None - return - _tmp = self.on_receive - # make sure this cb is not overriden by recursive calls - if not recv_handler(None) and _tmp == self.on_receive: - self.on_receive = recv_handler + def onreceive(self, recv_handler): + if not recv_handler: + if hasattr(self._owner, 'Dispatcher'): + self.on_receive = self._owner.Dispatcher.ProcessNonBlocking + else: + self.on_receive = None + return + _tmp = self.on_receive + # make sure this cb is not overriden by recursive calls + if not recv_handler(None) and _tmp == self.on_receive: + self.on_receive = recv_handler - def send(self, packet, is_message=False, now=False): - """ - Append stanza to the queue of messages to be send if now is False, else - send it instantly + def send(self, packet, is_message=False, now=False): + """ + Append stanza to the queue of messages to be send if now is False, else + send it instantly - If supplied data is unicode string, encode it to UTF-8. - """ - print 'ici' - if self.state <= 0: - return + If supplied data is unicode string, encode it to UTF-8. + """ + print 'ici' + if self.state <= 0: + return - r = packet + r = packet - if isinstance(r, unicode): - r = r.encode('utf-8') - elif not isinstance(r, str): - r = ustr(r).encode('utf-8') + if isinstance(r, unicode): + r = r.encode('utf-8') + elif not isinstance(r, str): + r = ustr(r).encode('utf-8') - if now: - self.sendqueue.insert(0, (r, is_message)) - self._do_send() - else: - self.sendqueue.append((r, is_message)) - self._plug_idle() + if now: + self.sendqueue.insert(0, (r, is_message)) + self._do_send() + else: + self.sendqueue.append((r, is_message)) + self._plug_idle() - def read_timeout(self): - ids = self.client.conn_holder.ids_of_awaiting_messages - if self.fd in ids and len(ids[self.fd]) > 0: - for (id_, thread_id) in ids[self.fd]: - if hasattr(self._owner, 'Dispatcher'): - self._owner.Dispatcher.Event('', DATA_ERROR, (self.client.to, - thread_id)) - else: - self._owner.on_not_ok('conenction timeout') - ids[self.fd] = [] - self.pollend() + def read_timeout(self): + ids = self.client.conn_holder.ids_of_awaiting_messages + if self.fd in ids and len(ids[self.fd]) > 0: + for (id_, thread_id) in ids[self.fd]: + if hasattr(self._owner, 'Dispatcher'): + self._owner.Dispatcher.Event('', DATA_ERROR, (self.client.to, + thread_id)) + else: + self._owner.on_not_ok('conenction timeout') + ids[self.fd] = [] + self.pollend() - def do_connect(self): - errnum = 0 - try: - self._sock.connect(self._server) - self._sock.setblocking(False) - except Exception, ee: - (errnum, errstr) = ee - if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): - return - # win32 needs this - elif errnum not in (0, 10056, errno.EISCONN) or self.state != 0: - log.error('Could not connect to %s: %s [%s]', self.host, errnum, - errstr) - self.connect_to_next_ip() - return - else: # socket is already connected - self._sock.setblocking(False) - self.state = 1 # connected - # we are connected - self.on_connect(self) + def do_connect(self): + errnum = 0 + try: + self._sock.connect(self._server) + self._sock.setblocking(False) + except Exception, ee: + (errnum, errstr) = ee + if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): + return + # win32 needs this + elif errnum not in (0, 10056, errno.EISCONN) or self.state != 0: + log.error('Could not connect to %s: %s [%s]', self.host, errnum, + errstr) + self.connect_to_next_ip() + return + else: # socket is already connected + self._sock.setblocking(False) + self.state = 1 # connected + # we are connected + self.on_connect(self) - def pollout(self): - if self.state == 0: - self.do_connect() - return - gajim.idlequeue.remove_timeout(self.fd) - self._do_send() + def pollout(self): + if self.state == 0: + self.do_connect() + return + gajim.idlequeue.remove_timeout(self.fd) + self._do_send() - def pollend(self): - self.state = -1 - self.disconnect() + def pollend(self): + self.state = -1 + self.disconnect() - def pollin(self): - """ - Reads all pending incoming data. Call owner's disconnected() method if - appropriate - """ - received = '' - errnum = 0 - try: - # get as many bites, as possible, but not more than RECV_BUFSIZE - received = self._sock.recv(MAX_BUFF_LEN) - except Exception, e: - if len(e.args) > 0 and isinstance(e.args[0], int): - errnum = e[0] - # "received" will be empty anyhow - if errnum == socket.SSL_ERROR_WANT_READ: - pass - elif errnum in [errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN]: - self.pollend() - # don't proccess result, cas it will raise error - return - elif not received : - if errnum != socket.SSL_ERROR_EOF: - # 8 EOF occurred in violation of protocol - self.pollend() - if self.state >= 0: - self.disconnect() - return + def pollin(self): + """ + Reads all pending incoming data. Call owner's disconnected() method if + appropriate + """ + received = '' + errnum = 0 + try: + # get as many bites, as possible, but not more than RECV_BUFSIZE + received = self._sock.recv(MAX_BUFF_LEN) + except Exception, e: + if len(e.args) > 0 and isinstance(e.args[0], int): + errnum = e[0] + # "received" will be empty anyhow + if errnum == socket.SSL_ERROR_WANT_READ: + pass + elif errnum in [errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN]: + self.pollend() + # don't proccess result, cas it will raise error + return + elif not received : + if errnum != socket.SSL_ERROR_EOF: + # 8 EOF occurred in violation of protocol + self.pollend() + if self.state >= 0: + self.disconnect() + return - if self.state < 0: - return - if self.on_receive: - if self._owner.sock_type == TYPE_CLIENT: - self.set_timeout(ACTIVITY_TIMEOUT_SECONDS) - if received.strip(): - log.debug('received: %s', received) - if hasattr(self._owner, 'Dispatcher'): - self._owner.Dispatcher.Event('', DATA_RECEIVED, received) - self.on_receive(received) - else: - # This should never happed, so we need the debug - log.error('Unhandled data received: %s' % received) - self.disconnect() - return True + if self.state < 0: + return + if self.on_receive: + if self._owner.sock_type == TYPE_CLIENT: + self.set_timeout(ACTIVITY_TIMEOUT_SECONDS) + if received.strip(): + log.debug('received: %s', received) + if hasattr(self._owner, 'Dispatcher'): + self._owner.Dispatcher.Event('', DATA_RECEIVED, received) + self.on_receive(received) + else: + # This should never happed, so we need the debug + log.error('Unhandled data received: %s' % received) + self.disconnect() + return True - def disconnect(self, message=''): - """ - Close the socket - """ - gajim.idlequeue.remove_timeout(self.fd) - gajim.idlequeue.unplug_idle(self.fd) - try: - self._sock.shutdown(socket.SHUT_RDWR) - self._sock.close() - except socket.error: - # socket is already closed - pass - self.fd = -1 - self.state = -1 - if self._owner: - self._owner.on_disconnect() + def disconnect(self, message=''): + """ + Close the socket + """ + gajim.idlequeue.remove_timeout(self.fd) + gajim.idlequeue.unplug_idle(self.fd) + try: + self._sock.shutdown(socket.SHUT_RDWR) + self._sock.close() + except socket.error: + # socket is already closed + pass + self.fd = -1 + self.state = -1 + if self._owner: + self._owner.on_disconnect() - def _do_send(self): - if not self.sendbuff: - if not self.sendqueue: - return None # nothing to send - self.sendbuff, self.buff_is_message = self.sendqueue.pop(0) - self.sent_data = self.sendbuff - try: - send_count = self._sock.send(self.sendbuff) - if send_count: - self.sendbuff = self.sendbuff[send_count:] - if not self.sendbuff and not self.sendqueue: - if self.state < 0: - gajim.idlequeue.unplug_idle(self.fd) - self._on_send() - self.disconnect() - return - # we are not waiting for write - self._plug_idle() - self._on_send() + def _do_send(self): + if not self.sendbuff: + if not self.sendqueue: + return None # nothing to send + self.sendbuff, self.buff_is_message = self.sendqueue.pop(0) + self.sent_data = self.sendbuff + try: + send_count = self._sock.send(self.sendbuff) + if send_count: + self.sendbuff = self.sendbuff[send_count:] + if not self.sendbuff and not self.sendqueue: + if self.state < 0: + gajim.idlequeue.unplug_idle(self.fd) + self._on_send() + self.disconnect() + return + # we are not waiting for write + self._plug_idle() + self._on_send() - except socket.error, e: - if e[0] == socket.SSL_ERROR_WANT_WRITE: - return True - if self.state < 0: - self.disconnect() - return - self._on_send_failure() - return - if self._owner.sock_type == TYPE_CLIENT: - self.set_timeout(ACTIVITY_TIMEOUT_SECONDS) - return True + except socket.error, e: + if e[0] == socket.SSL_ERROR_WANT_WRITE: + return True + if self.state < 0: + self.disconnect() + return + self._on_send_failure() + return + if self._owner.sock_type == TYPE_CLIENT: + self.set_timeout(ACTIVITY_TIMEOUT_SECONDS) + return True - def _plug_idle(self): - readable = self.state != 0 - if self.sendqueue or self.sendbuff: - writable = True - else: - writable = False - if self.writable != writable or self.readable != readable: - gajim.idlequeue.plug_idle(self, writable, readable) + def _plug_idle(self): + readable = self.state != 0 + if self.sendqueue or self.sendbuff: + writable = True + else: + writable = False + if self.writable != writable or self.readable != readable: + gajim.idlequeue.plug_idle(self, writable, readable) - def _on_send(self): - if self.sent_data and self.sent_data.strip(): - log.debug('sent: %s' % self.sent_data) - if hasattr(self._owner, 'Dispatcher'): - self._owner.Dispatcher.Event('', DATA_SENT, self.sent_data) - self.sent_data = None - if self.buff_is_message: - self._owner.on_message_sent(self.fd) - self.buff_is_message = False + def _on_send(self): + if self.sent_data and self.sent_data.strip(): + log.debug('sent: %s' % self.sent_data) + if hasattr(self._owner, 'Dispatcher'): + self._owner.Dispatcher.Event('', DATA_SENT, self.sent_data) + self.sent_data = None + if self.buff_is_message: + self._owner.on_message_sent(self.fd) + self.buff_is_message = False - def _on_send_failure(self): - log.error('Socket error while sending data') - self._owner.on_disconnect() - self.sent_data = None + def _on_send_failure(self): + log.error('Socket error while sending data') + self._owner.on_disconnect() + self.sent_data = None class ClientZeroconf: - def __init__(self, caller): - self.caller = caller - self.zeroconf = None - self.roster = None - self.last_msg = '' - self.connections = {} - self.recipient_to_hash = {} - self.ip_to_hash = {} - self.hash_to_port = {} - self.listener = None - self.ids_of_awaiting_messages = {} - self.disconnect_handlers = [] - self.disconnecting = False + def __init__(self, caller): + self.caller = caller + self.zeroconf = None + self.roster = None + self.last_msg = '' + self.connections = {} + self.recipient_to_hash = {} + self.ip_to_hash = {} + self.hash_to_port = {} + self.listener = None + self.ids_of_awaiting_messages = {} + self.disconnect_handlers = [] + self.disconnecting = False - def connect(self, show, msg): - self.port = self.start_listener(self.caller.port) - if not self.port: - return False - self.zeroconf_init(show, msg) - if not self.zeroconf.connect(): - self.disconnect() - return None - self.roster = roster_zeroconf.Roster(self.zeroconf) - return True + def connect(self, show, msg): + self.port = self.start_listener(self.caller.port) + if not self.port: + return False + self.zeroconf_init(show, msg) + if not self.zeroconf.connect(): + self.disconnect() + return None + self.roster = roster_zeroconf.Roster(self.zeroconf) + return True - def remove_announce(self): - if self.zeroconf: - return self.zeroconf.remove_announce() + def remove_announce(self): + if self.zeroconf: + return self.zeroconf.remove_announce() - def announce(self): - if self.zeroconf: - return self.zeroconf.announce() + def announce(self): + if self.zeroconf: + return self.zeroconf.announce() - def set_show_msg(self, show, msg): - if self.zeroconf: - self.zeroconf.txt['msg'] = msg - self.last_msg = msg - return self.zeroconf.update_txt(show) + def set_show_msg(self, show, msg): + if self.zeroconf: + self.zeroconf.txt['msg'] = msg + self.last_msg = msg + return self.zeroconf.update_txt(show) - def resolve_all(self): - if self.zeroconf: - self.zeroconf.resolve_all() + def resolve_all(self): + if self.zeroconf: + self.zeroconf.resolve_all() - def reannounce(self, txt): - self.remove_announce() - self.zeroconf.txt = txt - self.zeroconf.port = self.port - self.zeroconf.username = self.caller.username - return self.announce() + def reannounce(self, txt): + self.remove_announce() + self.zeroconf.txt = txt + self.zeroconf.port = self.port + self.zeroconf.username = self.caller.username + return self.announce() - def zeroconf_init(self, show, msg): - self.zeroconf = zeroconf.Zeroconf(self.caller._on_new_service, - self.caller._on_remove_service, self.caller._on_name_conflictCB, - self.caller._on_disconnected, self.caller._on_error, - self.caller.username, self.caller.host, self.port) - self.zeroconf.txt['msg'] = msg - self.zeroconf.txt['status'] = show - self.zeroconf.txt['1st'] = self.caller.first - self.zeroconf.txt['last'] = self.caller.last - self.zeroconf.txt['jid'] = self.caller.jabber_id - self.zeroconf.txt['email'] = self.caller.email - self.zeroconf.username = self.caller.username - self.zeroconf.host = self.caller.host - self.zeroconf.port = self.port - self.last_msg = msg + def zeroconf_init(self, show, msg): + self.zeroconf = zeroconf.Zeroconf(self.caller._on_new_service, + self.caller._on_remove_service, self.caller._on_name_conflictCB, + self.caller._on_disconnected, self.caller._on_error, + self.caller.username, self.caller.host, self.port) + self.zeroconf.txt['msg'] = msg + self.zeroconf.txt['status'] = show + self.zeroconf.txt['1st'] = self.caller.first + self.zeroconf.txt['last'] = self.caller.last + self.zeroconf.txt['jid'] = self.caller.jabber_id + self.zeroconf.txt['email'] = self.caller.email + self.zeroconf.username = self.caller.username + self.zeroconf.host = self.caller.host + self.zeroconf.port = self.port + self.last_msg = msg - def disconnect(self): - # to avoid recursive calls - if self.disconnecting: - return - if self.listener: - self.listener.disconnect() - self.listener = None - if self.zeroconf: - self.zeroconf.disconnect() - self.zeroconf = None - if self.roster: - self.roster.zeroconf = None - self.roster._data = None - self.roster = None - self.disconnecting = True - for i in reversed(self.disconnect_handlers): - log.debug('Calling disconnect handler %s' % i) - i() - self.disconnecting = False + def disconnect(self): + # to avoid recursive calls + if self.disconnecting: + return + if self.listener: + self.listener.disconnect() + self.listener = None + if self.zeroconf: + self.zeroconf.disconnect() + self.zeroconf = None + if self.roster: + self.roster.zeroconf = None + self.roster._data = None + self.roster = None + self.disconnecting = True + for i in reversed(self.disconnect_handlers): + log.debug('Calling disconnect handler %s' % i) + i() + self.disconnecting = False - def start_disconnect(self): - self.disconnect() + def start_disconnect(self): + self.disconnect() - def kill_all_connections(self): - for connection in self.connections.values(): - connection.force_disconnect() + def kill_all_connections(self): + for connection in self.connections.values(): + connection.force_disconnect() - def add_connection(self, connection, ip, port, recipient): - sock_hash=connection.sock_hash - if sock_hash not in self.connections: - self.connections[sock_hash] = connection - self.ip_to_hash[ip] = sock_hash - self.hash_to_port[sock_hash] = port - if recipient: - self.recipient_to_hash[recipient] = sock_hash + def add_connection(self, connection, ip, port, recipient): + sock_hash=connection.sock_hash + if sock_hash not in self.connections: + self.connections[sock_hash] = connection + self.ip_to_hash[ip] = sock_hash + self.hash_to_port[sock_hash] = port + if recipient: + self.recipient_to_hash[recipient] = sock_hash - def remove_connection(self, sock_hash): - if sock_hash in self.connections: - del self.connections[sock_hash] - for i in self.recipient_to_hash: - if self.recipient_to_hash[i] == sock_hash: - del self.recipient_to_hash[i] - break - for i in self.ip_to_hash: - if self.ip_to_hash[i] == sock_hash: - del self.ip_to_hash[i] - break - if self.hash_to_port.has_key(sock_hash): - del self.hash_to_port[sock_hash] + def remove_connection(self, sock_hash): + if sock_hash in self.connections: + del self.connections[sock_hash] + for i in self.recipient_to_hash: + if self.recipient_to_hash[i] == sock_hash: + del self.recipient_to_hash[i] + break + for i in self.ip_to_hash: + if self.ip_to_hash[i] == sock_hash: + del self.ip_to_hash[i] + break + if self.hash_to_port.has_key(sock_hash): + del self.hash_to_port[sock_hash] - def start_listener(self, port): - for p in range(port, port + 5): - self.listener = ZeroconfListener(p, self) - self.listener.bind() - if self.listener.started: - return p - self.listener = None - return False + def start_listener(self, port): + for p in range(port, port + 5): + self.listener = ZeroconfListener(p, self) + self.listener.bind() + if self.listener.started: + return p + self.listener = None + return False - def getRoster(self): - if self.roster: - return self.roster.getRoster() - return {} + def getRoster(self): + if self.roster: + return self.roster.getRoster() + return {} - def send(self, stanza, is_message=False, now=False, on_ok=None, - on_not_ok=None): - stanza.setFrom(self.roster.zeroconf.name) - to = stanza.getTo() + def send(self, stanza, is_message=False, now=False, on_ok=None, + on_not_ok=None): + stanza.setFrom(self.roster.zeroconf.name) + to = stanza.getTo() - try: - item = self.roster[to] - except KeyError: - # Contact offline - return -1 + try: + item = self.roster[to] + except KeyError: + # Contact offline + return -1 - # look for hashed connections - if to in self.recipient_to_hash: - conn = self.connections[self.recipient_to_hash[to]] - id_ = stanza.getID() or '' - if conn.add_stanza(stanza, is_message): - if on_ok: - on_ok(id_) - return + # look for hashed connections + if to in self.recipient_to_hash: + conn = self.connections[self.recipient_to_hash[to]] + id_ = stanza.getID() or '' + if conn.add_stanza(stanza, is_message): + if on_ok: + on_ok(id_) + return - if item['address'] in self.ip_to_hash: - hash_ = self.ip_to_hash[item['address']] - if self.hash_to_port[hash_] == item['port']: - conn = self.connections[hash_] - id_ = stanza.getID() or '' - if conn.add_stanza(stanza, is_message): - if on_ok: - on_ok(id_) - return + if item['address'] in self.ip_to_hash: + hash_ = self.ip_to_hash[item['address']] + if self.hash_to_port[hash_] == item['port']: + conn = self.connections[hash_] + id_ = stanza.getID() or '' + if conn.add_stanza(stanza, is_message): + if on_ok: + on_ok(id_) + return - # otherwise open new connection - if not stanza.getID(): - stanza.setID('zero') - P2PClient(None, item['address'], item['port'], self, - [(stanza, is_message)], to, on_ok=on_ok, on_not_ok=on_not_ok) + # otherwise open new connection + if not stanza.getID(): + stanza.setID('zero') + P2PClient(None, item['address'], item['port'], self, + [(stanza, is_message)], to, on_ok=on_ok, on_not_ok=on_not_ok) - def RegisterDisconnectHandler(self, handler): - """ - Register handler that will be called on disconnect - """ - self.disconnect_handlers.append(handler) + def RegisterDisconnectHandler(self, handler): + """ + Register handler that will be called on disconnect + """ + self.disconnect_handlers.append(handler) - def UnregisterDisconnectHandler(self, handler): - """ - Unregister handler that is called on disconnect - """ - self.disconnect_handlers.remove(handler) + def UnregisterDisconnectHandler(self, handler): + """ + Unregister handler that is called on disconnect + """ + self.disconnect_handlers.remove(handler) - def SendAndWaitForResponse(self, stanza, timeout=None, func=None, args=None): - """ - Send stanza and wait for recipient's response to it. Will call transports - on_timeout callback if response is not retrieved in time + def SendAndWaitForResponse(self, stanza, timeout=None, func=None, args=None): + """ + Send stanza and wait for recipient's response to it. Will call transports + on_timeout callback if response is not retrieved in time - Be aware: Only timeout of latest call of SendAndWait is active. - """ -# if timeout is None: -# timeout = DEFAULT_TIMEOUT_SECONDS - def on_ok(_waitid): -# if timeout: -# self._owner.set_timeout(timeout) - to = stanza.getTo() - conn = None - if to in self.recipient_to_hash: - conn = self.connections[self.recipient_to_hash[to]] - elif item['address'] in self.ip_to_hash: - hash_ = self.ip_to_hash[item['address']] - if self.hash_to_port[hash_] == item['port']: - conn = self.connections[hash_] - if func: - conn.Dispatcher.on_responses[_waitid] = (func, args) - conn.onreceive(conn.Dispatcher._WaitForData) - conn.Dispatcher._expected[_waitid] = None - self.send(stanza, on_ok=on_ok) + Be aware: Only timeout of latest call of SendAndWait is active. + """ +# if timeout is None: +# timeout = DEFAULT_TIMEOUT_SECONDS + def on_ok(_waitid): +# if timeout: +# self._owner.set_timeout(timeout) + to = stanza.getTo() + conn = None + if to in self.recipient_to_hash: + conn = self.connections[self.recipient_to_hash[to]] + elif item['address'] in self.ip_to_hash: + hash_ = self.ip_to_hash[item['address']] + if self.hash_to_port[hash_] == item['port']: + conn = self.connections[hash_] + if func: + conn.Dispatcher.on_responses[_waitid] = (func, args) + conn.onreceive(conn.Dispatcher._WaitForData) + conn.Dispatcher._expected[_waitid] = None + self.send(stanza, on_ok=on_ok) - def SendAndCallForResponse(self, stanza, func=None, args=None): - """ - Put stanza on the wire and call back when recipient replies. Additional - callback arguments can be specified in args. - """ - self.SendAndWaitForResponse(stanza, 0, func, args) - -# vim: se ts=3: + def SendAndCallForResponse(self, stanza, func=None, args=None): + """ + Put stanza on the wire and call back when recipient replies. Additional + callback arguments can be specified in args. + """ + self.SendAndWaitForResponse(stanza, 0, func, args) diff --git a/src/common/zeroconf/connection_handlers_zeroconf.py b/src/common/zeroconf/connection_handlers_zeroconf.py index 0d45379c3..9d9f8356a 100644 --- a/src/common/zeroconf/connection_handlers_zeroconf.py +++ b/src/common/zeroconf/connection_handlers_zeroconf.py @@ -2,10 +2,10 @@ ## Copyright (C) 2006 Gajim Team ## ## Contributors for this file: -## - Yann Leboulanger -## - Nikos Kouremenos -## - Dimitur Kirov -## - Travis Shirk +## - Yann Leboulanger +## - Nikos Kouremenos +## - Dimitur Kirov +## - Travis Shirk ## - Stefan Bethge ## ## This file is part of Gajim. @@ -41,153 +41,151 @@ import logging log = logging.getLogger('gajim.c.z.connection_handlers_zeroconf') STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd', - 'invisible'] + 'invisible'] # kind of events we can wait for an answer VCARD_PUBLISHED = 'vcard_published' VCARD_ARRIVED = 'vcard_arrived' AGENT_REMOVED = 'agent_removed' HAS_IDLE = True try: - import idle + import idle except Exception: - log.debug(_('Unable to load idle module')) - HAS_IDLE = False + log.debug(_('Unable to load idle module')) + HAS_IDLE = False from common import connection_handlers from session import ChatControlSession class ConnectionVcard(connection_handlers.ConnectionVcard): - def add_sha(self, p, send_caps = True): - return p + def add_sha(self, p, send_caps = True): + return p - def add_caps(self, p): - return p + def add_caps(self, p): + return p - def request_vcard(self, jid = None, is_fake_jid = False): - pass + def request_vcard(self, jid = None, is_fake_jid = False): + pass - def send_vcard(self, vcard): - pass + def send_vcard(self, vcard): + pass class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestreamZeroconf, ConnectionCommands, ConnectionPEP, connection_handlers.ConnectionHandlersBase): - def __init__(self): - ConnectionVcard.__init__(self) - ConnectionBytestreamZeroconf.__init__(self) - ConnectionCommands.__init__(self) - connection_handlers.ConnectionHandlersBase.__init__(self) + def __init__(self): + ConnectionVcard.__init__(self) + ConnectionBytestreamZeroconf.__init__(self) + ConnectionCommands.__init__(self) + connection_handlers.ConnectionHandlersBase.__init__(self) - try: - idle.init() - except Exception: - global HAS_IDLE - HAS_IDLE = False + try: + idle.init() + except Exception: + global HAS_IDLE + HAS_IDLE = False - def _messageCB(self, ip, con, msg): - """ - Called when we receive a message - """ - log.debug('Zeroconf MessageCB') + def _messageCB(self, ip, con, msg): + """ + Called when we receive a message + """ + log.debug('Zeroconf MessageCB') - frm = msg.getFrom() - mtype = msg.getType() - thread_id = msg.getThread() + frm = msg.getFrom() + mtype = msg.getType() + thread_id = msg.getThread() - if not mtype: - mtype = 'normal' + if not mtype: + mtype = 'normal' - if frm is None: - for key in self.connection.zeroconf.contacts: - if ip == self.connection.zeroconf.contacts[key][zeroconf.C_ADDRESS]: - frm = key + if frm is None: + for key in self.connection.zeroconf.contacts: + if ip == self.connection.zeroconf.contacts[key][zeroconf.C_ADDRESS]: + frm = key - frm = unicode(frm) + frm = unicode(frm) - session = self.get_or_create_session(frm, thread_id) + session = self.get_or_create_session(frm, thread_id) - if thread_id and not session.received_thread_id: - session.received_thread_id = True + if thread_id and not session.received_thread_id: + session.received_thread_id = True - if msg.getTag('feature') and msg.getTag('feature').namespace == \ - common.xmpp.NS_FEATURE: - if gajim.HAVE_PYCRYPTO: - self._FeatureNegCB(con, msg, session) - return + if msg.getTag('feature') and msg.getTag('feature').namespace == \ + common.xmpp.NS_FEATURE: + if gajim.HAVE_PYCRYPTO: + self._FeatureNegCB(con, msg, session) + return - if msg.getTag('init') and msg.getTag('init').namespace == \ - common.xmpp.NS_ESESSION_INIT: - self._InitE2ECB(con, msg, session) + if msg.getTag('init') and msg.getTag('init').namespace == \ + common.xmpp.NS_ESESSION_INIT: + self._InitE2ECB(con, msg, session) - encrypted = False - tim = msg.getTimestamp() - tim = helpers.datetime_tuple(tim) - tim = time.localtime(timegm(tim)) + encrypted = False + tim = msg.getTimestamp() + tim = helpers.datetime_tuple(tim) + tim = time.localtime(timegm(tim)) - if msg.getTag('c', namespace = common.xmpp.NS_STANZA_CRYPTO): - encrypted = True + if msg.getTag('c', namespace = common.xmpp.NS_STANZA_CRYPTO): + encrypted = True - try: - msg = session.decrypt_stanza(msg) - except Exception: - self.dispatch('FAILED_DECRYPT', (frm, tim)) + try: + msg = session.decrypt_stanza(msg) + except Exception: + self.dispatch('FAILED_DECRYPT', (frm, tim)) - msgtxt = msg.getBody() - subject = msg.getSubject() # if not there, it's None + msgtxt = msg.getBody() + subject = msg.getSubject() # if not there, it's None - # invitations - invite = None - encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED) + # invitations + invite = None + encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED) - if not encTag: - invite = msg.getTag('x', namespace = common.xmpp.NS_MUC_USER) - if invite and not invite.getTag('invite'): - invite = None + if not encTag: + invite = msg.getTag('x', namespace = common.xmpp.NS_MUC_USER) + if invite and not invite.getTag('invite'): + invite = None - if encTag and self.USE_GPG: - #decrypt - encmsg = encTag.getData() + if encTag and self.USE_GPG: + #decrypt + encmsg = encTag.getData() - keyID = gajim.config.get_per('accounts', self.name, 'keyid') - if keyID: - decmsg = self.gpg.decrypt(encmsg, keyID) - # \x00 chars are not allowed in C (so in GTK) - msgtxt = decmsg.replace('\x00', '') - encrypted = True + keyID = gajim.config.get_per('accounts', self.name, 'keyid') + if keyID: + decmsg = self.gpg.decrypt(encmsg, keyID) + # \x00 chars are not allowed in C (so in GTK) + msgtxt = decmsg.replace('\x00', '') + encrypted = True - if mtype == 'error': - self.dispatch_error_msg(msg, msgtxt, session, frm, tim, subject) - else: - # XXX this shouldn't be hardcoded - if isinstance(session, ChatControlSession): - session.received(frm, msgtxt, tim, encrypted, msg) - else: - session.received(msg) - # END messageCB + if mtype == 'error': + self.dispatch_error_msg(msg, msgtxt, session, frm, tim, subject) + else: + # XXX this shouldn't be hardcoded + if isinstance(session, ChatControlSession): + session.received(frm, msgtxt, tim, encrypted, msg) + else: + session.received(msg) + # END messageCB - def store_metacontacts(self, tags): - """ - Fake empty method - """ - # serverside metacontacts are not supported with zeroconf - # (there is no server) - pass + def store_metacontacts(self, tags): + """ + Fake empty method + """ + # serverside metacontacts are not supported with zeroconf + # (there is no server) + pass - def _DiscoverItemsGetCB(self, con, iq_obj): - log.debug('DiscoverItemsGetCB') + def _DiscoverItemsGetCB(self, con, iq_obj): + log.debug('DiscoverItemsGetCB') - if not self.connection or self.connected < 2: - return + if not self.connection or self.connected < 2: + return - if self.commandItemsQuery(con, iq_obj): - raise common.xmpp.NodeProcessed - node = iq_obj.getTagAttr('query', 'node') - if node is None: - result = iq_obj.buildReply('result') - self.connection.send(result) - raise common.xmpp.NodeProcessed - if node==common.xmpp.NS_COMMANDS: - self.commandListQuery(con, iq_obj) - raise common.xmpp.NodeProcessed - -# vim: se ts=3: + if self.commandItemsQuery(con, iq_obj): + raise common.xmpp.NodeProcessed + node = iq_obj.getTagAttr('query', 'node') + if node is None: + result = iq_obj.buildReply('result') + self.connection.send(result) + raise common.xmpp.NodeProcessed + if node==common.xmpp.NS_COMMANDS: + self.commandListQuery(con, iq_obj) + raise common.xmpp.NodeProcessed diff --git a/src/common/zeroconf/connection_zeroconf.py b/src/common/zeroconf/connection_zeroconf.py index 2059e9b82..6076f8b13 100644 --- a/src/common/zeroconf/connection_zeroconf.py +++ b/src/common/zeroconf/connection_zeroconf.py @@ -1,10 +1,10 @@ -## common/zeroconf/connection_zeroconf.py +## common/zeroconf/connection_zeroconf.py ## ## Contributors for this file: -## - Yann Leboulanger -## - Nikos Kouremenos -## - Dimitur Kirov -## - Travis Shirk +## - Yann Leboulanger +## - Nikos Kouremenos +## - Dimitur Kirov +## - Travis Shirk ## - Stefan Bethge ## ## Copyright (C) 2003-2004 Yann Leboulanger @@ -39,7 +39,7 @@ random.seed() import signal if os.name != 'nt': - signal.signal(signal.SIGPIPE, signal.SIG_DFL) + signal.signal(signal.SIGPIPE, signal.SIG_DFL) import getpass import gobject @@ -51,316 +51,314 @@ from common.zeroconf import zeroconf from connection_handlers_zeroconf import * class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf): - def __init__(self, name): - ConnectionHandlersZeroconf.__init__(self) - # system username - self.username = None - self.server_resource = '' # zeroconf has no resource, fake an empty one - self.call_resolve_timeout = False - # we don't need a password, but must be non-empty - self.password = 'zeroconf' - self.autoconnect = False + def __init__(self, name): + ConnectionHandlersZeroconf.__init__(self) + # system username + self.username = None + self.server_resource = '' # zeroconf has no resource, fake an empty one + self.call_resolve_timeout = False + # we don't need a password, but must be non-empty + self.password = 'zeroconf' + self.autoconnect = False - CommonConnection.__init__(self, name) - self.is_zeroconf = True + CommonConnection.__init__(self, name) + self.is_zeroconf = True - def get_config_values_or_default(self): - """ - Get name, host, port from config, or create zeroconf account with default - values - """ - if not gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name'): - gajim.log.debug('Creating zeroconf account') - gajim.config.add_per('accounts', gajim.ZEROCONF_ACC_NAME) - gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'autoconnect', True) - gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'no_log_for', - '') - gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'password', - 'zeroconf') - gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'sync_with_global_status', True) + def get_config_values_or_default(self): + """ + Get name, host, port from config, or create zeroconf account with default + values + """ + if not gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name'): + gajim.log.debug('Creating zeroconf account') + gajim.config.add_per('accounts', gajim.ZEROCONF_ACC_NAME) + gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'autoconnect', True) + gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'no_log_for', + '') + gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'password', + 'zeroconf') + gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'sync_with_global_status', True) - gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'custom_port', 5298) - gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'is_zeroconf', True) - gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'use_ft_proxies', False) - #XXX make sure host is US-ASCII - self.host = unicode(socket.gethostname()) - gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'hostname', - self.host) - self.port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'custom_port') - self.autoconnect = gajim.config.get_per('accounts', - gajim.ZEROCONF_ACC_NAME, 'autoconnect') - self.sync_with_global_status = gajim.config.get_per('accounts', - gajim.ZEROCONF_ACC_NAME, 'sync_with_global_status') - self.first = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'zeroconf_first_name') - self.last = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'zeroconf_last_name') - self.jabber_id = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'zeroconf_jabber_id') - self.email = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'zeroconf_email') + gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'custom_port', 5298) + gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'is_zeroconf', True) + gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'use_ft_proxies', False) + #XXX make sure host is US-ASCII + self.host = unicode(socket.gethostname()) + gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'hostname', + self.host) + self.port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'custom_port') + self.autoconnect = gajim.config.get_per('accounts', + gajim.ZEROCONF_ACC_NAME, 'autoconnect') + self.sync_with_global_status = gajim.config.get_per('accounts', + gajim.ZEROCONF_ACC_NAME, 'sync_with_global_status') + self.first = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'zeroconf_first_name') + self.last = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'zeroconf_last_name') + self.jabber_id = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'zeroconf_jabber_id') + self.email = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'zeroconf_email') - if not self.username: - self.username = unicode(getpass.getuser()) - gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name', - self.username) - else: - self.username = gajim.config.get_per('accounts', - gajim.ZEROCONF_ACC_NAME, 'name') - # END __init__ + if not self.username: + self.username = unicode(getpass.getuser()) + gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name', + self.username) + else: + self.username = gajim.config.get_per('accounts', + gajim.ZEROCONF_ACC_NAME, 'name') + # END __init__ - def check_jid(self, jid): - return jid + def check_jid(self, jid): + return jid - def _reconnect(self): - # Do not try to reco while we are already trying - self.time_to_reconnect = None - gajim.log.debug('reconnect') + def _reconnect(self): + # Do not try to reco while we are already trying + self.time_to_reconnect = None + gajim.log.debug('reconnect') - self.disconnect() - self.change_status(self.old_show, self.status) + self.disconnect() + self.change_status(self.old_show, self.status) - def disable_account(self): - self.disconnect() + def disable_account(self): + self.disconnect() - def _on_resolve_timeout(self): - if self.connected: - self.connection.resolve_all() - diffs = self.roster.getDiffs() - for key in diffs: - self.roster.setItem(key) - self.dispatch('ROSTER_INFO', (key, self.roster.getName(key), - 'both', 'no', self.roster.getGroups(key))) - self.dispatch('NOTIFY', (key, self.roster.getStatus(key), - self.roster.getMessage(key), 'local', 0, None, 0, None)) - #XXX open chat windows don't get refreshed (full name), add that - return self.call_resolve_timeout + def _on_resolve_timeout(self): + if self.connected: + self.connection.resolve_all() + diffs = self.roster.getDiffs() + for key in diffs: + self.roster.setItem(key) + self.dispatch('ROSTER_INFO', (key, self.roster.getName(key), + 'both', 'no', self.roster.getGroups(key))) + self.dispatch('NOTIFY', (key, self.roster.getStatus(key), + self.roster.getMessage(key), 'local', 0, None, 0, None)) + #XXX open chat windows don't get refreshed (full name), add that + return self.call_resolve_timeout - # callbacks called from zeroconf - def _on_new_service(self, jid): - self.roster.setItem(jid) - self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', 'no', - self.roster.getGroups(jid))) - self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid), - self.roster.getMessage(jid), 'local', 0, None, 0, None)) + # callbacks called from zeroconf + def _on_new_service(self, jid): + self.roster.setItem(jid) + self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', 'no', + self.roster.getGroups(jid))) + self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid), + self.roster.getMessage(jid), 'local', 0, None, 0, None)) - def _on_remove_service(self, jid): - self.roster.delItem(jid) - # 'NOTIFY' (account, (jid, status, status message, resource, priority, - # keyID, timestamp, contact_nickname)) - self.dispatch('NOTIFY', (jid, 'offline', '', 'local', 0, None, 0, None)) + def _on_remove_service(self, jid): + self.roster.delItem(jid) + # 'NOTIFY' (account, (jid, status, status message, resource, priority, + # keyID, timestamp, contact_nickname)) + self.dispatch('NOTIFY', (jid, 'offline', '', 'local', 0, None, 0, None)) - def _disconnectedReconnCB(self): - """ - Called when we are disconnected. Comes from network manager for example - we don't try to reconnect, network manager will tell us when we can - """ - if gajim.account_is_connected(self.name): - # we cannot change our status to offline or connecting - # after we auth to server - self.old_show = STATUS_LIST[self.connected] - self.connected = 0 - self.dispatch('STATUS', 'offline') - # random number to show we wait network manager to send us a reconenct - self.time_to_reconnect = 5 - self.on_purpose = False + def _disconnectedReconnCB(self): + """ + Called when we are disconnected. Comes from network manager for example + we don't try to reconnect, network manager will tell us when we can + """ + if gajim.account_is_connected(self.name): + # we cannot change our status to offline or connecting + # after we auth to server + self.old_show = STATUS_LIST[self.connected] + self.connected = 0 + self.dispatch('STATUS', 'offline') + # random number to show we wait network manager to send us a reconenct + self.time_to_reconnect = 5 + self.on_purpose = False - def _on_name_conflictCB(self, alt_name): - self.disconnect() - self.dispatch('STATUS', 'offline') - self.dispatch('ZC_NAME_CONFLICT', alt_name) + def _on_name_conflictCB(self, alt_name): + self.disconnect() + self.dispatch('STATUS', 'offline') + self.dispatch('ZC_NAME_CONFLICT', alt_name) - def _on_error(self, message): - self.dispatch('ERROR', (_('Avahi error'), - _('%s\nLink-local messaging might not work properly.') % message)) + def _on_error(self, message): + self.dispatch('ERROR', (_('Avahi error'), + _('%s\nLink-local messaging might not work properly.') % message)) - def connect(self, show='online', msg=''): - self.get_config_values_or_default() - if not self.connection: - self.connection = client_zeroconf.ClientZeroconf(self) - if not zeroconf.test_zeroconf(): - self.dispatch('STATUS', 'offline') - self.status = 'offline' - self.dispatch('CONNECTION_LOST', - (_('Could not connect to "%s"') % self.name, - _('Please check if Avahi or Bonjour is installed.'))) - self.disconnect() - return - result = self.connection.connect(show, msg) - if not result: - self.dispatch('STATUS', 'offline') - self.status = 'offline' - if result is False: - self.dispatch('CONNECTION_LOST', - (_('Could not start local service'), - _('Unable to bind to port %d.' % self.port))) - else: # result is None - self.dispatch('CONNECTION_LOST', - (_('Could not start local service'), - _('Please check if avahi-daemon is running.'))) - self.disconnect() - return - else: - self.connection.announce() - self.roster = self.connection.getRoster() - self.dispatch('ROSTER', self.roster) + def connect(self, show='online', msg=''): + self.get_config_values_or_default() + if not self.connection: + self.connection = client_zeroconf.ClientZeroconf(self) + if not zeroconf.test_zeroconf(): + self.dispatch('STATUS', 'offline') + self.status = 'offline' + self.dispatch('CONNECTION_LOST', + (_('Could not connect to "%s"') % self.name, + _('Please check if Avahi or Bonjour is installed.'))) + self.disconnect() + return + result = self.connection.connect(show, msg) + if not result: + self.dispatch('STATUS', 'offline') + self.status = 'offline' + if result is False: + self.dispatch('CONNECTION_LOST', + (_('Could not start local service'), + _('Unable to bind to port %d.' % self.port))) + else: # result is None + self.dispatch('CONNECTION_LOST', + (_('Could not start local service'), + _('Please check if avahi-daemon is running.'))) + self.disconnect() + return + else: + self.connection.announce() + self.roster = self.connection.getRoster() + self.dispatch('ROSTER', self.roster) - # display contacts already detected and resolved - for jid in self.roster.keys(): - self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', - 'no', self.roster.getGroups(jid))) - self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid), - self.roster.getMessage(jid), 'local', 0, None, 0, None)) + # display contacts already detected and resolved + for jid in self.roster.keys(): + self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', + 'no', self.roster.getGroups(jid))) + self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid), + self.roster.getMessage(jid), 'local', 0, None, 0, None)) - self.connected = STATUS_LIST.index(show) + self.connected = STATUS_LIST.index(show) - # refresh all contacts data every five seconds - self.call_resolve_timeout = True - gobject.timeout_add_seconds(5, self._on_resolve_timeout) - return True + # refresh all contacts data every five seconds + self.call_resolve_timeout = True + gobject.timeout_add_seconds(5, self._on_resolve_timeout) + return True - def disconnect(self, on_purpose=False): - self.connected = 0 - self.time_to_reconnect = None - if self.connection: - self.connection.disconnect() - self.connection = None - # stop calling the timeout - self.call_resolve_timeout = False + def disconnect(self, on_purpose=False): + self.connected = 0 + self.time_to_reconnect = None + if self.connection: + self.connection.disconnect() + self.connection = None + # stop calling the timeout + self.call_resolve_timeout = False - def reannounce(self): - if self.connected: - txt = {} - txt['1st'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'zeroconf_first_name') - txt['last'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'zeroconf_last_name') - txt['jid'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'zeroconf_jabber_id') - txt['email'] = gajim.config.get_per('accounts', - gajim.ZEROCONF_ACC_NAME, 'zeroconf_email') - self.connection.reannounce(txt) + def reannounce(self): + if self.connected: + txt = {} + txt['1st'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'zeroconf_first_name') + txt['last'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'zeroconf_last_name') + txt['jid'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'zeroconf_jabber_id') + txt['email'] = gajim.config.get_per('accounts', + gajim.ZEROCONF_ACC_NAME, 'zeroconf_email') + self.connection.reannounce(txt) - def update_details(self): - if self.connection: - port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'custom_port') - if port != self.port: - self.port = port - last_msg = self.connection.last_msg - self.disconnect() - if not self.connect(self.status, last_msg): - return - if self.status != 'invisible': - self.connection.announce() - else: - self.reannounce() + def update_details(self): + if self.connection: + port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'custom_port') + if port != self.port: + self.port = port + last_msg = self.connection.last_msg + self.disconnect() + if not self.connect(self.status, last_msg): + return + if self.status != 'invisible': + self.connection.announce() + else: + self.reannounce() - def connect_and_init(self, show, msg, sign_msg): - # to check for errors from zeroconf - check = True - if not self.connect(show, msg): - return - if show != 'invisible': - check = self.connection.announce() - else: - self.connected = STATUS_LIST.index(show) - self.dispatch('SIGNED_IN', ()) + def connect_and_init(self, show, msg, sign_msg): + # to check for errors from zeroconf + check = True + if not self.connect(show, msg): + return + if show != 'invisible': + check = self.connection.announce() + else: + self.connected = STATUS_LIST.index(show) + self.dispatch('SIGNED_IN', ()) - # stay offline when zeroconf does something wrong - if check: - self.dispatch('STATUS', show) - else: - # show notification that avahi or system bus is down - self.dispatch('STATUS', 'offline') - self.status = 'offline' - self.dispatch('CONNECTION_LOST', - (_('Could not change status of account "%s"') % self.name, - _('Please check if avahi-daemon is running.'))) + # stay offline when zeroconf does something wrong + if check: + self.dispatch('STATUS', show) + else: + # show notification that avahi or system bus is down + self.dispatch('STATUS', 'offline') + self.status = 'offline' + self.dispatch('CONNECTION_LOST', + (_('Could not change status of account "%s"') % self.name, + _('Please check if avahi-daemon is running.'))) - def _change_to_invisible(self, msg): - if self.connection.remove_announce(): - self.dispatch('STATUS', 'invisible') - else: - # show notification that avahi or system bus is down - self.dispatch('STATUS', 'offline') - self.status = 'offline' - self.dispatch('CONNECTION_LOST', - (_('Could not change status of account "%s"') % self.name, - _('Please check if avahi-daemon is running.'))) + def _change_to_invisible(self, msg): + if self.connection.remove_announce(): + self.dispatch('STATUS', 'invisible') + else: + # show notification that avahi or system bus is down + self.dispatch('STATUS', 'offline') + self.status = 'offline' + self.dispatch('CONNECTION_LOST', + (_('Could not change status of account "%s"') % self.name, + _('Please check if avahi-daemon is running.'))) - def _change_from_invisible(self): - self.connection.announce() + def _change_from_invisible(self): + self.connection.announce() - def _update_status(self, show, msg): - if self.connection.set_show_msg(show, msg): - self.dispatch('STATUS', show) - else: - # show notification that avahi or system bus is down - self.dispatch('STATUS', 'offline') - self.status = 'offline' - self.dispatch('CONNECTION_LOST', - (_('Could not change status of account "%s"') % self.name, - _('Please check if avahi-daemon is running.'))) + def _update_status(self, show, msg): + if self.connection.set_show_msg(show, msg): + self.dispatch('STATUS', show) + else: + # show notification that avahi or system bus is down + self.dispatch('STATUS', 'offline') + self.status = 'offline' + self.dispatch('CONNECTION_LOST', + (_('Could not change status of account "%s"') % self.name, + _('Please check if avahi-daemon is running.'))) - def send_message(self, jid, msg, keyID, type_='chat', subject='', - chatstate=None, msg_id=None, composing_xep=None, resource=None, - user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None, - original_message=None, delayed=None, callback=None, callback_args=[]): + def send_message(self, jid, msg, keyID, type_='chat', subject='', + chatstate=None, msg_id=None, composing_xep=None, resource=None, + user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None, + original_message=None, delayed=None, callback=None, callback_args=[]): - def on_send_ok(msg_id): - self.dispatch('MSGSENT', (jid, msg, keyID)) - if callback: - callback(msg_id, *callback_args) + def on_send_ok(msg_id): + self.dispatch('MSGSENT', (jid, msg, keyID)) + if callback: + callback(msg_id, *callback_args) - self.log_message(jid, msg, forward_from, session, original_message, - subject, type_) + self.log_message(jid, msg, forward_from, session, original_message, + subject, type_) - def on_send_not_ok(reason): - reason += ' ' + _('Your message could not be sent.') - self.dispatch('MSGERROR', [jid, -1, reason, None, None, session]) + def on_send_not_ok(reason): + reason += ' ' + _('Your message could not be sent.') + self.dispatch('MSGERROR', [jid, -1, reason, None, None, session]) - def cb(jid, msg, keyID, forward_from, session, original_message, subject, - type_, msg_iq): - ret = self.connection.send(msg_iq, msg is not None, on_ok=on_send_ok, - on_not_ok=on_send_not_ok) + def cb(jid, msg, keyID, forward_from, session, original_message, subject, + type_, msg_iq): + ret = self.connection.send(msg_iq, msg is not None, on_ok=on_send_ok, + on_not_ok=on_send_not_ok) - if ret == -1: - # Contact Offline - self.dispatch('MSGERROR', [jid, -1, _('Contact is offline. Your ' - 'message could not be sent.'), None, None, session]) + if ret == -1: + # Contact Offline + self.dispatch('MSGERROR', [jid, -1, _('Contact is offline. Your ' + 'message could not be sent.'), None, None, session]) - self._prepare_message(jid, msg, keyID, type_=type_, subject=subject, - chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep, - resource=resource, user_nick=user_nick, xhtml=xhtml, session=session, - forward_from=forward_from, form_node=form_node, - original_message=original_message, delayed=delayed, callback=cb) + self._prepare_message(jid, msg, keyID, type_=type_, subject=subject, + chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep, + resource=resource, user_nick=user_nick, xhtml=xhtml, session=session, + forward_from=forward_from, form_node=form_node, + original_message=original_message, delayed=delayed, callback=cb) - def send_stanza(self, stanza): - # send a stanza untouched - if not self.connection: - return - if not isinstance(stanza, common.xmpp.Node): - stanza = common.xmpp.Protocol(node=stanza) - self.connection.send(stanza) + def send_stanza(self, stanza): + # send a stanza untouched + if not self.connection: + return + if not isinstance(stanza, common.xmpp.Node): + stanza = common.xmpp.Protocol(node=stanza) + self.connection.send(stanza) - def _event_dispatcher(self, realm, event, data): - CommonConnection._event_dispatcher(self, realm, event, data) - if realm == '': - if event == common.xmpp.transports_nb.DATA_ERROR: - thread_id = data[1] - frm = unicode(data[0]) - session = self.get_or_create_session(frm, thread_id) - self.dispatch('MSGERROR', [frm, -1, - _('Connection to host could not be established: Timeout while ' - 'sending data.'), None, None, session]) + def _event_dispatcher(self, realm, event, data): + CommonConnection._event_dispatcher(self, realm, event, data) + if realm == '': + if event == common.xmpp.transports_nb.DATA_ERROR: + thread_id = data[1] + frm = unicode(data[0]) + session = self.get_or_create_session(frm, thread_id) + self.dispatch('MSGERROR', [frm, -1, + _('Connection to host could not be established: Timeout while ' + 'sending data.'), None, None, session]) # END ConnectionZeroconf - -# vim: se ts=3: diff --git a/src/common/zeroconf/roster_zeroconf.py b/src/common/zeroconf/roster_zeroconf.py index 4fe05aee1..17325192e 100644 --- a/src/common/zeroconf/roster_zeroconf.py +++ b/src/common/zeroconf/roster_zeroconf.py @@ -21,145 +21,143 @@ from common.zeroconf import zeroconf class Roster: - def __init__(self, zeroconf): - self._data = None - self.zeroconf = zeroconf # our zeroconf instance + def __init__(self, zeroconf): + self._data = None + self.zeroconf = zeroconf # our zeroconf instance - def update_roster(self): - for val in self.zeroconf.contacts.values(): - self.setItem(val[zeroconf.C_NAME]) + def update_roster(self): + for val in self.zeroconf.contacts.values(): + self.setItem(val[zeroconf.C_NAME]) - def getRoster(self): - #print 'roster_zeroconf.py: getRoster' - if self._data is None: - self._data = {} - self.update_roster() - return self + def getRoster(self): + #print 'roster_zeroconf.py: getRoster' + if self._data is None: + self._data = {} + self.update_roster() + return self - def getDiffs(self): - """ - Update the roster with new data and return dict with jid -> new status - pairs to do notifications and stuff - """ - diffs = {} - old_data = self._data.copy() - self.update_roster() - for key in old_data.keys(): - if key in self._data: - if old_data[key] != self._data[key]: - diffs[key] = self._data[key]['status'] - #print 'roster_zeroconf.py: diffs:' + str(diffs) - return diffs + def getDiffs(self): + """ + Update the roster with new data and return dict with jid -> new status + pairs to do notifications and stuff + """ + diffs = {} + old_data = self._data.copy() + self.update_roster() + for key in old_data.keys(): + if key in self._data: + if old_data[key] != self._data[key]: + diffs[key] = self._data[key]['status'] + #print 'roster_zeroconf.py: diffs:' + str(diffs) + return diffs - def setItem(self, jid, name='', groups=''): - #print 'roster_zeroconf.py: setItem %s' % jid - contact = self.zeroconf.get_contact(jid) - if not contact: - return + def setItem(self, jid, name='', groups=''): + #print 'roster_zeroconf.py: setItem %s' % jid + contact = self.zeroconf.get_contact(jid) + if not contact: + return - host, address, port = contact[4:7] - txt = contact[8] + host, address, port = contact[4:7] + txt = contact[8] - self._data[jid]={} - self._data[jid]['ask'] = 'none' - self._data[jid]['subscription'] = 'both' - self._data[jid]['groups'] = [] - self._data[jid]['resources'] = {} - self._data[jid]['address'] = address - self._data[jid]['host'] = host - self._data[jid]['port'] = port - txt_dict = self.zeroconf.txt_array_to_dict(txt) - status = txt_dict.get('status', '') - if not status: - status = 'avail' - nm = txt_dict.get('1st', '') - if 'last' in txt_dict: - if nm != '': - nm += ' ' - nm += txt_dict['last'] - if nm: - self._data[jid]['name'] = nm - else: - self._data[jid]['name'] = jid - if status == 'avail': - status = 'online' - self._data[jid]['txt_dict'] = txt_dict - if 'msg' not in self._data[jid]['txt_dict']: - self._data[jid]['txt_dict']['msg'] = '' - self._data[jid]['status'] = status - self._data[jid]['show'] = status + self._data[jid]={} + self._data[jid]['ask'] = 'none' + self._data[jid]['subscription'] = 'both' + self._data[jid]['groups'] = [] + self._data[jid]['resources'] = {} + self._data[jid]['address'] = address + self._data[jid]['host'] = host + self._data[jid]['port'] = port + txt_dict = self.zeroconf.txt_array_to_dict(txt) + status = txt_dict.get('status', '') + if not status: + status = 'avail' + nm = txt_dict.get('1st', '') + if 'last' in txt_dict: + if nm != '': + nm += ' ' + nm += txt_dict['last'] + if nm: + self._data[jid]['name'] = nm + else: + self._data[jid]['name'] = jid + if status == 'avail': + status = 'online' + self._data[jid]['txt_dict'] = txt_dict + if 'msg' not in self._data[jid]['txt_dict']: + self._data[jid]['txt_dict']['msg'] = '' + self._data[jid]['status'] = status + self._data[jid]['show'] = status - def setItemMulti(self, items): - for i in items: - self.setItem(jid=i['jid'], name=i['name'], groups=i['groups']) + def setItemMulti(self, items): + for i in items: + self.setItem(jid=i['jid'], name=i['name'], groups=i['groups']) - def delItem(self, jid): - #print 'roster_zeroconf.py: delItem %s' % jid - if jid in self._data: - del self._data[jid] + def delItem(self, jid): + #print 'roster_zeroconf.py: delItem %s' % jid + if jid in self._data: + del self._data[jid] - def getItem(self, jid): - #print 'roster_zeroconf.py: getItem: %s' % jid - if jid in self._data: - return self._data[jid] + def getItem(self, jid): + #print 'roster_zeroconf.py: getItem: %s' % jid + if jid in self._data: + return self._data[jid] - def __getitem__(self, jid): - #print 'roster_zeroconf.py: __getitem__' - return self._data[jid] + def __getitem__(self, jid): + #print 'roster_zeroconf.py: __getitem__' + return self._data[jid] - def getItems(self): - #print 'roster_zeroconf.py: getItems' - # Return list of all [bare] JIDs that the roster currently tracks. - return self._data.keys() + def getItems(self): + #print 'roster_zeroconf.py: getItems' + # Return list of all [bare] JIDs that the roster currently tracks. + return self._data.keys() - def keys(self): - #print 'roster_zeroconf.py: keys' - return self._data.keys() + def keys(self): + #print 'roster_zeroconf.py: keys' + return self._data.keys() - def getRaw(self): - #print 'roster_zeroconf.py: getRaw' - return self._data + def getRaw(self): + #print 'roster_zeroconf.py: getRaw' + return self._data - def getResources(self, jid): - #print 'roster_zeroconf.py: getResources(%s)' % jid - return {} + def getResources(self, jid): + #print 'roster_zeroconf.py: getResources(%s)' % jid + return {} - def getGroups(self, jid): - return self._data[jid]['groups'] + def getGroups(self, jid): + return self._data[jid]['groups'] - def getName(self, jid): - if jid in self._data: - return self._data[jid]['name'] + def getName(self, jid): + if jid in self._data: + return self._data[jid]['name'] - def getStatus(self, jid): - if jid in self._data: - return self._data[jid]['status'] + def getStatus(self, jid): + if jid in self._data: + return self._data[jid]['status'] - def getMessage(self, jid): - if jid in self._data: - return self._data[jid]['txt_dict']['msg'] + def getMessage(self, jid): + if jid in self._data: + return self._data[jid]['txt_dict']['msg'] - def getShow(self, jid): - #print 'roster_zeroconf.py: getShow' - return self.getStatus(jid) + def getShow(self, jid): + #print 'roster_zeroconf.py: getShow' + return self.getStatus(jid) - def getPriority(self, jid): - return 5 + def getPriority(self, jid): + return 5 - def getSubscription(self, jid): - #print 'roster_zeroconf.py: getSubscription' - return 'both' + def getSubscription(self, jid): + #print 'roster_zeroconf.py: getSubscription' + return 'both' - def Subscribe(self, jid): - pass + def Subscribe(self, jid): + pass - def Unsubscribe(self, jid): - pass + def Unsubscribe(self, jid): + pass - def Authorize(self, jid): - pass + def Authorize(self, jid): + pass - def Unauthorize(self, jid): - pass - -# vim: se ts=3: + def Unauthorize(self, jid): + pass diff --git a/src/common/zeroconf/zeroconf.py b/src/common/zeroconf/zeroconf.py index a99f0390d..36c2d8ab5 100644 --- a/src/common/zeroconf/zeroconf.py +++ b/src/common/zeroconf/zeroconf.py @@ -21,29 +21,27 @@ C_NAME, C_DOMAIN, C_INTERFACE, C_PROTOCOL, C_HOST, \ C_ADDRESS, C_PORT, C_BARE_NAME, C_TXT = range(9) def test_avahi(): - try: - import avahi - except ImportError: - return False - return True + try: + import avahi + except ImportError: + return False + return True def test_bonjour(): - try: - import pybonjour - except ImportError: - return False - except WindowsError: - return False - return True + try: + import pybonjour + except ImportError: + return False + except WindowsError: + return False + return True def test_zeroconf(): - return test_avahi() or test_bonjour() + return test_avahi() or test_bonjour() if test_avahi(): - from common.zeroconf import zeroconf_avahi - Zeroconf = zeroconf_avahi.Zeroconf + from common.zeroconf import zeroconf_avahi + Zeroconf = zeroconf_avahi.Zeroconf elif test_bonjour(): - from common.zeroconf import zeroconf_bonjour - Zeroconf = zeroconf_bonjour.Zeroconf - -# vim: se ts=3: + from common.zeroconf import zeroconf_bonjour + Zeroconf = zeroconf_bonjour.Zeroconf diff --git a/src/common/zeroconf/zeroconf_avahi.py b/src/common/zeroconf/zeroconf_avahi.py index 1db21adb4..9ea90bd5e 100644 --- a/src/common/zeroconf/zeroconf_avahi.py +++ b/src/common/zeroconf/zeroconf_avahi.py @@ -21,433 +21,431 @@ import logging log = logging.getLogger('gajim.c.z.zeroconf_avahi') try: - import dbus.glib + import dbus.glib except ImportError, e: - pass + pass from common.zeroconf.zeroconf import C_BARE_NAME, C_INTERFACE, C_PROTOCOL, C_DOMAIN class Zeroconf: - def __init__(self, new_serviceCB, remove_serviceCB, name_conflictCB, - disconnected_CB, error_CB, name, host, port): - self.avahi = None - self.domain = None # specific domain to browse - self.stype = '_presence._tcp' - self.port = port # listening port that gets announced - self.username = name - self.host = host - self.txt = {} # service data + def __init__(self, new_serviceCB, remove_serviceCB, name_conflictCB, + disconnected_CB, error_CB, name, host, port): + self.avahi = None + self.domain = None # specific domain to browse + self.stype = '_presence._tcp' + self.port = port # listening port that gets announced + self.username = name + self.host = host + self.txt = {} # service data - #XXX these CBs should be set to None when we destroy the object - # (go offline), because they create a circular reference - self.new_serviceCB = new_serviceCB - self.remove_serviceCB = remove_serviceCB - self.name_conflictCB = name_conflictCB - self.disconnected_CB = disconnected_CB - self.error_CB = error_CB + #XXX these CBs should be set to None when we destroy the object + # (go offline), because they create a circular reference + self.new_serviceCB = new_serviceCB + self.remove_serviceCB = remove_serviceCB + self.name_conflictCB = name_conflictCB + self.disconnected_CB = disconnected_CB + self.error_CB = error_CB - self.service_browser = None - self.domain_browser = None - self.bus = None - self.server = None - self.contacts = {} # all current local contacts with data - self.entrygroup = None - self.connected = False - self.announced = False - self.invalid_self_contact = {} + self.service_browser = None + self.domain_browser = None + self.bus = None + self.server = None + self.contacts = {} # all current local contacts with data + self.entrygroup = None + self.connected = False + self.announced = False + self.invalid_self_contact = {} - ## handlers for dbus callbacks - def entrygroup_commit_error_CB(self, err): - # left blank for possible later usage - pass + ## handlers for dbus callbacks + def entrygroup_commit_error_CB(self, err): + # left blank for possible later usage + pass - def error_callback1(self, err): - log.debug('Error while resolving: ' + str(err)) + def error_callback1(self, err): + log.debug('Error while resolving: ' + str(err)) - def error_callback(self, err): - log.debug(str(err)) - # timeouts are non-critical - if str(err) != 'Timeout reached': - self.disconnect() - self.disconnected_CB() + def error_callback(self, err): + log.debug(str(err)) + # timeouts are non-critical + if str(err) != 'Timeout reached': + self.disconnect() + self.disconnected_CB() - def new_service_callback(self, interface, protocol, name, stype, domain, - flags): - log.debug('Found service %s in domain %s on %i.%i.' % (name, domain, - interface, protocol)) - if not self.connected: - return + def new_service_callback(self, interface, protocol, name, stype, domain, + flags): + log.debug('Found service %s in domain %s on %i.%i.' % (name, domain, + interface, protocol)) + if not self.connected: + return - # synchronous resolving - self.server.ResolveService( int(interface), int(protocol), name, stype, - domain, self.avahi.PROTO_UNSPEC, dbus.UInt32(0), - reply_handler=self.service_resolved_callback, - error_handler=self.error_callback1) + # synchronous resolving + self.server.ResolveService( int(interface), int(protocol), name, stype, + domain, self.avahi.PROTO_UNSPEC, dbus.UInt32(0), + reply_handler=self.service_resolved_callback, + error_handler=self.error_callback1) - def remove_service_callback(self, interface, protocol, name, stype, domain, - flags): - log.debug('Service %s in domain %s on %i.%i disappeared.' % (name, - domain, interface, protocol)) - if not self.connected: - return - if name != self.name: - for key in self.contacts.keys(): - if self.contacts[key][C_BARE_NAME] == name: - del self.contacts[key] - self.remove_serviceCB(key) - return + def remove_service_callback(self, interface, protocol, name, stype, domain, + flags): + log.debug('Service %s in domain %s on %i.%i disappeared.' % (name, + domain, interface, protocol)) + if not self.connected: + return + if name != self.name: + for key in self.contacts.keys(): + if self.contacts[key][C_BARE_NAME] == name: + del self.contacts[key] + self.remove_serviceCB(key) + return - def new_service_type(self, interface, protocol, stype, domain, flags): - # Are we already browsing this domain for this type? - if self.service_browser: - return + def new_service_type(self, interface, protocol, stype, domain, flags): + # Are we already browsing this domain for this type? + if self.service_browser: + return - object_path = self.server.ServiceBrowserNew(interface, protocol, \ - stype, domain, dbus.UInt32(0)) + object_path = self.server.ServiceBrowserNew(interface, protocol, \ + stype, domain, dbus.UInt32(0)) - self.service_browser = dbus.Interface(self.bus.get_object( - self.avahi.DBUS_NAME, object_path), - self.avahi.DBUS_INTERFACE_SERVICE_BROWSER) - self.service_browser.connect_to_signal('ItemNew', - self.new_service_callback) - self.service_browser.connect_to_signal('ItemRemove', - self.remove_service_callback) - self.service_browser.connect_to_signal('Failure', self.error_callback) + self.service_browser = dbus.Interface(self.bus.get_object( + self.avahi.DBUS_NAME, object_path), + self.avahi.DBUS_INTERFACE_SERVICE_BROWSER) + self.service_browser.connect_to_signal('ItemNew', + self.new_service_callback) + self.service_browser.connect_to_signal('ItemRemove', + self.remove_service_callback) + self.service_browser.connect_to_signal('Failure', self.error_callback) - def new_domain_callback(self,interface, protocol, domain, flags): - if domain != 'local': - self.browse_domain(interface, protocol, domain) + def new_domain_callback(self, interface, protocol, domain, flags): + if domain != 'local': + self.browse_domain(interface, protocol, domain) - def txt_array_to_dict(self, txt_array): - txt_dict = {} - for els in txt_array: - key, val = '', None - for c in els: - c = chr(c) - if val is None: - if c == '=': - val = '' - else: - key += c - else: - val += c - if val is None: # missing '=' - val = '' - txt_dict[key] = val.decode('utf-8', 'ignore') - return txt_dict + def txt_array_to_dict(self, txt_array): + txt_dict = {} + for els in txt_array: + key, val = '', None + for c in els: + c = chr(c) + if val is None: + if c == '=': + val = '' + else: + key += c + else: + val += c + if val is None: # missing '=' + val = '' + txt_dict[key] = val.decode('utf-8', 'ignore') + return txt_dict - def service_resolved_callback(self, interface, protocol, name, stype, domain, - host, aprotocol, address, port, txt, flags): - log.debug('Service data for service %s in domain %s on %i.%i:' - % (name, domain, interface, protocol)) - log.debug('Host %s (%s), port %i, TXT data: %s' % (host, address, - port, self.txt_array_to_dict(txt))) - if not self.connected: - return - bare_name = name - if name.find('@') == -1: - name = name + '@' + name + def service_resolved_callback(self, interface, protocol, name, stype, domain, + host, aprotocol, address, port, txt, flags): + log.debug('Service data for service %s in domain %s on %i.%i:' + % (name, domain, interface, protocol)) + log.debug('Host %s (%s), port %i, TXT data: %s' % (host, address, + port, self.txt_array_to_dict(txt))) + if not self.connected: + return + bare_name = name + if name.find('@') == -1: + name = name + '@' + name - # we don't want to see ourselves in the list - if name != self.name: - self.contacts[name] = (name, domain, interface, protocol, host, - address, port, bare_name, txt) - self.new_serviceCB(name) - else: - # remember data - # In case this is not our own record but of another - # gajim instance on the same machine, - # it will be used when we get a new name. - self.invalid_self_contact[name] = (name, domain, interface, protocol, - host, address, port, bare_name, txt) + # we don't want to see ourselves in the list + if name != self.name: + self.contacts[name] = (name, domain, interface, protocol, host, + address, port, bare_name, txt) + self.new_serviceCB(name) + else: + # remember data + # In case this is not our own record but of another + # gajim instance on the same machine, + # it will be used when we get a new name. + self.invalid_self_contact[name] = (name, domain, interface, protocol, + host, address, port, bare_name, txt) - # different handler when resolving all contacts - def service_resolved_all_callback(self, interface, protocol, name, stype, - domain, host, aprotocol, address, port, txt, flags): - if not self.connected: - return - bare_name = name - if name.find('@') == -1: - name = name + '@' + name - self.contacts[name] = (name, domain, interface, protocol, host, address, - port, bare_name, txt) + # different handler when resolving all contacts + def service_resolved_all_callback(self, interface, protocol, name, stype, + domain, host, aprotocol, address, port, txt, flags): + if not self.connected: + return + bare_name = name + if name.find('@') == -1: + name = name + '@' + name + self.contacts[name] = (name, domain, interface, protocol, host, address, + port, bare_name, txt) - def service_added_callback(self): - log.debug('Service successfully added') + def service_added_callback(self): + log.debug('Service successfully added') - def service_committed_callback(self): - log.debug('Service successfully committed') + def service_committed_callback(self): + log.debug('Service successfully committed') - def service_updated_callback(self): - log.debug('Service successfully updated') + def service_updated_callback(self): + log.debug('Service successfully updated') - def service_add_fail_callback(self, err): - log.debug('Error while adding service. %s' % str(err)) - if 'Local name collision' in str(err): - alternative_name = self.server.GetAlternativeServiceName(self.username) - self.name_conflictCB(alternative_name) - return - self.error_CB(_('Error while adding service. %s') % str(err)) - self.disconnect() + def service_add_fail_callback(self, err): + log.debug('Error while adding service. %s' % str(err)) + if 'Local name collision' in str(err): + alternative_name = self.server.GetAlternativeServiceName(self.username) + self.name_conflictCB(alternative_name) + return + self.error_CB(_('Error while adding service. %s') % str(err)) + self.disconnect() - def server_state_changed_callback(self, state, error): - log.debug('server state changed to %s' % state) - if state == self.avahi.SERVER_RUNNING: - self.create_service() - elif state in (self.avahi.SERVER_COLLISION, - self.avahi.SERVER_REGISTERING): - self.disconnect() - self.entrygroup.Reset() - elif state == self.avahi.CLIENT_FAILURE: - # does it ever go here? - log.debug('CLIENT FAILURE') + def server_state_changed_callback(self, state, error): + log.debug('server state changed to %s' % state) + if state == self.avahi.SERVER_RUNNING: + self.create_service() + elif state in (self.avahi.SERVER_COLLISION, + self.avahi.SERVER_REGISTERING): + self.disconnect() + self.entrygroup.Reset() + elif state == self.avahi.CLIENT_FAILURE: + # does it ever go here? + log.debug('CLIENT FAILURE') - def entrygroup_state_changed_callback(self, state, error): - # the name is already present, so recreate - if state == self.avahi.ENTRY_GROUP_COLLISION: - log.debug('zeroconf.py: local name collision') - self.service_add_fail_callback('Local name collision') - elif state == self.avahi.ENTRY_GROUP_FAILURE: - self.disconnect() - self.entrygroup.Reset() - log.debug('zeroconf.py: ENTRY_GROUP_FAILURE reached(that' - ' should not happen)') + def entrygroup_state_changed_callback(self, state, error): + # the name is already present, so recreate + if state == self.avahi.ENTRY_GROUP_COLLISION: + log.debug('zeroconf.py: local name collision') + self.service_add_fail_callback('Local name collision') + elif state == self.avahi.ENTRY_GROUP_FAILURE: + self.disconnect() + self.entrygroup.Reset() + log.debug('zeroconf.py: ENTRY_GROUP_FAILURE reached(that' + ' should not happen)') - # make zeroconf-valid names - def replace_show(self, show): - if show in ['chat', 'online', '']: - return 'avail' - elif show == 'xa': - return 'away' - return show + # make zeroconf-valid names + def replace_show(self, show): + if show in ['chat', 'online', '']: + return 'avail' + elif show == 'xa': + return 'away' + return show - def avahi_txt(self): - utf8_dict = {} - for key in self.txt: - val = self.txt[key] - if isinstance(val, unicode): - utf8_dict[key] = val.encode('utf-8') - else: - utf8_dict[key] = val - return self.avahi.dict_to_txt_array(utf8_dict) + def avahi_txt(self): + utf8_dict = {} + for key in self.txt: + val = self.txt[key] + if isinstance(val, unicode): + utf8_dict[key] = val.encode('utf-8') + else: + utf8_dict[key] = val + return self.avahi.dict_to_txt_array(utf8_dict) - def create_service(self): - try: - if not self.entrygroup: - # create an EntryGroup for publishing - self.entrygroup = dbus.Interface(self.bus.get_object( - self.avahi.DBUS_NAME, self.server.EntryGroupNew()), - self.avahi.DBUS_INTERFACE_ENTRY_GROUP) - self.entrygroup.connect_to_signal('StateChanged', - self.entrygroup_state_changed_callback) + def create_service(self): + try: + if not self.entrygroup: + # create an EntryGroup for publishing + self.entrygroup = dbus.Interface(self.bus.get_object( + self.avahi.DBUS_NAME, self.server.EntryGroupNew()), + self.avahi.DBUS_INTERFACE_ENTRY_GROUP) + self.entrygroup.connect_to_signal('StateChanged', + self.entrygroup_state_changed_callback) - txt = {} + txt = {} - # remove empty keys - for key,val in self.txt.iteritems(): - if val: - txt[key] = val + # remove empty keys + for key, val in self.txt.iteritems(): + if val: + txt[key] = val - txt['port.p2pj'] = self.port - txt['version'] = 1 - txt['txtvers'] = 1 + txt['port.p2pj'] = self.port + txt['version'] = 1 + txt['txtvers'] = 1 - # replace gajim's show messages with compatible ones - if 'status' in self.txt: - txt['status'] = self.replace_show(self.txt['status']) - else: - txt['status'] = 'avail' + # replace gajim's show messages with compatible ones + if 'status' in self.txt: + txt['status'] = self.replace_show(self.txt['status']) + else: + txt['status'] = 'avail' - self.txt = txt - log.debug('Publishing service %s of type %s' % (self.name, - self.stype)) - self.entrygroup.AddService(self.avahi.IF_UNSPEC, - self.avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype, '', - '', dbus.UInt16(self.port), self.avahi_txt(), - reply_handler=self.service_added_callback, - error_handler=self.service_add_fail_callback) + self.txt = txt + log.debug('Publishing service %s of type %s' % (self.name, + self.stype)) + self.entrygroup.AddService(self.avahi.IF_UNSPEC, + self.avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype, '', + '', dbus.UInt16(self.port), self.avahi_txt(), + reply_handler=self.service_added_callback, + error_handler=self.service_add_fail_callback) - self.entrygroup.Commit(reply_handler=self.service_committed_callback, - error_handler=self.entrygroup_commit_error_CB) + self.entrygroup.Commit(reply_handler=self.service_committed_callback, + error_handler=self.entrygroup_commit_error_CB) - return True + return True - except dbus.DBusException, e: - log.debug(str(e)) - return False + except dbus.DBusException, e: + log.debug(str(e)) + return False - def announce(self): - if not self.connected: - return False + def announce(self): + if not self.connected: + return False - state = self.server.GetState() - if state == self.avahi.SERVER_RUNNING: - self.create_service() - self.announced = True - return True + state = self.server.GetState() + if state == self.avahi.SERVER_RUNNING: + self.create_service() + self.announced = True + return True - def remove_announce(self): - if self.announced == False: - return False - try: - if self.entrygroup.GetState() != self.avahi.ENTRY_GROUP_FAILURE: - self.entrygroup.Reset() - self.entrygroup.Free() - # .Free() has mem leaks - self.entrygroup._obj._bus = None - self.entrygroup._obj = None - self.entrygroup = None - self.announced = False + def remove_announce(self): + if self.announced == False: + return False + try: + if self.entrygroup.GetState() != self.avahi.ENTRY_GROUP_FAILURE: + self.entrygroup.Reset() + self.entrygroup.Free() + # .Free() has mem leaks + self.entrygroup._obj._bus = None + self.entrygroup._obj = None + self.entrygroup = None + self.announced = False - return True - else: - return False - except dbus.DBusException: - log.debug("Can't remove service. That should not happen") + return True + else: + return False + except dbus.DBusException: + log.debug("Can't remove service. That should not happen") - def browse_domain(self, interface, protocol, domain): - self.new_service_type(interface, protocol, self.stype, domain, '') + def browse_domain(self, interface, protocol, domain): + self.new_service_type(interface, protocol, self.stype, domain, '') - def avahi_dbus_connect_cb(self, a, connect, disconnect): - if connect != "": - log.debug('Lost connection to avahi-daemon') - self.disconnect() - if self.disconnected_CB: - self.disconnected_CB() - else: - log.debug('We are connected to avahi-daemon') + def avahi_dbus_connect_cb(self, a, connect, disconnect): + if connect != "": + log.debug('Lost connection to avahi-daemon') + self.disconnect() + if self.disconnected_CB: + self.disconnected_CB() + else: + log.debug('We are connected to avahi-daemon') - # connect to dbus - def connect_dbus(self): - try: - import dbus - except ImportError: - log.debug('Error: python-dbus needs to be installed. No ' - 'zeroconf support.') - return False - if self.bus: - return True - try: - self.bus = dbus.SystemBus() - self.bus.add_signal_receiver(self.avahi_dbus_connect_cb, - 'NameOwnerChanged', 'org.freedesktop.DBus', - arg0='org.freedesktop.Avahi') - except Exception, e: - # System bus is not present - self.bus = None - log.debug(str(e)) - return False - else: - return True + # connect to dbus + def connect_dbus(self): + try: + import dbus + except ImportError: + log.debug('Error: python-dbus needs to be installed. No ' + 'zeroconf support.') + return False + if self.bus: + return True + try: + self.bus = dbus.SystemBus() + self.bus.add_signal_receiver(self.avahi_dbus_connect_cb, + 'NameOwnerChanged', 'org.freedesktop.DBus', + arg0='org.freedesktop.Avahi') + except Exception, e: + # System bus is not present + self.bus = None + log.debug(str(e)) + return False + else: + return True - # connect to avahi - def connect_avahi(self): - if not self.connect_dbus(): - return False - try: - import avahi - self.avahi = avahi - except ImportError: - log.debug('Error: python-avahi needs to be installed. No ' - 'zeroconf support.') - return False + # connect to avahi + def connect_avahi(self): + if not self.connect_dbus(): + return False + try: + import avahi + self.avahi = avahi + except ImportError: + log.debug('Error: python-avahi needs to be installed. No ' + 'zeroconf support.') + return False - if self.server: - return True - try: - self.server = dbus.Interface(self.bus.get_object(self.avahi.DBUS_NAME, - self.avahi.DBUS_PATH_SERVER), self.avahi.DBUS_INTERFACE_SERVER) - self.server.connect_to_signal('StateChanged', - self.server_state_changed_callback) - except Exception, e: - # Avahi service is not present - self.server = None - log.debug(str(e)) - return False - else: - return True + if self.server: + return True + try: + self.server = dbus.Interface(self.bus.get_object(self.avahi.DBUS_NAME, + self.avahi.DBUS_PATH_SERVER), self.avahi.DBUS_INTERFACE_SERVER) + self.server.connect_to_signal('StateChanged', + self.server_state_changed_callback) + except Exception, e: + # Avahi service is not present + self.server = None + log.debug(str(e)) + return False + else: + return True - def connect(self): - self.name = self.username + '@' + self.host # service name - if not self.connect_avahi(): - return False + def connect(self): + self.name = self.username + '@' + self.host # service name + if not self.connect_avahi(): + return False - self.connected = True - # start browsing - if self.domain is None: - # Explicitly browse .local - self.browse_domain(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, - 'local') + self.connected = True + # start browsing + if self.domain is None: + # Explicitly browse .local + self.browse_domain(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, + 'local') - # Browse for other browsable domains - self.domain_browser = dbus.Interface(self.bus.get_object( - self.avahi.DBUS_NAME, self.server.DomainBrowserNew( - self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, '', - self.avahi.DOMAIN_BROWSER_BROWSE, dbus.UInt32(0))), - self.avahi.DBUS_INTERFACE_DOMAIN_BROWSER) - self.domain_browser.connect_to_signal('ItemNew', - self.new_domain_callback) - self.domain_browser.connect_to_signal('Failure', self.error_callback) - else: - self.browse_domain(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, - self.domain) + # Browse for other browsable domains + self.domain_browser = dbus.Interface(self.bus.get_object( + self.avahi.DBUS_NAME, self.server.DomainBrowserNew( + self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, '', + self.avahi.DOMAIN_BROWSER_BROWSE, dbus.UInt32(0))), + self.avahi.DBUS_INTERFACE_DOMAIN_BROWSER) + self.domain_browser.connect_to_signal('ItemNew', + self.new_domain_callback) + self.domain_browser.connect_to_signal('Failure', self.error_callback) + else: + self.browse_domain(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, + self.domain) - return True + return True - def disconnect(self): - if self.connected: - self.connected = False - if self.service_browser: - self.service_browser.Free() - self.service_browser._obj._bus = None - self.service_browser._obj = None - if self.domain_browser: - self.domain_browser.Free() - self.domain_browser._obj._bus = None - self.domain_browser._obj = None - self.remove_announce() - self.server._obj._bus = None - self.server._obj = None - self.server = None - self.service_browser = None - self.domain_browser = None + def disconnect(self): + if self.connected: + self.connected = False + if self.service_browser: + self.service_browser.Free() + self.service_browser._obj._bus = None + self.service_browser._obj = None + if self.domain_browser: + self.domain_browser.Free() + self.domain_browser._obj._bus = None + self.domain_browser._obj = None + self.remove_announce() + self.server._obj._bus = None + self.server._obj = None + self.server = None + self.service_browser = None + self.domain_browser = None - # refresh txt data of all contacts manually (no callback available) - def resolve_all(self): - if not self.connected: - return - for val in self.contacts.values(): - self.server.ResolveService(int(val[C_INTERFACE]), int(val[C_PROTOCOL]), - val[C_BARE_NAME], self.stype, val[C_DOMAIN], - self.avahi.PROTO_UNSPEC, dbus.UInt32(0), - reply_handler=self.service_resolved_all_callback, - error_handler=self.error_callback) + # refresh txt data of all contacts manually (no callback available) + def resolve_all(self): + if not self.connected: + return + for val in self.contacts.values(): + self.server.ResolveService(int(val[C_INTERFACE]), int(val[C_PROTOCOL]), + val[C_BARE_NAME], self.stype, val[C_DOMAIN], + self.avahi.PROTO_UNSPEC, dbus.UInt32(0), + reply_handler=self.service_resolved_all_callback, + error_handler=self.error_callback) - def get_contacts(self): - return self.contacts + def get_contacts(self): + return self.contacts - def get_contact(self, jid): - if not jid in self.contacts: - return None - return self.contacts[jid] + def get_contact(self, jid): + if not jid in self.contacts: + return None + return self.contacts[jid] - def update_txt(self, show = None): - if show: - self.txt['status'] = self.replace_show(show) + def update_txt(self, show = None): + if show: + self.txt['status'] = self.replace_show(show) - txt = self.avahi_txt() - if self.connected and self.entrygroup: - self.entrygroup.UpdateServiceTxt(self.avahi.IF_UNSPEC, - self.avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype, '', - txt, reply_handler=self.service_updated_callback, - error_handler=self.error_callback) - return True - else: - return False + txt = self.avahi_txt() + if self.connected and self.entrygroup: + self.entrygroup.UpdateServiceTxt(self.avahi.IF_UNSPEC, + self.avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype, '', + txt, reply_handler=self.service_updated_callback, + error_handler=self.error_callback) + return True + else: + return False # END Zeroconf - -# vim: se ts=3: diff --git a/src/common/zeroconf/zeroconf_bonjour.py b/src/common/zeroconf/zeroconf_bonjour.py index 772c9b2f0..b26f984a2 100644 --- a/src/common/zeroconf/zeroconf_bonjour.py +++ b/src/common/zeroconf/zeroconf_bonjour.py @@ -23,313 +23,310 @@ import re from common.zeroconf.zeroconf import C_BARE_NAME, C_DOMAIN try: - import pybonjour + import pybonjour except ImportError, e: - pass + pass resolve_timeout = 1 class Zeroconf: - def __init__(self, new_serviceCB, remove_serviceCB, name_conflictCB, - disconnected_CB, error_CB, name, host, port): - self.domain = None # specific domain to browse - self.stype = '_presence._tcp' - self.port = port # listening port that gets announced - self.username = name - self.host = host - self.txt = pybonjour.TXTRecord() # service data + def __init__(self, new_serviceCB, remove_serviceCB, name_conflictCB, + disconnected_CB, error_CB, name, host, port): + self.domain = None # specific domain to browse + self.stype = '_presence._tcp' + self.port = port # listening port that gets announced + self.username = name + self.host = host + self.txt = pybonjour.TXTRecord() # service data - # XXX these CBs should be set to None when we destroy the object - # (go offline), because they create a circular reference - self.new_serviceCB = new_serviceCB - self.remove_serviceCB = remove_serviceCB - self.name_conflictCB = name_conflictCB - self.disconnected_CB = disconnected_CB - self.error_CB = error_CB + # XXX these CBs should be set to None when we destroy the object + # (go offline), because they create a circular reference + self.new_serviceCB = new_serviceCB + self.remove_serviceCB = remove_serviceCB + self.name_conflictCB = name_conflictCB + self.disconnected_CB = disconnected_CB + self.error_CB = error_CB - self.contacts = {} # all current local contacts with data - self.connected = False - self.announced = False - self.invalid_self_contact = {} - self.resolved = [] + self.contacts = {} # all current local contacts with data + self.connected = False + self.announced = False + self.invalid_self_contact = {} + self.resolved = [] - def browse_callback(self, sdRef, flags, interfaceIndex, errorCode, serviceName, regtype, replyDomain): - gajim.log.debug('Found service %s in domain %s on %i(type: %s).' % (serviceName, replyDomain, interfaceIndex, regtype)) - if not self.connected: - return - if errorCode != pybonjour.kDNSServiceErr_NoError: - return - if not (flags & pybonjour.kDNSServiceFlagsAdd): - self.remove_service_callback(serviceName) - return + def browse_callback(self, sdRef, flags, interfaceIndex, errorCode, serviceName, regtype, replyDomain): + gajim.log.debug('Found service %s in domain %s on %i(type: %s).' % (serviceName, replyDomain, interfaceIndex, regtype)) + if not self.connected: + return + if errorCode != pybonjour.kDNSServiceErr_NoError: + return + if not (flags & pybonjour.kDNSServiceFlagsAdd): + self.remove_service_callback(serviceName) + return - # asynchronous resolving - resolve_sdRef = pybonjour.DNSServiceResolve(0, interfaceIndex, serviceName, regtype, replyDomain, self.service_resolved_callback) + # asynchronous resolving + resolve_sdRef = pybonjour.DNSServiceResolve(0, interfaceIndex, serviceName, regtype, replyDomain, self.service_resolved_callback) - try: - while not self.resolved: - ready = select.select([resolve_sdRef], [], [], resolve_timeout) - if resolve_sdRef not in ready[0]: - gajim.log.debug('Resolve timed out') - break - pybonjour.DNSServiceProcessResult(resolve_sdRef) - else: - self.resolved.pop() - finally: - resolve_sdRef.close() + try: + while not self.resolved: + ready = select.select([resolve_sdRef], [], [], resolve_timeout) + if resolve_sdRef not in ready[0]: + gajim.log.debug('Resolve timed out') + break + pybonjour.DNSServiceProcessResult(resolve_sdRef) + else: + self.resolved.pop() + finally: + resolve_sdRef.close() - def remove_service_callback(self, name): - gajim.log.debug('Service %s disappeared.' % name) - if not self.connected: - return - if name != self.name: - for key in self.contacts.keys(): - if self.contacts[key][C_BARE_NAME] == name: - del self.contacts[key] - self.remove_serviceCB(key) - return + def remove_service_callback(self, name): + gajim.log.debug('Service %s disappeared.' % name) + if not self.connected: + return + if name != self.name: + for key in self.contacts.keys(): + if self.contacts[key][C_BARE_NAME] == name: + del self.contacts[key] + self.remove_serviceCB(key) + return - def new_domain_callback(self,interface, protocol, domain, flags): - if domain != "local": - self.browse_domain(interface, protocol, domain) + def new_domain_callback(self, interface, protocol, domain, flags): + if domain != "local": + self.browse_domain(interface, protocol, domain) - # takes a TXTRecord instance - def txt_array_to_dict(self, txt): - items = pybonjour.TXTRecord.parse(txt)._items - return dict((v[0], v[1]) for v in items.values()) + # takes a TXTRecord instance + def txt_array_to_dict(self, txt): + items = pybonjour.TXTRecord.parse(txt)._items + return dict((v[0], v[1]) for v in items.values()) - def service_resolved_callback(self, sdRef, flags, interfaceIndex, errorCode, fullname, - hosttarget, port, txtRecord): + def service_resolved_callback(self, sdRef, flags, interfaceIndex, errorCode, fullname, + hosttarget, port, txtRecord): - # TODO: do proper decoding... - escaping= { - r'\.': '.', - r'\032': ' ', - r'\064': '@', - } + # TODO: do proper decoding... + escaping= { + r'\.': '.', + r'\032': ' ', + r'\064': '@', + } - # Split on '.' but do not split on '\.' - result = re.split('(? 0: - def login(account, show_before, status_before): - """ - Login with previous status - """ - # first make sure connection is really closed, - # 0.5 may not be enough - gajim.connections[account].disconnect(True) - gajim.interface.roster.send_status(account, show_before, - status_before) - - def relog(account): - self.dialog.destroy() - show_before = gajim.SHOW_LIST[gajim.connections[account].connected] - status_before = gajim.connections[account].status - gajim.interface.roster.send_status(account, 'offline', - _('Be right back.')) - gobject.timeout_add(500, login, account, show_before, status_before) - - def on_yes(checked, account): - relog(account) - def on_no(account): - if self.resend_presence: - self.resend(account) - if self.current_account in gajim.connections: - self.dialog = dialogs.YesNoDialog(_('Relogin now?'), - _('If you want all the changes to apply instantly, ' - 'you must relogin.'), on_response_yes=(on_yes, - self.current_account), on_response_no=(on_no, - self.current_account)) - elif self.resend_presence: - self.resend(self.current_account) - - self.need_relogin = False - self.resend_presence = False - - def on_accounts_treeview_cursor_changed(self, widget): - """ - Activate modify buttons when a row is selected, update accounts info - """ - sel = self.accounts_treeview.get_selection() - (model, iter_) = sel.get_selected() - if iter_: - account = model[iter_][0].decode('utf-8') - else: - account = None - if self.current_account and self.current_account == account: - # We're comming back to our current account, no need to update widgets - return - # Save config for previous account if needed cause focus_out event is - # called after the changed event - if self.current_account and self.window.get_focus(): - focused_widget = self.window.get_focus() - focused_widget_name = focused_widget.get_name() - if focused_widget_name in ('jid_entry1', 'resource_entry1', - 'custom_port_entry'): - if focused_widget_name == 'jid_entry1': - func = self.on_jid_entry1_focus_out_event - elif focused_widget_name == 'resource_entry1': - func = self.on_resource_entry1_focus_out_event - elif focused_widget_name == 'custom_port_entry': - func = self.on_custom_port_entry_focus_out_event - if func(focused_widget, None): - # Error detected in entry, don't change account, re-put cursor on - # previous row - self.select_account(self.current_account) - return True - self.window.set_focus(widget) - - self.check_resend_relog() - - if account: - self.remove_button.set_sensitive(True) - self.rename_button.set_sensitive(True) - else: - self.remove_button.set_sensitive(False) - self.rename_button.set_sensitive(False) - if iter_: - self.current_account = account - if account == gajim.ZEROCONF_ACC_NAME: - self.remove_button.set_sensitive(False) - self.init_account() - self.update_proxy_list() - - def update_proxy_list(self): - if self.current_account: - our_proxy = gajim.config.get_per('accounts', self.current_account, - 'proxy') - else: - our_proxy = '' - - if not our_proxy: - our_proxy = _('None') - proxy_combobox = self.xml.get_object('proxies_combobox1') - model = gtk.ListStore(str) - proxy_combobox.set_model(model) - l = gajim.config.get_per('proxies') - l.insert(0, _('None')) - for i in xrange(len(l)): - model.append([l[i]]) - if our_proxy == l[i]: - proxy_combobox.set_active(i) - - def init_account(self): - if not self.current_account: - self.notebook.set_current_page(0) - return - if gajim.config.get_per('accounts', self.current_account, 'is_zeroconf'): - self.ignore_events = True - self.init_zeroconf_account() - self.ignore_events = False - self.notebook.set_current_page(2) - return - self.ignore_events = True - self.init_normal_account() - self.ignore_events = False - self.notebook.set_current_page(1) - - def init_zeroconf_account(self): - active = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'active') - self.xml.get_object('enable_zeroconf_checkbutton2').set_active(active) - if not gajim.HAVE_ZEROCONF: - self.xml.get_object('enable_zeroconf_checkbutton2').set_sensitive( - False) - self.xml.get_object('zeroconf_notebook').set_sensitive(active) - # General tab - st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'autoconnect') - self.xml.get_object('autoconnect_checkbutton2').set_active(st) - - list_no_log_for = gajim.config.get_per('accounts', - gajim.ZEROCONF_ACC_NAME, 'no_log_for').split() - if gajim.ZEROCONF_ACC_NAME in list_no_log_for: - self.xml.get_object('log_history_checkbutton2').set_active(0) - else: - self.xml.get_object('log_history_checkbutton2').set_active(1) - - st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'sync_with_global_status') - self.xml.get_object('sync_with_global_status_checkbutton2').set_active(st) - - st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'use_custom_host') - self.xml.get_object('custom_port_checkbutton2').set_active(st) - self.xml.get_object('custom_port_entry2').set_sensitive(st) - - st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'custom_port') - if not st: - gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'custom_port', '5298') - st = '5298' - self.xml.get_object('custom_port_entry2').set_text(str(st)) - - # Personal tab - gpg_key_label = self.xml.get_object('gpg_key_label2') - if gajim.ZEROCONF_ACC_NAME in gajim.connections and \ - gajim.connections[gajim.ZEROCONF_ACC_NAME].gpg: - self.xml.get_object('gpg_choose_button2').set_sensitive(True) - self.init_account_gpg() - else: - gpg_key_label.set_text(_('OpenPGP is not usable on this computer')) - self.xml.get_object('gpg_choose_button2').set_sensitive(False) - - for opt in ('first_name', 'last_name', 'jabber_id', 'email'): - st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'zeroconf_' + opt) - self.xml.get_object(opt + '_entry2').set_text(st) - - def init_account_gpg(self): - account = self.current_account - keyid = gajim.config.get_per('accounts', account, 'keyid') - keyname = gajim.config.get_per('accounts', account, 'keyname') - use_gpg_agent = gajim.config.get('use_gpg_agent') - - if account == gajim.ZEROCONF_ACC_NAME: - widget_name_add = '2' - else: - widget_name_add = '1' - - gpg_key_label = self.xml.get_object('gpg_key_label' + widget_name_add) - gpg_name_label = self.xml.get_object('gpg_name_label' + widget_name_add) - use_gpg_agent_checkbutton = self.xml.get_object( - 'use_gpg_agent_checkbutton' + widget_name_add) - - if not keyid: - use_gpg_agent_checkbutton.set_sensitive(False) - gpg_key_label.set_text(_('No key selected')) - gpg_name_label.set_text('') - return - - gpg_key_label.set_text(keyid) - gpg_name_label.set_text(keyname) - use_gpg_agent_checkbutton.set_sensitive(True) - use_gpg_agent_checkbutton.set_active(use_gpg_agent) - - def draw_normal_jid(self): - account = self.current_account - self.ignore_events = True - active = gajim.config.get_per('accounts', account, 'active') - self.xml.get_object('enable_checkbutton1').set_active(active) - self.xml.get_object('normal_notebook1').set_sensitive(active) - if gajim.config.get_per('accounts', account, 'anonymous_auth'): - self.xml.get_object('anonymous_checkbutton1').set_active(True) - self.xml.get_object('jid_label1').set_text(_('Server:')) - save_password = self.xml.get_object('save_password_checkbutton1') - save_password.set_active(False) - save_password.set_sensitive(False) - password_entry = self.xml.get_object('password_entry1') - password_entry.set_text('') - password_entry.set_sensitive(False) - jid = gajim.config.get_per('accounts', account, 'hostname') - else: - self.xml.get_object('anonymous_checkbutton1').set_active(False) - self.xml.get_object('jid_label1').set_text(_('Jabber ID:')) - savepass = gajim.config.get_per('accounts', account, 'savepass') - save_password = self.xml.get_object('save_password_checkbutton1') - save_password.set_sensitive(True) - save_password.set_active(savepass) - password_entry = self.xml.get_object('password_entry1') - if savepass: - passstr = passwords.get_password(account) or '' - password_entry.set_sensitive(True) - else: - passstr = '' - password_entry.set_sensitive(False) - password_entry.set_text(passstr) - - jid = gajim.config.get_per('accounts', account, 'name') \ - + '@' + gajim.config.get_per('accounts', account, 'hostname') - self.xml.get_object('jid_entry1').set_text(jid) - self.ignore_events = False - - def init_normal_account(self): - account = self.current_account - # Account tab - self.draw_normal_jid() - self.xml.get_object('resource_entry1').set_text(gajim.config.get_per( - 'accounts', account, 'resource')) - self.xml.get_object('adjust_priority_with_status_checkbutton1').\ - set_active(gajim.config.get_per('accounts', account, - 'adjust_priority_with_status')) - spinbutton = self.xml.get_object('priority_spinbutton1') - if gajim.config.get('enable_negative_priority'): - spinbutton.set_range(-128, 127) - else: - spinbutton.set_range(0, 127) - spinbutton.set_value(gajim.config.get_per('accounts', account, - 'priority')) - - # Connection tab - use_env_http_proxy = gajim.config.get_per('accounts', account, - 'use_env_http_proxy') - self.xml.get_object('use_env_http_proxy_checkbutton1').set_active( - use_env_http_proxy) - self.xml.get_object('proxy_hbox1').set_sensitive(not use_env_http_proxy) - - warn_when_insecure_ssl = gajim.config.get_per('accounts', account, - 'warn_when_insecure_ssl_connection') - self.xml.get_object('warn_when_insecure_connection_checkbutton1').\ - set_active(warn_when_insecure_ssl) - - self.xml.get_object('send_keepalive_checkbutton1').set_active( - gajim.config.get_per('accounts', account, 'keep_alives_enabled')) - - use_custom_host = gajim.config.get_per('accounts', account, - 'use_custom_host') - self.xml.get_object('custom_host_port_checkbutton1').set_active( - use_custom_host) - custom_host = gajim.config.get_per('accounts', account, 'custom_host') - if not custom_host: - custom_host = gajim.config.get_per('accounts', account, 'hostname') - gajim.config.set_per('accounts', account, 'custom_host', custom_host) - self.xml.get_object('custom_host_entry1').set_text(custom_host) - custom_port = gajim.config.get_per('accounts', account, 'custom_port') - if not custom_port: - custom_port = 5222 - gajim.config.set_per('accounts', account, 'custom_port', custom_port) - self.xml.get_object('custom_port_entry1').set_text(unicode(custom_port)) - - # Personal tab - gpg_key_label = self.xml.get_object('gpg_key_label1') - if gajim.HAVE_GPG: - self.xml.get_object('gpg_choose_button1').set_sensitive(True) - self.init_account_gpg() - else: - gpg_key_label.set_text(_('OpenPGP is not usable on this computer')) - self.xml.get_object('gpg_choose_button1').set_sensitive(False) - - # General tab - self.xml.get_object('autoconnect_checkbutton1').set_active(gajim.config.\ - get_per('accounts', account, 'autoconnect')) - self.xml.get_object('autoreconnect_checkbutton1').set_active(gajim. - config.get_per('accounts', account, 'autoreconnect')) - - list_no_log_for = gajim.config.get_per('accounts', account, - 'no_log_for').split() - if account in list_no_log_for: - self.xml.get_object('log_history_checkbutton1').set_active(False) - else: - self.xml.get_object('log_history_checkbutton1').set_active(True) - - self.xml.get_object('sync_with_global_status_checkbutton1').set_active( - gajim.config.get_per('accounts', account, 'sync_with_global_status')) - self.xml.get_object('use_ft_proxies_checkbutton1').set_active( - gajim.config.get_per('accounts', account, 'use_ft_proxies')) - - def on_add_button_clicked(self, widget): - """ - When add button is clicked: open an account information window - """ - if 'account_creation_wizard' in gajim.interface.instances: - gajim.interface.instances['account_creation_wizard'].window.present() - else: - gajim.interface.instances['account_creation_wizard'] = \ - AccountCreationWizardWindow() - - def on_remove_button_clicked(self, widget): - """ - When delete button is clicked: Remove an account from the listStore and - from the config file - """ - if not self.current_account: - return - account = self.current_account - if len(gajim.events.get_events(account)): - dialogs.ErrorDialog(_('Unread events'), - _('Read all pending events before removing this account.')) - return - - if gajim.config.get_per('accounts', account, 'is_zeroconf'): - # Should never happen as button is insensitive - return - - win_opened = False - if gajim.interface.msg_win_mgr.get_controls(acct = account): - win_opened = True - else: - for key in gajim.interface.instances[account]: - if gajim.interface.instances[account][key] and key != \ - 'remove_account': - win_opened = True - break - # Detect if we have opened windows for this account - def remove(account): - if 'remove_account' in gajim.interface.instances[account]: - gajim.interface.instances[account]['remove_account'].window.\ - present() - else: - gajim.interface.instances[account]['remove_account'] = \ - RemoveAccountWindow(account) - if win_opened: - dialogs.ConfirmationDialog( - _('You have opened chat in account %s') % account, - _('All chat and groupchat windows will be closed. Do you want to ' - 'continue?'), - on_response_ok = (remove, account)) - else: - remove(account) - - def on_rename_button_clicked(self, widget): - if not self.current_account: - return - active = gajim.config.get_per('accounts', self.current_account, 'active') - if active and gajim.connections[self.current_account].connected != 0: - dialogs.ErrorDialog( - _('You are currently connected to the server'), - _('To change the account name, you must be disconnected.')) - return - if len(gajim.events.get_events(self.current_account)): - dialogs.ErrorDialog(_('Unread events'), - _('To change the account name, you must read all pending ' - 'events.')) - return - # Get the new name - def on_renamed(new_name, old_name): - if new_name in gajim.connections: - dialogs.ErrorDialog(_('Account Name Already Used'), - _('This name is already used by another of your accounts. ' - 'Please choose another name.')) - return - if (new_name == ''): - dialogs.ErrorDialog(_('Invalid account name'), - _('Account name cannot be empty.')) - return - if new_name.find(' ') != -1: - dialogs.ErrorDialog(_('Invalid account name'), - _('Account name cannot contain spaces.')) - return - if active: - # update variables - gajim.interface.instances[new_name] = gajim.interface.instances[ - old_name] - gajim.interface.minimized_controls[new_name] = \ - gajim.interface.minimized_controls[old_name] - gajim.nicks[new_name] = gajim.nicks[old_name] - gajim.block_signed_in_notifications[new_name] = \ - gajim.block_signed_in_notifications[old_name] - gajim.groups[new_name] = gajim.groups[old_name] - gajim.gc_connected[new_name] = gajim.gc_connected[old_name] - gajim.automatic_rooms[new_name] = gajim.automatic_rooms[old_name] - gajim.newly_added[new_name] = gajim.newly_added[old_name] - gajim.to_be_removed[new_name] = gajim.to_be_removed[old_name] - gajim.sleeper_state[new_name] = gajim.sleeper_state[old_name] - gajim.encrypted_chats[new_name] = gajim.encrypted_chats[old_name] - gajim.last_message_time[new_name] = \ - gajim.last_message_time[old_name] - gajim.status_before_autoaway[new_name] = \ - gajim.status_before_autoaway[old_name] - gajim.transport_avatar[new_name] = gajim.transport_avatar[old_name] - gajim.gajim_optional_features[new_name] = \ - gajim.gajim_optional_features[old_name] - gajim.caps_hash[new_name] = gajim.caps_hash[old_name] - - gajim.contacts.change_account_name(old_name, new_name) - gajim.events.change_account_name(old_name, new_name) - - # change account variable for chat / gc controls - gajim.interface.msg_win_mgr.change_account_name(old_name, new_name) - # upgrade account variable in opened windows - for kind in ('infos', 'disco', 'gc_config', 'search', - 'online_dialog'): - for j in gajim.interface.instances[new_name][kind]: - gajim.interface.instances[new_name][kind][j].account = \ - new_name - - # ServiceCache object keep old property account - if hasattr(gajim.connections[old_name], 'services_cache'): - gajim.connections[old_name].services_cache.account = new_name - del gajim.interface.instances[old_name] - del gajim.interface.minimized_controls[old_name] - del gajim.nicks[old_name] - del gajim.block_signed_in_notifications[old_name] - del gajim.groups[old_name] - del gajim.gc_connected[old_name] - del gajim.automatic_rooms[old_name] - del gajim.newly_added[old_name] - del gajim.to_be_removed[old_name] - del gajim.sleeper_state[old_name] - del gajim.encrypted_chats[old_name] - del gajim.last_message_time[old_name] - del gajim.status_before_autoaway[old_name] - del gajim.transport_avatar[old_name] - del gajim.gajim_optional_features[old_name] - del gajim.caps_hash[old_name] - gajim.connections[old_name].name = new_name - gajim.connections[new_name] = gajim.connections[old_name] - del gajim.connections[old_name] - gajim.config.add_per('accounts', new_name) - old_config = gajim.config.get_per('accounts', old_name) - for opt in old_config: - gajim.config.set_per('accounts', new_name, opt, old_config[opt][1]) - gajim.config.del_per('accounts', old_name) - if self.current_account == old_name: - self.current_account = new_name - if old_name == gajim.ZEROCONF_ACC_NAME: - gajim.ZEROCONF_ACC_NAME = new_name - # refresh roster - gajim.interface.roster.setup_and_draw_roster() - self.init_accounts() - self.select_account(new_name) - - title = _('Rename Account') - message = _('Enter a new name for account %s') % self.current_account - old_text = self.current_account - dialogs.InputDialog(title, message, old_text, is_modal=False, - ok_handler=(on_renamed, self.current_account)) - - def option_changed(self, option, value): - return gajim.config.get_per('accounts', self.current_account, option) != \ - value - - def on_jid_entry1_focus_out_event(self, widget, event): - if self.ignore_events: - return - jid = widget.get_text() - # check if jid is conform to RFC and stringprep it - try: - jid = helpers.parse_jid(jid) - except helpers.InvalidFormat, s: - if not widget.is_focus(): - pritext = _('Invalid Jabber ID') - dialogs.ErrorDialog(pritext, str(s)) - gobject.idle_add(lambda: widget.grab_focus()) - return True - - jid_splited = jid.split('@', 1) - if len(jid_splited) != 2 and not gajim.config.get_per('accounts', - self.current_account, 'anonymous_auth'): - if not widget.is_focus(): - pritext = _('Invalid Jabber ID') - sectext = _('A Jabber ID must be in the form "user@servername".') - dialogs.ErrorDialog(pritext, sectext) - gobject.idle_add(lambda: widget.grab_focus()) - return True - - - if gajim.config.get_per('accounts', self.current_account, - 'anonymous_auth'): - gajim.config.set_per('accounts', self.current_account, 'hostname', - jid_splited[0]) - if self.option_changed('hostname', jid_splited[0]): - self.need_relogin = True - else: - if self.option_changed('name', jid_splited[0]) or \ - self.option_changed('hostname', jid_splited[1]): - self.need_relogin = True - - gajim.config.set_per('accounts', self.current_account, 'name', - jid_splited[0]) - gajim.config.set_per('accounts', self.current_account, 'hostname', - jid_splited[1]) - - def on_anonymous_checkbutton1_toggled(self, widget): - if self.ignore_events: - return - active = widget.get_active() - gajim.config.set_per('accounts', self.current_account, 'anonymous_auth', - active) - self.draw_normal_jid() - - def on_password_entry1_changed(self, widget): - if self.ignore_events: - return - passwords.save_password(self.current_account, widget.get_text().decode( - 'utf-8')) - - def on_save_password_checkbutton1_toggled(self, widget): - if self.ignore_events: - return - active = widget.get_active() - password_entry = self.xml.get_object('password_entry1') - password_entry.set_sensitive(active) - gajim.config.set_per('accounts', self.current_account, 'savepass', active) - if active: - password = password_entry.get_text() - passwords.save_password(self.current_account, password) - else: - passwords.save_password(self.current_account, '') - - def on_resource_entry1_focus_out_event(self, widget, event): - if self.ignore_events: - return - resource = self.xml.get_object('resource_entry1').get_text().decode( - 'utf-8') - try: - resource = helpers.parse_resource(resource) - except helpers.InvalidFormat, s: - if not widget.is_focus(): - pritext = _('Invalid Jabber ID') - dialogs.ErrorDialog(pritext, str(s)) - gobject.idle_add(lambda: widget.grab_focus()) - return True - - if self.option_changed('resource', resource): - self.need_relogin = True - - gajim.config.set_per('accounts', self.current_account, 'resource', - resource) - - def on_adjust_priority_with_status_checkbutton1_toggled(self, widget): - self.xml.get_object('priority_spinbutton1').set_sensitive( - not widget.get_active()) - self.on_checkbutton_toggled(widget, 'adjust_priority_with_status', - account = self.current_account) - - def on_priority_spinbutton1_value_changed(self, widget): - prio = widget.get_value_as_int() - - if self.option_changed('priority', prio): - self.resend_presence = True - - gajim.config.set_per('accounts', self.current_account, 'priority', prio) - - def on_synchronise_contacts_button1_clicked(self, widget): - try: - dialogs.SynchroniseSelectAccountDialog(self.current_account) - except GajimGeneralException: - # If we showed ErrorDialog, there will not be dialog instance - return - - def on_change_password_button1_clicked(self, widget): - def on_changed(new_password): - if new_password is not None: - gajim.connections[self.current_account].change_password( - new_password) - if self.xml.get_object('save_password_checkbutton1').get_active(): - self.xml.get_object('password_entry1').set_text(new_password) - - try: - dialogs.ChangePasswordDialog(self.current_account, on_changed) - except GajimGeneralException: - # if we showed ErrorDialog, there will not be dialog instance - return - - def on_autoconnect_checkbutton_toggled(self, widget): - if self.ignore_events: - return - self.on_checkbutton_toggled(widget, 'autoconnect', - account=self.current_account) - - def on_autoreconnect_checkbutton_toggled(self, widget): - if self.ignore_events: - return - self.on_checkbutton_toggled(widget, 'autoreconnect', - account=self.current_account) - - def on_log_history_checkbutton_toggled(self, widget): - if self.ignore_events: - return - list_no_log_for = gajim.config.get_per('accounts', self.current_account, - 'no_log_for').split() - if self.current_account in list_no_log_for: - list_no_log_for.remove(self.current_account) - - if not widget.get_active(): - list_no_log_for.append(self.current_account) - gajim.config.set_per('accounts', self.current_account, 'no_log_for', - ' '.join(list_no_log_for)) - - def on_sync_with_global_status_checkbutton_toggled(self, widget): - if self.ignore_events: - return - self.on_checkbutton_toggled(widget, 'sync_with_global_status', - account=self.current_account) - gajim.interface.roster.update_status_combobox() - - def on_use_ft_proxies_checkbutton1_toggled(self, widget): - if self.ignore_events: - return - self.on_checkbutton_toggled(widget, 'use_ft_proxies', - account=self.current_account) - - def on_use_env_http_proxy_checkbutton1_toggled(self, widget): - if self.ignore_events: - return - self.on_checkbutton_toggled(widget, 'use_env_http_proxy', - account=self.current_account) - hbox = self.xml.get_object('proxy_hbox1') - hbox.set_sensitive(not widget.get_active()) - - def on_proxies_combobox1_changed(self, widget): - active = widget.get_active() - proxy = widget.get_model()[active][0].decode('utf-8') - if proxy == _('None'): - proxy = '' - - if self.option_changed('proxy', proxy): - self.need_relogin = True - - gajim.config.set_per('accounts', self.current_account, 'proxy', proxy) - - def on_manage_proxies_button1_clicked(self, widget): - if 'manage_proxies' in gajim.interface.instances: - gajim.interface.instances['manage_proxies'].window.present() - else: - gajim.interface.instances['manage_proxies'] = ManageProxiesWindow() - - def on_warn_when_insecure_connection_checkbutton1_toggled(self, widget): - if self.ignore_events: - return - - self.on_checkbutton_toggled(widget, 'warn_when_insecure_ssl_connection', - account=self.current_account) - - def on_send_keepalive_checkbutton1_toggled(self, widget): - if self.ignore_events: - return - self.on_checkbutton_toggled(widget, 'keep_alives_enabled', - account=self.current_account) - gajim.config.set_per('accounts', self.current_account, - 'ping_alives_enabled', widget.get_active()) - - def on_custom_host_port_checkbutton1_toggled(self, widget): - if self.option_changed('use_custom_host', widget.get_active()): - self.need_relogin = True - - self.on_checkbutton_toggled(widget, 'use_custom_host', - account=self.current_account) - active = widget.get_active() - self.xml.get_object('custom_host_port_hbox1').set_sensitive(active) - - def on_custom_host_entry1_changed(self, widget): - if self.ignore_events: - return - host = widget.get_text().decode('utf-8') - if self.option_changed('custom_host', host): - self.need_relogin = True - gajim.config.set_per('accounts', self.current_account, 'custom_host', - host) - - def on_custom_port_entry_focus_out_event(self, widget, event): - if self.ignore_events: - return - custom_port = widget.get_text() - try: - custom_port = int(custom_port) - except Exception: - if not widget.is_focus(): - dialogs.ErrorDialog(_('Invalid entry'), - _('Custom port must be a port number.')) - gobject.idle_add(lambda: widget.grab_focus()) - return True - if self.option_changed('custom_port', custom_port): - self.need_relogin = True - gajim.config.set_per('accounts', self.current_account, 'custom_port', - custom_port) - - def on_gpg_choose_button_clicked(self, widget, data = None): - if self.current_account in gajim.connections and \ - gajim.connections[self.current_account].gpg: - secret_keys = gajim.connections[self.current_account].\ - ask_gpg_secrete_keys() - - # self.current_account is None and/or gajim.connections is {} - else: - if gajim.HAVE_GPG: - secret_keys = GnuPG.GnuPG().get_secret_keys() - else: - secret_keys = [] - if not secret_keys: - dialogs.ErrorDialog(_('Failed to get secret keys'), - _('There is no OpenPGP secret key available.')) - secret_keys[_('None')] = _('None') - - def on_key_selected(keyID): - if keyID is None: - return - if self.current_account == gajim.ZEROCONF_ACC_NAME: - wiget_name_ext = '2' - else: - wiget_name_ext = '1' - gpg_key_label = self.xml.get_object('gpg_key_label' + wiget_name_ext) - gpg_name_label = self.xml.get_object('gpg_name_label' + wiget_name_ext) - use_gpg_agent_checkbutton = self.xml.get_object( - 'use_gpg_agent_checkbutton' + wiget_name_ext) - if keyID[0] == _('None'): - gpg_key_label.set_text(_('No key selected')) - gpg_name_label.set_text('') - use_gpg_agent_checkbutton.set_sensitive(False) - if self.option_changed('keyid', ''): - self.need_relogin = True - gajim.config.set_per('accounts', self.current_account, 'keyname', - '') - gajim.config.set_per('accounts', self.current_account, 'keyid', '') - else: - gpg_key_label.set_text(keyID[0]) - gpg_name_label.set_text(keyID[1]) - use_gpg_agent_checkbutton.set_sensitive(True) - if self.option_changed('keyid', keyID[0]): - self.need_relogin = True - gajim.config.set_per('accounts', self.current_account, 'keyname', - keyID[1]) - gajim.config.set_per('accounts', self.current_account, 'keyid', - keyID[0]) - - dialogs.ChooseGPGKeyDialog(_('OpenPGP Key Selection'), - _('Choose your OpenPGP key'), secret_keys, on_key_selected) - - def on_use_gpg_agent_checkbutton_toggled(self, widget): - self.on_checkbutton_toggled(widget, 'use_gpg_agent') - - def on_edit_details_button1_clicked(self, widget): - if self.current_account not in gajim.interface.instances: - dialogs.ErrorDialog(_('No such account available'), - _('You must create your account before editing your personal ' - 'information.')) - return - - # show error dialog if account is newly created (not in gajim.connections) - if self.current_account not in gajim.connections or \ - gajim.connections[self.current_account].connected < 2: - dialogs.ErrorDialog(_('You are not connected to the server'), - _('Without a connection, you can not edit your personal information.')) - return - - if not gajim.connections[self.current_account].vcard_supported: - dialogs.ErrorDialog(_("Your server doesn't support Vcard"), - _("Your server can't save your personal information.")) - return - - gajim.interface.edit_own_details(self.current_account) - - def on_checkbutton_toggled(self, widget, config_name, - change_sensitivity_widgets = None, account = None): - if account: - gajim.config.set_per('accounts', account, config_name, - widget.get_active()) - else: - gajim.config.set(config_name, widget.get_active()) - if change_sensitivity_widgets: - for w in change_sensitivity_widgets: - w.set_sensitive(widget.get_active()) - gajim.interface.save_config() - - def on_merge_checkbutton_toggled(self, widget): - self.on_checkbutton_toggled(widget, 'mergeaccounts') - if len(gajim.connections) >= 2: # Do not merge accounts if only one active - gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') - else: - gajim.interface.roster.regroup = False - gajim.interface.roster.setup_and_draw_roster() - - def _disable_account(self, account): - gajim.interface.roster.close_all(account) - if account == gajim.ZEROCONF_ACC_NAME: - gajim.connections[account].disable_account() - del gajim.connections[account] - gajim.interface.save_config() - del gajim.interface.instances[account] - del gajim.interface.minimized_controls[account] - del gajim.nicks[account] - del gajim.block_signed_in_notifications[account] - del gajim.groups[account] - gajim.contacts.remove_account(account) - del gajim.gc_connected[account] - del gajim.automatic_rooms[account] - del gajim.to_be_removed[account] - del gajim.newly_added[account] - del gajim.sleeper_state[account] - del gajim.encrypted_chats[account] - del gajim.last_message_time[account] - del gajim.status_before_autoaway[account] - del gajim.transport_avatar[account] - del gajim.gajim_optional_features[account] - del gajim.caps_hash[account] - if len(gajim.connections) >= 2: - # Do not merge accounts if only one exists - gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') - else: - gajim.interface.roster.regroup = False - gajim.interface.roster.setup_and_draw_roster() - gajim.interface.roster.set_actions_menu_needs_rebuild() - - def _enable_account(self, account): - if account == gajim.ZEROCONF_ACC_NAME: - gajim.connections[account] = connection_zeroconf.ConnectionZeroconf( - account) - if gajim.connections[account].gpg: - self.xml.get_object('gpg_choose_button2').set_sensitive(True) - else: - gajim.connections[account] = common.connection.Connection(account) - if gajim.connections[account].gpg: - self.xml.get_object('gpg_choose_button1').set_sensitive(True) - self.init_account_gpg() - # update variables - gajim.interface.instances[account] = {'infos': {}, - 'disco': {}, 'gc_config': {}, 'search': {}, 'online_dialog': {}} - gajim.interface.minimized_controls[account] = {} - gajim.connections[account].connected = 0 - gajim.groups[account] = {} - gajim.contacts.add_account(account) - gajim.gc_connected[account] = {} - gajim.automatic_rooms[account] = {} - gajim.newly_added[account] = [] - gajim.to_be_removed[account] = [] - if account == gajim.ZEROCONF_ACC_NAME: - gajim.nicks[account] = gajim.ZEROCONF_ACC_NAME - else: - gajim.nicks[account] = gajim.config.get_per('accounts', account, - 'name') - gajim.block_signed_in_notifications[account] = True - gajim.sleeper_state[account] = 'off' - gajim.encrypted_chats[account] = [] - gajim.last_message_time[account] = {} - gajim.status_before_autoaway[account] = '' - gajim.transport_avatar[account] = {} - gajim.gajim_optional_features[account] = [] - gajim.caps_hash[account] = '' - # refresh roster - if len(gajim.connections) >= 2: - # Do not merge accounts if only one exists - gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') - else: - gajim.interface.roster.regroup = False - gajim.interface.roster.setup_and_draw_roster() - gajim.interface.roster.set_actions_menu_needs_rebuild() - gajim.interface.save_config() - - def on_enable_zeroconf_checkbutton2_toggled(self, widget): - # don't do anything if there is an account with the local name but is a - # normal account - if self.ignore_events: - return - if self.current_account in gajim.connections and \ - gajim.connections[self.current_account].connected > 0: - self.ignore_events = True - self.xml.get_object('enable_zeroconf_checkbutton2').set_active(True) - self.ignore_events = False - dialogs.ErrorDialog( - _('You are currently connected to the server'), - _('To disable the account, you must be disconnected.')) - return - if gajim.ZEROCONF_ACC_NAME in gajim.connections and not \ - gajim.connections[gajim.ZEROCONF_ACC_NAME].is_zeroconf: - gajim.connections[gajim.ZEROCONF_ACC_NAME].dispatch('ERROR', - (_('Account Local already exists.'), - _('Please rename or remove it before enabling link-local messaging' - '.'))) - return - - if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \ - and not widget.get_active(): - self.xml.get_object('zeroconf_notebook').set_sensitive(False) - # disable - self._disable_account(gajim.ZEROCONF_ACC_NAME) - - elif not gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, - 'active') and widget.get_active(): - self.xml.get_object('zeroconf_notebook').set_sensitive(True) - # enable (will create new account if not present) - self._enable_account(gajim.ZEROCONF_ACC_NAME) - - self.on_checkbutton_toggled(widget, 'active', - account=gajim.ZEROCONF_ACC_NAME) - - def on_enable_checkbutton1_toggled(self, widget): - if self.ignore_events: - return - if self.current_account in gajim.connections and \ - gajim.connections[self.current_account].connected > 0: - # connecting or connected - self.ignore_events = True - self.xml.get_object('enable_checkbutton1').set_active(True) - self.ignore_events = False - dialogs.ErrorDialog( - _('You are currently connected to the server'), - _('To disable the account, you must be disconnected.')) - return - # add/remove account in roster and all variables - if widget.get_active(): - # enable - self._enable_account(self.current_account) - else: - # disable - self._disable_account(self.current_account) - self.on_checkbutton_toggled(widget, 'active', - account=self.current_account, change_sensitivity_widgets=[ - self.xml.get_object('normal_notebook1')]) - - def on_custom_port_checkbutton2_toggled(self, widget): - self.xml.get_object('custom_port_entry2').set_sensitive( - widget.get_active()) - self.on_checkbutton_toggled(widget, 'use_custom_host', - account = self.current_account) - if not widget.get_active(): - self.xml.get_object('custom_port_entry2').set_text('5298') - - def on_first_name_entry2_changed(self, widget): - if self.ignore_events: - return - name = widget.get_text().decode('utf-8') - if self.option_changed('zeroconf_first_name', name): - self.need_relogin = True - gajim.config.set_per('accounts', self.current_account, - 'zeroconf_first_name', name) - - def on_last_name_entry2_changed(self, widget): - if self.ignore_events: - return - name = widget.get_text().decode('utf-8') - if self.option_changed('zeroconf_last_name', name): - self.need_relogin = True - gajim.config.set_per('accounts', self.current_account, - 'zeroconf_last_name', name) - - def on_jabber_id_entry2_changed(self, widget): - if self.ignore_events: - return - id_ = widget.get_text().decode('utf-8') - if self.option_changed('zeroconf_jabber_id', id_): - self.need_relogin = True - gajim.config.set_per('accounts', self.current_account, - 'zeroconf_jabber_id', id_) - - def on_email_entry2_changed(self, widget): - if self.ignore_events: - return - email = widget.get_text().decode('utf-8') - if self.option_changed('zeroconf_email', email): - self.need_relogin = True - gajim.config.set_per('accounts', self.current_account, - 'zeroconf_email', email) + """ + Class for accounts window: list of accounts + """ + + def on_accounts_window_destroy(self, widget): + del gajim.interface.instances['accounts'] + + def on_close_button_clicked(self, widget): + self.check_resend_relog() + self.window.destroy() + + def __init__(self): + self.xml = gtkgui_helpers.get_gtk_builder('accounts_window.ui') + self.window = self.xml.get_object('accounts_window') + self.window.set_transient_for(gajim.interface.roster.window) + self.accounts_treeview = self.xml.get_object('accounts_treeview') + self.remove_button = self.xml.get_object('remove_button') + self.rename_button = self.xml.get_object('rename_button') + path_to_kbd_input_img = gtkgui_helpers.get_icon_path('gajim-kbd_input') + img = self.xml.get_object('rename_image') + img.set_from_file(path_to_kbd_input_img) + self.notebook = self.xml.get_object('notebook') + # Name + model = gtk.ListStore(str) + self.accounts_treeview.set_model(model) + # column + renderer = gtk.CellRendererText() + self.accounts_treeview.insert_column_with_attributes(-1, _('Name'), + renderer, text=0) + + self.current_account = None + # When we fill info, we don't want to handle the changed signals + self.ignore_events = False + self.need_relogin = False + self.resend_presence = False + + self.update_proxy_list() + self.xml.connect_signals(self) + self.init_accounts() + self.window.show_all() + + # Merge accounts + st = gajim.config.get('mergeaccounts') + checkbutton = self.xml.get_object('merge_checkbutton') + checkbutton.set_active(st) + # prevent roster redraws by connecting the signal after button state is + # set + checkbutton.connect('toggled', self.on_merge_checkbutton_toggled) + + self.avahi_available = True + try: + import avahi + except ImportError: + self.avahi_available = False + + def on_accounts_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: + self.check_resend_relog() + self.window.destroy() + + def select_account(self, account): + model = self.accounts_treeview.get_model() + iter_ = model.get_iter_root() + while iter_: + acct = model[iter_][0].decode('utf-8') + if account == acct: + self.accounts_treeview.set_cursor(model.get_path(iter_)) + return + iter_ = model.iter_next(iter_) + + def init_accounts(self): + """ + Initialize listStore with existing accounts + """ + self.remove_button.set_sensitive(False) + self.rename_button.set_sensitive(False) + self.current_account = None + model = self.accounts_treeview.get_model() + model.clear() + for account in gajim.config.get_per('accounts'): + iter_ = model.append() + model.set(iter_, 0, account) + + def resend(self, account): + if not account in gajim.connections: + return + show = gajim.SHOW_LIST[gajim.connections[account].connected] + status = gajim.connections[account].status + gajim.connections[account].change_status(show, status) + + def check_resend_relog(self): + if self.need_relogin and self.current_account == gajim.ZEROCONF_ACC_NAME: + if gajim.ZEROCONF_ACC_NAME in gajim.connections: + gajim.connections[gajim.ZEROCONF_ACC_NAME].update_details() + return + + elif self.need_relogin and self.current_account and \ + gajim.connections[self.current_account].connected > 0: + def login(account, show_before, status_before): + """ + Login with previous status + """ + # first make sure connection is really closed, + # 0.5 may not be enough + gajim.connections[account].disconnect(True) + gajim.interface.roster.send_status(account, show_before, + status_before) + + def relog(account): + self.dialog.destroy() + show_before = gajim.SHOW_LIST[gajim.connections[account].connected] + status_before = gajim.connections[account].status + gajim.interface.roster.send_status(account, 'offline', + _('Be right back.')) + gobject.timeout_add(500, login, account, show_before, status_before) + + def on_yes(checked, account): + relog(account) + def on_no(account): + if self.resend_presence: + self.resend(account) + if self.current_account in gajim.connections: + self.dialog = dialogs.YesNoDialog(_('Relogin now?'), + _('If you want all the changes to apply instantly, ' + 'you must relogin.'), on_response_yes=(on_yes, + self.current_account), on_response_no=(on_no, + self.current_account)) + elif self.resend_presence: + self.resend(self.current_account) + + self.need_relogin = False + self.resend_presence = False + + def on_accounts_treeview_cursor_changed(self, widget): + """ + Activate modify buttons when a row is selected, update accounts info + """ + sel = self.accounts_treeview.get_selection() + (model, iter_) = sel.get_selected() + if iter_: + account = model[iter_][0].decode('utf-8') + else: + account = None + if self.current_account and self.current_account == account: + # We're comming back to our current account, no need to update widgets + return + # Save config for previous account if needed cause focus_out event is + # called after the changed event + if self.current_account and self.window.get_focus(): + focused_widget = self.window.get_focus() + focused_widget_name = focused_widget.get_name() + if focused_widget_name in ('jid_entry1', 'resource_entry1', + 'custom_port_entry'): + if focused_widget_name == 'jid_entry1': + func = self.on_jid_entry1_focus_out_event + elif focused_widget_name == 'resource_entry1': + func = self.on_resource_entry1_focus_out_event + elif focused_widget_name == 'custom_port_entry': + func = self.on_custom_port_entry_focus_out_event + if func(focused_widget, None): + # Error detected in entry, don't change account, re-put cursor on + # previous row + self.select_account(self.current_account) + return True + self.window.set_focus(widget) + + self.check_resend_relog() + + if account: + self.remove_button.set_sensitive(True) + self.rename_button.set_sensitive(True) + else: + self.remove_button.set_sensitive(False) + self.rename_button.set_sensitive(False) + if iter_: + self.current_account = account + if account == gajim.ZEROCONF_ACC_NAME: + self.remove_button.set_sensitive(False) + self.init_account() + self.update_proxy_list() + + def update_proxy_list(self): + if self.current_account: + our_proxy = gajim.config.get_per('accounts', self.current_account, + 'proxy') + else: + our_proxy = '' + + if not our_proxy: + our_proxy = _('None') + proxy_combobox = self.xml.get_object('proxies_combobox1') + model = gtk.ListStore(str) + proxy_combobox.set_model(model) + l = gajim.config.get_per('proxies') + l.insert(0, _('None')) + for i in xrange(len(l)): + model.append([l[i]]) + if our_proxy == l[i]: + proxy_combobox.set_active(i) + + def init_account(self): + if not self.current_account: + self.notebook.set_current_page(0) + return + if gajim.config.get_per('accounts', self.current_account, 'is_zeroconf'): + self.ignore_events = True + self.init_zeroconf_account() + self.ignore_events = False + self.notebook.set_current_page(2) + return + self.ignore_events = True + self.init_normal_account() + self.ignore_events = False + self.notebook.set_current_page(1) + + def init_zeroconf_account(self): + active = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'active') + self.xml.get_object('enable_zeroconf_checkbutton2').set_active(active) + if not gajim.HAVE_ZEROCONF: + self.xml.get_object('enable_zeroconf_checkbutton2').set_sensitive( + False) + self.xml.get_object('zeroconf_notebook').set_sensitive(active) + # General tab + st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'autoconnect') + self.xml.get_object('autoconnect_checkbutton2').set_active(st) + + list_no_log_for = gajim.config.get_per('accounts', + gajim.ZEROCONF_ACC_NAME, 'no_log_for').split() + if gajim.ZEROCONF_ACC_NAME in list_no_log_for: + self.xml.get_object('log_history_checkbutton2').set_active(0) + else: + self.xml.get_object('log_history_checkbutton2').set_active(1) + + st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'sync_with_global_status') + self.xml.get_object('sync_with_global_status_checkbutton2').set_active(st) + + st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'use_custom_host') + self.xml.get_object('custom_port_checkbutton2').set_active(st) + self.xml.get_object('custom_port_entry2').set_sensitive(st) + + st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'custom_port') + if not st: + gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'custom_port', '5298') + st = '5298' + self.xml.get_object('custom_port_entry2').set_text(str(st)) + + # Personal tab + gpg_key_label = self.xml.get_object('gpg_key_label2') + if gajim.ZEROCONF_ACC_NAME in gajim.connections and \ + gajim.connections[gajim.ZEROCONF_ACC_NAME].gpg: + self.xml.get_object('gpg_choose_button2').set_sensitive(True) + self.init_account_gpg() + else: + gpg_key_label.set_text(_('OpenPGP is not usable on this computer')) + self.xml.get_object('gpg_choose_button2').set_sensitive(False) + + for opt in ('first_name', 'last_name', 'jabber_id', 'email'): + st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'zeroconf_' + opt) + self.xml.get_object(opt + '_entry2').set_text(st) + + def init_account_gpg(self): + account = self.current_account + keyid = gajim.config.get_per('accounts', account, 'keyid') + keyname = gajim.config.get_per('accounts', account, 'keyname') + use_gpg_agent = gajim.config.get('use_gpg_agent') + + if account == gajim.ZEROCONF_ACC_NAME: + widget_name_add = '2' + else: + widget_name_add = '1' + + gpg_key_label = self.xml.get_object('gpg_key_label' + widget_name_add) + gpg_name_label = self.xml.get_object('gpg_name_label' + widget_name_add) + use_gpg_agent_checkbutton = self.xml.get_object( + 'use_gpg_agent_checkbutton' + widget_name_add) + + if not keyid: + use_gpg_agent_checkbutton.set_sensitive(False) + gpg_key_label.set_text(_('No key selected')) + gpg_name_label.set_text('') + return + + gpg_key_label.set_text(keyid) + gpg_name_label.set_text(keyname) + use_gpg_agent_checkbutton.set_sensitive(True) + use_gpg_agent_checkbutton.set_active(use_gpg_agent) + + def draw_normal_jid(self): + account = self.current_account + self.ignore_events = True + active = gajim.config.get_per('accounts', account, 'active') + self.xml.get_object('enable_checkbutton1').set_active(active) + self.xml.get_object('normal_notebook1').set_sensitive(active) + if gajim.config.get_per('accounts', account, 'anonymous_auth'): + self.xml.get_object('anonymous_checkbutton1').set_active(True) + self.xml.get_object('jid_label1').set_text(_('Server:')) + save_password = self.xml.get_object('save_password_checkbutton1') + save_password.set_active(False) + save_password.set_sensitive(False) + password_entry = self.xml.get_object('password_entry1') + password_entry.set_text('') + password_entry.set_sensitive(False) + jid = gajim.config.get_per('accounts', account, 'hostname') + else: + self.xml.get_object('anonymous_checkbutton1').set_active(False) + self.xml.get_object('jid_label1').set_text(_('Jabber ID:')) + savepass = gajim.config.get_per('accounts', account, 'savepass') + save_password = self.xml.get_object('save_password_checkbutton1') + save_password.set_sensitive(True) + save_password.set_active(savepass) + password_entry = self.xml.get_object('password_entry1') + if savepass: + passstr = passwords.get_password(account) or '' + password_entry.set_sensitive(True) + else: + passstr = '' + password_entry.set_sensitive(False) + password_entry.set_text(passstr) + + jid = gajim.config.get_per('accounts', account, 'name') \ + + '@' + gajim.config.get_per('accounts', account, 'hostname') + self.xml.get_object('jid_entry1').set_text(jid) + self.ignore_events = False + + def init_normal_account(self): + account = self.current_account + # Account tab + self.draw_normal_jid() + self.xml.get_object('resource_entry1').set_text(gajim.config.get_per( + 'accounts', account, 'resource')) + self.xml.get_object('adjust_priority_with_status_checkbutton1').\ + set_active(gajim.config.get_per('accounts', account, + 'adjust_priority_with_status')) + spinbutton = self.xml.get_object('priority_spinbutton1') + if gajim.config.get('enable_negative_priority'): + spinbutton.set_range(-128, 127) + else: + spinbutton.set_range(0, 127) + spinbutton.set_value(gajim.config.get_per('accounts', account, + 'priority')) + + # Connection tab + use_env_http_proxy = gajim.config.get_per('accounts', account, + 'use_env_http_proxy') + self.xml.get_object('use_env_http_proxy_checkbutton1').set_active( + use_env_http_proxy) + self.xml.get_object('proxy_hbox1').set_sensitive(not use_env_http_proxy) + + warn_when_insecure_ssl = gajim.config.get_per('accounts', account, + 'warn_when_insecure_ssl_connection') + self.xml.get_object('warn_when_insecure_connection_checkbutton1').\ + set_active(warn_when_insecure_ssl) + + self.xml.get_object('send_keepalive_checkbutton1').set_active( + gajim.config.get_per('accounts', account, 'keep_alives_enabled')) + + use_custom_host = gajim.config.get_per('accounts', account, + 'use_custom_host') + self.xml.get_object('custom_host_port_checkbutton1').set_active( + use_custom_host) + custom_host = gajim.config.get_per('accounts', account, 'custom_host') + if not custom_host: + custom_host = gajim.config.get_per('accounts', account, 'hostname') + gajim.config.set_per('accounts', account, 'custom_host', custom_host) + self.xml.get_object('custom_host_entry1').set_text(custom_host) + custom_port = gajim.config.get_per('accounts', account, 'custom_port') + if not custom_port: + custom_port = 5222 + gajim.config.set_per('accounts', account, 'custom_port', custom_port) + self.xml.get_object('custom_port_entry1').set_text(unicode(custom_port)) + + # Personal tab + gpg_key_label = self.xml.get_object('gpg_key_label1') + if gajim.HAVE_GPG: + self.xml.get_object('gpg_choose_button1').set_sensitive(True) + self.init_account_gpg() + else: + gpg_key_label.set_text(_('OpenPGP is not usable on this computer')) + self.xml.get_object('gpg_choose_button1').set_sensitive(False) + + # General tab + self.xml.get_object('autoconnect_checkbutton1').set_active(gajim.config.\ + get_per('accounts', account, 'autoconnect')) + self.xml.get_object('autoreconnect_checkbutton1').set_active(gajim. + config.get_per('accounts', account, 'autoreconnect')) + + list_no_log_for = gajim.config.get_per('accounts', account, + 'no_log_for').split() + if account in list_no_log_for: + self.xml.get_object('log_history_checkbutton1').set_active(False) + else: + self.xml.get_object('log_history_checkbutton1').set_active(True) + + self.xml.get_object('sync_with_global_status_checkbutton1').set_active( + gajim.config.get_per('accounts', account, 'sync_with_global_status')) + self.xml.get_object('use_ft_proxies_checkbutton1').set_active( + gajim.config.get_per('accounts', account, 'use_ft_proxies')) + + def on_add_button_clicked(self, widget): + """ + When add button is clicked: open an account information window + """ + if 'account_creation_wizard' in gajim.interface.instances: + gajim.interface.instances['account_creation_wizard'].window.present() + else: + gajim.interface.instances['account_creation_wizard'] = \ + AccountCreationWizardWindow() + + def on_remove_button_clicked(self, widget): + """ + When delete button is clicked: Remove an account from the listStore and + from the config file + """ + if not self.current_account: + return + account = self.current_account + if len(gajim.events.get_events(account)): + dialogs.ErrorDialog(_('Unread events'), + _('Read all pending events before removing this account.')) + return + + if gajim.config.get_per('accounts', account, 'is_zeroconf'): + # Should never happen as button is insensitive + return + + win_opened = False + if gajim.interface.msg_win_mgr.get_controls(acct = account): + win_opened = True + else: + for key in gajim.interface.instances[account]: + if gajim.interface.instances[account][key] and key != \ + 'remove_account': + win_opened = True + break + # Detect if we have opened windows for this account + def remove(account): + if 'remove_account' in gajim.interface.instances[account]: + gajim.interface.instances[account]['remove_account'].window.\ + present() + else: + gajim.interface.instances[account]['remove_account'] = \ + RemoveAccountWindow(account) + if win_opened: + dialogs.ConfirmationDialog( + _('You have opened chat in account %s') % account, + _('All chat and groupchat windows will be closed. Do you want to ' + 'continue?'), + on_response_ok = (remove, account)) + else: + remove(account) + + def on_rename_button_clicked(self, widget): + if not self.current_account: + return + active = gajim.config.get_per('accounts', self.current_account, 'active') + if active and gajim.connections[self.current_account].connected != 0: + dialogs.ErrorDialog( + _('You are currently connected to the server'), + _('To change the account name, you must be disconnected.')) + return + if len(gajim.events.get_events(self.current_account)): + dialogs.ErrorDialog(_('Unread events'), + _('To change the account name, you must read all pending ' + 'events.')) + return + # Get the new name + def on_renamed(new_name, old_name): + if new_name in gajim.connections: + dialogs.ErrorDialog(_('Account Name Already Used'), + _('This name is already used by another of your accounts. ' + 'Please choose another name.')) + return + if (new_name == ''): + dialogs.ErrorDialog(_('Invalid account name'), + _('Account name cannot be empty.')) + return + if new_name.find(' ') != -1: + dialogs.ErrorDialog(_('Invalid account name'), + _('Account name cannot contain spaces.')) + return + if active: + # update variables + gajim.interface.instances[new_name] = gajim.interface.instances[ + old_name] + gajim.interface.minimized_controls[new_name] = \ + gajim.interface.minimized_controls[old_name] + gajim.nicks[new_name] = gajim.nicks[old_name] + gajim.block_signed_in_notifications[new_name] = \ + gajim.block_signed_in_notifications[old_name] + gajim.groups[new_name] = gajim.groups[old_name] + gajim.gc_connected[new_name] = gajim.gc_connected[old_name] + gajim.automatic_rooms[new_name] = gajim.automatic_rooms[old_name] + gajim.newly_added[new_name] = gajim.newly_added[old_name] + gajim.to_be_removed[new_name] = gajim.to_be_removed[old_name] + gajim.sleeper_state[new_name] = gajim.sleeper_state[old_name] + gajim.encrypted_chats[new_name] = gajim.encrypted_chats[old_name] + gajim.last_message_time[new_name] = \ + gajim.last_message_time[old_name] + gajim.status_before_autoaway[new_name] = \ + gajim.status_before_autoaway[old_name] + gajim.transport_avatar[new_name] = gajim.transport_avatar[old_name] + gajim.gajim_optional_features[new_name] = \ + gajim.gajim_optional_features[old_name] + gajim.caps_hash[new_name] = gajim.caps_hash[old_name] + + gajim.contacts.change_account_name(old_name, new_name) + gajim.events.change_account_name(old_name, new_name) + + # change account variable for chat / gc controls + gajim.interface.msg_win_mgr.change_account_name(old_name, new_name) + # upgrade account variable in opened windows + for kind in ('infos', 'disco', 'gc_config', 'search', + 'online_dialog'): + for j in gajim.interface.instances[new_name][kind]: + gajim.interface.instances[new_name][kind][j].account = \ + new_name + + # ServiceCache object keep old property account + if hasattr(gajim.connections[old_name], 'services_cache'): + gajim.connections[old_name].services_cache.account = new_name + del gajim.interface.instances[old_name] + del gajim.interface.minimized_controls[old_name] + del gajim.nicks[old_name] + del gajim.block_signed_in_notifications[old_name] + del gajim.groups[old_name] + del gajim.gc_connected[old_name] + del gajim.automatic_rooms[old_name] + del gajim.newly_added[old_name] + del gajim.to_be_removed[old_name] + del gajim.sleeper_state[old_name] + del gajim.encrypted_chats[old_name] + del gajim.last_message_time[old_name] + del gajim.status_before_autoaway[old_name] + del gajim.transport_avatar[old_name] + del gajim.gajim_optional_features[old_name] + del gajim.caps_hash[old_name] + gajim.connections[old_name].name = new_name + gajim.connections[new_name] = gajim.connections[old_name] + del gajim.connections[old_name] + gajim.config.add_per('accounts', new_name) + old_config = gajim.config.get_per('accounts', old_name) + for opt in old_config: + gajim.config.set_per('accounts', new_name, opt, old_config[opt][1]) + gajim.config.del_per('accounts', old_name) + if self.current_account == old_name: + self.current_account = new_name + if old_name == gajim.ZEROCONF_ACC_NAME: + gajim.ZEROCONF_ACC_NAME = new_name + # refresh roster + gajim.interface.roster.setup_and_draw_roster() + self.init_accounts() + self.select_account(new_name) + + title = _('Rename Account') + message = _('Enter a new name for account %s') % self.current_account + old_text = self.current_account + dialogs.InputDialog(title, message, old_text, is_modal=False, + ok_handler=(on_renamed, self.current_account)) + + def option_changed(self, option, value): + return gajim.config.get_per('accounts', self.current_account, option) != \ + value + + def on_jid_entry1_focus_out_event(self, widget, event): + if self.ignore_events: + return + jid = widget.get_text() + # check if jid is conform to RFC and stringprep it + try: + jid = helpers.parse_jid(jid) + except helpers.InvalidFormat, s: + if not widget.is_focus(): + pritext = _('Invalid Jabber ID') + dialogs.ErrorDialog(pritext, str(s)) + gobject.idle_add(lambda: widget.grab_focus()) + return True + + jid_splited = jid.split('@', 1) + if len(jid_splited) != 2 and not gajim.config.get_per('accounts', + self.current_account, 'anonymous_auth'): + if not widget.is_focus(): + pritext = _('Invalid Jabber ID') + sectext = _('A Jabber ID must be in the form "user@servername".') + dialogs.ErrorDialog(pritext, sectext) + gobject.idle_add(lambda: widget.grab_focus()) + return True + + + if gajim.config.get_per('accounts', self.current_account, + 'anonymous_auth'): + gajim.config.set_per('accounts', self.current_account, 'hostname', + jid_splited[0]) + if self.option_changed('hostname', jid_splited[0]): + self.need_relogin = True + else: + if self.option_changed('name', jid_splited[0]) or \ + self.option_changed('hostname', jid_splited[1]): + self.need_relogin = True + + gajim.config.set_per('accounts', self.current_account, 'name', + jid_splited[0]) + gajim.config.set_per('accounts', self.current_account, 'hostname', + jid_splited[1]) + + def on_anonymous_checkbutton1_toggled(self, widget): + if self.ignore_events: + return + active = widget.get_active() + gajim.config.set_per('accounts', self.current_account, 'anonymous_auth', + active) + self.draw_normal_jid() + + def on_password_entry1_changed(self, widget): + if self.ignore_events: + return + passwords.save_password(self.current_account, widget.get_text().decode( + 'utf-8')) + + def on_save_password_checkbutton1_toggled(self, widget): + if self.ignore_events: + return + active = widget.get_active() + password_entry = self.xml.get_object('password_entry1') + password_entry.set_sensitive(active) + gajim.config.set_per('accounts', self.current_account, 'savepass', active) + if active: + password = password_entry.get_text() + passwords.save_password(self.current_account, password) + else: + passwords.save_password(self.current_account, '') + + def on_resource_entry1_focus_out_event(self, widget, event): + if self.ignore_events: + return + resource = self.xml.get_object('resource_entry1').get_text().decode( + 'utf-8') + try: + resource = helpers.parse_resource(resource) + except helpers.InvalidFormat, s: + if not widget.is_focus(): + pritext = _('Invalid Jabber ID') + dialogs.ErrorDialog(pritext, str(s)) + gobject.idle_add(lambda: widget.grab_focus()) + return True + + if self.option_changed('resource', resource): + self.need_relogin = True + + gajim.config.set_per('accounts', self.current_account, 'resource', + resource) + + def on_adjust_priority_with_status_checkbutton1_toggled(self, widget): + self.xml.get_object('priority_spinbutton1').set_sensitive( + not widget.get_active()) + self.on_checkbutton_toggled(widget, 'adjust_priority_with_status', + account = self.current_account) + + def on_priority_spinbutton1_value_changed(self, widget): + prio = widget.get_value_as_int() + + if self.option_changed('priority', prio): + self.resend_presence = True + + gajim.config.set_per('accounts', self.current_account, 'priority', prio) + + def on_synchronise_contacts_button1_clicked(self, widget): + try: + dialogs.SynchroniseSelectAccountDialog(self.current_account) + except GajimGeneralException: + # If we showed ErrorDialog, there will not be dialog instance + return + + def on_change_password_button1_clicked(self, widget): + def on_changed(new_password): + if new_password is not None: + gajim.connections[self.current_account].change_password( + new_password) + if self.xml.get_object('save_password_checkbutton1').get_active(): + self.xml.get_object('password_entry1').set_text(new_password) + + try: + dialogs.ChangePasswordDialog(self.current_account, on_changed) + except GajimGeneralException: + # if we showed ErrorDialog, there will not be dialog instance + return + + def on_autoconnect_checkbutton_toggled(self, widget): + if self.ignore_events: + return + self.on_checkbutton_toggled(widget, 'autoconnect', + account=self.current_account) + + def on_autoreconnect_checkbutton_toggled(self, widget): + if self.ignore_events: + return + self.on_checkbutton_toggled(widget, 'autoreconnect', + account=self.current_account) + + def on_log_history_checkbutton_toggled(self, widget): + if self.ignore_events: + return + list_no_log_for = gajim.config.get_per('accounts', self.current_account, + 'no_log_for').split() + if self.current_account in list_no_log_for: + list_no_log_for.remove(self.current_account) + + if not widget.get_active(): + list_no_log_for.append(self.current_account) + gajim.config.set_per('accounts', self.current_account, 'no_log_for', + ' '.join(list_no_log_for)) + + def on_sync_with_global_status_checkbutton_toggled(self, widget): + if self.ignore_events: + return + self.on_checkbutton_toggled(widget, 'sync_with_global_status', + account=self.current_account) + gajim.interface.roster.update_status_combobox() + + def on_use_ft_proxies_checkbutton1_toggled(self, widget): + if self.ignore_events: + return + self.on_checkbutton_toggled(widget, 'use_ft_proxies', + account=self.current_account) + + def on_use_env_http_proxy_checkbutton1_toggled(self, widget): + if self.ignore_events: + return + self.on_checkbutton_toggled(widget, 'use_env_http_proxy', + account=self.current_account) + hbox = self.xml.get_object('proxy_hbox1') + hbox.set_sensitive(not widget.get_active()) + + def on_proxies_combobox1_changed(self, widget): + active = widget.get_active() + proxy = widget.get_model()[active][0].decode('utf-8') + if proxy == _('None'): + proxy = '' + + if self.option_changed('proxy', proxy): + self.need_relogin = True + + gajim.config.set_per('accounts', self.current_account, 'proxy', proxy) + + def on_manage_proxies_button1_clicked(self, widget): + if 'manage_proxies' in gajim.interface.instances: + gajim.interface.instances['manage_proxies'].window.present() + else: + gajim.interface.instances['manage_proxies'] = ManageProxiesWindow() + + def on_warn_when_insecure_connection_checkbutton1_toggled(self, widget): + if self.ignore_events: + return + + self.on_checkbutton_toggled(widget, 'warn_when_insecure_ssl_connection', + account=self.current_account) + + def on_send_keepalive_checkbutton1_toggled(self, widget): + if self.ignore_events: + return + self.on_checkbutton_toggled(widget, 'keep_alives_enabled', + account=self.current_account) + gajim.config.set_per('accounts', self.current_account, + 'ping_alives_enabled', widget.get_active()) + + def on_custom_host_port_checkbutton1_toggled(self, widget): + if self.option_changed('use_custom_host', widget.get_active()): + self.need_relogin = True + + self.on_checkbutton_toggled(widget, 'use_custom_host', + account=self.current_account) + active = widget.get_active() + self.xml.get_object('custom_host_port_hbox1').set_sensitive(active) + + def on_custom_host_entry1_changed(self, widget): + if self.ignore_events: + return + host = widget.get_text().decode('utf-8') + if self.option_changed('custom_host', host): + self.need_relogin = True + gajim.config.set_per('accounts', self.current_account, 'custom_host', + host) + + def on_custom_port_entry_focus_out_event(self, widget, event): + if self.ignore_events: + return + custom_port = widget.get_text() + try: + custom_port = int(custom_port) + except Exception: + if not widget.is_focus(): + dialogs.ErrorDialog(_('Invalid entry'), + _('Custom port must be a port number.')) + gobject.idle_add(lambda: widget.grab_focus()) + return True + if self.option_changed('custom_port', custom_port): + self.need_relogin = True + gajim.config.set_per('accounts', self.current_account, 'custom_port', + custom_port) + + def on_gpg_choose_button_clicked(self, widget, data = None): + if self.current_account in gajim.connections and \ + gajim.connections[self.current_account].gpg: + secret_keys = gajim.connections[self.current_account].\ + ask_gpg_secrete_keys() + + # self.current_account is None and/or gajim.connections is {} + else: + if gajim.HAVE_GPG: + secret_keys = GnuPG.GnuPG().get_secret_keys() + else: + secret_keys = [] + if not secret_keys: + dialogs.ErrorDialog(_('Failed to get secret keys'), + _('There is no OpenPGP secret key available.')) + secret_keys[_('None')] = _('None') + + def on_key_selected(keyID): + if keyID is None: + return + if self.current_account == gajim.ZEROCONF_ACC_NAME: + wiget_name_ext = '2' + else: + wiget_name_ext = '1' + gpg_key_label = self.xml.get_object('gpg_key_label' + wiget_name_ext) + gpg_name_label = self.xml.get_object('gpg_name_label' + wiget_name_ext) + use_gpg_agent_checkbutton = self.xml.get_object( + 'use_gpg_agent_checkbutton' + wiget_name_ext) + if keyID[0] == _('None'): + gpg_key_label.set_text(_('No key selected')) + gpg_name_label.set_text('') + use_gpg_agent_checkbutton.set_sensitive(False) + if self.option_changed('keyid', ''): + self.need_relogin = True + gajim.config.set_per('accounts', self.current_account, 'keyname', + '') + gajim.config.set_per('accounts', self.current_account, 'keyid', '') + else: + gpg_key_label.set_text(keyID[0]) + gpg_name_label.set_text(keyID[1]) + use_gpg_agent_checkbutton.set_sensitive(True) + if self.option_changed('keyid', keyID[0]): + self.need_relogin = True + gajim.config.set_per('accounts', self.current_account, 'keyname', + keyID[1]) + gajim.config.set_per('accounts', self.current_account, 'keyid', + keyID[0]) + + dialogs.ChooseGPGKeyDialog(_('OpenPGP Key Selection'), + _('Choose your OpenPGP key'), secret_keys, on_key_selected) + + def on_use_gpg_agent_checkbutton_toggled(self, widget): + self.on_checkbutton_toggled(widget, 'use_gpg_agent') + + def on_edit_details_button1_clicked(self, widget): + if self.current_account not in gajim.interface.instances: + dialogs.ErrorDialog(_('No such account available'), + _('You must create your account before editing your personal ' + 'information.')) + return + + # show error dialog if account is newly created (not in gajim.connections) + if self.current_account not in gajim.connections or \ + gajim.connections[self.current_account].connected < 2: + dialogs.ErrorDialog(_('You are not connected to the server'), + _('Without a connection, you can not edit your personal information.')) + return + + if not gajim.connections[self.current_account].vcard_supported: + dialogs.ErrorDialog(_("Your server doesn't support Vcard"), + _("Your server can't save your personal information.")) + return + + gajim.interface.edit_own_details(self.current_account) + + def on_checkbutton_toggled(self, widget, config_name, + change_sensitivity_widgets = None, account = None): + if account: + gajim.config.set_per('accounts', account, config_name, + widget.get_active()) + else: + gajim.config.set(config_name, widget.get_active()) + if change_sensitivity_widgets: + for w in change_sensitivity_widgets: + w.set_sensitive(widget.get_active()) + gajim.interface.save_config() + + def on_merge_checkbutton_toggled(self, widget): + self.on_checkbutton_toggled(widget, 'mergeaccounts') + if len(gajim.connections) >= 2: # Do not merge accounts if only one active + gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') + else: + gajim.interface.roster.regroup = False + gajim.interface.roster.setup_and_draw_roster() + + def _disable_account(self, account): + gajim.interface.roster.close_all(account) + if account == gajim.ZEROCONF_ACC_NAME: + gajim.connections[account].disable_account() + del gajim.connections[account] + gajim.interface.save_config() + del gajim.interface.instances[account] + del gajim.interface.minimized_controls[account] + del gajim.nicks[account] + del gajim.block_signed_in_notifications[account] + del gajim.groups[account] + gajim.contacts.remove_account(account) + del gajim.gc_connected[account] + del gajim.automatic_rooms[account] + del gajim.to_be_removed[account] + del gajim.newly_added[account] + del gajim.sleeper_state[account] + del gajim.encrypted_chats[account] + del gajim.last_message_time[account] + del gajim.status_before_autoaway[account] + del gajim.transport_avatar[account] + del gajim.gajim_optional_features[account] + del gajim.caps_hash[account] + if len(gajim.connections) >= 2: + # Do not merge accounts if only one exists + gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') + else: + gajim.interface.roster.regroup = False + gajim.interface.roster.setup_and_draw_roster() + gajim.interface.roster.set_actions_menu_needs_rebuild() + + def _enable_account(self, account): + if account == gajim.ZEROCONF_ACC_NAME: + gajim.connections[account] = connection_zeroconf.ConnectionZeroconf( + account) + if gajim.connections[account].gpg: + self.xml.get_object('gpg_choose_button2').set_sensitive(True) + else: + gajim.connections[account] = common.connection.Connection(account) + if gajim.connections[account].gpg: + self.xml.get_object('gpg_choose_button1').set_sensitive(True) + self.init_account_gpg() + # update variables + gajim.interface.instances[account] = {'infos': {}, + 'disco': {}, 'gc_config': {}, 'search': {}, 'online_dialog': {}} + gajim.interface.minimized_controls[account] = {} + gajim.connections[account].connected = 0 + gajim.groups[account] = {} + gajim.contacts.add_account(account) + gajim.gc_connected[account] = {} + gajim.automatic_rooms[account] = {} + gajim.newly_added[account] = [] + gajim.to_be_removed[account] = [] + if account == gajim.ZEROCONF_ACC_NAME: + gajim.nicks[account] = gajim.ZEROCONF_ACC_NAME + else: + gajim.nicks[account] = gajim.config.get_per('accounts', account, + 'name') + gajim.block_signed_in_notifications[account] = True + gajim.sleeper_state[account] = 'off' + gajim.encrypted_chats[account] = [] + gajim.last_message_time[account] = {} + gajim.status_before_autoaway[account] = '' + gajim.transport_avatar[account] = {} + gajim.gajim_optional_features[account] = [] + gajim.caps_hash[account] = '' + # refresh roster + if len(gajim.connections) >= 2: + # Do not merge accounts if only one exists + gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') + else: + gajim.interface.roster.regroup = False + gajim.interface.roster.setup_and_draw_roster() + gajim.interface.roster.set_actions_menu_needs_rebuild() + gajim.interface.save_config() + + def on_enable_zeroconf_checkbutton2_toggled(self, widget): + # don't do anything if there is an account with the local name but is a + # normal account + if self.ignore_events: + return + if self.current_account in gajim.connections and \ + gajim.connections[self.current_account].connected > 0: + self.ignore_events = True + self.xml.get_object('enable_zeroconf_checkbutton2').set_active(True) + self.ignore_events = False + dialogs.ErrorDialog( + _('You are currently connected to the server'), + _('To disable the account, you must be disconnected.')) + return + if gajim.ZEROCONF_ACC_NAME in gajim.connections and not \ + gajim.connections[gajim.ZEROCONF_ACC_NAME].is_zeroconf: + gajim.connections[gajim.ZEROCONF_ACC_NAME].dispatch('ERROR', + (_('Account Local already exists.'), + _('Please rename or remove it before enabling link-local messaging' + '.'))) + return + + if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \ + and not widget.get_active(): + self.xml.get_object('zeroconf_notebook').set_sensitive(False) + # disable + self._disable_account(gajim.ZEROCONF_ACC_NAME) + + elif not gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, + 'active') and widget.get_active(): + self.xml.get_object('zeroconf_notebook').set_sensitive(True) + # enable (will create new account if not present) + self._enable_account(gajim.ZEROCONF_ACC_NAME) + + self.on_checkbutton_toggled(widget, 'active', + account=gajim.ZEROCONF_ACC_NAME) + + def on_enable_checkbutton1_toggled(self, widget): + if self.ignore_events: + return + if self.current_account in gajim.connections and \ + gajim.connections[self.current_account].connected > 0: + # connecting or connected + self.ignore_events = True + self.xml.get_object('enable_checkbutton1').set_active(True) + self.ignore_events = False + dialogs.ErrorDialog( + _('You are currently connected to the server'), + _('To disable the account, you must be disconnected.')) + return + # add/remove account in roster and all variables + if widget.get_active(): + # enable + self._enable_account(self.current_account) + else: + # disable + self._disable_account(self.current_account) + self.on_checkbutton_toggled(widget, 'active', + account=self.current_account, change_sensitivity_widgets=[ + self.xml.get_object('normal_notebook1')]) + + def on_custom_port_checkbutton2_toggled(self, widget): + self.xml.get_object('custom_port_entry2').set_sensitive( + widget.get_active()) + self.on_checkbutton_toggled(widget, 'use_custom_host', + account = self.current_account) + if not widget.get_active(): + self.xml.get_object('custom_port_entry2').set_text('5298') + + def on_first_name_entry2_changed(self, widget): + if self.ignore_events: + return + name = widget.get_text().decode('utf-8') + if self.option_changed('zeroconf_first_name', name): + self.need_relogin = True + gajim.config.set_per('accounts', self.current_account, + 'zeroconf_first_name', name) + + def on_last_name_entry2_changed(self, widget): + if self.ignore_events: + return + name = widget.get_text().decode('utf-8') + if self.option_changed('zeroconf_last_name', name): + self.need_relogin = True + gajim.config.set_per('accounts', self.current_account, + 'zeroconf_last_name', name) + + def on_jabber_id_entry2_changed(self, widget): + if self.ignore_events: + return + id_ = widget.get_text().decode('utf-8') + if self.option_changed('zeroconf_jabber_id', id_): + self.need_relogin = True + gajim.config.set_per('accounts', self.current_account, + 'zeroconf_jabber_id', id_) + + def on_email_entry2_changed(self, widget): + if self.ignore_events: + return + email = widget.get_text().decode('utf-8') + if self.option_changed('zeroconf_email', email): + self.need_relogin = True + gajim.config.set_per('accounts', self.current_account, + 'zeroconf_email', email) class FakeDataForm(gtk.Table, object): - """ - Class for forms that are in XML format value1 infos in a - table {entry1: value1} - """ + """ + Class for forms that are in XML format value1 infos in a + table {entry1: value1} + """ - def __init__(self, infos): - gtk.Table.__init__(self) - self.infos = infos - self.entries = {} - self._draw_table() + def __init__(self, infos): + gtk.Table.__init__(self) + self.infos = infos + self.entries = {} + self._draw_table() - def _draw_table(self): - """ - Draw the table - """ - nbrow = 0 - if 'instructions' in self.infos: - nbrow = 1 - self.resize(rows = nbrow, columns = 2) - label = gtk.Label(self.infos['instructions']) - self.attach(label, 0, 2, 0, 1, 0, 0, 0, 0) - for name in self.infos.keys(): - if name in ('key', 'instructions', 'x', 'registered'): - continue - if not name: - continue + def _draw_table(self): + """ + Draw the table + """ + nbrow = 0 + if 'instructions' in self.infos: + nbrow = 1 + self.resize(rows = nbrow, columns = 2) + label = gtk.Label(self.infos['instructions']) + self.attach(label, 0, 2, 0, 1, 0, 0, 0, 0) + for name in self.infos.keys(): + if name in ('key', 'instructions', 'x', 'registered'): + continue + if not name: + continue - nbrow = nbrow + 1 - self.resize(rows = nbrow, columns = 2) - label = gtk.Label(name.capitalize() + ':') - self.attach(label, 0, 1, nbrow - 1, nbrow, 0, 0, 0, 0) - entry = gtk.Entry() - entry.set_activates_default(True) - if self.infos[name]: - entry.set_text(self.infos[name]) - if name == 'password': - entry.set_visibility(False) - self.attach(entry, 1, 2, nbrow - 1, nbrow, 0, 0, 0, 0) - self.entries[name] = entry - if nbrow == 1: - entry.grab_focus() + nbrow = nbrow + 1 + self.resize(rows = nbrow, columns = 2) + label = gtk.Label(name.capitalize() + ':') + self.attach(label, 0, 1, nbrow - 1, nbrow, 0, 0, 0, 0) + entry = gtk.Entry() + entry.set_activates_default(True) + if self.infos[name]: + entry.set_text(self.infos[name]) + if name == 'password': + entry.set_visibility(False) + self.attach(entry, 1, 2, nbrow - 1, nbrow, 0, 0, 0, 0) + self.entries[name] = entry + if nbrow == 1: + entry.grab_focus() - def get_infos(self): - for name in self.entries.keys(): - self.infos[name] = self.entries[name].get_text().decode('utf-8') - return self.infos + def get_infos(self): + for name in self.entries.keys(): + self.infos[name] = self.entries[name].get_text().decode('utf-8') + return self.infos class ServiceRegistrationWindow: - """ - Class for Service registration window. Window that appears when we want to - subscribe to a service if is_form we use dataforms_widget else we use - service_registarion_window - """ - def __init__(self, service, infos, account, is_form): - self.service = service - self.account = account - self.is_form = is_form - self.xml = gtkgui_helpers.get_gtk_builder('service_registration_window.ui') - self.window = self.xml.get_object('service_registration_window') - self.window.set_transient_for(gajim.interface.roster.window) - if self.is_form: - dataform = dataforms.ExtendForm(node = infos) - self.data_form_widget = dataforms_widget.DataFormWidget(dataform) - if self.data_form_widget.title: - self.window.set_title('%s - Gajim' % self.data_form_widget.title) - table = self.xml.get_object('table') - table.attach(self.data_form_widget, 0, 2, 0, 1) - else: - if 'registered' in infos: - self.window.set_title(_('Edit %s') % service) - else: - self.window.set_title(_('Register to %s') % service) - self.data_form_widget = FakeDataForm(infos) - table = self.xml.get_object('table') - table.attach(self.data_form_widget, 0, 2, 0, 1) + """ + Class for Service registration window. Window that appears when we want to + subscribe to a service if is_form we use dataforms_widget else we use + service_registarion_window + """ + def __init__(self, service, infos, account, is_form): + self.service = service + self.account = account + self.is_form = is_form + self.xml = gtkgui_helpers.get_gtk_builder('service_registration_window.ui') + self.window = self.xml.get_object('service_registration_window') + self.window.set_transient_for(gajim.interface.roster.window) + if self.is_form: + dataform = dataforms.ExtendForm(node = infos) + self.data_form_widget = dataforms_widget.DataFormWidget(dataform) + if self.data_form_widget.title: + self.window.set_title('%s - Gajim' % self.data_form_widget.title) + table = self.xml.get_object('table') + table.attach(self.data_form_widget, 0, 2, 0, 1) + else: + if 'registered' in infos: + self.window.set_title(_('Edit %s') % service) + else: + self.window.set_title(_('Register to %s') % service) + self.data_form_widget = FakeDataForm(infos) + table = self.xml.get_object('table') + table.attach(self.data_form_widget, 0, 2, 0, 1) - self.xml.connect_signals(self) - self.window.show_all() + self.xml.connect_signals(self) + self.window.show_all() - def on_cancel_button_clicked(self, widget): - self.window.destroy() + def on_cancel_button_clicked(self, widget): + self.window.destroy() - def on_ok_button_clicked(self, widget): - # send registration info to the core - if self.is_form: - form = self.data_form_widget.data_form - gajim.connections[self.account].register_agent(self.service, - form, True) # True is for is_form - else: - infos = self.data_form_widget.get_infos() - if 'instructions' in infos: - del infos['instructions'] - if 'registered' in infos: - del infos['registered'] - gajim.connections[self.account].register_agent(self.service, infos) + def on_ok_button_clicked(self, widget): + # send registration info to the core + if self.is_form: + form = self.data_form_widget.data_form + gajim.connections[self.account].register_agent(self.service, + form, True) # True is for is_form + else: + infos = self.data_form_widget.get_infos() + if 'instructions' in infos: + del infos['instructions'] + if 'registered' in infos: + del infos['registered'] + gajim.connections[self.account].register_agent(self.service, infos) - self.window.destroy() + self.window.destroy() class GroupchatConfigWindow: - def __init__(self, account, room_jid, form = None): - self.account = account - self.room_jid = room_jid - self.form = form - self.remove_button = {} - self.affiliation_treeview = {} - self.start_users_dict = {} # list at the beginning - self.affiliation_labels = {'outcast': _('Ban List'), - 'member': _('Member List'), - 'owner': _('Owner List'), - 'admin':_('Administrator List')} + def __init__(self, account, room_jid, form = None): + self.account = account + self.room_jid = room_jid + self.form = form + self.remove_button = {} + self.affiliation_treeview = {} + self.start_users_dict = {} # list at the beginning + self.affiliation_labels = {'outcast': _('Ban List'), + 'member': _('Member List'), + 'owner': _('Owner List'), + 'admin':_('Administrator List')} - self.xml = gtkgui_helpers.get_gtk_builder('data_form_window.ui', 'data_form_window') - self.window = self.xml.get_object('data_form_window') - self.window.set_transient_for(gajim.interface.roster.window) + self.xml = gtkgui_helpers.get_gtk_builder('data_form_window.ui', 'data_form_window') + self.window = self.xml.get_object('data_form_window') + self.window.set_transient_for(gajim.interface.roster.window) - if self.form: - config_vbox = self.xml.get_object('config_vbox') - dataform = dataforms.ExtendForm(node = self.form) - self.data_form_widget = dataforms_widget.DataFormWidget(dataform) - # hide scrollbar of this data_form_widget, we already have in this - # widget - sw = self.data_form_widget.xml.get_object('single_form_scrolledwindow') - sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER) + if self.form: + config_vbox = self.xml.get_object('config_vbox') + dataform = dataforms.ExtendForm(node = self.form) + self.data_form_widget = dataforms_widget.DataFormWidget(dataform) + # hide scrollbar of this data_form_widget, we already have in this + # widget + sw = self.data_form_widget.xml.get_object('single_form_scrolledwindow') + sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER) - self.data_form_widget.show() - config_vbox.pack_start(self.data_form_widget) + self.data_form_widget.show() + config_vbox.pack_start(self.data_form_widget) - # Draw the edit affiliation list things - add_on_vbox = self.xml.get_object('add_on_vbox') + # Draw the edit affiliation list things + add_on_vbox = self.xml.get_object('add_on_vbox') - for affiliation in self.affiliation_labels.keys(): - self.start_users_dict[affiliation] = {} - hbox = gtk.HBox(spacing = 5) - add_on_vbox.pack_start(hbox, False) + for affiliation in self.affiliation_labels.keys(): + self.start_users_dict[affiliation] = {} + hbox = gtk.HBox(spacing = 5) + add_on_vbox.pack_start(hbox, False) - label = gtk.Label(self.affiliation_labels[affiliation]) - hbox.pack_start(label, False) + label = gtk.Label(self.affiliation_labels[affiliation]) + hbox.pack_start(label, False) - bb = gtk.HButtonBox() - bb.set_layout(gtk.BUTTONBOX_END) - bb.set_spacing(5) - hbox.pack_start(bb) - add_button = gtk.Button(stock = gtk.STOCK_ADD) - add_button.connect('clicked', self.on_add_button_clicked, affiliation) - bb.pack_start(add_button) - self.remove_button[affiliation] = gtk.Button(stock = gtk.STOCK_REMOVE) - self.remove_button[affiliation].set_sensitive(False) - self.remove_button[affiliation].connect('clicked', - self.on_remove_button_clicked, affiliation) - bb.pack_start(self.remove_button[affiliation]) + bb = gtk.HButtonBox() + bb.set_layout(gtk.BUTTONBOX_END) + bb.set_spacing(5) + hbox.pack_start(bb) + add_button = gtk.Button(stock = gtk.STOCK_ADD) + add_button.connect('clicked', self.on_add_button_clicked, affiliation) + bb.pack_start(add_button) + self.remove_button[affiliation] = gtk.Button(stock = gtk.STOCK_REMOVE) + self.remove_button[affiliation].set_sensitive(False) + self.remove_button[affiliation].connect('clicked', + self.on_remove_button_clicked, affiliation) + bb.pack_start(self.remove_button[affiliation]) - liststore = gtk.ListStore(str, str, str, str) # Jid, reason, nick, role - self.affiliation_treeview[affiliation] = gtk.TreeView(liststore) - self.affiliation_treeview[affiliation].get_selection().set_mode( - gtk.SELECTION_MULTIPLE) - self.affiliation_treeview[affiliation].connect('cursor-changed', - self.on_affiliation_treeview_cursor_changed, affiliation) - renderer = gtk.CellRendererText() - col = gtk.TreeViewColumn(_('JID'), renderer) - col.add_attribute(renderer, 'text', 0) - col.set_resizable(True) - col.set_sort_column_id(0) - self.affiliation_treeview[affiliation].append_column(col) + liststore = gtk.ListStore(str, str, str, str) # Jid, reason, nick, role + self.affiliation_treeview[affiliation] = gtk.TreeView(liststore) + self.affiliation_treeview[affiliation].get_selection().set_mode( + gtk.SELECTION_MULTIPLE) + self.affiliation_treeview[affiliation].connect('cursor-changed', + self.on_affiliation_treeview_cursor_changed, affiliation) + renderer = gtk.CellRendererText() + col = gtk.TreeViewColumn(_('JID'), renderer) + col.add_attribute(renderer, 'text', 0) + col.set_resizable(True) + col.set_sort_column_id(0) + self.affiliation_treeview[affiliation].append_column(col) - if affiliation == 'outcast': - renderer = gtk.CellRendererText() - renderer.set_property('editable', True) - renderer.connect('edited', self.on_cell_edited) - col = gtk.TreeViewColumn(_('Reason'), renderer) - col.add_attribute(renderer, 'text', 1) - col.set_resizable(True) - col.set_sort_column_id(1) - self.affiliation_treeview[affiliation].append_column(col) - elif affiliation == 'member': - renderer = gtk.CellRendererText() - col = gtk.TreeViewColumn(_('Nick'), renderer) - col.add_attribute(renderer, 'text', 2) - col.set_resizable(True) - col.set_sort_column_id(2) - self.affiliation_treeview[affiliation].append_column(col) - renderer = gtk.CellRendererText() - col = gtk.TreeViewColumn(_('Role'), renderer) - col.add_attribute(renderer, 'text', 3) - col.set_resizable(True) - col.set_sort_column_id(3) - self.affiliation_treeview[affiliation].append_column(col) + if affiliation == 'outcast': + renderer = gtk.CellRendererText() + renderer.set_property('editable', True) + renderer.connect('edited', self.on_cell_edited) + col = gtk.TreeViewColumn(_('Reason'), renderer) + col.add_attribute(renderer, 'text', 1) + col.set_resizable(True) + col.set_sort_column_id(1) + self.affiliation_treeview[affiliation].append_column(col) + elif affiliation == 'member': + renderer = gtk.CellRendererText() + col = gtk.TreeViewColumn(_('Nick'), renderer) + col.add_attribute(renderer, 'text', 2) + col.set_resizable(True) + col.set_sort_column_id(2) + self.affiliation_treeview[affiliation].append_column(col) + renderer = gtk.CellRendererText() + col = gtk.TreeViewColumn(_('Role'), renderer) + col.add_attribute(renderer, 'text', 3) + col.set_resizable(True) + col.set_sort_column_id(3) + self.affiliation_treeview[affiliation].append_column(col) - sw = gtk.ScrolledWindow() - sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_NEVER) - sw.add(self.affiliation_treeview[affiliation]) - add_on_vbox.pack_start(sw) - gajim.connections[self.account].get_affiliation_list(self.room_jid, - affiliation) + sw = gtk.ScrolledWindow() + sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_NEVER) + sw.add(self.affiliation_treeview[affiliation]) + add_on_vbox.pack_start(sw) + gajim.connections[self.account].get_affiliation_list(self.room_jid, + affiliation) - self.xml.connect_signals(self) - self.window.show_all() + self.xml.connect_signals(self) + self.window.show_all() - def on_cancel_button_clicked(self, widget): - self.window.destroy() + def on_cancel_button_clicked(self, widget): + self.window.destroy() - def on_cell_edited(self, cell, path, new_text): - model = self.affiliation_treeview['outcast'].get_model() - new_text = new_text.decode('utf-8') - iter_ = model.get_iter(path) - model[iter_][1] = new_text + def on_cell_edited(self, cell, path, new_text): + model = self.affiliation_treeview['outcast'].get_model() + new_text = new_text.decode('utf-8') + iter_ = model.get_iter(path) + model[iter_][1] = new_text - def on_add_button_clicked(self, widget, affiliation): - if affiliation == 'outcast': - title = _('Banning...') - #You can move '\n' before user@domain if that line is TOO BIG - prompt = _('Whom do you want to ban?\n\n') - elif affiliation == 'member': - title = _('Adding Member...') - prompt = _('Whom do you want to make a member?\n\n') - elif affiliation == 'owner': - title = _('Adding Owner...') - prompt = _('Whom do you want to make an owner?\n\n') - else: - title = _('Adding Administrator...') - prompt = _('Whom do you want to make an administrator?\n\n') - prompt += _('Can be one of the following:\n' - '1. user@domain/resource (only that resource matches).\n' - '2. user@domain (any resource matches).\n' - '3. domain/resource (only that resource matches).\n' - '4. domain (the domain itself matches, as does any user@domain,\n' - 'domain/resource, or address containing a subdomain).') + def on_add_button_clicked(self, widget, affiliation): + if affiliation == 'outcast': + title = _('Banning...') + #You can move '\n' before user@domain if that line is TOO BIG + prompt = _('Whom do you want to ban?\n\n') + elif affiliation == 'member': + title = _('Adding Member...') + prompt = _('Whom do you want to make a member?\n\n') + elif affiliation == 'owner': + title = _('Adding Owner...') + prompt = _('Whom do you want to make an owner?\n\n') + else: + title = _('Adding Administrator...') + prompt = _('Whom do you want to make an administrator?\n\n') + prompt += _('Can be one of the following:\n' + '1. user@domain/resource (only that resource matches).\n' + '2. user@domain (any resource matches).\n' + '3. domain/resource (only that resource matches).\n' + '4. domain (the domain itself matches, as does any user@domain,\n' + 'domain/resource, or address containing a subdomain).') - def on_ok(jid): - if not jid: - return - model = self.affiliation_treeview[affiliation].get_model() - model.append((jid,'', '', '')) - dialogs.InputDialog(title, prompt, ok_handler=on_ok) + def on_ok(jid): + if not jid: + return + model = self.affiliation_treeview[affiliation].get_model() + model.append((jid, '', '', '')) + dialogs.InputDialog(title, prompt, ok_handler=on_ok) - def on_remove_button_clicked(self, widget, affiliation): - selection = self.affiliation_treeview[affiliation].get_selection() - model, paths = selection.get_selected_rows() - row_refs = [] - for path in paths: - row_refs.append(gtk.TreeRowReference(model, path)) - for row_ref in row_refs: - path = row_ref.get_path() - iter_ = model.get_iter(path) - jid = model[iter_][0] - model.remove(iter_) - self.remove_button[affiliation].set_sensitive(False) + def on_remove_button_clicked(self, widget, affiliation): + selection = self.affiliation_treeview[affiliation].get_selection() + model, paths = selection.get_selected_rows() + row_refs = [] + for path in paths: + row_refs.append(gtk.TreeRowReference(model, path)) + for row_ref in row_refs: + path = row_ref.get_path() + iter_ = model.get_iter(path) + jid = model[iter_][0] + model.remove(iter_) + self.remove_button[affiliation].set_sensitive(False) - def on_affiliation_treeview_cursor_changed(self, widget, affiliation): - self.remove_button[affiliation].set_sensitive(True) + def on_affiliation_treeview_cursor_changed(self, widget, affiliation): + self.remove_button[affiliation].set_sensitive(True) - def affiliation_list_received(self, users_dict): - """ - Fill the affiliation treeview - """ - for jid in users_dict: - affiliation = users_dict[jid]['affiliation'] - if affiliation not in self.affiliation_labels.keys(): - # Unknown affiliation or 'none' affiliation, do not show it - continue - self.start_users_dict[affiliation][jid] = users_dict[jid] - tv = self.affiliation_treeview[affiliation] - model = tv.get_model() - reason = users_dict[jid].get('reason', '') - nick = users_dict[jid].get('nick', '') - role = users_dict[jid].get('role', '') - model.append((jid, reason, nick, role)) + def affiliation_list_received(self, users_dict): + """ + Fill the affiliation treeview + """ + for jid in users_dict: + affiliation = users_dict[jid]['affiliation'] + if affiliation not in self.affiliation_labels.keys(): + # Unknown affiliation or 'none' affiliation, do not show it + continue + self.start_users_dict[affiliation][jid] = users_dict[jid] + tv = self.affiliation_treeview[affiliation] + model = tv.get_model() + reason = users_dict[jid].get('reason', '') + nick = users_dict[jid].get('nick', '') + role = users_dict[jid].get('role', '') + model.append((jid, reason, nick, role)) - def on_data_form_window_destroy(self, widget): - del gajim.interface.instances[self.account]['gc_config'][self.room_jid] + def on_data_form_window_destroy(self, widget): + del gajim.interface.instances[self.account]['gc_config'][self.room_jid] - def on_ok_button_clicked(self, widget): - if self.form: - form = self.data_form_widget.data_form - gajim.connections[self.account].send_gc_config(self.room_jid, form) - for affiliation in self.affiliation_labels.keys(): - users_dict = {} - actual_jid_list = [] - model = self.affiliation_treeview[affiliation].get_model() - iter_ = model.get_iter_first() - # add new jid - while iter_: - jid = model[iter_][0].decode('utf-8') - actual_jid_list.append(jid) - if jid not in self.start_users_dict[affiliation] or \ - (affiliation == 'outcast' and 'reason' in self.start_users_dict[affiliation]\ - [jid] and self.start_users_dict[affiliation][jid]\ - ['reason'] != model[iter_][1].decode('utf-8')): - users_dict[jid] = {'affiliation': affiliation} - if affiliation == 'outcast': - users_dict[jid]['reason'] = model[iter_][1].decode('utf-8') - iter_ = model.iter_next(iter_) - # remove removed one - for jid in self.start_users_dict[affiliation]: - if jid not in actual_jid_list: - users_dict[jid] = {'affiliation': 'none'} - if users_dict: - gajim.connections[self.account].send_gc_affiliation_list( - self.room_jid, users_dict) - self.window.destroy() + def on_ok_button_clicked(self, widget): + if self.form: + form = self.data_form_widget.data_form + gajim.connections[self.account].send_gc_config(self.room_jid, form) + for affiliation in self.affiliation_labels.keys(): + users_dict = {} + actual_jid_list = [] + model = self.affiliation_treeview[affiliation].get_model() + iter_ = model.get_iter_first() + # add new jid + while iter_: + jid = model[iter_][0].decode('utf-8') + actual_jid_list.append(jid) + if jid not in self.start_users_dict[affiliation] or \ + (affiliation == 'outcast' and 'reason' in self.start_users_dict[affiliation]\ + [jid] and self.start_users_dict[affiliation][jid]\ + ['reason'] != model[iter_][1].decode('utf-8')): + users_dict[jid] = {'affiliation': affiliation} + if affiliation == 'outcast': + users_dict[jid]['reason'] = model[iter_][1].decode('utf-8') + iter_ = model.iter_next(iter_) + # remove removed one + for jid in self.start_users_dict[affiliation]: + if jid not in actual_jid_list: + users_dict[jid] = {'affiliation': 'none'} + if users_dict: + gajim.connections[self.account].send_gc_affiliation_list( + self.room_jid, users_dict) + self.window.destroy() #---------- RemoveAccountWindow class -------------# class RemoveAccountWindow: - """ - Ask for removing from gajim only or from gajim and server too and do - removing of the account given - """ + """ + Ask for removing from gajim only or from gajim and server too and do + removing of the account given + """ - def on_remove_account_window_destroy(self, widget): - if self.account in gajim.interface.instances: - del gajim.interface.instances[self.account]['remove_account'] + def on_remove_account_window_destroy(self, widget): + if self.account in gajim.interface.instances: + del gajim.interface.instances[self.account]['remove_account'] - def on_cancel_button_clicked(self, widget): - self.window.destroy() + def on_cancel_button_clicked(self, widget): + self.window.destroy() - def __init__(self, account): - self.account = account - xml = gtkgui_helpers.get_gtk_builder('remove_account_window.ui') - self.window = xml.get_object('remove_account_window') - self.window.set_transient_for(gajim.interface.roster.window) - self.remove_and_unregister_radiobutton = xml.get_object( - 'remove_and_unregister_radiobutton') - self.window.set_title(_('Removing %s account') % self.account) - xml.connect_signals(self) - self.window.show_all() + def __init__(self, account): + self.account = account + xml = gtkgui_helpers.get_gtk_builder('remove_account_window.ui') + self.window = xml.get_object('remove_account_window') + self.window.set_transient_for(gajim.interface.roster.window) + self.remove_and_unregister_radiobutton = xml.get_object( + 'remove_and_unregister_radiobutton') + self.window.set_title(_('Removing %s account') % self.account) + xml.connect_signals(self) + self.window.show_all() - def on_remove_button_clicked(self, widget): - def remove(): - if gajim.connections[self.account].connected and \ - not self.remove_and_unregister_radiobutton.get_active(): - # change status to offline only if we will not remove this JID from - # server - gajim.connections[self.account].change_status('offline', 'offline') - if self.remove_and_unregister_radiobutton.get_active(): - if not gajim.connections[self.account].password: - def on_ok(passphrase, checked): - if passphrase == -1: - # We don't remove account cause we canceled pw window - return - gajim.connections[self.account].password = passphrase - gajim.connections[self.account].unregister_account( - self._on_remove_success) + def on_remove_button_clicked(self, widget): + def remove(): + if gajim.connections[self.account].connected and \ + not self.remove_and_unregister_radiobutton.get_active(): + # change status to offline only if we will not remove this JID from + # server + gajim.connections[self.account].change_status('offline', 'offline') + if self.remove_and_unregister_radiobutton.get_active(): + if not gajim.connections[self.account].password: + def on_ok(passphrase, checked): + if passphrase == -1: + # We don't remove account cause we canceled pw window + return + gajim.connections[self.account].password = passphrase + gajim.connections[self.account].unregister_account( + self._on_remove_success) - dialogs.PassphraseDialog( - _('Password Required'), - _('Enter your password for account %s') % self.account, - _('Save password'), ok_handler=on_ok) - return - gajim.connections[self.account].unregister_account( - self._on_remove_success) - else: - self._on_remove_success(True) + dialogs.PassphraseDialog( + _('Password Required'), + _('Enter your password for account %s') % self.account, + _('Save password'), ok_handler=on_ok) + return + gajim.connections[self.account].unregister_account( + self._on_remove_success) + else: + self._on_remove_success(True) - if gajim.connections[self.account].connected: - dialogs.ConfirmationDialog( - _('Account "%s" is connected to the server') % self.account, - _('If you remove it, the connection will be lost.'), - on_response_ok=remove) - else: - remove() + if gajim.connections[self.account].connected: + dialogs.ConfirmationDialog( + _('Account "%s" is connected to the server') % self.account, + _('If you remove it, the connection will be lost.'), + on_response_ok=remove) + else: + remove() - def on_remove_responce_ok(self, is_checked): - if is_checked[0]: - self._on_remove_success(True) + def on_remove_responce_ok(self, is_checked): + if is_checked[0]: + self._on_remove_success(True) - def _on_remove_success(self, res): - # action of unregistration has failed, we don't remove the account - # Error message is send by connect_and_auth() - if not res: - confirmation_check = dialogs.ConfirmationDialogDoubleRadio( - _('Connection to server %s failed') % self.account, - _('What would you like to do?'), - _('Remove only from Gajim'), - _('Don\'t remove anything. I\'ll try again later'), - on_response_ok=self.on_remove_responce_ok, is_modal=False) - return - # Close all opened windows - gajim.interface.roster.close_all(self.account, force = True) - gajim.connections[self.account].disconnect(on_purpose = True) - del gajim.connections[self.account] - gajim.logger.remove_roster(gajim.get_jid_from_account(self.account)) - gajim.config.del_per('accounts', self.account) - gajim.interface.save_config() - del gajim.interface.instances[self.account] - del gajim.interface.minimized_controls[self.account] - del gajim.nicks[self.account] - del gajim.block_signed_in_notifications[self.account] - del gajim.groups[self.account] - gajim.contacts.remove_account(self.account) - del gajim.gc_connected[self.account] - del gajim.automatic_rooms[self.account] - del gajim.to_be_removed[self.account] - del gajim.newly_added[self.account] - del gajim.sleeper_state[self.account] - del gajim.encrypted_chats[self.account] - del gajim.last_message_time[self.account] - del gajim.status_before_autoaway[self.account] - del gajim.transport_avatar[self.account] - del gajim.gajim_optional_features[self.account] - del gajim.caps_hash[self.account] - if len(gajim.connections) >= 2: # Do not merge accounts if only one exists - gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') - else: - gajim.interface.roster.regroup = False - gajim.interface.roster.setup_and_draw_roster() - gajim.interface.roster.set_actions_menu_needs_rebuild() - if 'accounts' in gajim.interface.instances: - gajim.interface.instances['accounts'].init_accounts() - gajim.interface.instances['accounts'].init_account() - self.window.destroy() + def _on_remove_success(self, res): + # action of unregistration has failed, we don't remove the account + # Error message is send by connect_and_auth() + if not res: + confirmation_check = dialogs.ConfirmationDialogDoubleRadio( + _('Connection to server %s failed') % self.account, + _('What would you like to do?'), + _('Remove only from Gajim'), + _('Don\'t remove anything. I\'ll try again later'), + on_response_ok=self.on_remove_responce_ok, is_modal=False) + return + # Close all opened windows + gajim.interface.roster.close_all(self.account, force = True) + gajim.connections[self.account].disconnect(on_purpose = True) + del gajim.connections[self.account] + gajim.logger.remove_roster(gajim.get_jid_from_account(self.account)) + gajim.config.del_per('accounts', self.account) + gajim.interface.save_config() + del gajim.interface.instances[self.account] + del gajim.interface.minimized_controls[self.account] + del gajim.nicks[self.account] + del gajim.block_signed_in_notifications[self.account] + del gajim.groups[self.account] + gajim.contacts.remove_account(self.account) + del gajim.gc_connected[self.account] + del gajim.automatic_rooms[self.account] + del gajim.to_be_removed[self.account] + del gajim.newly_added[self.account] + del gajim.sleeper_state[self.account] + del gajim.encrypted_chats[self.account] + del gajim.last_message_time[self.account] + del gajim.status_before_autoaway[self.account] + del gajim.transport_avatar[self.account] + del gajim.gajim_optional_features[self.account] + del gajim.caps_hash[self.account] + if len(gajim.connections) >= 2: # Do not merge accounts if only one exists + gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') + else: + gajim.interface.roster.regroup = False + gajim.interface.roster.setup_and_draw_roster() + gajim.interface.roster.set_actions_menu_needs_rebuild() + if 'accounts' in gajim.interface.instances: + gajim.interface.instances['accounts'].init_accounts() + gajim.interface.instances['accounts'].init_account() + self.window.destroy() #---------- ManageBookmarksWindow class -------------# class ManageBookmarksWindow: - def __init__(self): - self.xml = gtkgui_helpers.get_gtk_builder('manage_bookmarks_window.ui') - self.window = self.xml.get_object('manage_bookmarks_window') - self.window.set_transient_for(gajim.interface.roster.window) + def __init__(self): + self.xml = gtkgui_helpers.get_gtk_builder('manage_bookmarks_window.ui') + self.window = self.xml.get_object('manage_bookmarks_window') + self.window.set_transient_for(gajim.interface.roster.window) - # Account-JID, RoomName, Room-JID, Autojoin, Minimize, Passowrd, Nick, - # Show_Status - self.treestore = gtk.TreeStore(str, str, str, bool, bool, str, str, str) - self.treestore.set_sort_column_id(1, gtk.SORT_ASCENDING) + # Account-JID, RoomName, Room-JID, Autojoin, Minimize, Passowrd, Nick, + # Show_Status + self.treestore = gtk.TreeStore(str, str, str, bool, bool, str, str, str) + self.treestore.set_sort_column_id(1, gtk.SORT_ASCENDING) - # Store bookmarks in treeview. - for account in gajim.connections: - if gajim.connections[account].connected <= 1: - continue - if gajim.connections[account].is_zeroconf: - continue - if not gajim.connections[account].private_storage_supported: - continue - iter_ = self.treestore.append(None, [None, account, None, None, - None, None, None, None]) + # Store bookmarks in treeview. + for account in gajim.connections: + if gajim.connections[account].connected <= 1: + continue + if gajim.connections[account].is_zeroconf: + continue + if not gajim.connections[account].private_storage_supported: + continue + iter_ = self.treestore.append(None, [None, account, None, None, + None, None, None, None]) - for bookmark in gajim.connections[account].bookmarks: - if bookmark['name'] == '': - # No name was given for this bookmark. - # Use the first part of JID instead... - name = bookmark['jid'].split("@")[0] - bookmark['name'] = name + for bookmark in gajim.connections[account].bookmarks: + if bookmark['name'] == '': + # No name was given for this bookmark. + # Use the first part of JID instead... + name = bookmark['jid'].split("@")[0] + bookmark['name'] = name - # make '1', '0', 'true', 'false' (or other) to True/False - autojoin = helpers.from_xs_boolean_to_python_boolean( - bookmark['autojoin']) + # make '1', '0', 'true', 'false' (or other) to True/False + autojoin = helpers.from_xs_boolean_to_python_boolean( + bookmark['autojoin']) - minimize = helpers.from_xs_boolean_to_python_boolean( - bookmark['minimize']) + minimize = helpers.from_xs_boolean_to_python_boolean( + bookmark['minimize']) - print_status = bookmark.get('print_status', '') - if print_status not in ('', 'all', 'in_and_out', 'none'): - print_status = '' - self.treestore.append(iter_, [ - account, - bookmark['name'], - bookmark['jid'], - autojoin, - minimize, - bookmark['password'], - bookmark['nick'], - print_status ]) + print_status = bookmark.get('print_status', '') + if print_status not in ('', 'all', 'in_and_out', 'none'): + print_status = '' + self.treestore.append(iter_, [ + account, + bookmark['name'], + bookmark['jid'], + autojoin, + minimize, + bookmark['password'], + bookmark['nick'], + print_status ]) - self.print_status_combobox = self.xml.get_object('print_status_combobox') - model = gtk.ListStore(str, str) + self.print_status_combobox = self.xml.get_object('print_status_combobox') + model = gtk.ListStore(str, str) - self.option_list = {'': _('Default'), 'all': Q_('?print_status:All'), - 'in_and_out': _('Enter and leave only'), - 'none': Q_('?print_status:None')} - opts = sorted(self.option_list.keys()) - for opt in opts: - model.append([self.option_list[opt], opt]) + self.option_list = {'': _('Default'), 'all': Q_('?print_status:All'), + 'in_and_out': _('Enter and leave only'), + 'none': Q_('?print_status:None')} + opts = sorted(self.option_list.keys()) + for opt in opts: + model.append([self.option_list[opt], opt]) - self.print_status_combobox.set_model(model) - self.print_status_combobox.set_active(1) + self.print_status_combobox.set_model(model) + self.print_status_combobox.set_active(1) - self.view = self.xml.get_object('bookmarks_treeview') - self.view.set_model(self.treestore) - self.view.expand_all() + self.view = self.xml.get_object('bookmarks_treeview') + self.view.set_model(self.treestore) + self.view.expand_all() - renderer = gtk.CellRendererText() - column = gtk.TreeViewColumn('Bookmarks', renderer, text=1) - self.view.append_column(column) + renderer = gtk.CellRendererText() + column = gtk.TreeViewColumn('Bookmarks', renderer, text=1) + self.view.append_column(column) - self.selection = self.view.get_selection() - self.selection.connect('changed', self.bookmark_selected) + self.selection = self.view.get_selection() + self.selection.connect('changed', self.bookmark_selected) - #Prepare input fields - self.title_entry = self.xml.get_object('title_entry') - self.title_entry.connect('changed', self.on_title_entry_changed) - self.nick_entry = self.xml.get_object('nick_entry') - self.nick_entry.connect('changed', self.on_nick_entry_changed) - self.server_entry = self.xml.get_object('server_entry') - self.server_entry.connect('changed', self.on_server_entry_changed) - self.room_entry = self.xml.get_object('room_entry') - self.room_entry.connect('changed', self.on_room_entry_changed) - self.pass_entry = self.xml.get_object('pass_entry') - self.pass_entry.connect('changed', self.on_pass_entry_changed) - self.autojoin_checkbutton = self.xml.get_object('autojoin_checkbutton') - self.minimize_checkbutton = self.xml.get_object('minimize_checkbutton') + #Prepare input fields + self.title_entry = self.xml.get_object('title_entry') + self.title_entry.connect('changed', self.on_title_entry_changed) + self.nick_entry = self.xml.get_object('nick_entry') + self.nick_entry.connect('changed', self.on_nick_entry_changed) + self.server_entry = self.xml.get_object('server_entry') + self.server_entry.connect('changed', self.on_server_entry_changed) + self.room_entry = self.xml.get_object('room_entry') + self.room_entry.connect('changed', self.on_room_entry_changed) + self.pass_entry = self.xml.get_object('pass_entry') + self.pass_entry.connect('changed', self.on_pass_entry_changed) + self.autojoin_checkbutton = self.xml.get_object('autojoin_checkbutton') + self.minimize_checkbutton = self.xml.get_object('minimize_checkbutton') - self.xml.connect_signals(self) - self.window.show_all() + self.xml.connect_signals(self) + self.window.show_all() - def on_bookmarks_treeview_button_press_event(self, widget, event): - (model, iter_) = self.selection.get_selected() - if not iter_: - # Removed a bookmark before - return + def on_bookmarks_treeview_button_press_event(self, widget, event): + (model, iter_) = self.selection.get_selected() + if not iter_: + # Removed a bookmark before + return - if model.iter_parent(iter_): - # The currently selected node is a bookmark - return not self.check_valid_bookmark() + if model.iter_parent(iter_): + # The currently selected node is a bookmark + return not self.check_valid_bookmark() - def on_manage_bookmarks_window_destroy(self, widget, event): - del gajim.interface.instances['manage_bookmarks'] + def on_manage_bookmarks_window_destroy(self, widget, event): + del gajim.interface.instances['manage_bookmarks'] - def on_add_bookmark_button_clicked(self, widget): - """ - Add a new bookmark - """ - # Get the account that is currently used - # (the parent of the currently selected item) - (model, iter_) = self.selection.get_selected() - if not iter_: # Nothing selected, do nothing - return + def on_add_bookmark_button_clicked(self, widget): + """ + Add a new bookmark + """ + # Get the account that is currently used + # (the parent of the currently selected item) + (model, iter_) = self.selection.get_selected() + if not iter_: # Nothing selected, do nothing + return - parent = model.iter_parent(iter_) + parent = model.iter_parent(iter_) - if parent: - # We got a bookmark selected, so we add_to the parent - add_to = parent - else: - # No parent, so we got an account -> add to this. - add_to = iter_ + if parent: + # We got a bookmark selected, so we add_to the parent + add_to = parent + else: + # No parent, so we got an account -> add to this. + add_to = iter_ - account = model[add_to][1].decode('utf-8') - nick = gajim.nicks[account] - iter_ = self.treestore.append(add_to, [account, _('New Group Chat'), '', - False, False, '', nick, 'in_and_out']) + account = model[add_to][1].decode('utf-8') + nick = gajim.nicks[account] + iter_ = self.treestore.append(add_to, [account, _('New Group Chat'), '', + False, False, '', nick, 'in_and_out']) - self.view.expand_row(model.get_path(add_to), True) - self.view.set_cursor(model.get_path(iter_)) + self.view.expand_row(model.get_path(add_to), True) + self.view.set_cursor(model.get_path(iter_)) - def on_remove_bookmark_button_clicked(self, widget): - """ - Remove selected bookmark - """ - (model, iter_) = self.selection.get_selected() - if not iter_: # Nothing selected - return + def on_remove_bookmark_button_clicked(self, widget): + """ + Remove selected bookmark + """ + (model, iter_) = self.selection.get_selected() + if not iter_: # Nothing selected + return - if not model.iter_parent(iter_): - # Don't remove account iters - return + if not model.iter_parent(iter_): + # Don't remove account iters + return - model.remove(iter_) - self.clear_fields() + model.remove(iter_) + self.clear_fields() - def check_valid_bookmark(self): - """ - Check if all neccessary fields are entered correctly - """ - (model, iter_) = self.selection.get_selected() + def check_valid_bookmark(self): + """ + Check if all neccessary fields are entered correctly + """ + (model, iter_) = self.selection.get_selected() - if not model.iter_parent(iter_): - #Account data can't be changed - return + if not model.iter_parent(iter_): + #Account data can't be changed + return - if self.server_entry.get_text().decode('utf-8') == '' or \ - self.room_entry.get_text().decode('utf-8') == '': - dialogs.ErrorDialog(_('This bookmark has invalid data'), - _('Please be sure to fill out server and room fields or remove this' - ' bookmark.')) - return False + if self.server_entry.get_text().decode('utf-8') == '' or \ + self.room_entry.get_text().decode('utf-8') == '': + dialogs.ErrorDialog(_('This bookmark has invalid data'), + _('Please be sure to fill out server and room fields or remove this' + ' bookmark.')) + return False - return True + return True - def on_ok_button_clicked(self, widget): - """ - Parse the treestore data into our new bookmarks array, then send the new - bookmarks to the server. - """ - (model, iter_) = self.selection.get_selected() - if iter_ and model.iter_parent(iter_): - #bookmark selected, check it - if not self.check_valid_bookmark(): - return + def on_ok_button_clicked(self, widget): + """ + Parse the treestore data into our new bookmarks array, then send the new + bookmarks to the server. + """ + (model, iter_) = self.selection.get_selected() + if iter_ and model.iter_parent(iter_): + #bookmark selected, check it + if not self.check_valid_bookmark(): + return - for account in self.treestore: - account_unicode = account[1].decode('utf-8') - gajim.connections[account_unicode].bookmarks = [] + for account in self.treestore: + account_unicode = account[1].decode('utf-8') + gajim.connections[account_unicode].bookmarks = [] - for bm in account.iterchildren(): - #Convert True/False/None to '1' or '0' - autojoin = unicode(int(bm[3])) - minimize = unicode(int(bm[4])) + for bm in account.iterchildren(): + #Convert True/False/None to '1' or '0' + autojoin = unicode(int(bm[3])) + minimize = unicode(int(bm[4])) - #create the bookmark-dict - bmdict = { 'name': bm[1], 'jid': bm[2], 'autojoin': autojoin, - 'minimize': minimize, 'password': bm[5], 'nick': bm[6], - 'print_status': bm[7]} + #create the bookmark-dict + bmdict = { 'name': bm[1], 'jid': bm[2], 'autojoin': autojoin, + 'minimize': minimize, 'password': bm[5], 'nick': bm[6], + 'print_status': bm[7]} - gajim.connections[account_unicode].bookmarks.append(bmdict) + gajim.connections[account_unicode].bookmarks.append(bmdict) - gajim.connections[account_unicode].store_bookmarks() - gajim.interface.roster.set_actions_menu_needs_rebuild() - self.window.destroy() + gajim.connections[account_unicode].store_bookmarks() + gajim.interface.roster.set_actions_menu_needs_rebuild() + self.window.destroy() - def on_cancel_button_clicked(self, widget): - self.window.destroy() + def on_cancel_button_clicked(self, widget): + self.window.destroy() - def bookmark_selected(self, selection): - """ - Fill in the bookmark's data into the fields. - """ - (model, iter_) = selection.get_selected() + def bookmark_selected(self, selection): + """ + Fill in the bookmark's data into the fields. + """ + (model, iter_) = selection.get_selected() - if not iter_: - # After removing the last bookmark for one account - # this will be None, so we will just: - return + if not iter_: + # After removing the last bookmark for one account + # this will be None, so we will just: + return - widgets = [ self.title_entry, self.nick_entry, self.room_entry, - self.server_entry, self.pass_entry, self.autojoin_checkbutton, - self.minimize_checkbutton, self.print_status_combobox] + widgets = [ self.title_entry, self.nick_entry, self.room_entry, + self.server_entry, self.pass_entry, self.autojoin_checkbutton, + self.minimize_checkbutton, self.print_status_combobox] - if model.iter_parent(iter_): - # make the fields sensitive - for field in widgets: - field.set_sensitive(True) - else: - # Top-level has no data (it's the account fields) - # clear fields & make them insensitive - self.clear_fields() - for field in widgets: - field.set_sensitive(False) - return + if model.iter_parent(iter_): + # make the fields sensitive + for field in widgets: + field.set_sensitive(True) + else: + # Top-level has no data (it's the account fields) + # clear fields & make them insensitive + self.clear_fields() + for field in widgets: + field.set_sensitive(False) + return - # Fill in the data for childs - self.title_entry.set_text(model[iter_][1]) - room_jid = model[iter_][2].decode('utf-8') - try: - (room, server) = room_jid.split('@') - except ValueError: - # We just added this one - room = '' - server = '' - self.room_entry.set_text(room) - self.server_entry.set_text(server) + # Fill in the data for childs + self.title_entry.set_text(model[iter_][1]) + room_jid = model[iter_][2].decode('utf-8') + try: + (room, server) = room_jid.split('@') + except ValueError: + # We just added this one + room = '' + server = '' + self.room_entry.set_text(room) + self.server_entry.set_text(server) - self.autojoin_checkbutton.set_active(model[iter_][3]) - self.minimize_checkbutton.set_active(model[iter_][4]) - # sensitive only if auto join is checked - self.minimize_checkbutton.set_sensitive(model[iter_][3]) + self.autojoin_checkbutton.set_active(model[iter_][3]) + self.minimize_checkbutton.set_active(model[iter_][4]) + # sensitive only if auto join is checked + self.minimize_checkbutton.set_sensitive(model[iter_][3]) - if model[iter_][5] is not None: - password = model[iter_][5].decode('utf-8') - else: - password = None + if model[iter_][5] is not None: + password = model[iter_][5].decode('utf-8') + else: + password = None - if password: - self.pass_entry.set_text(password) - else: - self.pass_entry.set_text('') - nick = model[iter_][6] - if nick: - nick = nick.decode('utf-8') - self.nick_entry.set_text(nick) - else: - self.nick_entry.set_text('') + if password: + self.pass_entry.set_text(password) + else: + self.pass_entry.set_text('') + nick = model[iter_][6] + if nick: + nick = nick.decode('utf-8') + self.nick_entry.set_text(nick) + else: + self.nick_entry.set_text('') - print_status = model[iter_][7] - opts = sorted(self.option_list.keys()) - self.print_status_combobox.set_active(opts.index(print_status)) + print_status = model[iter_][7] + opts = sorted(self.option_list.keys()) + self.print_status_combobox.set_active(opts.index(print_status)) - def on_title_entry_changed(self, widget): - (model, iter_) = self.selection.get_selected() - if iter_: # After removing a bookmark, we got nothing selected - if model.iter_parent(iter_): - # Don't clear the title field for account nodes - model[iter_][1] = self.title_entry.get_text() + def on_title_entry_changed(self, widget): + (model, iter_) = self.selection.get_selected() + if iter_: # After removing a bookmark, we got nothing selected + if model.iter_parent(iter_): + # Don't clear the title field for account nodes + model[iter_][1] = self.title_entry.get_text() - def on_nick_entry_changed(self, widget): - (model, iter_) = self.selection.get_selected() - if iter_: - nick = self.nick_entry.get_text().decode('utf-8') - try: - nick = helpers.parse_resource(nick) - except helpers.InvalidFormat, e: - dialogs.ErrorDialog(_('Invalid nickname'), - _('Character not allowed')) - self.nick_entry.set_text(model[iter_][6]) - return True - model[iter_][6] = nick + def on_nick_entry_changed(self, widget): + (model, iter_) = self.selection.get_selected() + if iter_: + nick = self.nick_entry.get_text().decode('utf-8') + try: + nick = helpers.parse_resource(nick) + except helpers.InvalidFormat, e: + dialogs.ErrorDialog(_('Invalid nickname'), + _('Character not allowed')) + self.nick_entry.set_text(model[iter_][6]) + return True + model[iter_][6] = nick - def on_server_entry_changed(self, widget): - (model, iter_) = self.selection.get_selected() - if iter_: - room_jid = self.room_entry.get_text().decode('utf-8').strip() + '@' + \ - self.server_entry.get_text().decode('utf-8').strip() - try: - room_jid = helpers.parse_resource(room_jid) - except helpers.InvalidFormat, e: - dialogs.ErrorDialog(_('Invalid server'), - _('Character not allowed')) - self.server_entry.set_text(model[iter_][2].split('@')[1]) - return True - model[iter_][2] = room_jid + def on_server_entry_changed(self, widget): + (model, iter_) = self.selection.get_selected() + if iter_: + room_jid = self.room_entry.get_text().decode('utf-8').strip() + '@' + \ + self.server_entry.get_text().decode('utf-8').strip() + try: + room_jid = helpers.parse_resource(room_jid) + except helpers.InvalidFormat, e: + dialogs.ErrorDialog(_('Invalid server'), + _('Character not allowed')) + self.server_entry.set_text(model[iter_][2].split('@')[1]) + return True + model[iter_][2] = room_jid - def on_room_entry_changed(self, widget): - (model, iter_) = self.selection.get_selected() - if iter_: - room_jid = self.room_entry.get_text().decode('utf-8').strip() + '@' + \ - self.server_entry.get_text().decode('utf-8').strip() - try: - room_jid = helpers.parse_resource(room_jid) - except helpers.InvalidFormat, e: - dialogs.ErrorDialog(_('Invalid room'), - _('Character not allowed')) - self.room_entry.set_text(model[iter_][2].split('@')[0]) - return True - model[iter_][2] = room_jid + def on_room_entry_changed(self, widget): + (model, iter_) = self.selection.get_selected() + if iter_: + room_jid = self.room_entry.get_text().decode('utf-8').strip() + '@' + \ + self.server_entry.get_text().decode('utf-8').strip() + try: + room_jid = helpers.parse_resource(room_jid) + except helpers.InvalidFormat, e: + dialogs.ErrorDialog(_('Invalid room'), + _('Character not allowed')) + self.room_entry.set_text(model[iter_][2].split('@')[0]) + return True + model[iter_][2] = room_jid - def on_pass_entry_changed(self, widget): - (model, iter_) = self.selection.get_selected() - if iter_: - model[iter_][5] = self.pass_entry.get_text() + def on_pass_entry_changed(self, widget): + (model, iter_) = self.selection.get_selected() + if iter_: + model[iter_][5] = self.pass_entry.get_text() - def on_autojoin_checkbutton_toggled(self, widget): - (model, iter_) = self.selection.get_selected() - if iter_: - model[iter_][3] = self.autojoin_checkbutton.get_active() - self.minimize_checkbutton.set_sensitive(model[iter_][3]) + def on_autojoin_checkbutton_toggled(self, widget): + (model, iter_) = self.selection.get_selected() + if iter_: + model[iter_][3] = self.autojoin_checkbutton.get_active() + self.minimize_checkbutton.set_sensitive(model[iter_][3]) - def on_minimize_checkbutton_toggled(self, widget): - (model, iter_) = self.selection.get_selected() - if iter_: - model[iter_][4] = self.minimize_checkbutton.get_active() + def on_minimize_checkbutton_toggled(self, widget): + (model, iter_) = self.selection.get_selected() + if iter_: + model[iter_][4] = self.minimize_checkbutton.get_active() - def on_print_status_combobox_changed(self, widget): - active = widget.get_active() - model = widget.get_model() - print_status = model[active][1] - (model2, iter_) = self.selection.get_selected() - if iter_: - model2[iter_][7] = print_status + def on_print_status_combobox_changed(self, widget): + active = widget.get_active() + model = widget.get_model() + print_status = model[active][1] + (model2, iter_) = self.selection.get_selected() + if iter_: + model2[iter_][7] = print_status - def clear_fields(self): - widgets = [ self.title_entry, self.nick_entry, self.room_entry, - self.server_entry, self.pass_entry ] - for field in widgets: - field.set_text('') - self.autojoin_checkbutton.set_active(False) - self.minimize_checkbutton.set_active(False) - self.print_status_combobox.set_active(1) + def clear_fields(self): + widgets = [ self.title_entry, self.nick_entry, self.room_entry, + self.server_entry, self.pass_entry ] + for field in widgets: + field.set_text('') + self.autojoin_checkbutton.set_active(False) + self.minimize_checkbutton.set_active(False) + self.print_status_combobox.set_active(1) class AccountCreationWizardWindow: - def __init__(self): - self.xml = gtkgui_helpers.get_gtk_builder( - 'account_creation_wizard_window.ui') - self.window = self.xml.get_object('account_creation_wizard_window') - self.window.set_transient_for(gajim.interface.roster.window) + def __init__(self): + self.xml = gtkgui_helpers.get_gtk_builder( + 'account_creation_wizard_window.ui') + self.window = self.xml.get_object('account_creation_wizard_window') + self.window.set_transient_for(gajim.interface.roster.window) - completion = gtk.EntryCompletion() - # Connect events from comboboxentry.child - server_comboboxentry = self.xml.get_object('server_comboboxentry') - entry = server_comboboxentry.child - entry.connect('key_press_event', - self.on_server_comboboxentry_key_press_event, server_comboboxentry) - entry.set_completion(completion) - # Do the same for the other server comboboxentry - server_comboboxentry1 = self.xml.get_object('server_comboboxentry1') - entry = server_comboboxentry1.child - entry.connect('key_press_event', - self.on_server_comboboxentry_key_press_event, server_comboboxentry1) - entry.set_completion(completion) + completion = gtk.EntryCompletion() + # Connect events from comboboxentry.child + server_comboboxentry = self.xml.get_object('server_comboboxentry') + entry = server_comboboxentry.child + entry.connect('key_press_event', + self.on_server_comboboxentry_key_press_event, server_comboboxentry) + entry.set_completion(completion) + # Do the same for the other server comboboxentry + server_comboboxentry1 = self.xml.get_object('server_comboboxentry1') + entry = server_comboboxentry1.child + entry.connect('key_press_event', + self.on_server_comboboxentry_key_press_event, server_comboboxentry1) + entry.set_completion(completion) - self.update_proxy_list() + self.update_proxy_list() - # parse servers.xml - servers_xml = os.path.join(gajim.DATA_DIR, 'other', 'servers.xml') - servers = gtkgui_helpers.parse_server_xml(servers_xml) - servers_model = gtk.ListStore(str, int) - for server in servers: - if not server[2]['hidden']: - servers_model.append((str(server[0]), int(server[1]))) + # parse servers.xml + servers_xml = os.path.join(gajim.DATA_DIR, 'other', 'servers.xml') + servers = gtkgui_helpers.parse_server_xml(servers_xml) + servers_model = gtk.ListStore(str, int) + for server in servers: + if not server[2]['hidden']: + servers_model.append((str(server[0]), int(server[1]))) - completion.set_model(servers_model) - completion.set_text_column(0) + completion.set_model(servers_model) + completion.set_text_column(0) - # Put servers into comboboxentries - server_comboboxentry.set_model(servers_model) - server_comboboxentry.set_text_column(0) - server_comboboxentry1.set_model(servers_model) - server_comboboxentry1.set_text_column(0) + # Put servers into comboboxentries + server_comboboxentry.set_model(servers_model) + server_comboboxentry.set_text_column(0) + server_comboboxentry1.set_model(servers_model) + server_comboboxentry1.set_text_column(0) - # Generic widgets - self.notebook = self.xml.get_object('notebook') - self.cancel_button = self.xml.get_object('cancel_button') - self.back_button = self.xml.get_object('back_button') - self.forward_button = self.xml.get_object('forward_button') - self.finish_button = self.xml.get_object('finish_button') - self.advanced_button = self.xml.get_object('advanced_button') - self.finish_label = self.xml.get_object('finish_label') - self.go_online_checkbutton = self.xml.get_object( - 'go_online_checkbutton') - self.show_vcard_checkbutton = self.xml.get_object( - 'show_vcard_checkbutton') - self.progressbar = self.xml.get_object('progressbar') + # Generic widgets + self.notebook = self.xml.get_object('notebook') + self.cancel_button = self.xml.get_object('cancel_button') + self.back_button = self.xml.get_object('back_button') + self.forward_button = self.xml.get_object('forward_button') + self.finish_button = self.xml.get_object('finish_button') + self.advanced_button = self.xml.get_object('advanced_button') + self.finish_label = self.xml.get_object('finish_label') + self.go_online_checkbutton = self.xml.get_object( + 'go_online_checkbutton') + self.show_vcard_checkbutton = self.xml.get_object( + 'show_vcard_checkbutton') + self.progressbar = self.xml.get_object('progressbar') - # some vars - self.update_progressbar_timeout_id = None + # some vars + self.update_progressbar_timeout_id = None - self.notebook.set_current_page(0) - self.xml.connect_signals(self) - self.window.show_all() - gajim.ged.register_event_handler('NEW_ACC_CONNECTED', ged.CORE, - self.new_acc_connected) - gajim.ged.register_event_handler('NEW_ACC_NOT_CONNECTED', ged.CORE, - self.new_acc_not_connected) - gajim.ged.register_event_handler('ACC_OK', ged.CORE, self.acc_is_ok) - gajim.ged.register_event_handler('ACC_NOT_OK', ged.CORE, - self.acc_is_not_ok) + self.notebook.set_current_page(0) + self.xml.connect_signals(self) + self.window.show_all() + gajim.ged.register_event_handler('NEW_ACC_CONNECTED', ged.CORE, + self.new_acc_connected) + gajim.ged.register_event_handler('NEW_ACC_NOT_CONNECTED', ged.CORE, + self.new_acc_not_connected) + gajim.ged.register_event_handler('ACC_OK', ged.CORE, self.acc_is_ok) + gajim.ged.register_event_handler('ACC_NOT_OK', ged.CORE, + self.acc_is_not_ok) - def on_wizard_window_destroy(self, widget): - page = self.notebook.get_current_page() - if page in (4, 5) and self.account in gajim.connections: - # connection instance is saved in gajim.connections and we canceled the - # addition of the account - del gajim.connections[self.account] - if self.account in gajim.config.get_per('accounts'): - gajim.config.del_per('accounts', self.account) - gajim.ged.remove_event_handler('NEW_ACC_CONNECTED', ged.CORE, - self.new_acc_connected) - gajim.ged.remove_event_handler('NEW_ACC_NOT_CONNECTED', ged.CORE, - self.new_acc_not_connected) - gajim.ged.remove_event_handler('ACC_OK', ged.CORE, self.acc_is_ok) - gajim.ged.remove_event_handler('ACC_NOT_OK', ged.CORE, - self.acc_is_not_ok) - del gajim.interface.instances['account_creation_wizard'] + def on_wizard_window_destroy(self, widget): + page = self.notebook.get_current_page() + if page in (4, 5) and self.account in gajim.connections: + # connection instance is saved in gajim.connections and we canceled the + # addition of the account + del gajim.connections[self.account] + if self.account in gajim.config.get_per('accounts'): + gajim.config.del_per('accounts', self.account) + gajim.ged.remove_event_handler('NEW_ACC_CONNECTED', ged.CORE, + self.new_acc_connected) + gajim.ged.remove_event_handler('NEW_ACC_NOT_CONNECTED', ged.CORE, + self.new_acc_not_connected) + gajim.ged.remove_event_handler('ACC_OK', ged.CORE, self.acc_is_ok) + gajim.ged.remove_event_handler('ACC_NOT_OK', ged.CORE, + self.acc_is_not_ok) + del gajim.interface.instances['account_creation_wizard'] - def on_register_server_features_button_clicked(self, widget): - helpers.launch_browser_mailer('url', - 'http://www.jabber.org/network/oldnetwork.shtml') + def on_register_server_features_button_clicked(self, widget): + helpers.launch_browser_mailer('url', + 'http://www.jabber.org/network/oldnetwork.shtml') - def on_save_password_checkbutton_toggled(self, widget): - self.xml.get_object('password_entry').grab_focus() + def on_save_password_checkbutton_toggled(self, widget): + self.xml.get_object('password_entry').grab_focus() - def on_cancel_button_clicked(self, widget): - self.window.destroy() + def on_cancel_button_clicked(self, widget): + self.window.destroy() - def on_back_button_clicked(self, widget): - cur_page = self.notebook.get_current_page() - if cur_page in (1, 2): - self.notebook.set_current_page(0) - self.back_button.set_sensitive(False) - elif cur_page == 3: - self.xml.get_object('form_vbox').remove(self.data_form_widget) - self.notebook.set_current_page(2) # show server page - elif cur_page == 4: - if self.account in gajim.connections: - del gajim.connections[self.account] - self.notebook.set_current_page(2) - self.xml.get_object('form_vbox').remove(self.data_form_widget) - elif cur_page == 6: # finish page - self.forward_button.show() - if self.modify: - self.notebook.set_current_page(1) # Go to parameters page - else: - self.notebook.set_current_page(2) # Go to server page + def on_back_button_clicked(self, widget): + cur_page = self.notebook.get_current_page() + if cur_page in (1, 2): + self.notebook.set_current_page(0) + self.back_button.set_sensitive(False) + elif cur_page == 3: + self.xml.get_object('form_vbox').remove(self.data_form_widget) + self.notebook.set_current_page(2) # show server page + elif cur_page == 4: + if self.account in gajim.connections: + del gajim.connections[self.account] + self.notebook.set_current_page(2) + self.xml.get_object('form_vbox').remove(self.data_form_widget) + elif cur_page == 6: # finish page + self.forward_button.show() + if self.modify: + self.notebook.set_current_page(1) # Go to parameters page + else: + self.notebook.set_current_page(2) # Go to server page - def on_anonymous_checkbutton1_toggled(self, widget): - active = widget.get_active() - self.xml.get_object('username_entry').set_sensitive(not active) - self.xml.get_object('password_entry').set_sensitive(not active) - self.xml.get_object('save_password_checkbutton').set_sensitive(not active) + def on_anonymous_checkbutton1_toggled(self, widget): + active = widget.get_active() + self.xml.get_object('username_entry').set_sensitive(not active) + self.xml.get_object('password_entry').set_sensitive(not active) + self.xml.get_object('save_password_checkbutton').set_sensitive(not active) - def show_finish_page(self): - self.cancel_button.hide() - self.back_button.hide() - self.forward_button.hide() - if self.modify: - finish_text = '%s\n\n%s' % ( - _('Account has been added successfully'), - _('You can set advanced account options by pressing the ' - 'Advanced button, or later by choosing the Accounts menu item ' - 'under the Edit menu from the main window.')) - else: - finish_text = '%s\n\n%s' % ( - _('Your new account has been created successfully'), - _('You can set advanced account options by pressing the Advanced ' - 'button, or later by choosing the Accounts menu item under the Edit' - ' menu from the main window.')) - self.finish_label.set_markup(finish_text) - self.finish_button.show() - self.finish_button.set_property('has-default', True) - self.advanced_button.show() - self.go_online_checkbutton.show() - img = self.xml.get_object('finish_image') - if self.modify: - img.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_DIALOG) - else: - path_to_file = gtkgui_helpers.get_icon_path('gajim', 48) - img.set_from_file(path_to_file) - self.show_vcard_checkbutton.set_active(not self.modify) - self.notebook.set_current_page(6) # show finish page + def show_finish_page(self): + self.cancel_button.hide() + self.back_button.hide() + self.forward_button.hide() + if self.modify: + finish_text = '%s\n\n%s' % ( + _('Account has been added successfully'), + _('You can set advanced account options by pressing the ' + 'Advanced button, or later by choosing the Accounts menu item ' + 'under the Edit menu from the main window.')) + else: + finish_text = '%s\n\n%s' % ( + _('Your new account has been created successfully'), + _('You can set advanced account options by pressing the Advanced ' + 'button, or later by choosing the Accounts menu item under the Edit' + ' menu from the main window.')) + self.finish_label.set_markup(finish_text) + self.finish_button.show() + self.finish_button.set_property('has-default', True) + self.advanced_button.show() + self.go_online_checkbutton.show() + img = self.xml.get_object('finish_image') + if self.modify: + img.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_DIALOG) + else: + path_to_file = gtkgui_helpers.get_icon_path('gajim', 48) + img.set_from_file(path_to_file) + self.show_vcard_checkbutton.set_active(not self.modify) + self.notebook.set_current_page(6) # show finish page - def on_forward_button_clicked(self, widget): - cur_page = self.notebook.get_current_page() + def on_forward_button_clicked(self, widget): + cur_page = self.notebook.get_current_page() - if cur_page == 0: - widget = self.xml.get_object('use_existing_account_radiobutton') - if widget.get_active(): - self.modify = True - self.notebook.set_current_page(1) - else: - self.modify = False - self.notebook.set_current_page(2) - self.back_button.set_sensitive(True) - return + if cur_page == 0: + widget = self.xml.get_object('use_existing_account_radiobutton') + if widget.get_active(): + self.modify = True + self.notebook.set_current_page(1) + else: + self.modify = False + self.notebook.set_current_page(2) + self.back_button.set_sensitive(True) + return - elif cur_page == 1: - # We are adding an existing account - anonymous = self.xml.get_object('anonymous_checkbutton1').get_active() - username = self.xml.get_object('username_entry').get_text().decode( - 'utf-8').strip() - if not username and not anonymous: - pritext = _('Invalid username') - sectext = _( - 'You must provide a username to configure this account.') - dialogs.ErrorDialog(pritext, sectext) - return - server = self.xml.get_object('server_comboboxentry').child.get_text().\ - decode('utf-8').strip() - savepass = self.xml.get_object('save_password_checkbutton').\ - get_active() - password = self.xml.get_object('password_entry').get_text().decode( - 'utf-8') + elif cur_page == 1: + # We are adding an existing account + anonymous = self.xml.get_object('anonymous_checkbutton1').get_active() + username = self.xml.get_object('username_entry').get_text().decode( + 'utf-8').strip() + if not username and not anonymous: + pritext = _('Invalid username') + sectext = _( + 'You must provide a username to configure this account.') + dialogs.ErrorDialog(pritext, sectext) + return + server = self.xml.get_object('server_comboboxentry').child.get_text().\ + decode('utf-8').strip() + savepass = self.xml.get_object('save_password_checkbutton').\ + get_active() + password = self.xml.get_object('password_entry').get_text().decode( + 'utf-8') - jid = username + '@' + server - # check if jid is conform to RFC and stringprep it - try: - jid = helpers.parse_jid(jid) - except helpers.InvalidFormat, s: - pritext = _('Invalid Jabber ID') - dialogs.ErrorDialog(pritext, str(s)) - return + jid = username + '@' + server + # check if jid is conform to RFC and stringprep it + try: + jid = helpers.parse_jid(jid) + except helpers.InvalidFormat, s: + pritext = _('Invalid Jabber ID') + dialogs.ErrorDialog(pritext, str(s)) + return - self.account = server - i = 1 - while self.account in gajim.connections: - self.account = server + str(i) - i += 1 + self.account = server + i = 1 + while self.account in gajim.connections: + self.account = server + str(i) + i += 1 - username, server = gajim.get_name_and_server_from_jid(jid) - if self.xml.get_object('anonymous_checkbutton1').get_active(): - self.save_account('', server, False, '', anonymous=True) - else: - self.save_account(username, server, savepass, password) - self.show_finish_page() - elif cur_page == 2: - # We are creating a new account - server = self.xml.get_object('server_comboboxentry1').child.get_text()\ - .decode('utf-8') + username, server = gajim.get_name_and_server_from_jid(jid) + if self.xml.get_object('anonymous_checkbutton1').get_active(): + self.save_account('', server, False, '', anonymous=True) + else: + self.save_account(username, server, savepass, password) + self.show_finish_page() + elif cur_page == 2: + # We are creating a new account + server = self.xml.get_object('server_comboboxentry1').child.get_text()\ + .decode('utf-8') - if not server: - dialogs.ErrorDialog(_('Invalid server'), - _('Please provide a server on which you want to register.')) - return - self.account = server - i = 1 - while self.account in gajim.connections: - self.account = server + str(i) - i += 1 + if not server: + dialogs.ErrorDialog(_('Invalid server'), + _('Please provide a server on which you want to register.')) + return + self.account = server + i = 1 + while self.account in gajim.connections: + self.account = server + str(i) + i += 1 - config = self.get_config('', server, '', '') - # Get advanced options - proxies_combobox = self.xml.get_object('proxies_combobox') - active = proxies_combobox.get_active() - proxy = proxies_combobox.get_model()[active][0].decode('utf-8') - if proxy == _('None'): - proxy = '' - config['proxy'] = proxy + config = self.get_config('', server, '', '') + # Get advanced options + proxies_combobox = self.xml.get_object('proxies_combobox') + active = proxies_combobox.get_active() + proxy = proxies_combobox.get_model()[active][0].decode('utf-8') + if proxy == _('None'): + proxy = '' + config['proxy'] = proxy - config['use_custom_host'] = self.xml.get_object( - 'custom_host_port_checkbutton').get_active() - custom_port = self.xml.get_object('custom_port_entry').get_text() - try: - custom_port = int(custom_port) - except Exception: - dialogs.ErrorDialog(_('Invalid entry'), - _('Custom port must be a port number.')) - return - config['custom_port'] = custom_port - config['custom_host'] = self.xml.get_object( - 'custom_host_entry').get_text().decode('utf-8') + config['use_custom_host'] = self.xml.get_object( + 'custom_host_port_checkbutton').get_active() + custom_port = self.xml.get_object('custom_port_entry').get_text() + try: + custom_port = int(custom_port) + except Exception: + dialogs.ErrorDialog(_('Invalid entry'), + _('Custom port must be a port number.')) + return + config['custom_port'] = custom_port + config['custom_host'] = self.xml.get_object( + 'custom_host_entry').get_text().decode('utf-8') - if self.xml.get_object('anonymous_checkbutton2').get_active(): - self.modify = True - self.save_account('', server, False, '', anonymous=True) - self.show_finish_page() - else: - self.notebook.set_current_page(5) # show creating page - self.back_button.hide() - self.forward_button.hide() - self.update_progressbar_timeout_id = gobject.timeout_add(100, - self.update_progressbar) - # Get form from serveur - con = connection.Connection(self.account) - gajim.connections[self.account] = con - con.new_account(self.account, config) - elif cur_page == 3: - checked = self.xml.get_object('ssl_checkbutton').get_active() - if checked: - hostname = gajim.connections[self.account].new_account_info[ - 'hostname'] - # Check if cert is already in file - certs = '' - if os.path.isfile(gajim.MY_CACERTS): - f = open(gajim.MY_CACERTS) - certs = f.read() - f.close() - if self.ssl_cert in certs: - dialogs.ErrorDialog(_('Certificate Already in File'), - _('This certificate is already in file %s, so it\'s not added again.') % gajim.MY_CACERTS) - else: - f = open(gajim.MY_CACERTS, 'a') - f.write(hostname + '\n') - f.write(self.ssl_cert + '\n\n') - f.close() - gajim.connections[self.account].new_account_info[ - 'ssl_fingerprint_sha1'] = self.ssl_fingerprint - self.notebook.set_current_page(4) # show fom page - elif cur_page == 4: - if self.is_form: - form = self.data_form_widget.data_form - else: - form = self.data_form_widget.get_infos() - gajim.connections[self.account].send_new_account_infos(form, - self.is_form) - self.xml.get_object('form_vbox').remove(self.data_form_widget) - self.xml.get_object('progressbar_label').set_markup('Account is being created\n\nPlease wait...') - self.notebook.set_current_page(5) # show creating page - self.back_button.hide() - self.forward_button.hide() - self.update_progressbar_timeout_id = gobject.timeout_add(100, - self.update_progressbar) + if self.xml.get_object('anonymous_checkbutton2').get_active(): + self.modify = True + self.save_account('', server, False, '', anonymous=True) + self.show_finish_page() + else: + self.notebook.set_current_page(5) # show creating page + self.back_button.hide() + self.forward_button.hide() + self.update_progressbar_timeout_id = gobject.timeout_add(100, + self.update_progressbar) + # Get form from serveur + con = connection.Connection(self.account) + gajim.connections[self.account] = con + con.new_account(self.account, config) + elif cur_page == 3: + checked = self.xml.get_object('ssl_checkbutton').get_active() + if checked: + hostname = gajim.connections[self.account].new_account_info[ + 'hostname'] + # Check if cert is already in file + certs = '' + if os.path.isfile(gajim.MY_CACERTS): + f = open(gajim.MY_CACERTS) + certs = f.read() + f.close() + if self.ssl_cert in certs: + dialogs.ErrorDialog(_('Certificate Already in File'), + _('This certificate is already in file %s, so it\'s not added again.') % gajim.MY_CACERTS) + else: + f = open(gajim.MY_CACERTS, 'a') + f.write(hostname + '\n') + f.write(self.ssl_cert + '\n\n') + f.close() + gajim.connections[self.account].new_account_info[ + 'ssl_fingerprint_sha1'] = self.ssl_fingerprint + self.notebook.set_current_page(4) # show fom page + elif cur_page == 4: + if self.is_form: + form = self.data_form_widget.data_form + else: + form = self.data_form_widget.get_infos() + gajim.connections[self.account].send_new_account_infos(form, + self.is_form) + self.xml.get_object('form_vbox').remove(self.data_form_widget) + self.xml.get_object('progressbar_label').set_markup('Account is being created\n\nPlease wait...') + self.notebook.set_current_page(5) # show creating page + self.back_button.hide() + self.forward_button.hide() + self.update_progressbar_timeout_id = gobject.timeout_add(100, + self.update_progressbar) - def update_proxy_list(self): - proxies_combobox = self.xml.get_object('proxies_combobox') - model = gtk.ListStore(str) - proxies_combobox.set_model(model) - l = gajim.config.get_per('proxies') - l.insert(0, _('None')) - for i in xrange(len(l)): - model.append([l[i]]) - proxies_combobox.set_active(0) + def update_proxy_list(self): + proxies_combobox = self.xml.get_object('proxies_combobox') + model = gtk.ListStore(str) + proxies_combobox.set_model(model) + l = gajim.config.get_per('proxies') + l.insert(0, _('None')) + for i in xrange(len(l)): + model.append([l[i]]) + proxies_combobox.set_active(0) - def on_manage_proxies_button_clicked(self, widget): - if 'manage_proxies' in gajim.interface.instances: - gajim.interface.instances['manage_proxies'].window.present() - else: - gajim.interface.instances['manage_proxies'] = \ - ManageProxiesWindow() + def on_manage_proxies_button_clicked(self, widget): + if 'manage_proxies' in gajim.interface.instances: + gajim.interface.instances['manage_proxies'].window.present() + else: + gajim.interface.instances['manage_proxies'] = \ + ManageProxiesWindow() - def on_custom_host_port_checkbutton_toggled(self, widget): - self.xml.get_object('custom_host_hbox').set_sensitive(widget.get_active()) + def on_custom_host_port_checkbutton_toggled(self, widget): + self.xml.get_object('custom_host_hbox').set_sensitive(widget.get_active()) - def update_progressbar(self): - self.progressbar.pulse() - return True # loop forever + def update_progressbar(self): + self.progressbar.pulse() + return True # loop forever - def new_acc_connected(self, account, array): - """ - Connection to server succeded, present the form to the user - """ - # We receive events from all accounts from GED - if account != self.account: - return - form, is_form, ssl_msg, ssl_err, ssl_cert, ssl_fingerprint = array - if self.update_progressbar_timeout_id is not None: - gobject.source_remove(self.update_progressbar_timeout_id) - self.back_button.show() - self.forward_button.show() - self.is_form = is_form - if is_form: - dataform = dataforms.ExtendForm(node = form) - self.data_form_widget = dataforms_widget.DataFormWidget(dataform) - else: - self.data_form_widget = FakeDataForm(form) - self.data_form_widget.show_all() - self.xml.get_object('form_vbox').pack_start(self.data_form_widget) - self.ssl_fingerprint = ssl_fingerprint - self.ssl_cert = ssl_cert - if ssl_msg: - # An SSL warning occured, show it - hostname = gajim.connections[self.account].new_account_info['hostname'] - self.xml.get_object('ssl_label').set_markup(_('Security Warning' - '\n\nThe authenticity of the %(hostname)s SSL certificate could be ' - 'invalid.\nSSL Error: %(error)s\n' - 'Do you still want to connect to this server?') % { - 'hostname': hostname, 'error': ssl_msg}) - if ssl_err in (18, 27): - text = _('Add this certificate to the list of trusted certificates.\nSHA1 fingerprint of the certificate:\n%s') % ssl_fingerprint - self.xml.get_object('ssl_checkbutton').set_label(text) - else: - self.xml.get_object('ssl_checkbutton').set_no_show_all(True) - self.xml.get_object('ssl_checkbutton').hide() - self.notebook.set_current_page(3) # show SSL page - else: - self.notebook.set_current_page(4) # show form page + def new_acc_connected(self, account, array): + """ + Connection to server succeded, present the form to the user + """ + # We receive events from all accounts from GED + if account != self.account: + return + form, is_form, ssl_msg, ssl_err, ssl_cert, ssl_fingerprint = array + if self.update_progressbar_timeout_id is not None: + gobject.source_remove(self.update_progressbar_timeout_id) + self.back_button.show() + self.forward_button.show() + self.is_form = is_form + if is_form: + dataform = dataforms.ExtendForm(node = form) + self.data_form_widget = dataforms_widget.DataFormWidget(dataform) + else: + self.data_form_widget = FakeDataForm(form) + self.data_form_widget.show_all() + self.xml.get_object('form_vbox').pack_start(self.data_form_widget) + self.ssl_fingerprint = ssl_fingerprint + self.ssl_cert = ssl_cert + if ssl_msg: + # An SSL warning occured, show it + hostname = gajim.connections[self.account].new_account_info['hostname'] + self.xml.get_object('ssl_label').set_markup(_('Security Warning' + '\n\nThe authenticity of the %(hostname)s SSL certificate could be ' + 'invalid.\nSSL Error: %(error)s\n' + 'Do you still want to connect to this server?') % { + 'hostname': hostname, 'error': ssl_msg}) + if ssl_err in (18, 27): + text = _('Add this certificate to the list of trusted certificates.\nSHA1 fingerprint of the certificate:\n%s') % ssl_fingerprint + self.xml.get_object('ssl_checkbutton').set_label(text) + else: + self.xml.get_object('ssl_checkbutton').set_no_show_all(True) + self.xml.get_object('ssl_checkbutton').hide() + self.notebook.set_current_page(3) # show SSL page + else: + self.notebook.set_current_page(4) # show form page - def new_acc_not_connected(self, account, reason): - """ - Account creation failed: connection to server failed - """ - # We receive events from all accounts from GED - if account != self.account: - return - if self.account not in gajim.connections: - return - if self.update_progressbar_timeout_id is not None: - gobject.source_remove(self.update_progressbar_timeout_id) - del gajim.connections[self.account] - if self.account in gajim.config.get_per('accounts'): - gajim.config.del_per('accounts', self.account) - self.back_button.show() - self.cancel_button.show() - self.go_online_checkbutton.hide() - self.show_vcard_checkbutton.hide() - img = self.xml.get_object('finish_image') - img.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_DIALOG) - finish_text = '%s\n\n%s' % ( - _('An error occurred during account creation') , reason) - self.finish_label.set_markup(finish_text) - self.notebook.set_current_page(6) # show finish page + def new_acc_not_connected(self, account, reason): + """ + Account creation failed: connection to server failed + """ + # We receive events from all accounts from GED + if account != self.account: + return + if self.account not in gajim.connections: + return + if self.update_progressbar_timeout_id is not None: + gobject.source_remove(self.update_progressbar_timeout_id) + del gajim.connections[self.account] + if self.account in gajim.config.get_per('accounts'): + gajim.config.del_per('accounts', self.account) + self.back_button.show() + self.cancel_button.show() + self.go_online_checkbutton.hide() + self.show_vcard_checkbutton.hide() + img = self.xml.get_object('finish_image') + img.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_DIALOG) + finish_text = '%s\n\n%s' % ( + _('An error occurred during account creation'), reason) + self.finish_label.set_markup(finish_text) + self.notebook.set_current_page(6) # show finish page - def acc_is_ok(self, account, config): - """ - Account creation succeeded - """ - # We receive events from all accounts from GED - if account != self.account: - return - self.create_vars(config) - self.show_finish_page() + def acc_is_ok(self, account, config): + """ + Account creation succeeded + """ + # We receive events from all accounts from GED + if account != self.account: + return + self.create_vars(config) + self.show_finish_page() - if self.update_progressbar_timeout_id is not None: - gobject.source_remove(self.update_progressbar_timeout_id) + if self.update_progressbar_timeout_id is not None: + gobject.source_remove(self.update_progressbar_timeout_id) - def acc_is_not_ok(self, account, reason): - """ - Account creation failed - """ - # We receive events from all accounts from GED - if account != self.account: - return - self.back_button.show() - self.cancel_button.show() - self.go_online_checkbutton.hide() - self.show_vcard_checkbutton.hide() - del gajim.connections[self.account] - if self.account in gajim.config.get_per('accounts'): - gajim.config.del_per('accounts', self.account) - img = self.xml.get_object('finish_image') - img.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_DIALOG) - finish_text = '%s\n\n%s' % (_('An error occurred during ' - 'account creation') , reason) - self.finish_label.set_markup(finish_text) - self.notebook.set_current_page(6) # show finish page + def acc_is_not_ok(self, account, reason): + """ + Account creation failed + """ + # We receive events from all accounts from GED + if account != self.account: + return + self.back_button.show() + self.cancel_button.show() + self.go_online_checkbutton.hide() + self.show_vcard_checkbutton.hide() + del gajim.connections[self.account] + if self.account in gajim.config.get_per('accounts'): + gajim.config.del_per('accounts', self.account) + img = self.xml.get_object('finish_image') + img.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_DIALOG) + finish_text = '%s\n\n%s' % (_('An error occurred during ' + 'account creation'), reason) + self.finish_label.set_markup(finish_text) + self.notebook.set_current_page(6) # show finish page - if self.update_progressbar_timeout_id is not None: - gobject.source_remove(self.update_progressbar_timeout_id) + if self.update_progressbar_timeout_id is not None: + gobject.source_remove(self.update_progressbar_timeout_id) - def on_advanced_button_clicked(self, widget): - if 'accounts' in gajim.interface.instances: - gajim.interface.instances['accounts'].window.present() - else: - gajim.interface.instances['accounts'] = AccountsWindow() - gajim.interface.instances['accounts'].select_account( - self.account) - self.window.destroy() + def on_advanced_button_clicked(self, widget): + if 'accounts' in gajim.interface.instances: + gajim.interface.instances['accounts'].window.present() + else: + gajim.interface.instances['accounts'] = AccountsWindow() + gajim.interface.instances['accounts'].select_account( + self.account) + self.window.destroy() - def on_finish_button_clicked(self, widget): - go_online = self.xml.get_object('go_online_checkbutton').get_active() - show_vcard = self.xml.get_object('show_vcard_checkbutton').get_active() - self.window.destroy() - if show_vcard: - gajim.interface.show_vcard_when_connect.append(self.account) - if go_online: - gajim.interface.roster.send_status(self.account, 'online', '') + def on_finish_button_clicked(self, widget): + go_online = self.xml.get_object('go_online_checkbutton').get_active() + show_vcard = self.xml.get_object('show_vcard_checkbutton').get_active() + self.window.destroy() + if show_vcard: + gajim.interface.show_vcard_when_connect.append(self.account) + if go_online: + gajim.interface.roster.send_status(self.account, 'online', '') - def on_username_entry_key_press_event(self, widget, event): - # Check for pressed @ and jump to combobox if found - if event.keyval == gtk.keysyms.at: - combobox = self.xml.get_object('server_comboboxentry') - combobox.grab_focus() - combobox.child.set_position(-1) - return True + def on_username_entry_key_press_event(self, widget, event): + # Check for pressed @ and jump to combobox if found + if event.keyval == gtk.keysyms.at: + combobox = self.xml.get_object('server_comboboxentry') + combobox.grab_focus() + combobox.child.set_position(-1) + return True - def on_server_comboboxentry_key_press_event(self, widget, event, combobox): - # If backspace is pressed in empty field, return to the nick entry field - backspace = event.keyval == gtk.keysyms.BackSpace - empty = len(combobox.get_active_text()) == 0 - if backspace and empty and self.modify: - username_entry = self.xml.get_object('username_entry') - username_entry.grab_focus() - username_entry.set_position(-1) - return True + def on_server_comboboxentry_key_press_event(self, widget, event, combobox): + # If backspace is pressed in empty field, return to the nick entry field + backspace = event.keyval == gtk.keysyms.BackSpace + empty = len(combobox.get_active_text()) == 0 + if backspace and empty and self.modify: + username_entry = self.xml.get_object('username_entry') + username_entry.grab_focus() + username_entry.set_position(-1) + return True - def get_config(self, login, server, savepass, password, anonymous=False): - config = {} - config['name'] = login - config['hostname'] = server - config['savepass'] = savepass - config['password'] = password - config['resource'] = 'Gajim' - config['anonymous_auth'] = anonymous - config['priority'] = 5 - config['autoconnect'] = True - config['no_log_for'] = '' - config['sync_with_global_status'] = True - config['proxy'] = '' - config['usessl'] = False - config['use_custom_host'] = False - config['custom_port'] = 0 - config['custom_host'] = '' - config['keyname'] = '' - config['keyid'] = '' - return config + def get_config(self, login, server, savepass, password, anonymous=False): + config = {} + config['name'] = login + config['hostname'] = server + config['savepass'] = savepass + config['password'] = password + config['resource'] = 'Gajim' + config['anonymous_auth'] = anonymous + config['priority'] = 5 + config['autoconnect'] = True + config['no_log_for'] = '' + config['sync_with_global_status'] = True + config['proxy'] = '' + config['usessl'] = False + config['use_custom_host'] = False + config['custom_port'] = 0 + config['custom_host'] = '' + config['keyname'] = '' + config['keyid'] = '' + return config - def save_account(self, login, server, savepass, password, anonymous=False): - if self.account in gajim.connections: - dialogs.ErrorDialog(_('Account name is in use'), - _('You already have an account using this name.')) - return - con = connection.Connection(self.account) - con.password = password + def save_account(self, login, server, savepass, password, anonymous=False): + if self.account in gajim.connections: + dialogs.ErrorDialog(_('Account name is in use'), + _('You already have an account using this name.')) + return + con = connection.Connection(self.account) + con.password = password - config = self.get_config(login, server, savepass, password, anonymous) + config = self.get_config(login, server, savepass, password, anonymous) - if not self.modify: - con.new_account(self.account, config) - return - gajim.connections[self.account] = con - self.create_vars(config) + if not self.modify: + con.new_account(self.account, config) + return + gajim.connections[self.account] = con + self.create_vars(config) - def create_vars(self, config): - gajim.config.add_per('accounts', self.account) + def create_vars(self, config): + gajim.config.add_per('accounts', self.account) - if not config['savepass']: - config['password'] = '' + if not config['savepass']: + config['password'] = '' - for opt in config: - gajim.config.set_per('accounts', self.account, opt, config[opt]) + for opt in config: + gajim.config.set_per('accounts', self.account, opt, config[opt]) - # update variables - gajim.interface.instances[self.account] = {'infos': {}, 'disco': {}, - 'gc_config': {}, 'search': {}, 'online_dialog': {}} - gajim.interface.minimized_controls[self.account] = {} - gajim.connections[self.account].connected = 0 - gajim.connections[self.account].keepalives = gajim.config.get_per( - 'accounts', self.account, 'keep_alive_every_foo_secs') - gajim.groups[self.account] = {} - gajim.contacts.add_account(self.account) - gajim.gc_connected[self.account] = {} - gajim.automatic_rooms[self.account] = {} - gajim.newly_added[self.account] = [] - gajim.to_be_removed[self.account] = [] - gajim.nicks[self.account] = config['name'] - gajim.block_signed_in_notifications[self.account] = True - gajim.sleeper_state[self.account] = 'off' - gajim.encrypted_chats[self.account] = [] - gajim.last_message_time[self.account] = {} - gajim.status_before_autoaway[self.account] = '' - gajim.transport_avatar[self.account] = {} - gajim.gajim_optional_features[self.account] = [] - gajim.caps_hash[self.account] = '' - # refresh accounts window - if 'accounts' in gajim.interface.instances: - gajim.interface.instances['accounts'].init_accounts() - # refresh roster - if len(gajim.connections) >= 2: # Do not merge accounts if only one exists - gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') - else: - gajim.interface.roster.regroup = False - gajim.interface.roster.setup_and_draw_roster() - gajim.interface.roster.set_actions_menu_needs_rebuild() - gajim.interface.save_config() + # update variables + gajim.interface.instances[self.account] = {'infos': {}, 'disco': {}, + 'gc_config': {}, 'search': {}, 'online_dialog': {}} + gajim.interface.minimized_controls[self.account] = {} + gajim.connections[self.account].connected = 0 + gajim.connections[self.account].keepalives = gajim.config.get_per( + 'accounts', self.account, 'keep_alive_every_foo_secs') + gajim.groups[self.account] = {} + gajim.contacts.add_account(self.account) + gajim.gc_connected[self.account] = {} + gajim.automatic_rooms[self.account] = {} + gajim.newly_added[self.account] = [] + gajim.to_be_removed[self.account] = [] + gajim.nicks[self.account] = config['name'] + gajim.block_signed_in_notifications[self.account] = True + gajim.sleeper_state[self.account] = 'off' + gajim.encrypted_chats[self.account] = [] + gajim.last_message_time[self.account] = {} + gajim.status_before_autoaway[self.account] = '' + gajim.transport_avatar[self.account] = {} + gajim.gajim_optional_features[self.account] = [] + gajim.caps_hash[self.account] = '' + # refresh accounts window + if 'accounts' in gajim.interface.instances: + gajim.interface.instances['accounts'].init_accounts() + # refresh roster + if len(gajim.connections) >= 2: # Do not merge accounts if only one exists + gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') + else: + gajim.interface.roster.regroup = False + gajim.interface.roster.setup_and_draw_roster() + gajim.interface.roster.set_actions_menu_needs_rebuild() + gajim.interface.save_config() class ManagePEPServicesWindow: - def __init__(self, account): - self.xml = gtkgui_helpers.get_gtk_builder('manage_pep_services_window.ui') - self.window = self.xml.get_object('manage_pep_services_window') - self.window.set_transient_for(gajim.interface.roster.window) - self.xml.get_object('configure_button').set_sensitive(False) - self.xml.get_object('delete_button').set_sensitive(False) - self.xml.connect_signals(self) - self.account = account + def __init__(self, account): + self.xml = gtkgui_helpers.get_gtk_builder('manage_pep_services_window.ui') + self.window = self.xml.get_object('manage_pep_services_window') + self.window.set_transient_for(gajim.interface.roster.window) + self.xml.get_object('configure_button').set_sensitive(False) + self.xml.get_object('delete_button').set_sensitive(False) + self.xml.connect_signals(self) + self.account = account - self.init_services() - self.xml.get_object('services_treeview').get_selection().connect( - 'changed', self.on_services_selection_changed) - self.window.show_all() + self.init_services() + self.xml.get_object('services_treeview').get_selection().connect( + 'changed', self.on_services_selection_changed) + self.window.show_all() - def on_manage_pep_services_window_destroy(self, widget): - '''close window''' - del gajim.interface.instances[self.account]['pep_services'] + def on_manage_pep_services_window_destroy(self, widget): + '''close window''' + del gajim.interface.instances[self.account]['pep_services'] - def on_close_button_clicked(self, widget): - self.window.destroy() + def on_close_button_clicked(self, widget): + self.window.destroy() - def on_services_selection_changed(self, sel): - self.xml.get_object('configure_button').set_sensitive(True) - self.xml.get_object('delete_button').set_sensitive(True) + def on_services_selection_changed(self, sel): + self.xml.get_object('configure_button').set_sensitive(True) + self.xml.get_object('delete_button').set_sensitive(True) - def init_services(self): - self.treeview = self.xml.get_object('services_treeview') - # service, access_model, group - self.treestore = gtk.ListStore(str) - self.treeview.set_model(self.treestore) + def init_services(self): + self.treeview = self.xml.get_object('services_treeview') + # service, access_model, group + self.treestore = gtk.ListStore(str) + self.treeview.set_model(self.treestore) - col = gtk.TreeViewColumn('Service') - self.treeview.append_column(col) + col = gtk.TreeViewColumn('Service') + self.treeview.append_column(col) - cellrenderer_text = gtk.CellRendererText() - col.pack_start(cellrenderer_text) - col.add_attribute(cellrenderer_text, 'text', 0) + cellrenderer_text = gtk.CellRendererText() + col.pack_start(cellrenderer_text) + col.add_attribute(cellrenderer_text, 'text', 0) - our_jid = gajim.get_jid_from_account(self.account) - gajim.connections[self.account].discoverItems(our_jid) + our_jid = gajim.get_jid_from_account(self.account) + gajim.connections[self.account].discoverItems(our_jid) - def items_received(self, items): - our_jid = gajim.get_jid_from_account(self.account) - for item in items: - if 'jid' in item and item['jid'] == our_jid and 'node' in item: - self.treestore.append([item['node']]) + def items_received(self, items): + our_jid = gajim.get_jid_from_account(self.account) + for item in items: + if 'jid' in item and item['jid'] == our_jid and 'node' in item: + self.treestore.append([item['node']]) - def node_removed(self, node): - model = self.treeview.get_model() - iter_ = model.get_iter_root() - while iter_: - if model[iter_][0] == node: - model.remove(iter_) - break - iter_ = model.get_iter_next(iter_) + def node_removed(self, node): + model = self.treeview.get_model() + iter_ = model.get_iter_root() + while iter_: + if model[iter_][0] == node: + model.remove(iter_) + break + iter_ = model.get_iter_next(iter_) - def on_delete_button_clicked(self, widget): - selection = self.treeview.get_selection() - if not selection: - return - model, iter_ = selection.get_selected() - node = model[iter_][0] - our_jid = gajim.get_jid_from_account(self.account) - gajim.connections[self.account].send_pb_delete(our_jid, node) + def on_delete_button_clicked(self, widget): + selection = self.treeview.get_selection() + if not selection: + return + model, iter_ = selection.get_selected() + node = model[iter_][0] + our_jid = gajim.get_jid_from_account(self.account) + gajim.connections[self.account].send_pb_delete(our_jid, node) - def on_configure_button_clicked(self, widget): - selection = self.treeview.get_selection() - if not selection: - return - model, iter_ = selection.get_selected() - node = model[iter_][0] - our_jid = gajim.get_jid_from_account(self.account) - gajim.connections[self.account].request_pb_configuration(our_jid, node) + def on_configure_button_clicked(self, widget): + selection = self.treeview.get_selection() + if not selection: + return + model, iter_ = selection.get_selected() + node = model[iter_][0] + our_jid = gajim.get_jid_from_account(self.account) + gajim.connections[self.account].request_pb_configuration(our_jid, node) - def config(self, node, form): - def on_ok(form, node): - form.type = 'submit' - our_jid = gajim.get_jid_from_account(self.account) - gajim.connections[self.account].send_pb_configure(our_jid, node, form) - window = dialogs.DataFormWindow(form, (on_ok, node)) - title = "Configure %s" % node - window.set_title(title) - window.show_all() + def config(self, node, form): + def on_ok(form, node): + form.type = 'submit' + our_jid = gajim.get_jid_from_account(self.account) + gajim.connections[self.account].send_pb_configure(our_jid, node, form) + window = dialogs.DataFormWindow(form, (on_ok, node)) + title = "Configure %s" % node + window.set_title(title) + window.show_all() class ManageSoundsWindow: - def __init__(self): - self.xml = gtkgui_helpers.get_gtk_builder('manage_sounds_window.ui') - self.window = self.xml.get_object('manage_sounds_window') + def __init__(self): + self.xml = gtkgui_helpers.get_gtk_builder('manage_sounds_window.ui') + self.window = self.xml.get_object('manage_sounds_window') - # sounds treeview - self.sound_tree = self.xml.get_object('sounds_treeview') + # sounds treeview + self.sound_tree = self.xml.get_object('sounds_treeview') - # active, event ui name, path to sound file, event_config_name - model = gtk.ListStore(bool, str, str, str) - self.sound_tree.set_model(model) + # active, event ui name, path to sound file, event_config_name + model = gtk.ListStore(bool, str, str, str) + self.sound_tree.set_model(model) - col = gtk.TreeViewColumn(_('Active')) - self.sound_tree.append_column(col) - renderer = gtk.CellRendererToggle() - renderer.set_property('activatable', True) - renderer.connect('toggled', self.sound_toggled_cb) - col.pack_start(renderer) - col.set_attributes(renderer, active = 0) + col = gtk.TreeViewColumn(_('Active')) + self.sound_tree.append_column(col) + renderer = gtk.CellRendererToggle() + renderer.set_property('activatable', True) + renderer.connect('toggled', self.sound_toggled_cb) + col.pack_start(renderer) + col.set_attributes(renderer, active = 0) - col = gtk.TreeViewColumn(_('Event')) - self.sound_tree.append_column(col) - renderer = gtk.CellRendererText() - col.pack_start(renderer) - col.set_attributes(renderer, text = 1) + col = gtk.TreeViewColumn(_('Event')) + self.sound_tree.append_column(col) + renderer = gtk.CellRendererText() + col.pack_start(renderer) + col.set_attributes(renderer, text = 1) - self.fill_sound_treeview() + self.fill_sound_treeview() - self.xml.connect_signals(self) + self.xml.connect_signals(self) - self.sound_tree.get_model().connect('row-changed', - self.on_sounds_treemodel_row_changed) + self.sound_tree.get_model().connect('row-changed', + self.on_sounds_treemodel_row_changed) - self.window.show_all() + self.window.show_all() - def on_sounds_treemodel_row_changed(self, model, path, iter_): - sound_event = model[iter_][3].decode('utf-8') - gajim.config.set_per('soundevents', sound_event, 'enabled', - bool(model[path][0])) - gajim.config.set_per('soundevents', sound_event, 'path', - model[iter_][2].decode('utf-8')) - gajim.interface.save_config() + def on_sounds_treemodel_row_changed(self, model, path, iter_): + sound_event = model[iter_][3].decode('utf-8') + gajim.config.set_per('soundevents', sound_event, 'enabled', + bool(model[path][0])) + gajim.config.set_per('soundevents', sound_event, 'path', + model[iter_][2].decode('utf-8')) + gajim.interface.save_config() - def sound_toggled_cb(self, cell, path): - model = self.sound_tree.get_model() - model[path][0] = not model[path][0] + def sound_toggled_cb(self, cell, path): + model = self.sound_tree.get_model() + model[path][0] = not model[path][0] - def fill_sound_treeview(self): - model = self.sound_tree.get_model() - model.clear() - model.set_sort_column_id(1, gtk.SORT_ASCENDING) + def fill_sound_treeview(self): + model = self.sound_tree.get_model() + model.clear() + model.set_sort_column_id(1, gtk.SORT_ASCENDING) - # NOTE: sounds_ui_names MUST have all items of - # sounds = gajim.config.get_per('soundevents') as keys - sounds_dict = { - 'first_message_received': _('First Message Received'), - 'next_message_received_focused': _('Next Message Received Focused'), - 'next_message_received_unfocused': - _('Next Message Received Unfocused'), - 'contact_connected': _('Contact Connected'), - 'contact_disconnected': _('Contact Disconnected'), - 'message_sent': _('Message Sent'), - 'muc_message_highlight': _('Group Chat Message Highlight'), - 'muc_message_received': _('Group Chat Message Received'), - 'gmail_received': _('GMail Email Received') - } + # NOTE: sounds_ui_names MUST have all items of + # sounds = gajim.config.get_per('soundevents') as keys + sounds_dict = { + 'first_message_received': _('First Message Received'), + 'next_message_received_focused': _('Next Message Received Focused'), + 'next_message_received_unfocused': + _('Next Message Received Unfocused'), + 'contact_connected': _('Contact Connected'), + 'contact_disconnected': _('Contact Disconnected'), + 'message_sent': _('Message Sent'), + 'muc_message_highlight': _('Group Chat Message Highlight'), + 'muc_message_received': _('Group Chat Message Received'), + 'gmail_received': _('GMail Email Received') + } - for sound_event_config_name, sound_ui_name in sounds_dict.items(): - enabled = gajim.config.get_per('soundevents', - sound_event_config_name, 'enabled') - path = gajim.config.get_per('soundevents', - sound_event_config_name, 'path') - model.append((enabled, sound_ui_name, path, sound_event_config_name)) + for sound_event_config_name, sound_ui_name in sounds_dict.items(): + enabled = gajim.config.get_per('soundevents', + sound_event_config_name, 'enabled') + path = gajim.config.get_per('soundevents', + sound_event_config_name, 'path') + model.append((enabled, sound_ui_name, path, sound_event_config_name)) - def on_treeview_sounds_cursor_changed(self, widget, data = None): - (model, iter_) = self.sound_tree.get_selection().get_selected() - sounds_entry = self.xml.get_object('sounds_entry') - if not iter_: - sounds_entry.set_text('') - return - path_to_snd_file = model[iter_][2] - sounds_entry.set_text(path_to_snd_file) + def on_treeview_sounds_cursor_changed(self, widget, data = None): + (model, iter_) = self.sound_tree.get_selection().get_selected() + sounds_entry = self.xml.get_object('sounds_entry') + if not iter_: + sounds_entry.set_text('') + return + path_to_snd_file = model[iter_][2] + sounds_entry.set_text(path_to_snd_file) - def on_browse_for_sounds_button_clicked(self, widget, data = None): - (model, iter_) = self.sound_tree.get_selection().get_selected() - if not iter_: - return - def on_ok(widget, path_to_snd_file): - self.dialog.destroy() - model, iter_ = self.sound_tree.get_selection().get_selected() - if not path_to_snd_file: - model[iter_][2] = '' - self.xml.get_object('sounds_entry').set_text('') - model[iter_][0] = False - return - directory = os.path.dirname(path_to_snd_file) - gajim.config.set('last_sounds_dir', directory) - path_to_snd_file = helpers.strip_soundfile_path(path_to_snd_file) - self.xml.get_object('sounds_entry').set_text(path_to_snd_file) + def on_browse_for_sounds_button_clicked(self, widget, data = None): + (model, iter_) = self.sound_tree.get_selection().get_selected() + if not iter_: + return + def on_ok(widget, path_to_snd_file): + self.dialog.destroy() + model, iter_ = self.sound_tree.get_selection().get_selected() + if not path_to_snd_file: + model[iter_][2] = '' + self.xml.get_object('sounds_entry').set_text('') + model[iter_][0] = False + return + directory = os.path.dirname(path_to_snd_file) + gajim.config.set('last_sounds_dir', directory) + path_to_snd_file = helpers.strip_soundfile_path(path_to_snd_file) + self.xml.get_object('sounds_entry').set_text(path_to_snd_file) - model[iter_][2] = path_to_snd_file # set new path to sounds_model - model[iter_][0] = True # set the sound to enabled + model[iter_][2] = path_to_snd_file # set new path to sounds_model + model[iter_][0] = True # set the sound to enabled - def on_cancel(widget): - self.dialog.destroy() + def on_cancel(widget): + self.dialog.destroy() - path_to_snd_file = model[iter_][2].decode('utf-8') - self.dialog = dialogs.SoundChooserDialog(path_to_snd_file, on_ok, - on_cancel) + path_to_snd_file = model[iter_][2].decode('utf-8') + self.dialog = dialogs.SoundChooserDialog(path_to_snd_file, on_ok, + on_cancel) - def on_sounds_entry_changed(self, widget): - path_to_snd_file = widget.get_text() - model, iter_ = self.sound_tree.get_selection().get_selected() - model[iter_][2] = path_to_snd_file # set new path to sounds_model + def on_sounds_entry_changed(self, widget): + path_to_snd_file = widget.get_text() + model, iter_ = self.sound_tree.get_selection().get_selected() + model[iter_][2] = path_to_snd_file # set new path to sounds_model - def on_play_button_clicked(self, widget): - model, iter_ = self.sound_tree.get_selection().get_selected() - if not iter_: - return - snd_event_config_name = model[iter_][3] - helpers.play_sound(snd_event_config_name) + def on_play_button_clicked(self, widget): + model, iter_ = self.sound_tree.get_selection().get_selected() + if not iter_: + return + snd_event_config_name = model[iter_][3] + helpers.play_sound(snd_event_config_name) - def on_close_button_clicked(self, widget): - self.window.hide() + def on_close_button_clicked(self, widget): + self.window.hide() - def on_manage_sounds_window_delete_event(self, widget, event): - self.window.hide() - return True # do NOT destroy the window -# vim: se ts=3: + def on_manage_sounds_window_delete_event(self, widget, event): + self.window.hide() + return True # do NOT destroy the window diff --git a/src/conversation_textview.py b/src/conversation_textview.py index c94602615..c0f5b83ed 100644 --- a/src/conversation_textview.py +++ b/src/conversation_textview.py @@ -56,1297 +56,1295 @@ ALREADY_RECEIVED = 1 SHOWN = 2 def is_selection_modified(mark): - name = mark.get_name() - if name and name in ('selection_bound', 'insert'): - return True - else: - return False + name = mark.get_name() + if name and name in ('selection_bound', 'insert'): + return True + else: + return False def has_focus(widget): - return widget.flags() & gtk.HAS_FOCUS == gtk.HAS_FOCUS + return widget.flags() & gtk.HAS_FOCUS == gtk.HAS_FOCUS class TextViewImage(gtk.Image): - def __init__(self, anchor, text): - super(TextViewImage, self).__init__() - self.anchor = anchor - self._selected = False - self._disconnect_funcs = [] - self.connect('parent-set', self.on_parent_set) - self.connect('expose-event', self.on_expose) - self.set_tooltip_text(text) - self.anchor.set_data('plaintext', text) + def __init__(self, anchor, text): + super(TextViewImage, self).__init__() + self.anchor = anchor + self._selected = False + self._disconnect_funcs = [] + self.connect('parent-set', self.on_parent_set) + self.connect('expose-event', self.on_expose) + self.set_tooltip_text(text) + self.anchor.set_data('plaintext', text) - def _get_selected(self): - parent = self.get_parent() - if not parent or not self.anchor: return False - buffer_ = parent.get_buffer() - position = buffer_.get_iter_at_child_anchor(self.anchor) - bounds = buffer_.get_selection_bounds() - if bounds and position.in_range(*bounds): - return True - else: - return False + def _get_selected(self): + parent = self.get_parent() + if not parent or not self.anchor: return False + buffer_ = parent.get_buffer() + position = buffer_.get_iter_at_child_anchor(self.anchor) + bounds = buffer_.get_selection_bounds() + if bounds and position.in_range(*bounds): + return True + else: + return False - def get_state(self): - parent = self.get_parent() - if not parent: - return gtk.STATE_NORMAL - if self._selected: - if has_focus(parent): - return gtk.STATE_SELECTED - else: - return gtk.STATE_ACTIVE - else: - return gtk.STATE_NORMAL + def get_state(self): + parent = self.get_parent() + if not parent: + return gtk.STATE_NORMAL + if self._selected: + if has_focus(parent): + return gtk.STATE_SELECTED + else: + return gtk.STATE_ACTIVE + else: + return gtk.STATE_NORMAL - def _update_selected(self): - selected = self._get_selected() - if self._selected != selected: - self._selected = selected - self.queue_draw() + def _update_selected(self): + selected = self._get_selected() + if self._selected != selected: + self._selected = selected + self.queue_draw() - def _do_connect(self, widget, signal, callback): - id_ = widget.connect(signal, callback) - def disconnect(): - widget.disconnect(id_) - self._disconnect_funcs.append(disconnect) + def _do_connect(self, widget, signal, callback): + id_ = widget.connect(signal, callback) + def disconnect(): + widget.disconnect(id_) + self._disconnect_funcs.append(disconnect) - def _disconnect_signals(self): - for func in self._disconnect_funcs: - func() - self._disconnect_funcs = [] + def _disconnect_signals(self): + for func in self._disconnect_funcs: + func() + self._disconnect_funcs = [] - def on_parent_set(self, widget, old_parent): - parent = self.get_parent() - if not parent: - self._disconnect_signals() - return + def on_parent_set(self, widget, old_parent): + parent = self.get_parent() + if not parent: + self._disconnect_signals() + return - self._do_connect(parent, 'style-set', self.do_queue_draw) - self._do_connect(parent, 'focus-in-event', self.do_queue_draw) - self._do_connect(parent, 'focus-out-event', self.do_queue_draw) + self._do_connect(parent, 'style-set', self.do_queue_draw) + self._do_connect(parent, 'focus-in-event', self.do_queue_draw) + self._do_connect(parent, 'focus-out-event', self.do_queue_draw) - textbuf = parent.get_buffer() - self._do_connect(textbuf, 'mark-set', self.on_mark_set) - self._do_connect(textbuf, 'mark-deleted', self.on_mark_deleted) + textbuf = parent.get_buffer() + self._do_connect(textbuf, 'mark-set', self.on_mark_set) + self._do_connect(textbuf, 'mark-deleted', self.on_mark_deleted) - def do_queue_draw(self, *args): - self.queue_draw() - return False + def do_queue_draw(self, *args): + self.queue_draw() + return False - def on_mark_set(self, buf, iterat, mark): - self.on_mark_modified(mark) - return False + def on_mark_set(self, buf, iterat, mark): + self.on_mark_modified(mark) + return False - def on_mark_deleted(self, buf, mark): - self.on_mark_modified(mark) - return False + def on_mark_deleted(self, buf, mark): + self.on_mark_modified(mark) + return False - def on_mark_modified(self, mark): - if is_selection_modified(mark): - self._update_selected() + def on_mark_modified(self, mark): + if is_selection_modified(mark): + self._update_selected() - def on_expose(self, widget, event): - state = self.get_state() - if state != gtk.STATE_NORMAL: - gc = widget.get_style().base_gc[state] - area = widget.allocation - widget.window.draw_rectangle(gc, True, area.x, area.y, - area.width, area.height) - return False + def on_expose(self, widget, event): + state = self.get_state() + if state != gtk.STATE_NORMAL: + gc = widget.get_style().base_gc[state] + area = widget.allocation + widget.window.draw_rectangle(gc, True, area.x, area.y, + area.width, area.height) + return False class ConversationTextview(gobject.GObject): - """ - Class for the conversation textview (where user reads already said messages) - for chat/groupchat windows - """ - __gsignals__ = dict( - quote = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION, - None, # return value - (str, ) # arguments - ) - ) - - FOCUS_OUT_LINE_PIXBUF = gtkgui_helpers.get_icon_pixmap('gajim-muc_separator') - XEP0184_WARNING_PIXBUF = gtkgui_helpers.get_icon_pixmap( - 'gajim-receipt_missing') - - # smooth scroll constants - MAX_SCROLL_TIME = 0.4 # seconds - SCROLL_DELAY = 33 # milliseconds - - def __init__(self, account, used_in_history_window = False): - """ - If used_in_history_window is True, then we do not show Clear menuitem in - context menu - """ - gobject.GObject.__init__(self) - self.used_in_history_window = used_in_history_window - - self.fc = FuzzyClock() - - - # no need to inherit TextView, use it as atrribute is safer - self.tv = HtmlTextView() - self.tv.html_hyperlink_handler = self.html_hyperlink_handler - - # set properties - self.tv.set_border_width(1) - self.tv.set_accepts_tab(True) - self.tv.set_editable(False) - self.tv.set_cursor_visible(False) - self.tv.set_wrap_mode(gtk.WRAP_WORD_CHAR) - self.tv.set_left_margin(2) - self.tv.set_right_margin(2) - self.handlers = {} - self.images = [] - self.image_cache = {} - self.xep0184_marks = {} - self.xep0184_shown = {} - - # It's True when we scroll in the code, so we can detect scroll from user - self.auto_scrolling = False - - # connect signals - id_ = self.tv.connect('motion_notify_event', - self.on_textview_motion_notify_event) - self.handlers[id_] = self.tv - id_ = self.tv.connect('populate_popup', self.on_textview_populate_popup) - self.handlers[id_] = self.tv - id_ = self.tv.connect('button_press_event', - self.on_textview_button_press_event) - self.handlers[id_] = self.tv - - id_ = self.tv.connect('expose-event', - self.on_textview_expose_event) - self.handlers[id_] = self.tv - - - self.account = account - self.change_cursor = False - self.last_time_printout = 0 - - font = pango.FontDescription(gajim.config.get('conversation_font')) - self.tv.modify_font(font) - buffer_ = self.tv.get_buffer() - end_iter = buffer_.get_end_iter() - buffer_.create_mark('end', end_iter, False) - - self.tagIn = buffer_.create_tag('incoming') - color = gajim.config.get('inmsgcolor') - font = pango.FontDescription(gajim.config.get('inmsgfont')) - self.tagIn.set_property('foreground', color) - self.tagIn.set_property('font-desc', font) - - self.tagOut = buffer_.create_tag('outgoing') - color = gajim.config.get('outmsgcolor') - font = pango.FontDescription(gajim.config.get('outmsgfont')) - self.tagOut.set_property('foreground', color) - self.tagOut.set_property('font-desc', font) - - self.tagStatus = buffer_.create_tag('status') - color = gajim.config.get('statusmsgcolor') - font = pango.FontDescription(gajim.config.get('satusmsgfont')) - self.tagStatus.set_property('foreground', color) - self.tagStatus.set_property('font-desc', font) - - self.tagInText = buffer_.create_tag('incomingtxt') - color = gajim.config.get('inmsgtxtcolor') - font = pango.FontDescription(gajim.config.get('inmsgtxtfont')) - if color: - self.tagInText.set_property('foreground', color) - self.tagInText.set_property('font-desc', font) - - self.tagOutText = buffer_.create_tag('outgoingtxt') - color = gajim.config.get('outmsgtxtcolor') - if color: - font = pango.FontDescription(gajim.config.get('outmsgtxtfont')) - self.tagOutText.set_property('foreground', color) - self.tagOutText.set_property('font-desc', font) - - colors = gajim.config.get('gc_nicknames_colors') - colors = colors.split(':') - for i,color in enumerate(colors): - tagname = 'gc_nickname_color_' + str(i) - tag = buffer_.create_tag(tagname) - tag.set_property('foreground', color) - - tag = buffer_.create_tag('marked') - color = gajim.config.get('markedmsgcolor') - tag.set_property('foreground', color) - tag.set_property('weight', pango.WEIGHT_BOLD) - - tag = buffer_.create_tag('time_sometimes') - tag.set_property('foreground', 'darkgrey') - tag.set_property('scale', pango.SCALE_SMALL) - tag.set_property('justification', gtk.JUSTIFY_CENTER) - - tag = buffer_.create_tag('small') - tag.set_property('scale', pango.SCALE_SMALL) - - tag = buffer_.create_tag('restored_message') - color = gajim.config.get('restored_messages_color') - tag.set_property('foreground', color) - - self.tagURL = buffer_.create_tag('url') - color = gajim.config.get('urlmsgcolor') - self.tagURL.set_property('foreground', color) - self.tagURL.set_property('underline', pango.UNDERLINE_SINGLE) - id_ = self.tagURL.connect('event', self.hyperlink_handler, 'url') - self.handlers[id_] = self.tagURL - - self.tagMail = buffer_.create_tag('mail') - self.tagMail.set_property('foreground', color) - self.tagMail.set_property('underline', pango.UNDERLINE_SINGLE) - id_ = self.tagMail.connect('event', self.hyperlink_handler, 'mail') - self.handlers[id_] = self.tagMail - - self.tagXMPP = buffer_.create_tag('xmpp') - self.tagXMPP.set_property('foreground', color) - self.tagXMPP.set_property('underline', pango.UNDERLINE_SINGLE) - id_ = self.tagXMPP.connect('event', self.hyperlink_handler, 'xmpp') - self.handlers[id_] = self.tagXMPP - - self.tagSthAtSth = buffer_.create_tag('sth_at_sth') - self.tagSthAtSth.set_property('foreground', color) - self.tagSthAtSth.set_property('underline', pango.UNDERLINE_SINGLE) - id_ = self.tagSthAtSth.connect('event', self.hyperlink_handler, - 'sth_at_sth') - self.handlers[id_] = self.tagSthAtSth - - tag = buffer_.create_tag('bold') - tag.set_property('weight', pango.WEIGHT_BOLD) - - tag = buffer_.create_tag('italic') - tag.set_property('style', pango.STYLE_ITALIC) - - tag = buffer_.create_tag('underline') - tag.set_property('underline', pango.UNDERLINE_SINGLE) - - buffer_.create_tag('focus-out-line', justification = gtk.JUSTIFY_CENTER) - - tag = buffer_.create_tag('xep0184-warning') - - # One mark at the begining then 2 marks between each lines - size = gajim.config.get('max_conversation_lines') - size = 2 * size - 1 - self.marks_queue = Queue.Queue(size) - - self.allow_focus_out_line = True - # holds a mark at the end of --- line - self.focus_out_end_mark = None - - self.xep0184_warning_tooltip = tooltips.BaseTooltip() - - self.line_tooltip = tooltips.BaseTooltip() - # use it for hr too - self.tv.focus_out_line_pixbuf = ConversationTextview.FOCUS_OUT_LINE_PIXBUF - self.smooth_id = None - - def del_handlers(self): - for i in self.handlers.keys(): - if self.handlers[i].handler_is_connected(i): - self.handlers[i].disconnect(i) - del self.handlers - self.tv.destroy() - #FIXME: - # self.line_tooltip.destroy() - - def update_tags(self): - self.tagIn.set_property('foreground', gajim.config.get('inmsgcolor')) - self.tagOut.set_property('foreground', gajim.config.get('outmsgcolor')) - self.tagStatus.set_property('foreground', - gajim.config.get('statusmsgcolor')) - self.tagURL.set_property('foreground', gajim.config.get('urlmsgcolor')) - self.tagMail.set_property('foreground', gajim.config.get('urlmsgcolor')) - - def at_the_end(self): - buffer_ = self.tv.get_buffer() - end_iter = buffer_.get_end_iter() - end_rect = self.tv.get_iter_location(end_iter) - visible_rect = self.tv.get_visible_rect() - if end_rect.y <= (visible_rect.y + visible_rect.height): - return True - return False - - # Smooth scrolling inspired by Pidgin code - def smooth_scroll(self): - parent = self.tv.get_parent() - if not parent: - return False - vadj = parent.get_vadjustment() - max_val = vadj.upper - vadj.page_size + 1 - cur_val = vadj.get_value() - # scroll by 1/3rd of remaining distance - onethird = cur_val + ((max_val - cur_val) / 3.0) - self.auto_scrolling = True - vadj.set_value(onethird) - self.auto_scrolling = False - if max_val - onethird < 0.01: - self.smooth_id = None - self.smooth_scroll_timer.cancel() - return False - return True - - def smooth_scroll_timeout(self): - gobject.idle_add(self.do_smooth_scroll_timeout) - return - - def do_smooth_scroll_timeout(self): - if not self.smooth_id: - # we finished scrolling - return - gobject.source_remove(self.smooth_id) - self.smooth_id = None - parent = self.tv.get_parent() - if parent: - vadj = parent.get_vadjustment() - self.auto_scrolling = True - vadj.set_value(vadj.upper - vadj.page_size + 1) - self.auto_scrolling = False - - def smooth_scroll_to_end(self): - if None != self.smooth_id: # already scrolling - return False - self.smooth_id = gobject.timeout_add(self.SCROLL_DELAY, - self.smooth_scroll) - self.smooth_scroll_timer = Timer(self.MAX_SCROLL_TIME, - self.smooth_scroll_timeout) - self.smooth_scroll_timer.start() - return False - - def scroll_to_end(self): - parent = self.tv.get_parent() - buffer_ = self.tv.get_buffer() - end_mark = buffer_.get_mark('end') - if not end_mark: - return False - self.auto_scrolling = True - self.tv.scroll_to_mark(end_mark, 0, True, 0, 1) - adjustment = parent.get_hadjustment() - adjustment.set_value(0) - self.auto_scrolling = False - return False # when called in an idle_add, just do it once - - def bring_scroll_to_end(self, diff_y = 0, - use_smooth=gajim.config.get('use_smooth_scrolling')): - ''' scrolls to the end of textview if end is not visible ''' - buffer_ = self.tv.get_buffer() - end_iter = buffer_.get_end_iter() - end_rect = self.tv.get_iter_location(end_iter) - visible_rect = self.tv.get_visible_rect() - # scroll only if expected end is not visible - if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y): - if use_smooth: - gobject.idle_add(self.smooth_scroll_to_end) - else: - gobject.idle_add(self.scroll_to_end_iter) - - def scroll_to_end_iter(self): - buffer_ = self.tv.get_buffer() - end_iter = buffer_.get_end_iter() - if not end_iter: - return False - self.tv.scroll_to_iter(end_iter, 0, False, 1, 1) - return False # when called in an idle_add, just do it once - - def stop_scrolling(self): - if self.smooth_id: - gobject.source_remove(self.smooth_id) - self.smooth_id = None - self.smooth_scroll_timer.cancel() - - def show_xep0184_warning(self, id_): - if id_ in self.xep0184_marks: - return - - buffer_ = self.tv.get_buffer() - buffer_.begin_user_action() - - self.xep0184_marks[id_] = buffer_.create_mark(None, - buffer_.get_end_iter(), left_gravity=True) - self.xep0184_shown[id_] = NOT_SHOWN - - def show_it(): - if (not id_ in self.xep0184_shown) or \ - self.xep0184_shown[id_] == ALREADY_RECEIVED: - return False - - end_iter = buffer_.get_iter_at_mark(self.xep0184_marks[id_]) - buffer_.insert(end_iter, ' ') - anchor = buffer_.create_child_anchor(end_iter) - img = TextViewImage(anchor, '') - img.set_from_pixbuf(ConversationTextview.XEP0184_WARNING_PIXBUF) - img.show() - self.tv.add_child_at_anchor(img, anchor) - before_img_iter = buffer_.get_iter_at_mark(self.xep0184_marks[id_]) - before_img_iter.forward_char() - post_img_iter = before_img_iter.copy() - post_img_iter.forward_char() - buffer_.apply_tag_by_name('xep0184-warning', before_img_iter, - post_img_iter) - - self.xep0184_shown[id_] = SHOWN - return False - gobject.timeout_add_seconds(3, show_it) - - buffer_.end_user_action() - - def hide_xep0184_warning(self, id_): - if id_ not in self.xep0184_marks: - return - - if self.xep0184_shown[id_] == NOT_SHOWN: - self.xep0184_shown[id_] = ALREADY_RECEIVED - return - - buffer_ = self.tv.get_buffer() - buffer_.begin_user_action() - - begin_iter = buffer_.get_iter_at_mark(self.xep0184_marks[id_]) - - end_iter = begin_iter.copy() - # XXX: Is there a nicer way? - end_iter.forward_char() - end_iter.forward_char() - - buffer_.delete(begin_iter, end_iter) - buffer_.delete_mark(self.xep0184_marks[id_]) - - buffer_.end_user_action() - - del self.xep0184_marks[id_] - del self.xep0184_shown[id_] - - def show_focus_out_line(self): - if not self.allow_focus_out_line: - # if room did not receive focus-in from the last time we added - # --- line then do not readd - return - - print_focus_out_line = False - buffer_ = self.tv.get_buffer() - - if self.focus_out_end_mark is None: - # this happens only first time we focus out on this room - print_focus_out_line = True - - else: - focus_out_end_iter = buffer_.get_iter_at_mark(self.focus_out_end_mark) - focus_out_end_iter_offset = focus_out_end_iter.get_offset() - if focus_out_end_iter_offset != buffer_.get_end_iter().get_offset(): - # this means after last-focus something was printed - # (else end_iter's offset is the same as before) - # only then print ---- line (eg. we avoid printing many following - # ---- lines) - print_focus_out_line = True - - if print_focus_out_line and buffer_.get_char_count() > 0: - buffer_.begin_user_action() - - # remove previous focus out line if such focus out line exists - if self.focus_out_end_mark is not None: - end_iter_for_previous_line = buffer_.get_iter_at_mark( - self.focus_out_end_mark) - begin_iter_for_previous_line = end_iter_for_previous_line.copy() - # img_char+1 (the '\n') - begin_iter_for_previous_line.backward_chars(2) - - # remove focus out line - buffer_.delete(begin_iter_for_previous_line, - end_iter_for_previous_line) - buffer_.delete_mark(self.focus_out_end_mark) - - # add the new focus out line - end_iter = buffer_.get_end_iter() - buffer_.insert(end_iter, '\n') - buffer_.insert_pixbuf(end_iter, - ConversationTextview.FOCUS_OUT_LINE_PIXBUF) - - end_iter = buffer_.get_end_iter() - before_img_iter = end_iter.copy() - # one char back (an image also takes one char) - before_img_iter.backward_char() - buffer_.apply_tag_by_name('focus-out-line', before_img_iter, end_iter) - - self.allow_focus_out_line = False - - # update the iter we hold to make comparison the next time - self.focus_out_end_mark = buffer_.create_mark(None, - buffer_.get_end_iter(), left_gravity=True) - - buffer_.end_user_action() - - # scroll to the end (via idle in case the scrollbar has appeared) - gobject.idle_add(self.scroll_to_end) - - def show_xep0184_warning_tooltip(self): - pointer = self.tv.get_pointer() - x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, - pointer[0], pointer[1]) - tags = self.tv.get_iter_at_location(x, y).get_tags() - tag_table = self.tv.get_buffer().get_tag_table() - xep0184_warning = False - for tag in tags: - if tag == tag_table.lookup('xep0184-warning'): - xep0184_warning = True - break - if xep0184_warning and not self.xep0184_warning_tooltip.win: - # check if the current pointer is still over the line - position = self.tv.window.get_origin() - self.xep0184_warning_tooltip.show_tooltip(_('This icon indicates that ' - 'this message has not yet\nbeen received by the remote end. ' - "If this icon stays\nfor a long time, it's likely the message got " - 'lost.'), 8, position[1] + pointer[1]) - - def show_line_tooltip(self): - pointer = self.tv.get_pointer() - x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, - pointer[0], pointer[1]) - tags = self.tv.get_iter_at_location(x, y).get_tags() - tag_table = self.tv.get_buffer().get_tag_table() - over_line = False - for tag in tags: - if tag == tag_table.lookup('focus-out-line'): - over_line = True - break - if over_line and not self.line_tooltip.win: - # check if the current pointer is still over the line - position = self.tv.window.get_origin() - self.line_tooltip.show_tooltip(_('Text below this line is what has ' - 'been said since the\nlast time you paid attention to this group ' - 'chat'), 8, position[1] + pointer[1]) - - def on_textview_expose_event(self, widget, event): - expalloc = event.area - exp_x0 = expalloc.x - exp_y0 = expalloc.y - exp_x1 = exp_x0 + expalloc.width - exp_y1 = exp_y0 + expalloc.height - - try: - tryfirst = [self.image_cache[(exp_x0, exp_y0)]] - except KeyError: - tryfirst = [] - - for image in tryfirst + self.images: - imgalloc = image.allocation - img_x0 = imgalloc.x - img_y0 = imgalloc.y - img_x1 = img_x0 + imgalloc.width - img_y1 = img_y0 + imgalloc.height - - if img_x0 <= exp_x0 and img_y0 <= exp_y0 and \ - exp_x1 <= img_x1 and exp_y1 <= img_y1: - self.image_cache[(img_x0, img_y0)] = image - widget.propagate_expose(image, event) - return True - return False - - def on_textview_motion_notify_event(self, widget, event): - """ - Change the cursor to a hand when we are over a mail or an url - """ - pointer_x, pointer_y = self.tv.window.get_pointer()[0:2] - x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, - pointer_x, pointer_y) - tags = self.tv.get_iter_at_location(x, y).get_tags() - if self.change_cursor: - self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( - gtk.gdk.Cursor(gtk.gdk.XTERM)) - self.change_cursor = False - tag_table = self.tv.get_buffer().get_tag_table() - over_line = False - xep0184_warning = False - for tag in tags: - if tag in (tag_table.lookup('url'), tag_table.lookup('mail'), \ - tag_table.lookup('xmpp'), tag_table.lookup('sth_at_sth')): - self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( - gtk.gdk.Cursor(gtk.gdk.HAND2)) - self.change_cursor = True - elif tag == tag_table.lookup('focus-out-line'): - over_line = True - elif tag == tag_table.lookup('xep0184-warning'): - xep0184_warning = True - - if self.line_tooltip.timeout != 0: - # Check if we should hide the line tooltip - if not over_line: - self.line_tooltip.hide_tooltip() - if self.xep0184_warning_tooltip.timeout != 0: - # Check if we should hide the XEP-184 warning tooltip - if not xep0184_warning: - self.xep0184_warning_tooltip.hide_tooltip() - if over_line and not self.line_tooltip.win: - self.line_tooltip.timeout = gobject.timeout_add(500, - self.show_line_tooltip) - self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( - gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)) - self.change_cursor = True - if xep0184_warning and not self.xep0184_warning_tooltip.win: - self.xep0184_warning_tooltip.timeout = gobject.timeout_add(500, - self.show_xep0184_warning_tooltip) - self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( - gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)) - self.change_cursor = True - - def clear(self, tv = None): - """ - Clear text in the textview - """ - buffer_ = self.tv.get_buffer() - start, end = buffer_.get_bounds() - buffer_.delete(start, end) - size = gajim.config.get('max_conversation_lines') - size = 2 * size - 1 - self.marks_queue = Queue.Queue(size) - self.focus_out_end_mark = None - - def visit_url_from_menuitem(self, widget, link): - """ - Basically it filters out the widget instance - """ - helpers.launch_browser_mailer('url', link) - - def on_textview_populate_popup(self, textview, menu): - """ - Override the default context menu and we prepend Clear (only if - used_in_history_window is False) and if we have sth selected we show a - submenu with actions on the phrase (see - on_conversation_textview_button_press_event) - """ - separator_menuitem_was_added = False - if not self.used_in_history_window: - item = gtk.SeparatorMenuItem() - menu.prepend(item) - separator_menuitem_was_added = True - - item = gtk.ImageMenuItem(gtk.STOCK_CLEAR) - menu.prepend(item) - id_ = item.connect('activate', self.clear) - self.handlers[id_] = item - - if self.selected_phrase: - if not separator_menuitem_was_added: - item = gtk.SeparatorMenuItem() - menu.prepend(item) - - if not self.used_in_history_window: - item = gtk.MenuItem(_('_Quote')) - id_ = item.connect('activate', self.on_quote) - self.handlers[id_] = item - menu.prepend(item) - - _selected_phrase = helpers.reduce_chars_newlines( - self.selected_phrase, 25, 2) - item = gtk.MenuItem(_('_Actions for "%s"') % _selected_phrase) - menu.prepend(item) - submenu = gtk.Menu() - item.set_submenu(submenu) - - always_use_en = gajim.config.get('always_english_wikipedia') - if always_use_en: - link = 'http://en.wikipedia.org/wiki/Special:Search?search=%s'\ - % self.selected_phrase - else: - link = 'http://%s.wikipedia.org/wiki/Special:Search?search=%s'\ - % (gajim.LANG, self.selected_phrase) - item = gtk.MenuItem(_('Read _Wikipedia Article')) - id_ = item.connect('activate', self.visit_url_from_menuitem, link) - self.handlers[id_] = item - submenu.append(item) - - item = gtk.MenuItem(_('Look it up in _Dictionary')) - dict_link = gajim.config.get('dictionary_url') - if dict_link == 'WIKTIONARY': - # special link (yeah undocumented but default) - always_use_en = gajim.config.get('always_english_wiktionary') - if always_use_en: - link = 'http://en.wiktionary.org/wiki/Special:Search?search=%s'\ - % self.selected_phrase - else: - link = 'http://%s.wiktionary.org/wiki/Special:Search?search=%s'\ - % (gajim.LANG, self.selected_phrase) - id_ = item.connect('activate', self.visit_url_from_menuitem, link) - self.handlers[id_] = item - else: - if dict_link.find('%s') == -1: - # we must have %s in the url if not WIKTIONARY - item = gtk.MenuItem(_( - 'Dictionary URL is missing an "%s" and it is not WIKTIONARY')) - item.set_property('sensitive', False) - else: - link = dict_link % self.selected_phrase - id_ = item.connect('activate', self.visit_url_from_menuitem, - link) - self.handlers[id_] = item - submenu.append(item) - - - search_link = gajim.config.get('search_engine') - if search_link.find('%s') == -1: - # we must have %s in the url - item = gtk.MenuItem(_('Web Search URL is missing an "%s"')) - item.set_property('sensitive', False) - else: - item = gtk.MenuItem(_('Web _Search for it')) - link = search_link % self.selected_phrase - id_ = item.connect('activate', self.visit_url_from_menuitem, link) - self.handlers[id_] = item - submenu.append(item) - - item = gtk.MenuItem(_('Open as _Link')) - id_ = item.connect('activate', self.visit_url_from_menuitem, link) - self.handlers[id_] = item - submenu.append(item) - - menu.show_all() - - def on_quote(self, widget): - self.emit('quote', self.selected_phrase) - - def on_textview_button_press_event(self, widget, event): - # If we clicked on a taged text do NOT open the standard popup menu - # if normal text check if we have sth selected - self.selected_phrase = '' # do not move belove event button check! - - if event.button != 3: # if not right click - return False - - x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, - int(event.x), int(event.y)) - iter_ = self.tv.get_iter_at_location(x, y) - tags = iter_.get_tags() - - - if tags: # we clicked on sth special (it can be status message too) - for tag in tags: - tag_name = tag.get_property('name') - if tag_name in ('url', 'mail', 'xmpp', 'sth_at_sth'): - return True # we block normal context menu - - # we check if sth was selected and if it was we assign - # selected_phrase variable - # so on_conversation_textview_populate_popup can use it - buffer_ = self.tv.get_buffer() - return_val = buffer_.get_selection_bounds() - if return_val: # if sth was selected when we right-clicked - # get the selected text - start_sel, finish_sel = return_val[0], return_val[1] - self.selected_phrase = buffer_.get_text(start_sel, finish_sel).decode( - 'utf-8') - elif ord(iter_.get_char()) > 31: - # we clicked on a word, do as if it's selected for context menu - start_sel = iter_.copy() - if not start_sel.starts_word(): - start_sel.backward_word_start() - finish_sel = iter_.copy() - if not finish_sel.ends_word(): - finish_sel.forward_word_end() - self.selected_phrase = buffer_.get_text(start_sel, finish_sel).decode( - 'utf-8') - - def on_open_link_activate(self, widget, kind, text): - helpers.launch_browser_mailer(kind, text) - - def on_copy_link_activate(self, widget, text): - clip = gtk.clipboard_get() - clip.set_text(text) - - def on_start_chat_activate(self, widget, jid): - gajim.interface.new_chat_from_jid(self.account, jid) - - def on_join_group_chat_menuitem_activate(self, widget, room_jid): - if 'join_gc' in gajim.interface.instances[self.account]: - instance = gajim.interface.instances[self.account]['join_gc'] - instance.xml.get_object('room_jid_entry').set_text(room_jid) - gajim.interface.instances[self.account]['join_gc'].window.present() - else: - try: - dialogs.JoinGroupchatWindow(account=self.account, room_jid=room_jid) - except GajimGeneralException: - pass - - def on_add_to_roster_activate(self, widget, jid): - dialogs.AddNewContactWindow(self.account, jid) - - def make_link_menu(self, event, kind, text): - xml = gtkgui_helpers.get_gtk_builder('chat_context_menu.ui') - menu = xml.get_object('chat_context_menu') - childs = menu.get_children() - if kind == 'url': - id_ = childs[0].connect('activate', self.on_copy_link_activate, text) - self.handlers[id_] = childs[0] - id_ = childs[1].connect('activate', self.on_open_link_activate, kind, - text) - self.handlers[id_] = childs[1] - childs[2].hide() # copy mail address - childs[3].hide() # open mail composer - childs[4].hide() # jid section separator - childs[5].hide() # start chat - childs[6].hide() # join group chat - childs[7].hide() # add to roster - else: # It's a mail or a JID - # load muc icon - join_group_chat_menuitem = xml.get_object('join_group_chat_menuitem') - muc_icon = gtkgui_helpers.load_icon('muc_active') - if muc_icon: - join_group_chat_menuitem.set_image(muc_icon) - - text = text.lower() - if text.startswith('xmpp:'): - text = text[5:] - id_ = childs[2].connect('activate', self.on_copy_link_activate, text) - self.handlers[id_] = childs[2] - id_ = childs[3].connect('activate', self.on_open_link_activate, kind, - text) - self.handlers[id_] = childs[3] - id_ = childs[5].connect('activate', self.on_start_chat_activate, text) - self.handlers[id_] = childs[5] - id_ = childs[6].connect('activate', - self.on_join_group_chat_menuitem_activate, text) - self.handlers[id_] = childs[6] - - if self.account: - id_ = childs[7].connect('activate', self.on_add_to_roster_activate, - text) - self.handlers[id_] = childs[7] - childs[7].show() # show add to roster menuitem - else: - childs[7].hide() # hide add to roster menuitem - - if kind == 'xmpp': - childs[2].hide() # copy mail address - childs[3].hide() # open mail composer - childs[4].hide() # jid section separator - elif kind == 'mail': - childs[4].hide() # jid section separator - childs[5].hide() # start chat - childs[6].hide() # join group chat - childs[7].hide() # add to roster - - childs[0].hide() # copy link location - childs[1].hide() # open link in browser - - menu.popup(None, None, None, event.button, event.time) - - def hyperlink_handler(self, texttag, widget, event, iter_, kind): - if event.type == gtk.gdk.BUTTON_PRESS: - begin_iter = iter_.copy() - # we get the begining of the tag - while not begin_iter.begins_tag(texttag): - begin_iter.backward_char() - end_iter = iter_.copy() - # we get the end of the tag - while not end_iter.ends_tag(texttag): - end_iter.forward_char() - word = self.tv.get_buffer().get_text(begin_iter, end_iter).decode( - 'utf-8') - if event.button == 3: # right click - self.make_link_menu(event, kind, word) - else: - # we launch the correct application - if kind == 'xmpp': - word = word[5:] - if '?' in word: - (jid, action) = word.split('?') - if action == 'join': - self.on_join_group_chat_menuitem_activate(None, jid) - else: - self.on_start_chat_activate(None, jid) - else: - self.on_start_chat_activate(None, word) - else: - helpers.launch_browser_mailer(kind, word) - - def html_hyperlink_handler(self, texttag, widget, event, iter_, kind, href): - if event.type == gtk.gdk.BUTTON_PRESS: - if event.button == 3: # right click - self.make_link_menu(event, kind, href) - return True - else: - # we launch the correct application - helpers.launch_browser_mailer(kind, href) - - - def detect_and_print_special_text(self, otext, other_tags, graphics=True): - """ - Detect special text (emots & links & formatting), print normal text - before any special text it founds, then print special text (that happens - many times until last special text is printed) and then return the index - after *last* special text, so we can print it in - print_conversation_line() - """ - buffer_ = self.tv.get_buffer() - - insert_tags_func = buffer_.insert_with_tags_by_name - # detect_and_print_special_text() is also used by - # HtmlHandler.handle_specials() and there tags is gtk.TextTag objects, - # not strings - if other_tags and isinstance(other_tags[0], gtk.TextTag): - insert_tags_func = buffer_.insert_with_tags - - index = 0 - - # Too many special elements (emoticons, LaTeX formulas, etc) - # may cause Gajim to freeze (see #5129). - # We impose an arbitrary limit of 100 specials per message. - specials_limit = 100 - - # basic: links + mail + formatting is always checked (we like that) - if gajim.config.get('emoticons_theme') and graphics: - # search for emoticons & urls - iterator = gajim.interface.emot_and_basic_re.finditer(otext) - else: # search for just urls + mail + formatting - iterator = gajim.interface.basic_pattern_re.finditer(otext) - for match in iterator: - start, end = match.span() - special_text = otext[start:end] - if start > index: - text_before_special_text = otext[index:start] - end_iter = buffer_.get_end_iter() - # we insert normal text - insert_tags_func(end_iter, text_before_special_text, *other_tags) - index = end # update index - - # now print it - self.print_special_text(special_text, other_tags, graphics=graphics) - specials_limit -= 1 - if specials_limit <= 0: - break - - # add the rest of text located in the index and after - end_iter = buffer_.get_end_iter() - insert_tags_func(end_iter, otext[index:], *other_tags) - - return buffer_.get_end_iter() - - def print_special_text(self, special_text, other_tags, graphics=True): - """ - Is called by detect_and_print_special_text and prints special text - (emots, links, formatting) - """ - tags = [] - use_other_tags = True - text_is_valid_uri = False - show_ascii_formatting_chars = \ - gajim.config.get('show_ascii_formatting_chars') - buffer_ = self.tv.get_buffer() - - # Check if we accept this as an uri - schemes = gajim.config.get('uri_schemes').split() - for scheme in schemes: - if special_text.startswith(scheme + ':'): - text_is_valid_uri = True - - possible_emot_ascii_caps = special_text.upper() # emoticons keys are CAPS - if gajim.config.get('emoticons_theme') and \ - possible_emot_ascii_caps in gajim.interface.emoticons.keys() and graphics: - # it's an emoticon - emot_ascii = possible_emot_ascii_caps - end_iter = buffer_.get_end_iter() - anchor = buffer_.create_child_anchor(end_iter) - img = TextViewImage(anchor, special_text) - animations = gajim.interface.emoticons_animations - if not emot_ascii in animations: - animations[emot_ascii] = gtk.gdk.PixbufAnimation( - gajim.interface.emoticons[emot_ascii]) - img.set_from_animation(animations[emot_ascii]) - img.show() - self.images.append(img) - # add with possible animation - self.tv.add_child_at_anchor(img, anchor) - elif special_text.startswith('www.') or \ - special_text.startswith('ftp.') or \ - text_is_valid_uri: - tags.append('url') - use_other_tags = False - elif special_text.startswith('mailto:'): - tags.append('mail') - use_other_tags = False - elif special_text.startswith('xmpp:'): - tags.append('xmpp') - use_other_tags = False - elif gajim.interface.sth_at_sth_dot_sth_re.match(special_text): - # it's a JID or mail - tags.append('sth_at_sth') - use_other_tags = False - elif special_text.startswith('*'): # it's a bold text - tags.append('bold') - if special_text[1] == '/' and special_text[-2] == '/' and\ - len(special_text) > 4: # it's also italic - tags.append('italic') - if not show_ascii_formatting_chars: - special_text = special_text[2:-2] # remove */ /* - elif special_text[1] == '_' and special_text[-2] == '_' and \ - len(special_text) > 4: # it's also underlined - tags.append('underline') - if not show_ascii_formatting_chars: - special_text = special_text[2:-2] # remove *_ _* - else: - if not show_ascii_formatting_chars: - special_text = special_text[1:-1] # remove * * - elif special_text.startswith('/'): # it's an italic text - tags.append('italic') - if special_text[1] == '*' and special_text[-2] == '*' and \ - len(special_text) > 4: # it's also bold - tags.append('bold') - if not show_ascii_formatting_chars: - special_text = special_text[2:-2] # remove /* */ - elif special_text[1] == '_' and special_text[-2] == '_' and \ - len(special_text) > 4: # it's also underlined - tags.append('underline') - if not show_ascii_formatting_chars: - special_text = special_text[2:-2] # remove /_ _/ - else: - if not show_ascii_formatting_chars: - special_text = special_text[1:-1] # remove / / - elif special_text.startswith('_'): # it's an underlined text - tags.append('underline') - if special_text[1] == '*' and special_text[-2] == '*' and \ - len(special_text) > 4: # it's also bold - tags.append('bold') - if not show_ascii_formatting_chars: - special_text = special_text[2:-2] # remove _* *_ - elif special_text[1] == '/' and special_text[-2] == '/' and \ - len(special_text) > 4: # it's also italic - tags.append('italic') - if not show_ascii_formatting_chars: - special_text = special_text[2:-2] # remove _/ /_ - else: - if not show_ascii_formatting_chars: - special_text = special_text[1:-1] # remove _ _ - elif gajim.HAVE_LATEX and special_text.startswith('$$') and \ - special_text.endswith('$$') and graphics: - try: - imagepath = latex.latex_to_image(special_text[2:-2]) - except LatexError, e: - # print the error after the line has been written - gobject.idle_add(self.print_conversation_line, str(e), '', 'info', - '', None) - imagepath = None - end_iter = buffer_.get_end_iter() - if imagepath is not None: - anchor = buffer_.create_child_anchor(end_iter) - img = TextViewImage(anchor, special_text) - img.set_from_file(imagepath) - img.show() - # add - self.tv.add_child_at_anchor(img, anchor) - # delete old file - try: - os.remove(imagepath) - except Exception: - pass - else: - buffer_.insert(end_iter, special_text) - use_other_tags = False - else: - # It's nothing special - if use_other_tags: - end_iter = buffer_.get_end_iter() - insert_tags_func = buffer_.insert_with_tags_by_name - if other_tags and isinstance(other_tags[0], gtk.TextTag): - insert_tags_func = buffer_.insert_with_tags - - insert_tags_func(end_iter, special_text, *other_tags) - - if tags: - end_iter = buffer_.get_end_iter() - all_tags = tags[:] - if use_other_tags: - all_tags += other_tags - # convert all names to TextTag - ttt = buffer_.get_tag_table() - all_tags = [(ttt.lookup(t) if isinstance(t, str) else t) for t in all_tags] - buffer_.insert_with_tags(end_iter, special_text, *all_tags) - - def print_empty_line(self): - buffer_ = self.tv.get_buffer() - end_iter = buffer_.get_end_iter() - buffer_.insert_with_tags_by_name(end_iter, '\n', 'eol') - - def print_conversation_line(self, text, jid, kind, name, tim, - other_tags_for_name=[], other_tags_for_time=[], - other_tags_for_text=[], subject=None, old_kind=None, xhtml=None, - simple=False, graphics=True): - """ - Print 'chat' type messages - """ - buffer_ = self.tv.get_buffer() - buffer_.begin_user_action() - if self.marks_queue.full(): - # remove oldest line - m1 = self.marks_queue.get() - m2 = self.marks_queue.get() - i1 = buffer_.get_iter_at_mark(m1) - i2 = buffer_.get_iter_at_mark(m2) - buffer_.delete(i1, i2) - buffer_.delete_mark(m1) - end_iter = buffer_.get_end_iter() - end_offset = end_iter.get_offset() - at_the_end = self.at_the_end() - move_selection = False - if buffer_.get_has_selection() and buffer_.get_selection_bounds()[1].\ - get_offset() == end_offset: - move_selection = True - - # Create one mark and add it to queue once if it's the first line - # else twice (one for end bound, one for start bound) - mark = None - if buffer_.get_char_count() > 0: - if not simple: - buffer_.insert_with_tags_by_name(end_iter, '\n', 'eol') - if move_selection: - sel_start, sel_end = buffer_.get_selection_bounds() - sel_end.backward_char() - buffer_.select_range(sel_start, sel_end) - mark = buffer_.create_mark(None, end_iter, left_gravity=True) - self.marks_queue.put(mark) - if not mark: - mark = buffer_.create_mark(None, end_iter, left_gravity=True) - self.marks_queue.put(mark) - if kind == 'incoming_queue': - kind = 'incoming' - if old_kind == 'incoming_queue': - old_kind = 'incoming' - # print the time stamp - if not tim: - # We don't have tim for outgoing messages... - tim = time.localtime() - current_print_time = gajim.config.get('print_time') - if current_print_time == 'always' and kind != 'info' and not simple: - timestamp_str = self.get_time_to_show(tim) - timestamp = time.strftime(timestamp_str, tim) - buffer_.insert_with_tags_by_name(end_iter, timestamp, - *other_tags_for_time) - elif current_print_time == 'sometimes' and kind != 'info' and not simple: - every_foo_seconds = 60 * gajim.config.get( - 'print_ichat_every_foo_minutes') - seconds_passed = time.mktime(tim) - self.last_time_printout - if seconds_passed > every_foo_seconds: - self.last_time_printout = time.mktime(tim) - end_iter = buffer_.get_end_iter() - if gajim.config.get('print_time_fuzzy') > 0: - ft = self.fc.fuzzy_time(gajim.config.get('print_time_fuzzy'), tim) - tim_format = ft.decode(locale.getpreferredencoding()) - else: - tim_format = self.get_time_to_show(tim) - buffer_.insert_with_tags_by_name(end_iter, tim_format + '\n', - 'time_sometimes') - # kind = info, we print things as if it was a status: same color, ... - if kind in ('error', 'info'): - kind = 'status' - other_text_tag = self.detect_other_text_tag(text, kind) - text_tags = other_tags_for_text[:] # create a new list - if other_text_tag: - # note that color of /me may be overwritten in gc_control - text_tags.append(other_text_tag) - else: # not status nor /me - if gajim.config.get('chat_merge_consecutive_nickname'): - if kind != old_kind: - self.print_name(name, kind, other_tags_for_name) - else: - self.print_real_text(gajim.config.get( - 'chat_merge_consecutive_nickname_indent')) - else: - self.print_name(name, kind, other_tags_for_name) - if kind == 'incoming': - text_tags.append('incomingtxt') - elif kind == 'outgoing': - text_tags.append('outgoingtxt') - self.print_subject(subject) - self.print_real_text(text, text_tags, name, xhtml, graphics=graphics) - - # scroll to the end of the textview - if at_the_end or kind == 'outgoing': - # we are at the end or we are sending something - # scroll to the end (via idle in case the scrollbar has appeared) - if gajim.config.get('use_smooth_scrolling'): - gobject.idle_add(self.smooth_scroll_to_end) - else: - gobject.idle_add(self.scroll_to_end) - - buffer_.end_user_action() - - def get_time_to_show(self, tim): - """ - Get the time, with the day before if needed and return it. It DOESN'T - format a fuzzy time - """ - format = '' - # get difference in days since epoch (86400 = 24*3600) - # number of days since epoch for current time (in GMT) - - # number of days since epoch for message (in GMT) - diff_day = int(timegm(time.localtime())) / 86400 -\ - int(timegm(tim)) / 86400 - if diff_day == 0: - day_str = '' - else: - #%i is day in year (1-365) - day_str = i18n.ngettext('Yesterday', '%i days ago', diff_day, - replace_plural=diff_day) - if day_str: - format += day_str + ' ' - timestamp_str = gajim.config.get('time_stamp') - timestamp_str = helpers.from_one_line(timestamp_str) - format += timestamp_str - tim_format = time.strftime(format, tim) - if locale.getpreferredencoding() != 'KOI8-R': - # if tim_format comes as unicode because of day_str. - # we convert it to the encoding that we want (and that is utf-8) - tim_format = helpers.ensure_utf8_string(tim_format) - return tim_format - - def detect_other_text_tag(self, text, kind): - if kind == 'status': - return kind - elif text.startswith('/me ') or text.startswith('/me\n'): - return kind - - def print_name(self, name, kind, other_tags_for_name): - if name: - buffer_ = self.tv.get_buffer() - end_iter = buffer_.get_end_iter() - name_tags = other_tags_for_name[:] # create a new list - name_tags.append(kind) - before_str = gajim.config.get('before_nickname') - before_str = helpers.from_one_line(before_str) - after_str = gajim.config.get('after_nickname') - after_str = helpers.from_one_line(after_str) - format = before_str + name + after_str + ' ' - buffer_.insert_with_tags_by_name(end_iter, format, *name_tags) - - def print_subject(self, subject): - if subject: # if we have subject, show it too! - subject = _('Subject: %s\n') % subject - buffer_ = self.tv.get_buffer() - end_iter = buffer_.get_end_iter() - buffer_.insert(end_iter, subject) - self.print_empty_line() - - def print_real_text(self, text, text_tags=[], name=None, xhtml=None, - graphics=True): - """ - Add normal and special text. call this to add text - """ - if xhtml: - try: - if name and (text.startswith('/me ') or text.startswith('/me\n')): - xhtml = xhtml.replace('/me', '* %s' % (name,), 1) - self.tv.display_html(xhtml.encode('utf-8'), self) - return - except Exception, e: - gajim.log.debug('Error processing xhtml' + str(e)) - gajim.log.debug('with |' + xhtml + '|') - - # /me is replaced by name if name is given - if name and (text.startswith('/me ') or text.startswith('/me\n')): - text = '* ' + name + text[3:] - text_tags.append('italic') - # detect urls formatting and if the user has it on emoticons - self.detect_and_print_special_text(text, text_tags, graphics=graphics) - -# vim: se ts=3: + """ + Class for the conversation textview (where user reads already said messages) + for chat/groupchat windows + """ + __gsignals__ = dict( + quote = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION, + None, # return value + (str, ) # arguments + ) + ) + + FOCUS_OUT_LINE_PIXBUF = gtkgui_helpers.get_icon_pixmap('gajim-muc_separator') + XEP0184_WARNING_PIXBUF = gtkgui_helpers.get_icon_pixmap( + 'gajim-receipt_missing') + + # smooth scroll constants + MAX_SCROLL_TIME = 0.4 # seconds + SCROLL_DELAY = 33 # milliseconds + + def __init__(self, account, used_in_history_window = False): + """ + If used_in_history_window is True, then we do not show Clear menuitem in + context menu + """ + gobject.GObject.__init__(self) + self.used_in_history_window = used_in_history_window + + self.fc = FuzzyClock() + + + # no need to inherit TextView, use it as atrribute is safer + self.tv = HtmlTextView() + self.tv.html_hyperlink_handler = self.html_hyperlink_handler + + # set properties + self.tv.set_border_width(1) + self.tv.set_accepts_tab(True) + self.tv.set_editable(False) + self.tv.set_cursor_visible(False) + self.tv.set_wrap_mode(gtk.WRAP_WORD_CHAR) + self.tv.set_left_margin(2) + self.tv.set_right_margin(2) + self.handlers = {} + self.images = [] + self.image_cache = {} + self.xep0184_marks = {} + self.xep0184_shown = {} + + # It's True when we scroll in the code, so we can detect scroll from user + self.auto_scrolling = False + + # connect signals + id_ = self.tv.connect('motion_notify_event', + self.on_textview_motion_notify_event) + self.handlers[id_] = self.tv + id_ = self.tv.connect('populate_popup', self.on_textview_populate_popup) + self.handlers[id_] = self.tv + id_ = self.tv.connect('button_press_event', + self.on_textview_button_press_event) + self.handlers[id_] = self.tv + + id_ = self.tv.connect('expose-event', + self.on_textview_expose_event) + self.handlers[id_] = self.tv + + + self.account = account + self.change_cursor = False + self.last_time_printout = 0 + + font = pango.FontDescription(gajim.config.get('conversation_font')) + self.tv.modify_font(font) + buffer_ = self.tv.get_buffer() + end_iter = buffer_.get_end_iter() + buffer_.create_mark('end', end_iter, False) + + self.tagIn = buffer_.create_tag('incoming') + color = gajim.config.get('inmsgcolor') + font = pango.FontDescription(gajim.config.get('inmsgfont')) + self.tagIn.set_property('foreground', color) + self.tagIn.set_property('font-desc', font) + + self.tagOut = buffer_.create_tag('outgoing') + color = gajim.config.get('outmsgcolor') + font = pango.FontDescription(gajim.config.get('outmsgfont')) + self.tagOut.set_property('foreground', color) + self.tagOut.set_property('font-desc', font) + + self.tagStatus = buffer_.create_tag('status') + color = gajim.config.get('statusmsgcolor') + font = pango.FontDescription(gajim.config.get('satusmsgfont')) + self.tagStatus.set_property('foreground', color) + self.tagStatus.set_property('font-desc', font) + + self.tagInText = buffer_.create_tag('incomingtxt') + color = gajim.config.get('inmsgtxtcolor') + font = pango.FontDescription(gajim.config.get('inmsgtxtfont')) + if color: + self.tagInText.set_property('foreground', color) + self.tagInText.set_property('font-desc', font) + + self.tagOutText = buffer_.create_tag('outgoingtxt') + color = gajim.config.get('outmsgtxtcolor') + if color: + font = pango.FontDescription(gajim.config.get('outmsgtxtfont')) + self.tagOutText.set_property('foreground', color) + self.tagOutText.set_property('font-desc', font) + + colors = gajim.config.get('gc_nicknames_colors') + colors = colors.split(':') + for i, color in enumerate(colors): + tagname = 'gc_nickname_color_' + str(i) + tag = buffer_.create_tag(tagname) + tag.set_property('foreground', color) + + tag = buffer_.create_tag('marked') + color = gajim.config.get('markedmsgcolor') + tag.set_property('foreground', color) + tag.set_property('weight', pango.WEIGHT_BOLD) + + tag = buffer_.create_tag('time_sometimes') + tag.set_property('foreground', 'darkgrey') + tag.set_property('scale', pango.SCALE_SMALL) + tag.set_property('justification', gtk.JUSTIFY_CENTER) + + tag = buffer_.create_tag('small') + tag.set_property('scale', pango.SCALE_SMALL) + + tag = buffer_.create_tag('restored_message') + color = gajim.config.get('restored_messages_color') + tag.set_property('foreground', color) + + self.tagURL = buffer_.create_tag('url') + color = gajim.config.get('urlmsgcolor') + self.tagURL.set_property('foreground', color) + self.tagURL.set_property('underline', pango.UNDERLINE_SINGLE) + id_ = self.tagURL.connect('event', self.hyperlink_handler, 'url') + self.handlers[id_] = self.tagURL + + self.tagMail = buffer_.create_tag('mail') + self.tagMail.set_property('foreground', color) + self.tagMail.set_property('underline', pango.UNDERLINE_SINGLE) + id_ = self.tagMail.connect('event', self.hyperlink_handler, 'mail') + self.handlers[id_] = self.tagMail + + self.tagXMPP = buffer_.create_tag('xmpp') + self.tagXMPP.set_property('foreground', color) + self.tagXMPP.set_property('underline', pango.UNDERLINE_SINGLE) + id_ = self.tagXMPP.connect('event', self.hyperlink_handler, 'xmpp') + self.handlers[id_] = self.tagXMPP + + self.tagSthAtSth = buffer_.create_tag('sth_at_sth') + self.tagSthAtSth.set_property('foreground', color) + self.tagSthAtSth.set_property('underline', pango.UNDERLINE_SINGLE) + id_ = self.tagSthAtSth.connect('event', self.hyperlink_handler, + 'sth_at_sth') + self.handlers[id_] = self.tagSthAtSth + + tag = buffer_.create_tag('bold') + tag.set_property('weight', pango.WEIGHT_BOLD) + + tag = buffer_.create_tag('italic') + tag.set_property('style', pango.STYLE_ITALIC) + + tag = buffer_.create_tag('underline') + tag.set_property('underline', pango.UNDERLINE_SINGLE) + + buffer_.create_tag('focus-out-line', justification = gtk.JUSTIFY_CENTER) + + tag = buffer_.create_tag('xep0184-warning') + + # One mark at the begining then 2 marks between each lines + size = gajim.config.get('max_conversation_lines') + size = 2 * size - 1 + self.marks_queue = Queue.Queue(size) + + self.allow_focus_out_line = True + # holds a mark at the end of --- line + self.focus_out_end_mark = None + + self.xep0184_warning_tooltip = tooltips.BaseTooltip() + + self.line_tooltip = tooltips.BaseTooltip() + # use it for hr too + self.tv.focus_out_line_pixbuf = ConversationTextview.FOCUS_OUT_LINE_PIXBUF + self.smooth_id = None + + def del_handlers(self): + for i in self.handlers.keys(): + if self.handlers[i].handler_is_connected(i): + self.handlers[i].disconnect(i) + del self.handlers + self.tv.destroy() + #FIXME: + # self.line_tooltip.destroy() + + def update_tags(self): + self.tagIn.set_property('foreground', gajim.config.get('inmsgcolor')) + self.tagOut.set_property('foreground', gajim.config.get('outmsgcolor')) + self.tagStatus.set_property('foreground', + gajim.config.get('statusmsgcolor')) + self.tagURL.set_property('foreground', gajim.config.get('urlmsgcolor')) + self.tagMail.set_property('foreground', gajim.config.get('urlmsgcolor')) + + def at_the_end(self): + buffer_ = self.tv.get_buffer() + end_iter = buffer_.get_end_iter() + end_rect = self.tv.get_iter_location(end_iter) + visible_rect = self.tv.get_visible_rect() + if end_rect.y <= (visible_rect.y + visible_rect.height): + return True + return False + + # Smooth scrolling inspired by Pidgin code + def smooth_scroll(self): + parent = self.tv.get_parent() + if not parent: + return False + vadj = parent.get_vadjustment() + max_val = vadj.upper - vadj.page_size + 1 + cur_val = vadj.get_value() + # scroll by 1/3rd of remaining distance + onethird = cur_val + ((max_val - cur_val) / 3.0) + self.auto_scrolling = True + vadj.set_value(onethird) + self.auto_scrolling = False + if max_val - onethird < 0.01: + self.smooth_id = None + self.smooth_scroll_timer.cancel() + return False + return True + + def smooth_scroll_timeout(self): + gobject.idle_add(self.do_smooth_scroll_timeout) + return + + def do_smooth_scroll_timeout(self): + if not self.smooth_id: + # we finished scrolling + return + gobject.source_remove(self.smooth_id) + self.smooth_id = None + parent = self.tv.get_parent() + if parent: + vadj = parent.get_vadjustment() + self.auto_scrolling = True + vadj.set_value(vadj.upper - vadj.page_size + 1) + self.auto_scrolling = False + + def smooth_scroll_to_end(self): + if None != self.smooth_id: # already scrolling + return False + self.smooth_id = gobject.timeout_add(self.SCROLL_DELAY, + self.smooth_scroll) + self.smooth_scroll_timer = Timer(self.MAX_SCROLL_TIME, + self.smooth_scroll_timeout) + self.smooth_scroll_timer.start() + return False + + def scroll_to_end(self): + parent = self.tv.get_parent() + buffer_ = self.tv.get_buffer() + end_mark = buffer_.get_mark('end') + if not end_mark: + return False + self.auto_scrolling = True + self.tv.scroll_to_mark(end_mark, 0, True, 0, 1) + adjustment = parent.get_hadjustment() + adjustment.set_value(0) + self.auto_scrolling = False + return False # when called in an idle_add, just do it once + + def bring_scroll_to_end(self, diff_y = 0, + use_smooth=gajim.config.get('use_smooth_scrolling')): + ''' scrolls to the end of textview if end is not visible ''' + buffer_ = self.tv.get_buffer() + end_iter = buffer_.get_end_iter() + end_rect = self.tv.get_iter_location(end_iter) + visible_rect = self.tv.get_visible_rect() + # scroll only if expected end is not visible + if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y): + if use_smooth: + gobject.idle_add(self.smooth_scroll_to_end) + else: + gobject.idle_add(self.scroll_to_end_iter) + + def scroll_to_end_iter(self): + buffer_ = self.tv.get_buffer() + end_iter = buffer_.get_end_iter() + if not end_iter: + return False + self.tv.scroll_to_iter(end_iter, 0, False, 1, 1) + return False # when called in an idle_add, just do it once + + def stop_scrolling(self): + if self.smooth_id: + gobject.source_remove(self.smooth_id) + self.smooth_id = None + self.smooth_scroll_timer.cancel() + + def show_xep0184_warning(self, id_): + if id_ in self.xep0184_marks: + return + + buffer_ = self.tv.get_buffer() + buffer_.begin_user_action() + + self.xep0184_marks[id_] = buffer_.create_mark(None, + buffer_.get_end_iter(), left_gravity=True) + self.xep0184_shown[id_] = NOT_SHOWN + + def show_it(): + if (not id_ in self.xep0184_shown) or \ + self.xep0184_shown[id_] == ALREADY_RECEIVED: + return False + + end_iter = buffer_.get_iter_at_mark(self.xep0184_marks[id_]) + buffer_.insert(end_iter, ' ') + anchor = buffer_.create_child_anchor(end_iter) + img = TextViewImage(anchor, '') + img.set_from_pixbuf(ConversationTextview.XEP0184_WARNING_PIXBUF) + img.show() + self.tv.add_child_at_anchor(img, anchor) + before_img_iter = buffer_.get_iter_at_mark(self.xep0184_marks[id_]) + before_img_iter.forward_char() + post_img_iter = before_img_iter.copy() + post_img_iter.forward_char() + buffer_.apply_tag_by_name('xep0184-warning', before_img_iter, + post_img_iter) + + self.xep0184_shown[id_] = SHOWN + return False + gobject.timeout_add_seconds(3, show_it) + + buffer_.end_user_action() + + def hide_xep0184_warning(self, id_): + if id_ not in self.xep0184_marks: + return + + if self.xep0184_shown[id_] == NOT_SHOWN: + self.xep0184_shown[id_] = ALREADY_RECEIVED + return + + buffer_ = self.tv.get_buffer() + buffer_.begin_user_action() + + begin_iter = buffer_.get_iter_at_mark(self.xep0184_marks[id_]) + + end_iter = begin_iter.copy() + # XXX: Is there a nicer way? + end_iter.forward_char() + end_iter.forward_char() + + buffer_.delete(begin_iter, end_iter) + buffer_.delete_mark(self.xep0184_marks[id_]) + + buffer_.end_user_action() + + del self.xep0184_marks[id_] + del self.xep0184_shown[id_] + + def show_focus_out_line(self): + if not self.allow_focus_out_line: + # if room did not receive focus-in from the last time we added + # --- line then do not readd + return + + print_focus_out_line = False + buffer_ = self.tv.get_buffer() + + if self.focus_out_end_mark is None: + # this happens only first time we focus out on this room + print_focus_out_line = True + + else: + focus_out_end_iter = buffer_.get_iter_at_mark(self.focus_out_end_mark) + focus_out_end_iter_offset = focus_out_end_iter.get_offset() + if focus_out_end_iter_offset != buffer_.get_end_iter().get_offset(): + # this means after last-focus something was printed + # (else end_iter's offset is the same as before) + # only then print ---- line (eg. we avoid printing many following + # ---- lines) + print_focus_out_line = True + + if print_focus_out_line and buffer_.get_char_count() > 0: + buffer_.begin_user_action() + + # remove previous focus out line if such focus out line exists + if self.focus_out_end_mark is not None: + end_iter_for_previous_line = buffer_.get_iter_at_mark( + self.focus_out_end_mark) + begin_iter_for_previous_line = end_iter_for_previous_line.copy() + # img_char+1 (the '\n') + begin_iter_for_previous_line.backward_chars(2) + + # remove focus out line + buffer_.delete(begin_iter_for_previous_line, + end_iter_for_previous_line) + buffer_.delete_mark(self.focus_out_end_mark) + + # add the new focus out line + end_iter = buffer_.get_end_iter() + buffer_.insert(end_iter, '\n') + buffer_.insert_pixbuf(end_iter, + ConversationTextview.FOCUS_OUT_LINE_PIXBUF) + + end_iter = buffer_.get_end_iter() + before_img_iter = end_iter.copy() + # one char back (an image also takes one char) + before_img_iter.backward_char() + buffer_.apply_tag_by_name('focus-out-line', before_img_iter, end_iter) + + self.allow_focus_out_line = False + + # update the iter we hold to make comparison the next time + self.focus_out_end_mark = buffer_.create_mark(None, + buffer_.get_end_iter(), left_gravity=True) + + buffer_.end_user_action() + + # scroll to the end (via idle in case the scrollbar has appeared) + gobject.idle_add(self.scroll_to_end) + + def show_xep0184_warning_tooltip(self): + pointer = self.tv.get_pointer() + x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, + pointer[0], pointer[1]) + tags = self.tv.get_iter_at_location(x, y).get_tags() + tag_table = self.tv.get_buffer().get_tag_table() + xep0184_warning = False + for tag in tags: + if tag == tag_table.lookup('xep0184-warning'): + xep0184_warning = True + break + if xep0184_warning and not self.xep0184_warning_tooltip.win: + # check if the current pointer is still over the line + position = self.tv.window.get_origin() + self.xep0184_warning_tooltip.show_tooltip(_('This icon indicates that ' + 'this message has not yet\nbeen received by the remote end. ' + "If this icon stays\nfor a long time, it's likely the message got " + 'lost.'), 8, position[1] + pointer[1]) + + def show_line_tooltip(self): + pointer = self.tv.get_pointer() + x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, + pointer[0], pointer[1]) + tags = self.tv.get_iter_at_location(x, y).get_tags() + tag_table = self.tv.get_buffer().get_tag_table() + over_line = False + for tag in tags: + if tag == tag_table.lookup('focus-out-line'): + over_line = True + break + if over_line and not self.line_tooltip.win: + # check if the current pointer is still over the line + position = self.tv.window.get_origin() + self.line_tooltip.show_tooltip(_('Text below this line is what has ' + 'been said since the\nlast time you paid attention to this group ' + 'chat'), 8, position[1] + pointer[1]) + + def on_textview_expose_event(self, widget, event): + expalloc = event.area + exp_x0 = expalloc.x + exp_y0 = expalloc.y + exp_x1 = exp_x0 + expalloc.width + exp_y1 = exp_y0 + expalloc.height + + try: + tryfirst = [self.image_cache[(exp_x0, exp_y0)]] + except KeyError: + tryfirst = [] + + for image in tryfirst + self.images: + imgalloc = image.allocation + img_x0 = imgalloc.x + img_y0 = imgalloc.y + img_x1 = img_x0 + imgalloc.width + img_y1 = img_y0 + imgalloc.height + + if img_x0 <= exp_x0 and img_y0 <= exp_y0 and \ + exp_x1 <= img_x1 and exp_y1 <= img_y1: + self.image_cache[(img_x0, img_y0)] = image + widget.propagate_expose(image, event) + return True + return False + + def on_textview_motion_notify_event(self, widget, event): + """ + Change the cursor to a hand when we are over a mail or an url + """ + pointer_x, pointer_y = self.tv.window.get_pointer()[0:2] + x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, + pointer_x, pointer_y) + tags = self.tv.get_iter_at_location(x, y).get_tags() + if self.change_cursor: + self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( + gtk.gdk.Cursor(gtk.gdk.XTERM)) + self.change_cursor = False + tag_table = self.tv.get_buffer().get_tag_table() + over_line = False + xep0184_warning = False + for tag in tags: + if tag in (tag_table.lookup('url'), tag_table.lookup('mail'), \ + tag_table.lookup('xmpp'), tag_table.lookup('sth_at_sth')): + self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( + gtk.gdk.Cursor(gtk.gdk.HAND2)) + self.change_cursor = True + elif tag == tag_table.lookup('focus-out-line'): + over_line = True + elif tag == tag_table.lookup('xep0184-warning'): + xep0184_warning = True + + if self.line_tooltip.timeout != 0: + # Check if we should hide the line tooltip + if not over_line: + self.line_tooltip.hide_tooltip() + if self.xep0184_warning_tooltip.timeout != 0: + # Check if we should hide the XEP-184 warning tooltip + if not xep0184_warning: + self.xep0184_warning_tooltip.hide_tooltip() + if over_line and not self.line_tooltip.win: + self.line_tooltip.timeout = gobject.timeout_add(500, + self.show_line_tooltip) + self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( + gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)) + self.change_cursor = True + if xep0184_warning and not self.xep0184_warning_tooltip.win: + self.xep0184_warning_tooltip.timeout = gobject.timeout_add(500, + self.show_xep0184_warning_tooltip) + self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( + gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)) + self.change_cursor = True + + def clear(self, tv = None): + """ + Clear text in the textview + """ + buffer_ = self.tv.get_buffer() + start, end = buffer_.get_bounds() + buffer_.delete(start, end) + size = gajim.config.get('max_conversation_lines') + size = 2 * size - 1 + self.marks_queue = Queue.Queue(size) + self.focus_out_end_mark = None + + def visit_url_from_menuitem(self, widget, link): + """ + Basically it filters out the widget instance + """ + helpers.launch_browser_mailer('url', link) + + def on_textview_populate_popup(self, textview, menu): + """ + Override the default context menu and we prepend Clear (only if + used_in_history_window is False) and if we have sth selected we show a + submenu with actions on the phrase (see + on_conversation_textview_button_press_event) + """ + separator_menuitem_was_added = False + if not self.used_in_history_window: + item = gtk.SeparatorMenuItem() + menu.prepend(item) + separator_menuitem_was_added = True + + item = gtk.ImageMenuItem(gtk.STOCK_CLEAR) + menu.prepend(item) + id_ = item.connect('activate', self.clear) + self.handlers[id_] = item + + if self.selected_phrase: + if not separator_menuitem_was_added: + item = gtk.SeparatorMenuItem() + menu.prepend(item) + + if not self.used_in_history_window: + item = gtk.MenuItem(_('_Quote')) + id_ = item.connect('activate', self.on_quote) + self.handlers[id_] = item + menu.prepend(item) + + _selected_phrase = helpers.reduce_chars_newlines( + self.selected_phrase, 25, 2) + item = gtk.MenuItem(_('_Actions for "%s"') % _selected_phrase) + menu.prepend(item) + submenu = gtk.Menu() + item.set_submenu(submenu) + + always_use_en = gajim.config.get('always_english_wikipedia') + if always_use_en: + link = 'http://en.wikipedia.org/wiki/Special:Search?search=%s'\ + % self.selected_phrase + else: + link = 'http://%s.wikipedia.org/wiki/Special:Search?search=%s'\ + % (gajim.LANG, self.selected_phrase) + item = gtk.MenuItem(_('Read _Wikipedia Article')) + id_ = item.connect('activate', self.visit_url_from_menuitem, link) + self.handlers[id_] = item + submenu.append(item) + + item = gtk.MenuItem(_('Look it up in _Dictionary')) + dict_link = gajim.config.get('dictionary_url') + if dict_link == 'WIKTIONARY': + # special link (yeah undocumented but default) + always_use_en = gajim.config.get('always_english_wiktionary') + if always_use_en: + link = 'http://en.wiktionary.org/wiki/Special:Search?search=%s'\ + % self.selected_phrase + else: + link = 'http://%s.wiktionary.org/wiki/Special:Search?search=%s'\ + % (gajim.LANG, self.selected_phrase) + id_ = item.connect('activate', self.visit_url_from_menuitem, link) + self.handlers[id_] = item + else: + if dict_link.find('%s') == -1: + # we must have %s in the url if not WIKTIONARY + item = gtk.MenuItem(_( + 'Dictionary URL is missing an "%s" and it is not WIKTIONARY')) + item.set_property('sensitive', False) + else: + link = dict_link % self.selected_phrase + id_ = item.connect('activate', self.visit_url_from_menuitem, + link) + self.handlers[id_] = item + submenu.append(item) + + + search_link = gajim.config.get('search_engine') + if search_link.find('%s') == -1: + # we must have %s in the url + item = gtk.MenuItem(_('Web Search URL is missing an "%s"')) + item.set_property('sensitive', False) + else: + item = gtk.MenuItem(_('Web _Search for it')) + link = search_link % self.selected_phrase + id_ = item.connect('activate', self.visit_url_from_menuitem, link) + self.handlers[id_] = item + submenu.append(item) + + item = gtk.MenuItem(_('Open as _Link')) + id_ = item.connect('activate', self.visit_url_from_menuitem, link) + self.handlers[id_] = item + submenu.append(item) + + menu.show_all() + + def on_quote(self, widget): + self.emit('quote', self.selected_phrase) + + def on_textview_button_press_event(self, widget, event): + # If we clicked on a taged text do NOT open the standard popup menu + # if normal text check if we have sth selected + self.selected_phrase = '' # do not move belove event button check! + + if event.button != 3: # if not right click + return False + + x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, + int(event.x), int(event.y)) + iter_ = self.tv.get_iter_at_location(x, y) + tags = iter_.get_tags() + + + if tags: # we clicked on sth special (it can be status message too) + for tag in tags: + tag_name = tag.get_property('name') + if tag_name in ('url', 'mail', 'xmpp', 'sth_at_sth'): + return True # we block normal context menu + + # we check if sth was selected and if it was we assign + # selected_phrase variable + # so on_conversation_textview_populate_popup can use it + buffer_ = self.tv.get_buffer() + return_val = buffer_.get_selection_bounds() + if return_val: # if sth was selected when we right-clicked + # get the selected text + start_sel, finish_sel = return_val[0], return_val[1] + self.selected_phrase = buffer_.get_text(start_sel, finish_sel).decode( + 'utf-8') + elif ord(iter_.get_char()) > 31: + # we clicked on a word, do as if it's selected for context menu + start_sel = iter_.copy() + if not start_sel.starts_word(): + start_sel.backward_word_start() + finish_sel = iter_.copy() + if not finish_sel.ends_word(): + finish_sel.forward_word_end() + self.selected_phrase = buffer_.get_text(start_sel, finish_sel).decode( + 'utf-8') + + def on_open_link_activate(self, widget, kind, text): + helpers.launch_browser_mailer(kind, text) + + def on_copy_link_activate(self, widget, text): + clip = gtk.clipboard_get() + clip.set_text(text) + + def on_start_chat_activate(self, widget, jid): + gajim.interface.new_chat_from_jid(self.account, jid) + + def on_join_group_chat_menuitem_activate(self, widget, room_jid): + if 'join_gc' in gajim.interface.instances[self.account]: + instance = gajim.interface.instances[self.account]['join_gc'] + instance.xml.get_object('room_jid_entry').set_text(room_jid) + gajim.interface.instances[self.account]['join_gc'].window.present() + else: + try: + dialogs.JoinGroupchatWindow(account=self.account, room_jid=room_jid) + except GajimGeneralException: + pass + + def on_add_to_roster_activate(self, widget, jid): + dialogs.AddNewContactWindow(self.account, jid) + + def make_link_menu(self, event, kind, text): + xml = gtkgui_helpers.get_gtk_builder('chat_context_menu.ui') + menu = xml.get_object('chat_context_menu') + childs = menu.get_children() + if kind == 'url': + id_ = childs[0].connect('activate', self.on_copy_link_activate, text) + self.handlers[id_] = childs[0] + id_ = childs[1].connect('activate', self.on_open_link_activate, kind, + text) + self.handlers[id_] = childs[1] + childs[2].hide() # copy mail address + childs[3].hide() # open mail composer + childs[4].hide() # jid section separator + childs[5].hide() # start chat + childs[6].hide() # join group chat + childs[7].hide() # add to roster + else: # It's a mail or a JID + # load muc icon + join_group_chat_menuitem = xml.get_object('join_group_chat_menuitem') + muc_icon = gtkgui_helpers.load_icon('muc_active') + if muc_icon: + join_group_chat_menuitem.set_image(muc_icon) + + text = text.lower() + if text.startswith('xmpp:'): + text = text[5:] + id_ = childs[2].connect('activate', self.on_copy_link_activate, text) + self.handlers[id_] = childs[2] + id_ = childs[3].connect('activate', self.on_open_link_activate, kind, + text) + self.handlers[id_] = childs[3] + id_ = childs[5].connect('activate', self.on_start_chat_activate, text) + self.handlers[id_] = childs[5] + id_ = childs[6].connect('activate', + self.on_join_group_chat_menuitem_activate, text) + self.handlers[id_] = childs[6] + + if self.account: + id_ = childs[7].connect('activate', self.on_add_to_roster_activate, + text) + self.handlers[id_] = childs[7] + childs[7].show() # show add to roster menuitem + else: + childs[7].hide() # hide add to roster menuitem + + if kind == 'xmpp': + childs[2].hide() # copy mail address + childs[3].hide() # open mail composer + childs[4].hide() # jid section separator + elif kind == 'mail': + childs[4].hide() # jid section separator + childs[5].hide() # start chat + childs[6].hide() # join group chat + childs[7].hide() # add to roster + + childs[0].hide() # copy link location + childs[1].hide() # open link in browser + + menu.popup(None, None, None, event.button, event.time) + + def hyperlink_handler(self, texttag, widget, event, iter_, kind): + if event.type == gtk.gdk.BUTTON_PRESS: + begin_iter = iter_.copy() + # we get the begining of the tag + while not begin_iter.begins_tag(texttag): + begin_iter.backward_char() + end_iter = iter_.copy() + # we get the end of the tag + while not end_iter.ends_tag(texttag): + end_iter.forward_char() + word = self.tv.get_buffer().get_text(begin_iter, end_iter).decode( + 'utf-8') + if event.button == 3: # right click + self.make_link_menu(event, kind, word) + else: + # we launch the correct application + if kind == 'xmpp': + word = word[5:] + if '?' in word: + (jid, action) = word.split('?') + if action == 'join': + self.on_join_group_chat_menuitem_activate(None, jid) + else: + self.on_start_chat_activate(None, jid) + else: + self.on_start_chat_activate(None, word) + else: + helpers.launch_browser_mailer(kind, word) + + def html_hyperlink_handler(self, texttag, widget, event, iter_, kind, href): + if event.type == gtk.gdk.BUTTON_PRESS: + if event.button == 3: # right click + self.make_link_menu(event, kind, href) + return True + else: + # we launch the correct application + helpers.launch_browser_mailer(kind, href) + + + def detect_and_print_special_text(self, otext, other_tags, graphics=True): + """ + Detect special text (emots & links & formatting), print normal text + before any special text it founds, then print special text (that happens + many times until last special text is printed) and then return the index + after *last* special text, so we can print it in + print_conversation_line() + """ + buffer_ = self.tv.get_buffer() + + insert_tags_func = buffer_.insert_with_tags_by_name + # detect_and_print_special_text() is also used by + # HtmlHandler.handle_specials() and there tags is gtk.TextTag objects, + # not strings + if other_tags and isinstance(other_tags[0], gtk.TextTag): + insert_tags_func = buffer_.insert_with_tags + + index = 0 + + # Too many special elements (emoticons, LaTeX formulas, etc) + # may cause Gajim to freeze (see #5129). + # We impose an arbitrary limit of 100 specials per message. + specials_limit = 100 + + # basic: links + mail + formatting is always checked (we like that) + if gajim.config.get('emoticons_theme') and graphics: + # search for emoticons & urls + iterator = gajim.interface.emot_and_basic_re.finditer(otext) + else: # search for just urls + mail + formatting + iterator = gajim.interface.basic_pattern_re.finditer(otext) + for match in iterator: + start, end = match.span() + special_text = otext[start:end] + if start > index: + text_before_special_text = otext[index:start] + end_iter = buffer_.get_end_iter() + # we insert normal text + insert_tags_func(end_iter, text_before_special_text, *other_tags) + index = end # update index + + # now print it + self.print_special_text(special_text, other_tags, graphics=graphics) + specials_limit -= 1 + if specials_limit <= 0: + break + + # add the rest of text located in the index and after + end_iter = buffer_.get_end_iter() + insert_tags_func(end_iter, otext[index:], *other_tags) + + return buffer_.get_end_iter() + + def print_special_text(self, special_text, other_tags, graphics=True): + """ + Is called by detect_and_print_special_text and prints special text + (emots, links, formatting) + """ + tags = [] + use_other_tags = True + text_is_valid_uri = False + show_ascii_formatting_chars = \ + gajim.config.get('show_ascii_formatting_chars') + buffer_ = self.tv.get_buffer() + + # Check if we accept this as an uri + schemes = gajim.config.get('uri_schemes').split() + for scheme in schemes: + if special_text.startswith(scheme + ':'): + text_is_valid_uri = True + + possible_emot_ascii_caps = special_text.upper() # emoticons keys are CAPS + if gajim.config.get('emoticons_theme') and \ + possible_emot_ascii_caps in gajim.interface.emoticons.keys() and graphics: + # it's an emoticon + emot_ascii = possible_emot_ascii_caps + end_iter = buffer_.get_end_iter() + anchor = buffer_.create_child_anchor(end_iter) + img = TextViewImage(anchor, special_text) + animations = gajim.interface.emoticons_animations + if not emot_ascii in animations: + animations[emot_ascii] = gtk.gdk.PixbufAnimation( + gajim.interface.emoticons[emot_ascii]) + img.set_from_animation(animations[emot_ascii]) + img.show() + self.images.append(img) + # add with possible animation + self.tv.add_child_at_anchor(img, anchor) + elif special_text.startswith('www.') or \ + special_text.startswith('ftp.') or \ + text_is_valid_uri: + tags.append('url') + use_other_tags = False + elif special_text.startswith('mailto:'): + tags.append('mail') + use_other_tags = False + elif special_text.startswith('xmpp:'): + tags.append('xmpp') + use_other_tags = False + elif gajim.interface.sth_at_sth_dot_sth_re.match(special_text): + # it's a JID or mail + tags.append('sth_at_sth') + use_other_tags = False + elif special_text.startswith('*'): # it's a bold text + tags.append('bold') + if special_text[1] == '/' and special_text[-2] == '/' and\ + len(special_text) > 4: # it's also italic + tags.append('italic') + if not show_ascii_formatting_chars: + special_text = special_text[2:-2] # remove */ /* + elif special_text[1] == '_' and special_text[-2] == '_' and \ + len(special_text) > 4: # it's also underlined + tags.append('underline') + if not show_ascii_formatting_chars: + special_text = special_text[2:-2] # remove *_ _* + else: + if not show_ascii_formatting_chars: + special_text = special_text[1:-1] # remove * * + elif special_text.startswith('/'): # it's an italic text + tags.append('italic') + if special_text[1] == '*' and special_text[-2] == '*' and \ + len(special_text) > 4: # it's also bold + tags.append('bold') + if not show_ascii_formatting_chars: + special_text = special_text[2:-2] # remove /* */ + elif special_text[1] == '_' and special_text[-2] == '_' and \ + len(special_text) > 4: # it's also underlined + tags.append('underline') + if not show_ascii_formatting_chars: + special_text = special_text[2:-2] # remove /_ _/ + else: + if not show_ascii_formatting_chars: + special_text = special_text[1:-1] # remove / / + elif special_text.startswith('_'): # it's an underlined text + tags.append('underline') + if special_text[1] == '*' and special_text[-2] == '*' and \ + len(special_text) > 4: # it's also bold + tags.append('bold') + if not show_ascii_formatting_chars: + special_text = special_text[2:-2] # remove _* *_ + elif special_text[1] == '/' and special_text[-2] == '/' and \ + len(special_text) > 4: # it's also italic + tags.append('italic') + if not show_ascii_formatting_chars: + special_text = special_text[2:-2] # remove _/ /_ + else: + if not show_ascii_formatting_chars: + special_text = special_text[1:-1] # remove _ _ + elif gajim.HAVE_LATEX and special_text.startswith('$$') and \ + special_text.endswith('$$') and graphics: + try: + imagepath = latex.latex_to_image(special_text[2:-2]) + except LatexError, e: + # print the error after the line has been written + gobject.idle_add(self.print_conversation_line, str(e), '', 'info', + '', None) + imagepath = None + end_iter = buffer_.get_end_iter() + if imagepath is not None: + anchor = buffer_.create_child_anchor(end_iter) + img = TextViewImage(anchor, special_text) + img.set_from_file(imagepath) + img.show() + # add + self.tv.add_child_at_anchor(img, anchor) + # delete old file + try: + os.remove(imagepath) + except Exception: + pass + else: + buffer_.insert(end_iter, special_text) + use_other_tags = False + else: + # It's nothing special + if use_other_tags: + end_iter = buffer_.get_end_iter() + insert_tags_func = buffer_.insert_with_tags_by_name + if other_tags and isinstance(other_tags[0], gtk.TextTag): + insert_tags_func = buffer_.insert_with_tags + + insert_tags_func(end_iter, special_text, *other_tags) + + if tags: + end_iter = buffer_.get_end_iter() + all_tags = tags[:] + if use_other_tags: + all_tags += other_tags + # convert all names to TextTag + ttt = buffer_.get_tag_table() + all_tags = [(ttt.lookup(t) if isinstance(t, str) else t) for t in all_tags] + buffer_.insert_with_tags(end_iter, special_text, *all_tags) + + def print_empty_line(self): + buffer_ = self.tv.get_buffer() + end_iter = buffer_.get_end_iter() + buffer_.insert_with_tags_by_name(end_iter, '\n', 'eol') + + def print_conversation_line(self, text, jid, kind, name, tim, + other_tags_for_name=[], other_tags_for_time=[], + other_tags_for_text=[], subject=None, old_kind=None, xhtml=None, + simple=False, graphics=True): + """ + Print 'chat' type messages + """ + buffer_ = self.tv.get_buffer() + buffer_.begin_user_action() + if self.marks_queue.full(): + # remove oldest line + m1 = self.marks_queue.get() + m2 = self.marks_queue.get() + i1 = buffer_.get_iter_at_mark(m1) + i2 = buffer_.get_iter_at_mark(m2) + buffer_.delete(i1, i2) + buffer_.delete_mark(m1) + end_iter = buffer_.get_end_iter() + end_offset = end_iter.get_offset() + at_the_end = self.at_the_end() + move_selection = False + if buffer_.get_has_selection() and buffer_.get_selection_bounds()[1].\ + get_offset() == end_offset: + move_selection = True + + # Create one mark and add it to queue once if it's the first line + # else twice (one for end bound, one for start bound) + mark = None + if buffer_.get_char_count() > 0: + if not simple: + buffer_.insert_with_tags_by_name(end_iter, '\n', 'eol') + if move_selection: + sel_start, sel_end = buffer_.get_selection_bounds() + sel_end.backward_char() + buffer_.select_range(sel_start, sel_end) + mark = buffer_.create_mark(None, end_iter, left_gravity=True) + self.marks_queue.put(mark) + if not mark: + mark = buffer_.create_mark(None, end_iter, left_gravity=True) + self.marks_queue.put(mark) + if kind == 'incoming_queue': + kind = 'incoming' + if old_kind == 'incoming_queue': + old_kind = 'incoming' + # print the time stamp + if not tim: + # We don't have tim for outgoing messages... + tim = time.localtime() + current_print_time = gajim.config.get('print_time') + if current_print_time == 'always' and kind != 'info' and not simple: + timestamp_str = self.get_time_to_show(tim) + timestamp = time.strftime(timestamp_str, tim) + buffer_.insert_with_tags_by_name(end_iter, timestamp, + *other_tags_for_time) + elif current_print_time == 'sometimes' and kind != 'info' and not simple: + every_foo_seconds = 60 * gajim.config.get( + 'print_ichat_every_foo_minutes') + seconds_passed = time.mktime(tim) - self.last_time_printout + if seconds_passed > every_foo_seconds: + self.last_time_printout = time.mktime(tim) + end_iter = buffer_.get_end_iter() + if gajim.config.get('print_time_fuzzy') > 0: + ft = self.fc.fuzzy_time(gajim.config.get('print_time_fuzzy'), tim) + tim_format = ft.decode(locale.getpreferredencoding()) + else: + tim_format = self.get_time_to_show(tim) + buffer_.insert_with_tags_by_name(end_iter, tim_format + '\n', + 'time_sometimes') + # kind = info, we print things as if it was a status: same color, ... + if kind in ('error', 'info'): + kind = 'status' + other_text_tag = self.detect_other_text_tag(text, kind) + text_tags = other_tags_for_text[:] # create a new list + if other_text_tag: + # note that color of /me may be overwritten in gc_control + text_tags.append(other_text_tag) + else: # not status nor /me + if gajim.config.get('chat_merge_consecutive_nickname'): + if kind != old_kind: + self.print_name(name, kind, other_tags_for_name) + else: + self.print_real_text(gajim.config.get( + 'chat_merge_consecutive_nickname_indent')) + else: + self.print_name(name, kind, other_tags_for_name) + if kind == 'incoming': + text_tags.append('incomingtxt') + elif kind == 'outgoing': + text_tags.append('outgoingtxt') + self.print_subject(subject) + self.print_real_text(text, text_tags, name, xhtml, graphics=graphics) + + # scroll to the end of the textview + if at_the_end or kind == 'outgoing': + # we are at the end or we are sending something + # scroll to the end (via idle in case the scrollbar has appeared) + if gajim.config.get('use_smooth_scrolling'): + gobject.idle_add(self.smooth_scroll_to_end) + else: + gobject.idle_add(self.scroll_to_end) + + buffer_.end_user_action() + + def get_time_to_show(self, tim): + """ + Get the time, with the day before if needed and return it. It DOESN'T + format a fuzzy time + """ + format = '' + # get difference in days since epoch (86400 = 24*3600) + # number of days since epoch for current time (in GMT) - + # number of days since epoch for message (in GMT) + diff_day = int(timegm(time.localtime())) / 86400 -\ + int(timegm(tim)) / 86400 + if diff_day == 0: + day_str = '' + else: + #%i is day in year (1-365) + day_str = i18n.ngettext('Yesterday', '%i days ago', diff_day, + replace_plural=diff_day) + if day_str: + format += day_str + ' ' + timestamp_str = gajim.config.get('time_stamp') + timestamp_str = helpers.from_one_line(timestamp_str) + format += timestamp_str + tim_format = time.strftime(format, tim) + if locale.getpreferredencoding() != 'KOI8-R': + # if tim_format comes as unicode because of day_str. + # we convert it to the encoding that we want (and that is utf-8) + tim_format = helpers.ensure_utf8_string(tim_format) + return tim_format + + def detect_other_text_tag(self, text, kind): + if kind == 'status': + return kind + elif text.startswith('/me ') or text.startswith('/me\n'): + return kind + + def print_name(self, name, kind, other_tags_for_name): + if name: + buffer_ = self.tv.get_buffer() + end_iter = buffer_.get_end_iter() + name_tags = other_tags_for_name[:] # create a new list + name_tags.append(kind) + before_str = gajim.config.get('before_nickname') + before_str = helpers.from_one_line(before_str) + after_str = gajim.config.get('after_nickname') + after_str = helpers.from_one_line(after_str) + format = before_str + name + after_str + ' ' + buffer_.insert_with_tags_by_name(end_iter, format, *name_tags) + + def print_subject(self, subject): + if subject: # if we have subject, show it too! + subject = _('Subject: %s\n') % subject + buffer_ = self.tv.get_buffer() + end_iter = buffer_.get_end_iter() + buffer_.insert(end_iter, subject) + self.print_empty_line() + + def print_real_text(self, text, text_tags=[], name=None, xhtml=None, + graphics=True): + """ + Add normal and special text. call this to add text + """ + if xhtml: + try: + if name and (text.startswith('/me ') or text.startswith('/me\n')): + xhtml = xhtml.replace('/me', '* %s' % (name,), 1) + self.tv.display_html(xhtml.encode('utf-8'), self) + return + except Exception, e: + gajim.log.debug('Error processing xhtml' + str(e)) + gajim.log.debug('with |' + xhtml + '|') + + # /me is replaced by name if name is given + if name and (text.startswith('/me ') or text.startswith('/me\n')): + text = '* ' + name + text[3:] + text_tags.append('italic') + # detect urls formatting and if the user has it on emoticons + self.detect_and_print_special_text(text, text_tags, graphics=graphics) diff --git a/src/dataforms_widget.py b/src/dataforms_widget.py index 34ae77a68..5fa9a2397 100644 --- a/src/dataforms_widget.py +++ b/src/dataforms_widget.py @@ -38,587 +38,585 @@ import itertools class DataFormWidget(gtk.Alignment, object): # "public" interface - """ - Data Form widget. Use like any other widget - """ + """ + Data Form widget. Use like any other widget + """ - def __init__(self, dataformnode=None): - ''' Create a widget. ''' - gtk.Alignment.__init__(self, xscale=1.0, yscale=1.0) + def __init__(self, dataformnode=None): + ''' Create a widget. ''' + gtk.Alignment.__init__(self, xscale=1.0, yscale=1.0) - self._data_form = None + self._data_form = None - self.xml = gtkgui_helpers.get_gtk_builder('data_form_window.ui', - 'data_form_vbox') - self.xml.connect_signals(self) - for name in ('instructions_label', 'instructions_hseparator', - 'single_form_viewport', 'data_form_types_notebook', - 'single_form_scrolledwindow', 'multiple_form_hbox', - 'records_treeview', 'buttons_vbox', 'add_button', 'remove_button', - 'edit_button', 'up_button', 'down_button', 'clear_button'): - self.__dict__[name] = self.xml.get_object(name) + self.xml = gtkgui_helpers.get_gtk_builder('data_form_window.ui', + 'data_form_vbox') + self.xml.connect_signals(self) + for name in ('instructions_label', 'instructions_hseparator', + 'single_form_viewport', 'data_form_types_notebook', + 'single_form_scrolledwindow', 'multiple_form_hbox', + 'records_treeview', 'buttons_vbox', 'add_button', 'remove_button', + 'edit_button', 'up_button', 'down_button', 'clear_button'): + self.__dict__[name] = self.xml.get_object(name) - self.add(self.xml.get_object('data_form_vbox')) + self.add(self.xml.get_object('data_form_vbox')) - if dataformnode is not None: - self.set_data_form(dataformnode) + if dataformnode is not None: + self.set_data_form(dataformnode) - selection = self.records_treeview.get_selection() - selection.connect('changed', self.on_records_selection_changed) - selection.set_mode(gtk.SELECTION_MULTIPLE) + selection = self.records_treeview.get_selection() + selection.connect('changed', self.on_records_selection_changed) + selection.set_mode(gtk.SELECTION_MULTIPLE) - def set_data_form(self, dataform): - """ - Set the data form (xmpp.DataForm) displayed in widget - """ - assert isinstance(dataform, dataforms.DataForm) + def set_data_form(self, dataform): + """ + Set the data form (xmpp.DataForm) displayed in widget + """ + assert isinstance(dataform, dataforms.DataForm) - self.del_data_form() - self._data_form = dataform - if isinstance(dataform, dataforms.SimpleDataForm): - self.build_single_data_form() - else: - self.build_multiple_data_form() + self.del_data_form() + self._data_form = dataform + if isinstance(dataform, dataforms.SimpleDataForm): + self.build_single_data_form() + else: + self.build_multiple_data_form() - # create appropriate description for instructions field if there isn't any - if dataform.instructions == '': - self.instructions_label.set_no_show_all(True) - self.instructions_label.hide() - else: - self.instructions_label.set_text(dataform.instructions) - gtkgui_helpers.label_set_autowrap(self.instructions_label) + # create appropriate description for instructions field if there isn't any + if dataform.instructions == '': + self.instructions_label.set_no_show_all(True) + self.instructions_label.hide() + else: + self.instructions_label.set_text(dataform.instructions) + gtkgui_helpers.label_set_autowrap(self.instructions_label) - def get_data_form(self): - """ - Data form displayed in the widget or None if no form - """ - return self._data_form + def get_data_form(self): + """ + Data form displayed in the widget or None if no form + """ + return self._data_form - def del_data_form(self): - self.clean_data_form() - self._data_form = None + def del_data_form(self): + self.clean_data_form() + self._data_form = None - data_form = property(get_data_form, set_data_form, del_data_form, - 'Data form presented in a widget') + data_form = property(get_data_form, set_data_form, del_data_form, + 'Data form presented in a widget') - def get_title(self): - """ - Get the title of data form, as a unicode object. If no title or no form, - returns u''. Useful for setting window title - """ - if self._data_form is not None: - if self._data_form.title is not None: - return self._data_form.title - return u'' + def get_title(self): + """ + Get the title of data form, as a unicode object. If no title or no form, + returns u''. Useful for setting window title + """ + if self._data_form is not None: + if self._data_form.title is not None: + return self._data_form.title + return u'' - title = property(get_title, None, None, 'Data form title') + title = property(get_title, None, None, 'Data form title') - def show(self): - ''' Treat 'us' as one widget. ''' - self.show_all() + def show(self): + ''' Treat 'us' as one widget. ''' + self.show_all() # "private" methods # we have actually two different kinds of data forms: one is a simple form to fill, # second is a table with several records; - def empty_method(self): - pass + def empty_method(self): + pass - def clean_data_form(self): - """ - Remove data about existing form. This metod is empty, because it is - rewritten by build_*_data_form, according to type of form which is - actually displayed - """ - pass + def clean_data_form(self): + """ + Remove data about existing form. This metod is empty, because it is + rewritten by build_*_data_form, according to type of form which is + actually displayed + """ + pass - def build_single_data_form(self): - '''Invoked when new single form is to be created.''' - assert isinstance(self._data_form, dataforms.SimpleDataForm) + def build_single_data_form(self): + '''Invoked when new single form is to be created.''' + assert isinstance(self._data_form, dataforms.SimpleDataForm) - self.clean_data_form() + self.clean_data_form() - self.singleform = SingleForm(self._data_form) - self.singleform.show() - self.single_form_viewport.add(self.singleform) - self.data_form_types_notebook.set_current_page( - self.data_form_types_notebook.page_num( - self.single_form_scrolledwindow)) + self.singleform = SingleForm(self._data_form) + self.singleform.show() + self.single_form_viewport.add(self.singleform) + self.data_form_types_notebook.set_current_page( + self.data_form_types_notebook.page_num( + self.single_form_scrolledwindow)) - self.clean_data_form = self.clean_single_data_form + self.clean_data_form = self.clean_single_data_form - def clean_single_data_form(self): - """ - Called as clean_data_form, read the docs of clean_data_form(). Remove - form from widget - """ - self.singleform.destroy() - self.clean_data_form = self.empty_method # we won't call it twice - del self.singleform + def clean_single_data_form(self): + """ + Called as clean_data_form, read the docs of clean_data_form(). Remove + form from widget + """ + self.singleform.destroy() + self.clean_data_form = self.empty_method # we won't call it twice + del self.singleform - def build_multiple_data_form(self): - """ - Invoked when new multiple form is to be created - """ - assert isinstance(self._data_form, dataforms.MultipleDataForm) + def build_multiple_data_form(self): + """ + Invoked when new multiple form is to be created + """ + assert isinstance(self._data_form, dataforms.MultipleDataForm) - self.clean_data_form() + self.clean_data_form() - # creating model for form... - fieldtypes = [] - fieldvars = [] - for field in self._data_form.reported.iter_fields(): - # note: we store also text-private and hidden fields, - # we just do not display them. - # TODO: boolean fields - #elif field.type=='boolean': fieldtypes.append(bool) - fieldtypes.append(str) - fieldvars.append(field.var) + # creating model for form... + fieldtypes = [] + fieldvars = [] + for field in self._data_form.reported.iter_fields(): + # note: we store also text-private and hidden fields, + # we just do not display them. + # TODO: boolean fields + #elif field.type=='boolean': fieldtypes.append(bool) + fieldtypes.append(str) + fieldvars.append(field.var) - self.multiplemodel = gtk.ListStore(*fieldtypes) + self.multiplemodel = gtk.ListStore(*fieldtypes) - # moving all data to model - for item in self._data_form.iter_records(): - iter_ = self.multiplemodel.append() - for field in item.iter_fields(): - self.multiplemodel.set_value(iter_, fieldvars.index(field.var), - field.value) + # moving all data to model + for item in self._data_form.iter_records(): + iter_ = self.multiplemodel.append() + for field in item.iter_fields(): + self.multiplemodel.set_value(iter_, fieldvars.index(field.var), + field.value) - # constructing columns... - for field, counter in zip(self._data_form.reported.iter_fields(), - itertools.count()): - self.records_treeview.append_column( - gtk.TreeViewColumn(field.label, gtk.CellRendererText(), - text=counter)) + # constructing columns... + for field, counter in zip(self._data_form.reported.iter_fields(), + itertools.count()): + self.records_treeview.append_column( + gtk.TreeViewColumn(field.label, gtk.CellRendererText(), + text=counter)) - self.records_treeview.set_model(self.multiplemodel) - self.records_treeview.show_all() + self.records_treeview.set_model(self.multiplemodel) + self.records_treeview.show_all() - self.data_form_types_notebook.set_current_page( - self.data_form_types_notebook.page_num( - self.multiple_form_hbox)) + self.data_form_types_notebook.set_current_page( + self.data_form_types_notebook.page_num( + self.multiple_form_hbox)) - self.clean_data_form = self.clean_multiple_data_form + self.clean_data_form = self.clean_multiple_data_form - readwrite = self._data_form.type != 'result' - if not readwrite: - self.buttons_vbox.set_no_show_all(True) - self.buttons_vbox.hide() - else: - self.buttons_vbox.set_no_show_all(False) - # refresh list look - self.refresh_multiple_buttons() + readwrite = self._data_form.type != 'result' + if not readwrite: + self.buttons_vbox.set_no_show_all(True) + self.buttons_vbox.hide() + else: + self.buttons_vbox.set_no_show_all(False) + # refresh list look + self.refresh_multiple_buttons() - def clean_multiple_data_form(self): - """ - Called as clean_data_form, read the docs of clean_data_form(). Remove - form from widget - """ - self.clean_data_form = self.empty_method # we won't call it twice - del self.multiplemodel + def clean_multiple_data_form(self): + """ + Called as clean_data_form, read the docs of clean_data_form(). Remove + form from widget + """ + self.clean_data_form = self.empty_method # we won't call it twice + del self.multiplemodel - def refresh_multiple_buttons(self): - """ - Checks for treeview state and makes control buttons sensitive - """ - selection = self.records_treeview.get_selection() - model = self.records_treeview.get_model() - count = selection.count_selected_rows() - if count == 0: - self.remove_button.set_sensitive(False) - self.edit_button.set_sensitive(False) - self.up_button.set_sensitive(False) - self.down_button.set_sensitive(False) - elif count == 1: - self.remove_button.set_sensitive(True) - self.edit_button.set_sensitive(True) - _, (path,) = selection.get_selected_rows() - iter_ = model.get_iter(path) - if model.iter_next(iter_) is None: - self.up_button.set_sensitive(True) - self.down_button.set_sensitive(False) - elif path == (0, ): - self.up_button.set_sensitive(False) - self.down_button.set_sensitive(True) - else: - self.up_button.set_sensitive(True) - self.down_button.set_sensitive(True) - else: - self.remove_button.set_sensitive(True) - self.edit_button.set_sensitive(True) - self.up_button.set_sensitive(False) - self.down_button.set_sensitive(False) + def refresh_multiple_buttons(self): + """ + Checks for treeview state and makes control buttons sensitive + """ + selection = self.records_treeview.get_selection() + model = self.records_treeview.get_model() + count = selection.count_selected_rows() + if count == 0: + self.remove_button.set_sensitive(False) + self.edit_button.set_sensitive(False) + self.up_button.set_sensitive(False) + self.down_button.set_sensitive(False) + elif count == 1: + self.remove_button.set_sensitive(True) + self.edit_button.set_sensitive(True) + _, (path,) = selection.get_selected_rows() + iter_ = model.get_iter(path) + if model.iter_next(iter_) is None: + self.up_button.set_sensitive(True) + self.down_button.set_sensitive(False) + elif path == (0, ): + self.up_button.set_sensitive(False) + self.down_button.set_sensitive(True) + else: + self.up_button.set_sensitive(True) + self.down_button.set_sensitive(True) + else: + self.remove_button.set_sensitive(True) + self.edit_button.set_sensitive(True) + self.up_button.set_sensitive(False) + self.down_button.set_sensitive(False) - if len(model) == 0: - self.clear_button.set_sensitive(False) - else: - self.clear_button.set_sensitive(True) + if len(model) == 0: + self.clear_button.set_sensitive(False) + else: + self.clear_button.set_sensitive(True) - def on_clear_button_clicked(self, widget): - self.records_treeview.get_model().clear() + def on_clear_button_clicked(self, widget): + self.records_treeview.get_model().clear() - def on_remove_button_clicked(self, widget): - selection = self.records_treeview.get_selection() - model, rowrefs = selection.get_selected_rows() - # rowref is a list of paths - for i in xrange(len(rowrefs)): - rowrefs[i] = gtk.TreeRowReference(model, rowrefs[i]) - # rowref is a list of row references; need to convert because we will - # modify the model, paths would change - for rowref in rowrefs: - del model[rowref.get_path()] + def on_remove_button_clicked(self, widget): + selection = self.records_treeview.get_selection() + model, rowrefs = selection.get_selected_rows() + # rowref is a list of paths + for i in xrange(len(rowrefs)): + rowrefs[i] = gtk.TreeRowReference(model, rowrefs[i]) + # rowref is a list of row references; need to convert because we will + # modify the model, paths would change + for rowref in rowrefs: + del model[rowref.get_path()] - def on_up_button_clicked(self, widget): - selection = self.records_treeview.get_selection() - model, (path,) = selection.get_selected_rows() - iter_ = model.get_iter(path) - # constructing path for previous iter - previter = model.get_iter((path[0]-1,)) - model.swap(iter_, previter) + def on_up_button_clicked(self, widget): + selection = self.records_treeview.get_selection() + model, (path,) = selection.get_selected_rows() + iter_ = model.get_iter(path) + # constructing path for previous iter + previter = model.get_iter((path[0]-1,)) + model.swap(iter_, previter) - self.refresh_multiple_buttons() + self.refresh_multiple_buttons() - def on_down_button_clicked(self, widget): - selection = self.records_treeview.get_selection() - model, (path,) = selection.get_selected_rows() - iter_ = model.get_iter(path) - nextiter = model.iter_next(iter_) - model.swap(iter_, nextiter) + def on_down_button_clicked(self, widget): + selection = self.records_treeview.get_selection() + model, (path,) = selection.get_selected_rows() + iter_ = model.get_iter(path) + nextiter = model.iter_next(iter_) + model.swap(iter_, nextiter) - self.refresh_multiple_buttons() + self.refresh_multiple_buttons() - def on_records_selection_changed(self, widget): - self.refresh_multiple_buttons() + def on_records_selection_changed(self, widget): + self.refresh_multiple_buttons() class SingleForm(gtk.Table, object): - """ - Widget that represent DATAFORM_SINGLE mode form. Because this is used not - only to display single forms, but to form input windows of multiple-type - forms, it is in another class - """ + """ + Widget that represent DATAFORM_SINGLE mode form. Because this is used not + only to display single forms, but to form input windows of multiple-type + forms, it is in another class + """ - def __init__(self, dataform): - assert isinstance(dataform, dataforms.SimpleDataForm) + def __init__(self, dataform): + assert isinstance(dataform, dataforms.SimpleDataForm) - gtk.Table.__init__(self) - self.set_col_spacings(12) - self.set_row_spacings(6) + gtk.Table.__init__(self) + self.set_col_spacings(12) + self.set_row_spacings(6) - def decorate_with_tooltip(widget, field): - """ - Adds a tooltip containing field's description to a widget. Creates - EventBox if widget doesn't have its own gdk window. Returns decorated - widget - """ - if field.description != '': - if widget.flags() & gtk.NO_WINDOW: - evbox = gtk.EventBox() - evbox.add(widget) - widget = evbox - widget.set_tooltip_text(field.description) - return widget + def decorate_with_tooltip(widget, field): + """ + Adds a tooltip containing field's description to a widget. Creates + EventBox if widget doesn't have its own gdk window. Returns decorated + widget + """ + if field.description != '': + if widget.flags() & gtk.NO_WINDOW: + evbox = gtk.EventBox() + evbox.add(widget) + widget = evbox + widget.set_tooltip_text(field.description) + return widget - self._data_form = dataform + self._data_form = dataform - # building widget - linecounter = 0 + # building widget + linecounter = 0 - # is the form changeable? - readwrite = dataform.type != 'result' + # is the form changeable? + readwrite = dataform.type != 'result' - # for each field... - for field in self._data_form.iter_fields(): - if field.type == 'hidden': continue + # for each field... + for field in self._data_form.iter_fields(): + if field.type == 'hidden': continue - commonlabel = True - commonlabelcenter = False - commonwidget = True - widget = None + commonlabel = True + commonlabelcenter = False + commonwidget = True + widget = None - if field.type == 'boolean': - commonlabelcenter = True - widget = gtk.CheckButton() - widget.connect('toggled', self.on_boolean_checkbutton_toggled, - field) - widget.set_active(field.value) + if field.type == 'boolean': + commonlabelcenter = True + widget = gtk.CheckButton() + widget.connect('toggled', self.on_boolean_checkbutton_toggled, + field) + widget.set_active(field.value) - elif field.type == 'fixed': - leftattach = 1 - rightattach = 2 - if field.label is None: - commonlabel = False - leftattach = 0 + elif field.type == 'fixed': + leftattach = 1 + rightattach = 2 + if field.label is None: + commonlabel = False + leftattach = 0 - commonwidget = False - widget = gtk.Label(field.value) - widget.set_line_wrap(True) - self.attach(widget, leftattach, rightattach, linecounter, - linecounter+1, xoptions=gtk.FILL, yoptions=gtk.FILL) + commonwidget = False + widget = gtk.Label(field.value) + widget.set_line_wrap(True) + self.attach(widget, leftattach, rightattach, linecounter, + linecounter+1, xoptions=gtk.FILL, yoptions=gtk.FILL) - elif field.type == 'list-single': - # TODO: What if we have radio buttons and non-required field? - # TODO: We cannot deactivate them all... - if len(field.options) < 6: - # 5 option max: show radiobutton - widget = gtk.VBox() - first_radio = None - for value, label in field.iter_options(): - if not label: - label = value - radio = gtk.RadioButton(first_radio, label=label) - radio.connect('toggled', - self.on_list_single_radiobutton_toggled, field, value) - if first_radio is None: - first_radio = radio - if field.value == '': # TODO: is None when done - field.value = value - if value == field.value: - radio.set_active(True) - widget.pack_start(radio, expand=False) - else: - # more than 5 options: show combobox - def on_list_single_combobox_changed(combobox, f): - iter_ = combobox.get_active_iter() - if iter_: - model = combobox.get_model() - f.value = model[iter_][1] - else: - f.value = '' - widget = gtkgui_helpers.create_combobox(field.options, - field.value) - widget.connect('changed', on_list_single_combobox_changed, field) - widget.set_sensitive(readwrite) + elif field.type == 'list-single': + # TODO: What if we have radio buttons and non-required field? + # TODO: We cannot deactivate them all... + if len(field.options) < 6: + # 5 option max: show radiobutton + widget = gtk.VBox() + first_radio = None + for value, label in field.iter_options(): + if not label: + label = value + radio = gtk.RadioButton(first_radio, label=label) + radio.connect('toggled', + self.on_list_single_radiobutton_toggled, field, value) + if first_radio is None: + first_radio = radio + if field.value == '': # TODO: is None when done + field.value = value + if value == field.value: + radio.set_active(True) + widget.pack_start(radio, expand=False) + else: + # more than 5 options: show combobox + def on_list_single_combobox_changed(combobox, f): + iter_ = combobox.get_active_iter() + if iter_: + model = combobox.get_model() + f.value = model[iter_][1] + else: + f.value = '' + widget = gtkgui_helpers.create_combobox(field.options, + field.value) + widget.connect('changed', on_list_single_combobox_changed, field) + widget.set_sensitive(readwrite) - elif field.type == 'list-multi': - # TODO: When more than few choices, make a list - if len(field.options) < 6: - # 5 option max: show checkbutton - widget = gtk.VBox() - for value, label in field.iter_options(): - check = gtk.CheckButton(label, use_underline=False) - check.set_active(value in field.values) - check.connect('toggled', - self.on_list_multi_checkbutton_toggled, field, value) - widget.pack_start(check, expand=False) - else: - # more than 5 options: show combobox - def on_list_multi_treeview_changed(selection, f): - def for_selected(treemodel, path, iter): - vals.append(treemodel[iter][1]) - vals = [] - selection.selected_foreach(for_selected) - field.values = vals[:] - widget = gtk.ScrolledWindow() - widget.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - tv = gtkgui_helpers.create_list_multi(field.options, - field.values) - widget.add(tv) - widget.set_size_request(-1, 120) - tv.get_selection().connect('changed', - on_list_multi_treeview_changed, field) - widget.set_sensitive(readwrite) + elif field.type == 'list-multi': + # TODO: When more than few choices, make a list + if len(field.options) < 6: + # 5 option max: show checkbutton + widget = gtk.VBox() + for value, label in field.iter_options(): + check = gtk.CheckButton(label, use_underline=False) + check.set_active(value in field.values) + check.connect('toggled', + self.on_list_multi_checkbutton_toggled, field, value) + widget.pack_start(check, expand=False) + else: + # more than 5 options: show combobox + def on_list_multi_treeview_changed(selection, f): + def for_selected(treemodel, path, iter): + vals.append(treemodel[iter][1]) + vals = [] + selection.selected_foreach(for_selected) + field.values = vals[:] + widget = gtk.ScrolledWindow() + widget.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + tv = gtkgui_helpers.create_list_multi(field.options, + field.values) + widget.add(tv) + widget.set_size_request(-1, 120) + tv.get_selection().connect('changed', + on_list_multi_treeview_changed, field) + widget.set_sensitive(readwrite) - elif field.type == 'jid-single': - widget = gtk.Entry() - widget.connect('changed', self.on_text_single_entry_changed, field) - widget.set_text(field.value) + elif field.type == 'jid-single': + widget = gtk.Entry() + widget.connect('changed', self.on_text_single_entry_changed, field) + widget.set_text(field.value) - elif field.type == 'jid-multi': - commonwidget = False + elif field.type == 'jid-multi': + commonwidget = False - xml = gtkgui_helpers.get_gtk_builder('data_form_window.ui', - 'item_list_table') - widget = xml.get_object('item_list_table') - treeview = xml.get_object('item_treeview') + xml = gtkgui_helpers.get_gtk_builder('data_form_window.ui', + 'item_list_table') + widget = xml.get_object('item_list_table') + treeview = xml.get_object('item_treeview') - listmodel = gtk.ListStore(str) - for value in field.iter_values(): - # nobody will create several megabytes long stanza - listmodel.insert(999999, (value,)) + listmodel = gtk.ListStore(str) + for value in field.iter_values(): + # nobody will create several megabytes long stanza + listmodel.insert(999999, (value,)) - treeview.set_model(listmodel) + treeview.set_model(listmodel) - renderer = gtk.CellRendererText() - renderer.set_property('editable', True) - renderer.connect('edited', - self.on_jid_multi_cellrenderertext_edited, treeview, listmodel, - field) + renderer = gtk.CellRendererText() + renderer.set_property('editable', True) + renderer.connect('edited', + self.on_jid_multi_cellrenderertext_edited, treeview, listmodel, + field) - treeview.append_column(gtk.TreeViewColumn(None, renderer, - text=0)) + treeview.append_column(gtk.TreeViewColumn(None, renderer, + text=0)) - decorate_with_tooltip(treeview, field) + decorate_with_tooltip(treeview, field) - add_button=xml.get_object('add_button') - add_button.connect('clicked', - self.on_jid_multi_add_button_clicked, treeview, listmodel, field) - edit_button=xml.get_object('edit_button') - edit_button.connect('clicked', - self.on_jid_multi_edit_button_clicked, treeview) - remove_button=xml.get_object('remove_button') - remove_button.connect('clicked', - self.on_jid_multi_remove_button_clicked, treeview, field) - clear_button=xml.get_object('clear_button') - clear_button.connect('clicked', - self.on_jid_multi_clean_button_clicked, listmodel, field) - if not readwrite: - add_button.set_no_show_all(True) - edit_button.set_no_show_all(True) - remove_button.set_no_show_all(True) - clear_button.set_no_show_all(True) + add_button=xml.get_object('add_button') + add_button.connect('clicked', + self.on_jid_multi_add_button_clicked, treeview, listmodel, field) + edit_button=xml.get_object('edit_button') + edit_button.connect('clicked', + self.on_jid_multi_edit_button_clicked, treeview) + remove_button=xml.get_object('remove_button') + remove_button.connect('clicked', + self.on_jid_multi_remove_button_clicked, treeview, field) + clear_button=xml.get_object('clear_button') + clear_button.connect('clicked', + self.on_jid_multi_clean_button_clicked, listmodel, field) + if not readwrite: + add_button.set_no_show_all(True) + edit_button.set_no_show_all(True) + remove_button.set_no_show_all(True) + clear_button.set_no_show_all(True) - widget.set_sensitive(readwrite) - self.attach(widget, 1, 2, linecounter, linecounter+1) + widget.set_sensitive(readwrite) + self.attach(widget, 1, 2, linecounter, linecounter+1) - del xml + del xml - elif field.type == 'text-private': - commonlabelcenter = True - widget = gtk.Entry() - widget.connect('changed', self.on_text_single_entry_changed, field) - widget.set_visibility(False) - widget.set_text(field.value) + elif field.type == 'text-private': + commonlabelcenter = True + widget = gtk.Entry() + widget.connect('changed', self.on_text_single_entry_changed, field) + widget.set_visibility(False) + widget.set_text(field.value) - elif field.type == 'text-multi': - # TODO: bigger text view - commonwidget = False + elif field.type == 'text-multi': + # TODO: bigger text view + commonwidget = False - textwidget = gtk.TextView() - textwidget.set_wrap_mode(gtk.WRAP_WORD) - textwidget.get_buffer().connect('changed', - self.on_text_multi_textbuffer_changed, field) - textwidget.get_buffer().set_text(field.value) + textwidget = gtk.TextView() + textwidget.set_wrap_mode(gtk.WRAP_WORD) + textwidget.get_buffer().connect('changed', + self.on_text_multi_textbuffer_changed, field) + textwidget.get_buffer().set_text(field.value) - widget = gtk.ScrolledWindow() - widget.add(textwidget) + widget = gtk.ScrolledWindow() + widget.add(textwidget) - widget.set_sensitive(readwrite) - widget=decorate_with_tooltip(widget, field) - self.attach(widget, 1, 2, linecounter, linecounter+1) + widget.set_sensitive(readwrite) + widget=decorate_with_tooltip(widget, field) + self.attach(widget, 1, 2, linecounter, linecounter+1) - else: - # field.type == 'text-single' or field.type is nonstandard: - # JEP says that if we don't understand some type, we - # should handle it as text-single - commonlabelcenter = True - if readwrite: - widget = gtk.Entry() - widget.connect('changed', self.on_text_single_entry_changed, - field) - widget.set_sensitive(readwrite) - if field.value is None: - field.value = u'' - widget.set_text(field.value) - else: - commonwidget=False - widget = gtk.Label(field.value) - widget.set_sensitive(True) - widget.set_alignment(0.0, 0.5) - widget=decorate_with_tooltip(widget, field) - self.attach(widget, 1, 2, linecounter, linecounter+1, - yoptions=gtk.FILL) + else: + # field.type == 'text-single' or field.type is nonstandard: + # JEP says that if we don't understand some type, we + # should handle it as text-single + commonlabelcenter = True + if readwrite: + widget = gtk.Entry() + widget.connect('changed', self.on_text_single_entry_changed, + field) + widget.set_sensitive(readwrite) + if field.value is None: + field.value = u'' + widget.set_text(field.value) + else: + commonwidget=False + widget = gtk.Label(field.value) + widget.set_sensitive(True) + widget.set_alignment(0.0, 0.5) + widget=decorate_with_tooltip(widget, field) + self.attach(widget, 1, 2, linecounter, linecounter+1, + yoptions=gtk.FILL) - if commonlabel and field.label is not None: - label = gtk.Label(field.label) - if commonlabelcenter: - label.set_alignment(0.0, 0.5) - else: - label.set_alignment(0.0, 0.0) - label = decorate_with_tooltip(label, field) - self.attach(label, 0, 1, linecounter, linecounter+1, - xoptions=gtk.FILL, yoptions=gtk.FILL) + if commonlabel and field.label is not None: + label = gtk.Label(field.label) + if commonlabelcenter: + label.set_alignment(0.0, 0.5) + else: + label.set_alignment(0.0, 0.0) + label = decorate_with_tooltip(label, field) + self.attach(label, 0, 1, linecounter, linecounter+1, + xoptions=gtk.FILL, yoptions=gtk.FILL) - if commonwidget: - assert widget is not None - widget.set_sensitive(readwrite) - widget = decorate_with_tooltip(widget, field) - self.attach(widget, 1, 2, linecounter, linecounter+1, - yoptions=gtk.FILL) - widget.show_all() + if commonwidget: + assert widget is not None + widget.set_sensitive(readwrite) + widget = decorate_with_tooltip(widget, field) + self.attach(widget, 1, 2, linecounter, linecounter+1, + yoptions=gtk.FILL) + widget.show_all() - linecounter+=1 - if self.get_property('visible'): - self.show_all() + linecounter+=1 + if self.get_property('visible'): + self.show_all() - def show(self): - # simulate that we are one widget - self.show_all() + def show(self): + # simulate that we are one widget + self.show_all() - def on_boolean_checkbutton_toggled(self, widget, field): - field.value = widget.get_active() + def on_boolean_checkbutton_toggled(self, widget, field): + field.value = widget.get_active() - def on_list_single_radiobutton_toggled(self, widget, field, value): - field.value = value + def on_list_single_radiobutton_toggled(self, widget, field, value): + field.value = value - def on_list_multi_checkbutton_toggled(self, widget, field, value): - # TODO: make some methods like add_value and remove_value - if widget.get_active() and value not in field.values: - field.values += [value] - elif not widget.get_active() and value in field.values: - field.values = [v for v in field.values if v!=value] + def on_list_multi_checkbutton_toggled(self, widget, field, value): + # TODO: make some methods like add_value and remove_value + if widget.get_active() and value not in field.values: + field.values += [value] + elif not widget.get_active() and value in field.values: + field.values = [v for v in field.values if v!=value] - def on_text_single_entry_changed(self, widget, field): - field.value = widget.get_text() + def on_text_single_entry_changed(self, widget, field): + field.value = widget.get_text() - def on_text_multi_textbuffer_changed(self, widget, field): - field.value = widget.get_text( - widget.get_start_iter(), - widget.get_end_iter()) + def on_text_multi_textbuffer_changed(self, widget, field): + field.value = widget.get_text( + widget.get_start_iter(), + widget.get_end_iter()) - def on_jid_multi_cellrenderertext_edited(self, cell, path, newtext, treeview, - model, field): - old = model[path][0] - if old == newtext: - return - try: - newtext = helpers.parse_jid(newtext) - except helpers.InvalidFormat, s: - dialogs.ErrorDialog(_('Invalid Jabber ID'), str(s)) - return - if newtext in field.values: - dialogs.ErrorDialog( - _('Jabber ID already in list'), - _('The Jabber ID you entered is already in the list. Choose another one.')) - gobject.idle_add(treeview.set_cursor, path) - return - model[path][0]=newtext + def on_jid_multi_cellrenderertext_edited(self, cell, path, newtext, treeview, + model, field): + old = model[path][0] + if old == newtext: + return + try: + newtext = helpers.parse_jid(newtext) + except helpers.InvalidFormat, s: + dialogs.ErrorDialog(_('Invalid Jabber ID'), str(s)) + return + if newtext in field.values: + dialogs.ErrorDialog( + _('Jabber ID already in list'), + _('The Jabber ID you entered is already in the list. Choose another one.')) + gobject.idle_add(treeview.set_cursor, path) + return + model[path][0]=newtext - values = field.values - values[values.index(old)]=newtext - field.values = values + values = field.values + values[values.index(old)]=newtext + field.values = values - def on_jid_multi_add_button_clicked(self, widget, treeview, model, field): - #Default jid - jid = _('new@jabber.id') - if jid in field.values: - i = 1 - while _('new%d@jabber.id') % i in field.values: - i += 1 - jid = _('new%d@jabber.id') % i - iter_ = model.insert(999999, (jid,)) - treeview.set_cursor(model.get_path(iter_), treeview.get_column(0), True) - field.values = field.values + [jid] + def on_jid_multi_add_button_clicked(self, widget, treeview, model, field): + #Default jid + jid = _('new@jabber.id') + if jid in field.values: + i = 1 + while _('new%d@jabber.id') % i in field.values: + i += 1 + jid = _('new%d@jabber.id') % i + iter_ = model.insert(999999, (jid,)) + treeview.set_cursor(model.get_path(iter_), treeview.get_column(0), True) + field.values = field.values + [jid] - def on_jid_multi_edit_button_clicked(self, widget, treeview): - model, iter_ = treeview.get_selection().get_selected() - assert iter_ is not None + def on_jid_multi_edit_button_clicked(self, widget, treeview): + model, iter_ = treeview.get_selection().get_selected() + assert iter_ is not None - treeview.set_cursor(model.get_path(iter_), treeview.get_column(0), True) + treeview.set_cursor(model.get_path(iter_), treeview.get_column(0), True) - def on_jid_multi_remove_button_clicked(self, widget, treeview, field): - selection = treeview.get_selection() - deleted = [] + def on_jid_multi_remove_button_clicked(self, widget, treeview, field): + selection = treeview.get_selection() + deleted = [] - def remove(model, path, iter_, deleted): - deleted+=model[iter_] - model.remove(iter_) + def remove(model, path, iter_, deleted): + deleted+=model[iter_] + model.remove(iter_) - selection.selected_foreach(remove, deleted) - field.values = (v for v in field.values if v not in deleted) + selection.selected_foreach(remove, deleted) + field.values = (v for v in field.values if v not in deleted) - def on_jid_multi_clean_button_clicked(self, widget, model, field): - model.clear() - del field.values - -# vim: se ts=3: + def on_jid_multi_clean_button_clicked(self, widget, model, field): + model.clear() + del field.values diff --git a/src/dialogs.py b/src/dialogs.py index 1f2a93fc4..a77701b71 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -43,10 +43,10 @@ from random import randrange from common import pep try: - import gtkspell - HAS_GTK_SPELL = True + import gtkspell + HAS_GTK_SPELL = True except ImportError: - HAS_GTK_SPELL = False + HAS_GTK_SPELL = False # those imports are not used in this file, but in files that 'import dialogs' # so they can do dialog.GajimThemesWindow() for example @@ -60,4857 +60,4854 @@ from common import dataforms from common.exceptions import GajimGeneralException class EditGroupsDialog: - """ - Class for the edit group dialog window - """ + """ + Class for the edit group dialog window + """ - def __init__(self, list_): - """ - list_ is a list of (contact, account) tuples - """ - self.xml = gtkgui_helpers.get_gtk_builder('edit_groups_dialog.ui') - self.dialog = self.xml.get_object('edit_groups_dialog') - self.dialog.set_transient_for(gajim.interface.roster.window) - self.list_ = list_ - self.changes_made = False - self.treeview = self.xml.get_object('groups_treeview') - if len(list_) == 1: - contact = list_[0][0] - self.xml.get_object('nickname_label').set_markup( - _('Contact name: %s') % contact.get_shown_name()) - self.xml.get_object('jid_label').set_markup( - _('Jabber ID: %s') % contact.jid) - else: - self.xml.get_object('nickname_label').set_no_show_all(True) - self.xml.get_object('nickname_label').hide() - self.xml.get_object('jid_label').set_no_show_all(True) - self.xml.get_object('jid_label').hide() + def __init__(self, list_): + """ + list_ is a list of (contact, account) tuples + """ + self.xml = gtkgui_helpers.get_gtk_builder('edit_groups_dialog.ui') + self.dialog = self.xml.get_object('edit_groups_dialog') + self.dialog.set_transient_for(gajim.interface.roster.window) + self.list_ = list_ + self.changes_made = False + self.treeview = self.xml.get_object('groups_treeview') + if len(list_) == 1: + contact = list_[0][0] + self.xml.get_object('nickname_label').set_markup( + _('Contact name: %s') % contact.get_shown_name()) + self.xml.get_object('jid_label').set_markup( + _('Jabber ID: %s') % contact.jid) + else: + self.xml.get_object('nickname_label').set_no_show_all(True) + self.xml.get_object('nickname_label').hide() + self.xml.get_object('jid_label').set_no_show_all(True) + self.xml.get_object('jid_label').hide() - self.xml.connect_signals(self) - self.init_list() + self.xml.connect_signals(self) + self.init_list() - self.dialog.show_all() - if self.changes_made: - for (contact, account) in self.list_: - gajim.connections[account].update_contact(contact.jid, contact.name, - contact.groups) + self.dialog.show_all() + if self.changes_made: + for (contact, account) in self.list_: + gajim.connections[account].update_contact(contact.jid, contact.name, + contact.groups) - def on_edit_groups_dialog_response(self, widget, response_id): - if response_id == gtk.RESPONSE_CLOSE: - self.dialog.destroy() + def on_edit_groups_dialog_response(self, widget, response_id): + if response_id == gtk.RESPONSE_CLOSE: + self.dialog.destroy() - def remove_group(self, group): - """ - Remove group group from all contacts and all their brothers - """ - for (contact, account) in self.list_: - gajim.interface.roster.remove_contact_from_groups(contact.jid, account, [group]) + def remove_group(self, group): + """ + Remove group group from all contacts and all their brothers + """ + for (contact, account) in self.list_: + gajim.interface.roster.remove_contact_from_groups(contact.jid, account, [group]) - # FIXME: Ugly workaround. - gajim.interface.roster.draw_group(_('General'), account) + # FIXME: Ugly workaround. + gajim.interface.roster.draw_group(_('General'), account) - def add_group(self, group): - """ - Add group group to all contacts and all their brothers - """ - for (contact, account) in self.list_: - gajim.interface.roster.add_contact_to_groups(contact.jid, account, [group]) + def add_group(self, group): + """ + Add group group to all contacts and all their brothers + """ + for (contact, account) in self.list_: + gajim.interface.roster.add_contact_to_groups(contact.jid, account, [group]) - # FIXME: Ugly workaround. Maybe we haven't been in any group (defaults to General) - gajim.interface.roster.draw_group(_('General'), account) + # FIXME: Ugly workaround. Maybe we haven't been in any group (defaults to General) + gajim.interface.roster.draw_group(_('General'), account) - def on_add_button_clicked(self, widget): - group = self.xml.get_object('group_entry').get_text().decode('utf-8') - if not group: - return - # Do not allow special groups - if group in helpers.special_groups: - return - # check if it already exists - model = self.treeview.get_model() - iter_ = model.get_iter_root() - while iter_: - if model.get_value(iter_, 0).decode('utf-8') == group: - return - iter_ = model.iter_next(iter_) - self.changes_made = True - model.append((group, True, False)) - self.add_group(group) - self.init_list() # Re-draw list to sort new item + def on_add_button_clicked(self, widget): + group = self.xml.get_object('group_entry').get_text().decode('utf-8') + if not group: + return + # Do not allow special groups + if group in helpers.special_groups: + return + # check if it already exists + model = self.treeview.get_model() + iter_ = model.get_iter_root() + while iter_: + if model.get_value(iter_, 0).decode('utf-8') == group: + return + iter_ = model.iter_next(iter_) + self.changes_made = True + model.append((group, True, False)) + self.add_group(group) + self.init_list() # Re-draw list to sort new item - def group_toggled_cb(self, cell, path): - self.changes_made = True - model = self.treeview.get_model() - if model[path][2]: - model[path][2] = False - model[path][1] = True - else: - model[path][1] = not model[path][1] - group = model[path][0].decode('utf-8') - if model[path][1]: - self.add_group(group) - else: - self.remove_group(group) + def group_toggled_cb(self, cell, path): + self.changes_made = True + model = self.treeview.get_model() + if model[path][2]: + model[path][2] = False + model[path][1] = True + else: + model[path][1] = not model[path][1] + group = model[path][0].decode('utf-8') + if model[path][1]: + self.add_group(group) + else: + self.remove_group(group) - def init_list(self): - store = gtk.ListStore(str, bool, bool) - self.treeview.set_model(store) - for column in self.treeview.get_columns(): - # Clear treeview when re-drawing - self.treeview.remove_column(column) - accounts = [] - # Store groups in a list so we can sort them and the number of contacts in - # it - groups = {} - for (contact, account) in self.list_: - if account not in accounts: - accounts.append(account) - for g in gajim.groups[account].keys(): - if g in groups: - continue - groups[g] = 0 - c_groups = contact.groups - for g in c_groups: - groups[g] += 1 - group_list = [] - # Remove special groups if they are empty - for group in groups: - if group not in helpers.special_groups or groups[group] > 0: - group_list.append(group) - group_list.sort() - for group in group_list: - iter_ = store.append() - store.set(iter_, 0, group) # Group name - if groups[group] == 0: - store.set(iter_, 1, False) - else: - store.set(iter_, 1, True) - if groups[group] == len(self.list_): - # all contacts are in this group - store.set(iter_, 2, False) - else: - store.set(iter_, 2, True) - column = gtk.TreeViewColumn(_('Group')) - column.set_expand(True) - self.treeview.append_column(column) - renderer = gtk.CellRendererText() - column.pack_start(renderer) - column.set_attributes(renderer, text=0) + def init_list(self): + store = gtk.ListStore(str, bool, bool) + self.treeview.set_model(store) + for column in self.treeview.get_columns(): + # Clear treeview when re-drawing + self.treeview.remove_column(column) + accounts = [] + # Store groups in a list so we can sort them and the number of contacts in + # it + groups = {} + for (contact, account) in self.list_: + if account not in accounts: + accounts.append(account) + for g in gajim.groups[account].keys(): + if g in groups: + continue + groups[g] = 0 + c_groups = contact.groups + for g in c_groups: + groups[g] += 1 + group_list = [] + # Remove special groups if they are empty + for group in groups: + if group not in helpers.special_groups or groups[group] > 0: + group_list.append(group) + group_list.sort() + for group in group_list: + iter_ = store.append() + store.set(iter_, 0, group) # Group name + if groups[group] == 0: + store.set(iter_, 1, False) + else: + store.set(iter_, 1, True) + if groups[group] == len(self.list_): + # all contacts are in this group + store.set(iter_, 2, False) + else: + store.set(iter_, 2, True) + column = gtk.TreeViewColumn(_('Group')) + column.set_expand(True) + self.treeview.append_column(column) + renderer = gtk.CellRendererText() + column.pack_start(renderer) + column.set_attributes(renderer, text=0) - column = gtk.TreeViewColumn(_('In the group')) - column.set_expand(False) - self.treeview.append_column(column) - renderer = gtk.CellRendererToggle() - column.pack_start(renderer) - renderer.set_property('activatable', True) - renderer.connect('toggled', self.group_toggled_cb) - column.set_attributes(renderer, active=1, inconsistent=2) + column = gtk.TreeViewColumn(_('In the group')) + column.set_expand(False) + self.treeview.append_column(column) + renderer = gtk.CellRendererToggle() + column.pack_start(renderer) + renderer.set_property('activatable', True) + renderer.connect('toggled', self.group_toggled_cb) + column.set_attributes(renderer, active=1, inconsistent=2) class PassphraseDialog: - """ - Class for Passphrase dialog - """ - def __init__(self, titletext, labeltext, checkbuttontext=None, - ok_handler=None, cancel_handler=None): - self.xml = gtkgui_helpers.get_gtk_builder('passphrase_dialog.ui') - self.window = self.xml.get_object('passphrase_dialog') - self.passphrase_entry = self.xml.get_object('passphrase_entry') - self.passphrase = -1 - self.window.set_title(titletext) - self.xml.get_object('message_label').set_text(labeltext) + """ + Class for Passphrase dialog + """ + def __init__(self, titletext, labeltext, checkbuttontext=None, + ok_handler=None, cancel_handler=None): + self.xml = gtkgui_helpers.get_gtk_builder('passphrase_dialog.ui') + self.window = self.xml.get_object('passphrase_dialog') + self.passphrase_entry = self.xml.get_object('passphrase_entry') + self.passphrase = -1 + self.window.set_title(titletext) + self.xml.get_object('message_label').set_text(labeltext) - self.ok = False + self.ok = False - self.cancel_handler = cancel_handler - self.ok_handler = ok_handler - okbutton = self.xml.get_object('ok_button') - okbutton.connect('clicked', self.on_okbutton_clicked) - cancelbutton = self.xml.get_object('cancel_button') - cancelbutton.connect('clicked', self.on_cancelbutton_clicked) + self.cancel_handler = cancel_handler + self.ok_handler = ok_handler + okbutton = self.xml.get_object('ok_button') + okbutton.connect('clicked', self.on_okbutton_clicked) + cancelbutton = self.xml.get_object('cancel_button') + cancelbutton.connect('clicked', self.on_cancelbutton_clicked) - self.xml.connect_signals(self) - self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - self.window.show_all() + self.xml.connect_signals(self) + self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + self.window.show_all() - self.check = bool(checkbuttontext) - checkbutton = self.xml.get_object('save_passphrase_checkbutton') - if self.check: - checkbutton.set_label(checkbuttontext) - else: - checkbutton.hide() + self.check = bool(checkbuttontext) + checkbutton = self.xml.get_object('save_passphrase_checkbutton') + if self.check: + checkbutton.set_label(checkbuttontext) + else: + checkbutton.hide() - def on_okbutton_clicked(self, widget): - if not self.ok_handler: - return + def on_okbutton_clicked(self, widget): + if not self.ok_handler: + return - passph = self.passphrase_entry.get_text().decode('utf-8') + passph = self.passphrase_entry.get_text().decode('utf-8') - if self.check: - checked = self.xml.get_object('save_passphrase_checkbutton').\ - get_active() - else: - checked = False + if self.check: + checked = self.xml.get_object('save_passphrase_checkbutton').\ + get_active() + else: + checked = False - self.ok = True + self.ok = True - self.window.destroy() + self.window.destroy() - if isinstance(self.ok_handler, tuple): - self.ok_handler[0](passph, checked, *self.ok_handler[1:]) - else: - self.ok_handler(passph, checked) + if isinstance(self.ok_handler, tuple): + self.ok_handler[0](passph, checked, *self.ok_handler[1:]) + else: + self.ok_handler(passph, checked) - def on_cancelbutton_clicked(self, widget): - self.window.destroy() + def on_cancelbutton_clicked(self, widget): + self.window.destroy() - def on_passphrase_dialog_destroy(self, widget): - if self.cancel_handler and not self.ok: - self.cancel_handler() + def on_passphrase_dialog_destroy(self, widget): + if self.cancel_handler and not self.ok: + self.cancel_handler() class ChooseGPGKeyDialog: - """ - Class for GPG key dialog - """ + """ + Class for GPG key dialog + """ - def __init__(self, title_text, prompt_text, secret_keys, on_response, - selected=None): - '''secret_keys : {keyID: userName, ...}''' - self.on_response = on_response - xml = gtkgui_helpers.get_gtk_builder('choose_gpg_key_dialog.ui') - self.window = xml.get_object('choose_gpg_key_dialog') - self.window.set_title(title_text) - self.keys_treeview = xml.get_object('keys_treeview') - prompt_label = xml.get_object('prompt_label') - prompt_label.set_text(prompt_text) - model = gtk.ListStore(str, str) - model.set_sort_func(1, self.sort_keys) - model.set_sort_column_id(1, gtk.SORT_ASCENDING) - self.keys_treeview.set_model(model) - #columns - renderer = gtk.CellRendererText() - col = self.keys_treeview.insert_column_with_attributes(-1, _('KeyID'), - renderer, text=0) - col.set_sort_column_id(0) - renderer = gtk.CellRendererText() - col = self.keys_treeview.insert_column_with_attributes(-1, - _('Contact name'), renderer, text=1) - col.set_sort_column_id(1) - self.keys_treeview.set_search_column(1) - self.fill_tree(secret_keys, selected) - self.window.connect('response', self.on_dialog_response) - self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - self.window.show_all() + def __init__(self, title_text, prompt_text, secret_keys, on_response, + selected=None): + '''secret_keys : {keyID: userName, ...}''' + self.on_response = on_response + xml = gtkgui_helpers.get_gtk_builder('choose_gpg_key_dialog.ui') + self.window = xml.get_object('choose_gpg_key_dialog') + self.window.set_title(title_text) + self.keys_treeview = xml.get_object('keys_treeview') + prompt_label = xml.get_object('prompt_label') + prompt_label.set_text(prompt_text) + model = gtk.ListStore(str, str) + model.set_sort_func(1, self.sort_keys) + model.set_sort_column_id(1, gtk.SORT_ASCENDING) + self.keys_treeview.set_model(model) + #columns + renderer = gtk.CellRendererText() + col = self.keys_treeview.insert_column_with_attributes(-1, _('KeyID'), + renderer, text=0) + col.set_sort_column_id(0) + renderer = gtk.CellRendererText() + col = self.keys_treeview.insert_column_with_attributes(-1, + _('Contact name'), renderer, text=1) + col.set_sort_column_id(1) + self.keys_treeview.set_search_column(1) + self.fill_tree(secret_keys, selected) + self.window.connect('response', self.on_dialog_response) + self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + self.window.show_all() - def sort_keys(self, model, iter1, iter2): - value1 = model[iter1][1] - value2 = model[iter2][1] - if value1 == _('None'): - return -1 - elif value2 == _('None'): - return 1 - elif value1 < value2: - return -1 - return 1 + def sort_keys(self, model, iter1, iter2): + value1 = model[iter1][1] + value2 = model[iter2][1] + if value1 == _('None'): + return -1 + elif value2 == _('None'): + return 1 + elif value1 < value2: + return -1 + return 1 - def on_dialog_response(self, dialog, response): - selection = self.keys_treeview.get_selection() - (model, iter_) = selection.get_selected() - if iter_ and response == gtk.RESPONSE_OK: - keyID = [ model[iter_][0].decode('utf-8'), - model[iter_][1].decode('utf-8') ] - else: - keyID = None - self.on_response(keyID) - self.window.destroy() + def on_dialog_response(self, dialog, response): + selection = self.keys_treeview.get_selection() + (model, iter_) = selection.get_selected() + if iter_ and response == gtk.RESPONSE_OK: + keyID = [ model[iter_][0].decode('utf-8'), + model[iter_][1].decode('utf-8') ] + else: + keyID = None + self.on_response(keyID) + self.window.destroy() - def fill_tree(self, list_, selected): - model = self.keys_treeview.get_model() - for keyID in list_.keys(): - iter_ = model.append((keyID, list_[keyID])) - if keyID == selected: - path = model.get_path(iter_) - self.keys_treeview.set_cursor(path) + def fill_tree(self, list_, selected): + model = self.keys_treeview.get_model() + for keyID in list_.keys(): + iter_ = model.append((keyID, list_[keyID])) + if keyID == selected: + path = model.get_path(iter_) + self.keys_treeview.set_cursor(path) class ChangeActivityDialog: - PAGELIST = ['doing_chores', 'drinking', 'eating', 'exercising', 'grooming', - 'having_appointment', 'inactive', 'relaxing', 'talking', 'traveling', - 'working'] + PAGELIST = ['doing_chores', 'drinking', 'eating', 'exercising', 'grooming', + 'having_appointment', 'inactive', 'relaxing', 'talking', 'traveling', + 'working'] - def __init__(self, on_response, activity=None, subactivity=None, text=''): - self.on_response = on_response - self.activity = activity - self.subactivity = subactivity - self.text = text - self.xml = gtkgui_helpers.get_gtk_builder( - 'change_activity_dialog.ui') - self.window = self.xml.get_object('change_activity_dialog') - self.window.set_transient_for(gajim.interface.roster.window) + def __init__(self, on_response, activity=None, subactivity=None, text=''): + self.on_response = on_response + self.activity = activity + self.subactivity = subactivity + self.text = text + self.xml = gtkgui_helpers.get_gtk_builder( + 'change_activity_dialog.ui') + self.window = self.xml.get_object('change_activity_dialog') + self.window.set_transient_for(gajim.interface.roster.window) - self.checkbutton = self.xml.get_object('enable_checkbutton') - self.notebook = self.xml.get_object('notebook') - self.entry = self.xml.get_object('description_entry') + self.checkbutton = self.xml.get_object('enable_checkbutton') + self.notebook = self.xml.get_object('notebook') + self.entry = self.xml.get_object('description_entry') - rbtns = {} - group = None + rbtns = {} + group = None - for category in pep.ACTIVITIES: - item = self.xml.get_object(category + '_image') - item.set_from_pixbuf( - gtkgui_helpers.load_activity_icon(category).get_pixbuf()) - item.set_tooltip_text(pep.ACTIVITIES[category]['category']) + for category in pep.ACTIVITIES: + item = self.xml.get_object(category + '_image') + item.set_from_pixbuf( + gtkgui_helpers.load_activity_icon(category).get_pixbuf()) + item.set_tooltip_text(pep.ACTIVITIES[category]['category']) - vbox = self.xml.get_object(category + '_vbox') - vbox.set_border_width(5) + vbox = self.xml.get_object(category + '_vbox') + vbox.set_border_width(5) - # Other - act = category + '_other' + # Other + act = category + '_other' - if group: - rbtns[act] = gtk.RadioButton(group) - else: - rbtns[act] = group = gtk.RadioButton() + if group: + rbtns[act] = gtk.RadioButton(group) + else: + rbtns[act] = group = gtk.RadioButton() - hbox = gtk.HBox(False, 5) - hbox.pack_start(gtkgui_helpers.load_activity_icon(category), False, - False, 0) - lbl = gtk.Label('' + pep.ACTIVITIES[category]['category'] + '') - lbl.set_use_markup(True) - hbox.pack_start(lbl, False, False, 0) - rbtns[act].add(hbox) - rbtns[act].connect('toggled', self.on_rbtn_toggled, - [category, 'other']) - vbox.pack_start(rbtns[act], False, False, 0) + hbox = gtk.HBox(False, 5) + hbox.pack_start(gtkgui_helpers.load_activity_icon(category), False, + False, 0) + lbl = gtk.Label('' + pep.ACTIVITIES[category]['category'] + '') + lbl.set_use_markup(True) + hbox.pack_start(lbl, False, False, 0) + rbtns[act].add(hbox) + rbtns[act].connect('toggled', self.on_rbtn_toggled, + [category, 'other']) + vbox.pack_start(rbtns[act], False, False, 0) - activities = [] - for activity in pep.ACTIVITIES[category]: - activities.append(activity) - activities.sort() + activities = [] + for activity in pep.ACTIVITIES[category]: + activities.append(activity) + activities.sort() - for activity in activities: - if activity == 'category': - continue + for activity in activities: + if activity == 'category': + continue - act = category + '_' + activity + act = category + '_' + activity - if group: - rbtns[act] = gtk.RadioButton(group) - else: - rbtns[act] = group = gtk.RadioButton() + if group: + rbtns[act] = gtk.RadioButton(group) + else: + rbtns[act] = group = gtk.RadioButton() - hbox = gtk.HBox(False, 5) - hbox.pack_start(gtkgui_helpers.load_activity_icon(category, - activity), False, False, 0) - hbox.pack_start(gtk.Label(pep.ACTIVITIES[category][activity]), - False, False, 0) - rbtns[act].connect('toggled', self.on_rbtn_toggled, - [category, activity]) - rbtns[act].add(hbox) - vbox.pack_start(rbtns[act], False, False, 0) + hbox = gtk.HBox(False, 5) + hbox.pack_start(gtkgui_helpers.load_activity_icon(category, + activity), False, False, 0) + hbox.pack_start(gtk.Label(pep.ACTIVITIES[category][activity]), + False, False, 0) + rbtns[act].connect('toggled', self.on_rbtn_toggled, + [category, activity]) + rbtns[act].add(hbox) + vbox.pack_start(rbtns[act], False, False, 0) - if self.activity in pep.ACTIVITIES: - if not self.subactivity in pep.ACTIVITIES[self.activity]: - self.subactivity = 'other' + if self.activity in pep.ACTIVITIES: + if not self.subactivity in pep.ACTIVITIES[self.activity]: + self.subactivity = 'other' - rbtns[self.activity + '_' + self.subactivity].set_active(True) + rbtns[self.activity + '_' + self.subactivity].set_active(True) - self.checkbutton.set_active(True) - self.notebook.set_sensitive(True) - self.entry.set_sensitive(True) + self.checkbutton.set_active(True) + self.notebook.set_sensitive(True) + self.entry.set_sensitive(True) - self.notebook.set_current_page( - self.PAGELIST.index(self.activity)) + self.notebook.set_current_page( + self.PAGELIST.index(self.activity)) - self.entry.set_text(text) + self.entry.set_text(text) - else: - self.checkbutton.set_active(False) + else: + self.checkbutton.set_active(False) - self.xml.connect_signals(self) - self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - self.window.show_all() + self.xml.connect_signals(self) + self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + self.window.show_all() - def on_enable_checkbutton_toggled(self, widget): - self.notebook.set_sensitive(widget.get_active()) - self.entry.set_sensitive(widget.get_active()) + def on_enable_checkbutton_toggled(self, widget): + self.notebook.set_sensitive(widget.get_active()) + self.entry.set_sensitive(widget.get_active()) - def on_rbtn_toggled(self, widget, data): - if widget.get_active(): - self.activity = data[0] - self.subactivity = data[1] + def on_rbtn_toggled(self, widget, data): + if widget.get_active(): + self.activity = data[0] + self.subactivity = data[1] - def on_ok_button_clicked(self, widget): - """ - Return activity and messsage (None if no activity selected) - """ - if self.checkbutton.get_active(): - self.on_response(self.activity, self.subactivity, - self.entry.get_text().decode('utf-8')) - else: - self.on_response(None, None, '') - self.window.destroy() + def on_ok_button_clicked(self, widget): + """ + Return activity and messsage (None if no activity selected) + """ + if self.checkbutton.get_active(): + self.on_response(self.activity, self.subactivity, + self.entry.get_text().decode('utf-8')) + else: + self.on_response(None, None, '') + self.window.destroy() - def on_cancel_button_clicked(self, widget): - self.window.destroy() + def on_cancel_button_clicked(self, widget): + self.window.destroy() class ChangeMoodDialog: - COLS = 11 + COLS = 11 - def __init__(self, on_response, mood=None, text=''): - self.on_response = on_response - self.mood = mood - self.text = text - self.xml = gtkgui_helpers.get_gtk_builder('change_mood_dialog.ui') + def __init__(self, on_response, mood=None, text=''): + self.on_response = on_response + self.mood = mood + self.text = text + self.xml = gtkgui_helpers.get_gtk_builder('change_mood_dialog.ui') - self.window = self.xml.get_object('change_mood_dialog') - self.window.set_transient_for(gajim.interface.roster.window) - self.window.set_title(_('Set Mood')) + self.window = self.xml.get_object('change_mood_dialog') + self.window.set_transient_for(gajim.interface.roster.window) + self.window.set_title(_('Set Mood')) - table = self.xml.get_object('mood_icons_table') - self.label = self.xml.get_object('mood_label') - self.entry = self.xml.get_object('description_entry') + table = self.xml.get_object('mood_icons_table') + self.label = self.xml.get_object('mood_label') + self.entry = self.xml.get_object('description_entry') - no_mood_button = self.xml.get_object('no_mood_button') - no_mood_button.set_mode(False) - no_mood_button.connect('clicked', - self.on_mood_button_clicked, None) + no_mood_button = self.xml.get_object('no_mood_button') + no_mood_button.set_mode(False) + no_mood_button.connect('clicked', + self.on_mood_button_clicked, None) - x = 1 - y = 0 - self.mood_buttons = {} + x = 1 + y = 0 + self.mood_buttons = {} - # Order them first - self.MOODS = [] - for mood in pep.MOODS: - self.MOODS.append(mood) - self.MOODS.sort() + # Order them first + self.MOODS = [] + for mood in pep.MOODS: + self.MOODS.append(mood) + self.MOODS.sort() - for mood in self.MOODS: - self.mood_buttons[mood] = gtk.RadioButton(no_mood_button) - self.mood_buttons[mood].set_mode(False) - self.mood_buttons[mood].add(gtkgui_helpers.load_mood_icon(mood)) - self.mood_buttons[mood].set_relief(gtk.RELIEF_NONE) - self.mood_buttons[mood].set_tooltip_text(pep.MOODS[mood]) - self.mood_buttons[mood].connect('clicked', - self.on_mood_button_clicked, mood) - table.attach(self.mood_buttons[mood], x, x + 1, y, y + 1) + for mood in self.MOODS: + self.mood_buttons[mood] = gtk.RadioButton(no_mood_button) + self.mood_buttons[mood].set_mode(False) + self.mood_buttons[mood].add(gtkgui_helpers.load_mood_icon(mood)) + self.mood_buttons[mood].set_relief(gtk.RELIEF_NONE) + self.mood_buttons[mood].set_tooltip_text(pep.MOODS[mood]) + self.mood_buttons[mood].connect('clicked', + self.on_mood_button_clicked, mood) + table.attach(self.mood_buttons[mood], x, x + 1, y, y + 1) - # Calculate the next position - x += 1 - if x >= self.COLS: - x = 0 - y += 1 + # Calculate the next position + x += 1 + if x >= self.COLS: + x = 0 + y += 1 - if self.mood in pep.MOODS: - self.mood_buttons[self.mood].set_active(True) - self.label.set_text(pep.MOODS[self.mood]) - self.entry.set_sensitive(True) - if self.text: - self.entry.set_text(self.text) - else: - self.label.set_text(_('None')) - self.entry.set_text('') - self.entry.set_sensitive(False) + if self.mood in pep.MOODS: + self.mood_buttons[self.mood].set_active(True) + self.label.set_text(pep.MOODS[self.mood]) + self.entry.set_sensitive(True) + if self.text: + self.entry.set_text(self.text) + else: + self.label.set_text(_('None')) + self.entry.set_text('') + self.entry.set_sensitive(False) - self.xml.connect_signals(self) - self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - self.window.show_all() + self.xml.connect_signals(self) + self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + self.window.show_all() - def on_mood_button_clicked(self, widget, data): - if data: - self.label.set_text(pep.MOODS[data]) - self.entry.set_sensitive(True) - else: - self.label.set_text(_('None')) - self.entry.set_text('') - self.entry.set_sensitive(False) - self.mood = data + def on_mood_button_clicked(self, widget, data): + if data: + self.label.set_text(pep.MOODS[data]) + self.entry.set_sensitive(True) + else: + self.label.set_text(_('None')) + self.entry.set_text('') + self.entry.set_sensitive(False) + self.mood = data - def on_ok_button_clicked(self, widget): - '''Return mood and messsage (None if no mood selected)''' - message = self.entry.get_text().decode('utf-8') - self.on_response(self.mood, message) - self.window.destroy() + def on_ok_button_clicked(self, widget): + '''Return mood and messsage (None if no mood selected)''' + message = self.entry.get_text().decode('utf-8') + self.on_response(self.mood, message) + self.window.destroy() - def on_cancel_button_clicked(self, widget): - self.window.destroy() + def on_cancel_button_clicked(self, widget): + self.window.destroy() class TimeoutDialog: - """ - Class designed to be derivated to create timeout'd dialogs (dialogs that - closes automatically after a timeout) - """ - def __init__(self, timeout, on_timeout): - self.countdown_left = timeout - self.countdown_enabled = True - self.title_text = '' - self.on_timeout = on_timeout + """ + Class designed to be derivated to create timeout'd dialogs (dialogs that + closes automatically after a timeout) + """ + def __init__(self, timeout, on_timeout): + self.countdown_left = timeout + self.countdown_enabled = True + self.title_text = '' + self.on_timeout = on_timeout - def run_timeout(self): - if self.countdown_left > 0: - self.countdown() - gobject.timeout_add_seconds(1, self.countdown) + def run_timeout(self): + if self.countdown_left > 0: + self.countdown() + gobject.timeout_add_seconds(1, self.countdown) - def on_timeout(): - """ - To be implemented in derivated classes - """ - pass + def on_timeout(): + """ + To be implemented in derivated classes + """ + pass - def countdown(self): - if self.countdown_enabled: - if self.countdown_left <= 0: - self.on_timeout() - return False - self.dialog.set_title('%s [%s]' % (self.title_text, - str(self.countdown_left))) - self.countdown_left -= 1 - return True - else: - self.dialog.set_title(self.title_text) - return False + def countdown(self): + if self.countdown_enabled: + if self.countdown_left <= 0: + self.on_timeout() + return False + self.dialog.set_title('%s [%s]' % (self.title_text, + str(self.countdown_left))) + self.countdown_left -= 1 + return True + else: + self.dialog.set_title(self.title_text) + return False class ChangeStatusMessageDialog(TimeoutDialog): - def __init__(self, on_response, show=None, show_pep=True): - countdown_time = gajim.config.get('change_status_window_timeout') - TimeoutDialog.__init__(self, countdown_time, self.on_timeout) - self.show = show - self.pep_dict = {} - self.show_pep = show_pep - self.on_response = on_response - self.xml = gtkgui_helpers.get_gtk_builder('change_status_message_dialog.ui') - self.dialog = self.xml.get_object('change_status_message_dialog') - self.dialog.set_transient_for(gajim.interface.roster.window) - msg = None - if show: - uf_show = helpers.get_uf_show(show) - self.title_text = _('%s Status Message') % uf_show - msg = gajim.config.get_per('statusmsg', '_last_' + self.show, - 'message') - self.pep_dict['activity'] = gajim.config.get_per('statusmsg', - '_last_' + self.show, 'activity') - self.pep_dict['subactivity'] = gajim.config.get_per('statusmsg', - '_last_' + self.show, 'subactivity') - self.pep_dict['activity_text'] = gajim.config.get_per('statusmsg', - '_last_' + self.show, 'activity_text') - self.pep_dict['mood'] = gajim.config.get_per('statusmsg', - '_last_' + self.show, 'mood') - self.pep_dict['mood_text'] = gajim.config.get_per('statusmsg', - '_last_' + self.show, 'mood_text') - else: - self.title_text = _('Status Message') - self.dialog.set_title(self.title_text) + def __init__(self, on_response, show=None, show_pep=True): + countdown_time = gajim.config.get('change_status_window_timeout') + TimeoutDialog.__init__(self, countdown_time, self.on_timeout) + self.show = show + self.pep_dict = {} + self.show_pep = show_pep + self.on_response = on_response + self.xml = gtkgui_helpers.get_gtk_builder('change_status_message_dialog.ui') + self.dialog = self.xml.get_object('change_status_message_dialog') + self.dialog.set_transient_for(gajim.interface.roster.window) + msg = None + if show: + uf_show = helpers.get_uf_show(show) + self.title_text = _('%s Status Message') % uf_show + msg = gajim.config.get_per('statusmsg', '_last_' + self.show, + 'message') + self.pep_dict['activity'] = gajim.config.get_per('statusmsg', + '_last_' + self.show, 'activity') + self.pep_dict['subactivity'] = gajim.config.get_per('statusmsg', + '_last_' + self.show, 'subactivity') + self.pep_dict['activity_text'] = gajim.config.get_per('statusmsg', + '_last_' + self.show, 'activity_text') + self.pep_dict['mood'] = gajim.config.get_per('statusmsg', + '_last_' + self.show, 'mood') + self.pep_dict['mood_text'] = gajim.config.get_per('statusmsg', + '_last_' + self.show, 'mood_text') + else: + self.title_text = _('Status Message') + self.dialog.set_title(self.title_text) - message_textview = self.xml.get_object('message_textview') - self.message_buffer = message_textview.get_buffer() - self.message_buffer.connect('changed', self.on_message_buffer_changed) - if not msg: - msg = '' - msg = helpers.from_one_line(msg) - self.message_buffer.set_text(msg) + message_textview = self.xml.get_object('message_textview') + self.message_buffer = message_textview.get_buffer() + self.message_buffer.connect('changed', self.on_message_buffer_changed) + if not msg: + msg = '' + msg = helpers.from_one_line(msg) + self.message_buffer.set_text(msg) - # have an empty string selectable, so user can clear msg - self.preset_messages_dict = {'': ['', '', '', '', '', '']} - for msg_name in gajim.config.get_per('statusmsg'): - if msg_name.startswith('_last_'): - continue - opts = [] - for opt in ['message', 'activity', 'subactivity', 'activity_text', - 'mood', 'mood_text']: - opts.append(gajim.config.get_per('statusmsg', msg_name, opt)) - opts[0] = helpers.from_one_line(opts[0]) - self.preset_messages_dict[msg_name] = opts - sorted_keys_list = helpers.get_sorted_keys(self.preset_messages_dict) + # have an empty string selectable, so user can clear msg + self.preset_messages_dict = {'': ['', '', '', '', '', '']} + for msg_name in gajim.config.get_per('statusmsg'): + if msg_name.startswith('_last_'): + continue + opts = [] + for opt in ['message', 'activity', 'subactivity', 'activity_text', + 'mood', 'mood_text']: + opts.append(gajim.config.get_per('statusmsg', msg_name, opt)) + opts[0] = helpers.from_one_line(opts[0]) + self.preset_messages_dict[msg_name] = opts + sorted_keys_list = helpers.get_sorted_keys(self.preset_messages_dict) - self.message_liststore = gtk.ListStore(str) # msg_name - self.message_combobox = self.xml.get_object('message_combobox') - self.message_combobox.set_model(self.message_liststore) - cellrenderertext = gtk.CellRendererText() - self.message_combobox.pack_start(cellrenderertext, True) - self.message_combobox.add_attribute(cellrenderertext, 'text', 0) - for msg_name in sorted_keys_list: - self.message_liststore.append((msg_name,)) + self.message_liststore = gtk.ListStore(str) # msg_name + self.message_combobox = self.xml.get_object('message_combobox') + self.message_combobox.set_model(self.message_liststore) + cellrenderertext = gtk.CellRendererText() + self.message_combobox.pack_start(cellrenderertext, True) + self.message_combobox.add_attribute(cellrenderertext, 'text', 0) + for msg_name in sorted_keys_list: + self.message_liststore.append((msg_name,)) - if show_pep: - self.draw_activity() - self.draw_mood() - else: - # remove acvtivity / mood lines - self.xml.get_object('activity_label').set_no_show_all(True) - self.xml.get_object('activity_button').set_no_show_all(True) - self.xml.get_object('mood_label').set_no_show_all(True) - self.xml.get_object('mood_button').set_no_show_all(True) - self.xml.get_object('activity_label').hide() - self.xml.get_object('activity_button').hide() - self.xml.get_object('mood_label').hide() - self.xml.get_object('mood_button').hide() + if show_pep: + self.draw_activity() + self.draw_mood() + else: + # remove acvtivity / mood lines + self.xml.get_object('activity_label').set_no_show_all(True) + self.xml.get_object('activity_button').set_no_show_all(True) + self.xml.get_object('mood_label').set_no_show_all(True) + self.xml.get_object('mood_button').set_no_show_all(True) + self.xml.get_object('activity_label').hide() + self.xml.get_object('activity_button').hide() + self.xml.get_object('mood_label').hide() + self.xml.get_object('mood_button').hide() - self.xml.connect_signals(self) - self.run_timeout() - self.dialog.connect('response', self.on_dialog_response) - self.dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - self.dialog.show_all() + self.xml.connect_signals(self) + self.run_timeout() + self.dialog.connect('response', self.on_dialog_response) + self.dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + self.dialog.show_all() - def draw_activity(self): - """ - Set activity button - """ - img = self.xml.get_object('activity_image') - label = self.xml.get_object('activity_button_label') - if 'activity' in self.pep_dict and self.pep_dict['activity'] in \ - pep.ACTIVITIES: - if 'subactivity' in self.pep_dict and self.pep_dict['subactivity'] in \ - pep.ACTIVITIES[self.pep_dict['activity']]: - img.set_from_pixbuf(gtkgui_helpers.load_activity_icon( - self.pep_dict['activity'], self.pep_dict['subactivity']).\ - get_pixbuf()) - else: - img.set_from_pixbuf(gtkgui_helpers.load_activity_icon( - self.pep_dict['activity']).get_pixbuf()) -# item.set_tooltip_text(pep.ACTIVITIES[category]['category']) - if self.pep_dict['activity_text']: - label.set_text(self.pep_dict['activity_text']) - else: - label.set_text('') - else: - img.set_from_pixbuf(None) - label.set_text('') + def draw_activity(self): + """ + Set activity button + """ + img = self.xml.get_object('activity_image') + label = self.xml.get_object('activity_button_label') + if 'activity' in self.pep_dict and self.pep_dict['activity'] in \ + pep.ACTIVITIES: + if 'subactivity' in self.pep_dict and self.pep_dict['subactivity'] in \ + pep.ACTIVITIES[self.pep_dict['activity']]: + img.set_from_pixbuf(gtkgui_helpers.load_activity_icon( + self.pep_dict['activity'], self.pep_dict['subactivity']).\ + get_pixbuf()) + else: + img.set_from_pixbuf(gtkgui_helpers.load_activity_icon( + self.pep_dict['activity']).get_pixbuf()) +# item.set_tooltip_text(pep.ACTIVITIES[category]['category']) + if self.pep_dict['activity_text']: + label.set_text(self.pep_dict['activity_text']) + else: + label.set_text('') + else: + img.set_from_pixbuf(None) + label.set_text('') - def draw_mood(self): - """ - Set mood button - """ - img = self.xml.get_object('mood_image') - label = self.xml.get_object('mood_button_label') - if self.pep_dict['mood'] in pep.MOODS: - img.set_from_pixbuf(gtkgui_helpers.load_mood_icon( - self.pep_dict['mood']).get_pixbuf()) - if self.pep_dict['mood_text']: - label.set_text(self.pep_dict['mood_text']) - else: - label.set_text('') - else: - img.set_from_pixbuf(None) - label.set_text('') + def draw_mood(self): + """ + Set mood button + """ + img = self.xml.get_object('mood_image') + label = self.xml.get_object('mood_button_label') + if self.pep_dict['mood'] in pep.MOODS: + img.set_from_pixbuf(gtkgui_helpers.load_mood_icon( + self.pep_dict['mood']).get_pixbuf()) + if self.pep_dict['mood_text']: + label.set_text(self.pep_dict['mood_text']) + else: + label.set_text('') + else: + img.set_from_pixbuf(None) + label.set_text('') - def on_timeout(self): - # Prevent GUI freeze when the combobox menu is opened on close - self.message_combobox.popdown() - self.dialog.response(gtk.RESPONSE_OK) + def on_timeout(self): + # Prevent GUI freeze when the combobox menu is opened on close + self.message_combobox.popdown() + self.dialog.response(gtk.RESPONSE_OK) - def on_dialog_response(self, dialog, response): - if response == gtk.RESPONSE_OK: - beg, end = self.message_buffer.get_bounds() - message = self.message_buffer.get_text(beg, end).decode('utf-8')\ - .strip() - message = helpers.remove_invalid_xml_chars(message) - msg = helpers.to_one_line(message) - if self.show: - gajim.config.set_per('statusmsg', '_last_' + self.show, 'message', - msg) - if self.show_pep: - gajim.config.set_per('statusmsg', '_last_' + self.show, - 'activity', self.pep_dict['activity']) - gajim.config.set_per('statusmsg', '_last_' + self.show, - 'subactivity', self.pep_dict['subactivity']) - gajim.config.set_per('statusmsg', '_last_' + self.show, - 'activity_text', self.pep_dict['activity_text']) - gajim.config.set_per('statusmsg', '_last_' + self.show, 'mood', - self.pep_dict['mood']) - gajim.config.set_per('statusmsg', '_last_' + self.show, - 'mood_text', self.pep_dict['mood_text']) - else: - message = None # user pressed Cancel button or X wm button - self.dialog.destroy() - self.on_response(message, self.pep_dict) + def on_dialog_response(self, dialog, response): + if response == gtk.RESPONSE_OK: + beg, end = self.message_buffer.get_bounds() + message = self.message_buffer.get_text(beg, end).decode('utf-8')\ + .strip() + message = helpers.remove_invalid_xml_chars(message) + msg = helpers.to_one_line(message) + if self.show: + gajim.config.set_per('statusmsg', '_last_' + self.show, 'message', + msg) + if self.show_pep: + gajim.config.set_per('statusmsg', '_last_' + self.show, + 'activity', self.pep_dict['activity']) + gajim.config.set_per('statusmsg', '_last_' + self.show, + 'subactivity', self.pep_dict['subactivity']) + gajim.config.set_per('statusmsg', '_last_' + self.show, + 'activity_text', self.pep_dict['activity_text']) + gajim.config.set_per('statusmsg', '_last_' + self.show, 'mood', + self.pep_dict['mood']) + gajim.config.set_per('statusmsg', '_last_' + self.show, + 'mood_text', self.pep_dict['mood_text']) + else: + message = None # user pressed Cancel button or X wm button + self.dialog.destroy() + self.on_response(message, self.pep_dict) - def on_message_combobox_changed(self, widget): - self.countdown_enabled = False - model = widget.get_model() - active = widget.get_active() - if active < 0: - return None - name = model[active][0].decode('utf-8') - self.message_buffer.set_text(self.preset_messages_dict[name][0]) - self.pep_dict['activity'] = self.preset_messages_dict[name][1] - self.pep_dict['subactivity'] = self.preset_messages_dict[name][2] - self.pep_dict['activity_text'] = self.preset_messages_dict[name][3] - self.pep_dict['mood'] = self.preset_messages_dict[name][4] - self.pep_dict['mood_text'] = self.preset_messages_dict[name][5] - self.draw_activity() - self.draw_mood() + def on_message_combobox_changed(self, widget): + self.countdown_enabled = False + model = widget.get_model() + active = widget.get_active() + if active < 0: + return None + name = model[active][0].decode('utf-8') + self.message_buffer.set_text(self.preset_messages_dict[name][0]) + self.pep_dict['activity'] = self.preset_messages_dict[name][1] + self.pep_dict['subactivity'] = self.preset_messages_dict[name][2] + self.pep_dict['activity_text'] = self.preset_messages_dict[name][3] + self.pep_dict['mood'] = self.preset_messages_dict[name][4] + self.pep_dict['mood_text'] = self.preset_messages_dict[name][5] + self.draw_activity() + self.draw_mood() - def on_change_status_message_dialog_key_press_event(self, widget, event): - self.countdown_enabled = False - if event.keyval == gtk.keysyms.Return or \ - event.keyval == gtk.keysyms.KP_Enter: # catch CTRL+ENTER - if (event.state & gtk.gdk.CONTROL_MASK): - self.dialog.response(gtk.RESPONSE_OK) - # Stop the event - return True + def on_change_status_message_dialog_key_press_event(self, widget, event): + self.countdown_enabled = False + if event.keyval == gtk.keysyms.Return or \ + event.keyval == gtk.keysyms.KP_Enter: # catch CTRL+ENTER + if (event.state & gtk.gdk.CONTROL_MASK): + self.dialog.response(gtk.RESPONSE_OK) + # Stop the event + return True - def on_message_buffer_changed(self, widget): - self.countdown_enabled = False - self.toggle_sensitiviy_of_save_as_preset() + def on_message_buffer_changed(self, widget): + self.countdown_enabled = False + self.toggle_sensitiviy_of_save_as_preset() - def toggle_sensitiviy_of_save_as_preset(self): - btn = self.xml.get_object('save_as_preset_button') - if self.message_buffer.get_char_count() == 0: - btn.set_sensitive(False) - else: - btn.set_sensitive(True) + def toggle_sensitiviy_of_save_as_preset(self): + btn = self.xml.get_object('save_as_preset_button') + if self.message_buffer.get_char_count() == 0: + btn.set_sensitive(False) + else: + btn.set_sensitive(True) - def on_save_as_preset_button_clicked(self, widget): - self.countdown_enabled = False - start_iter, finish_iter = self.message_buffer.get_bounds() - status_message_to_save_as_preset = self.message_buffer.get_text( - start_iter, finish_iter) - def on_ok(msg_name): - msg_text = status_message_to_save_as_preset.decode('utf-8') - msg_text_1l = helpers.to_one_line(msg_text) - if not msg_name: # msg_name was '' - msg_name = msg_text_1l.decode('utf-8') + def on_save_as_preset_button_clicked(self, widget): + self.countdown_enabled = False + start_iter, finish_iter = self.message_buffer.get_bounds() + status_message_to_save_as_preset = self.message_buffer.get_text( + start_iter, finish_iter) + def on_ok(msg_name): + msg_text = status_message_to_save_as_preset.decode('utf-8') + msg_text_1l = helpers.to_one_line(msg_text) + if not msg_name: # msg_name was '' + msg_name = msg_text_1l.decode('utf-8') - def on_ok2(): - self.preset_messages_dict[msg_name] = [msg_text, self.pep_dict.get( - 'activity'), self.pep_dict.get('subactivity'), self.pep_dict.get( - 'activity_text'), self.pep_dict.get('mood'), self.pep_dict.get( - 'mood_text')] - gajim.config.set_per('statusmsg', msg_name, 'message', msg_text_1l) - gajim.config.set_per('statusmsg', msg_name, 'activity', - self.pep_dict.get('activity')) - gajim.config.set_per('statusmsg', msg_name, 'subactivity', - self.pep_dict.get('subactivity')) - gajim.config.set_per('statusmsg', msg_name, 'activity_text', - self.pep_dict.get('activity_text')) - gajim.config.set_per('statusmsg', msg_name, 'mood', - self.pep_dict.get('mood')) - gajim.config.set_per('statusmsg', msg_name, 'mood_text', - self.pep_dict.get('mood_text')) - if msg_name in self.preset_messages_dict: - ConfirmationDialog(_('Overwrite Status Message?'), - _('This name is already used. Do you want to overwrite this ' - 'status message?'), on_response_ok=on_ok2) - return - gajim.config.add_per('statusmsg', msg_name) - on_ok2() - iter_ = self.message_liststore.append((msg_name,)) - # select in combobox the one we just saved - self.message_combobox.set_active_iter(iter_) - InputDialog(_('Save as Preset Status Message'), - _('Please type a name for this status message'), is_modal=False, - ok_handler=on_ok) + def on_ok2(): + self.preset_messages_dict[msg_name] = [msg_text, self.pep_dict.get( + 'activity'), self.pep_dict.get('subactivity'), self.pep_dict.get( + 'activity_text'), self.pep_dict.get('mood'), self.pep_dict.get( + 'mood_text')] + gajim.config.set_per('statusmsg', msg_name, 'message', msg_text_1l) + gajim.config.set_per('statusmsg', msg_name, 'activity', + self.pep_dict.get('activity')) + gajim.config.set_per('statusmsg', msg_name, 'subactivity', + self.pep_dict.get('subactivity')) + gajim.config.set_per('statusmsg', msg_name, 'activity_text', + self.pep_dict.get('activity_text')) + gajim.config.set_per('statusmsg', msg_name, 'mood', + self.pep_dict.get('mood')) + gajim.config.set_per('statusmsg', msg_name, 'mood_text', + self.pep_dict.get('mood_text')) + if msg_name in self.preset_messages_dict: + ConfirmationDialog(_('Overwrite Status Message?'), + _('This name is already used. Do you want to overwrite this ' + 'status message?'), on_response_ok=on_ok2) + return + gajim.config.add_per('statusmsg', msg_name) + on_ok2() + iter_ = self.message_liststore.append((msg_name,)) + # select in combobox the one we just saved + self.message_combobox.set_active_iter(iter_) + InputDialog(_('Save as Preset Status Message'), + _('Please type a name for this status message'), is_modal=False, + ok_handler=on_ok) - def on_activity_button_clicked(self, widget): - self.countdown_enabled = False - def on_response(activity, subactivity, text): - self.pep_dict['activity'] = activity or '' - self.pep_dict['subactivity'] = subactivity or '' - self.pep_dict['activity_text'] = text - self.draw_activity() - ChangeActivityDialog(on_response, self.pep_dict['activity'], - self.pep_dict['subactivity'], self.pep_dict['activity_text']) + def on_activity_button_clicked(self, widget): + self.countdown_enabled = False + def on_response(activity, subactivity, text): + self.pep_dict['activity'] = activity or '' + self.pep_dict['subactivity'] = subactivity or '' + self.pep_dict['activity_text'] = text + self.draw_activity() + ChangeActivityDialog(on_response, self.pep_dict['activity'], + self.pep_dict['subactivity'], self.pep_dict['activity_text']) - def on_mood_button_clicked(self, widget): - self.countdown_enabled = False - def on_response(mood, text): - self.pep_dict['mood'] = mood or '' - self.pep_dict['mood_text'] = text - self.draw_mood() - ChangeMoodDialog(on_response, self.pep_dict['mood'], - self.pep_dict['mood_text']) + def on_mood_button_clicked(self, widget): + self.countdown_enabled = False + def on_response(mood, text): + self.pep_dict['mood'] = mood or '' + self.pep_dict['mood_text'] = text + self.draw_mood() + ChangeMoodDialog(on_response, self.pep_dict['mood'], + self.pep_dict['mood_text']) class AddNewContactWindow: - """ - Class for AddNewContactWindow - """ + """ + Class for AddNewContactWindow + """ - uid_labels = {'jabber': _('Jabber ID:'), - 'aim': _('AIM Address:'), - 'gadu-gadu': _('GG Number:'), - 'icq': _('ICQ Number:'), - 'msn': _('MSN Address:'), - 'yahoo': _('Yahoo! Address:')} + uid_labels = {'jabber': _('Jabber ID:'), + 'aim': _('AIM Address:'), + 'gadu-gadu': _('GG Number:'), + 'icq': _('ICQ Number:'), + 'msn': _('MSN Address:'), + 'yahoo': _('Yahoo! Address:')} - def __init__(self, account=None, jid=None, user_nick=None, group=None): - self.account = account - if account is None: - # fill accounts with active accounts - accounts = [] - for account in gajim.connections.keys(): - if gajim.connections[account].connected > 1: - accounts.append(account) - if not accounts: - return - if len(accounts) == 1: - self.account = account - else: - accounts = [self.account] - if self.account: - location = gajim.interface.instances[self.account] - else: - location = gajim.interface.instances - if 'add_contact' in location: - location['add_contact'].window.present() - # An instance is already opened - return - location['add_contact'] = self - self.xml = gtkgui_helpers.get_gtk_builder('add_new_contact_window.ui') - self.xml.connect_signals(self) - self.window = self.xml.get_object('add_new_contact_window') - for w in ('account_combobox', 'account_hbox', 'account_label', - 'uid_label', 'uid_entry', 'protocol_combobox', 'protocol_jid_combobox', - 'protocol_hbox', 'nickname_entry', 'message_scrolledwindow', - 'save_message_checkbutton', 'register_hbox', 'subscription_table', - 'add_button', 'message_textview', 'connected_label', - 'group_comboboxentry', 'auto_authorize_checkbutton'): - self.__dict__[w] = self.xml.get_object(w) - if account and len(gajim.connections) >= 2: - prompt_text = \ + def __init__(self, account=None, jid=None, user_nick=None, group=None): + self.account = account + if account is None: + # fill accounts with active accounts + accounts = [] + for account in gajim.connections.keys(): + if gajim.connections[account].connected > 1: + accounts.append(account) + if not accounts: + return + if len(accounts) == 1: + self.account = account + else: + accounts = [self.account] + if self.account: + location = gajim.interface.instances[self.account] + else: + location = gajim.interface.instances + if 'add_contact' in location: + location['add_contact'].window.present() + # An instance is already opened + return + location['add_contact'] = self + self.xml = gtkgui_helpers.get_gtk_builder('add_new_contact_window.ui') + self.xml.connect_signals(self) + self.window = self.xml.get_object('add_new_contact_window') + for w in ('account_combobox', 'account_hbox', 'account_label', + 'uid_label', 'uid_entry', 'protocol_combobox', 'protocol_jid_combobox', + 'protocol_hbox', 'nickname_entry', 'message_scrolledwindow', + 'save_message_checkbutton', 'register_hbox', 'subscription_table', + 'add_button', 'message_textview', 'connected_label', + 'group_comboboxentry', 'auto_authorize_checkbutton'): + self.__dict__[w] = self.xml.get_object(w) + if account and len(gajim.connections) >= 2: + prompt_text = \ _('Please fill in the data of the contact you want to add in account %s') % account - else: - prompt_text = \ - _('Please fill in the data of the contact you want to add') - self.xml.get_object('prompt_label').set_text(prompt_text) - self.agents = {'jabber': []} - # types to which we are not subscribed but account has an agent for it - self.available_types = [] - for acct in accounts: - for j in gajim.contacts.get_jid_list(acct): - if gajim.jid_is_transport(j): - type_ = gajim.get_transport_name_from_jid(j, False) - if not type_: - continue - if type_ in self.agents: - self.agents[type_].append(j) - else: - self.agents[type_] = [j] - # Now add the one to which we can register - for acct in accounts: - for type_ in gajim.connections[acct].available_transports: - if type_ in self.agents: - continue - self.agents[type_] = [] - for jid_ in gajim.connections[acct].available_transports[type_]: - if not jid_ in self.agents[type_]: - self.agents[type_].append(jid_) - self.available_types.append(type_) - # Combobox with transport/jabber icons - liststore = gtk.ListStore(str, gtk.gdk.Pixbuf, str) - cell = gtk.CellRendererPixbuf() - self.protocol_combobox.pack_start(cell, False) - self.protocol_combobox.add_attribute(cell, 'pixbuf', 1) - cell = gtk.CellRendererText() - cell.set_property('xpad', 5) - self.protocol_combobox.pack_start(cell, True) - self.protocol_combobox.add_attribute(cell, 'text', 0) - self.protocol_combobox.set_model(liststore) - uf_type = {'jabber': 'Jabber', 'aim': 'AIM', 'gadu-gadu': 'Gadu Gadu', - 'icq': 'ICQ', 'msn': 'MSN', 'yahoo': 'Yahoo'} - # Jabber as first - img = gajim.interface.jabber_state_images['16']['online'] - liststore.append(['Jabber', img.get_pixbuf(), 'jabber']) - for type_ in self.agents: - if type_ == 'jabber': - continue - imgs = gajim.interface.roster.transports_state_images - img = None - if type_ in imgs['16'] and 'online' in imgs['16'][type_]: - img = imgs['16'][type_]['online'] - if type_ in uf_type: - liststore.append([uf_type[type_], img.get_pixbuf(), type_]) - else: - liststore.append([type_, img.get_pixbuf(), type_]) - else: - liststore.append([type_, img, type_]) - self.protocol_combobox.set_active(0) - self.auto_authorize_checkbutton.show() - liststore = gtk.ListStore(str) - self.protocol_jid_combobox.set_model(liststore) - if jid: - type_ = gajim.get_transport_name_from_jid(jid) - if not type_: - type_ = 'jabber' - if type_ == 'jabber': - self.uid_entry.set_text(jid) - else: - uid, transport = gajim.get_name_and_server_from_jid(jid) - self.uid_entry.set_text(uid.replace('%', '@', 1)) - # set protocol_combobox - model = self.protocol_combobox.get_model() - iter_ = model.get_iter_first() - i = 0 - while iter_: - if model[iter_][2] == type_: - self.protocol_combobox.set_active(i) - break - iter_ = model.iter_next(iter_) - i += 1 + else: + prompt_text = \ + _('Please fill in the data of the contact you want to add') + self.xml.get_object('prompt_label').set_text(prompt_text) + self.agents = {'jabber': []} + # types to which we are not subscribed but account has an agent for it + self.available_types = [] + for acct in accounts: + for j in gajim.contacts.get_jid_list(acct): + if gajim.jid_is_transport(j): + type_ = gajim.get_transport_name_from_jid(j, False) + if not type_: + continue + if type_ in self.agents: + self.agents[type_].append(j) + else: + self.agents[type_] = [j] + # Now add the one to which we can register + for acct in accounts: + for type_ in gajim.connections[acct].available_transports: + if type_ in self.agents: + continue + self.agents[type_] = [] + for jid_ in gajim.connections[acct].available_transports[type_]: + if not jid_ in self.agents[type_]: + self.agents[type_].append(jid_) + self.available_types.append(type_) + # Combobox with transport/jabber icons + liststore = gtk.ListStore(str, gtk.gdk.Pixbuf, str) + cell = gtk.CellRendererPixbuf() + self.protocol_combobox.pack_start(cell, False) + self.protocol_combobox.add_attribute(cell, 'pixbuf', 1) + cell = gtk.CellRendererText() + cell.set_property('xpad', 5) + self.protocol_combobox.pack_start(cell, True) + self.protocol_combobox.add_attribute(cell, 'text', 0) + self.protocol_combobox.set_model(liststore) + uf_type = {'jabber': 'Jabber', 'aim': 'AIM', 'gadu-gadu': 'Gadu Gadu', + 'icq': 'ICQ', 'msn': 'MSN', 'yahoo': 'Yahoo'} + # Jabber as first + img = gajim.interface.jabber_state_images['16']['online'] + liststore.append(['Jabber', img.get_pixbuf(), 'jabber']) + for type_ in self.agents: + if type_ == 'jabber': + continue + imgs = gajim.interface.roster.transports_state_images + img = None + if type_ in imgs['16'] and 'online' in imgs['16'][type_]: + img = imgs['16'][type_]['online'] + if type_ in uf_type: + liststore.append([uf_type[type_], img.get_pixbuf(), type_]) + else: + liststore.append([type_, img.get_pixbuf(), type_]) + else: + liststore.append([type_, img, type_]) + self.protocol_combobox.set_active(0) + self.auto_authorize_checkbutton.show() + liststore = gtk.ListStore(str) + self.protocol_jid_combobox.set_model(liststore) + if jid: + type_ = gajim.get_transport_name_from_jid(jid) + if not type_: + type_ = 'jabber' + if type_ == 'jabber': + self.uid_entry.set_text(jid) + else: + uid, transport = gajim.get_name_and_server_from_jid(jid) + self.uid_entry.set_text(uid.replace('%', '@', 1)) + # set protocol_combobox + model = self.protocol_combobox.get_model() + iter_ = model.get_iter_first() + i = 0 + while iter_: + if model[iter_][2] == type_: + self.protocol_combobox.set_active(i) + break + iter_ = model.iter_next(iter_) + i += 1 - # set protocol_jid_combobox - self.protocol_jid_combobox.set_active(0) - model = self.protocol_jid_combobox.get_model() - iter_ = model.get_iter_first() - i = 0 - while iter_: - if model[iter_][0] == transport: - self.protocol_jid_combobox.set_active(i) - break - iter_ = model.iter_next(iter_) - i += 1 - if user_nick: - self.nickname_entry.set_text(user_nick) - self.nickname_entry.grab_focus() - else: - self.uid_entry.grab_focus() - group_names = [] - for acct in accounts: - for g in gajim.groups[acct].keys(): - if g not in helpers.special_groups and g not in group_names: - group_names.append(g) - group_names.sort() - i = 0 - for g in group_names: - self.group_comboboxentry.append_text(g) - if group == g: - self.group_comboboxentry.set_active(i) - i += 1 + # set protocol_jid_combobox + self.protocol_jid_combobox.set_active(0) + model = self.protocol_jid_combobox.get_model() + iter_ = model.get_iter_first() + i = 0 + while iter_: + if model[iter_][0] == transport: + self.protocol_jid_combobox.set_active(i) + break + iter_ = model.iter_next(iter_) + i += 1 + if user_nick: + self.nickname_entry.set_text(user_nick) + self.nickname_entry.grab_focus() + else: + self.uid_entry.grab_focus() + group_names = [] + for acct in accounts: + for g in gajim.groups[acct].keys(): + if g not in helpers.special_groups and g not in group_names: + group_names.append(g) + group_names.sort() + i = 0 + for g in group_names: + self.group_comboboxentry.append_text(g) + if group == g: + self.group_comboboxentry.set_active(i) + i += 1 - self.window.show_all() + self.window.show_all() - if self.account: - self.account_label.hide() - self.account_hbox.hide() - else: - liststore = gtk.ListStore(str, str) - for acct in accounts: - liststore.append([acct, acct]) - self.account_combobox.set_model(liststore) - self.account_combobox.set_active(0) + if self.account: + self.account_label.hide() + self.account_hbox.hide() + else: + liststore = gtk.ListStore(str, str) + for acct in accounts: + liststore.append([acct, acct]) + self.account_combobox.set_model(liststore) + self.account_combobox.set_active(0) - if self.account: - message_buffer = self.message_textview.get_buffer() - message_buffer.set_text(helpers.get_subscription_request_msg( - self.account)) + if self.account: + message_buffer = self.message_textview.get_buffer() + message_buffer.set_text(helpers.get_subscription_request_msg( + self.account)) - def on_add_new_contact_window_destroy(self, widget): - if self.account: - location = gajim.interface.instances[self.account] - else: - location = gajim.interface.instances - del location['add_contact'] + def on_add_new_contact_window_destroy(self, widget): + if self.account: + location = gajim.interface.instances[self.account] + else: + location = gajim.interface.instances + del location['add_contact'] - def on_register_button_clicked(self, widget): - jid = self.protocol_jid_combobox.get_active_text().decode('utf-8') - gajim.connections[self.account].request_register_agent_info(jid) + def on_register_button_clicked(self, widget): + jid = self.protocol_jid_combobox.get_active_text().decode('utf-8') + gajim.connections[self.account].request_register_agent_info(jid) - def on_add_new_contact_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: # ESCAPE - self.window.destroy() + def on_add_new_contact_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: # ESCAPE + self.window.destroy() - def on_cancel_button_clicked(self, widget): - """ - When Cancel button is clicked - """ - self.window.destroy() + def on_cancel_button_clicked(self, widget): + """ + When Cancel button is clicked + """ + self.window.destroy() - def on_add_button_clicked(self, widget): - """ - When Subscribe button is clicked - """ - jid = self.uid_entry.get_text().decode('utf-8').strip() - if not jid: - return + def on_add_button_clicked(self, widget): + """ + When Subscribe button is clicked + """ + jid = self.uid_entry.get_text().decode('utf-8').strip() + if not jid: + return - model = self.protocol_combobox.get_model() - iter_ = self.protocol_combobox.get_active_iter() - type_ = model[iter_][2] - if type_ != 'jabber': - transport = self.protocol_jid_combobox.get_active_text().decode( - 'utf-8') - jid = jid.replace('@', '%') + '@' + transport + model = self.protocol_combobox.get_model() + iter_ = self.protocol_combobox.get_active_iter() + type_ = model[iter_][2] + if type_ != 'jabber': + transport = self.protocol_jid_combobox.get_active_text().decode( + 'utf-8') + jid = jid.replace('@', '%') + '@' + transport - # check if jid is conform to RFC and stringprep it - try: - jid = helpers.parse_jid(jid) - except helpers.InvalidFormat, s: - pritext = _('Invalid User ID') - ErrorDialog(pritext, str(s)) - return + # check if jid is conform to RFC and stringprep it + try: + jid = helpers.parse_jid(jid) + except helpers.InvalidFormat, s: + pritext = _('Invalid User ID') + ErrorDialog(pritext, str(s)) + return - # No resource in jid - if jid.find('/') >= 0: - pritext = _('Invalid User ID') - ErrorDialog(pritext, _('The user ID must not contain a resource.')) - return + # No resource in jid + if jid.find('/') >= 0: + pritext = _('Invalid User ID') + ErrorDialog(pritext, _('The user ID must not contain a resource.')) + return - if jid == gajim.get_jid_from_account(self.account): - pritext = _('Invalid User ID') - ErrorDialog(pritext, _('You cannot add yourself to your roster.')) - return + if jid == gajim.get_jid_from_account(self.account): + pritext = _('Invalid User ID') + ErrorDialog(pritext, _('You cannot add yourself to your roster.')) + return - nickname = self.nickname_entry.get_text().decode('utf-8') or '' - # get value of account combobox, if account was not specified - if not self.account: - model = self.account_combobox.get_model() - index = self.account_combobox.get_active() - self.account = model[index][1] + nickname = self.nickname_entry.get_text().decode('utf-8') or '' + # get value of account combobox, if account was not specified + if not self.account: + model = self.account_combobox.get_model() + index = self.account_combobox.get_active() + self.account = model[index][1] - # Check if jid is already in roster - if jid in gajim.contacts.get_jid_list(self.account): - c = gajim.contacts.get_first_contact_from_jid(self.account, jid) - if _('Not in Roster') not in c.groups and c.sub in ('both', 'to'): - ErrorDialog(_('Contact already in roster'), - _('This contact is already listed in your roster.')) - return + # Check if jid is already in roster + if jid in gajim.contacts.get_jid_list(self.account): + c = gajim.contacts.get_first_contact_from_jid(self.account, jid) + if _('Not in Roster') not in c.groups and c.sub in ('both', 'to'): + ErrorDialog(_('Contact already in roster'), + _('This contact is already listed in your roster.')) + return - if type_ == 'jabber': - message_buffer = self.message_textview.get_buffer() - start_iter = message_buffer.get_start_iter() - end_iter = message_buffer.get_end_iter() - message = message_buffer.get_text(start_iter, end_iter).decode('utf-8') - if self.save_message_checkbutton.get_active(): - gajim.config.set_per('accounts', self.account, - 'subscription_request_msg', message) - else: - message= '' - group = self.group_comboboxentry.child.get_text().decode('utf-8') - groups = [] - if group: - groups = [group] - auto_auth = self.auto_authorize_checkbutton.get_active() - gajim.interface.roster.req_sub(self, jid, message, self.account, - groups=groups, nickname=nickname, auto_auth=auto_auth) - self.window.destroy() + if type_ == 'jabber': + message_buffer = self.message_textview.get_buffer() + start_iter = message_buffer.get_start_iter() + end_iter = message_buffer.get_end_iter() + message = message_buffer.get_text(start_iter, end_iter).decode('utf-8') + if self.save_message_checkbutton.get_active(): + gajim.config.set_per('accounts', self.account, + 'subscription_request_msg', message) + else: + message= '' + group = self.group_comboboxentry.child.get_text().decode('utf-8') + groups = [] + if group: + groups = [group] + auto_auth = self.auto_authorize_checkbutton.get_active() + gajim.interface.roster.req_sub(self, jid, message, self.account, + groups=groups, nickname=nickname, auto_auth=auto_auth) + self.window.destroy() - def on_account_combobox_changed(self, widget): - model = widget.get_model() - iter_ = widget.get_active_iter() - account = model[iter_][0] - message_buffer = self.message_textview.get_buffer() - message_buffer.set_text(helpers.get_subscription_request_msg(account)) + def on_account_combobox_changed(self, widget): + model = widget.get_model() + iter_ = widget.get_active_iter() + account = model[iter_][0] + message_buffer = self.message_textview.get_buffer() + message_buffer.set_text(helpers.get_subscription_request_msg(account)) - def on_protocol_combobox_changed(self, widget): - model = widget.get_model() - iter_ = widget.get_active_iter() - type_ = model[iter_][2] - model = self.protocol_jid_combobox.get_model() - model.clear() - if len(self.agents[type_]): - for jid_ in self.agents[type_]: - model.append([jid_]) - self.protocol_jid_combobox.set_active(0) - if len(self.agents[type_]) > 1: - self.protocol_jid_combobox.show() - else: - self.protocol_jid_combobox.hide() - if type_ in self.uid_labels: - self.uid_label.set_text(self.uid_labels[type_]) - else: - self.uid_label.set_text(_('User ID:')) - if type_ == 'jabber': - self.message_scrolledwindow.show() - self.save_message_checkbutton.show() - else: - self.message_scrolledwindow.hide() - self.save_message_checkbutton.hide() - if type_ in self.available_types: - self.register_hbox.show() - self.auto_authorize_checkbutton.hide() - self.connected_label.hide() - self.subscription_table.hide() - self.add_button.set_sensitive(False) - else: - self.register_hbox.hide() - if type_ != 'jabber': - jid = self.protocol_jid_combobox.get_active_text() - contact = gajim.contacts.get_first_contact_from_jid(self.account, - jid) - if contact.show in ('offline', 'error'): - self.subscription_table.hide() - self.connected_label.show() - self.add_button.set_sensitive(False) - self.auto_authorize_checkbutton.hide() - return - self.subscription_table.show() - self.auto_authorize_checkbutton.show() - self.connected_label.hide() - self.add_button.set_sensitive(True) + def on_protocol_combobox_changed(self, widget): + model = widget.get_model() + iter_ = widget.get_active_iter() + type_ = model[iter_][2] + model = self.protocol_jid_combobox.get_model() + model.clear() + if len(self.agents[type_]): + for jid_ in self.agents[type_]: + model.append([jid_]) + self.protocol_jid_combobox.set_active(0) + if len(self.agents[type_]) > 1: + self.protocol_jid_combobox.show() + else: + self.protocol_jid_combobox.hide() + if type_ in self.uid_labels: + self.uid_label.set_text(self.uid_labels[type_]) + else: + self.uid_label.set_text(_('User ID:')) + if type_ == 'jabber': + self.message_scrolledwindow.show() + self.save_message_checkbutton.show() + else: + self.message_scrolledwindow.hide() + self.save_message_checkbutton.hide() + if type_ in self.available_types: + self.register_hbox.show() + self.auto_authorize_checkbutton.hide() + self.connected_label.hide() + self.subscription_table.hide() + self.add_button.set_sensitive(False) + else: + self.register_hbox.hide() + if type_ != 'jabber': + jid = self.protocol_jid_combobox.get_active_text() + contact = gajim.contacts.get_first_contact_from_jid(self.account, + jid) + if contact.show in ('offline', 'error'): + self.subscription_table.hide() + self.connected_label.show() + self.add_button.set_sensitive(False) + self.auto_authorize_checkbutton.hide() + return + self.subscription_table.show() + self.auto_authorize_checkbutton.show() + self.connected_label.hide() + self.add_button.set_sensitive(True) - def transport_signed_in(self, jid): - if self.protocol_jid_combobox.get_active_text() == jid: - self.register_hbox.hide() - self.connected_label.hide() - self.subscription_table.show() - self.auto_authorize_checkbutton.show() - self.add_button.set_sensitive(True) + def transport_signed_in(self, jid): + if self.protocol_jid_combobox.get_active_text() == jid: + self.register_hbox.hide() + self.connected_label.hide() + self.subscription_table.show() + self.auto_authorize_checkbutton.show() + self.add_button.set_sensitive(True) - def transport_signed_out(self, jid): - if self.protocol_jid_combobox.get_active_text() == jid: - self.subscription_table.hide() - self.auto_authorize_checkbutton.hide() - self.connected_label.show() - self.add_button.set_sensitive(False) + def transport_signed_out(self, jid): + if self.protocol_jid_combobox.get_active_text() == jid: + self.subscription_table.hide() + self.auto_authorize_checkbutton.hide() + self.connected_label.show() + self.add_button.set_sensitive(False) class AboutDialog: - """ - Class for about dialog - """ - - def __init__(self): - dlg = gtk.AboutDialog() - dlg.set_transient_for(gajim.interface.roster.window) - dlg.set_name('Gajim') - dlg.set_version(gajim.version) - s = u'Copyright © 2003-2009 Gajim Team' - dlg.set_copyright(s) - copying_file_path = self.get_path('COPYING') - if copying_file_path: - text = open(copying_file_path).read() - dlg.set_license(text) + """ + Class for about dialog + """ - dlg.set_comments('%s\n%s %s\n%s %s' - % (_('A GTK+ jabber client'), \ - _('GTK+ Version:'), self.tuple2str(gtk.gtk_version), \ - _('PyGTK Version:'), self.tuple2str(gtk.pygtk_version))) - dlg.set_website('http://www.gajim.org/') + def __init__(self): + dlg = gtk.AboutDialog() + dlg.set_transient_for(gajim.interface.roster.window) + dlg.set_name('Gajim') + dlg.set_version(gajim.version) + s = u'Copyright © 2003-2009 Gajim Team' + dlg.set_copyright(s) + copying_file_path = self.get_path('COPYING') + if copying_file_path: + text = open(copying_file_path).read() + dlg.set_license(text) - authors_file_path = self.get_path('AUTHORS') - if authors_file_path: - authors = [] - authors_file = open(authors_file_path).read() - authors_file = authors_file.split('\n') - for author in authors_file: - if author == 'CURRENT DEVELOPERS:': - authors.append(_('Current Developers:')) - elif author == 'PAST DEVELOPERS:': - authors.append('\n' + _('Past Developers:')) - elif author != '': # Real author line - authors.append(author) + dlg.set_comments('%s\n%s %s\n%s %s' + % (_('A GTK+ jabber client'), \ + _('GTK+ Version:'), self.tuple2str(gtk.gtk_version), \ + _('PyGTK Version:'), self.tuple2str(gtk.pygtk_version))) + dlg.set_website('http://www.gajim.org/') - thanks_file_path = self.get_path('THANKS') - if thanks_file_path: - authors.append('\n' + _('THANKS:')) + authors_file_path = self.get_path('AUTHORS') + if authors_file_path: + authors = [] + authors_file = open(authors_file_path).read() + authors_file = authors_file.split('\n') + for author in authors_file: + if author == 'CURRENT DEVELOPERS:': + authors.append(_('Current Developers:')) + elif author == 'PAST DEVELOPERS:': + authors.append('\n' + _('Past Developers:')) + elif author != '': # Real author line + authors.append(author) - text = open(thanks_file_path).read() - text_splitted = text.split('\n') - text = '\n'.join(text_splitted[:-2]) # remove one english sentence - # and add it manually as translatable - text += '\n%s\n' % _('Last but not least, we would like to thank all ' - 'the package maintainers.') - authors.append(text) + thanks_file_path = self.get_path('THANKS') + if thanks_file_path: + authors.append('\n' + _('THANKS:')) - dlg.set_authors(authors) + text = open(thanks_file_path).read() + text_splitted = text.split('\n') + text = '\n'.join(text_splitted[:-2]) # remove one english sentence + # and add it manually as translatable + text += '\n%s\n' % _('Last but not least, we would like to thank all ' + 'the package maintainers.') + authors.append(text) - dlg.props.wrap_license = True + dlg.set_authors(authors) - pixbuf = gtkgui_helpers.get_icon_pixmap('gajim-about', 128) + dlg.props.wrap_license = True - dlg.set_logo(pixbuf) - #here you write your name in the form Name FamilyName - dlg.set_translator_credits(_('translator-credits')) + pixbuf = gtkgui_helpers.get_icon_pixmap('gajim-about', 128) - thanks_artists_file_path = self.get_path('THANKS.artists') - if thanks_artists_file_path: - artists_text = open(thanks_artists_file_path).read() - artists = artists_text.split('\n') - dlg.set_artists(artists) - # connect close button to destroy() function - for button in dlg.action_area.get_children(): - if button.get_property('label') == gtk.STOCK_CLOSE: - button.connect('clicked', lambda x:dlg.destroy()) - dlg.show_all() + dlg.set_logo(pixbuf) + #here you write your name in the form Name FamilyName + dlg.set_translator_credits(_('translator-credits')) - def tuple2str(self, tuple_): - str_ = '' - for num in tuple_: - str_ += str(num) + '.' - return str_[0:-1] # remove latest . + thanks_artists_file_path = self.get_path('THANKS.artists') + if thanks_artists_file_path: + artists_text = open(thanks_artists_file_path).read() + artists = artists_text.split('\n') + dlg.set_artists(artists) + # connect close button to destroy() function + for button in dlg.action_area.get_children(): + if button.get_property('label') == gtk.STOCK_CLOSE: + button.connect('clicked', lambda x:dlg.destroy()) + dlg.show_all() - def get_path(self, filename): - """ - Where can we find this Credits file? - """ - if os.path.isfile(os.path.join(gajim.defs.docdir, filename)): - return os.path.join(gajim.defs.docdir, filename) - elif os.path.isfile('../' + filename): - return ('../' + filename) - else: - return None + def tuple2str(self, tuple_): + str_ = '' + for num in tuple_: + str_ += str(num) + '.' + return str_[0:-1] # remove latest . + + def get_path(self, filename): + """ + Where can we find this Credits file? + """ + if os.path.isfile(os.path.join(gajim.defs.docdir, filename)): + return os.path.join(gajim.defs.docdir, filename) + elif os.path.isfile('../' + filename): + return ('../' + filename) + else: + return None class Dialog(gtk.Dialog): - def __init__(self, parent, title, buttons, default=None, - on_response_ok=None, on_response_cancel=None): - gtk.Dialog.__init__(self, title, parent, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR) + def __init__(self, parent, title, buttons, default=None, + on_response_ok=None, on_response_cancel=None): + gtk.Dialog.__init__(self, title, parent, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR) - self.user_response_ok = on_response_ok - self.user_response_cancel = on_response_cancel - self.set_border_width(6) - self.vbox.set_spacing(12) - self.set_resizable(False) + self.user_response_ok = on_response_ok + self.user_response_cancel = on_response_cancel + self.set_border_width(6) + self.vbox.set_spacing(12) + self.set_resizable(False) - possible_responses = {gtk.STOCK_OK: self.on_response_ok, - gtk.STOCK_CANCEL: self.on_response_cancel} - for stock, response in buttons: - b = self.add_button(stock, response) - for response in possible_responses: - if stock == response: - b.connect('clicked', possible_responses[response]) - break + possible_responses = {gtk.STOCK_OK: self.on_response_ok, + gtk.STOCK_CANCEL: self.on_response_cancel} + for stock, response in buttons: + b = self.add_button(stock, response) + for response in possible_responses: + if stock == response: + b.connect('clicked', possible_responses[response]) + break - if default is not None: - self.set_default_response(default) - else: - self.set_default_response(buttons[-1][1]) + if default is not None: + self.set_default_response(default) + else: + self.set_default_response(buttons[-1][1]) - def on_response_ok(self, widget): - if self.user_response_ok: - if isinstance(self.user_response_ok, tuple): - self.user_response_ok[0](*self.user_response_ok[1:]) - else: - self.user_response_ok() - self.destroy() + def on_response_ok(self, widget): + if self.user_response_ok: + if isinstance(self.user_response_ok, tuple): + self.user_response_ok[0](*self.user_response_ok[1:]) + else: + self.user_response_ok() + self.destroy() - def on_response_cancel(self, widget): - if self.user_response_cancel: - if isinstance(self.user_response_cancel, tuple): - self.user_response_cancel[0](*self.user_response_ok[1:]) - else: - self.user_response_cancel() - self.destroy() + def on_response_cancel(self, widget): + if self.user_response_cancel: + if isinstance(self.user_response_cancel, tuple): + self.user_response_cancel[0](*self.user_response_ok[1:]) + else: + self.user_response_cancel() + self.destroy() - def just_destroy(self, widget): - self.destroy() + def just_destroy(self, widget): + self.destroy() - def get_button(self, index): - buttons = self.action_area.get_children() - return index < len(buttons) and buttons[index] or None + def get_button(self, index): + buttons = self.action_area.get_children() + return index < len(buttons) and buttons[index] or None class HigDialog(gtk.MessageDialog): - def __init__(self, parent, type_, buttons, pritext, sectext, - on_response_ok=None, on_response_cancel=None, on_response_yes=None, - on_response_no=None): - self.call_cancel_on_destroy = True - gtk.MessageDialog.__init__(self, parent, - gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, - type_, buttons, message_format = pritext) + def __init__(self, parent, type_, buttons, pritext, sectext, + on_response_ok=None, on_response_cancel=None, on_response_yes=None, + on_response_no=None): + self.call_cancel_on_destroy = True + gtk.MessageDialog.__init__(self, parent, + gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, + type_, buttons, message_format = pritext) - self.format_secondary_markup(sectext) + self.format_secondary_markup(sectext) - buttons = self.action_area.get_children() - self.possible_responses = {gtk.STOCK_OK: on_response_ok, - gtk.STOCK_CANCEL: on_response_cancel, gtk.STOCK_YES: on_response_yes, - gtk.STOCK_NO: on_response_no} - for b in buttons: - for response in self.possible_responses: - if b.get_label() == response: - if not self.possible_responses[response]: - b.connect('clicked', self.just_destroy) - elif isinstance(self.possible_responses[response], tuple): - if len(self.possible_responses[response]) == 1: - b.connect('clicked', self.possible_responses[response][0]) - else: - b.connect('clicked', *self.possible_responses[response]) - else: - b.connect('clicked', self.possible_responses[response]) - break + buttons = self.action_area.get_children() + self.possible_responses = {gtk.STOCK_OK: on_response_ok, + gtk.STOCK_CANCEL: on_response_cancel, gtk.STOCK_YES: on_response_yes, + gtk.STOCK_NO: on_response_no} + for b in buttons: + for response in self.possible_responses: + if b.get_label() == response: + if not self.possible_responses[response]: + b.connect('clicked', self.just_destroy) + elif isinstance(self.possible_responses[response], tuple): + if len(self.possible_responses[response]) == 1: + b.connect('clicked', self.possible_responses[response][0]) + else: + b.connect('clicked', *self.possible_responses[response]) + else: + b.connect('clicked', self.possible_responses[response]) + break - self.connect('destroy', self.on_dialog_destroy) + self.connect('destroy', self.on_dialog_destroy) - def on_dialog_destroy(self, widget): - if not self.call_cancel_on_destroy: - return - cancel_handler = self.possible_responses[gtk.STOCK_CANCEL] - if not cancel_handler: - return False - if isinstance(cancel_handler, tuple): - cancel_handler[0](None, *cancel_handler[1:]) - else: - cancel_handler(None) + def on_dialog_destroy(self, widget): + if not self.call_cancel_on_destroy: + return + cancel_handler = self.possible_responses[gtk.STOCK_CANCEL] + if not cancel_handler: + return False + if isinstance(cancel_handler, tuple): + cancel_handler[0](None, *cancel_handler[1:]) + else: + cancel_handler(None) - def just_destroy(self, widget): - self.destroy() + def just_destroy(self, widget): + self.destroy() - def popup(self): - """ - Show dialog - """ - vb = self.get_children()[0].get_children()[0] # Give focus to top vbox - vb.set_flags(gtk.CAN_FOCUS) - vb.grab_focus() - self.show_all() + def popup(self): + """ + Show dialog + """ + vb = self.get_children()[0].get_children()[0] # Give focus to top vbox + vb.set_flags(gtk.CAN_FOCUS) + vb.grab_focus() + self.show_all() class FileChooserDialog(gtk.FileChooserDialog): - """ - Non-blocking FileChooser Dialog around gtk.FileChooserDialog - """ - def __init__(self, title_text, action, buttons, default_response, - select_multiple = False, current_folder = None, on_response_ok = None, - on_response_cancel = None): + """ + Non-blocking FileChooser Dialog around gtk.FileChooserDialog + """ + def __init__(self, title_text, action, buttons, default_response, + select_multiple = False, current_folder = None, on_response_ok = None, + on_response_cancel = None): - gtk.FileChooserDialog.__init__(self, title=title_text, action=action, - buttons=buttons) + gtk.FileChooserDialog.__init__(self, title=title_text, action=action, + buttons=buttons) - self.set_default_response(default_response) - self.set_select_multiple(select_multiple) - if current_folder and os.path.isdir(current_folder): - self.set_current_folder(current_folder) - else: - self.set_current_folder(helpers.get_documents_path()) - self.response_ok, self.response_cancel = \ - on_response_ok, on_response_cancel - # in gtk+-2.10 clicked signal on some of the buttons in a dialog - # is emitted twice, so we cannot rely on 'clicked' signal - self.connect('response', self.on_dialog_response) - self.show_all() + self.set_default_response(default_response) + self.set_select_multiple(select_multiple) + if current_folder and os.path.isdir(current_folder): + self.set_current_folder(current_folder) + else: + self.set_current_folder(helpers.get_documents_path()) + self.response_ok, self.response_cancel = \ + on_response_ok, on_response_cancel + # in gtk+-2.10 clicked signal on some of the buttons in a dialog + # is emitted twice, so we cannot rely on 'clicked' signal + self.connect('response', self.on_dialog_response) + self.show_all() - def on_dialog_response(self, dialog, response): - if response in (gtk.RESPONSE_CANCEL, gtk.RESPONSE_CLOSE): - if self.response_cancel: - if isinstance(self.response_cancel, tuple): - self.response_cancel[0](dialog, *self.response_cancel[1:]) - else: - self.response_cancel(dialog) - else: - self.just_destroy(dialog) - elif response == gtk.RESPONSE_OK: - if self.response_ok: - if isinstance(self.response_ok, tuple): - self.response_ok[0](dialog, *self.response_ok[1:]) - else: - self.response_ok(dialog) - else: - self.just_destroy(dialog) + def on_dialog_response(self, dialog, response): + if response in (gtk.RESPONSE_CANCEL, gtk.RESPONSE_CLOSE): + if self.response_cancel: + if isinstance(self.response_cancel, tuple): + self.response_cancel[0](dialog, *self.response_cancel[1:]) + else: + self.response_cancel(dialog) + else: + self.just_destroy(dialog) + elif response == gtk.RESPONSE_OK: + if self.response_ok: + if isinstance(self.response_ok, tuple): + self.response_ok[0](dialog, *self.response_ok[1:]) + else: + self.response_ok(dialog) + else: + self.just_destroy(dialog) - def just_destroy(self, widget): - self.destroy() + def just_destroy(self, widget): + self.destroy() class AspellDictError: - def __init__(self, lang): - ErrorDialog( - _('Dictionary for lang %s not available') % lang, - _('You have to install %s dictionary to use spellchecking, or ' - 'choose another language by setting the speller_language option.' - '\n\nHighlighting misspelled words feature will not be used') % lang) - gajim.config.set('use_speller', False) + def __init__(self, lang): + ErrorDialog( + _('Dictionary for lang %s not available') % lang, + _('You have to install %s dictionary to use spellchecking, or ' + 'choose another language by setting the speller_language option.' + '\n\nHighlighting misspelled words feature will not be used') % lang) + gajim.config.set('use_speller', False) class ConfirmationDialog(HigDialog): - """ - HIG compliant confirmation dialog - """ + """ + HIG compliant confirmation dialog + """ - def __init__(self, pritext, sectext='', on_response_ok=None, - on_response_cancel=None): - self.user_response_ok = on_response_ok - self.user_response_cancel = on_response_cancel - HigDialog.__init__(self, None, - gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, pritext, sectext, - self.on_response_ok, self.on_response_cancel) - self.popup() + def __init__(self, pritext, sectext='', on_response_ok=None, + on_response_cancel=None): + self.user_response_ok = on_response_ok + self.user_response_cancel = on_response_cancel + HigDialog.__init__(self, None, + gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, pritext, sectext, + self.on_response_ok, self.on_response_cancel) + self.popup() - def on_response_ok(self, widget): - if self.user_response_ok: - if isinstance(self.user_response_ok, tuple): - self.user_response_ok[0](*self.user_response_ok[1:]) - else: - self.user_response_ok() - self.call_cancel_on_destroy = False - self.destroy() + def on_response_ok(self, widget): + if self.user_response_ok: + if isinstance(self.user_response_ok, tuple): + self.user_response_ok[0](*self.user_response_ok[1:]) + else: + self.user_response_ok() + self.call_cancel_on_destroy = False + self.destroy() - def on_response_cancel(self, widget): - if self.user_response_cancel: - if isinstance(self.user_response_cancel, tuple): - self.user_response_cancel[0](*self.user_response_ok[1:]) - else: - self.user_response_cancel() - self.call_cancel_on_destroy = False - self.destroy() + def on_response_cancel(self, widget): + if self.user_response_cancel: + if isinstance(self.user_response_cancel, tuple): + self.user_response_cancel[0](*self.user_response_ok[1:]) + else: + self.user_response_cancel() + self.call_cancel_on_destroy = False + self.destroy() class NonModalConfirmationDialog(HigDialog): - """ - HIG compliant non modal confirmation dialog - """ + """ + HIG compliant non modal confirmation dialog + """ - def __init__(self, pritext, sectext='', on_response_ok=None, - on_response_cancel=None): - self.user_response_ok = on_response_ok - self.user_response_cancel = on_response_cancel - HigDialog.__init__(self, None, - gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, pritext, sectext, - self.on_response_ok, self.on_response_cancel) - self.set_modal(False) + def __init__(self, pritext, sectext='', on_response_ok=None, + on_response_cancel=None): + self.user_response_ok = on_response_ok + self.user_response_cancel = on_response_cancel + HigDialog.__init__(self, None, + gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, pritext, sectext, + self.on_response_ok, self.on_response_cancel) + self.set_modal(False) - def on_response_ok(self, widget): - if self.user_response_ok: - if isinstance(self.user_response_ok, tuple): - self.user_response_ok[0](*self.user_response_ok[1:]) - else: - self.user_response_ok() - self.call_cancel_on_destroy = False - self.destroy() + def on_response_ok(self, widget): + if self.user_response_ok: + if isinstance(self.user_response_ok, tuple): + self.user_response_ok[0](*self.user_response_ok[1:]) + else: + self.user_response_ok() + self.call_cancel_on_destroy = False + self.destroy() - def on_response_cancel(self, widget): - if self.user_response_cancel: - if isinstance(self.user_response_cancel, tuple): - self.user_response_cancel[0](*self.user_response_cancel[1:]) - else: - self.user_response_cancel() - self.call_cancel_on_destroy = False - self.destroy() + def on_response_cancel(self, widget): + if self.user_response_cancel: + if isinstance(self.user_response_cancel, tuple): + self.user_response_cancel[0](*self.user_response_cancel[1:]) + else: + self.user_response_cancel() + self.call_cancel_on_destroy = False + self.destroy() class WarningDialog(HigDialog): - """ - HIG compliant warning dialog - """ + """ + HIG compliant warning dialog + """ - def __init__(self, pritext, sectext=''): - HigDialog.__init__( self, None, - gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, pritext, sectext) - self.set_modal(False) - if hasattr(gajim.interface, 'roster'): - self.set_transient_for(gajim.interface.roster.window) - self.popup() + def __init__(self, pritext, sectext=''): + HigDialog.__init__( self, None, + gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, pritext, sectext) + self.set_modal(False) + if hasattr(gajim.interface, 'roster'): + self.set_transient_for(gajim.interface.roster.window) + self.popup() class InformationDialog(HigDialog): - """ - HIG compliant info dialog - """ + """ + HIG compliant info dialog + """ - def __init__(self, pritext, sectext=''): - HigDialog.__init__(self, None, - gtk.MESSAGE_INFO, gtk.BUTTONS_OK, pritext, sectext) - self.set_modal(False) - self.set_transient_for(gajim.interface.roster.window) - self.popup() + def __init__(self, pritext, sectext=''): + HigDialog.__init__(self, None, + gtk.MESSAGE_INFO, gtk.BUTTONS_OK, pritext, sectext) + self.set_modal(False) + self.set_transient_for(gajim.interface.roster.window) + self.popup() class ErrorDialog(HigDialog): - """ - HIG compliant error dialog - """ + """ + HIG compliant error dialog + """ - def __init__(self, pritext, sectext=''): - HigDialog.__init__( self, None, - gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, pritext, sectext) - self.popup() + def __init__(self, pritext, sectext=''): + HigDialog.__init__( self, None, + gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, pritext, sectext) + self.popup() class YesNoDialog(HigDialog): - """ - HIG compliant YesNo dialog - """ + """ + HIG compliant YesNo dialog + """ - def __init__(self, pritext, sectext='', checktext='', on_response_yes=None, - on_response_no=None): - self.user_response_yes = on_response_yes - self.user_response_no = on_response_no - HigDialog.__init__( self, None, - gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, pritext, sectext, - on_response_yes=self.on_response_yes, - on_response_no=self.on_response_no) + def __init__(self, pritext, sectext='', checktext='', on_response_yes=None, + on_response_no=None): + self.user_response_yes = on_response_yes + self.user_response_no = on_response_no + HigDialog.__init__( self, None, + gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, pritext, sectext, + on_response_yes=self.on_response_yes, + on_response_no=self.on_response_no) - if checktext: - self.checkbutton = gtk.CheckButton(checktext) - self.vbox.pack_start(self.checkbutton, expand=False, fill=True) - else: - self.checkbutton = None - self.set_modal(False) - self.popup() + if checktext: + self.checkbutton = gtk.CheckButton(checktext) + self.vbox.pack_start(self.checkbutton, expand=False, fill=True) + else: + self.checkbutton = None + self.set_modal(False) + self.popup() - def on_response_yes(self, widget): - if self.user_response_yes: - if isinstance(self.user_response_yes, tuple): - self.user_response_yes[0](self.is_checked(), - *self.user_response_yes[1:]) - else: - self.user_response_yes(self.is_checked()) - self.call_cancel_on_destroy = False - self.destroy() + def on_response_yes(self, widget): + if self.user_response_yes: + if isinstance(self.user_response_yes, tuple): + self.user_response_yes[0](self.is_checked(), + *self.user_response_yes[1:]) + else: + self.user_response_yes(self.is_checked()) + self.call_cancel_on_destroy = False + self.destroy() - def on_response_no(self, widget): - if self.user_response_no: - if isinstance(self.user_response_no, tuple): - self.user_response_no[0](*self.user_response_no[1:]) - else: - self.user_response_no() - self.call_cancel_on_destroy = False - self.destroy() + def on_response_no(self, widget): + if self.user_response_no: + if isinstance(self.user_response_no, tuple): + self.user_response_no[0](*self.user_response_no[1:]) + else: + self.user_response_no() + self.call_cancel_on_destroy = False + self.destroy() - def is_checked(self): - """ - Get active state of the checkbutton - """ - if not self.checkbutton: - return False - return self.checkbutton.get_active() + def is_checked(self): + """ + Get active state of the checkbutton + """ + if not self.checkbutton: + return False + return self.checkbutton.get_active() class ConfirmationDialogCheck(ConfirmationDialog): - """ - HIG compliant confirmation dialog with checkbutton - """ + """ + HIG compliant confirmation dialog with checkbutton + """ - def __init__(self, pritext, sectext='', checktext='', on_response_ok=None, - on_response_cancel=None, is_modal=True): - self.user_response_ok = on_response_ok - self.user_response_cancel = on_response_cancel + def __init__(self, pritext, sectext='', checktext='', on_response_ok=None, + on_response_cancel=None, is_modal=True): + self.user_response_ok = on_response_ok + self.user_response_cancel = on_response_cancel - HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION, - gtk.BUTTONS_OK_CANCEL, pritext, sectext, self.on_response_ok, - self.on_response_cancel) + HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION, + gtk.BUTTONS_OK_CANCEL, pritext, sectext, self.on_response_ok, + self.on_response_cancel) - self.set_default_response(gtk.RESPONSE_OK) + self.set_default_response(gtk.RESPONSE_OK) - ok_button = self.action_area.get_children()[0] # right to left - ok_button.grab_focus() + ok_button = self.action_area.get_children()[0] # right to left + ok_button.grab_focus() - self.checkbutton = gtk.CheckButton(checktext) - self.vbox.pack_start(self.checkbutton, expand=False, fill=True) - self.set_modal(is_modal) - self.popup() + self.checkbutton = gtk.CheckButton(checktext) + self.vbox.pack_start(self.checkbutton, expand=False, fill=True) + self.set_modal(is_modal) + self.popup() - def on_response_ok(self, widget): - if self.user_response_ok: - if isinstance(self.user_response_ok, tuple): - self.user_response_ok[0](self.is_checked(), - *self.user_response_ok[1:]) - else: - self.user_response_ok(self.is_checked()) - self.call_cancel_on_destroy = False - self.destroy() + def on_response_ok(self, widget): + if self.user_response_ok: + if isinstance(self.user_response_ok, tuple): + self.user_response_ok[0](self.is_checked(), + *self.user_response_ok[1:]) + else: + self.user_response_ok(self.is_checked()) + self.call_cancel_on_destroy = False + self.destroy() - def on_response_cancel(self, widget): - if self.user_response_cancel: - if isinstance(self.user_response_cancel, tuple): - self.user_response_cancel[0](self.is_checked(), - *self.user_response_cancel[1:]) - else: - self.user_response_cancel(self.is_checked()) - self.call_cancel_on_destroy = False - self.destroy() + def on_response_cancel(self, widget): + if self.user_response_cancel: + if isinstance(self.user_response_cancel, tuple): + self.user_response_cancel[0](self.is_checked(), + *self.user_response_cancel[1:]) + else: + self.user_response_cancel(self.is_checked()) + self.call_cancel_on_destroy = False + self.destroy() - def is_checked(self): - """ - Get active state of the checkbutton - """ - return self.checkbutton.get_active() + def is_checked(self): + """ + Get active state of the checkbutton + """ + return self.checkbutton.get_active() class ConfirmationDialogDubbleCheck(ConfirmationDialog): - """ - HIG compliant confirmation dialog with 2 checkbuttons - """ + """ + HIG compliant confirmation dialog with 2 checkbuttons + """ - def __init__(self, pritext, sectext='', checktext1='', checktext2='', - on_response_ok=None, on_response_cancel=None, is_modal=True): - self.user_response_ok = on_response_ok - self.user_response_cancel = on_response_cancel + def __init__(self, pritext, sectext='', checktext1='', checktext2='', + on_response_ok=None, on_response_cancel=None, is_modal=True): + self.user_response_ok = on_response_ok + self.user_response_cancel = on_response_cancel - HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION, - gtk.BUTTONS_OK_CANCEL, pritext, sectext, self.on_response_ok, - self.on_response_cancel) + HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION, + gtk.BUTTONS_OK_CANCEL, pritext, sectext, self.on_response_ok, + self.on_response_cancel) - self.set_default_response(gtk.RESPONSE_OK) + self.set_default_response(gtk.RESPONSE_OK) - ok_button = self.action_area.get_children()[0] # right to left - ok_button.grab_focus() + ok_button = self.action_area.get_children()[0] # right to left + ok_button.grab_focus() - if checktext1: - self.checkbutton1 = gtk.CheckButton(checktext1) - self.vbox.pack_start(self.checkbutton1, expand=False, fill=True) - else: - self.checkbutton1 = None - if checktext2: - self.checkbutton2 = gtk.CheckButton(checktext2) - self.vbox.pack_start(self.checkbutton2, expand=False, fill=True) - else: - self.checkbutton2 = None + if checktext1: + self.checkbutton1 = gtk.CheckButton(checktext1) + self.vbox.pack_start(self.checkbutton1, expand=False, fill=True) + else: + self.checkbutton1 = None + if checktext2: + self.checkbutton2 = gtk.CheckButton(checktext2) + self.vbox.pack_start(self.checkbutton2, expand=False, fill=True) + else: + self.checkbutton2 = None - self.set_modal(is_modal) - self.popup() + self.set_modal(is_modal) + self.popup() - def on_response_ok(self, widget): - if self.user_response_ok: - if isinstance(self.user_response_ok, tuple): - self.user_response_ok[0](self.is_checked(), - *self.user_response_ok[1:]) - else: - self.user_response_ok(self.is_checked()) - self.call_cancel_on_destroy = False - self.destroy() + def on_response_ok(self, widget): + if self.user_response_ok: + if isinstance(self.user_response_ok, tuple): + self.user_response_ok[0](self.is_checked(), + *self.user_response_ok[1:]) + else: + self.user_response_ok(self.is_checked()) + self.call_cancel_on_destroy = False + self.destroy() - def on_response_cancel(self, widget): - if self.user_response_cancel: - if isinstance(self.user_response_cancel, tuple): - self.user_response_cancel[0](*self.user_response_cancel[1:]) - else: - self.user_response_cancel() - self.call_cancel_on_destroy = False - self.destroy() + def on_response_cancel(self, widget): + if self.user_response_cancel: + if isinstance(self.user_response_cancel, tuple): + self.user_response_cancel[0](*self.user_response_cancel[1:]) + else: + self.user_response_cancel() + self.call_cancel_on_destroy = False + self.destroy() - def is_checked(self): - ''' Get active state of the checkbutton ''' - if self.checkbutton1: - is_checked_1 = self.checkbutton1.get_active() - else: - is_checked_1 = False - if self.checkbutton2: - is_checked_2 = self.checkbutton2.get_active() - else: - is_checked_2 = False - return [is_checked_1, is_checked_2] + def is_checked(self): + ''' Get active state of the checkbutton ''' + if self.checkbutton1: + is_checked_1 = self.checkbutton1.get_active() + else: + is_checked_1 = False + if self.checkbutton2: + is_checked_2 = self.checkbutton2.get_active() + else: + is_checked_2 = False + return [is_checked_1, is_checked_2] class ConfirmationDialogDoubleRadio(ConfirmationDialog): - """ - HIG compliant confirmation dialog with 2 radios - """ + """ + HIG compliant confirmation dialog with 2 radios + """ - def __init__(self, pritext, sectext='', radiotext1='', radiotext2='', - on_response_ok=None, on_response_cancel=None, is_modal=True): - self.user_response_ok = on_response_ok - self.user_response_cancel = on_response_cancel + def __init__(self, pritext, sectext='', radiotext1='', radiotext2='', + on_response_ok=None, on_response_cancel=None, is_modal=True): + self.user_response_ok = on_response_ok + self.user_response_cancel = on_response_cancel - HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION, - gtk.BUTTONS_OK_CANCEL, pritext, sectext, self.on_response_ok, - self.on_response_cancel) + HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION, + gtk.BUTTONS_OK_CANCEL, pritext, sectext, self.on_response_ok, + self.on_response_cancel) - self.set_default_response(gtk.RESPONSE_OK) + self.set_default_response(gtk.RESPONSE_OK) - ok_button = self.action_area.get_children()[0] # right to left - ok_button.grab_focus() + ok_button = self.action_area.get_children()[0] # right to left + ok_button.grab_focus() - self.radiobutton1 = gtk.RadioButton(label=radiotext1) - self.vbox.pack_start(self.radiobutton1, expand=False, fill=True) + self.radiobutton1 = gtk.RadioButton(label=radiotext1) + self.vbox.pack_start(self.radiobutton1, expand=False, fill=True) - self.radiobutton2 = gtk.RadioButton(group=self.radiobutton1, - label=radiotext2) - self.vbox.pack_start(self.radiobutton2, expand=False, fill=True) + self.radiobutton2 = gtk.RadioButton(group=self.radiobutton1, + label=radiotext2) + self.vbox.pack_start(self.radiobutton2, expand=False, fill=True) - self.set_modal(is_modal) - self.popup() + self.set_modal(is_modal) + self.popup() - def on_response_ok(self, widget): - if self.user_response_ok: - if isinstance(self.user_response_ok, tuple): - self.user_response_ok[0](self.is_checked(), - *self.user_response_ok[1:]) - else: - self.user_response_ok(self.is_checked()) - self.call_cancel_on_destroy = False - self.destroy() + def on_response_ok(self, widget): + if self.user_response_ok: + if isinstance(self.user_response_ok, tuple): + self.user_response_ok[0](self.is_checked(), + *self.user_response_ok[1:]) + else: + self.user_response_ok(self.is_checked()) + self.call_cancel_on_destroy = False + self.destroy() - def on_response_cancel(self, widget): - if self.user_response_cancel: - if isinstance(self.user_response_cancel, tuple): - self.user_response_cancel[0](*self.user_response_cancel[1:]) - else: - self.user_response_cancel() - self.call_cancel_on_destroy = False - self.destroy() + def on_response_cancel(self, widget): + if self.user_response_cancel: + if isinstance(self.user_response_cancel, tuple): + self.user_response_cancel[0](*self.user_response_cancel[1:]) + else: + self.user_response_cancel() + self.call_cancel_on_destroy = False + self.destroy() - def is_checked(self): - ''' Get active state of the checkbutton ''' - if self.radiobutton1: - is_checked_1 = self.radiobutton1.get_active() - else: - is_checked_1 = False - if self.radiobutton2: - is_checked_2 = self.radiobutton2.get_active() - else: - is_checked_2 = False - return [is_checked_1, is_checked_2] + def is_checked(self): + ''' Get active state of the checkbutton ''' + if self.radiobutton1: + is_checked_1 = self.radiobutton1.get_active() + else: + is_checked_1 = False + if self.radiobutton2: + is_checked_2 = self.radiobutton2.get_active() + else: + is_checked_2 = False + return [is_checked_1, is_checked_2] class FTOverwriteConfirmationDialog(ConfirmationDialog): - """ - HIG compliant confirmation dialog to overwrite or resume a file transfert - """ + """ + HIG compliant confirmation dialog to overwrite or resume a file transfert + """ - def __init__(self, pritext, sectext='', propose_resume=True, - on_response=None): - HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION, gtk.BUTTONS_CANCEL, - pritext, sectext) + def __init__(self, pritext, sectext='', propose_resume=True, + on_response=None): + HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION, gtk.BUTTONS_CANCEL, + pritext, sectext) - self.on_response = on_response + self.on_response = on_response - if propose_resume: - b = gtk.Button('', gtk.STOCK_REFRESH) - align = b.get_children()[0] - hbox = align.get_children()[0] - label = hbox.get_children()[1] - label.set_text('_Resume') - label.set_use_underline(True) - self.add_action_widget(b, 100) + if propose_resume: + b = gtk.Button('', gtk.STOCK_REFRESH) + align = b.get_children()[0] + hbox = align.get_children()[0] + label = hbox.get_children()[1] + label.set_text('_Resume') + label.set_use_underline(True) + self.add_action_widget(b, 100) - b = gtk.Button('', gtk.STOCK_SAVE_AS) - align = b.get_children()[0] - hbox = align.get_children()[0] - label = hbox.get_children()[1] - label.set_text('Re_place') - label.set_use_underline(True) - self.add_action_widget(b, 200) + b = gtk.Button('', gtk.STOCK_SAVE_AS) + align = b.get_children()[0] + hbox = align.get_children()[0] + label = hbox.get_children()[1] + label.set_text('Re_place') + label.set_use_underline(True) + self.add_action_widget(b, 200) - self.connect('response', self.on_dialog_response) - self.show_all() + self.connect('response', self.on_dialog_response) + self.show_all() - def on_dialog_response(self, dialog, response): - if self.on_response: - if isinstance(self.on_response, tuple): - self.on_response[0](response, *self.on_response[1:]) - else: - self.on_response(response) - self.call_cancel_on_destroy = False - self.destroy() + def on_dialog_response(self, dialog, response): + if self.on_response: + if isinstance(self.on_response, tuple): + self.on_response[0](response, *self.on_response[1:]) + else: + self.on_response(response) + self.call_cancel_on_destroy = False + self.destroy() class CommonInputDialog: - """ - Common Class for Input dialogs - """ - - def __init__(self, title, label_str, is_modal, ok_handler, cancel_handler): - self.dialog = self.xml.get_object('input_dialog') - label = self.xml.get_object('label') - self.dialog.set_title(title) - label.set_markup(label_str) - self.cancel_handler = cancel_handler - self.vbox = self.xml.get_object('vbox') + """ + Common Class for Input dialogs + """ - self.ok_handler = ok_handler - okbutton = self.xml.get_object('okbutton') - okbutton.connect('clicked', self.on_okbutton_clicked) - cancelbutton = self.xml.get_object('cancelbutton') - cancelbutton.connect('clicked', self.on_cancelbutton_clicked) - self.xml.connect_signals(self) - self.dialog.show_all() + def __init__(self, title, label_str, is_modal, ok_handler, cancel_handler): + self.dialog = self.xml.get_object('input_dialog') + label = self.xml.get_object('label') + self.dialog.set_title(title) + label.set_markup(label_str) + self.cancel_handler = cancel_handler + self.vbox = self.xml.get_object('vbox') - def on_input_dialog_destroy(self, widget): - if self.cancel_handler: - self.cancel_handler() + self.ok_handler = ok_handler + okbutton = self.xml.get_object('okbutton') + okbutton.connect('clicked', self.on_okbutton_clicked) + cancelbutton = self.xml.get_object('cancelbutton') + cancelbutton.connect('clicked', self.on_cancelbutton_clicked) + self.xml.connect_signals(self) + self.dialog.show_all() - def on_okbutton_clicked(self, widget): - user_input = self.get_text() - if user_input: - user_input = user_input.decode('utf-8') - self.cancel_handler = None - self.dialog.destroy() - if isinstance(self.ok_handler, tuple): - self.ok_handler[0](user_input, *self.ok_handler[1:]) - else: - self.ok_handler(user_input) + def on_input_dialog_destroy(self, widget): + if self.cancel_handler: + self.cancel_handler() - def on_cancelbutton_clicked(self, widget): - self.dialog.destroy() + def on_okbutton_clicked(self, widget): + user_input = self.get_text() + if user_input: + user_input = user_input.decode('utf-8') + self.cancel_handler = None + self.dialog.destroy() + if isinstance(self.ok_handler, tuple): + self.ok_handler[0](user_input, *self.ok_handler[1:]) + else: + self.ok_handler(user_input) + + def on_cancelbutton_clicked(self, widget): + self.dialog.destroy() class InputDialog(CommonInputDialog): - """ - Class for Input dialog - """ + """ + Class for Input dialog + """ - def __init__(self, title, label_str, input_str=None, is_modal=True, - ok_handler=None, cancel_handler=None): - self.xml = gtkgui_helpers.get_gtk_builder('input_dialog.ui') - CommonInputDialog.__init__(self, title, label_str, is_modal, ok_handler, - cancel_handler) - self.input_entry = self.xml.get_object('input_entry') - if input_str: - self.set_entry(input_str) + def __init__(self, title, label_str, input_str=None, is_modal=True, + ok_handler=None, cancel_handler=None): + self.xml = gtkgui_helpers.get_gtk_builder('input_dialog.ui') + CommonInputDialog.__init__(self, title, label_str, is_modal, ok_handler, + cancel_handler) + self.input_entry = self.xml.get_object('input_entry') + if input_str: + self.set_entry(input_str) - def set_entry(self, value): - self.input_entry.set_text(value) - self.input_entry.select_region(0, -1) # select all + def set_entry(self, value): + self.input_entry.set_text(value) + self.input_entry.select_region(0, -1) # select all - def get_text(self): - return self.input_entry.get_text().decode('utf-8') + def get_text(self): + return self.input_entry.get_text().decode('utf-8') class InputDialogCheck(InputDialog): - """ - Class for Input dialog - """ + """ + Class for Input dialog + """ - def __init__(self, title, label_str, checktext='', input_str=None, - is_modal=True, ok_handler=None, cancel_handler=None): - self.xml = gtkgui_helpers.get_gtk_builder('input_dialog.ui') - InputDialog.__init__(self, title, label_str, input_str=input_str, - is_modal=is_modal, ok_handler=ok_handler, - cancel_handler=cancel_handler) - self.input_entry = self.xml.get_object('input_entry') - if input_str: - self.input_entry.set_text(input_str) - self.input_entry.select_region(0, -1) # select all + def __init__(self, title, label_str, checktext='', input_str=None, + is_modal=True, ok_handler=None, cancel_handler=None): + self.xml = gtkgui_helpers.get_gtk_builder('input_dialog.ui') + InputDialog.__init__(self, title, label_str, input_str=input_str, + is_modal=is_modal, ok_handler=ok_handler, + cancel_handler=cancel_handler) + self.input_entry = self.xml.get_object('input_entry') + if input_str: + self.input_entry.set_text(input_str) + self.input_entry.select_region(0, -1) # select all - if checktext: - self.checkbutton = gtk.CheckButton(checktext) - self.vbox.pack_start(self.checkbutton, expand=False, fill=True) - self.checkbutton.show() + if checktext: + self.checkbutton = gtk.CheckButton(checktext) + self.vbox.pack_start(self.checkbutton, expand=False, fill=True) + self.checkbutton.show() - def on_okbutton_clicked(self, widget): - user_input = self.get_text() - if user_input: - user_input = user_input.decode('utf-8') - self.cancel_handler = None - self.dialog.destroy() - if isinstance(self.ok_handler, tuple): - self.ok_handler[0](user_input, self.is_checked(), *self.ok_handler[1:]) - else: - self.ok_handler(user_input, self.is_checked()) + def on_okbutton_clicked(self, widget): + user_input = self.get_text() + if user_input: + user_input = user_input.decode('utf-8') + self.cancel_handler = None + self.dialog.destroy() + if isinstance(self.ok_handler, tuple): + self.ok_handler[0](user_input, self.is_checked(), *self.ok_handler[1:]) + else: + self.ok_handler(user_input, self.is_checked()) - def get_text(self): - return self.input_entry.get_text().decode('utf-8') + def get_text(self): + return self.input_entry.get_text().decode('utf-8') - def is_checked(self): - """ - Get active state of the checkbutton - """ - try: - return self.checkbutton.get_active() - except Exception: - # There is no checkbutton - return False + def is_checked(self): + """ + Get active state of the checkbutton + """ + try: + return self.checkbutton.get_active() + except Exception: + # There is no checkbutton + return False class ChangeNickDialog(InputDialogCheck): - """ - Class for changing room nickname in case of conflict - """ + """ + Class for changing room nickname in case of conflict + """ - def __init__(self, account, room_jid, title, prompt, check_text=None): - InputDialogCheck.__init__(self, title, '', checktext=check_text, - input_str='', is_modal=True, ok_handler=None, cancel_handler=None) - self.room_queue = [(account, room_jid, prompt)] - self.check_next() + def __init__(self, account, room_jid, title, prompt, check_text=None): + InputDialogCheck.__init__(self, title, '', checktext=check_text, + input_str='', is_modal=True, ok_handler=None, cancel_handler=None) + self.room_queue = [(account, room_jid, prompt)] + self.check_next() - def on_input_dialog_delete_event(self, widget, event): - self.on_cancelbutton_clicked(widget) - return True + def on_input_dialog_delete_event(self, widget, event): + self.on_cancelbutton_clicked(widget) + return True - def setup_dialog(self): - self.gc_control = gajim.interface.msg_win_mgr.get_gc_control( - self.room_jid, self.account) - if not self.gc_control and \ - self.room_jid in gajim.interface.minimized_controls[self.account]: - self.gc_control = \ - gajim.interface.minimized_controls[self.account][self.room_jid] - if not self.gc_control: - self.check_next() - return - label = self.xml.get_object('label') - label.set_markup(self.prompt) - self.set_entry(self.gc_control.nick + \ - gajim.config.get('gc_proposed_nick_char')) + def setup_dialog(self): + self.gc_control = gajim.interface.msg_win_mgr.get_gc_control( + self.room_jid, self.account) + if not self.gc_control and \ + self.room_jid in gajim.interface.minimized_controls[self.account]: + self.gc_control = \ + gajim.interface.minimized_controls[self.account][self.room_jid] + if not self.gc_control: + self.check_next() + return + label = self.xml.get_object('label') + label.set_markup(self.prompt) + self.set_entry(self.gc_control.nick + \ + gajim.config.get('gc_proposed_nick_char')) - def check_next(self): - if len(self.room_queue) == 0: - self.cancel_handler = None - self.dialog.destroy() - if 'change_nick_dialog' in gajim.interface.instances: - del gajim.interface.instances['change_nick_dialog'] - return - self.account, self.room_jid, self.prompt = self.room_queue.pop(0) - self.setup_dialog() + def check_next(self): + if len(self.room_queue) == 0: + self.cancel_handler = None + self.dialog.destroy() + if 'change_nick_dialog' in gajim.interface.instances: + del gajim.interface.instances['change_nick_dialog'] + return + self.account, self.room_jid, self.prompt = self.room_queue.pop(0) + self.setup_dialog() - if gajim.new_room_nick is not None and not gajim.gc_connected[ - self.account][self.room_jid] and self.gc_control.nick != \ - gajim.new_room_nick: - self.dialog.hide() - self.on_ok(gajim.new_room_nick, True) - else: - self.dialog.show() + if gajim.new_room_nick is not None and not gajim.gc_connected[ + self.account][self.room_jid] and self.gc_control.nick != \ + gajim.new_room_nick: + self.dialog.hide() + self.on_ok(gajim.new_room_nick, True) + else: + self.dialog.show() - def on_okbutton_clicked(self, widget): - nick = self.get_text() - if nick: - nick = nick.decode('utf-8') - # send presence to room - try: - nick = helpers.parse_resource(nick) - except Exception: - # invalid char - dialogs.ErrorDialog(_('Invalid nickname'), - _('The nickname has not allowed characters.')) - return - self.on_ok(nick, self.is_checked()) + def on_okbutton_clicked(self, widget): + nick = self.get_text() + if nick: + nick = nick.decode('utf-8') + # send presence to room + try: + nick = helpers.parse_resource(nick) + except Exception: + # invalid char + dialogs.ErrorDialog(_('Invalid nickname'), + _('The nickname has not allowed characters.')) + return + self.on_ok(nick, self.is_checked()) - def on_ok(self, nick, is_checked): - if is_checked: - gajim.new_room_nick = nick - gajim.connections[self.account].join_gc(nick, self.room_jid, None, - change_nick=True) - if gajim.gc_connected[self.account][self.room_jid]: - # We are changing nick, we will change self.nick when we receive - # presence that inform that it works - self.gc_control.new_nick = nick - else: - # We are connecting, we will not get a changed nick presence so - # change it NOW. We don't already have a nick so it's harmless - self.gc_control.nick = nick - self.check_next() + def on_ok(self, nick, is_checked): + if is_checked: + gajim.new_room_nick = nick + gajim.connections[self.account].join_gc(nick, self.room_jid, None, + change_nick=True) + if gajim.gc_connected[self.account][self.room_jid]: + # We are changing nick, we will change self.nick when we receive + # presence that inform that it works + self.gc_control.new_nick = nick + else: + # We are connecting, we will not get a changed nick presence so + # change it NOW. We don't already have a nick so it's harmless + self.gc_control.nick = nick + self.check_next() - def on_cancelbutton_clicked(self, widget): - self.gc_control.new_nick = '' - self.check_next() + def on_cancelbutton_clicked(self, widget): + self.gc_control.new_nick = '' + self.check_next() - def add_room(self, account, room_jid, prompt): - if (account, room_jid, prompt) not in self.room_queue: - self.room_queue.append((account, room_jid, prompt)) + def add_room(self, account, room_jid, prompt): + if (account, room_jid, prompt) not in self.room_queue: + self.room_queue.append((account, room_jid, prompt)) class InputTextDialog(CommonInputDialog): - """ - Class for multilines Input dialog (more place than InputDialog) - """ + """ + Class for multilines Input dialog (more place than InputDialog) + """ - def __init__(self, title, label_str, input_str=None, is_modal=True, - ok_handler=None, cancel_handler=None): - self.xml = gtkgui_helpers.get_gtk_builder('input_text_dialog.ui') - CommonInputDialog.__init__(self, title, label_str, is_modal, ok_handler, - cancel_handler) - self.input_buffer = self.xml.get_object('input_textview').get_buffer() - if input_str: - self.input_buffer.set_text(input_str) - start_iter, end_iter = self.input_buffer.get_bounds() - self.input_buffer.select_range(start_iter, end_iter) # select all + def __init__(self, title, label_str, input_str=None, is_modal=True, + ok_handler=None, cancel_handler=None): + self.xml = gtkgui_helpers.get_gtk_builder('input_text_dialog.ui') + CommonInputDialog.__init__(self, title, label_str, is_modal, ok_handler, + cancel_handler) + self.input_buffer = self.xml.get_object('input_textview').get_buffer() + if input_str: + self.input_buffer.set_text(input_str) + start_iter, end_iter = self.input_buffer.get_bounds() + self.input_buffer.select_range(start_iter, end_iter) # select all - def get_text(self): - start_iter, end_iter = self.input_buffer.get_bounds() - return self.input_buffer.get_text(start_iter, end_iter).decode('utf-8') + def get_text(self): + start_iter, end_iter = self.input_buffer.get_bounds() + return self.input_buffer.get_text(start_iter, end_iter).decode('utf-8') class DubbleInputDialog: - """ - Class for Dubble Input dialog - """ + """ + Class for Dubble Input dialog + """ - def __init__(self, title, label_str1, label_str2, input_str1=None, - input_str2=None, is_modal=True, ok_handler=None, cancel_handler=None): - self.xml = gtkgui_helpers.get_gtk_builder('dubbleinput_dialog.ui') - self.dialog = self.xml.get_object('dubbleinput_dialog') - label1 = self.xml.get_object('label1') - self.input_entry1 = self.xml.get_object('input_entry1') - label2 = self.xml.get_object('label2') - self.input_entry2 = self.xml.get_object('input_entry2') - self.dialog.set_title(title) - label1.set_markup(label_str1) - label2.set_markup(label_str2) - self.cancel_handler = cancel_handler - if input_str1: - self.input_entry1.set_text(input_str1) - self.input_entry1.select_region(0, -1) # select all - if input_str2: - self.input_entry2.set_text(input_str2) - self.input_entry2.select_region(0, -1) # select all + def __init__(self, title, label_str1, label_str2, input_str1=None, + input_str2=None, is_modal=True, ok_handler=None, cancel_handler=None): + self.xml = gtkgui_helpers.get_gtk_builder('dubbleinput_dialog.ui') + self.dialog = self.xml.get_object('dubbleinput_dialog') + label1 = self.xml.get_object('label1') + self.input_entry1 = self.xml.get_object('input_entry1') + label2 = self.xml.get_object('label2') + self.input_entry2 = self.xml.get_object('input_entry2') + self.dialog.set_title(title) + label1.set_markup(label_str1) + label2.set_markup(label_str2) + self.cancel_handler = cancel_handler + if input_str1: + self.input_entry1.set_text(input_str1) + self.input_entry1.select_region(0, -1) # select all + if input_str2: + self.input_entry2.set_text(input_str2) + self.input_entry2.select_region(0, -1) # select all - self.dialog.set_modal(is_modal) + self.dialog.set_modal(is_modal) - self.ok_handler = ok_handler - okbutton = self.xml.get_object('okbutton') - okbutton.connect('clicked', self.on_okbutton_clicked) - cancelbutton = self.xml.get_object('cancelbutton') - cancelbutton.connect('clicked', self.on_cancelbutton_clicked) - self.xml.connect_signals(self) - self.dialog.show_all() + self.ok_handler = ok_handler + okbutton = self.xml.get_object('okbutton') + okbutton.connect('clicked', self.on_okbutton_clicked) + cancelbutton = self.xml.get_object('cancelbutton') + cancelbutton.connect('clicked', self.on_cancelbutton_clicked) + self.xml.connect_signals(self) + self.dialog.show_all() - def on_dubbleinput_dialog_destroy(self, widget): - if not self.cancel_handler: - return False - if isinstance(self.cancel_handler, tuple): - self.cancel_handler[0](*self.cancel_handler[1:]) - else: - self.cancel_handler() + def on_dubbleinput_dialog_destroy(self, widget): + if not self.cancel_handler: + return False + if isinstance(self.cancel_handler, tuple): + self.cancel_handler[0](*self.cancel_handler[1:]) + else: + self.cancel_handler() - def on_okbutton_clicked(self, widget): - user_input1 = self.input_entry1.get_text().decode('utf-8') - user_input2 = self.input_entry2.get_text().decode('utf-8') - self.dialog.destroy() - if not self.ok_handler: - return - if isinstance(self.ok_handler, tuple): - self.ok_handler[0](user_input1, user_input2, *self.ok_handler[1:]) - else: - self.ok_handler(user_input1, user_input2) + def on_okbutton_clicked(self, widget): + user_input1 = self.input_entry1.get_text().decode('utf-8') + user_input2 = self.input_entry2.get_text().decode('utf-8') + self.dialog.destroy() + if not self.ok_handler: + return + if isinstance(self.ok_handler, tuple): + self.ok_handler[0](user_input1, user_input2, *self.ok_handler[1:]) + else: + self.ok_handler(user_input1, user_input2) - def on_cancelbutton_clicked(self, widget): - self.dialog.destroy() - if not self.cancel_handler: - return - if isinstance(self.cancel_handler, tuple): - self.cancel_handler[0](*self.cancel_handler[1:]) - else: - self.cancel_handler() + def on_cancelbutton_clicked(self, widget): + self.dialog.destroy() + if not self.cancel_handler: + return + if isinstance(self.cancel_handler, tuple): + self.cancel_handler[0](*self.cancel_handler[1:]) + else: + self.cancel_handler() class SubscriptionRequestWindow: - def __init__(self, jid, text, account, user_nick=None): - xml = gtkgui_helpers.get_gtk_builder('subscription_request_window.ui') - self.window = xml.get_object('subscription_request_window') - self.jid = jid - self.account = account - self.user_nick = user_nick - if len(gajim.connections) >= 2: - prompt_text = \ - _('Subscription request for account %(account)s from %(jid)s')\ - % {'account': account, 'jid': self.jid} - else: - prompt_text = _('Subscription request from %s') % self.jid - xml.get_object('from_label').set_text(prompt_text) - xml.get_object('message_textview').get_buffer().set_text(text) - xml.connect_signals(self) - self.window.show_all() + def __init__(self, jid, text, account, user_nick=None): + xml = gtkgui_helpers.get_gtk_builder('subscription_request_window.ui') + self.window = xml.get_object('subscription_request_window') + self.jid = jid + self.account = account + self.user_nick = user_nick + if len(gajim.connections) >= 2: + prompt_text = \ + _('Subscription request for account %(account)s from %(jid)s')\ + % {'account': account, 'jid': self.jid} + else: + prompt_text = _('Subscription request from %s') % self.jid + xml.get_object('from_label').set_text(prompt_text) + xml.get_object('message_textview').get_buffer().set_text(text) + xml.connect_signals(self) + self.window.show_all() - def prepare_popup_menu(self): - xml = gtkgui_helpers.get_gtk_builder('subscription_request_popup_menu.ui') - menu = xml.get_object('subscription_request_popup_menu') - xml.connect_signals(self) - return menu + def prepare_popup_menu(self): + xml = gtkgui_helpers.get_gtk_builder('subscription_request_popup_menu.ui') + menu = xml.get_object('subscription_request_popup_menu') + xml.connect_signals(self) + return menu - def on_close_button_clicked(self, widget): - self.window.destroy() + def on_close_button_clicked(self, widget): + self.window.destroy() - def on_authorize_button_clicked(self, widget): - """ - Accept the request - """ - gajim.connections[self.account].send_authorization(self.jid) - self.window.destroy() - contact = gajim.contacts.get_contact(self.account, self.jid) - if not contact or _('Not in Roster') in contact.groups: - AddNewContactWindow(self.account, self.jid, self.user_nick) + def on_authorize_button_clicked(self, widget): + """ + Accept the request + """ + gajim.connections[self.account].send_authorization(self.jid) + self.window.destroy() + contact = gajim.contacts.get_contact(self.account, self.jid) + if not contact or _('Not in Roster') in contact.groups: + AddNewContactWindow(self.account, self.jid, self.user_nick) - def on_contact_info_activate(self, widget): - """ - Ask vcard - """ - if self.jid in gajim.interface.instances[self.account]['infos']: - gajim.interface.instances[self.account]['infos'][self.jid].window.present() - else: - contact = gajim.contacts.create_contact(jid=self.jid, account=self.account) - gajim.interface.instances[self.account]['infos'][self.jid] = \ - vcard.VcardWindow(contact, self.account) - # Remove jabber page - gajim.interface.instances[self.account]['infos'][self.jid].xml.\ - get_object('information_notebook').remove_page(0) + def on_contact_info_activate(self, widget): + """ + Ask vcard + """ + if self.jid in gajim.interface.instances[self.account]['infos']: + gajim.interface.instances[self.account]['infos'][self.jid].window.present() + else: + contact = gajim.contacts.create_contact(jid=self.jid, account=self.account) + gajim.interface.instances[self.account]['infos'][self.jid] = \ + vcard.VcardWindow(contact, self.account) + # Remove jabber page + gajim.interface.instances[self.account]['infos'][self.jid].xml.\ + get_object('information_notebook').remove_page(0) - def on_start_chat_activate(self, widget): - """ - Open chat - """ - gajim.interface.new_chat_from_jid(self.account, self.jid) + def on_start_chat_activate(self, widget): + """ + Open chat + """ + gajim.interface.new_chat_from_jid(self.account, self.jid) - def on_deny_button_clicked(self, widget): - """ - Refuse the request - """ - gajim.connections[self.account].refuse_authorization(self.jid) - contact = gajim.contacts.get_contact(self.account, self.jid) - if contact and _('Not in Roster') in contact.get_shown_groups(): - gajim.interface.roster.remove_contact(self.jid, self.account) - self.window.destroy() + def on_deny_button_clicked(self, widget): + """ + Refuse the request + """ + gajim.connections[self.account].refuse_authorization(self.jid) + contact = gajim.contacts.get_contact(self.account, self.jid) + if contact and _('Not in Roster') in contact.get_shown_groups(): + gajim.interface.roster.remove_contact(self.jid, self.account) + self.window.destroy() - def on_actions_button_clicked(self, widget): - """ - Popup action menu - """ - menu = self.prepare_popup_menu() - menu.show_all() - gtkgui_helpers.popup_emoticons_under_button(menu, widget, self.window.window) + def on_actions_button_clicked(self, widget): + """ + Popup action menu + """ + menu = self.prepare_popup_menu() + menu.show_all() + gtkgui_helpers.popup_emoticons_under_button(menu, widget, self.window.window) class JoinGroupchatWindow: - def __init__(self, account=None, room_jid='', nick='', password='', - automatic=False): - """ - Automatic is a dict like {'invities': []}. If automatic is not empty, - this means room must be automaticaly configured and when done, invities - must be automatically invited - """ - if account: - if room_jid != '' and room_jid in gajim.gc_connected[account] and\ - gajim.gc_connected[account][room_jid]: - ErrorDialog(_('You are already in group chat %s') % room_jid) - raise GajimGeneralException, 'You are already in this group chat' - if nick == '': - nick = gajim.nicks[account] - if gajim.connections[account].connected < 2: - ErrorDialog(_('You are not connected to the server'), - _('You can not join a group chat unless you are connected.')) - raise GajimGeneralException, 'You must be connected to join a groupchat' + def __init__(self, account=None, room_jid='', nick='', password='', + automatic=False): + """ + Automatic is a dict like {'invities': []}. If automatic is not empty, + this means room must be automaticaly configured and when done, invities + must be automatically invited + """ + if account: + if room_jid != '' and room_jid in gajim.gc_connected[account] and\ + gajim.gc_connected[account][room_jid]: + ErrorDialog(_('You are already in group chat %s') % room_jid) + raise GajimGeneralException, 'You are already in this group chat' + if nick == '': + nick = gajim.nicks[account] + if gajim.connections[account].connected < 2: + ErrorDialog(_('You are not connected to the server'), + _('You can not join a group chat unless you are connected.')) + raise GajimGeneralException, 'You must be connected to join a groupchat' - self.xml = gtkgui_helpers.get_gtk_builder('join_groupchat_window.ui') - - account_label = self.xml.get_object('account_label') - account_combobox = self.xml.get_object('account_combobox') - account_label.set_no_show_all(False) - account_combobox.set_no_show_all(False) - liststore = gtk.ListStore(str) - account_combobox.set_model(liststore) - cell = gtk.CellRendererText() - account_combobox.pack_start(cell, True) - account_combobox.add_attribute(cell, 'text', 0) - account_combobox.set_active(-1) - - # Add accounts, set current as active if it matches 'account' - for acct in [a for a in gajim.connections if \ - gajim.account_is_connected(a)]: - account_combobox.append_text(acct) - if account and account == acct: - account_combobox.set_active(liststore.iter_n_children(None)-1) - - self.account = account - self.automatic = automatic - self._empty_required_widgets = [] + self.xml = gtkgui_helpers.get_gtk_builder('join_groupchat_window.ui') - self.window = self.xml.get_object('join_groupchat_window') - self._room_jid_entry = self.xml.get_object('room_jid_entry') - self._nickname_entry = self.xml.get_object('nickname_entry') - self._password_entry = self.xml.get_object('password_entry') + account_label = self.xml.get_object('account_label') + account_combobox = self.xml.get_object('account_combobox') + account_label.set_no_show_all(False) + account_combobox.set_no_show_all(False) + liststore = gtk.ListStore(str) + account_combobox.set_model(liststore) + cell = gtk.CellRendererText() + account_combobox.pack_start(cell, True) + account_combobox.add_attribute(cell, 'text', 0) + account_combobox.set_active(-1) - self._room_jid_entry.set_text(room_jid) - self._nickname_entry.set_text(nick) - if password: - self._password_entry.set_text(password) - self.xml.connect_signals(self) - title = None - if account: - # now add us to open windows - gajim.interface.instances[account]['join_gc'] = self - if len(gajim.connections) > 1: - title = _('Join Group Chat with account %s') % account - if title is None: - title = _('Join Group Chat') - self.window.set_title(title) + # Add accounts, set current as active if it matches 'account' + for acct in [a for a in gajim.connections if \ + gajim.account_is_connected(a)]: + account_combobox.append_text(acct) + if account and account == acct: + account_combobox.set_active(liststore.iter_n_children(None)-1) - self.recently_combobox = self.xml.get_object('recently_combobox') - liststore = gtk.ListStore(str) - self.recently_combobox.set_model(liststore) - cell = gtk.CellRendererText() - self.recently_combobox.pack_start(cell, True) - self.recently_combobox.add_attribute(cell, 'text', 0) - self.recently_groupchat = gajim.config.get('recently_groupchat').split() - for g in self.recently_groupchat: - self.recently_combobox.append_text(g) - if len(self.recently_groupchat) == 0: - self.recently_combobox.set_sensitive(False) - elif room_jid == '': - self.recently_combobox.set_active(0) - self._room_jid_entry.select_region(0, -1) - elif room_jid != '': - self.xml.get_object('join_button').grab_focus() + self.account = account + self.automatic = automatic + self._empty_required_widgets = [] - if not self._room_jid_entry.get_text(): - self._empty_required_widgets.append(self._room_jid_entry) - if not self._nickname_entry.get_text(): - self._empty_required_widgets.append(self._nickname_entry) - if len(self._empty_required_widgets): - self.xml.get_object('join_button').set_sensitive(False) + self.window = self.xml.get_object('join_groupchat_window') + self._room_jid_entry = self.xml.get_object('room_jid_entry') + self._nickname_entry = self.xml.get_object('nickname_entry') + self._password_entry = self.xml.get_object('password_entry') - if account and not gajim.connections[account].private_storage_supported: - self.xml.get_object('bookmark_checkbutton').set_sensitive(False) + self._room_jid_entry.set_text(room_jid) + self._nickname_entry.set_text(nick) + if password: + self._password_entry.set_text(password) + self.xml.connect_signals(self) + title = None + if account: + # now add us to open windows + gajim.interface.instances[account]['join_gc'] = self + if len(gajim.connections) > 1: + title = _('Join Group Chat with account %s') % account + if title is None: + title = _('Join Group Chat') + self.window.set_title(title) - self.window.show_all() + self.recently_combobox = self.xml.get_object('recently_combobox') + liststore = gtk.ListStore(str) + self.recently_combobox.set_model(liststore) + cell = gtk.CellRendererText() + self.recently_combobox.pack_start(cell, True) + self.recently_combobox.add_attribute(cell, 'text', 0) + self.recently_groupchat = gajim.config.get('recently_groupchat').split() + for g in self.recently_groupchat: + self.recently_combobox.append_text(g) + if len(self.recently_groupchat) == 0: + self.recently_combobox.set_sensitive(False) + elif room_jid == '': + self.recently_combobox.set_active(0) + self._room_jid_entry.select_region(0, -1) + elif room_jid != '': + self.xml.get_object('join_button').grab_focus() - def on_join_groupchat_window_destroy(self, widget): - """ - Close window - """ - if self.account and 'join_gc' in gajim.interface.instances[self.account]: - # remove us from open windows - del gajim.interface.instances[self.account]['join_gc'] - gajim.interface.roster.window.present() + if not self._room_jid_entry.get_text(): + self._empty_required_widgets.append(self._room_jid_entry) + if not self._nickname_entry.get_text(): + self._empty_required_widgets.append(self._nickname_entry) + if len(self._empty_required_widgets): + self.xml.get_object('join_button').set_sensitive(False) - def on_join_groupchat_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: # ESCAPE - widget.destroy() + if account and not gajim.connections[account].private_storage_supported: + self.xml.get_object('bookmark_checkbutton').set_sensitive(False) - def on_required_entry_changed(self, widget): - if not widget.get_text(): - self._empty_required_widgets.append(widget) - self.xml.get_object('join_button').set_sensitive(False) - else: - if widget in self._empty_required_widgets: - self._empty_required_widgets.remove(widget) - if len(self._empty_required_widgets) == 0 and self.account: - self.xml.get_object('join_button').set_sensitive(True) + self.window.show_all() - def on_account_combobox_changed(self, widget): - model = widget.get_model() - iter_ = widget.get_active_iter() - self.account = model[iter_][0].decode('utf-8') - self.on_required_entry_changed(self._nickname_entry) + def on_join_groupchat_window_destroy(self, widget): + """ + Close window + """ + if self.account and 'join_gc' in gajim.interface.instances[self.account]: + # remove us from open windows + del gajim.interface.instances[self.account]['join_gc'] + gajim.interface.roster.window.present() - def on_recently_combobox_changed(self, widget): - model = widget.get_model() - iter_ = widget.get_active_iter() - room_jid = model[iter_][0].decode('utf-8') - self._room_jid_entry.set_text(room_jid) + def on_join_groupchat_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: # ESCAPE + widget.destroy() - def on_cancel_button_clicked(self, widget): - """ - When Cancel button is clicked - """ - self.window.destroy() + def on_required_entry_changed(self, widget): + if not widget.get_text(): + self._empty_required_widgets.append(widget) + self.xml.get_object('join_button').set_sensitive(False) + else: + if widget in self._empty_required_widgets: + self._empty_required_widgets.remove(widget) + if len(self._empty_required_widgets) == 0 and self.account: + self.xml.get_object('join_button').set_sensitive(True) - def on_bookmark_checkbutton_toggled(self, widget): - auto_join_checkbutton = self.xml.get_object('auto_join_checkbutton') - if widget.get_active(): - auto_join_checkbutton.set_sensitive(True) - else: - auto_join_checkbutton.set_sensitive(False) + def on_account_combobox_changed(self, widget): + model = widget.get_model() + iter_ = widget.get_active_iter() + self.account = model[iter_][0].decode('utf-8') + self.on_required_entry_changed(self._nickname_entry) - def on_join_button_clicked(self, widget): - """ - When Join button is clicked - """ - if not self.account: - ErrorDialog(_('Invalid Account'), - _('You have to choose an account from which you want to join the ' - 'groupchat.')) - return - nickname = self._nickname_entry.get_text().decode('utf-8') - room_jid = self._room_jid_entry.get_text().decode('utf-8') - password = self._password_entry.get_text().decode('utf-8') - try: - nickname = helpers.parse_resource(nickname) - except Exception: - ErrorDialog(_('Invalid Nickname'), - _('The nickname has not allowed characters.')) - return - user, server, resource = helpers.decompose_jid(room_jid) - if not user or not server or resource: - ErrorDialog(_('Invalid group chat Jabber ID'), - _('Please enter the group chat Jabber ID as room@server.')) - return - try: - room_jid = helpers.parse_jid(room_jid) - except Exception: - ErrorDialog(_('Invalid group chat Jabber ID'), - _('The group chat Jabber ID has not allowed characters.')) - return + def on_recently_combobox_changed(self, widget): + model = widget.get_model() + iter_ = widget.get_active_iter() + room_jid = model[iter_][0].decode('utf-8') + self._room_jid_entry.set_text(room_jid) - if gajim.interface.msg_win_mgr.has_window(room_jid, self.account): - ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid, - self.account) - if ctrl.type_id != message_control.TYPE_GC: - ErrorDialog(_('This is not a group chat'), - _('%s is not the name of a group chat.') % room_jid) - return - if room_jid in self.recently_groupchat: - self.recently_groupchat.remove(room_jid) - self.recently_groupchat.insert(0, room_jid) - if len(self.recently_groupchat) > 10: - self.recently_groupchat = self.recently_groupchat[0:10] - gajim.config.set('recently_groupchat', - ' '.join(self.recently_groupchat)) + def on_cancel_button_clicked(self, widget): + """ + When Cancel button is clicked + """ + self.window.destroy() - if self.xml.get_object('bookmark_checkbutton').get_active(): - if self.xml.get_object('auto_join_checkbutton').get_active(): - autojoin = '1' - else: - autojoin = '0' - # Add as bookmark, with autojoin and not minimized - name = gajim.get_nick_from_jid(room_jid) - gajim.interface.add_gc_bookmark(self.account, name, room_jid, autojoin, - '0', password, nickname) + def on_bookmark_checkbutton_toggled(self, widget): + auto_join_checkbutton = self.xml.get_object('auto_join_checkbutton') + if widget.get_active(): + auto_join_checkbutton.set_sensitive(True) + else: + auto_join_checkbutton.set_sensitive(False) - if self.automatic: - gajim.automatic_rooms[self.account][room_jid] = self.automatic - gajim.interface.join_gc_room(self.account, room_jid, nickname, password) + def on_join_button_clicked(self, widget): + """ + When Join button is clicked + """ + if not self.account: + ErrorDialog(_('Invalid Account'), + _('You have to choose an account from which you want to join the ' + 'groupchat.')) + return + nickname = self._nickname_entry.get_text().decode('utf-8') + room_jid = self._room_jid_entry.get_text().decode('utf-8') + password = self._password_entry.get_text().decode('utf-8') + try: + nickname = helpers.parse_resource(nickname) + except Exception: + ErrorDialog(_('Invalid Nickname'), + _('The nickname has not allowed characters.')) + return + user, server, resource = helpers.decompose_jid(room_jid) + if not user or not server or resource: + ErrorDialog(_('Invalid group chat Jabber ID'), + _('Please enter the group chat Jabber ID as room@server.')) + return + try: + room_jid = helpers.parse_jid(room_jid) + except Exception: + ErrorDialog(_('Invalid group chat Jabber ID'), + _('The group chat Jabber ID has not allowed characters.')) + return - self.window.destroy() + if gajim.interface.msg_win_mgr.has_window(room_jid, self.account): + ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid, + self.account) + if ctrl.type_id != message_control.TYPE_GC: + ErrorDialog(_('This is not a group chat'), + _('%s is not the name of a group chat.') % room_jid) + return + if room_jid in self.recently_groupchat: + self.recently_groupchat.remove(room_jid) + self.recently_groupchat.insert(0, room_jid) + if len(self.recently_groupchat) > 10: + self.recently_groupchat = self.recently_groupchat[0:10] + gajim.config.set('recently_groupchat', + ' '.join(self.recently_groupchat)) + + if self.xml.get_object('bookmark_checkbutton').get_active(): + if self.xml.get_object('auto_join_checkbutton').get_active(): + autojoin = '1' + else: + autojoin = '0' + # Add as bookmark, with autojoin and not minimized + name = gajim.get_nick_from_jid(room_jid) + gajim.interface.add_gc_bookmark(self.account, name, room_jid, autojoin, + '0', password, nickname) + + if self.automatic: + gajim.automatic_rooms[self.account][room_jid] = self.automatic + gajim.interface.join_gc_room(self.account, room_jid, nickname, password) + + self.window.destroy() class SynchroniseSelectAccountDialog: - def __init__(self, account): - # 'account' can be None if we are about to create our first one - if not account or gajim.connections[account].connected < 2: - ErrorDialog(_('You are not connected to the server'), - _('Without a connection, you can not synchronise your contacts.')) - raise GajimGeneralException, 'You are not connected to the server' - self.account = account - self.xml = gtkgui_helpers.get_gtk_builder('synchronise_select_account_dialog.ui') - self.dialog = self.xml.get_object('synchronise_select_account_dialog') - self.accounts_treeview = self.xml.get_object('accounts_treeview') - model = gtk.ListStore(str, str, bool) - self.accounts_treeview.set_model(model) - # columns - renderer = gtk.CellRendererText() - self.accounts_treeview.insert_column_with_attributes(-1, - _('Name'), renderer, text=0) - renderer = gtk.CellRendererText() - self.accounts_treeview.insert_column_with_attributes(-1, - _('Server'), renderer, text=1) + def __init__(self, account): + # 'account' can be None if we are about to create our first one + if not account or gajim.connections[account].connected < 2: + ErrorDialog(_('You are not connected to the server'), + _('Without a connection, you can not synchronise your contacts.')) + raise GajimGeneralException, 'You are not connected to the server' + self.account = account + self.xml = gtkgui_helpers.get_gtk_builder('synchronise_select_account_dialog.ui') + self.dialog = self.xml.get_object('synchronise_select_account_dialog') + self.accounts_treeview = self.xml.get_object('accounts_treeview') + model = gtk.ListStore(str, str, bool) + self.accounts_treeview.set_model(model) + # columns + renderer = gtk.CellRendererText() + self.accounts_treeview.insert_column_with_attributes(-1, + _('Name'), renderer, text=0) + renderer = gtk.CellRendererText() + self.accounts_treeview.insert_column_with_attributes(-1, + _('Server'), renderer, text=1) - self.xml.connect_signals(self) - self.init_accounts() - self.dialog.show_all() + self.xml.connect_signals(self) + self.init_accounts() + self.dialog.show_all() - def on_accounts_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: - self.window.destroy() + def on_accounts_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: + self.window.destroy() - def init_accounts(self): - """ - Initialize listStore with existing accounts - """ - model = self.accounts_treeview.get_model() - model.clear() - for remote_account in gajim.connections: - if remote_account == self.account: - # Do not show the account we're sync'ing - continue - iter_ = model.append() - model.set(iter_, 0, remote_account, 1, gajim.get_hostname_from_account( - remote_account)) + def init_accounts(self): + """ + Initialize listStore with existing accounts + """ + model = self.accounts_treeview.get_model() + model.clear() + for remote_account in gajim.connections: + if remote_account == self.account: + # Do not show the account we're sync'ing + continue + iter_ = model.append() + model.set(iter_, 0, remote_account, 1, gajim.get_hostname_from_account( + remote_account)) - def on_cancel_button_clicked(self, widget): - self.dialog.destroy() + def on_cancel_button_clicked(self, widget): + self.dialog.destroy() - def on_ok_button_clicked(self, widget): - sel = self.accounts_treeview.get_selection() - (model, iter_) = sel.get_selected() - if not iter_: - return - remote_account = model.get_value(iter_, 0).decode('utf-8') + def on_ok_button_clicked(self, widget): + sel = self.accounts_treeview.get_selection() + (model, iter_) = sel.get_selected() + if not iter_: + return + remote_account = model.get_value(iter_, 0).decode('utf-8') - if gajim.connections[remote_account].connected < 2: - ErrorDialog(_('This account is not connected to the server'), - _('You cannot synchronize with an account unless it is connected.')) - return - else: - try: - SynchroniseSelectContactsDialog(self.account, remote_account) - except GajimGeneralException: - # if we showed ErrorDialog, there will not be dialog instance - return - self.dialog.destroy() + if gajim.connections[remote_account].connected < 2: + ErrorDialog(_('This account is not connected to the server'), + _('You cannot synchronize with an account unless it is connected.')) + return + else: + try: + SynchroniseSelectContactsDialog(self.account, remote_account) + except GajimGeneralException: + # if we showed ErrorDialog, there will not be dialog instance + return + self.dialog.destroy() class SynchroniseSelectContactsDialog: - def __init__(self, account, remote_account): - self.local_account = account - self.remote_account = remote_account - self.xml = gtkgui_helpers.get_gtk_builder('synchronise_select_contacts_dialog.ui') - self.dialog = self.xml.get_object('synchronise_select_contacts_dialog') - self.contacts_treeview = self.xml.get_object('contacts_treeview') - model = gtk.ListStore(bool, str) - self.contacts_treeview.set_model(model) - # columns - renderer1 = gtk.CellRendererToggle() - renderer1.set_property('activatable', True) - renderer1.connect('toggled', self.toggled_callback) - self.contacts_treeview.insert_column_with_attributes(-1, - _('Synchronise'), renderer1, active=0) - renderer2 = gtk.CellRendererText() - self.contacts_treeview.insert_column_with_attributes(-1, - _('Name'), renderer2, text=1) + def __init__(self, account, remote_account): + self.local_account = account + self.remote_account = remote_account + self.xml = gtkgui_helpers.get_gtk_builder('synchronise_select_contacts_dialog.ui') + self.dialog = self.xml.get_object('synchronise_select_contacts_dialog') + self.contacts_treeview = self.xml.get_object('contacts_treeview') + model = gtk.ListStore(bool, str) + self.contacts_treeview.set_model(model) + # columns + renderer1 = gtk.CellRendererToggle() + renderer1.set_property('activatable', True) + renderer1.connect('toggled', self.toggled_callback) + self.contacts_treeview.insert_column_with_attributes(-1, + _('Synchronise'), renderer1, active=0) + renderer2 = gtk.CellRendererText() + self.contacts_treeview.insert_column_with_attributes(-1, + _('Name'), renderer2, text=1) - self.xml.connect_signals(self) - self.init_contacts() - self.dialog.show_all() + self.xml.connect_signals(self) + self.init_contacts() + self.dialog.show_all() - def toggled_callback(self, cell, path): - model = self.contacts_treeview.get_model() - iter_ = model.get_iter(path) - model[iter_][0] = not cell.get_active() + def toggled_callback(self, cell, path): + model = self.contacts_treeview.get_model() + iter_ = model.get_iter(path) + model[iter_][0] = not cell.get_active() - def on_contacts_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: - self.window.destroy() + def on_contacts_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: + self.window.destroy() - def init_contacts(self): - """ - Initialize listStore with existing accounts - """ - model = self.contacts_treeview.get_model() - model.clear() + def init_contacts(self): + """ + Initialize listStore with existing accounts + """ + model = self.contacts_treeview.get_model() + model.clear() - # recover local contacts - local_jid_list = gajim.contacts.get_contacts_jid_list(self.local_account) + # recover local contacts + local_jid_list = gajim.contacts.get_contacts_jid_list(self.local_account) - remote_jid_list = gajim.contacts.get_contacts_jid_list( - self.remote_account) - for remote_jid in remote_jid_list: - if remote_jid not in local_jid_list: - iter_ = model.append() - model.set(iter_, 0, True, 1, remote_jid) + remote_jid_list = gajim.contacts.get_contacts_jid_list( + self.remote_account) + for remote_jid in remote_jid_list: + if remote_jid not in local_jid_list: + iter_ = model.append() + model.set(iter_, 0, True, 1, remote_jid) - def on_cancel_button_clicked(self, widget): - self.dialog.destroy() + def on_cancel_button_clicked(self, widget): + self.dialog.destroy() - def on_ok_button_clicked(self, widget): - model = self.contacts_treeview.get_model() - iter_ = model.get_iter_root() - while iter_: - if model[iter_][0]: - # it is selected - remote_jid = model[iter_][1].decode('utf-8') - message = 'I\'m synchronizing my contacts from my %s account, could you please add this address to your contact list?' % \ - gajim.get_hostname_from_account(self.remote_account) - remote_contact = gajim.contacts.get_first_contact_from_jid( - self.remote_account, remote_jid) - # keep same groups and same nickname - gajim.interface.roster.req_sub(self, remote_jid, message, - self.local_account, groups = remote_contact.groups, - nickname = remote_contact.name, auto_auth = True) - iter_ = model.iter_next(iter_) - self.dialog.destroy() + def on_ok_button_clicked(self, widget): + model = self.contacts_treeview.get_model() + iter_ = model.get_iter_root() + while iter_: + if model[iter_][0]: + # it is selected + remote_jid = model[iter_][1].decode('utf-8') + message = 'I\'m synchronizing my contacts from my %s account, could you please add this address to your contact list?' % \ + gajim.get_hostname_from_account(self.remote_account) + remote_contact = gajim.contacts.get_first_contact_from_jid( + self.remote_account, remote_jid) + # keep same groups and same nickname + gajim.interface.roster.req_sub(self, remote_jid, message, + self.local_account, groups = remote_contact.groups, + nickname = remote_contact.name, auto_auth = True) + iter_ = model.iter_next(iter_) + self.dialog.destroy() class NewChatDialog(InputDialog): - def __init__(self, account): - self.account = account + def __init__(self, account): + self.account = account - if len(gajim.connections) > 1: - title = _('Start Chat with account %s') % account - else: - title = _('Start Chat') - prompt_text = _('Fill in the nickname or the Jabber ID of the contact you would like\nto send a chat message to:') - InputDialog.__init__(self, title, prompt_text, is_modal=False) + if len(gajim.connections) > 1: + title = _('Start Chat with account %s') % account + else: + title = _('Start Chat') + prompt_text = _('Fill in the nickname or the Jabber ID of the contact you would like\nto send a chat message to:') + InputDialog.__init__(self, title, prompt_text, is_modal=False) - self.completion_dict = {} - liststore = gtkgui_helpers.get_completion_liststore(self.input_entry) - self.completion_dict = helpers.get_contact_dict_for_account(account) - # add all contacts to the model - keys = sorted(self.completion_dict.keys()) - for jid in keys: - contact = self.completion_dict[jid] - img = gajim.interface.jabber_state_images['16'][contact.show] - liststore.append((img.get_pixbuf(), jid)) + self.completion_dict = {} + liststore = gtkgui_helpers.get_completion_liststore(self.input_entry) + self.completion_dict = helpers.get_contact_dict_for_account(account) + # add all contacts to the model + keys = sorted(self.completion_dict.keys()) + for jid in keys: + contact = self.completion_dict[jid] + img = gajim.interface.jabber_state_images['16'][contact.show] + liststore.append((img.get_pixbuf(), jid)) - self.ok_handler = self.new_chat_response - okbutton = self.xml.get_object('okbutton') - okbutton.connect('clicked', self.on_okbutton_clicked) - cancelbutton = self.xml.get_object('cancelbutton') - cancelbutton.connect('clicked', self.on_cancelbutton_clicked) - self.dialog.show_all() + self.ok_handler = self.new_chat_response + okbutton = self.xml.get_object('okbutton') + okbutton.connect('clicked', self.on_okbutton_clicked) + cancelbutton = self.xml.get_object('cancelbutton') + cancelbutton.connect('clicked', self.on_cancelbutton_clicked) + self.dialog.show_all() - def new_chat_response(self, jid): - """ - Called when ok button is clicked - """ - if gajim.connections[self.account].connected <= 1: - #if offline or connecting - ErrorDialog(_('Connection not available'), - _('Please make sure you are connected with "%s".') % self.account) - return + def new_chat_response(self, jid): + """ + Called when ok button is clicked + """ + if gajim.connections[self.account].connected <= 1: + #if offline or connecting + ErrorDialog(_('Connection not available'), + _('Please make sure you are connected with "%s".') % self.account) + return - if jid in self.completion_dict: - jid = self.completion_dict[jid].jid - else: - try: - jid = helpers.parse_jid(jid) - except helpers.InvalidFormat, e: - ErrorDialog(_('Invalid JID'), e[0]) - return - except: - ErrorDialog(_('Invalid JID'), _('Unable to parse "%s".') % jid) - return - gajim.interface.new_chat_from_jid(self.account, jid) + if jid in self.completion_dict: + jid = self.completion_dict[jid].jid + else: + try: + jid = helpers.parse_jid(jid) + except helpers.InvalidFormat, e: + ErrorDialog(_('Invalid JID'), e[0]) + return + except: + ErrorDialog(_('Invalid JID'), _('Unable to parse "%s".') % jid) + return + gajim.interface.new_chat_from_jid(self.account, jid) class ChangePasswordDialog: - def __init__(self, account, on_response): - # 'account' can be None if we are about to create our first one - if not account or gajim.connections[account].connected < 2: - ErrorDialog(_('You are not connected to the server'), - _('Without a connection, you can not change your password.')) - raise GajimGeneralException, 'You are not connected to the server' - self.account = account - self.on_response = on_response - self.xml = gtkgui_helpers.get_gtk_builder('change_password_dialog.ui') - self.dialog = self.xml.get_object('change_password_dialog') - self.password1_entry = self.xml.get_object('password1_entry') - self.password2_entry = self.xml.get_object('password2_entry') - self.dialog.connect('response', self.on_dialog_response) + def __init__(self, account, on_response): + # 'account' can be None if we are about to create our first one + if not account or gajim.connections[account].connected < 2: + ErrorDialog(_('You are not connected to the server'), + _('Without a connection, you can not change your password.')) + raise GajimGeneralException, 'You are not connected to the server' + self.account = account + self.on_response = on_response + self.xml = gtkgui_helpers.get_gtk_builder('change_password_dialog.ui') + self.dialog = self.xml.get_object('change_password_dialog') + self.password1_entry = self.xml.get_object('password1_entry') + self.password2_entry = self.xml.get_object('password2_entry') + self.dialog.connect('response', self.on_dialog_response) - self.dialog.show_all() + self.dialog.show_all() - def on_dialog_response(self, dialog, response): - if response != gtk.RESPONSE_OK: - dialog.destroy() - self.on_response(None) - return - password1 = self.password1_entry.get_text().decode('utf-8') - if not password1: - ErrorDialog(_('Invalid password'), _('You must enter a password.')) - return - password2 = self.password2_entry.get_text().decode('utf-8') - if password1 != password2: - ErrorDialog(_('Passwords do not match'), - _('The passwords typed in both fields must be identical.')) - return - dialog.destroy() - self.on_response(password1) + def on_dialog_response(self, dialog, response): + if response != gtk.RESPONSE_OK: + dialog.destroy() + self.on_response(None) + return + password1 = self.password1_entry.get_text().decode('utf-8') + if not password1: + ErrorDialog(_('Invalid password'), _('You must enter a password.')) + return + password2 = self.password2_entry.get_text().decode('utf-8') + if password1 != password2: + ErrorDialog(_('Passwords do not match'), + _('The passwords typed in both fields must be identical.')) + return + dialog.destroy() + self.on_response(password1) class PopupNotificationWindow: - def __init__(self, event_type, jid, account, msg_type='', - path_to_image=None, title=None, text=None): - self.account = account - self.jid = jid - self.msg_type = msg_type + def __init__(self, event_type, jid, account, msg_type='', + path_to_image=None, title=None, text=None): + self.account = account + self.jid = jid + self.msg_type = msg_type - xml = gtkgui_helpers.get_gtk_builder('popup_notification_window.ui') - self.window = xml.get_object('popup_notification_window') - self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_TOOLTIP) - close_button = xml.get_object('close_button') - event_type_label = xml.get_object('event_type_label') - event_description_label = xml.get_object('event_description_label') - eventbox = xml.get_object('eventbox') - image = xml.get_object('notification_image') + xml = gtkgui_helpers.get_gtk_builder('popup_notification_window.ui') + self.window = xml.get_object('popup_notification_window') + self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_TOOLTIP) + close_button = xml.get_object('close_button') + event_type_label = xml.get_object('event_type_label') + event_description_label = xml.get_object('event_description_label') + eventbox = xml.get_object('eventbox') + image = xml.get_object('notification_image') - if not text: - text = gajim.get_name_from_jid(account, jid) # default value of text - if not title: - title = '' + if not text: + text = gajim.get_name_from_jid(account, jid) # default value of text + if not title: + title = '' - event_type_label.set_markup( - '%s' % - gobject.markup_escape_text(title)) + event_type_label.set_markup( + '%s' % + gobject.markup_escape_text(title)) - # set colors [ http://www.pitt.edu/~nisg/cis/web/cgi/rgb.html ] - self.window.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse('black')) + # set colors [ http://www.pitt.edu/~nisg/cis/web/cgi/rgb.html ] + self.window.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse('black')) - # default image - if not path_to_image: - path_to_image = gtkgui_helpers.get_icon_path('gajim-chat_msg_recv', 48) + # default image + if not path_to_image: + path_to_image = gtkgui_helpers.get_icon_path('gajim-chat_msg_recv', 48) - if event_type == _('Contact Signed In'): - bg_color = 'limegreen' - elif event_type == _('Contact Signed Out'): - bg_color = 'red' - elif event_type in (_('New Message'), _('New Single Message'), - _('New Private Message'), _('New E-mail')): - bg_color = 'dodgerblue' - elif event_type == _('File Transfer Request'): - bg_color = 'khaki' - elif event_type == _('File Transfer Error'): - bg_color = 'firebrick' - elif event_type in (_('File Transfer Completed'), - _('File Transfer Stopped')): - bg_color = 'yellowgreen' - elif event_type == _('Groupchat Invitation'): - bg_color = 'tan1' - elif event_type == _('Contact Changed Status'): - bg_color = 'thistle2' - else: # Unknown event! Shouldn't happen but deal with it - bg_color = 'white' - popup_bg_color = gtk.gdk.color_parse(bg_color) - close_button.modify_bg(gtk.STATE_NORMAL, popup_bg_color) - eventbox.modify_bg(gtk.STATE_NORMAL, popup_bg_color) - event_description_label.set_markup('%s' % - gobject.markup_escape_text(text)) + if event_type == _('Contact Signed In'): + bg_color = 'limegreen' + elif event_type == _('Contact Signed Out'): + bg_color = 'red' + elif event_type in (_('New Message'), _('New Single Message'), + _('New Private Message'), _('New E-mail')): + bg_color = 'dodgerblue' + elif event_type == _('File Transfer Request'): + bg_color = 'khaki' + elif event_type == _('File Transfer Error'): + bg_color = 'firebrick' + elif event_type in (_('File Transfer Completed'), + _('File Transfer Stopped')): + bg_color = 'yellowgreen' + elif event_type == _('Groupchat Invitation'): + bg_color = 'tan1' + elif event_type == _('Contact Changed Status'): + bg_color = 'thistle2' + else: # Unknown event! Shouldn't happen but deal with it + bg_color = 'white' + popup_bg_color = gtk.gdk.color_parse(bg_color) + close_button.modify_bg(gtk.STATE_NORMAL, popup_bg_color) + eventbox.modify_bg(gtk.STATE_NORMAL, popup_bg_color) + event_description_label.set_markup('%s' % + gobject.markup_escape_text(text)) - # set the image - image.set_from_file(path_to_image) + # set the image + image.set_from_file(path_to_image) - # position the window to bottom-right of screen - window_width, self.window_height = self.window.get_size() - gajim.interface.roster.popups_notification_height += self.window_height - pos_x = gajim.config.get('notification_position_x') - if pos_x < 0: - pos_x = gtk.gdk.screen_width() - window_width + pos_x + 1 - pos_y = gajim.config.get('notification_position_y') - if pos_y < 0: - pos_y = gtk.gdk.screen_height() - \ - gajim.interface.roster.popups_notification_height + pos_y + 1 - self.window.move(pos_x, pos_y) + # position the window to bottom-right of screen + window_width, self.window_height = self.window.get_size() + gajim.interface.roster.popups_notification_height += self.window_height + pos_x = gajim.config.get('notification_position_x') + if pos_x < 0: + pos_x = gtk.gdk.screen_width() - window_width + pos_x + 1 + pos_y = gajim.config.get('notification_position_y') + if pos_y < 0: + pos_y = gtk.gdk.screen_height() - \ + gajim.interface.roster.popups_notification_height + pos_y + 1 + self.window.move(pos_x, pos_y) - xml.connect_signals(self) - self.window.show_all() - timeout = gajim.config.get('notification_timeout') - gobject.timeout_add_seconds(timeout, self.on_timeout) + xml.connect_signals(self) + self.window.show_all() + timeout = gajim.config.get('notification_timeout') + gobject.timeout_add_seconds(timeout, self.on_timeout) - def on_close_button_clicked(self, widget): - self.adjust_height_and_move_popup_notification_windows() + def on_close_button_clicked(self, widget): + self.adjust_height_and_move_popup_notification_windows() - def on_timeout(self): - self.adjust_height_and_move_popup_notification_windows() + def on_timeout(self): + self.adjust_height_and_move_popup_notification_windows() - def adjust_height_and_move_popup_notification_windows(self): - #remove - gajim.interface.roster.popups_notification_height -= self.window_height - self.window.destroy() + def adjust_height_and_move_popup_notification_windows(self): + #remove + gajim.interface.roster.popups_notification_height -= self.window_height + self.window.destroy() - if len(gajim.interface.roster.popup_notification_windows) > 0: - # we want to remove the first window added in the list - gajim.interface.roster.popup_notification_windows.pop(0) + if len(gajim.interface.roster.popup_notification_windows) > 0: + # we want to remove the first window added in the list + gajim.interface.roster.popup_notification_windows.pop(0) - # move the rest of popup windows - gajim.interface.roster.popups_notification_height = 0 - for window_instance in gajim.interface.roster.popup_notification_windows: - window_width, window_height = window_instance.window.get_size() - gajim.interface.roster.popups_notification_height += window_height - window_instance.window.move(gtk.gdk.screen_width() - window_width, - gtk.gdk.screen_height() - \ - gajim.interface.roster.popups_notification_height) + # move the rest of popup windows + gajim.interface.roster.popups_notification_height = 0 + for window_instance in gajim.interface.roster.popup_notification_windows: + window_width, window_height = window_instance.window.get_size() + gajim.interface.roster.popups_notification_height += window_height + window_instance.window.move(gtk.gdk.screen_width() - window_width, + gtk.gdk.screen_height() - \ + gajim.interface.roster.popups_notification_height) - def on_popup_notification_window_button_press_event(self, widget, event): - if event.button != 1: - self.window.destroy() - return - gajim.interface.handle_event(self.account, self.jid, self.msg_type) - self.adjust_height_and_move_popup_notification_windows() + def on_popup_notification_window_button_press_event(self, widget, event): + if event.button != 1: + self.window.destroy() + return + gajim.interface.handle_event(self.account, self.jid, self.msg_type) + self.adjust_height_and_move_popup_notification_windows() class SingleMessageWindow: - """ - SingleMessageWindow can send or show a received singled message depending on - action argument which can be 'send' or 'receive' - """ - # Keep a reference on windows so garbage collector don't restroy them - instances = [] - def __init__(self, account, to='', action='', from_whom='', subject='', - message='', resource='', session=None, form_node=None): - self.instances.append(self) - self.account = account - self.action = action + """ + SingleMessageWindow can send or show a received singled message depending on + action argument which can be 'send' or 'receive' + """ + # Keep a reference on windows so garbage collector don't restroy them + instances = [] + def __init__(self, account, to='', action='', from_whom='', subject='', + message='', resource='', session=None, form_node=None): + self.instances.append(self) + self.account = account + self.action = action - self.subject = subject - self.message = message - self.to = to - self.from_whom = from_whom - self.resource = resource - self.session = session + self.subject = subject + self.message = message + self.to = to + self.from_whom = from_whom + self.resource = resource + self.session = session - self.xml = gtkgui_helpers.get_gtk_builder('single_message_window.ui') - self.window = self.xml.get_object('single_message_window') - self.count_chars_label = self.xml.get_object('count_chars_label') - self.from_label = self.xml.get_object('from_label') - self.from_entry = self.xml.get_object('from_entry') - self.to_label = self.xml.get_object('to_label') - self.to_entry = self.xml.get_object('to_entry') - self.subject_entry = self.xml.get_object('subject_entry') - self.message_scrolledwindow = self.xml.get_object( - 'message_scrolledwindow') - self.message_textview = self.xml.get_object('message_textview') - self.message_tv_buffer = self.message_textview.get_buffer() - self.conversation_scrolledwindow = self.xml.get_object( - 'conversation_scrolledwindow') - self.conversation_textview = conversation_textview.ConversationTextview( - account) - self.conversation_textview.tv.show() - self.conversation_tv_buffer = self.conversation_textview.tv.get_buffer() - self.xml.get_object('conversation_scrolledwindow').add( - self.conversation_textview.tv) + self.xml = gtkgui_helpers.get_gtk_builder('single_message_window.ui') + self.window = self.xml.get_object('single_message_window') + self.count_chars_label = self.xml.get_object('count_chars_label') + self.from_label = self.xml.get_object('from_label') + self.from_entry = self.xml.get_object('from_entry') + self.to_label = self.xml.get_object('to_label') + self.to_entry = self.xml.get_object('to_entry') + self.subject_entry = self.xml.get_object('subject_entry') + self.message_scrolledwindow = self.xml.get_object( + 'message_scrolledwindow') + self.message_textview = self.xml.get_object('message_textview') + self.message_tv_buffer = self.message_textview.get_buffer() + self.conversation_scrolledwindow = self.xml.get_object( + 'conversation_scrolledwindow') + self.conversation_textview = conversation_textview.ConversationTextview( + account) + self.conversation_textview.tv.show() + self.conversation_tv_buffer = self.conversation_textview.tv.get_buffer() + self.xml.get_object('conversation_scrolledwindow').add( + self.conversation_textview.tv) - self.form_widget = None - parent_box = self.xml.get_object('conversation_scrolledwindow').\ - get_parent() - if form_node: - dataform = dataforms.ExtendForm(node=form_node) - self.form_widget = dataforms_widget.DataFormWidget(dataform) - self.form_widget.show_all() - parent_box.add(self.form_widget) - parent_box.child_set_property(self.form_widget, 'position', - parent_box.child_get_property(self.xml.get_object( - 'conversation_scrolledwindow'), 'position')) - self.action = 'form' + self.form_widget = None + parent_box = self.xml.get_object('conversation_scrolledwindow').\ + get_parent() + if form_node: + dataform = dataforms.ExtendForm(node=form_node) + self.form_widget = dataforms_widget.DataFormWidget(dataform) + self.form_widget.show_all() + parent_box.add(self.form_widget) + parent_box.child_set_property(self.form_widget, 'position', + parent_box.child_get_property(self.xml.get_object( + 'conversation_scrolledwindow'), 'position')) + self.action = 'form' - self.send_button = self.xml.get_object('send_button') - self.reply_button = self.xml.get_object('reply_button') - self.send_and_close_button = self.xml.get_object('send_and_close_button') - self.cancel_button = self.xml.get_object('cancel_button') - self.close_button = self.xml.get_object('close_button') - self.message_tv_buffer.connect('changed', self.update_char_counter) - if isinstance(to, list): - jid = ', '.join( [i[0].jid + '/' + i[0].resource for i in to]) - self.to_entry.set_text(jid) - self.to_entry.set_sensitive(False) - else: - self.to_entry.set_text(to) + self.send_button = self.xml.get_object('send_button') + self.reply_button = self.xml.get_object('reply_button') + self.send_and_close_button = self.xml.get_object('send_and_close_button') + self.cancel_button = self.xml.get_object('cancel_button') + self.close_button = self.xml.get_object('close_button') + self.message_tv_buffer.connect('changed', self.update_char_counter) + if isinstance(to, list): + jid = ', '.join( [i[0].jid + '/' + i[0].resource for i in to]) + self.to_entry.set_text(jid) + self.to_entry.set_sensitive(False) + else: + self.to_entry.set_text(to) - if gajim.config.get('use_speller') and HAS_GTK_SPELL and action == 'send': - try: - lang = gajim.config.get('speller_language') - if not lang: - lang = gajim.LANG - gtkspell.Spell(self.conversation_textview.tv, lang) - gtkspell.Spell(self.message_textview, lang) - except (gobject.GError, TypeError, RuntimeError, OSError): - AspellDictError(lang) + if gajim.config.get('use_speller') and HAS_GTK_SPELL and action == 'send': + try: + lang = gajim.config.get('speller_language') + if not lang: + lang = gajim.LANG + gtkspell.Spell(self.conversation_textview.tv, lang) + gtkspell.Spell(self.message_textview, lang) + except (gobject.GError, TypeError, RuntimeError, OSError): + AspellDictError(lang) - self.prepare_widgets_for(self.action) + self.prepare_widgets_for(self.action) - # set_text(None) raises TypeError exception - if self.subject is None: - self.subject = '' - self.subject_entry.set_text(self.subject) + # set_text(None) raises TypeError exception + if self.subject is None: + self.subject = '' + self.subject_entry.set_text(self.subject) - if to == '': - liststore = gtkgui_helpers.get_completion_liststore(self.to_entry) - self.completion_dict = helpers.get_contact_dict_for_account(account) - keys = sorted(self.completion_dict.keys()) - for jid in keys: - contact = self.completion_dict[jid] - img = gajim.interface.jabber_state_images['16'][contact.show] - liststore.append((img.get_pixbuf(), jid)) - else: - self.completion_dict = {} - self.xml.connect_signals(self) + if to == '': + liststore = gtkgui_helpers.get_completion_liststore(self.to_entry) + self.completion_dict = helpers.get_contact_dict_for_account(account) + keys = sorted(self.completion_dict.keys()) + for jid in keys: + contact = self.completion_dict[jid] + img = gajim.interface.jabber_state_images['16'][contact.show] + liststore.append((img.get_pixbuf(), jid)) + else: + self.completion_dict = {} + self.xml.connect_signals(self) - # get window position and size from config - gtkgui_helpers.resize_window(self.window, - gajim.config.get('single-msg-width'), - gajim.config.get('single-msg-height')) - gtkgui_helpers.move_window(self.window, - gajim.config.get('single-msg-x-position'), - gajim.config.get('single-msg-y-position')) + # get window position and size from config + gtkgui_helpers.resize_window(self.window, + gajim.config.get('single-msg-width'), + gajim.config.get('single-msg-height')) + gtkgui_helpers.move_window(self.window, + gajim.config.get('single-msg-x-position'), + gajim.config.get('single-msg-y-position')) - self.window.show_all() + self.window.show_all() - def on_single_message_window_destroy(self, widget): - self.instances.remove(self) - c = gajim.contacts.get_contact_with_highest_priority(self.account, - self.from_whom) - if not c: - # Groupchat is maybe already destroyed - return - if c.is_groupchat() and not self.from_whom in \ - gajim.interface.minimized_controls[self.account] and self.action == \ - 'receive' and gajim.events.get_nb_roster_events(self.account, - self.from_whom, types=['chat', 'normal']) == 0: - gajim.interface.roster.remove_groupchat(self.from_whom, self.account) + def on_single_message_window_destroy(self, widget): + self.instances.remove(self) + c = gajim.contacts.get_contact_with_highest_priority(self.account, + self.from_whom) + if not c: + # Groupchat is maybe already destroyed + return + if c.is_groupchat() and not self.from_whom in \ + gajim.interface.minimized_controls[self.account] and self.action == \ + 'receive' and gajim.events.get_nb_roster_events(self.account, + self.from_whom, types=['chat', 'normal']) == 0: + gajim.interface.roster.remove_groupchat(self.from_whom, self.account) - def set_cursor_to_end(self): - end_iter = self.message_tv_buffer.get_end_iter() - self.message_tv_buffer.place_cursor(end_iter) + def set_cursor_to_end(self): + end_iter = self.message_tv_buffer.get_end_iter() + self.message_tv_buffer.place_cursor(end_iter) - def save_pos(self): - # save the window size and position - x, y = self.window.get_position() - gajim.config.set('single-msg-x-position', x) - gajim.config.set('single-msg-y-position', y) - width, height = self.window.get_size() - gajim.config.set('single-msg-width', width) - gajim.config.set('single-msg-height', height) - gajim.interface.save_config() + def save_pos(self): + # save the window size and position + x, y = self.window.get_position() + gajim.config.set('single-msg-x-position', x) + gajim.config.set('single-msg-y-position', y) + width, height = self.window.get_size() + gajim.config.set('single-msg-width', width) + gajim.config.set('single-msg-height', height) + gajim.interface.save_config() - def on_single_message_window_delete_event(self, window, ev): - self.save_pos() + def on_single_message_window_delete_event(self, window, ev): + self.save_pos() - def prepare_widgets_for(self, action): - if len(gajim.connections) > 1: - if action == 'send': - title = _('Single Message using account %s') % self.account - else: - title = _('Single Message in account %s') % self.account - else: - title = _('Single Message') + def prepare_widgets_for(self, action): + if len(gajim.connections) > 1: + if action == 'send': + title = _('Single Message using account %s') % self.account + else: + title = _('Single Message in account %s') % self.account + else: + title = _('Single Message') - if action == 'send': # prepare UI for Sending - title = _('Send %s') % title - self.send_button.show() - self.send_and_close_button.show() - self.to_label.show() - self.to_entry.show() - self.reply_button.hide() - self.from_label.hide() - self.from_entry.hide() - self.conversation_scrolledwindow.hide() - self.message_scrolledwindow.show() + if action == 'send': # prepare UI for Sending + title = _('Send %s') % title + self.send_button.show() + self.send_and_close_button.show() + self.to_label.show() + self.to_entry.show() + self.reply_button.hide() + self.from_label.hide() + self.from_entry.hide() + self.conversation_scrolledwindow.hide() + self.message_scrolledwindow.show() - if self.message: # we come from a reply? - self.message_textview.grab_focus() - self.cancel_button.hide() - self.close_button.show() - self.message_tv_buffer.set_text(self.message) - gobject.idle_add(self.set_cursor_to_end) - else: # we write a new message (not from reply) - self.close_button.hide() - if self.to: # do we already have jid? - self.subject_entry.grab_focus() + if self.message: # we come from a reply? + self.message_textview.grab_focus() + self.cancel_button.hide() + self.close_button.show() + self.message_tv_buffer.set_text(self.message) + gobject.idle_add(self.set_cursor_to_end) + else: # we write a new message (not from reply) + self.close_button.hide() + if self.to: # do we already have jid? + self.subject_entry.grab_focus() - elif action == 'receive': # prepare UI for Receiving - title = _('Received %s') % title - self.reply_button.show() - self.from_label.show() - self.from_entry.show() - self.send_button.hide() - self.send_and_close_button.hide() - self.to_label.hide() - self.to_entry.hide() - self.conversation_scrolledwindow.show() - self.message_scrolledwindow.hide() + elif action == 'receive': # prepare UI for Receiving + title = _('Received %s') % title + self.reply_button.show() + self.from_label.show() + self.from_entry.show() + self.send_button.hide() + self.send_and_close_button.hide() + self.to_label.hide() + self.to_entry.hide() + self.conversation_scrolledwindow.show() + self.message_scrolledwindow.hide() - if self.message: - self.conversation_textview.print_real_text(self.message) - fjid = self.from_whom - if self.resource: - fjid += '/' + self.resource # Full jid of sender (with resource) - self.from_entry.set_text(fjid) - self.from_entry.set_property('editable', False) - self.subject_entry.set_property('editable', False) - self.reply_button.grab_focus() - self.cancel_button.hide() - self.close_button.show() - elif action == 'form': # prepare UI for Receiving - title = _('Form %s') % title - self.send_button.show() - self.send_and_close_button.show() - self.to_label.show() - self.to_entry.show() - self.reply_button.hide() - self.from_label.hide() - self.from_entry.hide() - self.conversation_scrolledwindow.hide() - self.message_scrolledwindow.hide() + if self.message: + self.conversation_textview.print_real_text(self.message) + fjid = self.from_whom + if self.resource: + fjid += '/' + self.resource # Full jid of sender (with resource) + self.from_entry.set_text(fjid) + self.from_entry.set_property('editable', False) + self.subject_entry.set_property('editable', False) + self.reply_button.grab_focus() + self.cancel_button.hide() + self.close_button.show() + elif action == 'form': # prepare UI for Receiving + title = _('Form %s') % title + self.send_button.show() + self.send_and_close_button.show() + self.to_label.show() + self.to_entry.show() + self.reply_button.hide() + self.from_label.hide() + self.from_entry.hide() + self.conversation_scrolledwindow.hide() + self.message_scrolledwindow.hide() - self.window.set_title(title) + self.window.set_title(title) - def on_cancel_button_clicked(self, widget): - self.save_pos() - self.window.destroy() + def on_cancel_button_clicked(self, widget): + self.save_pos() + self.window.destroy() - def on_close_button_clicked(self, widget): - self.save_pos() - self.window.destroy() + def on_close_button_clicked(self, widget): + self.save_pos() + self.window.destroy() - def update_char_counter(self, widget): - characters_no = self.message_tv_buffer.get_char_count() - self.count_chars_label.set_text(unicode(characters_no)) + def update_char_counter(self, widget): + characters_no = self.message_tv_buffer.get_char_count() + self.count_chars_label.set_text(unicode(characters_no)) - def send_single_message(self): - if gajim.connections[self.account].connected <= 1: - # if offline or connecting - ErrorDialog(_('Connection not available'), - _('Please make sure you are connected with "%s".') % self.account) - return - if isinstance(self.to, list): - sender_list = [i[0].jid + '/' + i[0].resource for i in self.to] - else: - sender_list = [self.to_entry.get_text().decode('utf-8')] + def send_single_message(self): + if gajim.connections[self.account].connected <= 1: + # if offline or connecting + ErrorDialog(_('Connection not available'), + _('Please make sure you are connected with "%s".') % self.account) + return + if isinstance(self.to, list): + sender_list = [i[0].jid + '/' + i[0].resource for i in self.to] + else: + sender_list = [self.to_entry.get_text().decode('utf-8')] - for to_whom_jid in sender_list: - if to_whom_jid in self.completion_dict: - to_whom_jid = self.completion_dict[to_whom_jid].jid - try: - to_whom_jid = helpers.parse_jid(to_whom_jid) - except helpers.InvalidFormat: - ErrorDialog(_('Invalid Jabber ID'), - _('It is not possible to send a message to %s, this JID is not ' - 'valid.') % to_whom_jid) - return + for to_whom_jid in sender_list: + if to_whom_jid in self.completion_dict: + to_whom_jid = self.completion_dict[to_whom_jid].jid + try: + to_whom_jid = helpers.parse_jid(to_whom_jid) + except helpers.InvalidFormat: + ErrorDialog(_('Invalid Jabber ID'), + _('It is not possible to send a message to %s, this JID is not ' + 'valid.') % to_whom_jid) + return - subject = self.subject_entry.get_text().decode('utf-8') - begin, end = self.message_tv_buffer.get_bounds() - message = self.message_tv_buffer.get_text(begin, end).decode('utf-8') + subject = self.subject_entry.get_text().decode('utf-8') + begin, end = self.message_tv_buffer.get_bounds() + message = self.message_tv_buffer.get_text(begin, end).decode('utf-8') - if '/announce/' in to_whom_jid: - gajim.connections[self.account].send_motd(to_whom_jid, subject, - message) - continue + if '/announce/' in to_whom_jid: + gajim.connections[self.account].send_motd(to_whom_jid, subject, + message) + continue - if self.session: - session = self.session - else: - session = gajim.connections[self.account].make_new_session( - to_whom_jid) + if self.session: + session = self.session + else: + session = gajim.connections[self.account].make_new_session( + to_whom_jid) - if self.form_widget: - form_node = self.form_widget.data_form - else: - form_node = None - # FIXME: allow GPG message some day - gajim.connections[self.account].send_message(to_whom_jid, message, - keyID=None, type_='normal', subject=subject, session=session, - form_node=form_node) + if self.form_widget: + form_node = self.form_widget.data_form + else: + form_node = None + # FIXME: allow GPG message some day + gajim.connections[self.account].send_message(to_whom_jid, message, + keyID=None, type_='normal', subject=subject, session=session, + form_node=form_node) - self.subject_entry.set_text('') # we sent ok, clear the subject - self.message_tv_buffer.set_text('') # we sent ok, clear the textview + self.subject_entry.set_text('') # we sent ok, clear the subject + self.message_tv_buffer.set_text('') # we sent ok, clear the textview - def on_send_button_clicked(self, widget): - self.send_single_message() + def on_send_button_clicked(self, widget): + self.send_single_message() - def on_reply_button_clicked(self, widget): - # we create a new blank window to send and we preset RE: and to jid - self.subject = _('RE: %s') % self.subject - self.message = _('%s wrote:\n') % self.from_whom + self.message - # add > at the begining of each line - self.message = self.message.replace('\n', '\n> ') + '\n\n' - self.window.destroy() - SingleMessageWindow(self.account, to=self.from_whom, action='send', - from_whom=self.from_whom, subject=self.subject, message=self.message, - session=self.session) + def on_reply_button_clicked(self, widget): + # we create a new blank window to send and we preset RE: and to jid + self.subject = _('RE: %s') % self.subject + self.message = _('%s wrote:\n') % self.from_whom + self.message + # add > at the begining of each line + self.message = self.message.replace('\n', '\n> ') + '\n\n' + self.window.destroy() + SingleMessageWindow(self.account, to=self.from_whom, action='send', + from_whom=self.from_whom, subject=self.subject, message=self.message, + session=self.session) - def on_send_and_close_button_clicked(self, widget): - self.send_single_message() - self.save_pos() - self.window.destroy() + def on_send_and_close_button_clicked(self, widget): + self.send_single_message() + self.save_pos() + self.window.destroy() - def on_single_message_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: # ESCAPE - self.save_pos() - self.window.destroy() + def on_single_message_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: # ESCAPE + self.save_pos() + self.window.destroy() class XMLConsoleWindow: - def __init__(self, account): - self.account = account + def __init__(self, account): + self.account = account - self.xml = gtkgui_helpers.get_gtk_builder('xml_console_window.ui') - self.window = self.xml.get_object('xml_console_window') - self.input_textview = self.xml.get_object('input_textview') - self.stanzas_log_textview = self.xml.get_object('stanzas_log_textview') - self.input_tv_buffer = self.input_textview.get_buffer() - buffer_ = self.stanzas_log_textview.get_buffer() - end_iter = buffer_.get_end_iter() - buffer_.create_mark('end', end_iter, False) + self.xml = gtkgui_helpers.get_gtk_builder('xml_console_window.ui') + self.window = self.xml.get_object('xml_console_window') + self.input_textview = self.xml.get_object('input_textview') + self.stanzas_log_textview = self.xml.get_object('stanzas_log_textview') + self.input_tv_buffer = self.input_textview.get_buffer() + buffer_ = self.stanzas_log_textview.get_buffer() + end_iter = buffer_.get_end_iter() + buffer_.create_mark('end', end_iter, False) - self.tagIn = buffer_.create_tag('incoming') - color = gajim.config.get('inmsgcolor') - self.tagIn.set_property('foreground', color) - self.tagInPresence = buffer_.create_tag('incoming_presence') - self.tagInPresence.set_property('foreground', color) - self.tagInMessage = buffer_.create_tag('incoming_message') - self.tagInMessage.set_property('foreground', color) - self.tagInIq = buffer_.create_tag('incoming_iq') - self.tagInIq.set_property('foreground', color) + self.tagIn = buffer_.create_tag('incoming') + color = gajim.config.get('inmsgcolor') + self.tagIn.set_property('foreground', color) + self.tagInPresence = buffer_.create_tag('incoming_presence') + self.tagInPresence.set_property('foreground', color) + self.tagInMessage = buffer_.create_tag('incoming_message') + self.tagInMessage.set_property('foreground', color) + self.tagInIq = buffer_.create_tag('incoming_iq') + self.tagInIq.set_property('foreground', color) - self.tagOut = buffer_.create_tag('outgoing') - color = gajim.config.get('outmsgcolor') - self.tagOut.set_property('foreground', color) - self.tagOutPresence = buffer_.create_tag('outgoing_presence') - self.tagOutPresence.set_property('foreground', color) - self.tagOutMessage = buffer_.create_tag('outgoing_message') - self.tagOutMessage.set_property('foreground', color) - self.tagOutIq = buffer_.create_tag('outgoing_iq') - self.tagOutIq.set_property('foreground', color) - buffer_.create_tag('') # Default tag + self.tagOut = buffer_.create_tag('outgoing') + color = gajim.config.get('outmsgcolor') + self.tagOut.set_property('foreground', color) + self.tagOutPresence = buffer_.create_tag('outgoing_presence') + self.tagOutPresence.set_property('foreground', color) + self.tagOutMessage = buffer_.create_tag('outgoing_message') + self.tagOutMessage.set_property('foreground', color) + self.tagOutIq = buffer_.create_tag('outgoing_iq') + self.tagOutIq.set_property('foreground', color) + buffer_.create_tag('') # Default tag - self.enabled = False + self.enabled = False - self.input_textview.modify_text( - gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) + self.input_textview.modify_text( + gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) - if len(gajim.connections) > 1: - title = _('XML Console for %s') % self.account - else: - title = _('XML Console') + if len(gajim.connections) > 1: + title = _('XML Console for %s') % self.account + else: + title = _('XML Console') - self.window.set_title(title) - self.window.show_all() + self.window.set_title(title) + self.window.show_all() - self.xml.connect_signals(self) + self.xml.connect_signals(self) - def on_xml_console_window_delete_event(self, widget, event): - self.window.hide() - return True # do NOT destroy the window + def on_xml_console_window_delete_event(self, widget, event): + self.window.hide() + return True # do NOT destroy the window - def on_clear_button_clicked(self, widget): - buffer_ = self.stanzas_log_textview.get_buffer() - buffer_.set_text('') + def on_clear_button_clicked(self, widget): + buffer_ = self.stanzas_log_textview.get_buffer() + buffer_.set_text('') - def on_enable_checkbutton_toggled(self, widget): - self.enabled = widget.get_active() + def on_enable_checkbutton_toggled(self, widget): + self.enabled = widget.get_active() - def on_in_stanza_checkbutton_toggled(self, widget): - active = widget.get_active() - self.tagIn.set_property('invisible', active) - self.tagInPresence.set_property('invisible', active) - self.tagInMessage.set_property('invisible', active) - self.tagInIq.set_property('invisible', active) + def on_in_stanza_checkbutton_toggled(self, widget): + active = widget.get_active() + self.tagIn.set_property('invisible', active) + self.tagInPresence.set_property('invisible', active) + self.tagInMessage.set_property('invisible', active) + self.tagInIq.set_property('invisible', active) - def on_presence_stanza_checkbutton_toggled(self, widget): - active = widget.get_active() - self.tagInPresence.set_property('invisible', active) - self.tagOutPresence.set_property('invisible', active) + def on_presence_stanza_checkbutton_toggled(self, widget): + active = widget.get_active() + self.tagInPresence.set_property('invisible', active) + self.tagOutPresence.set_property('invisible', active) - def on_out_stanza_checkbutton_toggled(self, widget): - active = widget.get_active() - self.tagOut.set_property('invisible', active) - self.tagOutPresence.set_property('invisible', active) - self.tagOutMessage.set_property('invisible', active) - self.tagOutIq.set_property('invisible', active) + def on_out_stanza_checkbutton_toggled(self, widget): + active = widget.get_active() + self.tagOut.set_property('invisible', active) + self.tagOutPresence.set_property('invisible', active) + self.tagOutMessage.set_property('invisible', active) + self.tagOutIq.set_property('invisible', active) - def on_message_stanza_checkbutton_toggled(self, widget): - active = widget.get_active() - self.tagInMessage.set_property('invisible', active) - self.tagOutMessage.set_property('invisible', active) + def on_message_stanza_checkbutton_toggled(self, widget): + active = widget.get_active() + self.tagInMessage.set_property('invisible', active) + self.tagOutMessage.set_property('invisible', active) - def on_iq_stanza_checkbutton_toggled(self, widget): - active = widget.get_active() - self.tagInIq.set_property('invisible', active) - self.tagOutIq.set_property('invisible', active) + def on_iq_stanza_checkbutton_toggled(self, widget): + active = widget.get_active() + self.tagInIq.set_property('invisible', active) + self.tagOutIq.set_property('invisible', active) - def scroll_to_end(self, ): - parent = self.stanzas_log_textview.get_parent() - buffer_ = self.stanzas_log_textview.get_buffer() - end_mark = buffer_.get_mark('end') - if not end_mark: - return False - self.stanzas_log_textview.scroll_to_mark(end_mark, 0, True, 0, 1) - adjustment = parent.get_hadjustment() - adjustment.set_value(0) - return False + def scroll_to_end(self, ): + parent = self.stanzas_log_textview.get_parent() + buffer_ = self.stanzas_log_textview.get_buffer() + end_mark = buffer_.get_mark('end') + if not end_mark: + return False + self.stanzas_log_textview.scroll_to_mark(end_mark, 0, True, 0, 1) + adjustment = parent.get_hadjustment() + adjustment.set_value(0) + return False - def print_stanza(self, stanza, kind): - # kind must be 'incoming' or 'outgoing' - if not self.enabled: - return - if not stanza: - return + def print_stanza(self, stanza, kind): + # kind must be 'incoming' or 'outgoing' + if not self.enabled: + return + if not stanza: + return - buffer = self.stanzas_log_textview.get_buffer() - at_the_end = False - end_iter = buffer.get_end_iter() - end_rect = self.stanzas_log_textview.get_iter_location(end_iter) - visible_rect = self.stanzas_log_textview.get_visible_rect() - if end_rect.y <= (visible_rect.y + visible_rect.height): - at_the_end = True - end_iter = buffer.get_end_iter() + buffer = self.stanzas_log_textview.get_buffer() + at_the_end = False + end_iter = buffer.get_end_iter() + end_rect = self.stanzas_log_textview.get_iter_location(end_iter) + visible_rect = self.stanzas_log_textview.get_visible_rect() + if end_rect.y <= (visible_rect.y + visible_rect.height): + at_the_end = True + end_iter = buffer.get_end_iter() - type_ = '' - if stanza[1:9] == 'presence': - type_ = 'presence' - elif stanza[1:8] == 'message': - type_ = 'message' - elif stanza[1:3] == 'iq': - type_ = 'iq' + type_ = '' + if stanza[1:9] == 'presence': + type_ = 'presence' + elif stanza[1:8] == 'message': + type_ = 'message' + elif stanza[1:3] == 'iq': + type_ = 'iq' - if type_: - type_ = kind + '_' + type_ - else: - type_ = kind # 'incoming' or 'outgoing' + if type_: + type_ = kind + '_' + type_ + else: + type_ = kind # 'incoming' or 'outgoing' - if kind == 'incoming': - buffer.insert_with_tags_by_name(end_iter, '\n', - type_) - elif kind == 'outgoing': - buffer.insert_with_tags_by_name(end_iter, '\n', - type_) - end_iter = buffer.get_end_iter() - buffer.insert_with_tags_by_name(end_iter, stanza.replace('><', '>\n<') +\ - '\n\n', type_) - if at_the_end: - gobject.idle_add(self.scroll_to_end) + if kind == 'incoming': + buffer.insert_with_tags_by_name(end_iter, '\n', + type_) + elif kind == 'outgoing': + buffer.insert_with_tags_by_name(end_iter, '\n', + type_) + end_iter = buffer.get_end_iter() + buffer.insert_with_tags_by_name(end_iter, stanza.replace('><', '>\n<') +\ + '\n\n', type_) + if at_the_end: + gobject.idle_add(self.scroll_to_end) - def on_send_button_clicked(self, widget): - if gajim.connections[self.account].connected <= 1: - #if offline or connecting - ErrorDialog(_('Connection not available'), - _('Please make sure you are connected with "%s".') % self.account) - return - begin_iter, end_iter = self.input_tv_buffer.get_bounds() - stanza = self.input_tv_buffer.get_text(begin_iter, end_iter).decode( - 'utf-8') - if stanza: - gajim.connections[self.account].send_stanza(stanza) - self.input_tv_buffer.set_text('') # we sent ok, clear the textview + def on_send_button_clicked(self, widget): + if gajim.connections[self.account].connected <= 1: + #if offline or connecting + ErrorDialog(_('Connection not available'), + _('Please make sure you are connected with "%s".') % self.account) + return + begin_iter, end_iter = self.input_tv_buffer.get_bounds() + stanza = self.input_tv_buffer.get_text(begin_iter, end_iter).decode( + 'utf-8') + if stanza: + gajim.connections[self.account].send_stanza(stanza) + self.input_tv_buffer.set_text('') # we sent ok, clear the textview - def on_presence_button_clicked(self, widget): - self.input_tv_buffer.set_text( - '' - '') + def on_presence_button_clicked(self, widget): + self.input_tv_buffer.set_text( + '' + '') - def on_iq_button_clicked(self, widget): - self.input_tv_buffer.set_text( - '') + def on_iq_button_clicked(self, widget): + self.input_tv_buffer.set_text( + '') - def on_message_button_clicked(self, widget): - self.input_tv_buffer.set_text( - '') + def on_message_button_clicked(self, widget): + self.input_tv_buffer.set_text( + '') - def on_expander_activate(self, widget): - if not widget.get_expanded(): # it's the opposite! - # it's expanded!! - self.input_textview.grab_focus() + def on_expander_activate(self, widget): + if not widget.get_expanded(): # it's the opposite! + # it's expanded!! + self.input_textview.grab_focus() #Action that can be done with an incoming list of contacts TRANSLATED_ACTION = {'add': _('add'), 'modify': _('modify'), - 'remove': _('remove')} + 'remove': _('remove')} class RosterItemExchangeWindow: - """ - Windows used when someone send you a exchange contact suggestion - """ + """ + Windows used when someone send you a exchange contact suggestion + """ - def __init__(self, account, action, exchange_list, jid_from, - message_body=None): - self.account = account - self.action = action - self.exchange_list = exchange_list - self.message_body = message_body - self.jid_from = jid_from + def __init__(self, account, action, exchange_list, jid_from, + message_body=None): + self.account = account + self.action = action + self.exchange_list = exchange_list + self.message_body = message_body + self.jid_from = jid_from - show_dialog = False + show_dialog = False - # Connect to gtk builder - self.xml = gtkgui_helpers.get_gtk_builder('roster_item_exchange_window.ui') - self.window = self.xml.get_object('roster_item_exchange_window') + # Connect to gtk builder + self.xml = gtkgui_helpers.get_gtk_builder('roster_item_exchange_window.ui') + self.window = self.xml.get_object('roster_item_exchange_window') - # Add Widgets. - for widget_to_add in ['accept_button_label', 'type_label', - 'body_scrolledwindow', 'body_textview', 'items_list_treeview']: - self.__dict__[widget_to_add] = self.xml.get_object(widget_to_add) + # Add Widgets. + for widget_to_add in ['accept_button_label', 'type_label', + 'body_scrolledwindow', 'body_textview', 'items_list_treeview']: + self.__dict__[widget_to_add] = self.xml.get_object(widget_to_add) - # Set labels - # self.action can be 'add', 'modify' or 'remove' - self.type_label.set_label( - _('%(jid)s would like you to %(action)s some contacts ' - 'in your roster.') % {'jid': jid_from, - 'action': TRANSLATED_ACTION[self.action]}) - if message_body: - buffer_ = self.body_textview.get_buffer() - buffer_.set_text(self.message_body) - else: - self.body_scrolledwindow.hide() - # Treeview - model = gtk.ListStore(bool, str, str, str, str) - self.items_list_treeview.set_model(model) - # columns - renderer1 = gtk.CellRendererToggle() - renderer1.set_property('activatable', True) - renderer1.connect('toggled', self.toggled_callback) - if self.action == 'add': - title = _('Add') - elif self.action == 'modify': - title = _('Modify') - elif self.action == 'delete': - title = _('Delete') - self.items_list_treeview.insert_column_with_attributes(-1, title, - renderer1, active=0) - renderer2 = gtk.CellRendererText() - self.items_list_treeview.insert_column_with_attributes(-1, _('Jabber ID'), - renderer2, text=1) - renderer3 = gtk.CellRendererText() - self.items_list_treeview.insert_column_with_attributes(-1, _('Name'), - renderer3, text=2) - renderer4 = gtk.CellRendererText() - self.items_list_treeview.insert_column_with_attributes(-1, _('Groups'), - renderer4, text=3) + # Set labels + # self.action can be 'add', 'modify' or 'remove' + self.type_label.set_label( + _('%(jid)s would like you to %(action)s some contacts ' + 'in your roster.') % {'jid': jid_from, + 'action': TRANSLATED_ACTION[self.action]}) + if message_body: + buffer_ = self.body_textview.get_buffer() + buffer_.set_text(self.message_body) + else: + self.body_scrolledwindow.hide() + # Treeview + model = gtk.ListStore(bool, str, str, str, str) + self.items_list_treeview.set_model(model) + # columns + renderer1 = gtk.CellRendererToggle() + renderer1.set_property('activatable', True) + renderer1.connect('toggled', self.toggled_callback) + if self.action == 'add': + title = _('Add') + elif self.action == 'modify': + title = _('Modify') + elif self.action == 'delete': + title = _('Delete') + self.items_list_treeview.insert_column_with_attributes(-1, title, + renderer1, active=0) + renderer2 = gtk.CellRendererText() + self.items_list_treeview.insert_column_with_attributes(-1, _('Jabber ID'), + renderer2, text=1) + renderer3 = gtk.CellRendererText() + self.items_list_treeview.insert_column_with_attributes(-1, _('Name'), + renderer3, text=2) + renderer4 = gtk.CellRendererText() + self.items_list_treeview.insert_column_with_attributes(-1, _('Groups'), + renderer4, text=3) - # Init contacts - model = self.items_list_treeview.get_model() - model.clear() + # Init contacts + model = self.items_list_treeview.get_model() + model.clear() - if action == 'add': - for jid in self.exchange_list: - groups = '' - is_in_roster = True - contact = gajim.contacts.get_contact_with_highest_priority( - self.account, jid) - if not contact: - is_in_roster = False - name = self.exchange_list[jid][0] - num_list = len(self.exchange_list[jid][1]) - current = 0 - for group in self.exchange_list[jid][1]: - current += 1 - if contact and not group in contact.groups: - is_in_roster = False - if current == num_list: - groups = groups + group - else: - groups = groups + group + ', ' - if not is_in_roster: - show_dialog = True - iter_ = model.append() - model.set(iter_, 0, True, 1, jid, 2, name, 3, groups) + if action == 'add': + for jid in self.exchange_list: + groups = '' + is_in_roster = True + contact = gajim.contacts.get_contact_with_highest_priority( + self.account, jid) + if not contact: + is_in_roster = False + name = self.exchange_list[jid][0] + num_list = len(self.exchange_list[jid][1]) + current = 0 + for group in self.exchange_list[jid][1]: + current += 1 + if contact and not group in contact.groups: + is_in_roster = False + if current == num_list: + groups = groups + group + else: + groups = groups + group + ', ' + if not is_in_roster: + show_dialog = True + iter_ = model.append() + model.set(iter_, 0, True, 1, jid, 2, name, 3, groups) - # Change label for accept_button to action name instead of 'OK'. - self.accept_button_label.set_label(_('Add')) - elif action == 'modify': - for jid in self.exchange_list: - groups = '' - is_in_roster = True - is_right = True - contact = gajim.contacts.get_contact_with_highest_priority( - self.account, jid) - name = self.exchange_list[jid][0] - if not contact: - is_in_roster = False - is_right = False - else: - if name != contact.name: - is_right = False - num_list = len(self.exchange_list[jid][1]) - current = 0 - for group in self.exchange_list[jid][1]: - current += 1 - if contact and not group in contact.groups: - is_right = False - if current == num_list: - groups = groups + group - else: - groups = groups + group + ', ' - if not is_right and is_in_roster: - show_dialog = True - iter_ = model.append() - model.set(iter_, 0, True, 1, jid, 2, name, 3, groups) + # Change label for accept_button to action name instead of 'OK'. + self.accept_button_label.set_label(_('Add')) + elif action == 'modify': + for jid in self.exchange_list: + groups = '' + is_in_roster = True + is_right = True + contact = gajim.contacts.get_contact_with_highest_priority( + self.account, jid) + name = self.exchange_list[jid][0] + if not contact: + is_in_roster = False + is_right = False + else: + if name != contact.name: + is_right = False + num_list = len(self.exchange_list[jid][1]) + current = 0 + for group in self.exchange_list[jid][1]: + current += 1 + if contact and not group in contact.groups: + is_right = False + if current == num_list: + groups = groups + group + else: + groups = groups + group + ', ' + if not is_right and is_in_roster: + show_dialog = True + iter_ = model.append() + model.set(iter_, 0, True, 1, jid, 2, name, 3, groups) - # Change label for accept_button to action name instead of 'OK'. - self.accept_button_label.set_label(_('Modify')) - elif action == 'delete': - for jid in self.exchange_list: - groups = '' - is_in_roster = True - contact = gajim.contacts.get_contact_with_highest_priority( - self.account, jid) - name = self.exchange_list[jid][0] - if not contact: - is_in_roster = False - num_list = len(self.exchange_list[jid][1]) - current = 0 - for group in self.exchange_list[jid][1]: - current += 1 - if current == num_list: - groups = groups + group - else: - groups = groups + group + ', ' - if is_in_roster: - show_dialog = True - iter_ = model.append() - model.set(iter_, 0, True, 1, jid, 2, name, 3, groups) + # Change label for accept_button to action name instead of 'OK'. + self.accept_button_label.set_label(_('Modify')) + elif action == 'delete': + for jid in self.exchange_list: + groups = '' + is_in_roster = True + contact = gajim.contacts.get_contact_with_highest_priority( + self.account, jid) + name = self.exchange_list[jid][0] + if not contact: + is_in_roster = False + num_list = len(self.exchange_list[jid][1]) + current = 0 + for group in self.exchange_list[jid][1]: + current += 1 + if current == num_list: + groups = groups + group + else: + groups = groups + group + ', ' + if is_in_roster: + show_dialog = True + iter_ = model.append() + model.set(iter_, 0, True, 1, jid, 2, name, 3, groups) - # Change label for accept_button to action name instead of 'OK'. - self.accept_button_label.set_label(_('Delete')) + # Change label for accept_button to action name instead of 'OK'. + self.accept_button_label.set_label(_('Delete')) - if show_dialog: - self.window.show_all() - self.xml.connect_signals(self) + if show_dialog: + self.window.show_all() + self.xml.connect_signals(self) - def toggled_callback(self, cell, path): - model = self.items_list_treeview.get_model() - iter_ = model.get_iter(path) - model[iter_][0] = not cell.get_active() + def toggled_callback(self, cell, path): + model = self.items_list_treeview.get_model() + iter_ = model.get_iter(path) + model[iter_][0] = not cell.get_active() - def on_accept_button_clicked(self, widget): - model = self.items_list_treeview.get_model() - iter_ = model.get_iter_root() - if self.action == 'add': - a = 0 - while iter_: - if model[iter_][0]: - a+=1 - # it is selected - #remote_jid = model[iter_][1].decode('utf-8') - message = _('%s suggested me to add you in my roster.' - % self.jid_from) - # keep same groups and same nickname - groups = model[iter_][3].split(', ') - if groups == ['']: - groups = [] - jid = model[iter_][1].decode('utf-8') - if gajim.jid_is_transport(self.jid_from): - gajim.connections[self.account].automatically_added.append( - jid) - gajim.interface.roster.req_sub(self, jid, message, - self.account, groups=groups, nickname=model[iter_][2], - auto_auth=True) - iter_ = model.iter_next(iter_) - InformationDialog(_('Added %s contacts') % str(a)) - elif self.action == 'modify': - a = 0 - while iter_: - if model[iter_][0]: - a+=1 - # it is selected - jid = model[iter_][1].decode('utf-8') - # keep same groups and same nickname - groups = model[iter_][3].split(', ') - if groups == ['']: - groups = [] - for u in gajim.contacts.get_contact(self.account, jid): - u.name = model[iter_][2] - gajim.connections[self.account].update_contact(jid, - model[iter_][2], groups) - self.draw_contact(jid, account) - # Update opened chat - ctrl = gajim.interface.msg_win_mgr.get_control(jid, self.account) - if ctrl: - ctrl.update_ui() - win = gajim.interface.msg_win_mgr.get_window(jid, - self.account) - win.redraw_tab(ctrl) - win.show_title() - iter_ = model.iter_next(iter_) - elif self.action == 'delete': - a = 0 - while iter_: - if model[iter_][0]: - a+=1 - # it is selected - jid = model[iter_][1].decode('utf-8') - gajim.connections[self.account].unsubscribe(jid) - gajim.interface.roster.remove_contact(jid, self.account) - gajim.contacts.remove_jid(self.account, jid) - iter_ = model.iter_next(iter_) - InformationDialog(_('Removed %s contacts') % str(a)) - self.window.destroy() + def on_accept_button_clicked(self, widget): + model = self.items_list_treeview.get_model() + iter_ = model.get_iter_root() + if self.action == 'add': + a = 0 + while iter_: + if model[iter_][0]: + a+=1 + # it is selected + #remote_jid = model[iter_][1].decode('utf-8') + message = _('%s suggested me to add you in my roster.' + % self.jid_from) + # keep same groups and same nickname + groups = model[iter_][3].split(', ') + if groups == ['']: + groups = [] + jid = model[iter_][1].decode('utf-8') + if gajim.jid_is_transport(self.jid_from): + gajim.connections[self.account].automatically_added.append( + jid) + gajim.interface.roster.req_sub(self, jid, message, + self.account, groups=groups, nickname=model[iter_][2], + auto_auth=True) + iter_ = model.iter_next(iter_) + InformationDialog(_('Added %s contacts') % str(a)) + elif self.action == 'modify': + a = 0 + while iter_: + if model[iter_][0]: + a+=1 + # it is selected + jid = model[iter_][1].decode('utf-8') + # keep same groups and same nickname + groups = model[iter_][3].split(', ') + if groups == ['']: + groups = [] + for u in gajim.contacts.get_contact(self.account, jid): + u.name = model[iter_][2] + gajim.connections[self.account].update_contact(jid, + model[iter_][2], groups) + self.draw_contact(jid, account) + # Update opened chat + ctrl = gajim.interface.msg_win_mgr.get_control(jid, self.account) + if ctrl: + ctrl.update_ui() + win = gajim.interface.msg_win_mgr.get_window(jid, + self.account) + win.redraw_tab(ctrl) + win.show_title() + iter_ = model.iter_next(iter_) + elif self.action == 'delete': + a = 0 + while iter_: + if model[iter_][0]: + a+=1 + # it is selected + jid = model[iter_][1].decode('utf-8') + gajim.connections[self.account].unsubscribe(jid) + gajim.interface.roster.remove_contact(jid, self.account) + gajim.contacts.remove_jid(self.account, jid) + iter_ = model.iter_next(iter_) + InformationDialog(_('Removed %s contacts') % str(a)) + self.window.destroy() - def on_cancel_button_clicked(self, widget): - self.window.destroy() + def on_cancel_button_clicked(self, widget): + self.window.destroy() class PrivacyListWindow: - """ - Window that is used for creating NEW or EDITING already there privacy lists - """ + """ + Window that is used for creating NEW or EDITING already there privacy lists + """ - def __init__(self, account, privacy_list_name, action): - '''action is 'EDIT' or 'NEW' depending on if we create a new priv list - or edit an already existing one''' - self.account = account - self.privacy_list_name = privacy_list_name + def __init__(self, account, privacy_list_name, action): + '''action is 'EDIT' or 'NEW' depending on if we create a new priv list + or edit an already existing one''' + self.account = account + self.privacy_list_name = privacy_list_name - # Dicts and Default Values - self.active_rule = '' - self.global_rules = {} - self.list_of_groups = {} + # Dicts and Default Values + self.active_rule = '' + self.global_rules = {} + self.list_of_groups = {} - self.max_order = 0 + self.max_order = 0 - # Default Edit Values - self.edit_rule_type = 'jid' - self.allow_deny = 'allow' + # Default Edit Values + self.edit_rule_type = 'jid' + self.allow_deny = 'allow' - # Connect to gtk builder - self.xml = gtkgui_helpers.get_gtk_builder('privacy_list_window.ui') - self.window = self.xml.get_object('privacy_list_edit_window') + # Connect to gtk builder + self.xml = gtkgui_helpers.get_gtk_builder('privacy_list_window.ui') + self.window = self.xml.get_object('privacy_list_edit_window') - # Add Widgets + # Add Widgets - for widget_to_add in ('title_hbox', 'privacy_lists_title_label', - 'list_of_rules_label', 'add_edit_rule_label', 'delete_open_buttons_hbox', - 'privacy_list_active_checkbutton', 'privacy_list_default_checkbutton', - 'list_of_rules_combobox', 'delete_open_buttons_hbox', - 'delete_rule_button', 'open_rule_button', 'edit_allow_radiobutton', - 'edit_deny_radiobutton', 'edit_type_jabberid_radiobutton', - 'edit_type_jabberid_entry', 'edit_type_group_radiobutton', - 'edit_type_group_combobox', 'edit_type_subscription_radiobutton', - 'edit_type_subscription_combobox', 'edit_type_select_all_radiobutton', - 'edit_queries_send_checkbutton', 'edit_send_messages_checkbutton', - 'edit_view_status_checkbutton', 'edit_all_checkbutton', - 'edit_order_spinbutton', 'new_rule_button', 'save_rule_button', - 'privacy_list_refresh_button', 'privacy_list_close_button', - 'edit_send_status_checkbutton', 'add_edit_vbox', - 'privacy_list_active_checkbutton', 'privacy_list_default_checkbutton'): - self.__dict__[widget_to_add] = self.xml.get_object(widget_to_add) + for widget_to_add in ('title_hbox', 'privacy_lists_title_label', + 'list_of_rules_label', 'add_edit_rule_label', 'delete_open_buttons_hbox', + 'privacy_list_active_checkbutton', 'privacy_list_default_checkbutton', + 'list_of_rules_combobox', 'delete_open_buttons_hbox', + 'delete_rule_button', 'open_rule_button', 'edit_allow_radiobutton', + 'edit_deny_radiobutton', 'edit_type_jabberid_radiobutton', + 'edit_type_jabberid_entry', 'edit_type_group_radiobutton', + 'edit_type_group_combobox', 'edit_type_subscription_radiobutton', + 'edit_type_subscription_combobox', 'edit_type_select_all_radiobutton', + 'edit_queries_send_checkbutton', 'edit_send_messages_checkbutton', + 'edit_view_status_checkbutton', 'edit_all_checkbutton', + 'edit_order_spinbutton', 'new_rule_button', 'save_rule_button', + 'privacy_list_refresh_button', 'privacy_list_close_button', + 'edit_send_status_checkbutton', 'add_edit_vbox', + 'privacy_list_active_checkbutton', 'privacy_list_default_checkbutton'): + self.__dict__[widget_to_add] = self.xml.get_object(widget_to_add) - self.privacy_lists_title_label.set_label( - _('Privacy List %s') % \ - gobject.markup_escape_text(self.privacy_list_name)) + self.privacy_lists_title_label.set_label( + _('Privacy List %s') % \ + gobject.markup_escape_text(self.privacy_list_name)) - if len(gajim.connections) > 1: - title = _('Privacy List for %s') % self.account - else: - title = _('Privacy List') + if len(gajim.connections) > 1: + title = _('Privacy List for %s') % self.account + else: + title = _('Privacy List') - self.delete_rule_button.set_sensitive(False) - self.open_rule_button.set_sensitive(False) - self.privacy_list_active_checkbutton.set_sensitive(False) - self.privacy_list_default_checkbutton.set_sensitive(False) - self.list_of_rules_combobox.set_sensitive(False) + self.delete_rule_button.set_sensitive(False) + self.open_rule_button.set_sensitive(False) + self.privacy_list_active_checkbutton.set_sensitive(False) + self.privacy_list_default_checkbutton.set_sensitive(False) + self.list_of_rules_combobox.set_sensitive(False) - # set jabber id completion - jids_list_store = gtk.ListStore(gobject.TYPE_STRING) - for jid in gajim.contacts.get_jid_list(self.account): - jids_list_store.append([jid]) - jid_entry_completion = gtk.EntryCompletion() - jid_entry_completion.set_text_column(0) - jid_entry_completion.set_model(jids_list_store) - jid_entry_completion.set_popup_completion(True) - self.edit_type_jabberid_entry.set_completion(jid_entry_completion) - if action == 'EDIT': - self.refresh_rules() + # set jabber id completion + jids_list_store = gtk.ListStore(gobject.TYPE_STRING) + for jid in gajim.contacts.get_jid_list(self.account): + jids_list_store.append([jid]) + jid_entry_completion = gtk.EntryCompletion() + jid_entry_completion.set_text_column(0) + jid_entry_completion.set_model(jids_list_store) + jid_entry_completion.set_popup_completion(True) + self.edit_type_jabberid_entry.set_completion(jid_entry_completion) + if action == 'EDIT': + self.refresh_rules() - count = 0 - for group in gajim.groups[self.account]: - self.list_of_groups[group] = count - count += 1 - self.edit_type_group_combobox.append_text(group) - self.edit_type_group_combobox.set_active(0) + count = 0 + for group in gajim.groups[self.account]: + self.list_of_groups[group] = count + count += 1 + self.edit_type_group_combobox.append_text(group) + self.edit_type_group_combobox.set_active(0) - self.window.set_title(title) + self.window.set_title(title) - self.window.show_all() - self.add_edit_vbox.hide() + self.window.show_all() + self.add_edit_vbox.hide() - self.xml.connect_signals(self) + self.xml.connect_signals(self) - def on_privacy_list_edit_window_destroy(self, widget): - key_name = 'privacy_list_%s' % self.privacy_list_name - if key_name in gajim.interface.instances[self.account]: - del gajim.interface.instances[self.account][key_name] + def on_privacy_list_edit_window_destroy(self, widget): + key_name = 'privacy_list_%s' % self.privacy_list_name + if key_name in gajim.interface.instances[self.account]: + del gajim.interface.instances[self.account][key_name] - def check_active_default(self, a_d_dict): - if a_d_dict['active'] == self.privacy_list_name: - self.privacy_list_active_checkbutton.set_active(True) - else: - self.privacy_list_active_checkbutton.set_active(False) - if a_d_dict['default'] == self.privacy_list_name: - self.privacy_list_default_checkbutton.set_active(True) - else: - self.privacy_list_default_checkbutton.set_active(False) + def check_active_default(self, a_d_dict): + if a_d_dict['active'] == self.privacy_list_name: + self.privacy_list_active_checkbutton.set_active(True) + else: + self.privacy_list_active_checkbutton.set_active(False) + if a_d_dict['default'] == self.privacy_list_name: + self.privacy_list_default_checkbutton.set_active(True) + else: + self.privacy_list_default_checkbutton.set_active(False) - def privacy_list_received(self, rules): - self.list_of_rules_combobox.get_model().clear() - self.global_rules = {} - for rule in rules: - if 'type' in rule: - text_item = _('Order: %(order)s, action: %(action)s, type: %(type)s' - ', value: %(value)s') % {'order': rule['order'], - 'action': rule['action'], 'type': rule['type'], - 'value': rule['value']} - else: - text_item = _('Order: %(order)s, action: %(action)s') % \ - {'order': rule['order'], 'action': rule['action']} - if int(rule['order']) > self.max_order: - self.max_order = int(rule['order']) - self.global_rules[text_item] = rule - self.list_of_rules_combobox.append_text(text_item) - if len(rules) == 0: - self.title_hbox.set_sensitive(False) - self.list_of_rules_combobox.set_sensitive(False) - self.delete_rule_button.set_sensitive(False) - self.open_rule_button.set_sensitive(False) - self.privacy_list_active_checkbutton.set_sensitive(False) - self.privacy_list_default_checkbutton.set_sensitive(False) - else: - self.list_of_rules_combobox.set_active(0) - self.title_hbox.set_sensitive(True) - self.list_of_rules_combobox.set_sensitive(True) - self.delete_rule_button.set_sensitive(True) - self.open_rule_button.set_sensitive(True) - self.privacy_list_active_checkbutton.set_sensitive(True) - self.privacy_list_default_checkbutton.set_sensitive(True) - self.reset_fields() - gajim.connections[self.account].get_active_default_lists() + def privacy_list_received(self, rules): + self.list_of_rules_combobox.get_model().clear() + self.global_rules = {} + for rule in rules: + if 'type' in rule: + text_item = _('Order: %(order)s, action: %(action)s, type: %(type)s' + ', value: %(value)s') % {'order': rule['order'], + 'action': rule['action'], 'type': rule['type'], + 'value': rule['value']} + else: + text_item = _('Order: %(order)s, action: %(action)s') % \ + {'order': rule['order'], 'action': rule['action']} + if int(rule['order']) > self.max_order: + self.max_order = int(rule['order']) + self.global_rules[text_item] = rule + self.list_of_rules_combobox.append_text(text_item) + if len(rules) == 0: + self.title_hbox.set_sensitive(False) + self.list_of_rules_combobox.set_sensitive(False) + self.delete_rule_button.set_sensitive(False) + self.open_rule_button.set_sensitive(False) + self.privacy_list_active_checkbutton.set_sensitive(False) + self.privacy_list_default_checkbutton.set_sensitive(False) + else: + self.list_of_rules_combobox.set_active(0) + self.title_hbox.set_sensitive(True) + self.list_of_rules_combobox.set_sensitive(True) + self.delete_rule_button.set_sensitive(True) + self.open_rule_button.set_sensitive(True) + self.privacy_list_active_checkbutton.set_sensitive(True) + self.privacy_list_default_checkbutton.set_sensitive(True) + self.reset_fields() + gajim.connections[self.account].get_active_default_lists() - def refresh_rules(self): - gajim.connections[self.account].get_privacy_list(self.privacy_list_name) + def refresh_rules(self): + gajim.connections[self.account].get_privacy_list(self.privacy_list_name) - def on_delete_rule_button_clicked(self, widget): - tags = [] - for rule in self.global_rules: - if rule != self.list_of_rules_combobox.get_active_text(): - tags.append(self.global_rules[rule]) - gajim.connections[self.account].set_privacy_list( - self.privacy_list_name, tags) - self.privacy_list_received(tags) - self.add_edit_vbox.hide() - if not tags: # we removed latest rule - if 'privacy_lists' in gajim.interface.instances[self.account]: - win = gajim.interface.instances[self.account]['privacy_lists'] - win.remove_privacy_list_from_combobox(self.privacy_list_name) - win.draw_widgets() + def on_delete_rule_button_clicked(self, widget): + tags = [] + for rule in self.global_rules: + if rule != self.list_of_rules_combobox.get_active_text(): + tags.append(self.global_rules[rule]) + gajim.connections[self.account].set_privacy_list( + self.privacy_list_name, tags) + self.privacy_list_received(tags) + self.add_edit_vbox.hide() + if not tags: # we removed latest rule + if 'privacy_lists' in gajim.interface.instances[self.account]: + win = gajim.interface.instances[self.account]['privacy_lists'] + win.remove_privacy_list_from_combobox(self.privacy_list_name) + win.draw_widgets() - def on_open_rule_button_clicked(self, widget): - self.add_edit_rule_label.set_label( - _('Edit a rule')) - active_num = self.list_of_rules_combobox.get_active() - if active_num == -1: - self.active_rule = '' - else: - self.active_rule = \ - self.list_of_rules_combobox.get_active_text().decode('utf-8') - if self.active_rule != '': - rule_info = self.global_rules[self.active_rule] - self.edit_order_spinbutton.set_value(int(rule_info['order'])) - if 'type' in rule_info: - if rule_info['type'] == 'jid': - self.edit_type_jabberid_radiobutton.set_active(True) - self.edit_type_jabberid_entry.set_text(rule_info['value']) - elif rule_info['type'] == 'group': - self.edit_type_group_radiobutton.set_active(True) - if rule_info['value'] in self.list_of_groups: - self.edit_type_group_combobox.set_active( - self.list_of_groups[rule_info['value']]) - else: - self.edit_type_group_combobox.set_active(0) - elif rule_info['type'] == 'subscription': - self.edit_type_subscription_radiobutton.set_active(True) - sub_value = rule_info['value'] - if sub_value == 'none': - self.edit_type_subscription_combobox.set_active(0) - elif sub_value == 'both': - self.edit_type_subscription_combobox.set_active(1) - elif sub_value == 'from': - self.edit_type_subscription_combobox.set_active(2) - elif sub_value == 'to': - self.edit_type_subscription_combobox.set_active(3) - else: - self.edit_type_select_all_radiobutton.set_active(True) - else: - self.edit_type_select_all_radiobutton.set_active(True) - self.edit_send_messages_checkbutton.set_active(False) - self.edit_queries_send_checkbutton.set_active(False) - self.edit_view_status_checkbutton.set_active(False) - self.edit_send_status_checkbutton.set_active(False) - self.edit_all_checkbutton.set_active(False) - if not rule_info['child']: - self.edit_all_checkbutton.set_active(True) - else: - if 'presence-out' in rule_info['child']: - self.edit_send_status_checkbutton.set_active(True) - if 'presence-in' in rule_info['child']: - self.edit_view_status_checkbutton.set_active(True) - if 'iq' in rule_info['child']: - self.edit_queries_send_checkbutton.set_active(True) - if 'message' in rule_info['child']: - self.edit_send_messages_checkbutton.set_active(True) + def on_open_rule_button_clicked(self, widget): + self.add_edit_rule_label.set_label( + _('Edit a rule')) + active_num = self.list_of_rules_combobox.get_active() + if active_num == -1: + self.active_rule = '' + else: + self.active_rule = \ + self.list_of_rules_combobox.get_active_text().decode('utf-8') + if self.active_rule != '': + rule_info = self.global_rules[self.active_rule] + self.edit_order_spinbutton.set_value(int(rule_info['order'])) + if 'type' in rule_info: + if rule_info['type'] == 'jid': + self.edit_type_jabberid_radiobutton.set_active(True) + self.edit_type_jabberid_entry.set_text(rule_info['value']) + elif rule_info['type'] == 'group': + self.edit_type_group_radiobutton.set_active(True) + if rule_info['value'] in self.list_of_groups: + self.edit_type_group_combobox.set_active( + self.list_of_groups[rule_info['value']]) + else: + self.edit_type_group_combobox.set_active(0) + elif rule_info['type'] == 'subscription': + self.edit_type_subscription_radiobutton.set_active(True) + sub_value = rule_info['value'] + if sub_value == 'none': + self.edit_type_subscription_combobox.set_active(0) + elif sub_value == 'both': + self.edit_type_subscription_combobox.set_active(1) + elif sub_value == 'from': + self.edit_type_subscription_combobox.set_active(2) + elif sub_value == 'to': + self.edit_type_subscription_combobox.set_active(3) + else: + self.edit_type_select_all_radiobutton.set_active(True) + else: + self.edit_type_select_all_radiobutton.set_active(True) + self.edit_send_messages_checkbutton.set_active(False) + self.edit_queries_send_checkbutton.set_active(False) + self.edit_view_status_checkbutton.set_active(False) + self.edit_send_status_checkbutton.set_active(False) + self.edit_all_checkbutton.set_active(False) + if not rule_info['child']: + self.edit_all_checkbutton.set_active(True) + else: + if 'presence-out' in rule_info['child']: + self.edit_send_status_checkbutton.set_active(True) + if 'presence-in' in rule_info['child']: + self.edit_view_status_checkbutton.set_active(True) + if 'iq' in rule_info['child']: + self.edit_queries_send_checkbutton.set_active(True) + if 'message' in rule_info['child']: + self.edit_send_messages_checkbutton.set_active(True) - if rule_info['action'] == 'allow': - self.edit_allow_radiobutton.set_active(True) - else: - self.edit_deny_radiobutton.set_active(True) - self.add_edit_vbox.show() + if rule_info['action'] == 'allow': + self.edit_allow_radiobutton.set_active(True) + else: + self.edit_deny_radiobutton.set_active(True) + self.add_edit_vbox.show() - def on_edit_all_checkbutton_toggled(self, widget): - if widget.get_active(): - self.edit_send_messages_checkbutton.set_active(True) - self.edit_queries_send_checkbutton.set_active(True) - self.edit_view_status_checkbutton.set_active(True) - self.edit_send_status_checkbutton.set_active(True) - self.edit_send_messages_checkbutton.set_sensitive(False) - self.edit_queries_send_checkbutton.set_sensitive(False) - self.edit_view_status_checkbutton.set_sensitive(False) - self.edit_send_status_checkbutton.set_sensitive(False) - else: - self.edit_send_messages_checkbutton.set_active(False) - self.edit_queries_send_checkbutton.set_active(False) - self.edit_view_status_checkbutton.set_active(False) - self.edit_send_status_checkbutton.set_active(False) - self.edit_send_messages_checkbutton.set_sensitive(True) - self.edit_queries_send_checkbutton.set_sensitive(True) - self.edit_view_status_checkbutton.set_sensitive(True) - self.edit_send_status_checkbutton.set_sensitive(True) + def on_edit_all_checkbutton_toggled(self, widget): + if widget.get_active(): + self.edit_send_messages_checkbutton.set_active(True) + self.edit_queries_send_checkbutton.set_active(True) + self.edit_view_status_checkbutton.set_active(True) + self.edit_send_status_checkbutton.set_active(True) + self.edit_send_messages_checkbutton.set_sensitive(False) + self.edit_queries_send_checkbutton.set_sensitive(False) + self.edit_view_status_checkbutton.set_sensitive(False) + self.edit_send_status_checkbutton.set_sensitive(False) + else: + self.edit_send_messages_checkbutton.set_active(False) + self.edit_queries_send_checkbutton.set_active(False) + self.edit_view_status_checkbutton.set_active(False) + self.edit_send_status_checkbutton.set_active(False) + self.edit_send_messages_checkbutton.set_sensitive(True) + self.edit_queries_send_checkbutton.set_sensitive(True) + self.edit_view_status_checkbutton.set_sensitive(True) + self.edit_send_status_checkbutton.set_sensitive(True) - def on_privacy_list_active_checkbutton_toggled(self, widget): - if widget.get_active(): - gajim.connections[self.account].set_active_list( - self.privacy_list_name) - else: - gajim.connections[self.account].set_active_list(None) + def on_privacy_list_active_checkbutton_toggled(self, widget): + if widget.get_active(): + gajim.connections[self.account].set_active_list( + self.privacy_list_name) + else: + gajim.connections[self.account].set_active_list(None) - def on_privacy_list_default_checkbutton_toggled(self, widget): - if widget.get_active(): - gajim.connections[self.account].set_default_list( - self.privacy_list_name) - else: - gajim.connections[self.account].set_default_list(None) + def on_privacy_list_default_checkbutton_toggled(self, widget): + if widget.get_active(): + gajim.connections[self.account].set_default_list( + self.privacy_list_name) + else: + gajim.connections[self.account].set_default_list(None) - def on_new_rule_button_clicked(self, widget): - self.reset_fields() - self.add_edit_vbox.show() + def on_new_rule_button_clicked(self, widget): + self.reset_fields() + self.add_edit_vbox.show() - def reset_fields(self): - self.edit_type_jabberid_entry.set_text('') - self.edit_allow_radiobutton.set_active(True) - self.edit_type_jabberid_radiobutton.set_active(True) - self.active_rule = '' - self.edit_send_messages_checkbutton.set_active(False) - self.edit_queries_send_checkbutton.set_active(False) - self.edit_view_status_checkbutton.set_active(False) - self.edit_send_status_checkbutton.set_active(False) - self.edit_all_checkbutton.set_active(False) - self.edit_order_spinbutton.set_value(self.max_order + 1) - self.edit_type_group_combobox.set_active(0) - self.edit_type_subscription_combobox.set_active(0) - self.add_edit_rule_label.set_label( - _('Add a rule')) + def reset_fields(self): + self.edit_type_jabberid_entry.set_text('') + self.edit_allow_radiobutton.set_active(True) + self.edit_type_jabberid_radiobutton.set_active(True) + self.active_rule = '' + self.edit_send_messages_checkbutton.set_active(False) + self.edit_queries_send_checkbutton.set_active(False) + self.edit_view_status_checkbutton.set_active(False) + self.edit_send_status_checkbutton.set_active(False) + self.edit_all_checkbutton.set_active(False) + self.edit_order_spinbutton.set_value(self.max_order + 1) + self.edit_type_group_combobox.set_active(0) + self.edit_type_subscription_combobox.set_active(0) + self.add_edit_rule_label.set_label( + _('Add a rule')) - def get_current_tags(self): - if self.edit_type_jabberid_radiobutton.get_active(): - edit_type = 'jid' - edit_value = self.edit_type_jabberid_entry.get_text() - elif self.edit_type_group_radiobutton.get_active(): - edit_type = 'group' - edit_value = self.edit_type_group_combobox.get_active_text() - elif self.edit_type_subscription_radiobutton.get_active(): - edit_type = 'subscription' - subs = ['none', 'both', 'from', 'to'] - edit_value = subs[self.edit_type_subscription_combobox.get_active()] - elif self.edit_type_select_all_radiobutton.get_active(): - edit_type = '' - edit_value = '' - edit_order = str(self.edit_order_spinbutton.get_value_as_int()) - if self.edit_allow_radiobutton.get_active(): - edit_deny = 'allow' - else: - edit_deny = 'deny' - child = [] - if not self.edit_all_checkbutton.get_active(): - if self.edit_send_messages_checkbutton.get_active(): - child.append('message') - if self.edit_queries_send_checkbutton.get_active(): - child.append('iq') - if self.edit_send_status_checkbutton.get_active(): - child.append('presence-out') - if self.edit_view_status_checkbutton.get_active(): - child.append('presence-in') - if edit_type != '': - return {'order': edit_order, 'action': edit_deny, - 'type': edit_type, 'value': edit_value, 'child': child} - return {'order': edit_order, 'action': edit_deny, 'child': child} + def get_current_tags(self): + if self.edit_type_jabberid_radiobutton.get_active(): + edit_type = 'jid' + edit_value = self.edit_type_jabberid_entry.get_text() + elif self.edit_type_group_radiobutton.get_active(): + edit_type = 'group' + edit_value = self.edit_type_group_combobox.get_active_text() + elif self.edit_type_subscription_radiobutton.get_active(): + edit_type = 'subscription' + subs = ['none', 'both', 'from', 'to'] + edit_value = subs[self.edit_type_subscription_combobox.get_active()] + elif self.edit_type_select_all_radiobutton.get_active(): + edit_type = '' + edit_value = '' + edit_order = str(self.edit_order_spinbutton.get_value_as_int()) + if self.edit_allow_radiobutton.get_active(): + edit_deny = 'allow' + else: + edit_deny = 'deny' + child = [] + if not self.edit_all_checkbutton.get_active(): + if self.edit_send_messages_checkbutton.get_active(): + child.append('message') + if self.edit_queries_send_checkbutton.get_active(): + child.append('iq') + if self.edit_send_status_checkbutton.get_active(): + child.append('presence-out') + if self.edit_view_status_checkbutton.get_active(): + child.append('presence-in') + if edit_type != '': + return {'order': edit_order, 'action': edit_deny, + 'type': edit_type, 'value': edit_value, 'child': child} + return {'order': edit_order, 'action': edit_deny, 'child': child} - def on_save_rule_button_clicked(self, widget): - tags=[] - current_tags = self.get_current_tags() - if int(current_tags['order']) > self.max_order: - self.max_order = int(current_tags['order']) - if self.active_rule == '': - tags.append(current_tags) + def on_save_rule_button_clicked(self, widget): + tags=[] + current_tags = self.get_current_tags() + if int(current_tags['order']) > self.max_order: + self.max_order = int(current_tags['order']) + if self.active_rule == '': + tags.append(current_tags) - for rule in self.global_rules: - if rule != self.active_rule: - tags.append(self.global_rules[rule]) - else: - tags.append(current_tags) + for rule in self.global_rules: + if rule != self.active_rule: + tags.append(self.global_rules[rule]) + else: + tags.append(current_tags) - gajim.connections[self.account].set_privacy_list( - self.privacy_list_name, tags) - self.refresh_rules() - self.add_edit_vbox.hide() - if 'privacy_lists' in gajim.interface.instances[self.account]: - win = gajim.interface.instances[self.account]['privacy_lists'] - win.add_privacy_list_to_combobox(self.privacy_list_name) - win.draw_widgets() + gajim.connections[self.account].set_privacy_list( + self.privacy_list_name, tags) + self.refresh_rules() + self.add_edit_vbox.hide() + if 'privacy_lists' in gajim.interface.instances[self.account]: + win = gajim.interface.instances[self.account]['privacy_lists'] + win.add_privacy_list_to_combobox(self.privacy_list_name) + win.draw_widgets() - def on_list_of_rules_combobox_changed(self, widget): - self.add_edit_vbox.hide() + def on_list_of_rules_combobox_changed(self, widget): + self.add_edit_vbox.hide() - def on_edit_type_radiobutton_changed(self, widget, radiobutton): - active_bool = widget.get_active() - if active_bool: - self.edit_rule_type = radiobutton + def on_edit_type_radiobutton_changed(self, widget, radiobutton): + active_bool = widget.get_active() + if active_bool: + self.edit_rule_type = radiobutton - def on_edit_allow_radiobutton_changed(self, widget, radiobutton): - active_bool = widget.get_active() - if active_bool: - self.allow_deny = radiobutton + def on_edit_allow_radiobutton_changed(self, widget, radiobutton): + active_bool = widget.get_active() + if active_bool: + self.allow_deny = radiobutton - def on_close_button_clicked(self, widget): - self.window.destroy() + def on_close_button_clicked(self, widget): + self.window.destroy() class PrivacyListsWindow: - """ - Window that is the main window for Privacy Lists; we can list there the - privacy lists and ask to create a new one or edit an already there one - """ - def __init__(self, account): - self.account = account - self.privacy_lists_save = [] + """ + Window that is the main window for Privacy Lists; we can list there the + privacy lists and ask to create a new one or edit an already there one + """ + def __init__(self, account): + self.account = account + self.privacy_lists_save = [] - self.xml = gtkgui_helpers.get_gtk_builder('privacy_lists_window.ui') + self.xml = gtkgui_helpers.get_gtk_builder('privacy_lists_window.ui') - self.window = self.xml.get_object('privacy_lists_first_window') - for widget_to_add in ('list_of_privacy_lists_combobox', - 'delete_privacy_list_button', 'open_privacy_list_button', - 'new_privacy_list_button', 'new_privacy_list_entry', - 'privacy_lists_refresh_button', 'close_privacy_lists_window_button'): - self.__dict__[widget_to_add] = self.xml.get_object( - widget_to_add) + self.window = self.xml.get_object('privacy_lists_first_window') + for widget_to_add in ('list_of_privacy_lists_combobox', + 'delete_privacy_list_button', 'open_privacy_list_button', + 'new_privacy_list_button', 'new_privacy_list_entry', + 'privacy_lists_refresh_button', 'close_privacy_lists_window_button'): + self.__dict__[widget_to_add] = self.xml.get_object( + widget_to_add) - self.draw_privacy_lists_in_combobox([]) - self.privacy_lists_refresh() + self.draw_privacy_lists_in_combobox([]) + self.privacy_lists_refresh() - self.enabled = True + self.enabled = True - if len(gajim.connections) > 1: - title = _('Privacy Lists for %s') % self.account - else: - title = _('Privacy Lists') + if len(gajim.connections) > 1: + title = _('Privacy Lists for %s') % self.account + else: + title = _('Privacy Lists') - self.window.set_title(title) + self.window.set_title(title) - self.window.show_all() + self.window.show_all() - self.xml.connect_signals(self) + self.xml.connect_signals(self) - def on_privacy_lists_first_window_destroy(self, widget): - if 'privacy_lists' in gajim.interface.instances[self.account]: - del gajim.interface.instances[self.account]['privacy_lists'] + def on_privacy_lists_first_window_destroy(self, widget): + if 'privacy_lists' in gajim.interface.instances[self.account]: + del gajim.interface.instances[self.account]['privacy_lists'] - def remove_privacy_list_from_combobox(self, privacy_list): - if privacy_list not in self.privacy_lists_save: - return - privacy_list_index = self.privacy_lists_save.index(privacy_list) - self.list_of_privacy_lists_combobox.remove_text(privacy_list_index) - self.privacy_lists_save.remove(privacy_list) + def remove_privacy_list_from_combobox(self, privacy_list): + if privacy_list not in self.privacy_lists_save: + return + privacy_list_index = self.privacy_lists_save.index(privacy_list) + self.list_of_privacy_lists_combobox.remove_text(privacy_list_index) + self.privacy_lists_save.remove(privacy_list) - def add_privacy_list_to_combobox(self, privacy_list): - if privacy_list in self.privacy_lists_save: - return - self.list_of_privacy_lists_combobox.append_text(privacy_list) - self.privacy_lists_save.append(privacy_list) + def add_privacy_list_to_combobox(self, privacy_list): + if privacy_list in self.privacy_lists_save: + return + self.list_of_privacy_lists_combobox.append_text(privacy_list) + self.privacy_lists_save.append(privacy_list) - def draw_privacy_lists_in_combobox(self, privacy_lists): - self.list_of_privacy_lists_combobox.set_active(-1) - self.list_of_privacy_lists_combobox.get_model().clear() - self.privacy_lists_save = [] - for add_item in privacy_lists: - self.add_privacy_list_to_combobox(add_item) - self.draw_widgets() + def draw_privacy_lists_in_combobox(self, privacy_lists): + self.list_of_privacy_lists_combobox.set_active(-1) + self.list_of_privacy_lists_combobox.get_model().clear() + self.privacy_lists_save = [] + for add_item in privacy_lists: + self.add_privacy_list_to_combobox(add_item) + self.draw_widgets() - def draw_widgets(self): - if len(self.privacy_lists_save) == 0: - self.list_of_privacy_lists_combobox.set_sensitive(False) - self.open_privacy_list_button.set_sensitive(False) - self.delete_privacy_list_button.set_sensitive(False) - else: - self.list_of_privacy_lists_combobox.set_sensitive(True) - self.list_of_privacy_lists_combobox.set_active(0) - self.open_privacy_list_button.set_sensitive(True) - self.delete_privacy_list_button.set_sensitive(True) + def draw_widgets(self): + if len(self.privacy_lists_save) == 0: + self.list_of_privacy_lists_combobox.set_sensitive(False) + self.open_privacy_list_button.set_sensitive(False) + self.delete_privacy_list_button.set_sensitive(False) + else: + self.list_of_privacy_lists_combobox.set_sensitive(True) + self.list_of_privacy_lists_combobox.set_active(0) + self.open_privacy_list_button.set_sensitive(True) + self.delete_privacy_list_button.set_sensitive(True) - def on_close_button_clicked(self, widget): - self.window.destroy() + def on_close_button_clicked(self, widget): + self.window.destroy() - def on_delete_privacy_list_button_clicked(self, widget): - active_list = self.privacy_lists_save[ - self.list_of_privacy_lists_combobox.get_active()] - gajim.connections[self.account].del_privacy_list(active_list) + def on_delete_privacy_list_button_clicked(self, widget): + active_list = self.privacy_lists_save[ + self.list_of_privacy_lists_combobox.get_active()] + gajim.connections[self.account].del_privacy_list(active_list) - def privacy_list_removed(self, active_list): - self.privacy_lists_save.remove(active_list) - self.privacy_lists_received({'lists': self.privacy_lists_save}) + def privacy_list_removed(self, active_list): + self.privacy_lists_save.remove(active_list) + self.privacy_lists_received({'lists': self.privacy_lists_save}) - def privacy_lists_received(self, lists): - if not lists: - return - privacy_lists = [] - for privacy_list in lists['lists']: - privacy_lists.append(privacy_list) - self.draw_privacy_lists_in_combobox(privacy_lists) + def privacy_lists_received(self, lists): + if not lists: + return + privacy_lists = [] + for privacy_list in lists['lists']: + privacy_lists.append(privacy_list) + self.draw_privacy_lists_in_combobox(privacy_lists) - def privacy_lists_refresh(self): - gajim.connections[self.account].get_privacy_lists() + def privacy_lists_refresh(self): + gajim.connections[self.account].get_privacy_lists() - def on_new_privacy_list_button_clicked(self, widget): - name = self.new_privacy_list_entry.get_text() - if not name: - ErrorDialog(_('Invalid List Name'), - _('You must enter a name to create a privacy list.')) - return - key_name = 'privacy_list_%s' % name - if key_name in gajim.interface.instances[self.account]: - gajim.interface.instances[self.account][key_name].window.present() - else: - gajim.interface.instances[self.account][key_name] = \ - PrivacyListWindow(self.account, name, 'NEW') - self.new_privacy_list_entry.set_text('') + def on_new_privacy_list_button_clicked(self, widget): + name = self.new_privacy_list_entry.get_text() + if not name: + ErrorDialog(_('Invalid List Name'), + _('You must enter a name to create a privacy list.')) + return + key_name = 'privacy_list_%s' % name + if key_name in gajim.interface.instances[self.account]: + gajim.interface.instances[self.account][key_name].window.present() + else: + gajim.interface.instances[self.account][key_name] = \ + PrivacyListWindow(self.account, name, 'NEW') + self.new_privacy_list_entry.set_text('') - def on_privacy_lists_refresh_button_clicked(self, widget): - self.privacy_lists_refresh() + def on_privacy_lists_refresh_button_clicked(self, widget): + self.privacy_lists_refresh() - def on_open_privacy_list_button_clicked(self, widget): - name = self.privacy_lists_save[ - self.list_of_privacy_lists_combobox.get_active()] - key_name = 'privacy_list_%s' % name - if key_name in gajim.interface.instances[self.account]: - gajim.interface.instances[self.account][key_name].window.present() - else: - gajim.interface.instances[self.account][key_name] = \ - PrivacyListWindow(self.account, name, 'EDIT') + def on_open_privacy_list_button_clicked(self, widget): + name = self.privacy_lists_save[ + self.list_of_privacy_lists_combobox.get_active()] + key_name = 'privacy_list_%s' % name + if key_name in gajim.interface.instances[self.account]: + gajim.interface.instances[self.account][key_name].window.present() + else: + gajim.interface.instances[self.account][key_name] = \ + PrivacyListWindow(self.account, name, 'EDIT') class InvitationReceivedDialog: - def __init__(self, account, room_jid, contact_jid, password=None, - comment=None, is_continued=False): + def __init__(self, account, room_jid, contact_jid, password=None, + comment=None, is_continued=False): - self.room_jid = room_jid - self.account = account - self.password = password - self.is_continued = is_continued + self.room_jid = room_jid + self.account = account + self.password = password + self.is_continued = is_continued - pritext = _('''You are invited to a groupchat''') - #Don't translate $Contact - if is_continued: - sectext = _('$Contact has invited you to join a discussion') - else: - sectext = _('$Contact has invited you to group chat %(room_jid)s')\ - % {'room_jid': room_jid} - contact = gajim.contacts.get_first_contact_from_jid(account, contact_jid) - contact_text = contact and contact.name or contact_jid - sectext = sectext.replace('$Contact', contact_text) + pritext = _('''You are invited to a groupchat''') + #Don't translate $Contact + if is_continued: + sectext = _('$Contact has invited you to join a discussion') + else: + sectext = _('$Contact has invited you to group chat %(room_jid)s')\ + % {'room_jid': room_jid} + contact = gajim.contacts.get_first_contact_from_jid(account, contact_jid) + contact_text = contact and contact.name or contact_jid + sectext = sectext.replace('$Contact', contact_text) - if comment: # only if not None and not '' - comment = gobject.markup_escape_text(comment) - comment = _('Comment: %s') % comment - sectext += '\n\n%s' % comment - sectext += '\n\n' + _('Do you want to accept the invitation?') + if comment: # only if not None and not '' + comment = gobject.markup_escape_text(comment) + comment = _('Comment: %s') % comment + sectext += '\n\n%s' % comment + sectext += '\n\n' + _('Do you want to accept the invitation?') - def on_yes(checked): - try: - if self.is_continued: - gajim.interface.join_gc_room(self.account, self.room_jid, - gajim.nicks[self.account], None, is_continued=True) - else: - JoinGroupchatWindow(self.account, self.room_jid) - except GajimGeneralException: - pass + def on_yes(checked): + try: + if self.is_continued: + gajim.interface.join_gc_room(self.account, self.room_jid, + gajim.nicks[self.account], None, is_continued=True) + else: + JoinGroupchatWindow(self.account, self.room_jid) + except GajimGeneralException: + pass - YesNoDialog(pritext, sectext, on_response_yes=on_yes) + YesNoDialog(pritext, sectext, on_response_yes=on_yes) class ProgressDialog: - def __init__(self, title_text, during_text, messages_queue): - """ - During text is what to show during the procedure, messages_queue has the - message to show in the textview - """ - self.xml = gtkgui_helpers.get_gtk_builder('progress_dialog.ui') - self.dialog = self.xml.get_object('progress_dialog') - self.label = self.xml.get_object('label') - self.label.set_markup('' + during_text + '') - self.progressbar = self.xml.get_object('progressbar') - self.dialog.set_title(title_text) - self.dialog.set_default_size(450, 250) - self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - self.dialog.show_all() - self.xml.connect_signals(self) + def __init__(self, title_text, during_text, messages_queue): + """ + During text is what to show during the procedure, messages_queue has the + message to show in the textview + """ + self.xml = gtkgui_helpers.get_gtk_builder('progress_dialog.ui') + self.dialog = self.xml.get_object('progress_dialog') + self.label = self.xml.get_object('label') + self.label.set_markup('' + during_text + '') + self.progressbar = self.xml.get_object('progressbar') + self.dialog.set_title(title_text) + self.dialog.set_default_size(450, 250) + self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + self.dialog.show_all() + self.xml.connect_signals(self) - self.update_progressbar_timeout_id = gobject.timeout_add(100, - self.update_progressbar) + self.update_progressbar_timeout_id = gobject.timeout_add(100, + self.update_progressbar) - def update_progressbar(self): - if self.dialog: - self.progressbar.pulse() - return True # loop forever - return False + def update_progressbar(self): + if self.dialog: + self.progressbar.pulse() + return True # loop forever + return False - def on_progress_dialog_delete_event(self, widget, event): - return True # WM's X button or Escape key should not destroy the window + def on_progress_dialog_delete_event(self, widget, event): + return True # WM's X button or Escape key should not destroy the window class SoundChooserDialog(FileChooserDialog): - def __init__(self, path_to_snd_file='', on_response_ok=None, - on_response_cancel=None): - """ - Optionally accepts path_to_snd_file so it has that as selected - """ - def on_ok(widget, callback): - """ - Check if file exists and call callback - """ - path_to_snd_file = self.get_filename() - path_to_snd_file = gtkgui_helpers.decode_filechooser_file_paths( - (path_to_snd_file,))[0] - if os.path.exists(path_to_snd_file): - callback(widget, path_to_snd_file) + def __init__(self, path_to_snd_file='', on_response_ok=None, + on_response_cancel=None): + """ + Optionally accepts path_to_snd_file so it has that as selected + """ + def on_ok(widget, callback): + """ + Check if file exists and call callback + """ + path_to_snd_file = self.get_filename() + path_to_snd_file = gtkgui_helpers.decode_filechooser_file_paths( + (path_to_snd_file,))[0] + if os.path.exists(path_to_snd_file): + callback(widget, path_to_snd_file) - FileChooserDialog.__init__(self, - title_text = _('Choose Sound'), - action = gtk.FILE_CHOOSER_ACTION_OPEN, - buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_OPEN, gtk.RESPONSE_OK), - default_response = gtk.RESPONSE_OK, - current_folder = gajim.config.get('last_sounds_dir'), - on_response_ok = (on_ok, on_response_ok), - on_response_cancel = on_response_cancel) + FileChooserDialog.__init__(self, + title_text = _('Choose Sound'), + action = gtk.FILE_CHOOSER_ACTION_OPEN, + buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_OPEN, gtk.RESPONSE_OK), + default_response = gtk.RESPONSE_OK, + current_folder = gajim.config.get('last_sounds_dir'), + on_response_ok = (on_ok, on_response_ok), + on_response_cancel = on_response_cancel) - filter_ = gtk.FileFilter() - filter_.set_name(_('All files')) - filter_.add_pattern('*') - self.add_filter(filter_) + filter_ = gtk.FileFilter() + filter_.set_name(_('All files')) + filter_.add_pattern('*') + self.add_filter(filter_) - filter_ = gtk.FileFilter() - filter_.set_name(_('Wav Sounds')) - filter_.add_pattern('*.wav') - self.add_filter(filter_) - self.set_filter(filter_) + filter_ = gtk.FileFilter() + filter_.set_name(_('Wav Sounds')) + filter_.add_pattern('*.wav') + self.add_filter(filter_) + self.set_filter(filter_) - path_to_snd_file = helpers.check_soundfile_path(path_to_snd_file) - if path_to_snd_file: - # set_filename accept only absolute path - path_to_snd_file = os.path.abspath(path_to_snd_file) - self.set_filename(path_to_snd_file) + path_to_snd_file = helpers.check_soundfile_path(path_to_snd_file) + if path_to_snd_file: + # set_filename accept only absolute path + path_to_snd_file = os.path.abspath(path_to_snd_file) + self.set_filename(path_to_snd_file) class ImageChooserDialog(FileChooserDialog): - def __init__(self, path_to_file='', on_response_ok=None, - on_response_cancel=None): - """ - Optionally accepts path_to_snd_file so it has that as selected - """ - def on_ok(widget, callback): - '''check if file exists and call callback''' - path_to_file = self.get_filename() - if not path_to_file: - return - path_to_file = gtkgui_helpers.decode_filechooser_file_paths( - (path_to_file,))[0] - if os.path.exists(path_to_file): - if isinstance(callback, tuple): - callback[0](widget, path_to_file, *callback[1:]) - else: - callback(widget, path_to_file) + def __init__(self, path_to_file='', on_response_ok=None, + on_response_cancel=None): + """ + Optionally accepts path_to_snd_file so it has that as selected + """ + def on_ok(widget, callback): + '''check if file exists and call callback''' + path_to_file = self.get_filename() + if not path_to_file: + return + path_to_file = gtkgui_helpers.decode_filechooser_file_paths( + (path_to_file,))[0] + if os.path.exists(path_to_file): + if isinstance(callback, tuple): + callback[0](widget, path_to_file, *callback[1:]) + else: + callback(widget, path_to_file) - try: - if os.name == 'nt': - path = helpers.get_my_pictures_path() - else: - path = os.environ['HOME'] - except Exception: - path = '' - FileChooserDialog.__init__(self, - title_text = _('Choose Image'), - action = gtk.FILE_CHOOSER_ACTION_OPEN, - buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_OPEN, gtk.RESPONSE_OK), - default_response = gtk.RESPONSE_OK, - current_folder = path, - on_response_ok = (on_ok, on_response_ok), - on_response_cancel = on_response_cancel) + try: + if os.name == 'nt': + path = helpers.get_my_pictures_path() + else: + path = os.environ['HOME'] + except Exception: + path = '' + FileChooserDialog.__init__(self, + title_text = _('Choose Image'), + action = gtk.FILE_CHOOSER_ACTION_OPEN, + buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_OPEN, gtk.RESPONSE_OK), + default_response = gtk.RESPONSE_OK, + current_folder = path, + on_response_ok = (on_ok, on_response_ok), + on_response_cancel = on_response_cancel) - if on_response_cancel: - self.connect('destroy', on_response_cancel) + if on_response_cancel: + self.connect('destroy', on_response_cancel) - filter_ = gtk.FileFilter() - filter_.set_name(_('All files')) - filter_.add_pattern('*') - self.add_filter(filter_) + filter_ = gtk.FileFilter() + filter_.set_name(_('All files')) + filter_.add_pattern('*') + self.add_filter(filter_) - filter_ = gtk.FileFilter() - filter_.set_name(_('Images')) - filter_.add_mime_type('image/png') - filter_.add_mime_type('image/jpeg') - filter_.add_mime_type('image/gif') - filter_.add_mime_type('image/tiff') - filter_.add_mime_type('image/svg+xml') - filter_.add_mime_type('image/x-xpixmap') # xpm - self.add_filter(filter_) - self.set_filter(filter_) + filter_ = gtk.FileFilter() + filter_.set_name(_('Images')) + filter_.add_mime_type('image/png') + filter_.add_mime_type('image/jpeg') + filter_.add_mime_type('image/gif') + filter_.add_mime_type('image/tiff') + filter_.add_mime_type('image/svg+xml') + filter_.add_mime_type('image/x-xpixmap') # xpm + self.add_filter(filter_) + self.set_filter(filter_) - if path_to_file: - self.set_filename(path_to_file) + if path_to_file: + self.set_filename(path_to_file) - self.set_use_preview_label(False) - self.set_preview_widget(gtk.Image()) - self.connect('selection-changed', self.update_preview) + self.set_use_preview_label(False) + self.set_preview_widget(gtk.Image()) + self.connect('selection-changed', self.update_preview) - def update_preview(self, widget): - path_to_file = widget.get_preview_filename() - if path_to_file is None or os.path.isdir(path_to_file): - # nothing to preview or directory - # make sure you clean image do show nothing - widget.get_preview_widget().set_from_file(None) - return - try: - pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(path_to_file, 100, 100) - except gobject.GError: - return - widget.get_preview_widget().set_from_pixbuf(pixbuf) + def update_preview(self, widget): + path_to_file = widget.get_preview_filename() + if path_to_file is None or os.path.isdir(path_to_file): + # nothing to preview or directory + # make sure you clean image do show nothing + widget.get_preview_widget().set_from_file(None) + return + try: + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(path_to_file, 100, 100) + except gobject.GError: + return + widget.get_preview_widget().set_from_pixbuf(pixbuf) class AvatarChooserDialog(ImageChooserDialog): - def __init__(self, path_to_file='', on_response_ok=None, - on_response_cancel=None, on_response_clear=None): - ImageChooserDialog.__init__(self, path_to_file, on_response_ok, - on_response_cancel) - button = gtk.Button(None, gtk.STOCK_CLEAR) - self.response_clear = on_response_clear - if on_response_clear: - button.connect('clicked', self.on_clear) - button.show_all() - self.action_area.pack_start(button) - self.action_area.reorder_child(button, 0) + def __init__(self, path_to_file='', on_response_ok=None, + on_response_cancel=None, on_response_clear=None): + ImageChooserDialog.__init__(self, path_to_file, on_response_ok, + on_response_cancel) + button = gtk.Button(None, gtk.STOCK_CLEAR) + self.response_clear = on_response_clear + if on_response_clear: + button.connect('clicked', self.on_clear) + button.show_all() + self.action_area.pack_start(button) + self.action_area.reorder_child(button, 0) - def on_clear(self, widget): - if isinstance(self.response_clear, tuple): - self.response_clear[0](widget, *self.response_clear[1:]) - else: - self.response_clear(widget) + def on_clear(self, widget): + if isinstance(self.response_clear, tuple): + self.response_clear[0](widget, *self.response_clear[1:]) + else: + self.response_clear(widget) class AddSpecialNotificationDialog: - def __init__(self, jid): - """ - jid is the jid for which we want to add special notification (sound and - notification popups) - """ - self.xml = gtkgui_helpers.get_gtk_builder('add_special_notification_window.ui') - self.window = self.xml.get_object('add_special_notification_window') - self.condition_combobox = self.xml.get_object('condition_combobox') - self.condition_combobox.set_active(0) - self.notification_popup_yes_no_combobox = self.xml.get_object( - 'notification_popup_yes_no_combobox') - self.notification_popup_yes_no_combobox.set_active(0) - self.listen_sound_combobox = self.xml.get_object('listen_sound_combobox') - self.listen_sound_combobox.set_active(0) + def __init__(self, jid): + """ + jid is the jid for which we want to add special notification (sound and + notification popups) + """ + self.xml = gtkgui_helpers.get_gtk_builder('add_special_notification_window.ui') + self.window = self.xml.get_object('add_special_notification_window') + self.condition_combobox = self.xml.get_object('condition_combobox') + self.condition_combobox.set_active(0) + self.notification_popup_yes_no_combobox = self.xml.get_object( + 'notification_popup_yes_no_combobox') + self.notification_popup_yes_no_combobox.set_active(0) + self.listen_sound_combobox = self.xml.get_object('listen_sound_combobox') + self.listen_sound_combobox.set_active(0) - self.jid = jid - self.xml.get_object('when_foo_becomes_label').set_text( - _('When %s becomes:') % self.jid) + self.jid = jid + self.xml.get_object('when_foo_becomes_label').set_text( + _('When %s becomes:') % self.jid) - self.window.set_title(_('Adding Special Notification for %s') % jid) - self.window.show_all() - self.xml.connect_signals(self) + self.window.set_title(_('Adding Special Notification for %s') % jid) + self.window.show_all() + self.xml.connect_signals(self) - def on_cancel_button_clicked(self, widget): - self.window.destroy() + def on_cancel_button_clicked(self, widget): + self.window.destroy() - def on_add_special_notification_window_delete_event(self, widget, event): - self.window.destroy() + def on_add_special_notification_window_delete_event(self, widget, event): + self.window.destroy() - def on_listen_sound_combobox_changed(self, widget): - active = widget.get_active() - if active == 1: # user selected 'choose sound' - def on_ok(widget, path_to_snd_file): - pass - #print path_to_snd_file + def on_listen_sound_combobox_changed(self, widget): + active = widget.get_active() + if active == 1: # user selected 'choose sound' + def on_ok(widget, path_to_snd_file): + pass + #print path_to_snd_file - def on_cancel(widget): - widget.set_active(0) # go back to No Sound + def on_cancel(widget): + widget.set_active(0) # go back to No Sound - self.dialog = SoundChooserDialog(on_response_ok=on_ok, - on_response_cancel=on_cancel) + self.dialog = SoundChooserDialog(on_response_ok=on_ok, + on_response_cancel=on_cancel) - def on_ok_button_clicked(self, widget): - conditions = ('online', 'chat', 'online_and_chat', - 'away', 'xa', 'away_and_xa', 'dnd', 'xa_and_dnd', 'offline') - active = self.condition_combobox.get_active() + def on_ok_button_clicked(self, widget): + conditions = ('online', 'chat', 'online_and_chat', + 'away', 'xa', 'away_and_xa', 'dnd', 'xa_and_dnd', 'offline') + active = self.condition_combobox.get_active() - active_iter = self.listen_sound_combobox.get_active_iter() - listen_sound_model = self.listen_sound_combobox.get_model() + active_iter = self.listen_sound_combobox.get_active_iter() + listen_sound_model = self.listen_sound_combobox.get_model() class AdvancedNotificationsWindow: - events_list = ['message_received', 'contact_connected', - 'contact_disconnected', 'contact_change_status', 'gc_msg_highlight', - 'gc_msg', 'ft_request', 'ft_started', 'ft_finished'] - recipient_types_list = ['contact', 'group', 'all'] - config_options = ['event', 'recipient_type', 'recipients', 'status', - 'tab_opened', 'sound', 'sound_file', 'popup', 'auto_open', - 'run_command', 'command', 'systray', 'roster', 'urgency_hint'] - def __init__(self): - self.xml = gtkgui_helpers.get_gtk_builder('advanced_notifications_window.ui') - self.window = self.xml.get_object('advanced_notifications_window') - for w in ('conditions_treeview', 'config_vbox', 'event_combobox', - 'recipient_type_combobox', 'recipient_list_entry', 'delete_button', - 'status_hbox', 'use_sound_cb', 'disable_sound_cb', 'use_popup_cb', - 'disable_popup_cb', 'use_auto_open_cb', 'disable_auto_open_cb', - 'use_systray_cb', 'disable_systray_cb', 'use_roster_cb', - 'disable_roster_cb', 'tab_opened_cb', 'not_tab_opened_cb', - 'sound_entry', 'sound_file_hbox', 'up_button', 'down_button', - 'run_command_cb', 'command_entry', 'urgency_hint_cb'): - self.__dict__[w] = self.xml.get_object(w) + events_list = ['message_received', 'contact_connected', + 'contact_disconnected', 'contact_change_status', 'gc_msg_highlight', + 'gc_msg', 'ft_request', 'ft_started', 'ft_finished'] + recipient_types_list = ['contact', 'group', 'all'] + config_options = ['event', 'recipient_type', 'recipients', 'status', + 'tab_opened', 'sound', 'sound_file', 'popup', 'auto_open', + 'run_command', 'command', 'systray', 'roster', 'urgency_hint'] + def __init__(self): + self.xml = gtkgui_helpers.get_gtk_builder('advanced_notifications_window.ui') + self.window = self.xml.get_object('advanced_notifications_window') + for w in ('conditions_treeview', 'config_vbox', 'event_combobox', + 'recipient_type_combobox', 'recipient_list_entry', 'delete_button', + 'status_hbox', 'use_sound_cb', 'disable_sound_cb', 'use_popup_cb', + 'disable_popup_cb', 'use_auto_open_cb', 'disable_auto_open_cb', + 'use_systray_cb', 'disable_systray_cb', 'use_roster_cb', + 'disable_roster_cb', 'tab_opened_cb', 'not_tab_opened_cb', + 'sound_entry', 'sound_file_hbox', 'up_button', 'down_button', + 'run_command_cb', 'command_entry', 'urgency_hint_cb'): + self.__dict__[w] = self.xml.get_object(w) - # Contains status checkboxes - childs = self.status_hbox.get_children() + # Contains status checkboxes + childs = self.status_hbox.get_children() - self.all_status_rb = childs[0] - self.special_status_rb = childs[1] - self.online_cb = childs[2] - self.away_cb = childs[3] - self.xa_cb = childs[4] - self.dnd_cb = childs[5] - self.invisible_cb = childs[6] + self.all_status_rb = childs[0] + self.special_status_rb = childs[1] + self.online_cb = childs[2] + self.away_cb = childs[3] + self.xa_cb = childs[4] + self.dnd_cb = childs[5] + self.invisible_cb = childs[6] - model = gtk.ListStore(int, str) - model.set_sort_column_id(0, gtk.SORT_ASCENDING) - model.clear() - self.conditions_treeview.set_model(model) + model = gtk.ListStore(int, str) + model.set_sort_column_id(0, gtk.SORT_ASCENDING) + model.clear() + self.conditions_treeview.set_model(model) - ## means number - col = gtk.TreeViewColumn(_('#')) - self.conditions_treeview.append_column(col) - renderer = gtk.CellRendererText() - col.pack_start(renderer, expand=False) - col.set_attributes(renderer, text=0) + ## means number + col = gtk.TreeViewColumn(_('#')) + self.conditions_treeview.append_column(col) + renderer = gtk.CellRendererText() + col.pack_start(renderer, expand=False) + col.set_attributes(renderer, text=0) - col = gtk.TreeViewColumn(_('Condition')) - self.conditions_treeview.append_column(col) - renderer = gtk.CellRendererText() - col.pack_start(renderer, expand=True) - col.set_attributes(renderer, text=1) + col = gtk.TreeViewColumn(_('Condition')) + self.conditions_treeview.append_column(col) + renderer = gtk.CellRendererText() + col.pack_start(renderer, expand=True) + col.set_attributes(renderer, text=1) - self.xml.connect_signals(self) + self.xml.connect_signals(self) - # Fill conditions_treeview - num = 0 - while gajim.config.get_per('notifications', str(num)): - iter_ = model.append((num, '')) - path = model.get_path(iter_) - self.conditions_treeview.set_cursor(path) - self.active_num = num - self.initiate_rule_state() - self.set_treeview_string() - num += 1 + # Fill conditions_treeview + num = 0 + while gajim.config.get_per('notifications', str(num)): + iter_ = model.append((num, '')) + path = model.get_path(iter_) + self.conditions_treeview.set_cursor(path) + self.active_num = num + self.initiate_rule_state() + self.set_treeview_string() + num += 1 - # No rule selected at init time - self.conditions_treeview.get_selection().unselect_all() - self.active_num = -1 - self.config_vbox.set_sensitive(False) - self.delete_button.set_sensitive(False) - self.down_button.set_sensitive(False) - self.up_button.set_sensitive(False) + # No rule selected at init time + self.conditions_treeview.get_selection().unselect_all() + self.active_num = -1 + self.config_vbox.set_sensitive(False) + self.delete_button.set_sensitive(False) + self.down_button.set_sensitive(False) + self.up_button.set_sensitive(False) - self.window.show_all() + self.window.show_all() - def initiate_rule_state(self): - """ - Set values for all widgets - """ - if self.active_num < 0: - return - # event - value = gajim.config.get_per('notifications', str(self.active_num), - 'event') - if value: - self.event_combobox.set_active(self.events_list.index(value)) - else: - self.event_combobox.set_active(-1) - # recipient_type - value = gajim.config.get_per('notifications', str(self.active_num), - 'recipient_type') - if value: - self.recipient_type_combobox.set_active( - self.recipient_types_list.index(value)) - else: - self.recipient_type_combobox.set_active(-1) - # recipient - value = gajim.config.get_per('notifications', str(self.active_num), - 'recipients') - if not value: - value = '' - self.recipient_list_entry.set_text(value) - # status - value = gajim.config.get_per('notifications', str(self.active_num), - 'status') - if value == 'all': - self.all_status_rb.set_active(True) - else: - self.special_status_rb.set_active(True) - values = value.split() - for v in ('online', 'away', 'xa', 'dnd', 'invisible'): - if v in values: - self.__dict__[v + '_cb'].set_active(True) - else: - self.__dict__[v + '_cb'].set_active(False) - self.on_status_radiobutton_toggled(self.all_status_rb) - # tab_opened - value = gajim.config.get_per('notifications', str(self.active_num), - 'tab_opened') - self.tab_opened_cb.set_active(True) - self.not_tab_opened_cb.set_active(True) - if value == 'no': - self.tab_opened_cb.set_active(False) - elif value == 'yes': - self.not_tab_opened_cb.set_active(False) - # sound_file - value = gajim.config.get_per('notifications', str(self.active_num), - 'sound_file') - self.sound_entry.set_text(value) - # sound, popup, auto_open, systray, roster - for option in ('sound', 'popup', 'auto_open', 'systray', 'roster'): - value = gajim.config.get_per('notifications', str(self.active_num), - option) - if value == 'yes': - self.__dict__['use_' + option + '_cb'].set_active(True) - else: - self.__dict__['use_' + option + '_cb'].set_active(False) - if value == 'no': - self.__dict__['disable_' + option + '_cb'].set_active(True) - else: - self.__dict__['disable_' + option + '_cb'].set_active(False) - # run_command - value = gajim.config.get_per('notifications', str(self.active_num), - 'run_command') - self.run_command_cb.set_active(value) - # command - value = gajim.config.get_per('notifications', str(self.active_num), - 'command') - self.command_entry.set_text(value) - # urgency_hint - value = gajim.config.get_per('notifications', str(self.active_num), - 'urgency_hint') - self.urgency_hint_cb.set_active(value) + def initiate_rule_state(self): + """ + Set values for all widgets + """ + if self.active_num < 0: + return + # event + value = gajim.config.get_per('notifications', str(self.active_num), + 'event') + if value: + self.event_combobox.set_active(self.events_list.index(value)) + else: + self.event_combobox.set_active(-1) + # recipient_type + value = gajim.config.get_per('notifications', str(self.active_num), + 'recipient_type') + if value: + self.recipient_type_combobox.set_active( + self.recipient_types_list.index(value)) + else: + self.recipient_type_combobox.set_active(-1) + # recipient + value = gajim.config.get_per('notifications', str(self.active_num), + 'recipients') + if not value: + value = '' + self.recipient_list_entry.set_text(value) + # status + value = gajim.config.get_per('notifications', str(self.active_num), + 'status') + if value == 'all': + self.all_status_rb.set_active(True) + else: + self.special_status_rb.set_active(True) + values = value.split() + for v in ('online', 'away', 'xa', 'dnd', 'invisible'): + if v in values: + self.__dict__[v + '_cb'].set_active(True) + else: + self.__dict__[v + '_cb'].set_active(False) + self.on_status_radiobutton_toggled(self.all_status_rb) + # tab_opened + value = gajim.config.get_per('notifications', str(self.active_num), + 'tab_opened') + self.tab_opened_cb.set_active(True) + self.not_tab_opened_cb.set_active(True) + if value == 'no': + self.tab_opened_cb.set_active(False) + elif value == 'yes': + self.not_tab_opened_cb.set_active(False) + # sound_file + value = gajim.config.get_per('notifications', str(self.active_num), + 'sound_file') + self.sound_entry.set_text(value) + # sound, popup, auto_open, systray, roster + for option in ('sound', 'popup', 'auto_open', 'systray', 'roster'): + value = gajim.config.get_per('notifications', str(self.active_num), + option) + if value == 'yes': + self.__dict__['use_' + option + '_cb'].set_active(True) + else: + self.__dict__['use_' + option + '_cb'].set_active(False) + if value == 'no': + self.__dict__['disable_' + option + '_cb'].set_active(True) + else: + self.__dict__['disable_' + option + '_cb'].set_active(False) + # run_command + value = gajim.config.get_per('notifications', str(self.active_num), + 'run_command') + self.run_command_cb.set_active(value) + # command + value = gajim.config.get_per('notifications', str(self.active_num), + 'command') + self.command_entry.set_text(value) + # urgency_hint + value = gajim.config.get_per('notifications', str(self.active_num), + 'urgency_hint') + self.urgency_hint_cb.set_active(value) - def set_treeview_string(self): - (model, iter_) = self.conditions_treeview.get_selection().get_selected() - if not iter_: - return - event = self.event_combobox.get_active_text() - recipient_type = self.recipient_type_combobox.get_active_text() - recipient = '' - if recipient_type != 'everybody': - recipient = self.recipient_list_entry.get_text() - if self.all_status_rb.get_active(): - status = '' - else: - status = _('when I am ') - for st in ('online', 'away', 'xa', 'dnd', 'invisible'): - if self.__dict__[st + '_cb'].get_active(): - status += helpers.get_uf_show(st) + ' ' - model[iter_][1] = "When %s for %s %s %s" % (event, recipient_type, - recipient, status) + def set_treeview_string(self): + (model, iter_) = self.conditions_treeview.get_selection().get_selected() + if not iter_: + return + event = self.event_combobox.get_active_text() + recipient_type = self.recipient_type_combobox.get_active_text() + recipient = '' + if recipient_type != 'everybody': + recipient = self.recipient_list_entry.get_text() + if self.all_status_rb.get_active(): + status = '' + else: + status = _('when I am ') + for st in ('online', 'away', 'xa', 'dnd', 'invisible'): + if self.__dict__[st + '_cb'].get_active(): + status += helpers.get_uf_show(st) + ' ' + model[iter_][1] = "When %s for %s %s %s" % (event, recipient_type, + recipient, status) - def on_conditions_treeview_cursor_changed(self, widget): - (model, iter_) = widget.get_selection().get_selected() - if not iter_: - self.active_num = -1 - return - self.active_num = model[iter_][0] - if self.active_num == 0: - self.up_button.set_sensitive(False) - else: - self.up_button.set_sensitive(True) - max = self.conditions_treeview.get_model().iter_n_children(None) - if self.active_num == max - 1: - self.down_button.set_sensitive(False) - else: - self.down_button.set_sensitive(True) - self.initiate_rule_state() - self.config_vbox.set_sensitive(True) - self.delete_button.set_sensitive(True) + def on_conditions_treeview_cursor_changed(self, widget): + (model, iter_) = widget.get_selection().get_selected() + if not iter_: + self.active_num = -1 + return + self.active_num = model[iter_][0] + if self.active_num == 0: + self.up_button.set_sensitive(False) + else: + self.up_button.set_sensitive(True) + max = self.conditions_treeview.get_model().iter_n_children(None) + if self.active_num == max - 1: + self.down_button.set_sensitive(False) + else: + self.down_button.set_sensitive(True) + self.initiate_rule_state() + self.config_vbox.set_sensitive(True) + self.delete_button.set_sensitive(True) - def on_new_button_clicked(self, widget): - model = self.conditions_treeview.get_model() - num = self.conditions_treeview.get_model().iter_n_children(None) - gajim.config.add_per('notifications', str(num)) - iter_ = model.append((num, '')) - path = model.get_path(iter_) - self.conditions_treeview.set_cursor(path) - self.active_num = num - self.set_treeview_string() - self.config_vbox.set_sensitive(True) + def on_new_button_clicked(self, widget): + model = self.conditions_treeview.get_model() + num = self.conditions_treeview.get_model().iter_n_children(None) + gajim.config.add_per('notifications', str(num)) + iter_ = model.append((num, '')) + path = model.get_path(iter_) + self.conditions_treeview.set_cursor(path) + self.active_num = num + self.set_treeview_string() + self.config_vbox.set_sensitive(True) - def on_delete_button_clicked(self, widget): - (model, iter_) = self.conditions_treeview.get_selection().get_selected() - if not iter_: - return - # up all others - iter2 = model.iter_next(iter_) - num = self.active_num - while iter2: - num = model[iter2][0] - model[iter2][0] = num - 1 - for opt in self.config_options: - val = gajim.config.get_per('notifications', str(num), opt) - gajim.config.set_per('notifications', str(num - 1), opt, val) - iter2 = model.iter_next(iter2) - model.remove(iter_) - gajim.config.del_per('notifications', str(num)) # delete latest - self.active_num = -1 - self.config_vbox.set_sensitive(False) - self.delete_button.set_sensitive(False) - self.up_button.set_sensitive(False) - self.down_button.set_sensitive(False) + def on_delete_button_clicked(self, widget): + (model, iter_) = self.conditions_treeview.get_selection().get_selected() + if not iter_: + return + # up all others + iter2 = model.iter_next(iter_) + num = self.active_num + while iter2: + num = model[iter2][0] + model[iter2][0] = num - 1 + for opt in self.config_options: + val = gajim.config.get_per('notifications', str(num), opt) + gajim.config.set_per('notifications', str(num - 1), opt, val) + iter2 = model.iter_next(iter2) + model.remove(iter_) + gajim.config.del_per('notifications', str(num)) # delete latest + self.active_num = -1 + self.config_vbox.set_sensitive(False) + self.delete_button.set_sensitive(False) + self.up_button.set_sensitive(False) + self.down_button.set_sensitive(False) - def on_up_button_clicked(self, widget): - (model, iter_) = self.conditions_treeview.get_selection().\ - get_selected() - if not iter_: - return - for opt in self.config_options: - val = gajim.config.get_per('notifications', str(self.active_num), opt) - val2 = gajim.config.get_per('notifications', str(self.active_num - 1), - opt) - gajim.config.set_per('notifications', str(self.active_num), opt, val2) - gajim.config.set_per('notifications', str(self.active_num - 1), opt, - val) + def on_up_button_clicked(self, widget): + (model, iter_) = self.conditions_treeview.get_selection().\ + get_selected() + if not iter_: + return + for opt in self.config_options: + val = gajim.config.get_per('notifications', str(self.active_num), opt) + val2 = gajim.config.get_per('notifications', str(self.active_num - 1), + opt) + gajim.config.set_per('notifications', str(self.active_num), opt, val2) + gajim.config.set_per('notifications', str(self.active_num - 1), opt, + val) - model[iter_][0] = self.active_num - 1 - # get previous iter - path = model.get_path(iter_) - iter_ = model.get_iter((path[0] - 1,)) - model[iter_][0] = self.active_num - self.on_conditions_treeview_cursor_changed(self.conditions_treeview) + model[iter_][0] = self.active_num - 1 + # get previous iter + path = model.get_path(iter_) + iter_ = model.get_iter((path[0] - 1,)) + model[iter_][0] = self.active_num + self.on_conditions_treeview_cursor_changed(self.conditions_treeview) - def on_down_button_clicked(self, widget): - (model, iter_) = self.conditions_treeview.get_selection().get_selected() - if not iter_: - return - for opt in self.config_options: - val = gajim.config.get_per('notifications', str(self.active_num), opt) - val2 = gajim.config.get_per('notifications', str(self.active_num + 1), - opt) - gajim.config.set_per('notifications', str(self.active_num), opt, val2) - gajim.config.set_per('notifications', str(self.active_num + 1), opt, - val) + def on_down_button_clicked(self, widget): + (model, iter_) = self.conditions_treeview.get_selection().get_selected() + if not iter_: + return + for opt in self.config_options: + val = gajim.config.get_per('notifications', str(self.active_num), opt) + val2 = gajim.config.get_per('notifications', str(self.active_num + 1), + opt) + gajim.config.set_per('notifications', str(self.active_num), opt, val2) + gajim.config.set_per('notifications', str(self.active_num + 1), opt, + val) - model[iter_][0] = self.active_num + 1 - iter_ = model.iter_next(iter_) - model[iter_][0] = self.active_num - self.on_conditions_treeview_cursor_changed(self.conditions_treeview) + model[iter_][0] = self.active_num + 1 + iter_ = model.iter_next(iter_) + model[iter_][0] = self.active_num + self.on_conditions_treeview_cursor_changed(self.conditions_treeview) - def on_event_combobox_changed(self, widget): - if self.active_num < 0: - return - active = self.event_combobox.get_active() - if active == -1: - event = '' - else: - event = self.events_list[active] - gajim.config.set_per('notifications', str(self.active_num), 'event', - event) - self.set_treeview_string() + def on_event_combobox_changed(self, widget): + if self.active_num < 0: + return + active = self.event_combobox.get_active() + if active == -1: + event = '' + else: + event = self.events_list[active] + gajim.config.set_per('notifications', str(self.active_num), 'event', + event) + self.set_treeview_string() - def on_recipient_type_combobox_changed(self, widget): - if self.active_num < 0: - return - recipient_type = self.recipient_types_list[self.recipient_type_combobox.\ - get_active()] - gajim.config.set_per('notifications', str(self.active_num), - 'recipient_type', recipient_type) - if recipient_type == 'all': - self.recipient_list_entry.hide() - else: - self.recipient_list_entry.show() - self.set_treeview_string() + def on_recipient_type_combobox_changed(self, widget): + if self.active_num < 0: + return + recipient_type = self.recipient_types_list[self.recipient_type_combobox.\ + get_active()] + gajim.config.set_per('notifications', str(self.active_num), + 'recipient_type', recipient_type) + if recipient_type == 'all': + self.recipient_list_entry.hide() + else: + self.recipient_list_entry.show() + self.set_treeview_string() - def on_recipient_list_entry_changed(self, widget): - if self.active_num < 0: - return - recipients = widget.get_text().decode('utf-8') - #TODO: do some check - gajim.config.set_per('notifications', str(self.active_num), - 'recipients', recipients) - self.set_treeview_string() + def on_recipient_list_entry_changed(self, widget): + if self.active_num < 0: + return + recipients = widget.get_text().decode('utf-8') + #TODO: do some check + gajim.config.set_per('notifications', str(self.active_num), + 'recipients', recipients) + self.set_treeview_string() - def set_status_config(self): - if self.active_num < 0: - return - status = '' - for st in ('online', 'away', 'xa', 'dnd', 'invisible'): - if self.__dict__[st + '_cb'].get_active(): - status += st + ' ' - if status: - status = status[:-1] - gajim.config.set_per('notifications', str(self.active_num), 'status', - status) - self.set_treeview_string() + def set_status_config(self): + if self.active_num < 0: + return + status = '' + for st in ('online', 'away', 'xa', 'dnd', 'invisible'): + if self.__dict__[st + '_cb'].get_active(): + status += st + ' ' + if status: + status = status[:-1] + gajim.config.set_per('notifications', str(self.active_num), 'status', + status) + self.set_treeview_string() - def on_status_radiobutton_toggled(self, widget): - if self.active_num < 0: - return - if self.all_status_rb.get_active(): - gajim.config.set_per('notifications', str(self.active_num), 'status', - 'all') - # 'All status' clicked - for st in ('online', 'away', 'xa', 'dnd', 'invisible'): - self.__dict__[st + '_cb'].hide() + def on_status_radiobutton_toggled(self, widget): + if self.active_num < 0: + return + if self.all_status_rb.get_active(): + gajim.config.set_per('notifications', str(self.active_num), 'status', + 'all') + # 'All status' clicked + for st in ('online', 'away', 'xa', 'dnd', 'invisible'): + self.__dict__[st + '_cb'].hide() - self.special_status_rb.show() - else: - self.set_status_config() - # 'special status' clicked - for st in ('online', 'away', 'xa', 'dnd', 'invisible'): - self.__dict__[st + '_cb'].show() + self.special_status_rb.show() + else: + self.set_status_config() + # 'special status' clicked + for st in ('online', 'away', 'xa', 'dnd', 'invisible'): + self.__dict__[st + '_cb'].show() - self.special_status_rb.hide() - self.set_treeview_string() + self.special_status_rb.hide() + self.set_treeview_string() - def on_status_cb_toggled(self, widget): - if self.active_num < 0: - return - self.set_status_config() + def on_status_cb_toggled(self, widget): + if self.active_num < 0: + return + self.set_status_config() - # tab_opened OR (not xor) not_tab_opened must be active - def on_tab_opened_cb_toggled(self, widget): - if self.active_num < 0: - return - if self.tab_opened_cb.get_active(): - if self.not_tab_opened_cb.get_active(): - gajim.config.set_per('notifications', str(self.active_num), - 'tab_opened', 'both') - else: - gajim.config.set_per('notifications', str(self.active_num), - 'tab_opened', 'yes') - elif not self.not_tab_opened_cb.get_active(): - self.not_tab_opened_cb.set_active(True) - gajim.config.set_per('notifications', str(self.active_num), - 'tab_opened', 'no') + # tab_opened OR (not xor) not_tab_opened must be active + def on_tab_opened_cb_toggled(self, widget): + if self.active_num < 0: + return + if self.tab_opened_cb.get_active(): + if self.not_tab_opened_cb.get_active(): + gajim.config.set_per('notifications', str(self.active_num), + 'tab_opened', 'both') + else: + gajim.config.set_per('notifications', str(self.active_num), + 'tab_opened', 'yes') + elif not self.not_tab_opened_cb.get_active(): + self.not_tab_opened_cb.set_active(True) + gajim.config.set_per('notifications', str(self.active_num), + 'tab_opened', 'no') - def on_not_tab_opened_cb_toggled(self, widget): - if self.active_num < 0: - return - if self.not_tab_opened_cb.get_active(): - if self.tab_opened_cb.get_active(): - gajim.config.set_per('notifications', str(self.active_num), - 'tab_opened', 'both') - else: - gajim.config.set_per('notifications', str(self.active_num), - 'tab_opened', 'no') - elif not self.tab_opened_cb.get_active(): - self.tab_opened_cb.set_active(True) - gajim.config.set_per('notifications', str(self.active_num), - 'tab_opened', 'yes') + def on_not_tab_opened_cb_toggled(self, widget): + if self.active_num < 0: + return + if self.not_tab_opened_cb.get_active(): + if self.tab_opened_cb.get_active(): + gajim.config.set_per('notifications', str(self.active_num), + 'tab_opened', 'both') + else: + gajim.config.set_per('notifications', str(self.active_num), + 'tab_opened', 'no') + elif not self.tab_opened_cb.get_active(): + self.tab_opened_cb.set_active(True) + gajim.config.set_per('notifications', str(self.active_num), + 'tab_opened', 'yes') - def on_use_it_toggled(self, widget, oposite_widget, option): - if widget.get_active(): - if oposite_widget.get_active(): - oposite_widget.set_active(False) - gajim.config.set_per('notifications', str(self.active_num), option, - 'yes') - elif oposite_widget.get_active(): - gajim.config.set_per('notifications', str(self.active_num), option, - 'no') - else: - gajim.config.set_per('notifications', str(self.active_num), option, '') + def on_use_it_toggled(self, widget, oposite_widget, option): + if widget.get_active(): + if oposite_widget.get_active(): + oposite_widget.set_active(False) + gajim.config.set_per('notifications', str(self.active_num), option, + 'yes') + elif oposite_widget.get_active(): + gajim.config.set_per('notifications', str(self.active_num), option, + 'no') + else: + gajim.config.set_per('notifications', str(self.active_num), option, '') - def on_disable_it_toggled(self, widget, oposite_widget, option): - if widget.get_active(): - if oposite_widget.get_active(): - oposite_widget.set_active(False) - gajim.config.set_per('notifications', str(self.active_num), option, - 'no') - elif oposite_widget.get_active(): - gajim.config.set_per('notifications', str(self.active_num), option, - 'yes') - else: - gajim.config.set_per('notifications', str(self.active_num), option, '') + def on_disable_it_toggled(self, widget, oposite_widget, option): + if widget.get_active(): + if oposite_widget.get_active(): + oposite_widget.set_active(False) + gajim.config.set_per('notifications', str(self.active_num), option, + 'no') + elif oposite_widget.get_active(): + gajim.config.set_per('notifications', str(self.active_num), option, + 'yes') + else: + gajim.config.set_per('notifications', str(self.active_num), option, '') - def on_use_sound_cb_toggled(self, widget): - self.on_use_it_toggled(widget, self.disable_sound_cb, 'sound') - if widget.get_active(): - self.sound_file_hbox.set_sensitive(True) - else: - self.sound_file_hbox.set_sensitive(False) + def on_use_sound_cb_toggled(self, widget): + self.on_use_it_toggled(widget, self.disable_sound_cb, 'sound') + if widget.get_active(): + self.sound_file_hbox.set_sensitive(True) + else: + self.sound_file_hbox.set_sensitive(False) - def on_browse_for_sounds_button_clicked(self, widget, data=None): - if self.active_num < 0: - return + def on_browse_for_sounds_button_clicked(self, widget, data=None): + if self.active_num < 0: + return - def on_ok(widget, path_to_snd_file): - dialog.destroy() - if not path_to_snd_file: - path_to_snd_file = '' - gajim.config.set_per('notifications', str(self.active_num), - 'sound_file', path_to_snd_file) - self.sound_entry.set_text(path_to_snd_file) + def on_ok(widget, path_to_snd_file): + dialog.destroy() + if not path_to_snd_file: + path_to_snd_file = '' + gajim.config.set_per('notifications', str(self.active_num), + 'sound_file', path_to_snd_file) + self.sound_entry.set_text(path_to_snd_file) - path_to_snd_file = self.sound_entry.get_text().decode('utf-8') - path_to_snd_file = os.path.join(os.getcwd(), path_to_snd_file) - dialog = SoundChooserDialog(path_to_snd_file, on_ok) + path_to_snd_file = self.sound_entry.get_text().decode('utf-8') + path_to_snd_file = os.path.join(os.getcwd(), path_to_snd_file) + dialog = SoundChooserDialog(path_to_snd_file, on_ok) - def on_play_button_clicked(self, widget): - helpers.play_sound_file(self.sound_entry.get_text().decode('utf-8')) + def on_play_button_clicked(self, widget): + helpers.play_sound_file(self.sound_entry.get_text().decode('utf-8')) - def on_disable_sound_cb_toggled(self, widget): - self.on_disable_it_toggled(widget, self.use_sound_cb, 'sound') + def on_disable_sound_cb_toggled(self, widget): + self.on_disable_it_toggled(widget, self.use_sound_cb, 'sound') - def on_sound_entry_changed(self, widget): - gajim.config.set_per('notifications', str(self.active_num), - 'sound_file', widget.get_text().decode('utf-8')) + def on_sound_entry_changed(self, widget): + gajim.config.set_per('notifications', str(self.active_num), + 'sound_file', widget.get_text().decode('utf-8')) - def on_use_popup_cb_toggled(self, widget): - self.on_use_it_toggled(widget, self.disable_popup_cb, 'popup') + def on_use_popup_cb_toggled(self, widget): + self.on_use_it_toggled(widget, self.disable_popup_cb, 'popup') - def on_disable_popup_cb_toggled(self, widget): - self.on_disable_it_toggled(widget, self.use_popup_cb, 'popup') + def on_disable_popup_cb_toggled(self, widget): + self.on_disable_it_toggled(widget, self.use_popup_cb, 'popup') - def on_use_auto_open_cb_toggled(self, widget): - self.on_use_it_toggled(widget, self.disable_auto_open_cb, 'auto_open') + def on_use_auto_open_cb_toggled(self, widget): + self.on_use_it_toggled(widget, self.disable_auto_open_cb, 'auto_open') - def on_disable_auto_open_cb_toggled(self, widget): - self.on_disable_it_toggled(widget, self.use_auto_open_cb, 'auto_open') + def on_disable_auto_open_cb_toggled(self, widget): + self.on_disable_it_toggled(widget, self.use_auto_open_cb, 'auto_open') - def on_run_command_cb_toggled(self, widget): - gajim.config.set_per('notifications', str(self.active_num), 'run_command', - widget.get_active()) - if widget.get_active(): - self.command_entry.set_sensitive(True) - else: - self.command_entry.set_sensitive(False) + def on_run_command_cb_toggled(self, widget): + gajim.config.set_per('notifications', str(self.active_num), 'run_command', + widget.get_active()) + if widget.get_active(): + self.command_entry.set_sensitive(True) + else: + self.command_entry.set_sensitive(False) - def on_command_entry_changed(self, widget): - gajim.config.set_per('notifications', str(self.active_num), 'command', - widget.get_text().decode('utf-8')) + def on_command_entry_changed(self, widget): + gajim.config.set_per('notifications', str(self.active_num), 'command', + widget.get_text().decode('utf-8')) - def on_use_systray_cb_toggled(self, widget): - self.on_use_it_toggled(widget, self.disable_systray_cb, 'systray') + def on_use_systray_cb_toggled(self, widget): + self.on_use_it_toggled(widget, self.disable_systray_cb, 'systray') - def on_disable_systray_cb_toggled(self, widget): - self.on_disable_it_toggled(widget, self.use_systray_cb, 'systray') + def on_disable_systray_cb_toggled(self, widget): + self.on_disable_it_toggled(widget, self.use_systray_cb, 'systray') - def on_use_roster_cb_toggled(self, widget): - self.on_use_it_toggled(widget, self.disable_roster_cb, 'roster') + def on_use_roster_cb_toggled(self, widget): + self.on_use_it_toggled(widget, self.disable_roster_cb, 'roster') - def on_disable_roster_cb_toggled(self, widget): - self.on_disable_it_toggled(widget, self.use_roster_cb, 'roster') + def on_disable_roster_cb_toggled(self, widget): + self.on_disable_it_toggled(widget, self.use_roster_cb, 'roster') - def on_urgency_hint_cb_toggled(self, widget): - gajim.config.set_per('notifications', str(self.active_num), - 'uregency_hint', widget.get_active()) + def on_urgency_hint_cb_toggled(self, widget): + gajim.config.set_per('notifications', str(self.active_num), + 'uregency_hint', widget.get_active()) - def on_close_window(self, widget): - self.window.destroy() + def on_close_window(self, widget): + self.window.destroy() class TransformChatToMUC: - # Keep a reference on windows so garbage collector don't restroy them - instances = [] - def __init__(self, account, jids, preselected=None): - """ - This window is used to trasform a one-to-one chat to a MUC. We do 2 - things: first select the server and then make a guests list - """ + # Keep a reference on windows so garbage collector don't restroy them + instances = [] + def __init__(self, account, jids, preselected=None): + """ + This window is used to trasform a one-to-one chat to a MUC. We do 2 + things: first select the server and then make a guests list + """ - self.instances.append(self) - self.account = account - self.auto_jids = jids - self.preselected_jids = preselected + self.instances.append(self) + self.account = account + self.auto_jids = jids + self.preselected_jids = preselected - self.xml = gtkgui_helpers.get_gtk_builder('chat_to_muc_window.ui') - self.window = self.xml.get_object('chat_to_muc_window') + self.xml = gtkgui_helpers.get_gtk_builder('chat_to_muc_window.ui') + self.window = self.xml.get_object('chat_to_muc_window') - for widget_to_add in ('invite_button', 'cancel_button', - 'server_list_comboboxentry', 'guests_treeview', - 'server_and_guests_hseparator', 'server_select_label'): - self.__dict__[widget_to_add] = self.xml.get_object(widget_to_add) + for widget_to_add in ('invite_button', 'cancel_button', + 'server_list_comboboxentry', 'guests_treeview', + 'server_and_guests_hseparator', 'server_select_label'): + self.__dict__[widget_to_add] = self.xml.get_object(widget_to_add) - server_list = [] - self.servers = gtk.ListStore(str) - self.server_list_comboboxentry.set_model(self.servers) + server_list = [] + self.servers = gtk.ListStore(str) + self.server_list_comboboxentry.set_model(self.servers) - self.server_list_comboboxentry.set_text_column(0) + self.server_list_comboboxentry.set_text_column(0) - # get the muc server of our server - if 'jabber' in gajim.connections[account].muc_jid: - server_list.append(gajim.connections[account].muc_jid['jabber']) - # add servers or recently joined groupchats - recently_groupchat = gajim.config.get('recently_groupchat').split() - for g in recently_groupchat: - server = gajim.get_server_from_jid(g) - if server not in server_list and not server.startswith('irc'): - server_list.append(server) - # add a default server - if not server_list: - server_list.append('conference.jabber.org') + # get the muc server of our server + if 'jabber' in gajim.connections[account].muc_jid: + server_list.append(gajim.connections[account].muc_jid['jabber']) + # add servers or recently joined groupchats + recently_groupchat = gajim.config.get('recently_groupchat').split() + for g in recently_groupchat: + server = gajim.get_server_from_jid(g) + if server not in server_list and not server.startswith('irc'): + server_list.append(server) + # add a default server + if not server_list: + server_list.append('conference.jabber.org') - for s in server_list: - self.servers.append([s]) + for s in server_list: + self.servers.append([s]) - self.server_list_comboboxentry.set_active(0) + self.server_list_comboboxentry.set_active(0) - # set treeview - # name, jid - self.store = gtk.ListStore(gtk.gdk.Pixbuf, str, str) - self.store.set_sort_column_id(1, gtk.SORT_ASCENDING) - self.guests_treeview.set_model(self.store) + # set treeview + # name, jid + self.store = gtk.ListStore(gtk.gdk.Pixbuf, str, str) + self.store.set_sort_column_id(1, gtk.SORT_ASCENDING) + self.guests_treeview.set_model(self.store) - renderer1 = gtk.CellRendererText() - renderer2 = gtk.CellRendererPixbuf() - column = gtk.TreeViewColumn('Status', renderer2, pixbuf=0) - self.guests_treeview.append_column(column) - column = gtk.TreeViewColumn('Name', renderer1, text=1) - self.guests_treeview.append_column(column) + renderer1 = gtk.CellRendererText() + renderer2 = gtk.CellRendererPixbuf() + column = gtk.TreeViewColumn('Status', renderer2, pixbuf=0) + self.guests_treeview.append_column(column) + column = gtk.TreeViewColumn('Name', renderer1, text=1) + self.guests_treeview.append_column(column) - self.guests_treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) + self.guests_treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) - # All contacts beside the following can be invited: - # transports, zeroconf contacts, minimized groupchats - def invitable(contact, contact_transport=None): - return (contact.jid not in self.auto_jids and - contact.jid != gajim.get_jid_from_account(self.account) and - contact.jid not in gajim.interface.minimized_controls[account] and - not contact.is_transport() and - not contact_transport) + # All contacts beside the following can be invited: + # transports, zeroconf contacts, minimized groupchats + def invitable(contact, contact_transport=None): + return (contact.jid not in self.auto_jids and + contact.jid != gajim.get_jid_from_account(self.account) and + contact.jid not in gajim.interface.minimized_controls[account] and + not contact.is_transport() and + not contact_transport) - # set jabber id and pseudos - for account in gajim.contacts.get_accounts(): - if gajim.connections[account].is_zeroconf: - continue - for jid in gajim.contacts.get_jid_list(account): - contact = \ - gajim.contacts.get_contact_with_highest_priority(account, jid) - contact_transport = gajim.get_transport_name_from_jid(jid) - # Add contact if it can be invited - if invitable(contact, contact_transport) and \ - contact.show not in ('offline', 'error'): - img = gajim.interface.jabber_state_images['16'][contact.show] - name = contact.name - if name == '': - name = jid.split('@')[0] - iter_ = self.store.append([img.get_pixbuf(), name, jid]) - # preselect treeview rows - if self.preselected_jids and jid in self.preselected_jids: - path = self.store.get_path(iter_) - self.guests_treeview.get_selection().\ - select_path(path) + # set jabber id and pseudos + for account in gajim.contacts.get_accounts(): + if gajim.connections[account].is_zeroconf: + continue + for jid in gajim.contacts.get_jid_list(account): + contact = \ + gajim.contacts.get_contact_with_highest_priority(account, jid) + contact_transport = gajim.get_transport_name_from_jid(jid) + # Add contact if it can be invited + if invitable(contact, contact_transport) and \ + contact.show not in ('offline', 'error'): + img = gajim.interface.jabber_state_images['16'][contact.show] + name = contact.name + if name == '': + name = jid.split('@')[0] + iter_ = self.store.append([img.get_pixbuf(), name, jid]) + # preselect treeview rows + if self.preselected_jids and jid in self.preselected_jids: + path = self.store.get_path(iter_) + self.guests_treeview.get_selection().\ + select_path(path) - # show all - self.window.show_all() + # show all + self.window.show_all() - self.xml.connect_signals(self) + self.xml.connect_signals(self) - def on_chat_to_muc_window_destroy(self, widget): - self.instances.remove(self) + def on_chat_to_muc_window_destroy(self, widget): + self.instances.remove(self) - def on_chat_to_muc_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: # ESCAPE - self.window.destroy() + def on_chat_to_muc_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: # ESCAPE + self.window.destroy() - def on_invite_button_clicked(self, widget): - server = self.server_list_comboboxentry.get_active_text() - if server == '': - return - gajim.connections[self.account].check_unique_room_id_support(server, self) + def on_invite_button_clicked(self, widget): + server = self.server_list_comboboxentry.get_active_text() + if server == '': + return + gajim.connections[self.account].check_unique_room_id_support(server, self) - def unique_room_id_supported(self, server, room_id): - guest_list = [] - guests = self.guests_treeview.get_selection().get_selected_rows() - for guest in guests[1]: - iter_ = self.store.get_iter(guest) - guest_list.append(self.store[iter_][2].decode('utf-8')) - for guest in self.auto_jids: - guest_list.append(guest) - room_jid = room_id + '@' + server - gajim.automatic_rooms[self.account][room_jid] = {} - gajim.automatic_rooms[self.account][room_jid]['invities'] = guest_list - gajim.automatic_rooms[self.account][room_jid]['continue_tag'] = True - gajim.interface.join_gc_room(self.account, room_jid, - gajim.nicks[self.account], None, is_continued=True) - self.window.destroy() + def unique_room_id_supported(self, server, room_id): + guest_list = [] + guests = self.guests_treeview.get_selection().get_selected_rows() + for guest in guests[1]: + iter_ = self.store.get_iter(guest) + guest_list.append(self.store[iter_][2].decode('utf-8')) + for guest in self.auto_jids: + guest_list.append(guest) + room_jid = room_id + '@' + server + gajim.automatic_rooms[self.account][room_jid] = {} + gajim.automatic_rooms[self.account][room_jid]['invities'] = guest_list + gajim.automatic_rooms[self.account][room_jid]['continue_tag'] = True + gajim.interface.join_gc_room(self.account, room_jid, + gajim.nicks[self.account], None, is_continued=True) + self.window.destroy() - def on_cancel_button_clicked(self, widget): - self.window.destroy() + def on_cancel_button_clicked(self, widget): + self.window.destroy() - def unique_room_id_error(self, server): - self.unique_room_id_supported(server, - gajim.nicks[self.account].lower().replace(' ','') + str(randrange( - 9999999))) + def unique_room_id_error(self, server): + self.unique_room_id_supported(server, + gajim.nicks[self.account].lower().replace(' ', '') + str(randrange( + 9999999))) class DataFormWindow(Dialog): - def __init__(self, form, on_response_ok): - self.df_response_ok = on_response_ok - Dialog.__init__(self, None, 'test', [(gtk.STOCK_CANCEL, - gtk.RESPONSE_REJECT), (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)], - on_response_ok=self.on_ok) - self.set_resizable(True) - gtkgui_helpers.resize_window(self, 600, 400) - self.dataform_widget = dataforms_widget.DataFormWidget() - self.dataform = dataforms.ExtendForm(node=form) - self.dataform_widget.set_sensitive(True) - self.dataform_widget.data_form = self.dataform - self.dataform_widget.show_all() - self.vbox.pack_start(self.dataform_widget) + def __init__(self, form, on_response_ok): + self.df_response_ok = on_response_ok + Dialog.__init__(self, None, 'test', [(gtk.STOCK_CANCEL, + gtk.RESPONSE_REJECT), (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)], + on_response_ok=self.on_ok) + self.set_resizable(True) + gtkgui_helpers.resize_window(self, 600, 400) + self.dataform_widget = dataforms_widget.DataFormWidget() + self.dataform = dataforms.ExtendForm(node=form) + self.dataform_widget.set_sensitive(True) + self.dataform_widget.data_form = self.dataform + self.dataform_widget.show_all() + self.vbox.pack_start(self.dataform_widget) - def on_ok(self): - form = self.dataform_widget.data_form - if isinstance(self.df_response_ok, tuple): - self.df_response_ok[0](form, *self.df_response_ok[1:]) - else: - self.df_response_ok(form) - self.destroy() + def on_ok(self): + form = self.dataform_widget.data_form + if isinstance(self.df_response_ok, tuple): + self.df_response_ok[0](form, *self.df_response_ok[1:]) + else: + self.df_response_ok(form) + self.destroy() class ESessionInfoWindow: - """ - Class for displaying information about a XEP-0116 encrypted session - """ - def __init__(self, session): - self.session = session + """ + Class for displaying information about a XEP-0116 encrypted session + """ + def __init__(self, session): + self.session = session - self.xml = gtkgui_helpers.get_gtk_builder('esession_info_window.ui') - self.xml.connect_signals(self) + self.xml = gtkgui_helpers.get_gtk_builder('esession_info_window.ui') + self.xml.connect_signals(self) - self.security_image = self.xml.get_object('security_image') - self.verify_now_button = self.xml.get_object('verify_now_button') - self.button_label = self.xml.get_object('button_label') - self.window = self.xml.get_object('esession_info_window') - self.update_info() + self.security_image = self.xml.get_object('security_image') + self.verify_now_button = self.xml.get_object('verify_now_button') + self.button_label = self.xml.get_object('button_label') + self.window = self.xml.get_object('esession_info_window') + self.update_info() - self.window.show_all() + self.window.show_all() - def update_info(self): - labeltext = _('''Your chat session with %(jid)s is encrypted.\n\nThis session's Short Authentication String is %(sas)s.''') % {'jid': self.session.jid, 'sas': self.session.sas} + def update_info(self): + labeltext = _('''Your chat session with %(jid)s is encrypted.\n\nThis session's Short Authentication String is %(sas)s.''') % {'jid': self.session.jid, 'sas': self.session.sas} - if self.session.verified_identity: - labeltext += '\n\n' + _('''You have already verified this contact's identity.''') - security_image = 'gajim-security_high' - if self.session.control: - self.session.control._show_lock_image(True, 'E2E', True, - self.session.is_loggable(), True) + if self.session.verified_identity: + labeltext += '\n\n' + _('''You have already verified this contact's identity.''') + security_image = 'gajim-security_high' + if self.session.control: + self.session.control._show_lock_image(True, 'E2E', True, + self.session.is_loggable(), True) - verification_status = _('''Contact's identity verified''') - self.window.set_title(verification_status) - self.xml.get_object('verification_status_label').set_markup( - '%s' % verification_status) + verification_status = _('''Contact's identity verified''') + self.window.set_title(verification_status) + self.xml.get_object('verification_status_label').set_markup( + '%s' % verification_status) - self.xml.get_object('dialog-action_area1').set_no_show_all(True) - self.button_label.set_text(_('Verify again...')) - else: - if self.session.control: - self.session.control._show_lock_image(True, 'E2E', True, - self.session.is_loggable(), False) - labeltext += '\n\n' + _('''To be certain that only the expected person can read your messages or send you messages, you need to verify their identity by clicking the button below.''') - security_image = 'gajim-security_low' + self.xml.get_object('dialog-action_area1').set_no_show_all(True) + self.button_label.set_text(_('Verify again...')) + else: + if self.session.control: + self.session.control._show_lock_image(True, 'E2E', True, + self.session.is_loggable(), False) + labeltext += '\n\n' + _('''To be certain that only the expected person can read your messages or send you messages, you need to verify their identity by clicking the button below.''') + security_image = 'gajim-security_low' - verification_status = _('''Contact's identity NOT verified''') - self.window.set_title(verification_status) - self.xml.get_object('verification_status_label').set_markup( - '%s' % verification_status) + verification_status = _('''Contact's identity NOT verified''') + self.window.set_title(verification_status) + self.xml.get_object('verification_status_label').set_markup( + '%s' % verification_status) - self.button_label.set_text(_('Verify...')) + self.button_label.set_text(_('Verify...')) - path = gtkgui_helpers.get_icon_path(security_image, 32) - self.security_image.set_from_file(path) + path = gtkgui_helpers.get_icon_path(security_image, 32) + self.security_image.set_from_file(path) - self.xml.get_object('info_display').set_markup(labeltext) + self.xml.get_object('info_display').set_markup(labeltext) - def on_close_button_clicked(self, widget): - self.window.destroy() + def on_close_button_clicked(self, widget): + self.window.destroy() - def on_verify_now_button_clicked(self, widget): - pritext = _('''Have you verified the contact's identity?''') - sectext = _('''To prevent talking to an unknown person, you should speak to %(jid)s directly (in person or on the phone) and verify that they see the same Short Authentication String (SAS) as you.\n\nThis session's Short Authentication String is %(sas)s.''') % {'jid': self.session.jid, 'sas': self.session.sas} - sectext += '\n\n' + _('Did you talk to the remote contact and verify the SAS?') + def on_verify_now_button_clicked(self, widget): + pritext = _('''Have you verified the contact's identity?''') + sectext = _('''To prevent talking to an unknown person, you should speak to %(jid)s directly (in person or on the phone) and verify that they see the same Short Authentication String (SAS) as you.\n\nThis session's Short Authentication String is %(sas)s.''') % {'jid': self.session.jid, 'sas': self.session.sas} + sectext += '\n\n' + _('Did you talk to the remote contact and verify the SAS?') - def on_yes(checked): - self.session._verified_srs_cb() - self.session.verified_identity = True - self.update_info() + def on_yes(checked): + self.session._verified_srs_cb() + self.session.verified_identity = True + self.update_info() - def on_no(): - self.session._unverified_srs_cb() - self.session.verified_identity = False - self.update_info() + def on_no(): + self.session._unverified_srs_cb() + self.session.verified_identity = False + self.update_info() - YesNoDialog(pritext, sectext, on_response_yes=on_yes, on_response_no=on_no) + YesNoDialog(pritext, sectext, on_response_yes=on_yes, on_response_no=on_no) class GPGInfoWindow: - """ - Class for displaying information about a XEP-0116 encrypted session - """ - def __init__(self, control): - xml = gtkgui_helpers.get_gtk_builder('esession_info_window.ui') - security_image = xml.get_object('security_image') - status_label = xml.get_object('verification_status_label') - info_label = xml.get_object('info_display') - verify_now_button = xml.get_object('verify_now_button') - self.window = xml.get_object('esession_info_window') - account = control.account - keyID = control.contact.keyID - error = None + """ + Class for displaying information about a XEP-0116 encrypted session + """ + def __init__(self, control): + xml = gtkgui_helpers.get_gtk_builder('esession_info_window.ui') + security_image = xml.get_object('security_image') + status_label = xml.get_object('verification_status_label') + info_label = xml.get_object('info_display') + verify_now_button = xml.get_object('verify_now_button') + self.window = xml.get_object('esession_info_window') + account = control.account + keyID = control.contact.keyID + error = None - verify_now_button.set_no_show_all(True) - verify_now_button.hide() + verify_now_button.set_no_show_all(True) + verify_now_button.hide() - if keyID.endswith('MISMATCH'): - verification_status = _('''Contact's identity NOT verified''') - info = _('The contact\'s key (%s) does not match the key ' - 'assigned in Gajim.') % keyID[:8] - image = 'gajim-security_low' - elif not keyID: - # No key assigned nor a key is used by remote contact - verification_status = _('No GPG key assigned') - info = _('No GPG key is assigned to this contact. So you cannot ' - 'encrypt messages.') - image = 'gajim-security_low' - else: - error = gajim.connections[account].gpg.encrypt('test', [keyID])[1] - if error: - verification_status = _('''Contact's identity NOT verified''') - info = _('GPG key is assigned to this contact, but you do not ' - 'trust his key, so message cannot be encrypted. Use ' - 'your GPG client to trust this key.') - image = 'gajim-security_low' - else: - verification_status = _('''Contact's identity verified''') - info = _('GPG Key is assigned to this contact, and you trust his ' - 'key, so messages will be encrypted.') - image = 'gajim-security_high' + if keyID.endswith('MISMATCH'): + verification_status = _('''Contact's identity NOT verified''') + info = _('The contact\'s key (%s) does not match the key ' + 'assigned in Gajim.') % keyID[:8] + image = 'gajim-security_low' + elif not keyID: + # No key assigned nor a key is used by remote contact + verification_status = _('No GPG key assigned') + info = _('No GPG key is assigned to this contact. So you cannot ' + 'encrypt messages.') + image = 'gajim-security_low' + else: + error = gajim.connections[account].gpg.encrypt('test', [keyID])[1] + if error: + verification_status = _('''Contact's identity NOT verified''') + info = _('GPG key is assigned to this contact, but you do not ' + 'trust his key, so message cannot be encrypted. Use ' + 'your GPG client to trust this key.') + image = 'gajim-security_low' + else: + verification_status = _('''Contact's identity verified''') + info = _('GPG Key is assigned to this contact, and you trust his ' + 'key, so messages will be encrypted.') + image = 'gajim-security_high' - status_label.set_markup('%s' % \ - verification_status) - info_label.set_markup(info) + status_label.set_markup('%s' % \ + verification_status) + info_label.set_markup(info) - path = gtkgui_helpers.get_icon_path(image, 32) - security_image.set_from_file(path) + path = gtkgui_helpers.get_icon_path(image, 32) + security_image.set_from_file(path) - xml.connect_signals(self) - self.window.show_all() + xml.connect_signals(self) + self.window.show_all() - def on_close_button_clicked(self, widget): - self.window.destroy() + def on_close_button_clicked(self, widget): + self.window.destroy() class ResourceConflictDialog(TimeoutDialog, InputDialog): - def __init__(self, title, text, resource, ok_handler): - TimeoutDialog.__init__(self, 15, self.on_timeout) - InputDialog.__init__(self, title, text, input_str=resource, - is_modal=False, ok_handler=ok_handler) - self.title_text = title - self.run_timeout() + def __init__(self, title, text, resource, ok_handler): + TimeoutDialog.__init__(self, 15, self.on_timeout) + InputDialog.__init__(self, title, text, input_str=resource, + is_modal=False, ok_handler=ok_handler) + self.title_text = title + self.run_timeout() - def on_timeout(self): - self.on_okbutton_clicked(None) + def on_timeout(self): + self.on_okbutton_clicked(None) class VoIPCallReceivedDialog(object): - instances = {} - def __init__(self, account, contact_jid, sid, content_types): - self.instances[(contact_jid, sid)] = self - self.account = account - self.fjid = contact_jid - self.sid = sid - self.content_types = content_types + instances = {} + def __init__(self, account, contact_jid, sid, content_types): + self.instances[(contact_jid, sid)] = self + self.account = account + self.fjid = contact_jid + self.sid = sid + self.content_types = content_types - xml = gtkgui_helpers.get_gtk_builder('voip_call_received_dialog.ui') - xml.connect_signals(self) + xml = gtkgui_helpers.get_gtk_builder('voip_call_received_dialog.ui') + xml.connect_signals(self) - jid = gajim.get_jid_without_resource(self.fjid) - contact = gajim.contacts.get_first_contact_from_jid(account, jid) - if contact and contact.name: - self.contact_text = '%s (%s)' % (contact.name, jid) - else: - self.contact_text = contact_jid + jid = gajim.get_jid_without_resource(self.fjid) + contact = gajim.contacts.get_first_contact_from_jid(account, jid) + if contact and contact.name: + self.contact_text = '%s (%s)' % (contact.name, jid) + else: + self.contact_text = contact_jid - self.dialog = xml.get_object('voip_call_received_messagedialog') - self.set_secondary_text() + self.dialog = xml.get_object('voip_call_received_messagedialog') + self.set_secondary_text() - self.dialog.show_all() + self.dialog.show_all() - @classmethod - def get_dialog(cls, jid, sid): - if (jid, sid) in cls.instances: - return cls.instances[(jid, sid)] - else: - return None + @classmethod + def get_dialog(cls, jid, sid): + if (jid, sid) in cls.instances: + return cls.instances[(jid, sid)] + else: + return None - def set_secondary_text(self): - if 'audio' in self.content_types and 'video' in self.content_types: - types_text = _('an audio and video') - elif 'audio' in self.content_types: - types_text = _('an audio') - elif 'video' in self.content_types: - types_text = _('a video') + def set_secondary_text(self): + if 'audio' in self.content_types and 'video' in self.content_types: + types_text = _('an audio and video') + elif 'audio' in self.content_types: + types_text = _('an audio') + elif 'video' in self.content_types: + types_text = _('a video') - # do the substitution - self.dialog.set_property('secondary-text', - _('%(contact)s wants to start %(type)s session with you. Do you want ' - 'to answer the call?') % {'contact': self.contact_text, 'type': types_text}) + # do the substitution + self.dialog.set_property('secondary-text', + _('%(contact)s wants to start %(type)s session with you. Do you want ' + 'to answer the call?') % {'contact': self.contact_text, 'type': types_text}) - def add_contents(self, content_types): - for type_ in content_types: - if type_ not in self.content_types: - self.content_types.add(type_) - self.set_secondary_text() + def add_contents(self, content_types): + for type_ in content_types: + if type_ not in self.content_types: + self.content_types.add(type_) + self.set_secondary_text() - def on_voip_call_received_messagedialog_destroy(self, dialog): - if (self.fjid, self.sid) in self.instances: - del self.instances[(self.fjid, self.sid)] + def on_voip_call_received_messagedialog_destroy(self, dialog): + if (self.fjid, self.sid) in self.instances: + del self.instances[(self.fjid, self.sid)] - def on_voip_call_received_messagedialog_close(self, dialog): - return self.on_voip_call_received_messagedialog_response(dialog, - gtk.RESPONSE_NO) + def on_voip_call_received_messagedialog_close(self, dialog): + return self.on_voip_call_received_messagedialog_response(dialog, + gtk.RESPONSE_NO) - def on_voip_call_received_messagedialog_response(self, dialog, response): - # we've got response from user, either stop connecting or accept the call - session = gajim.connections[self.account].get_jingle_session(self.fjid, - self.sid) - if not session: - return - if response == gtk.RESPONSE_YES: - #TODO: Ensure that ctrl.contact.resource == resource - jid = gajim.get_jid_without_resource(self.fjid) - resource = gajim.get_resource_from_jid(self.fjid) - ctrl = gajim.interface.msg_win_mgr.get_control(self.fjid, self.account) - if not ctrl: - ctrl = gajim.interface.msg_win_mgr.get_control(jid, self.account) - if not ctrl: - # open chat control - contact = gajim.contacts.get_contact(self.account, jid, resource) - if not contact: - contact = gajim.contacts.get_contact(self.account, jid) - if not contact: - return - ctrl = gajim.interface.new_chat(contact, self.account, resource) - # Chat control opened, update content's status - audio = session.get_content('audio') - video = session.get_content('video') - if audio and not audio.negotiated: - ctrl.set_audio_state('connecting', self.sid) - if video and not video.negotiated: - ctrl.set_video_state('connecting', self.sid) - # Now, accept the content/sessions. - # This should be done after the chat control is running - if not session.accepted: - session.approve_session() - for content in self.content_types: - session.approve_content(content) - else: # response==gtk.RESPONSE_NO - if not session.accepted: - session.decline_session() - else: - for content in self.content_types: - session.reject_content(content) + def on_voip_call_received_messagedialog_response(self, dialog, response): + # we've got response from user, either stop connecting or accept the call + session = gajim.connections[self.account].get_jingle_session(self.fjid, + self.sid) + if not session: + return + if response == gtk.RESPONSE_YES: + #TODO: Ensure that ctrl.contact.resource == resource + jid = gajim.get_jid_without_resource(self.fjid) + resource = gajim.get_resource_from_jid(self.fjid) + ctrl = gajim.interface.msg_win_mgr.get_control(self.fjid, self.account) + if not ctrl: + ctrl = gajim.interface.msg_win_mgr.get_control(jid, self.account) + if not ctrl: + # open chat control + contact = gajim.contacts.get_contact(self.account, jid, resource) + if not contact: + contact = gajim.contacts.get_contact(self.account, jid) + if not contact: + return + ctrl = gajim.interface.new_chat(contact, self.account, resource) + # Chat control opened, update content's status + audio = session.get_content('audio') + video = session.get_content('video') + if audio and not audio.negotiated: + ctrl.set_audio_state('connecting', self.sid) + if video and not video.negotiated: + ctrl.set_video_state('connecting', self.sid) + # Now, accept the content/sessions. + # This should be done after the chat control is running + if not session.accepted: + session.approve_session() + for content in self.content_types: + session.approve_content(content) + else: # response==gtk.RESPONSE_NO + if not session.accepted: + session.decline_session() + else: + for content in self.content_types: + session.reject_content(content) - dialog.destroy() - - -# vim: se ts=3: + dialog.destroy() diff --git a/src/disco.py b/src/disco.py index c557e5777..46f0e82ac 100644 --- a/src/disco.py +++ b/src/disco.py @@ -69,2189 +69,2187 @@ from common import ged # For the browser class, None means that the service will only be browsable # when it advertises disco as it's feature, False means it's never browsable. def _gen_agent_type_info(): - return { - # Defaults - (0, 0): (None, None), + return { + # Defaults + (0, 0): (None, None), - # Jabber server - ('server', 'im'): (ToplevelAgentBrowser, 'jabber'), - ('services', 'jabber'): (ToplevelAgentBrowser, 'jabber'), - ('hierarchy', 'branch'): (AgentBrowser, 'jabber'), + # Jabber server + ('server', 'im'): (ToplevelAgentBrowser, 'jabber'), + ('services', 'jabber'): (ToplevelAgentBrowser, 'jabber'), + ('hierarchy', 'branch'): (AgentBrowser, 'jabber'), - # Services - ('conference', 'text'): (MucBrowser, 'conference'), - ('headline', 'rss'): (AgentBrowser, 'rss'), - ('headline', 'weather'): (False, 'weather'), - ('gateway', 'weather'): (False, 'weather'), - ('_jid', 'weather'): (False, 'weather'), - ('gateway', 'sip'): (False, 'sip'), - ('directory', 'user'): (None, 'jud'), - ('pubsub', 'generic'): (PubSubBrowser, 'pubsub'), - ('pubsub', 'service'): (PubSubBrowser, 'pubsub'), - ('proxy', 'bytestreams'): (None, 'bytestreams'), # Socks5 FT proxy - ('headline', 'newmail'): (ToplevelAgentBrowser, 'mail'), + # Services + ('conference', 'text'): (MucBrowser, 'conference'), + ('headline', 'rss'): (AgentBrowser, 'rss'), + ('headline', 'weather'): (False, 'weather'), + ('gateway', 'weather'): (False, 'weather'), + ('_jid', 'weather'): (False, 'weather'), + ('gateway', 'sip'): (False, 'sip'), + ('directory', 'user'): (None, 'jud'), + ('pubsub', 'generic'): (PubSubBrowser, 'pubsub'), + ('pubsub', 'service'): (PubSubBrowser, 'pubsub'), + ('proxy', 'bytestreams'): (None, 'bytestreams'), # Socks5 FT proxy + ('headline', 'newmail'): (ToplevelAgentBrowser, 'mail'), - # Transports - ('conference', 'irc'): (ToplevelAgentBrowser, 'irc'), - ('_jid', 'irc'): (False, 'irc'), - ('gateway', 'aim'): (False, 'aim'), - ('_jid', 'aim'): (False, 'aim'), - ('gateway', 'gadu-gadu'): (False, 'gadu-gadu'), - ('_jid', 'gadugadu'): (False, 'gadu-gadu'), - ('gateway', 'http-ws'): (False, 'http-ws'), - ('gateway', 'icq'): (False, 'icq'), - ('_jid', 'icq'): (False, 'icq'), - ('gateway', 'msn'): (False, 'msn'), - ('_jid', 'msn'): (False, 'msn'), - ('gateway', 'sms'): (False, 'sms'), - ('_jid', 'sms'): (False, 'sms'), - ('gateway', 'smtp'): (False, 'mail'), - ('gateway', 'yahoo'): (False, 'yahoo'), - ('_jid', 'yahoo'): (False, 'yahoo'), - ('gateway', 'mrim'): (False, 'mrim'), - ('_jid', 'mrim'): (False, 'mrim'), - ('gateway', 'facebook'): (False, 'facebook'), - ('_jid', 'facebook'): (False, 'facebook'), - } + # Transports + ('conference', 'irc'): (ToplevelAgentBrowser, 'irc'), + ('_jid', 'irc'): (False, 'irc'), + ('gateway', 'aim'): (False, 'aim'), + ('_jid', 'aim'): (False, 'aim'), + ('gateway', 'gadu-gadu'): (False, 'gadu-gadu'), + ('_jid', 'gadugadu'): (False, 'gadu-gadu'), + ('gateway', 'http-ws'): (False, 'http-ws'), + ('gateway', 'icq'): (False, 'icq'), + ('_jid', 'icq'): (False, 'icq'), + ('gateway', 'msn'): (False, 'msn'), + ('_jid', 'msn'): (False, 'msn'), + ('gateway', 'sms'): (False, 'sms'), + ('_jid', 'sms'): (False, 'sms'), + ('gateway', 'smtp'): (False, 'mail'), + ('gateway', 'yahoo'): (False, 'yahoo'), + ('_jid', 'yahoo'): (False, 'yahoo'), + ('gateway', 'mrim'): (False, 'mrim'), + ('_jid', 'mrim'): (False, 'mrim'), + ('gateway', 'facebook'): (False, 'facebook'), + ('_jid', 'facebook'): (False, 'facebook'), + } # Category type to "human-readable" description string, and sort priority _cat_to_descr = { - 'other': (_('Others'), 2), - 'gateway': (_('Transports'), 0), - '_jid': (_('Transports'), 0), - #conference is a category for listing mostly groupchats in service discovery - 'conference': (_('Conference'), 1), + 'other': (_('Others'), 2), + 'gateway': (_('Transports'), 0), + '_jid': (_('Transports'), 0), + #conference is a category for listing mostly groupchats in service discovery + 'conference': (_('Conference'), 1), } class CacheDictionary: - """ - A dictionary that keeps items around for only a specific time. Lifetime is - in minutes. Getrefresh specifies whether to refresh when an item is merely - accessed instead of set aswell - """ + """ + A dictionary that keeps items around for only a specific time. Lifetime is + in minutes. Getrefresh specifies whether to refresh when an item is merely + accessed instead of set aswell + """ - def __init__(self, lifetime, getrefresh = True): - self.lifetime = lifetime * 1000 * 60 - self.getrefresh = getrefresh - self.cache = {} + def __init__(self, lifetime, getrefresh = True): + self.lifetime = lifetime * 1000 * 60 + self.getrefresh = getrefresh + self.cache = {} - class CacheItem: - """ - An object to store cache items and their timeouts - """ - def __init__(self, value): - self.value = value - self.source = None + class CacheItem: + """ + An object to store cache items and their timeouts + """ + def __init__(self, value): + self.value = value + self.source = None - def __call__(self): - return self.value + def __call__(self): + return self.value - def cleanup(self): - for key in self.cache.keys(): - item = self.cache[key] - if item.source: - gobject.source_remove(item.source) - del self.cache[key] + def cleanup(self): + for key in self.cache.keys(): + item = self.cache[key] + if item.source: + gobject.source_remove(item.source) + del self.cache[key] - def _expire_timeout(self, key): - """ - The timeout has expired, remove the object - """ - if key in self.cache: - del self.cache[key] - return False + def _expire_timeout(self, key): + """ + The timeout has expired, remove the object + """ + if key in self.cache: + del self.cache[key] + return False - def _refresh_timeout(self, key): - """ - The object was accessed, refresh the timeout - """ - item = self.cache[key] - if item.source: - gobject.source_remove(item.source) - if self.lifetime: - source = gobject.timeout_add_seconds(self.lifetime/1000, self._expire_timeout, key) - item.source = source + def _refresh_timeout(self, key): + """ + The object was accessed, refresh the timeout + """ + item = self.cache[key] + if item.source: + gobject.source_remove(item.source) + if self.lifetime: + source = gobject.timeout_add_seconds(self.lifetime/1000, self._expire_timeout, key) + item.source = source - def __getitem__(self, key): - item = self.cache[key] - if self.getrefresh: - self._refresh_timeout(key) - return item() + def __getitem__(self, key): + item = self.cache[key] + if self.getrefresh: + self._refresh_timeout(key) + return item() - def __setitem__(self, key, value): - item = self.CacheItem(value) - self.cache[key] = item - self._refresh_timeout(key) + def __setitem__(self, key, value): + item = self.CacheItem(value) + self.cache[key] = item + self._refresh_timeout(key) - def __delitem__(self, key): - item = self.cache[key] - if item.source: - gobject.source_remove(item.source) - del self.cache[key] + def __delitem__(self, key): + item = self.cache[key] + if item.source: + gobject.source_remove(item.source) + del self.cache[key] - def __contains__(self, key): - return key in self.cache - has_key = __contains__ + def __contains__(self, key): + return key in self.cache + has_key = __contains__ _icon_cache = CacheDictionary(15) def get_agent_address(jid, node = None): - """ - Get an agent's address for displaying in the GUI - """ - if node: - return '%s@%s' % (node, str(jid)) - else: - return str(jid) + """ + Get an agent's address for displaying in the GUI + """ + if node: + return '%s@%s' % (node, str(jid)) + else: + return str(jid) class Closure(object): - """ - A weak reference to a callback with arguments as an object + """ + A weak reference to a callback with arguments as an object - Weak references to methods immediatly die, even if the object is still - alive. Besides a handy way to store a callback, this provides a workaround - that keeps a reference to the object instead. + Weak references to methods immediatly die, even if the object is still + alive. Besides a handy way to store a callback, this provides a workaround + that keeps a reference to the object instead. - Userargs and removeargs must be tuples. - """ + Userargs and removeargs must be tuples. + """ - def __init__(self, cb, userargs = (), remove = None, removeargs = ()): - self.userargs = userargs - self.remove = remove - self.removeargs = removeargs - if isinstance(cb, types.MethodType): - self.meth_self = weakref.ref(cb.im_self, self._remove) - self.meth_name = cb.func_name - elif callable(cb): - self.meth_self = None - self.cb = weakref.ref(cb, self._remove) - else: - raise TypeError('Object is not callable') + def __init__(self, cb, userargs = (), remove = None, removeargs = ()): + self.userargs = userargs + self.remove = remove + self.removeargs = removeargs + if isinstance(cb, types.MethodType): + self.meth_self = weakref.ref(cb.im_self, self._remove) + self.meth_name = cb.func_name + elif callable(cb): + self.meth_self = None + self.cb = weakref.ref(cb, self._remove) + else: + raise TypeError('Object is not callable') - def _remove(self, ref): - if self.remove: - self.remove(self, *self.removeargs) + def _remove(self, ref): + if self.remove: + self.remove(self, *self.removeargs) - def __call__(self, *args, **kwargs): - if self.meth_self: - obj = self.meth_self() - cb = getattr(obj, self.meth_name) - else: - cb = self.cb() - args = args + self.userargs - return cb(*args, **kwargs) + def __call__(self, *args, **kwargs): + if self.meth_self: + obj = self.meth_self() + cb = getattr(obj, self.meth_name) + else: + cb = self.cb() + args = args + self.userargs + return cb(*args, **kwargs) class ServicesCache: - """ - Class that caches our query results. Each connection will have it's own - ServiceCache instance - """ + """ + Class that caches our query results. Each connection will have it's own + ServiceCache instance + """ - def __init__(self, account): - self.account = account - self._items = CacheDictionary(0, getrefresh = False) - self._info = CacheDictionary(0, getrefresh = False) - self._subscriptions = CacheDictionary(5, getrefresh=False) - self._cbs = {} - gajim.ged.register_event_handler('AGENT_ERROR_INFO', ged.CORE, - self.agent_info_error) - gajim.ged.register_event_handler('AGENT_ERROR_ITEMS', ged.CORE, - self.agent_items_error) - gajim.ged.register_event_handler('AGENT_INFO_ITEMS', ged.CORE, - self.agent_items) - gajim.ged.register_event_handler('AGENT_INFO_INFO', ged.CORE, - self.agent_info) - - def __del__(self): - gajim.ged.remove_event_handler('AGENT_ERROR_INFO', ged.CORE, - self.agent_info_error) - gajim.ged.remove_event_handler('AGENT_ERROR_ITEMS', ged.CORE, - self.agent_items_error) - gajim.ged.remove_event_handler('AGENT_INFO_ITEMS', ged.CORE, - self.agent_items) - gajim.ged.remove_event_handler('AGENT_INFO_INFO', ged.CORE, - self.agent_info) + def __init__(self, account): + self.account = account + self._items = CacheDictionary(0, getrefresh = False) + self._info = CacheDictionary(0, getrefresh = False) + self._subscriptions = CacheDictionary(5, getrefresh=False) + self._cbs = {} + gajim.ged.register_event_handler('AGENT_ERROR_INFO', ged.CORE, + self.agent_info_error) + gajim.ged.register_event_handler('AGENT_ERROR_ITEMS', ged.CORE, + self.agent_items_error) + gajim.ged.register_event_handler('AGENT_INFO_ITEMS', ged.CORE, + self.agent_items) + gajim.ged.register_event_handler('AGENT_INFO_INFO', ged.CORE, + self.agent_info) - def cleanup(self): - self._items.cleanup() - self._info.cleanup() + def __del__(self): + gajim.ged.remove_event_handler('AGENT_ERROR_INFO', ged.CORE, + self.agent_info_error) + gajim.ged.remove_event_handler('AGENT_ERROR_ITEMS', ged.CORE, + self.agent_items_error) + gajim.ged.remove_event_handler('AGENT_INFO_ITEMS', ged.CORE, + self.agent_items) + gajim.ged.remove_event_handler('AGENT_INFO_INFO', ged.CORE, + self.agent_info) - def _clean_closure(self, cb, type_, addr): - # A closure died, clean up - cbkey = (type_, addr) - try: - self._cbs[cbkey].remove(cb) - except KeyError: - return - except ValueError: - return - # Clean an empty list - if not self._cbs[cbkey]: - del self._cbs[cbkey] + def cleanup(self): + self._items.cleanup() + self._info.cleanup() - def get_icon(self, identities = []): - """ - Return the icon for an agent - """ - # Grab the first identity with an icon - for identity in identities: - try: - cat, type_ = identity['category'], identity['type'] - info = _agent_type_info[(cat, type_)] - except KeyError: - continue - filename = info[1] - if filename: - break - else: - # Loop fell through, default to unknown - info = _agent_type_info[(0, 0)] - filename = info[1] - if not filename: # we don't have an image to show for this type - filename = 'jabber' - # Use the cache if possible - if filename in _icon_cache: - return _icon_cache[filename] - # Or load it - pix = gtkgui_helpers.get_icon_pixmap('gajim-agent-' + filename, size=32) - # Store in cache - _icon_cache[filename] = pix - return pix + def _clean_closure(self, cb, type_, addr): + # A closure died, clean up + cbkey = (type_, addr) + try: + self._cbs[cbkey].remove(cb) + except KeyError: + return + except ValueError: + return + # Clean an empty list + if not self._cbs[cbkey]: + del self._cbs[cbkey] - def get_browser(self, identities=[], features=[]): - """ - Return the browser class for an agent - """ - # First pass, we try to find a ToplevelAgentBrowser - for identity in identities: - try: - cat, type_ = identity['category'], identity['type'] - info = _agent_type_info[(cat, type_)] - except KeyError: - continue - browser = info[0] - if browser and browser == ToplevelAgentBrowser: - return browser + def get_icon(self, identities = []): + """ + Return the icon for an agent + """ + # Grab the first identity with an icon + for identity in identities: + try: + cat, type_ = identity['category'], identity['type'] + info = _agent_type_info[(cat, type_)] + except KeyError: + continue + filename = info[1] + if filename: + break + else: + # Loop fell through, default to unknown + info = _agent_type_info[(0, 0)] + filename = info[1] + if not filename: # we don't have an image to show for this type + filename = 'jabber' + # Use the cache if possible + if filename in _icon_cache: + return _icon_cache[filename] + # Or load it + pix = gtkgui_helpers.get_icon_pixmap('gajim-agent-' + filename, size=32) + # Store in cache + _icon_cache[filename] = pix + return pix - # second pass, we haven't found a ToplevelAgentBrowser - for identity in identities: - try: - cat, type_ = identity['category'], identity['type'] - info = _agent_type_info[(cat, type_)] - except KeyError: - continue - browser = info[0] - if browser: - return browser - # NS_BROWSE is deprecated, but we check for it anyways. - # Some services list it in features and respond to - # NS_DISCO_ITEMS anyways. - # Allow browsing for unknown types aswell. - if (not features and not identities) or \ - xmpp.NS_DISCO_ITEMS in features or xmpp.NS_BROWSE in features: - return ToplevelAgentBrowser - return None + def get_browser(self, identities=[], features=[]): + """ + Return the browser class for an agent + """ + # First pass, we try to find a ToplevelAgentBrowser + for identity in identities: + try: + cat, type_ = identity['category'], identity['type'] + info = _agent_type_info[(cat, type_)] + except KeyError: + continue + browser = info[0] + if browser and browser == ToplevelAgentBrowser: + return browser - def get_info(self, jid, node, cb, force = False, nofetch = False, args = ()): - """ - Get info for an agent - """ - addr = get_agent_address(jid, node) - # Check the cache - if addr in self._info: - args = self._info[addr] + args - cb(jid, node, *args) - return - if nofetch: - return + # second pass, we haven't found a ToplevelAgentBrowser + for identity in identities: + try: + cat, type_ = identity['category'], identity['type'] + info = _agent_type_info[(cat, type_)] + except KeyError: + continue + browser = info[0] + if browser: + return browser + # NS_BROWSE is deprecated, but we check for it anyways. + # Some services list it in features and respond to + # NS_DISCO_ITEMS anyways. + # Allow browsing for unknown types aswell. + if (not features and not identities) or \ + xmpp.NS_DISCO_ITEMS in features or xmpp.NS_BROWSE in features: + return ToplevelAgentBrowser + return None - # Create a closure object - cbkey = ('info', addr) - cb = Closure(cb, userargs = args, remove = self._clean_closure, - removeargs = cbkey) - # Are we already fetching this? - if cbkey in self._cbs: - self._cbs[cbkey].append(cb) - else: - self._cbs[cbkey] = [cb] - gajim.connections[self.account].discoverInfo(jid, node) + def get_info(self, jid, node, cb, force = False, nofetch = False, args = ()): + """ + Get info for an agent + """ + addr = get_agent_address(jid, node) + # Check the cache + if addr in self._info: + args = self._info[addr] + args + cb(jid, node, *args) + return + if nofetch: + return - def get_items(self, jid, node, cb, force = False, nofetch = False, args = ()): - """ - Get a list of items in an agent - """ - addr = get_agent_address(jid, node) - # Check the cache - if addr in self._items: - args = (self._items[addr],) + args - cb(jid, node, *args) - return - if nofetch: - return + # Create a closure object + cbkey = ('info', addr) + cb = Closure(cb, userargs = args, remove = self._clean_closure, + removeargs = cbkey) + # Are we already fetching this? + if cbkey in self._cbs: + self._cbs[cbkey].append(cb) + else: + self._cbs[cbkey] = [cb] + gajim.connections[self.account].discoverInfo(jid, node) - # Create a closure object - cbkey = ('items', addr) - cb = Closure(cb, userargs = args, remove = self._clean_closure, - removeargs = cbkey) - # Are we already fetching this? - if cbkey in self._cbs: - self._cbs[cbkey].append(cb) - else: - self._cbs[cbkey] = [cb] - gajim.connections[self.account].discoverItems(jid, node) + def get_items(self, jid, node, cb, force = False, nofetch = False, args = ()): + """ + Get a list of items in an agent + """ + addr = get_agent_address(jid, node) + # Check the cache + if addr in self._items: + args = (self._items[addr],) + args + cb(jid, node, *args) + return + if nofetch: + return - def agent_info(self, account, array): - """ - Callback for when we receive an agent's info - array is (agent, node, identities, features, data) - """ - # We receive events from all accounts from GED - if account != self.account: - return - jid, node, identities, features, data = array - addr = get_agent_address(jid, node) + # Create a closure object + cbkey = ('items', addr) + cb = Closure(cb, userargs = args, remove = self._clean_closure, + removeargs = cbkey) + # Are we already fetching this? + if cbkey in self._cbs: + self._cbs[cbkey].append(cb) + else: + self._cbs[cbkey] = [cb] + gajim.connections[self.account].discoverItems(jid, node) - # Store in cache - self._info[addr] = (identities, features, data) + def agent_info(self, account, array): + """ + Callback for when we receive an agent's info + array is (agent, node, identities, features, data) + """ + # We receive events from all accounts from GED + if account != self.account: + return + jid, node, identities, features, data = array + addr = get_agent_address(jid, node) - # Call callbacks - cbkey = ('info', addr) - if cbkey in self._cbs: - for cb in self._cbs[cbkey]: - cb(jid, node, identities, features, data) - # clean_closure may have beaten us to it - if cbkey in self._cbs: - del self._cbs[cbkey] + # Store in cache + self._info[addr] = (identities, features, data) - def agent_items(self, account, array): - """ - Callback for when we receive an agent's items - array is (agent, node, items) - """ - # We receive events from all accounts from GED - if account != self.account: - return - jid, node, items = array - addr = get_agent_address(jid, node) + # Call callbacks + cbkey = ('info', addr) + if cbkey in self._cbs: + for cb in self._cbs[cbkey]: + cb(jid, node, identities, features, data) + # clean_closure may have beaten us to it + if cbkey in self._cbs: + del self._cbs[cbkey] - # Store in cache - self._items[addr] = items + def agent_items(self, account, array): + """ + Callback for when we receive an agent's items + array is (agent, node, items) + """ + # We receive events from all accounts from GED + if account != self.account: + return + jid, node, items = array + addr = get_agent_address(jid, node) - # Call callbacks - cbkey = ('items', addr) - if cbkey in self._cbs: - for cb in self._cbs[cbkey]: - cb(jid, node, items) - # clean_closure may have beaten us to it - if cbkey in self._cbs: - del self._cbs[cbkey] + # Store in cache + self._items[addr] = items - def agent_info_error(self, account, jid): - """ - Callback for when a query fails. Even after the browse and agents - namespaces - """ - # We receive events from all accounts from GED - if account != self.account: - return - addr = get_agent_address(jid) + # Call callbacks + cbkey = ('items', addr) + if cbkey in self._cbs: + for cb in self._cbs[cbkey]: + cb(jid, node, items) + # clean_closure may have beaten us to it + if cbkey in self._cbs: + del self._cbs[cbkey] - # Call callbacks - cbkey = ('info', addr) - if cbkey in self._cbs: - for cb in self._cbs[cbkey]: - cb(jid, '', 0, 0, 0) - # clean_closure may have beaten us to it - if cbkey in self._cbs: - del self._cbs[cbkey] + def agent_info_error(self, account, jid): + """ + Callback for when a query fails. Even after the browse and agents + namespaces + """ + # We receive events from all accounts from GED + if account != self.account: + return + addr = get_agent_address(jid) - def agent_items_error(self, account, jid): - """ - Callback for when a query fails. Even after the browse and agents - namespaces - """ - # We receive events from all accounts from GED - if account != self.account: - return - addr = get_agent_address(jid) + # Call callbacks + cbkey = ('info', addr) + if cbkey in self._cbs: + for cb in self._cbs[cbkey]: + cb(jid, '', 0, 0, 0) + # clean_closure may have beaten us to it + if cbkey in self._cbs: + del self._cbs[cbkey] - # Call callbacks - cbkey = ('items', addr) - if cbkey in self._cbs: - for cb in self._cbs[cbkey]: - cb(jid, '', 0) - # clean_closure may have beaten us to it - if cbkey in self._cbs: - del self._cbs[cbkey] + def agent_items_error(self, account, jid): + """ + Callback for when a query fails. Even after the browse and agents + namespaces + """ + # We receive events from all accounts from GED + if account != self.account: + return + addr = get_agent_address(jid) + + # Call callbacks + cbkey = ('items', addr) + if cbkey in self._cbs: + for cb in self._cbs[cbkey]: + cb(jid, '', 0) + # clean_closure may have beaten us to it + if cbkey in self._cbs: + del self._cbs[cbkey] # object is needed so that @property works class ServiceDiscoveryWindow(object): - """ - Class that represents the Services Discovery window - """ + """ + Class that represents the Services Discovery window + """ - def __init__(self, account, jid = '', node = '', - address_entry = False, parent = None): - self.account = account - self.parent = parent - if not jid: - jid = gajim.config.get_per('accounts', account, 'hostname') - node = '' + def __init__(self, account, jid = '', node = '', + address_entry = False, parent = None): + self.account = account + self.parent = parent + if not jid: + jid = gajim.config.get_per('accounts', account, 'hostname') + node = '' - self.jid = None - self.browser = None - self.children = [] - self.dying = False - self.node = None + self.jid = None + self.browser = None + self.children = [] + self.dying = False + self.node = None - # Check connection - if gajim.connections[account].connected < 2: - dialogs.ErrorDialog(_('You are not connected to the server'), + # Check connection + if gajim.connections[account].connected < 2: + dialogs.ErrorDialog(_('You are not connected to the server'), _('Without a connection, you can not browse available services')) - raise RuntimeError, 'You must be connected to browse services' + raise RuntimeError, 'You must be connected to browse services' - # Get a ServicesCache object. - try: - self.cache = gajim.connections[account].services_cache - except AttributeError: - self.cache = ServicesCache(account) - gajim.connections[account].services_cache = self.cache + # Get a ServicesCache object. + try: + self.cache = gajim.connections[account].services_cache + except AttributeError: + self.cache = ServicesCache(account) + gajim.connections[account].services_cache = self.cache - self.xml = gtkgui_helpers.get_gtk_builder('service_discovery_window.ui') - self.window = self.xml.get_object('service_discovery_window') - self.services_treeview = self.xml.get_object('services_treeview') - self.model = None - # This is more reliable than the cursor-changed signal. - selection = self.services_treeview.get_selection() - selection.connect_after('changed', - self.on_services_treeview_selection_changed) - self.services_scrollwin = self.xml.get_object('services_scrollwin') - self.progressbar = self.xml.get_object('services_progressbar') - self.banner = self.xml.get_object('banner_agent_label') - self.banner_icon = self.xml.get_object('banner_agent_icon') - self.banner_eventbox = self.xml.get_object('banner_agent_eventbox') - self.style_event_id = 0 - self.banner.realize() - self.paint_banner() - self.action_buttonbox = self.xml.get_object('action_buttonbox') + self.xml = gtkgui_helpers.get_gtk_builder('service_discovery_window.ui') + self.window = self.xml.get_object('service_discovery_window') + self.services_treeview = self.xml.get_object('services_treeview') + self.model = None + # This is more reliable than the cursor-changed signal. + selection = self.services_treeview.get_selection() + selection.connect_after('changed', + self.on_services_treeview_selection_changed) + self.services_scrollwin = self.xml.get_object('services_scrollwin') + self.progressbar = self.xml.get_object('services_progressbar') + self.banner = self.xml.get_object('banner_agent_label') + self.banner_icon = self.xml.get_object('banner_agent_icon') + self.banner_eventbox = self.xml.get_object('banner_agent_eventbox') + self.style_event_id = 0 + self.banner.realize() + self.paint_banner() + self.action_buttonbox = self.xml.get_object('action_buttonbox') - # Address combobox - self.address_comboboxentry = None - address_table = self.xml.get_object('address_table') - if address_entry: - self.address_comboboxentry = self.xml.get_object( - 'address_comboboxentry') - self.address_comboboxentry_entry = self.address_comboboxentry.child - self.address_comboboxentry_entry.set_activates_default(True) + # Address combobox + self.address_comboboxentry = None + address_table = self.xml.get_object('address_table') + if address_entry: + self.address_comboboxentry = self.xml.get_object( + 'address_comboboxentry') + self.address_comboboxentry_entry = self.address_comboboxentry.child + self.address_comboboxentry_entry.set_activates_default(True) - liststore = gtk.ListStore(str) - self.address_comboboxentry.set_model(liststore) - self.latest_addresses = gajim.config.get( - 'latest_disco_addresses').split() - if jid in self.latest_addresses: - self.latest_addresses.remove(jid) - self.latest_addresses.insert(0, jid) - if len(self.latest_addresses) > 10: - self.latest_addresses = self.latest_addresses[0:10] - for j in self.latest_addresses: - self.address_comboboxentry.append_text(j) - self.address_comboboxentry.child.set_text(jid) - else: - # Don't show it at all if we didn't ask for it - address_table.set_no_show_all(True) - address_table.hide() + liststore = gtk.ListStore(str) + self.address_comboboxentry.set_model(liststore) + self.latest_addresses = gajim.config.get( + 'latest_disco_addresses').split() + if jid in self.latest_addresses: + self.latest_addresses.remove(jid) + self.latest_addresses.insert(0, jid) + if len(self.latest_addresses) > 10: + self.latest_addresses = self.latest_addresses[0:10] + for j in self.latest_addresses: + self.address_comboboxentry.append_text(j) + self.address_comboboxentry.child.set_text(jid) + else: + # Don't show it at all if we didn't ask for it + address_table.set_no_show_all(True) + address_table.hide() - self._initial_state() - self.xml.connect_signals(self) - self.travel(jid, node) - self.window.show_all() + self._initial_state() + self.xml.connect_signals(self) + self.travel(jid, node) + self.window.show_all() - @property - def _get_account(self): - return self.account + @property + def _get_account(self): + return self.account - @property - def _set_account(self, value): - self.account = value - self.cache.account = value - if self.browser: - self.browser.account = value + @property + def _set_account(self, value): + self.account = value + self.cache.account = value + if self.browser: + self.browser.account = value - def _initial_state(self): - """ - Set some initial state on the window. Separated in a method because it's - handy to use within browser's cleanup method - """ - self.progressbar.hide() - title_text = _('Service Discovery using account %s') % self.account - self.window.set_title(title_text) - self._set_window_banner_text(_('Service Discovery')) - self.banner_icon.clear() - self.banner_icon.hide() # Just clearing it doesn't work + def _initial_state(self): + """ + Set some initial state on the window. Separated in a method because it's + handy to use within browser's cleanup method + """ + self.progressbar.hide() + title_text = _('Service Discovery using account %s') % self.account + self.window.set_title(title_text) + self._set_window_banner_text(_('Service Discovery')) + self.banner_icon.clear() + self.banner_icon.hide() # Just clearing it doesn't work - def _set_window_banner_text(self, text, text_after = None): - theme = gajim.config.get('roster_theme') - bannerfont = gajim.config.get_per('themes', theme, 'bannerfont') - bannerfontattrs = gajim.config.get_per('themes', theme, - 'bannerfontattrs') + def _set_window_banner_text(self, text, text_after = None): + theme = gajim.config.get('roster_theme') + bannerfont = gajim.config.get_per('themes', theme, 'bannerfont') + bannerfontattrs = gajim.config.get_per('themes', theme, + 'bannerfontattrs') - if bannerfont: - font = pango.FontDescription(bannerfont) - else: - font = pango.FontDescription('Normal') - if bannerfontattrs: - # B is attribute set by default - if 'B' in bannerfontattrs: - font.set_weight(pango.WEIGHT_HEAVY) - if 'I' in bannerfontattrs: - font.set_style(pango.STYLE_ITALIC) + if bannerfont: + font = pango.FontDescription(bannerfont) + else: + font = pango.FontDescription('Normal') + if bannerfontattrs: + # B is attribute set by default + if 'B' in bannerfontattrs: + font.set_weight(pango.WEIGHT_HEAVY) + if 'I' in bannerfontattrs: + font.set_style(pango.STYLE_ITALIC) - font_attrs = 'font_desc="%s"' % font.to_string() - font_size = font.get_size() + font_attrs = 'font_desc="%s"' % font.to_string() + font_size = font.get_size() - # in case there is no font specified we use x-large font size - if font_size == 0: - font_attrs = '%s size="large"' % font_attrs - markup = '%s' % (font_attrs, text) - if text_after: - font.set_weight(pango.WEIGHT_NORMAL) - markup = '%s\n%s' % \ - (markup, font.to_string(), text_after) - self.banner.set_markup(markup) + # in case there is no font specified we use x-large font size + if font_size == 0: + font_attrs = '%s size="large"' % font_attrs + markup = '%s' % (font_attrs, text) + if text_after: + font.set_weight(pango.WEIGHT_NORMAL) + markup = '%s\n%s' % \ + (markup, font.to_string(), text_after) + self.banner.set_markup(markup) - def paint_banner(self): - """ - Repaint the banner with theme color - """ - theme = gajim.config.get('roster_theme') - bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor') - textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor') - self.disconnect_style_event() - if bgcolor: - color = gtk.gdk.color_parse(bgcolor) - self.banner_eventbox.modify_bg(gtk.STATE_NORMAL, color) - default_bg = False - else: - default_bg = True + def paint_banner(self): + """ + Repaint the banner with theme color + """ + theme = gajim.config.get('roster_theme') + bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor') + textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor') + self.disconnect_style_event() + if bgcolor: + color = gtk.gdk.color_parse(bgcolor) + self.banner_eventbox.modify_bg(gtk.STATE_NORMAL, color) + default_bg = False + else: + default_bg = True - if textcolor: - color = gtk.gdk.color_parse(textcolor) - self.banner.modify_fg(gtk.STATE_NORMAL, color) - default_fg = False - else: - default_fg = True - if default_fg or default_bg: - self._on_style_set_event(self.banner, None, default_fg, default_bg) - if self.browser: - self.browser.update_theme() + if textcolor: + color = gtk.gdk.color_parse(textcolor) + self.banner.modify_fg(gtk.STATE_NORMAL, color) + default_fg = False + else: + default_fg = True + if default_fg or default_bg: + self._on_style_set_event(self.banner, None, default_fg, default_bg) + if self.browser: + self.browser.update_theme() - def disconnect_style_event(self): - if self.style_event_id: - self.banner.disconnect(self.style_event_id) - self.style_event_id = 0 + def disconnect_style_event(self): + if self.style_event_id: + self.banner.disconnect(self.style_event_id) + self.style_event_id = 0 - def connect_style_event(self, set_fg = False, set_bg = False): - self.disconnect_style_event() - self.style_event_id = self.banner.connect('style-set', - self._on_style_set_event, set_fg, set_bg) + def connect_style_event(self, set_fg = False, set_bg = False): + self.disconnect_style_event() + self.style_event_id = self.banner.connect('style-set', + self._on_style_set_event, set_fg, set_bg) - def _on_style_set_event(self, widget, style, *opts): - """ - Set style of widget from style class *.Frame.Eventbox - opts[0] == True -> set fg color - opts[1] == True -> set bg color - """ - self.disconnect_style_event() - if opts[1]: - bg_color = widget.style.bg[gtk.STATE_SELECTED] - self.banner_eventbox.modify_bg(gtk.STATE_NORMAL, bg_color) - if opts[0]: - fg_color = widget.style.fg[gtk.STATE_SELECTED] - self.banner.modify_fg(gtk.STATE_NORMAL, fg_color) - self.banner.ensure_style() - self.connect_style_event(opts[0], opts[1]) + def _on_style_set_event(self, widget, style, *opts): + """ + Set style of widget from style class *.Frame.Eventbox + opts[0] == True -> set fg color + opts[1] == True -> set bg color + """ + self.disconnect_style_event() + if opts[1]: + bg_color = widget.style.bg[gtk.STATE_SELECTED] + self.banner_eventbox.modify_bg(gtk.STATE_NORMAL, bg_color) + if opts[0]: + fg_color = widget.style.fg[gtk.STATE_SELECTED] + self.banner.modify_fg(gtk.STATE_NORMAL, fg_color) + self.banner.ensure_style() + self.connect_style_event(opts[0], opts[1]) - def destroy(self, chain = False): - """ - Close the browser. This can optionally close its children and propagate - to the parent. This should happen on actions like register, or join to - kill off the entire browser chain - """ - if self.dying: - return - self.dying = True + def destroy(self, chain = False): + """ + Close the browser. This can optionally close its children and propagate + to the parent. This should happen on actions like register, or join to + kill off the entire browser chain + """ + if self.dying: + return + self.dying = True - # self.browser._get_agent_address() would break when no browser. - addr = get_agent_address(self.jid, self.node) - if addr in gajim.interface.instances[self.account]['disco']: - del gajim.interface.instances[self.account]['disco'][addr] + # self.browser._get_agent_address() would break when no browser. + addr = get_agent_address(self.jid, self.node) + if addr in gajim.interface.instances[self.account]['disco']: + del gajim.interface.instances[self.account]['disco'][addr] - if self.browser: - self.window.hide() - self.browser.cleanup() - self.browser = None - self.window.destroy() + if self.browser: + self.window.hide() + self.browser.cleanup() + self.browser = None + self.window.destroy() - for child in self.children[:]: - child.parent = None - if chain: - child.destroy(chain = chain) - self.children.remove(child) - if self.parent: - if self in self.parent.children: - self.parent.children.remove(self) - if chain and not self.parent.children: - self.parent.destroy(chain = chain) - self.parent = None - else: - self.cache.cleanup() + for child in self.children[:]: + child.parent = None + if chain: + child.destroy(chain = chain) + self.children.remove(child) + if self.parent: + if self in self.parent.children: + self.parent.children.remove(self) + if chain and not self.parent.children: + self.parent.destroy(chain = chain) + self.parent = None + else: + self.cache.cleanup() - def travel(self, jid, node): - """ - Travel to an agent within the current services window - """ - if self.browser: - self.browser.cleanup() - self.browser = None - # Update the window list - if self.jid: - old_addr = get_agent_address(self.jid, self.node) - if old_addr in gajim.interface.instances[self.account]['disco']: - del gajim.interface.instances[self.account]['disco'][old_addr] - addr = get_agent_address(jid, node) - gajim.interface.instances[self.account]['disco'][addr] = self - # We need to store these, self.browser is not always available. - self.jid = jid - self.node = node - self.cache.get_info(jid, node, self._travel) + def travel(self, jid, node): + """ + Travel to an agent within the current services window + """ + if self.browser: + self.browser.cleanup() + self.browser = None + # Update the window list + if self.jid: + old_addr = get_agent_address(self.jid, self.node) + if old_addr in gajim.interface.instances[self.account]['disco']: + del gajim.interface.instances[self.account]['disco'][old_addr] + addr = get_agent_address(jid, node) + gajim.interface.instances[self.account]['disco'][addr] = self + # We need to store these, self.browser is not always available. + self.jid = jid + self.node = node + self.cache.get_info(jid, node, self._travel) - def _travel(self, jid, node, identities, features, data): - """ - Continuation of travel - """ - if self.dying or jid != self.jid or node != self.node: - return - if not identities: - if not self.address_comboboxentry: - # We can't travel anywhere else. - self.destroy() - dialogs.ErrorDialog(_('The service could not be found'), + def _travel(self, jid, node, identities, features, data): + """ + Continuation of travel + """ + if self.dying or jid != self.jid or node != self.node: + return + if not identities: + if not self.address_comboboxentry: + # We can't travel anywhere else. + self.destroy() + dialogs.ErrorDialog(_('The service could not be found'), _('There is no service at the address you entered, or it is not responding. Check the address and try again.')) - return - klass = self.cache.get_browser(identities, features) - if not klass: - dialogs.ErrorDialog(_('The service is not browsable'), + return + klass = self.cache.get_browser(identities, features) + if not klass: + dialogs.ErrorDialog(_('The service is not browsable'), _('This type of service does not contain any items to browse.')) - return - elif klass is None: - klass = AgentBrowser - self.browser = klass(self.account, jid, node) - self.browser.prepare_window(self) - self.browser.browse() + return + elif klass is None: + klass = AgentBrowser + self.browser = klass(self.account, jid, node) + self.browser.prepare_window(self) + self.browser.browse() - def open(self, jid, node): - """ - Open an agent. By default, this happens in a new window - """ - try: - win = gajim.interface.instances[self.account]['disco']\ - [get_agent_address(jid, node)] - win.window.present() - return - except KeyError: - pass - try: - win = ServiceDiscoveryWindow(self.account, jid, node, parent=self) - except RuntimeError: - # Disconnected, perhaps - return - self.children.append(win) + def open(self, jid, node): + """ + Open an agent. By default, this happens in a new window + """ + try: + win = gajim.interface.instances[self.account]['disco']\ + [get_agent_address(jid, node)] + win.window.present() + return + except KeyError: + pass + try: + win = ServiceDiscoveryWindow(self.account, jid, node, parent=self) + except RuntimeError: + # Disconnected, perhaps + return + self.children.append(win) - def on_service_discovery_window_destroy(self, widget): - self.destroy() + def on_service_discovery_window_destroy(self, widget): + self.destroy() - def on_close_button_clicked(self, widget): - self.destroy() + def on_close_button_clicked(self, widget): + self.destroy() - def on_address_comboboxentry_changed(self, widget): - if self.address_comboboxentry.get_active() != -1: - # user selected one of the entries so do auto-visit - jid = self.address_comboboxentry.child.get_text().decode('utf-8') - try: - jid = helpers.parse_jid(jid) - except helpers.InvalidFormat, s: - pritext = _('Invalid Server Name') - dialogs.ErrorDialog(pritext, str(s)) - return - self.travel(jid, '') + def on_address_comboboxentry_changed(self, widget): + if self.address_comboboxentry.get_active() != -1: + # user selected one of the entries so do auto-visit + jid = self.address_comboboxentry.child.get_text().decode('utf-8') + try: + jid = helpers.parse_jid(jid) + except helpers.InvalidFormat, s: + pritext = _('Invalid Server Name') + dialogs.ErrorDialog(pritext, str(s)) + return + self.travel(jid, '') - def on_go_button_clicked(self, widget): - jid = self.address_comboboxentry.child.get_text().decode('utf-8') - try: - jid = helpers.parse_jid(jid) - except helpers.InvalidFormat, s: - pritext = _('Invalid Server Name') - dialogs.ErrorDialog(pritext, str(s)) - return - if jid == self.jid: # jid has not changed - return - if jid in self.latest_addresses: - self.latest_addresses.remove(jid) - self.latest_addresses.insert(0, jid) - if len(self.latest_addresses) > 10: - self.latest_addresses = self.latest_addresses[0:10] - self.address_comboboxentry.get_model().clear() - for j in self.latest_addresses: - self.address_comboboxentry.append_text(j) - gajim.config.set('latest_disco_addresses', - ' '.join(self.latest_addresses)) - gajim.interface.save_config() - self.travel(jid, '') + def on_go_button_clicked(self, widget): + jid = self.address_comboboxentry.child.get_text().decode('utf-8') + try: + jid = helpers.parse_jid(jid) + except helpers.InvalidFormat, s: + pritext = _('Invalid Server Name') + dialogs.ErrorDialog(pritext, str(s)) + return + if jid == self.jid: # jid has not changed + return + if jid in self.latest_addresses: + self.latest_addresses.remove(jid) + self.latest_addresses.insert(0, jid) + if len(self.latest_addresses) > 10: + self.latest_addresses = self.latest_addresses[0:10] + self.address_comboboxentry.get_model().clear() + for j in self.latest_addresses: + self.address_comboboxentry.append_text(j) + gajim.config.set('latest_disco_addresses', + ' '.join(self.latest_addresses)) + gajim.interface.save_config() + self.travel(jid, '') - def on_services_treeview_row_activated(self, widget, path, col = 0): - if self.browser: - self.browser.default_action() + def on_services_treeview_row_activated(self, widget, path, col = 0): + if self.browser: + self.browser.default_action() - def on_services_treeview_selection_changed(self, widget): - if self.browser: - self.browser.update_actions() + def on_services_treeview_selection_changed(self, widget): + if self.browser: + self.browser.update_actions() class AgentBrowser: - """ - Class that deals with browsing agents and appearance of the browser window. - This class and subclasses should basically be treated as "part" of the - ServiceDiscoveryWindow class, but had to be separated because this part is - dynamic - """ + """ + Class that deals with browsing agents and appearance of the browser window. + This class and subclasses should basically be treated as "part" of the + ServiceDiscoveryWindow class, but had to be separated because this part is + dynamic + """ - def __init__(self, account, jid, node): - self.account = account - self.jid = jid - self.node = node - self._total_items = 0 - self.browse_button = None - # This is for some timeout callbacks - self.active = False + def __init__(self, account, jid, node): + self.account = account + self.jid = jid + self.node = node + self._total_items = 0 + self.browse_button = None + # This is for some timeout callbacks + self.active = False - def _get_agent_address(self): - """ - Get the agent's address for displaying in the GUI - """ - return get_agent_address(self.jid, self.node) + def _get_agent_address(self): + """ + Get the agent's address for displaying in the GUI + """ + return get_agent_address(self.jid, self.node) - def _set_initial_title(self): - """ - Set the initial window title based on agent address - """ - self.window.window.set_title(_('Browsing %(address)s using account ' - '%(account)s') % {'address': self._get_agent_address(), - 'account': self.account}) - self.window._set_window_banner_text(self._get_agent_address()) + def _set_initial_title(self): + """ + Set the initial window title based on agent address + """ + self.window.window.set_title(_('Browsing %(address)s using account ' + '%(account)s') % {'address': self._get_agent_address(), + 'account': self.account}) + self.window._set_window_banner_text(self._get_agent_address()) - def _create_treemodel(self): - """ - Create the treemodel for the services treeview. When subclassing, note - that the first two columns should ALWAYS be of type string and contain - the JID and node of the item respectively - """ - # JID, node, name, address - self.model = gtk.ListStore(str, str, str, str) - self.model.set_sort_column_id(3, gtk.SORT_ASCENDING) - self.window.services_treeview.set_model(self.model) - # Name column - col = gtk.TreeViewColumn(_('Name')) - renderer = gtk.CellRendererText() - col.pack_start(renderer) - col.set_attributes(renderer, text = 2) - self.window.services_treeview.insert_column(col, -1) - col.set_resizable(True) - # Address column - col = gtk.TreeViewColumn(_('JID')) - renderer = gtk.CellRendererText() - col.pack_start(renderer) - col.set_attributes(renderer, text = 3) - self.window.services_treeview.insert_column(col, -1) - col.set_resizable(True) - self.window.services_treeview.set_headers_visible(True) + def _create_treemodel(self): + """ + Create the treemodel for the services treeview. When subclassing, note + that the first two columns should ALWAYS be of type string and contain + the JID and node of the item respectively + """ + # JID, node, name, address + self.model = gtk.ListStore(str, str, str, str) + self.model.set_sort_column_id(3, gtk.SORT_ASCENDING) + self.window.services_treeview.set_model(self.model) + # Name column + col = gtk.TreeViewColumn(_('Name')) + renderer = gtk.CellRendererText() + col.pack_start(renderer) + col.set_attributes(renderer, text = 2) + self.window.services_treeview.insert_column(col, -1) + col.set_resizable(True) + # Address column + col = gtk.TreeViewColumn(_('JID')) + renderer = gtk.CellRendererText() + col.pack_start(renderer) + col.set_attributes(renderer, text = 3) + self.window.services_treeview.insert_column(col, -1) + col.set_resizable(True) + self.window.services_treeview.set_headers_visible(True) - def _clean_treemodel(self): - self.model.clear() - for col in self.window.services_treeview.get_columns(): - self.window.services_treeview.remove_column(col) - self.window.services_treeview.set_headers_visible(False) + def _clean_treemodel(self): + self.model.clear() + for col in self.window.services_treeview.get_columns(): + self.window.services_treeview.remove_column(col) + self.window.services_treeview.set_headers_visible(False) - def _add_actions(self): - """ - Add the action buttons to the buttonbox for actions the browser can - perform - """ - self.browse_button = gtk.Button() - image = gtk.image_new_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_BUTTON) - label = gtk.Label(_('_Browse')) - label.set_use_underline(True) - hbox = gtk.HBox() - hbox.pack_start(image, False, True, 6) - hbox.pack_end(label, True, True) - self.browse_button.add(hbox) - self.browse_button.connect('clicked', self.on_browse_button_clicked) - self.window.action_buttonbox.add(self.browse_button) - self.browse_button.show_all() + def _add_actions(self): + """ + Add the action buttons to the buttonbox for actions the browser can + perform + """ + self.browse_button = gtk.Button() + image = gtk.image_new_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_BUTTON) + label = gtk.Label(_('_Browse')) + label.set_use_underline(True) + hbox = gtk.HBox() + hbox.pack_start(image, False, True, 6) + hbox.pack_end(label, True, True) + self.browse_button.add(hbox) + self.browse_button.connect('clicked', self.on_browse_button_clicked) + self.window.action_buttonbox.add(self.browse_button) + self.browse_button.show_all() - def _clean_actions(self): - """ - Remove the action buttons specific to this browser - """ - if self.browse_button: - self.browse_button.destroy() - self.browse_button = None + def _clean_actions(self): + """ + Remove the action buttons specific to this browser + """ + if self.browse_button: + self.browse_button.destroy() + self.browse_button = None - def _set_title(self, jid, node, identities, features, data): - """ - Set the window title based on agent info - """ - # Set the banner and window title - if 'name' in identities[0]: - name = identities[0]['name'] - self.window._set_window_banner_text(self._get_agent_address(), name) + def _set_title(self, jid, node, identities, features, data): + """ + Set the window title based on agent info + """ + # Set the banner and window title + if 'name' in identities[0]: + name = identities[0]['name'] + self.window._set_window_banner_text(self._get_agent_address(), name) - # Add an icon to the banner. - pix = self.cache.get_icon(identities) - self.window.banner_icon.set_from_pixbuf(pix) - self.window.banner_icon.show() + # Add an icon to the banner. + pix = self.cache.get_icon(identities) + self.window.banner_icon.set_from_pixbuf(pix) + self.window.banner_icon.show() - def _clean_title(self): - # Everything done here is done in window._initial_state - # This is for subclasses. - pass + def _clean_title(self): + # Everything done here is done in window._initial_state + # This is for subclasses. + pass - def prepare_window(self, window): - """ - Prepare the service discovery window. Called when a browser is hooked up - with a ServiceDiscoveryWindow instance - """ - self.window = window - self.cache = window.cache + def prepare_window(self, window): + """ + Prepare the service discovery window. Called when a browser is hooked up + with a ServiceDiscoveryWindow instance + """ + self.window = window + self.cache = window.cache - self._set_initial_title() - self._create_treemodel() - self._add_actions() + self._set_initial_title() + self._create_treemodel() + self._add_actions() - # This is a hack. The buttonbox apparently doesn't care about pack_start - # or pack_end, so we repack the close button here to make sure it's last - close_button = self.window.xml.get_object('close_button') - self.window.action_buttonbox.remove(close_button) - self.window.action_buttonbox.pack_end(close_button) - close_button.show_all() + # This is a hack. The buttonbox apparently doesn't care about pack_start + # or pack_end, so we repack the close button here to make sure it's last + close_button = self.window.xml.get_object('close_button') + self.window.action_buttonbox.remove(close_button) + self.window.action_buttonbox.pack_end(close_button) + close_button.show_all() - self.update_actions() + self.update_actions() - self.active = True - self.cache.get_info(self.jid, self.node, self._set_title) + self.active = True + self.cache.get_info(self.jid, self.node, self._set_title) - def cleanup(self): - """ - Cleanup when the window intends to switch browsers - """ - self.active = False + def cleanup(self): + """ + Cleanup when the window intends to switch browsers + """ + self.active = False - self._clean_actions() - self._clean_treemodel() - self._clean_title() + self._clean_actions() + self._clean_treemodel() + self._clean_title() - self.window._initial_state() + self.window._initial_state() - def update_theme(self): - """ - Called when the default theme is changed - """ - pass + def update_theme(self): + """ + Called when the default theme is changed + """ + pass - def on_browse_button_clicked(self, widget = None): - """ - When we want to browse an agent: open a new services window with a - browser for the agent type - """ - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if not iter_: - return - jid = model[iter_][0].decode('utf-8') - if jid: - node = model[iter_][1].decode('utf-8') - self.window.open(jid, node) + def on_browse_button_clicked(self, widget = None): + """ + When we want to browse an agent: open a new services window with a + browser for the agent type + """ + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if not iter_: + return + jid = model[iter_][0].decode('utf-8') + if jid: + node = model[iter_][1].decode('utf-8') + self.window.open(jid, node) - def update_actions(self): - """ - When we select a row: activate action buttons based on the agent's info - """ - if self.browse_button: - self.browse_button.set_sensitive(False) - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if not iter_: - return - jid = model[iter_][0].decode('utf-8') - node = model[iter_][1].decode('utf-8') - if jid: - self.cache.get_info(jid, node, self._update_actions, nofetch = True) + def update_actions(self): + """ + When we select a row: activate action buttons based on the agent's info + """ + if self.browse_button: + self.browse_button.set_sensitive(False) + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if not iter_: + return + jid = model[iter_][0].decode('utf-8') + node = model[iter_][1].decode('utf-8') + if jid: + self.cache.get_info(jid, node, self._update_actions, nofetch = True) - def _update_actions(self, jid, node, identities, features, data): - """ - Continuation of update_actions - """ - if not identities or not self.browse_button: - return - klass = self.cache.get_browser(identities, features) - if klass: - self.browse_button.set_sensitive(True) + def _update_actions(self, jid, node, identities, features, data): + """ + Continuation of update_actions + """ + if not identities or not self.browse_button: + return + klass = self.cache.get_browser(identities, features) + if klass: + self.browse_button.set_sensitive(True) - def default_action(self): - """ - When we double-click a row: perform the default action on the selected - item - """ - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if not iter_: - return - jid = model[iter_][0].decode('utf-8') - node = model[iter_][1].decode('utf-8') - if jid: - self.cache.get_info(jid, node, self._default_action, nofetch = True) + def default_action(self): + """ + When we double-click a row: perform the default action on the selected + item + """ + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if not iter_: + return + jid = model[iter_][0].decode('utf-8') + node = model[iter_][1].decode('utf-8') + if jid: + self.cache.get_info(jid, node, self._default_action, nofetch = True) - def _default_action(self, jid, node, identities, features, data): - """ - Continuation of default_action - """ - if self.cache.get_browser(identities, features): - # Browse if we can - self.on_browse_button_clicked() - return True - return False + def _default_action(self, jid, node, identities, features, data): + """ + Continuation of default_action + """ + if self.cache.get_browser(identities, features): + # Browse if we can + self.on_browse_button_clicked() + return True + return False - def browse(self, force = False): - """ - Fill the treeview with agents, fetching the info if necessary - """ - self.model.clear() - self._total_items = self._progress = 0 - self.window.progressbar.show() - self._pulse_timeout = gobject.timeout_add(250, self._pulse_timeout_cb) - self.cache.get_items(self.jid, self.node, self._agent_items, - force=force, args=(force,)) + def browse(self, force = False): + """ + Fill the treeview with agents, fetching the info if necessary + """ + self.model.clear() + self._total_items = self._progress = 0 + self.window.progressbar.show() + self._pulse_timeout = gobject.timeout_add(250, self._pulse_timeout_cb) + self.cache.get_items(self.jid, self.node, self._agent_items, + force=force, args=(force,)) - def _pulse_timeout_cb(self, *args): - """ - Simple callback to keep the progressbar pulsing - """ - if not self.active: - return False - self.window.progressbar.pulse() - return True + def _pulse_timeout_cb(self, *args): + """ + Simple callback to keep the progressbar pulsing + """ + if not self.active: + return False + self.window.progressbar.pulse() + return True - def _find_item(self, jid, node): - """ - Check if an item is already in the treeview. Return an iter to it if so, - None otherwise - """ - iter_ = self.model.get_iter_root() - while iter_: - cjid = self.model.get_value(iter_, 0).decode('utf-8') - cnode = self.model.get_value(iter_, 1).decode('utf-8') - if jid == cjid and node == cnode: - break - iter_ = self.model.iter_next(iter_) - if iter_: - return iter_ - return None + def _find_item(self, jid, node): + """ + Check if an item is already in the treeview. Return an iter to it if so, + None otherwise + """ + iter_ = self.model.get_iter_root() + while iter_: + cjid = self.model.get_value(iter_, 0).decode('utf-8') + cnode = self.model.get_value(iter_, 1).decode('utf-8') + if jid == cjid and node == cnode: + break + iter_ = self.model.iter_next(iter_) + if iter_: + return iter_ + return None - def _agent_items(self, jid, node, items, force): - """ - Callback for when we receive a list of agent items - """ - self.model.clear() - self._total_items = 0 - gobject.source_remove(self._pulse_timeout) - self.window.progressbar.hide() - # The server returned an error - if items == 0: - if not self.window.address_comboboxentry: - # We can't travel anywhere else. - self.window.destroy() - dialogs.ErrorDialog(_('The service is not browsable'), + def _agent_items(self, jid, node, items, force): + """ + Callback for when we receive a list of agent items + """ + self.model.clear() + self._total_items = 0 + gobject.source_remove(self._pulse_timeout) + self.window.progressbar.hide() + # The server returned an error + if items == 0: + if not self.window.address_comboboxentry: + # We can't travel anywhere else. + self.window.destroy() + dialogs.ErrorDialog(_('The service is not browsable'), _('This service does not contain any items to browse.')) - return - # We got a list of items - self.window.services_treeview.set_model(None) - for item in items: - jid_ = item['jid'] - node_ = item.get('node', '') - # If such an item is already here: don't add it - if self._find_item(jid_, node_): - continue - self._total_items += 1 - self._add_item(jid_, node_, node, item, force) - self.window.services_treeview.set_model(self.model) + return + # We got a list of items + self.window.services_treeview.set_model(None) + for item in items: + jid_ = item['jid'] + node_ = item.get('node', '') + # If such an item is already here: don't add it + if self._find_item(jid_, node_): + continue + self._total_items += 1 + self._add_item(jid_, node_, node, item, force) + self.window.services_treeview.set_model(self.model) - def _agent_info(self, jid, node, identities, features, data): - """ - Callback for when we receive info about an agent's item - """ - iter_ = self._find_item(jid, node) - if not iter_: - # Not in the treeview, stop - return - if identities == 0: - # The server returned an error - self._update_error(iter_, jid, node) - else: - # We got our info - self._update_info(iter_, jid, node, identities, features, data) - self.update_actions() + def _agent_info(self, jid, node, identities, features, data): + """ + Callback for when we receive info about an agent's item + """ + iter_ = self._find_item(jid, node) + if not iter_: + # Not in the treeview, stop + return + if identities == 0: + # The server returned an error + self._update_error(iter_, jid, node) + else: + # We got our info + self._update_info(iter_, jid, node, identities, features, data) + self.update_actions() - def _add_item(self, jid, node, parent_node, item, force): - """ - Called when an item should be added to the model. The result of a - disco#items query - """ - self.model.append((jid, node, item.get('name', ''), - get_agent_address(jid, node))) - self.cache.get_info(jid, node, self._agent_info, force = force) + def _add_item(self, jid, node, parent_node, item, force): + """ + Called when an item should be added to the model. The result of a + disco#items query + """ + self.model.append((jid, node, item.get('name', ''), + get_agent_address(jid, node))) + self.cache.get_info(jid, node, self._agent_info, force = force) - def _update_item(self, iter_, jid, node, item): - """ - Called when an item should be updated in the model. The result of a - disco#items query - """ - if 'name' in item: - self.model[iter_][2] = item['name'] + def _update_item(self, iter_, jid, node, item): + """ + Called when an item should be updated in the model. The result of a + disco#items query + """ + if 'name' in item: + self.model[iter_][2] = item['name'] - def _update_info(self, iter_, jid, node, identities, features, data): - """ - Called when an item should be updated in the model with further info. - The result of a disco#info query - """ - name = identities[0].get('name', '') - if name: - self.model[iter_][2] = name + def _update_info(self, iter_, jid, node, identities, features, data): + """ + Called when an item should be updated in the model with further info. + The result of a disco#info query + """ + name = identities[0].get('name', '') + if name: + self.model[iter_][2] = name - def _update_error(self, iter_, jid, node): - '''Called when a disco#info query failed for an item.''' - pass + def _update_error(self, iter_, jid, node): + '''Called when a disco#info query failed for an item.''' + pass class ToplevelAgentBrowser(AgentBrowser): - """ - This browser is used at the top level of a jabber server to browse services - such as transports, conference servers, etc - """ + """ + This browser is used at the top level of a jabber server to browse services + such as transports, conference servers, etc + """ - def __init__(self, *args): - AgentBrowser.__init__(self, *args) - self._progressbar_sourceid = None - self._renderer = None - self._progress = 0 - self.tooltip = tooltips.ServiceDiscoveryTooltip() - self.register_button = None - self.join_button = None - self.execute_button = None - self.search_button = None - # Keep track of our treeview signals - self._view_signals = [] - self._scroll_signal = None + def __init__(self, *args): + AgentBrowser.__init__(self, *args) + self._progressbar_sourceid = None + self._renderer = None + self._progress = 0 + self.tooltip = tooltips.ServiceDiscoveryTooltip() + self.register_button = None + self.join_button = None + self.execute_button = None + self.search_button = None + # Keep track of our treeview signals + self._view_signals = [] + self._scroll_signal = None - def _pixbuf_renderer_data_func(self, col, cell, model, iter_): - """ - Callback for setting the pixbuf renderer's properties - """ - jid = model.get_value(iter_, 0) - if jid: - pix = model.get_value(iter_, 2) - cell.set_property('visible', True) - cell.set_property('pixbuf', pix) - else: - cell.set_property('visible', False) + def _pixbuf_renderer_data_func(self, col, cell, model, iter_): + """ + Callback for setting the pixbuf renderer's properties + """ + jid = model.get_value(iter_, 0) + if jid: + pix = model.get_value(iter_, 2) + cell.set_property('visible', True) + cell.set_property('pixbuf', pix) + else: + cell.set_property('visible', False) - def _text_renderer_data_func(self, col, cell, model, iter_): - """ - Callback for setting the text renderer's properties - """ - jid = model.get_value(iter_, 0) - markup = model.get_value(iter_, 3) - state = model.get_value(iter_, 4) - cell.set_property('markup', markup) - if jid: - cell.set_property('cell_background_set', False) - if state > 0: - # 1 = fetching, 2 = error - cell.set_property('foreground_set', True) - else: - # Normal/succes - cell.set_property('foreground_set', False) - else: - theme = gajim.config.get('roster_theme') - bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor') - if bgcolor: - cell.set_property('cell_background_set', True) - cell.set_property('foreground_set', False) + def _text_renderer_data_func(self, col, cell, model, iter_): + """ + Callback for setting the text renderer's properties + """ + jid = model.get_value(iter_, 0) + markup = model.get_value(iter_, 3) + state = model.get_value(iter_, 4) + cell.set_property('markup', markup) + if jid: + cell.set_property('cell_background_set', False) + if state > 0: + # 1 = fetching, 2 = error + cell.set_property('foreground_set', True) + else: + # Normal/succes + cell.set_property('foreground_set', False) + else: + theme = gajim.config.get('roster_theme') + bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor') + if bgcolor: + cell.set_property('cell_background_set', True) + cell.set_property('foreground_set', False) - def _treemodel_sort_func(self, model, iter1, iter2): - """ - Sort function for our treemode - """ - # Compare state - statecmp = cmp(model.get_value(iter1, 4), model.get_value(iter2, 4)) - if statecmp == 0: - # These can be None, apparently - descr1 = model.get_value(iter1, 3) - if descr1: - descr1 = descr1.decode('utf-8') - descr2 = model.get_value(iter2, 3) - if descr2: - descr2 = descr2.decode('utf-8') - # Compare strings - return cmp(descr1, descr2) - return statecmp + def _treemodel_sort_func(self, model, iter1, iter2): + """ + Sort function for our treemode + """ + # Compare state + statecmp = cmp(model.get_value(iter1, 4), model.get_value(iter2, 4)) + if statecmp == 0: + # These can be None, apparently + descr1 = model.get_value(iter1, 3) + if descr1: + descr1 = descr1.decode('utf-8') + descr2 = model.get_value(iter2, 3) + if descr2: + descr2 = descr2.decode('utf-8') + # Compare strings + return cmp(descr1, descr2) + return statecmp - def _show_tooltip(self, state): - view = self.window.services_treeview - pointer = view.get_pointer() - props = view.get_path_at_pos(pointer[0], pointer[1]) - # check if the current pointer is at the same path - # as it was before setting the timeout - if props and self.tooltip.id == props[0]: - # bounding rectangle of coordinates for the cell within the treeview - rect = view.get_cell_area(props[0], props[1]) - # position of the treeview on the screen - position = view.window.get_origin() - self.tooltip.show_tooltip(state, rect.height, position[1] + rect.y) - else: - self.tooltip.hide_tooltip() + def _show_tooltip(self, state): + view = self.window.services_treeview + pointer = view.get_pointer() + props = view.get_path_at_pos(pointer[0], pointer[1]) + # check if the current pointer is at the same path + # as it was before setting the timeout + if props and self.tooltip.id == props[0]: + # bounding rectangle of coordinates for the cell within the treeview + rect = view.get_cell_area(props[0], props[1]) + # position of the treeview on the screen + position = view.window.get_origin() + self.tooltip.show_tooltip(state, rect.height, position[1] + rect.y) + else: + self.tooltip.hide_tooltip() - # These are all callbacks to make tooltips work - def on_treeview_leave_notify_event(self, widget, event): - props = widget.get_path_at_pos(int(event.x), int(event.y)) - if self.tooltip.timeout > 0: - if not props or self.tooltip.id == props[0]: - self.tooltip.hide_tooltip() + # These are all callbacks to make tooltips work + def on_treeview_leave_notify_event(self, widget, event): + props = widget.get_path_at_pos(int(event.x), int(event.y)) + if self.tooltip.timeout > 0: + if not props or self.tooltip.id == props[0]: + self.tooltip.hide_tooltip() - def on_treeview_motion_notify_event(self, widget, event): - props = widget.get_path_at_pos(int(event.x), int(event.y)) - if self.tooltip.timeout > 0: - if not props or self.tooltip.id != props[0]: - self.tooltip.hide_tooltip() - if props: - row = props[0] - iter_ = None - try: - iter_ = self.model.get_iter(row) - except Exception: - self.tooltip.hide_tooltip() - return - jid = self.model[iter_][0] - state = self.model[iter_][4] - # Not a category, and we have something to say about state - if jid and state > 0 and \ - (self.tooltip.timeout == 0 or self.tooltip.id != props[0]): - self.tooltip.id = row - self.tooltip.timeout = gobject.timeout_add(500, - self._show_tooltip, state) + def on_treeview_motion_notify_event(self, widget, event): + props = widget.get_path_at_pos(int(event.x), int(event.y)) + if self.tooltip.timeout > 0: + if not props or self.tooltip.id != props[0]: + self.tooltip.hide_tooltip() + if props: + row = props[0] + iter_ = None + try: + iter_ = self.model.get_iter(row) + except Exception: + self.tooltip.hide_tooltip() + return + jid = self.model[iter_][0] + state = self.model[iter_][4] + # Not a category, and we have something to say about state + if jid and state > 0 and \ + (self.tooltip.timeout == 0 or self.tooltip.id != props[0]): + self.tooltip.id = row + self.tooltip.timeout = gobject.timeout_add(500, + self._show_tooltip, state) - def on_treeview_event_hide_tooltip(self, widget, event): - """ - This happens on scroll_event, key_press_event and button_press_event - """ - self.tooltip.hide_tooltip() + def on_treeview_event_hide_tooltip(self, widget, event): + """ + This happens on scroll_event, key_press_event and button_press_event + """ + self.tooltip.hide_tooltip() - def _create_treemodel(self): - # JID, node, icon, description, state - # State means 2 when error, 1 when fetching, 0 when succes. - view = self.window.services_treeview - self.model = gtk.TreeStore(str, str, gtk.gdk.Pixbuf, str, int) - self.model.set_sort_func(4, self._treemodel_sort_func) - self.model.set_sort_column_id(4, gtk.SORT_ASCENDING) - view.set_model(self.model) + def _create_treemodel(self): + # JID, node, icon, description, state + # State means 2 when error, 1 when fetching, 0 when succes. + view = self.window.services_treeview + self.model = gtk.TreeStore(str, str, gtk.gdk.Pixbuf, str, int) + self.model.set_sort_func(4, self._treemodel_sort_func) + self.model.set_sort_column_id(4, gtk.SORT_ASCENDING) + view.set_model(self.model) - col = gtk.TreeViewColumn() - # Icon Renderer - renderer = gtk.CellRendererPixbuf() - renderer.set_property('xpad', 6) - col.pack_start(renderer, expand=False) - col.set_cell_data_func(renderer, self._pixbuf_renderer_data_func) - # Text Renderer - renderer = gtk.CellRendererText() - col.pack_start(renderer, expand=True) - col.set_cell_data_func(renderer, self._text_renderer_data_func) - renderer.set_property('foreground', 'dark gray') - # Save this so we can go along with theme changes - self._renderer = renderer - self.update_theme() + col = gtk.TreeViewColumn() + # Icon Renderer + renderer = gtk.CellRendererPixbuf() + renderer.set_property('xpad', 6) + col.pack_start(renderer, expand=False) + col.set_cell_data_func(renderer, self._pixbuf_renderer_data_func) + # Text Renderer + renderer = gtk.CellRendererText() + col.pack_start(renderer, expand=True) + col.set_cell_data_func(renderer, self._text_renderer_data_func) + renderer.set_property('foreground', 'dark gray') + # Save this so we can go along with theme changes + self._renderer = renderer + self.update_theme() - view.insert_column(col, -1) - col.set_resizable(True) + view.insert_column(col, -1) + col.set_resizable(True) - # Connect signals - scrollwin = self.window.services_scrollwin - self._view_signals.append(view.connect('leave-notify-event', - self.on_treeview_leave_notify_event)) - self._view_signals.append(view.connect('motion-notify-event', - self.on_treeview_motion_notify_event)) - self._view_signals.append(view.connect('key-press-event', - self.on_treeview_event_hide_tooltip)) - self._view_signals.append(view.connect('button-press-event', - self.on_treeview_event_hide_tooltip)) - self._scroll_signal = scrollwin.connect('scroll-event', - self.on_treeview_event_hide_tooltip) + # Connect signals + scrollwin = self.window.services_scrollwin + self._view_signals.append(view.connect('leave-notify-event', + self.on_treeview_leave_notify_event)) + self._view_signals.append(view.connect('motion-notify-event', + self.on_treeview_motion_notify_event)) + self._view_signals.append(view.connect('key-press-event', + self.on_treeview_event_hide_tooltip)) + self._view_signals.append(view.connect('button-press-event', + self.on_treeview_event_hide_tooltip)) + self._scroll_signal = scrollwin.connect('scroll-event', + self.on_treeview_event_hide_tooltip) - def _clean_treemodel(self): - # Disconnect signals - view = self.window.services_treeview - for sig in self._view_signals: - view.disconnect(sig) - self._view_signals = [] - if self._scroll_signal: - scrollwin = self.window.services_scrollwin - scrollwin.disconnect(self._scroll_signal) - self._scroll_signal = None - AgentBrowser._clean_treemodel(self) + def _clean_treemodel(self): + # Disconnect signals + view = self.window.services_treeview + for sig in self._view_signals: + view.disconnect(sig) + self._view_signals = [] + if self._scroll_signal: + scrollwin = self.window.services_scrollwin + scrollwin.disconnect(self._scroll_signal) + self._scroll_signal = None + AgentBrowser._clean_treemodel(self) - def _add_actions(self): - AgentBrowser._add_actions(self) - self.execute_button = gtk.Button() - image = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_BUTTON) - label = gtk.Label(_('_Execute Command')) - label.set_use_underline(True) - hbox = gtk.HBox() - hbox.pack_start(image, False, True, 6) - hbox.pack_end(label, True, True) - self.execute_button.add(hbox) - self.execute_button.connect('clicked', self.on_execute_button_clicked) - self.window.action_buttonbox.add(self.execute_button) - self.execute_button.show_all() + def _add_actions(self): + AgentBrowser._add_actions(self) + self.execute_button = gtk.Button() + image = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_BUTTON) + label = gtk.Label(_('_Execute Command')) + label.set_use_underline(True) + hbox = gtk.HBox() + hbox.pack_start(image, False, True, 6) + hbox.pack_end(label, True, True) + self.execute_button.add(hbox) + self.execute_button.connect('clicked', self.on_execute_button_clicked) + self.window.action_buttonbox.add(self.execute_button) + self.execute_button.show_all() - self.register_button = gtk.Button(label=_("Re_gister"), - use_underline=True) - self.register_button.connect('clicked', self.on_register_button_clicked) - self.window.action_buttonbox.add(self.register_button) - self.register_button.show_all() + self.register_button = gtk.Button(label=_("Re_gister"), + use_underline=True) + self.register_button.connect('clicked', self.on_register_button_clicked) + self.window.action_buttonbox.add(self.register_button) + self.register_button.show_all() - self.join_button = gtk.Button() - image = gtk.image_new_from_stock(gtk.STOCK_CONNECT, gtk.ICON_SIZE_BUTTON) - label = gtk.Label(_('_Join')) - label.set_use_underline(True) - hbox = gtk.HBox() - hbox.pack_start(image, False, True, 6) - hbox.pack_end(label, True, True) - self.join_button.add(hbox) - self.join_button.connect('clicked', self.on_join_button_clicked) - self.window.action_buttonbox.add(self.join_button) - self.join_button.show_all() + self.join_button = gtk.Button() + image = gtk.image_new_from_stock(gtk.STOCK_CONNECT, gtk.ICON_SIZE_BUTTON) + label = gtk.Label(_('_Join')) + label.set_use_underline(True) + hbox = gtk.HBox() + hbox.pack_start(image, False, True, 6) + hbox.pack_end(label, True, True) + self.join_button.add(hbox) + self.join_button.connect('clicked', self.on_join_button_clicked) + self.window.action_buttonbox.add(self.join_button) + self.join_button.show_all() - self.search_button = gtk.Button() - image = gtk.image_new_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_BUTTON) - label = gtk.Label(_('_Search')) - label.set_use_underline(True) - hbox = gtk.HBox() - hbox.pack_start(image, False, True, 6) - hbox.pack_end(label, True, True) - self.search_button.add(hbox) - self.search_button.connect('clicked', self.on_search_button_clicked) - self.window.action_buttonbox.add(self.search_button) - self.search_button.show_all() + self.search_button = gtk.Button() + image = gtk.image_new_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_BUTTON) + label = gtk.Label(_('_Search')) + label.set_use_underline(True) + hbox = gtk.HBox() + hbox.pack_start(image, False, True, 6) + hbox.pack_end(label, True, True) + self.search_button.add(hbox) + self.search_button.connect('clicked', self.on_search_button_clicked) + self.window.action_buttonbox.add(self.search_button) + self.search_button.show_all() - def _clean_actions(self): - if self.execute_button: - self.execute_button.destroy() - self.execute_button = None - if self.register_button: - self.register_button.destroy() - self.register_button = None - if self.join_button: - self.join_button.destroy() - self.join_button = None - if self.search_button: - self.search_button.destroy() - self.search_button = None - AgentBrowser._clean_actions(self) + def _clean_actions(self): + if self.execute_button: + self.execute_button.destroy() + self.execute_button = None + if self.register_button: + self.register_button.destroy() + self.register_button = None + if self.join_button: + self.join_button.destroy() + self.join_button = None + if self.search_button: + self.search_button.destroy() + self.search_button = None + AgentBrowser._clean_actions(self) - def on_search_button_clicked(self, widget = None): - """ - When we want to search something: open search window - """ - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if not iter_: - return - service = model[iter_][0].decode('utf-8') - if service in gajim.interface.instances[self.account]['search']: - gajim.interface.instances[self.account]['search'][service].window.\ - present() - else: - gajim.interface.instances[self.account]['search'][service] = \ - search_window.SearchWindow(self.account, service) + def on_search_button_clicked(self, widget = None): + """ + When we want to search something: open search window + """ + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if not iter_: + return + service = model[iter_][0].decode('utf-8') + if service in gajim.interface.instances[self.account]['search']: + gajim.interface.instances[self.account]['search'][service].window.\ + present() + else: + gajim.interface.instances[self.account]['search'][service] = \ + search_window.SearchWindow(self.account, service) - def cleanup(self): - self.tooltip.hide_tooltip() - AgentBrowser.cleanup(self) + def cleanup(self): + self.tooltip.hide_tooltip() + AgentBrowser.cleanup(self) - def update_theme(self): - theme = gajim.config.get('roster_theme') - bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor') - if bgcolor: - self._renderer.set_property('cell-background', bgcolor) - self.window.services_treeview.queue_draw() + def update_theme(self): + theme = gajim.config.get('roster_theme') + bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor') + if bgcolor: + self._renderer.set_property('cell-background', bgcolor) + self.window.services_treeview.queue_draw() - def on_execute_button_clicked(self, widget=None): - """ - When we want to execute a command: open adhoc command window - """ - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if not iter_: - return - service = model[iter_][0].decode('utf-8') - node = model[iter_][1].decode('utf-8') - adhoc_commands.CommandWindow(self.account, service, commandnode=node) + def on_execute_button_clicked(self, widget=None): + """ + When we want to execute a command: open adhoc command window + """ + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if not iter_: + return + service = model[iter_][0].decode('utf-8') + node = model[iter_][1].decode('utf-8') + adhoc_commands.CommandWindow(self.account, service, commandnode=node) - def on_register_button_clicked(self, widget = None): - """ - When we want to register an agent: request information about registering - with the agent and close the window - """ - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if not iter_: - return - jid = model[iter_][0].decode('utf-8') - if jid: - gajim.connections[self.account].request_register_agent_info(jid) - self.window.destroy(chain = True) + def on_register_button_clicked(self, widget = None): + """ + When we want to register an agent: request information about registering + with the agent and close the window + """ + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if not iter_: + return + jid = model[iter_][0].decode('utf-8') + if jid: + gajim.connections[self.account].request_register_agent_info(jid) + self.window.destroy(chain = True) - def on_join_button_clicked(self, widget): - """ - When we want to join an IRC room or create a new MUC room: Opens the - join_groupchat_window - """ - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if not iter_: - return - service = model[iter_][0].decode('utf-8') - if 'join_gc' not in gajim.interface.instances[self.account]: - try: - dialogs.JoinGroupchatWindow(self.account, service) - except GajimGeneralException: - pass - else: - gajim.interface.instances[self.account]['join_gc'].window.present() - self.window.destroy(chain = True) + def on_join_button_clicked(self, widget): + """ + When we want to join an IRC room or create a new MUC room: Opens the + join_groupchat_window + """ + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if not iter_: + return + service = model[iter_][0].decode('utf-8') + if 'join_gc' not in gajim.interface.instances[self.account]: + try: + dialogs.JoinGroupchatWindow(self.account, service) + except GajimGeneralException: + pass + else: + gajim.interface.instances[self.account]['join_gc'].window.present() + self.window.destroy(chain = True) - def update_actions(self): - if self.execute_button: - self.execute_button.set_sensitive(False) - if self.register_button: - self.register_button.set_sensitive(False) - if self.browse_button: - self.browse_button.set_sensitive(False) - if self.join_button: - self.join_button.set_sensitive(False) - if self.search_button: - self.search_button.set_sensitive(False) - model, iter_ = self.window.services_treeview.get_selection().get_selected() - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if not iter_: - return - if not model[iter_][0]: - # We're on a category row - return - if model[iter_][4] != 0: - # We don't have the info (yet) - # It's either unknown or a transport, register button should be active - if self.register_button: - self.register_button.set_sensitive(True) - # Guess what kind of service we're dealing with - if self.browse_button: - jid = model[iter_][0].decode('utf-8') - type_ = gajim.get_transport_name_from_jid(jid, - use_config_setting = False) - if type_: - identity = {'category': '_jid', 'type': type_} - klass = self.cache.get_browser([identity]) - if klass: - self.browse_button.set_sensitive(True) - else: - # We couldn't guess - self.browse_button.set_sensitive(True) - else: - # Normal case, we have info - AgentBrowser.update_actions(self) + def update_actions(self): + if self.execute_button: + self.execute_button.set_sensitive(False) + if self.register_button: + self.register_button.set_sensitive(False) + if self.browse_button: + self.browse_button.set_sensitive(False) + if self.join_button: + self.join_button.set_sensitive(False) + if self.search_button: + self.search_button.set_sensitive(False) + model, iter_ = self.window.services_treeview.get_selection().get_selected() + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if not iter_: + return + if not model[iter_][0]: + # We're on a category row + return + if model[iter_][4] != 0: + # We don't have the info (yet) + # It's either unknown or a transport, register button should be active + if self.register_button: + self.register_button.set_sensitive(True) + # Guess what kind of service we're dealing with + if self.browse_button: + jid = model[iter_][0].decode('utf-8') + type_ = gajim.get_transport_name_from_jid(jid, + use_config_setting = False) + if type_: + identity = {'category': '_jid', 'type': type_} + klass = self.cache.get_browser([identity]) + if klass: + self.browse_button.set_sensitive(True) + else: + # We couldn't guess + self.browse_button.set_sensitive(True) + else: + # Normal case, we have info + AgentBrowser.update_actions(self) - def _update_actions(self, jid, node, identities, features, data): - AgentBrowser._update_actions(self, jid, node, identities, features, data) - if self.execute_button and xmpp.NS_COMMANDS in features: - self.execute_button.set_sensitive(True) - if self.search_button and xmpp.NS_SEARCH in features: - self.search_button.set_sensitive(True) - if self.register_button and xmpp.NS_REGISTER in features: - # We can register this agent - registered_transports = [] - jid_list = gajim.contacts.get_jid_list(self.account) - for jid in jid_list: - contact = gajim.contacts.get_first_contact_from_jid( - self.account, jid) - if _('Transports') in contact.groups: - registered_transports.append(jid) - if jid in registered_transports: - self.register_button.set_label(_('_Edit')) - else: - self.register_button.set_label(_('Re_gister')) - self.register_button.set_sensitive(True) - if self.join_button and xmpp.NS_MUC in features: - self.join_button.set_sensitive(True) + def _update_actions(self, jid, node, identities, features, data): + AgentBrowser._update_actions(self, jid, node, identities, features, data) + if self.execute_button and xmpp.NS_COMMANDS in features: + self.execute_button.set_sensitive(True) + if self.search_button and xmpp.NS_SEARCH in features: + self.search_button.set_sensitive(True) + if self.register_button and xmpp.NS_REGISTER in features: + # We can register this agent + registered_transports = [] + jid_list = gajim.contacts.get_jid_list(self.account) + for jid in jid_list: + contact = gajim.contacts.get_first_contact_from_jid( + self.account, jid) + if _('Transports') in contact.groups: + registered_transports.append(jid) + if jid in registered_transports: + self.register_button.set_label(_('_Edit')) + else: + self.register_button.set_label(_('Re_gister')) + self.register_button.set_sensitive(True) + if self.join_button and xmpp.NS_MUC in features: + self.join_button.set_sensitive(True) - def _default_action(self, jid, node, identities, features, data): - if AgentBrowser._default_action(self, jid, node, identities, features, data): - return True - if xmpp.NS_REGISTER in features: - # Register if we can't browse - self.on_register_button_clicked() - return True - return False + def _default_action(self, jid, node, identities, features, data): + if AgentBrowser._default_action(self, jid, node, identities, features, data): + return True + if xmpp.NS_REGISTER in features: + # Register if we can't browse + self.on_register_button_clicked() + return True + return False - def browse(self, force = False): - self._progress = 0 - AgentBrowser.browse(self, force = force) + def browse(self, force = False): + self._progress = 0 + AgentBrowser.browse(self, force = force) - def _expand_all(self): - """ - Expand all items in the treeview - """ - # GTK apparently screws up here occasionally. :/ - #def expand_all(*args): - # self.window.services_treeview.expand_all() - # self.expanding = False - # return False - #self.expanding = True - #gobject.idle_add(expand_all) - self.window.services_treeview.expand_all() + def _expand_all(self): + """ + Expand all items in the treeview + """ + # GTK apparently screws up here occasionally. :/ + #def expand_all(*args): + # self.window.services_treeview.expand_all() + # self.expanding = False + # return False + #self.expanding = True + #gobject.idle_add(expand_all) + self.window.services_treeview.expand_all() - def _update_progressbar(self): - """ - Update the progressbar - """ - # Refresh this every update - if self._progressbar_sourceid: - gobject.source_remove(self._progressbar_sourceid) + def _update_progressbar(self): + """ + Update the progressbar + """ + # Refresh this every update + if self._progressbar_sourceid: + gobject.source_remove(self._progressbar_sourceid) - fraction = 0 - if self._total_items: - self.window.progressbar.set_text(_("Scanning %(current)d / %(total)d.." - ) % {'current': self._progress, 'total': self._total_items}) - fraction = float(self._progress) / float(self._total_items) - if self._progress >= self._total_items: - # We show the progressbar for just a bit before hiding it. - id_ = gobject.timeout_add_seconds(2, self._hide_progressbar_cb) - self._progressbar_sourceid = id_ - else: - self.window.progressbar.show() - # Hide the progressbar if we're timing out anyways. (20 secs) - id_ = gobject.timeout_add_seconds(20, self._hide_progressbar_cb) - self._progressbar_sourceid = id_ - self.window.progressbar.set_fraction(fraction) + fraction = 0 + if self._total_items: + self.window.progressbar.set_text(_("Scanning %(current)d / %(total)d.." + ) % {'current': self._progress, 'total': self._total_items}) + fraction = float(self._progress) / float(self._total_items) + if self._progress >= self._total_items: + # We show the progressbar for just a bit before hiding it. + id_ = gobject.timeout_add_seconds(2, self._hide_progressbar_cb) + self._progressbar_sourceid = id_ + else: + self.window.progressbar.show() + # Hide the progressbar if we're timing out anyways. (20 secs) + id_ = gobject.timeout_add_seconds(20, self._hide_progressbar_cb) + self._progressbar_sourceid = id_ + self.window.progressbar.set_fraction(fraction) - def _hide_progressbar_cb(self, *args): - """ - Simple callback to hide the progressbar a second after we finish - """ - if self.active: - self.window.progressbar.hide() - return False + def _hide_progressbar_cb(self, *args): + """ + Simple callback to hide the progressbar a second after we finish + """ + if self.active: + self.window.progressbar.hide() + return False - def _friendly_category(self, category, type_=None): - """ - Get the friendly category name and priority - """ - cat = None - if type_: - # Try type-specific override - try: - cat, prio = _cat_to_descr[(category, type_)] - except KeyError: - pass - if not cat: - try: - cat, prio = _cat_to_descr[category] - except KeyError: - cat, prio = _cat_to_descr['other'] - return cat, prio + def _friendly_category(self, category, type_=None): + """ + Get the friendly category name and priority + """ + cat = None + if type_: + # Try type-specific override + try: + cat, prio = _cat_to_descr[(category, type_)] + except KeyError: + pass + if not cat: + try: + cat, prio = _cat_to_descr[category] + except KeyError: + cat, prio = _cat_to_descr['other'] + return cat, prio - def _create_category(self, cat, type_=None): - """ - Creates a category row - """ - cat, prio = self._friendly_category(cat, type_) - return self.model.append(None, ('', '', None, cat, prio)) + def _create_category(self, cat, type_=None): + """ + Creates a category row + """ + cat, prio = self._friendly_category(cat, type_) + return self.model.append(None, ('', '', None, cat, prio)) - def _find_category(self, cat, type_=None): - """ - Looks up a category row and returns the iterator to it, or None - """ - cat = self._friendly_category(cat, type_)[0] - iter_ = self.model.get_iter_root() - while iter_: - if self.model.get_value(iter_, 3).decode('utf-8') == cat: - break - iter_ = self.model.iter_next(iter_) - if iter_: - return iter_ - return None + def _find_category(self, cat, type_=None): + """ + Looks up a category row and returns the iterator to it, or None + """ + cat = self._friendly_category(cat, type_)[0] + iter_ = self.model.get_iter_root() + while iter_: + if self.model.get_value(iter_, 3).decode('utf-8') == cat: + break + iter_ = self.model.iter_next(iter_) + if iter_: + return iter_ + return None - def _find_item(self, jid, node): - iter_ = None - cat_iter = self.model.get_iter_root() - while cat_iter and not iter_: - iter_ = self.model.iter_children(cat_iter) - while iter_: - cjid = self.model.get_value(iter_, 0).decode('utf-8') - cnode = self.model.get_value(iter_, 1).decode('utf-8') - if jid == cjid and node == cnode: - break - iter_ = self.model.iter_next(iter_) - cat_iter = self.model.iter_next(cat_iter) - if iter_: - return iter_ - return None + def _find_item(self, jid, node): + iter_ = None + cat_iter = self.model.get_iter_root() + while cat_iter and not iter_: + iter_ = self.model.iter_children(cat_iter) + while iter_: + cjid = self.model.get_value(iter_, 0).decode('utf-8') + cnode = self.model.get_value(iter_, 1).decode('utf-8') + if jid == cjid and node == cnode: + break + iter_ = self.model.iter_next(iter_) + cat_iter = self.model.iter_next(cat_iter) + if iter_: + return iter_ + return None - def _add_item(self, jid, node, parent_node, item, force): - # Row text - addr = get_agent_address(jid, node) - if 'name' in item: - descr = "%s\n%s" % (item['name'], addr) - else: - descr = "%s" % addr - # Guess which kind of service this is - identities = [] - type_ = gajim.get_transport_name_from_jid(jid, - use_config_setting = False) - if type_: - identity = {'category': '_jid', 'type': type_} - identities.append(identity) - cat_args = ('_jid', type_) - else: - # Put it in the 'other' category for now - cat_args = ('other',) - # Set the pixmap for the row - pix = self.cache.get_icon(identities) - # Put it in the right category - cat = self._find_category(*cat_args) - if not cat: - cat = self._create_category(*cat_args) - self.model.append(cat, (jid, node, pix, descr, 1)) - gobject.idle_add(self._expand_all) - # Grab info on the service - self.cache.get_info(jid, node, self._agent_info, force=force) - self._update_progressbar() + def _add_item(self, jid, node, parent_node, item, force): + # Row text + addr = get_agent_address(jid, node) + if 'name' in item: + descr = "%s\n%s" % (item['name'], addr) + else: + descr = "%s" % addr + # Guess which kind of service this is + identities = [] + type_ = gajim.get_transport_name_from_jid(jid, + use_config_setting = False) + if type_: + identity = {'category': '_jid', 'type': type_} + identities.append(identity) + cat_args = ('_jid', type_) + else: + # Put it in the 'other' category for now + cat_args = ('other',) + # Set the pixmap for the row + pix = self.cache.get_icon(identities) + # Put it in the right category + cat = self._find_category(*cat_args) + if not cat: + cat = self._create_category(*cat_args) + self.model.append(cat, (jid, node, pix, descr, 1)) + gobject.idle_add(self._expand_all) + # Grab info on the service + self.cache.get_info(jid, node, self._agent_info, force=force) + self._update_progressbar() - def _update_item(self, iter_, jid, node, item): - addr = get_agent_address(jid, node) - if 'name' in item: - descr = "%s\n%s" % (item['name'], addr) - else: - descr = "%s" % addr - self.model[iter_][3] = descr + def _update_item(self, iter_, jid, node, item): + addr = get_agent_address(jid, node) + if 'name' in item: + descr = "%s\n%s" % (item['name'], addr) + else: + descr = "%s" % addr + self.model[iter_][3] = descr - def _update_info(self, iter_, jid, node, identities, features, data): - addr = get_agent_address(jid, node) - name = identities[0].get('name', '') - if name: - descr = "%s\n%s" % (name, addr) - else: - descr = "%s" % addr + def _update_info(self, iter_, jid, node, identities, features, data): + addr = get_agent_address(jid, node) + name = identities[0].get('name', '') + if name: + descr = "%s\n%s" % (name, addr) + else: + descr = "%s" % addr - # Update progress - self._progress += 1 - self._update_progressbar() + # Update progress + self._progress += 1 + self._update_progressbar() - # Search for an icon and category we can display - pix = self.cache.get_icon(identities) - for identity in identities: - try: - cat, type_ = identity['category'], identity['type'] - except KeyError: - continue - break + # Search for an icon and category we can display + pix = self.cache.get_icon(identities) + for identity in identities: + try: + cat, type_ = identity['category'], identity['type'] + except KeyError: + continue + break - # Check if we have to move categories - old_cat_iter = self.model.iter_parent(iter_) - old_cat = self.model.get_value(old_cat_iter, 3).decode('utf-8') - if self.model.get_value(old_cat_iter, 3) == cat: - # Already in the right category, just update - self.model[iter_][2] = pix - self.model[iter_][3] = descr - self.model[iter_][4] = 0 - return - # Not in the right category, move it. - self.model.remove(iter_) + # Check if we have to move categories + old_cat_iter = self.model.iter_parent(iter_) + old_cat = self.model.get_value(old_cat_iter, 3).decode('utf-8') + if self.model.get_value(old_cat_iter, 3) == cat: + # Already in the right category, just update + self.model[iter_][2] = pix + self.model[iter_][3] = descr + self.model[iter_][4] = 0 + return + # Not in the right category, move it. + self.model.remove(iter_) - # Check if the old category is empty - if not self.model.iter_is_valid(old_cat_iter): - old_cat_iter = self._find_category(old_cat) - if not self.model.iter_children(old_cat_iter): - self.model.remove(old_cat_iter) + # Check if the old category is empty + if not self.model.iter_is_valid(old_cat_iter): + old_cat_iter = self._find_category(old_cat) + if not self.model.iter_children(old_cat_iter): + self.model.remove(old_cat_iter) - cat_iter = self._find_category(cat, type_) - if not cat_iter: - cat_iter = self._create_category(cat, type_) - self.model.append(cat_iter, (jid, node, pix, descr, 0)) - self._expand_all() + cat_iter = self._find_category(cat, type_) + if not cat_iter: + cat_iter = self._create_category(cat, type_) + self.model.append(cat_iter, (jid, node, pix, descr, 0)) + self._expand_all() - def _update_error(self, iter_, jid, node): - self.model[iter_][4] = 2 - self._progress += 1 - self._update_progressbar() + def _update_error(self, iter_, jid, node): + self.model[iter_][4] = 2 + self._progress += 1 + self._update_progressbar() class MucBrowser(AgentBrowser): - def __init__(self, *args, **kwargs): - AgentBrowser.__init__(self, *args, **kwargs) - self.join_button = None - self.bookmark_button = None + def __init__(self, *args, **kwargs): + AgentBrowser.__init__(self, *args, **kwargs) + self.join_button = None + self.bookmark_button = None - def _create_treemodel(self): - # JID, node, name, users_int, users_str, description, fetched - # This is rather long, I'd rather not use a data_func here though. - # Users is a string, because want to be able to leave it empty. - self.model = gtk.ListStore(str, str, str, int, str, str, bool) - self.model.set_sort_column_id(2, gtk.SORT_ASCENDING) - self.window.services_treeview.set_model(self.model) - # Name column - col = gtk.TreeViewColumn(_('Name')) - col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) - col.set_fixed_width(100) - renderer = gtk.CellRendererText() - col.pack_start(renderer) - col.set_attributes(renderer, text = 2) - col.set_sort_column_id(2) - self.window.services_treeview.insert_column(col, -1) - col.set_resizable(True) - # Users column - col = gtk.TreeViewColumn(_('Users')) - renderer = gtk.CellRendererText() - col.pack_start(renderer) - col.set_attributes(renderer, text = 4) - col.set_sort_column_id(3) - self.window.services_treeview.insert_column(col, -1) - col.set_resizable(True) - # Description column - col = gtk.TreeViewColumn(_('Description')) - renderer = gtk.CellRendererText() - col.pack_start(renderer) - col.set_attributes(renderer, text = 5) - col.set_sort_column_id(4) - self.window.services_treeview.insert_column(col, -1) - col.set_resizable(True) - # Id column - col = gtk.TreeViewColumn(_('Id')) - renderer = gtk.CellRendererText() - col.pack_start(renderer) - col.set_attributes(renderer, text = 0) - col.set_sort_column_id(0) - self.window.services_treeview.insert_column(col, -1) - col.set_resizable(True) - self.window.services_treeview.set_headers_visible(True) - self.window.services_treeview.set_headers_clickable(True) - # Source id for idle callback used to start disco#info queries. - self._fetch_source = None - # Query failure counter - self._broken = 0 - # Connect to scrollwindow scrolling - self.vadj = self.window.services_scrollwin.get_property('vadjustment') - self.vadj_cbid = self.vadj.connect('value-changed', self.on_scroll) - # And to size changes - self.size_cbid = self.window.services_scrollwin.connect( - 'size-allocate', self.on_scroll) + def _create_treemodel(self): + # JID, node, name, users_int, users_str, description, fetched + # This is rather long, I'd rather not use a data_func here though. + # Users is a string, because want to be able to leave it empty. + self.model = gtk.ListStore(str, str, str, int, str, str, bool) + self.model.set_sort_column_id(2, gtk.SORT_ASCENDING) + self.window.services_treeview.set_model(self.model) + # Name column + col = gtk.TreeViewColumn(_('Name')) + col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) + col.set_fixed_width(100) + renderer = gtk.CellRendererText() + col.pack_start(renderer) + col.set_attributes(renderer, text = 2) + col.set_sort_column_id(2) + self.window.services_treeview.insert_column(col, -1) + col.set_resizable(True) + # Users column + col = gtk.TreeViewColumn(_('Users')) + renderer = gtk.CellRendererText() + col.pack_start(renderer) + col.set_attributes(renderer, text = 4) + col.set_sort_column_id(3) + self.window.services_treeview.insert_column(col, -1) + col.set_resizable(True) + # Description column + col = gtk.TreeViewColumn(_('Description')) + renderer = gtk.CellRendererText() + col.pack_start(renderer) + col.set_attributes(renderer, text = 5) + col.set_sort_column_id(4) + self.window.services_treeview.insert_column(col, -1) + col.set_resizable(True) + # Id column + col = gtk.TreeViewColumn(_('Id')) + renderer = gtk.CellRendererText() + col.pack_start(renderer) + col.set_attributes(renderer, text = 0) + col.set_sort_column_id(0) + self.window.services_treeview.insert_column(col, -1) + col.set_resizable(True) + self.window.services_treeview.set_headers_visible(True) + self.window.services_treeview.set_headers_clickable(True) + # Source id for idle callback used to start disco#info queries. + self._fetch_source = None + # Query failure counter + self._broken = 0 + # Connect to scrollwindow scrolling + self.vadj = self.window.services_scrollwin.get_property('vadjustment') + self.vadj_cbid = self.vadj.connect('value-changed', self.on_scroll) + # And to size changes + self.size_cbid = self.window.services_scrollwin.connect( + 'size-allocate', self.on_scroll) - def _clean_treemodel(self): - if self.size_cbid: - self.window.services_scrollwin.disconnect(self.size_cbid) - self.size_cbid = None - if self.vadj_cbid: - self.vadj.disconnect(self.vadj_cbid) - self.vadj_cbid = None - AgentBrowser._clean_treemodel(self) + def _clean_treemodel(self): + if self.size_cbid: + self.window.services_scrollwin.disconnect(self.size_cbid) + self.size_cbid = None + if self.vadj_cbid: + self.vadj.disconnect(self.vadj_cbid) + self.vadj_cbid = None + AgentBrowser._clean_treemodel(self) - def _add_actions(self): - self.bookmark_button = gtk.Button(label=_('_Bookmark'), use_underline=True) - self.bookmark_button.connect('clicked', self.on_bookmark_button_clicked) - self.window.action_buttonbox.add(self.bookmark_button) - self.bookmark_button.show_all() - self.join_button = gtk.Button(label=_('_Join'), use_underline=True) - self.join_button.connect('clicked', self.on_join_button_clicked) - self.window.action_buttonbox.add(self.join_button) - self.join_button.show_all() + def _add_actions(self): + self.bookmark_button = gtk.Button(label=_('_Bookmark'), use_underline=True) + self.bookmark_button.connect('clicked', self.on_bookmark_button_clicked) + self.window.action_buttonbox.add(self.bookmark_button) + self.bookmark_button.show_all() + self.join_button = gtk.Button(label=_('_Join'), use_underline=True) + self.join_button.connect('clicked', self.on_join_button_clicked) + self.window.action_buttonbox.add(self.join_button) + self.join_button.show_all() - def _clean_actions(self): - if self.bookmark_button: - self.bookmark_button.destroy() - self.bookmark_button = None - if self.join_button: - self.join_button.destroy() - self.join_button = None + def _clean_actions(self): + if self.bookmark_button: + self.bookmark_button.destroy() + self.bookmark_button = None + if self.join_button: + self.join_button.destroy() + self.join_button = None - def on_bookmark_button_clicked(self, *args): - model, iter = self.window.services_treeview.get_selection().get_selected() - if not iter: - return - name = gajim.config.get_per('accounts', self.account, 'name') - room_jid = model[iter][0].decode('utf-8') - bm = { - 'name': room_jid.split('@')[0], - 'jid': room_jid, - 'autojoin': '0', - 'minimize': '0', - 'password': '', - 'nick': name - } + def on_bookmark_button_clicked(self, *args): + model, iter = self.window.services_treeview.get_selection().get_selected() + if not iter: + return + name = gajim.config.get_per('accounts', self.account, 'name') + room_jid = model[iter][0].decode('utf-8') + bm = { + 'name': room_jid.split('@')[0], + 'jid': room_jid, + 'autojoin': '0', + 'minimize': '0', + 'password': '', + 'nick': name + } - for bookmark in gajim.connections[self.account].bookmarks: - if bookmark['jid'] == bm['jid']: - dialogs.ErrorDialog( - _('Bookmark already set'), - _('Group Chat "%s" is already in your bookmarks.') % bm['jid']) - return + for bookmark in gajim.connections[self.account].bookmarks: + if bookmark['jid'] == bm['jid']: + dialogs.ErrorDialog( + _('Bookmark already set'), + _('Group Chat "%s" is already in your bookmarks.') % bm['jid']) + return - gajim.connections[self.account].bookmarks.append(bm) - gajim.connections[self.account].store_bookmarks() + gajim.connections[self.account].bookmarks.append(bm) + gajim.connections[self.account].store_bookmarks() - gajim.interface.roster.set_actions_menu_needs_rebuild() + gajim.interface.roster.set_actions_menu_needs_rebuild() - dialogs.InformationDialog( - _('Bookmark has been added successfully'), - _('You can manage your bookmarks via Actions menu in your roster.')) + dialogs.InformationDialog( + _('Bookmark has been added successfully'), + _('You can manage your bookmarks via Actions menu in your roster.')) - def on_join_button_clicked(self, *args): - """ - When we want to join a conference: ask specific informations about the - selected agent and close the window - """ - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if not iter_: - return - service = model[iter_][0].decode('utf-8') - room = model[iter_][1].decode('utf-8') - if 'join_gc' not in gajim.interface.instances[self.account]: - try: - dialogs.JoinGroupchatWindow(self.account, service) - except GajimGeneralException: - pass - else: - gajim.interface.instances[self.account]['join_gc'].window.present() - self.window.destroy(chain = True) + def on_join_button_clicked(self, *args): + """ + When we want to join a conference: ask specific informations about the + selected agent and close the window + """ + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if not iter_: + return + service = model[iter_][0].decode('utf-8') + room = model[iter_][1].decode('utf-8') + if 'join_gc' not in gajim.interface.instances[self.account]: + try: + dialogs.JoinGroupchatWindow(self.account, service) + except GajimGeneralException: + pass + else: + gajim.interface.instances[self.account]['join_gc'].window.present() + self.window.destroy(chain = True) - def update_actions(self): - sens = self.window.services_treeview.get_selection().count_selected_rows() - if self.bookmark_button: - self.bookmark_button.set_sensitive(sens > 0) - if self.join_button: - self.join_button.set_sensitive(sens > 0) + def update_actions(self): + sens = self.window.services_treeview.get_selection().count_selected_rows() + if self.bookmark_button: + self.bookmark_button.set_sensitive(sens > 0) + if self.join_button: + self.join_button.set_sensitive(sens > 0) - def default_action(self): - self.on_join_button_clicked() + def default_action(self): + self.on_join_button_clicked() - def _start_info_query(self): - """ - Idle callback to start checking for visible rows - """ - self._fetch_source = None - self._query_visible() - return False + def _start_info_query(self): + """ + Idle callback to start checking for visible rows + """ + self._fetch_source = None + self._query_visible() + return False - def on_scroll(self, *args): - """ - Scrollwindow callback to trigger new queries on scolling - """ - # This apparently happens when inactive sometimes - self._query_visible() + def on_scroll(self, *args): + """ + Scrollwindow callback to trigger new queries on scolling + """ + # This apparently happens when inactive sometimes + self._query_visible() - def _query_visible(self): - """ - Query the next visible row for info - """ - if self._fetch_source: - # We're already fetching - return - view = self.window.services_treeview - if not view.flags() & gtk.REALIZED: - # Prevent a silly warning, try again in a bit. - self._fetch_source = gobject.timeout_add(100, self._start_info_query) - return - # We have to do this in a pygtk <2.8 compatible way :/ - #start, end = self.window.services_treeview.get_visible_range() - rect = view.get_visible_rect() - iter_ = end = None - # Top row - try: - sx, sy = view.tree_to_widget_coords(rect.x, rect.y) - spath = view.get_path_at_pos(sx, sy)[0] - iter_ = self.model.get_iter(spath) - except TypeError: - self._fetch_source = None - return - # Bottom row - # Iter compare is broke, use the path instead - try: - ex, ey = view.tree_to_widget_coords(rect.x + rect.height, - rect.y + rect.height) - end = view.get_path_at_pos(ex, ey)[0] - # end is the last visible, we want to query that aswell - end = (end[0] + 1,) - except TypeError: - # We're at the end of the model, we can leave end=None though. - pass - while iter_ and self.model.get_path(iter_) != end: - if not self.model.get_value(iter_, 6): - jid = self.model.get_value(iter_, 0).decode('utf-8') - node = self.model.get_value(iter_, 1).decode('utf-8') - self.cache.get_info(jid, node, self._agent_info) - self._fetch_source = True - return - iter_ = self.model.iter_next(iter_) - self._fetch_source = None + def _query_visible(self): + """ + Query the next visible row for info + """ + if self._fetch_source: + # We're already fetching + return + view = self.window.services_treeview + if not view.flags() & gtk.REALIZED: + # Prevent a silly warning, try again in a bit. + self._fetch_source = gobject.timeout_add(100, self._start_info_query) + return + # We have to do this in a pygtk <2.8 compatible way :/ + #start, end = self.window.services_treeview.get_visible_range() + rect = view.get_visible_rect() + iter_ = end = None + # Top row + try: + sx, sy = view.tree_to_widget_coords(rect.x, rect.y) + spath = view.get_path_at_pos(sx, sy)[0] + iter_ = self.model.get_iter(spath) + except TypeError: + self._fetch_source = None + return + # Bottom row + # Iter compare is broke, use the path instead + try: + ex, ey = view.tree_to_widget_coords(rect.x + rect.height, + rect.y + rect.height) + end = view.get_path_at_pos(ex, ey)[0] + # end is the last visible, we want to query that aswell + end = (end[0] + 1,) + except TypeError: + # We're at the end of the model, we can leave end=None though. + pass + while iter_ and self.model.get_path(iter_) != end: + if not self.model.get_value(iter_, 6): + jid = self.model.get_value(iter_, 0).decode('utf-8') + node = self.model.get_value(iter_, 1).decode('utf-8') + self.cache.get_info(jid, node, self._agent_info) + self._fetch_source = True + return + iter_ = self.model.iter_next(iter_) + self._fetch_source = None - def _channel_altinfo(self, jid, node, items, name = None): - """ - Callback for the alternate disco#items query. We try to atleast get the - amount of users in the room if the service does not support MUC dataforms - """ - if items == 0: - # The server returned an error - self._broken += 1 - if self._broken >= 3: - # Disable queries completely after 3 failures - if self.size_cbid: - self.window.services_scrollwin.disconnect(self.size_cbid) - self.size_cbid = None - if self.vadj_cbid: - self.vadj.disconnect(self.vadj_cbid) - self.vadj_cbid = None - self._fetch_source = None - return - else: - iter_ = self._find_item(jid, node) - if iter_: - if name: - self.model[iter_][2] = name - self.model[iter_][3] = len(items) # The number of users - self.model[iter_][4] = str(len(items)) # The number of users - self.model[iter_][6] = True - self._fetch_source = None - self._query_visible() + def _channel_altinfo(self, jid, node, items, name = None): + """ + Callback for the alternate disco#items query. We try to atleast get the + amount of users in the room if the service does not support MUC dataforms + """ + if items == 0: + # The server returned an error + self._broken += 1 + if self._broken >= 3: + # Disable queries completely after 3 failures + if self.size_cbid: + self.window.services_scrollwin.disconnect(self.size_cbid) + self.size_cbid = None + if self.vadj_cbid: + self.vadj.disconnect(self.vadj_cbid) + self.vadj_cbid = None + self._fetch_source = None + return + else: + iter_ = self._find_item(jid, node) + if iter_: + if name: + self.model[iter_][2] = name + self.model[iter_][3] = len(items) # The number of users + self.model[iter_][4] = str(len(items)) # The number of users + self.model[iter_][6] = True + self._fetch_source = None + self._query_visible() - def _add_item(self, jid, node, parent_node, item, force): - self.model.append((jid, node, item.get('name', ''), -1, '', '', False)) - if not self._fetch_source: - self._fetch_source = gobject.idle_add(self._start_info_query) + def _add_item(self, jid, node, parent_node, item, force): + self.model.append((jid, node, item.get('name', ''), -1, '', '', False)) + if not self._fetch_source: + self._fetch_source = gobject.idle_add(self._start_info_query) - def _update_info(self, iter_, jid, node, identities, features, data): - name = identities[0].get('name', '') - for form in data: - typefield = form.getField('FORM_TYPE') - if typefield and typefield.getValue() == \ - 'http://jabber.org/protocol/muc#roominfo': - # Fill model row from the form's fields - users = form.getField('muc#roominfo_occupants') - descr = form.getField('muc#roominfo_description') - if users: - self.model[iter_][3] = int(users.getValue()) - self.model[iter_][4] = users.getValue() - if descr: - self.model[iter_][5] = descr.getValue() - # Only set these when we find a form with additional info - # Some servers don't support forms and put extra info in - # the name attribute, so we preserve it in that case. - self.model[iter_][2] = name - self.model[iter_][6] = True - break - else: - # We didn't find a form, switch to alternate query mode - self.cache.get_items(jid, node, self._channel_altinfo, args = (name,)) - return - # Continue with the next - self._fetch_source = None - self._query_visible() + def _update_info(self, iter_, jid, node, identities, features, data): + name = identities[0].get('name', '') + for form in data: + typefield = form.getField('FORM_TYPE') + if typefield and typefield.getValue() == \ + 'http://jabber.org/protocol/muc#roominfo': + # Fill model row from the form's fields + users = form.getField('muc#roominfo_occupants') + descr = form.getField('muc#roominfo_description') + if users: + self.model[iter_][3] = int(users.getValue()) + self.model[iter_][4] = users.getValue() + if descr: + self.model[iter_][5] = descr.getValue() + # Only set these when we find a form with additional info + # Some servers don't support forms and put extra info in + # the name attribute, so we preserve it in that case. + self.model[iter_][2] = name + self.model[iter_][6] = True + break + else: + # We didn't find a form, switch to alternate query mode + self.cache.get_items(jid, node, self._channel_altinfo, args = (name,)) + return + # Continue with the next + self._fetch_source = None + self._query_visible() - def _update_error(self, iter_, jid, node): - # switch to alternate query mode - self.cache.get_items(jid, node, self._channel_altinfo) + def _update_error(self, iter_, jid, node): + # switch to alternate query mode + self.cache.get_items(jid, node, self._channel_altinfo) def PubSubBrowser(account, jid, node): - """ - Return an AgentBrowser subclass that will display service discovery for - particular pubsub service. Different pubsub services may need to present - different data during browsing - """ - # for now, only discussion groups are supported... - # TODO: check if it has appropriate features to be such kind of service - return DiscussionGroupsBrowser(account, jid, node) + """ + Return an AgentBrowser subclass that will display service discovery for + particular pubsub service. Different pubsub services may need to present + different data during browsing + """ + # for now, only discussion groups are supported... + # TODO: check if it has appropriate features to be such kind of service + return DiscussionGroupsBrowser(account, jid, node) class DiscussionGroupsBrowser(AgentBrowser): - """ - For browsing pubsub-based discussion groups service - """ + """ + For browsing pubsub-based discussion groups service + """ - def __init__(self, account, jid, node): - AgentBrowser.__init__(self, account, jid, node) + def __init__(self, account, jid, node): + AgentBrowser.__init__(self, account, jid, node) - # this will become set object when we get subscriptions; None means - # we don't know yet which groups are subscribed - self.subscriptions = None + # this will become set object when we get subscriptions; None means + # we don't know yet which groups are subscribed + self.subscriptions = None - # this will become our action widgets when we create them; None means - # we don't have them yet (needed for check in callback) - self.subscribe_button = None - self.unsubscribe_button = None + # this will become our action widgets when we create them; None means + # we don't have them yet (needed for check in callback) + self.subscribe_button = None + self.unsubscribe_button = None - gajim.connections[account].send_pb_subscription_query(jid, self._on_pep_subscriptions) + gajim.connections[account].send_pb_subscription_query(jid, self._on_pep_subscriptions) - def _create_treemodel(self): - """ - Create treemodel for the window - """ - # JID, node, name (with description) - pango markup, dont have info?, subscribed? - self.model = gtk.TreeStore(str, str, str, bool, bool) - # sort by name - self.model.set_sort_column_id(2, gtk.SORT_ASCENDING) - self.window.services_treeview.set_model(self.model) + def _create_treemodel(self): + """ + Create treemodel for the window + """ + # JID, node, name (with description) - pango markup, dont have info?, subscribed? + self.model = gtk.TreeStore(str, str, str, bool, bool) + # sort by name + self.model.set_sort_column_id(2, gtk.SORT_ASCENDING) + self.window.services_treeview.set_model(self.model) - # Name column - # Pango markup for name and description, description printed with - # font - renderer = gtk.CellRendererText() - col = gtk.TreeViewColumn(_('Name')) - col.pack_start(renderer) - col.set_attributes(renderer, markup=2) - col.set_resizable(True) - self.window.services_treeview.insert_column(col, -1) - self.window.services_treeview.set_headers_visible(True) + # Name column + # Pango markup for name and description, description printed with + # font + renderer = gtk.CellRendererText() + col = gtk.TreeViewColumn(_('Name')) + col.pack_start(renderer) + col.set_attributes(renderer, markup=2) + col.set_resizable(True) + self.window.services_treeview.insert_column(col, -1) + self.window.services_treeview.set_headers_visible(True) - # Subscription state - renderer = gtk.CellRendererToggle() - col = gtk.TreeViewColumn(_('Subscribed')) - col.pack_start(renderer) - col.set_attributes(renderer, inconsistent=3, active=4) - col.set_resizable(False) - self.window.services_treeview.insert_column(col, -1) + # Subscription state + renderer = gtk.CellRendererToggle() + col = gtk.TreeViewColumn(_('Subscribed')) + col.pack_start(renderer) + col.set_attributes(renderer, inconsistent=3, active=4) + col.set_resizable(False) + self.window.services_treeview.insert_column(col, -1) - # Node Column - renderer = gtk.CellRendererText() - col = gtk.TreeViewColumn(_('Node')) - col.pack_start(renderer) - col.set_attributes(renderer, markup=1) - col.set_resizable(True) - self.window.services_treeview.insert_column(col, -1) + # Node Column + renderer = gtk.CellRendererText() + col = gtk.TreeViewColumn(_('Node')) + col.pack_start(renderer) + col.set_attributes(renderer, markup=1) + col.set_resizable(True) + self.window.services_treeview.insert_column(col, -1) - def _add_items(self, jid, node, items, force): - for item in items: - jid_ = item['jid'] - node_ = item.get('node', '') - self._total_items += 1 - self._add_item(jid_, node_, node, item, force) + def _add_items(self, jid, node, items, force): + for item in items: + jid_ = item['jid'] + node_ = item.get('node', '') + self._total_items += 1 + self._add_item(jid_, node_, node, item, force) - def _in_list_foreach(self, model, path, iter_, node): - if model[path][1] == node: - self.in_list = True + def _in_list_foreach(self, model, path, iter_, node): + if model[path][1] == node: + self.in_list = True - def _in_list(self, node): - self.in_list = False - self.model.foreach(self._in_list_foreach, node) - return self.in_list + def _in_list(self, node): + self.in_list = False + self.model.foreach(self._in_list_foreach, node) + return self.in_list - def _add_item(self, jid, node, parent_node, item, force): - """ - Called when we got basic information about new node from query. Show the - item - """ - name = item.get('name', '') + def _add_item(self, jid, node, parent_node, item, force): + """ + Called when we got basic information about new node from query. Show the + item + """ + name = item.get('name', '') - if self.subscriptions is not None: - dunno = False - subscribed = node in self.subscriptions - else: - dunno = True - subscribed = False + if self.subscriptions is not None: + dunno = False + subscribed = node in self.subscriptions + else: + dunno = True + subscribed = False - name = gobject.markup_escape_text(name) - name = '%s' % name + name = gobject.markup_escape_text(name) + name = '%s' % name - parent_iter = self._get_iter(parent_node) - if not self._in_list(node): - self.model.append(parent_iter, (jid, node, name, dunno, subscribed)) - self.cache.get_items(jid, node, self._add_items, force = force, - args = (force,)) + parent_iter = self._get_iter(parent_node) + if not self._in_list(node): + self.model.append(parent_iter, (jid, node, name, dunno, subscribed)) + self.cache.get_items(jid, node, self._add_items, force = force, + args = (force,)) - def _get_child_iter(self, parent_iter, node): - child_iter = self.model.iter_children(parent_iter) - while child_iter: - if self.model[child_iter][1] == node: - return child_iter - child_iter = self.model.iter_next(child_iter) - return None + def _get_child_iter(self, parent_iter, node): + child_iter = self.model.iter_children(parent_iter) + while child_iter: + if self.model[child_iter][1] == node: + return child_iter + child_iter = self.model.iter_next(child_iter) + return None - def _get_iter(self, node): - ''' Look for an iter with the given node ''' - self.found_iter = None - def is_node(model, path, iter, node): - if model[iter][1] == node: - self.found_iter = iter - return True - self.model.foreach(is_node, node) - return self.found_iter + def _get_iter(self, node): + ''' Look for an iter with the given node ''' + self.found_iter = None + def is_node(model, path, iter, node): + if model[iter][1] == node: + self.found_iter = iter + return True + self.model.foreach(is_node, node) + return self.found_iter - def _add_actions(self): - self.post_button = gtk.Button(label=_('New post'), use_underline=True) - self.post_button.set_sensitive(False) - self.post_button.connect('clicked', self.on_post_button_clicked) - self.window.action_buttonbox.add(self.post_button) - self.post_button.show_all() + def _add_actions(self): + self.post_button = gtk.Button(label=_('New post'), use_underline=True) + self.post_button.set_sensitive(False) + self.post_button.connect('clicked', self.on_post_button_clicked) + self.window.action_buttonbox.add(self.post_button) + self.post_button.show_all() - self.subscribe_button = gtk.Button(label=_('_Subscribe'), use_underline=True) - self.subscribe_button.set_sensitive(False) - self.subscribe_button.connect('clicked', self.on_subscribe_button_clicked) - self.window.action_buttonbox.add(self.subscribe_button) - self.subscribe_button.show_all() + self.subscribe_button = gtk.Button(label=_('_Subscribe'), use_underline=True) + self.subscribe_button.set_sensitive(False) + self.subscribe_button.connect('clicked', self.on_subscribe_button_clicked) + self.window.action_buttonbox.add(self.subscribe_button) + self.subscribe_button.show_all() - self.unsubscribe_button = gtk.Button(label=_('_Unsubscribe'), use_underline=True) - self.unsubscribe_button.set_sensitive(False) - self.unsubscribe_button.connect('clicked', self.on_unsubscribe_button_clicked) - self.window.action_buttonbox.add(self.unsubscribe_button) - self.unsubscribe_button.show_all() + self.unsubscribe_button = gtk.Button(label=_('_Unsubscribe'), use_underline=True) + self.unsubscribe_button.set_sensitive(False) + self.unsubscribe_button.connect('clicked', self.on_unsubscribe_button_clicked) + self.window.action_buttonbox.add(self.unsubscribe_button) + self.unsubscribe_button.show_all() - def _clean_actions(self): - if self.post_button is not None: - self.post_button.destroy() - self.post_button = None + def _clean_actions(self): + if self.post_button is not None: + self.post_button.destroy() + self.post_button = None - if self.subscribe_button is not None: - self.subscribe_button.destroy() - self.subscribe_button = None + if self.subscribe_button is not None: + self.subscribe_button.destroy() + self.subscribe_button = None - if self.unsubscribe_button is not None: - self.unsubscribe_button.destroy() - self.unsubscribe_button = None + if self.unsubscribe_button is not None: + self.unsubscribe_button.destroy() + self.unsubscribe_button = None - def update_actions(self): - """ - Called when user selected a row. Make subscribe/unsubscribe buttons - sensitive appropriatelly - """ - # we have nothing to do if we don't have buttons... - if self.subscribe_button is None: return + def update_actions(self): + """ + Called when user selected a row. Make subscribe/unsubscribe buttons + sensitive appropriatelly + """ + # we have nothing to do if we don't have buttons... + if self.subscribe_button is None: return - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if not iter_ or self.subscriptions is None: - # no item selected or no subscriptions info, all buttons are insensitive - self.post_button.set_sensitive(False) - self.subscribe_button.set_sensitive(False) - self.unsubscribe_button.set_sensitive(False) - else: - subscribed = model.get_value(iter_, 4) # 4 = subscribed? - self.post_button.set_sensitive(subscribed) - self.subscribe_button.set_sensitive(not subscribed) - self.unsubscribe_button.set_sensitive(subscribed) + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if not iter_ or self.subscriptions is None: + # no item selected or no subscriptions info, all buttons are insensitive + self.post_button.set_sensitive(False) + self.subscribe_button.set_sensitive(False) + self.unsubscribe_button.set_sensitive(False) + else: + subscribed = model.get_value(iter_, 4) # 4 = subscribed? + self.post_button.set_sensitive(subscribed) + self.subscribe_button.set_sensitive(not subscribed) + self.unsubscribe_button.set_sensitive(subscribed) - def on_post_button_clicked(self, widget): - """ - Called when 'post' button is pressed. Open window to create post - """ - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if iter_ is None: return + def on_post_button_clicked(self, widget): + """ + Called when 'post' button is pressed. Open window to create post + """ + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if iter_ is None: return - groupnode = model.get_value(iter_, 1) # 1 = groupnode + groupnode = model.get_value(iter_, 1) # 1 = groupnode - groups.GroupsPostWindow(self.account, self.jid, groupnode) + groups.GroupsPostWindow(self.account, self.jid, groupnode) - def on_subscribe_button_clicked(self, widget): - """ - Called when 'subscribe' button is pressed. Send subscribtion request - """ - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if iter_ is None: return + def on_subscribe_button_clicked(self, widget): + """ + Called when 'subscribe' button is pressed. Send subscribtion request + """ + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if iter_ is None: return - groupnode = model.get_value(iter_, 1) # 1 = groupnode + groupnode = model.get_value(iter_, 1) # 1 = groupnode - gajim.connections[self.account].send_pb_subscribe(self.jid, groupnode, self._on_pep_subscribe, groupnode) + gajim.connections[self.account].send_pb_subscribe(self.jid, groupnode, self._on_pep_subscribe, groupnode) - def on_unsubscribe_button_clicked(self, widget): - """ - Called when 'unsubscribe' button is pressed. Send unsubscription request - """ - model, iter_ = self.window.services_treeview.get_selection().get_selected() - if iter_ is None: return + def on_unsubscribe_button_clicked(self, widget): + """ + Called when 'unsubscribe' button is pressed. Send unsubscription request + """ + model, iter_ = self.window.services_treeview.get_selection().get_selected() + if iter_ is None: return - groupnode = model.get_value(iter_, 1) # 1 = groupnode + groupnode = model.get_value(iter_, 1) # 1 = groupnode - gajim.connections[self.account].send_pb_unsubscribe(self.jid, groupnode, self._on_pep_unsubscribe, groupnode) + gajim.connections[self.account].send_pb_unsubscribe(self.jid, groupnode, self._on_pep_unsubscribe, groupnode) - def _on_pep_subscriptions(self, conn, request): - """ - We got the subscribed groups list stanza. Now, if we already have items - on the list, we should actualize them - """ - try: - subscriptions = request.getTag('pubsub').getTag('subscriptions') - except Exception: - return + def _on_pep_subscriptions(self, conn, request): + """ + We got the subscribed groups list stanza. Now, if we already have items + on the list, we should actualize them + """ + try: + subscriptions = request.getTag('pubsub').getTag('subscriptions') + except Exception: + return - groups = set() - for child in subscriptions.getTags('subscription'): - groups.add(child['node']) + groups = set() + for child in subscriptions.getTags('subscription'): + groups.add(child['node']) - self.subscriptions = groups + self.subscriptions = groups - # try to setup existing items in model - model = self.window.services_treeview.get_model() - for row in model: - # 1 = group node - # 3 = insensitive checkbox for subscribed - # 4 = subscribed? - groupnode = row[1] - row[3] = False - row[4] = groupnode in groups + # try to setup existing items in model + model = self.window.services_treeview.get_model() + for row in model: + # 1 = group node + # 3 = insensitive checkbox for subscribed + # 4 = subscribed? + groupnode = row[1] + row[3] = False + row[4] = groupnode in groups - # we now know subscriptions, update button states - self.update_actions() + # we now know subscriptions, update button states + self.update_actions() - raise xmpp.NodeProcessed + raise xmpp.NodeProcessed - def _on_pep_subscribe(self, conn, request, groupnode): - """ - We have just subscribed to a node. Update UI - """ - self.subscriptions.add(groupnode) + def _on_pep_subscribe(self, conn, request, groupnode): + """ + We have just subscribed to a node. Update UI + """ + self.subscriptions.add(groupnode) - model = self.window.services_treeview.get_model() - for row in model: - if row[1] == groupnode: # 1 = groupnode - row[4] = True - break + model = self.window.services_treeview.get_model() + for row in model: + if row[1] == groupnode: # 1 = groupnode + row[4] = True + break - self.update_actions() + self.update_actions() - raise xmpp.NodeProcessed + raise xmpp.NodeProcessed - def _on_pep_unsubscribe(self, conn, request, groupnode): - """ - We have just unsubscribed from a node. Update UI - """ - self.subscriptions.remove(groupnode) + def _on_pep_unsubscribe(self, conn, request, groupnode): + """ + We have just unsubscribed from a node. Update UI + """ + self.subscriptions.remove(groupnode) - model = self.window.services_treeview.get_model() - for row in model: - if row[1] == groupnode: # 1 = groupnode - row[4]=False - break + model = self.window.services_treeview.get_model() + for row in model: + if row[1] == groupnode: # 1 = groupnode + row[4]=False + break - self.update_actions() + self.update_actions() - raise xmpp.NodeProcessed + raise xmpp.NodeProcessed # Fill the global agent type info dictionary _agent_type_info = _gen_agent_type_info() - -# vim: se ts=3: diff --git a/src/features_window.py b/src/features_window.py index b737d4e37..e15c9b21d 100644 --- a/src/features_window.py +++ b/src/features_window.py @@ -33,226 +33,224 @@ from common import helpers from common import kwalletbinding class FeaturesWindow: - """ - Class for features window - """ + """ + Class for features window + """ - def __init__(self): - self.xml = gtkgui_helpers.get_gtk_builder('features_window.ui') - self.window = self.xml.get_object('features_window') - treeview = self.xml.get_object('features_treeview') - self.desc_label = self.xml.get_object('feature_desc_label') + def __init__(self): + self.xml = gtkgui_helpers.get_gtk_builder('features_window.ui') + self.window = self.xml.get_object('features_window') + treeview = self.xml.get_object('features_treeview') + self.desc_label = self.xml.get_object('feature_desc_label') - # {name: (available_function, unix_text, windows_text)} - self.features = { - _('SSL certificat validation'): (self.pyopenssl_available, - _('A library used to validate server certificates to ensure a secure connection.'), - _('Requires python-pyopenssl.'), - _('Requires python-pyopenssl.')), - _('Bonjour / Zeroconf'): (self.zeroconf_available, - _('Serverless chatting with autodetected clients in a local network.'), - _('Requires python-avahi.'), - _('Requires pybonjour (http://o2s.csail.mit.edu/o2s-wiki/pybonjour).')), - _('Command line'): (self.dbus_available, - _('A script to control Gajim via commandline.'), - _('Requires python-dbus.'), - _('Feature not available under Windows.')), - _('OpenGPG message encryption'): (self.gpg_available, - _('Encrypting chat messages with gpg keys.'), - _('Requires gpg and python-GnuPGInterface.'), - _('Feature not available under Windows.')), - _('Network-manager'): (self.network_manager_available, - _('Autodetection of network status.'), - _('Requires gnome-network-manager and python-dbus.'), - _('Feature not available under Windows.')), - _('Session Management'): (self.session_management_available, - _('Gajim session is stored on logout and restored on login.'), - _('Requires python-gnome2.'), - _('Feature not available under Windows.')), - _('Password encryption'): (self.some_keyring_available, - _('Passwords can be stored securely and not just in plaintext.'), - _('Requires gnome-keyring and python-gnome2-desktop, or kwalletcli.'), - _('Feature not available under Windows.')), - _('SRV'): (self.srv_available, - _('Ability to connect to servers which are using SRV records.'), - _('Requires dnsutils.'), - _('Requires nslookup to use SRV records.')), - _('Spell Checker'): (self.speller_available, - _('Spellchecking of composed messages.'), - _('Requires libgtkspell.'), - _('Feature not available under Windows.')), - _('Notification'): (self.notification_available, - _('Passive popups notifying for new events.'), - _('Requires python-notify or instead python-dbus in conjunction with notification-daemon.'), - _('Feature not available under Windows.')), - _('Automatic status'): (self.idle_available, - _('Ability to measure idle time, in order to set auto status.'), - _('Requires libxss library.'), - _('Requires python2.5.')), - _('LaTeX'): (self.latex_available, - _('Transform LaTeX expressions between $$ $$.'), - _('Requires texlive-latex-base and dvipng. You have to set \'use_latex\' to True in the Advanced Configuration Editor.'), - _('Requires texlive-latex-base and dvipng (All is in MikTeX). You have to set \'use_latex\' to True in the Advanced Configuration Editor.')), - _('End to End message encryption'): (self.pycrypto_available, - _('Encrypting chat messages.'), - _('Requires python-crypto.'), - _('Requires python-crypto.')), - _('RST Generator'): (self.docutils_available, - _('Generate XHTML output from RST code (see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html).'), - _('Requires python-docutils.'), - _('Requires python-docutils.')), - _('Audio / Video'): (self.farsight_available, - _('Ability to start audio and video chat.'), - _('Requires python-farsight.'), - _('Feature not available under Windows.')), - } + # {name: (available_function, unix_text, windows_text)} + self.features = { + _('SSL certificat validation'): (self.pyopenssl_available, + _('A library used to validate server certificates to ensure a secure connection.'), + _('Requires python-pyopenssl.'), + _('Requires python-pyopenssl.')), + _('Bonjour / Zeroconf'): (self.zeroconf_available, + _('Serverless chatting with autodetected clients in a local network.'), + _('Requires python-avahi.'), + _('Requires pybonjour (http://o2s.csail.mit.edu/o2s-wiki/pybonjour).')), + _('Command line'): (self.dbus_available, + _('A script to control Gajim via commandline.'), + _('Requires python-dbus.'), + _('Feature not available under Windows.')), + _('OpenGPG message encryption'): (self.gpg_available, + _('Encrypting chat messages with gpg keys.'), + _('Requires gpg and python-GnuPGInterface.'), + _('Feature not available under Windows.')), + _('Network-manager'): (self.network_manager_available, + _('Autodetection of network status.'), + _('Requires gnome-network-manager and python-dbus.'), + _('Feature not available under Windows.')), + _('Session Management'): (self.session_management_available, + _('Gajim session is stored on logout and restored on login.'), + _('Requires python-gnome2.'), + _('Feature not available under Windows.')), + _('Password encryption'): (self.some_keyring_available, + _('Passwords can be stored securely and not just in plaintext.'), + _('Requires gnome-keyring and python-gnome2-desktop, or kwalletcli.'), + _('Feature not available under Windows.')), + _('SRV'): (self.srv_available, + _('Ability to connect to servers which are using SRV records.'), + _('Requires dnsutils.'), + _('Requires nslookup to use SRV records.')), + _('Spell Checker'): (self.speller_available, + _('Spellchecking of composed messages.'), + _('Requires libgtkspell.'), + _('Feature not available under Windows.')), + _('Notification'): (self.notification_available, + _('Passive popups notifying for new events.'), + _('Requires python-notify or instead python-dbus in conjunction with notification-daemon.'), + _('Feature not available under Windows.')), + _('Automatic status'): (self.idle_available, + _('Ability to measure idle time, in order to set auto status.'), + _('Requires libxss library.'), + _('Requires python2.5.')), + _('LaTeX'): (self.latex_available, + _('Transform LaTeX expressions between $$ $$.'), + _('Requires texlive-latex-base and dvipng. You have to set \'use_latex\' to True in the Advanced Configuration Editor.'), + _('Requires texlive-latex-base and dvipng (All is in MikTeX). You have to set \'use_latex\' to True in the Advanced Configuration Editor.')), + _('End to End message encryption'): (self.pycrypto_available, + _('Encrypting chat messages.'), + _('Requires python-crypto.'), + _('Requires python-crypto.')), + _('RST Generator'): (self.docutils_available, + _('Generate XHTML output from RST code (see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html).'), + _('Requires python-docutils.'), + _('Requires python-docutils.')), + _('Audio / Video'): (self.farsight_available, + _('Ability to start audio and video chat.'), + _('Requires python-farsight.'), + _('Feature not available under Windows.')), + } - # name, supported - self.model = gtk.ListStore(str, bool) - treeview.set_model(self.model) + # name, supported + self.model = gtk.ListStore(str, bool) + treeview.set_model(self.model) - col = gtk.TreeViewColumn(_('Available')) - treeview.append_column(col) - cell = gtk.CellRendererToggle() - cell.set_property('radio', True) - col.pack_start(cell) - col.set_attributes(cell, active = 1) + col = gtk.TreeViewColumn(_('Available')) + treeview.append_column(col) + cell = gtk.CellRendererToggle() + cell.set_property('radio', True) + col.pack_start(cell) + col.set_attributes(cell, active = 1) - col = gtk.TreeViewColumn(_('Feature')) - treeview.append_column(col) - cell = gtk.CellRendererText() - col.pack_start(cell, expand = True) - col.add_attribute(cell, 'text', 0) + col = gtk.TreeViewColumn(_('Feature')) + treeview.append_column(col) + cell = gtk.CellRendererText() + col.pack_start(cell, expand = True) + col.add_attribute(cell, 'text', 0) - # Fill model - for feature in self.features: - func = self.features[feature][0] - rep = func() - self.model.append([feature, rep]) + # Fill model + for feature in self.features: + func = self.features[feature][0] + rep = func() + self.model.append([feature, rep]) - self.model.set_sort_column_id(0, gtk.SORT_ASCENDING) + self.model.set_sort_column_id(0, gtk.SORT_ASCENDING) - self.xml.connect_signals(self) - self.window.show_all() - self.xml.get_object('close_button').grab_focus() + self.xml.connect_signals(self) + self.window.show_all() + self.xml.get_object('close_button').grab_focus() - def on_close_button_clicked(self, widget): - self.window.destroy() + def on_close_button_clicked(self, widget): + self.window.destroy() - def on_features_treeview_cursor_changed(self, widget): - selection = widget.get_selection() - if not selection: - return - rows = selection.get_selected_rows()[1] - if not rows: - return - path = rows[0] - feature = self.model[path][0].decode('utf-8') - text = self.features[feature][1] + '\n' - if os.name == 'nt': - text = text + self.features[feature][3] - else: - text = text + self.features[feature][2] - self.desc_label.set_text(text) + def on_features_treeview_cursor_changed(self, widget): + selection = widget.get_selection() + if not selection: + return + rows = selection.get_selected_rows()[1] + if not rows: + return + path = rows[0] + feature = self.model[path][0].decode('utf-8') + text = self.features[feature][1] + '\n' + if os.name == 'nt': + text = text + self.features[feature][3] + else: + text = text + self.features[feature][2] + self.desc_label.set_text(text) - def pyopenssl_available(self): - try: - import OpenSSL.SSL - import OpenSSL.crypto - except Exception: - return False - return True + def pyopenssl_available(self): + try: + import OpenSSL.SSL + import OpenSSL.crypto + except Exception: + return False + return True - def zeroconf_available(self): - try: - import avahi - except Exception: - try: - import pybonjour - except Exception: - return False - return True + def zeroconf_available(self): + try: + import avahi + except Exception: + try: + import pybonjour + except Exception: + return False + return True - def dbus_available(self): - if os.name == 'nt': - return False - from common import dbus_support - return dbus_support.supported + def dbus_available(self): + if os.name == 'nt': + return False + from common import dbus_support + return dbus_support.supported - def gpg_available(self): - if os.name == 'nt': - return False - return gajim.HAVE_GPG + def gpg_available(self): + if os.name == 'nt': + return False + return gajim.HAVE_GPG - def network_manager_available(self): - if os.name == 'nt': - return False - import network_manager_listener - return network_manager_listener.supported + def network_manager_available(self): + if os.name == 'nt': + return False + import network_manager_listener + return network_manager_listener.supported - def session_management_available(self): - if os.name == 'nt': - return False - try: - import gnome.ui - except Exception: - return False - return True + def session_management_available(self): + if os.name == 'nt': + return False + try: + import gnome.ui + except Exception: + return False + return True - def some_keyring_available(self): - if os.name == 'nt': - return False - if kwalletbinding.kwallet_available(): - return True - try: - import gnomekeyring - except Exception: - return False - return True + def some_keyring_available(self): + if os.name == 'nt': + return False + if kwalletbinding.kwallet_available(): + return True + try: + import gnomekeyring + except Exception: + return False + return True - def srv_available(self): - return helpers.is_in_path('nslookup') + def srv_available(self): + return helpers.is_in_path('nslookup') - def speller_available(self): - if os.name == 'nt': - return False - try: - import gtkspell - except ImportError: - return False - return True + def speller_available(self): + if os.name == 'nt': + return False + try: + import gtkspell + except ImportError: + return False + return True - def notification_available(self): - if os.name == 'nt': - return False - from common import dbus_support - if self.dbus_available() and dbus_support.get_notifications_interface(): - return True - try: - import pynotify - except Exception: - return False - return True + def notification_available(self): + if os.name == 'nt': + return False + from common import dbus_support + if self.dbus_available() and dbus_support.get_notifications_interface(): + return True + try: + import pynotify + except Exception: + return False + return True - def idle_available(self): - from common import sleepy - return sleepy.SUPPORTED + def idle_available(self): + from common import sleepy + return sleepy.SUPPORTED - def latex_available(self): - from common import latex - return latex.check_for_latex_support() + def latex_available(self): + from common import latex + return latex.check_for_latex_support() - def pycrypto_available(self): - return gajim.HAVE_PYCRYPTO + def pycrypto_available(self): + return gajim.HAVE_PYCRYPTO - def docutils_available(self): - try: - import docutils - except Exception: - return False - return True + def docutils_available(self): + try: + import docutils + except Exception: + return False + return True - def farsight_available(self): - return gajim.HAVE_FARSIGHT - -# vim: se ts=3: + def farsight_available(self): + return gajim.HAVE_FARSIGHT diff --git a/src/filetransfers_window.py b/src/filetransfers_window.py index b1ec59f1d..621386eb7 100644 --- a/src/filetransfers_window.py +++ b/src/filetransfers_window.py @@ -34,7 +34,7 @@ import dialogs from common import gajim from common import helpers from common.protocol.bytestream import (is_transfer_active, is_transfer_paused, - is_transfer_stopped) + is_transfer_stopped) C_IMAGE = 0 C_LABELS = 1 @@ -46,918 +46,916 @@ C_SID = 6 class FileTransfersWindow: - def __init__(self): - self.files_props = {'r' : {}, 's': {}} - self.height_diff = 0 - self.xml = gtkgui_helpers.get_gtk_builder('filetransfers.ui') - self.window = self.xml.get_object('file_transfers_window') - self.tree = self.xml.get_object('transfers_list') - self.cancel_button = self.xml.get_object('cancel_button') - self.pause_button = self.xml.get_object('pause_restore_button') - self.cleanup_button = self.xml.get_object('cleanup_button') - self.notify_ft_checkbox = self.xml.get_object( - 'notify_ft_complete_checkbox') - - shall_notify = gajim.config.get('notify_on_file_complete') - self.notify_ft_checkbox.set_active(shall_notify - ) - self.model = gtk.ListStore(gtk.gdk.Pixbuf, str, str, str, str, int, str) - self.tree.set_model(self.model) - col = gtk.TreeViewColumn() - - render_pixbuf = gtk.CellRendererPixbuf() - - col.pack_start(render_pixbuf, expand=True) - render_pixbuf.set_property('xpad', 3) - render_pixbuf.set_property('ypad', 3) - render_pixbuf.set_property('yalign', .0) - col.add_attribute(render_pixbuf, 'pixbuf', 0) - self.tree.append_column(col) - - col = gtk.TreeViewColumn(_('File')) - renderer = gtk.CellRendererText() - col.pack_start(renderer, expand=False) - col.add_attribute(renderer, 'markup' , C_LABELS) - renderer.set_property('yalign', 0.) - renderer = gtk.CellRendererText() - col.pack_start(renderer, expand=True) - col.add_attribute(renderer, 'markup' , C_FILE) - renderer.set_property('xalign', 0.) - renderer.set_property('yalign', 0.) - renderer.set_property('ellipsize', pango.ELLIPSIZE_END) - col.set_resizable(True) - col.set_expand(True) - self.tree.append_column(col) - - col = gtk.TreeViewColumn(_('Time')) - renderer = gtk.CellRendererText() - col.pack_start(renderer, expand=False) - col.add_attribute(renderer, 'markup' , C_TIME) - renderer.set_property('yalign', 0.5) - renderer.set_property('xalign', 0.5) - renderer = gtk.CellRendererText() - renderer.set_property('ellipsize', pango.ELLIPSIZE_END) - col.set_resizable(True) - col.set_expand(False) - self.tree.append_column(col) - - col = gtk.TreeViewColumn(_('Progress')) - renderer = gtk.CellRendererProgress() - renderer.set_property('yalign', 0.5) - renderer.set_property('xalign', 0.5) - col.pack_start(renderer, expand=False) - col.add_attribute(renderer, 'text' , C_PROGRESS) - col.add_attribute(renderer, 'value' , C_PERCENT) - col.set_resizable(True) - col.set_expand(False) - self.tree.append_column(col) - - self.images = {} - self.icons = { - 'upload': gtk.STOCK_GO_UP, - 'download': gtk.STOCK_GO_DOWN, - 'stop': gtk.STOCK_STOP, - 'waiting': gtk.STOCK_REFRESH, - 'pause': gtk.STOCK_MEDIA_PAUSE, - 'continue': gtk.STOCK_MEDIA_PLAY, - 'ok': gtk.STOCK_APPLY, - } - - self.tree.get_selection().set_mode(gtk.SELECTION_SINGLE) - self.tree.get_selection().connect('changed', self.selection_changed) - self.tooltip = tooltips.FileTransfersTooltip() - self.file_transfers_menu = self.xml.get_object('file_transfers_menu') - self.open_folder_menuitem = self.xml.get_object('open_folder_menuitem') - self.cancel_menuitem = self.xml.get_object('cancel_menuitem') - self.pause_menuitem = self.xml.get_object('pause_menuitem') - self.continue_menuitem = self.xml.get_object('continue_menuitem') - self.remove_menuitem = self.xml.get_object('remove_menuitem') - self.xml.connect_signals(self) - - def find_transfer_by_jid(self, account, jid): - """ - Find all transfers with peer 'jid' that belong to 'account' - """ - active_transfers = [[], []] # ['senders', 'receivers'] - - # 'account' is the sender - for file_props in self.files_props['s'].values(): - if file_props['tt_account'] == account: - receiver_jid = unicode(file_props['receiver']).split('/')[0] - if jid == receiver_jid: - if not is_transfer_stopped(file_props): - active_transfers[0].append(file_props) - - # 'account' is the recipient - for file_props in self.files_props['r'].values(): - if file_props['tt_account'] == account: - sender_jid = unicode(file_props['sender']).split('/')[0] - if jid == sender_jid: - if not is_transfer_stopped(file_props): - active_transfers[1].append(file_props) - return active_transfers - - def show_completed(self, jid, file_props): - """ - Show a dialog saying that file (file_props) has been transferred - """ - def on_open(widget, file_props): - dialog.destroy() - if 'file-name' not in file_props: - return - path = os.path.split(file_props['file-name'])[0] - if os.path.exists(path) and os.path.isdir(path): - helpers.launch_file_manager(path) - self.tree.get_selection().unselect_all() - - if file_props['type'] == 'r': - # file path is used below in 'Save in' - (file_path, file_name) = os.path.split(file_props['file-name']) - else: - file_name = file_props['name'] - sectext = '\t' + _('Filename: %s') % file_name - sectext += '\n\t' + _('Size: %s') % \ - helpers.convert_bytes(file_props['size']) - if file_props['type'] == 'r': - jid = unicode(file_props['sender']).split('/')[0] - sender_name = gajim.contacts.get_first_contact_from_jid( - file_props['tt_account'], jid).get_shown_name() - sender = sender_name - else: - #You is a reply of who sent a file - sender = _('You') - sectext += '\n\t' + _('Sender: %s') % sender - sectext += '\n\t' + _('Recipient: ') - if file_props['type'] == 's': - jid = unicode(file_props['receiver']).split('/')[0] - receiver_name = gajim.contacts.get_first_contact_from_jid( - file_props['tt_account'], jid).get_shown_name() - recipient = receiver_name - else: - #You is a reply of who received a file - recipient = _('You') - sectext += recipient - if file_props['type'] == 'r': - sectext += '\n\t' + _('Saved in: %s') % file_path - dialog = dialogs.HigDialog(None, gtk.MESSAGE_INFO, gtk.BUTTONS_NONE, - _('File transfer completed'), sectext) - if file_props['type'] == 'r': - button = gtk.Button(_('_Open Containing Folder')) - button.connect('clicked', on_open, file_props) - dialog.action_area.pack_start(button) - ok_button = dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) - def on_ok(widget): - dialog.destroy() - ok_button.connect('clicked', on_ok) - dialog.show_all() - - def show_request_error(self, file_props): - """ - Show error dialog to the recipient saying that transfer has been canceled - """ - dialogs.InformationDialog(_('File transfer cancelled'), _('Connection with peer cannot be established.')) - self.tree.get_selection().unselect_all() - - def show_send_error(self, file_props): - """ - Show error dialog to the sender saying that transfer has been canceled - """ - dialogs.InformationDialog(_('File transfer cancelled'), - _('Connection with peer cannot be established.')) - self.tree.get_selection().unselect_all() - - def show_stopped(self, jid, file_props, error_msg=''): - if file_props['type'] == 'r': - file_name = os.path.basename(file_props['file-name']) - else: - file_name = file_props['name'] - sectext = '\t' + _('Filename: %s') % file_name - sectext += '\n\t' + _('Recipient: %s') % jid - if error_msg: - sectext += '\n\t' + _('Error message: %s') % error_msg - dialogs.ErrorDialog(_('File transfer stopped'), sectext) - self.tree.get_selection().unselect_all() - - def show_file_send_request(self, account, contact): - desc_entry = gtk.Entry() - - def on_ok(widget): - file_dir = None - files_path_list = dialog.get_filenames() - files_path_list = gtkgui_helpers.decode_filechooser_file_paths( - files_path_list) - desc = desc_entry.get_text() - for file_path in files_path_list: - if self.send_file(account, contact, file_path, desc) and file_dir is None: - file_dir = os.path.dirname(file_path) - if file_dir: - gajim.config.set('last_send_dir', file_dir) - dialog.destroy() - - dialog = dialogs.FileChooserDialog(_('Choose File to Send...'), - gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), - gtk.RESPONSE_OK, - True, # select multiple true as we can select many files to send - gajim.config.get('last_send_dir'), - on_response_ok=on_ok, - on_response_cancel=lambda e:dialog.destroy() - ) - - btn = gtk.Button(_('_Send')) - btn.set_property('can-default', True) - # FIXME: add send icon to this button (JUMP_TO) - dialog.add_action_widget(btn, gtk.RESPONSE_OK) - dialog.set_default_response(gtk.RESPONSE_OK) - - desc_hbox = gtk.HBox(False, 5) - desc_hbox.pack_start(gtk.Label(_('Description: ')), False, False, 0) - desc_hbox.pack_start(desc_entry, True, True, 0) - - dialog.vbox.pack_start(desc_hbox, False, False, 0) - - btn.show() - desc_hbox.show_all() - - def send_file(self, account, contact, file_path, file_desc=''): - """ - Start the real transfer(upload) of the file - """ - if gtkgui_helpers.file_is_locked(file_path): - pritext = _('Gajim cannot access this file') - sextext = _('This file is being used by another process.') - dialogs.ErrorDialog(pritext, sextext) - return - - if isinstance(contact, str): - if contact.find('/') == -1: - return - (jid, resource) = contact.split('/', 1) - contact = gajim.contacts.create_contact(jid=jid, account=account, resource=resource) - file_name = os.path.split(file_path)[1] - file_props = self.get_send_file_props(account, contact, - file_path, file_name, file_desc) - if file_props is None: - return False - self.add_transfer(account, contact, file_props) - gajim.connections[account].send_file_request(file_props) - return True - - def _start_receive(self, file_path, account, contact, file_props): - file_dir = os.path.dirname(file_path) - if file_dir: - gajim.config.set('last_save_dir', file_dir) - file_props['file-name'] = file_path - self.add_transfer(account, contact, file_props) - gajim.connections[account].send_file_approval(file_props) - - def show_file_request(self, account, contact, file_props): - """ - Show dialog asking for comfirmation and store location of new file - requested by a contact - """ - if file_props is None or 'name' not in file_props: - return - sec_text = '\t' + _('File: %s') % gobject.markup_escape_text( - file_props['name']) - if 'size' in file_props: - sec_text += '\n\t' + _('Size: %s') % \ - helpers.convert_bytes(file_props['size']) - if 'mime-type' in file_props: - sec_text += '\n\t' + _('Type: %s') % file_props['mime-type'] - if 'desc' in file_props: - sec_text += '\n\t' + _('Description: %s') % file_props['desc'] - prim_text = _('%s wants to send you a file:') % contact.jid - dialog = None - - def on_response_ok(account, contact, file_props): - - def on_ok(widget, account, contact, file_props): - file_path = dialog2.get_filename() - file_path = gtkgui_helpers.decode_filechooser_file_paths( - (file_path,))[0] - if os.path.exists(file_path): - # check if we have write permissions - if not os.access(file_path, os.W_OK): - file_name = os.path.basename(file_path) - dialogs.ErrorDialog(_('Cannot overwrite existing file "%s"' % file_name), - _('A file with this name already exists and you do not have permission to overwrite it.')) - return - stat = os.stat(file_path) - dl_size = stat.st_size - file_size = file_props['size'] - dl_finished = dl_size >= file_size - - def on_response(response): - if response < 0: - return - elif response == 100: - file_props['offset'] = dl_size - dialog2.destroy() - self._start_receive(file_path, account, contact, file_props) - - dialog = dialogs.FTOverwriteConfirmationDialog( - _('This file already exists'), _('What do you want to do?'), - propose_resume=not dl_finished, on_response=on_response) - dialog.set_transient_for(dialog2) - dialog.set_destroy_with_parent(True) - return - else: - dirname = os.path.dirname(file_path) - if not os.access(dirname, os.W_OK) and os.name != 'nt': - # read-only bit is used to mark special folder under windows, - # not to mark that a folder is read-only. See ticket #3587 - dialogs.ErrorDialog(_('Directory "%s" is not writable') % dirname, _('You do not have permission to create files in this directory.')) - return - dialog2.destroy() - self._start_receive(file_path, account, contact, file_props) - - def on_cancel(widget, account, contact, file_props): - dialog2.destroy() - gajim.connections[account].send_file_rejection(file_props) - - dialog2 = dialogs.FileChooserDialog( - title_text=_('Save File as...'), - action=gtk.FILE_CHOOSER_ACTION_SAVE, - buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_SAVE, gtk.RESPONSE_OK), - default_response=gtk.RESPONSE_OK, - current_folder=gajim.config.get('last_save_dir'), - on_response_ok=(on_ok, account, contact, file_props), - on_response_cancel=(on_cancel, account, contact, file_props)) - - dialog2.set_current_name(file_props['name']) - dialog2.connect('delete-event', lambda widget, event: - on_cancel(widget, account, contact, file_props)) - - def on_response_cancel(account, file_props): - gajim.connections[account].send_file_rejection(file_props) - - dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text, - on_response_ok=(on_response_ok, account, contact, file_props), - on_response_cancel=(on_response_cancel, account, file_props)) - dialog.connect('delete-event', lambda widget, event: - on_response_cancel(widget, account, file_props)) - dialog.popup() - - def get_icon(self, ident): - return self.images.setdefault(ident, - self.window.render_icon(self.icons[ident], gtk.ICON_SIZE_MENU)) - - def set_status(self, typ, sid, status): - """ - Change the status of a transfer to state 'status' - """ - iter_ = self.get_iter_by_sid(typ, sid) - if iter_ is None: - return - sid = self.model[iter_][C_SID].decode('utf-8') - file_props = self.files_props[sid[0]][sid[1:]] - if status == 'stop': - file_props['stopped'] = True - elif status == 'ok': - file_props['completed'] = True - self.model.set(iter_, C_IMAGE, self.get_icon(status)) - path = self.model.get_path(iter_) - self.select_func(path) - - def _format_percent(self, percent): - """ - Add extra spaces from both sides of the percent, so that progress string - has always a fixed size - """ - _str = ' ' - if percent != 100.: - _str += ' ' - if percent < 10: - _str += ' ' - _str += unicode(percent) + '% \n' - return _str - - def _format_time(self, _time): - times = { 'hours': 0, 'minutes': 0, 'seconds': 0 } - _time = int(_time) - times['seconds'] = _time % 60 - if _time >= 60: - _time /= 60 - times['minutes'] = _time % 60 - if _time >= 60: - times['hours'] = _time / 60 - - #Print remaining time in format 00:00:00 - #You can change the places of (hours), (minutes), (seconds) - - #they are not translatable. - return _('%(hours)02.d:%(minutes)02.d:%(seconds)02.d') % times - - def _get_eta_and_speed(self, full_size, transfered_size, file_props): - if len(file_props['transfered_size']) == 0: - return 0., 0. - elif len(file_props['transfered_size']) == 1: - speed = round(float(transfered_size) / file_props['elapsed-time']) - else: - # first and last are (time, transfered_size) - first = file_props['transfered_size'][0] - last = file_props['transfered_size'][-1] - transfered = last[1] - first[1] - tim = last[0] - first[0] - if tim == 0: - return 0., 0. - speed = round(float(transfered) / tim) - if speed == 0.: - return 0., 0. - remaining_size = full_size - transfered_size - eta = remaining_size / speed - return eta, speed - - def _remove_transfer(self, iter_, sid, file_props): - self.model.remove(iter_) - if 'tt_account' in file_props: - # file transfer is set - account = file_props['tt_account'] - if account in gajim.connections: - # there is a connection to the account - gajim.connections[account].remove_transfer(file_props) - if file_props['type'] == 'r': # we receive a file - other = file_props['sender'] - else: # we send a file - other = file_props['receiver'] - if isinstance(other, unicode): - jid = gajim.get_jid_without_resource(other) - else: # It's a Contact instance - jid = other.jid - for ev_type in ('file-error', 'file-completed', 'file-request-error', - 'file-send-error', 'file-stopped'): - for event in gajim.events.get_events(account, jid, [ev_type]): - if event.parameters['sid'] == file_props['sid']: - gajim.events.remove_events(account, jid, event) - gajim.interface.roster.draw_contact(jid, account) - gajim.interface.roster.show_title() - del(self.files_props[sid[0]][sid[1:]]) - del(file_props) - - def set_progress(self, typ, sid, transfered_size, iter_=None): - """ - Change the progress of a transfer with new transfered size - """ - if sid not in self.files_props[typ]: - return - file_props = self.files_props[typ][sid] - full_size = int(file_props['size']) - if full_size == 0: - percent = 0 - else: - percent = round(float(transfered_size) / full_size * 100, 1) - if iter_ is None: - iter_ = self.get_iter_by_sid(typ, sid) - if iter_ is not None: - just_began = False - if self.model[iter_][C_PERCENT] == 0 and int(percent > 0): - just_began = True - text = self._format_percent(percent) - if transfered_size == 0: - text += '0' - else: - text += helpers.convert_bytes(transfered_size) - text += '/' + helpers.convert_bytes(full_size) - # Kb/s - - # remaining time - if 'offset' in file_props and file_props['offset']: - transfered_size -= file_props['offset'] - full_size -= file_props['offset'] - - if file_props['elapsed-time'] > 0: - file_props['transfered_size'].append((file_props['last-time'], transfered_size)) - if len(file_props['transfered_size']) > 6: - file_props['transfered_size'].pop(0) - eta, speed = self._get_eta_and_speed(full_size, transfered_size, - file_props) - - self.model.set(iter_, C_PROGRESS, text) - self.model.set(iter_, C_PERCENT, int(percent)) - text = self._format_time(eta) - text += '\n' - #This should make the string Kb/s, - #where 'Kb' part is taken from %s. - #Only the 's' after / (which means second) should be translated. - text += _('(%(filesize_unit)s/s)') % {'filesize_unit': - helpers.convert_bytes(speed)} - self.model.set(iter_, C_TIME, text) - - # try to guess what should be the status image - if file_props['type'] == 'r': - status = 'download' - else: - status = 'upload' - if 'paused' in file_props and file_props['paused'] == True: - status = 'pause' - elif 'stalled' in file_props and file_props['stalled'] == True: - status = 'waiting' - if 'connected' in file_props and file_props['connected'] == False: - status = 'stop' - self.model.set(iter_, 0, self.get_icon(status)) - if transfered_size == full_size: - self.set_status(typ, sid, 'ok') - elif just_began: - path = self.model.get_path(iter_) - self.select_func(path) - - def get_iter_by_sid(self, typ, sid): - """ - Return iter to the row, which holds file transfer, identified by the - session id - """ - iter_ = self.model.get_iter_root() - while iter_: - if typ + sid == self.model[iter_][C_SID].decode('utf-8'): - return iter_ - iter_ = self.model.iter_next(iter_) - - def get_send_file_props(self, account, contact, file_path, file_name, - file_desc=''): - """ - Create new file_props dict and set initial file transfer properties in it - """ - file_props = {'file-name' : file_path, 'name' : file_name, - 'type' : 's', 'desc' : file_desc} - if os.path.isfile(file_path): - stat = os.stat(file_path) - else: - dialogs.ErrorDialog(_('Invalid File'), _('File: ') + file_path) - return None - if stat[6] == 0: - dialogs.ErrorDialog(_('Invalid File'), - _('It is not possible to send empty files')) - return None - file_props['elapsed-time'] = 0 - file_props['size'] = unicode(stat[6]) - file_props['sid'] = helpers.get_random_string_16() - file_props['completed'] = False - file_props['started'] = False - file_props['sender'] = account - file_props['receiver'] = contact - file_props['tt_account'] = account - # keep the last time: transfered_size to compute transfer speed - file_props['transfered_size'] = [] - return file_props - - def add_transfer(self, account, contact, file_props): - """ - Add new transfer to FT window and show the FT window - """ - self.on_transfers_list_leave_notify_event(None) - if file_props is None: - return - file_props['elapsed-time'] = 0 - self.files_props[file_props['type']][file_props['sid']] = file_props - iter_ = self.model.prepend() - text_labels = '' + _('Name: ') + '\n' - if file_props['type'] == 'r': - text_labels += '' + _('Sender: ') + '' - else: - text_labels += '' + _('Recipient: ') + '' - - if file_props['type'] == 'r': - file_name = os.path.split(file_props['file-name'])[1] - else: - file_name = file_props['name'] - text_props = gobject.markup_escape_text(file_name) + '\n' - text_props += contact.get_shown_name() - self.model.set(iter_, 1, text_labels, 2, text_props, C_SID, - file_props['type'] + file_props['sid']) - self.set_progress(file_props['type'], file_props['sid'], 0, iter_) - if 'started' in file_props and file_props['started'] is False: - status = 'waiting' - elif file_props['type'] == 'r': - status = 'download' - else: - status = 'upload' - file_props['tt_account'] = account - self.set_status(file_props['type'], file_props['sid'], status) - self.set_cleanup_sensitivity() - self.window.show_all() - - def on_transfers_list_motion_notify_event(self, widget, event): - pointer = self.tree.get_pointer() - props = widget.get_path_at_pos(int(event.x), int(event.y)) - self.height_diff = pointer[1] - int(event.y) - if self.tooltip.timeout > 0: - if not props or self.tooltip.id != props[0]: - self.tooltip.hide_tooltip() - if props: - row = props[0] - iter_ = None - try: - iter_ = self.model.get_iter(row) - except Exception: - self.tooltip.hide_tooltip() - return - sid = self.model[iter_][C_SID].decode('utf-8') - file_props = self.files_props[sid[0]][sid[1:]] - if file_props is not None: - if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: - self.tooltip.id = row - self.tooltip.timeout = gobject.timeout_add(500, - self.show_tooltip, widget) - - def on_transfers_list_leave_notify_event(self, widget=None, event=None): - if event is not None: - self.height_diff = int(event.y) - elif self.height_diff is 0: - return - pointer = self.tree.get_pointer() - props = self.tree.get_path_at_pos(pointer[0], pointer[1] - self.height_diff) - if self.tooltip.timeout > 0: - if not props or self.tooltip.id == props[0]: - self.tooltip.hide_tooltip() - - def on_transfers_list_row_activated(self, widget, path, col): - # try to open the containing folder - self.on_open_folder_menuitem_activate(widget) - - def set_cleanup_sensitivity(self): - """ - Check if there are transfer rows and set cleanup_button sensitive, or - insensitive if model is empty - """ - if len(self.model) == 0: - self.cleanup_button.set_sensitive(False) - else: - self.cleanup_button.set_sensitive(True) - - def set_all_insensitive(self): - """ - Make all buttons/menuitems insensitive - """ - self.pause_button.set_sensitive(False) - self.pause_menuitem.set_sensitive(False) - self.continue_menuitem.set_sensitive(False) - self.remove_menuitem.set_sensitive(False) - self.cancel_button.set_sensitive(False) - self.cancel_menuitem.set_sensitive(False) - self.open_folder_menuitem.set_sensitive(False) - self.set_cleanup_sensitivity() - - def set_buttons_sensitive(self, path, is_row_selected): - """ - Make buttons/menuitems sensitive as appropriate to the state of file - transfer located at path 'path' - """ - if path is None: - self.set_all_insensitive() - return - current_iter = self.model.get_iter(path) - sid = self.model[current_iter][C_SID].decode('utf-8') - file_props = self.files_props[sid[0]][sid[1:]] - self.remove_menuitem.set_sensitive(is_row_selected) - self.open_folder_menuitem.set_sensitive(is_row_selected) - is_stopped = False - if is_transfer_stopped(file_props): - is_stopped = True - self.cancel_button.set_sensitive(not is_stopped) - self.cancel_menuitem.set_sensitive(not is_stopped) - if not is_row_selected: - # no selection, disable the buttons - self.set_all_insensitive() - elif not is_stopped: - if is_transfer_active(file_props): - # file transfer is active - self.toggle_pause_continue(True) - self.pause_button.set_sensitive(True) - elif is_transfer_paused(file_props): - # file transfer is paused - self.toggle_pause_continue(False) - self.pause_button.set_sensitive(True) - else: - self.pause_button.set_sensitive(False) - self.pause_menuitem.set_sensitive(False) - self.continue_menuitem.set_sensitive(False) - else: - self.pause_button.set_sensitive(False) - self.pause_menuitem.set_sensitive(False) - self.continue_menuitem.set_sensitive(False) - return True - - def selection_changed(self, args): - """ - Selection has changed - change the sensitivity of the buttons/menuitems - """ - selection = args - selected = selection.get_selected_rows() - if selected[1] != []: - selected_path = selected[1][0] - self.select_func(selected_path) - else: - self.set_all_insensitive() - - def select_func(self, path): - is_selected = False - selected = self.tree.get_selection().get_selected_rows() - if selected[1] != []: - selected_path = selected[1][0] - if selected_path == path: - is_selected = True - self.set_buttons_sensitive(path, is_selected) - self.set_cleanup_sensitivity() - return True - - def on_cleanup_button_clicked(self, widget): - i = len(self.model) - 1 - while i >= 0: - iter_ = self.model.get_iter((i)) - sid = self.model[iter_][C_SID].decode('utf-8') - file_props = self.files_props[sid[0]][sid[1:]] - if is_transfer_stopped(file_props): - self._remove_transfer(iter_, sid, file_props) - i -= 1 - self.tree.get_selection().unselect_all() - self.set_all_insensitive() - - def toggle_pause_continue(self, status): - if status: - label = _('Pause') - self.pause_button.set_label(label) - self.pause_button.set_image(gtk.image_new_from_stock( - gtk.STOCK_MEDIA_PAUSE, gtk.ICON_SIZE_MENU)) - - self.pause_menuitem.set_sensitive(True) - self.pause_menuitem.set_no_show_all(False) - self.continue_menuitem.hide() - self.continue_menuitem.set_no_show_all(True) - - else: - label = _('_Continue') - self.pause_button.set_label(label) - self.pause_button.set_image(gtk.image_new_from_stock( - gtk.STOCK_MEDIA_PLAY, gtk.ICON_SIZE_MENU)) - self.pause_menuitem.hide() - self.pause_menuitem.set_no_show_all(True) - self.continue_menuitem.set_sensitive(True) - self.continue_menuitem.set_no_show_all(False) - - def on_pause_restore_button_clicked(self, widget): - selected = self.tree.get_selection().get_selected() - if selected is None or selected[1] is None: - return - s_iter = selected[1] - sid = self.model[s_iter][C_SID].decode('utf-8') - file_props = self.files_props[sid[0]][sid[1:]] - if self.is_transfer_paused(file_props): - file_props['last-time'] = time.time() - file_props['paused'] = False - types = {'r' : 'download', 's' : 'upload'} - self.set_status(file_props['type'], file_props['sid'], types[sid[0]]) - self.toggle_pause_continue(True) - file_props['continue_cb']() - elif is_transfer_active(file_props): - file_props['paused'] = True - self.set_status(file_props['type'], file_props['sid'], 'pause') - # reset that to compute speed only when we resume - file_props['transfered_size'] = [] - self.toggle_pause_continue(False) - - def on_cancel_button_clicked(self, widget): - selected = self.tree.get_selection().get_selected() - if selected is None or selected[1] is None: - return - s_iter = selected[1] - sid = self.model[s_iter][C_SID].decode('utf-8') - file_props = self.files_props[sid[0]][sid[1:]] - if 'tt_account' not in file_props: - return - account = file_props['tt_account'] - if account not in gajim.connections: - return - gajim.connections[account].disconnect_transfer(file_props) - self.set_status(file_props['type'], file_props['sid'], 'stop') - - def show_tooltip(self, widget): - if self.height_diff == 0: - self.tooltip.hide_tooltip() - return - pointer = self.tree.get_pointer() - props = self.tree.get_path_at_pos(pointer[0], - pointer[1] - self.height_diff) - # check if the current pointer is at the same path - # as it was before setting the timeout - if props and self.tooltip.id == props[0]: - iter_ = self.model.get_iter(props[0]) - sid = self.model[iter_][C_SID].decode('utf-8') - file_props = self.files_props[sid[0]][sid[1:]] - # bounding rectangle of coordinates for the cell within the treeview - rect = self.tree.get_cell_area(props[0], props[1]) - # position of the treeview on the screen - position = widget.window.get_origin() - self.tooltip.show_tooltip(file_props , rect.height, - position[1] + rect.y + self.height_diff) - else: - self.tooltip.hide_tooltip() - - def on_notify_ft_complete_checkbox_toggled(self, widget): - gajim.config.set('notify_on_file_complete', - widget.get_active()) - - def on_file_transfers_dialog_delete_event(self, widget, event): - self.on_transfers_list_leave_notify_event(widget, None) - self.window.hide() - return True # do NOT destory window - - def on_close_button_clicked(self, widget): - self.window.hide() - - def show_context_menu(self, event, iter_): - # change the sensitive propery of the buttons and menuitems - if iter_: - path = self.model.get_path(iter_) - self.set_buttons_sensitive(path, True) - - event_button = gtkgui_helpers.get_possible_button_event(event) - self.file_transfers_menu.show_all() - self.file_transfers_menu.popup(None, self.tree, None, - event_button, event.time) - - def on_transfers_list_key_press_event(self, widget, event): - """ - When a key is pressed in the treeviews - """ - self.tooltip.hide_tooltip() - iter_ = None - try: - iter_ = self.tree.get_selection().get_selected()[1] - except TypeError: - self.tree.get_selection().unselect_all() - - if iter_ is not None: - path = self.model.get_path(iter_) - self.tree.get_selection().select_path(path) - - if event.keyval == gtk.keysyms.Menu: - self.show_context_menu(event, iter_) - return True - - - def on_transfers_list_button_release_event(self, widget, event): - # hide tooltip, no matter the button is pressed - self.tooltip.hide_tooltip() - path = None - try: - path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0] - except TypeError: - self.tree.get_selection().unselect_all() - if path is None: - self.set_all_insensitive() - else: - self.select_func(path) - - def on_transfers_list_button_press_event(self, widget, event): - # hide tooltip, no matter the button is pressed - self.tooltip.hide_tooltip() - path, iter_ = None, None - try: - path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0] - except TypeError: - self.tree.get_selection().unselect_all() - if event.button == 3: # Right click - if path: - self.tree.get_selection().select_path(path) - iter_ = self.model.get_iter(path) - self.show_context_menu(event, iter_) - if path: - return True - - def on_open_folder_menuitem_activate(self, widget): - selected = self.tree.get_selection().get_selected() - if not selected or not selected[1]: - return - s_iter = selected[1] - sid = self.model[s_iter][C_SID].decode('utf-8') - file_props = self.files_props[sid[0]][sid[1:]] - if 'file-name' not in file_props: - return - path = os.path.split(file_props['file-name'])[0] - if os.path.exists(path) and os.path.isdir(path): - helpers.launch_file_manager(path) - - def on_cancel_menuitem_activate(self, widget): - self.on_cancel_button_clicked(widget) - - def on_continue_menuitem_activate(self, widget): - self.on_pause_restore_button_clicked(widget) - - def on_pause_menuitem_activate(self, widget): - self.on_pause_restore_button_clicked(widget) - - def on_remove_menuitem_activate(self, widget): - selected = self.tree.get_selection().get_selected() - if not selected or not selected[1]: - return - s_iter = selected[1] - sid = self.model[s_iter][C_SID].decode('utf-8') - file_props = self.files_props[sid[0]][sid[1:]] - self._remove_transfer(s_iter, sid, file_props) - self.set_all_insensitive() - - def on_file_transfers_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: # ESCAPE - self.window.hide() - -# vim: se ts=3: + def __init__(self): + self.files_props = {'r' : {}, 's': {}} + self.height_diff = 0 + self.xml = gtkgui_helpers.get_gtk_builder('filetransfers.ui') + self.window = self.xml.get_object('file_transfers_window') + self.tree = self.xml.get_object('transfers_list') + self.cancel_button = self.xml.get_object('cancel_button') + self.pause_button = self.xml.get_object('pause_restore_button') + self.cleanup_button = self.xml.get_object('cleanup_button') + self.notify_ft_checkbox = self.xml.get_object( + 'notify_ft_complete_checkbox') + + shall_notify = gajim.config.get('notify_on_file_complete') + self.notify_ft_checkbox.set_active(shall_notify + ) + self.model = gtk.ListStore(gtk.gdk.Pixbuf, str, str, str, str, int, str) + self.tree.set_model(self.model) + col = gtk.TreeViewColumn() + + render_pixbuf = gtk.CellRendererPixbuf() + + col.pack_start(render_pixbuf, expand=True) + render_pixbuf.set_property('xpad', 3) + render_pixbuf.set_property('ypad', 3) + render_pixbuf.set_property('yalign', .0) + col.add_attribute(render_pixbuf, 'pixbuf', 0) + self.tree.append_column(col) + + col = gtk.TreeViewColumn(_('File')) + renderer = gtk.CellRendererText() + col.pack_start(renderer, expand=False) + col.add_attribute(renderer, 'markup', C_LABELS) + renderer.set_property('yalign', 0.) + renderer = gtk.CellRendererText() + col.pack_start(renderer, expand=True) + col.add_attribute(renderer, 'markup', C_FILE) + renderer.set_property('xalign', 0.) + renderer.set_property('yalign', 0.) + renderer.set_property('ellipsize', pango.ELLIPSIZE_END) + col.set_resizable(True) + col.set_expand(True) + self.tree.append_column(col) + + col = gtk.TreeViewColumn(_('Time')) + renderer = gtk.CellRendererText() + col.pack_start(renderer, expand=False) + col.add_attribute(renderer, 'markup', C_TIME) + renderer.set_property('yalign', 0.5) + renderer.set_property('xalign', 0.5) + renderer = gtk.CellRendererText() + renderer.set_property('ellipsize', pango.ELLIPSIZE_END) + col.set_resizable(True) + col.set_expand(False) + self.tree.append_column(col) + + col = gtk.TreeViewColumn(_('Progress')) + renderer = gtk.CellRendererProgress() + renderer.set_property('yalign', 0.5) + renderer.set_property('xalign', 0.5) + col.pack_start(renderer, expand=False) + col.add_attribute(renderer, 'text', C_PROGRESS) + col.add_attribute(renderer, 'value', C_PERCENT) + col.set_resizable(True) + col.set_expand(False) + self.tree.append_column(col) + + self.images = {} + self.icons = { + 'upload': gtk.STOCK_GO_UP, + 'download': gtk.STOCK_GO_DOWN, + 'stop': gtk.STOCK_STOP, + 'waiting': gtk.STOCK_REFRESH, + 'pause': gtk.STOCK_MEDIA_PAUSE, + 'continue': gtk.STOCK_MEDIA_PLAY, + 'ok': gtk.STOCK_APPLY, + } + + self.tree.get_selection().set_mode(gtk.SELECTION_SINGLE) + self.tree.get_selection().connect('changed', self.selection_changed) + self.tooltip = tooltips.FileTransfersTooltip() + self.file_transfers_menu = self.xml.get_object('file_transfers_menu') + self.open_folder_menuitem = self.xml.get_object('open_folder_menuitem') + self.cancel_menuitem = self.xml.get_object('cancel_menuitem') + self.pause_menuitem = self.xml.get_object('pause_menuitem') + self.continue_menuitem = self.xml.get_object('continue_menuitem') + self.remove_menuitem = self.xml.get_object('remove_menuitem') + self.xml.connect_signals(self) + + def find_transfer_by_jid(self, account, jid): + """ + Find all transfers with peer 'jid' that belong to 'account' + """ + active_transfers = [[], []] # ['senders', 'receivers'] + + # 'account' is the sender + for file_props in self.files_props['s'].values(): + if file_props['tt_account'] == account: + receiver_jid = unicode(file_props['receiver']).split('/')[0] + if jid == receiver_jid: + if not is_transfer_stopped(file_props): + active_transfers[0].append(file_props) + + # 'account' is the recipient + for file_props in self.files_props['r'].values(): + if file_props['tt_account'] == account: + sender_jid = unicode(file_props['sender']).split('/')[0] + if jid == sender_jid: + if not is_transfer_stopped(file_props): + active_transfers[1].append(file_props) + return active_transfers + + def show_completed(self, jid, file_props): + """ + Show a dialog saying that file (file_props) has been transferred + """ + def on_open(widget, file_props): + dialog.destroy() + if 'file-name' not in file_props: + return + path = os.path.split(file_props['file-name'])[0] + if os.path.exists(path) and os.path.isdir(path): + helpers.launch_file_manager(path) + self.tree.get_selection().unselect_all() + + if file_props['type'] == 'r': + # file path is used below in 'Save in' + (file_path, file_name) = os.path.split(file_props['file-name']) + else: + file_name = file_props['name'] + sectext = '\t' + _('Filename: %s') % file_name + sectext += '\n\t' + _('Size: %s') % \ + helpers.convert_bytes(file_props['size']) + if file_props['type'] == 'r': + jid = unicode(file_props['sender']).split('/')[0] + sender_name = gajim.contacts.get_first_contact_from_jid( + file_props['tt_account'], jid).get_shown_name() + sender = sender_name + else: + #You is a reply of who sent a file + sender = _('You') + sectext += '\n\t' + _('Sender: %s') % sender + sectext += '\n\t' + _('Recipient: ') + if file_props['type'] == 's': + jid = unicode(file_props['receiver']).split('/')[0] + receiver_name = gajim.contacts.get_first_contact_from_jid( + file_props['tt_account'], jid).get_shown_name() + recipient = receiver_name + else: + #You is a reply of who received a file + recipient = _('You') + sectext += recipient + if file_props['type'] == 'r': + sectext += '\n\t' + _('Saved in: %s') % file_path + dialog = dialogs.HigDialog(None, gtk.MESSAGE_INFO, gtk.BUTTONS_NONE, + _('File transfer completed'), sectext) + if file_props['type'] == 'r': + button = gtk.Button(_('_Open Containing Folder')) + button.connect('clicked', on_open, file_props) + dialog.action_area.pack_start(button) + ok_button = dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) + def on_ok(widget): + dialog.destroy() + ok_button.connect('clicked', on_ok) + dialog.show_all() + + def show_request_error(self, file_props): + """ + Show error dialog to the recipient saying that transfer has been canceled + """ + dialogs.InformationDialog(_('File transfer cancelled'), _('Connection with peer cannot be established.')) + self.tree.get_selection().unselect_all() + + def show_send_error(self, file_props): + """ + Show error dialog to the sender saying that transfer has been canceled + """ + dialogs.InformationDialog(_('File transfer cancelled'), + _('Connection with peer cannot be established.')) + self.tree.get_selection().unselect_all() + + def show_stopped(self, jid, file_props, error_msg=''): + if file_props['type'] == 'r': + file_name = os.path.basename(file_props['file-name']) + else: + file_name = file_props['name'] + sectext = '\t' + _('Filename: %s') % file_name + sectext += '\n\t' + _('Recipient: %s') % jid + if error_msg: + sectext += '\n\t' + _('Error message: %s') % error_msg + dialogs.ErrorDialog(_('File transfer stopped'), sectext) + self.tree.get_selection().unselect_all() + + def show_file_send_request(self, account, contact): + desc_entry = gtk.Entry() + + def on_ok(widget): + file_dir = None + files_path_list = dialog.get_filenames() + files_path_list = gtkgui_helpers.decode_filechooser_file_paths( + files_path_list) + desc = desc_entry.get_text() + for file_path in files_path_list: + if self.send_file(account, contact, file_path, desc) and file_dir is None: + file_dir = os.path.dirname(file_path) + if file_dir: + gajim.config.set('last_send_dir', file_dir) + dialog.destroy() + + dialog = dialogs.FileChooserDialog(_('Choose File to Send...'), + gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), + gtk.RESPONSE_OK, + True, # select multiple true as we can select many files to send + gajim.config.get('last_send_dir'), + on_response_ok=on_ok, + on_response_cancel=lambda e:dialog.destroy() + ) + + btn = gtk.Button(_('_Send')) + btn.set_property('can-default', True) + # FIXME: add send icon to this button (JUMP_TO) + dialog.add_action_widget(btn, gtk.RESPONSE_OK) + dialog.set_default_response(gtk.RESPONSE_OK) + + desc_hbox = gtk.HBox(False, 5) + desc_hbox.pack_start(gtk.Label(_('Description: ')), False, False, 0) + desc_hbox.pack_start(desc_entry, True, True, 0) + + dialog.vbox.pack_start(desc_hbox, False, False, 0) + + btn.show() + desc_hbox.show_all() + + def send_file(self, account, contact, file_path, file_desc=''): + """ + Start the real transfer(upload) of the file + """ + if gtkgui_helpers.file_is_locked(file_path): + pritext = _('Gajim cannot access this file') + sextext = _('This file is being used by another process.') + dialogs.ErrorDialog(pritext, sextext) + return + + if isinstance(contact, str): + if contact.find('/') == -1: + return + (jid, resource) = contact.split('/', 1) + contact = gajim.contacts.create_contact(jid=jid, account=account, resource=resource) + file_name = os.path.split(file_path)[1] + file_props = self.get_send_file_props(account, contact, + file_path, file_name, file_desc) + if file_props is None: + return False + self.add_transfer(account, contact, file_props) + gajim.connections[account].send_file_request(file_props) + return True + + def _start_receive(self, file_path, account, contact, file_props): + file_dir = os.path.dirname(file_path) + if file_dir: + gajim.config.set('last_save_dir', file_dir) + file_props['file-name'] = file_path + self.add_transfer(account, contact, file_props) + gajim.connections[account].send_file_approval(file_props) + + def show_file_request(self, account, contact, file_props): + """ + Show dialog asking for comfirmation and store location of new file + requested by a contact + """ + if file_props is None or 'name' not in file_props: + return + sec_text = '\t' + _('File: %s') % gobject.markup_escape_text( + file_props['name']) + if 'size' in file_props: + sec_text += '\n\t' + _('Size: %s') % \ + helpers.convert_bytes(file_props['size']) + if 'mime-type' in file_props: + sec_text += '\n\t' + _('Type: %s') % file_props['mime-type'] + if 'desc' in file_props: + sec_text += '\n\t' + _('Description: %s') % file_props['desc'] + prim_text = _('%s wants to send you a file:') % contact.jid + dialog = None + + def on_response_ok(account, contact, file_props): + + def on_ok(widget, account, contact, file_props): + file_path = dialog2.get_filename() + file_path = gtkgui_helpers.decode_filechooser_file_paths( + (file_path,))[0] + if os.path.exists(file_path): + # check if we have write permissions + if not os.access(file_path, os.W_OK): + file_name = os.path.basename(file_path) + dialogs.ErrorDialog(_('Cannot overwrite existing file "%s"' % file_name), + _('A file with this name already exists and you do not have permission to overwrite it.')) + return + stat = os.stat(file_path) + dl_size = stat.st_size + file_size = file_props['size'] + dl_finished = dl_size >= file_size + + def on_response(response): + if response < 0: + return + elif response == 100: + file_props['offset'] = dl_size + dialog2.destroy() + self._start_receive(file_path, account, contact, file_props) + + dialog = dialogs.FTOverwriteConfirmationDialog( + _('This file already exists'), _('What do you want to do?'), + propose_resume=not dl_finished, on_response=on_response) + dialog.set_transient_for(dialog2) + dialog.set_destroy_with_parent(True) + return + else: + dirname = os.path.dirname(file_path) + if not os.access(dirname, os.W_OK) and os.name != 'nt': + # read-only bit is used to mark special folder under windows, + # not to mark that a folder is read-only. See ticket #3587 + dialogs.ErrorDialog(_('Directory "%s" is not writable') % dirname, _('You do not have permission to create files in this directory.')) + return + dialog2.destroy() + self._start_receive(file_path, account, contact, file_props) + + def on_cancel(widget, account, contact, file_props): + dialog2.destroy() + gajim.connections[account].send_file_rejection(file_props) + + dialog2 = dialogs.FileChooserDialog( + title_text=_('Save File as...'), + action=gtk.FILE_CHOOSER_ACTION_SAVE, + buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_SAVE, gtk.RESPONSE_OK), + default_response=gtk.RESPONSE_OK, + current_folder=gajim.config.get('last_save_dir'), + on_response_ok=(on_ok, account, contact, file_props), + on_response_cancel=(on_cancel, account, contact, file_props)) + + dialog2.set_current_name(file_props['name']) + dialog2.connect('delete-event', lambda widget, event: + on_cancel(widget, account, contact, file_props)) + + def on_response_cancel(account, file_props): + gajim.connections[account].send_file_rejection(file_props) + + dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text, + on_response_ok=(on_response_ok, account, contact, file_props), + on_response_cancel=(on_response_cancel, account, file_props)) + dialog.connect('delete-event', lambda widget, event: + on_response_cancel(widget, account, file_props)) + dialog.popup() + + def get_icon(self, ident): + return self.images.setdefault(ident, + self.window.render_icon(self.icons[ident], gtk.ICON_SIZE_MENU)) + + def set_status(self, typ, sid, status): + """ + Change the status of a transfer to state 'status' + """ + iter_ = self.get_iter_by_sid(typ, sid) + if iter_ is None: + return + sid = self.model[iter_][C_SID].decode('utf-8') + file_props = self.files_props[sid[0]][sid[1:]] + if status == 'stop': + file_props['stopped'] = True + elif status == 'ok': + file_props['completed'] = True + self.model.set(iter_, C_IMAGE, self.get_icon(status)) + path = self.model.get_path(iter_) + self.select_func(path) + + def _format_percent(self, percent): + """ + Add extra spaces from both sides of the percent, so that progress string + has always a fixed size + """ + _str = ' ' + if percent != 100.: + _str += ' ' + if percent < 10: + _str += ' ' + _str += unicode(percent) + '% \n' + return _str + + def _format_time(self, _time): + times = { 'hours': 0, 'minutes': 0, 'seconds': 0 } + _time = int(_time) + times['seconds'] = _time % 60 + if _time >= 60: + _time /= 60 + times['minutes'] = _time % 60 + if _time >= 60: + times['hours'] = _time / 60 + + #Print remaining time in format 00:00:00 + #You can change the places of (hours), (minutes), (seconds) - + #they are not translatable. + return _('%(hours)02.d:%(minutes)02.d:%(seconds)02.d') % times + + def _get_eta_and_speed(self, full_size, transfered_size, file_props): + if len(file_props['transfered_size']) == 0: + return 0., 0. + elif len(file_props['transfered_size']) == 1: + speed = round(float(transfered_size) / file_props['elapsed-time']) + else: + # first and last are (time, transfered_size) + first = file_props['transfered_size'][0] + last = file_props['transfered_size'][-1] + transfered = last[1] - first[1] + tim = last[0] - first[0] + if tim == 0: + return 0., 0. + speed = round(float(transfered) / tim) + if speed == 0.: + return 0., 0. + remaining_size = full_size - transfered_size + eta = remaining_size / speed + return eta, speed + + def _remove_transfer(self, iter_, sid, file_props): + self.model.remove(iter_) + if 'tt_account' in file_props: + # file transfer is set + account = file_props['tt_account'] + if account in gajim.connections: + # there is a connection to the account + gajim.connections[account].remove_transfer(file_props) + if file_props['type'] == 'r': # we receive a file + other = file_props['sender'] + else: # we send a file + other = file_props['receiver'] + if isinstance(other, unicode): + jid = gajim.get_jid_without_resource(other) + else: # It's a Contact instance + jid = other.jid + for ev_type in ('file-error', 'file-completed', 'file-request-error', + 'file-send-error', 'file-stopped'): + for event in gajim.events.get_events(account, jid, [ev_type]): + if event.parameters['sid'] == file_props['sid']: + gajim.events.remove_events(account, jid, event) + gajim.interface.roster.draw_contact(jid, account) + gajim.interface.roster.show_title() + del(self.files_props[sid[0]][sid[1:]]) + del(file_props) + + def set_progress(self, typ, sid, transfered_size, iter_=None): + """ + Change the progress of a transfer with new transfered size + """ + if sid not in self.files_props[typ]: + return + file_props = self.files_props[typ][sid] + full_size = int(file_props['size']) + if full_size == 0: + percent = 0 + else: + percent = round(float(transfered_size) / full_size * 100, 1) + if iter_ is None: + iter_ = self.get_iter_by_sid(typ, sid) + if iter_ is not None: + just_began = False + if self.model[iter_][C_PERCENT] == 0 and int(percent > 0): + just_began = True + text = self._format_percent(percent) + if transfered_size == 0: + text += '0' + else: + text += helpers.convert_bytes(transfered_size) + text += '/' + helpers.convert_bytes(full_size) + # Kb/s + + # remaining time + if 'offset' in file_props and file_props['offset']: + transfered_size -= file_props['offset'] + full_size -= file_props['offset'] + + if file_props['elapsed-time'] > 0: + file_props['transfered_size'].append((file_props['last-time'], transfered_size)) + if len(file_props['transfered_size']) > 6: + file_props['transfered_size'].pop(0) + eta, speed = self._get_eta_and_speed(full_size, transfered_size, + file_props) + + self.model.set(iter_, C_PROGRESS, text) + self.model.set(iter_, C_PERCENT, int(percent)) + text = self._format_time(eta) + text += '\n' + #This should make the string Kb/s, + #where 'Kb' part is taken from %s. + #Only the 's' after / (which means second) should be translated. + text += _('(%(filesize_unit)s/s)') % {'filesize_unit': + helpers.convert_bytes(speed)} + self.model.set(iter_, C_TIME, text) + + # try to guess what should be the status image + if file_props['type'] == 'r': + status = 'download' + else: + status = 'upload' + if 'paused' in file_props and file_props['paused'] == True: + status = 'pause' + elif 'stalled' in file_props and file_props['stalled'] == True: + status = 'waiting' + if 'connected' in file_props and file_props['connected'] == False: + status = 'stop' + self.model.set(iter_, 0, self.get_icon(status)) + if transfered_size == full_size: + self.set_status(typ, sid, 'ok') + elif just_began: + path = self.model.get_path(iter_) + self.select_func(path) + + def get_iter_by_sid(self, typ, sid): + """ + Return iter to the row, which holds file transfer, identified by the + session id + """ + iter_ = self.model.get_iter_root() + while iter_: + if typ + sid == self.model[iter_][C_SID].decode('utf-8'): + return iter_ + iter_ = self.model.iter_next(iter_) + + def get_send_file_props(self, account, contact, file_path, file_name, + file_desc=''): + """ + Create new file_props dict and set initial file transfer properties in it + """ + file_props = {'file-name' : file_path, 'name' : file_name, + 'type' : 's', 'desc' : file_desc} + if os.path.isfile(file_path): + stat = os.stat(file_path) + else: + dialogs.ErrorDialog(_('Invalid File'), _('File: ') + file_path) + return None + if stat[6] == 0: + dialogs.ErrorDialog(_('Invalid File'), + _('It is not possible to send empty files')) + return None + file_props['elapsed-time'] = 0 + file_props['size'] = unicode(stat[6]) + file_props['sid'] = helpers.get_random_string_16() + file_props['completed'] = False + file_props['started'] = False + file_props['sender'] = account + file_props['receiver'] = contact + file_props['tt_account'] = account + # keep the last time: transfered_size to compute transfer speed + file_props['transfered_size'] = [] + return file_props + + def add_transfer(self, account, contact, file_props): + """ + Add new transfer to FT window and show the FT window + """ + self.on_transfers_list_leave_notify_event(None) + if file_props is None: + return + file_props['elapsed-time'] = 0 + self.files_props[file_props['type']][file_props['sid']] = file_props + iter_ = self.model.prepend() + text_labels = '' + _('Name: ') + '\n' + if file_props['type'] == 'r': + text_labels += '' + _('Sender: ') + '' + else: + text_labels += '' + _('Recipient: ') + '' + + if file_props['type'] == 'r': + file_name = os.path.split(file_props['file-name'])[1] + else: + file_name = file_props['name'] + text_props = gobject.markup_escape_text(file_name) + '\n' + text_props += contact.get_shown_name() + self.model.set(iter_, 1, text_labels, 2, text_props, C_SID, + file_props['type'] + file_props['sid']) + self.set_progress(file_props['type'], file_props['sid'], 0, iter_) + if 'started' in file_props and file_props['started'] is False: + status = 'waiting' + elif file_props['type'] == 'r': + status = 'download' + else: + status = 'upload' + file_props['tt_account'] = account + self.set_status(file_props['type'], file_props['sid'], status) + self.set_cleanup_sensitivity() + self.window.show_all() + + def on_transfers_list_motion_notify_event(self, widget, event): + pointer = self.tree.get_pointer() + props = widget.get_path_at_pos(int(event.x), int(event.y)) + self.height_diff = pointer[1] - int(event.y) + if self.tooltip.timeout > 0: + if not props or self.tooltip.id != props[0]: + self.tooltip.hide_tooltip() + if props: + row = props[0] + iter_ = None + try: + iter_ = self.model.get_iter(row) + except Exception: + self.tooltip.hide_tooltip() + return + sid = self.model[iter_][C_SID].decode('utf-8') + file_props = self.files_props[sid[0]][sid[1:]] + if file_props is not None: + if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: + self.tooltip.id = row + self.tooltip.timeout = gobject.timeout_add(500, + self.show_tooltip, widget) + + def on_transfers_list_leave_notify_event(self, widget=None, event=None): + if event is not None: + self.height_diff = int(event.y) + elif self.height_diff is 0: + return + pointer = self.tree.get_pointer() + props = self.tree.get_path_at_pos(pointer[0], pointer[1] - self.height_diff) + if self.tooltip.timeout > 0: + if not props or self.tooltip.id == props[0]: + self.tooltip.hide_tooltip() + + def on_transfers_list_row_activated(self, widget, path, col): + # try to open the containing folder + self.on_open_folder_menuitem_activate(widget) + + def set_cleanup_sensitivity(self): + """ + Check if there are transfer rows and set cleanup_button sensitive, or + insensitive if model is empty + """ + if len(self.model) == 0: + self.cleanup_button.set_sensitive(False) + else: + self.cleanup_button.set_sensitive(True) + + def set_all_insensitive(self): + """ + Make all buttons/menuitems insensitive + """ + self.pause_button.set_sensitive(False) + self.pause_menuitem.set_sensitive(False) + self.continue_menuitem.set_sensitive(False) + self.remove_menuitem.set_sensitive(False) + self.cancel_button.set_sensitive(False) + self.cancel_menuitem.set_sensitive(False) + self.open_folder_menuitem.set_sensitive(False) + self.set_cleanup_sensitivity() + + def set_buttons_sensitive(self, path, is_row_selected): + """ + Make buttons/menuitems sensitive as appropriate to the state of file + transfer located at path 'path' + """ + if path is None: + self.set_all_insensitive() + return + current_iter = self.model.get_iter(path) + sid = self.model[current_iter][C_SID].decode('utf-8') + file_props = self.files_props[sid[0]][sid[1:]] + self.remove_menuitem.set_sensitive(is_row_selected) + self.open_folder_menuitem.set_sensitive(is_row_selected) + is_stopped = False + if is_transfer_stopped(file_props): + is_stopped = True + self.cancel_button.set_sensitive(not is_stopped) + self.cancel_menuitem.set_sensitive(not is_stopped) + if not is_row_selected: + # no selection, disable the buttons + self.set_all_insensitive() + elif not is_stopped: + if is_transfer_active(file_props): + # file transfer is active + self.toggle_pause_continue(True) + self.pause_button.set_sensitive(True) + elif is_transfer_paused(file_props): + # file transfer is paused + self.toggle_pause_continue(False) + self.pause_button.set_sensitive(True) + else: + self.pause_button.set_sensitive(False) + self.pause_menuitem.set_sensitive(False) + self.continue_menuitem.set_sensitive(False) + else: + self.pause_button.set_sensitive(False) + self.pause_menuitem.set_sensitive(False) + self.continue_menuitem.set_sensitive(False) + return True + + def selection_changed(self, args): + """ + Selection has changed - change the sensitivity of the buttons/menuitems + """ + selection = args + selected = selection.get_selected_rows() + if selected[1] != []: + selected_path = selected[1][0] + self.select_func(selected_path) + else: + self.set_all_insensitive() + + def select_func(self, path): + is_selected = False + selected = self.tree.get_selection().get_selected_rows() + if selected[1] != []: + selected_path = selected[1][0] + if selected_path == path: + is_selected = True + self.set_buttons_sensitive(path, is_selected) + self.set_cleanup_sensitivity() + return True + + def on_cleanup_button_clicked(self, widget): + i = len(self.model) - 1 + while i >= 0: + iter_ = self.model.get_iter((i)) + sid = self.model[iter_][C_SID].decode('utf-8') + file_props = self.files_props[sid[0]][sid[1:]] + if is_transfer_stopped(file_props): + self._remove_transfer(iter_, sid, file_props) + i -= 1 + self.tree.get_selection().unselect_all() + self.set_all_insensitive() + + def toggle_pause_continue(self, status): + if status: + label = _('Pause') + self.pause_button.set_label(label) + self.pause_button.set_image(gtk.image_new_from_stock( + gtk.STOCK_MEDIA_PAUSE, gtk.ICON_SIZE_MENU)) + + self.pause_menuitem.set_sensitive(True) + self.pause_menuitem.set_no_show_all(False) + self.continue_menuitem.hide() + self.continue_menuitem.set_no_show_all(True) + + else: + label = _('_Continue') + self.pause_button.set_label(label) + self.pause_button.set_image(gtk.image_new_from_stock( + gtk.STOCK_MEDIA_PLAY, gtk.ICON_SIZE_MENU)) + self.pause_menuitem.hide() + self.pause_menuitem.set_no_show_all(True) + self.continue_menuitem.set_sensitive(True) + self.continue_menuitem.set_no_show_all(False) + + def on_pause_restore_button_clicked(self, widget): + selected = self.tree.get_selection().get_selected() + if selected is None or selected[1] is None: + return + s_iter = selected[1] + sid = self.model[s_iter][C_SID].decode('utf-8') + file_props = self.files_props[sid[0]][sid[1:]] + if self.is_transfer_paused(file_props): + file_props['last-time'] = time.time() + file_props['paused'] = False + types = {'r' : 'download', 's' : 'upload'} + self.set_status(file_props['type'], file_props['sid'], types[sid[0]]) + self.toggle_pause_continue(True) + file_props['continue_cb']() + elif is_transfer_active(file_props): + file_props['paused'] = True + self.set_status(file_props['type'], file_props['sid'], 'pause') + # reset that to compute speed only when we resume + file_props['transfered_size'] = [] + self.toggle_pause_continue(False) + + def on_cancel_button_clicked(self, widget): + selected = self.tree.get_selection().get_selected() + if selected is None or selected[1] is None: + return + s_iter = selected[1] + sid = self.model[s_iter][C_SID].decode('utf-8') + file_props = self.files_props[sid[0]][sid[1:]] + if 'tt_account' not in file_props: + return + account = file_props['tt_account'] + if account not in gajim.connections: + return + gajim.connections[account].disconnect_transfer(file_props) + self.set_status(file_props['type'], file_props['sid'], 'stop') + + def show_tooltip(self, widget): + if self.height_diff == 0: + self.tooltip.hide_tooltip() + return + pointer = self.tree.get_pointer() + props = self.tree.get_path_at_pos(pointer[0], + pointer[1] - self.height_diff) + # check if the current pointer is at the same path + # as it was before setting the timeout + if props and self.tooltip.id == props[0]: + iter_ = self.model.get_iter(props[0]) + sid = self.model[iter_][C_SID].decode('utf-8') + file_props = self.files_props[sid[0]][sid[1:]] + # bounding rectangle of coordinates for the cell within the treeview + rect = self.tree.get_cell_area(props[0], props[1]) + # position of the treeview on the screen + position = widget.window.get_origin() + self.tooltip.show_tooltip(file_props, rect.height, + position[1] + rect.y + self.height_diff) + else: + self.tooltip.hide_tooltip() + + def on_notify_ft_complete_checkbox_toggled(self, widget): + gajim.config.set('notify_on_file_complete', + widget.get_active()) + + def on_file_transfers_dialog_delete_event(self, widget, event): + self.on_transfers_list_leave_notify_event(widget, None) + self.window.hide() + return True # do NOT destory window + + def on_close_button_clicked(self, widget): + self.window.hide() + + def show_context_menu(self, event, iter_): + # change the sensitive propery of the buttons and menuitems + if iter_: + path = self.model.get_path(iter_) + self.set_buttons_sensitive(path, True) + + event_button = gtkgui_helpers.get_possible_button_event(event) + self.file_transfers_menu.show_all() + self.file_transfers_menu.popup(None, self.tree, None, + event_button, event.time) + + def on_transfers_list_key_press_event(self, widget, event): + """ + When a key is pressed in the treeviews + """ + self.tooltip.hide_tooltip() + iter_ = None + try: + iter_ = self.tree.get_selection().get_selected()[1] + except TypeError: + self.tree.get_selection().unselect_all() + + if iter_ is not None: + path = self.model.get_path(iter_) + self.tree.get_selection().select_path(path) + + if event.keyval == gtk.keysyms.Menu: + self.show_context_menu(event, iter_) + return True + + + def on_transfers_list_button_release_event(self, widget, event): + # hide tooltip, no matter the button is pressed + self.tooltip.hide_tooltip() + path = None + try: + path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0] + except TypeError: + self.tree.get_selection().unselect_all() + if path is None: + self.set_all_insensitive() + else: + self.select_func(path) + + def on_transfers_list_button_press_event(self, widget, event): + # hide tooltip, no matter the button is pressed + self.tooltip.hide_tooltip() + path, iter_ = None, None + try: + path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0] + except TypeError: + self.tree.get_selection().unselect_all() + if event.button == 3: # Right click + if path: + self.tree.get_selection().select_path(path) + iter_ = self.model.get_iter(path) + self.show_context_menu(event, iter_) + if path: + return True + + def on_open_folder_menuitem_activate(self, widget): + selected = self.tree.get_selection().get_selected() + if not selected or not selected[1]: + return + s_iter = selected[1] + sid = self.model[s_iter][C_SID].decode('utf-8') + file_props = self.files_props[sid[0]][sid[1:]] + if 'file-name' not in file_props: + return + path = os.path.split(file_props['file-name'])[0] + if os.path.exists(path) and os.path.isdir(path): + helpers.launch_file_manager(path) + + def on_cancel_menuitem_activate(self, widget): + self.on_cancel_button_clicked(widget) + + def on_continue_menuitem_activate(self, widget): + self.on_pause_restore_button_clicked(widget) + + def on_pause_menuitem_activate(self, widget): + self.on_pause_restore_button_clicked(widget) + + def on_remove_menuitem_activate(self, widget): + selected = self.tree.get_selection().get_selected() + if not selected or not selected[1]: + return + s_iter = selected[1] + sid = self.model[s_iter][C_SID].decode('utf-8') + file_props = self.files_props[sid[0]][sid[1:]] + self._remove_transfer(s_iter, sid, file_props) + self.set_all_insensitive() + + def on_file_transfers_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: # ESCAPE + self.window.hide() diff --git a/src/gajim-remote-plugin.py b/src/gajim-remote-plugin.py index c475d18eb..e9acaf8f1 100755 --- a/src/gajim-remote-plugin.py +++ b/src/gajim-remote-plugin.py @@ -31,25 +31,25 @@ from common import exceptions from common import i18n try: - PREFERRED_ENCODING = locale.getpreferredencoding() + PREFERRED_ENCODING = locale.getpreferredencoding() except: - PREFERRED_ENCODING = 'UTF-8' + PREFERRED_ENCODING = 'UTF-8' def send_error(error_message): - '''Writes error message to stderr and exits''' - print >> sys.stderr, error_message.encode(PREFERRED_ENCODING) - sys.exit(1) + '''Writes error message to stderr and exits''' + print >> sys.stderr, error_message.encode(PREFERRED_ENCODING) + sys.exit(1) try: - if sys.platform == 'darwin': - import osx.dbus - osx.dbus.load(False) - import dbus - import dbus.service - import dbus.glib + if sys.platform == 'darwin': + import osx.dbus + osx.dbus.load(False) + import dbus + import dbus.service + import dbus.glib except: - print str(exceptions.DbusNotSupported()) - sys.exit(1) + print str(exceptions.DbusNotSupported()) + sys.exit(1) OBJ_PATH = '/org/gajim/dbusplugin/RemoteObject' INTERFACE = 'org.gajim.dbusplugin.RemoteInterface' @@ -59,490 +59,490 @@ BASENAME = 'gajim-remote-plugin' class GajimRemote: - def __init__(self): - self.argv_len = len(sys.argv) - # define commands dict. Prototype : - # { - # 'command': [comment, [list of arguments] ] - # } - # - # each argument is defined as a tuple: - # (argument name, help on argument, is mandatory) - # - self.commands = { - 'help':[ - _('Shows a help on specific command'), - [ - #User gets help for the command, specified by this parameter - (_('command'), - _('show help on command'), False) - ] - ], - 'toggle_roster_appearance' : [ - _('Shows or hides the roster window'), - [] - ], - 'show_next_pending_event': [ - _('Pops up a window with the next pending event'), - [] - ], - 'list_contacts': [ - _('Prints a list of all contacts in the roster. Each contact ' - 'appears on a separate line'), - [ - (_('account'), _('show only contacts of the given account'), - False) - ] + def __init__(self): + self.argv_len = len(sys.argv) + # define commands dict. Prototype : + # { + # 'command': [comment, [list of arguments] ] + # } + # + # each argument is defined as a tuple: + # (argument name, help on argument, is mandatory) + # + self.commands = { + 'help': [ + _('Shows a help on specific command'), + [ + #User gets help for the command, specified by this parameter + (_('command'), + _('show help on command'), False) + ] + ], + 'toggle_roster_appearance': [ + _('Shows or hides the roster window'), + [] + ], + 'show_next_pending_event': [ + _('Pops up a window with the next pending event'), + [] + ], + 'list_contacts': [ + _('Prints a list of all contacts in the roster. Each contact ' + 'appears on a separate line'), + [ + (_('account'), _('show only contacts of the given account'), + False) + ] - ], - 'list_accounts': [ - _('Prints a list of registered accounts'), - [] - ], - 'change_status': [ - _('Changes the status of account or accounts'), - [ + ], + 'list_accounts': [ + _('Prints a list of registered accounts'), + [] + ], + 'change_status': [ + _('Changes the status of account or accounts'), + [ #offline, online, chat, away, xa, dnd, invisible should not be translated - (_('status'), _('one of: offline, online, chat, away, xa, dnd, invisible '), True), - (_('message'), _('status message'), False), - (_('account'), _('change status of account "account". ' - 'If not specified, try to change status of all accounts that have ' - '"sync with global status" option set'), False) - ] - ], - 'open_chat': [ - _('Shows the chat dialog so that you can send messages to a contact'), - [ - ('jid', _('JID of the contact that you want to chat with'), - True), - (_('account'), _('if specified, contact is taken from the ' - 'contact list of this account'), False) - ] - ], - 'send_chat_message':[ - _('Sends new chat message to a contact in the roster. Both OpenPGP key ' - 'and account are optional. If you want to set only \'account\', ' - 'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'), - [ - ('jid', _('JID of the contact that will receive the message'), True), - (_('message'), _('message contents'), True), - (_('pgp key'), _('if specified, the message will be encrypted ' - 'using this public key'), False), - (_('account'), _('if specified, the message will be sent ' - 'using this account'), False), - ] - ], - 'send_single_message':[ - _('Sends new single message to a contact in the roster. Both OpenPGP key ' - 'and account are optional. If you want to set only \'account\', ' - 'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'), - [ - ('jid', _('JID of the contact that will receive the message'), True), - (_('subject'), _('message subject'), True), - (_('message'), _('message contents'), True), - (_('pgp key'), _('if specified, the message will be encrypted ' - 'using this public key'), False), - (_('account'), _('if specified, the message will be sent ' - 'using this account'), False), - ] - ], - 'send_groupchat_message':[ - _('Sends new message to a groupchat you\'ve joined.'), - [ - ('room_jid', _('JID of the room that will receive the message'), True), - (_('message'), _('message contents'), True), - (_('account'), _('if specified, the message will be sent ' - 'using this account'), False), - ] - ], - 'contact_info': [ - _('Gets detailed info on a contact'), - [ - ('jid', _('JID of the contact'), True) - ] - ], - 'account_info': [ - _('Gets detailed info on a account'), - [ - ('account', _('Name of the account'), True) - ] - ], - 'send_file': [ - _('Sends file to a contact'), - [ - (_('file'), _('File path'), True), - ('jid', _('JID of the contact'), True), - (_('account'), _('if specified, file will be sent using this ' - 'account'), False) - ] - ], - 'prefs_list': [ - _('Lists all preferences and their values'), - [ ] - ], - 'prefs_put': [ - _('Sets value of \'key\' to \'value\'.'), - [ - (_('key=value'), _('\'key\' is the name of the preference, ' - '\'value\' is the value to set it to'), True) - ] - ], - 'prefs_del': [ - _('Deletes a preference item'), - [ - (_('key'), _('name of the preference to be deleted'), True) - ] - ], - 'prefs_store': [ - _('Writes the current state of Gajim preferences to the .config ' - 'file'), - [ ] - ], - 'remove_contact': [ - _('Removes contact from roster'), - [ - ('jid', _('JID of the contact'), True), - (_('account'), _('if specified, contact is taken from the ' - 'contact list of this account'), False) + (_('status'), _('one of: offline, online, chat, away, xa, dnd, invisible '), True), + (_('message'), _('status message'), False), + (_('account'), _('change status of account "account". ' + 'If not specified, try to change status of all accounts that have ' + '"sync with global status" option set'), False) + ] + ], + 'open_chat': [ + _('Shows the chat dialog so that you can send messages to a contact'), + [ + ('jid', _('JID of the contact that you want to chat with'), + True), + (_('account'), _('if specified, contact is taken from the ' + 'contact list of this account'), False) + ] + ], + 'send_chat_message': [ + _('Sends new chat message to a contact in the roster. Both OpenPGP key ' + 'and account are optional. If you want to set only \'account\', ' + 'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'), + [ + ('jid', _('JID of the contact that will receive the message'), True), + (_('message'), _('message contents'), True), + (_('pgp key'), _('if specified, the message will be encrypted ' + 'using this public key'), False), + (_('account'), _('if specified, the message will be sent ' + 'using this account'), False), + ] + ], + 'send_single_message': [ + _('Sends new single message to a contact in the roster. Both OpenPGP key ' + 'and account are optional. If you want to set only \'account\', ' + 'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'), + [ + ('jid', _('JID of the contact that will receive the message'), True), + (_('subject'), _('message subject'), True), + (_('message'), _('message contents'), True), + (_('pgp key'), _('if specified, the message will be encrypted ' + 'using this public key'), False), + (_('account'), _('if specified, the message will be sent ' + 'using this account'), False), + ] + ], + 'send_groupchat_message': [ + _('Sends new message to a groupchat you\'ve joined.'), + [ + ('room_jid', _('JID of the room that will receive the message'), True), + (_('message'), _('message contents'), True), + (_('account'), _('if specified, the message will be sent ' + 'using this account'), False), + ] + ], + 'contact_info': [ + _('Gets detailed info on a contact'), + [ + ('jid', _('JID of the contact'), True) + ] + ], + 'account_info': [ + _('Gets detailed info on a account'), + [ + ('account', _('Name of the account'), True) + ] + ], + 'send_file': [ + _('Sends file to a contact'), + [ + (_('file'), _('File path'), True), + ('jid', _('JID of the contact'), True), + (_('account'), _('if specified, file will be sent using this ' + 'account'), False) + ] + ], + 'prefs_list': [ + _('Lists all preferences and their values'), + [ ] + ], + 'prefs_put': [ + _('Sets value of \'key\' to \'value\'.'), + [ + (_('key=value'), _('\'key\' is the name of the preference, ' + '\'value\' is the value to set it to'), True) + ] + ], + 'prefs_del': [ + _('Deletes a preference item'), + [ + (_('key'), _('name of the preference to be deleted'), True) + ] + ], + 'prefs_store': [ + _('Writes the current state of Gajim preferences to the .config ' + 'file'), + [ ] + ], + 'remove_contact': [ + _('Removes contact from roster'), + [ + ('jid', _('JID of the contact'), True), + (_('account'), _('if specified, contact is taken from the ' + 'contact list of this account'), False) - ] - ], - 'add_contact': [ - _('Adds contact to roster'), - [ - (_('jid'), _('JID of the contact'), True), - (_('account'), _('Adds new contact to this account'), False) - ] - ], + ] + ], + 'add_contact': [ + _('Adds contact to roster'), + [ + (_('jid'), _('JID of the contact'), True), + (_('account'), _('Adds new contact to this account'), False) + ] + ], - 'get_status': [ - _('Returns current status (the global one unless account is specified)'), - [ - (_('account'), _(''), False) - ] - ], + 'get_status': [ + _('Returns current status (the global one unless account is specified)'), + [ + (_('account'), _(''), False) + ] + ], - 'get_status_message': [ - _('Returns current status message(the global one unless account is specified)'), - [ - (_('account'), _(''), False) - ] - ], + 'get_status_message': [ + _('Returns current status message(the global one unless account is specified)'), + [ + (_('account'), _(''), False) + ] + ], - 'get_unread_msgs_number': [ - _('Returns number of unread messages'), - [ ] - ], - 'start_chat': [ - _('Opens \'Start Chat\' dialog'), - [ - (_('account'), _('Starts chat, using this account'), True) - ] - ], - 'send_xml': [ - _('Sends custom XML'), - [ - ('xml', _('XML to send'), True), - ('account', _('Account in which the xml will be sent; ' - 'if not specified, xml will be sent to all accounts'), - False) - ] - ], - 'handle_uri': [ - _('Handle a xmpp:/ uri'), - [ - (_('uri'), _(''), True), - (_('account'), _(''), False) - ] - ], - 'join_room': [ - _('Join a MUC room'), - [ - (_('room'), _(''), True), - (_('nick'), _(''), False), - (_('password'), _(''), False), - (_('account'), _(''), False) - ] - ], - 'check_gajim_running':[ - _('Check if Gajim is running'), - [] - ], - 'toggle_ipython' : [ - _('Shows or hides the ipython window'), - [] - ], + 'get_unread_msgs_number': [ + _('Returns number of unread messages'), + [ ] + ], + 'start_chat': [ + _('Opens \'Start Chat\' dialog'), + [ + (_('account'), _('Starts chat, using this account'), True) + ] + ], + 'send_xml': [ + _('Sends custom XML'), + [ + ('xml', _('XML to send'), True), + ('account', _('Account in which the xml will be sent; ' + 'if not specified, xml will be sent to all accounts'), + False) + ] + ], + 'handle_uri': [ + _('Handle a xmpp:/ uri'), + [ + (_('uri'), _(''), True), + (_('account'), _(''), False) + ] + ], + 'join_room': [ + _('Join a MUC room'), + [ + (_('room'), _(''), True), + (_('nick'), _(''), False), + (_('password'), _(''), False), + (_('account'), _(''), False) + ] + ], + 'check_gajim_running': [ + _('Check if Gajim is running'), + [] + ], + 'toggle_ipython': [ + _('Shows or hides the ipython window'), + [] + ], - } + } - path = os.getcwd() - if '.svn' in os.listdir(path) or '_svn' in os.listdir(path): - # command only for svn - self.commands['toggle_ipython'] = [ - _('Shows or hides the ipython window'), - [] - ] - self.sbus = None - if self.argv_len < 2 or sys.argv[1] not in self.commands.keys(): - # no args or bad args - send_error(self.compose_help()) - self.command = sys.argv[1] - if self.command == 'help': - if self.argv_len == 3: - print self.help_on_command(sys.argv[2]).encode(PREFERRED_ENCODING) - else: - print self.compose_help().encode(PREFERRED_ENCODING) - sys.exit(0) - if self.command == 'handle_uri': - self.handle_uri() - if self.command == 'check_gajim_running': - print self.check_gajim_running() - sys.exit(0) - self.init_connection() - self.check_arguments() + path = os.getcwd() + if '.svn' in os.listdir(path) or '_svn' in os.listdir(path): + # command only for svn + self.commands['toggle_ipython'] = [ + _('Shows or hides the ipython window'), + [] + ] + self.sbus = None + if self.argv_len < 2 or sys.argv[1] not in self.commands.keys(): + # no args or bad args + send_error(self.compose_help()) + self.command = sys.argv[1] + if self.command == 'help': + if self.argv_len == 3: + print self.help_on_command(sys.argv[2]).encode(PREFERRED_ENCODING) + else: + print self.compose_help().encode(PREFERRED_ENCODING) + sys.exit(0) + if self.command == 'handle_uri': + self.handle_uri() + if self.command == 'check_gajim_running': + print self.check_gajim_running() + sys.exit(0) + self.init_connection() + self.check_arguments() - if self.command == 'contact_info': - if self.argv_len < 3: - send_error(_('Missing argument "contact_jid"')) + if self.command == 'contact_info': + if self.argv_len < 3: + send_error(_('Missing argument "contact_jid"')) - try: - res = self.call_remote_method() - except exceptions.ServiceNotAvailable: - # At this point an error message has already been displayed - sys.exit(1) - else: - self.print_result(res) + try: + res = self.call_remote_method() + except exceptions.ServiceNotAvailable: + # At this point an error message has already been displayed + sys.exit(1) + else: + self.print_result(res) - def print_result(self, res): - ''' Print retrieved result to the output ''' - if res is not None: - if self.command in ('open_chat', 'send_chat_message', 'send_single_message', 'start_chat'): - if self.command in ('send_message', 'send_single_message'): - self.argv_len -= 2 + def print_result(self, res): + ''' Print retrieved result to the output ''' + if res is not None: + if self.command in ('open_chat', 'send_chat_message', 'send_single_message', 'start_chat'): + if self.command in ('send_message', 'send_single_message'): + self.argv_len -= 2 - if res is False: - if self.argv_len < 4: - send_error(_('\'%s\' is not in your roster.\n' - 'Please specify account for sending the message.') % sys.argv[2]) - else: - send_error(_('You have no active account')) - elif self.command == 'list_accounts': - if isinstance(res, list): - for account in res: - if isinstance(account, unicode): - print account.encode(PREFERRED_ENCODING) - else: - print account - elif self.command == 'account_info': - if res: - print self.print_info(0, res, True) - elif self.command == 'list_contacts': - for account_dict in res: - print self.print_info(0, account_dict, True) - elif self.command == 'prefs_list': - pref_keys = res.keys() - pref_keys.sort() - for pref_key in pref_keys: - result = '%s = %s' % (pref_key, res[pref_key]) - if isinstance(result, unicode): - print result.encode(PREFERRED_ENCODING) - else: - print result - elif self.command == 'contact_info': - print self.print_info(0, res, True) - elif res: - print unicode(res).encode(PREFERRED_ENCODING) + if res is False: + if self.argv_len < 4: + send_error(_('\'%s\' is not in your roster.\n' + 'Please specify account for sending the message.') % sys.argv[2]) + else: + send_error(_('You have no active account')) + elif self.command == 'list_accounts': + if isinstance(res, list): + for account in res: + if isinstance(account, unicode): + print account.encode(PREFERRED_ENCODING) + else: + print account + elif self.command == 'account_info': + if res: + print self.print_info(0, res, True) + elif self.command == 'list_contacts': + for account_dict in res: + print self.print_info(0, account_dict, True) + elif self.command == 'prefs_list': + pref_keys = res.keys() + pref_keys.sort() + for pref_key in pref_keys: + result = '%s = %s' % (pref_key, res[pref_key]) + if isinstance(result, unicode): + print result.encode(PREFERRED_ENCODING) + else: + print result + elif self.command == 'contact_info': + print self.print_info(0, res, True) + elif res: + print unicode(res).encode(PREFERRED_ENCODING) - def check_gajim_running(self): - if not self.sbus: - try: - self.sbus = dbus.SessionBus() - except: - raise exceptions.SessionBusNotPresent + def check_gajim_running(self): + if not self.sbus: + try: + self.sbus = dbus.SessionBus() + except: + raise exceptions.SessionBusNotPresent - test = False - if hasattr(self.sbus, 'name_has_owner'): - if self.sbus.name_has_owner(SERVICE): - test = True - elif dbus.dbus_bindings.bus_name_has_owner(self.sbus.get_connection(), - SERVICE): - test = True - return test + test = False + if hasattr(self.sbus, 'name_has_owner'): + if self.sbus.name_has_owner(SERVICE): + test = True + elif dbus.dbus_bindings.bus_name_has_owner(self.sbus.get_connection(), + SERVICE): + test = True + return test - def init_connection(self): - ''' create the onnection to the session dbus, - or exit if it is not possible ''' - try: - self.sbus = dbus.SessionBus() - except: - raise exceptions.SessionBusNotPresent + def init_connection(self): + ''' create the onnection to the session dbus, + or exit if it is not possible ''' + try: + self.sbus = dbus.SessionBus() + except: + raise exceptions.SessionBusNotPresent - from pprint import pprint - pprint(list(self.sbus.list_names())) - if not self.check_gajim_running(): - send_error(_('It seems Gajim is not running. So you can\'t use gajim-remote.')) - obj = self.sbus.get_object(SERVICE, OBJ_PATH) - interface = dbus.Interface(obj, INTERFACE) + from pprint import pprint + pprint(list(self.sbus.list_names())) + if not self.check_gajim_running(): + send_error(_('It seems Gajim is not running. So you can\'t use gajim-remote.')) + obj = self.sbus.get_object(SERVICE, OBJ_PATH) + interface = dbus.Interface(obj, INTERFACE) - # get the function asked - self.method = interface.__getattr__(self.command) + # get the function asked + self.method = interface.__getattr__(self.command) - def make_arguments_row(self, args): - ''' return arguments list. Mandatory arguments are enclosed with: - '<', '>', optional arguments - with '[', ']' ''' - str = '' - for argument in args: - str += ' ' - if argument[2]: - str += '<' - else: - str += '[' - str += argument[0] - if argument[2]: - str += '>' - else: - str += ']' - return str + def make_arguments_row(self, args): + ''' return arguments list. Mandatory arguments are enclosed with: + '<', '>', optional arguments - with '[', ']' ''' + str = '' + for argument in args: + str += ' ' + if argument[2]: + str += '<' + else: + str += '[' + str += argument[0] + if argument[2]: + str += '>' + else: + str += ']' + return str - def help_on_command(self, command): - ''' return help message for a given command ''' - if command in self.commands: - command_props = self.commands[command] - arguments_str = self.make_arguments_row(command_props[1]) - str = _('Usage: %s %s %s \n\t %s') % (BASENAME, command, - arguments_str, command_props[0]) - if len(command_props[1]) > 0: - str += '\n\n' + _('Arguments:') + '\n' - for argument in command_props[1]: - str += ' ' + argument[0] + ' - ' + argument[1] + '\n' - return str - send_error(_('%s not found') % command) + def help_on_command(self, command): + ''' return help message for a given command ''' + if command in self.commands: + command_props = self.commands[command] + arguments_str = self.make_arguments_row(command_props[1]) + str = _('Usage: %s %s %s \n\t %s') % (BASENAME, command, + arguments_str, command_props[0]) + if len(command_props[1]) > 0: + str += '\n\n' + _('Arguments:') + '\n' + for argument in command_props[1]: + str += ' ' + argument[0] + ' - ' + argument[1] + '\n' + return str + send_error(_('%s not found') % command) - def compose_help(self): - ''' print usage, and list available commands ''' - str = _('Usage: %s command [arguments]\nCommand is one of:\n' ) % BASENAME - commands = self.commands.keys() - commands.sort() - for command in commands: - str += ' ' + command - for argument in self.commands[command][1]: - str += ' ' - if argument[2]: - str += '<' - else: - str += '[' - str += argument[0] - if argument[2]: - str += '>' - else: - str += ']' - str += '\n' - return str + def compose_help(self): + ''' print usage, and list available commands ''' + str = _('Usage: %s command [arguments]\nCommand is one of:\n' ) % BASENAME + commands = self.commands.keys() + commands.sort() + for command in commands: + str += ' ' + command + for argument in self.commands[command][1]: + str += ' ' + if argument[2]: + str += '<' + else: + str += '[' + str += argument[0] + if argument[2]: + str += '>' + else: + str += ']' + str += '\n' + return str - def print_info(self, level, prop_dict, encode_return = False): - ''' return formated string from data structure ''' - if prop_dict is None or not isinstance(prop_dict, (dict, list, tuple)): - return '' - ret_str = '' - if isinstance(prop_dict, (list, tuple)): - ret_str = '' - spacing = ' ' * level * 4 - for val in prop_dict: - if val is None: - ret_str +='\t' - elif isinstance(val, int): - ret_str +='\t' + str(val) - elif isinstance(val, (str, unicode)): - ret_str +='\t' + val - elif isinstance(val, (list, tuple)): - res = '' - for items in val: - res += self.print_info(level+1, items) - if res != '': - ret_str += '\t' + res - elif isinstance(val, dict): - ret_str += self.print_info(level+1, val) - ret_str = '%s(%s)\n' % (spacing, ret_str[1:]) - elif isinstance(prop_dict, dict): - for key in prop_dict.keys(): - val = prop_dict[key] - spacing = ' ' * level * 4 - if isinstance(val, (unicode, int, str)): - if val is not None: - val = val.strip() - ret_str += '%s%-10s: %s\n' % (spacing, key, val) - elif isinstance(val, (list, tuple)): - res = '' - for items in val: - res += self.print_info(level+1, items) - if res != '': - ret_str += '%s%s: \n%s' % (spacing, key, res) - elif isinstance(val, dict): - res = self.print_info(level+1, val) - if res != '': - ret_str += '%s%s: \n%s' % (spacing, key, res) - if (encode_return): - try: - ret_str = ret_str.encode(PREFERRED_ENCODING) - except: - pass - return ret_str + def print_info(self, level, prop_dict, encode_return = False): + ''' return formated string from data structure ''' + if prop_dict is None or not isinstance(prop_dict, (dict, list, tuple)): + return '' + ret_str = '' + if isinstance(prop_dict, (list, tuple)): + ret_str = '' + spacing = ' ' * level * 4 + for val in prop_dict: + if val is None: + ret_str +='\t' + elif isinstance(val, int): + ret_str +='\t' + str(val) + elif isinstance(val, (str, unicode)): + ret_str +='\t' + val + elif isinstance(val, (list, tuple)): + res = '' + for items in val: + res += self.print_info(level+1, items) + if res != '': + ret_str += '\t' + res + elif isinstance(val, dict): + ret_str += self.print_info(level+1, val) + ret_str = '%s(%s)\n' % (spacing, ret_str[1:]) + elif isinstance(prop_dict, dict): + for key in prop_dict.keys(): + val = prop_dict[key] + spacing = ' ' * level * 4 + if isinstance(val, (unicode, int, str)): + if val is not None: + val = val.strip() + ret_str += '%s%-10s: %s\n' % (spacing, key, val) + elif isinstance(val, (list, tuple)): + res = '' + for items in val: + res += self.print_info(level+1, items) + if res != '': + ret_str += '%s%s: \n%s' % (spacing, key, res) + elif isinstance(val, dict): + res = self.print_info(level+1, val) + if res != '': + ret_str += '%s%s: \n%s' % (spacing, key, res) + if (encode_return): + try: + ret_str = ret_str.encode(PREFERRED_ENCODING) + except: + pass + return ret_str - def check_arguments(self): - ''' Make check if all necessary arguments are given ''' - argv_len = self.argv_len - 2 - args = self.commands[self.command][1] - if len(args) < argv_len: - send_error(_('Too many arguments. \n' - 'Type "%s help %s" for more info') % (BASENAME, self.command)) - if len(args) > argv_len: - if args[argv_len][2]: - send_error(_('Argument "%s" is not specified. \n' - 'Type "%s help %s" for more info') % - (args[argv_len][0], BASENAME, self.command)) - self.arguments = [] - i = 0 - for arg in sys.argv[2:]: - i += 1 - if i < len(args): - self.arguments.append(arg) - else: - # it's latest argument with spaces - self.arguments.append(' '.join(sys.argv[i+1:])) - break - # add empty string for missing args - self.arguments += ['']*(len(args)-i) + def check_arguments(self): + ''' Make check if all necessary arguments are given ''' + argv_len = self.argv_len - 2 + args = self.commands[self.command][1] + if len(args) < argv_len: + send_error(_('Too many arguments. \n' + 'Type "%s help %s" for more info') % (BASENAME, self.command)) + if len(args) > argv_len: + if args[argv_len][2]: + send_error(_('Argument "%s" is not specified. \n' + 'Type "%s help %s" for more info') % + (args[argv_len][0], BASENAME, self.command)) + self.arguments = [] + i = 0 + for arg in sys.argv[2:]: + i += 1 + if i < len(args): + self.arguments.append(arg) + else: + # it's latest argument with spaces + self.arguments.append(' '.join(sys.argv[i+1:])) + break + # add empty string for missing args + self.arguments += ['']*(len(args)-i) - def handle_uri(self): - if not sys.argv[2:][0].startswith('xmpp:'): - send_error(_('Wrong uri')) - sys.argv[2] = sys.argv[2][5:] - uri = sys.argv[2:][0] - if not '?' in uri: - self.command = sys.argv[1] = 'open_chat' - return - (jid, action) = uri.split('?', 1) - sys.argv[2] = jid - if action == 'join': - self.command = sys.argv[1] = 'join_room' - # Move account parameter from position 3 to 5 - sys.argv.append('') - sys.argv.append(sys.argv[3]) - sys.argv[3] = '' - return + def handle_uri(self): + if not sys.argv[2:][0].startswith('xmpp:'): + send_error(_('Wrong uri')) + sys.argv[2] = sys.argv[2][5:] + uri = sys.argv[2:][0] + if not '?' in uri: + self.command = sys.argv[1] = 'open_chat' + return + (jid, action) = uri.split('?', 1) + sys.argv[2] = jid + if action == 'join': + self.command = sys.argv[1] = 'join_room' + # Move account parameter from position 3 to 5 + sys.argv.append('') + sys.argv.append(sys.argv[3]) + sys.argv[3] = '' + return - sys.exit(0) + sys.exit(0) - def call_remote_method(self): - ''' calls self.method with arguments from sys.argv[2:] ''' - args = [i.decode(PREFERRED_ENCODING) for i in self.arguments] - args = [dbus.String(i) for i in args] - try: - res = self.method(*args) - return res - except Exception: - raise exceptions.ServiceNotAvailable - return None + def call_remote_method(self): + ''' calls self.method with arguments from sys.argv[2:] ''' + args = [i.decode(PREFERRED_ENCODING) for i in self.arguments] + args = [dbus.String(i) for i in args] + try: + res = self.method(*args) + return res + except Exception: + raise exceptions.ServiceNotAvailable + return None if __name__ == '__main__': - GajimRemote() + GajimRemote() diff --git a/src/gajim-remote.py b/src/gajim-remote.py index 212e7644e..555319a4f 100644 --- a/src/gajim-remote.py +++ b/src/gajim-remote.py @@ -36,24 +36,24 @@ from common import exceptions from common import i18n # This installs _() function try: - PREFERRED_ENCODING = locale.getpreferredencoding() + PREFERRED_ENCODING = locale.getpreferredencoding() except Exception: - PREFERRED_ENCODING = 'UTF-8' + PREFERRED_ENCODING = 'UTF-8' def send_error(error_message): - '''Writes error message to stderr and exits''' - print >> sys.stderr, error_message.encode(PREFERRED_ENCODING) - sys.exit(1) + '''Writes error message to stderr and exits''' + print >> sys.stderr, error_message.encode(PREFERRED_ENCODING) + sys.exit(1) try: - import dbus - import dbus.service - import dbus.glib - # test if dbus-x11 is installed - bus = dbus.SessionBus() + import dbus + import dbus.service + import dbus.glib + # test if dbus-x11 is installed + bus = dbus.SessionBus() except Exception: - print str(exceptions.DbusNotSupported()) - sys.exit(1) + print str(exceptions.DbusNotSupported()) + sys.exit(1) OBJ_PATH = '/org/gajim/dbus/RemoteObject' INTERFACE = 'org.gajim.dbus.RemoteInterface' @@ -62,537 +62,535 @@ BASENAME = 'gajim-remote-plugin' class GajimRemote: - def __init__(self): - self.argv_len = len(sys.argv) - # define commands dict. Prototype : - # { - # 'command': [comment, [list of arguments] ] - # } - # - # each argument is defined as a tuple: - # (argument name, help on argument, is mandatory) - # - self.commands = { - 'help':[ - _('Shows a help on specific command'), - [ - #User gets help for the command, specified by this parameter - (_('command'), - _('show help on command'), False) - ] - ], - 'toggle_roster_appearance' : [ - _('Shows or hides the roster window'), - [] - ], - 'show_next_pending_event': [ - _('Pops up a window with the next pending event'), - [] - ], - 'list_contacts': [ - _('Prints a list of all contacts in the roster. Each contact ' - 'appears on a separate line'), - [ - (_('account'), _('show only contacts of the given account'), - False) - ] + def __init__(self): + self.argv_len = len(sys.argv) + # define commands dict. Prototype : + # { + # 'command': [comment, [list of arguments] ] + # } + # + # each argument is defined as a tuple: + # (argument name, help on argument, is mandatory) + # + self.commands = { + 'help': [ + _('Shows a help on specific command'), + [ + #User gets help for the command, specified by this parameter + (_('command'), + _('show help on command'), False) + ] + ], + 'toggle_roster_appearance': [ + _('Shows or hides the roster window'), + [] + ], + 'show_next_pending_event': [ + _('Pops up a window with the next pending event'), + [] + ], + 'list_contacts': [ + _('Prints a list of all contacts in the roster. Each contact ' + 'appears on a separate line'), + [ + (_('account'), _('show only contacts of the given account'), + False) + ] - ], - 'list_accounts': [ - _('Prints a list of registered accounts'), - [] - ], - 'change_status': [ - _('Changes the status of account or accounts'), - [ + ], + 'list_accounts': [ + _('Prints a list of registered accounts'), + [] + ], + 'change_status': [ + _('Changes the status of account or accounts'), + [ #offline, online, chat, away, xa, dnd, invisible should not be translated - (_('status'), _('one of: offline, online, chat, away, xa, dnd, invisible. If not set, use accoun\'t previous status'), False), - (_('message'), _('status message'), False), - (_('account'), _('change status of account "account". ' - 'If not specified, try to change status of all accounts that have ' - '"sync with global status" option set'), False) - ] - ], - 'set_priority': [ - _('Changes the priority of account or accounts'), - [ - (_('priority'), _('priority you want to give to the account'), - True), - (_('account'), _('change the priority of the given account. ' - 'If not specified, change status of all accounts that have' - ' "sync with global status" option set'), False) - ] - ], - 'open_chat': [ - _('Shows the chat dialog so that you can send messages to a contact'), - [ - ('jid', _('JID of the contact that you want to chat with'), - True), - (_('account'), _('if specified, contact is taken from the ' - 'contact list of this account'), False), - (_('message'), - _('message content. The account must be specified or ""'), - False) - ] - ], - 'send_chat_message':[ - _('Sends new chat message to a contact in the roster. Both OpenPGP key ' - 'and account are optional. If you want to set only \'account\', ' - 'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'), - [ - ('jid', _('JID of the contact that will receive the message'), True), - (_('message'), _('message contents'), True), - (_('pgp key'), _('if specified, the message will be encrypted ' - 'using this public key'), False), - (_('account'), _('if specified, the message will be sent ' - 'using this account'), False), - ] - ], - 'send_single_message':[ - _('Sends new single message to a contact in the roster. Both OpenPGP key ' - 'and account are optional. If you want to set only \'account\', ' - 'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'), - [ - ('jid', _('JID of the contact that will receive the message'), True), - (_('subject'), _('message subject'), True), - (_('message'), _('message contents'), True), - (_('pgp key'), _('if specified, the message will be encrypted ' - 'using this public key'), False), - (_('account'), _('if specified, the message will be sent ' - 'using this account'), False), - ] - ], - 'send_groupchat_message':[ - _('Sends new message to a groupchat you\'ve joined.'), - [ - ('room_jid', _('JID of the room that will receive the message'), True), - (_('message'), _('message contents'), True), - (_('account'), _('if specified, the message will be sent ' - 'using this account'), False), - ] - ], - 'contact_info': [ - _('Gets detailed info on a contact'), - [ - ('jid', _('JID of the contact'), True) - ] - ], - 'account_info': [ - _('Gets detailed info on a account'), - [ - ('account', _('Name of the account'), True) - ] - ], - 'send_file': [ - _('Sends file to a contact'), - [ - (_('file'), _('File path'), True), - ('jid', _('JID of the contact'), True), - (_('account'), _('if specified, file will be sent using this ' - 'account'), False) - ] - ], - 'prefs_list': [ - _('Lists all preferences and their values'), - [ ] - ], - 'prefs_put': [ - _('Sets value of \'key\' to \'value\'.'), - [ - (_('key=value'), _('\'key\' is the name of the preference, ' - '\'value\' is the value to set it to'), True) - ] - ], - 'prefs_del': [ - _('Deletes a preference item'), - [ - (_('key'), _('name of the preference to be deleted'), True) - ] - ], - 'prefs_store': [ - _('Writes the current state of Gajim preferences to the .config ' - 'file'), - [ ] - ], - 'remove_contact': [ - _('Removes contact from roster'), - [ - ('jid', _('JID of the contact'), True), - (_('account'), _('if specified, contact is taken from the ' - 'contact list of this account'), False) + (_('status'), _('one of: offline, online, chat, away, xa, dnd, invisible. If not set, use accoun\'t previous status'), False), + (_('message'), _('status message'), False), + (_('account'), _('change status of account "account". ' + 'If not specified, try to change status of all accounts that have ' + '"sync with global status" option set'), False) + ] + ], + 'set_priority': [ + _('Changes the priority of account or accounts'), + [ + (_('priority'), _('priority you want to give to the account'), + True), + (_('account'), _('change the priority of the given account. ' + 'If not specified, change status of all accounts that have' + ' "sync with global status" option set'), False) + ] + ], + 'open_chat': [ + _('Shows the chat dialog so that you can send messages to a contact'), + [ + ('jid', _('JID of the contact that you want to chat with'), + True), + (_('account'), _('if specified, contact is taken from the ' + 'contact list of this account'), False), + (_('message'), + _('message content. The account must be specified or ""'), + False) + ] + ], + 'send_chat_message': [ + _('Sends new chat message to a contact in the roster. Both OpenPGP key ' + 'and account are optional. If you want to set only \'account\', ' + 'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'), + [ + ('jid', _('JID of the contact that will receive the message'), True), + (_('message'), _('message contents'), True), + (_('pgp key'), _('if specified, the message will be encrypted ' + 'using this public key'), False), + (_('account'), _('if specified, the message will be sent ' + 'using this account'), False), + ] + ], + 'send_single_message': [ + _('Sends new single message to a contact in the roster. Both OpenPGP key ' + 'and account are optional. If you want to set only \'account\', ' + 'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'), + [ + ('jid', _('JID of the contact that will receive the message'), True), + (_('subject'), _('message subject'), True), + (_('message'), _('message contents'), True), + (_('pgp key'), _('if specified, the message will be encrypted ' + 'using this public key'), False), + (_('account'), _('if specified, the message will be sent ' + 'using this account'), False), + ] + ], + 'send_groupchat_message': [ + _('Sends new message to a groupchat you\'ve joined.'), + [ + ('room_jid', _('JID of the room that will receive the message'), True), + (_('message'), _('message contents'), True), + (_('account'), _('if specified, the message will be sent ' + 'using this account'), False), + ] + ], + 'contact_info': [ + _('Gets detailed info on a contact'), + [ + ('jid', _('JID of the contact'), True) + ] + ], + 'account_info': [ + _('Gets detailed info on a account'), + [ + ('account', _('Name of the account'), True) + ] + ], + 'send_file': [ + _('Sends file to a contact'), + [ + (_('file'), _('File path'), True), + ('jid', _('JID of the contact'), True), + (_('account'), _('if specified, file will be sent using this ' + 'account'), False) + ] + ], + 'prefs_list': [ + _('Lists all preferences and their values'), + [ ] + ], + 'prefs_put': [ + _('Sets value of \'key\' to \'value\'.'), + [ + (_('key=value'), _('\'key\' is the name of the preference, ' + '\'value\' is the value to set it to'), True) + ] + ], + 'prefs_del': [ + _('Deletes a preference item'), + [ + (_('key'), _('name of the preference to be deleted'), True) + ] + ], + 'prefs_store': [ + _('Writes the current state of Gajim preferences to the .config ' + 'file'), + [ ] + ], + 'remove_contact': [ + _('Removes contact from roster'), + [ + ('jid', _('JID of the contact'), True), + (_('account'), _('if specified, contact is taken from the ' + 'contact list of this account'), False) - ] - ], - 'add_contact': [ - _('Adds contact to roster'), - [ - (_('jid'), _('JID of the contact'), True), - (_('account'), _('Adds new contact to this account'), False) - ] - ], + ] + ], + 'add_contact': [ + _('Adds contact to roster'), + [ + (_('jid'), _('JID of the contact'), True), + (_('account'), _('Adds new contact to this account'), False) + ] + ], - 'get_status': [ - _('Returns current status (the global one unless account is specified)'), - [ - (_('account'), '', False) - ] - ], + 'get_status': [ + _('Returns current status (the global one unless account is specified)'), + [ + (_('account'), '', False) + ] + ], - 'get_status_message': [ - _('Returns current status message (the global one unless account is specified)'), - [ - (_('account'), '', False) - ] - ], + 'get_status_message': [ + _('Returns current status message (the global one unless account is specified)'), + [ + (_('account'), '', False) + ] + ], - 'get_unread_msgs_number': [ - _('Returns number of unread messages'), - [ ] - ], - 'start_chat': [ - _('Opens \'Start Chat\' dialog'), - [ - (_('account'), _('Starts chat, using this account'), True) - ] - ], - 'send_xml': [ - _('Sends custom XML'), - [ - ('xml', _('XML to send'), True), - ('account', _('Account in which the xml will be sent; ' - 'if not specified, xml will be sent to all accounts'), - False) - ] - ], - 'change_avatar': [ - _('Change the avatar'), - [ - ('picture', _('Picture to use'), True), - ('account', _('Account in which the avatar will be set; ' - 'if not specified, the avatar will be set for all accounts'), - False) - ] - ], - 'handle_uri': [ - _('Handle a xmpp:/ uri'), - [ - (_('uri'), _('URI to handle'), True), - (_('account'), _('Account in which you want to handle it'), - False), - (_('message'), _('Message content'), False) - ] - ], - 'join_room': [ - _('Join a MUC room'), - [ - (_('room'), _('Room JID'), True), - (_('nick'), _('Nickname to use'), False), - (_('password'), _('Password to enter the room'), False), - (_('account'), _('Account from which you want to enter the ' - 'room'), False) - ] - ], - 'check_gajim_running':[ - _('Check if Gajim is running'), - [] - ], - 'toggle_ipython' : [ - _('Shows or hides the ipython window'), - [] - ], + 'get_unread_msgs_number': [ + _('Returns number of unread messages'), + [ ] + ], + 'start_chat': [ + _('Opens \'Start Chat\' dialog'), + [ + (_('account'), _('Starts chat, using this account'), True) + ] + ], + 'send_xml': [ + _('Sends custom XML'), + [ + ('xml', _('XML to send'), True), + ('account', _('Account in which the xml will be sent; ' + 'if not specified, xml will be sent to all accounts'), + False) + ] + ], + 'change_avatar': [ + _('Change the avatar'), + [ + ('picture', _('Picture to use'), True), + ('account', _('Account in which the avatar will be set; ' + 'if not specified, the avatar will be set for all accounts'), + False) + ] + ], + 'handle_uri': [ + _('Handle a xmpp:/ uri'), + [ + (_('uri'), _('URI to handle'), True), + (_('account'), _('Account in which you want to handle it'), + False), + (_('message'), _('Message content'), False) + ] + ], + 'join_room': [ + _('Join a MUC room'), + [ + (_('room'), _('Room JID'), True), + (_('nick'), _('Nickname to use'), False), + (_('password'), _('Password to enter the room'), False), + (_('account'), _('Account from which you want to enter the ' + 'room'), False) + ] + ], + 'check_gajim_running': [ + _('Check if Gajim is running'), + [] + ], + 'toggle_ipython': [ + _('Shows or hides the ipython window'), + [] + ], - } + } - self.sbus = None - if self.argv_len < 2 or sys.argv[1] not in self.commands.keys(): - # no args or bad args - send_error(self.compose_help()) - self.command = sys.argv[1] - if self.command == 'help': - if self.argv_len == 3: - print self.help_on_command(sys.argv[2]).encode(PREFERRED_ENCODING) - else: - print self.compose_help().encode(PREFERRED_ENCODING) - sys.exit(0) - if self.command == 'handle_uri': - self.handle_uri() - if self.command == 'check_gajim_running': - print self.check_gajim_running() - sys.exit(0) - self.init_connection() - self.check_arguments() + self.sbus = None + if self.argv_len < 2 or sys.argv[1] not in self.commands.keys(): + # no args or bad args + send_error(self.compose_help()) + self.command = sys.argv[1] + if self.command == 'help': + if self.argv_len == 3: + print self.help_on_command(sys.argv[2]).encode(PREFERRED_ENCODING) + else: + print self.compose_help().encode(PREFERRED_ENCODING) + sys.exit(0) + if self.command == 'handle_uri': + self.handle_uri() + if self.command == 'check_gajim_running': + print self.check_gajim_running() + sys.exit(0) + self.init_connection() + self.check_arguments() - if self.command == 'contact_info': - if self.argv_len < 3: - send_error(_('Missing argument "contact_jid"')) + if self.command == 'contact_info': + if self.argv_len < 3: + send_error(_('Missing argument "contact_jid"')) - try: - res = self.call_remote_method() - except exceptions.ServiceNotAvailable: - # At this point an error message has already been displayed - sys.exit(1) - else: - self.print_result(res) + try: + res = self.call_remote_method() + except exceptions.ServiceNotAvailable: + # At this point an error message has already been displayed + sys.exit(1) + else: + self.print_result(res) - def print_result(self, res): - """ - Print retrieved result to the output - """ - if res is not None: - if self.command in ('open_chat', 'send_chat_message', 'send_single_message', 'start_chat'): - if self.command in ('send_message', 'send_single_message'): - self.argv_len -= 2 + def print_result(self, res): + """ + Print retrieved result to the output + """ + if res is not None: + if self.command in ('open_chat', 'send_chat_message', 'send_single_message', 'start_chat'): + if self.command in ('send_message', 'send_single_message'): + self.argv_len -= 2 - if res is False: - if self.argv_len < 4: - send_error(_('\'%s\' is not in your roster.\n' - 'Please specify account for sending the message.') % sys.argv[2]) - else: - send_error(_('You have no active account')) - elif self.command == 'list_accounts': - if isinstance(res, list): - for account in res: - if isinstance(account, unicode): - print account.encode(PREFERRED_ENCODING) - else: - print account - elif self.command == 'account_info': - if res: - print self.print_info(0, res, True) - elif self.command == 'list_contacts': - for account_dict in res: - print self.print_info(0, account_dict, True) - elif self.command == 'prefs_list': - pref_keys = sorted(res.keys()) - for pref_key in pref_keys: - result = '%s = %s' % (pref_key, res[pref_key]) - if isinstance(result, unicode): - print result.encode(PREFERRED_ENCODING) - else: - print result - elif self.command == 'contact_info': - print self.print_info(0, res, True) - elif res: - print unicode(res).encode(PREFERRED_ENCODING) + if res is False: + if self.argv_len < 4: + send_error(_('\'%s\' is not in your roster.\n' + 'Please specify account for sending the message.') % sys.argv[2]) + else: + send_error(_('You have no active account')) + elif self.command == 'list_accounts': + if isinstance(res, list): + for account in res: + if isinstance(account, unicode): + print account.encode(PREFERRED_ENCODING) + else: + print account + elif self.command == 'account_info': + if res: + print self.print_info(0, res, True) + elif self.command == 'list_contacts': + for account_dict in res: + print self.print_info(0, account_dict, True) + elif self.command == 'prefs_list': + pref_keys = sorted(res.keys()) + for pref_key in pref_keys: + result = '%s = %s' % (pref_key, res[pref_key]) + if isinstance(result, unicode): + print result.encode(PREFERRED_ENCODING) + else: + print result + elif self.command == 'contact_info': + print self.print_info(0, res, True) + elif res: + print unicode(res).encode(PREFERRED_ENCODING) - def check_gajim_running(self): - if not self.sbus: - try: - self.sbus = dbus.SessionBus() - except Exception: - raise exceptions.SessionBusNotPresent + def check_gajim_running(self): + if not self.sbus: + try: + self.sbus = dbus.SessionBus() + except Exception: + raise exceptions.SessionBusNotPresent - test = False - if hasattr(self.sbus, 'name_has_owner'): - if self.sbus.name_has_owner(SERVICE): - test = True - elif dbus.dbus_bindings.bus_name_has_owner(self.sbus.get_connection(), - SERVICE): - test = True - return test + test = False + if hasattr(self.sbus, 'name_has_owner'): + if self.sbus.name_has_owner(SERVICE): + test = True + elif dbus.dbus_bindings.bus_name_has_owner(self.sbus.get_connection(), + SERVICE): + test = True + return test - def init_connection(self): - """ - Create the onnection to the session dbus, or exit if it is not possible - """ - try: - self.sbus = dbus.SessionBus() - except Exception: - raise exceptions.SessionBusNotPresent + def init_connection(self): + """ + Create the onnection to the session dbus, or exit if it is not possible + """ + try: + self.sbus = dbus.SessionBus() + except Exception: + raise exceptions.SessionBusNotPresent - if not self.check_gajim_running(): - send_error(_('It seems Gajim is not running. So you can\'t use gajim-remote.')) - obj = self.sbus.get_object(SERVICE, OBJ_PATH) - interface = dbus.Interface(obj, INTERFACE) + if not self.check_gajim_running(): + send_error(_('It seems Gajim is not running. So you can\'t use gajim-remote.')) + obj = self.sbus.get_object(SERVICE, OBJ_PATH) + interface = dbus.Interface(obj, INTERFACE) - # get the function asked - self.method = interface.__getattr__(self.command) + # get the function asked + self.method = interface.__getattr__(self.command) - def make_arguments_row(self, args): - """ - Return arguments list. Mandatory arguments are enclosed with: - '<', '>', optional arguments - with '[', ']' - """ - s = '' - for arg in args: - if arg[2]: - s += ' <' + arg[0] + '>' - else: - s += ' [' + arg[0] + ']' - return s + def make_arguments_row(self, args): + """ + Return arguments list. Mandatory arguments are enclosed with: + '<', '>', optional arguments - with '[', ']' + """ + s = '' + for arg in args: + if arg[2]: + s += ' <' + arg[0] + '>' + else: + s += ' [' + arg[0] + ']' + return s - def help_on_command(self, command): - """ - Return help message for a given command - """ - if command in self.commands: - command_props = self.commands[command] - arguments_str = self.make_arguments_row(command_props[1]) - str_ = _('Usage: %(basename)s %(command)s %(arguments)s \n\t %(help)s')\ - % {'basename': BASENAME, 'command': command, - 'arguments': arguments_str, 'help': command_props[0]} - if len(command_props[1]) > 0: - str_ += '\n\n' + _('Arguments:') + '\n' - for argument in command_props[1]: - str_ += ' ' + argument[0] + ' - ' + argument[1] + '\n' - return str_ - send_error(_('%s not found') % command) + def help_on_command(self, command): + """ + Return help message for a given command + """ + if command in self.commands: + command_props = self.commands[command] + arguments_str = self.make_arguments_row(command_props[1]) + str_ = _('Usage: %(basename)s %(command)s %(arguments)s \n\t %(help)s')\ + % {'basename': BASENAME, 'command': command, + 'arguments': arguments_str, 'help': command_props[0]} + if len(command_props[1]) > 0: + str_ += '\n\n' + _('Arguments:') + '\n' + for argument in command_props[1]: + str_ += ' ' + argument[0] + ' - ' + argument[1] + '\n' + return str_ + send_error(_('%s not found') % command) - def compose_help(self): - """ - Print usage, and list available commands - """ - s = _('Usage: %s command [arguments]\nCommand is one of:\n' ) % BASENAME - for command in sorted(self.commands): - s += ' ' + command - for arg in self.commands[command][1]: - if arg[2]: - s += ' <' + arg[0] + '>' - else: - s += ' [' + arg[0] + ']' - s += '\n' - return s + def compose_help(self): + """ + Print usage, and list available commands + """ + s = _('Usage: %s command [arguments]\nCommand is one of:\n' ) % BASENAME + for command in sorted(self.commands): + s += ' ' + command + for arg in self.commands[command][1]: + if arg[2]: + s += ' <' + arg[0] + '>' + else: + s += ' [' + arg[0] + ']' + s += '\n' + return s - def print_info(self, level, prop_dict, encode_return = False): - """ - Return formated string from data structure - """ - if prop_dict is None or not isinstance(prop_dict, (dict, list, tuple)): - return '' - ret_str = '' - if isinstance(prop_dict, (list, tuple)): - ret_str = '' - spacing = ' ' * level * 4 - for val in prop_dict: - if val is None: - ret_str +='\t' - elif isinstance(val, int): - ret_str +='\t' + str(val) - elif isinstance(val, (str, unicode)): - ret_str +='\t' + val - elif isinstance(val, (list, tuple)): - res = '' - for items in val: - res += self.print_info(level+1, items) - if res != '': - ret_str += '\t' + res - elif isinstance(val, dict): - ret_str += self.print_info(level+1, val) - ret_str = '%s(%s)\n' % (spacing, ret_str[1:]) - elif isinstance(prop_dict, dict): - for key in prop_dict.keys(): - val = prop_dict[key] - spacing = ' ' * level * 4 - if isinstance(val, (unicode, int, str)): - if val is not None: - val = val.strip() - ret_str += '%s%-10s: %s\n' % (spacing, key, val) - elif isinstance(val, (list, tuple)): - res = '' - for items in val: - res += self.print_info(level+1, items) - if res != '': - ret_str += '%s%s: \n%s' % (spacing, key, res) - elif isinstance(val, dict): - res = self.print_info(level+1, val) - if res != '': - ret_str += '%s%s: \n%s' % (spacing, key, res) - if (encode_return): - try: - ret_str = ret_str.encode(PREFERRED_ENCODING) - except Exception: - pass - return ret_str + def print_info(self, level, prop_dict, encode_return = False): + """ + Return formated string from data structure + """ + if prop_dict is None or not isinstance(prop_dict, (dict, list, tuple)): + return '' + ret_str = '' + if isinstance(prop_dict, (list, tuple)): + ret_str = '' + spacing = ' ' * level * 4 + for val in prop_dict: + if val is None: + ret_str +='\t' + elif isinstance(val, int): + ret_str +='\t' + str(val) + elif isinstance(val, (str, unicode)): + ret_str +='\t' + val + elif isinstance(val, (list, tuple)): + res = '' + for items in val: + res += self.print_info(level+1, items) + if res != '': + ret_str += '\t' + res + elif isinstance(val, dict): + ret_str += self.print_info(level+1, val) + ret_str = '%s(%s)\n' % (spacing, ret_str[1:]) + elif isinstance(prop_dict, dict): + for key in prop_dict.keys(): + val = prop_dict[key] + spacing = ' ' * level * 4 + if isinstance(val, (unicode, int, str)): + if val is not None: + val = val.strip() + ret_str += '%s%-10s: %s\n' % (spacing, key, val) + elif isinstance(val, (list, tuple)): + res = '' + for items in val: + res += self.print_info(level+1, items) + if res != '': + ret_str += '%s%s: \n%s' % (spacing, key, res) + elif isinstance(val, dict): + res = self.print_info(level+1, val) + if res != '': + ret_str += '%s%s: \n%s' % (spacing, key, res) + if (encode_return): + try: + ret_str = ret_str.encode(PREFERRED_ENCODING) + except Exception: + pass + return ret_str - def check_arguments(self): - """ - Make check if all necessary arguments are given - """ - argv_len = self.argv_len - 2 - args = self.commands[self.command][1] - if len(args) < argv_len: - send_error(_('Too many arguments. \n' - 'Type "%(basename)s help %(command)s" for more info') % { - 'basename': BASENAME, 'command': self.command}) - if len(args) > argv_len: - if args[argv_len][2]: - send_error(_('Argument "%(arg)s" is not specified. \n' - 'Type "%(basename)s help %(command)s" for more info') % - {'arg': args[argv_len][0], 'basename': BASENAME, - 'command': self.command}) - self.arguments = [] - i = 0 - for arg in sys.argv[2:]: - i += 1 - if i < len(args): - self.arguments.append(arg) - else: - # it's latest argument with spaces - self.arguments.append(' '.join(sys.argv[i+1:])) - break - # add empty string for missing args - self.arguments += ['']*(len(args)-i) + def check_arguments(self): + """ + Make check if all necessary arguments are given + """ + argv_len = self.argv_len - 2 + args = self.commands[self.command][1] + if len(args) < argv_len: + send_error(_('Too many arguments. \n' + 'Type "%(basename)s help %(command)s" for more info') % { + 'basename': BASENAME, 'command': self.command}) + if len(args) > argv_len: + if args[argv_len][2]: + send_error(_('Argument "%(arg)s" is not specified. \n' + 'Type "%(basename)s help %(command)s" for more info') % + {'arg': args[argv_len][0], 'basename': BASENAME, + 'command': self.command}) + self.arguments = [] + i = 0 + for arg in sys.argv[2:]: + i += 1 + if i < len(args): + self.arguments.append(arg) + else: + # it's latest argument with spaces + self.arguments.append(' '.join(sys.argv[i+1:])) + break + # add empty string for missing args + self.arguments += ['']*(len(args)-i) - def handle_uri(self): - if not sys.argv[2].startswith('xmpp:'): - send_error(_('Wrong uri')) - sys.argv[2] = sys.argv[2][5:] - uri = sys.argv[2] - if not '?' in uri: - self.command = sys.argv[1] = 'open_chat' - return - if 'body=' in uri: - # Open chat window and paste the text in the input message dialog - self.command = sys.argv[1] = 'open_chat' - message = uri.split('body=') - message = message[1].split(';')[0] - try: - message = urllib.unquote(message) - except UnicodeDecodeError: - pass - sys.argv[2] = uri.split('?')[0] - if len(sys.argv) == 4: - # jid in the sys.argv - sys.argv.append(message) - else: - sys.argv.append('') - sys.argv.append(message) - sys.argv[3] = '' - sys.argv[4] = message - return - (jid, action) = uri.split('?', 1) - try: - jid = urllib.unquote(jid) - except UnicodeDecodeError: - pass - sys.argv[2] = jid - if action == 'join': - self.command = sys.argv[1] = 'join_room' - # Move account parameter from position 3 to 5 - sys.argv.append('') - sys.argv.append(sys.argv[3]) - sys.argv[3] = '' - return - if action.startswith('roster'): - # Add contact to roster - self.command = sys.argv[1] = 'add_contact' - return - sys.exit(0) + def handle_uri(self): + if not sys.argv[2].startswith('xmpp:'): + send_error(_('Wrong uri')) + sys.argv[2] = sys.argv[2][5:] + uri = sys.argv[2] + if not '?' in uri: + self.command = sys.argv[1] = 'open_chat' + return + if 'body=' in uri: + # Open chat window and paste the text in the input message dialog + self.command = sys.argv[1] = 'open_chat' + message = uri.split('body=') + message = message[1].split(';')[0] + try: + message = urllib.unquote(message) + except UnicodeDecodeError: + pass + sys.argv[2] = uri.split('?')[0] + if len(sys.argv) == 4: + # jid in the sys.argv + sys.argv.append(message) + else: + sys.argv.append('') + sys.argv.append(message) + sys.argv[3] = '' + sys.argv[4] = message + return + (jid, action) = uri.split('?', 1) + try: + jid = urllib.unquote(jid) + except UnicodeDecodeError: + pass + sys.argv[2] = jid + if action == 'join': + self.command = sys.argv[1] = 'join_room' + # Move account parameter from position 3 to 5 + sys.argv.append('') + sys.argv.append(sys.argv[3]) + sys.argv[3] = '' + return + if action.startswith('roster'): + # Add contact to roster + self.command = sys.argv[1] = 'add_contact' + return + sys.exit(0) - def call_remote_method(self): - """ - Calls self.method with arguments from sys.argv[2:] - """ - args = [i.decode(PREFERRED_ENCODING) for i in self.arguments] - args = [dbus.String(i) for i in args] - try: - res = self.method(*args) - return res - except Exception: - raise exceptions.ServiceNotAvailable - return None + def call_remote_method(self): + """ + Calls self.method with arguments from sys.argv[2:] + """ + args = [i.decode(PREFERRED_ENCODING) for i in self.arguments] + args = [dbus.String(i) for i in args] + try: + res = self.method(*args) + return res + except Exception: + raise exceptions.ServiceNotAvailable + return None if __name__ == '__main__': - GajimRemote() - -# vim: se ts=3: + GajimRemote() diff --git a/src/gajim.py b/src/gajim.py index bff8085a5..e3c9bb5a2 100644 --- a/src/gajim.py +++ b/src/gajim.py @@ -40,24 +40,24 @@ import sys import warnings if os.name == 'nt': - warnings.filterwarnings(action='ignore') + warnings.filterwarnings(action='ignore') - if os.path.isdir('gtk'): - # Used to create windows installer with GTK included - paths = os.environ['PATH'] - list_ = paths.split(';') - new_list = [] - for p in list_: - if p.find('gtk') < 0 and p.find('GTK') < 0: - new_list.append(p) - new_list.insert(0, 'gtk/lib') - new_list.insert(0, 'gtk/bin') - os.environ['PATH'] = ';'.join(new_list) - os.environ['GTK_BASEPATH'] = 'gtk' + if os.path.isdir('gtk'): + # Used to create windows installer with GTK included + paths = os.environ['PATH'] + list_ = paths.split(';') + new_list = [] + for p in list_: + if p.find('gtk') < 0 and p.find('GTK') < 0: + new_list.append(p) + new_list.insert(0, 'gtk/lib') + new_list.insert(0, 'gtk/bin') + os.environ['PATH'] = ';'.join(new_list) + os.environ['GTK_BASEPATH'] = 'gtk' if os.name == 'nt': - # needed for docutils - sys.path.append('.') + # needed for docutils + sys.path.append('.') from common import logging_helpers logging_helpers.init('TERM' in os.environ) @@ -70,32 +70,32 @@ import getopt from common import i18n def parseOpts(): - profile = '' - config_path = None + profile = '' + config_path = None - try: - shortargs = 'hqvl:p:c:' - longargs = 'help quiet verbose loglevel= profile= config_path=' - opts = getopt.getopt(sys.argv[1:], shortargs, longargs.split())[0] - except getopt.error, msg: - print msg - print 'for help use --help' - sys.exit(2) - for o, a in opts: - if o in ('-h', '--help'): - print 'gajim [--help] [--quiet] [--verbose] [--loglevel subsystem=level[,subsystem=level[...]]] [--profile name] [--config-path]' - sys.exit() - elif o in ('-q', '--quiet'): - logging_helpers.set_quiet() - elif o in ('-v', '--verbose'): - logging_helpers.set_verbose() - elif o in ('-p', '--profile'): # gajim --profile name - profile = a - elif o in ('-l', '--loglevel'): - logging_helpers.set_loglevels(a) - elif o in ('-c', '--config-path'): - config_path = a - return profile, config_path + try: + shortargs = 'hqvl:p:c:' + longargs = 'help quiet verbose loglevel= profile= config_path=' + opts = getopt.getopt(sys.argv[1:], shortargs, longargs.split())[0] + except getopt.error, msg: + print msg + print 'for help use --help' + sys.exit(2) + for o, a in opts: + if o in ('-h', '--help'): + print 'gajim [--help] [--quiet] [--verbose] [--loglevel subsystem=level[,subsystem=level[...]]] [--profile name] [--config-path]' + sys.exit() + elif o in ('-q', '--quiet'): + logging_helpers.set_quiet() + elif o in ('-v', '--verbose'): + logging_helpers.set_verbose() + elif o in ('-p', '--profile'): # gajim --profile name + profile = a + elif o in ('-l', '--loglevel'): + logging_helpers.set_loglevels(a) + elif o in ('-c', '--config-path'): + config_path = a + return profile, config_path profile, config_path = parseOpts() del parseOpts @@ -110,102 +110,102 @@ common.configpaths.gajimpaths.init_profile(profile) del profile if os.name == 'nt': - class MyStderr(object): - _file = None - _error = None - def write(self, text): - fname=os.path.join(common.configpaths.gajimpaths.cache_root, - os.path.split(sys.executable)[1]+'.log') - if self._file is None and self._error is None: - try: - self._file = open(fname, 'a') - except Exception, details: - self._error = details - if self._file is not None: - self._file.write(text) - self._file.flush() - def flush(self): - if self._file is not None: - self._file.flush() + class MyStderr(object): + _file = None + _error = None + def write(self, text): + fname=os.path.join(common.configpaths.gajimpaths.cache_root, + os.path.split(sys.executable)[1]+'.log') + if self._file is None and self._error is None: + try: + self._file = open(fname, 'a') + except Exception, details: + self._error = details + if self._file is not None: + self._file.write(text) + self._file.flush() + def flush(self): + if self._file is not None: + self._file.flush() - sys.stderr = MyStderr() + sys.stderr = MyStderr() # PyGTK2.10+ only throws a warning warnings.filterwarnings('error', module='gtk') try: - import gtk + import gtk except Warning, msg: - if str(msg) == 'could not open display': - print >> sys.stderr, _('Gajim needs X server to run. Quiting...') - else: - print >> sys.stderr, _('importing PyGTK failed: %s') % str(msg) - sys.exit() + if str(msg) == 'could not open display': + print >> sys.stderr, _('Gajim needs X server to run. Quiting...') + else: + print >> sys.stderr, _('importing PyGTK failed: %s') % str(msg) + sys.exit() warnings.resetwarnings() if os.name == 'nt': - warnings.filterwarnings(action='ignore') + warnings.filterwarnings(action='ignore') pritext = '' from common import exceptions try: - from common import gajim + from common import gajim except exceptions.DatabaseMalformed: - pritext = _('Database Error') - sectext = _('The database file (%s) cannot be read. Try to repair it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove it (all history will be lost).') % common.logger.LOG_DB_PATH + pritext = _('Database Error') + sectext = _('The database file (%s) cannot be read. Try to repair it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove it (all history will be lost).') % common.logger.LOG_DB_PATH else: - from common import dbus_support - if dbus_support.supported: - from music_track_listener import MusicTrackListener - import dbus + from common import dbus_support + if dbus_support.supported: + from music_track_listener import MusicTrackListener + import dbus - from ctypes import CDLL - from ctypes.util import find_library - import platform + from ctypes import CDLL + from ctypes.util import find_library + import platform - sysname = platform.system() - if sysname in ('Linux', 'FreeBSD', 'OpenBSD', 'NetBSD'): - libc = CDLL(find_library('c')) + sysname = platform.system() + if sysname in ('Linux', 'FreeBSD', 'OpenBSD', 'NetBSD'): + libc = CDLL(find_library('c')) - # The constant defined in which is used to set the name of - # the process. - PR_SET_NAME = 15 + # The constant defined in which is used to set the name of + # the process. + PR_SET_NAME = 15 - if sysname == 'Linux': - libc.prctl(PR_SET_NAME, 'gajim') - elif sysname in ('FreeBSD', 'OpenBSD', 'NetBSD'): - libc.setproctitle('gajim') + if sysname == 'Linux': + libc.prctl(PR_SET_NAME, 'gajim') + elif sysname in ('FreeBSD', 'OpenBSD', 'NetBSD'): + libc.setproctitle('gajim') - if gtk.pygtk_version < (2, 16, 0): - pritext = _('Gajim needs PyGTK 2.16 or above') - sectext = _('Gajim needs PyGTK 2.16 or above to run. Quiting...') - elif gtk.gtk_version < (2, 16, 0): - pritext = _('Gajim needs GTK 2.16 or above') - sectext = _('Gajim needs GTK 2.16 or above to run. Quiting...') + if gtk.pygtk_version < (2, 16, 0): + pritext = _('Gajim needs PyGTK 2.16 or above') + sectext = _('Gajim needs PyGTK 2.16 or above to run. Quiting...') + elif gtk.gtk_version < (2, 16, 0): + pritext = _('Gajim needs GTK 2.16 or above') + sectext = _('Gajim needs GTK 2.16 or above to run. Quiting...') - try: - from common import check_paths - except exceptions.PysqliteNotAvailable, e: - pritext = _('Gajim needs PySQLite2 to run') - sectext = str(e) + try: + from common import check_paths + except exceptions.PysqliteNotAvailable, e: + pritext = _('Gajim needs PySQLite2 to run') + sectext = str(e) - if os.name == 'nt': - try: - import winsound # windows-only built-in module for playing wav - import win32api # do NOT remove. we req this module - except Exception: - pritext = _('Gajim needs pywin32 to run') - sectext = _('Please make sure that Pywin32 is installed on your system. You can get it at %s') % 'http://sourceforge.net/project/showfiles.php?group_id=78018' + if os.name == 'nt': + try: + import winsound # windows-only built-in module for playing wav + import win32api # do NOT remove. we req this module + except Exception: + pritext = _('Gajim needs pywin32 to run') + sectext = _('Please make sure that Pywin32 is installed on your system. You can get it at %s') % 'http://sourceforge.net/project/showfiles.php?group_id=78018' if pritext: - dlg = gtk.MessageDialog(None, - gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, - gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message_format = pritext) + dlg = gtk.MessageDialog(None, + gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, + gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message_format = pritext) - dlg.format_secondary_text(sectext) - dlg.run() - dlg.destroy() - sys.exit() + dlg.format_secondary_text(sectext) + dlg.run() + dlg.destroy() + sys.exit() del pritext @@ -213,9 +213,9 @@ import gtkexcepthook import gobject if not hasattr(gobject, 'timeout_add_seconds'): - def timeout_add_seconds_fake(time_sec, *args): - return gobject.timeout_add(time_sec * 1000, *args) - gobject.timeout_add_seconds = timeout_add_seconds_fake + def timeout_add_seconds_fake(time_sec, *args): + return gobject.timeout_add(time_sec * 1000, *args) + gobject.timeout_add_seconds = timeout_add_seconds_fake import signal @@ -231,184 +231,182 @@ import errno import dialogs def pid_alive(): - try: - pf = open(pid_filename) - except IOError: - # probably file not found - return False + try: + pf = open(pid_filename) + except IOError: + # probably file not found + return False - try: - pid = int(pf.read().strip()) - pf.close() - except Exception: - traceback.print_exc() - # PID file exists, but something happened trying to read PID - # Could be 0.10 style empty PID file, so assume Gajim is running - return True + try: + pid = int(pf.read().strip()) + pf.close() + except Exception: + traceback.print_exc() + # PID file exists, but something happened trying to read PID + # Could be 0.10 style empty PID file, so assume Gajim is running + return True - if os.name == 'nt': - try: - from ctypes import (windll, c_ulong, c_int, Structure, c_char, POINTER, pointer, ) - except Exception: - return True + if os.name == 'nt': + try: + from ctypes import (windll, c_ulong, c_int, Structure, c_char, POINTER, pointer, ) + except Exception: + return True - class PROCESSENTRY32(Structure): - _fields_ = [ - ('dwSize', c_ulong, ), - ('cntUsage', c_ulong, ), - ('th32ProcessID', c_ulong, ), - ('th32DefaultHeapID', c_ulong, ), - ('th32ModuleID', c_ulong, ), - ('cntThreads', c_ulong, ), - ('th32ParentProcessID', c_ulong, ), - ('pcPriClassBase', c_ulong, ), - ('dwFlags', c_ulong, ), - ('szExeFile', c_char*512, ), - ] - def __init__(self): - Structure.__init__(self, 512+9*4) + class PROCESSENTRY32(Structure): + _fields_ = [ + ('dwSize', c_ulong, ), + ('cntUsage', c_ulong, ), + ('th32ProcessID', c_ulong, ), + ('th32DefaultHeapID', c_ulong, ), + ('th32ModuleID', c_ulong, ), + ('cntThreads', c_ulong, ), + ('th32ParentProcessID', c_ulong, ), + ('pcPriClassBase', c_ulong, ), + ('dwFlags', c_ulong, ), + ('szExeFile', c_char*512, ), + ] + def __init__(self): + Structure.__init__(self, 512+9*4) - k = windll.kernel32 - k.CreateToolhelp32Snapshot.argtypes = c_ulong, c_ulong, - k.CreateToolhelp32Snapshot.restype = c_int - k.Process32First.argtypes = c_int, POINTER(PROCESSENTRY32), - k.Process32First.restype = c_int - k.Process32Next.argtypes = c_int, POINTER(PROCESSENTRY32), - k.Process32Next.restype = c_int + k = windll.kernel32 + k.CreateToolhelp32Snapshot.argtypes = c_ulong, c_ulong, + k.CreateToolhelp32Snapshot.restype = c_int + k.Process32First.argtypes = c_int, POINTER(PROCESSENTRY32), + k.Process32First.restype = c_int + k.Process32Next.argtypes = c_int, POINTER(PROCESSENTRY32), + k.Process32Next.restype = c_int - def get_p(p): - h = k.CreateToolhelp32Snapshot(2, 0) # TH32CS_SNAPPROCESS - assert h > 0, 'CreateToolhelp32Snapshot failed' - b = pointer(PROCESSENTRY32()) - f = k.Process32First(h, b) - while f: - if b.contents.th32ProcessID == p: - return b.contents.szExeFile - f = k.Process32Next(h, b) + def get_p(p): + h = k.CreateToolhelp32Snapshot(2, 0) # TH32CS_SNAPPROCESS + assert h > 0, 'CreateToolhelp32Snapshot failed' + b = pointer(PROCESSENTRY32()) + f = k.Process32First(h, b) + while f: + if b.contents.th32ProcessID == p: + return b.contents.szExeFile + f = k.Process32Next(h, b) - if get_p(pid) in ('python.exe', 'gajim.exe'): - return True - return False - try: - if not os.path.exists('/proc'): - return True # no /proc, assume Gajim is running + if get_p(pid) in ('python.exe', 'gajim.exe'): + return True + return False + try: + if not os.path.exists('/proc'): + return True # no /proc, assume Gajim is running - try: - f = open('/proc/%d/cmdline'% pid) - except IOError, e: - if e.errno == errno.ENOENT: - return False # file/pid does not exist - raise + try: + f = open('/proc/%d/cmdline'% pid) + except IOError, e: + if e.errno == errno.ENOENT: + return False # file/pid does not exist + raise - n = f.read().lower() - f.close() - if n.find('gajim') < 0: - return False - return True # Running Gajim found at pid - except Exception: - traceback.print_exc() + n = f.read().lower() + f.close() + if n.find('gajim') < 0: + return False + return True # Running Gajim found at pid + except Exception: + traceback.print_exc() - # If we are here, pidfile exists, but some unexpected error occured. - # Assume Gajim is running. - return True + # If we are here, pidfile exists, but some unexpected error occured. + # Assume Gajim is running. + return True if pid_alive(): - pix = gtkgui_helpers.get_icon_pixmap('gajim', 48) - gtk.window_set_default_icon(pix) # set the icon to all newly opened wind - pritext = _('Gajim is already running') - sectext = _('Another instance of Gajim seems to be running\nRun anyway?') - dialog = dialogs.YesNoDialog(pritext, sectext) - dialog.popup() - if dialog.run() != gtk.RESPONSE_YES: - sys.exit(3) - dialog.destroy() - # run anyway, delete pid and useless global vars - if os.path.exists(pid_filename): - os.remove(pid_filename) - del path_to_file - del pix - del pritext - del sectext - dialog.destroy() + pix = gtkgui_helpers.get_icon_pixmap('gajim', 48) + gtk.window_set_default_icon(pix) # set the icon to all newly opened wind + pritext = _('Gajim is already running') + sectext = _('Another instance of Gajim seems to be running\nRun anyway?') + dialog = dialogs.YesNoDialog(pritext, sectext) + dialog.popup() + if dialog.run() != gtk.RESPONSE_YES: + sys.exit(3) + dialog.destroy() + # run anyway, delete pid and useless global vars + if os.path.exists(pid_filename): + os.remove(pid_filename) + del path_to_file + del pix + del pritext + del sectext + dialog.destroy() # Create .gajim dir pid_dir = os.path.dirname(pid_filename) if not os.path.exists(pid_dir): - check_paths.create_path(pid_dir) + check_paths.create_path(pid_dir) # Create pid file try: - f = open(pid_filename, 'w') - f.write(str(os.getpid())) - f.close() + f = open(pid_filename, 'w') + f.write(str(os.getpid())) + f.close() except IOError, e: - dlg = dialogs.ErrorDialog(_('Disk Write Error'), str(e)) - dlg.run() - dlg.destroy() - sys.exit() + dlg = dialogs.ErrorDialog(_('Disk Write Error'), str(e)) + dlg.run() + dlg.destroy() + sys.exit() del pid_dir del f def on_exit(): - # delete pid file on normal exit - if os.path.exists(pid_filename): - os.remove(pid_filename) - # Shutdown GUI and save config - if hasattr(gajim.interface, 'roster'): - gajim.interface.roster.prepare_quit() + # delete pid file on normal exit + if os.path.exists(pid_filename): + os.remove(pid_filename) + # Shutdown GUI and save config + if hasattr(gajim.interface, 'roster'): + gajim.interface.roster.prepare_quit() import atexit atexit.register(on_exit) - + from gui_interface import Interface if __name__ == '__main__': - def sigint_cb(num, stack): - sys.exit(5) - # ^C exits the application normally to delete pid file - signal.signal(signal.SIGINT, sigint_cb) + def sigint_cb(num, stack): + sys.exit(5) + # ^C exits the application normally to delete pid file + signal.signal(signal.SIGINT, sigint_cb) - log.info("Encodings: d:%s, fs:%s, p:%s", sys.getdefaultencoding(), \ - sys.getfilesystemencoding(), locale.getpreferredencoding()) + log.info("Encodings: d:%s, fs:%s, p:%s", sys.getdefaultencoding(), \ + sys.getfilesystemencoding(), locale.getpreferredencoding()) - if os.name != 'nt': - # Session Management support - try: - import gnome.ui - raise ImportError - except ImportError: - pass - else: - def die_cb(cli): - gajim.interface.roster.quit_gtkgui_interface() - gnome.program_init('gajim', gajim.version) - cli = gnome.ui.master_client() - cli.connect('die', die_cb) + if os.name != 'nt': + # Session Management support + try: + import gnome.ui + raise ImportError + except ImportError: + pass + else: + def die_cb(cli): + gajim.interface.roster.quit_gtkgui_interface() + gnome.program_init('gajim', gajim.version) + cli = gnome.ui.master_client() + cli.connect('die', die_cb) - path_to_gajim_script = gtkgui_helpers.get_abspath_for_script( - 'gajim') + path_to_gajim_script = gtkgui_helpers.get_abspath_for_script( + 'gajim') - if path_to_gajim_script: - argv = [path_to_gajim_script] - # FIXME: remove this typeerror catch when gnome python is old and - # not bad patched by distro men [2.12.0 + should not need all that - # NORMALLY] - try: - cli.set_restart_command(argv) - except AttributeError: - cli.set_restart_command(len(argv), argv) + if path_to_gajim_script: + argv = [path_to_gajim_script] + # FIXME: remove this typeerror catch when gnome python is old and + # not bad patched by distro men [2.12.0 + should not need all that + # NORMALLY] + try: + cli.set_restart_command(argv) + except AttributeError: + cli.set_restart_command(len(argv), argv) - check_paths.check_and_possibly_create_paths() + check_paths.check_and_possibly_create_paths() - interface = Interface() - interface.run() + interface = Interface() + interface.run() - try: - if os.name != 'nt': - # This makes Gajim unusable under windows, and threads are used only - # for GPG, so not under windows - gtk.gdk.threads_init() - gtk.main() - except KeyboardInterrupt: - print >> sys.stderr, 'KeyboardInterrupt' - -# vim: se ts=3: + try: + if os.name != 'nt': + # This makes Gajim unusable under windows, and threads are used only + # for GPG, so not under windows + gtk.gdk.threads_init() + gtk.main() + except KeyboardInterrupt: + print >> sys.stderr, 'KeyboardInterrupt' diff --git a/src/gajim_themes_window.py b/src/gajim_themes_window.py index b754b7226..5d83de43b 100644 --- a/src/gajim_themes_window.py +++ b/src/gajim_themes_window.py @@ -31,378 +31,376 @@ from common import gajim class GajimThemesWindow: - def __init__(self): - self.xml = gtkgui_helpers.get_gtk_builder('gajim_themes_window.ui') - self.window = self.xml.get_object('gajim_themes_window') - self.window.set_transient_for(gajim.interface.roster.window) + def __init__(self): + self.xml = gtkgui_helpers.get_gtk_builder('gajim_themes_window.ui') + self.window = self.xml.get_object('gajim_themes_window') + self.window.set_transient_for(gajim.interface.roster.window) - self.options = ['account', 'group', 'contact', 'banner'] - self.options_combobox = self.xml.get_object('options_combobox') - self.textcolor_checkbutton = self.xml.get_object('textcolor_checkbutton') - self.background_checkbutton = self.xml.get_object('background_checkbutton') - self.textfont_checkbutton = self.xml.get_object('textfont_checkbutton') - self.text_colorbutton = self.xml.get_object('text_colorbutton') - self.background_colorbutton = self.xml.get_object('background_colorbutton') - self.text_fontbutton = self.xml.get_object('text_fontbutton') - self.bold_togglebutton = self.xml.get_object('bold_togglebutton') - self.italic_togglebutton = self.xml.get_object('italic_togglebutton') - self.themes_tree = self.xml.get_object('themes_treeview') - self.theme_options_vbox = self.xml.get_object('theme_options_vbox') - self.theme_options_table = self.xml.get_object('theme_options_table') - self.colorbuttons = {} - for chatstate in ('inactive', 'composing', 'paused', 'gone', - 'muc_msg', 'muc_directed_msg'): - self.colorbuttons[chatstate] = self.xml.get_object(chatstate + \ - '_colorbutton') - model = gtk.ListStore(str) - self.themes_tree.set_model(model) - col = gtk.TreeViewColumn(_('Theme')) - self.themes_tree.append_column(col) - renderer = gtk.CellRendererText() - col.pack_start(renderer, True) - col.set_attributes(renderer, text = 0) - renderer.connect('edited', self.on_theme_cell_edited) - renderer.set_property('editable', True) - self.current_theme = gajim.config.get('roster_theme') - self.no_update = False - self.fill_themes_treeview() - self.select_active_theme() - self.current_option = self.options[0] - self.set_theme_options(self.current_theme, self.current_option) + self.options = ['account', 'group', 'contact', 'banner'] + self.options_combobox = self.xml.get_object('options_combobox') + self.textcolor_checkbutton = self.xml.get_object('textcolor_checkbutton') + self.background_checkbutton = self.xml.get_object('background_checkbutton') + self.textfont_checkbutton = self.xml.get_object('textfont_checkbutton') + self.text_colorbutton = self.xml.get_object('text_colorbutton') + self.background_colorbutton = self.xml.get_object('background_colorbutton') + self.text_fontbutton = self.xml.get_object('text_fontbutton') + self.bold_togglebutton = self.xml.get_object('bold_togglebutton') + self.italic_togglebutton = self.xml.get_object('italic_togglebutton') + self.themes_tree = self.xml.get_object('themes_treeview') + self.theme_options_vbox = self.xml.get_object('theme_options_vbox') + self.theme_options_table = self.xml.get_object('theme_options_table') + self.colorbuttons = {} + for chatstate in ('inactive', 'composing', 'paused', 'gone', + 'muc_msg', 'muc_directed_msg'): + self.colorbuttons[chatstate] = self.xml.get_object(chatstate + \ + '_colorbutton') + model = gtk.ListStore(str) + self.themes_tree.set_model(model) + col = gtk.TreeViewColumn(_('Theme')) + self.themes_tree.append_column(col) + renderer = gtk.CellRendererText() + col.pack_start(renderer, True) + col.set_attributes(renderer, text = 0) + renderer.connect('edited', self.on_theme_cell_edited) + renderer.set_property('editable', True) + self.current_theme = gajim.config.get('roster_theme') + self.no_update = False + self.fill_themes_treeview() + self.select_active_theme() + self.current_option = self.options[0] + self.set_theme_options(self.current_theme, self.current_option) - self.xml.connect_signals(self) - self.window.connect('delete-event', self.on_themese_window_delete_event) - self.themes_tree.get_selection().connect('changed', - self.selection_changed) - self.window.show_all() + self.xml.connect_signals(self) + self.window.connect('delete-event', self.on_themese_window_delete_event) + self.themes_tree.get_selection().connect('changed', + self.selection_changed) + self.window.show_all() - def on_themese_window_delete_event(self, widget, event): - self.window.hide() - return True # do NOT destroy the window + def on_themese_window_delete_event(self, widget, event): + self.window.hide() + return True # do NOT destroy the window - def on_close_button_clicked(self, widget): - if 'preferences' in gajim.interface.instances: - gajim.interface.instances['preferences'].update_theme_list() - self.window.hide() + def on_close_button_clicked(self, widget): + if 'preferences' in gajim.interface.instances: + gajim.interface.instances['preferences'].update_theme_list() + self.window.hide() - def on_theme_cell_edited(self, cell, row, new_name): - model = self.themes_tree.get_model() - iter_ = model.get_iter_from_string(row) - old_name = model.get_value(iter_, 0).decode('utf-8') - new_name = new_name.decode('utf-8') - if old_name == new_name: - return - if old_name == 'default': - dialogs.ErrorDialog( - _('You cannot make changes to the default theme'), - _('Please create a clean new theme with your desired name.')) - return - new_config_name = new_name.replace(' ', '_') - if new_config_name in gajim.config.get_per('themes'): - return - gajim.config.add_per('themes', new_config_name) - # Copy old theme values - old_config_name = old_name.replace(' ', '_') - properties = ['textcolor', 'bgcolor', 'font', 'fontattrs'] - gajim.config.add_per('themes', new_config_name) - for option in self.options: - for property_ in properties: - option_name = option + property_ - gajim.config.set_per('themes', new_config_name, option_name, - gajim.config.get_per('themes', old_config_name, option_name)) - gajim.config.del_per('themes', old_config_name) - if old_config_name == gajim.config.get('roster_theme'): - gajim.config.set('roster_theme', new_config_name) - model.set_value(iter_, 0, new_name) - self.current_theme = new_name + def on_theme_cell_edited(self, cell, row, new_name): + model = self.themes_tree.get_model() + iter_ = model.get_iter_from_string(row) + old_name = model.get_value(iter_, 0).decode('utf-8') + new_name = new_name.decode('utf-8') + if old_name == new_name: + return + if old_name == 'default': + dialogs.ErrorDialog( + _('You cannot make changes to the default theme'), + _('Please create a clean new theme with your desired name.')) + return + new_config_name = new_name.replace(' ', '_') + if new_config_name in gajim.config.get_per('themes'): + return + gajim.config.add_per('themes', new_config_name) + # Copy old theme values + old_config_name = old_name.replace(' ', '_') + properties = ['textcolor', 'bgcolor', 'font', 'fontattrs'] + gajim.config.add_per('themes', new_config_name) + for option in self.options: + for property_ in properties: + option_name = option + property_ + gajim.config.set_per('themes', new_config_name, option_name, + gajim.config.get_per('themes', old_config_name, option_name)) + gajim.config.del_per('themes', old_config_name) + if old_config_name == gajim.config.get('roster_theme'): + gajim.config.set('roster_theme', new_config_name) + model.set_value(iter_, 0, new_name) + self.current_theme = new_name - def fill_themes_treeview(self): - model = self.themes_tree.get_model() - model.clear() - for config_theme in gajim.config.get_per('themes'): - theme = config_theme.replace('_', ' ') - model.append([theme]) + def fill_themes_treeview(self): + model = self.themes_tree.get_model() + model.clear() + for config_theme in gajim.config.get_per('themes'): + theme = config_theme.replace('_', ' ') + model.append([theme]) - def select_active_theme(self): - model = self.themes_tree.get_model() - iter_ = model.get_iter_root() - active_theme = gajim.config.get('roster_theme').replace('_', ' ') - while iter_: - theme = model[iter_][0] - if theme == active_theme: - self.themes_tree.get_selection().select_iter(iter_) - if active_theme == 'default': - self.xml.get_object('remove_button').set_sensitive(False) - self.theme_options_vbox.set_sensitive(False) - self.theme_options_table.set_sensitive(False) - else: - self.xml.get_object('remove_button').set_sensitive(True) - self.theme_options_vbox.set_sensitive(True) - self.theme_options_table.set_sensitive(True) - break - iter_ = model.iter_next(iter_) + def select_active_theme(self): + model = self.themes_tree.get_model() + iter_ = model.get_iter_root() + active_theme = gajim.config.get('roster_theme').replace('_', ' ') + while iter_: + theme = model[iter_][0] + if theme == active_theme: + self.themes_tree.get_selection().select_iter(iter_) + if active_theme == 'default': + self.xml.get_object('remove_button').set_sensitive(False) + self.theme_options_vbox.set_sensitive(False) + self.theme_options_table.set_sensitive(False) + else: + self.xml.get_object('remove_button').set_sensitive(True) + self.theme_options_vbox.set_sensitive(True) + self.theme_options_table.set_sensitive(True) + break + iter_ = model.iter_next(iter_) - def selection_changed(self, widget = None): - (model, iter_) = self.themes_tree.get_selection().get_selected() - selected = self.themes_tree.get_selection().get_selected_rows() - if not iter_ or selected[1] == []: - self.theme_options_vbox.set_sensitive(False) - self.theme_options_table.set_sensitive(False) - return - self.current_theme = model.get_value(iter_, 0).decode('utf-8') - self.current_theme = self.current_theme.replace(' ', '_') - self.set_theme_options(self.current_theme) - if self.current_theme == 'default': - self.xml.get_object('remove_button').set_sensitive(False) - self.theme_options_vbox.set_sensitive(False) - self.theme_options_table.set_sensitive(False) - else: - self.xml.get_object('remove_button').set_sensitive(True) - self.theme_options_vbox.set_sensitive(True) - self.theme_options_table.set_sensitive(True) + def selection_changed(self, widget = None): + (model, iter_) = self.themes_tree.get_selection().get_selected() + selected = self.themes_tree.get_selection().get_selected_rows() + if not iter_ or selected[1] == []: + self.theme_options_vbox.set_sensitive(False) + self.theme_options_table.set_sensitive(False) + return + self.current_theme = model.get_value(iter_, 0).decode('utf-8') + self.current_theme = self.current_theme.replace(' ', '_') + self.set_theme_options(self.current_theme) + if self.current_theme == 'default': + self.xml.get_object('remove_button').set_sensitive(False) + self.theme_options_vbox.set_sensitive(False) + self.theme_options_table.set_sensitive(False) + else: + self.xml.get_object('remove_button').set_sensitive(True) + self.theme_options_vbox.set_sensitive(True) + self.theme_options_table.set_sensitive(True) - def on_add_button_clicked(self, widget): - model = self.themes_tree.get_model() - iter_ = model.append() - i = 0 - # don't confuse translators - theme_name = _('theme name') - theme_name_ns = theme_name.replace(' ', '_') - while theme_name_ns + unicode(i) in gajim.config.get_per('themes'): - i += 1 - model.set_value(iter_, 0, theme_name + unicode(i)) - gajim.config.add_per('themes', theme_name_ns + unicode(i)) - self.themes_tree.get_selection().select_iter(iter_) - col = self.themes_tree.get_column(0) - path = model.get_path(iter_) - self.themes_tree.set_cursor(path, col, True) + def on_add_button_clicked(self, widget): + model = self.themes_tree.get_model() + iter_ = model.append() + i = 0 + # don't confuse translators + theme_name = _('theme name') + theme_name_ns = theme_name.replace(' ', '_') + while theme_name_ns + unicode(i) in gajim.config.get_per('themes'): + i += 1 + model.set_value(iter_, 0, theme_name + unicode(i)) + gajim.config.add_per('themes', theme_name_ns + unicode(i)) + self.themes_tree.get_selection().select_iter(iter_) + col = self.themes_tree.get_column(0) + path = model.get_path(iter_) + self.themes_tree.set_cursor(path, col, True) - def on_remove_button_clicked(self, widget): - (model, iter_) = self.themes_tree.get_selection().get_selected() - if not iter_: - return - if self.current_theme == gajim.config.get('roster_theme'): - dialogs.ErrorDialog( - _('You cannot delete your current theme'), - _('Please first choose another for your current theme.')) - return - self.theme_options_vbox.set_sensitive(False) - self.theme_options_table.set_sensitive(False) - self.xml.get_object('remove_button').set_sensitive(False) - gajim.config.del_per('themes', self.current_theme) - model.remove(iter_) + def on_remove_button_clicked(self, widget): + (model, iter_) = self.themes_tree.get_selection().get_selected() + if not iter_: + return + if self.current_theme == gajim.config.get('roster_theme'): + dialogs.ErrorDialog( + _('You cannot delete your current theme'), + _('Please first choose another for your current theme.')) + return + self.theme_options_vbox.set_sensitive(False) + self.theme_options_table.set_sensitive(False) + self.xml.get_object('remove_button').set_sensitive(False) + gajim.config.del_per('themes', self.current_theme) + model.remove(iter_) - def set_theme_options(self, theme, option = 'account'): - self.no_update = True - self.options_combobox.set_active(self.options.index(option)) - textcolor = gajim.config.get_per('themes', theme, option + 'textcolor') - if textcolor: - state = True - self.text_colorbutton.set_color(gtk.gdk.color_parse(textcolor)) - else: - state = False - self.textcolor_checkbutton.set_active(state) - self.text_colorbutton.set_sensitive(state) - bgcolor = gajim.config.get_per('themes', theme, option + 'bgcolor') - if bgcolor: - state = True - self.background_colorbutton.set_color(gtk.gdk.color_parse( - bgcolor)) - else: - state = False - self.background_checkbutton.set_active(state) - self.background_colorbutton.set_sensitive(state) + def set_theme_options(self, theme, option = 'account'): + self.no_update = True + self.options_combobox.set_active(self.options.index(option)) + textcolor = gajim.config.get_per('themes', theme, option + 'textcolor') + if textcolor: + state = True + self.text_colorbutton.set_color(gtk.gdk.color_parse(textcolor)) + else: + state = False + self.textcolor_checkbutton.set_active(state) + self.text_colorbutton.set_sensitive(state) + bgcolor = gajim.config.get_per('themes', theme, option + 'bgcolor') + if bgcolor: + state = True + self.background_colorbutton.set_color(gtk.gdk.color_parse( + bgcolor)) + else: + state = False + self.background_checkbutton.set_active(state) + self.background_colorbutton.set_sensitive(state) - # get the font name before we set widgets and it will not be overriden - font_name = gajim.config.get_per('themes', theme, option + 'font') - font_attrs = gajim.config.get_per('themes', theme, option + 'fontattrs') - self._set_font_widgets(font_attrs) - if font_name: - state = True - self.text_fontbutton.set_font_name(font_name) - else: - state = False - self.textfont_checkbutton.set_active(state) - self.text_fontbutton.set_sensitive(state) - self.no_update = False - gajim.interface.roster.change_roster_style(None) + # get the font name before we set widgets and it will not be overriden + font_name = gajim.config.get_per('themes', theme, option + 'font') + font_attrs = gajim.config.get_per('themes', theme, option + 'fontattrs') + self._set_font_widgets(font_attrs) + if font_name: + state = True + self.text_fontbutton.set_font_name(font_name) + else: + state = False + self.textfont_checkbutton.set_active(state) + self.text_fontbutton.set_sensitive(state) + self.no_update = False + gajim.interface.roster.change_roster_style(None) - for chatstate in ('inactive', 'composing', 'paused', 'gone', - 'muc_msg', 'muc_directed_msg'): - color = gajim.config.get_per('themes', theme, 'state_' + chatstate + \ - '_color') - self.colorbuttons[chatstate].set_color(gtk.gdk.color_parse(color)) + for chatstate in ('inactive', 'composing', 'paused', 'gone', + 'muc_msg', 'muc_directed_msg'): + color = gajim.config.get_per('themes', theme, 'state_' + chatstate + \ + '_color') + self.colorbuttons[chatstate].set_color(gtk.gdk.color_parse(color)) - def on_textcolor_checkbutton_toggled(self, widget): - state = widget.get_active() - self.text_colorbutton.set_sensitive(state) - self._set_color(state, self.text_colorbutton, - 'textcolor') + def on_textcolor_checkbutton_toggled(self, widget): + state = widget.get_active() + self.text_colorbutton.set_sensitive(state) + self._set_color(state, self.text_colorbutton, + 'textcolor') - def on_background_checkbutton_toggled(self, widget): - state = widget.get_active() - self.background_colorbutton.set_sensitive(state) - self._set_color(state, self.background_colorbutton, - 'bgcolor') + def on_background_checkbutton_toggled(self, widget): + state = widget.get_active() + self.background_colorbutton.set_sensitive(state) + self._set_color(state, self.background_colorbutton, + 'bgcolor') - def on_textfont_checkbutton_toggled(self, widget): - self.text_fontbutton.set_sensitive(widget.get_active()) - self._set_font() + def on_textfont_checkbutton_toggled(self, widget): + self.text_fontbutton.set_sensitive(widget.get_active()) + self._set_font() - def on_text_colorbutton_color_set(self, widget): - self._set_color(True, widget, 'textcolor') + def on_text_colorbutton_color_set(self, widget): + self._set_color(True, widget, 'textcolor') - def on_background_colorbutton_color_set(self, widget): - self._set_color(True, widget, 'bgcolor') + def on_background_colorbutton_color_set(self, widget): + self._set_color(True, widget, 'bgcolor') - def on_text_fontbutton_font_set(self, widget): - self._set_font() + def on_text_fontbutton_font_set(self, widget): + self._set_font() - def on_options_combobox_changed(self, widget): - index = self.options_combobox.get_active() - if index == -1: - return - self.current_option = self.options[index] - self.set_theme_options(self.current_theme, - self.current_option) + def on_options_combobox_changed(self, widget): + index = self.options_combobox.get_active() + if index == -1: + return + self.current_option = self.options[index] + self.set_theme_options(self.current_theme, + self.current_option) - def on_bold_togglebutton_toggled(self, widget): - if not self.no_update: - self._set_font() + def on_bold_togglebutton_toggled(self, widget): + if not self.no_update: + self._set_font() - def on_italic_togglebutton_toggled(self, widget): - if not self.no_update: - self._set_font() + def on_italic_togglebutton_toggled(self, widget): + if not self.no_update: + self._set_font() - def _set_color(self, state, widget, option): - """ - Set color value in prefs and update the UI - """ - if state: - color = widget.get_color() - color_string = gtkgui_helpers.make_color_string(color) - else: - color_string = '' - begin_option = '' - if not option.startswith('state'): - begin_option = self.current_option - gajim.config.set_per('themes', self.current_theme, - begin_option + option, color_string) - # use faster functions for this - if self.current_option == 'banner': - gajim.interface.roster.repaint_themed_widgets() - gajim.interface.save_config() - return - if self.no_update: - return - gajim.interface.roster.change_roster_style(self.current_option) - gajim.interface.save_config() + def _set_color(self, state, widget, option): + """ + Set color value in prefs and update the UI + """ + if state: + color = widget.get_color() + color_string = gtkgui_helpers.make_color_string(color) + else: + color_string = '' + begin_option = '' + if not option.startswith('state'): + begin_option = self.current_option + gajim.config.set_per('themes', self.current_theme, + begin_option + option, color_string) + # use faster functions for this + if self.current_option == 'banner': + gajim.interface.roster.repaint_themed_widgets() + gajim.interface.save_config() + return + if self.no_update: + return + gajim.interface.roster.change_roster_style(self.current_option) + gajim.interface.save_config() - def _set_font(self): - """ - Set font value in prefs and update the UI - """ - state = self.textfont_checkbutton.get_active() - if state: - font_string = self.text_fontbutton.get_font_name() - else: - font_string = '' - gajim.config.set_per('themes', self.current_theme, - self.current_option + 'font', font_string) - font_attrs = self._get_font_attrs() - gajim.config.set_per('themes', self.current_theme, - self.current_option + 'fontattrs', font_attrs) - # use faster functions for this - if self.current_option == 'banner': - gajim.interface.roster.repaint_themed_widgets() - if self.no_update: - return - gajim.interface.roster.change_roster_style(self.current_option) - gajim.interface.save_config() + def _set_font(self): + """ + Set font value in prefs and update the UI + """ + state = self.textfont_checkbutton.get_active() + if state: + font_string = self.text_fontbutton.get_font_name() + else: + font_string = '' + gajim.config.set_per('themes', self.current_theme, + self.current_option + 'font', font_string) + font_attrs = self._get_font_attrs() + gajim.config.set_per('themes', self.current_theme, + self.current_option + 'fontattrs', font_attrs) + # use faster functions for this + if self.current_option == 'banner': + gajim.interface.roster.repaint_themed_widgets() + if self.no_update: + return + gajim.interface.roster.change_roster_style(self.current_option) + gajim.interface.save_config() - def _toggle_font_widgets(self, font_props): - """ - Toggle font buttons with the bool values of font_props tuple - """ - self.bold_togglebutton.set_active(font_props[0]) - self.italic_togglebutton.set_active(font_props[1]) + def _toggle_font_widgets(self, font_props): + """ + Toggle font buttons with the bool values of font_props tuple + """ + self.bold_togglebutton.set_active(font_props[0]) + self.italic_togglebutton.set_active(font_props[1]) - def _get_font_description(self): - """ - Return a FontDescription from togglebuttons states - """ - fd = pango.FontDescription() - if self.bold_togglebutton.get_active(): - fd.set_weight(pango.WEIGHT_BOLD) - if self.italic_togglebutton.get_active(): - fd.set_style(pango.STYLE_ITALIC) - return fd + def _get_font_description(self): + """ + Return a FontDescription from togglebuttons states + """ + fd = pango.FontDescription() + if self.bold_togglebutton.get_active(): + fd.set_weight(pango.WEIGHT_BOLD) + if self.italic_togglebutton.get_active(): + fd.set_style(pango.STYLE_ITALIC) + return fd - def _set_font_widgets(self, font_attrs): - """ - Set the correct toggle state of font style buttons by a font string of - type 'BI' - """ - font_props = [False, False, False] - if font_attrs: - if font_attrs.find('B') != -1: - font_props[0] = True - if font_attrs.find('I') != -1: - font_props[1] = True - self._toggle_font_widgets(font_props) + def _set_font_widgets(self, font_attrs): + """ + Set the correct toggle state of font style buttons by a font string of + type 'BI' + """ + font_props = [False, False, False] + if font_attrs: + if font_attrs.find('B') != -1: + font_props[0] = True + if font_attrs.find('I') != -1: + font_props[1] = True + self._toggle_font_widgets(font_props) - def _get_font_attrs(self): - """ - Get a string with letters of font attribures: 'BI' - """ - attrs = '' - if self.bold_togglebutton.get_active(): - attrs += 'B' - if self.italic_togglebutton.get_active(): - attrs += 'I' - return attrs + def _get_font_attrs(self): + """ + Get a string with letters of font attribures: 'BI' + """ + attrs = '' + if self.bold_togglebutton.get_active(): + attrs += 'B' + if self.italic_togglebutton.get_active(): + attrs += 'I' + return attrs - def _get_font_props(self, font_name): - """ - Get tuple of font properties: weight, style - """ - font_props = [False, False, False] - font_description = pango.FontDescription(font_name) - if font_description.get_weight() != pango.WEIGHT_NORMAL: - font_props[0] = True - if font_description.get_style() != pango.STYLE_ITALIC: - font_props[1] = True - return font_props + def _get_font_props(self, font_name): + """ + Get tuple of font properties: weight, style + """ + font_props = [False, False, False] + font_description = pango.FontDescription(font_name) + if font_description.get_weight() != pango.WEIGHT_NORMAL: + font_props[0] = True + if font_description.get_style() != pango.STYLE_ITALIC: + font_props[1] = True + return font_props - def on_inactive_colorbutton_color_set(self, widget): - self.no_update = True - self._set_color(True, widget, 'state_inactive_color') - self.no_update = False + def on_inactive_colorbutton_color_set(self, widget): + self.no_update = True + self._set_color(True, widget, 'state_inactive_color') + self.no_update = False - def on_composing_colorbutton_color_set(self, widget): - self.no_update = True - self._set_color(True, widget, 'state_composing_color') - self.no_update = False + def on_composing_colorbutton_color_set(self, widget): + self.no_update = True + self._set_color(True, widget, 'state_composing_color') + self.no_update = False - def on_paused_colorbutton_color_set(self, widget): - self.no_update = True - self._set_color(True, widget, 'state_paused_color') - self.no_update = False + def on_paused_colorbutton_color_set(self, widget): + self.no_update = True + self._set_color(True, widget, 'state_paused_color') + self.no_update = False - def on_gone_colorbutton_color_set(self, widget): - self.no_update = True - self._set_color(True, widget, 'state_gone_color') - self.no_update = False + def on_gone_colorbutton_color_set(self, widget): + self.no_update = True + self._set_color(True, widget, 'state_gone_color') + self.no_update = False - def on_muc_msg_colorbutton_color_set(self, widget): - self.no_update = True - self._set_color(True, widget, 'state_muc_msg_color') - self.no_update = False + def on_muc_msg_colorbutton_color_set(self, widget): + self.no_update = True + self._set_color(True, widget, 'state_muc_msg_color') + self.no_update = False - def on_muc_directed_msg_colorbutton_color_set(self, widget): - self.no_update = True - self._set_color(True, widget, 'state_muc_directed_msg_color') - self.no_update = False - -# vim: se ts=3: + def on_muc_directed_msg_colorbutton_color_set(self, widget): + self.no_update = True + self._set_color(True, widget, 'state_muc_directed_msg_color') + self.no_update = False diff --git a/src/groupchat_control.py b/src/groupchat_control.py index 4fdcc55fe..36f84b69f 100644 --- a/src/groupchat_control.py +++ b/src/groupchat_control.py @@ -64,2353 +64,2351 @@ C_AVATAR, # avatar of the contact ) = range(5) def set_renderer_color(treeview, renderer, set_background=True): - """ - Set style for group row, using PRELIGHT system color - """ - if set_background: - bgcolor = treeview.style.bg[gtk.STATE_PRELIGHT] - renderer.set_property('cell-background-gdk', bgcolor) - else: - fgcolor = treeview.style.fg[gtk.STATE_PRELIGHT] - renderer.set_property('foreground-gdk', fgcolor) + """ + Set style for group row, using PRELIGHT system color + """ + if set_background: + bgcolor = treeview.style.bg[gtk.STATE_PRELIGHT] + renderer.set_property('cell-background-gdk', bgcolor) + else: + fgcolor = treeview.style.fg[gtk.STATE_PRELIGHT] + renderer.set_property('foreground-gdk', fgcolor) def tree_cell_data_func(column, renderer, model, iter_, tv=None): - # cell data func is global, because we don't want it to keep - # reference to GroupchatControl instance (self) - theme = gajim.config.get('roster_theme') - # allocate space for avatar only if needed - parent_iter = model.iter_parent(iter_) - if isinstance(renderer, gtk.CellRendererPixbuf): - avatar_position = gajim.config.get('avatar_position_in_roster') - if avatar_position == 'right': - renderer.set_property('xalign', 1) # align pixbuf to the right - else: - renderer.set_property('xalign', 0.5) - if parent_iter and (model[iter_][C_AVATAR] or avatar_position == 'left'): - renderer.set_property('visible', True) - renderer.set_property('width', gajim.config.get('roster_avatar_width')) - else: - renderer.set_property('visible', False) - if parent_iter: - bgcolor = gajim.config.get_per('themes', theme, 'contactbgcolor') - if bgcolor: - renderer.set_property('cell-background', bgcolor) - else: - renderer.set_property('cell-background', None) - if isinstance(renderer, gtk.CellRendererText): - # foreground property is only with CellRendererText - color = gajim.config.get_per('themes', theme, 'contacttextcolor') - if color: - renderer.set_property('foreground', color) - else: - renderer.set_property('foreground', None) - renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont')) - else: # it is root (eg. group) - bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor') - if bgcolor: - renderer.set_property('cell-background', bgcolor) - else: - set_renderer_color(tv, renderer) - if isinstance(renderer, gtk.CellRendererText): - # foreground property is only with CellRendererText - color = gajim.config.get_per('themes', theme, 'grouptextcolor') - if color: - renderer.set_property('foreground', color) - else: - set_renderer_color(tv, renderer, False) - renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont')) + # cell data func is global, because we don't want it to keep + # reference to GroupchatControl instance (self) + theme = gajim.config.get('roster_theme') + # allocate space for avatar only if needed + parent_iter = model.iter_parent(iter_) + if isinstance(renderer, gtk.CellRendererPixbuf): + avatar_position = gajim.config.get('avatar_position_in_roster') + if avatar_position == 'right': + renderer.set_property('xalign', 1) # align pixbuf to the right + else: + renderer.set_property('xalign', 0.5) + if parent_iter and (model[iter_][C_AVATAR] or avatar_position == 'left'): + renderer.set_property('visible', True) + renderer.set_property('width', gajim.config.get('roster_avatar_width')) + else: + renderer.set_property('visible', False) + if parent_iter: + bgcolor = gajim.config.get_per('themes', theme, 'contactbgcolor') + if bgcolor: + renderer.set_property('cell-background', bgcolor) + else: + renderer.set_property('cell-background', None) + if isinstance(renderer, gtk.CellRendererText): + # foreground property is only with CellRendererText + color = gajim.config.get_per('themes', theme, 'contacttextcolor') + if color: + renderer.set_property('foreground', color) + else: + renderer.set_property('foreground', None) + renderer.set_property('font', + gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont')) + else: # it is root (eg. group) + bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor') + if bgcolor: + renderer.set_property('cell-background', bgcolor) + else: + set_renderer_color(tv, renderer) + if isinstance(renderer, gtk.CellRendererText): + # foreground property is only with CellRendererText + color = gajim.config.get_per('themes', theme, 'grouptextcolor') + if color: + renderer.set_property('foreground', color) + else: + set_renderer_color(tv, renderer, False) + renderer.set_property('font', + gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont')) class PrivateChatControl(ChatControl): - TYPE_ID = message_control.TYPE_PM + TYPE_ID = message_control.TYPE_PM - # Set a command host to bound to. Every command given through a private chat - # will be processed with this command host. - COMMAND_HOST = PrivateChatCommands + # Set a command host to bound to. Every command given through a private chat + # will be processed with this command host. + COMMAND_HOST = PrivateChatCommands - def __init__(self, parent_win, gc_contact, contact, account, session): - room_jid = gc_contact.room_jid - room_ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid, account) - if room_jid in gajim.interface.minimized_controls[account]: - room_ctrl = gajim.interface.minimized_controls[account][room_jid] - if room_ctrl: - self.room_name = room_ctrl.name - else: - self.room_name = room_jid - self.gc_contact = gc_contact - ChatControl.__init__(self, parent_win, contact, account, session) - self.TYPE_ID = 'pm' + def __init__(self, parent_win, gc_contact, contact, account, session): + room_jid = gc_contact.room_jid + room_ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid, account) + if room_jid in gajim.interface.minimized_controls[account]: + room_ctrl = gajim.interface.minimized_controls[account][room_jid] + if room_ctrl: + self.room_name = room_ctrl.name + else: + self.room_name = room_jid + self.gc_contact = gc_contact + ChatControl.__init__(self, parent_win, contact, account, session) + self.TYPE_ID = 'pm' - def send_message(self, message, xhtml=None, process_commands=True): - """ - Call this method to send the message - """ - message = helpers.remove_invalid_xml_chars(message) - if not message: - return + def send_message(self, message, xhtml=None, process_commands=True): + """ + Call this method to send the message + """ + message = helpers.remove_invalid_xml_chars(message) + if not message: + return - # We need to make sure that we can still send through the room and that - # the recipient did not go away - contact = gajim.contacts.get_first_contact_from_jid(self.account, - self.contact.jid) - if not contact: - # contact was from pm in MUC - room, nick = gajim.get_room_and_nick_from_fjid(self.contact.jid) - gc_contact = gajim.contacts.get_gc_contact(self.account, room, nick) - if not gc_contact: - dialogs.ErrorDialog( - _('Sending private message failed'), - #in second %s code replaces with nickname - _('You are no longer in group chat "%(room)s" or "%(nick)s" has ' - 'left.') % {'room': room, 'nick': nick}) - return + # We need to make sure that we can still send through the room and that + # the recipient did not go away + contact = gajim.contacts.get_first_contact_from_jid(self.account, + self.contact.jid) + if not contact: + # contact was from pm in MUC + room, nick = gajim.get_room_and_nick_from_fjid(self.contact.jid) + gc_contact = gajim.contacts.get_gc_contact(self.account, room, nick) + if not gc_contact: + dialogs.ErrorDialog( + _('Sending private message failed'), + #in second %s code replaces with nickname + _('You are no longer in group chat "%(room)s" or "%(nick)s" has ' + 'left.') % {'room': room, 'nick': nick}) + return - ChatControl.send_message(self, message, xhtml=xhtml, - process_commands=process_commands) + ChatControl.send_message(self, message, xhtml=xhtml, + process_commands=process_commands) - def update_ui(self): - if self.contact.show == 'offline': - self.got_disconnected() - else: - self.got_connected() - ChatControl.update_ui(self) + def update_ui(self): + if self.contact.show == 'offline': + self.got_disconnected() + else: + self.got_connected() + ChatControl.update_ui(self) - def update_contact(self): - self.contact = self.gc_contact.as_contact() + def update_contact(self): + self.contact = self.gc_contact.as_contact() - def begin_e2e_negotiation(self): - self.no_autonegotiation = True + def begin_e2e_negotiation(self): + self.no_autonegotiation = True - if not self.session: - fjid = self.gc_contact.get_full_jid() - new_sess = gajim.connections[self.account].make_new_session(fjid, type_=self.type_id) - self.set_session(new_sess) + if not self.session: + fjid = self.gc_contact.get_full_jid() + new_sess = gajim.connections[self.account].make_new_session(fjid, type_=self.type_id) + self.set_session(new_sess) - self.session.negotiate_e2e(False) + self.session.negotiate_e2e(False) class GroupchatControl(ChatControlBase): - TYPE_ID = message_control.TYPE_GC - - # Set a command host to bound to. Every command given through a group chat - # will be processed with this command host. - COMMAND_HOST = GroupChatCommands - - def __init__(self, parent_win, contact, acct, is_continued=False): - ChatControlBase.__init__(self, self.TYPE_ID, parent_win, - 'groupchat_control', contact, acct) - - self.is_continued=is_continued - self.is_anonymous = True - - # Controls the state of autorejoin. - # None - autorejoin is neutral. - # False - autorejoin is to be prevented (gets reset to initial state in - # got_connected()). - # int - autorejoin is being active and working (gets reset to initial - # state in got_connected()). - self.autorejoin = None - - self.actions_button = self.xml.get_object('muc_window_actions_button') - id_ = self.actions_button.connect('clicked', - self.on_actions_button_clicked) - self.handlers[id_] = self.actions_button - - widget = self.xml.get_object('change_nick_button') - id_ = widget.connect('clicked', self._on_change_nick_menuitem_activate) - self.handlers[id_] = widget - - widget = self.xml.get_object('change_subject_button') - id_ = widget.connect('clicked', self._on_change_subject_menuitem_activate) - self.handlers[id_] = widget - - widget = self.xml.get_object('bookmark_button') - for bm in gajim.connections[self.account].bookmarks: - if bm['jid'] == self.contact.jid: - widget.hide() - break - else: - id_ = widget.connect('clicked', - self._on_bookmark_room_menuitem_activate) - self.handlers[id_] = widget - widget.show() - - widget = self.xml.get_object('list_treeview') - id_ = widget.connect('row_expanded', self.on_list_treeview_row_expanded) - self.handlers[id_] = widget - - id_ = widget.connect('row_collapsed', self.on_list_treeview_row_collapsed) - self.handlers[id_] = widget - - id_ = widget.connect('row_activated', - self.on_list_treeview_row_activated) - self.handlers[id_] = widget - - id_ = widget.connect('button_press_event', - self.on_list_treeview_button_press_event) - self.handlers[id_] = widget - - id_ = widget.connect('key_press_event', - self.on_list_treeview_key_press_event) - self.handlers[id_] = widget - - id_ = widget.connect('motion_notify_event', - self.on_list_treeview_motion_notify_event) - self.handlers[id_] = widget - - id_ = widget.connect('leave_notify_event', - self.on_list_treeview_leave_notify_event) - self.handlers[id_] = widget - - self.room_jid = self.contact.jid - self.nick = contact.name.decode('utf-8') - self.new_nick = '' - self.name = '' - for bm in gajim.connections[self.account].bookmarks: - if bm['jid'] == self.room_jid: - self.name = bm['name'] - break - if not self.name: - self.name = self.room_jid.split('@')[0] - - compact_view = gajim.config.get('compact_view') - self.chat_buttons_set_visible(compact_view) - self.widget_set_visible(self.xml.get_object('banner_eventbox'), - gajim.config.get('hide_groupchat_banner')) - self.widget_set_visible(self.xml.get_object('list_scrolledwindow'), - gajim.config.get('hide_groupchat_occupants_list')) - - self._last_selected_contact = None # None or holds jid, account tuple - - # muc attention flag (when we are mentioned in a muc) - # if True, the room has mentioned us - self.attention_flag = False - - # sorted list of nicks who mentioned us (last at the end) - self.attention_list = [] - self.room_creation = int(time.time()) # Use int to reduce mem usage - self.nick_hits = [] - self.last_key_tabs = False - - self.subject = '' - - self.tooltip = tooltips.GCTooltip() - - # nickname coloring - self.gc_count_nicknames_colors = 0 - self.gc_custom_colors = {} - self.number_of_colors = len(gajim.config.get('gc_nicknames_colors').\ - split(':')) - - self.name_label = self.xml.get_object('banner_name_label') - self.event_box = self.xml.get_object('banner_eventbox') - - # set the position of the current hpaned - hpaned_position = gajim.config.get('gc-hpaned-position') - self.hpaned = self.xml.get_object('hpaned') - self.hpaned.set_position(hpaned_position) - - self.list_treeview = self.xml.get_object('list_treeview') - selection = self.list_treeview.get_selection() - id_ = selection.connect('changed', - self.on_list_treeview_selection_changed) - self.handlers[id_] = selection - id_ = self.list_treeview.connect('style-set', - self.on_list_treeview_style_set) - self.handlers[id_] = self.list_treeview - self.resize_from_another_muc = False - # we want to know when the the widget resizes, because that is - # an indication that the hpaned has moved... - # FIXME: Find a better indicator that the hpaned has moved. - id_ = self.list_treeview.connect('size-allocate', - self.on_treeview_size_allocate) - self.handlers[id_] = self.list_treeview - #status_image, shown_nick, type, nickname, avatar - store = gtk.TreeStore(gtk.Image, str, str, str, gtk.gdk.Pixbuf) - store.set_sort_func(C_NICK, self.tree_compare_iters) - store.set_sort_column_id(C_NICK, gtk.SORT_ASCENDING) - self.list_treeview.set_model(store) - - # columns - - # this col has 3 cells: - # first one img, second one text, third is sec pixbuf - column = gtk.TreeViewColumn() - - def add_avatar_renderer(): - renderer_pixbuf = gtk.CellRendererPixbuf() # avatar image - column.pack_start(renderer_pixbuf, expand=False) - column.add_attribute(renderer_pixbuf, 'pixbuf', C_AVATAR) - column.set_cell_data_func(renderer_pixbuf, tree_cell_data_func, - self.list_treeview) - - if gajim.config.get('avatar_position_in_roster') == 'left': - add_avatar_renderer() - - renderer_image = cell_renderer_image.CellRendererImage(0, 0) # status img - renderer_image.set_property('width', 26) - column.pack_start(renderer_image, expand=False) - column.add_attribute(renderer_image, 'image', C_IMG) - column.set_cell_data_func(renderer_image, tree_cell_data_func, - self.list_treeview) - - renderer_text = gtk.CellRendererText() # nickname - column.pack_start(renderer_text, expand=True) - column.add_attribute(renderer_text, 'markup', C_TEXT) - renderer_text.set_property("ellipsize", pango.ELLIPSIZE_END) - column.set_cell_data_func(renderer_text, tree_cell_data_func, - self.list_treeview) - - if gajim.config.get('avatar_position_in_roster') == 'right': - add_avatar_renderer() - - self.list_treeview.append_column(column) - - # workaround to avoid gtk arrows to be shown - column = gtk.TreeViewColumn() # 2nd COLUMN - renderer = gtk.CellRendererPixbuf() - column.pack_start(renderer, expand=False) - self.list_treeview.append_column(column) - column.set_visible(False) - self.list_treeview.set_expander_column(column) - - gajim.gc_connected[self.account][self.room_jid] = False - # disable win, we are not connected yet - ChatControlBase.got_disconnected(self) - - self.update_ui() - self.conv_textview.tv.grab_focus() - self.widget.show_all() - - def tree_compare_iters(self, model, iter1, iter2): - """ - Compare two iters to sort them - """ - type1 = model[iter1][C_TYPE] - type2 = model[iter2][C_TYPE] - if not type1 or not type2: - return 0 - nick1 = model[iter1][C_NICK] - nick2 = model[iter2][C_NICK] - if not nick1 or not nick2: - return 0 - nick1 = nick1.decode('utf-8') - nick2 = nick2.decode('utf-8') - if type1 == 'role': - return locale.strcoll(nick1, nick2) - if type1 == 'contact': - gc_contact1 = gajim.contacts.get_gc_contact(self.account, - self.room_jid, nick1) - if not gc_contact1: - return 0 - if type2 == 'contact': - gc_contact2 = gajim.contacts.get_gc_contact(self.account, - self.room_jid, nick2) - if not gc_contact2: - return 0 - if type1 == 'contact' and type2 == 'contact' and \ - gajim.config.get('sort_by_show_in_muc'): - cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4, - 'invisible': 5, 'offline': 6, 'error': 7} - show1 = cshow[gc_contact1.show] - show2 = cshow[gc_contact2.show] - if show1 < show2: - return -1 - elif show1 > show2: - return 1 - # We compare names - name1 = gc_contact1.get_shown_name() - name2 = gc_contact2.get_shown_name() - return locale.strcoll(name1.lower(), name2.lower()) - - def on_msg_textview_populate_popup(self, textview, menu): - """ - Override the default context menu and we prepend Clear - and the ability to insert a nick - """ - ChatControlBase.on_msg_textview_populate_popup(self, textview, menu) - item = gtk.SeparatorMenuItem() - menu.prepend(item) - - item = gtk.MenuItem(_('Insert Nickname')) - menu.prepend(item) - submenu = gtk.Menu() - item.set_submenu(submenu) - - for nick in sorted(gajim.contacts.get_nick_list(self.account, - self.room_jid)): - item = gtk.MenuItem(nick, use_underline=False) - submenu.append(item) - id_ = item.connect('activate', self.append_nick_in_msg_textview, nick) - self.handlers[id_] = item - - menu.show_all() - - def resize_occupant_treeview(self, position): - self.resize_from_another_muc = True - self.hpaned.set_position(position) - def reset_flag(): - self.resize_from_another_muc = False - # Reset the flag when everything will be redrawn, and in particular when - # on_treeview_size_allocate will have been called. - gobject.idle_add(reset_flag) - - def on_treeview_size_allocate(self, widget, allocation): - """ - The MUC treeview has resized. Move the hpaned in all tabs to match - """ - if self.resize_from_another_muc: - # Don't send the event to other MUC - return - hpaned_position = self.hpaned.get_position() - for account in gajim.gc_connected: - for room_jid in [i for i in gajim.gc_connected[account] if \ - gajim.gc_connected[account][i] and i != self.room_jid]: - ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid, account) - if not ctrl: - ctrl = gajim.interface.minimized_controls[account][room_jid] - if ctrl: - ctrl.resize_occupant_treeview(hpaned_position) - - def iter_contact_rows(self): - """ - Iterate over all contact rows in the tree model - """ - model = self.list_treeview.get_model() - role_iter = model.get_iter_root() - while role_iter: - contact_iter = model.iter_children(role_iter) - while contact_iter: - yield model[contact_iter] - contact_iter = model.iter_next(contact_iter) - role_iter = model.iter_next(role_iter) - - def on_list_treeview_style_set(self, treeview, style): - """ - When style (theme) changes, redraw all contacts - """ - # Get the room_jid from treeview - for contact in self.iter_contact_rows(): - nick = contact[C_NICK].decode('utf-8') - self.draw_contact(nick) - - def on_list_treeview_selection_changed(self, selection): - model, selected_iter = selection.get_selected() - self.draw_contact(self.nick) - if self._last_selected_contact is not None: - self.draw_contact(self._last_selected_contact) - if selected_iter is None: - self._last_selected_contact = None - return - contact = model[selected_iter] - nick = contact[C_NICK].decode('utf-8') - self._last_selected_contact = nick - if contact[C_TYPE] != 'contact': - return - self.draw_contact(nick, selected=True, focus=True) - - def get_tab_label(self, chatstate): - """ - Markup the label if necessary. Returns a tuple such as: (new_label_str, - color) either of which can be None if chatstate is given that means we - have HE SENT US a chatstate - """ - - has_focus = self.parent_win.window.get_property('has-toplevel-focus') - current_tab = self.parent_win.get_active_control() == self - color_name = None - color = None - theme = gajim.config.get('roster_theme') - if chatstate == 'attention' and (not has_focus or not current_tab): - self.attention_flag = True - color_name = gajim.config.get_per('themes', theme, - 'state_muc_directed_msg_color') - elif chatstate: - if chatstate == 'active' or (current_tab and has_focus): - self.attention_flag = False - # get active color from gtk - color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE] - elif chatstate == 'newmsg' and (not has_focus or not current_tab) and\ - not self.attention_flag: - color_name = gajim.config.get_per('themes', theme, - 'state_muc_msg_color') - if color_name: - color = gtk.gdk.colormap_get_system().alloc_color(color_name) - - if self.is_continued: - # if this is a continued conversation - label_str = self.get_continued_conversation_name() - else: - label_str = self.name - - # count waiting highlighted messages - unread = '' - num_unread = self.get_nb_unread() - if num_unread == 1: - unread = '*' - elif num_unread > 1: - unread = '[' + unicode(num_unread) + ']' - label_str = unread + label_str - return (label_str, color) - - def get_tab_image(self, count_unread=True): - # Set tab image (always 16x16) - tab_image = None - if gajim.gc_connected[self.account][self.room_jid]: - tab_image = gtkgui_helpers.load_icon('muc_active') - else: - tab_image = gtkgui_helpers.load_icon('muc_inactive') - return tab_image - - def update_ui(self): - ChatControlBase.update_ui(self) - for nick in gajim.contacts.get_nick_list(self.account, self.room_jid): - self.draw_contact(nick) - - def _change_style(self, model, path, iter_): - model[iter_][C_NICK] = model[iter_][C_NICK] - - def change_roster_style(self): - model = self.list_treeview.get_model() - model.foreach(self._change_style) - - def repaint_themed_widgets(self): - ChatControlBase.repaint_themed_widgets(self) - self.change_roster_style() - - def _update_banner_state_image(self): - banner_status_img = self.xml.get_object('banner_status_image') - images = gajim.interface.jabber_state_images - if self.room_jid in gajim.gc_connected[self.account] and \ - gajim.gc_connected[self.account][self.room_jid]: - image = 'muc_active' - else: - image = 'muc_inactive' - if '32' in images and image in images['32']: - muc_icon = images['32'][image] - if muc_icon.get_storage_type() != gtk.IMAGE_EMPTY: - pix = muc_icon.get_pixbuf() - banner_status_img.set_from_pixbuf(pix) - return - # we need to scale 16x16 to 32x32 - muc_icon = images['16'][image] - pix = muc_icon.get_pixbuf() - scaled_pix = pix.scale_simple(32, 32, gtk.gdk.INTERP_BILINEAR) - banner_status_img.set_from_pixbuf(scaled_pix) - - def get_continued_conversation_name(self): - """ - Get the name of a continued conversation. Will return Continued - Conversation if there isn't any other contact in the room - """ - nicks = [] - for nick in gajim.contacts.get_nick_list(self.account, - self.room_jid): - if nick != self.nick: - nicks.append(nick) - if nicks != []: - title = ', ' - title = _('Conversation with ') + title.join(nicks) - else: - title = _('Continued conversation') - return title - - def draw_banner_text(self): - """ - Draw the text in the fat line at the top of the window that houses the - room jid, subject - """ - self.name_label.set_ellipsize(pango.ELLIPSIZE_END) - self.banner_status_label.set_ellipsize(pango.ELLIPSIZE_END) - font_attrs, font_attrs_small = self.get_font_attrs() - if self.is_continued: - name = self.get_continued_conversation_name() - else: - name = self.room_jid - text = '%s' % (font_attrs, name) - self.name_label.set_markup(text) - - if self.subject: - subject = helpers.reduce_chars_newlines(self.subject, max_lines=2) - subject = gobject.markup_escape_text(subject) - subject_text = self.urlfinder.sub(self.make_href, subject) - subject_text = '%s' % (font_attrs_small, subject_text) - - # tooltip must always hold ALL the subject - self.event_box.set_tooltip_text(self.subject) - self.banner_status_label.set_no_show_all(False) - self.banner_status_label.show() - else: - subject_text = '' - self.event_box.set_has_tooltip(False) - self.banner_status_label.hide() - self.banner_status_label.set_no_show_all(True) - - self.banner_status_label.set_markup(subject_text) - - def prepare_context_menu(self, hide_buttonbar_items=False): - """ - Set sensitivity state for configure_room - """ - xml = gtkgui_helpers.get_gtk_builder('gc_control_popup_menu.ui') - menu = xml.get_object('gc_control_popup_menu') - - bookmark_room_menuitem = xml.get_object('bookmark_room_menuitem') - change_nick_menuitem = xml.get_object('change_nick_menuitem') - configure_room_menuitem = xml.get_object('configure_room_menuitem') - destroy_room_menuitem = xml.get_object('destroy_room_menuitem') - change_subject_menuitem = xml.get_object('change_subject_menuitem') - history_menuitem = xml.get_object('history_menuitem') - minimize_menuitem = xml.get_object('minimize_menuitem') - bookmark_separator = xml.get_object('bookmark_separator') - separatormenuitem2 = xml.get_object('separatormenuitem2') - - if hide_buttonbar_items: - change_nick_menuitem.hide() - change_subject_menuitem.hide() - bookmark_room_menuitem.hide() - history_menuitem.hide() - bookmark_separator.hide() - separatormenuitem2.hide() - else: - change_nick_menuitem.show() - change_subject_menuitem.show() - bookmark_room_menuitem.show() - history_menuitem.show() - bookmark_separator.show() - separatormenuitem2.show() - for bm in gajim.connections[self.account].bookmarks: - if bm['jid'] == self.room_jid: - bookmark_room_menuitem.hide() - bookmark_separator.hide() - break - - ag = gtk.accel_groups_from_object(self.parent_win.window)[0] - change_nick_menuitem.add_accelerator('activate', ag, gtk.keysyms.n, - gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK, gtk.ACCEL_VISIBLE) - change_subject_menuitem.add_accelerator('activate', ag, - gtk.keysyms.t, gtk.gdk.MOD1_MASK, gtk.ACCEL_VISIBLE) - bookmark_room_menuitem.add_accelerator('activate', ag, gtk.keysyms.b, - gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) - history_menuitem.add_accelerator('activate', ag, gtk.keysyms.h, - gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) - - if self.contact.jid in gajim.config.get_per('accounts', self.account, - 'minimized_gc').split(' '): - minimize_menuitem.set_active(True) - if not gajim.connections[self.account].private_storage_supported: - bookmark_room_menuitem.set_sensitive(False) - if gajim.gc_connected[self.account][self.room_jid]: - c = gajim.contacts.get_gc_contact(self.account, self.room_jid, - self.nick) - if c.affiliation not in ('owner', 'admin'): - configure_room_menuitem.set_sensitive(False) - else: - configure_room_menuitem.set_sensitive(True) - if c.affiliation != 'owner': - destroy_room_menuitem.set_sensitive(False) - else: - destroy_room_menuitem.set_sensitive(True) - change_subject_menuitem.set_sensitive(True) - change_nick_menuitem.set_sensitive(True) - else: - # We are not connected to this groupchat, disable unusable menuitems - configure_room_menuitem.set_sensitive(False) - destroy_room_menuitem.set_sensitive(False) - change_subject_menuitem.set_sensitive(False) - change_nick_menuitem.set_sensitive(False) - - # connect the menuitems to their respective functions - id_ = bookmark_room_menuitem.connect('activate', - self._on_bookmark_room_menuitem_activate) - self.handlers[id_] = bookmark_room_menuitem - - id_ = change_nick_menuitem.connect('activate', - self._on_change_nick_menuitem_activate) - self.handlers[id_] = change_nick_menuitem - - id_ = configure_room_menuitem.connect('activate', - self._on_configure_room_menuitem_activate) - self.handlers[id_] = configure_room_menuitem - - id_ = destroy_room_menuitem.connect('activate', - self._on_destroy_room_menuitem_activate) - self.handlers[id_] = destroy_room_menuitem - - id_ = change_subject_menuitem.connect('activate', - self._on_change_subject_menuitem_activate) - self.handlers[id_] = change_subject_menuitem - - id_ = history_menuitem.connect('activate', - self._on_history_menuitem_activate) - self.handlers[id_] = history_menuitem - - id_ = minimize_menuitem.connect('toggled', - self.on_minimize_menuitem_toggled) - self.handlers[id_] = minimize_menuitem - - menu.connect('selection-done', self.destroy_menu, - change_nick_menuitem, change_subject_menuitem, - bookmark_room_menuitem, history_menuitem) - return menu - - def destroy_menu(self, menu, change_nick_menuitem, change_subject_menuitem, - bookmark_room_menuitem, history_menuitem): - # destroy accelerators - ag = gtk.accel_groups_from_object(self.parent_win.window)[0] - change_nick_menuitem.remove_accelerator(ag, gtk.keysyms.n, - gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK) - change_subject_menuitem.remove_accelerator(ag, gtk.keysyms.t, - gtk.gdk.MOD1_MASK) - bookmark_room_menuitem.remove_accelerator(ag, gtk.keysyms.b, - gtk.gdk.CONTROL_MASK) - history_menuitem.remove_accelerator(ag, gtk.keysyms.h, - gtk.gdk.CONTROL_MASK) - # destroy menu - menu.destroy() - - def on_message(self, nick, msg, tim, has_timestamp=False, xhtml=None, - status_code=[]): - if '100' in status_code: - # Room is not anonymous - self.is_anonymous = False - if not nick: - # message from server - self.print_conversation(msg, tim=tim, xhtml=xhtml) - else: - # message from someone - if has_timestamp: - # don't print xhtml if it's an old message. - # Like that xhtml messages are grayed too. - self.print_old_conversation(msg, nick, tim, None) - else: - self.print_conversation(msg, nick, tim, xhtml) - - def on_private_message(self, nick, msg, tim, xhtml, session, msg_id=None, - encrypted=False): - # Do we have a queue? - fjid = self.room_jid + '/' + nick - no_queue = len(gajim.events.get_events(self.account, fjid)) == 0 - - event = gajim.events.create_event('pm', (msg, '', 'incoming', tim, - encrypted, '', msg_id, xhtml, session)) - gajim.events.add_event(self.account, fjid, event) - - autopopup = gajim.config.get('autopopup') - autopopupaway = gajim.config.get('autopopupaway') - iter_ = self.get_contact_iter(nick) - path = self.list_treeview.get_model().get_path(iter_) - if not autopopup or (not autopopupaway and \ - gajim.connections[self.account].connected > 2): - if no_queue: # We didn't have a queue: we change icons - model = self.list_treeview.get_model() - state_images =\ - gajim.interface.roster.get_appropriate_state_images( - self.room_jid, icon_name='event') - image = state_images['event'] - model[iter_][C_IMG] = image - if self.parent_win: - self.parent_win.show_title() - self.parent_win.redraw_tab(self) - else: - self._start_private_message(nick) - # Scroll to line - self.list_treeview.expand_row(path[0:1], False) - self.list_treeview.scroll_to_cell(path) - self.list_treeview.set_cursor(path) - contact = gajim.contacts.get_contact_with_highest_priority(self.account, \ - self.room_jid) - if contact: - gajim.interface.roster.draw_contact(self.room_jid, self.account) - - def get_contact_iter(self, nick): - model = self.list_treeview.get_model() - role_iter = model.get_iter_root() - while role_iter: - user_iter = model.iter_children(role_iter) - while user_iter: - if nick == model[user_iter][C_NICK].decode('utf-8'): - return user_iter - else: - user_iter = model.iter_next(user_iter) - role_iter = model.iter_next(role_iter) - return None - - def print_old_conversation(self, text, contact='', tim=None, xhtml = None): - if isinstance(text, str): - text = unicode(text, 'utf-8') - if contact: - if contact == self.nick: # it's us - kind = 'outgoing' - else: - kind = 'incoming' - else: - kind = 'status' - if gajim.config.get('restored_messages_small'): - small_attr = ['small'] - else: - small_attr = [] - ChatControlBase.print_conversation_line(self, text, kind, contact, tim, - small_attr, small_attr + ['restored_message'], - small_attr + ['restored_message'], count_as_new=False, xhtml=xhtml) - - def print_conversation(self, text, contact='', tim=None, xhtml=None, - graphics=True): - """ - Print a line in the conversation - - If contact is set: it's a message from someone or an info message - (contact = 'info' in such a case). - If contact is not set: it's a message from the server or help. - """ - if isinstance(text, str): - text = unicode(text, 'utf-8') - other_tags_for_name = [] - other_tags_for_text = [] - if contact: - if contact == self.nick: # it's us - kind = 'outgoing' - elif contact == 'info': - kind = 'info' - contact = None - else: - kind = 'incoming' - # muc-specific chatstate - if self.parent_win: - self.parent_win.redraw_tab(self, 'newmsg') - else: - kind = 'status' - - if kind == 'incoming': # it's a message NOT from us - # highlighting and sounds - (highlight, sound) = self.highlighting_for_message(text, tim) - if contact in self.gc_custom_colors: - other_tags_for_name.append('gc_nickname_color_' + \ - str(self.gc_custom_colors[contact])) - else: - self.gc_count_nicknames_colors += 1 - if self.gc_count_nicknames_colors == self.number_of_colors: - self.gc_count_nicknames_colors = 0 - self.gc_custom_colors[contact] = \ - self.gc_count_nicknames_colors - other_tags_for_name.append('gc_nickname_color_' + \ - str(self.gc_count_nicknames_colors)) - if highlight: - # muc-specific chatstate - if self.parent_win: - self.parent_win.redraw_tab(self, 'attention') - else: - self.attention_flag = True - other_tags_for_name.append('bold') - other_tags_for_text.append('marked') - - if contact in self.attention_list: - self.attention_list.remove(contact) - elif len(self.attention_list) > 6: - self.attention_list.pop(0) # remove older - self.attention_list.append(contact) - - if sound == 'received': - helpers.play_sound('muc_message_received') - elif sound == 'highlight': - helpers.play_sound('muc_message_highlight') - if text.startswith('/me ') or text.startswith('/me\n'): - other_tags_for_text.append('gc_nickname_color_' + \ - str(self.gc_custom_colors[contact])) - - self.check_and_possibly_add_focus_out_line() - - ChatControlBase.print_conversation_line(self, text, kind, contact, tim, - other_tags_for_name, [], other_tags_for_text, xhtml=xhtml, - graphics=graphics) - - def get_nb_unread(self): - type_events = ['printed_marked_gc_msg'] - if gajim.config.get('notify_on_all_muc_messages'): - type_events.append('printed_gc_msg') - nb = len(gajim.events.get_events(self.account, self.room_jid, - type_events)) - nb += self.get_nb_unread_pm() - return nb - - def get_nb_unread_pm(self): - nb = 0 - for nick in gajim.contacts.get_nick_list(self.account, self.room_jid): - nb += len(gajim.events.get_events(self.account, self.room_jid + '/' + \ - nick, ['pm'])) - return nb - - def highlighting_for_message(self, text, tim): - """ - Returns a 2-Tuple. The first says whether or not to highlight the text, - the second, what sound to play - """ - highlight, sound = (None, None) - - # Are any of the defined highlighting words in the text? - if self.needs_visual_notification(text): - highlight = True - if gajim.config.get_per('soundevents', 'muc_message_highlight', - 'enabled'): - sound = 'highlight' - - # Do we play a sound on every muc message? - elif gajim.config.get_per('soundevents', 'muc_message_received', \ - 'enabled'): - sound = 'received' - - # Is it a history message? Don't want sound-floods when we join. - if tim != time.localtime(): - sound = None - - return (highlight, sound) - - def check_and_possibly_add_focus_out_line(self): - """ - Check and possibly add focus out line for room_jid if it needs it and - does not already have it as last event. If it goes to add this line - - remove previous line first - """ - win = gajim.interface.msg_win_mgr.get_window(self.room_jid, self.account) - if win and self.room_jid == win.get_active_jid() and\ - win.window.get_property('has-toplevel-focus') and\ - self.parent_win.get_active_control() == self: - # it's the current room and it's the focused window. - # we have full focus (we are reading it!) - return - - self.conv_textview.show_focus_out_line() - - def needs_visual_notification(self, text): - """ - Check text to see whether any of the words in (muc_highlight_words and - nick) appear - """ - special_words = gajim.config.get('muc_highlight_words').split(';') - special_words.append(self.nick) - # Strip empties: ''.split(';') == [''] and would highlight everything. - # Also lowercase everything for case insensitive compare. - special_words = [word.lower() for word in special_words if word] - text = text.lower() - - for special_word in special_words: - found_here = text.find(special_word) - while(found_here > -1): - end_here = found_here + len(special_word) - if (found_here == 0 or not text[found_here - 1].isalpha()) and \ - (end_here == len(text) or not text[end_here].isalpha()): - # It is beginning of text or char before is not alpha AND - # it is end of text or char after is not alpha - return True - # continue searching - start = found_here + 1 - found_here = text.find(special_word, start) - return False - - def set_subject(self, subject): - self.subject = subject - self.draw_banner_text() - - def got_connected(self): - # Make autorejoin stop. - if self.autorejoin: - gobject.source_remove(self.autorejoin) - self.autorejoin = None - - gajim.gc_connected[self.account][self.room_jid] = True - ChatControlBase.got_connected(self) - # We don't redraw the whole banner here, because only icon change - self._update_banner_state_image() - if self.parent_win: - self.parent_win.redraw_tab(self) - - def got_disconnected(self): - self.list_treeview.get_model().clear() - nick_list = gajim.contacts.get_nick_list(self.account, self.room_jid) - for nick in nick_list: - # Update pm chat window - fjid = self.room_jid + '/' + nick - gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, - nick) - - ctrl = gajim.interface.msg_win_mgr.get_control(fjid, self.account) - if ctrl: - gc_contact.show = 'offline' - gc_contact.status = '' - ctrl.update_ui() - if ctrl.parent_win: - ctrl.parent_win.redraw_tab(ctrl) - - gajim.contacts.remove_gc_contact(self.account, gc_contact) - gajim.gc_connected[self.account][self.room_jid] = False - ChatControlBase.got_disconnected(self) - # Tell connection to note the date we disconnect to avoid duplicate logs - gajim.connections[self.account].gc_got_disconnected(self.room_jid) - # We don't redraw the whole banner here, because only icon change - self._update_banner_state_image() - if self.parent_win: - self.parent_win.redraw_tab(self) - - # Autorejoin stuff goes here. - # Notice that we don't need to activate autorejoin if connection is lost - # or in progress. - if self.autorejoin is None and gajim.account_is_connected(self.account): - ar_to = gajim.config.get('muc_autorejoin_timeout') - if ar_to: - self.autorejoin = gobject.timeout_add_seconds(ar_to, self.rejoin) - - def rejoin(self): - if not self.autorejoin: - return False - password = gajim.gc_passwords.get(self.room_jid, '') - gajim.connections[self.account].join_gc(self.nick, self.room_jid, - password) - return True - - def draw_roster(self): - self.list_treeview.get_model().clear() - for nick in gajim.contacts.get_nick_list(self.account, self.room_jid): - gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, - nick) - self.add_contact_to_roster(nick, gc_contact.show, gc_contact.role, - gc_contact.affiliation, gc_contact.status, gc_contact.jid) - self.draw_all_roles() - # Recalculate column width for ellipsizin - self.list_treeview.columns_autosize() - - def on_send_pm(self, widget=None, model=None, iter_=None, nick=None, - msg=None): - """ - Open a chat window and if msg is not None - send private message to a - contact in a room - """ - if nick is None: - nick = model[iter_][C_NICK].decode('utf-8') - - ctrl = self._start_private_message(nick) - if ctrl and msg: - ctrl.send_message(msg) - - def on_send_file(self, widget, gc_contact): - """ - Send a file to a contact in the room - """ - self._on_send_file(gc_contact) - - def draw_contact(self, nick, selected=False, focus=False): - iter_ = self.get_contact_iter(nick) - if not iter_: - return - model = self.list_treeview.get_model() - gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, - nick) - state_images = gajim.interface.jabber_state_images['16'] - if len(gajim.events.get_events(self.account, self.room_jid + '/' + nick)): - image = state_images['event'] - else: - image = state_images[gc_contact.show] - - name = gobject.markup_escape_text(gc_contact.name) - - # Strike name if blocked - fjid = self.room_jid + '/' + nick - if helpers.jid_is_blocked(self.account, fjid): - name = '%s' % name - - status = gc_contact.status - # add status msg, if not empty, under contact name in the treeview - if status and gajim.config.get('show_status_msgs_in_roster'): - status = status.strip() - if status != '': - status = helpers.reduce_chars_newlines(status, max_lines=1) - # escape markup entities and make them small italic and fg color - color = gtkgui_helpers._get_fade_color(self.list_treeview, - selected, focus) - colorstring = "#%04x%04x%04x" % (color.red, color.green, color.blue) - name += ('\n' - '%s') % (colorstring, gobject.markup_escape_text(status)) - - if image.get_storage_type() == gtk.IMAGE_PIXBUF and \ - gc_contact.affiliation != 'none' and gajim.config.get( - 'show_affiliation_in_groupchat'): - pixbuf1 = image.get_pixbuf().copy() - pixbuf2 = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, 4, 4) - if gc_contact.affiliation == 'owner': - pixbuf2.fill(0xff0000ff) # Red - elif gc_contact.affiliation == 'admin': - pixbuf2.fill(0xffb200ff) # Oragne - elif gc_contact.affiliation == 'member': - pixbuf2.fill(0x00ff00ff) # Green - pixbuf2.composite(pixbuf1, 12, 12, pixbuf2.get_property('width'), - pixbuf2.get_property('height'), 0, 0, 1.0, 1.0, - gtk.gdk.INTERP_HYPER, 127) - image = gtk.image_new_from_pixbuf(pixbuf1) - model[iter_][C_IMG] = image - model[iter_][C_TEXT] = name - - def draw_avatar(self, nick): - if not gajim.config.get('show_avatars_in_roster'): - return - model = self.list_treeview.get_model() - iter_ = self.get_contact_iter(nick) - if not iter_: - return - fake_jid = self.room_jid + '/' + nick - pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(fake_jid) - if pixbuf in ('ask', None): - scaled_pixbuf = None - else: - scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'roster') - model[iter_][C_AVATAR] = scaled_pixbuf - - def draw_role(self, role): - role_iter = self.get_role_iter(role) - if not role_iter: - return - model = self.list_treeview.get_model() - role_name = helpers.get_uf_role(role, plural=True) - if gajim.config.get('show_contacts_number'): - nbr_role, nbr_total = gajim.contacts.get_nb_role_total_gc_contacts( - self.account, self.room_jid, role) - role_name += ' (%s/%s)' % (repr(nbr_role), repr(nbr_total)) - model[role_iter][C_TEXT] = role_name - - def draw_all_roles(self): - for role in ('visitor', 'participant', 'moderator'): - self.draw_role(role) - - def chg_contact_status(self, nick, show, status, role, affiliation, jid, - reason, actor, statusCode, new_nick, avatar_sha, tim=None): - """ - When an occupant changes his or her status - """ - if show == 'invisible': - return - - if not role: - role = 'visitor' - if not affiliation: - affiliation = 'none' - fake_jid = self.room_jid + '/' + nick - newly_created = False - nick_jid = nick - - # Set to true if role or affiliation have changed - right_changed = False - - if jid: - # delete ressource - simple_jid = gajim.get_jid_without_resource(jid) - nick_jid += ' (%s)' % simple_jid - - # statusCode - # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes-init - if statusCode: - if '100' in statusCode: - # Can be a message (see handle_event_gc_config_change in gajim.py) - self.print_conversation(\ - _('Any occupant is allowed to see your full JID')) - if '170' in statusCode: - # Can be a message (see handle_event_gc_config_change in gajim.py) - self.print_conversation(_('Room logging is enabled')) - if '201' in statusCode: - self.print_conversation(_('A new room has been created')) - if '210' in statusCode: - self.print_conversation(\ - _('The server has assigned or modified your roomnick')) - - if show in ('offline', 'error'): - if statusCode: - if '307' in statusCode: - if actor is None: # do not print 'kicked by None' - s = _('%(nick)s has been kicked: %(reason)s') % { - 'nick': nick, - 'reason': reason } - else: - s = _('%(nick)s has been kicked by %(who)s: %(reason)s') % { - 'nick': nick, - 'who': actor, - 'reason': reason } - self.print_conversation(s, 'info', tim=tim, graphics=False) - if nick == self.nick and not gajim.config.get( - 'muc_autorejoin_on_kick'): - self.autorejoin = False - elif '301' in statusCode: - if actor is None: # do not print 'banned by None' - s = _('%(nick)s has been banned: %(reason)s') % { - 'nick': nick, - 'reason': reason } - else: - s = _('%(nick)s has been banned by %(who)s: %(reason)s') % { - 'nick': nick, - 'who': actor, - 'reason': reason } - self.print_conversation(s, 'info', tim=tim, graphics=False) - if nick == self.nick: - self.autorejoin = False - elif '303' in statusCode: # Someone changed his or her nick - if new_nick == self.new_nick or nick == self.nick: - # We changed our nick - self.nick = new_nick - self.new_nick = '' - s = _('You are now known as %s') % new_nick - # Stop all E2E sessions - nick_list = gajim.contacts.get_nick_list(self.account, - self.room_jid) - for nick_ in nick_list: - fjid_ = self.room_jid + '/' + nick_ - ctrl = gajim.interface.msg_win_mgr.get_control(fjid_, - self.account) - if ctrl and ctrl.session and \ - ctrl.session.enable_encryption: - thread_id = ctrl.session.thread_id - ctrl.session.terminate_e2e() - gajim.connections[self.account].delete_session(fjid_, - thread_id) - ctrl.no_autonegotiation = False - else: - s = _('%(nick)s is now known as %(new_nick)s') % { - 'nick': nick, 'new_nick': new_nick} - # We add new nick to muc roster here, so we don't see - # that "new_nick has joined the room" when he just changed nick. - # add_contact_to_roster will be called a second time - # after that, but that doesn't hurt - self.add_contact_to_roster(new_nick, show, role, affiliation, - status, jid) - if nick in self.attention_list: - self.attention_list.remove(nick) - # keep nickname color - if nick in self.gc_custom_colors: - self.gc_custom_colors[new_nick] = \ - self.gc_custom_colors[nick] - # rename vcard / avatar - puny_jid = helpers.sanitize_filename(self.room_jid) - puny_nick = helpers.sanitize_filename(nick) - puny_new_nick = helpers.sanitize_filename(new_nick) - old_path = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) - new_path = os.path.join(gajim.VCARD_PATH, puny_jid, - puny_new_nick) - files = {old_path: new_path} - path = os.path.join(gajim.AVATAR_PATH, puny_jid) - # possible extensions - for ext in ('.png', '.jpeg', '_notif_size_bw.png', - '_notif_size_colored.png'): - files[os.path.join(path, puny_nick + ext)] = \ - os.path.join(path, puny_new_nick + ext) - for old_file in files: - if os.path.exists(old_file) and old_file != files[old_file]: - if os.path.exists(files[old_file]) and helpers.windowsify( - old_file) != helpers.windowsify(files[old_file]): - # Windows require this, but os.remove('test') will also - # remove 'TEST' - os.remove(files[old_file]) - os.rename(old_file, files[old_file]) - self.print_conversation(s, 'info', tim=tim, graphics=False) - elif '321' in statusCode: - s = _('%(nick)s has been removed from the room (%(reason)s)') % { - 'nick': nick, 'reason': _('affiliation changed') } - self.print_conversation(s, 'info', tim=tim, graphics=False) - elif '322' in statusCode: - s = _('%(nick)s has been removed from the room (%(reason)s)') % { - 'nick': nick, - 'reason': _('room configuration changed to members-only') } - self.print_conversation(s, 'info', tim=tim, graphics=False) - elif '332' in statusCode: - s = _('%(nick)s has been removed from the room (%(reason)s)') % { - 'nick': nick, - 'reason': _('system shutdown') } - self.print_conversation(s, 'info', tim=tim, graphics=False) - # Room has been destroyed. - elif 'destroyed' in statusCode: - self.autorejoin = False - self.print_conversation(reason, 'info', tim, graphics=False) - - if len(gajim.events.get_events(self.account, jid=fake_jid, - types=['pm'])) == 0: - self.remove_contact(nick) - self.draw_all_roles() - else: - c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick) - c.show = show - c.status = status - if nick == self.nick and (not statusCode or \ - '303' not in statusCode): # We became offline - self.got_disconnected() - contact = gajim.contacts.\ - get_contact_with_highest_priority(self.account, self.room_jid) - if contact: - gajim.interface.roster.draw_contact(self.room_jid, self.account) - if self.parent_win: - self.parent_win.redraw_tab(self) - else: - iter_ = self.get_contact_iter(nick) - if not iter_: - if '210' in statusCode: - # Server changed our nick - self.nick = nick - s = _('You are now known as %s') % nick - self.print_conversation(s, 'info', tim=tim, graphics=False) - iter_ = self.add_contact_to_roster(nick, show, role, affiliation, - status, jid) - newly_created = True - self.draw_all_roles() - if statusCode and '201' in statusCode: # We just created the room - gajim.connections[self.account].request_gc_config(self.room_jid) - else: - gc_c = gajim.contacts.get_gc_contact(self.account, self.room_jid, - nick) - if not gc_c: - log.error('%s has an iter, but no gc_contact instance') - return - # Re-get vcard if avatar has changed - # We do that here because we may request it to the real JID if we - # knows it. connections.py doesn't know it. - con = gajim.connections[self.account] - if gc_c and gc_c.jid: - real_jid = gc_c.jid - if gc_c.resource: - real_jid += '/' + gc_c.resource - else: - real_jid = fake_jid - if fake_jid in con.vcard_shas: - if avatar_sha != con.vcard_shas[fake_jid]: - server = gajim.get_server_from_jid(self.room_jid) - if not server.startswith('irc'): - con.request_vcard(real_jid, fake_jid) - else: - cached_vcard = con.get_cached_vcard(fake_jid, True) - if cached_vcard and 'PHOTO' in cached_vcard and \ - 'SHA' in cached_vcard['PHOTO']: - cached_sha = cached_vcard['PHOTO']['SHA'] - else: - cached_sha = '' - if cached_sha != avatar_sha: - # avatar has been updated - # sha in mem will be updated later - server = gajim.get_server_from_jid(self.room_jid) - if not server.startswith('irc'): - con.request_vcard(real_jid, fake_jid) - else: - # save sha in mem NOW - con.vcard_shas[fake_jid] = avatar_sha - - actual_affiliation = gc_c.affiliation - if affiliation != actual_affiliation: - if actor: - st = _('** Affiliation of %(nick)s has been set to ' - '%(affiliation)s by %(actor)s') % {'nick': nick_jid, - 'affiliation': affiliation, 'actor': actor} - else: - st = _('** Affiliation of %(nick)s has been set to ' - '%(affiliation)s') % {'nick': nick_jid, - 'affiliation': affiliation} - if reason: - st += ' (%s)' % reason - self.print_conversation(st, tim=tim, graphics=False) - right_changed = True - actual_role = self.get_role(nick) - if role != actual_role: - self.remove_contact(nick) - self.add_contact_to_roster(nick, show, role, - affiliation, status, jid) - self.draw_role(actual_role) - self.draw_role(role) - if actor: - st = _('** Role of %(nick)s has been set to %(role)s by ' - '%(actor)s') % {'nick': nick_jid, 'role': role, - 'actor': actor} - else: - st = _('** Role of %(nick)s has been set to %(role)s') % { - 'nick': nick_jid, 'role': role} - if reason: - st += ' (%s)' % reason - self.print_conversation(st, tim=tim, graphics=False) - right_changed = True - else: - if gc_c.show == show and gc_c.status == status and \ - gc_c.affiliation == affiliation: # no change - return - gc_c.show = show - gc_c.affiliation = affiliation - gc_c.status = status - self.draw_contact(nick) - if (time.time() - self.room_creation) > 30 and nick != self.nick and \ - (not statusCode or '303' not in statusCode) and not right_changed: - st = '' - print_status = None - for bookmark in gajim.connections[self.account].bookmarks: - if bookmark['jid'] == self.room_jid: - print_status = bookmark.get('print_status', None) - break - if not print_status: - print_status = gajim.config.get('print_status_in_muc') - if show == 'offline': - if nick in self.attention_list: - self.attention_list.remove(nick) - if show == 'offline' and print_status in ('all', 'in_and_out') and \ - (not statusCode or '307' not in statusCode): - st = _('%s has left') % nick_jid - if reason: - st += ' [%s]' % reason - else: - if newly_created and print_status in ('all', 'in_and_out'): - st = _('%s has joined the group chat') % nick_jid - elif print_status == 'all': - st = _('%(nick)s is now %(status)s') % {'nick': nick_jid, - 'status': helpers.get_uf_show(show)} - if st: - if status: - st += ' (' + status + ')' - self.print_conversation(st, tim=tim, graphics=False) - - def add_contact_to_roster(self, nick, show, role, affiliation, status, - jid=''): - model = self.list_treeview.get_model() - role_name = helpers.get_uf_role(role, plural=True) - - resource = '' - if jid: - jids = jid.split('/', 1) - j = jids[0] - if len(jids) > 1: - resource = jids[1] - else: - j = '' - - name = nick - - role_iter = self.get_role_iter(role) - if not role_iter: - role_iter = model.append(None, - (gajim.interface.jabber_state_images['16']['closed'], role, - 'role', role_name, None)) - self.draw_all_roles() - iter_ = model.append(role_iter, (None, nick, 'contact', name, None)) - if not nick in gajim.contacts.get_nick_list(self.account, self.room_jid): - gc_contact = gajim.contacts.create_gc_contact(room_jid=self.room_jid, account=self.account, - name=nick, show=show, status=status, role=role, - affiliation=affiliation, jid=j, resource=resource) - gajim.contacts.add_gc_contact(self.account, gc_contact) - self.draw_contact(nick) - self.draw_avatar(nick) - # Do not ask avatar to irc rooms as irc transports reply with messages - server = gajim.get_server_from_jid(self.room_jid) - if gajim.config.get('ask_avatars_on_startup') and \ - not server.startswith('irc'): - fake_jid = self.room_jid + '/' + nick - pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(fake_jid) - if pixbuf == 'ask': - if j: - fjid = j - if resource: - fjid += '/' + resource - gajim.connections[self.account].request_vcard(fjid, fake_jid) - else: - gajim.connections[self.account].request_vcard(fake_jid, fake_jid) - if nick == self.nick: # we became online - self.got_connected() - self.list_treeview.expand_row((model.get_path(role_iter)), False) - if self.is_continued: - self.draw_banner_text() - return iter_ - - def get_role_iter(self, role): - model = self.list_treeview.get_model() - role_iter = model.get_iter_root() - while role_iter: - role_name = model[role_iter][C_NICK].decode('utf-8') - if role == role_name: - return role_iter - role_iter = model.iter_next(role_iter) - return None - - def remove_contact(self, nick): - """ - Remove a user from the contacts_list - """ - model = self.list_treeview.get_model() - iter_ = self.get_contact_iter(nick) - if not iter_: - return - gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, - nick) - if gc_contact: - gajim.contacts.remove_gc_contact(self.account, gc_contact) - parent_iter = model.iter_parent(iter_) - model.remove(iter_) - if model.iter_n_children(parent_iter) == 0: - model.remove(parent_iter) - - def send_message(self, message, xhtml=None, process_commands=True): - """ - Call this function to send our message - """ - if not message: - return - - if process_commands and self.process_as_command(message): - return - - message = helpers.remove_invalid_xml_chars(message) - - if not message: - return - - if message != '' or message != '\n': - self.save_sent_message(message) - - # Send the message - gajim.connections[self.account].send_gc_message(self.room_jid, - message, xhtml=xhtml) - self.msg_textview.get_buffer().set_text('') - self.msg_textview.grab_focus() - - def get_role(self, nick): - gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, - nick) - if gc_contact: - return gc_contact.role - else: - return 'visitor' - - def minimizable(self): - if self.contact.jid in gajim.config.get_per('accounts', self.account, - 'minimized_gc').split(' '): - return True - return False - - def minimize(self, status='offline'): - # Minimize it - win = gajim.interface.msg_win_mgr.get_window(self.contact.jid, - self.account) - ctrl = win.get_control(self.contact.jid, self.account) - - ctrl_page = win.notebook.page_num(ctrl.widget) - control = win.notebook.get_nth_page(ctrl_page) - - win.notebook.remove_page(ctrl_page) - control.unparent() - ctrl.parent_win = None - - gajim.interface.roster.add_groupchat(self.contact.jid, self.account, - status = self.subject) - - del win._controls[self.account][self.contact.jid] - - def shutdown(self, status='offline'): - # PluginSystem: calling shutdown of super class (ChatControlBase) - # to let it remove it's GUI extension points - super(GroupchatControl, self).shutdown() - - # Preventing autorejoin from being activated - self.autorejoin = False - - if self.room_jid in gajim.gc_connected[self.account] and \ - gajim.gc_connected[self.account][self.room_jid]: - # Tell connection to note the date we disconnect to avoid duplicate - # logs. We do it only when connected because if connection was lost - # there may be new messages since disconnection. - gajim.connections[self.account].gc_got_disconnected(self.room_jid) - gajim.connections[self.account].send_gc_status(self.nick, self.room_jid, - show='offline', status=status) - nick_list = gajim.contacts.get_nick_list(self.account, self.room_jid) - for nick in nick_list: - # Update pm chat window - fjid = self.room_jid + '/' + nick - ctrl = gajim.interface.msg_win_mgr.get_gc_control(fjid, self.account) - if ctrl: - contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, - nick) - contact.show = 'offline' - contact.status = '' - ctrl.update_ui() - ctrl.parent_win.redraw_tab(ctrl) - for sess in gajim.connections[self.account].get_sessions(fjid): - if sess.control: - sess.control.no_autonegotiation = False - if sess.enable_encryption: - sess.terminate_e2e() - gajim.connections[self.account].delete_session(fjid, - sess.thread_id) - # They can already be removed by the destroy function - if self.room_jid in gajim.contacts.get_gc_list(self.account): - gajim.contacts.remove_room(self.account, self.room_jid) - del gajim.gc_connected[self.account][self.room_jid] - # Save hpaned position - gajim.config.set('gc-hpaned-position', self.hpaned.get_position()) - # remove all register handlers on wigets, created by self.xml - # to prevent circular references among objects - for i in self.handlers.keys(): - if self.handlers[i].handler_is_connected(i): - self.handlers[i].disconnect(i) - del self.handlers[i] - # Remove unread events from systray - gajim.events.remove_events(self.account, self.room_jid) - - def safe_shutdown(self): - if self.minimizable(): - return True - includes = gajim.config.get('confirm_close_muc_rooms').split(' ') - excludes = gajim.config.get('noconfirm_close_muc_rooms').split(' ') - # whether to ask for comfirmation before closing muc - if (gajim.config.get('confirm_close_muc') or self.room_jid in includes) \ - and gajim.gc_connected[self.account][self.room_jid] and self.room_jid not\ - in excludes: - return False - return True - - def allow_shutdown(self, method, on_yes, on_no, on_minimize): - if self.minimizable(): - on_minimize(self) - return - if method == self.parent_win.CLOSE_ESC: - iter_ = self.list_treeview.get_selection().get_selected()[1] - if iter_: - self.list_treeview.get_selection().unselect_all() - on_no(self) - return - includes = gajim.config.get('confirm_close_muc_rooms').split(' ') - excludes = gajim.config.get('noconfirm_close_muc_rooms').split(' ') - # whether to ask for comfirmation before closing muc - if (gajim.config.get('confirm_close_muc') or self.room_jid in includes) \ - and gajim.gc_connected[self.account][self.room_jid] and self.room_jid not\ - in excludes: - - def on_ok(clicked): - if clicked: - # user does not want to be asked again - gajim.config.set('confirm_close_muc', False) - on_yes(self) - - def on_cancel(clicked): - if clicked: - # user does not want to be asked again - gajim.config.set('confirm_close_muc', False) - on_no(self) - - pritext = _('Are you sure you want to leave group chat "%s"?')\ - % self.name - sectext = _('If you close this window, you will be disconnected ' - 'from this group chat.') - - dialogs.ConfirmationDialogCheck(pritext, sectext, - _('Do _not ask me again'), on_response_ok=on_ok, - on_response_cancel=on_cancel) - return - - on_yes(self) - - def set_control_active(self, state): - self.conv_textview.allow_focus_out_line = True - self.attention_flag = False - ChatControlBase.set_control_active(self, state) - if not state: - # add the focus-out line to the tab we are leaving - self.check_and_possibly_add_focus_out_line() - # Sending active to undo unread state - self.parent_win.redraw_tab(self, 'active') - - def get_specific_unread(self): - # returns the number of the number of unread msgs - # for room_jid & number of unread private msgs with each contact - # that we have - nb = 0 - for nick in gajim.contacts.get_nick_list(self.account, self.room_jid): - fjid = self.room_jid + '/' + nick - nb += len(gajim.events.get_events(self.account, fjid)) - # gc can only have messages as event - return nb - - def _on_change_subject_menuitem_activate(self, widget): - def on_ok(subject): - # Note, we don't update self.subject since we don't know whether it - # will work yet - gajim.connections[self.account].send_gc_subject(self.room_jid, subject) - - dialogs.InputTextDialog(_('Changing Subject'), - _('Please specify the new subject:'), input_str=self.subject, - ok_handler=on_ok) - - def _on_change_nick_menuitem_activate(self, widget): - if 'change_nick_dialog' in gajim.interface.instances: - gajim.interface.instances['change_nick_dialog'].present() - else: - title = _('Changing Nickname') - prompt = _('Please specify the new nickname you want to use:') - gajim.interface.instances['change_nick_dialog'] = \ - dialogs.ChangeNickDialog(self.account, self.room_jid, title, - prompt) - - def _on_configure_room_menuitem_activate(self, widget): - c = gajim.contacts.get_gc_contact(self.account, self.room_jid, self.nick) - if c.affiliation == 'owner': - gajim.connections[self.account].request_gc_config(self.room_jid) - elif c.affiliation == 'admin': - if self.room_jid not in gajim.interface.instances[self.account][ - 'gc_config']: - gajim.interface.instances[self.account]['gc_config'][self.room_jid]\ - = config.GroupchatConfigWindow(self.account, self.room_jid) - - def _on_destroy_room_menuitem_activate(self, widget): - def on_ok(reason, jid): - if jid: - # Test jid - try: - jid = helpers.parse_jid(jid) - except Exception: - dialogs.ErrorDialog(_('Invalid group chat Jabber ID'), - _('The group chat Jabber ID has not allowed characters.')) - return - gajim.connections[self.account].destroy_gc_room(self.room_jid, reason, - jid) - - # Ask for a reason - dialogs.DubbleInputDialog(_('Destroying %s') % self.room_jid, - _('You are going to definitively destroy this room.\n' - 'You may specify a reason below:'), - _('You may also enter an alternate venue:'), ok_handler=on_ok) - - def _on_bookmark_room_menuitem_activate(self, widget): - """ - Bookmark the room, without autojoin and not minimized - """ - password = gajim.gc_passwords.get(self.room_jid, '') - gajim.interface.add_gc_bookmark(self.account, self.name, self.room_jid, \ - '0', '0', password, self.nick) - - def _on_drag_data_received(self, widget, context, x, y, selection, - target_type, timestamp): - # Invite contact to groupchat - treeview = gajim.interface.roster.tree - model = treeview.get_model() - if not selection.data or target_type == 80: - # target_type = 80 means a file is dropped - return - data = selection.data - path = treeview.get_selection().get_selected_rows()[1][0] - iter_ = model.get_iter(path) - type_ = model[iter_][2] - if type_ != 'contact': # source is not a contact - return - contact_jid = data.decode('utf-8') - gajim.connections[self.account].send_invite(self.room_jid, contact_jid) - - def handle_message_textview_mykey_press(self, widget, event_keyval, - event_keymod): - # NOTE: handles mykeypress which is custom signal connected to this - # CB in new_room(). for this singal see message_textview.py - - # construct event instance from binding - event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here - event.keyval = event_keyval - event.state = event_keymod - event.time = 0 # assign current time - - message_buffer = widget.get_buffer() - start_iter, end_iter = message_buffer.get_bounds() - - if event.keyval == gtk.keysyms.Tab: # TAB - cursor_position = message_buffer.get_insert() - end_iter = message_buffer.get_iter_at_mark(cursor_position) - text = message_buffer.get_text(start_iter, end_iter, False).decode( - 'utf-8') - - splitted_text = text.split() - - # HACK: Not the best soltution. - if (text.startswith(self.COMMAND_PREFIX) and not - text.startswith(self.COMMAND_PREFIX * 2) and len(splitted_text) == 1): - return super(GroupchatControl, - self).handle_message_textview_mykey_press(widget, event_keyval, - event_keymod) - - # nick completion - # check if tab is pressed with empty message - if len(splitted_text): # if there are any words - begin = splitted_text[-1] # last word we typed - else: - begin = '' - - gc_refer_to_nick_char = gajim.config.get('gc_refer_to_nick_char') - with_refer_to_nick_char = False - after_nick_len = 1 # the space that is printed after we type [Tab] - - # first part of this if : works fine even if refer_to_nick_char - if gc_refer_to_nick_char and text.endswith( - gc_refer_to_nick_char + ' '): - with_refer_to_nick_char = True - after_nick_len = len(gc_refer_to_nick_char + ' ') - if len(self.nick_hits) and self.last_key_tabs and \ - text[:-after_nick_len].endswith(self.nick_hits[0]): - # we should cycle - # Previous nick in list may had a space inside, so we check text and - # not splitted_text and store it into 'begin' var - self.nick_hits.append(self.nick_hits[0]) - begin = self.nick_hits.pop(0) - else: - self.nick_hits = [] # clear the hit list - list_nick = gajim.contacts.get_nick_list(self.account, - self.room_jid) - list_nick.sort(key=unicode.lower) # case-insensitive sort - if begin == '': - # empty message, show lasts nicks that highlighted us first - for nick in self.attention_list: - if nick in list_nick: - list_nick.remove(nick) - list_nick.insert(0, nick) - - list_nick.remove(self.nick) # Skip self - for nick in list_nick: - if nick.lower().startswith(begin.lower()): - # the word is the begining of a nick - self.nick_hits.append(nick) - if len(self.nick_hits): - if len(splitted_text) < 2 or with_refer_to_nick_char: - # This is the 1st word of the line or no word or we are cycling - # at the beginning, possibly with a space in one nick - add = gc_refer_to_nick_char + ' ' - else: - add = ' ' - start_iter = end_iter.copy() - if self.last_key_tabs and with_refer_to_nick_char or (text and \ - text[-1] == ' '): - # have to accomodate for the added space from last - # completion - # gc_refer_to_nick_char may be more than one char! - start_iter.backward_chars(len(begin) + len(add)) - elif self.last_key_tabs and not gajim.config.get( - 'shell_like_completion'): - # have to accomodate for the added space from last - # completion - start_iter.backward_chars(len(begin) + \ - len(gc_refer_to_nick_char)) - else: - start_iter.backward_chars(len(begin)) - - message_buffer.delete(start_iter, end_iter) - # get a shell-like completion - # if there's more than one nick for this completion, complete only - # the part that all these nicks have in common - if gajim.config.get('shell_like_completion') and \ - len(self.nick_hits) > 1: - end = False - completion = '' - add = "" # if nick is not complete, don't add anything - while not end and len(completion) < len(self.nick_hits[0]): - completion = self.nick_hits[0][:len(completion)+1] - for nick in self.nick_hits: - if completion.lower() not in nick.lower(): - end = True - completion = completion[:-1] - break - # if the current nick matches a COMPLETE existing nick, - # and if the user tab TWICE, complete that nick (with the "add") - if self.last_key_tabs: - for nick in self.nick_hits: - if nick == completion: - # The user seems to want this nick, so - # complete it as if it were the only nick - # available - add = gc_refer_to_nick_char + ' ' - else: - completion = self.nick_hits[0] - message_buffer.insert_at_cursor(completion + add) - self.last_key_tabs = True - return True - self.last_key_tabs = False - - def on_list_treeview_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: - selection = widget.get_selection() - iter_ = selection.get_selected()[1] - if iter_: - widget.get_selection().unselect_all() - return True - - def on_list_treeview_row_expanded(self, widget, iter_, path): - """ - When a row is expanded: change the icon of the arrow - """ - model = widget.get_model() - image = gajim.interface.jabber_state_images['16']['opened'] - model[iter_][C_IMG] = image - - def on_list_treeview_row_collapsed(self, widget, iter_, path): - """ - When a row is collapsed: change the icon of the arrow - """ - model = widget.get_model() - image = gajim.interface.jabber_state_images['16']['closed'] - model[iter_][C_IMG] = image - - def kick(self, widget, nick): - """ - Kick a user - """ - def on_ok(reason): - gajim.connections[self.account].gc_set_role(self.room_jid, nick, - 'none', reason) - - # ask for reason - dialogs.InputDialog(_('Kicking %s') % nick, - _('You may specify a reason below:'), ok_handler=on_ok) - - def mk_menu(self, event, iter_): - """ - Make contact's popup menu - """ - model = self.list_treeview.get_model() - nick = model[iter_][C_NICK].decode('utf-8') - c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick) - fjid = self.room_jid + '/' + nick - jid = c.jid - target_affiliation = c.affiliation - target_role = c.role - - # looking for user's affiliation and role - user_nick = self.nick - user_affiliation = gajim.contacts.get_gc_contact(self.account, - self.room_jid, user_nick).affiliation - user_role = self.get_role(user_nick) - - # making menu from gtk builder - xml = gtkgui_helpers.get_gtk_builder('gc_occupants_menu.ui') - - # these conditions were taken from JEP 0045 - item = xml.get_object('kick_menuitem') - if user_role != 'moderator' or \ - (user_affiliation == 'admin' and target_affiliation == 'owner') or \ - (user_affiliation == 'member' and target_affiliation in ('admin', - 'owner')) or (user_affiliation == 'none' and target_affiliation != \ - 'none'): - item.set_sensitive(False) - id_ = item.connect('activate', self.kick, nick) - self.handlers[id_] = item - - item = xml.get_object('voice_checkmenuitem') - item.set_active(target_role != 'visitor') - if user_role != 'moderator' or \ - user_affiliation == 'none' or \ - (user_affiliation=='member' and target_affiliation!='none') or \ - target_affiliation in ('admin', 'owner'): - item.set_sensitive(False) - id_ = item.connect('activate', self.on_voice_checkmenuitem_activate, - nick) - self.handlers[id_] = item - - item = xml.get_object('moderator_checkmenuitem') - item.set_active(target_role == 'moderator') - if not user_affiliation in ('admin', 'owner') or \ - target_affiliation in ('admin', 'owner'): - item.set_sensitive(False) - id_ = item.connect('activate', self.on_moderator_checkmenuitem_activate, - nick) - self.handlers[id_] = item - - item = xml.get_object('ban_menuitem') - if not user_affiliation in ('admin', 'owner') or \ - (target_affiliation in ('admin', 'owner') and\ - user_affiliation != 'owner'): - item.set_sensitive(False) - id_ = item.connect('activate', self.ban, jid) - self.handlers[id_] = item - - item = xml.get_object('member_checkmenuitem') - item.set_active(target_affiliation != 'none') - if not user_affiliation in ('admin', 'owner') or \ - (user_affiliation != 'owner' and target_affiliation in ('admin','owner')): - item.set_sensitive(False) - id_ = item.connect('activate', self.on_member_checkmenuitem_activate, jid) - self.handlers[id_] = item - - item = xml.get_object('admin_checkmenuitem') - item.set_active(target_affiliation in ('admin', 'owner')) - if not user_affiliation == 'owner': - item.set_sensitive(False) - id_ = item.connect('activate', self.on_admin_checkmenuitem_activate, jid) - self.handlers[id_] = item - - item = xml.get_object('owner_checkmenuitem') - item.set_active(target_affiliation == 'owner') - if not user_affiliation == 'owner': - item.set_sensitive(False) - id_ = item.connect('activate', self.on_owner_checkmenuitem_activate, jid) - self.handlers[id_] = item - - item = xml.get_object('information_menuitem') - id_ = item.connect('activate', self.on_info, nick) - self.handlers[id_] = item - - item = xml.get_object('history_menuitem') - id_ = item.connect('activate', self.on_history, nick) - self.handlers[id_] = item - - item = xml.get_object('add_to_roster_menuitem') - our_jid = gajim.get_jid_from_account(self.account) - if not jid or jid == our_jid: - item.set_sensitive(False) - else: - id_ = item.connect('activate', self.on_add_to_roster, jid) - self.handlers[id_] = item - - item = xml.get_object('block_menuitem') - item2 = xml.get_object('unblock_menuitem') - if helpers.jid_is_blocked(self.account, fjid): - item.set_no_show_all(True) - item.hide() - id_ = item2.connect('activate', self.on_unblock, nick) - self.handlers[id_] = item2 - else: - id_ = item.connect('activate', self.on_block, nick) - self.handlers[id_] = item - item2.set_no_show_all(True) - item2.hide() - - item = xml.get_object('send_private_message_menuitem') - id_ = item.connect('activate', self.on_send_pm, model, iter_) - self.handlers[id_] = item - - item = xml.get_object('send_file_menuitem') - # add a special img for send file menuitem - path_to_upload_img = gtkgui_helpers.get_icon_path('gajim-upload') - img = gtk.Image() - img.set_from_file(path_to_upload_img) - item.set_image(img) - - if not c.resource: - item.set_sensitive(False) - else: - id_ = item.connect('activate', self.on_send_file, c) - self.handlers[id_] = item - - # show the popup now! - menu = xml.get_object('gc_occupants_menu') - menu.show_all() - menu.popup(None, None, None, event.button, event.time) - - def _start_private_message(self, nick): - gc_c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick) - nick_jid = gc_c.get_full_jid() - - ctrl = gajim.interface.msg_win_mgr.get_control(nick_jid, self.account) - if not ctrl: - ctrl = gajim.interface.new_private_chat(gc_c, self.account) - - if ctrl: - ctrl.parent_win.set_active_tab(ctrl) - - return ctrl - - def on_row_activated(self, widget, path): - """ - When an iter is activated (dubblick or single click if gnome is set this - way - """ - model = widget.get_model() - if len(path) == 1: # It's a group - if (widget.row_expanded(path)): - widget.collapse_row(path) - else: - widget.expand_row(path, False) - else: # We want to send a private message - nick = model[path][C_NICK].decode('utf-8') - self._start_private_message(nick) - - def on_list_treeview_row_activated(self, widget, path, col=0): - """ - When an iter is double clicked: open the chat window - """ - if not gajim.single_click: - self.on_row_activated(widget, path) - - def on_list_treeview_button_press_event(self, widget, event): - """ - Popup user's group's or agent menu - """ - # hide tooltip, no matter the button is pressed - self.tooltip.hide_tooltip() - try: - pos = widget.get_path_at_pos(int(event.x), int(event.y)) - path, x = pos[0], pos[2] - except TypeError: - widget.get_selection().unselect_all() - return - if event.button == 3: # right click - widget.get_selection().select_path(path) - model = widget.get_model() - iter_ = model.get_iter(path) - if len(path) == 2: - self.mk_menu(event, iter_) - return True - - elif event.button == 2: # middle click - widget.get_selection().select_path(path) - model = widget.get_model() - iter_ = model.get_iter(path) - if len(path) == 2: - nick = model[iter_][C_NICK].decode('utf-8') - self._start_private_message(nick) - return True - - elif event.button == 1: # left click - if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK: - self.on_row_activated(widget, path) - return True - else: - model = widget.get_model() - iter_ = model.get_iter(path) - nick = model[iter_][C_NICK].decode('utf-8') - if not nick in gajim.contacts.get_nick_list(self.account, - self.room_jid): - # it's a group - if x < 27: - if (widget.row_expanded(path)): - widget.collapse_row(path) - else: - widget.expand_row(path, False) - elif event.state & gtk.gdk.SHIFT_MASK: - self.append_nick_in_msg_textview(self.msg_textview, nick) - self.msg_textview.grab_focus() - return True - - def append_nick_in_msg_textview(self, widget, nick): - message_buffer = self.msg_textview.get_buffer() - start_iter, end_iter = message_buffer.get_bounds() - cursor_position = message_buffer.get_insert() - end_iter = message_buffer.get_iter_at_mark(cursor_position) - text = message_buffer.get_text(start_iter, end_iter, False) - start = '' - if text: # Cursor is not at first position - if not text[-1] in (' ', '\n', '\t'): - start = ' ' - add = ' ' - else: - gc_refer_to_nick_char = gajim.config.get('gc_refer_to_nick_char') - add = gc_refer_to_nick_char + ' ' - message_buffer.insert_at_cursor(start + nick + add) - - def on_list_treeview_motion_notify_event(self, widget, event): - model = widget.get_model() - props = widget.get_path_at_pos(int(event.x), int(event.y)) - if self.tooltip.timeout > 0: - if not props or self.tooltip.id != props[0]: - self.tooltip.hide_tooltip() - if props: - [row, col, x, y] = props - iter_ = None - try: - iter_ = model.get_iter(row) - except Exception: - self.tooltip.hide_tooltip() - return - typ = model[iter_][C_TYPE].decode('utf-8') - if typ == 'contact': - account = self.account - - if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: - self.tooltip.id = row - nick = model[iter_][C_NICK].decode('utf-8') - self.tooltip.timeout = gobject.timeout_add(500, - self.show_tooltip, gajim.contacts.get_gc_contact(account, - self.room_jid, nick)) - - def on_list_treeview_leave_notify_event(self, widget, event): - props = widget.get_path_at_pos(int(event.x), int(event.y)) - if self.tooltip.timeout > 0: - if not props or self.tooltip.id == props[0]: - self.tooltip.hide_tooltip() - - def show_tooltip(self, contact): - if not self.list_treeview.window: - # control has been destroyed since tooltip was requested - return - pointer = self.list_treeview.get_pointer() - props = self.list_treeview.get_path_at_pos(pointer[0], pointer[1]) - # check if the current pointer is at the same path - # as it was before setting the timeout - if props and self.tooltip.id == props[0]: - rect = self.list_treeview.get_cell_area(props[0],props[1]) - position = self.list_treeview.window.get_origin() - self.tooltip.show_tooltip(contact, rect.height, - position[1] + rect.y) - else: - self.tooltip.hide_tooltip() - - def grant_voice(self, widget, nick): - """ - Grant voice privilege to a user - """ - gajim.connections[self.account].gc_set_role(self.room_jid, nick, - 'participant') - - def revoke_voice(self, widget, nick): - """ - Revoke voice privilege to a user - """ - gajim.connections[self.account].gc_set_role(self.room_jid, nick, - 'visitor') - - def grant_moderator(self, widget, nick): - """ - Grant moderator privilege to a user - """ - gajim.connections[self.account].gc_set_role(self.room_jid, nick, - 'moderator') - - def revoke_moderator(self, widget, nick): - """ - Revoke moderator privilege to a user - """ - gajim.connections[self.account].gc_set_role(self.room_jid, nick, - 'participant') - - def ban(self, widget, jid): - """ - Ban a user - """ - def on_ok(reason): - gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, - 'outcast', reason) - - # to ban we know the real jid. so jid is not fakejid - nick = gajim.get_nick_from_jid(jid) - # ask for reason - dialogs.InputDialog(_('Banning %s') % nick, - _('You may specify a reason below:'), ok_handler=on_ok) - - def grant_membership(self, widget, jid): - """ - Grant membership privilege to a user - """ - gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, - 'member') - - def revoke_membership(self, widget, jid): - """ - Revoke membership privilege to a user - """ - gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, - 'none') - - def grant_admin(self, widget, jid): - """ - Grant administrative privilege to a user - """ - gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, - 'admin') - - def revoke_admin(self, widget, jid): - """ - Revoke administrative privilege to a user - """ - gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, - 'member') - - def grant_owner(self, widget, jid): - """ - Grant owner privilege to a user - """ - gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, - 'owner') - - def revoke_owner(self, widget, jid): - """ - Revoke owner privilege to a user - """ - gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, - 'admin') - - def on_info(self, widget, nick): - """ - Call vcard_information_window class to display user's information - """ - gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick) - contact = gc_contact.as_contact() - if contact.jid in gajim.interface.instances[self.account]['infos']: - gajim.interface.instances[self.account]['infos'][contact.jid].window.\ - present() - else: - gajim.interface.instances[self.account]['infos'][contact.jid] = \ - vcard.VcardWindow(contact, self.account, gc_contact) - - def on_history(self, widget, nick): - jid = gajim.construct_fjid(self.room_jid, nick) - self._on_history_menuitem_activate(widget=widget, jid=jid) - - def on_add_to_roster(self, widget, jid): - dialogs.AddNewContactWindow(self.account, jid) - - def on_block(self, widget, nick): - fjid = self.room_jid + '/' + nick - connection = gajim.connections[self.account] - if fjid in connection.blocked_contacts: - return - new_rule = {'order': u'1', 'type': u'jid', 'action': u'deny', - 'value' : fjid, 'child': [u'message', u'iq', u'presence-out']} - connection.blocked_list.append(new_rule) - connection.blocked_contacts.append(fjid) - self.draw_contact(nick) - connection.set_privacy_list('block', connection.blocked_list) - if len(connection.blocked_list) == 1: - connection.set_active_list('block') - connection.set_default_list('block') - connection.get_privacy_list('block') - - def on_unblock(self, widget, nick): - fjid = self.room_jid + '/' + nick - connection = gajim.connections[self.account] - connection.new_blocked_list = [] - # needed for draw_contact: - if fjid in connection.blocked_contacts: - connection.blocked_contacts.remove(fjid) - self.draw_contact(nick) - for rule in connection.blocked_list: - if rule['action'] != 'deny' or rule['type'] != 'jid' \ - or rule['value'] != fjid: - connection.new_blocked_list.append(rule) - - connection.set_privacy_list('block', connection.new_blocked_list) - connection.get_privacy_list('block') - if len(connection.new_blocked_list) == 0: - connection.blocked_list = [] - connection.blocked_contacts = [] - connection.blocked_groups = [] - connection.set_default_list('') - connection.set_active_list('') - connection.del_privacy_list('block') - if 'blocked_contacts' in gajim.interface.instances[self.account]: - gajim.interface.instances[self.account]['blocked_contacts'].\ - privacy_list_received([]) - - def on_voice_checkmenuitem_activate(self, widget, nick): - if widget.get_active(): - self.grant_voice(widget, nick) - else: - self.revoke_voice(widget, nick) - - def on_moderator_checkmenuitem_activate(self, widget, nick): - if widget.get_active(): - self.grant_moderator(widget, nick) - else: - self.revoke_moderator(widget, nick) - - def on_member_checkmenuitem_activate(self, widget, jid): - if widget.get_active(): - self.grant_membership(widget, jid) - else: - self.revoke_membership(widget, jid) - - def on_admin_checkmenuitem_activate(self, widget, jid): - if widget.get_active(): - self.grant_admin(widget, jid) - else: - self.revoke_admin(widget, jid) - - def on_owner_checkmenuitem_activate(self, widget, jid): - if widget.get_active(): - self.grant_owner(widget, jid) - else: - self.revoke_owner(widget, jid) - -# vim: se ts=3: + TYPE_ID = message_control.TYPE_GC + + # Set a command host to bound to. Every command given through a group chat + # will be processed with this command host. + COMMAND_HOST = GroupChatCommands + + def __init__(self, parent_win, contact, acct, is_continued=False): + ChatControlBase.__init__(self, self.TYPE_ID, parent_win, + 'groupchat_control', contact, acct) + + self.is_continued=is_continued + self.is_anonymous = True + + # Controls the state of autorejoin. + # None - autorejoin is neutral. + # False - autorejoin is to be prevented (gets reset to initial state in + # got_connected()). + # int - autorejoin is being active and working (gets reset to initial + # state in got_connected()). + self.autorejoin = None + + self.actions_button = self.xml.get_object('muc_window_actions_button') + id_ = self.actions_button.connect('clicked', + self.on_actions_button_clicked) + self.handlers[id_] = self.actions_button + + widget = self.xml.get_object('change_nick_button') + id_ = widget.connect('clicked', self._on_change_nick_menuitem_activate) + self.handlers[id_] = widget + + widget = self.xml.get_object('change_subject_button') + id_ = widget.connect('clicked', self._on_change_subject_menuitem_activate) + self.handlers[id_] = widget + + widget = self.xml.get_object('bookmark_button') + for bm in gajim.connections[self.account].bookmarks: + if bm['jid'] == self.contact.jid: + widget.hide() + break + else: + id_ = widget.connect('clicked', + self._on_bookmark_room_menuitem_activate) + self.handlers[id_] = widget + widget.show() + + widget = self.xml.get_object('list_treeview') + id_ = widget.connect('row_expanded', self.on_list_treeview_row_expanded) + self.handlers[id_] = widget + + id_ = widget.connect('row_collapsed', self.on_list_treeview_row_collapsed) + self.handlers[id_] = widget + + id_ = widget.connect('row_activated', + self.on_list_treeview_row_activated) + self.handlers[id_] = widget + + id_ = widget.connect('button_press_event', + self.on_list_treeview_button_press_event) + self.handlers[id_] = widget + + id_ = widget.connect('key_press_event', + self.on_list_treeview_key_press_event) + self.handlers[id_] = widget + + id_ = widget.connect('motion_notify_event', + self.on_list_treeview_motion_notify_event) + self.handlers[id_] = widget + + id_ = widget.connect('leave_notify_event', + self.on_list_treeview_leave_notify_event) + self.handlers[id_] = widget + + self.room_jid = self.contact.jid + self.nick = contact.name.decode('utf-8') + self.new_nick = '' + self.name = '' + for bm in gajim.connections[self.account].bookmarks: + if bm['jid'] == self.room_jid: + self.name = bm['name'] + break + if not self.name: + self.name = self.room_jid.split('@')[0] + + compact_view = gajim.config.get('compact_view') + self.chat_buttons_set_visible(compact_view) + self.widget_set_visible(self.xml.get_object('banner_eventbox'), + gajim.config.get('hide_groupchat_banner')) + self.widget_set_visible(self.xml.get_object('list_scrolledwindow'), + gajim.config.get('hide_groupchat_occupants_list')) + + self._last_selected_contact = None # None or holds jid, account tuple + + # muc attention flag (when we are mentioned in a muc) + # if True, the room has mentioned us + self.attention_flag = False + + # sorted list of nicks who mentioned us (last at the end) + self.attention_list = [] + self.room_creation = int(time.time()) # Use int to reduce mem usage + self.nick_hits = [] + self.last_key_tabs = False + + self.subject = '' + + self.tooltip = tooltips.GCTooltip() + + # nickname coloring + self.gc_count_nicknames_colors = 0 + self.gc_custom_colors = {} + self.number_of_colors = len(gajim.config.get('gc_nicknames_colors').\ + split(':')) + + self.name_label = self.xml.get_object('banner_name_label') + self.event_box = self.xml.get_object('banner_eventbox') + + # set the position of the current hpaned + hpaned_position = gajim.config.get('gc-hpaned-position') + self.hpaned = self.xml.get_object('hpaned') + self.hpaned.set_position(hpaned_position) + + self.list_treeview = self.xml.get_object('list_treeview') + selection = self.list_treeview.get_selection() + id_ = selection.connect('changed', + self.on_list_treeview_selection_changed) + self.handlers[id_] = selection + id_ = self.list_treeview.connect('style-set', + self.on_list_treeview_style_set) + self.handlers[id_] = self.list_treeview + self.resize_from_another_muc = False + # we want to know when the the widget resizes, because that is + # an indication that the hpaned has moved... + # FIXME: Find a better indicator that the hpaned has moved. + id_ = self.list_treeview.connect('size-allocate', + self.on_treeview_size_allocate) + self.handlers[id_] = self.list_treeview + #status_image, shown_nick, type, nickname, avatar + store = gtk.TreeStore(gtk.Image, str, str, str, gtk.gdk.Pixbuf) + store.set_sort_func(C_NICK, self.tree_compare_iters) + store.set_sort_column_id(C_NICK, gtk.SORT_ASCENDING) + self.list_treeview.set_model(store) + + # columns + + # this col has 3 cells: + # first one img, second one text, third is sec pixbuf + column = gtk.TreeViewColumn() + + def add_avatar_renderer(): + renderer_pixbuf = gtk.CellRendererPixbuf() # avatar image + column.pack_start(renderer_pixbuf, expand=False) + column.add_attribute(renderer_pixbuf, 'pixbuf', C_AVATAR) + column.set_cell_data_func(renderer_pixbuf, tree_cell_data_func, + self.list_treeview) + + if gajim.config.get('avatar_position_in_roster') == 'left': + add_avatar_renderer() + + renderer_image = cell_renderer_image.CellRendererImage(0, 0) # status img + renderer_image.set_property('width', 26) + column.pack_start(renderer_image, expand=False) + column.add_attribute(renderer_image, 'image', C_IMG) + column.set_cell_data_func(renderer_image, tree_cell_data_func, + self.list_treeview) + + renderer_text = gtk.CellRendererText() # nickname + column.pack_start(renderer_text, expand=True) + column.add_attribute(renderer_text, 'markup', C_TEXT) + renderer_text.set_property("ellipsize", pango.ELLIPSIZE_END) + column.set_cell_data_func(renderer_text, tree_cell_data_func, + self.list_treeview) + + if gajim.config.get('avatar_position_in_roster') == 'right': + add_avatar_renderer() + + self.list_treeview.append_column(column) + + # workaround to avoid gtk arrows to be shown + column = gtk.TreeViewColumn() # 2nd COLUMN + renderer = gtk.CellRendererPixbuf() + column.pack_start(renderer, expand=False) + self.list_treeview.append_column(column) + column.set_visible(False) + self.list_treeview.set_expander_column(column) + + gajim.gc_connected[self.account][self.room_jid] = False + # disable win, we are not connected yet + ChatControlBase.got_disconnected(self) + + self.update_ui() + self.conv_textview.tv.grab_focus() + self.widget.show_all() + + def tree_compare_iters(self, model, iter1, iter2): + """ + Compare two iters to sort them + """ + type1 = model[iter1][C_TYPE] + type2 = model[iter2][C_TYPE] + if not type1 or not type2: + return 0 + nick1 = model[iter1][C_NICK] + nick2 = model[iter2][C_NICK] + if not nick1 or not nick2: + return 0 + nick1 = nick1.decode('utf-8') + nick2 = nick2.decode('utf-8') + if type1 == 'role': + return locale.strcoll(nick1, nick2) + if type1 == 'contact': + gc_contact1 = gajim.contacts.get_gc_contact(self.account, + self.room_jid, nick1) + if not gc_contact1: + return 0 + if type2 == 'contact': + gc_contact2 = gajim.contacts.get_gc_contact(self.account, + self.room_jid, nick2) + if not gc_contact2: + return 0 + if type1 == 'contact' and type2 == 'contact' and \ + gajim.config.get('sort_by_show_in_muc'): + cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4, + 'invisible': 5, 'offline': 6, 'error': 7} + show1 = cshow[gc_contact1.show] + show2 = cshow[gc_contact2.show] + if show1 < show2: + return -1 + elif show1 > show2: + return 1 + # We compare names + name1 = gc_contact1.get_shown_name() + name2 = gc_contact2.get_shown_name() + return locale.strcoll(name1.lower(), name2.lower()) + + def on_msg_textview_populate_popup(self, textview, menu): + """ + Override the default context menu and we prepend Clear + and the ability to insert a nick + """ + ChatControlBase.on_msg_textview_populate_popup(self, textview, menu) + item = gtk.SeparatorMenuItem() + menu.prepend(item) + + item = gtk.MenuItem(_('Insert Nickname')) + menu.prepend(item) + submenu = gtk.Menu() + item.set_submenu(submenu) + + for nick in sorted(gajim.contacts.get_nick_list(self.account, + self.room_jid)): + item = gtk.MenuItem(nick, use_underline=False) + submenu.append(item) + id_ = item.connect('activate', self.append_nick_in_msg_textview, nick) + self.handlers[id_] = item + + menu.show_all() + + def resize_occupant_treeview(self, position): + self.resize_from_another_muc = True + self.hpaned.set_position(position) + def reset_flag(): + self.resize_from_another_muc = False + # Reset the flag when everything will be redrawn, and in particular when + # on_treeview_size_allocate will have been called. + gobject.idle_add(reset_flag) + + def on_treeview_size_allocate(self, widget, allocation): + """ + The MUC treeview has resized. Move the hpaned in all tabs to match + """ + if self.resize_from_another_muc: + # Don't send the event to other MUC + return + hpaned_position = self.hpaned.get_position() + for account in gajim.gc_connected: + for room_jid in [i for i in gajim.gc_connected[account] if \ + gajim.gc_connected[account][i] and i != self.room_jid]: + ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid, account) + if not ctrl: + ctrl = gajim.interface.minimized_controls[account][room_jid] + if ctrl: + ctrl.resize_occupant_treeview(hpaned_position) + + def iter_contact_rows(self): + """ + Iterate over all contact rows in the tree model + """ + model = self.list_treeview.get_model() + role_iter = model.get_iter_root() + while role_iter: + contact_iter = model.iter_children(role_iter) + while contact_iter: + yield model[contact_iter] + contact_iter = model.iter_next(contact_iter) + role_iter = model.iter_next(role_iter) + + def on_list_treeview_style_set(self, treeview, style): + """ + When style (theme) changes, redraw all contacts + """ + # Get the room_jid from treeview + for contact in self.iter_contact_rows(): + nick = contact[C_NICK].decode('utf-8') + self.draw_contact(nick) + + def on_list_treeview_selection_changed(self, selection): + model, selected_iter = selection.get_selected() + self.draw_contact(self.nick) + if self._last_selected_contact is not None: + self.draw_contact(self._last_selected_contact) + if selected_iter is None: + self._last_selected_contact = None + return + contact = model[selected_iter] + nick = contact[C_NICK].decode('utf-8') + self._last_selected_contact = nick + if contact[C_TYPE] != 'contact': + return + self.draw_contact(nick, selected=True, focus=True) + + def get_tab_label(self, chatstate): + """ + Markup the label if necessary. Returns a tuple such as: (new_label_str, + color) either of which can be None if chatstate is given that means we + have HE SENT US a chatstate + """ + + has_focus = self.parent_win.window.get_property('has-toplevel-focus') + current_tab = self.parent_win.get_active_control() == self + color_name = None + color = None + theme = gajim.config.get('roster_theme') + if chatstate == 'attention' and (not has_focus or not current_tab): + self.attention_flag = True + color_name = gajim.config.get_per('themes', theme, + 'state_muc_directed_msg_color') + elif chatstate: + if chatstate == 'active' or (current_tab and has_focus): + self.attention_flag = False + # get active color from gtk + color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE] + elif chatstate == 'newmsg' and (not has_focus or not current_tab) and\ + not self.attention_flag: + color_name = gajim.config.get_per('themes', theme, + 'state_muc_msg_color') + if color_name: + color = gtk.gdk.colormap_get_system().alloc_color(color_name) + + if self.is_continued: + # if this is a continued conversation + label_str = self.get_continued_conversation_name() + else: + label_str = self.name + + # count waiting highlighted messages + unread = '' + num_unread = self.get_nb_unread() + if num_unread == 1: + unread = '*' + elif num_unread > 1: + unread = '[' + unicode(num_unread) + ']' + label_str = unread + label_str + return (label_str, color) + + def get_tab_image(self, count_unread=True): + # Set tab image (always 16x16) + tab_image = None + if gajim.gc_connected[self.account][self.room_jid]: + tab_image = gtkgui_helpers.load_icon('muc_active') + else: + tab_image = gtkgui_helpers.load_icon('muc_inactive') + return tab_image + + def update_ui(self): + ChatControlBase.update_ui(self) + for nick in gajim.contacts.get_nick_list(self.account, self.room_jid): + self.draw_contact(nick) + + def _change_style(self, model, path, iter_): + model[iter_][C_NICK] = model[iter_][C_NICK] + + def change_roster_style(self): + model = self.list_treeview.get_model() + model.foreach(self._change_style) + + def repaint_themed_widgets(self): + ChatControlBase.repaint_themed_widgets(self) + self.change_roster_style() + + def _update_banner_state_image(self): + banner_status_img = self.xml.get_object('banner_status_image') + images = gajim.interface.jabber_state_images + if self.room_jid in gajim.gc_connected[self.account] and \ + gajim.gc_connected[self.account][self.room_jid]: + image = 'muc_active' + else: + image = 'muc_inactive' + if '32' in images and image in images['32']: + muc_icon = images['32'][image] + if muc_icon.get_storage_type() != gtk.IMAGE_EMPTY: + pix = muc_icon.get_pixbuf() + banner_status_img.set_from_pixbuf(pix) + return + # we need to scale 16x16 to 32x32 + muc_icon = images['16'][image] + pix = muc_icon.get_pixbuf() + scaled_pix = pix.scale_simple(32, 32, gtk.gdk.INTERP_BILINEAR) + banner_status_img.set_from_pixbuf(scaled_pix) + + def get_continued_conversation_name(self): + """ + Get the name of a continued conversation. Will return Continued + Conversation if there isn't any other contact in the room + """ + nicks = [] + for nick in gajim.contacts.get_nick_list(self.account, + self.room_jid): + if nick != self.nick: + nicks.append(nick) + if nicks != []: + title = ', ' + title = _('Conversation with ') + title.join(nicks) + else: + title = _('Continued conversation') + return title + + def draw_banner_text(self): + """ + Draw the text in the fat line at the top of the window that houses the + room jid, subject + """ + self.name_label.set_ellipsize(pango.ELLIPSIZE_END) + self.banner_status_label.set_ellipsize(pango.ELLIPSIZE_END) + font_attrs, font_attrs_small = self.get_font_attrs() + if self.is_continued: + name = self.get_continued_conversation_name() + else: + name = self.room_jid + text = '%s' % (font_attrs, name) + self.name_label.set_markup(text) + + if self.subject: + subject = helpers.reduce_chars_newlines(self.subject, max_lines=2) + subject = gobject.markup_escape_text(subject) + subject_text = self.urlfinder.sub(self.make_href, subject) + subject_text = '%s' % (font_attrs_small, subject_text) + + # tooltip must always hold ALL the subject + self.event_box.set_tooltip_text(self.subject) + self.banner_status_label.set_no_show_all(False) + self.banner_status_label.show() + else: + subject_text = '' + self.event_box.set_has_tooltip(False) + self.banner_status_label.hide() + self.banner_status_label.set_no_show_all(True) + + self.banner_status_label.set_markup(subject_text) + + def prepare_context_menu(self, hide_buttonbar_items=False): + """ + Set sensitivity state for configure_room + """ + xml = gtkgui_helpers.get_gtk_builder('gc_control_popup_menu.ui') + menu = xml.get_object('gc_control_popup_menu') + + bookmark_room_menuitem = xml.get_object('bookmark_room_menuitem') + change_nick_menuitem = xml.get_object('change_nick_menuitem') + configure_room_menuitem = xml.get_object('configure_room_menuitem') + destroy_room_menuitem = xml.get_object('destroy_room_menuitem') + change_subject_menuitem = xml.get_object('change_subject_menuitem') + history_menuitem = xml.get_object('history_menuitem') + minimize_menuitem = xml.get_object('minimize_menuitem') + bookmark_separator = xml.get_object('bookmark_separator') + separatormenuitem2 = xml.get_object('separatormenuitem2') + + if hide_buttonbar_items: + change_nick_menuitem.hide() + change_subject_menuitem.hide() + bookmark_room_menuitem.hide() + history_menuitem.hide() + bookmark_separator.hide() + separatormenuitem2.hide() + else: + change_nick_menuitem.show() + change_subject_menuitem.show() + bookmark_room_menuitem.show() + history_menuitem.show() + bookmark_separator.show() + separatormenuitem2.show() + for bm in gajim.connections[self.account].bookmarks: + if bm['jid'] == self.room_jid: + bookmark_room_menuitem.hide() + bookmark_separator.hide() + break + + ag = gtk.accel_groups_from_object(self.parent_win.window)[0] + change_nick_menuitem.add_accelerator('activate', ag, gtk.keysyms.n, + gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK, gtk.ACCEL_VISIBLE) + change_subject_menuitem.add_accelerator('activate', ag, + gtk.keysyms.t, gtk.gdk.MOD1_MASK, gtk.ACCEL_VISIBLE) + bookmark_room_menuitem.add_accelerator('activate', ag, gtk.keysyms.b, + gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) + history_menuitem.add_accelerator('activate', ag, gtk.keysyms.h, + gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) + + if self.contact.jid in gajim.config.get_per('accounts', self.account, + 'minimized_gc').split(' '): + minimize_menuitem.set_active(True) + if not gajim.connections[self.account].private_storage_supported: + bookmark_room_menuitem.set_sensitive(False) + if gajim.gc_connected[self.account][self.room_jid]: + c = gajim.contacts.get_gc_contact(self.account, self.room_jid, + self.nick) + if c.affiliation not in ('owner', 'admin'): + configure_room_menuitem.set_sensitive(False) + else: + configure_room_menuitem.set_sensitive(True) + if c.affiliation != 'owner': + destroy_room_menuitem.set_sensitive(False) + else: + destroy_room_menuitem.set_sensitive(True) + change_subject_menuitem.set_sensitive(True) + change_nick_menuitem.set_sensitive(True) + else: + # We are not connected to this groupchat, disable unusable menuitems + configure_room_menuitem.set_sensitive(False) + destroy_room_menuitem.set_sensitive(False) + change_subject_menuitem.set_sensitive(False) + change_nick_menuitem.set_sensitive(False) + + # connect the menuitems to their respective functions + id_ = bookmark_room_menuitem.connect('activate', + self._on_bookmark_room_menuitem_activate) + self.handlers[id_] = bookmark_room_menuitem + + id_ = change_nick_menuitem.connect('activate', + self._on_change_nick_menuitem_activate) + self.handlers[id_] = change_nick_menuitem + + id_ = configure_room_menuitem.connect('activate', + self._on_configure_room_menuitem_activate) + self.handlers[id_] = configure_room_menuitem + + id_ = destroy_room_menuitem.connect('activate', + self._on_destroy_room_menuitem_activate) + self.handlers[id_] = destroy_room_menuitem + + id_ = change_subject_menuitem.connect('activate', + self._on_change_subject_menuitem_activate) + self.handlers[id_] = change_subject_menuitem + + id_ = history_menuitem.connect('activate', + self._on_history_menuitem_activate) + self.handlers[id_] = history_menuitem + + id_ = minimize_menuitem.connect('toggled', + self.on_minimize_menuitem_toggled) + self.handlers[id_] = minimize_menuitem + + menu.connect('selection-done', self.destroy_menu, + change_nick_menuitem, change_subject_menuitem, + bookmark_room_menuitem, history_menuitem) + return menu + + def destroy_menu(self, menu, change_nick_menuitem, change_subject_menuitem, + bookmark_room_menuitem, history_menuitem): + # destroy accelerators + ag = gtk.accel_groups_from_object(self.parent_win.window)[0] + change_nick_menuitem.remove_accelerator(ag, gtk.keysyms.n, + gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK) + change_subject_menuitem.remove_accelerator(ag, gtk.keysyms.t, + gtk.gdk.MOD1_MASK) + bookmark_room_menuitem.remove_accelerator(ag, gtk.keysyms.b, + gtk.gdk.CONTROL_MASK) + history_menuitem.remove_accelerator(ag, gtk.keysyms.h, + gtk.gdk.CONTROL_MASK) + # destroy menu + menu.destroy() + + def on_message(self, nick, msg, tim, has_timestamp=False, xhtml=None, + status_code=[]): + if '100' in status_code: + # Room is not anonymous + self.is_anonymous = False + if not nick: + # message from server + self.print_conversation(msg, tim=tim, xhtml=xhtml) + else: + # message from someone + if has_timestamp: + # don't print xhtml if it's an old message. + # Like that xhtml messages are grayed too. + self.print_old_conversation(msg, nick, tim, None) + else: + self.print_conversation(msg, nick, tim, xhtml) + + def on_private_message(self, nick, msg, tim, xhtml, session, msg_id=None, + encrypted=False): + # Do we have a queue? + fjid = self.room_jid + '/' + nick + no_queue = len(gajim.events.get_events(self.account, fjid)) == 0 + + event = gajim.events.create_event('pm', (msg, '', 'incoming', tim, + encrypted, '', msg_id, xhtml, session)) + gajim.events.add_event(self.account, fjid, event) + + autopopup = gajim.config.get('autopopup') + autopopupaway = gajim.config.get('autopopupaway') + iter_ = self.get_contact_iter(nick) + path = self.list_treeview.get_model().get_path(iter_) + if not autopopup or (not autopopupaway and \ + gajim.connections[self.account].connected > 2): + if no_queue: # We didn't have a queue: we change icons + model = self.list_treeview.get_model() + state_images =\ + gajim.interface.roster.get_appropriate_state_images( + self.room_jid, icon_name='event') + image = state_images['event'] + model[iter_][C_IMG] = image + if self.parent_win: + self.parent_win.show_title() + self.parent_win.redraw_tab(self) + else: + self._start_private_message(nick) + # Scroll to line + self.list_treeview.expand_row(path[0:1], False) + self.list_treeview.scroll_to_cell(path) + self.list_treeview.set_cursor(path) + contact = gajim.contacts.get_contact_with_highest_priority(self.account, \ + self.room_jid) + if contact: + gajim.interface.roster.draw_contact(self.room_jid, self.account) + + def get_contact_iter(self, nick): + model = self.list_treeview.get_model() + role_iter = model.get_iter_root() + while role_iter: + user_iter = model.iter_children(role_iter) + while user_iter: + if nick == model[user_iter][C_NICK].decode('utf-8'): + return user_iter + else: + user_iter = model.iter_next(user_iter) + role_iter = model.iter_next(role_iter) + return None + + def print_old_conversation(self, text, contact='', tim=None, xhtml = None): + if isinstance(text, str): + text = unicode(text, 'utf-8') + if contact: + if contact == self.nick: # it's us + kind = 'outgoing' + else: + kind = 'incoming' + else: + kind = 'status' + if gajim.config.get('restored_messages_small'): + small_attr = ['small'] + else: + small_attr = [] + ChatControlBase.print_conversation_line(self, text, kind, contact, tim, + small_attr, small_attr + ['restored_message'], + small_attr + ['restored_message'], count_as_new=False, xhtml=xhtml) + + def print_conversation(self, text, contact='', tim=None, xhtml=None, + graphics=True): + """ + Print a line in the conversation + + If contact is set: it's a message from someone or an info message + (contact = 'info' in such a case). + If contact is not set: it's a message from the server or help. + """ + if isinstance(text, str): + text = unicode(text, 'utf-8') + other_tags_for_name = [] + other_tags_for_text = [] + if contact: + if contact == self.nick: # it's us + kind = 'outgoing' + elif contact == 'info': + kind = 'info' + contact = None + else: + kind = 'incoming' + # muc-specific chatstate + if self.parent_win: + self.parent_win.redraw_tab(self, 'newmsg') + else: + kind = 'status' + + if kind == 'incoming': # it's a message NOT from us + # highlighting and sounds + (highlight, sound) = self.highlighting_for_message(text, tim) + if contact in self.gc_custom_colors: + other_tags_for_name.append('gc_nickname_color_' + \ + str(self.gc_custom_colors[contact])) + else: + self.gc_count_nicknames_colors += 1 + if self.gc_count_nicknames_colors == self.number_of_colors: + self.gc_count_nicknames_colors = 0 + self.gc_custom_colors[contact] = \ + self.gc_count_nicknames_colors + other_tags_for_name.append('gc_nickname_color_' + \ + str(self.gc_count_nicknames_colors)) + if highlight: + # muc-specific chatstate + if self.parent_win: + self.parent_win.redraw_tab(self, 'attention') + else: + self.attention_flag = True + other_tags_for_name.append('bold') + other_tags_for_text.append('marked') + + if contact in self.attention_list: + self.attention_list.remove(contact) + elif len(self.attention_list) > 6: + self.attention_list.pop(0) # remove older + self.attention_list.append(contact) + + if sound == 'received': + helpers.play_sound('muc_message_received') + elif sound == 'highlight': + helpers.play_sound('muc_message_highlight') + if text.startswith('/me ') or text.startswith('/me\n'): + other_tags_for_text.append('gc_nickname_color_' + \ + str(self.gc_custom_colors[contact])) + + self.check_and_possibly_add_focus_out_line() + + ChatControlBase.print_conversation_line(self, text, kind, contact, tim, + other_tags_for_name, [], other_tags_for_text, xhtml=xhtml, + graphics=graphics) + + def get_nb_unread(self): + type_events = ['printed_marked_gc_msg'] + if gajim.config.get('notify_on_all_muc_messages'): + type_events.append('printed_gc_msg') + nb = len(gajim.events.get_events(self.account, self.room_jid, + type_events)) + nb += self.get_nb_unread_pm() + return nb + + def get_nb_unread_pm(self): + nb = 0 + for nick in gajim.contacts.get_nick_list(self.account, self.room_jid): + nb += len(gajim.events.get_events(self.account, self.room_jid + '/' + \ + nick, ['pm'])) + return nb + + def highlighting_for_message(self, text, tim): + """ + Returns a 2-Tuple. The first says whether or not to highlight the text, + the second, what sound to play + """ + highlight, sound = (None, None) + + # Are any of the defined highlighting words in the text? + if self.needs_visual_notification(text): + highlight = True + if gajim.config.get_per('soundevents', 'muc_message_highlight', + 'enabled'): + sound = 'highlight' + + # Do we play a sound on every muc message? + elif gajim.config.get_per('soundevents', 'muc_message_received', \ + 'enabled'): + sound = 'received' + + # Is it a history message? Don't want sound-floods when we join. + if tim != time.localtime(): + sound = None + + return (highlight, sound) + + def check_and_possibly_add_focus_out_line(self): + """ + Check and possibly add focus out line for room_jid if it needs it and + does not already have it as last event. If it goes to add this line + - remove previous line first + """ + win = gajim.interface.msg_win_mgr.get_window(self.room_jid, self.account) + if win and self.room_jid == win.get_active_jid() and\ + win.window.get_property('has-toplevel-focus') and\ + self.parent_win.get_active_control() == self: + # it's the current room and it's the focused window. + # we have full focus (we are reading it!) + return + + self.conv_textview.show_focus_out_line() + + def needs_visual_notification(self, text): + """ + Check text to see whether any of the words in (muc_highlight_words and + nick) appear + """ + special_words = gajim.config.get('muc_highlight_words').split(';') + special_words.append(self.nick) + # Strip empties: ''.split(';') == [''] and would highlight everything. + # Also lowercase everything for case insensitive compare. + special_words = [word.lower() for word in special_words if word] + text = text.lower() + + for special_word in special_words: + found_here = text.find(special_word) + while(found_here > -1): + end_here = found_here + len(special_word) + if (found_here == 0 or not text[found_here - 1].isalpha()) and \ + (end_here == len(text) or not text[end_here].isalpha()): + # It is beginning of text or char before is not alpha AND + # it is end of text or char after is not alpha + return True + # continue searching + start = found_here + 1 + found_here = text.find(special_word, start) + return False + + def set_subject(self, subject): + self.subject = subject + self.draw_banner_text() + + def got_connected(self): + # Make autorejoin stop. + if self.autorejoin: + gobject.source_remove(self.autorejoin) + self.autorejoin = None + + gajim.gc_connected[self.account][self.room_jid] = True + ChatControlBase.got_connected(self) + # We don't redraw the whole banner here, because only icon change + self._update_banner_state_image() + if self.parent_win: + self.parent_win.redraw_tab(self) + + def got_disconnected(self): + self.list_treeview.get_model().clear() + nick_list = gajim.contacts.get_nick_list(self.account, self.room_jid) + for nick in nick_list: + # Update pm chat window + fjid = self.room_jid + '/' + nick + gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, + nick) + + ctrl = gajim.interface.msg_win_mgr.get_control(fjid, self.account) + if ctrl: + gc_contact.show = 'offline' + gc_contact.status = '' + ctrl.update_ui() + if ctrl.parent_win: + ctrl.parent_win.redraw_tab(ctrl) + + gajim.contacts.remove_gc_contact(self.account, gc_contact) + gajim.gc_connected[self.account][self.room_jid] = False + ChatControlBase.got_disconnected(self) + # Tell connection to note the date we disconnect to avoid duplicate logs + gajim.connections[self.account].gc_got_disconnected(self.room_jid) + # We don't redraw the whole banner here, because only icon change + self._update_banner_state_image() + if self.parent_win: + self.parent_win.redraw_tab(self) + + # Autorejoin stuff goes here. + # Notice that we don't need to activate autorejoin if connection is lost + # or in progress. + if self.autorejoin is None and gajim.account_is_connected(self.account): + ar_to = gajim.config.get('muc_autorejoin_timeout') + if ar_to: + self.autorejoin = gobject.timeout_add_seconds(ar_to, self.rejoin) + + def rejoin(self): + if not self.autorejoin: + return False + password = gajim.gc_passwords.get(self.room_jid, '') + gajim.connections[self.account].join_gc(self.nick, self.room_jid, + password) + return True + + def draw_roster(self): + self.list_treeview.get_model().clear() + for nick in gajim.contacts.get_nick_list(self.account, self.room_jid): + gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, + nick) + self.add_contact_to_roster(nick, gc_contact.show, gc_contact.role, + gc_contact.affiliation, gc_contact.status, gc_contact.jid) + self.draw_all_roles() + # Recalculate column width for ellipsizin + self.list_treeview.columns_autosize() + + def on_send_pm(self, widget=None, model=None, iter_=None, nick=None, + msg=None): + """ + Open a chat window and if msg is not None - send private message to a + contact in a room + """ + if nick is None: + nick = model[iter_][C_NICK].decode('utf-8') + + ctrl = self._start_private_message(nick) + if ctrl and msg: + ctrl.send_message(msg) + + def on_send_file(self, widget, gc_contact): + """ + Send a file to a contact in the room + """ + self._on_send_file(gc_contact) + + def draw_contact(self, nick, selected=False, focus=False): + iter_ = self.get_contact_iter(nick) + if not iter_: + return + model = self.list_treeview.get_model() + gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, + nick) + state_images = gajim.interface.jabber_state_images['16'] + if len(gajim.events.get_events(self.account, self.room_jid + '/' + nick)): + image = state_images['event'] + else: + image = state_images[gc_contact.show] + + name = gobject.markup_escape_text(gc_contact.name) + + # Strike name if blocked + fjid = self.room_jid + '/' + nick + if helpers.jid_is_blocked(self.account, fjid): + name = '%s' % name + + status = gc_contact.status + # add status msg, if not empty, under contact name in the treeview + if status and gajim.config.get('show_status_msgs_in_roster'): + status = status.strip() + if status != '': + status = helpers.reduce_chars_newlines(status, max_lines=1) + # escape markup entities and make them small italic and fg color + color = gtkgui_helpers._get_fade_color(self.list_treeview, + selected, focus) + colorstring = "#%04x%04x%04x" % (color.red, color.green, color.blue) + name += ('\n' + '%s') % (colorstring, gobject.markup_escape_text(status)) + + if image.get_storage_type() == gtk.IMAGE_PIXBUF and \ + gc_contact.affiliation != 'none' and gajim.config.get( + 'show_affiliation_in_groupchat'): + pixbuf1 = image.get_pixbuf().copy() + pixbuf2 = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, 4, 4) + if gc_contact.affiliation == 'owner': + pixbuf2.fill(0xff0000ff) # Red + elif gc_contact.affiliation == 'admin': + pixbuf2.fill(0xffb200ff) # Oragne + elif gc_contact.affiliation == 'member': + pixbuf2.fill(0x00ff00ff) # Green + pixbuf2.composite(pixbuf1, 12, 12, pixbuf2.get_property('width'), + pixbuf2.get_property('height'), 0, 0, 1.0, 1.0, + gtk.gdk.INTERP_HYPER, 127) + image = gtk.image_new_from_pixbuf(pixbuf1) + model[iter_][C_IMG] = image + model[iter_][C_TEXT] = name + + def draw_avatar(self, nick): + if not gajim.config.get('show_avatars_in_roster'): + return + model = self.list_treeview.get_model() + iter_ = self.get_contact_iter(nick) + if not iter_: + return + fake_jid = self.room_jid + '/' + nick + pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(fake_jid) + if pixbuf in ('ask', None): + scaled_pixbuf = None + else: + scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'roster') + model[iter_][C_AVATAR] = scaled_pixbuf + + def draw_role(self, role): + role_iter = self.get_role_iter(role) + if not role_iter: + return + model = self.list_treeview.get_model() + role_name = helpers.get_uf_role(role, plural=True) + if gajim.config.get('show_contacts_number'): + nbr_role, nbr_total = gajim.contacts.get_nb_role_total_gc_contacts( + self.account, self.room_jid, role) + role_name += ' (%s/%s)' % (repr(nbr_role), repr(nbr_total)) + model[role_iter][C_TEXT] = role_name + + def draw_all_roles(self): + for role in ('visitor', 'participant', 'moderator'): + self.draw_role(role) + + def chg_contact_status(self, nick, show, status, role, affiliation, jid, + reason, actor, statusCode, new_nick, avatar_sha, tim=None): + """ + When an occupant changes his or her status + """ + if show == 'invisible': + return + + if not role: + role = 'visitor' + if not affiliation: + affiliation = 'none' + fake_jid = self.room_jid + '/' + nick + newly_created = False + nick_jid = nick + + # Set to true if role or affiliation have changed + right_changed = False + + if jid: + # delete ressource + simple_jid = gajim.get_jid_without_resource(jid) + nick_jid += ' (%s)' % simple_jid + + # statusCode + # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes-init + if statusCode: + if '100' in statusCode: + # Can be a message (see handle_event_gc_config_change in gajim.py) + self.print_conversation(\ + _('Any occupant is allowed to see your full JID')) + if '170' in statusCode: + # Can be a message (see handle_event_gc_config_change in gajim.py) + self.print_conversation(_('Room logging is enabled')) + if '201' in statusCode: + self.print_conversation(_('A new room has been created')) + if '210' in statusCode: + self.print_conversation(\ + _('The server has assigned or modified your roomnick')) + + if show in ('offline', 'error'): + if statusCode: + if '307' in statusCode: + if actor is None: # do not print 'kicked by None' + s = _('%(nick)s has been kicked: %(reason)s') % { + 'nick': nick, + 'reason': reason } + else: + s = _('%(nick)s has been kicked by %(who)s: %(reason)s') % { + 'nick': nick, + 'who': actor, + 'reason': reason } + self.print_conversation(s, 'info', tim=tim, graphics=False) + if nick == self.nick and not gajim.config.get( + 'muc_autorejoin_on_kick'): + self.autorejoin = False + elif '301' in statusCode: + if actor is None: # do not print 'banned by None' + s = _('%(nick)s has been banned: %(reason)s') % { + 'nick': nick, + 'reason': reason } + else: + s = _('%(nick)s has been banned by %(who)s: %(reason)s') % { + 'nick': nick, + 'who': actor, + 'reason': reason } + self.print_conversation(s, 'info', tim=tim, graphics=False) + if nick == self.nick: + self.autorejoin = False + elif '303' in statusCode: # Someone changed his or her nick + if new_nick == self.new_nick or nick == self.nick: + # We changed our nick + self.nick = new_nick + self.new_nick = '' + s = _('You are now known as %s') % new_nick + # Stop all E2E sessions + nick_list = gajim.contacts.get_nick_list(self.account, + self.room_jid) + for nick_ in nick_list: + fjid_ = self.room_jid + '/' + nick_ + ctrl = gajim.interface.msg_win_mgr.get_control(fjid_, + self.account) + if ctrl and ctrl.session and \ + ctrl.session.enable_encryption: + thread_id = ctrl.session.thread_id + ctrl.session.terminate_e2e() + gajim.connections[self.account].delete_session(fjid_, + thread_id) + ctrl.no_autonegotiation = False + else: + s = _('%(nick)s is now known as %(new_nick)s') % { + 'nick': nick, 'new_nick': new_nick} + # We add new nick to muc roster here, so we don't see + # that "new_nick has joined the room" when he just changed nick. + # add_contact_to_roster will be called a second time + # after that, but that doesn't hurt + self.add_contact_to_roster(new_nick, show, role, affiliation, + status, jid) + if nick in self.attention_list: + self.attention_list.remove(nick) + # keep nickname color + if nick in self.gc_custom_colors: + self.gc_custom_colors[new_nick] = \ + self.gc_custom_colors[nick] + # rename vcard / avatar + puny_jid = helpers.sanitize_filename(self.room_jid) + puny_nick = helpers.sanitize_filename(nick) + puny_new_nick = helpers.sanitize_filename(new_nick) + old_path = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) + new_path = os.path.join(gajim.VCARD_PATH, puny_jid, + puny_new_nick) + files = {old_path: new_path} + path = os.path.join(gajim.AVATAR_PATH, puny_jid) + # possible extensions + for ext in ('.png', '.jpeg', '_notif_size_bw.png', + '_notif_size_colored.png'): + files[os.path.join(path, puny_nick + ext)] = \ + os.path.join(path, puny_new_nick + ext) + for old_file in files: + if os.path.exists(old_file) and old_file != files[old_file]: + if os.path.exists(files[old_file]) and helpers.windowsify( + old_file) != helpers.windowsify(files[old_file]): + # Windows require this, but os.remove('test') will also + # remove 'TEST' + os.remove(files[old_file]) + os.rename(old_file, files[old_file]) + self.print_conversation(s, 'info', tim=tim, graphics=False) + elif '321' in statusCode: + s = _('%(nick)s has been removed from the room (%(reason)s)') % { + 'nick': nick, 'reason': _('affiliation changed') } + self.print_conversation(s, 'info', tim=tim, graphics=False) + elif '322' in statusCode: + s = _('%(nick)s has been removed from the room (%(reason)s)') % { + 'nick': nick, + 'reason': _('room configuration changed to members-only') } + self.print_conversation(s, 'info', tim=tim, graphics=False) + elif '332' in statusCode: + s = _('%(nick)s has been removed from the room (%(reason)s)') % { + 'nick': nick, + 'reason': _('system shutdown') } + self.print_conversation(s, 'info', tim=tim, graphics=False) + # Room has been destroyed. + elif 'destroyed' in statusCode: + self.autorejoin = False + self.print_conversation(reason, 'info', tim, graphics=False) + + if len(gajim.events.get_events(self.account, jid=fake_jid, + types=['pm'])) == 0: + self.remove_contact(nick) + self.draw_all_roles() + else: + c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick) + c.show = show + c.status = status + if nick == self.nick and (not statusCode or \ + '303' not in statusCode): # We became offline + self.got_disconnected() + contact = gajim.contacts.\ + get_contact_with_highest_priority(self.account, self.room_jid) + if contact: + gajim.interface.roster.draw_contact(self.room_jid, self.account) + if self.parent_win: + self.parent_win.redraw_tab(self) + else: + iter_ = self.get_contact_iter(nick) + if not iter_: + if '210' in statusCode: + # Server changed our nick + self.nick = nick + s = _('You are now known as %s') % nick + self.print_conversation(s, 'info', tim=tim, graphics=False) + iter_ = self.add_contact_to_roster(nick, show, role, affiliation, + status, jid) + newly_created = True + self.draw_all_roles() + if statusCode and '201' in statusCode: # We just created the room + gajim.connections[self.account].request_gc_config(self.room_jid) + else: + gc_c = gajim.contacts.get_gc_contact(self.account, self.room_jid, + nick) + if not gc_c: + log.error('%s has an iter, but no gc_contact instance') + return + # Re-get vcard if avatar has changed + # We do that here because we may request it to the real JID if we + # knows it. connections.py doesn't know it. + con = gajim.connections[self.account] + if gc_c and gc_c.jid: + real_jid = gc_c.jid + if gc_c.resource: + real_jid += '/' + gc_c.resource + else: + real_jid = fake_jid + if fake_jid in con.vcard_shas: + if avatar_sha != con.vcard_shas[fake_jid]: + server = gajim.get_server_from_jid(self.room_jid) + if not server.startswith('irc'): + con.request_vcard(real_jid, fake_jid) + else: + cached_vcard = con.get_cached_vcard(fake_jid, True) + if cached_vcard and 'PHOTO' in cached_vcard and \ + 'SHA' in cached_vcard['PHOTO']: + cached_sha = cached_vcard['PHOTO']['SHA'] + else: + cached_sha = '' + if cached_sha != avatar_sha: + # avatar has been updated + # sha in mem will be updated later + server = gajim.get_server_from_jid(self.room_jid) + if not server.startswith('irc'): + con.request_vcard(real_jid, fake_jid) + else: + # save sha in mem NOW + con.vcard_shas[fake_jid] = avatar_sha + + actual_affiliation = gc_c.affiliation + if affiliation != actual_affiliation: + if actor: + st = _('** Affiliation of %(nick)s has been set to ' + '%(affiliation)s by %(actor)s') % {'nick': nick_jid, + 'affiliation': affiliation, 'actor': actor} + else: + st = _('** Affiliation of %(nick)s has been set to ' + '%(affiliation)s') % {'nick': nick_jid, + 'affiliation': affiliation} + if reason: + st += ' (%s)' % reason + self.print_conversation(st, tim=tim, graphics=False) + right_changed = True + actual_role = self.get_role(nick) + if role != actual_role: + self.remove_contact(nick) + self.add_contact_to_roster(nick, show, role, + affiliation, status, jid) + self.draw_role(actual_role) + self.draw_role(role) + if actor: + st = _('** Role of %(nick)s has been set to %(role)s by ' + '%(actor)s') % {'nick': nick_jid, 'role': role, + 'actor': actor} + else: + st = _('** Role of %(nick)s has been set to %(role)s') % { + 'nick': nick_jid, 'role': role} + if reason: + st += ' (%s)' % reason + self.print_conversation(st, tim=tim, graphics=False) + right_changed = True + else: + if gc_c.show == show and gc_c.status == status and \ + gc_c.affiliation == affiliation: # no change + return + gc_c.show = show + gc_c.affiliation = affiliation + gc_c.status = status + self.draw_contact(nick) + if (time.time() - self.room_creation) > 30 and nick != self.nick and \ + (not statusCode or '303' not in statusCode) and not right_changed: + st = '' + print_status = None + for bookmark in gajim.connections[self.account].bookmarks: + if bookmark['jid'] == self.room_jid: + print_status = bookmark.get('print_status', None) + break + if not print_status: + print_status = gajim.config.get('print_status_in_muc') + if show == 'offline': + if nick in self.attention_list: + self.attention_list.remove(nick) + if show == 'offline' and print_status in ('all', 'in_and_out') and \ + (not statusCode or '307' not in statusCode): + st = _('%s has left') % nick_jid + if reason: + st += ' [%s]' % reason + else: + if newly_created and print_status in ('all', 'in_and_out'): + st = _('%s has joined the group chat') % nick_jid + elif print_status == 'all': + st = _('%(nick)s is now %(status)s') % {'nick': nick_jid, + 'status': helpers.get_uf_show(show)} + if st: + if status: + st += ' (' + status + ')' + self.print_conversation(st, tim=tim, graphics=False) + + def add_contact_to_roster(self, nick, show, role, affiliation, status, + jid=''): + model = self.list_treeview.get_model() + role_name = helpers.get_uf_role(role, plural=True) + + resource = '' + if jid: + jids = jid.split('/', 1) + j = jids[0] + if len(jids) > 1: + resource = jids[1] + else: + j = '' + + name = nick + + role_iter = self.get_role_iter(role) + if not role_iter: + role_iter = model.append(None, + (gajim.interface.jabber_state_images['16']['closed'], role, + 'role', role_name, None)) + self.draw_all_roles() + iter_ = model.append(role_iter, (None, nick, 'contact', name, None)) + if not nick in gajim.contacts.get_nick_list(self.account, self.room_jid): + gc_contact = gajim.contacts.create_gc_contact(room_jid=self.room_jid, account=self.account, + name=nick, show=show, status=status, role=role, + affiliation=affiliation, jid=j, resource=resource) + gajim.contacts.add_gc_contact(self.account, gc_contact) + self.draw_contact(nick) + self.draw_avatar(nick) + # Do not ask avatar to irc rooms as irc transports reply with messages + server = gajim.get_server_from_jid(self.room_jid) + if gajim.config.get('ask_avatars_on_startup') and \ + not server.startswith('irc'): + fake_jid = self.room_jid + '/' + nick + pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(fake_jid) + if pixbuf == 'ask': + if j: + fjid = j + if resource: + fjid += '/' + resource + gajim.connections[self.account].request_vcard(fjid, fake_jid) + else: + gajim.connections[self.account].request_vcard(fake_jid, fake_jid) + if nick == self.nick: # we became online + self.got_connected() + self.list_treeview.expand_row((model.get_path(role_iter)), False) + if self.is_continued: + self.draw_banner_text() + return iter_ + + def get_role_iter(self, role): + model = self.list_treeview.get_model() + role_iter = model.get_iter_root() + while role_iter: + role_name = model[role_iter][C_NICK].decode('utf-8') + if role == role_name: + return role_iter + role_iter = model.iter_next(role_iter) + return None + + def remove_contact(self, nick): + """ + Remove a user from the contacts_list + """ + model = self.list_treeview.get_model() + iter_ = self.get_contact_iter(nick) + if not iter_: + return + gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, + nick) + if gc_contact: + gajim.contacts.remove_gc_contact(self.account, gc_contact) + parent_iter = model.iter_parent(iter_) + model.remove(iter_) + if model.iter_n_children(parent_iter) == 0: + model.remove(parent_iter) + + def send_message(self, message, xhtml=None, process_commands=True): + """ + Call this function to send our message + """ + if not message: + return + + if process_commands and self.process_as_command(message): + return + + message = helpers.remove_invalid_xml_chars(message) + + if not message: + return + + if message != '' or message != '\n': + self.save_sent_message(message) + + # Send the message + gajim.connections[self.account].send_gc_message(self.room_jid, + message, xhtml=xhtml) + self.msg_textview.get_buffer().set_text('') + self.msg_textview.grab_focus() + + def get_role(self, nick): + gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, + nick) + if gc_contact: + return gc_contact.role + else: + return 'visitor' + + def minimizable(self): + if self.contact.jid in gajim.config.get_per('accounts', self.account, + 'minimized_gc').split(' '): + return True + return False + + def minimize(self, status='offline'): + # Minimize it + win = gajim.interface.msg_win_mgr.get_window(self.contact.jid, + self.account) + ctrl = win.get_control(self.contact.jid, self.account) + + ctrl_page = win.notebook.page_num(ctrl.widget) + control = win.notebook.get_nth_page(ctrl_page) + + win.notebook.remove_page(ctrl_page) + control.unparent() + ctrl.parent_win = None + + gajim.interface.roster.add_groupchat(self.contact.jid, self.account, + status = self.subject) + + del win._controls[self.account][self.contact.jid] + + def shutdown(self, status='offline'): + # PluginSystem: calling shutdown of super class (ChatControlBase) + # to let it remove it's GUI extension points + super(GroupchatControl, self).shutdown() + + # Preventing autorejoin from being activated + self.autorejoin = False + + if self.room_jid in gajim.gc_connected[self.account] and \ + gajim.gc_connected[self.account][self.room_jid]: + # Tell connection to note the date we disconnect to avoid duplicate + # logs. We do it only when connected because if connection was lost + # there may be new messages since disconnection. + gajim.connections[self.account].gc_got_disconnected(self.room_jid) + gajim.connections[self.account].send_gc_status(self.nick, self.room_jid, + show='offline', status=status) + nick_list = gajim.contacts.get_nick_list(self.account, self.room_jid) + for nick in nick_list: + # Update pm chat window + fjid = self.room_jid + '/' + nick + ctrl = gajim.interface.msg_win_mgr.get_gc_control(fjid, self.account) + if ctrl: + contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, + nick) + contact.show = 'offline' + contact.status = '' + ctrl.update_ui() + ctrl.parent_win.redraw_tab(ctrl) + for sess in gajim.connections[self.account].get_sessions(fjid): + if sess.control: + sess.control.no_autonegotiation = False + if sess.enable_encryption: + sess.terminate_e2e() + gajim.connections[self.account].delete_session(fjid, + sess.thread_id) + # They can already be removed by the destroy function + if self.room_jid in gajim.contacts.get_gc_list(self.account): + gajim.contacts.remove_room(self.account, self.room_jid) + del gajim.gc_connected[self.account][self.room_jid] + # Save hpaned position + gajim.config.set('gc-hpaned-position', self.hpaned.get_position()) + # remove all register handlers on wigets, created by self.xml + # to prevent circular references among objects + for i in self.handlers.keys(): + if self.handlers[i].handler_is_connected(i): + self.handlers[i].disconnect(i) + del self.handlers[i] + # Remove unread events from systray + gajim.events.remove_events(self.account, self.room_jid) + + def safe_shutdown(self): + if self.minimizable(): + return True + includes = gajim.config.get('confirm_close_muc_rooms').split(' ') + excludes = gajim.config.get('noconfirm_close_muc_rooms').split(' ') + # whether to ask for comfirmation before closing muc + if (gajim.config.get('confirm_close_muc') or self.room_jid in includes) \ + and gajim.gc_connected[self.account][self.room_jid] and self.room_jid not\ + in excludes: + return False + return True + + def allow_shutdown(self, method, on_yes, on_no, on_minimize): + if self.minimizable(): + on_minimize(self) + return + if method == self.parent_win.CLOSE_ESC: + iter_ = self.list_treeview.get_selection().get_selected()[1] + if iter_: + self.list_treeview.get_selection().unselect_all() + on_no(self) + return + includes = gajim.config.get('confirm_close_muc_rooms').split(' ') + excludes = gajim.config.get('noconfirm_close_muc_rooms').split(' ') + # whether to ask for comfirmation before closing muc + if (gajim.config.get('confirm_close_muc') or self.room_jid in includes) \ + and gajim.gc_connected[self.account][self.room_jid] and self.room_jid not\ + in excludes: + + def on_ok(clicked): + if clicked: + # user does not want to be asked again + gajim.config.set('confirm_close_muc', False) + on_yes(self) + + def on_cancel(clicked): + if clicked: + # user does not want to be asked again + gajim.config.set('confirm_close_muc', False) + on_no(self) + + pritext = _('Are you sure you want to leave group chat "%s"?')\ + % self.name + sectext = _('If you close this window, you will be disconnected ' + 'from this group chat.') + + dialogs.ConfirmationDialogCheck(pritext, sectext, + _('Do _not ask me again'), on_response_ok=on_ok, + on_response_cancel=on_cancel) + return + + on_yes(self) + + def set_control_active(self, state): + self.conv_textview.allow_focus_out_line = True + self.attention_flag = False + ChatControlBase.set_control_active(self, state) + if not state: + # add the focus-out line to the tab we are leaving + self.check_and_possibly_add_focus_out_line() + # Sending active to undo unread state + self.parent_win.redraw_tab(self, 'active') + + def get_specific_unread(self): + # returns the number of the number of unread msgs + # for room_jid & number of unread private msgs with each contact + # that we have + nb = 0 + for nick in gajim.contacts.get_nick_list(self.account, self.room_jid): + fjid = self.room_jid + '/' + nick + nb += len(gajim.events.get_events(self.account, fjid)) + # gc can only have messages as event + return nb + + def _on_change_subject_menuitem_activate(self, widget): + def on_ok(subject): + # Note, we don't update self.subject since we don't know whether it + # will work yet + gajim.connections[self.account].send_gc_subject(self.room_jid, subject) + + dialogs.InputTextDialog(_('Changing Subject'), + _('Please specify the new subject:'), input_str=self.subject, + ok_handler=on_ok) + + def _on_change_nick_menuitem_activate(self, widget): + if 'change_nick_dialog' in gajim.interface.instances: + gajim.interface.instances['change_nick_dialog'].present() + else: + title = _('Changing Nickname') + prompt = _('Please specify the new nickname you want to use:') + gajim.interface.instances['change_nick_dialog'] = \ + dialogs.ChangeNickDialog(self.account, self.room_jid, title, + prompt) + + def _on_configure_room_menuitem_activate(self, widget): + c = gajim.contacts.get_gc_contact(self.account, self.room_jid, self.nick) + if c.affiliation == 'owner': + gajim.connections[self.account].request_gc_config(self.room_jid) + elif c.affiliation == 'admin': + if self.room_jid not in gajim.interface.instances[self.account][ + 'gc_config']: + gajim.interface.instances[self.account]['gc_config'][self.room_jid]\ + = config.GroupchatConfigWindow(self.account, self.room_jid) + + def _on_destroy_room_menuitem_activate(self, widget): + def on_ok(reason, jid): + if jid: + # Test jid + try: + jid = helpers.parse_jid(jid) + except Exception: + dialogs.ErrorDialog(_('Invalid group chat Jabber ID'), + _('The group chat Jabber ID has not allowed characters.')) + return + gajim.connections[self.account].destroy_gc_room(self.room_jid, reason, + jid) + + # Ask for a reason + dialogs.DubbleInputDialog(_('Destroying %s') % self.room_jid, + _('You are going to definitively destroy this room.\n' + 'You may specify a reason below:'), + _('You may also enter an alternate venue:'), ok_handler=on_ok) + + def _on_bookmark_room_menuitem_activate(self, widget): + """ + Bookmark the room, without autojoin and not minimized + """ + password = gajim.gc_passwords.get(self.room_jid, '') + gajim.interface.add_gc_bookmark(self.account, self.name, self.room_jid, \ + '0', '0', password, self.nick) + + def _on_drag_data_received(self, widget, context, x, y, selection, + target_type, timestamp): + # Invite contact to groupchat + treeview = gajim.interface.roster.tree + model = treeview.get_model() + if not selection.data or target_type == 80: + # target_type = 80 means a file is dropped + return + data = selection.data + path = treeview.get_selection().get_selected_rows()[1][0] + iter_ = model.get_iter(path) + type_ = model[iter_][2] + if type_ != 'contact': # source is not a contact + return + contact_jid = data.decode('utf-8') + gajim.connections[self.account].send_invite(self.room_jid, contact_jid) + + def handle_message_textview_mykey_press(self, widget, event_keyval, + event_keymod): + # NOTE: handles mykeypress which is custom signal connected to this + # CB in new_room(). for this singal see message_textview.py + + # construct event instance from binding + event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here + event.keyval = event_keyval + event.state = event_keymod + event.time = 0 # assign current time + + message_buffer = widget.get_buffer() + start_iter, end_iter = message_buffer.get_bounds() + + if event.keyval == gtk.keysyms.Tab: # TAB + cursor_position = message_buffer.get_insert() + end_iter = message_buffer.get_iter_at_mark(cursor_position) + text = message_buffer.get_text(start_iter, end_iter, False).decode( + 'utf-8') + + splitted_text = text.split() + + # HACK: Not the best soltution. + if (text.startswith(self.COMMAND_PREFIX) and not + text.startswith(self.COMMAND_PREFIX * 2) and len(splitted_text) == 1): + return super(GroupchatControl, + self).handle_message_textview_mykey_press(widget, event_keyval, + event_keymod) + + # nick completion + # check if tab is pressed with empty message + if len(splitted_text): # if there are any words + begin = splitted_text[-1] # last word we typed + else: + begin = '' + + gc_refer_to_nick_char = gajim.config.get('gc_refer_to_nick_char') + with_refer_to_nick_char = False + after_nick_len = 1 # the space that is printed after we type [Tab] + + # first part of this if : works fine even if refer_to_nick_char + if gc_refer_to_nick_char and text.endswith( + gc_refer_to_nick_char + ' '): + with_refer_to_nick_char = True + after_nick_len = len(gc_refer_to_nick_char + ' ') + if len(self.nick_hits) and self.last_key_tabs and \ + text[:-after_nick_len].endswith(self.nick_hits[0]): + # we should cycle + # Previous nick in list may had a space inside, so we check text and + # not splitted_text and store it into 'begin' var + self.nick_hits.append(self.nick_hits[0]) + begin = self.nick_hits.pop(0) + else: + self.nick_hits = [] # clear the hit list + list_nick = gajim.contacts.get_nick_list(self.account, + self.room_jid) + list_nick.sort(key=unicode.lower) # case-insensitive sort + if begin == '': + # empty message, show lasts nicks that highlighted us first + for nick in self.attention_list: + if nick in list_nick: + list_nick.remove(nick) + list_nick.insert(0, nick) + + list_nick.remove(self.nick) # Skip self + for nick in list_nick: + if nick.lower().startswith(begin.lower()): + # the word is the begining of a nick + self.nick_hits.append(nick) + if len(self.nick_hits): + if len(splitted_text) < 2 or with_refer_to_nick_char: + # This is the 1st word of the line or no word or we are cycling + # at the beginning, possibly with a space in one nick + add = gc_refer_to_nick_char + ' ' + else: + add = ' ' + start_iter = end_iter.copy() + if self.last_key_tabs and with_refer_to_nick_char or (text and \ + text[-1] == ' '): + # have to accomodate for the added space from last + # completion + # gc_refer_to_nick_char may be more than one char! + start_iter.backward_chars(len(begin) + len(add)) + elif self.last_key_tabs and not gajim.config.get( + 'shell_like_completion'): + # have to accomodate for the added space from last + # completion + start_iter.backward_chars(len(begin) + \ + len(gc_refer_to_nick_char)) + else: + start_iter.backward_chars(len(begin)) + + message_buffer.delete(start_iter, end_iter) + # get a shell-like completion + # if there's more than one nick for this completion, complete only + # the part that all these nicks have in common + if gajim.config.get('shell_like_completion') and \ + len(self.nick_hits) > 1: + end = False + completion = '' + add = "" # if nick is not complete, don't add anything + while not end and len(completion) < len(self.nick_hits[0]): + completion = self.nick_hits[0][:len(completion)+1] + for nick in self.nick_hits: + if completion.lower() not in nick.lower(): + end = True + completion = completion[:-1] + break + # if the current nick matches a COMPLETE existing nick, + # and if the user tab TWICE, complete that nick (with the "add") + if self.last_key_tabs: + for nick in self.nick_hits: + if nick == completion: + # The user seems to want this nick, so + # complete it as if it were the only nick + # available + add = gc_refer_to_nick_char + ' ' + else: + completion = self.nick_hits[0] + message_buffer.insert_at_cursor(completion + add) + self.last_key_tabs = True + return True + self.last_key_tabs = False + + def on_list_treeview_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: + selection = widget.get_selection() + iter_ = selection.get_selected()[1] + if iter_: + widget.get_selection().unselect_all() + return True + + def on_list_treeview_row_expanded(self, widget, iter_, path): + """ + When a row is expanded: change the icon of the arrow + """ + model = widget.get_model() + image = gajim.interface.jabber_state_images['16']['opened'] + model[iter_][C_IMG] = image + + def on_list_treeview_row_collapsed(self, widget, iter_, path): + """ + When a row is collapsed: change the icon of the arrow + """ + model = widget.get_model() + image = gajim.interface.jabber_state_images['16']['closed'] + model[iter_][C_IMG] = image + + def kick(self, widget, nick): + """ + Kick a user + """ + def on_ok(reason): + gajim.connections[self.account].gc_set_role(self.room_jid, nick, + 'none', reason) + + # ask for reason + dialogs.InputDialog(_('Kicking %s') % nick, + _('You may specify a reason below:'), ok_handler=on_ok) + + def mk_menu(self, event, iter_): + """ + Make contact's popup menu + """ + model = self.list_treeview.get_model() + nick = model[iter_][C_NICK].decode('utf-8') + c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick) + fjid = self.room_jid + '/' + nick + jid = c.jid + target_affiliation = c.affiliation + target_role = c.role + + # looking for user's affiliation and role + user_nick = self.nick + user_affiliation = gajim.contacts.get_gc_contact(self.account, + self.room_jid, user_nick).affiliation + user_role = self.get_role(user_nick) + + # making menu from gtk builder + xml = gtkgui_helpers.get_gtk_builder('gc_occupants_menu.ui') + + # these conditions were taken from JEP 0045 + item = xml.get_object('kick_menuitem') + if user_role != 'moderator' or \ + (user_affiliation == 'admin' and target_affiliation == 'owner') or \ + (user_affiliation == 'member' and target_affiliation in ('admin', + 'owner')) or (user_affiliation == 'none' and target_affiliation != \ + 'none'): + item.set_sensitive(False) + id_ = item.connect('activate', self.kick, nick) + self.handlers[id_] = item + + item = xml.get_object('voice_checkmenuitem') + item.set_active(target_role != 'visitor') + if user_role != 'moderator' or \ + user_affiliation == 'none' or \ + (user_affiliation=='member' and target_affiliation!='none') or \ + target_affiliation in ('admin', 'owner'): + item.set_sensitive(False) + id_ = item.connect('activate', self.on_voice_checkmenuitem_activate, + nick) + self.handlers[id_] = item + + item = xml.get_object('moderator_checkmenuitem') + item.set_active(target_role == 'moderator') + if not user_affiliation in ('admin', 'owner') or \ + target_affiliation in ('admin', 'owner'): + item.set_sensitive(False) + id_ = item.connect('activate', self.on_moderator_checkmenuitem_activate, + nick) + self.handlers[id_] = item + + item = xml.get_object('ban_menuitem') + if not user_affiliation in ('admin', 'owner') or \ + (target_affiliation in ('admin', 'owner') and\ + user_affiliation != 'owner'): + item.set_sensitive(False) + id_ = item.connect('activate', self.ban, jid) + self.handlers[id_] = item + + item = xml.get_object('member_checkmenuitem') + item.set_active(target_affiliation != 'none') + if not user_affiliation in ('admin', 'owner') or \ + (user_affiliation != 'owner' and target_affiliation in ('admin', 'owner')): + item.set_sensitive(False) + id_ = item.connect('activate', self.on_member_checkmenuitem_activate, jid) + self.handlers[id_] = item + + item = xml.get_object('admin_checkmenuitem') + item.set_active(target_affiliation in ('admin', 'owner')) + if not user_affiliation == 'owner': + item.set_sensitive(False) + id_ = item.connect('activate', self.on_admin_checkmenuitem_activate, jid) + self.handlers[id_] = item + + item = xml.get_object('owner_checkmenuitem') + item.set_active(target_affiliation == 'owner') + if not user_affiliation == 'owner': + item.set_sensitive(False) + id_ = item.connect('activate', self.on_owner_checkmenuitem_activate, jid) + self.handlers[id_] = item + + item = xml.get_object('information_menuitem') + id_ = item.connect('activate', self.on_info, nick) + self.handlers[id_] = item + + item = xml.get_object('history_menuitem') + id_ = item.connect('activate', self.on_history, nick) + self.handlers[id_] = item + + item = xml.get_object('add_to_roster_menuitem') + our_jid = gajim.get_jid_from_account(self.account) + if not jid or jid == our_jid: + item.set_sensitive(False) + else: + id_ = item.connect('activate', self.on_add_to_roster, jid) + self.handlers[id_] = item + + item = xml.get_object('block_menuitem') + item2 = xml.get_object('unblock_menuitem') + if helpers.jid_is_blocked(self.account, fjid): + item.set_no_show_all(True) + item.hide() + id_ = item2.connect('activate', self.on_unblock, nick) + self.handlers[id_] = item2 + else: + id_ = item.connect('activate', self.on_block, nick) + self.handlers[id_] = item + item2.set_no_show_all(True) + item2.hide() + + item = xml.get_object('send_private_message_menuitem') + id_ = item.connect('activate', self.on_send_pm, model, iter_) + self.handlers[id_] = item + + item = xml.get_object('send_file_menuitem') + # add a special img for send file menuitem + path_to_upload_img = gtkgui_helpers.get_icon_path('gajim-upload') + img = gtk.Image() + img.set_from_file(path_to_upload_img) + item.set_image(img) + + if not c.resource: + item.set_sensitive(False) + else: + id_ = item.connect('activate', self.on_send_file, c) + self.handlers[id_] = item + + # show the popup now! + menu = xml.get_object('gc_occupants_menu') + menu.show_all() + menu.popup(None, None, None, event.button, event.time) + + def _start_private_message(self, nick): + gc_c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick) + nick_jid = gc_c.get_full_jid() + + ctrl = gajim.interface.msg_win_mgr.get_control(nick_jid, self.account) + if not ctrl: + ctrl = gajim.interface.new_private_chat(gc_c, self.account) + + if ctrl: + ctrl.parent_win.set_active_tab(ctrl) + + return ctrl + + def on_row_activated(self, widget, path): + """ + When an iter is activated (dubblick or single click if gnome is set this + way + """ + model = widget.get_model() + if len(path) == 1: # It's a group + if (widget.row_expanded(path)): + widget.collapse_row(path) + else: + widget.expand_row(path, False) + else: # We want to send a private message + nick = model[path][C_NICK].decode('utf-8') + self._start_private_message(nick) + + def on_list_treeview_row_activated(self, widget, path, col=0): + """ + When an iter is double clicked: open the chat window + """ + if not gajim.single_click: + self.on_row_activated(widget, path) + + def on_list_treeview_button_press_event(self, widget, event): + """ + Popup user's group's or agent menu + """ + # hide tooltip, no matter the button is pressed + self.tooltip.hide_tooltip() + try: + pos = widget.get_path_at_pos(int(event.x), int(event.y)) + path, x = pos[0], pos[2] + except TypeError: + widget.get_selection().unselect_all() + return + if event.button == 3: # right click + widget.get_selection().select_path(path) + model = widget.get_model() + iter_ = model.get_iter(path) + if len(path) == 2: + self.mk_menu(event, iter_) + return True + + elif event.button == 2: # middle click + widget.get_selection().select_path(path) + model = widget.get_model() + iter_ = model.get_iter(path) + if len(path) == 2: + nick = model[iter_][C_NICK].decode('utf-8') + self._start_private_message(nick) + return True + + elif event.button == 1: # left click + if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK: + self.on_row_activated(widget, path) + return True + else: + model = widget.get_model() + iter_ = model.get_iter(path) + nick = model[iter_][C_NICK].decode('utf-8') + if not nick in gajim.contacts.get_nick_list(self.account, + self.room_jid): + # it's a group + if x < 27: + if (widget.row_expanded(path)): + widget.collapse_row(path) + else: + widget.expand_row(path, False) + elif event.state & gtk.gdk.SHIFT_MASK: + self.append_nick_in_msg_textview(self.msg_textview, nick) + self.msg_textview.grab_focus() + return True + + def append_nick_in_msg_textview(self, widget, nick): + message_buffer = self.msg_textview.get_buffer() + start_iter, end_iter = message_buffer.get_bounds() + cursor_position = message_buffer.get_insert() + end_iter = message_buffer.get_iter_at_mark(cursor_position) + text = message_buffer.get_text(start_iter, end_iter, False) + start = '' + if text: # Cursor is not at first position + if not text[-1] in (' ', '\n', '\t'): + start = ' ' + add = ' ' + else: + gc_refer_to_nick_char = gajim.config.get('gc_refer_to_nick_char') + add = gc_refer_to_nick_char + ' ' + message_buffer.insert_at_cursor(start + nick + add) + + def on_list_treeview_motion_notify_event(self, widget, event): + model = widget.get_model() + props = widget.get_path_at_pos(int(event.x), int(event.y)) + if self.tooltip.timeout > 0: + if not props or self.tooltip.id != props[0]: + self.tooltip.hide_tooltip() + if props: + [row, col, x, y] = props + iter_ = None + try: + iter_ = model.get_iter(row) + except Exception: + self.tooltip.hide_tooltip() + return + typ = model[iter_][C_TYPE].decode('utf-8') + if typ == 'contact': + account = self.account + + if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: + self.tooltip.id = row + nick = model[iter_][C_NICK].decode('utf-8') + self.tooltip.timeout = gobject.timeout_add(500, + self.show_tooltip, gajim.contacts.get_gc_contact(account, + self.room_jid, nick)) + + def on_list_treeview_leave_notify_event(self, widget, event): + props = widget.get_path_at_pos(int(event.x), int(event.y)) + if self.tooltip.timeout > 0: + if not props or self.tooltip.id == props[0]: + self.tooltip.hide_tooltip() + + def show_tooltip(self, contact): + if not self.list_treeview.window: + # control has been destroyed since tooltip was requested + return + pointer = self.list_treeview.get_pointer() + props = self.list_treeview.get_path_at_pos(pointer[0], pointer[1]) + # check if the current pointer is at the same path + # as it was before setting the timeout + if props and self.tooltip.id == props[0]: + rect = self.list_treeview.get_cell_area(props[0], props[1]) + position = self.list_treeview.window.get_origin() + self.tooltip.show_tooltip(contact, rect.height, + position[1] + rect.y) + else: + self.tooltip.hide_tooltip() + + def grant_voice(self, widget, nick): + """ + Grant voice privilege to a user + """ + gajim.connections[self.account].gc_set_role(self.room_jid, nick, + 'participant') + + def revoke_voice(self, widget, nick): + """ + Revoke voice privilege to a user + """ + gajim.connections[self.account].gc_set_role(self.room_jid, nick, + 'visitor') + + def grant_moderator(self, widget, nick): + """ + Grant moderator privilege to a user + """ + gajim.connections[self.account].gc_set_role(self.room_jid, nick, + 'moderator') + + def revoke_moderator(self, widget, nick): + """ + Revoke moderator privilege to a user + """ + gajim.connections[self.account].gc_set_role(self.room_jid, nick, + 'participant') + + def ban(self, widget, jid): + """ + Ban a user + """ + def on_ok(reason): + gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, + 'outcast', reason) + + # to ban we know the real jid. so jid is not fakejid + nick = gajim.get_nick_from_jid(jid) + # ask for reason + dialogs.InputDialog(_('Banning %s') % nick, + _('You may specify a reason below:'), ok_handler=on_ok) + + def grant_membership(self, widget, jid): + """ + Grant membership privilege to a user + """ + gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, + 'member') + + def revoke_membership(self, widget, jid): + """ + Revoke membership privilege to a user + """ + gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, + 'none') + + def grant_admin(self, widget, jid): + """ + Grant administrative privilege to a user + """ + gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, + 'admin') + + def revoke_admin(self, widget, jid): + """ + Revoke administrative privilege to a user + """ + gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, + 'member') + + def grant_owner(self, widget, jid): + """ + Grant owner privilege to a user + """ + gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, + 'owner') + + def revoke_owner(self, widget, jid): + """ + Revoke owner privilege to a user + """ + gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid, + 'admin') + + def on_info(self, widget, nick): + """ + Call vcard_information_window class to display user's information + """ + gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick) + contact = gc_contact.as_contact() + if contact.jid in gajim.interface.instances[self.account]['infos']: + gajim.interface.instances[self.account]['infos'][contact.jid].window.\ + present() + else: + gajim.interface.instances[self.account]['infos'][contact.jid] = \ + vcard.VcardWindow(contact, self.account, gc_contact) + + def on_history(self, widget, nick): + jid = gajim.construct_fjid(self.room_jid, nick) + self._on_history_menuitem_activate(widget=widget, jid=jid) + + def on_add_to_roster(self, widget, jid): + dialogs.AddNewContactWindow(self.account, jid) + + def on_block(self, widget, nick): + fjid = self.room_jid + '/' + nick + connection = gajim.connections[self.account] + if fjid in connection.blocked_contacts: + return + new_rule = {'order': u'1', 'type': u'jid', 'action': u'deny', + 'value' : fjid, 'child': [u'message', u'iq', u'presence-out']} + connection.blocked_list.append(new_rule) + connection.blocked_contacts.append(fjid) + self.draw_contact(nick) + connection.set_privacy_list('block', connection.blocked_list) + if len(connection.blocked_list) == 1: + connection.set_active_list('block') + connection.set_default_list('block') + connection.get_privacy_list('block') + + def on_unblock(self, widget, nick): + fjid = self.room_jid + '/' + nick + connection = gajim.connections[self.account] + connection.new_blocked_list = [] + # needed for draw_contact: + if fjid in connection.blocked_contacts: + connection.blocked_contacts.remove(fjid) + self.draw_contact(nick) + for rule in connection.blocked_list: + if rule['action'] != 'deny' or rule['type'] != 'jid' \ + or rule['value'] != fjid: + connection.new_blocked_list.append(rule) + + connection.set_privacy_list('block', connection.new_blocked_list) + connection.get_privacy_list('block') + if len(connection.new_blocked_list) == 0: + connection.blocked_list = [] + connection.blocked_contacts = [] + connection.blocked_groups = [] + connection.set_default_list('') + connection.set_active_list('') + connection.del_privacy_list('block') + if 'blocked_contacts' in gajim.interface.instances[self.account]: + gajim.interface.instances[self.account]['blocked_contacts'].\ + privacy_list_received([]) + + def on_voice_checkmenuitem_activate(self, widget, nick): + if widget.get_active(): + self.grant_voice(widget, nick) + else: + self.revoke_voice(widget, nick) + + def on_moderator_checkmenuitem_activate(self, widget, nick): + if widget.get_active(): + self.grant_moderator(widget, nick) + else: + self.revoke_moderator(widget, nick) + + def on_member_checkmenuitem_activate(self, widget, jid): + if widget.get_active(): + self.grant_membership(widget, jid) + else: + self.revoke_membership(widget, jid) + + def on_admin_checkmenuitem_activate(self, widget, jid): + if widget.get_active(): + self.grant_admin(widget, jid) + else: + self.revoke_admin(widget, jid) + + def on_owner_checkmenuitem_activate(self, widget, jid): + if widget.get_active(): + self.grant_owner(widget, jid) + else: + self.revoke_owner(widget, jid) diff --git a/src/groups.py b/src/groups.py index 26e4e36b1..298e1f4b6 100644 --- a/src/groups.py +++ b/src/groups.py @@ -25,51 +25,49 @@ from common import gajim, xmpp import gtkgui_helpers class GroupsPostWindow: - def __init__(self, account, servicejid, groupid): - """ - Open new 'create post' window to create message for groupid on servicejid - service - """ - assert isinstance(servicejid, basestring) - assert isinstance(groupid, basestring) + def __init__(self, account, servicejid, groupid): + """ + Open new 'create post' window to create message for groupid on servicejid + service + """ + assert isinstance(servicejid, basestring) + assert isinstance(groupid, basestring) - self.account = account - self.servicejid = servicejid - self.groupid = groupid + self.account = account + self.servicejid = servicejid + self.groupid = groupid - self.xml = gtkgui_helpers.get_gtk_builder('groups_post_window.ui') - self.window = self.xml.get_object('groups_post_window') - for name in ('from_entry', 'subject_entry', 'contents_textview'): - self.__dict__[name] = self.xml.get_object(name) + self.xml = gtkgui_helpers.get_gtk_builder('groups_post_window.ui') + self.window = self.xml.get_object('groups_post_window') + for name in ('from_entry', 'subject_entry', 'contents_textview'): + self.__dict__[name] = self.xml.get_object(name) - self.xml.connect_signals(self) - self.window.show_all() + self.xml.connect_signals(self) + self.window.show_all() - def on_cancel_button_clicked(self, w): - """ - Close window - """ - self.window.destroy() + def on_cancel_button_clicked(self, w): + """ + Close window + """ + self.window.destroy() - def on_send_button_clicked(self, w): - """ - Gather info from widgets and send it as a message - """ - # constructing item to publish... that's atom:entry element - item = xmpp.Node('entry', {'xmlns':'http://www.w3.org/2005/Atom'}) - author = item.addChild('author') - author.addChild('name', {}, [self.from_entry.get_text()]) - item.addChild('generator', {}, ['Gajim']) - item.addChild('title', {}, [self.subject_entry.get_text()]) - item.addChild('id', {}, ['0']) + def on_send_button_clicked(self, w): + """ + Gather info from widgets and send it as a message + """ + # constructing item to publish... that's atom:entry element + item = xmpp.Node('entry', {'xmlns':'http://www.w3.org/2005/Atom'}) + author = item.addChild('author') + author.addChild('name', {}, [self.from_entry.get_text()]) + item.addChild('generator', {}, ['Gajim']) + item.addChild('title', {}, [self.subject_entry.get_text()]) + item.addChild('id', {}, ['0']) - buf = self.contents_textview.get_buffer() - item.addChild('content', {}, [buf.get_text(buf.get_start_iter(), buf.get_end_iter())]) + buf = self.contents_textview.get_buffer() + item.addChild('content', {}, [buf.get_text(buf.get_start_iter(), buf.get_end_iter())]) - # publish it to node - gajim.connections[self.account].send_pb_publish(self.servicejid, self.groupid, item, '0') + # publish it to node + gajim.connections[self.account].send_pb_publish(self.servicejid, self.groupid, item, '0') - # close the window - self.window.destroy() - -# vim: se ts=3: + # close the window + self.window.destroy() diff --git a/src/gtkexcepthook.py b/src/gtkexcepthook.py index 1c08fcd24..f4d6d73c1 100644 --- a/src/gtkexcepthook.py +++ b/src/gtkexcepthook.py @@ -36,72 +36,70 @@ from common import helpers _exception_in_progress = threading.Lock() def _info(type_, value, tb): - if not _exception_in_progress.acquire(False): - # Exceptions have piled up, so we use the default exception - # handler for such exceptions - _excepthook_save(type_, value, tb) - return + if not _exception_in_progress.acquire(False): + # Exceptions have piled up, so we use the default exception + # handler for such exceptions + _excepthook_save(type_, value, tb) + return - dialog = dialogs.HigDialog(None, gtk.MESSAGE_WARNING, gtk.BUTTONS_NONE, - _('A programming error has been detected'), - _('It probably is not fatal, but should be reported ' - 'to the developers nonetheless.')) + dialog = dialogs.HigDialog(None, gtk.MESSAGE_WARNING, gtk.BUTTONS_NONE, + _('A programming error has been detected'), + _('It probably is not fatal, but should be reported ' + 'to the developers nonetheless.')) - dialog.set_modal(False) - #FIXME: add icon to this button - RESPONSE_REPORT_BUG = 42 - dialog.add_buttons(gtk.STOCK_CLOSE, gtk.BUTTONS_CLOSE, - _('_Report Bug'), RESPONSE_REPORT_BUG) - report_button = dialog.action_area.get_children()[0] # right to left - report_button.grab_focus() + dialog.set_modal(False) + #FIXME: add icon to this button + RESPONSE_REPORT_BUG = 42 + dialog.add_buttons(gtk.STOCK_CLOSE, gtk.BUTTONS_CLOSE, + _('_Report Bug'), RESPONSE_REPORT_BUG) + report_button = dialog.action_area.get_children()[0] # right to left + report_button.grab_focus() - # Details - textview = gtk.TextView() - textview.set_editable(False) - textview.modify_font(pango.FontDescription('Monospace')) - sw = gtk.ScrolledWindow() - sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - sw.add(textview) - frame = gtk.Frame() - frame.set_shadow_type(gtk.SHADOW_IN) - frame.add(sw) - frame.set_border_width(6) - textbuffer = textview.get_buffer() - trace = StringIO() - traceback.print_exception(type_, value, tb, None, trace) - textbuffer.set_text(trace.getvalue()) - textview.set_size_request( - gtk.gdk.screen_width() / 3, - gtk.gdk.screen_height() / 4) - expander = gtk.Expander(_('Details')) - expander.add(frame) - dialog.vbox.add(expander) + # Details + textview = gtk.TextView() + textview.set_editable(False) + textview.modify_font(pango.FontDescription('Monospace')) + sw = gtk.ScrolledWindow() + sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + sw.add(textview) + frame = gtk.Frame() + frame.set_shadow_type(gtk.SHADOW_IN) + frame.add(sw) + frame.set_border_width(6) + textbuffer = textview.get_buffer() + trace = StringIO() + traceback.print_exception(type_, value, tb, None, trace) + textbuffer.set_text(trace.getvalue()) + textview.set_size_request( + gtk.gdk.screen_width() / 3, + gtk.gdk.screen_height() / 4) + expander = gtk.Expander(_('Details')) + expander.add(frame) + dialog.vbox.add(expander) - dialog.set_resizable(True) - # on expand the details the dialog remains centered on screen - dialog.set_position(gtk.WIN_POS_CENTER_ALWAYS) + dialog.set_resizable(True) + # on expand the details the dialog remains centered on screen + dialog.set_position(gtk.WIN_POS_CENTER_ALWAYS) - def on_dialog_response(dialog, response): - if response == RESPONSE_REPORT_BUG: - url = 'http://trac.gajim.org/wiki/HowToCreateATicket' - helpers.launch_browser_mailer('url', url) - else: - dialog.destroy() - dialog.connect('response', on_dialog_response) - dialog.show_all() + def on_dialog_response(dialog, response): + if response == RESPONSE_REPORT_BUG: + url = 'http://trac.gajim.org/wiki/HowToCreateATicket' + helpers.launch_browser_mailer('url', url) + else: + dialog.destroy() + dialog.connect('response', on_dialog_response) + dialog.show_all() - _exception_in_progress.release() + _exception_in_progress.release() # gdb/kdm etc if we use startx this is not True if os.name == 'nt' or not sys.stderr.isatty(): - #FIXME: maybe always show dialog? - _excepthook_save = sys.excepthook - sys.excepthook = _info + #FIXME: maybe always show dialog? + _excepthook_save = sys.excepthook + sys.excepthook = _info # this is just to assist testing (python gtkexcepthook.py) if __name__ == '__main__': - _excepthook_save = sys.excepthook - sys.excepthook = _info - raise Exception() - -# vim: se ts=3: + _excepthook_save = sys.excepthook + sys.excepthook = _info + raise Exception() diff --git a/src/gtkgui_helpers.py b/src/gtkgui_helpers.py index 7f3ab1f57..9d5188207 100644 --- a/src/gtkgui_helpers.py +++ b/src/gtkgui_helpers.py @@ -45,21 +45,21 @@ gtk_icon_theme = gtk.icon_theme_get_default() gtk_icon_theme.append_search_path(gajim.ICONS_DIR) def get_icon_pixmap(icon_name, size=16): - try: - return gtk_icon_theme.load_icon(icon_name, size, 0) - except gobject.GError, e: - log.error('Unable to load icon %s: %s' % (icon_name, str(e))) + try: + return gtk_icon_theme.load_icon(icon_name, size, 0) + except gobject.GError, e: + log.error('Unable to load icon %s: %s' % (icon_name, str(e))) def get_icon_path(icon_name, size=16): - try: - icon_info = gtk_icon_theme.lookup_icon(icon_name, size, 0) - if icon_info == None: - log.error('Icon not found: %s' % icon_name) - return "" - else: - return icon_info.get_filename() - except gobject.GError, e: - log.error("Unable to find icon %s: %s" % (icon_name, str(e))) + try: + icon_info = gtk_icon_theme.lookup_icon(icon_name, size, 0) + if icon_info == None: + log.error('Icon not found: %s' % icon_name) + return "" + else: + return icon_info.get_filename() + except gobject.GError, e: + log.error("Unable to find icon %s: %s" % (icon_name, str(e))) import vcard import dialogs @@ -67,12 +67,12 @@ import dialogs HAS_PYWIN32 = True if os.name == 'nt': - try: - import win32file - import win32con - import pywintypes - except ImportError: - HAS_PYWIN32 = False + try: + import win32file + import win32con + import pywintypes + except ImportError: + HAS_PYWIN32 = False from common import helpers @@ -80,690 +80,690 @@ screen_w = gtk.gdk.screen_width() screen_h = gtk.gdk.screen_height() def add_image_to_menuitem(menuitem, icon_name): - img = gtk.Image() - path_img = get_icon_path(icon_name) - img.set_from_file(path_img) - menuitem.set_image(img) + img = gtk.Image() + path_img = get_icon_path(icon_name) + img.set_from_file(path_img) + menuitem.set_image(img) def add_image_to_button(button, icon_name): - add_image_to_menuitem(button, icon_name) + add_image_to_menuitem(button, icon_name) GUI_DIR = os.path.join(gajim.DATA_DIR, 'gui') def get_gtk_builder(file_name, widget=None): - file_path = os.path.join(GUI_DIR, file_name) - builder = gtk.Builder() - builder.set_translation_domain(i18n.APP) - if widget: - builder.add_objects_from_file(file_path, [widget]) - else: - builder.add_from_file(file_path) - return builder + file_path = os.path.join(GUI_DIR, file_name) + builder = gtk.Builder() + builder.set_translation_domain(i18n.APP) + if widget: + builder.add_objects_from_file(file_path, [widget]) + else: + builder.add_from_file(file_path) + return builder def get_completion_liststore(entry): - """ - Create a completion model for entry widget completion list consists of - (Pixbuf, Text) rows - """ - completion = gtk.EntryCompletion() - liststore = gtk.ListStore(gtk.gdk.Pixbuf, str) + """ + Create a completion model for entry widget completion list consists of + (Pixbuf, Text) rows + """ + completion = gtk.EntryCompletion() + liststore = gtk.ListStore(gtk.gdk.Pixbuf, str) - render_pixbuf = gtk.CellRendererPixbuf() - completion.pack_start(render_pixbuf, expand = False) - completion.add_attribute(render_pixbuf, 'pixbuf', 0) + render_pixbuf = gtk.CellRendererPixbuf() + completion.pack_start(render_pixbuf, expand = False) + completion.add_attribute(render_pixbuf, 'pixbuf', 0) - render_text = gtk.CellRendererText() - completion.pack_start(render_text, expand = True) - completion.add_attribute(render_text, 'text', 1) - completion.set_property('text_column', 1) - completion.set_model(liststore) - entry.set_completion(completion) - return liststore + render_text = gtk.CellRendererText() + completion.pack_start(render_text, expand = True) + completion.add_attribute(render_text, 'text', 1) + completion.set_property('text_column', 1) + completion.set_model(liststore) + entry.set_completion(completion) + return liststore def popup_emoticons_under_button(menu, button, parent_win): - """ - Popup the emoticons menu under button, which is in parent_win - """ - window_x1, window_y1 = parent_win.get_origin() - def position_menu_under_button(menu): - # inline function, which will not keep refs, when used as CB - button_x, button_y = button.allocation.x, button.allocation.y + """ + Popup the emoticons menu under button, which is in parent_win + """ + window_x1, window_y1 = parent_win.get_origin() + def position_menu_under_button(menu): + # inline function, which will not keep refs, when used as CB + button_x, button_y = button.allocation.x, button.allocation.y - # now convert them to X11-relative - window_x, window_y = window_x1, window_y1 - x = window_x + button_x - y = window_y + button_y + # now convert them to X11-relative + window_x, window_y = window_x1, window_y1 + x = window_x + button_x + y = window_y + button_y - menu_height = menu.size_request()[1] + menu_height = menu.size_request()[1] - ## should we pop down or up? - if (y + button.allocation.height + menu_height - < gtk.gdk.screen_height()): - # now move the menu below the button - y += button.allocation.height - else: - # now move the menu above the button - y -= menu_height + ## should we pop down or up? + if (y + button.allocation.height + menu_height + < gtk.gdk.screen_height()): + # now move the menu below the button + y += button.allocation.height + else: + # now move the menu above the button + y -= menu_height - # push_in is True so all the menuitems are always inside screen - push_in = True - return (x, y, push_in) + # push_in is True so all the menuitems are always inside screen + push_in = True + return (x, y, push_in) - menu.popup(None, None, position_menu_under_button, 1, 0) + menu.popup(None, None, position_menu_under_button, 1, 0) def get_theme_font_for_option(theme, option): - """ - Return string description of the font, stored in theme preferences - """ - font_name = gajim.config.get_per('themes', theme, option) - font_desc = pango.FontDescription() - font_prop_str = gajim.config.get_per('themes', theme, option + 'attrs') - if font_prop_str: - if font_prop_str.find('B') != -1: - font_desc.set_weight(pango.WEIGHT_BOLD) - if font_prop_str.find('I') != -1: - font_desc.set_style(pango.STYLE_ITALIC) - fd = pango.FontDescription(font_name) - fd.merge(font_desc, True) - return fd.to_string() + """ + Return string description of the font, stored in theme preferences + """ + font_name = gajim.config.get_per('themes', theme, option) + font_desc = pango.FontDescription() + font_prop_str = gajim.config.get_per('themes', theme, option + 'attrs') + if font_prop_str: + if font_prop_str.find('B') != -1: + font_desc.set_weight(pango.WEIGHT_BOLD) + if font_prop_str.find('I') != -1: + font_desc.set_style(pango.STYLE_ITALIC) + fd = pango.FontDescription(font_name) + fd.merge(font_desc, True) + return fd.to_string() def get_default_font(): - """ - Get the desktop setting for application font first check for GNOME, then - Xfce and last KDE it returns None on failure or else a string 'Font Size' - """ - try: - import gconf - # in try because daemon may not be there - client = gconf.client_get_default() + """ + Get the desktop setting for application font first check for GNOME, then + Xfce and last KDE it returns None on failure or else a string 'Font Size' + """ + try: + import gconf + # in try because daemon may not be there + client = gconf.client_get_default() - return client.get_string('/desktop/gnome/interface/font_name' - ).decode('utf-8') - except Exception: - pass + return client.get_string('/desktop/gnome/interface/font_name' + ).decode('utf-8') + except Exception: + pass - # try to get Xfce default font - # Xfce 4.2 and higher follow freedesktop.org's Base Directory Specification - # see http://www.xfce.org/~benny/xfce/file-locations.html - # and http://freedesktop.org/Standards/basedir-spec - xdg_config_home = os.environ.get('XDG_CONFIG_HOME', '') - if xdg_config_home == '': - xdg_config_home = os.path.expanduser('~/.config') # default - xfce_config_file = os.path.join(xdg_config_home, 'xfce4/mcs_settings/gtk.xml') + # try to get Xfce default font + # Xfce 4.2 and higher follow freedesktop.org's Base Directory Specification + # see http://www.xfce.org/~benny/xfce/file-locations.html + # and http://freedesktop.org/Standards/basedir-spec + xdg_config_home = os.environ.get('XDG_CONFIG_HOME', '') + if xdg_config_home == '': + xdg_config_home = os.path.expanduser('~/.config') # default + xfce_config_file = os.path.join(xdg_config_home, 'xfce4/mcs_settings/gtk.xml') - kde_config_file = os.path.expanduser('~/.kde/share/config/kdeglobals') + kde_config_file = os.path.expanduser('~/.kde/share/config/kdeglobals') - if os.path.exists(xfce_config_file): - try: - for line in open(xfce_config_file): - if line.find('name="Gtk/FontName"') != -1: - start = line.find('value="') + 7 - return line[start:line.find('"', start)].decode('utf-8') - except Exception: - #we talk about file - print >> sys.stderr, _('Error: cannot open %s for reading') % xfce_config_file + if os.path.exists(xfce_config_file): + try: + for line in open(xfce_config_file): + if line.find('name="Gtk/FontName"') != -1: + start = line.find('value="') + 7 + return line[start:line.find('"', start)].decode('utf-8') + except Exception: + #we talk about file + print >> sys.stderr, _('Error: cannot open %s for reading') % xfce_config_file - elif os.path.exists(kde_config_file): - try: - for line in open(kde_config_file): - if line.find('font=') == 0: # font=Verdana,9,other_numbers - start = 5 # 5 is len('font=') - line = line[start:] - values = line.split(',') - font_name = values[0] - font_size = values[1] - font_string = '%s %s' % (font_name, font_size) # Verdana 9 - return font_string.decode('utf-8') - except Exception: - #we talk about file - print >> sys.stderr, _('Error: cannot open %s for reading') % kde_config_file + elif os.path.exists(kde_config_file): + try: + for line in open(kde_config_file): + if line.find('font=') == 0: # font=Verdana,9,other_numbers + start = 5 # 5 is len('font=') + line = line[start:] + values = line.split(',') + font_name = values[0] + font_size = values[1] + font_string = '%s %s' % (font_name, font_size) # Verdana 9 + return font_string.decode('utf-8') + except Exception: + #we talk about file + print >> sys.stderr, _('Error: cannot open %s for reading') % kde_config_file - return None + return None def autodetect_browser_mailer(): - # recognize the environment and set appropriate browser/mailer - if user_runs_gnome(): - gajim.config.set('openwith', 'gnome-open') - elif user_runs_kde(): - gajim.config.set('openwith', 'kfmclient exec') - elif user_runs_xfce(): - gajim.config.set('openwith', 'exo-open') - else: - gajim.config.set('openwith', 'custom') + # recognize the environment and set appropriate browser/mailer + if user_runs_gnome(): + gajim.config.set('openwith', 'gnome-open') + elif user_runs_kde(): + gajim.config.set('openwith', 'kfmclient exec') + elif user_runs_xfce(): + gajim.config.set('openwith', 'exo-open') + else: + gajim.config.set('openwith', 'custom') def user_runs_gnome(): - return 'gnome-session' in get_running_processes() + return 'gnome-session' in get_running_processes() def user_runs_kde(): - return 'startkde' in get_running_processes() + return 'startkde' in get_running_processes() def user_runs_xfce(): - procs = get_running_processes() - if 'startxfce4' in procs or 'xfce4-session' in procs: - return True - return False + procs = get_running_processes() + if 'startxfce4' in procs or 'xfce4-session' in procs: + return True + return False def get_running_processes(): - """ - Return running processes or None (if /proc does not exist) - """ - if os.path.isdir('/proc'): - # under Linux: checking if 'gnome-session' or - # 'startkde' programs were run before gajim, by - # checking /proc (if it exists) - # - # if something is unclear, read `man proc`; - # if /proc exists, directories that have only numbers - # in their names contain data about processes. - # /proc/[xxx]/exe is a symlink to executable started - # as process number [xxx]. - # filter out everything that we are not interested in: - files = os.listdir('/proc') + """ + Return running processes or None (if /proc does not exist) + """ + if os.path.isdir('/proc'): + # under Linux: checking if 'gnome-session' or + # 'startkde' programs were run before gajim, by + # checking /proc (if it exists) + # + # if something is unclear, read `man proc`; + # if /proc exists, directories that have only numbers + # in their names contain data about processes. + # /proc/[xxx]/exe is a symlink to executable started + # as process number [xxx]. + # filter out everything that we are not interested in: + files = os.listdir('/proc') - # files that doesn't have only digits in names... - files = filter(str.isdigit, files) + # files that doesn't have only digits in names... + files = filter(str.isdigit, files) - # files that aren't directories... - files = [f for f in files if os.path.isdir('/proc/' + f)] + # files that aren't directories... + files = [f for f in files if os.path.isdir('/proc/' + f)] - # processes owned by somebody not running gajim... - # (we check if we have access to that file) - files = [f for f in files if os.access('/proc/' + f +'/exe', os.F_OK)] + # processes owned by somebody not running gajim... + # (we check if we have access to that file) + files = [f for f in files if os.access('/proc/' + f +'/exe', os.F_OK)] - # be sure that /proc/[number]/exe is really a symlink - # to avoid TBs in incorrectly configured systems - files = [f for f in files if os.path.islink('/proc/' + f + '/exe')] + # be sure that /proc/[number]/exe is really a symlink + # to avoid TBs in incorrectly configured systems + files = [f for f in files if os.path.islink('/proc/' + f + '/exe')] - # list of processes - processes = [os.path.basename(os.readlink('/proc/' + f +'/exe')) for f in files] + # list of processes + processes = [os.path.basename(os.readlink('/proc/' + f +'/exe')) for f in files] - return processes - return [] + return processes + return [] def move_window(window, x, y): - """ - Move the window, but also check if out of screen - """ - if x < 0: - x = 0 - if y < 0: - y = 0 - w, h = window.get_size() - if x + w > screen_w: - x = screen_w - w - if y + h > screen_h: - y = screen_h - h - window.move(x, y) + """ + Move the window, but also check if out of screen + """ + if x < 0: + x = 0 + if y < 0: + y = 0 + w, h = window.get_size() + if x + w > screen_w: + x = screen_w - w + if y + h > screen_h: + y = screen_h - h + window.move(x, y) def resize_window(window, w, h): - """ - Resize window, but also checks if huge window or negative values - """ - if not w or not h: - return - if w > screen_w: - w = screen_w - if h > screen_h: - h = screen_h - window.resize(abs(w), abs(h)) + """ + Resize window, but also checks if huge window or negative values + """ + if not w or not h: + return + if w > screen_w: + w = screen_w + if h > screen_h: + h = screen_h + window.resize(abs(w), abs(h)) class HashDigest: - def __init__(self, algo, digest): - self.algo = self.cleanID(algo) - self.digest = self.cleanID(digest) + def __init__(self, algo, digest): + self.algo = self.cleanID(algo) + self.digest = self.cleanID(digest) - def cleanID(self, id_): - id_ = id_.strip().lower() - for strip in (' :.-_'): id_ = id_.replace(strip, '') - return id_ + def cleanID(self, id_): + id_ = id_.strip().lower() + for strip in (' :.-_'): id_ = id_.replace(strip, '') + return id_ - def __eq__(self, other): - sa, sd = self.algo, self.digest - if isinstance(other, self.__class__): - oa, od = other.algo, other.digest - elif isinstance(other, basestring): - sa, oa, od = None, None, self.cleanID(other) - elif isinstance(other, tuple) and len(other) == 2: - oa, od = self.cleanID(other[0]), self.cleanID(other[1]) - else: - return False + def __eq__(self, other): + sa, sd = self.algo, self.digest + if isinstance(other, self.__class__): + oa, od = other.algo, other.digest + elif isinstance(other, basestring): + sa, oa, od = None, None, self.cleanID(other) + elif isinstance(other, tuple) and len(other) == 2: + oa, od = self.cleanID(other[0]), self.cleanID(other[1]) + else: + return False - return sa == oa and sd == od + return sa == oa and sd == od - def __ne__(self, other): - return not self == other + def __ne__(self, other): + return not self == other - def __hash__(self): - return self.algo ^ self.digest + def __hash__(self): + return self.algo ^ self.digest - def __str__(self): - prettydigest = '' - for i in xrange(0, len(self.digest), 2): - prettydigest += self.digest[i:i + 2] + ':' - return prettydigest[:-1] + def __str__(self): + prettydigest = '' + for i in xrange(0, len(self.digest), 2): + prettydigest += self.digest[i:i + 2] + ':' + return prettydigest[:-1] - def __repr__(self): - return "%s(%s, %s)" % (self.__class__, repr(self.algo), repr(str(self))) + def __repr__(self): + return "%s(%s, %s)" % (self.__class__, repr(self.algo), repr(str(self))) class ServersXMLHandler(xml.sax.ContentHandler): - def __init__(self): - xml.sax.ContentHandler.__init__(self) - self.servers = [] + def __init__(self): + xml.sax.ContentHandler.__init__(self) + self.servers = [] - def startElement(self, name, attributes): - if name == 'item': - # we will get the port next time so we just set it 0 here - sitem = [None, 0, {}] - sitem[2]['digest'] = {} - sitem[2]['hidden'] = False - for attribute in attributes.getNames(): - if attribute == 'jid': - jid = attributes.getValue(attribute) - sitem[0] = jid - elif attribute == 'hidden': - hidden = attributes.getValue(attribute) - if hidden.lower() in ('1', 'y', 'yes', 't', 'true', 'on'): - sitem[2]['hidden'] = True - self.servers.append(sitem) - elif name == 'active': - for attribute in attributes.getNames(): - if attribute == 'port': - port = attributes.getValue(attribute) - # we received the jid last time, so we now assign the port - # number to the last jid in the list - self.servers[-1][1] = port - elif name == 'digest': - algo, digest = None, None - for attribute in attributes.getNames(): - if attribute == 'algo': - algo = attributes.getValue(attribute) - elif attribute == 'value': - digest = attributes.getValue(attribute) - hd = HashDigest(algo, digest) - self.servers[-1][2]['digest'][hd.algo] = hd + def startElement(self, name, attributes): + if name == 'item': + # we will get the port next time so we just set it 0 here + sitem = [None, 0, {}] + sitem[2]['digest'] = {} + sitem[2]['hidden'] = False + for attribute in attributes.getNames(): + if attribute == 'jid': + jid = attributes.getValue(attribute) + sitem[0] = jid + elif attribute == 'hidden': + hidden = attributes.getValue(attribute) + if hidden.lower() in ('1', 'y', 'yes', 't', 'true', 'on'): + sitem[2]['hidden'] = True + self.servers.append(sitem) + elif name == 'active': + for attribute in attributes.getNames(): + if attribute == 'port': + port = attributes.getValue(attribute) + # we received the jid last time, so we now assign the port + # number to the last jid in the list + self.servers[-1][1] = port + elif name == 'digest': + algo, digest = None, None + for attribute in attributes.getNames(): + if attribute == 'algo': + algo = attributes.getValue(attribute) + elif attribute == 'value': + digest = attributes.getValue(attribute) + hd = HashDigest(algo, digest) + self.servers[-1][2]['digest'][hd.algo] = hd - def endElement(self, name): - pass + def endElement(self, name): + pass def parse_server_xml(path_to_file): - try: - handler = ServersXMLHandler() - xml.sax.parse(path_to_file, handler) - return handler.servers - # handle exception if unable to open file - except IOError, message: - print >> sys.stderr, _('Error reading file:'), message - # handle exception parsing file - except xml.sax.SAXParseException, message: - print >> sys.stderr, _('Error parsing file:'), message + try: + handler = ServersXMLHandler() + xml.sax.parse(path_to_file, handler) + return handler.servers + # handle exception if unable to open file + except IOError, message: + print >> sys.stderr, _('Error reading file:'), message + # handle exception parsing file + except xml.sax.SAXParseException, message: + print >> sys.stderr, _('Error parsing file:'), message def set_unset_urgency_hint(window, unread_messages_no): - """ - Sets/unset urgency hint in window argument depending if we have unread - messages or not - """ - if gajim.config.get('use_urgency_hint'): - if unread_messages_no > 0: - window.props.urgency_hint = True - else: - window.props.urgency_hint = False + """ + Sets/unset urgency hint in window argument depending if we have unread + messages or not + """ + if gajim.config.get('use_urgency_hint'): + if unread_messages_no > 0: + window.props.urgency_hint = True + else: + window.props.urgency_hint = False def get_abspath_for_script(scriptname, want_type = False): - """ - Check if we are svn or normal user and return abspath to asked script if - want_type is True we return 'svn' or 'install' - """ - if os.path.isdir('.svn'): # we are svn user - type_ = 'svn' - cwd = os.getcwd() # it's always ending with src + """ + Check if we are svn or normal user and return abspath to asked script if + want_type is True we return 'svn' or 'install' + """ + if os.path.isdir('.svn'): # we are svn user + type_ = 'svn' + cwd = os.getcwd() # it's always ending with src - if scriptname == 'gajim-remote': - path_to_script = cwd + '/gajim-remote.py' + if scriptname == 'gajim-remote': + path_to_script = cwd + '/gajim-remote.py' - elif scriptname == 'gajim': - script = '#!/bin/sh\n' # the script we may create - script += 'cd %s' % cwd - path_to_script = cwd + '/../scripts/gajim_sm_script' + elif scriptname == 'gajim': + script = '#!/bin/sh\n' # the script we may create + script += 'cd %s' % cwd + path_to_script = cwd + '/../scripts/gajim_sm_script' - try: - if os.path.exists(path_to_script): - os.remove(path_to_script) + try: + if os.path.exists(path_to_script): + os.remove(path_to_script) - f = open(path_to_script, 'w') - script += '\nexec python -OOt gajim.py $0 $@\n' - f.write(script) - f.close() - os.chmod(path_to_script, 0700) - except OSError: # do not traceback (could be a permission problem) - #we talk about a file here - s = _('Could not write to %s. Session Management support will not work') % path_to_script - print >> sys.stderr, s + f = open(path_to_script, 'w') + script += '\nexec python -OOt gajim.py $0 $@\n' + f.write(script) + f.close() + os.chmod(path_to_script, 0700) + except OSError: # do not traceback (could be a permission problem) + #we talk about a file here + s = _('Could not write to %s. Session Management support will not work') % path_to_script + print >> sys.stderr, s - else: # normal user (not svn user) - type_ = 'install' - # always make it like '/usr/local/bin/gajim' - path_to_script = helpers.is_in_path(scriptname, True) + else: # normal user (not svn user) + type_ = 'install' + # always make it like '/usr/local/bin/gajim' + path_to_script = helpers.is_in_path(scriptname, True) - if want_type: - return path_to_script, type_ - else: - return path_to_script + if want_type: + return path_to_script, type_ + else: + return path_to_script def get_pixbuf_from_data(file_data, want_type = False): - """ - Get image data and returns gtk.gdk.Pixbuf if want_type is True it also - returns 'jpeg', 'png' etc - """ - pixbufloader = gtk.gdk.PixbufLoader() - try: - pixbufloader.write(file_data) - pixbufloader.close() - pixbuf = pixbufloader.get_pixbuf() - except gobject.GError: # 'unknown image format' - pixbufloader.close() - pixbuf = None - if want_type: - return None, None - else: - return None + """ + Get image data and returns gtk.gdk.Pixbuf if want_type is True it also + returns 'jpeg', 'png' etc + """ + pixbufloader = gtk.gdk.PixbufLoader() + try: + pixbufloader.write(file_data) + pixbufloader.close() + pixbuf = pixbufloader.get_pixbuf() + except gobject.GError: # 'unknown image format' + pixbufloader.close() + pixbuf = None + if want_type: + return None, None + else: + return None - if want_type: - typ = pixbufloader.get_format()['name'] - return pixbuf, typ - else: - return pixbuf + if want_type: + typ = pixbufloader.get_format()['name'] + return pixbuf, typ + else: + return pixbuf def get_invisible_cursor(): - pixmap = gtk.gdk.Pixmap(None, 1, 1, 1) - color = gtk.gdk.Color() - cursor = gtk.gdk.Cursor(pixmap, pixmap, color, color, 0, 0) - return cursor + pixmap = gtk.gdk.Pixmap(None, 1, 1, 1) + color = gtk.gdk.Color() + cursor = gtk.gdk.Cursor(pixmap, pixmap, color, color, 0, 0) + return cursor def get_current_desktop(window): - """ - Return the current virtual desktop for given window + """ + Return the current virtual desktop for given window - NOTE: Window is a GDK window. - """ - prop = window.property_get('_NET_CURRENT_DESKTOP') - if prop is None: # it means it's normal window (not root window) - # so we look for it's current virtual desktop in another property - prop = window.property_get('_NET_WM_DESKTOP') + NOTE: Window is a GDK window. + """ + prop = window.property_get('_NET_CURRENT_DESKTOP') + if prop is None: # it means it's normal window (not root window) + # so we look for it's current virtual desktop in another property + prop = window.property_get('_NET_WM_DESKTOP') - if prop is not None: - # f.e. prop is ('CARDINAL', 32, [0]) we want 0 or 1.. from [0] - current_virtual_desktop_no = prop[2][0] - return current_virtual_desktop_no + if prop is not None: + # f.e. prop is ('CARDINAL', 32, [0]) we want 0 or 1.. from [0] + current_virtual_desktop_no = prop[2][0] + return current_virtual_desktop_no def possibly_move_window_in_current_desktop(window): - """ - Moves GTK window to current virtual desktop if it is not in the current - virtual desktop + """ + Moves GTK window to current virtual desktop if it is not in the current + virtual desktop - NOTE: Window is a GDK window. - """ - if os.name == 'nt': - return False + NOTE: Window is a GDK window. + """ + if os.name == 'nt': + return False - root_window = gtk.gdk.screen_get_default().get_root_window() - # current user's vd - current_virtual_desktop_no = get_current_desktop(root_window) + root_window = gtk.gdk.screen_get_default().get_root_window() + # current user's vd + current_virtual_desktop_no = get_current_desktop(root_window) - # vd roster window is in - window_virtual_desktop = get_current_desktop(window.window) + # vd roster window is in + window_virtual_desktop = get_current_desktop(window.window) - # if one of those is None, something went wrong and we cannot know - # VD info, just hide it (default action) and not show it afterwards - if None not in (window_virtual_desktop, current_virtual_desktop_no): - if current_virtual_desktop_no != window_virtual_desktop: - # we are in another VD that the window was - # so show it in current VD - window.present() - return True - return False + # if one of those is None, something went wrong and we cannot know + # VD info, just hide it (default action) and not show it afterwards + if None not in (window_virtual_desktop, current_virtual_desktop_no): + if current_virtual_desktop_no != window_virtual_desktop: + # we are in another VD that the window was + # so show it in current VD + window.present() + return True + return False def file_is_locked(path_to_file): - """ - Return True if file is locked + """ + Return True if file is locked - NOTE: Windows only. - """ - if os.name != 'nt': # just in case - return + NOTE: Windows only. + """ + if os.name != 'nt': # just in case + return - if not HAS_PYWIN32: - return + if not HAS_PYWIN32: + return - secur_att = pywintypes.SECURITY_ATTRIBUTES() - secur_att.Initialize() + secur_att = pywintypes.SECURITY_ATTRIBUTES() + secur_att.Initialize() - try: - # try make a handle for READING the file - hfile = win32file.CreateFile( - path_to_file, # path to file - win32con.GENERIC_READ, # open for reading - 0, # do not share with other proc - secur_att, - win32con.OPEN_EXISTING, # existing file only - win32con.FILE_ATTRIBUTE_NORMAL, # normal file - 0 # no attr. template - ) - except pywintypes.error: - return True - else: # in case all went ok, close file handle (go to hell WinAPI) - hfile.Close() - return False + try: + # try make a handle for READING the file + hfile = win32file.CreateFile( + path_to_file, # path to file + win32con.GENERIC_READ, # open for reading + 0, # do not share with other proc + secur_att, + win32con.OPEN_EXISTING, # existing file only + win32con.FILE_ATTRIBUTE_NORMAL, # normal file + 0 # no attr. template + ) + except pywintypes.error: + return True + else: # in case all went ok, close file handle (go to hell WinAPI) + hfile.Close() + return False def _get_fade_color(treeview, selected, focused): - """ - Get a gdk color that is between foreground and background in 0.3 - 0.7 respectively colors of the cell for the given treeview - """ - style = treeview.style - if selected: - if focused: # is the window focused? - state = gtk.STATE_SELECTED - else: # is it not? NOTE: many gtk themes change bg on this - state = gtk.STATE_ACTIVE - else: - state = gtk.STATE_NORMAL - bg = style.base[state] - fg = style.text[state] + """ + Get a gdk color that is between foreground and background in 0.3 + 0.7 respectively colors of the cell for the given treeview + """ + style = treeview.style + if selected: + if focused: # is the window focused? + state = gtk.STATE_SELECTED + else: # is it not? NOTE: many gtk themes change bg on this + state = gtk.STATE_ACTIVE + else: + state = gtk.STATE_NORMAL + bg = style.base[state] + fg = style.text[state] - p = 0.3 # background - q = 0.7 # foreground # p + q should do 1.0 - return gtk.gdk.Color(int(bg.red*p + fg.red*q), - int(bg.green*p + fg.green*q), - int(bg.blue*p + fg.blue*q)) + p = 0.3 # background + q = 0.7 # foreground # p + q should do 1.0 + return gtk.gdk.Color(int(bg.red*p + fg.red*q), + int(bg.green*p + fg.green*q), + int(bg.blue*p + fg.blue*q)) def get_scaled_pixbuf(pixbuf, kind): - """ - Return scaled pixbuf, keeping ratio etc or None kind is either "chat", - "roster", "notification", "tooltip", "vcard" - """ - # resize to a width / height for the avatar not to have distortion - # (keep aspect ratio) - width = gajim.config.get(kind + '_avatar_width') - height = gajim.config.get(kind + '_avatar_height') - if width < 1 or height < 1: - return None + """ + Return scaled pixbuf, keeping ratio etc or None kind is either "chat", + "roster", "notification", "tooltip", "vcard" + """ + # resize to a width / height for the avatar not to have distortion + # (keep aspect ratio) + width = gajim.config.get(kind + '_avatar_width') + height = gajim.config.get(kind + '_avatar_height') + if width < 1 or height < 1: + return None - # Pixbuf size - pix_width = pixbuf.get_width() - pix_height = pixbuf.get_height() - # don't make avatars bigger than they are - if pix_width < width and pix_height < height: - return pixbuf # we don't want to make avatar bigger + # Pixbuf size + pix_width = pixbuf.get_width() + pix_height = pixbuf.get_height() + # don't make avatars bigger than they are + if pix_width < width and pix_height < height: + return pixbuf # we don't want to make avatar bigger - ratio = float(pix_width) / float(pix_height) - if ratio > 1: - w = width - h = int(w / ratio) - else: - h = height - w = int(h * ratio) - scaled_buf = pixbuf.scale_simple(w, h, gtk.gdk.INTERP_HYPER) - return scaled_buf + ratio = float(pix_width) / float(pix_height) + if ratio > 1: + w = width + h = int(w / ratio) + else: + h = height + w = int(h * ratio) + scaled_buf = pixbuf.scale_simple(w, h, gtk.gdk.INTERP_HYPER) + return scaled_buf def get_avatar_pixbuf_from_cache(fjid, use_local=True): - """ - Check if jid has cached avatar and if that avatar is valid image (can be - shown) + """ + Check if jid has cached avatar and if that avatar is valid image (can be + shown) - Returns None if there is no image in vcard/ - Returns 'ask' if cached vcard should not be used (user changed his vcard, so - we have new sha) or if we don't have the vcard - """ - jid, nick = gajim.get_room_and_nick_from_fjid(fjid) - if gajim.config.get('hide_avatar_of_transport') and\ - gajim.jid_is_transport(jid): - # don't show avatar for the transport itself - return None - - if any(jid in gajim.contacts.get_gc_list(acc) for acc in gajim.connections): - is_groupchat_contact = True - else: - is_groupchat_contact = False + Returns None if there is no image in vcard/ + Returns 'ask' if cached vcard should not be used (user changed his vcard, so + we have new sha) or if we don't have the vcard + """ + jid, nick = gajim.get_room_and_nick_from_fjid(fjid) + if gajim.config.get('hide_avatar_of_transport') and\ + gajim.jid_is_transport(jid): + # don't show avatar for the transport itself + return None - puny_jid = helpers.sanitize_filename(jid) - if is_groupchat_contact: - puny_nick = helpers.sanitize_filename(nick) - path = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) - local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid, - puny_nick) + '_local' - else: - path = os.path.join(gajim.VCARD_PATH, puny_jid) - local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid) + \ - '_local' - if use_local: - for extension in ('.png', '.jpeg'): - local_avatar_path = local_avatar_basepath + extension - if os.path.isfile(local_avatar_path): - avatar_file = open(local_avatar_path, 'rb') - avatar_data = avatar_file.read() - avatar_file.close() - return get_pixbuf_from_data(avatar_data) + if any(jid in gajim.contacts.get_gc_list(acc) for acc in gajim.connections): + is_groupchat_contact = True + else: + is_groupchat_contact = False - if not os.path.isfile(path): - return 'ask' + puny_jid = helpers.sanitize_filename(jid) + if is_groupchat_contact: + puny_nick = helpers.sanitize_filename(nick) + path = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) + local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid, + puny_nick) + '_local' + else: + path = os.path.join(gajim.VCARD_PATH, puny_jid) + local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid) + \ + '_local' + if use_local: + for extension in ('.png', '.jpeg'): + local_avatar_path = local_avatar_basepath + extension + if os.path.isfile(local_avatar_path): + avatar_file = open(local_avatar_path, 'rb') + avatar_data = avatar_file.read() + avatar_file.close() + return get_pixbuf_from_data(avatar_data) - vcard_dict = gajim.connections.values()[0].get_cached_vcard(fjid, - is_groupchat_contact) - if not vcard_dict: # This can happen if cached vcard is too old - return 'ask' - if 'PHOTO' not in vcard_dict: - return None - pixbuf = vcard.get_avatar_pixbuf_encoded_mime(vcard_dict['PHOTO'])[0] - return pixbuf + if not os.path.isfile(path): + return 'ask' + + vcard_dict = gajim.connections.values()[0].get_cached_vcard(fjid, + is_groupchat_contact) + if not vcard_dict: # This can happen if cached vcard is too old + return 'ask' + if 'PHOTO' not in vcard_dict: + return None + pixbuf = vcard.get_avatar_pixbuf_encoded_mime(vcard_dict['PHOTO'])[0] + return pixbuf def make_gtk_month_python_month(month): - """ - GTK starts counting months from 0, so January is 0 but Python's time start - from 1, so align to Python + """ + GTK starts counting months from 0, so January is 0 but Python's time start + from 1, so align to Python - NOTE: Month MUST be an integer. - """ - return month + 1 + NOTE: Month MUST be an integer. + """ + return month + 1 def make_python_month_gtk_month(month): - return month - 1 + return month - 1 def make_color_string(color): - """ - Create #aabbcc color string from gtk color - """ - col = '#' - for i in ('red', 'green', 'blue'): - h = hex(getattr(color, i) / (16*16)).split('x')[1] - if len(h) == 1: - h = '0' + h - col += h - return col + """ + Create #aabbcc color string from gtk color + """ + col = '#' + for i in ('red', 'green', 'blue'): + h = hex(getattr(color, i) / (16*16)).split('x')[1] + if len(h) == 1: + h = '0' + h + col += h + return col def make_pixbuf_grayscale(pixbuf): - pixbuf2 = pixbuf.copy() - pixbuf.saturate_and_pixelate(pixbuf2, 0.0, False) - return pixbuf2 + pixbuf2 = pixbuf.copy() + pixbuf.saturate_and_pixelate(pixbuf2, 0.0, False) + return pixbuf2 def get_path_to_generic_or_avatar(generic, jid = None, suffix = None): - """ - Choose between avatar image and default image + """ + Choose between avatar image and default image - Returns full path to the avatar image if it exists, otherwise returns full - path to the image. generic must be with extension and suffix without - """ - if jid: - # we want an avatar - puny_jid = helpers.sanitize_filename(jid) - path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) + suffix - path_to_local_file = path_to_file + '_local' - for extension in ('.png', '.jpeg'): - path_to_local_file_full = path_to_local_file + extension - if os.path.exists(path_to_local_file_full): - return path_to_local_file_full - for extension in ('.png', '.jpeg'): - path_to_file_full = path_to_file + extension - if os.path.exists(path_to_file_full): - return path_to_file_full - return os.path.abspath(generic) + Returns full path to the avatar image if it exists, otherwise returns full + path to the image. generic must be with extension and suffix without + """ + if jid: + # we want an avatar + puny_jid = helpers.sanitize_filename(jid) + path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) + suffix + path_to_local_file = path_to_file + '_local' + for extension in ('.png', '.jpeg'): + path_to_local_file_full = path_to_local_file + extension + if os.path.exists(path_to_local_file_full): + return path_to_local_file_full + for extension in ('.png', '.jpeg'): + path_to_file_full = path_to_file + extension + if os.path.exists(path_to_file_full): + return path_to_file_full + return os.path.abspath(generic) def decode_filechooser_file_paths(file_paths): - """ - Decode as UTF-8 under Windows and ask sys.getfilesystemencoding() in POSIX - file_paths MUST be LIST - """ - file_paths_list = list() + """ + Decode as UTF-8 under Windows and ask sys.getfilesystemencoding() in POSIX + file_paths MUST be LIST + """ + file_paths_list = list() - if os.name == 'nt': # decode as UTF-8 under Windows - for file_path in file_paths: - file_path = file_path.decode('utf8') - file_paths_list.append(file_path) - else: - for file_path in file_paths: - try: - file_path = file_path.decode(sys.getfilesystemencoding()) - except Exception: - try: - file_path = file_path.decode('utf-8') - except Exception: - pass - file_paths_list.append(file_path) + if os.name == 'nt': # decode as UTF-8 under Windows + for file_path in file_paths: + file_path = file_path.decode('utf8') + file_paths_list.append(file_path) + else: + for file_path in file_paths: + try: + file_path = file_path.decode(sys.getfilesystemencoding()) + except Exception: + try: + file_path = file_path.decode('utf-8') + except Exception: + pass + file_paths_list.append(file_path) - return file_paths_list + return file_paths_list def possibly_set_gajim_as_xmpp_handler(): - """ - Register (by default only the first time) 'xmmp:' to Gajim - """ - path_to_dot_kde = os.path.expanduser('~/.kde') - if os.path.exists(path_to_dot_kde): - path_to_kde_file = os.path.join(path_to_dot_kde, - 'share/services/xmpp.protocol') - else: - path_to_kde_file = None + """ + Register (by default only the first time) 'xmmp:' to Gajim + """ + path_to_dot_kde = os.path.expanduser('~/.kde') + if os.path.exists(path_to_dot_kde): + path_to_kde_file = os.path.join(path_to_dot_kde, + 'share/services/xmpp.protocol') + else: + path_to_kde_file = None - def set_gajim_as_xmpp_handler(is_checked=None): - if is_checked is not None: - # come from confirmation dialog - gajim.config.set('check_if_gajim_is_default', is_checked) - path_to_gajim_script, typ = get_abspath_for_script('gajim-remote', True) - if path_to_gajim_script: - if typ == 'svn': - command = path_to_gajim_script + ' handle_uri %s' - else: # 'installed' - command = 'gajim-remote handle_uri %s' + def set_gajim_as_xmpp_handler(is_checked=None): + if is_checked is not None: + # come from confirmation dialog + gajim.config.set('check_if_gajim_is_default', is_checked) + path_to_gajim_script, typ = get_abspath_for_script('gajim-remote', True) + if path_to_gajim_script: + if typ == 'svn': + command = path_to_gajim_script + ' handle_uri %s' + else: # 'installed' + command = 'gajim-remote handle_uri %s' - # setting for GNOME/Gconf - client.set_bool('/desktop/gnome/url-handlers/xmpp/enabled', True) - client.set_string('/desktop/gnome/url-handlers/xmpp/command', command) - client.set_bool('/desktop/gnome/url-handlers/xmpp/needs_terminal', False) + # setting for GNOME/Gconf + client.set_bool('/desktop/gnome/url-handlers/xmpp/enabled', True) + client.set_string('/desktop/gnome/url-handlers/xmpp/command', command) + client.set_bool('/desktop/gnome/url-handlers/xmpp/needs_terminal', False) - # setting for KDE - if path_to_kde_file is not None: # user has run kde at least once - try: - f = open(path_to_kde_file, 'a') - f.write('''\ + # setting for KDE + if path_to_kde_file is not None: # user has run kde at least once + try: + f = open(path_to_kde_file, 'a') + f.write('''\ [Protocol] exec=%s "%%u" protocol=xmpp @@ -778,355 +778,353 @@ deleting=false icon=gajim Description=xmpp ''' % command) - f.close() - except IOError: - log.debug("I/O Error writing settings to %s", repr(path_to_kde_file), exc_info=True) - else: # no gajim remote, stop ask user everytime - gajim.config.set('check_if_gajim_is_default', False) + f.close() + except IOError: + log.debug("I/O Error writing settings to %s", repr(path_to_kde_file), exc_info=True) + else: # no gajim remote, stop ask user everytime + gajim.config.set('check_if_gajim_is_default', False) - try: - import gconf - # in try because daemon may not be there - client = gconf.client_get_default() - except Exception: - return + try: + import gconf + # in try because daemon may not be there + client = gconf.client_get_default() + except Exception: + return - old_command = client.get_string('/desktop/gnome/url-handlers/xmpp/command') - if not old_command or old_command.endswith(' open_chat %s'): - # first time (GNOME/GCONF) or old Gajim version - we_set = True - elif path_to_kde_file is not None and not os.path.exists(path_to_kde_file): - # only the first time (KDE) - we_set = True - else: - we_set = False + old_command = client.get_string('/desktop/gnome/url-handlers/xmpp/command') + if not old_command or old_command.endswith(' open_chat %s'): + # first time (GNOME/GCONF) or old Gajim version + we_set = True + elif path_to_kde_file is not None and not os.path.exists(path_to_kde_file): + # only the first time (KDE) + we_set = True + else: + we_set = False - if we_set: - set_gajim_as_xmpp_handler() - elif old_command and not old_command.endswith(' handle_uri %s'): - # xmpp: is currently handled by another program, so ask the user - pritext = _('Gajim is not the default Jabber client') - sectext = _('Would you like to make Gajim the default Jabber client?') - checktext = _('Always check to see if Gajim is the default Jabber client ' - 'on startup') - def on_cancel(checked): - gajim.config.set('check_if_gajim_is_default', checked) - dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, checktext, - set_gajim_as_xmpp_handler, on_cancel) - if gajim.config.get('check_if_gajim_is_default'): - dlg.checkbutton.set_active(True) + if we_set: + set_gajim_as_xmpp_handler() + elif old_command and not old_command.endswith(' handle_uri %s'): + # xmpp: is currently handled by another program, so ask the user + pritext = _('Gajim is not the default Jabber client') + sectext = _('Would you like to make Gajim the default Jabber client?') + checktext = _('Always check to see if Gajim is the default Jabber client ' + 'on startup') + def on_cancel(checked): + gajim.config.set('check_if_gajim_is_default', checked) + dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, checktext, + set_gajim_as_xmpp_handler, on_cancel) + if gajim.config.get('check_if_gajim_is_default'): + dlg.checkbutton.set_active(True) def escape_underscore(s): - """ - Escape underlines to prevent them from being interpreted as keyboard - accelerators - """ - return s.replace('_', '__') + """ + Escape underlines to prevent them from being interpreted as keyboard + accelerators + """ + return s.replace('_', '__') def get_state_image_from_file_path_show(file_path, show): - state_file = show.replace(' ', '_') - files = [] - files.append(os.path.join(file_path, state_file + '.png')) - files.append(os.path.join(file_path, state_file + '.gif')) - image = gtk.Image() - image.set_from_pixbuf(None) - for file_ in files: - if os.path.exists(file_): - image.set_from_file(file_) - break + state_file = show.replace(' ', '_') + files = [] + files.append(os.path.join(file_path, state_file + '.png')) + files.append(os.path.join(file_path, state_file + '.gif')) + image = gtk.Image() + image.set_from_pixbuf(None) + for file_ in files: + if os.path.exists(file_): + image.set_from_file(file_) + break - return image + return image def get_possible_button_event(event): - """ - Mouse or keyboard caused the event? - """ - if event.type == gtk.gdk.KEY_PRESS: - return 0 # no event.button so pass 0 - # BUTTON_PRESS event, so pass event.button - return event.button + """ + Mouse or keyboard caused the event? + """ + if event.type == gtk.gdk.KEY_PRESS: + return 0 # no event.button so pass 0 + # BUTTON_PRESS event, so pass event.button + return event.button def destroy_widget(widget): - widget.destroy() + widget.destroy() def on_avatar_save_as_menuitem_activate(widget, jid, account, default_name=''): - def on_continue(response, file_path): - if response < 0: - return - pixbuf = get_avatar_pixbuf_from_cache(jid) - path, extension = os.path.splitext(file_path) - if not extension: - # Silently save as Jpeg image - image_format = 'jpeg' - file_path += '.jpeg' - elif extension == 'jpg': - image_format = 'jpeg' - else: - image_format = extension[1:] # remove leading dot + def on_continue(response, file_path): + if response < 0: + return + pixbuf = get_avatar_pixbuf_from_cache(jid) + path, extension = os.path.splitext(file_path) + if not extension: + # Silently save as Jpeg image + image_format = 'jpeg' + file_path += '.jpeg' + elif extension == 'jpg': + image_format = 'jpeg' + else: + image_format = extension[1:] # remove leading dot - # Save image - try: - pixbuf.save(file_path, image_format) - except glib.GError, e: - log.debug('Error saving avatar: %s' % str(e)) - if os.path.exists(file_path): - os.remove(file_path) - new_file_path = '.'.join(file_path.split('.')[:-1]) + '.jpeg' - def on_ok(file_path, pixbuf): - pixbuf.save(file_path, 'jpeg') - dialogs.ConfirmationDialog(_('Extension not supported'), - _('Image cannot be saved in %(type)s format. Save as %(new_filename)s?' - ) % {'type': image_format, 'new_filename': new_file_path}, - on_response_ok = (on_ok, new_file_path, pixbuf)) - else: - dialog.destroy() + # Save image + try: + pixbuf.save(file_path, image_format) + except glib.GError, e: + log.debug('Error saving avatar: %s' % str(e)) + if os.path.exists(file_path): + os.remove(file_path) + new_file_path = '.'.join(file_path.split('.')[:-1]) + '.jpeg' + def on_ok(file_path, pixbuf): + pixbuf.save(file_path, 'jpeg') + dialogs.ConfirmationDialog(_('Extension not supported'), + _('Image cannot be saved in %(type)s format. Save as %(new_filename)s?' + ) % {'type': image_format, 'new_filename': new_file_path}, + on_response_ok = (on_ok, new_file_path, pixbuf)) + else: + dialog.destroy() - def on_ok(widget): - file_path = dialog.get_filename() - file_path = decode_filechooser_file_paths((file_path,))[0] - if os.path.exists(file_path): - # check if we have write permissions - if not os.access(file_path, os.W_OK): - file_name = os.path.basename(file_path) - dialogs.ErrorDialog(_('Cannot overwrite existing file "%s"' % - file_name), - _('A file with this name already exists and you do not have ' - 'permission to overwrite it.')) - return - dialog2 = dialogs.FTOverwriteConfirmationDialog( - _('This file already exists'), _('What do you want to do?'), - propose_resume=False, on_response=(on_continue, file_path)) - dialog2.set_transient_for(dialog) - dialog2.set_destroy_with_parent(True) - else: - dirname = os.path.dirname(file_path) - if not os.access(dirname, os.W_OK): - dialogs.ErrorDialog(_('Directory "%s" is not writable') % \ - dirname, _('You do not have permission to create files in this' - ' directory.')) - return + def on_ok(widget): + file_path = dialog.get_filename() + file_path = decode_filechooser_file_paths((file_path,))[0] + if os.path.exists(file_path): + # check if we have write permissions + if not os.access(file_path, os.W_OK): + file_name = os.path.basename(file_path) + dialogs.ErrorDialog(_('Cannot overwrite existing file "%s"' % + file_name), + _('A file with this name already exists and you do not have ' + 'permission to overwrite it.')) + return + dialog2 = dialogs.FTOverwriteConfirmationDialog( + _('This file already exists'), _('What do you want to do?'), + propose_resume=False, on_response=(on_continue, file_path)) + dialog2.set_transient_for(dialog) + dialog2.set_destroy_with_parent(True) + else: + dirname = os.path.dirname(file_path) + if not os.access(dirname, os.W_OK): + dialogs.ErrorDialog(_('Directory "%s" is not writable') % \ + dirname, _('You do not have permission to create files in this' + ' directory.')) + return - on_continue(0, file_path) + on_continue(0, file_path) - def on_cancel(widget): - dialog.destroy() + def on_cancel(widget): + dialog.destroy() - dialog = dialogs.FileChooserDialog(title_text=_('Save Image as...'), - action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL, - gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK), - default_response=gtk.RESPONSE_OK, - current_folder=gajim.config.get('last_save_dir'), on_response_ok=on_ok, - on_response_cancel=on_cancel) + dialog = dialogs.FileChooserDialog(title_text=_('Save Image as...'), + action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL, + gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK), + default_response=gtk.RESPONSE_OK, + current_folder=gajim.config.get('last_save_dir'), on_response_ok=on_ok, + on_response_cancel=on_cancel) - dialog.set_current_name(default_name + '.jpeg') - dialog.connect('delete-event', lambda widget, event: - on_cancel(widget)) + dialog.set_current_name(default_name + '.jpeg') + dialog.connect('delete-event', lambda widget, event: + on_cancel(widget)) def on_bm_header_changed_state(widget, event): - widget.set_state(gtk.STATE_NORMAL) #do not allow selected_state + widget.set_state(gtk.STATE_NORMAL) #do not allow selected_state def create_combobox(value_list, selected_value = None): - """ - Value_list is [(label1, value1)] - """ - liststore = gtk.ListStore(str, str) - combobox = gtk.ComboBox(liststore) - cell = gtk.CellRendererText() - combobox.pack_start(cell, True) - combobox.add_attribute(cell, 'text', 0) - i = -1 - for value in value_list: - liststore.append(value) - if selected_value == value[1]: - i = value_list.index(value) - if i > -1: - combobox.set_active(i) - combobox.show_all() - return combobox + """ + Value_list is [(label1, value1)] + """ + liststore = gtk.ListStore(str, str) + combobox = gtk.ComboBox(liststore) + cell = gtk.CellRendererText() + combobox.pack_start(cell, True) + combobox.add_attribute(cell, 'text', 0) + i = -1 + for value in value_list: + liststore.append(value) + if selected_value == value[1]: + i = value_list.index(value) + if i > -1: + combobox.set_active(i) + combobox.show_all() + return combobox def create_list_multi(value_list, selected_values=None): - """ - Value_list is [(label1, value1)] - """ - liststore = gtk.ListStore(str, str) - treeview = gtk.TreeView(liststore) - treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) - treeview.set_headers_visible(False) - col = gtk.TreeViewColumn() - treeview.append_column(col) - cell = gtk.CellRendererText() - col.pack_start(cell, True) - col.set_attributes(cell, text=0) - for value in value_list: - iter = liststore.append(value) - if value[1] in selected_values: - treeview.get_selection().select_iter(iter) - treeview.show_all() - return treeview + """ + Value_list is [(label1, value1)] + """ + liststore = gtk.ListStore(str, str) + treeview = gtk.TreeView(liststore) + treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) + treeview.set_headers_visible(False) + col = gtk.TreeViewColumn() + treeview.append_column(col) + cell = gtk.CellRendererText() + col.pack_start(cell, True) + col.set_attributes(cell, text=0) + for value in value_list: + iter = liststore.append(value) + if value[1] in selected_values: + treeview.get_selection().select_iter(iter) + treeview.show_all() + return treeview def load_iconset(path, pixbuf2=None, transport=False): - """ - Load full iconset from the given path, and add pixbuf2 on top left of each - static images - """ - path += '/' - if transport: - list_ = ('online', 'chat', 'away', 'xa', 'dnd', 'offline', - 'not in roster') - else: - list_ = ('connecting', 'online', 'chat', 'away', 'xa', 'dnd', - 'invisible', 'offline', 'error', 'requested', 'event', 'opened', - 'closed', 'not in roster', 'muc_active', 'muc_inactive') - if pixbuf2: - list_ = ('connecting', 'online', 'chat', 'away', 'xa', 'dnd', - 'offline', 'error', 'requested', 'event', 'not in roster') - return _load_icon_list(list_, path, pixbuf2) + """ + Load full iconset from the given path, and add pixbuf2 on top left of each + static images + """ + path += '/' + if transport: + list_ = ('online', 'chat', 'away', 'xa', 'dnd', 'offline', + 'not in roster') + else: + list_ = ('connecting', 'online', 'chat', 'away', 'xa', 'dnd', + 'invisible', 'offline', 'error', 'requested', 'event', 'opened', + 'closed', 'not in roster', 'muc_active', 'muc_inactive') + if pixbuf2: + list_ = ('connecting', 'online', 'chat', 'away', 'xa', 'dnd', + 'offline', 'error', 'requested', 'event', 'not in roster') + return _load_icon_list(list_, path, pixbuf2) def load_icon(icon_name): - """ - Load an icon from the iconset in 16x16 - """ - iconset = gajim.config.get('iconset') - path = os.path.join(helpers.get_iconset_path(iconset), '16x16', '') - icon_list = _load_icon_list([icon_name], path) - return icon_list[icon_name] + """ + Load an icon from the iconset in 16x16 + """ + iconset = gajim.config.get('iconset') + path = os.path.join(helpers.get_iconset_path(iconset), '16x16', '') + icon_list = _load_icon_list([icon_name], path) + return icon_list[icon_name] def load_mood_icon(icon_name): - """ - Load an icon from the mood iconset in 16x16 - """ - iconset = gajim.config.get('mood_iconset') - path = os.path.join(helpers.get_mood_iconset_path(iconset), '') - icon_list = _load_icon_list([icon_name], path) - return icon_list[icon_name] + """ + Load an icon from the mood iconset in 16x16 + """ + iconset = gajim.config.get('mood_iconset') + path = os.path.join(helpers.get_mood_iconset_path(iconset), '') + icon_list = _load_icon_list([icon_name], path) + return icon_list[icon_name] def load_activity_icon(category, activity = None): - """ - Load an icon from the activity iconset in 16x16 - """ - iconset = gajim.config.get('activity_iconset') - path = os.path.join(helpers.get_activity_iconset_path(iconset), - category, '') - if activity is None: - activity = 'category' - icon_list = _load_icon_list([activity], path) - return icon_list[activity] + """ + Load an icon from the activity iconset in 16x16 + """ + iconset = gajim.config.get('activity_iconset') + path = os.path.join(helpers.get_activity_iconset_path(iconset), + category, '') + if activity is None: + activity = 'category' + icon_list = _load_icon_list([activity], path) + return icon_list[activity] def load_icons_meta(): - """ - Load and return - AND + small icons to put on top left of an icon for meta - contacts - """ - iconset = gajim.config.get('iconset') - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - # try to find opened_meta.png file, else opened.png else nopixbuf merge - path_opened = os.path.join(path, 'opened_meta.png') - if not os.path.isfile(path_opened): - path_opened = os.path.join(path, 'opened.png') - if os.path.isfile(path_opened): - pixo = gtk.gdk.pixbuf_new_from_file(path_opened) - else: - pixo = None - # Same thing for closed - path_closed = os.path.join(path, 'opened_meta.png') - if not os.path.isfile(path_closed): - path_closed = os.path.join(path, 'closed.png') - if os.path.isfile(path_closed): - pixc = gtk.gdk.pixbuf_new_from_file(path_closed) - else: - pixc = None - return pixo, pixc + """ + Load and return - AND + small icons to put on top left of an icon for meta + contacts + """ + iconset = gajim.config.get('iconset') + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + # try to find opened_meta.png file, else opened.png else nopixbuf merge + path_opened = os.path.join(path, 'opened_meta.png') + if not os.path.isfile(path_opened): + path_opened = os.path.join(path, 'opened.png') + if os.path.isfile(path_opened): + pixo = gtk.gdk.pixbuf_new_from_file(path_opened) + else: + pixo = None + # Same thing for closed + path_closed = os.path.join(path, 'opened_meta.png') + if not os.path.isfile(path_closed): + path_closed = os.path.join(path, 'closed.png') + if os.path.isfile(path_closed): + pixc = gtk.gdk.pixbuf_new_from_file(path_closed) + else: + pixc = None + return pixo, pixc def _load_icon_list(icons_list, path, pixbuf2 = None): - """ - Load icons in icons_list from the given path, and add pixbuf2 on top left of - each static images - """ - imgs = {} - for icon in icons_list: - # try to open a pixfile with the correct method - icon_file = icon.replace(' ', '_') - files = [] - files.append(path + icon_file + '.gif') - files.append(path + icon_file + '.png') - image = gtk.Image() - image.show() - imgs[icon] = image - for file_ in files: # loop seeking for either gif or png - if os.path.exists(file_): - image.set_from_file(file_) - if pixbuf2 and image.get_storage_type() == gtk.IMAGE_PIXBUF: - # add pixbuf2 on top-left corner of image - pixbuf1 = image.get_pixbuf() - pixbuf2.composite(pixbuf1, 0, 0, - pixbuf2.get_property('width'), - pixbuf2.get_property('height'), 0, 0, 1.0, 1.0, - gtk.gdk.INTERP_NEAREST, 255) - image.set_from_pixbuf(pixbuf1) - break - return imgs + """ + Load icons in icons_list from the given path, and add pixbuf2 on top left of + each static images + """ + imgs = {} + for icon in icons_list: + # try to open a pixfile with the correct method + icon_file = icon.replace(' ', '_') + files = [] + files.append(path + icon_file + '.gif') + files.append(path + icon_file + '.png') + image = gtk.Image() + image.show() + imgs[icon] = image + for file_ in files: # loop seeking for either gif or png + if os.path.exists(file_): + image.set_from_file(file_) + if pixbuf2 and image.get_storage_type() == gtk.IMAGE_PIXBUF: + # add pixbuf2 on top-left corner of image + pixbuf1 = image.get_pixbuf() + pixbuf2.composite(pixbuf1, 0, 0, + pixbuf2.get_property('width'), + pixbuf2.get_property('height'), 0, 0, 1.0, 1.0, + gtk.gdk.INTERP_NEAREST, 255) + image.set_from_pixbuf(pixbuf1) + break + return imgs def make_jabber_state_images(): - """ - Initialize jabber_state_images dictionary - """ - iconset = gajim.config.get('iconset') - if iconset: - if helpers.get_iconset_path(iconset): - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - if not os.path.exists(path): - iconset = gajim.config.DEFAULT_ICONSET - gajim.config.set('iconset', iconset) - else: - iconset = gajim.config.DEFAULT_ICONSET - gajim.config.set('iconset', iconset) - else: - iconset = gajim.config.DEFAULT_ICONSET - gajim.config.set('iconset', iconset) + """ + Initialize jabber_state_images dictionary + """ + iconset = gajim.config.get('iconset') + if iconset: + if helpers.get_iconset_path(iconset): + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + if not os.path.exists(path): + iconset = gajim.config.DEFAULT_ICONSET + gajim.config.set('iconset', iconset) + else: + iconset = gajim.config.DEFAULT_ICONSET + gajim.config.set('iconset', iconset) + else: + iconset = gajim.config.DEFAULT_ICONSET + gajim.config.set('iconset', iconset) - path = os.path.join(helpers.get_iconset_path(iconset), '32x32') - gajim.interface.jabber_state_images['32'] = load_iconset(path) + path = os.path.join(helpers.get_iconset_path(iconset), '32x32') + gajim.interface.jabber_state_images['32'] = load_iconset(path) - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - gajim.interface.jabber_state_images['16'] = load_iconset(path) + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + gajim.interface.jabber_state_images['16'] = load_iconset(path) - pixo, pixc = load_icons_meta() - gajim.interface.jabber_state_images['opened'] = load_iconset(path, pixo) - gajim.interface.jabber_state_images['closed'] = load_iconset(path, pixc) + pixo, pixc = load_icons_meta() + gajim.interface.jabber_state_images['opened'] = load_iconset(path, pixo) + gajim.interface.jabber_state_images['closed'] = load_iconset(path, pixc) def reload_jabber_state_images(): - make_jabber_state_images() - gajim.interface.roster.update_jabber_state_images() + make_jabber_state_images() + gajim.interface.roster.update_jabber_state_images() def label_set_autowrap(widget): - """ - Make labels automatically re-wrap if their containers are resized. - Accepts label or container widgets - """ - if isinstance (widget, gtk.Container): - children = widget.get_children() - for i in xrange (len (children)): - label_set_autowrap(children[i]) - elif isinstance(widget, gtk.Label): - widget.set_line_wrap(True) - widget.connect_after('size-allocate', __label_size_allocate) + """ + Make labels automatically re-wrap if their containers are resized. + Accepts label or container widgets + """ + if isinstance (widget, gtk.Container): + children = widget.get_children() + for i in xrange (len (children)): + label_set_autowrap(children[i]) + elif isinstance(widget, gtk.Label): + widget.set_line_wrap(True) + widget.connect_after('size-allocate', __label_size_allocate) def __label_size_allocate(widget, allocation): - """ - Callback which re-allocates the size of a label - """ - layout = widget.get_layout() + """ + Callback which re-allocates the size of a label + """ + layout = widget.get_layout() - lw_old, lh_old = layout.get_size() - # fixed width labels - if lw_old/pango.SCALE == allocation.width: - return + lw_old, lh_old = layout.get_size() + # fixed width labels + if lw_old/pango.SCALE == allocation.width: + return - # set wrap width to the pango.Layout of the labels ### - layout.set_width (allocation.width * pango.SCALE) - lw, lh = layout.get_size () + # set wrap width to the pango.Layout of the labels ### + layout.set_width (allocation.width * pango.SCALE) + lw, lh = layout.get_size () - if lh_old != lh: - widget.set_size_request (-1, lh / pango.SCALE) - -# vim: se ts=3: + if lh_old != lh: + widget.set_size_request (-1, lh / pango.SCALE) diff --git a/src/gtkspell.py b/src/gtkspell.py index c5dc62af8..aa1adaec7 100644 --- a/src/gtkspell.py +++ b/src/gtkspell.py @@ -97,4 +97,3 @@ class Spell(object): def get_from_text_view(textview): return Spell(textview, create=False) - diff --git a/src/gui_interface.py b/src/gui_interface.py index aa3991bed..f62e67682 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -49,9 +49,9 @@ from common import gajim from common import dbus_support if dbus_support.supported: - from music_track_listener import MusicTrackListener - from common import location_listener - import dbus + from music_track_listener import MusicTrackListener + from common import location_listener + import dbus import gtkgui_helpers @@ -102,3356 +102,3354 @@ class Interface: ### Methods handling events from connection ################################################################################ - def handle_event_roster(self, account, data): - #('ROSTER', account, array) - # FIXME: Those methods depend to highly on each other - # and the order in which they are called - self.roster.fill_contacts_and_groups_dicts(data, account) - self.roster.add_account_contacts(account) - self.roster.fire_up_unread_messages_events(account) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('Roster', (account, data)) - - def handle_event_warning(self, unused, data): - #('WARNING', account, (title_text, section_text)) - dialogs.WarningDialog(data[0], data[1]) - - def handle_event_error(self, unused, data): - #('ERROR', account, (title_text, section_text)) - dialogs.ErrorDialog(data[0], data[1]) - - def handle_event_information(self, unused, data): - #('INFORMATION', account, (title_text, section_text)) - dialogs.InformationDialog(data[0], data[1]) - - def handle_event_ask_new_nick(self, account, data): - #('ASK_NEW_NICK', account, (room_jid,)) - room_jid = data[0] - title = _('Unable to join group chat') - prompt = _('Your desired nickname in group chat %s is in use or ' - 'registered by another occupant.\nPlease specify another nickname ' - 'below:') % room_jid - check_text = _('Always use this nickname when there is a conflict') - if 'change_nick_dialog' in self.instances: - self.instances['change_nick_dialog'].add_room(account, room_jid, - prompt) - else: - self.instances['change_nick_dialog'] = dialogs.ChangeNickDialog( - account, room_jid, title, prompt) - - def handle_event_http_auth(self, account, data): - #('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg)) - def response(account, iq_obj, answer): - self.dialog.destroy() - gajim.connections[account].build_http_auth_answer(iq_obj, answer) - - def on_yes(is_checked, account, iq_obj): - response(account, iq_obj, 'yes') - - sec_msg = _('Do you accept this request?') - if gajim.get_number_of_connected_accounts() > 1: - sec_msg = _('Do you accept this request on account %s?') % account - if data[4]: - sec_msg = data[4] + '\n' + sec_msg - self.dialog = dialogs.YesNoDialog(_('HTTP (%(method)s) Authorization for ' - '%(url)s (id: %(id)s)') % {'method': data[0], 'url': data[1], - 'id': data[2]}, sec_msg, on_response_yes=(on_yes, account, data[3]), - on_response_no=(response, account, data[3], 'no')) - - def handle_event_error_answer(self, account, array): - #('ERROR_ANSWER', account, (id, jid_from, errmsg, errcode)) - id_, jid_from, errmsg, errcode = array - if unicode(errcode) in ('400', '403', '406') and id_: - # show the error dialog - ft = self.instances['file_transfers'] - sid = id_ - if len(id_) > 3 and id_[2] == '_': - sid = id_[3:] - if sid in ft.files_props['s']: - file_props = ft.files_props['s'][sid] - if unicode(errcode) == '400': - file_props['error'] = -3 - else: - file_props['error'] = -4 - self.handle_event_file_request_error(account, - (jid_from, file_props, errmsg)) - conn = gajim.connections[account] - conn.disconnect_transfer(file_props) - return - elif unicode(errcode) == '404': - conn = gajim.connections[account] - sid = id_ - if len(id_) > 3 and id_[2] == '_': - sid = id_[3:] - if sid in conn.files_props: - file_props = conn.files_props[sid] - self.handle_event_file_send_error(account, - (jid_from, file_props)) - conn.disconnect_transfer(file_props) - return - - ctrl = self.msg_win_mgr.get_control(jid_from, account) - if ctrl and ctrl.type_id == message_control.TYPE_GC: - ctrl.print_conversation('Error %s: %s' % (array[2], array[1])) - - def handle_event_con_type(self, account, con_type): - # ('CON_TYPE', account, con_type) which can be 'ssl', 'tls', 'plain' - gajim.con_types[account] = con_type - self.roster.draw_account(account) - - def handle_event_connection_lost(self, account, array): - # ('CONNECTION_LOST', account, [title, text]) - path = gtkgui_helpers.get_icon_path('gajim-connection_lost', 48) - notify.popup(_('Connection Failed'), account, account, - 'connection_failed', path, array[0], array[1]) - - def unblock_signed_in_notifications(self, account): - gajim.block_signed_in_notifications[account] = False - - def handle_event_status(self, account, show): # OUR status - #('STATUS', account, show) - model = self.roster.status_combobox.get_model() - if show in ('offline', 'error'): - for name in self.instances[account]['online_dialog'].keys(): - # .keys() is needed to not have a dictionary length changed during - # iteration error - self.instances[account]['online_dialog'][name].destroy() - del self.instances[account]['online_dialog'][name] - for request in self.gpg_passphrase.values(): - if request: - request.interrupt() - if account in self.pass_dialog: - self.pass_dialog[account].window.destroy() - if show == 'offline': - # sensitivity for this menuitem - if gajim.get_number_of_connected_accounts() == 0: - model[self.roster.status_message_menuitem_iter][3] = False - gajim.block_signed_in_notifications[account] = True - else: - # 30 seconds after we change our status to sth else than offline - # we stop blocking notifications of any kind - # this prevents from getting the roster items as 'just signed in' - # contacts. 30 seconds should be enough time - gobject.timeout_add_seconds(30, self.unblock_signed_in_notifications, account) - # sensitivity for this menuitem - model[self.roster.status_message_menuitem_iter][3] = True - - # Inform all controls for this account of the connection state change - ctrls = self.msg_win_mgr.get_controls() - if account in self.minimized_controls: - # Can not be the case when we remove account - ctrls += self.minimized_controls[account].values() - for ctrl in ctrls: - if ctrl.account == account: - if show == 'offline' or (show == 'invisible' and \ - gajim.connections[account].is_zeroconf): - ctrl.got_disconnected() - else: - # Other code rejoins all GCs, so we don't do it here - if not ctrl.type_id == message_control.TYPE_GC: - ctrl.got_connected() - if ctrl.parent_win: - ctrl.parent_win.redraw_tab(ctrl) - - self.roster.on_status_changed(account, show) - if account in self.show_vcard_when_connect and show not in ('offline', - 'error'): - self.edit_own_details(account) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('AccountPresence', (show, account)) - - def handle_event_new_jid(self, account, data): - #('NEW_JID', account, (old_jid, new_jid)) - """ - This event is raised when our JID changed (most probably because we use - anonymous account. We update contact and roster entry in this case - """ - self.roster.rename_self_contact(data[0], data[1], account) - - def edit_own_details(self, account): - jid = gajim.get_jid_from_account(account) - if 'profile' not in self.instances[account]: - self.instances[account]['profile'] = \ - profile_window.ProfileWindow(account) - gajim.connections[account].request_vcard(jid) - - def handle_event_notify(self, account, array): - # 'NOTIFY' (account, (jid, status, status message, resource, - # priority, # keyID, timestamp, contact_nickname)) - # - # Contact changed show - - # FIXME: Drop and rewrite... - - statuss = ['offline', 'error', 'online', 'chat', 'away', 'xa', 'dnd', - 'invisible'] - # Ignore invalid show - if array[1] not in statuss: - return - old_show = 0 - new_show = statuss.index(array[1]) - status_message = array[2] - jid = array[0].split('/')[0] - keyID = array[5] - contact_nickname = array[7] - - # Get the proper keyID - keyID = helpers.prepare_and_validate_gpg_keyID(account, jid, keyID) - - resource = array[3] - if not resource: - resource = '' - priority = array[4] - if gajim.jid_is_transport(jid): - # It must be an agent - ji = jid.replace('@', '') - else: - ji = jid - - highest = gajim.contacts. \ - get_contact_with_highest_priority(account, jid) - was_highest = (highest and highest.resource == resource) - - conn = gajim.connections[account] - - # Update contact - jid_list = gajim.contacts.get_jid_list(account) - if ji in jid_list or jid == gajim.get_jid_from_account(account): - lcontact = gajim.contacts.get_contacts(account, ji) - contact1 = None - resources = [] - for c in lcontact: - resources.append(c.resource) - if c.resource == resource: - contact1 = c - break - - if contact1: - if contact1.show in statuss: - old_show = statuss.index(contact1.show) - # nick changed - if contact_nickname is not None and \ - contact1.contact_name != contact_nickname: - contact1.contact_name = contact_nickname - self.roster.draw_contact(jid, account) - - if old_show == new_show and contact1.status == status_message and \ - contact1.priority == priority: # no change - return - else: - contact1 = gajim.contacts.get_first_contact_from_jid(account, ji) - if not contact1: - # Presence of another resource of our - # jid - # Create self contact and add to roster - if resource == conn.server_resource: - return - # Ignore offline presence of unknown self resource - if new_show < 2: - return - contact1 = gajim.contacts.create_self_contact(jid=ji, - account=account, show=array[1], status=status_message, - priority=priority, keyID=keyID, resource=resource) - old_show = 0 - gajim.contacts.add_contact(account, contact1) - lcontact.append(contact1) - elif contact1.show in statuss: - old_show = statuss.index(contact1.show) - if (resources != [''] and (len(lcontact) != 1 or \ - lcontact[0].show != 'offline')) and jid.find('@') > 0: - # Another resource of an existing contact connected - old_show = 0 - contact1 = gajim.contacts.copy_contact(contact1) - lcontact.append(contact1) - contact1.resource = resource - - self.roster.add_contact(contact1.jid, account) - - if contact1.jid.find('@') > 0 and len(lcontact) == 1: - # It's not an agent - if old_show == 0 and new_show > 1: - if not contact1.jid in gajim.newly_added[account]: - gajim.newly_added[account].append(contact1.jid) - if contact1.jid in gajim.to_be_removed[account]: - gajim.to_be_removed[account].remove(contact1.jid) - gobject.timeout_add_seconds(5, self.roster.remove_newly_added, - contact1.jid, account) - elif old_show > 1 and new_show == 0 and conn.connected > 1: - if not contact1.jid in gajim.to_be_removed[account]: - gajim.to_be_removed[account].append(contact1.jid) - if contact1.jid in gajim.newly_added[account]: - gajim.newly_added[account].remove(contact1.jid) - self.roster.draw_contact(contact1.jid, account) - gobject.timeout_add_seconds(5, self.roster.remove_to_be_removed, - contact1.jid, account) - - # unset custom status - if (old_show == 0 and new_show > 1) or (old_show > 1 and new_show == 0\ - and conn.connected > 1): - if account in self.status_sent_to_users and \ - jid in self.status_sent_to_users[account]: - del self.status_sent_to_users[account][jid] - - contact1.show = array[1] - contact1.status = status_message - contact1.priority = priority - contact1.keyID = keyID - timestamp = array[6] - if timestamp: - contact1.last_status_time = timestamp - elif not gajim.block_signed_in_notifications[account]: - # We're connected since more that 30 seconds - contact1.last_status_time = time.localtime() - contact1.contact_nickname = contact_nickname - - if gajim.jid_is_transport(jid): - # It must be an agent - if ji in jid_list: - # Update existing iter and group counting - self.roster.draw_contact(ji, account) - self.roster.draw_group(_('Transports'), account) - if new_show > 1 and ji in gajim.transport_avatar[account]: - # transport just signed in. - # request avatars - for jid_ in gajim.transport_avatar[account][ji]: - conn.request_vcard(jid_) - # transport just signed in/out, don't show - # popup notifications for 30s - account_ji = account + '/' + ji - gajim.block_signed_in_notifications[account_ji] = True - gobject.timeout_add_seconds(30, - self.unblock_signed_in_notifications, account_ji) - locations = (self.instances, self.instances[account]) - for location in locations: - if 'add_contact' in location: - if old_show == 0 and new_show > 1: - location['add_contact'].transport_signed_in(jid) - break - elif old_show > 1 and new_show == 0: - location['add_contact'].transport_signed_out(jid) - break - elif ji in jid_list: - # It isn't an agent - # reset chatstate if needed: - # (when contact signs out or has errors) - if array[1] in ('offline', 'error'): - contact1.our_chatstate = contact1.chatstate = \ - contact1.composing_xep = None - - # TODO: This causes problems when another - # resource signs off! - conn.stop_all_active_file_transfers(contact1) - - # disable encryption, since if any messages are - # lost they'll be not decryptable (note that - # this contradicts XEP-0201 - trying to get that - # in the XEP, though) - - # there won't be any sessions here if the contact terminated - # their sessions before going offline (which we do) - for sess in conn.get_sessions(ji): - if (ji+'/'+resource) != str(sess.jid): - continue - if sess.control: - sess.control.no_autonegotiation = False - if sess.enable_encryption: - sess.terminate_e2e() - conn.delete_session(jid, sess.thread_id) - - self.roster.chg_contact_status(contact1, array[1], status_message, - account) - # Notifications - if old_show < 2 and new_show > 1: - notify.notify('contact_connected', jid, account, status_message) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('ContactPresence', (account, - array)) - - elif old_show > 1 and new_show < 2: - notify.notify('contact_disconnected', jid, account, status_message) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('ContactAbsence', (account, array)) - # FIXME: stop non active file transfers - # Status change (not connected/disconnected or - # error (<1)) - elif new_show > 1: - notify.notify('status_change', jid, account, [new_show, - status_message]) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('ContactStatus', (account, array)) - else: - # FIXME: MSN transport (CMSN1.2.1 and PyMSN) don't - # follow the XEP, still the case in 2008. - # It's maybe a GC_NOTIFY (specialy for MSN gc) - self.handle_event_gc_notify(account, (jid, array[1], status_message, - array[3], None, None, None, None, None, [], None, None)) - - highest = gajim.contacts.get_contact_with_highest_priority(account, jid) - is_highest = (highest and highest.resource == resource) - - # disconnect the session from the ctrl if the highest resource has changed - if (was_highest and not is_highest) or (not was_highest and is_highest): - ctrl = self.msg_win_mgr.get_control(jid, account) - - if ctrl: - ctrl.no_autonegotiation = False - ctrl.set_session(None) - ctrl.contact = highest - - def handle_event_msgerror(self, account, array): - #'MSGERROR' (account, (jid, error_code, error_msg, msg, time[, session])) - full_jid_with_resource = array[0] - jids = full_jid_with_resource.split('/', 1) - jid = jids[0] - - if array[1] == '503': - # If we get server-not-found error, stop sending chatstates - for contact in gajim.contacts.get_contacts(account, jid): - contact.composing_xep = False - - session = None - if len(array) > 5: - session = array[5] - - gc_control = self.msg_win_mgr.get_gc_control(jid, account) - if not gc_control and \ - jid in self.minimized_controls[account]: - gc_control = self.minimized_controls[account][jid] - if gc_control and gc_control.type_id != message_control.TYPE_GC: - gc_control = None - if gc_control: - if len(jids) > 1: # it's a pm - nick = jids[1] - - if session: - ctrl = session.control - else: - ctrl = self.msg_win_mgr.get_control(full_jid_with_resource, account) - - if not ctrl: - tv = gc_control.list_treeview - model = tv.get_model() - iter_ = gc_control.get_contact_iter(nick) - if iter_: - show = model[iter_][3] - else: - show = 'offline' - gc_c = gajim.contacts.create_gc_contact(room_jid=jid, account=account, - name=nick, show=show) - ctrl = self.new_private_chat(gc_c, account, session) - - ctrl.print_conversation(_('Error %(code)s: %(msg)s') % { - 'code': array[1], 'msg': array[2]}, 'status') - return - - gc_control.print_conversation(_('Error %(code)s: %(msg)s') % { - 'code': array[1], 'msg': array[2]}, 'status') - if gc_control.parent_win and gc_control.parent_win.get_active_jid() == jid: - gc_control.set_subject(gc_control.subject) - return - - if gajim.jid_is_transport(jid): - jid = jid.replace('@', '') - msg = array[2] - if array[3]: - msg = _('error while sending %(message)s ( %(error)s )') % { - 'message': array[3], 'error': msg} - if session: - session.roster_message(jid, msg, array[4], msg_type='error') - - def handle_event_msgsent(self, account, array): - #('MSGSENT', account, (jid, msg, keyID)) - msg = array[1] - # do not play sound when standalone chatstate message (eg no msg) - if msg and gajim.config.get_per('soundevents', 'message_sent', 'enabled'): - helpers.play_sound('message_sent') - - def handle_event_msgnotsent(self, account, array): - #('MSGNOTSENT', account, (jid, ierror_msg, msg, time, session)) - msg = _('error while sending %(message)s ( %(error)s )') % { - 'message': array[2], 'error': array[1]} - if not array[4]: - # No session. This can happen when sending a message from gajim-remote - log.warn(msg) - return - array[4].roster_message(array[0], msg, array[3], account, - msg_type='error') - - def handle_event_subscribe(self, account, array): - #('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172 - if self.remote_ctrl: - self.remote_ctrl.raise_signal('Subscribe', (account, array)) - - jid = array[0] - text = array[1] - nick = array[2] - if helpers.allow_popup_window(account) or not self.systray_enabled: - dialogs.SubscriptionRequestWindow(jid, text, account, nick) - return - - self.add_event(account, jid, 'subscription_request', (text, nick)) - - if helpers.allow_showing_notification(account): - path = gtkgui_helpers.get_icon_path('gajim-subscription_request', 48) - event_type = _('Subscription request') - notify.popup(event_type, jid, account, 'subscription_request', path, - event_type, jid) - - def handle_event_subscribed(self, account, array): - #('SUBSCRIBED', account, (jid, resource)) - jid = array[0] - if jid in gajim.contacts.get_jid_list(account): - c = gajim.contacts.get_first_contact_from_jid(account, jid) - c.resource = array[1] - self.roster.remove_contact_from_groups(c.jid, account, - [_('Not in Roster'), _('Observers')], update=False) - else: - keyID = '' - attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() - if jid in attached_keys: - keyID = attached_keys[attached_keys.index(jid) + 1] - name = jid.split('@', 1)[0] - name = name.split('%', 1)[0] - contact1 = gajim.contacts.create_contact(jid=jid, account=account, - name=name, groups=[], show='online', status='online', - ask='to', resource=array[1], keyID=keyID) - gajim.contacts.add_contact(account, contact1) - self.roster.add_contact(jid, account) - dialogs.InformationDialog(_('Authorization accepted'), - _('The contact "%s" has authorized you to see his or her status.') - % jid) - if not gajim.config.get_per('accounts', account, 'dont_ack_subscription'): - gajim.connections[account].ack_subscribed(jid) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('Subscribed', (account, array)) - - def show_unsubscribed_dialog(self, account, contact): - def on_yes(is_checked, list_): - self.roster.on_req_usub(None, list_) - list_ = [(contact, account)] - dialogs.YesNoDialog( - _('Contact "%s" removed subscription from you') % contact.jid, - _('You will always see him or her as offline.\nDo you want to ' - 'remove him or her from your contact list?'), - on_response_yes=(on_yes, list_)) - # FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does - # not show deny - - def handle_event_unsubscribed(self, account, jid): - #('UNSUBSCRIBED', account, jid) - gajim.connections[account].ack_unsubscribed(jid) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('Unsubscribed', (account, jid)) - - contact = gajim.contacts.get_first_contact_from_jid(account, jid) - if not contact: - return - - if helpers.allow_popup_window(account) or not self.systray_enabled: - self.show_unsubscribed_dialog(account, contact) - return - - self.add_event(account, jid, 'unsubscribed', contact) - - if helpers.allow_showing_notification(account): - path = gtkgui_helpers.get_icon_path('gajim-unsubscribed', 48) - event_type = _('Unsubscribed') - notify.popup(event_type, jid, account, 'unsubscribed', path, - event_type, jid) - - def handle_event_agent_removed(self, account, agent): - # remove transport's contacts from treeview - jid_list = gajim.contacts.get_jid_list(account) - for jid in jid_list: - if jid.endswith('@' + agent): - c = gajim.contacts.get_first_contact_from_jid(account, jid) - gajim.log.debug( - 'Removing contact %s due to unregistered transport %s'\ - % (jid, agent)) - gajim.connections[account].unsubscribe(c.jid) - # Transport contacts can't have 2 resources - if c.jid in gajim.to_be_removed[account]: - # This way we'll really remove it - gajim.to_be_removed[account].remove(c.jid) - self.roster.remove_contact(c.jid, account, backend=True) - - def handle_event_register_agent_info(self, account, array): - # ('REGISTER_AGENT_INFO', account, (agent, infos, is_form)) - # info in a dataform if is_form is True - if array[2] or 'instructions' in array[1]: - config.ServiceRegistrationWindow(array[0], array[1], account, - array[2]) - else: - dialogs.ErrorDialog(_('Contact with "%s" cannot be established') \ - % array[0], _('Check your connection or try again later.')) - - def handle_event_agent_info_items(self, account, array): - #('AGENT_INFO_ITEMS', account, (agent, node, items)) - our_jid = gajim.get_jid_from_account(account) - if 'pep_services' in gajim.interface.instances[account] and \ - array[0] == our_jid: - gajim.interface.instances[account]['pep_services'].items_received( - array[2]) - - def handle_event_acc_ok(self, account, array): - #('ACC_OK', account, (config)) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('NewAccount', (account, array)) - - def handle_event_quit(self, p1, p2): - self.roster.quit_gtkgui_interface() - - def handle_event_myvcard(self, account, array): - nick = '' - if 'NICKNAME' in array and array['NICKNAME']: - gajim.nicks[account] = array['NICKNAME'] - elif 'FN' in array and array['FN']: - gajim.nicks[account] = array['FN'] - if 'profile' in self.instances[account]: - win = self.instances[account]['profile'] - win.set_values(array) - if account in self.show_vcard_when_connect: - self.show_vcard_when_connect.remove(account) - jid = array['jid'] - if jid in self.instances[account]['infos']: - self.instances[account]['infos'][jid].set_values(array) - - def handle_event_vcard(self, account, vcard): - # ('VCARD', account, data) - '''vcard holds the vcard data''' - jid = vcard['jid'] - resource = vcard.get('resource', '') - fjid = jid + '/' + str(resource) - - # vcard window - win = None - if jid in self.instances[account]['infos']: - win = self.instances[account]['infos'][jid] - elif resource and fjid in self.instances[account]['infos']: - win = self.instances[account]['infos'][fjid] - if win: - win.set_values(vcard) - - # show avatar in chat - ctrl = None - if resource and self.msg_win_mgr.has_window(fjid, account): - win = self.msg_win_mgr.get_window(fjid, account) - ctrl = win.get_control(fjid, account) - elif self.msg_win_mgr.has_window(jid, account): - win = self.msg_win_mgr.get_window(jid, account) - ctrl = win.get_control(jid, account) - - if ctrl and ctrl.type_id != message_control.TYPE_GC: - ctrl.show_avatar() - - # Show avatar in roster or gc_roster - gc_ctrl = self.msg_win_mgr.get_gc_control(jid, account) - if not gc_ctrl and \ - jid in self.minimized_controls[account]: - gc_ctrl = self.minimized_controls[account][jid] - if gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC: - gc_ctrl.draw_avatar(resource) - else: - self.roster.draw_avatar(jid, account) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('VcardInfo', (account, vcard)) - - def handle_event_last_status_time(self, account, array): - # ('LAST_STATUS_TIME', account, (jid, resource, seconds, status)) - tim = array[2] - if tim < 0: - # Ann error occured - return - win = None - if array[0] in self.instances[account]['infos']: - win = self.instances[account]['infos'][array[0]] - elif array[0] + '/' + array[1] in self.instances[account]['infos']: - win = self.instances[account]['infos'][array[0] + '/' + array[1]] - c = gajim.contacts.get_contact(account, array[0], array[1]) - if c: # c can be none if it's a gc contact - if array[3]: - c.status = array[3] - self.roster.draw_contact(c.jid, account) # draw offline status - last_time = time.localtime(time.time() - tim) - if c.show == 'offline': - c.last_status_time = last_time - else: - c.last_activity_time = last_time - if win: - win.set_last_status_time() - if self.roster.tooltip.id and self.roster.tooltip.win: - self.roster.tooltip.update_last_time(last_time) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('LastStatusTime', (account, array)) - - def handle_event_os_info(self, account, array): - #'OS_INFO' (account, (jid, resource, client_info, os_info)) - win = None - if array[0] in self.instances[account]['infos']: - win = self.instances[account]['infos'][array[0]] - elif array[0] + '/' + array[1] in self.instances[account]['infos']: - win = self.instances[account]['infos'][array[0] + '/' + array[1]] - if win: - win.set_os_info(array[1], array[2], array[3]) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('OsInfo', (account, array)) - - def handle_event_entity_time(self, account, array): - #'ENTITY_TIME' (account, (jid, resource, time_info)) - win = None - if array[0] in self.instances[account]['infos']: - win = self.instances[account]['infos'][array[0]] - elif array[0] + '/' + array[1] in self.instances[account]['infos']: - win = self.instances[account]['infos'][array[0] + '/' + array[1]] - if win: - win.set_entity_time(array[1], array[2]) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('EntityTime', (account, array)) - - def handle_event_gc_notify(self, account, array): - #'GC_NOTIFY' (account, (room_jid, show, status, nick, - # role, affiliation, jid, reason, actor, statusCode, newNick, avatar_sha)) - nick = array[3] - if not nick: - return - room_jid = array[0] - fjid = room_jid + '/' + nick - show = array[1] - status = array[2] - conn = gajim.connections[account] - - # Get the window and control for the updated status, this may be a - # PrivateChatControl - control = self.msg_win_mgr.get_gc_control(room_jid, account) - - if not control and \ - room_jid in self.minimized_controls[account]: - control = self.minimized_controls[account][room_jid] - - if not control or (control and control.type_id != message_control.TYPE_GC): - return - - control.chg_contact_status(nick, show, status, array[4], array[5], - array[6], array[7], array[8], array[9], array[10], array[11]) - - contact = gajim.contacts.\ - get_contact_with_highest_priority(account, room_jid) - if contact: - self.roster.draw_contact(room_jid, account) - - # print status in chat window and update status/GPG image - ctrl = self.msg_win_mgr.get_control(fjid, account) - if ctrl: - statusCode = array[9] - if '303' in statusCode: - new_nick = array[10] - ctrl.print_conversation(_('%(nick)s is now known as %(new_nick)s') \ - % {'nick': nick, 'new_nick': new_nick}, 'status') - gc_c = gajim.contacts.get_gc_contact(account, room_jid, new_nick) - c = gc_c.as_contact() - ctrl.gc_contact = gc_c - ctrl.contact = c - if ctrl.session: - # stop e2e - if ctrl.session.enable_encryption: - thread_id = ctrl.session.thread_id - ctrl.session.terminate_e2e() - conn.delete_session(fjid, thread_id) - ctrl.no_autonegotiation = False - ctrl.draw_banner() - old_jid = room_jid + '/' + nick - new_jid = room_jid + '/' + new_nick - self.msg_win_mgr.change_key(old_jid, new_jid, account) - else: - contact = ctrl.contact - contact.show = show - contact.status = status - gc_contact = ctrl.gc_contact - gc_contact.show = show - gc_contact.status = status - uf_show = helpers.get_uf_show(show) - ctrl.print_conversation(_('%(nick)s is now %(status)s') % { - 'nick': nick, 'status': uf_show}, 'status') - if status: - ctrl.print_conversation(' (', 'status', simple=True) - ctrl.print_conversation('%s' % (status), 'status', simple=True) - ctrl.print_conversation(')', 'status', simple=True) - ctrl.parent_win.redraw_tab(ctrl) - ctrl.update_ui() - if self.remote_ctrl: - self.remote_ctrl.raise_signal('GCPresence', (account, array)) - - def handle_event_gc_msg(self, account, array): - # ('GC_MSG', account, (jid, msg, time, has_timestamp, htmlmsg, - # [status_codes])) - jids = array[0].split('/', 1) - room_jid = jids[0] - - msg = array[1] - - gc_control = self.msg_win_mgr.get_gc_control(room_jid, account) - if not gc_control and \ - room_jid in self.minimized_controls[account]: - gc_control = self.minimized_controls[account][room_jid] - - if not gc_control: - return - xhtml = array[4] - - if gajim.config.get('ignore_incoming_xhtml'): - xhtml = None - if len(jids) == 1: - # message from server - nick = '' - else: - # message from someone - nick = jids[1] - - gc_control.on_message(nick, msg, array[2], array[3], xhtml, array[5]) - - if self.remote_ctrl: - highlight = gc_control.needs_visual_notification(msg) - array += (highlight,) - self.remote_ctrl.raise_signal('GCMessage', (account, array)) - - def handle_event_gc_subject(self, account, array): - #('GC_SUBJECT', account, (jid, subject, body, has_timestamp)) - jids = array[0].split('/', 1) - jid = jids[0] - - gc_control = self.msg_win_mgr.get_gc_control(jid, account) - - if not gc_control and \ - jid in self.minimized_controls[account]: - gc_control = self.minimized_controls[account][jid] - - contact = gajim.contacts.\ - get_contact_with_highest_priority(account, jid) - if contact: - contact.status = array[1] - self.roster.draw_contact(jid, account) - - if not gc_control: - return - gc_control.set_subject(array[1]) - # Standard way, the message comes from the occupant who set the subject - text = None - if len(jids) > 1: - text = _('%(jid)s has set the subject to %(subject)s') % { - 'jid': jids[1], 'subject': array[1]} - # Workaround for psi bug http://flyspray.psi-im.org/task/595 , to be - # deleted one day. We can receive a subject with a body that contains - # "X has set the subject to Y" ... - elif array[2]: - text = array[2] - if text is not None: - if array[3]: - gc_control.print_old_conversation(text) - else: - gc_control.print_conversation(text) - - def handle_event_gc_config(self, account, array): - #('GC_CONFIG', account, (jid, form)) config is a dict - room_jid = array[0].split('/')[0] - if room_jid in gajim.automatic_rooms[account]: - if 'continue_tag' in gajim.automatic_rooms[account][room_jid]: - # We're converting chat to muc. allow participants to invite - form = dataforms.ExtendForm(node = array[1]) - for f in form.iter_fields(): - if f.var == 'muc#roomconfig_allowinvites': - f.value = True - elif f.var == 'muc#roomconfig_publicroom': - f.value = False - elif f.var == 'muc#roomconfig_membersonly': - f.value = True - elif f.var == 'public_list': - f.value = False - gajim.connections[account].send_gc_config(room_jid, form) - else: - # use default configuration - gajim.connections[account].send_gc_config(room_jid, array[1]) - # invite contacts - # check if it is necessary to add - continue_tag = False - if 'continue_tag' in gajim.automatic_rooms[account][room_jid]: - continue_tag = True - if 'invities' in gajim.automatic_rooms[account][room_jid]: - for jid in gajim.automatic_rooms[account][room_jid]['invities']: - gajim.connections[account].send_invite(room_jid, jid, - continue_tag=continue_tag) - del gajim.automatic_rooms[account][room_jid] - elif room_jid not in self.instances[account]['gc_config']: - self.instances[account]['gc_config'][room_jid] = \ - config.GroupchatConfigWindow(account, room_jid, array[1]) - - def handle_event_gc_config_change(self, account, array): - #('GC_CONFIG_CHANGE', account, (jid, statusCode)) statuscode is a list - # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify - # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes-init - jid = array[0] - statusCode = array[1] - - gc_control = self.msg_win_mgr.get_gc_control(jid, account) - if not gc_control and \ - jid in self.minimized_controls[account]: - gc_control = self.minimized_controls[account][jid] - if not gc_control: - return - - changes = [] - if '100' in statusCode: - # Can be a presence (see chg_contact_status in groupchat_control.py) - changes.append(_('Any occupant is allowed to see your full JID')) - gc_control.is_anonymous = False - if '102' in statusCode: - changes.append(_('Room now shows unavailable member')) - if '103' in statusCode: - changes.append(_('room now does not show unavailable members')) - if '104' in statusCode: - changes.append( - _('A non-privacy-related room configuration change has occurred')) - if '170' in statusCode: - # Can be a presence (see chg_contact_status in groupchat_control.py) - changes.append(_('Room logging is now enabled')) - if '171' in statusCode: - changes.append(_('Room logging is now disabled')) - if '172' in statusCode: - changes.append(_('Room is now non-anonymous')) - gc_control.is_anonymous = False - if '173' in statusCode: - changes.append(_('Room is now semi-anonymous')) - gc_control.is_anonymous = True - if '174' in statusCode: - changes.append(_('Room is now fully-anonymous')) - gc_control.is_anonymous = True - - for change in changes: - gc_control.print_conversation(change) - - def handle_event_gc_affiliation(self, account, array): - #('GC_AFFILIATION', account, (room_jid, users_dict)) - room_jid = array[0] - if room_jid in self.instances[account]['gc_config']: - self.instances[account]['gc_config'][room_jid].\ - affiliation_list_received(array[1]) - - def handle_event_gc_password_required(self, account, array): - #('GC_PASSWORD_REQUIRED', account, (room_jid, nick)) - room_jid = array[0] - nick = array[1] - - def on_ok(text): - gajim.connections[account].join_gc(nick, room_jid, text) - gajim.gc_passwords[room_jid] = text - - def on_cancel(): - # get and destroy window - if room_jid in gajim.interface.minimized_controls[account]: - self.roster.on_disconnect(None, room_jid, account) - else: - win = self.msg_win_mgr.get_window(room_jid, account) - ctrl = self.msg_win_mgr.get_gc_control(room_jid, account) - win.remove_tab(ctrl, 3) - - dlg = dialogs.InputDialog(_('Password Required'), - _('A Password is required to join the room %s. Please type it.') % \ - room_jid, is_modal=False, ok_handler=on_ok, cancel_handler=on_cancel) - dlg.input_entry.set_visibility(False) - - def handle_event_gc_invitation(self, account, array): - #('GC_INVITATION', (room_jid, jid_from, reason, password, is_continued)) - jid = gajim.get_jid_without_resource(array[1]) - room_jid = array[0] - if helpers.allow_popup_window(account) or not self.systray_enabled: - dialogs.InvitationReceivedDialog(account, room_jid, jid, array[3], - array[2], is_continued=array[4]) - return - - self.add_event(account, jid, 'gc-invitation', (room_jid, array[2], - array[3], array[4])) - - if helpers.allow_showing_notification(account): - path = gtkgui_helpers.get_icon_path('gajim-gc_invitation', 48) - event_type = _('Groupchat Invitation') - notify.popup(event_type, jid, account, 'gc-invitation', path, - event_type, room_jid) - - def forget_gpg_passphrase(self, keyid): - if keyid in self.gpg_passphrase: - del self.gpg_passphrase[keyid] - return False - - def handle_event_bad_passphrase(self, account, array): - #('BAD_PASSPHRASE', account, ()) - use_gpg_agent = gajim.config.get('use_gpg_agent') - sectext = '' - if use_gpg_agent: - sectext = _('You configured Gajim to use GPG agent, but there is no ' - 'GPG agent running or it returned a wrong passphrase.\n') - sectext += _('You are currently connected without your OpenPGP key.') - dialogs.WarningDialog(_('Your passphrase is incorrect'), sectext) - else: - path = gtkgui_helpers.get_icon_path('gajim-warning', 48) - notify.popup('warning', account, account, 'warning', path, - _('OpenGPG Passphrase Incorrect'), - _('You are currently connected without your OpenPGP key.')) - keyID = gajim.config.get_per('accounts', account, 'keyid') - self.forget_gpg_passphrase(keyID) - - def handle_event_gpg_password_required(self, account, array): - #('GPG_PASSWORD_REQUIRED', account, (callback,)) - callback = array[0] - keyid = gajim.config.get_per('accounts', account, 'keyid') - if keyid in self.gpg_passphrase: - request = self.gpg_passphrase[keyid] - else: - request = PassphraseRequest(keyid) - self.gpg_passphrase[keyid] = request - request.add_callback(account, callback) - - def handle_event_gpg_always_trust(self, account, callback): - #('GPG_ALWAYS_TRUST', account, callback) - def on_yes(checked): - if checked: - gajim.connections[account].gpg.always_trust = True - callback(True) - - def on_no(): - callback(False) - - dialogs.YesNoDialog(_('GPG key not trusted'), _('The GPG key used to ' - 'encrypt this chat is not trusted. Do you really want to encrypt this ' - 'message?'), checktext=_('Do _not ask me again'), - on_response_yes=on_yes, on_response_no=on_no) - - def handle_event_password_required(self, account, array): - #('PASSWORD_REQUIRED', account, None) - if account in self.pass_dialog: - return - text = _('Enter your password for account %s') % account - if passwords.USER_HAS_GNOMEKEYRING and \ - not passwords.USER_USES_GNOMEKEYRING: - text += '\n' + _('Gnome Keyring is installed but not \ - correctly started (environment variable probably not \ - correctly set)') - - def on_ok(passphrase, save): - if save: - gajim.config.set_per('accounts', account, 'savepass', True) - passwords.save_password(account, passphrase) - gajim.connections[account].set_password(passphrase) - del self.pass_dialog[account] - - def on_cancel(): - self.roster.set_state(account, 'offline') - self.roster.update_status_combobox() - del self.pass_dialog[account] - - self.pass_dialog[account] = dialogs.PassphraseDialog( - _('Password Required'), text, _('Save password'), ok_handler=on_ok, - cancel_handler=on_cancel) - - def handle_event_roster_info(self, account, array): - #('ROSTER_INFO', account, (jid, name, sub, ask, groups)) - jid = array[0] - name = array[1] - sub = array[2] - ask = array[3] - groups = array[4] - contacts = gajim.contacts.get_contacts(account, jid) - if (not sub or sub == 'none') and (not ask or ask == 'none') and \ - not name and not groups: - # contact removed us. - if contacts: - self.roster.remove_contact(jid, account, backend=True) - return - elif not contacts: - if sub == 'remove': - return - # Add new contact to roster - contact = gajim.contacts.create_contact(jid=jid, account=account, - name=name, groups=groups, show='offline', sub=sub, ask=ask) - gajim.contacts.add_contact(account, contact) - self.roster.add_contact(jid, account) - else: - # it is an existing contact that might has changed - re_place = False - # If contact has changed (sub, ask or group) update roster - # Mind about observer status changes: - # According to xep 0162, a contact is not an observer anymore when - # we asked for auth, so also remove him if ask changed - old_groups = contacts[0].groups - if contacts[0].sub != sub or contacts[0].ask != ask\ - or old_groups != groups: - re_place = True - # c.get_shown_groups() has changed. Reflect that in roster_winodow - self.roster.remove_contact(jid, account, force=True) - for contact in contacts: - contact.name = name or '' - contact.sub = sub - contact.ask = ask - contact.groups = groups or [] - if re_place: - self.roster.add_contact(jid, account) - # Refilter and update old groups - for group in old_groups: - self.roster.draw_group(group, account) - else: - self.roster.draw_contact(jid, account) - - if self.remote_ctrl: - self.remote_ctrl.raise_signal('RosterInfo', (account, array)) - - def handle_event_bookmarks(self, account, bms): - # ('BOOKMARKS', account, [{name,jid,autojoin,password,nick}, {}]) - # We received a bookmark item from the server (JEP48) - # Auto join GC windows if neccessary - - self.roster.set_actions_menu_needs_rebuild() - invisible_show = gajim.SHOW_LIST.index('invisible') - # do not autojoin if we are invisible - if gajim.connections[account].connected == invisible_show: - return - - self.auto_join_bookmarks(account) - - def handle_event_file_send_error(self, account, array): - jid = array[0] - file_props = array[1] - ft = self.instances['file_transfers'] - ft.set_status(file_props['type'], file_props['sid'], 'stop') - - if helpers.allow_popup_window(account): - ft.show_send_error(file_props) - return - - self.add_event(account, jid, 'file-send-error', file_props) - - if helpers.allow_showing_notification(account): - path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48) - event_type = _('File Transfer Error') - notify.popup(event_type, jid, account, 'file-send-error', path, - event_type, file_props['name']) - - def handle_event_gmail_notify(self, account, array): - jid = array[0] - gmail_new_messages = int(array[1]) - gmail_messages_list = array[2] - if gajim.config.get('notify_on_new_gmail_email'): - path = gtkgui_helpers.get_icon_path('gajim-new_email_recv', 48) - title = _('New mail on %(gmail_mail_address)s') % \ - {'gmail_mail_address': jid} - text = i18n.ngettext('You have %d new mail conversation', - 'You have %d new mail conversations', gmail_new_messages, - gmail_new_messages, gmail_new_messages) - - if gajim.config.get('notify_on_new_gmail_email_extra'): - cnt = 0 - for gmessage in gmail_messages_list: - #FIXME: emulate Gtalk client popups. find out what they parse and - # how they decide what to show each message has a 'From', - # 'Subject' and 'Snippet' field - if cnt >=5: - break - senders = ',\n '.join(reversed(gmessage['From'])) - text += _('\n\nFrom: %(from_address)s\nSubject: %(subject)s\n%(snippet)s') % \ - {'from_address': senders, 'subject': gmessage['Subject'], - 'snippet': gmessage['Snippet']} - cnt += 1 - - if gajim.config.get_per('soundevents', 'gmail_received', 'enabled'): - helpers.play_sound('gmail_received') - notify.popup(_('New E-mail'), jid, account, 'gmail', - path_to_image=path, title=title, - text=text) - - if self.remote_ctrl: - self.remote_ctrl.raise_signal('NewGmail', (account, array)) - - def handle_event_file_request_error(self, account, array): - # ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg)) - jid, file_props, errmsg = array - jid = gajim.get_jid_without_resource(jid) - ft = self.instances['file_transfers'] - ft.set_status(file_props['type'], file_props['sid'], 'stop') - errno = file_props['error'] - - if helpers.allow_popup_window(account): - if errno in (-4, -5): - ft.show_stopped(jid, file_props, errmsg) - else: - ft.show_request_error(file_props) - return - - if errno in (-4, -5): - msg_type = 'file-error' - else: - msg_type = 'file-request-error' - - self.add_event(account, jid, msg_type, file_props) - - if helpers.allow_showing_notification(account): - # check if we should be notified - path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48) - event_type = _('File Transfer Error') - notify.popup(event_type, jid, account, msg_type, path, - title = event_type, text = file_props['name']) - - def handle_event_file_request(self, account, array): - jid = array[0] - jid = gajim.get_jid_without_resource(jid) - if jid not in gajim.contacts.get_jid_list(account): - keyID = '' - attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() - if jid in attached_keys: - keyID = attached_keys[attached_keys.index(jid) + 1] - contact = gajim.contacts.create_not_in_roster_contact(jid=jid, - account=account, keyID=keyID) - gajim.contacts.add_contact(account, contact) - self.roster.add_contact(contact.jid, account) - file_props = array[1] - contact = gajim.contacts.get_first_contact_from_jid(account, jid) - - if helpers.allow_popup_window(account): - self.instances['file_transfers'].show_file_request(account, contact, - file_props) - return - - self.add_event(account, jid, 'file-request', file_props) - - if helpers.allow_showing_notification(account): - path = gtkgui_helpers.get_icon_path('gajim-ft_request', 48) - txt = _('%s wants to send you a file.') % gajim.get_name_from_jid( - account, jid) - event_type = _('File Transfer Request') - notify.popup(event_type, jid, account, 'file-request', - path_to_image = path, title = event_type, text = txt) - - def handle_event_file_error(self, title, message): - dialogs.ErrorDialog(title, message) - - def handle_event_file_progress(self, account, file_props): - if time.time() - self.last_ftwindow_update > 0.5: - # update ft window every 500ms - self.last_ftwindow_update = time.time() - self.instances['file_transfers'].set_progress(file_props['type'], - file_props['sid'], file_props['received-len']) - - def handle_event_file_rcv_completed(self, account, file_props): - ft = self.instances['file_transfers'] - if file_props['error'] == 0: - ft.set_progress(file_props['type'], file_props['sid'], - file_props['received-len']) - else: - ft.set_status(file_props['type'], file_props['sid'], 'stop') - if 'stalled' in file_props and file_props['stalled'] or \ - 'paused' in file_props and file_props['paused']: - return - if file_props['type'] == 'r': # we receive a file - jid = unicode(file_props['sender']) - else: # we send a file - jid = unicode(file_props['receiver']) - - if helpers.allow_popup_window(account): - if file_props['error'] == 0: - if gajim.config.get('notify_on_file_complete'): - ft.show_completed(jid, file_props) - elif file_props['error'] == -1: - ft.show_stopped(jid, file_props, - error_msg=_('Remote contact stopped transfer')) - elif file_props['error'] == -6: - ft.show_stopped(jid, file_props, error_msg=_('Error opening file')) - return - - msg_type = '' - event_type = '' - if file_props['error'] == 0 and gajim.config.get( - 'notify_on_file_complete'): - msg_type = 'file-completed' - event_type = _('File Transfer Completed') - elif file_props['error'] in (-1, -6): - msg_type = 'file-stopped' - event_type = _('File Transfer Stopped') - - if event_type == '': - # FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs) - # this should never happen but it does. see process_result() in socks5.py - # who calls this func (sth is really wrong unless this func is also registered - # as progress_cb - return - - if msg_type: - self.add_event(account, jid, msg_type, file_props) - - if file_props is not None: - if file_props['type'] == 'r': - # get the name of the sender, as it is in the roster - sender = unicode(file_props['sender']).split('/')[0] - name = gajim.contacts.get_first_contact_from_jid(account, - sender).get_shown_name() - filename = os.path.basename(file_props['file-name']) - if event_type == _('File Transfer Completed'): - txt = _('You successfully received %(filename)s from %(name)s.')\ - % {'filename': filename, 'name': name} - img_name = 'gajim-ft_done' - else: # ft stopped - txt = _('File transfer of %(filename)s from %(name)s stopped.')\ - % {'filename': filename, 'name': name} - img_name = 'gajim-ft_stopped' - else: - receiver = file_props['receiver'] - if hasattr(receiver, 'jid'): - receiver = receiver.jid - receiver = receiver.split('/')[0] - # get the name of the contact, as it is in the roster - name = gajim.contacts.get_first_contact_from_jid(account, - receiver).get_shown_name() - filename = os.path.basename(file_props['file-name']) - if event_type == _('File Transfer Completed'): - txt = _('You successfully sent %(filename)s to %(name)s.')\ - % {'filename': filename, 'name': name} - img_name = 'gajim-ft_done' - else: # ft stopped - txt = _('File transfer of %(filename)s to %(name)s stopped.')\ - % {'filename': filename, 'name': name} - img_name = 'gajim-ft_stopped' - path = gtkgui_helpers.get_icon_path(img_name, 48) - else: - txt = '' - path = '' - - if gajim.config.get('notify_on_file_complete') and \ - (gajim.config.get('autopopupaway') or \ - gajim.connections[account].connected in (2, 3)): - # we want to be notified and we are online/chat or we don't mind - # bugged when away/na/busy - notify.popup(event_type, jid, account, msg_type, path_to_image=path, - title=event_type, text=txt) - - def handle_event_stanza_arrived(self, account, stanza): - if account not in self.instances: - return - if 'xml_console' in self.instances[account]: - self.instances[account]['xml_console'].print_stanza(stanza, 'incoming') - - def handle_event_stanza_sent(self, account, stanza): - if account not in self.instances: - return - if 'xml_console' in self.instances[account]: - self.instances[account]['xml_console'].print_stanza(stanza, 'outgoing') - - def handle_event_vcard_published(self, account, array): - if 'profile' in self.instances[account]: - win = self.instances[account]['profile'] - win.vcard_published() - for gc_control in self.msg_win_mgr.get_controls(message_control.TYPE_GC) + \ - self.minimized_controls[account].values(): - if gc_control.account == account: - show = gajim.SHOW_LIST[gajim.connections[account].connected] - status = gajim.connections[account].status - gajim.connections[account].send_gc_status(gc_control.nick, - gc_control.room_jid, show, status) - - def handle_event_vcard_not_published(self, account, array): - if 'profile' in self.instances[account]: - win = self.instances[account]['profile'] - win.vcard_not_published() - - def ask_offline_status(self, account): - for contact in gajim.contacts.iter_contacts(account): - gajim.connections[account].request_last_status_time(contact.jid, - contact.resource) - - def handle_event_signed_in(self, account, empty): - """ - SIGNED_IN event is emitted when we sign in, so handle it - """ - # ('SIGNED_IN', account, ()) - # block signed in notifications for 30 seconds - gajim.block_signed_in_notifications[account] = True - self.roster.set_actions_menu_needs_rebuild() - self.roster.draw_account(account) - state = self.sleeper.getState() - connected = gajim.connections[account].connected - if gajim.config.get('ask_offline_status_on_connection'): - # Ask offline status in 1 minute so w'are sure we got all online - # presences - gobject.timeout_add_seconds(60, self.ask_offline_status, account) - if state != common.sleepy.STATE_UNKNOWN and connected in (2, 3): - # we go online or free for chat, so we activate auto status - gajim.sleeper_state[account] = 'online' - elif not ((state == common.sleepy.STATE_AWAY and connected == 4) or \ - (state == common.sleepy.STATE_XA and connected == 5)): - # If we are autoaway/xa and come back after a disconnection, do nothing - # Else disable autoaway - gajim.sleeper_state[account] = 'off' - invisible_show = gajim.SHOW_LIST.index('invisible') - # We cannot join rooms if we are invisible - if gajim.connections[account].connected == invisible_show: - return - # join already open groupchats - for gc_control in self.msg_win_mgr.get_controls(message_control.TYPE_GC) \ - + self.minimized_controls[account].values(): - if account != gc_control.account: - continue - room_jid = gc_control.room_jid - if room_jid in gajim.gc_connected[account] and \ - gajim.gc_connected[account][room_jid]: - continue - nick = gc_control.nick - password = gajim.gc_passwords.get(room_jid, '') - gajim.connections[account].join_gc(nick, room_jid, password) - # send currently played music - if gajim.connections[account].pep_supported and dbus_support.supported \ - and gajim.config.get_per('accounts', account, 'publish_tune'): - self.enable_music_listener() - # enable location listener - if gajim.connections[account].pep_supported and dbus_support.supported \ - and gajim.config.get_per('accounts', account, 'publish_location'): - location_listener.enable() - - def handle_event_metacontacts(self, account, tags_list): - gajim.contacts.define_metacontacts(account, tags_list) - self.roster.redraw_metacontacts(account) - - def handle_atom_entry(self, account, data): - atom_entry, = data - AtomWindow.newAtomEntry(atom_entry) - - def handle_event_failed_decrypt(self, account, data): - jid, tim, session = data - - details = _('Unable to decrypt message from ' - '%s\nIt may have been tampered with.') % jid - - ctrl = session.control - if ctrl: - ctrl.print_conversation_line(details, 'status', '', tim) - else: - dialogs.WarningDialog(_('Unable to decrypt message'), - details) - - # terminate the session - session.terminate_e2e() - session.conn.delete_session(jid, session.thread_id) - - # restart the session - if ctrl: - ctrl.begin_e2e_negotiation() - - def handle_event_privacy_lists_received(self, account, data): - # ('PRIVACY_LISTS_RECEIVED', account, list) - if account not in self.instances: - return - if 'privacy_lists' in self.instances[account]: - self.instances[account]['privacy_lists'].privacy_lists_received(data) - - def handle_event_privacy_list_received(self, account, data): - # ('PRIVACY_LIST_RECEIVED', account, (name, rules)) - if account not in self.instances: - return - name = data[0] - rules = data[1] - if 'privacy_list_%s' % name in self.instances[account]: - self.instances[account]['privacy_list_%s' % name].\ - privacy_list_received(rules) - if name == 'block': - gajim.connections[account].blocked_contacts = [] - gajim.connections[account].blocked_groups = [] - gajim.connections[account].blocked_list = [] - gajim.connections[account].blocked_all = False - for rule in rules: - if not 'type' in rule: - gajim.connections[account].blocked_all = True - elif rule['type'] == 'jid' and rule['action'] == 'deny': - gajim.connections[account].blocked_contacts.append(rule['value']) - elif rule['type'] == 'group' and rule['action'] == 'deny': - gajim.connections[account].blocked_groups.append(rule['value']) - gajim.connections[account].blocked_list.append(rule) - #elif rule['type'] == "group" and action == "deny": - # text_item = _('%s group "%s"') % _(rule['action']), rule['value'] - # self.store.append([text_item]) - # self.global_rules.append(rule) - #else: - # self.global_rules_to_append.append(rule) - if 'blocked_contacts' in self.instances[account]: - self.instances[account]['blocked_contacts'].\ - privacy_list_received(rules) - - def handle_event_privacy_lists_active_default(self, account, data): - if not data: - return - # Send to all privacy_list_* windows as we can't know which one asked - for win in self.instances[account]: - if win.startswith('privacy_list_'): - self.instances[account][win].check_active_default(data) - - def handle_event_privacy_list_removed(self, account, name): - # ('PRIVACY_LISTS_REMOVED', account, name) - if account not in self.instances: - return - if 'privacy_lists' in self.instances[account]: - self.instances[account]['privacy_lists'].privacy_list_removed(name) - - def handle_event_zc_name_conflict(self, account, data): - def on_ok(new_name): - gajim.config.set_per('accounts', account, 'name', new_name) - status = gajim.connections[account].status - gajim.connections[account].username = new_name - gajim.connections[account].change_status(status, '') - def on_cancel(): - gajim.connections[account].change_status('offline','') - - dlg = dialogs.InputDialog(_('Username Conflict'), - _('Please type a new username for your local account'), input_str=data, - is_modal=True, ok_handler=on_ok, cancel_handler=on_cancel) - - def handle_event_ping_sent(self, account, contact): - if contact.jid == contact.get_full_jid(): - # If contact is a groupchat user - jids = [contact.jid] - else: - jids = [contact.jid, contact.get_full_jid()] - for jid in jids: - ctrl = self.msg_win_mgr.get_control(jid, account) - if ctrl: - ctrl.print_conversation(_('Ping?'), 'status') - - def handle_event_ping_reply(self, account, data): - contact = data[0] - seconds = data[1] - if contact.jid == contact.get_full_jid(): - # If contact is a groupchat user - jids = [contact.jid] - else: - jids = [contact.jid, contact.get_full_jid()] - for jid in jids: - ctrl = self.msg_win_mgr.get_control(jid, account) - if ctrl: - ctrl.print_conversation(_('Pong! (%s s.)') % seconds, 'status') - - def handle_event_ping_error(self, account, contact): - if contact.jid == contact.get_full_jid(): - # If contact is a groupchat user - jids = [contact.jid] - else: - jids = [contact.jid, contact.get_full_jid()] - for jid in jids: - ctrl = self.msg_win_mgr.get_control(jid, account) - if ctrl: - ctrl.print_conversation(_('Error.'), 'status') - - def handle_event_search_form(self, account, data): - # ('SEARCH_FORM', account, (jid, dataform, is_dataform)) - if data[0] not in self.instances[account]['search']: - return - self.instances[account]['search'][data[0]].on_form_arrived(data[1], - data[2]) - - def handle_event_search_result(self, account, data): - # ('SEARCH_RESULT', account, (jid, dataform, is_dataform)) - if data[0] not in self.instances[account]['search']: - return - self.instances[account]['search'][data[0]].on_result_arrived(data[1], - data[2]) - - def handle_event_resource_conflict(self, account, data): - # ('RESOURCE_CONFLICT', account, ()) - # First we go offline, but we don't overwrite status message - self.roster.send_status(account, 'offline', - gajim.connections[account].status) - def on_ok(new_resource): - gajim.config.set_per('accounts', account, 'resource', new_resource) - self.roster.send_status(account, gajim.connections[account].old_show, - gajim.connections[account].status) - proposed_resource = gajim.connections[account].server_resource - proposed_resource += gajim.config.get('gc_proposed_nick_char') - dlg = dialogs.ResourceConflictDialog(_('Resource Conflict'), - _('You are already connected to this account with the same resource. ' - 'Please type a new one'), resource=proposed_resource, ok_handler=on_ok) - - def handle_event_jingle_incoming(self, account, data): - # ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type, - # data...)) - # TODO: conditional blocking if peer is not in roster - - # unpack data - peerjid, sid, contents = data - content_types = set(c[0] for c in contents) - - # check type of jingle session - if 'audio' in content_types or 'video' in content_types: - # a voip session... - # we now handle only voip, so the only thing we will do here is - # not to return from function - pass - else: - # unknown session type... it should be declined in common/jingle.py - return - - jid = gajim.get_jid_without_resource(peerjid) - resource = gajim.get_resource_from_jid(peerjid) - ctrl = self.msg_win_mgr.get_control(peerjid, account) - if not ctrl: - ctrl = self.msg_win_mgr.get_control(jid, account) - if ctrl: - if 'audio' in content_types: - ctrl.set_audio_state('connection_received', sid) - if 'video' in content_types: - ctrl.set_video_state('connection_received', sid) - - dlg = dialogs.VoIPCallReceivedDialog.get_dialog(peerjid, sid) - if dlg: - dlg.add_contents(content_types) - return - - if helpers.allow_popup_window(account): - dialogs.VoIPCallReceivedDialog(account, peerjid, sid, content_types) - return - - self.add_event(account, peerjid, 'jingle-incoming', (peerjid, sid, - content_types)) - - if helpers.allow_showing_notification(account): - # TODO: we should use another pixmap ;-) - txt = _('%s wants to start a voice chat.') % gajim.get_name_from_jid( - account, peerjid) - path = gtkgui_helpers.get_icon_path('gajim-mic_active', 48) - event_type = _('Voice Chat Request') - notify.popup(event_type, peerjid, account, 'jingle-incoming', - path_to_image = path, title = event_type, text = txt) - - def handle_event_jingle_connected(self, account, data): - # ('JINGLE_CONNECTED', account, (peerjid, sid, media)) - peerjid, sid, media = data - if media in ('audio', 'video'): - jid = gajim.get_jid_without_resource(peerjid) - resource = gajim.get_resource_from_jid(peerjid) - ctrl = self.msg_win_mgr.get_control(peerjid, account) - if not ctrl: - ctrl = self.msg_win_mgr.get_control(jid, account) - if ctrl: - if media == 'audio': - ctrl.set_audio_state('connected', sid) - else: - ctrl.set_video_state('connected', sid) - - def handle_event_jingle_disconnected(self, account, data): - # ('JINGLE_DISCONNECTED', account, (peerjid, sid, reason)) - peerjid, sid, media, reason = data - jid = gajim.get_jid_without_resource(peerjid) - resource = gajim.get_resource_from_jid(peerjid) - ctrl = self.msg_win_mgr.get_control(peerjid, account) - if not ctrl: - ctrl = self.msg_win_mgr.get_control(jid, account) - if ctrl: - if media in ('audio', None): - ctrl.set_audio_state('stop', sid=sid, reason=reason) - if media in ('video', None): - ctrl.set_video_state('stop', sid=sid, reason=reason) - dialog = dialogs.VoIPCallReceivedDialog.get_dialog(peerjid, sid) - if dialog: - dialog.dialog.destroy() - - def handle_event_jingle_error(self, account, data): - # ('JINGLE_ERROR', account, (peerjid, sid, reason)) - peerjid, sid, reason = data - jid = gajim.get_jid_without_resource(peerjid) - resource = gajim.get_resource_from_jid(peerjid) - ctrl = self.msg_win_mgr.get_control(peerjid, account) - if not ctrl: - ctrl = self.msg_win_mgr.get_control(jid, account) - if ctrl: - ctrl.set_audio_state('error', reason=reason) - - def handle_event_pep_config(self, account, data): - # ('PEP_CONFIG', account, (node, form)) - if 'pep_services' in self.instances[account]: - self.instances[account]['pep_services'].config(data[0], data[1]) - - def handle_event_roster_item_exchange(self, account, data): - # data = (action in [add, delete, modify], exchange_list, jid_from) - dialogs.RosterItemExchangeWindow(account, data[0], data[1], data[2]) - - def handle_event_unique_room_id_supported(self, account, data): - """ - Receive confirmation that unique_room_id are supported - """ - # ('UNIQUE_ROOM_ID_SUPPORTED', server, instance, room_id) - instance = data[1] - instance.unique_room_id_supported(data[0], data[2]) - - def handle_event_unique_room_id_unsupported(self, account, data): - # ('UNIQUE_ROOM_ID_UNSUPPORTED', server, instance) - instance = data[1] - instance.unique_room_id_error(data[0]) - - def handle_event_ssl_error(self, account, data): - # ('SSL_ERROR', account, (text, errnum, cert, sha1_fingerprint)) - server = gajim.config.get_per('accounts', account, 'hostname') - - def on_ok(is_checked): - del self.instances[account]['online_dialog']['ssl_error'] - if is_checked[0]: - # Check if cert is already in file - certs = '' - if os.path.isfile(gajim.MY_CACERTS): - f = open(gajim.MY_CACERTS) - certs = f.read() - f.close() - if data[2] in certs: - dialogs.ErrorDialog(_('Certificate Already in File'), - _('This certificate is already in file %s, so it\'s not added again.') % gajim.MY_CACERTS) - else: - f = open(gajim.MY_CACERTS, 'a') - f.write(server + '\n') - f.write(data[2] + '\n\n') - f.close() - gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1', - data[3]) - if is_checked[1]: - ignore_ssl_errors = gajim.config.get_per('accounts', account, - 'ignore_ssl_errors').split() - ignore_ssl_errors.append(str(data[1])) - gajim.config.set_per('accounts', account, 'ignore_ssl_errors', - ' '.join(ignore_ssl_errors)) - gajim.connections[account].ssl_certificate_accepted() - - def on_cancel(): - del self.instances[account]['online_dialog']['ssl_error'] - gajim.connections[account].disconnect(on_purpose=True) - self.handle_event_status(account, 'offline') - - pritext = _('Error verifying SSL certificate') - 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]} - if data[1] in (18, 27): - checktext1 = _('Add this certificate to the list of trusted certificates.\nSHA1 fingerprint of the certificate:\n%s') % data[3] - else: - checktext1 = '' - checktext2 = _('Ignore this error for this certificate.') - if 'ssl_error' in self.instances[account]['online_dialog']: - self.instances[account]['online_dialog']['ssl_error'].destroy() - self.instances[account]['online_dialog']['ssl_error'] = \ - dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, checktext1, - checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel) - - def handle_event_fingerprint_error(self, account, data): - # ('FINGERPRINT_ERROR', account, (new_fingerprint,)) - def on_yes(is_checked): - del self.instances[account]['online_dialog']['fingerprint_error'] - gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1', - data[0]) - # Reset the ignored ssl errors - gajim.config.set_per('accounts', account, 'ignore_ssl_errors', '') - gajim.connections[account].ssl_certificate_accepted() - def on_no(): - del self.instances[account]['online_dialog']['fingerprint_error'] - gajim.connections[account].disconnect(on_purpose=True) - self.handle_event_status(account, 'offline') - pritext = _('SSL certificate error') - sectext = _('It seems the SSL certificate of account %(account)s has ' - 'changed or your connection is being hacked.\nOld fingerprint: %(old)s' - '\nNew fingerprint: %(new)s\n\nDo you still want to connect and update' - ' the fingerprint of the certificate?') % {'account': account, - 'old': gajim.config.get_per('accounts', account, - 'ssl_fingerprint_sha1'), 'new': data[0]} - if 'fingerprint_error' in self.instances[account]['online_dialog']: - self.instances[account]['online_dialog']['fingerprint_error'].destroy() - self.instances[account]['online_dialog']['fingerprint_error'] = \ - dialogs.YesNoDialog(pritext, sectext, on_response_yes=on_yes, - on_response_no=on_no) - - def handle_event_plain_connection(self, account, data): - # ('PLAIN_CONNECTION', account, (connection)) - server = gajim.config.get_per('accounts', account, 'hostname') - def on_ok(is_checked): - if not is_checked[0]: - on_cancel() - return - # On cancel call del self.instances, so don't call it another time - # before - del self.instances[account]['online_dialog']['plain_connection'] - if is_checked[1]: - gajim.config.set_per('accounts', account, - 'warn_when_plaintext_connection', False) - gajim.connections[account].connection_accepted(data[0], 'plain') - def on_cancel(): - del self.instances[account]['online_dialog']['plain_connection'] - gajim.connections[account].disconnect(on_purpose=True) - self.handle_event_status(account, 'offline') - pritext = _('Insecure connection') - sectext = _('You are about to send your password on an unencrypted ' - 'connection. Are you sure you want to do that?') - checktext1 = _('Yes, I really want to connect insecurely') - checktext2 = _('Do _not ask me again') - if 'plain_connection' in self.instances[account]['online_dialog']: - self.instances[account]['online_dialog']['plain_connection'].destroy() - self.instances[account]['online_dialog']['plain_connection'] = \ - dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, - checktext1, checktext2, on_response_ok=on_ok, - on_response_cancel=on_cancel, is_modal=False) - - def handle_event_insecure_ssl_connection(self, account, data): - # ('INSECURE_SSL_CONNECTION', account, (connection, connection_type)) - server = gajim.config.get_per('accounts', account, 'hostname') - def on_ok(is_checked): - if not is_checked[0]: - on_cancel() - return - del self.instances[account]['online_dialog']['insecure_ssl'] - if is_checked[1]: - gajim.config.set_per('accounts', account, - 'warn_when_insecure_ssl_connection', False) - if gajim.connections[account].connected == 0: - # We have been disconnecting (too long time since window is opened) - # re-connect with auto-accept - gajim.connections[account].connection_auto_accepted = True - show, msg = gajim.connections[account].continue_connect_info[:2] - self.roster.send_status(account, show, msg) - return - gajim.connections[account].connection_accepted(data[0], data[1]) - def on_cancel(): - del self.instances[account]['online_dialog']['insecure_ssl'] - gajim.connections[account].disconnect(on_purpose=True) - self.handle_event_status(account, 'offline') - pritext = _('Insecure connection') - sectext = _('You are about to send your password on an insecure ' - 'connection. You should install PyOpenSSL to prevent that. Are you sure you want to do that?') - checktext1 = _('Yes, I really want to connect insecurely') - checktext2 = _('Do _not ask me again') - if 'insecure_ssl' in self.instances[account]['online_dialog']: - self.instances[account]['online_dialog']['insecure_ssl'].destroy() - self.instances[account]['online_dialog']['insecure_ssl'] = \ - dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, - checktext1, checktext2, on_response_ok=on_ok, - on_response_cancel=on_cancel, is_modal=False) - - def handle_event_pubsub_node_removed(self, account, data): - # ('PUBSUB_NODE_REMOVED', account, (jid, node)) - if 'pep_services' in self.instances[account]: - if data[0] == gajim.get_jid_from_account(account): - self.instances[account]['pep_services'].node_removed(data[1]) - - def handle_event_pubsub_node_not_removed(self, account, data): - # ('PUBSUB_NODE_NOT_REMOVED', account, (jid, node, msg)) - if data[0] == gajim.get_jid_from_account(account): - dialogs.WarningDialog(_('PEP node was not removed'), - _('PEP node %(node)s was not removed: %(message)s') % { - 'node': data[1], 'message': data[2]}) - - def handle_event_pep_received(self, account, data): - # ('PEP_RECEIVED', account, (jid, pep_type)) - jid = data[0] - pep_type = data[1] - ctrl = common.gajim.interface.msg_win_mgr.get_control(jid, account) - - if jid == common.gajim.get_jid_from_account(account): - self.roster.draw_account(account) - - if pep_type == 'nickname': - self.roster.draw_contact(jid, account) - if ctrl: - ctrl.update_ui() - win = ctrl.parent_win - win.redraw_tab(ctrl) - win.show_title() - else: - self.roster.draw_pep(jid, account, pep_type) - if ctrl: - ctrl.update_pep(pep_type) - - def handle_event_caps_received(self, account, data): - # ('CAPS_RECEIVED', account, (full_jid)) - full_jid = data[0] - pm_ctrl = gajim.interface.msg_win_mgr.get_control(full_jid, account) - if pm_ctrl and hasattr(pm_ctrl, "update_contact"): - pm_ctrl.update_contact() - - def create_core_handlers_list(self): - self.handlers = { - 'ROSTER': [self.handle_event_roster], - 'WARNING': [self.handle_event_warning], - 'ERROR': [self.handle_event_error], - 'INFORMATION': [self.handle_event_information], - 'ERROR_ANSWER': [self.handle_event_error_answer], - 'STATUS': [self.handle_event_status], - 'NEW_JID': [self.handle_event_new_jid], - 'NOTIFY': [self.handle_event_notify], - 'MSGERROR': [self.handle_event_msgerror], - 'MSGSENT': [self.handle_event_msgsent], - 'MSGNOTSENT': [self.handle_event_msgnotsent], - 'SUBSCRIBED': [self.handle_event_subscribed], - 'UNSUBSCRIBED': [self.handle_event_unsubscribed], - 'SUBSCRIBE': [self.handle_event_subscribe], - 'AGENT_REMOVED': [self.handle_event_agent_removed], - 'REGISTER_AGENT_INFO': [self.handle_event_register_agent_info], - 'AGENT_INFO_ITEMS': [self.handle_event_agent_info_items], - 'QUIT': [self.handle_event_quit], - 'ACC_OK': [self.handle_event_acc_ok], - 'MYVCARD': [self.handle_event_myvcard], - 'VCARD': [self.handle_event_vcard], - 'LAST_STATUS_TIME': [self.handle_event_last_status_time], - 'OS_INFO': [self.handle_event_os_info], - 'ENTITY_TIME': [self.handle_event_entity_time], - 'GC_NOTIFY': [self.handle_event_gc_notify], - 'GC_MSG': [self.handle_event_gc_msg], - 'GC_SUBJECT': [self.handle_event_gc_subject], - 'GC_CONFIG': [self.handle_event_gc_config], - 'GC_CONFIG_CHANGE': [self.handle_event_gc_config_change], - 'GC_INVITATION': [self.handle_event_gc_invitation], - 'GC_AFFILIATION': [self.handle_event_gc_affiliation], - 'GC_PASSWORD_REQUIRED': [self.handle_event_gc_password_required], - 'BAD_PASSPHRASE': [self.handle_event_bad_passphrase], - 'ROSTER_INFO': [self.handle_event_roster_info], - 'BOOKMARKS': [self.handle_event_bookmarks], - 'CON_TYPE': [self.handle_event_con_type], - 'CONNECTION_LOST': [self.handle_event_connection_lost], - 'FILE_REQUEST': [self.handle_event_file_request], - 'GMAIL_NOTIFY': [self.handle_event_gmail_notify], - 'FILE_REQUEST_ERROR': [self.handle_event_file_request_error], - 'FILE_SEND_ERROR': [self.handle_event_file_send_error], - 'STANZA_ARRIVED': [self.handle_event_stanza_arrived], - 'STANZA_SENT': [self.handle_event_stanza_sent], - 'HTTP_AUTH': [self.handle_event_http_auth], - 'VCARD_PUBLISHED': [self.handle_event_vcard_published], - 'VCARD_NOT_PUBLISHED': [self.handle_event_vcard_not_published], - 'ASK_NEW_NICK': [self.handle_event_ask_new_nick], - 'SIGNED_IN': [self.handle_event_signed_in], - 'METACONTACTS': [self.handle_event_metacontacts], - 'ATOM_ENTRY': [self.handle_atom_entry], - 'FAILED_DECRYPT': [self.handle_event_failed_decrypt], - 'PRIVACY_LISTS_RECEIVED': [self.handle_event_privacy_lists_received], - 'PRIVACY_LIST_RECEIVED': [self.handle_event_privacy_list_received], - 'PRIVACY_LISTS_ACTIVE_DEFAULT': \ - [self.handle_event_privacy_lists_active_default], - 'PRIVACY_LIST_REMOVED': [self.handle_event_privacy_list_removed], - 'ZC_NAME_CONFLICT': [self.handle_event_zc_name_conflict], - 'PING_SENT': [self.handle_event_ping_sent], - 'PING_REPLY': [self.handle_event_ping_reply], - 'PING_ERROR': [self.handle_event_ping_error], - 'SEARCH_FORM': [self.handle_event_search_form], - 'SEARCH_RESULT': [self.handle_event_search_result], - 'RESOURCE_CONFLICT': [self.handle_event_resource_conflict], - 'ROSTERX': [self.handle_event_roster_item_exchange], - 'PEP_CONFIG': [self.handle_event_pep_config], - 'UNIQUE_ROOM_ID_UNSUPPORTED': \ - [self.handle_event_unique_room_id_unsupported], - 'UNIQUE_ROOM_ID_SUPPORTED': [self.handle_event_unique_room_id_supported], - 'GPG_PASSWORD_REQUIRED': [self.handle_event_gpg_password_required], - 'GPG_ALWAYS_TRUST': [self.handle_event_gpg_always_trust], - 'PASSWORD_REQUIRED': [self.handle_event_password_required], - 'SSL_ERROR': [self.handle_event_ssl_error], - 'FINGERPRINT_ERROR': [self.handle_event_fingerprint_error], - 'PLAIN_CONNECTION': [self.handle_event_plain_connection], - 'INSECURE_SSL_CONNECTION': [self.handle_event_insecure_ssl_connection], - 'PUBSUB_NODE_REMOVED': [self.handle_event_pubsub_node_removed], - 'PUBSUB_NODE_NOT_REMOVED': [self.handle_event_pubsub_node_not_removed], - 'JINGLE_INCOMING': [self.handle_event_jingle_incoming], - 'JINGLE_CONNECTED': [self.handle_event_jingle_connected], - 'JINGLE_DISCONNECTED': [self.handle_event_jingle_disconnected], - 'JINGLE_ERROR': [self.handle_event_jingle_error], - 'PEP_RECEIVED': [self.handle_event_pep_received], - 'CAPS_RECEIVED': [self.handle_event_caps_received] - } - - def register_core_handlers(self): - """ - Register core handlers in Global Events Dispatcher (GED). - - This is part of rewriting whole events handling system to use GED. - """ - for event_name, event_handlers in self.handlers.iteritems(): - for event_handler in event_handlers: - gajim.ged.register_event_handler(event_name, ged.CORE, - event_handler) + def handle_event_roster(self, account, data): + #('ROSTER', account, array) + # FIXME: Those methods depend to highly on each other + # and the order in which they are called + self.roster.fill_contacts_and_groups_dicts(data, account) + self.roster.add_account_contacts(account) + self.roster.fire_up_unread_messages_events(account) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('Roster', (account, data)) + + def handle_event_warning(self, unused, data): + #('WARNING', account, (title_text, section_text)) + dialogs.WarningDialog(data[0], data[1]) + + def handle_event_error(self, unused, data): + #('ERROR', account, (title_text, section_text)) + dialogs.ErrorDialog(data[0], data[1]) + + def handle_event_information(self, unused, data): + #('INFORMATION', account, (title_text, section_text)) + dialogs.InformationDialog(data[0], data[1]) + + def handle_event_ask_new_nick(self, account, data): + #('ASK_NEW_NICK', account, (room_jid,)) + room_jid = data[0] + title = _('Unable to join group chat') + prompt = _('Your desired nickname in group chat %s is in use or ' + 'registered by another occupant.\nPlease specify another nickname ' + 'below:') % room_jid + check_text = _('Always use this nickname when there is a conflict') + if 'change_nick_dialog' in self.instances: + self.instances['change_nick_dialog'].add_room(account, room_jid, + prompt) + else: + self.instances['change_nick_dialog'] = dialogs.ChangeNickDialog( + account, room_jid, title, prompt) + + def handle_event_http_auth(self, account, data): + #('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg)) + def response(account, iq_obj, answer): + self.dialog.destroy() + gajim.connections[account].build_http_auth_answer(iq_obj, answer) + + def on_yes(is_checked, account, iq_obj): + response(account, iq_obj, 'yes') + + sec_msg = _('Do you accept this request?') + if gajim.get_number_of_connected_accounts() > 1: + sec_msg = _('Do you accept this request on account %s?') % account + if data[4]: + sec_msg = data[4] + '\n' + sec_msg + self.dialog = dialogs.YesNoDialog(_('HTTP (%(method)s) Authorization for ' + '%(url)s (id: %(id)s)') % {'method': data[0], 'url': data[1], + 'id': data[2]}, sec_msg, on_response_yes=(on_yes, account, data[3]), + on_response_no=(response, account, data[3], 'no')) + + def handle_event_error_answer(self, account, array): + #('ERROR_ANSWER', account, (id, jid_from, errmsg, errcode)) + id_, jid_from, errmsg, errcode = array + if unicode(errcode) in ('400', '403', '406') and id_: + # show the error dialog + ft = self.instances['file_transfers'] + sid = id_ + if len(id_) > 3 and id_[2] == '_': + sid = id_[3:] + if sid in ft.files_props['s']: + file_props = ft.files_props['s'][sid] + if unicode(errcode) == '400': + file_props['error'] = -3 + else: + file_props['error'] = -4 + self.handle_event_file_request_error(account, + (jid_from, file_props, errmsg)) + conn = gajim.connections[account] + conn.disconnect_transfer(file_props) + return + elif unicode(errcode) == '404': + conn = gajim.connections[account] + sid = id_ + if len(id_) > 3 and id_[2] == '_': + sid = id_[3:] + if sid in conn.files_props: + file_props = conn.files_props[sid] + self.handle_event_file_send_error(account, + (jid_from, file_props)) + conn.disconnect_transfer(file_props) + return + + ctrl = self.msg_win_mgr.get_control(jid_from, account) + if ctrl and ctrl.type_id == message_control.TYPE_GC: + ctrl.print_conversation('Error %s: %s' % (array[2], array[1])) + + def handle_event_con_type(self, account, con_type): + # ('CON_TYPE', account, con_type) which can be 'ssl', 'tls', 'plain' + gajim.con_types[account] = con_type + self.roster.draw_account(account) + + def handle_event_connection_lost(self, account, array): + # ('CONNECTION_LOST', account, [title, text]) + path = gtkgui_helpers.get_icon_path('gajim-connection_lost', 48) + notify.popup(_('Connection Failed'), account, account, + 'connection_failed', path, array[0], array[1]) + + def unblock_signed_in_notifications(self, account): + gajim.block_signed_in_notifications[account] = False + + def handle_event_status(self, account, show): # OUR status + #('STATUS', account, show) + model = self.roster.status_combobox.get_model() + if show in ('offline', 'error'): + for name in self.instances[account]['online_dialog'].keys(): + # .keys() is needed to not have a dictionary length changed during + # iteration error + self.instances[account]['online_dialog'][name].destroy() + del self.instances[account]['online_dialog'][name] + for request in self.gpg_passphrase.values(): + if request: + request.interrupt() + if account in self.pass_dialog: + self.pass_dialog[account].window.destroy() + if show == 'offline': + # sensitivity for this menuitem + if gajim.get_number_of_connected_accounts() == 0: + model[self.roster.status_message_menuitem_iter][3] = False + gajim.block_signed_in_notifications[account] = True + else: + # 30 seconds after we change our status to sth else than offline + # we stop blocking notifications of any kind + # this prevents from getting the roster items as 'just signed in' + # contacts. 30 seconds should be enough time + gobject.timeout_add_seconds(30, self.unblock_signed_in_notifications, account) + # sensitivity for this menuitem + model[self.roster.status_message_menuitem_iter][3] = True + + # Inform all controls for this account of the connection state change + ctrls = self.msg_win_mgr.get_controls() + if account in self.minimized_controls: + # Can not be the case when we remove account + ctrls += self.minimized_controls[account].values() + for ctrl in ctrls: + if ctrl.account == account: + if show == 'offline' or (show == 'invisible' and \ + gajim.connections[account].is_zeroconf): + ctrl.got_disconnected() + else: + # Other code rejoins all GCs, so we don't do it here + if not ctrl.type_id == message_control.TYPE_GC: + ctrl.got_connected() + if ctrl.parent_win: + ctrl.parent_win.redraw_tab(ctrl) + + self.roster.on_status_changed(account, show) + if account in self.show_vcard_when_connect and show not in ('offline', + 'error'): + self.edit_own_details(account) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('AccountPresence', (show, account)) + + def handle_event_new_jid(self, account, data): + #('NEW_JID', account, (old_jid, new_jid)) + """ + This event is raised when our JID changed (most probably because we use + anonymous account. We update contact and roster entry in this case + """ + self.roster.rename_self_contact(data[0], data[1], account) + + def edit_own_details(self, account): + jid = gajim.get_jid_from_account(account) + if 'profile' not in self.instances[account]: + self.instances[account]['profile'] = \ + profile_window.ProfileWindow(account) + gajim.connections[account].request_vcard(jid) + + def handle_event_notify(self, account, array): + # 'NOTIFY' (account, (jid, status, status message, resource, + # priority, # keyID, timestamp, contact_nickname)) + # + # Contact changed show + + # FIXME: Drop and rewrite... + + statuss = ['offline', 'error', 'online', 'chat', 'away', 'xa', 'dnd', + 'invisible'] + # Ignore invalid show + if array[1] not in statuss: + return + old_show = 0 + new_show = statuss.index(array[1]) + status_message = array[2] + jid = array[0].split('/')[0] + keyID = array[5] + contact_nickname = array[7] + + # Get the proper keyID + keyID = helpers.prepare_and_validate_gpg_keyID(account, jid, keyID) + + resource = array[3] + if not resource: + resource = '' + priority = array[4] + if gajim.jid_is_transport(jid): + # It must be an agent + ji = jid.replace('@', '') + else: + ji = jid + + highest = gajim.contacts. \ + get_contact_with_highest_priority(account, jid) + was_highest = (highest and highest.resource == resource) + + conn = gajim.connections[account] + + # Update contact + jid_list = gajim.contacts.get_jid_list(account) + if ji in jid_list or jid == gajim.get_jid_from_account(account): + lcontact = gajim.contacts.get_contacts(account, ji) + contact1 = None + resources = [] + for c in lcontact: + resources.append(c.resource) + if c.resource == resource: + contact1 = c + break + + if contact1: + if contact1.show in statuss: + old_show = statuss.index(contact1.show) + # nick changed + if contact_nickname is not None and \ + contact1.contact_name != contact_nickname: + contact1.contact_name = contact_nickname + self.roster.draw_contact(jid, account) + + if old_show == new_show and contact1.status == status_message and \ + contact1.priority == priority: # no change + return + else: + contact1 = gajim.contacts.get_first_contact_from_jid(account, ji) + if not contact1: + # Presence of another resource of our + # jid + # Create self contact and add to roster + if resource == conn.server_resource: + return + # Ignore offline presence of unknown self resource + if new_show < 2: + return + contact1 = gajim.contacts.create_self_contact(jid=ji, + account=account, show=array[1], status=status_message, + priority=priority, keyID=keyID, resource=resource) + old_show = 0 + gajim.contacts.add_contact(account, contact1) + lcontact.append(contact1) + elif contact1.show in statuss: + old_show = statuss.index(contact1.show) + if (resources != [''] and (len(lcontact) != 1 or \ + lcontact[0].show != 'offline')) and jid.find('@') > 0: + # Another resource of an existing contact connected + old_show = 0 + contact1 = gajim.contacts.copy_contact(contact1) + lcontact.append(contact1) + contact1.resource = resource + + self.roster.add_contact(contact1.jid, account) + + if contact1.jid.find('@') > 0 and len(lcontact) == 1: + # It's not an agent + if old_show == 0 and new_show > 1: + if not contact1.jid in gajim.newly_added[account]: + gajim.newly_added[account].append(contact1.jid) + if contact1.jid in gajim.to_be_removed[account]: + gajim.to_be_removed[account].remove(contact1.jid) + gobject.timeout_add_seconds(5, self.roster.remove_newly_added, + contact1.jid, account) + elif old_show > 1 and new_show == 0 and conn.connected > 1: + if not contact1.jid in gajim.to_be_removed[account]: + gajim.to_be_removed[account].append(contact1.jid) + if contact1.jid in gajim.newly_added[account]: + gajim.newly_added[account].remove(contact1.jid) + self.roster.draw_contact(contact1.jid, account) + gobject.timeout_add_seconds(5, self.roster.remove_to_be_removed, + contact1.jid, account) + + # unset custom status + if (old_show == 0 and new_show > 1) or (old_show > 1 and new_show == 0\ + and conn.connected > 1): + if account in self.status_sent_to_users and \ + jid in self.status_sent_to_users[account]: + del self.status_sent_to_users[account][jid] + + contact1.show = array[1] + contact1.status = status_message + contact1.priority = priority + contact1.keyID = keyID + timestamp = array[6] + if timestamp: + contact1.last_status_time = timestamp + elif not gajim.block_signed_in_notifications[account]: + # We're connected since more that 30 seconds + contact1.last_status_time = time.localtime() + contact1.contact_nickname = contact_nickname + + if gajim.jid_is_transport(jid): + # It must be an agent + if ji in jid_list: + # Update existing iter and group counting + self.roster.draw_contact(ji, account) + self.roster.draw_group(_('Transports'), account) + if new_show > 1 and ji in gajim.transport_avatar[account]: + # transport just signed in. + # request avatars + for jid_ in gajim.transport_avatar[account][ji]: + conn.request_vcard(jid_) + # transport just signed in/out, don't show + # popup notifications for 30s + account_ji = account + '/' + ji + gajim.block_signed_in_notifications[account_ji] = True + gobject.timeout_add_seconds(30, + self.unblock_signed_in_notifications, account_ji) + locations = (self.instances, self.instances[account]) + for location in locations: + if 'add_contact' in location: + if old_show == 0 and new_show > 1: + location['add_contact'].transport_signed_in(jid) + break + elif old_show > 1 and new_show == 0: + location['add_contact'].transport_signed_out(jid) + break + elif ji in jid_list: + # It isn't an agent + # reset chatstate if needed: + # (when contact signs out or has errors) + if array[1] in ('offline', 'error'): + contact1.our_chatstate = contact1.chatstate = \ + contact1.composing_xep = None + + # TODO: This causes problems when another + # resource signs off! + conn.stop_all_active_file_transfers(contact1) + + # disable encryption, since if any messages are + # lost they'll be not decryptable (note that + # this contradicts XEP-0201 - trying to get that + # in the XEP, though) + + # there won't be any sessions here if the contact terminated + # their sessions before going offline (which we do) + for sess in conn.get_sessions(ji): + if (ji+'/'+resource) != str(sess.jid): + continue + if sess.control: + sess.control.no_autonegotiation = False + if sess.enable_encryption: + sess.terminate_e2e() + conn.delete_session(jid, sess.thread_id) + + self.roster.chg_contact_status(contact1, array[1], status_message, + account) + # Notifications + if old_show < 2 and new_show > 1: + notify.notify('contact_connected', jid, account, status_message) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('ContactPresence', (account, + array)) + + elif old_show > 1 and new_show < 2: + notify.notify('contact_disconnected', jid, account, status_message) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('ContactAbsence', (account, array)) + # FIXME: stop non active file transfers + # Status change (not connected/disconnected or + # error (<1)) + elif new_show > 1: + notify.notify('status_change', jid, account, [new_show, + status_message]) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('ContactStatus', (account, array)) + else: + # FIXME: MSN transport (CMSN1.2.1 and PyMSN) don't + # follow the XEP, still the case in 2008. + # It's maybe a GC_NOTIFY (specialy for MSN gc) + self.handle_event_gc_notify(account, (jid, array[1], status_message, + array[3], None, None, None, None, None, [], None, None)) + + highest = gajim.contacts.get_contact_with_highest_priority(account, jid) + is_highest = (highest and highest.resource == resource) + + # disconnect the session from the ctrl if the highest resource has changed + if (was_highest and not is_highest) or (not was_highest and is_highest): + ctrl = self.msg_win_mgr.get_control(jid, account) + + if ctrl: + ctrl.no_autonegotiation = False + ctrl.set_session(None) + ctrl.contact = highest + + def handle_event_msgerror(self, account, array): + #'MSGERROR' (account, (jid, error_code, error_msg, msg, time[, session])) + full_jid_with_resource = array[0] + jids = full_jid_with_resource.split('/', 1) + jid = jids[0] + + if array[1] == '503': + # If we get server-not-found error, stop sending chatstates + for contact in gajim.contacts.get_contacts(account, jid): + contact.composing_xep = False + + session = None + if len(array) > 5: + session = array[5] + + gc_control = self.msg_win_mgr.get_gc_control(jid, account) + if not gc_control and \ + jid in self.minimized_controls[account]: + gc_control = self.minimized_controls[account][jid] + if gc_control and gc_control.type_id != message_control.TYPE_GC: + gc_control = None + if gc_control: + if len(jids) > 1: # it's a pm + nick = jids[1] + + if session: + ctrl = session.control + else: + ctrl = self.msg_win_mgr.get_control(full_jid_with_resource, account) + + if not ctrl: + tv = gc_control.list_treeview + model = tv.get_model() + iter_ = gc_control.get_contact_iter(nick) + if iter_: + show = model[iter_][3] + else: + show = 'offline' + gc_c = gajim.contacts.create_gc_contact(room_jid=jid, account=account, + name=nick, show=show) + ctrl = self.new_private_chat(gc_c, account, session) + + ctrl.print_conversation(_('Error %(code)s: %(msg)s') % { + 'code': array[1], 'msg': array[2]}, 'status') + return + + gc_control.print_conversation(_('Error %(code)s: %(msg)s') % { + 'code': array[1], 'msg': array[2]}, 'status') + if gc_control.parent_win and gc_control.parent_win.get_active_jid() == jid: + gc_control.set_subject(gc_control.subject) + return + + if gajim.jid_is_transport(jid): + jid = jid.replace('@', '') + msg = array[2] + if array[3]: + msg = _('error while sending %(message)s ( %(error)s )') % { + 'message': array[3], 'error': msg} + if session: + session.roster_message(jid, msg, array[4], msg_type='error') + + def handle_event_msgsent(self, account, array): + #('MSGSENT', account, (jid, msg, keyID)) + msg = array[1] + # do not play sound when standalone chatstate message (eg no msg) + if msg and gajim.config.get_per('soundevents', 'message_sent', 'enabled'): + helpers.play_sound('message_sent') + + def handle_event_msgnotsent(self, account, array): + #('MSGNOTSENT', account, (jid, ierror_msg, msg, time, session)) + msg = _('error while sending %(message)s ( %(error)s )') % { + 'message': array[2], 'error': array[1]} + if not array[4]: + # No session. This can happen when sending a message from gajim-remote + log.warn(msg) + return + array[4].roster_message(array[0], msg, array[3], account, + msg_type='error') + + def handle_event_subscribe(self, account, array): + #('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172 + if self.remote_ctrl: + self.remote_ctrl.raise_signal('Subscribe', (account, array)) + + jid = array[0] + text = array[1] + nick = array[2] + if helpers.allow_popup_window(account) or not self.systray_enabled: + dialogs.SubscriptionRequestWindow(jid, text, account, nick) + return + + self.add_event(account, jid, 'subscription_request', (text, nick)) + + if helpers.allow_showing_notification(account): + path = gtkgui_helpers.get_icon_path('gajim-subscription_request', 48) + event_type = _('Subscription request') + notify.popup(event_type, jid, account, 'subscription_request', path, + event_type, jid) + + def handle_event_subscribed(self, account, array): + #('SUBSCRIBED', account, (jid, resource)) + jid = array[0] + if jid in gajim.contacts.get_jid_list(account): + c = gajim.contacts.get_first_contact_from_jid(account, jid) + c.resource = array[1] + self.roster.remove_contact_from_groups(c.jid, account, + [_('Not in Roster'), _('Observers')], update=False) + else: + keyID = '' + attached_keys = gajim.config.get_per('accounts', account, + 'attached_gpg_keys').split() + if jid in attached_keys: + keyID = attached_keys[attached_keys.index(jid) + 1] + name = jid.split('@', 1)[0] + name = name.split('%', 1)[0] + contact1 = gajim.contacts.create_contact(jid=jid, account=account, + name=name, groups=[], show='online', status='online', + ask='to', resource=array[1], keyID=keyID) + gajim.contacts.add_contact(account, contact1) + self.roster.add_contact(jid, account) + dialogs.InformationDialog(_('Authorization accepted'), + _('The contact "%s" has authorized you to see his or her status.') + % jid) + if not gajim.config.get_per('accounts', account, 'dont_ack_subscription'): + gajim.connections[account].ack_subscribed(jid) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('Subscribed', (account, array)) + + def show_unsubscribed_dialog(self, account, contact): + def on_yes(is_checked, list_): + self.roster.on_req_usub(None, list_) + list_ = [(contact, account)] + dialogs.YesNoDialog( + _('Contact "%s" removed subscription from you') % contact.jid, + _('You will always see him or her as offline.\nDo you want to ' + 'remove him or her from your contact list?'), + on_response_yes=(on_yes, list_)) + # FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does + # not show deny + + def handle_event_unsubscribed(self, account, jid): + #('UNSUBSCRIBED', account, jid) + gajim.connections[account].ack_unsubscribed(jid) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('Unsubscribed', (account, jid)) + + contact = gajim.contacts.get_first_contact_from_jid(account, jid) + if not contact: + return + + if helpers.allow_popup_window(account) or not self.systray_enabled: + self.show_unsubscribed_dialog(account, contact) + return + + self.add_event(account, jid, 'unsubscribed', contact) + + if helpers.allow_showing_notification(account): + path = gtkgui_helpers.get_icon_path('gajim-unsubscribed', 48) + event_type = _('Unsubscribed') + notify.popup(event_type, jid, account, 'unsubscribed', path, + event_type, jid) + + def handle_event_agent_removed(self, account, agent): + # remove transport's contacts from treeview + jid_list = gajim.contacts.get_jid_list(account) + for jid in jid_list: + if jid.endswith('@' + agent): + c = gajim.contacts.get_first_contact_from_jid(account, jid) + gajim.log.debug( + 'Removing contact %s due to unregistered transport %s'\ + % (jid, agent)) + gajim.connections[account].unsubscribe(c.jid) + # Transport contacts can't have 2 resources + if c.jid in gajim.to_be_removed[account]: + # This way we'll really remove it + gajim.to_be_removed[account].remove(c.jid) + self.roster.remove_contact(c.jid, account, backend=True) + + def handle_event_register_agent_info(self, account, array): + # ('REGISTER_AGENT_INFO', account, (agent, infos, is_form)) + # info in a dataform if is_form is True + if array[2] or 'instructions' in array[1]: + config.ServiceRegistrationWindow(array[0], array[1], account, + array[2]) + else: + dialogs.ErrorDialog(_('Contact with "%s" cannot be established') \ + % array[0], _('Check your connection or try again later.')) + + def handle_event_agent_info_items(self, account, array): + #('AGENT_INFO_ITEMS', account, (agent, node, items)) + our_jid = gajim.get_jid_from_account(account) + if 'pep_services' in gajim.interface.instances[account] and \ + array[0] == our_jid: + gajim.interface.instances[account]['pep_services'].items_received( + array[2]) + + def handle_event_acc_ok(self, account, array): + #('ACC_OK', account, (config)) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('NewAccount', (account, array)) + + def handle_event_quit(self, p1, p2): + self.roster.quit_gtkgui_interface() + + def handle_event_myvcard(self, account, array): + nick = '' + if 'NICKNAME' in array and array['NICKNAME']: + gajim.nicks[account] = array['NICKNAME'] + elif 'FN' in array and array['FN']: + gajim.nicks[account] = array['FN'] + if 'profile' in self.instances[account]: + win = self.instances[account]['profile'] + win.set_values(array) + if account in self.show_vcard_when_connect: + self.show_vcard_when_connect.remove(account) + jid = array['jid'] + if jid in self.instances[account]['infos']: + self.instances[account]['infos'][jid].set_values(array) + + def handle_event_vcard(self, account, vcard): + # ('VCARD', account, data) + '''vcard holds the vcard data''' + jid = vcard['jid'] + resource = vcard.get('resource', '') + fjid = jid + '/' + str(resource) + + # vcard window + win = None + if jid in self.instances[account]['infos']: + win = self.instances[account]['infos'][jid] + elif resource and fjid in self.instances[account]['infos']: + win = self.instances[account]['infos'][fjid] + if win: + win.set_values(vcard) + + # show avatar in chat + ctrl = None + if resource and self.msg_win_mgr.has_window(fjid, account): + win = self.msg_win_mgr.get_window(fjid, account) + ctrl = win.get_control(fjid, account) + elif self.msg_win_mgr.has_window(jid, account): + win = self.msg_win_mgr.get_window(jid, account) + ctrl = win.get_control(jid, account) + + if ctrl and ctrl.type_id != message_control.TYPE_GC: + ctrl.show_avatar() + + # Show avatar in roster or gc_roster + gc_ctrl = self.msg_win_mgr.get_gc_control(jid, account) + if not gc_ctrl and \ + jid in self.minimized_controls[account]: + gc_ctrl = self.minimized_controls[account][jid] + if gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC: + gc_ctrl.draw_avatar(resource) + else: + self.roster.draw_avatar(jid, account) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('VcardInfo', (account, vcard)) + + def handle_event_last_status_time(self, account, array): + # ('LAST_STATUS_TIME', account, (jid, resource, seconds, status)) + tim = array[2] + if tim < 0: + # Ann error occured + return + win = None + if array[0] in self.instances[account]['infos']: + win = self.instances[account]['infos'][array[0]] + elif array[0] + '/' + array[1] in self.instances[account]['infos']: + win = self.instances[account]['infos'][array[0] + '/' + array[1]] + c = gajim.contacts.get_contact(account, array[0], array[1]) + if c: # c can be none if it's a gc contact + if array[3]: + c.status = array[3] + self.roster.draw_contact(c.jid, account) # draw offline status + last_time = time.localtime(time.time() - tim) + if c.show == 'offline': + c.last_status_time = last_time + else: + c.last_activity_time = last_time + if win: + win.set_last_status_time() + if self.roster.tooltip.id and self.roster.tooltip.win: + self.roster.tooltip.update_last_time(last_time) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('LastStatusTime', (account, array)) + + def handle_event_os_info(self, account, array): + #'OS_INFO' (account, (jid, resource, client_info, os_info)) + win = None + if array[0] in self.instances[account]['infos']: + win = self.instances[account]['infos'][array[0]] + elif array[0] + '/' + array[1] in self.instances[account]['infos']: + win = self.instances[account]['infos'][array[0] + '/' + array[1]] + if win: + win.set_os_info(array[1], array[2], array[3]) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('OsInfo', (account, array)) + + def handle_event_entity_time(self, account, array): + #'ENTITY_TIME' (account, (jid, resource, time_info)) + win = None + if array[0] in self.instances[account]['infos']: + win = self.instances[account]['infos'][array[0]] + elif array[0] + '/' + array[1] in self.instances[account]['infos']: + win = self.instances[account]['infos'][array[0] + '/' + array[1]] + if win: + win.set_entity_time(array[1], array[2]) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('EntityTime', (account, array)) + + def handle_event_gc_notify(self, account, array): + #'GC_NOTIFY' (account, (room_jid, show, status, nick, + # role, affiliation, jid, reason, actor, statusCode, newNick, avatar_sha)) + nick = array[3] + if not nick: + return + room_jid = array[0] + fjid = room_jid + '/' + nick + show = array[1] + status = array[2] + conn = gajim.connections[account] + + # Get the window and control for the updated status, this may be a + # PrivateChatControl + control = self.msg_win_mgr.get_gc_control(room_jid, account) + + if not control and \ + room_jid in self.minimized_controls[account]: + control = self.minimized_controls[account][room_jid] + + if not control or (control and control.type_id != message_control.TYPE_GC): + return + + control.chg_contact_status(nick, show, status, array[4], array[5], + array[6], array[7], array[8], array[9], array[10], array[11]) + + contact = gajim.contacts.\ + get_contact_with_highest_priority(account, room_jid) + if contact: + self.roster.draw_contact(room_jid, account) + + # print status in chat window and update status/GPG image + ctrl = self.msg_win_mgr.get_control(fjid, account) + if ctrl: + statusCode = array[9] + if '303' in statusCode: + new_nick = array[10] + ctrl.print_conversation(_('%(nick)s is now known as %(new_nick)s') \ + % {'nick': nick, 'new_nick': new_nick}, 'status') + gc_c = gajim.contacts.get_gc_contact(account, room_jid, new_nick) + c = gc_c.as_contact() + ctrl.gc_contact = gc_c + ctrl.contact = c + if ctrl.session: + # stop e2e + if ctrl.session.enable_encryption: + thread_id = ctrl.session.thread_id + ctrl.session.terminate_e2e() + conn.delete_session(fjid, thread_id) + ctrl.no_autonegotiation = False + ctrl.draw_banner() + old_jid = room_jid + '/' + nick + new_jid = room_jid + '/' + new_nick + self.msg_win_mgr.change_key(old_jid, new_jid, account) + else: + contact = ctrl.contact + contact.show = show + contact.status = status + gc_contact = ctrl.gc_contact + gc_contact.show = show + gc_contact.status = status + uf_show = helpers.get_uf_show(show) + ctrl.print_conversation(_('%(nick)s is now %(status)s') % { + 'nick': nick, 'status': uf_show}, 'status') + if status: + ctrl.print_conversation(' (', 'status', simple=True) + ctrl.print_conversation('%s' % (status), 'status', simple=True) + ctrl.print_conversation(')', 'status', simple=True) + ctrl.parent_win.redraw_tab(ctrl) + ctrl.update_ui() + if self.remote_ctrl: + self.remote_ctrl.raise_signal('GCPresence', (account, array)) + + def handle_event_gc_msg(self, account, array): + # ('GC_MSG', account, (jid, msg, time, has_timestamp, htmlmsg, + # [status_codes])) + jids = array[0].split('/', 1) + room_jid = jids[0] + + msg = array[1] + + gc_control = self.msg_win_mgr.get_gc_control(room_jid, account) + if not gc_control and \ + room_jid in self.minimized_controls[account]: + gc_control = self.minimized_controls[account][room_jid] + + if not gc_control: + return + xhtml = array[4] + + if gajim.config.get('ignore_incoming_xhtml'): + xhtml = None + if len(jids) == 1: + # message from server + nick = '' + else: + # message from someone + nick = jids[1] + + gc_control.on_message(nick, msg, array[2], array[3], xhtml, array[5]) + + if self.remote_ctrl: + highlight = gc_control.needs_visual_notification(msg) + array += (highlight,) + self.remote_ctrl.raise_signal('GCMessage', (account, array)) + + def handle_event_gc_subject(self, account, array): + #('GC_SUBJECT', account, (jid, subject, body, has_timestamp)) + jids = array[0].split('/', 1) + jid = jids[0] + + gc_control = self.msg_win_mgr.get_gc_control(jid, account) + + if not gc_control and \ + jid in self.minimized_controls[account]: + gc_control = self.minimized_controls[account][jid] + + contact = gajim.contacts.\ + get_contact_with_highest_priority(account, jid) + if contact: + contact.status = array[1] + self.roster.draw_contact(jid, account) + + if not gc_control: + return + gc_control.set_subject(array[1]) + # Standard way, the message comes from the occupant who set the subject + text = None + if len(jids) > 1: + text = _('%(jid)s has set the subject to %(subject)s') % { + 'jid': jids[1], 'subject': array[1]} + # Workaround for psi bug http://flyspray.psi-im.org/task/595 , to be + # deleted one day. We can receive a subject with a body that contains + # "X has set the subject to Y" ... + elif array[2]: + text = array[2] + if text is not None: + if array[3]: + gc_control.print_old_conversation(text) + else: + gc_control.print_conversation(text) + + def handle_event_gc_config(self, account, array): + #('GC_CONFIG', account, (jid, form)) config is a dict + room_jid = array[0].split('/')[0] + if room_jid in gajim.automatic_rooms[account]: + if 'continue_tag' in gajim.automatic_rooms[account][room_jid]: + # We're converting chat to muc. allow participants to invite + form = dataforms.ExtendForm(node = array[1]) + for f in form.iter_fields(): + if f.var == 'muc#roomconfig_allowinvites': + f.value = True + elif f.var == 'muc#roomconfig_publicroom': + f.value = False + elif f.var == 'muc#roomconfig_membersonly': + f.value = True + elif f.var == 'public_list': + f.value = False + gajim.connections[account].send_gc_config(room_jid, form) + else: + # use default configuration + gajim.connections[account].send_gc_config(room_jid, array[1]) + # invite contacts + # check if it is necessary to add + continue_tag = False + if 'continue_tag' in gajim.automatic_rooms[account][room_jid]: + continue_tag = True + if 'invities' in gajim.automatic_rooms[account][room_jid]: + for jid in gajim.automatic_rooms[account][room_jid]['invities']: + gajim.connections[account].send_invite(room_jid, jid, + continue_tag=continue_tag) + del gajim.automatic_rooms[account][room_jid] + elif room_jid not in self.instances[account]['gc_config']: + self.instances[account]['gc_config'][room_jid] = \ + config.GroupchatConfigWindow(account, room_jid, array[1]) + + def handle_event_gc_config_change(self, account, array): + #('GC_CONFIG_CHANGE', account, (jid, statusCode)) statuscode is a list + # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify + # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes-init + jid = array[0] + statusCode = array[1] + + gc_control = self.msg_win_mgr.get_gc_control(jid, account) + if not gc_control and \ + jid in self.minimized_controls[account]: + gc_control = self.minimized_controls[account][jid] + if not gc_control: + return + + changes = [] + if '100' in statusCode: + # Can be a presence (see chg_contact_status in groupchat_control.py) + changes.append(_('Any occupant is allowed to see your full JID')) + gc_control.is_anonymous = False + if '102' in statusCode: + changes.append(_('Room now shows unavailable member')) + if '103' in statusCode: + changes.append(_('room now does not show unavailable members')) + if '104' in statusCode: + changes.append( + _('A non-privacy-related room configuration change has occurred')) + if '170' in statusCode: + # Can be a presence (see chg_contact_status in groupchat_control.py) + changes.append(_('Room logging is now enabled')) + if '171' in statusCode: + changes.append(_('Room logging is now disabled')) + if '172' in statusCode: + changes.append(_('Room is now non-anonymous')) + gc_control.is_anonymous = False + if '173' in statusCode: + changes.append(_('Room is now semi-anonymous')) + gc_control.is_anonymous = True + if '174' in statusCode: + changes.append(_('Room is now fully-anonymous')) + gc_control.is_anonymous = True + + for change in changes: + gc_control.print_conversation(change) + + def handle_event_gc_affiliation(self, account, array): + #('GC_AFFILIATION', account, (room_jid, users_dict)) + room_jid = array[0] + if room_jid in self.instances[account]['gc_config']: + self.instances[account]['gc_config'][room_jid].\ + affiliation_list_received(array[1]) + + def handle_event_gc_password_required(self, account, array): + #('GC_PASSWORD_REQUIRED', account, (room_jid, nick)) + room_jid = array[0] + nick = array[1] + + def on_ok(text): + gajim.connections[account].join_gc(nick, room_jid, text) + gajim.gc_passwords[room_jid] = text + + def on_cancel(): + # get and destroy window + if room_jid in gajim.interface.minimized_controls[account]: + self.roster.on_disconnect(None, room_jid, account) + else: + win = self.msg_win_mgr.get_window(room_jid, account) + ctrl = self.msg_win_mgr.get_gc_control(room_jid, account) + win.remove_tab(ctrl, 3) + + dlg = dialogs.InputDialog(_('Password Required'), + _('A Password is required to join the room %s. Please type it.') % \ + room_jid, is_modal=False, ok_handler=on_ok, cancel_handler=on_cancel) + dlg.input_entry.set_visibility(False) + + def handle_event_gc_invitation(self, account, array): + #('GC_INVITATION', (room_jid, jid_from, reason, password, is_continued)) + jid = gajim.get_jid_without_resource(array[1]) + room_jid = array[0] + if helpers.allow_popup_window(account) or not self.systray_enabled: + dialogs.InvitationReceivedDialog(account, room_jid, jid, array[3], + array[2], is_continued=array[4]) + return + + self.add_event(account, jid, 'gc-invitation', (room_jid, array[2], + array[3], array[4])) + + if helpers.allow_showing_notification(account): + path = gtkgui_helpers.get_icon_path('gajim-gc_invitation', 48) + event_type = _('Groupchat Invitation') + notify.popup(event_type, jid, account, 'gc-invitation', path, + event_type, room_jid) + + def forget_gpg_passphrase(self, keyid): + if keyid in self.gpg_passphrase: + del self.gpg_passphrase[keyid] + return False + + def handle_event_bad_passphrase(self, account, array): + #('BAD_PASSPHRASE', account, ()) + use_gpg_agent = gajim.config.get('use_gpg_agent') + sectext = '' + if use_gpg_agent: + sectext = _('You configured Gajim to use GPG agent, but there is no ' + 'GPG agent running or it returned a wrong passphrase.\n') + sectext += _('You are currently connected without your OpenPGP key.') + dialogs.WarningDialog(_('Your passphrase is incorrect'), sectext) + else: + path = gtkgui_helpers.get_icon_path('gajim-warning', 48) + notify.popup('warning', account, account, 'warning', path, + _('OpenGPG Passphrase Incorrect'), + _('You are currently connected without your OpenPGP key.')) + keyID = gajim.config.get_per('accounts', account, 'keyid') + self.forget_gpg_passphrase(keyID) + + def handle_event_gpg_password_required(self, account, array): + #('GPG_PASSWORD_REQUIRED', account, (callback,)) + callback = array[0] + keyid = gajim.config.get_per('accounts', account, 'keyid') + if keyid in self.gpg_passphrase: + request = self.gpg_passphrase[keyid] + else: + request = PassphraseRequest(keyid) + self.gpg_passphrase[keyid] = request + request.add_callback(account, callback) + + def handle_event_gpg_always_trust(self, account, callback): + #('GPG_ALWAYS_TRUST', account, callback) + def on_yes(checked): + if checked: + gajim.connections[account].gpg.always_trust = True + callback(True) + + def on_no(): + callback(False) + + dialogs.YesNoDialog(_('GPG key not trusted'), _('The GPG key used to ' + 'encrypt this chat is not trusted. Do you really want to encrypt this ' + 'message?'), checktext=_('Do _not ask me again'), + on_response_yes=on_yes, on_response_no=on_no) + + def handle_event_password_required(self, account, array): + #('PASSWORD_REQUIRED', account, None) + if account in self.pass_dialog: + return + text = _('Enter your password for account %s') % account + if passwords.USER_HAS_GNOMEKEYRING and \ + not passwords.USER_USES_GNOMEKEYRING: + text += '\n' + _('Gnome Keyring is installed but not \ + correctly started (environment variable probably not \ + correctly set)') + + def on_ok(passphrase, save): + if save: + gajim.config.set_per('accounts', account, 'savepass', True) + passwords.save_password(account, passphrase) + gajim.connections[account].set_password(passphrase) + del self.pass_dialog[account] + + def on_cancel(): + self.roster.set_state(account, 'offline') + self.roster.update_status_combobox() + del self.pass_dialog[account] + + self.pass_dialog[account] = dialogs.PassphraseDialog( + _('Password Required'), text, _('Save password'), ok_handler=on_ok, + cancel_handler=on_cancel) + + def handle_event_roster_info(self, account, array): + #('ROSTER_INFO', account, (jid, name, sub, ask, groups)) + jid = array[0] + name = array[1] + sub = array[2] + ask = array[3] + groups = array[4] + contacts = gajim.contacts.get_contacts(account, jid) + if (not sub or sub == 'none') and (not ask or ask == 'none') and \ + not name and not groups: + # contact removed us. + if contacts: + self.roster.remove_contact(jid, account, backend=True) + return + elif not contacts: + if sub == 'remove': + return + # Add new contact to roster + contact = gajim.contacts.create_contact(jid=jid, account=account, + name=name, groups=groups, show='offline', sub=sub, ask=ask) + gajim.contacts.add_contact(account, contact) + self.roster.add_contact(jid, account) + else: + # it is an existing contact that might has changed + re_place = False + # If contact has changed (sub, ask or group) update roster + # Mind about observer status changes: + # According to xep 0162, a contact is not an observer anymore when + # we asked for auth, so also remove him if ask changed + old_groups = contacts[0].groups + if contacts[0].sub != sub or contacts[0].ask != ask\ + or old_groups != groups: + re_place = True + # c.get_shown_groups() has changed. Reflect that in roster_winodow + self.roster.remove_contact(jid, account, force=True) + for contact in contacts: + contact.name = name or '' + contact.sub = sub + contact.ask = ask + contact.groups = groups or [] + if re_place: + self.roster.add_contact(jid, account) + # Refilter and update old groups + for group in old_groups: + self.roster.draw_group(group, account) + else: + self.roster.draw_contact(jid, account) + + if self.remote_ctrl: + self.remote_ctrl.raise_signal('RosterInfo', (account, array)) + + def handle_event_bookmarks(self, account, bms): + # ('BOOKMARKS', account, [{name,jid,autojoin,password,nick}, {}]) + # We received a bookmark item from the server (JEP48) + # Auto join GC windows if neccessary + + self.roster.set_actions_menu_needs_rebuild() + invisible_show = gajim.SHOW_LIST.index('invisible') + # do not autojoin if we are invisible + if gajim.connections[account].connected == invisible_show: + return + + self.auto_join_bookmarks(account) + + def handle_event_file_send_error(self, account, array): + jid = array[0] + file_props = array[1] + ft = self.instances['file_transfers'] + ft.set_status(file_props['type'], file_props['sid'], 'stop') + + if helpers.allow_popup_window(account): + ft.show_send_error(file_props) + return + + self.add_event(account, jid, 'file-send-error', file_props) + + if helpers.allow_showing_notification(account): + path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48) + event_type = _('File Transfer Error') + notify.popup(event_type, jid, account, 'file-send-error', path, + event_type, file_props['name']) + + def handle_event_gmail_notify(self, account, array): + jid = array[0] + gmail_new_messages = int(array[1]) + gmail_messages_list = array[2] + if gajim.config.get('notify_on_new_gmail_email'): + path = gtkgui_helpers.get_icon_path('gajim-new_email_recv', 48) + title = _('New mail on %(gmail_mail_address)s') % \ + {'gmail_mail_address': jid} + text = i18n.ngettext('You have %d new mail conversation', + 'You have %d new mail conversations', gmail_new_messages, + gmail_new_messages, gmail_new_messages) + + if gajim.config.get('notify_on_new_gmail_email_extra'): + cnt = 0 + for gmessage in gmail_messages_list: + #FIXME: emulate Gtalk client popups. find out what they parse and + # how they decide what to show each message has a 'From', + # 'Subject' and 'Snippet' field + if cnt >=5: + break + senders = ',\n '.join(reversed(gmessage['From'])) + text += _('\n\nFrom: %(from_address)s\nSubject: %(subject)s\n%(snippet)s') % \ + {'from_address': senders, 'subject': gmessage['Subject'], + 'snippet': gmessage['Snippet']} + cnt += 1 + + if gajim.config.get_per('soundevents', 'gmail_received', 'enabled'): + helpers.play_sound('gmail_received') + notify.popup(_('New E-mail'), jid, account, 'gmail', + path_to_image=path, title=title, + text=text) + + if self.remote_ctrl: + self.remote_ctrl.raise_signal('NewGmail', (account, array)) + + def handle_event_file_request_error(self, account, array): + # ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg)) + jid, file_props, errmsg = array + jid = gajim.get_jid_without_resource(jid) + ft = self.instances['file_transfers'] + ft.set_status(file_props['type'], file_props['sid'], 'stop') + errno = file_props['error'] + + if helpers.allow_popup_window(account): + if errno in (-4, -5): + ft.show_stopped(jid, file_props, errmsg) + else: + ft.show_request_error(file_props) + return + + if errno in (-4, -5): + msg_type = 'file-error' + else: + msg_type = 'file-request-error' + + self.add_event(account, jid, msg_type, file_props) + + if helpers.allow_showing_notification(account): + # check if we should be notified + path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48) + event_type = _('File Transfer Error') + notify.popup(event_type, jid, account, msg_type, path, + title = event_type, text = file_props['name']) + + def handle_event_file_request(self, account, array): + jid = array[0] + jid = gajim.get_jid_without_resource(jid) + if jid not in gajim.contacts.get_jid_list(account): + keyID = '' + attached_keys = gajim.config.get_per('accounts', account, + 'attached_gpg_keys').split() + if jid in attached_keys: + keyID = attached_keys[attached_keys.index(jid) + 1] + contact = gajim.contacts.create_not_in_roster_contact(jid=jid, + account=account, keyID=keyID) + gajim.contacts.add_contact(account, contact) + self.roster.add_contact(contact.jid, account) + file_props = array[1] + contact = gajim.contacts.get_first_contact_from_jid(account, jid) + + if helpers.allow_popup_window(account): + self.instances['file_transfers'].show_file_request(account, contact, + file_props) + return + + self.add_event(account, jid, 'file-request', file_props) + + if helpers.allow_showing_notification(account): + path = gtkgui_helpers.get_icon_path('gajim-ft_request', 48) + txt = _('%s wants to send you a file.') % gajim.get_name_from_jid( + account, jid) + event_type = _('File Transfer Request') + notify.popup(event_type, jid, account, 'file-request', + path_to_image = path, title = event_type, text = txt) + + def handle_event_file_error(self, title, message): + dialogs.ErrorDialog(title, message) + + def handle_event_file_progress(self, account, file_props): + if time.time() - self.last_ftwindow_update > 0.5: + # update ft window every 500ms + self.last_ftwindow_update = time.time() + self.instances['file_transfers'].set_progress(file_props['type'], + file_props['sid'], file_props['received-len']) + + def handle_event_file_rcv_completed(self, account, file_props): + ft = self.instances['file_transfers'] + if file_props['error'] == 0: + ft.set_progress(file_props['type'], file_props['sid'], + file_props['received-len']) + else: + ft.set_status(file_props['type'], file_props['sid'], 'stop') + if 'stalled' in file_props and file_props['stalled'] or \ + 'paused' in file_props and file_props['paused']: + return + if file_props['type'] == 'r': # we receive a file + jid = unicode(file_props['sender']) + else: # we send a file + jid = unicode(file_props['receiver']) + + if helpers.allow_popup_window(account): + if file_props['error'] == 0: + if gajim.config.get('notify_on_file_complete'): + ft.show_completed(jid, file_props) + elif file_props['error'] == -1: + ft.show_stopped(jid, file_props, + error_msg=_('Remote contact stopped transfer')) + elif file_props['error'] == -6: + ft.show_stopped(jid, file_props, error_msg=_('Error opening file')) + return + + msg_type = '' + event_type = '' + if file_props['error'] == 0 and gajim.config.get( + 'notify_on_file_complete'): + msg_type = 'file-completed' + event_type = _('File Transfer Completed') + elif file_props['error'] in (-1, -6): + msg_type = 'file-stopped' + event_type = _('File Transfer Stopped') + + if event_type == '': + # FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs) + # this should never happen but it does. see process_result() in socks5.py + # who calls this func (sth is really wrong unless this func is also registered + # as progress_cb + return + + if msg_type: + self.add_event(account, jid, msg_type, file_props) + + if file_props is not None: + if file_props['type'] == 'r': + # get the name of the sender, as it is in the roster + sender = unicode(file_props['sender']).split('/')[0] + name = gajim.contacts.get_first_contact_from_jid(account, + sender).get_shown_name() + filename = os.path.basename(file_props['file-name']) + if event_type == _('File Transfer Completed'): + txt = _('You successfully received %(filename)s from %(name)s.')\ + % {'filename': filename, 'name': name} + img_name = 'gajim-ft_done' + else: # ft stopped + txt = _('File transfer of %(filename)s from %(name)s stopped.')\ + % {'filename': filename, 'name': name} + img_name = 'gajim-ft_stopped' + else: + receiver = file_props['receiver'] + if hasattr(receiver, 'jid'): + receiver = receiver.jid + receiver = receiver.split('/')[0] + # get the name of the contact, as it is in the roster + name = gajim.contacts.get_first_contact_from_jid(account, + receiver).get_shown_name() + filename = os.path.basename(file_props['file-name']) + if event_type == _('File Transfer Completed'): + txt = _('You successfully sent %(filename)s to %(name)s.')\ + % {'filename': filename, 'name': name} + img_name = 'gajim-ft_done' + else: # ft stopped + txt = _('File transfer of %(filename)s to %(name)s stopped.')\ + % {'filename': filename, 'name': name} + img_name = 'gajim-ft_stopped' + path = gtkgui_helpers.get_icon_path(img_name, 48) + else: + txt = '' + path = '' + + if gajim.config.get('notify_on_file_complete') and \ + (gajim.config.get('autopopupaway') or \ + gajim.connections[account].connected in (2, 3)): + # we want to be notified and we are online/chat or we don't mind + # bugged when away/na/busy + notify.popup(event_type, jid, account, msg_type, path_to_image=path, + title=event_type, text=txt) + + def handle_event_stanza_arrived(self, account, stanza): + if account not in self.instances: + return + if 'xml_console' in self.instances[account]: + self.instances[account]['xml_console'].print_stanza(stanza, 'incoming') + + def handle_event_stanza_sent(self, account, stanza): + if account not in self.instances: + return + if 'xml_console' in self.instances[account]: + self.instances[account]['xml_console'].print_stanza(stanza, 'outgoing') + + def handle_event_vcard_published(self, account, array): + if 'profile' in self.instances[account]: + win = self.instances[account]['profile'] + win.vcard_published() + for gc_control in self.msg_win_mgr.get_controls(message_control.TYPE_GC) + \ + self.minimized_controls[account].values(): + if gc_control.account == account: + show = gajim.SHOW_LIST[gajim.connections[account].connected] + status = gajim.connections[account].status + gajim.connections[account].send_gc_status(gc_control.nick, + gc_control.room_jid, show, status) + + def handle_event_vcard_not_published(self, account, array): + if 'profile' in self.instances[account]: + win = self.instances[account]['profile'] + win.vcard_not_published() + + def ask_offline_status(self, account): + for contact in gajim.contacts.iter_contacts(account): + gajim.connections[account].request_last_status_time(contact.jid, + contact.resource) + + def handle_event_signed_in(self, account, empty): + """ + SIGNED_IN event is emitted when we sign in, so handle it + """ + # ('SIGNED_IN', account, ()) + # block signed in notifications for 30 seconds + gajim.block_signed_in_notifications[account] = True + self.roster.set_actions_menu_needs_rebuild() + self.roster.draw_account(account) + state = self.sleeper.getState() + connected = gajim.connections[account].connected + if gajim.config.get('ask_offline_status_on_connection'): + # Ask offline status in 1 minute so w'are sure we got all online + # presences + gobject.timeout_add_seconds(60, self.ask_offline_status, account) + if state != common.sleepy.STATE_UNKNOWN and connected in (2, 3): + # we go online or free for chat, so we activate auto status + gajim.sleeper_state[account] = 'online' + elif not ((state == common.sleepy.STATE_AWAY and connected == 4) or \ + (state == common.sleepy.STATE_XA and connected == 5)): + # If we are autoaway/xa and come back after a disconnection, do nothing + # Else disable autoaway + gajim.sleeper_state[account] = 'off' + invisible_show = gajim.SHOW_LIST.index('invisible') + # We cannot join rooms if we are invisible + if gajim.connections[account].connected == invisible_show: + return + # join already open groupchats + for gc_control in self.msg_win_mgr.get_controls(message_control.TYPE_GC) \ + + self.minimized_controls[account].values(): + if account != gc_control.account: + continue + room_jid = gc_control.room_jid + if room_jid in gajim.gc_connected[account] and \ + gajim.gc_connected[account][room_jid]: + continue + nick = gc_control.nick + password = gajim.gc_passwords.get(room_jid, '') + gajim.connections[account].join_gc(nick, room_jid, password) + # send currently played music + if gajim.connections[account].pep_supported and dbus_support.supported \ + and gajim.config.get_per('accounts', account, 'publish_tune'): + self.enable_music_listener() + # enable location listener + if gajim.connections[account].pep_supported and dbus_support.supported \ + and gajim.config.get_per('accounts', account, 'publish_location'): + location_listener.enable() + + def handle_event_metacontacts(self, account, tags_list): + gajim.contacts.define_metacontacts(account, tags_list) + self.roster.redraw_metacontacts(account) + + def handle_atom_entry(self, account, data): + atom_entry, = data + AtomWindow.newAtomEntry(atom_entry) + + def handle_event_failed_decrypt(self, account, data): + jid, tim, session = data + + details = _('Unable to decrypt message from ' + '%s\nIt may have been tampered with.') % jid + + ctrl = session.control + if ctrl: + ctrl.print_conversation_line(details, 'status', '', tim) + else: + dialogs.WarningDialog(_('Unable to decrypt message'), + details) + + # terminate the session + session.terminate_e2e() + session.conn.delete_session(jid, session.thread_id) + + # restart the session + if ctrl: + ctrl.begin_e2e_negotiation() + + def handle_event_privacy_lists_received(self, account, data): + # ('PRIVACY_LISTS_RECEIVED', account, list) + if account not in self.instances: + return + if 'privacy_lists' in self.instances[account]: + self.instances[account]['privacy_lists'].privacy_lists_received(data) + + def handle_event_privacy_list_received(self, account, data): + # ('PRIVACY_LIST_RECEIVED', account, (name, rules)) + if account not in self.instances: + return + name = data[0] + rules = data[1] + if 'privacy_list_%s' % name in self.instances[account]: + self.instances[account]['privacy_list_%s' % name].\ + privacy_list_received(rules) + if name == 'block': + gajim.connections[account].blocked_contacts = [] + gajim.connections[account].blocked_groups = [] + gajim.connections[account].blocked_list = [] + gajim.connections[account].blocked_all = False + for rule in rules: + if not 'type' in rule: + gajim.connections[account].blocked_all = True + elif rule['type'] == 'jid' and rule['action'] == 'deny': + gajim.connections[account].blocked_contacts.append(rule['value']) + elif rule['type'] == 'group' and rule['action'] == 'deny': + gajim.connections[account].blocked_groups.append(rule['value']) + gajim.connections[account].blocked_list.append(rule) + #elif rule['type'] == "group" and action == "deny": + # text_item = _('%s group "%s"') % _(rule['action']), rule['value'] + # self.store.append([text_item]) + # self.global_rules.append(rule) + #else: + # self.global_rules_to_append.append(rule) + if 'blocked_contacts' in self.instances[account]: + self.instances[account]['blocked_contacts'].\ + privacy_list_received(rules) + + def handle_event_privacy_lists_active_default(self, account, data): + if not data: + return + # Send to all privacy_list_* windows as we can't know which one asked + for win in self.instances[account]: + if win.startswith('privacy_list_'): + self.instances[account][win].check_active_default(data) + + def handle_event_privacy_list_removed(self, account, name): + # ('PRIVACY_LISTS_REMOVED', account, name) + if account not in self.instances: + return + if 'privacy_lists' in self.instances[account]: + self.instances[account]['privacy_lists'].privacy_list_removed(name) + + def handle_event_zc_name_conflict(self, account, data): + def on_ok(new_name): + gajim.config.set_per('accounts', account, 'name', new_name) + status = gajim.connections[account].status + gajim.connections[account].username = new_name + gajim.connections[account].change_status(status, '') + def on_cancel(): + gajim.connections[account].change_status('offline', '') + + dlg = dialogs.InputDialog(_('Username Conflict'), + _('Please type a new username for your local account'), input_str=data, + is_modal=True, ok_handler=on_ok, cancel_handler=on_cancel) + + def handle_event_ping_sent(self, account, contact): + if contact.jid == contact.get_full_jid(): + # If contact is a groupchat user + jids = [contact.jid] + else: + jids = [contact.jid, contact.get_full_jid()] + for jid in jids: + ctrl = self.msg_win_mgr.get_control(jid, account) + if ctrl: + ctrl.print_conversation(_('Ping?'), 'status') + + def handle_event_ping_reply(self, account, data): + contact = data[0] + seconds = data[1] + if contact.jid == contact.get_full_jid(): + # If contact is a groupchat user + jids = [contact.jid] + else: + jids = [contact.jid, contact.get_full_jid()] + for jid in jids: + ctrl = self.msg_win_mgr.get_control(jid, account) + if ctrl: + ctrl.print_conversation(_('Pong! (%s s.)') % seconds, 'status') + + def handle_event_ping_error(self, account, contact): + if contact.jid == contact.get_full_jid(): + # If contact is a groupchat user + jids = [contact.jid] + else: + jids = [contact.jid, contact.get_full_jid()] + for jid in jids: + ctrl = self.msg_win_mgr.get_control(jid, account) + if ctrl: + ctrl.print_conversation(_('Error.'), 'status') + + def handle_event_search_form(self, account, data): + # ('SEARCH_FORM', account, (jid, dataform, is_dataform)) + if data[0] not in self.instances[account]['search']: + return + self.instances[account]['search'][data[0]].on_form_arrived(data[1], + data[2]) + + def handle_event_search_result(self, account, data): + # ('SEARCH_RESULT', account, (jid, dataform, is_dataform)) + if data[0] not in self.instances[account]['search']: + return + self.instances[account]['search'][data[0]].on_result_arrived(data[1], + data[2]) + + def handle_event_resource_conflict(self, account, data): + # ('RESOURCE_CONFLICT', account, ()) + # First we go offline, but we don't overwrite status message + self.roster.send_status(account, 'offline', + gajim.connections[account].status) + def on_ok(new_resource): + gajim.config.set_per('accounts', account, 'resource', new_resource) + self.roster.send_status(account, gajim.connections[account].old_show, + gajim.connections[account].status) + proposed_resource = gajim.connections[account].server_resource + proposed_resource += gajim.config.get('gc_proposed_nick_char') + dlg = dialogs.ResourceConflictDialog(_('Resource Conflict'), + _('You are already connected to this account with the same resource. ' + 'Please type a new one'), resource=proposed_resource, ok_handler=on_ok) + + def handle_event_jingle_incoming(self, account, data): + # ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type, + # data...)) + # TODO: conditional blocking if peer is not in roster + + # unpack data + peerjid, sid, contents = data + content_types = set(c[0] for c in contents) + + # check type of jingle session + if 'audio' in content_types or 'video' in content_types: + # a voip session... + # we now handle only voip, so the only thing we will do here is + # not to return from function + pass + else: + # unknown session type... it should be declined in common/jingle.py + return + + jid = gajim.get_jid_without_resource(peerjid) + resource = gajim.get_resource_from_jid(peerjid) + ctrl = self.msg_win_mgr.get_control(peerjid, account) + if not ctrl: + ctrl = self.msg_win_mgr.get_control(jid, account) + if ctrl: + if 'audio' in content_types: + ctrl.set_audio_state('connection_received', sid) + if 'video' in content_types: + ctrl.set_video_state('connection_received', sid) + + dlg = dialogs.VoIPCallReceivedDialog.get_dialog(peerjid, sid) + if dlg: + dlg.add_contents(content_types) + return + + if helpers.allow_popup_window(account): + dialogs.VoIPCallReceivedDialog(account, peerjid, sid, content_types) + return + + self.add_event(account, peerjid, 'jingle-incoming', (peerjid, sid, + content_types)) + + if helpers.allow_showing_notification(account): + # TODO: we should use another pixmap ;-) + txt = _('%s wants to start a voice chat.') % gajim.get_name_from_jid( + account, peerjid) + path = gtkgui_helpers.get_icon_path('gajim-mic_active', 48) + event_type = _('Voice Chat Request') + notify.popup(event_type, peerjid, account, 'jingle-incoming', + path_to_image = path, title = event_type, text = txt) + + def handle_event_jingle_connected(self, account, data): + # ('JINGLE_CONNECTED', account, (peerjid, sid, media)) + peerjid, sid, media = data + if media in ('audio', 'video'): + jid = gajim.get_jid_without_resource(peerjid) + resource = gajim.get_resource_from_jid(peerjid) + ctrl = self.msg_win_mgr.get_control(peerjid, account) + if not ctrl: + ctrl = self.msg_win_mgr.get_control(jid, account) + if ctrl: + if media == 'audio': + ctrl.set_audio_state('connected', sid) + else: + ctrl.set_video_state('connected', sid) + + def handle_event_jingle_disconnected(self, account, data): + # ('JINGLE_DISCONNECTED', account, (peerjid, sid, reason)) + peerjid, sid, media, reason = data + jid = gajim.get_jid_without_resource(peerjid) + resource = gajim.get_resource_from_jid(peerjid) + ctrl = self.msg_win_mgr.get_control(peerjid, account) + if not ctrl: + ctrl = self.msg_win_mgr.get_control(jid, account) + if ctrl: + if media in ('audio', None): + ctrl.set_audio_state('stop', sid=sid, reason=reason) + if media in ('video', None): + ctrl.set_video_state('stop', sid=sid, reason=reason) + dialog = dialogs.VoIPCallReceivedDialog.get_dialog(peerjid, sid) + if dialog: + dialog.dialog.destroy() + + def handle_event_jingle_error(self, account, data): + # ('JINGLE_ERROR', account, (peerjid, sid, reason)) + peerjid, sid, reason = data + jid = gajim.get_jid_without_resource(peerjid) + resource = gajim.get_resource_from_jid(peerjid) + ctrl = self.msg_win_mgr.get_control(peerjid, account) + if not ctrl: + ctrl = self.msg_win_mgr.get_control(jid, account) + if ctrl: + ctrl.set_audio_state('error', reason=reason) + + def handle_event_pep_config(self, account, data): + # ('PEP_CONFIG', account, (node, form)) + if 'pep_services' in self.instances[account]: + self.instances[account]['pep_services'].config(data[0], data[1]) + + def handle_event_roster_item_exchange(self, account, data): + # data = (action in [add, delete, modify], exchange_list, jid_from) + dialogs.RosterItemExchangeWindow(account, data[0], data[1], data[2]) + + def handle_event_unique_room_id_supported(self, account, data): + """ + Receive confirmation that unique_room_id are supported + """ + # ('UNIQUE_ROOM_ID_SUPPORTED', server, instance, room_id) + instance = data[1] + instance.unique_room_id_supported(data[0], data[2]) + + def handle_event_unique_room_id_unsupported(self, account, data): + # ('UNIQUE_ROOM_ID_UNSUPPORTED', server, instance) + instance = data[1] + instance.unique_room_id_error(data[0]) + + def handle_event_ssl_error(self, account, data): + # ('SSL_ERROR', account, (text, errnum, cert, sha1_fingerprint)) + server = gajim.config.get_per('accounts', account, 'hostname') + + def on_ok(is_checked): + del self.instances[account]['online_dialog']['ssl_error'] + if is_checked[0]: + # Check if cert is already in file + certs = '' + if os.path.isfile(gajim.MY_CACERTS): + f = open(gajim.MY_CACERTS) + certs = f.read() + f.close() + if data[2] in certs: + dialogs.ErrorDialog(_('Certificate Already in File'), + _('This certificate is already in file %s, so it\'s not added again.') % gajim.MY_CACERTS) + else: + f = open(gajim.MY_CACERTS, 'a') + f.write(server + '\n') + f.write(data[2] + '\n\n') + f.close() + gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1', + data[3]) + if is_checked[1]: + ignore_ssl_errors = gajim.config.get_per('accounts', account, + 'ignore_ssl_errors').split() + ignore_ssl_errors.append(str(data[1])) + gajim.config.set_per('accounts', account, 'ignore_ssl_errors', + ' '.join(ignore_ssl_errors)) + gajim.connections[account].ssl_certificate_accepted() + + def on_cancel(): + del self.instances[account]['online_dialog']['ssl_error'] + gajim.connections[account].disconnect(on_purpose=True) + self.handle_event_status(account, 'offline') + + pritext = _('Error verifying SSL certificate') + 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]} + if data[1] in (18, 27): + checktext1 = _('Add this certificate to the list of trusted certificates.\nSHA1 fingerprint of the certificate:\n%s') % data[3] + else: + checktext1 = '' + checktext2 = _('Ignore this error for this certificate.') + if 'ssl_error' in self.instances[account]['online_dialog']: + self.instances[account]['online_dialog']['ssl_error'].destroy() + self.instances[account]['online_dialog']['ssl_error'] = \ + dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, checktext1, + checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel) + + def handle_event_fingerprint_error(self, account, data): + # ('FINGERPRINT_ERROR', account, (new_fingerprint,)) + def on_yes(is_checked): + del self.instances[account]['online_dialog']['fingerprint_error'] + gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1', + data[0]) + # Reset the ignored ssl errors + gajim.config.set_per('accounts', account, 'ignore_ssl_errors', '') + gajim.connections[account].ssl_certificate_accepted() + def on_no(): + del self.instances[account]['online_dialog']['fingerprint_error'] + gajim.connections[account].disconnect(on_purpose=True) + self.handle_event_status(account, 'offline') + pritext = _('SSL certificate error') + sectext = _('It seems the SSL certificate of account %(account)s has ' + 'changed or your connection is being hacked.\nOld fingerprint: %(old)s' + '\nNew fingerprint: %(new)s\n\nDo you still want to connect and update' + ' the fingerprint of the certificate?') % {'account': account, + 'old': gajim.config.get_per('accounts', account, + 'ssl_fingerprint_sha1'), 'new': data[0]} + if 'fingerprint_error' in self.instances[account]['online_dialog']: + self.instances[account]['online_dialog']['fingerprint_error'].destroy() + self.instances[account]['online_dialog']['fingerprint_error'] = \ + dialogs.YesNoDialog(pritext, sectext, on_response_yes=on_yes, + on_response_no=on_no) + + def handle_event_plain_connection(self, account, data): + # ('PLAIN_CONNECTION', account, (connection)) + server = gajim.config.get_per('accounts', account, 'hostname') + def on_ok(is_checked): + if not is_checked[0]: + on_cancel() + return + # On cancel call del self.instances, so don't call it another time + # before + del self.instances[account]['online_dialog']['plain_connection'] + if is_checked[1]: + gajim.config.set_per('accounts', account, + 'warn_when_plaintext_connection', False) + gajim.connections[account].connection_accepted(data[0], 'plain') + def on_cancel(): + del self.instances[account]['online_dialog']['plain_connection'] + gajim.connections[account].disconnect(on_purpose=True) + self.handle_event_status(account, 'offline') + pritext = _('Insecure connection') + sectext = _('You are about to send your password on an unencrypted ' + 'connection. Are you sure you want to do that?') + checktext1 = _('Yes, I really want to connect insecurely') + checktext2 = _('Do _not ask me again') + if 'plain_connection' in self.instances[account]['online_dialog']: + self.instances[account]['online_dialog']['plain_connection'].destroy() + self.instances[account]['online_dialog']['plain_connection'] = \ + dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, + checktext1, checktext2, on_response_ok=on_ok, + on_response_cancel=on_cancel, is_modal=False) + + def handle_event_insecure_ssl_connection(self, account, data): + # ('INSECURE_SSL_CONNECTION', account, (connection, connection_type)) + server = gajim.config.get_per('accounts', account, 'hostname') + def on_ok(is_checked): + if not is_checked[0]: + on_cancel() + return + del self.instances[account]['online_dialog']['insecure_ssl'] + if is_checked[1]: + gajim.config.set_per('accounts', account, + 'warn_when_insecure_ssl_connection', False) + if gajim.connections[account].connected == 0: + # We have been disconnecting (too long time since window is opened) + # re-connect with auto-accept + gajim.connections[account].connection_auto_accepted = True + show, msg = gajim.connections[account].continue_connect_info[:2] + self.roster.send_status(account, show, msg) + return + gajim.connections[account].connection_accepted(data[0], data[1]) + def on_cancel(): + del self.instances[account]['online_dialog']['insecure_ssl'] + gajim.connections[account].disconnect(on_purpose=True) + self.handle_event_status(account, 'offline') + pritext = _('Insecure connection') + sectext = _('You are about to send your password on an insecure ' + 'connection. You should install PyOpenSSL to prevent that. Are you sure you want to do that?') + checktext1 = _('Yes, I really want to connect insecurely') + checktext2 = _('Do _not ask me again') + if 'insecure_ssl' in self.instances[account]['online_dialog']: + self.instances[account]['online_dialog']['insecure_ssl'].destroy() + self.instances[account]['online_dialog']['insecure_ssl'] = \ + dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, + checktext1, checktext2, on_response_ok=on_ok, + on_response_cancel=on_cancel, is_modal=False) + + def handle_event_pubsub_node_removed(self, account, data): + # ('PUBSUB_NODE_REMOVED', account, (jid, node)) + if 'pep_services' in self.instances[account]: + if data[0] == gajim.get_jid_from_account(account): + self.instances[account]['pep_services'].node_removed(data[1]) + + def handle_event_pubsub_node_not_removed(self, account, data): + # ('PUBSUB_NODE_NOT_REMOVED', account, (jid, node, msg)) + if data[0] == gajim.get_jid_from_account(account): + dialogs.WarningDialog(_('PEP node was not removed'), + _('PEP node %(node)s was not removed: %(message)s') % { + 'node': data[1], 'message': data[2]}) + + def handle_event_pep_received(self, account, data): + # ('PEP_RECEIVED', account, (jid, pep_type)) + jid = data[0] + pep_type = data[1] + ctrl = common.gajim.interface.msg_win_mgr.get_control(jid, account) + + if jid == common.gajim.get_jid_from_account(account): + self.roster.draw_account(account) + + if pep_type == 'nickname': + self.roster.draw_contact(jid, account) + if ctrl: + ctrl.update_ui() + win = ctrl.parent_win + win.redraw_tab(ctrl) + win.show_title() + else: + self.roster.draw_pep(jid, account, pep_type) + if ctrl: + ctrl.update_pep(pep_type) + + def handle_event_caps_received(self, account, data): + # ('CAPS_RECEIVED', account, (full_jid)) + full_jid = data[0] + pm_ctrl = gajim.interface.msg_win_mgr.get_control(full_jid, account) + if pm_ctrl and hasattr(pm_ctrl, "update_contact"): + pm_ctrl.update_contact() + + def create_core_handlers_list(self): + self.handlers = { + 'ROSTER': [self.handle_event_roster], + 'WARNING': [self.handle_event_warning], + 'ERROR': [self.handle_event_error], + 'INFORMATION': [self.handle_event_information], + 'ERROR_ANSWER': [self.handle_event_error_answer], + 'STATUS': [self.handle_event_status], + 'NEW_JID': [self.handle_event_new_jid], + 'NOTIFY': [self.handle_event_notify], + 'MSGERROR': [self.handle_event_msgerror], + 'MSGSENT': [self.handle_event_msgsent], + 'MSGNOTSENT': [self.handle_event_msgnotsent], + 'SUBSCRIBED': [self.handle_event_subscribed], + 'UNSUBSCRIBED': [self.handle_event_unsubscribed], + 'SUBSCRIBE': [self.handle_event_subscribe], + 'AGENT_REMOVED': [self.handle_event_agent_removed], + 'REGISTER_AGENT_INFO': [self.handle_event_register_agent_info], + 'AGENT_INFO_ITEMS': [self.handle_event_agent_info_items], + 'QUIT': [self.handle_event_quit], + 'ACC_OK': [self.handle_event_acc_ok], + 'MYVCARD': [self.handle_event_myvcard], + 'VCARD': [self.handle_event_vcard], + 'LAST_STATUS_TIME': [self.handle_event_last_status_time], + 'OS_INFO': [self.handle_event_os_info], + 'ENTITY_TIME': [self.handle_event_entity_time], + 'GC_NOTIFY': [self.handle_event_gc_notify], + 'GC_MSG': [self.handle_event_gc_msg], + 'GC_SUBJECT': [self.handle_event_gc_subject], + 'GC_CONFIG': [self.handle_event_gc_config], + 'GC_CONFIG_CHANGE': [self.handle_event_gc_config_change], + 'GC_INVITATION': [self.handle_event_gc_invitation], + 'GC_AFFILIATION': [self.handle_event_gc_affiliation], + 'GC_PASSWORD_REQUIRED': [self.handle_event_gc_password_required], + 'BAD_PASSPHRASE': [self.handle_event_bad_passphrase], + 'ROSTER_INFO': [self.handle_event_roster_info], + 'BOOKMARKS': [self.handle_event_bookmarks], + 'CON_TYPE': [self.handle_event_con_type], + 'CONNECTION_LOST': [self.handle_event_connection_lost], + 'FILE_REQUEST': [self.handle_event_file_request], + 'GMAIL_NOTIFY': [self.handle_event_gmail_notify], + 'FILE_REQUEST_ERROR': [self.handle_event_file_request_error], + 'FILE_SEND_ERROR': [self.handle_event_file_send_error], + 'STANZA_ARRIVED': [self.handle_event_stanza_arrived], + 'STANZA_SENT': [self.handle_event_stanza_sent], + 'HTTP_AUTH': [self.handle_event_http_auth], + 'VCARD_PUBLISHED': [self.handle_event_vcard_published], + 'VCARD_NOT_PUBLISHED': [self.handle_event_vcard_not_published], + 'ASK_NEW_NICK': [self.handle_event_ask_new_nick], + 'SIGNED_IN': [self.handle_event_signed_in], + 'METACONTACTS': [self.handle_event_metacontacts], + 'ATOM_ENTRY': [self.handle_atom_entry], + 'FAILED_DECRYPT': [self.handle_event_failed_decrypt], + 'PRIVACY_LISTS_RECEIVED': [self.handle_event_privacy_lists_received], + 'PRIVACY_LIST_RECEIVED': [self.handle_event_privacy_list_received], + 'PRIVACY_LISTS_ACTIVE_DEFAULT': \ + [self.handle_event_privacy_lists_active_default], + 'PRIVACY_LIST_REMOVED': [self.handle_event_privacy_list_removed], + 'ZC_NAME_CONFLICT': [self.handle_event_zc_name_conflict], + 'PING_SENT': [self.handle_event_ping_sent], + 'PING_REPLY': [self.handle_event_ping_reply], + 'PING_ERROR': [self.handle_event_ping_error], + 'SEARCH_FORM': [self.handle_event_search_form], + 'SEARCH_RESULT': [self.handle_event_search_result], + 'RESOURCE_CONFLICT': [self.handle_event_resource_conflict], + 'ROSTERX': [self.handle_event_roster_item_exchange], + 'PEP_CONFIG': [self.handle_event_pep_config], + 'UNIQUE_ROOM_ID_UNSUPPORTED': \ + [self.handle_event_unique_room_id_unsupported], + 'UNIQUE_ROOM_ID_SUPPORTED': [self.handle_event_unique_room_id_supported], + 'GPG_PASSWORD_REQUIRED': [self.handle_event_gpg_password_required], + 'GPG_ALWAYS_TRUST': [self.handle_event_gpg_always_trust], + 'PASSWORD_REQUIRED': [self.handle_event_password_required], + 'SSL_ERROR': [self.handle_event_ssl_error], + 'FINGERPRINT_ERROR': [self.handle_event_fingerprint_error], + 'PLAIN_CONNECTION': [self.handle_event_plain_connection], + 'INSECURE_SSL_CONNECTION': [self.handle_event_insecure_ssl_connection], + 'PUBSUB_NODE_REMOVED': [self.handle_event_pubsub_node_removed], + 'PUBSUB_NODE_NOT_REMOVED': [self.handle_event_pubsub_node_not_removed], + 'JINGLE_INCOMING': [self.handle_event_jingle_incoming], + 'JINGLE_CONNECTED': [self.handle_event_jingle_connected], + 'JINGLE_DISCONNECTED': [self.handle_event_jingle_disconnected], + 'JINGLE_ERROR': [self.handle_event_jingle_error], + 'PEP_RECEIVED': [self.handle_event_pep_received], + 'CAPS_RECEIVED': [self.handle_event_caps_received] + } + + def register_core_handlers(self): + """ + Register core handlers in Global Events Dispatcher (GED). + + This is part of rewriting whole events handling system to use GED. + """ + for event_name, event_handlers in self.handlers.iteritems(): + for event_handler in event_handlers: + gajim.ged.register_event_handler(event_name, ged.CORE, + event_handler) ################################################################################ ### Methods dealing with gajim.events ################################################################################ - def add_event(self, account, jid, type_, event_args): - """ - Add an event to the gajim.events var - """ - # We add it to the gajim.events queue - # Do we have a queue? - jid = gajim.get_jid_without_resource(jid) - no_queue = len(gajim.events.get_events(account, jid)) == 0 - # type_ can be gc-invitation file-send-error file-error file-request-error - # file-request file-completed file-stopped jingle-incoming - # event_type can be in advancedNotificationWindow.events_list - event_types = {'file-request': 'ft_request', - 'file-completed': 'ft_finished'} - event_type = event_types.get(type_) - show_in_roster = notify.get_show_in_roster(event_type, account, jid) - show_in_systray = notify.get_show_in_systray(event_type, account, jid) - event = gajim.events.create_event(type_, event_args, - show_in_roster=show_in_roster, - show_in_systray=show_in_systray) - gajim.events.add_event(account, jid, event) + def add_event(self, account, jid, type_, event_args): + """ + Add an event to the gajim.events var + """ + # We add it to the gajim.events queue + # Do we have a queue? + jid = gajim.get_jid_without_resource(jid) + no_queue = len(gajim.events.get_events(account, jid)) == 0 + # type_ can be gc-invitation file-send-error file-error file-request-error + # file-request file-completed file-stopped jingle-incoming + # event_type can be in advancedNotificationWindow.events_list + event_types = {'file-request': 'ft_request', + 'file-completed': 'ft_finished'} + event_type = event_types.get(type_) + show_in_roster = notify.get_show_in_roster(event_type, account, jid) + show_in_systray = notify.get_show_in_systray(event_type, account, jid) + event = gajim.events.create_event(type_, event_args, + show_in_roster=show_in_roster, + show_in_systray=show_in_systray) + gajim.events.add_event(account, jid, event) - self.roster.show_title() - if no_queue: # We didn't have a queue: we change icons - if not gajim.contacts.get_contact_with_highest_priority(account, jid): - if type_ == 'gc-invitation': - self.roster.add_groupchat(jid, account, status='offline') - else: - # add contact to roster ("Not In The Roster") if he is not - self.roster.add_to_not_in_the_roster(account, jid) - else: - self.roster.draw_contact(jid, account) + self.roster.show_title() + if no_queue: # We didn't have a queue: we change icons + if not gajim.contacts.get_contact_with_highest_priority(account, jid): + if type_ == 'gc-invitation': + self.roster.add_groupchat(jid, account, status='offline') + else: + # add contact to roster ("Not In The Roster") if he is not + self.roster.add_to_not_in_the_roster(account, jid) + else: + self.roster.draw_contact(jid, account) - # Select the big brother contact in roster, it's visible because it has - # events. - family = gajim.contacts.get_metacontacts_family(account, jid) - if family: - nearby_family, bb_jid, bb_account = \ - gajim.contacts.get_nearby_family_and_big_brother(family, account) - else: - bb_jid, bb_account = jid, account - self.roster.select_contact(bb_jid, bb_account) + # Select the big brother contact in roster, it's visible because it has + # events. + family = gajim.contacts.get_metacontacts_family(account, jid) + if family: + nearby_family, bb_jid, bb_account = \ + gajim.contacts.get_nearby_family_and_big_brother(family, account) + else: + bb_jid, bb_account = jid, account + self.roster.select_contact(bb_jid, bb_account) - def handle_event(self, account, fjid, type_): - w = None - ctrl = None - session = None + def handle_event(self, account, fjid, type_): + w = None + ctrl = None + session = None - resource = gajim.get_resource_from_jid(fjid) - jid = gajim.get_jid_without_resource(fjid) + resource = gajim.get_resource_from_jid(fjid) + jid = gajim.get_jid_without_resource(fjid) - if type_ in ('printed_gc_msg', 'printed_marked_gc_msg', 'gc_msg'): - w = self.msg_win_mgr.get_window(jid, account) - if jid in self.minimized_controls[account]: - self.roster.on_groupchat_maximized(None, jid, account) - return - else: - ctrl = self.msg_win_mgr.get_gc_control(jid, account) + if type_ in ('printed_gc_msg', 'printed_marked_gc_msg', 'gc_msg'): + w = self.msg_win_mgr.get_window(jid, account) + if jid in self.minimized_controls[account]: + self.roster.on_groupchat_maximized(None, jid, account) + return + else: + ctrl = self.msg_win_mgr.get_gc_control(jid, account) - elif type_ in ('printed_chat', 'chat', ''): - # '' is for log in/out notifications + elif type_ in ('printed_chat', 'chat', ''): + # '' is for log in/out notifications - if type_ != '': - event = gajim.events.get_first_event(account, fjid, type_) - if not event: - event = gajim.events.get_first_event(account, jid, type_) - if not event: - return + if type_ != '': + event = gajim.events.get_first_event(account, fjid, type_) + if not event: + event = gajim.events.get_first_event(account, jid, type_) + if not event: + return - if type_ == 'printed_chat': - ctrl = event.parameters[0] - elif type_ == 'chat': - session = event.parameters[8] - ctrl = session.control - elif type_ == '': - ctrl = self.msg_win_mgr.get_control(fjid, account) + if type_ == 'printed_chat': + ctrl = event.parameters[0] + elif type_ == 'chat': + session = event.parameters[8] + ctrl = session.control + elif type_ == '': + ctrl = self.msg_win_mgr.get_control(fjid, account) - if not ctrl: - highest_contact = gajim.contacts.get_contact_with_highest_priority( - account, jid) - # jid can have a window if this resource was lower when he sent - # message and is now higher because the other one is offline - if resource and highest_contact.resource == resource and \ - not self.msg_win_mgr.has_window(jid, account): - # remove resource of events too - gajim.events.change_jid(account, fjid, jid) - resource = None - fjid = jid - contact = None - if resource: - contact = gajim.contacts.get_contact(account, jid, resource) - if not contact: - contact = highest_contact + if not ctrl: + highest_contact = gajim.contacts.get_contact_with_highest_priority( + account, jid) + # jid can have a window if this resource was lower when he sent + # message and is now higher because the other one is offline + if resource and highest_contact.resource == resource and \ + not self.msg_win_mgr.has_window(jid, account): + # remove resource of events too + gajim.events.change_jid(account, fjid, jid) + resource = None + fjid = jid + contact = None + if resource: + contact = gajim.contacts.get_contact(account, jid, resource) + if not contact: + contact = highest_contact - ctrl = self.new_chat(contact, account, resource = resource, session = session) + ctrl = self.new_chat(contact, account, resource = resource, session = session) - gajim.last_message_time[account][jid] = 0 # long time ago + gajim.last_message_time[account][jid] = 0 # long time ago - w = ctrl.parent_win - elif type_ in ('printed_pm', 'pm'): - # assume that the most recently updated control we have for this party - # is the one that this event was in - event = gajim.events.get_first_event(account, fjid, type_) - if not event: - event = gajim.events.get_first_event(account, jid, type_) + w = ctrl.parent_win + elif type_ in ('printed_pm', 'pm'): + # assume that the most recently updated control we have for this party + # is the one that this event was in + event = gajim.events.get_first_event(account, fjid, type_) + if not event: + event = gajim.events.get_first_event(account, jid, type_) - if type_ == 'printed_pm': - ctrl = event.parameters[0] - elif type_ == 'pm': - session = event.parameters[8] + if type_ == 'printed_pm': + ctrl = event.parameters[0] + elif type_ == 'pm': + session = event.parameters[8] - if session and session.control: - ctrl = session.control - elif not ctrl: - room_jid = jid - nick = resource - gc_contact = gajim.contacts.get_gc_contact(account, room_jid, - nick) - if gc_contact: - show = gc_contact.show - else: - show = 'offline' - gc_contact = gajim.contacts.create_gc_contact( - room_jid=room_jid, account=account, name=nick, show=show) + if session and session.control: + ctrl = session.control + elif not ctrl: + room_jid = jid + nick = resource + gc_contact = gajim.contacts.get_gc_contact(account, room_jid, + nick) + if gc_contact: + show = gc_contact.show + else: + show = 'offline' + gc_contact = gajim.contacts.create_gc_contact( + room_jid=room_jid, account=account, name=nick, show=show) - if not session: - session = gajim.connections[account].make_new_session( - fjid, None, type_='pm') + if not session: + session = gajim.connections[account].make_new_session( + fjid, None, type_='pm') - self.new_private_chat(gc_contact, account, session=session) - ctrl = session.control + self.new_private_chat(gc_contact, account, session=session) + ctrl = session.control - w = ctrl.parent_win - elif type_ in ('normal', 'file-request', 'file-request-error', - 'file-send-error', 'file-error', 'file-stopped', 'file-completed', - 'jingle-incoming'): - # Get the first single message event - event = gajim.events.get_first_event(account, fjid, type_) - if not event: - # default to jid without resource - event = gajim.events.get_first_event(account, jid, type_) - if not event: - return - # Open the window - self.roster.open_event(account, jid, event) - else: - # Open the window - self.roster.open_event(account, fjid, event) - elif type_ == 'gmail': - url=gajim.connections[account].gmail_url - if url: - helpers.launch_browser_mailer('url', url) - elif type_ == 'gc-invitation': - event = gajim.events.get_first_event(account, jid, type_) - data = event.parameters - dialogs.InvitationReceivedDialog(account, data[0], jid, data[2], - data[1], data[3]) - gajim.events.remove_events(account, jid, event) - self.roster.draw_contact(jid, account) - elif type_ == 'subscription_request': - event = gajim.events.get_first_event(account, jid, type_) - data = event.parameters - dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1]) - gajim.events.remove_events(account, jid, event) - self.roster.draw_contact(jid, account) - elif type_ == 'unsubscribed': - event = gajim.events.get_first_event(account, jid, type_) - contact = event.parameters - self.show_unsubscribed_dialog(account, contact) - gajim.events.remove_events(account, jid, event) - self.roster.draw_contact(jid, account) - if w: - w.set_active_tab(ctrl) - w.window.window.focus(gtk.get_current_event_time()) - # Using isinstance here because we want to catch all derived types - if isinstance(ctrl, ChatControlBase): - tv = ctrl.conv_textview - tv.scroll_to_end() + w = ctrl.parent_win + elif type_ in ('normal', 'file-request', 'file-request-error', + 'file-send-error', 'file-error', 'file-stopped', 'file-completed', + 'jingle-incoming'): + # Get the first single message event + event = gajim.events.get_first_event(account, fjid, type_) + if not event: + # default to jid without resource + event = gajim.events.get_first_event(account, jid, type_) + if not event: + return + # Open the window + self.roster.open_event(account, jid, event) + else: + # Open the window + self.roster.open_event(account, fjid, event) + elif type_ == 'gmail': + url=gajim.connections[account].gmail_url + if url: + helpers.launch_browser_mailer('url', url) + elif type_ == 'gc-invitation': + event = gajim.events.get_first_event(account, jid, type_) + data = event.parameters + dialogs.InvitationReceivedDialog(account, data[0], jid, data[2], + data[1], data[3]) + gajim.events.remove_events(account, jid, event) + self.roster.draw_contact(jid, account) + elif type_ == 'subscription_request': + event = gajim.events.get_first_event(account, jid, type_) + data = event.parameters + dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1]) + gajim.events.remove_events(account, jid, event) + self.roster.draw_contact(jid, account) + elif type_ == 'unsubscribed': + event = gajim.events.get_first_event(account, jid, type_) + contact = event.parameters + self.show_unsubscribed_dialog(account, contact) + gajim.events.remove_events(account, jid, event) + self.roster.draw_contact(jid, account) + if w: + w.set_active_tab(ctrl) + w.window.window.focus(gtk.get_current_event_time()) + # Using isinstance here because we want to catch all derived types + if isinstance(ctrl, ChatControlBase): + tv = ctrl.conv_textview + tv.scroll_to_end() ################################################################################ ### Methods dealing with emoticons ################################################################################ - def image_is_ok(self, image): - if not os.path.exists(image): - return False - img = gtk.Image() - try: - img.set_from_file(image) - except Exception: - return False - t = img.get_storage_type() - if t != gtk.IMAGE_PIXBUF and t != gtk.IMAGE_ANIMATION: - return False - return True + def image_is_ok(self, image): + if not os.path.exists(image): + return False + img = gtk.Image() + try: + img.set_from_file(image) + except Exception: + return False + t = img.get_storage_type() + if t != gtk.IMAGE_PIXBUF and t != gtk.IMAGE_ANIMATION: + return False + return True - @property - def basic_pattern_re(self): - try: - return self._basic_pattern_re - except AttributeError: - self._basic_pattern_re = re.compile(self.basic_pattern, re.IGNORECASE) - return self._basic_pattern_re + @property + def basic_pattern_re(self): + try: + return self._basic_pattern_re + except AttributeError: + self._basic_pattern_re = re.compile(self.basic_pattern, re.IGNORECASE) + return self._basic_pattern_re - @property - def emot_and_basic_re(self): - try: - return self._emot_and_basic_re - except AttributeError: - self._emot_and_basic_re = re.compile(self.emot_and_basic, - re.IGNORECASE + re.UNICODE) - return self._emot_and_basic_re + @property + def emot_and_basic_re(self): + try: + return self._emot_and_basic_re + except AttributeError: + self._emot_and_basic_re = re.compile(self.emot_and_basic, + re.IGNORECASE + re.UNICODE) + return self._emot_and_basic_re - @property - def sth_at_sth_dot_sth_re(self): - try: - return self._sth_at_sth_dot_sth_re - except AttributeError: - self._sth_at_sth_dot_sth_re = re.compile(self.sth_at_sth_dot_sth) - return self._sth_at_sth_dot_sth_re + @property + def sth_at_sth_dot_sth_re(self): + try: + return self._sth_at_sth_dot_sth_re + except AttributeError: + self._sth_at_sth_dot_sth_re = re.compile(self.sth_at_sth_dot_sth) + return self._sth_at_sth_dot_sth_re - @property - def invalid_XML_chars_re(self): - try: - return self._invalid_XML_chars_re - except AttributeError: - self._invalid_XML_chars_re = re.compile(self.invalid_XML_chars) - return self._invalid_XML_chars_re + @property + def invalid_XML_chars_re(self): + try: + return self._invalid_XML_chars_re + except AttributeError: + self._invalid_XML_chars_re = re.compile(self.invalid_XML_chars) + return self._invalid_XML_chars_re - def make_regexps(self): - # regexp meta characters are: . ^ $ * + ? { } [ ] \ | ( ) - # one escapes the metachars with \ - # \S matches anything but ' ' '\t' '\n' '\r' '\f' and '\v' - # \s matches any whitespace character - # \w any alphanumeric character - # \W any non-alphanumeric character - # \b means word boundary. This is a zero-width assertion that - # matches only at the beginning or end of a word. - # ^ matches at the beginning of lines - # - # * means 0 or more times - # + means 1 or more times - # ? means 0 or 1 time - # | means or - # [^*] anything but '*' (inside [] you don't have to escape metachars) - # [^\s*] anything but whitespaces and '*' - # (? in the matching string don't match ? or ) etc.. if at the end - # so http://be) will match http://be and http://be)be) will match http://be)be + def make_regexps(self): + # regexp meta characters are: . ^ $ * + ? { } [ ] \ | ( ) + # one escapes the metachars with \ + # \S matches anything but ' ' '\t' '\n' '\r' '\f' and '\v' + # \s matches any whitespace character + # \w any alphanumeric character + # \W any non-alphanumeric character + # \b means word boundary. This is a zero-width assertion that + # matches only at the beginning or end of a word. + # ^ matches at the beginning of lines + # + # * means 0 or more times + # + means 1 or more times + # ? means 0 or 1 time + # | means or + # [^*] anything but '*' (inside [] you don't have to escape metachars) + # [^\s*] anything but whitespaces and '*' + # (? in the matching string don't match ? or ) etc.. if at the end + # so http://be) will match http://be and http://be)be) will match http://be)be - legacy_prefixes = r"((?<=\()(www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+(?=\)))"\ - r"|((www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\ - r"\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)" - # NOTE: it's ok to catch www.gr such stuff exist! + legacy_prefixes = r"((?<=\()(www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+(?=\)))"\ + r"|((www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\ + r"\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)" + # NOTE: it's ok to catch www.gr such stuff exist! - #FIXME: recognize xmpp: and treat it specially - links = r"((?<=\()[A-Za-z][A-Za-z0-9\+\.\-]*:"\ - r"([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\ - r"(?=\)))|([A-Za-z][A-Za-z0-9\+\.\-]*:([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)" + #FIXME: recognize xmpp: and treat it specially + links = r"((?<=\()[A-Za-z][A-Za-z0-9\+\.\-]*:"\ + r"([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\ + r"(?=\)))|([A-Za-z][A-Za-z0-9\+\.\-]*:([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)" - #2nd one: at_least_one_char@at_least_one_char.at_least_one_char - mail = r'\bmailto:\S*[^\s\W]|' r'\b\S+@\S+\.\S*[^\s\W]' + #2nd one: at_least_one_char@at_least_one_char.at_least_one_char + mail = r'\bmailto:\S*[^\s\W]|' r'\b\S+@\S+\.\S*[^\s\W]' - #detects eg. *b* *bold* *bold bold* test *bold* *bold*! (*bold*) - #doesn't detect (it's a feature :P) * bold* *bold * * bold * test*bold* - formatting = r'|(?> sys.stderr, err_str - # it is good to notify the user - # in case he or she cannot see the output of the console - dialogs.ErrorDialog(_('Could not save your settings and preferences'), - err_str) - sys.exit() - - def save_avatar_files(self, jid, photo, puny_nick = None, local = False): - """ - Save 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 - """ - puny_jid = helpers.sanitize_filename(jid) - path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) - if puny_nick: - path_to_file = os.path.join(path_to_file, puny_nick) - # remove old avatars - for typ in ('jpeg', 'png'): - if local: - path_to_original_file = path_to_file + '_local'+ '.' + typ - else: - path_to_original_file = path_to_file + '.' + typ - if os.path.isfile(path_to_original_file): - os.remove(path_to_original_file) - if local and photo: - pixbuf = photo - typ = 'png' - extension = '_local.png' # save local avatars as png file - else: - pixbuf, typ = gtkgui_helpers.get_pixbuf_from_data(photo, want_type = True) - if pixbuf is None: - return - extension = '.' + typ - if typ not in ('jpeg', 'png'): - gajim.log.debug('gtkpixbuf cannot save other than jpeg and png formats. saving %s\'avatar as png file (originaly %s)' % (jid, typ)) - typ = 'png' - extension = '.png' - path_to_original_file = path_to_file + extension - try: - pixbuf.save(path_to_original_file, typ) - except Exception, e: - log.error('Error writing avatar file %s: %s' % (path_to_original_file, - str(e))) - # Generate and save the resized, color avatar - pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'notification') - if pixbuf: - path_to_normal_file = path_to_file + '_notif_size_colored' + extension - try: - pixbuf.save(path_to_normal_file, 'png') - except Exception, e: - log.error('Error writing avatar file %s: %s' % \ - (path_to_original_file, str(e))) - # Generate and save the resized, black and white avatar - bwbuf = gtkgui_helpers.get_scaled_pixbuf( - gtkgui_helpers.make_pixbuf_grayscale(pixbuf), 'notification') - if bwbuf: - path_to_bw_file = path_to_file + '_notif_size_bw' + extension - try: - bwbuf.save(path_to_bw_file, 'png') - except Exception, e: - log.error('Error writing avatar file %s: %s' % \ - (path_to_original_file, str(e))) - - def remove_avatar_files(self, jid, puny_nick = None, local = False): - """ - Remove avatar files of a jid - """ - puny_jid = helpers.sanitize_filename(jid) - path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) - if puny_nick: - path_to_file = os.path.join(path_to_file, puny_nick) - for ext in ('.jpeg', '.png'): - if local: - ext = '_local' + ext - path_to_original_file = path_to_file + ext - if os.path.isfile(path_to_file + ext): - os.remove(path_to_file + ext) - if os.path.isfile(path_to_file + '_notif_size_colored' + ext): - os.remove(path_to_file + '_notif_size_colored' + ext) - if os.path.isfile(path_to_file + '_notif_size_bw' + ext): - os.remove(path_to_file + '_notif_size_bw' + ext) - - def auto_join_bookmarks(self, account): - """ - Autojoin bookmarked GCs that have 'auto join' on for this account - """ - for bm in gajim.connections[account].bookmarks: - if bm['autojoin'] in ('1', 'true'): - jid = bm['jid'] - # Only join non-opened groupchats. Opened one are already - # auto-joined on re-connection - if not jid in gajim.gc_connected[account]: - # we are not already connected - minimize = bm['minimize'] in ('1', 'true') - gajim.interface.join_gc_room(account, jid, bm['nick'], - bm['password'], minimize = minimize) - elif jid in self.minimized_controls[account]: - # more or less a hack: - # On disconnect the minimized gc contact instances - # were set to offline. Reconnect them to show up in the roster. - self.roster.add_groupchat(jid, account) - - def add_gc_bookmark(self, account, name, jid, autojoin, minimize, password, - nick): - """ - Add a bookmark for this account, sorted in bookmark list - """ - bm = { - 'name': name, - 'jid': jid, - 'autojoin': autojoin, - 'minimize': minimize, - 'password': password, - 'nick': nick - } - place_found = False - index = 0 - # check for duplicate entry and respect alpha order - for bookmark in gajim.connections[account].bookmarks: - if bookmark['jid'] == bm['jid']: - dialogs.ErrorDialog( - _('Bookmark already set'), - _('Group Chat "%s" is already in your bookmarks.') % bm['jid']) - return - if bookmark['name'] > bm['name']: - place_found = True - break - index += 1 - if place_found: - gajim.connections[account].bookmarks.insert(index, bm) - else: - gajim.connections[account].bookmarks.append(bm) - gajim.connections[account].store_bookmarks() - self.roster.set_actions_menu_needs_rebuild() - dialogs.InformationDialog( - _('Bookmark has been added successfully'), - _('You can manage your bookmarks via Actions menu in your roster.')) - - - # does JID exist only within a groupchat? - def is_pm_contact(self, fjid, account): - bare_jid = gajim.get_jid_without_resource(fjid) - - gc_ctrl = self.msg_win_mgr.get_gc_control(bare_jid, account) - - if not gc_ctrl and \ - bare_jid in self.minimized_controls[account]: - gc_ctrl = self.minimized_controls[account][bare_jid] - - return gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC - - def create_ipython_window(self): - try: - from ipython_view import IPythonView - except ImportError: - print 'ipython_view not found' - return - import pango - - if os.name == 'nt': - font = 'Lucida Console 9' - else: - font = 'Luxi Mono 10' - - window = gtk.Window() - window.set_size_request(750,550) - window.set_resizable(True) - sw = gtk.ScrolledWindow() - sw.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC) - view = IPythonView() - view.modify_font(pango.FontDescription(font)) - view.set_wrap_mode(gtk.WRAP_CHAR) - sw.add(view) - window.add(sw) - window.show_all() - def on_delete(win, event): - win.hide() - return True - window.connect('delete_event',on_delete) - view.updateNamespace({'gajim': gajim}) - gajim.ipython_window = window - - def run(self): - if gajim.config.get('trayicon') != 'never': - self.show_systray() - - self.roster = roster_window.RosterWindow() - for account in gajim.connections: - gajim.connections[account].load_roster_from_db() - - # get instances for windows/dialogs that will show_all()/hide() - self.instances['file_transfers'] = dialogs.FileTransfersWindow() - - gobject.timeout_add(100, self.autoconnect) - timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT - if in_seconds: - gobject.timeout_add_seconds(timeout, self.process_connections) - else: - gobject.timeout_add(timeout, self.process_connections) - gobject.timeout_add_seconds(gajim.config.get( - 'check_idle_every_foo_seconds'), self.read_sleepy) - - # when using libasyncns we need to process resolver in regular intervals - if resolver.USE_LIBASYNCNS: - gobject.timeout_add(200, gajim.resolver.process) - - # setup the indicator - if gajim.HAVE_INDICATOR: - notify.setup_indicator_server() - - def remote_init(): - if gajim.config.get('remote_control'): - try: - import remote_control - self.remote_ctrl = remote_control.Remote() - except Exception: - pass - gobject.timeout_add_seconds(5, remote_init) - - # Creating plugin manager - import plugins - gajim.plugin_manager = plugins.PluginManager() - - def __init__(self): - gajim.interface = self - gajim.thread_interface = ThreadInterface - # This is the manager and factory of message windows set by the module - self.msg_win_mgr = None - self.jabber_state_images = {'16': {}, '32': {}, 'opened': {}, - 'closed': {}} - self.emoticons_menu = None - # handler when an emoticon is clicked in emoticons_menu - self.emoticon_menuitem_clicked = None - self.minimized_controls = {} - self.status_sent_to_users = {} - self.status_sent_to_groups = {} - self.gpg_passphrase = {} - self.pass_dialog = {} - self.default_colors = { - 'inmsgcolor': gajim.config.get('inmsgcolor'), - 'outmsgcolor': gajim.config.get('outmsgcolor'), - 'inmsgtxtcolor': gajim.config.get('inmsgtxtcolor'), - 'outmsgtxtcolor': gajim.config.get('outmsgtxtcolor'), - 'statusmsgcolor': gajim.config.get('statusmsgcolor'), - 'urlmsgcolor': gajim.config.get('urlmsgcolor'), - } - - cfg_was_read = parser.read() - - from common import latex - gajim.HAVE_LATEX = gajim.config.get('use_latex') and \ - latex.check_for_latex_support() - - gajim.logger.reset_shown_unread_messages() - # override logging settings from config (don't take care of '-q' option) - if gajim.config.get('verbose'): - logging_helpers.set_verbose() - - # Is Gajim default app? - if os.name != 'nt' and gajim.config.get('check_if_gajim_is_default'): - gtkgui_helpers.possibly_set_gajim_as_xmpp_handler() - - for account in gajim.config.get_per('accounts'): - if gajim.config.get_per('accounts', account, 'is_zeroconf'): - gajim.ZEROCONF_ACC_NAME = account - break - # Is gnome configured to activate row on single click ? - try: - import gconf - client = gconf.client_get_default() - click_policy = client.get_string( - '/apps/nautilus/preferences/click_policy') - if click_policy == 'single': - gajim.single_click = True - except Exception: - pass - # add default status messages if there is not in the config file - if len(gajim.config.get_per('statusmsg')) == 0: - default = gajim.config.statusmsg_default - for msg in default: - gajim.config.add_per('statusmsg', msg) - gajim.config.set_per('statusmsg', msg, 'message', default[msg][0]) - gajim.config.set_per('statusmsg', msg, 'activity', default[msg][1]) - gajim.config.set_per('statusmsg', msg, 'subactivity', - default[msg][2]) - gajim.config.set_per('statusmsg', msg, 'activity_text', - default[msg][3]) - gajim.config.set_per('statusmsg', msg, 'mood', default[msg][4]) - gajim.config.set_per('statusmsg', msg, 'mood_text', default[msg][5]) - #add default themes if there is not in the config file - theme = gajim.config.get('roster_theme') - if not theme in gajim.config.get_per('themes'): - gajim.config.set('roster_theme', _('default')) - if len(gajim.config.get_per('themes')) == 0: - d = ['accounttextcolor', 'accountbgcolor', 'accountfont', - 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont', - 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', - 'contactfont', 'contactfontattrs', 'bannertextcolor', - 'bannerbgcolor'] - - default = gajim.config.themes_default - for theme_name in default: - gajim.config.add_per('themes', theme_name) - theme = default[theme_name] - for o in d: - gajim.config.set_per('themes', theme_name, o, - theme[d.index(o)]) - - if gajim.config.get('autodetect_browser_mailer') or not cfg_was_read: - gtkgui_helpers.autodetect_browser_mailer() - - gajim.idlequeue = idlequeue.get_idlequeue() - # resolve and keep current record of resolved hosts - gajim.resolver = resolver.get_resolver(gajim.idlequeue) - gajim.socks5queue = socks5.SocksQueue(gajim.idlequeue, - self.handle_event_file_rcv_completed, - self.handle_event_file_progress, - self.handle_event_file_error) - gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue) - gajim.default_session_type = ChatControlSession - - # Creating Global Events Dispatcher - from common import ged - gajim.ged = ged.GlobalEventsDispatcher() - # Creating Network Events Controller - from common import nec - gajim.nec = nec.NetworkEventsController() - self.create_core_handlers_list() - self.register_core_handlers() - - if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \ - and gajim.HAVE_ZEROCONF: - gajim.connections[gajim.ZEROCONF_ACC_NAME] = \ - connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME) - for account in gajim.config.get_per('accounts'): - if not gajim.config.get_per('accounts', account, 'is_zeroconf') and \ - gajim.config.get_per('accounts', account, 'active'): - gajim.connections[account] = common.connection.Connection(account) - - # gtk hooks - gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail') - gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url') - gtk.link_button_set_uri_hook(self.on_launch_browser_mailer, 'url') - - self.instances = {} - - for a in gajim.connections: - self.instances[a] = {'infos': {}, 'disco': {}, 'gc_config': {}, - 'search': {}, 'online_dialog': {}} - # online_dialog contains all dialogs that have a meaning only when we - # are not disconnected - self.minimized_controls[a] = {} - gajim.contacts.add_account(a) - gajim.groups[a] = {} - gajim.gc_connected[a] = {} - gajim.automatic_rooms[a] = {} - gajim.newly_added[a] = [] - gajim.to_be_removed[a] = [] - gajim.nicks[a] = gajim.config.get_per('accounts', a, 'name') - gajim.block_signed_in_notifications[a] = True - gajim.sleeper_state[a] = 0 - gajim.encrypted_chats[a] = [] - gajim.last_message_time[a] = {} - gajim.status_before_autoaway[a] = '' - gajim.transport_avatar[a] = {} - gajim.gajim_optional_features[a] = [] - gajim.caps_hash[a] = '' - - helpers.update_optional_features() - # prepopulate data which we are sure of; note: we do not log these info - for account in gajim.connections: - gajimcaps = caps_cache.capscache[('sha-1', gajim.caps_hash[account])] - gajimcaps.identities = [gajim.gajim_identity] - gajimcaps.features = gajim.gajim_common_features + \ - gajim.gajim_optional_features[account] - - self.remote_ctrl = None - - if gajim.config.get('networkmanager_support') and dbus_support.supported: - import network_manager_listener - - # Handle gnome screensaver - if dbus_support.supported: - def gnome_screensaver_ActiveChanged_cb(active): - if not active: - for account in gajim.connections: - if gajim.sleeper_state[account] == 'autoaway-forced': - # We came back online ofter gnome-screensaver autoaway - self.roster.send_status(account, 'online', - gajim.status_before_autoaway[account]) - gajim.status_before_autoaway[account] = '' - gajim.sleeper_state[account] = 'online' - return - if not gajim.config.get('autoaway'): - # Don't go auto away if user disabled the option - return - for account in gajim.connections: - if account not in gajim.sleeper_state or \ - not gajim.sleeper_state[account]: - continue - if gajim.sleeper_state[account] == 'online': - # we save out online status - gajim.status_before_autoaway[account] = \ - gajim.connections[account].status - # we go away (no auto status) [we pass True to auto param] - auto_message = gajim.config.get('autoaway_message') - if not auto_message: - auto_message = gajim.connections[account].status - else: - auto_message = auto_message.replace('$S','%(status)s') - auto_message = auto_message.replace('$T','%(time)s') - auto_message = auto_message % { - 'status': gajim.status_before_autoaway[account], - 'time': gajim.config.get('autoxatime') - } - self.roster.send_status(account, 'away', auto_message, - auto=True) - gajim.sleeper_state[account] = 'autoaway-forced' - - try: - bus = dbus.SessionBus() - bus.add_signal_receiver(gnome_screensaver_ActiveChanged_cb, - 'ActiveChanged', 'org.gnome.ScreenSaver') - except Exception: - pass - - self.show_vcard_when_connect = [] - - self.sleeper = common.sleepy.Sleepy( - gajim.config.get('autoawaytime') * 60, # make minutes to seconds - gajim.config.get('autoxatime') * 60) - - gtkgui_helpers.make_jabber_state_images() - - self.systray_enabled = False - - import statusicon - self.systray = statusicon.StatusIcon() - - pix = gtkgui_helpers.get_icon_pixmap('gajim', 32) - # set the icon to all windows - gtk.window_set_default_icon(pix) - - self.init_emoticons() - self.make_regexps() - - # get transports type from DB - gajim.transport_type = gajim.logger.get_transports_type() - - # test is dictionnary is present for speller - if gajim.config.get('use_speller'): - lang = gajim.config.get('speller_language') - if not lang: - lang = gajim.LANG - tv = gtk.TextView() - try: - import gtkspell - spell = gtkspell.Spell(tv, lang) - except (ImportError, TypeError, RuntimeError, OSError): - dialogs.AspellDictError(lang) - - if gajim.config.get('soundplayer') == '': - # only on first time Gajim starts - commands = ('aplay', 'play', 'esdplay', 'artsplay', 'ossplay') - for command in commands: - if helpers.is_in_path(command): - if command == 'aplay': - command += ' -q' - gajim.config.set('soundplayer', command) - break - - self.last_ftwindow_update = 0 - - self.music_track_changed_signal = None + def _change_awn_icon_status(self, status): + if not dbus_support.supported: + # do nothing if user doesn't have D-Bus bindings + return + try: + bus = dbus.SessionBus() + if not 'com.google.code.Awn' in bus.list_names(): + # Awn is not installed + return + except Exception: + return + iconset = gajim.config.get('iconset') + prefix = os.path.join(helpers.get_iconset_path(iconset), '32x32') + if status in ('chat', 'away', 'xa', 'dnd', 'invisible', 'offline'): + status = status + '.png' + elif status == 'online': + prefix = '' + status = gtkgui_helpers.get_icon_path('gajim', 32) + path = os.path.join(prefix, status) + try: + obj = bus.get_object('com.google.code.Awn', '/com/google/code/Awn') + awn = dbus.Interface(obj, 'com.google.code.Awn') + awn.SetTaskIconByName('Gajim', os.path.abspath(path)) + except Exception: + pass + + def enable_music_listener(self): + listener = MusicTrackListener.get() + if not self.music_track_changed_signal: + self.music_track_changed_signal = listener.connect( + 'music-track-changed', self.music_track_changed) + track = listener.get_playing_track() + self.music_track_changed(listener, track) + + def disable_music_listener(self): + listener = MusicTrackListener.get() + listener.disconnect(self.music_track_changed_signal) + self.music_track_changed_signal = None + + def music_track_changed(self, unused_listener, music_track_info, + account=None): + if not account: + accounts = gajim.connections.keys() + else: + accounts = [account] + + is_paused = hasattr(music_track_info, 'paused') and \ + music_track_info.paused == 0 + if not music_track_info or is_paused: + artist = title = source = '' + else: + artist = music_track_info.artist + title = music_track_info.title + source = music_track_info.album + for acct in accounts: + if not gajim.account_is_connected(acct): + continue + if not gajim.config.get_per('accounts', acct, 'publish_tune'): + continue + if gajim.connections[acct].music_track_info == music_track_info: + continue + gajim.connections[acct].send_tune(artist, title, source) + gajim.connections[acct].music_track_info = music_track_info + + def get_bg_fg_colors(self): + def gdkcolor_to_rgb (gdkcolor): + return [c / 65535. for c in (gdkcolor.red, gdkcolor.green, + gdkcolor.blue)] + + def format_rgb (r, g, b): + return ' '.join([str(c) for c in ('rgb', r, g, b)]) + + def format_gdkcolor (gdkcolor): + return format_rgb (*gdkcolor_to_rgb (gdkcolor)) + + # get style colors and create string for dvipng + dummy = gtk.Invisible() + dummy.ensure_style() + style = dummy.get_style() + bg_str = format_gdkcolor(style.base[gtk.STATE_NORMAL]) + fg_str = format_gdkcolor(style.text[gtk.STATE_NORMAL]) + return (bg_str, fg_str) + + def read_sleepy(self): + """ + Check idle status and change that status if needed + """ + if not self.sleeper.poll(): + # idle detection is not supported in that OS + return False # stop looping in vain + state = self.sleeper.getState() + for account in gajim.connections: + if account not in gajim.sleeper_state or \ + not gajim.sleeper_state[account]: + continue + if state == common.sleepy.STATE_AWAKE and \ + gajim.sleeper_state[account] in ('autoaway', 'autoxa'): + # we go online + self.roster.send_status(account, 'online', + gajim.status_before_autoaway[account]) + gajim.status_before_autoaway[account] = '' + gajim.sleeper_state[account] = 'online' + elif state == common.sleepy.STATE_AWAY and \ + gajim.sleeper_state[account] == 'online' and \ + gajim.config.get('autoaway'): + # we save out online status + gajim.status_before_autoaway[account] = \ + gajim.connections[account].status + # we go away (no auto status) [we pass True to auto param] + auto_message = gajim.config.get('autoaway_message') + if not auto_message: + auto_message = gajim.connections[account].status + else: + auto_message = auto_message.replace('$S', '%(status)s') + auto_message = auto_message.replace('$T', '%(time)s') + auto_message = auto_message % { + 'status': gajim.status_before_autoaway[account], + 'time': gajim.config.get('autoawaytime') + } + self.roster.send_status(account, 'away', auto_message, auto=True) + gajim.sleeper_state[account] = 'autoaway' + elif state == common.sleepy.STATE_XA and \ + gajim.sleeper_state[account] in ('online', 'autoaway', + 'autoaway-forced') and gajim.config.get('autoxa'): + # we go extended away [we pass True to auto param] + auto_message = gajim.config.get('autoxa_message') + if not auto_message: + auto_message = gajim.connections[account].status + else: + auto_message = auto_message.replace('$S', '%(status)s') + auto_message = auto_message.replace('$T', '%(time)s') + auto_message = auto_message % { + 'status': gajim.status_before_autoaway[account], + 'time': gajim.config.get('autoxatime') + } + self.roster.send_status(account, 'xa', auto_message, auto=True) + gajim.sleeper_state[account] = 'autoxa' + return True # renew timeout (loop for ever) + + def autoconnect(self): + """ + Auto connect at startup + """ + # dict of account that want to connect sorted by status + shows = {} + for a in gajim.connections: + if gajim.config.get_per('accounts', a, 'autoconnect'): + if gajim.config.get_per('accounts', a, 'restore_last_status'): + self.roster.send_status(a, gajim.config.get_per('accounts', a, + 'last_status'), helpers.from_one_line(gajim.config.get_per( + 'accounts', a, 'last_status_msg'))) + continue + show = gajim.config.get_per('accounts', a, 'autoconnect_as') + if not show in gajim.SHOW_LIST: + continue + if not show in shows: + shows[show] = [a] + else: + shows[show].append(a) + def on_message(message, pep_dict): + if message is None: + return + for a in shows[show]: + self.roster.send_status(a, show, message) + self.roster.send_pep(a, pep_dict) + for show in shows: + message = self.roster.get_status_message(show, on_message) + return False + + def show_systray(self): + self.systray_enabled = True + self.systray.show_icon() + + def hide_systray(self): + self.systray_enabled = False + self.systray.hide_icon() + + def on_launch_browser_mailer(self, widget, url, kind): + helpers.launch_browser_mailer(kind, url) + + def process_connections(self): + """ + Called each foo (200) miliseconds. Check for idlequeue timeouts + """ + try: + gajim.idlequeue.process() + except Exception: + # Otherwise, an exception will stop our loop + timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT + if in_seconds: + gobject.timeout_add_seconds(timeout, self.process_connections) + else: + gobject.timeout_add(timeout, self.process_connections) + raise + return True # renew timeout (loop for ever) + + def save_config(self): + err_str = parser.write() + if err_str is not None: + print >> sys.stderr, err_str + # it is good to notify the user + # in case he or she cannot see the output of the console + dialogs.ErrorDialog(_('Could not save your settings and preferences'), + err_str) + sys.exit() + + def save_avatar_files(self, jid, photo, puny_nick = None, local = False): + """ + Save 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 + """ + puny_jid = helpers.sanitize_filename(jid) + path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) + if puny_nick: + path_to_file = os.path.join(path_to_file, puny_nick) + # remove old avatars + for typ in ('jpeg', 'png'): + if local: + path_to_original_file = path_to_file + '_local'+ '.' + typ + else: + path_to_original_file = path_to_file + '.' + typ + if os.path.isfile(path_to_original_file): + os.remove(path_to_original_file) + if local and photo: + pixbuf = photo + typ = 'png' + extension = '_local.png' # save local avatars as png file + else: + pixbuf, typ = gtkgui_helpers.get_pixbuf_from_data(photo, want_type = True) + if pixbuf is None: + return + extension = '.' + typ + if typ not in ('jpeg', 'png'): + gajim.log.debug('gtkpixbuf cannot save other than jpeg and png formats. saving %s\'avatar as png file (originaly %s)' % (jid, typ)) + typ = 'png' + extension = '.png' + path_to_original_file = path_to_file + extension + try: + pixbuf.save(path_to_original_file, typ) + except Exception, e: + log.error('Error writing avatar file %s: %s' % (path_to_original_file, + str(e))) + # Generate and save the resized, color avatar + pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'notification') + if pixbuf: + path_to_normal_file = path_to_file + '_notif_size_colored' + extension + try: + pixbuf.save(path_to_normal_file, 'png') + except Exception, e: + log.error('Error writing avatar file %s: %s' % \ + (path_to_original_file, str(e))) + # Generate and save the resized, black and white avatar + bwbuf = gtkgui_helpers.get_scaled_pixbuf( + gtkgui_helpers.make_pixbuf_grayscale(pixbuf), 'notification') + if bwbuf: + path_to_bw_file = path_to_file + '_notif_size_bw' + extension + try: + bwbuf.save(path_to_bw_file, 'png') + except Exception, e: + log.error('Error writing avatar file %s: %s' % \ + (path_to_original_file, str(e))) + + def remove_avatar_files(self, jid, puny_nick = None, local = False): + """ + Remove avatar files of a jid + """ + puny_jid = helpers.sanitize_filename(jid) + path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) + if puny_nick: + path_to_file = os.path.join(path_to_file, puny_nick) + for ext in ('.jpeg', '.png'): + if local: + ext = '_local' + ext + path_to_original_file = path_to_file + ext + if os.path.isfile(path_to_file + ext): + os.remove(path_to_file + ext) + if os.path.isfile(path_to_file + '_notif_size_colored' + ext): + os.remove(path_to_file + '_notif_size_colored' + ext) + if os.path.isfile(path_to_file + '_notif_size_bw' + ext): + os.remove(path_to_file + '_notif_size_bw' + ext) + + def auto_join_bookmarks(self, account): + """ + Autojoin bookmarked GCs that have 'auto join' on for this account + """ + for bm in gajim.connections[account].bookmarks: + if bm['autojoin'] in ('1', 'true'): + jid = bm['jid'] + # Only join non-opened groupchats. Opened one are already + # auto-joined on re-connection + if not jid in gajim.gc_connected[account]: + # we are not already connected + minimize = bm['minimize'] in ('1', 'true') + gajim.interface.join_gc_room(account, jid, bm['nick'], + bm['password'], minimize = minimize) + elif jid in self.minimized_controls[account]: + # more or less a hack: + # On disconnect the minimized gc contact instances + # were set to offline. Reconnect them to show up in the roster. + self.roster.add_groupchat(jid, account) + + def add_gc_bookmark(self, account, name, jid, autojoin, minimize, password, + nick): + """ + Add a bookmark for this account, sorted in bookmark list + """ + bm = { + 'name': name, + 'jid': jid, + 'autojoin': autojoin, + 'minimize': minimize, + 'password': password, + 'nick': nick + } + place_found = False + index = 0 + # check for duplicate entry and respect alpha order + for bookmark in gajim.connections[account].bookmarks: + if bookmark['jid'] == bm['jid']: + dialogs.ErrorDialog( + _('Bookmark already set'), + _('Group Chat "%s" is already in your bookmarks.') % bm['jid']) + return + if bookmark['name'] > bm['name']: + place_found = True + break + index += 1 + if place_found: + gajim.connections[account].bookmarks.insert(index, bm) + else: + gajim.connections[account].bookmarks.append(bm) + gajim.connections[account].store_bookmarks() + self.roster.set_actions_menu_needs_rebuild() + dialogs.InformationDialog( + _('Bookmark has been added successfully'), + _('You can manage your bookmarks via Actions menu in your roster.')) + + + # does JID exist only within a groupchat? + def is_pm_contact(self, fjid, account): + bare_jid = gajim.get_jid_without_resource(fjid) + + gc_ctrl = self.msg_win_mgr.get_gc_control(bare_jid, account) + + if not gc_ctrl and \ + bare_jid in self.minimized_controls[account]: + gc_ctrl = self.minimized_controls[account][bare_jid] + + return gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC + + def create_ipython_window(self): + try: + from ipython_view import IPythonView + except ImportError: + print 'ipython_view not found' + return + import pango + + if os.name == 'nt': + font = 'Lucida Console 9' + else: + font = 'Luxi Mono 10' + + window = gtk.Window() + window.set_size_request(750, 550) + window.set_resizable(True) + sw = gtk.ScrolledWindow() + sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + view = IPythonView() + view.modify_font(pango.FontDescription(font)) + view.set_wrap_mode(gtk.WRAP_CHAR) + sw.add(view) + window.add(sw) + window.show_all() + def on_delete(win, event): + win.hide() + return True + window.connect('delete_event', on_delete) + view.updateNamespace({'gajim': gajim}) + gajim.ipython_window = window + + def run(self): + if gajim.config.get('trayicon') != 'never': + self.show_systray() + + self.roster = roster_window.RosterWindow() + for account in gajim.connections: + gajim.connections[account].load_roster_from_db() + + # get instances for windows/dialogs that will show_all()/hide() + self.instances['file_transfers'] = dialogs.FileTransfersWindow() + + gobject.timeout_add(100, self.autoconnect) + timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT + if in_seconds: + gobject.timeout_add_seconds(timeout, self.process_connections) + else: + gobject.timeout_add(timeout, self.process_connections) + gobject.timeout_add_seconds(gajim.config.get( + 'check_idle_every_foo_seconds'), self.read_sleepy) + + # when using libasyncns we need to process resolver in regular intervals + if resolver.USE_LIBASYNCNS: + gobject.timeout_add(200, gajim.resolver.process) + + # setup the indicator + if gajim.HAVE_INDICATOR: + notify.setup_indicator_server() + + def remote_init(): + if gajim.config.get('remote_control'): + try: + import remote_control + self.remote_ctrl = remote_control.Remote() + except Exception: + pass + gobject.timeout_add_seconds(5, remote_init) + + # Creating plugin manager + import plugins + gajim.plugin_manager = plugins.PluginManager() + + def __init__(self): + gajim.interface = self + gajim.thread_interface = ThreadInterface + # This is the manager and factory of message windows set by the module + self.msg_win_mgr = None + self.jabber_state_images = {'16': {}, '32': {}, 'opened': {}, + 'closed': {}} + self.emoticons_menu = None + # handler when an emoticon is clicked in emoticons_menu + self.emoticon_menuitem_clicked = None + self.minimized_controls = {} + self.status_sent_to_users = {} + self.status_sent_to_groups = {} + self.gpg_passphrase = {} + self.pass_dialog = {} + self.default_colors = { + 'inmsgcolor': gajim.config.get('inmsgcolor'), + 'outmsgcolor': gajim.config.get('outmsgcolor'), + 'inmsgtxtcolor': gajim.config.get('inmsgtxtcolor'), + 'outmsgtxtcolor': gajim.config.get('outmsgtxtcolor'), + 'statusmsgcolor': gajim.config.get('statusmsgcolor'), + 'urlmsgcolor': gajim.config.get('urlmsgcolor'), + } + + cfg_was_read = parser.read() + + from common import latex + gajim.HAVE_LATEX = gajim.config.get('use_latex') and \ + latex.check_for_latex_support() + + gajim.logger.reset_shown_unread_messages() + # override logging settings from config (don't take care of '-q' option) + if gajim.config.get('verbose'): + logging_helpers.set_verbose() + + # Is Gajim default app? + if os.name != 'nt' and gajim.config.get('check_if_gajim_is_default'): + gtkgui_helpers.possibly_set_gajim_as_xmpp_handler() + + for account in gajim.config.get_per('accounts'): + if gajim.config.get_per('accounts', account, 'is_zeroconf'): + gajim.ZEROCONF_ACC_NAME = account + break + # Is gnome configured to activate row on single click ? + try: + import gconf + client = gconf.client_get_default() + click_policy = client.get_string( + '/apps/nautilus/preferences/click_policy') + if click_policy == 'single': + gajim.single_click = True + except Exception: + pass + # add default status messages if there is not in the config file + if len(gajim.config.get_per('statusmsg')) == 0: + default = gajim.config.statusmsg_default + for msg in default: + gajim.config.add_per('statusmsg', msg) + gajim.config.set_per('statusmsg', msg, 'message', default[msg][0]) + gajim.config.set_per('statusmsg', msg, 'activity', default[msg][1]) + gajim.config.set_per('statusmsg', msg, 'subactivity', + default[msg][2]) + gajim.config.set_per('statusmsg', msg, 'activity_text', + default[msg][3]) + gajim.config.set_per('statusmsg', msg, 'mood', default[msg][4]) + gajim.config.set_per('statusmsg', msg, 'mood_text', default[msg][5]) + #add default themes if there is not in the config file + theme = gajim.config.get('roster_theme') + if not theme in gajim.config.get_per('themes'): + gajim.config.set('roster_theme', _('default')) + if len(gajim.config.get_per('themes')) == 0: + d = ['accounttextcolor', 'accountbgcolor', 'accountfont', + 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont', + 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', + 'contactfont', 'contactfontattrs', 'bannertextcolor', + 'bannerbgcolor'] + + default = gajim.config.themes_default + for theme_name in default: + gajim.config.add_per('themes', theme_name) + theme = default[theme_name] + for o in d: + gajim.config.set_per('themes', theme_name, o, + theme[d.index(o)]) + + if gajim.config.get('autodetect_browser_mailer') or not cfg_was_read: + gtkgui_helpers.autodetect_browser_mailer() + + gajim.idlequeue = idlequeue.get_idlequeue() + # resolve and keep current record of resolved hosts + gajim.resolver = resolver.get_resolver(gajim.idlequeue) + gajim.socks5queue = socks5.SocksQueue(gajim.idlequeue, + self.handle_event_file_rcv_completed, + self.handle_event_file_progress, + self.handle_event_file_error) + gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue) + gajim.default_session_type = ChatControlSession + + # Creating Global Events Dispatcher + from common import ged + gajim.ged = ged.GlobalEventsDispatcher() + # Creating Network Events Controller + from common import nec + gajim.nec = nec.NetworkEventsController() + self.create_core_handlers_list() + self.register_core_handlers() + + if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \ + and gajim.HAVE_ZEROCONF: + gajim.connections[gajim.ZEROCONF_ACC_NAME] = \ + connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME) + for account in gajim.config.get_per('accounts'): + if not gajim.config.get_per('accounts', account, 'is_zeroconf') and \ + gajim.config.get_per('accounts', account, 'active'): + gajim.connections[account] = common.connection.Connection(account) + + # gtk hooks + gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail') + gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url') + gtk.link_button_set_uri_hook(self.on_launch_browser_mailer, 'url') + + self.instances = {} + + for a in gajim.connections: + self.instances[a] = {'infos': {}, 'disco': {}, 'gc_config': {}, + 'search': {}, 'online_dialog': {}} + # online_dialog contains all dialogs that have a meaning only when we + # are not disconnected + self.minimized_controls[a] = {} + gajim.contacts.add_account(a) + gajim.groups[a] = {} + gajim.gc_connected[a] = {} + gajim.automatic_rooms[a] = {} + gajim.newly_added[a] = [] + gajim.to_be_removed[a] = [] + gajim.nicks[a] = gajim.config.get_per('accounts', a, 'name') + gajim.block_signed_in_notifications[a] = True + gajim.sleeper_state[a] = 0 + gajim.encrypted_chats[a] = [] + gajim.last_message_time[a] = {} + gajim.status_before_autoaway[a] = '' + gajim.transport_avatar[a] = {} + gajim.gajim_optional_features[a] = [] + gajim.caps_hash[a] = '' + + helpers.update_optional_features() + # prepopulate data which we are sure of; note: we do not log these info + for account in gajim.connections: + gajimcaps = caps_cache.capscache[('sha-1', gajim.caps_hash[account])] + gajimcaps.identities = [gajim.gajim_identity] + gajimcaps.features = gajim.gajim_common_features + \ + gajim.gajim_optional_features[account] + + self.remote_ctrl = None + + if gajim.config.get('networkmanager_support') and dbus_support.supported: + import network_manager_listener + + # Handle gnome screensaver + if dbus_support.supported: + def gnome_screensaver_ActiveChanged_cb(active): + if not active: + for account in gajim.connections: + if gajim.sleeper_state[account] == 'autoaway-forced': + # We came back online ofter gnome-screensaver autoaway + self.roster.send_status(account, 'online', + gajim.status_before_autoaway[account]) + gajim.status_before_autoaway[account] = '' + gajim.sleeper_state[account] = 'online' + return + if not gajim.config.get('autoaway'): + # Don't go auto away if user disabled the option + return + for account in gajim.connections: + if account not in gajim.sleeper_state or \ + not gajim.sleeper_state[account]: + continue + if gajim.sleeper_state[account] == 'online': + # we save out online status + gajim.status_before_autoaway[account] = \ + gajim.connections[account].status + # we go away (no auto status) [we pass True to auto param] + auto_message = gajim.config.get('autoaway_message') + if not auto_message: + auto_message = gajim.connections[account].status + else: + auto_message = auto_message.replace('$S', '%(status)s') + auto_message = auto_message.replace('$T', '%(time)s') + auto_message = auto_message % { + 'status': gajim.status_before_autoaway[account], + 'time': gajim.config.get('autoxatime') + } + self.roster.send_status(account, 'away', auto_message, + auto=True) + gajim.sleeper_state[account] = 'autoaway-forced' + + try: + bus = dbus.SessionBus() + bus.add_signal_receiver(gnome_screensaver_ActiveChanged_cb, + 'ActiveChanged', 'org.gnome.ScreenSaver') + except Exception: + pass + + self.show_vcard_when_connect = [] + + self.sleeper = common.sleepy.Sleepy( + gajim.config.get('autoawaytime') * 60, # make minutes to seconds + gajim.config.get('autoxatime') * 60) + + gtkgui_helpers.make_jabber_state_images() + + self.systray_enabled = False + + import statusicon + self.systray = statusicon.StatusIcon() + + pix = gtkgui_helpers.get_icon_pixmap('gajim', 32) + # set the icon to all windows + gtk.window_set_default_icon(pix) + + self.init_emoticons() + self.make_regexps() + + # get transports type from DB + gajim.transport_type = gajim.logger.get_transports_type() + + # test is dictionnary is present for speller + if gajim.config.get('use_speller'): + lang = gajim.config.get('speller_language') + if not lang: + lang = gajim.LANG + tv = gtk.TextView() + try: + import gtkspell + spell = gtkspell.Spell(tv, lang) + except (ImportError, TypeError, RuntimeError, OSError): + dialogs.AspellDictError(lang) + + if gajim.config.get('soundplayer') == '': + # only on first time Gajim starts + commands = ('aplay', 'play', 'esdplay', 'artsplay', 'ossplay') + for command in commands: + if helpers.is_in_path(command): + if command == 'aplay': + command += ' -q' + gajim.config.set('soundplayer', command) + break + + self.last_ftwindow_update = 0 + + self.music_track_changed_signal = None class PassphraseRequest: - def __init__(self, keyid): - self.keyid = keyid - self.callbacks = [] - self.dialog_created = False - self.dialog = None - self.completed = False + def __init__(self, keyid): + self.keyid = keyid + self.callbacks = [] + self.dialog_created = False + self.dialog = None + self.completed = False - def interrupt(self): - self.dialog.window.destroy() - self.callbacks = [] + def interrupt(self): + self.dialog.window.destroy() + self.callbacks = [] - def run_callback(self, account, callback): - gajim.connections[account].gpg_passphrase(self.passphrase) - callback() + def run_callback(self, account, callback): + gajim.connections[account].gpg_passphrase(self.passphrase) + callback() - def add_callback(self, account, cb): - if self.completed: - self.run_callback(account, cb) - else: - self.callbacks.append((account, cb)) - if not self.dialog_created: - self.create_dialog(account) + def add_callback(self, account, cb): + if self.completed: + self.run_callback(account, cb) + else: + self.callbacks.append((account, cb)) + if not self.dialog_created: + self.create_dialog(account) - def complete(self, passphrase): - self.passphrase = passphrase - self.completed = True - if passphrase is not None: - gobject.timeout_add_seconds(30, gajim.interface.forget_gpg_passphrase, - self.keyid) - for (account, cb) in self.callbacks: - self.run_callback(account, cb) - del self.callbacks + def complete(self, passphrase): + self.passphrase = passphrase + self.completed = True + if passphrase is not None: + gobject.timeout_add_seconds(30, gajim.interface.forget_gpg_passphrase, + self.keyid) + for (account, cb) in self.callbacks: + self.run_callback(account, cb) + del self.callbacks - def create_dialog(self, account): - title = _('Passphrase Required') - second = _('Enter GPG key passphrase for key %(keyid)s (account ' - '%(account)s).') % {'keyid': self.keyid, 'account': account} + def create_dialog(self, account): + title = _('Passphrase Required') + second = _('Enter GPG key passphrase for key %(keyid)s (account ' + '%(account)s).') % {'keyid': self.keyid, 'account': account} - def _cancel(): - # user cancelled, continue without GPG - self.complete(None) + def _cancel(): + # user cancelled, continue without GPG + self.complete(None) - def _ok(passphrase, checked, count): - result = gajim.connections[account].test_gpg_passphrase(passphrase) - if result == 'ok': - # passphrase is good - self.complete(passphrase) - return - elif result == 'expired': - dialogs.ErrorDialog(_('GPG key expired'), - _('Your GPG key has expired, you will be connected to %s without' - ' OpenPGP.') % account) - # Don't try to connect with GPG - gajim.connections[account].continue_connect_info[2] = False - self.complete(None) - return + def _ok(passphrase, checked, count): + result = gajim.connections[account].test_gpg_passphrase(passphrase) + if result == 'ok': + # passphrase is good + self.complete(passphrase) + return + elif result == 'expired': + dialogs.ErrorDialog(_('GPG key expired'), + _('Your GPG key has expired, you will be connected to %s without' + ' OpenPGP.') % account) + # Don't try to connect with GPG + gajim.connections[account].continue_connect_info[2] = False + self.complete(None) + return - if count < 3: - # ask again - dialogs.PassphraseDialog(_('Wrong Passphrase'), - _('Please retype your GPG passphrase or press Cancel.'), - ok_handler=(_ok, count + 1), cancel_handler=_cancel) - else: - # user failed 3 times, continue without GPG - self.complete(None) + if count < 3: + # ask again + dialogs.PassphraseDialog(_('Wrong Passphrase'), + _('Please retype your GPG passphrase or press Cancel.'), + ok_handler=(_ok, count + 1), cancel_handler=_cancel) + else: + # user failed 3 times, continue without GPG + self.complete(None) - self.dialog = dialogs.PassphraseDialog(title, second, ok_handler=(_ok, 1), - cancel_handler=_cancel) - self.dialog_created = True + self.dialog = dialogs.PassphraseDialog(title, second, ok_handler=(_ok, 1), + cancel_handler=_cancel) + self.dialog_created = True class ThreadInterface: - def __init__(self, func, func_args, callback, callback_args): - """ - Call a function in a thread - """ - def thread_function(func, func_args, callback, callback_args): - output = func(*func_args) - gobject.idle_add(callback, output, *callback_args) + def __init__(self, func, func_args, callback, callback_args): + """ + Call a function in a thread + """ + def thread_function(func, func_args, callback, callback_args): + output = func(*func_args) + gobject.idle_add(callback, output, *callback_args) - Thread(target=thread_function, args=(func, func_args, callback, - callback_args)).start() - -# vim: se ts=3: + Thread(target=thread_function, args=(func, func_args, callback, + callback_args)).start() diff --git a/src/gui_menu_builder.py b/src/gui_menu_builder.py index 3e9bbb181..6652badef 100644 --- a/src/gui_menu_builder.py +++ b/src/gui_menu_builder.py @@ -28,448 +28,446 @@ from common import helpers from common.xmpp.protocol import NS_COMMANDS, NS_FILE, NS_MUC, NS_ESESSION def build_resources_submenu(contacts, account, action, room_jid=None, - room_account=None, cap=None): - """ - Build a submenu with contact's resources. room_jid and room_account are for - action self.on_invite_to_room - """ - roster = gajim.interface.roster - sub_menu = gtk.Menu() + room_account=None, cap=None): + """ + Build a submenu with contact's resources. room_jid and room_account are for + action self.on_invite_to_room + """ + roster = gajim.interface.roster + sub_menu = gtk.Menu() - iconset = gajim.config.get('iconset') - if not iconset: - iconset = gajim.config.DEFAULT_ICONSET - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - for c in contacts: - # icon MUST be different instance for every item - state_images = gtkgui_helpers.load_iconset(path) - item = gtk.ImageMenuItem('%s (%s)' % (c.resource, str(c.priority))) - icon_name = helpers.get_icon_name_to_show(c, account) - icon = state_images[icon_name] - item.set_image(icon) - sub_menu.append(item) - - if action == roster.on_invite_to_room: - item.connect('activate', action, [(c, account)], room_jid, - room_account, c.resource) - elif action == roster.on_invite_to_new_room: - item.connect('activate', action, [(c, account)], c.resource) - else: # start_chat, execute_command, send_file - item.connect('activate', action, c, account, c.resource) + iconset = gajim.config.get('iconset') + if not iconset: + iconset = gajim.config.DEFAULT_ICONSET + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + for c in contacts: + # icon MUST be different instance for every item + state_images = gtkgui_helpers.load_iconset(path) + item = gtk.ImageMenuItem('%s (%s)' % (c.resource, str(c.priority))) + icon_name = helpers.get_icon_name_to_show(c, account) + icon = state_images[icon_name] + item.set_image(icon) + sub_menu.append(item) - if cap and not c.supports(cap): - item.set_sensitive(False) + if action == roster.on_invite_to_room: + item.connect('activate', action, [(c, account)], room_jid, + room_account, c.resource) + elif action == roster.on_invite_to_new_room: + item.connect('activate', action, [(c, account)], c.resource) + else: # start_chat, execute_command, send_file + item.connect('activate', action, c, account, c.resource) - return sub_menu + if cap and not c.supports(cap): + item.set_sensitive(False) + + return sub_menu def build_invite_submenu(invite_menuitem, list_): - """ - list_ in a list of (contact, account) - """ - roster = gajim.interface.roster - # used if we invite only one contact with several resources - contact_list = [] - if len(list_) == 1: - contact, account = list_[0] - contact_list = gajim.contacts.get_contacts(account, contact.jid) - contacts_transport = -1 - connected_accounts = [] - # -1 is at start, False when not from the same, None when jabber - for (contact, account) in list_: - if not account in connected_accounts: - connected_accounts.append(account) - transport = gajim.get_transport_name_from_jid(contact.jid) - if contacts_transport == -1: - contacts_transport = transport - elif contacts_transport != transport: - contacts_transport = False + """ + list_ in a list of (contact, account) + """ + roster = gajim.interface.roster + # used if we invite only one contact with several resources + contact_list = [] + if len(list_) == 1: + contact, account = list_[0] + contact_list = gajim.contacts.get_contacts(account, contact.jid) + contacts_transport = -1 + connected_accounts = [] + # -1 is at start, False when not from the same, None when jabber + for (contact, account) in list_: + if not account in connected_accounts: + connected_accounts.append(account) + transport = gajim.get_transport_name_from_jid(contact.jid) + if contacts_transport == -1: + contacts_transport = transport + elif contacts_transport != transport: + contacts_transport = False - if contacts_transport == False: - # they are not all from the same transport - invite_menuitem.set_sensitive(False) - return - invite_to_submenu = gtk.Menu() - invite_menuitem.set_submenu(invite_to_submenu) - invite_to_new_room_menuitem = gtk.ImageMenuItem(_('_New Group Chat')) - icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) - invite_to_new_room_menuitem.set_image(icon) - if len(contact_list) > 1: # several resources - invite_to_new_room_menuitem.set_submenu(build_resources_submenu( - contact_list, account, roster.on_invite_to_new_room, cap=NS_MUC)) - elif len(list_) == 1 and contact.supports(NS_MUC): - invite_menuitem.set_sensitive(True) - # use resource if it's self contact - if contact.jid == gajim.get_jid_from_account(account): - resource = contact.resource - else: - resource = None - invite_to_new_room_menuitem.connect('activate', - roster.on_invite_to_new_room, list_, resource) - else: - invite_menuitem.set_sensitive(False) - # transform None in 'jabber' - c_t = contacts_transport or 'jabber' - muc_jid = {} - for account in connected_accounts: - for t in gajim.connections[account].muc_jid: - muc_jid[t] = gajim.connections[account].muc_jid[t] - if c_t not in muc_jid: - invite_to_new_room_menuitem.set_sensitive(False) - rooms = [] # a list of (room_jid, account) tuple - invite_to_submenu.append(invite_to_new_room_menuitem) - rooms = [] # a list of (room_jid, account) tuple - minimized_controls = [] - for account in connected_accounts: - minimized_controls += gajim.interface.minimized_controls[account].values() - for gc_control in gajim.interface.msg_win_mgr.get_controls( - message_control.TYPE_GC) + minimized_controls: - acct = gc_control.account - room_jid = gc_control.room_jid - if room_jid in gajim.gc_connected[acct] and \ - gajim.gc_connected[acct][room_jid] and \ - contacts_transport == gajim.get_transport_name_from_jid(room_jid): - rooms.append((room_jid, acct)) - if len(rooms): - item = gtk.SeparatorMenuItem() # separator - invite_to_submenu.append(item) - for (room_jid, account) in rooms: - menuitem = gtk.MenuItem(room_jid.split('@')[0]) - if len(contact_list) > 1: # several resources - menuitem.set_submenu(build_resources_submenu( - contact_list, account, roster.on_invite_to_room, room_jid, - account)) - else: - # use resource if it's self contact - if contact.jid == gajim.get_jid_from_account(account): - resource = contact.resource - else: - resource = None - menuitem.connect('activate', roster.on_invite_to_room, list_, - room_jid, account, resource) - invite_to_submenu.append(menuitem) + if contacts_transport == False: + # they are not all from the same transport + invite_menuitem.set_sensitive(False) + return + invite_to_submenu = gtk.Menu() + invite_menuitem.set_submenu(invite_to_submenu) + invite_to_new_room_menuitem = gtk.ImageMenuItem(_('_New Group Chat')) + icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) + invite_to_new_room_menuitem.set_image(icon) + if len(contact_list) > 1: # several resources + invite_to_new_room_menuitem.set_submenu(build_resources_submenu( + contact_list, account, roster.on_invite_to_new_room, cap=NS_MUC)) + elif len(list_) == 1 and contact.supports(NS_MUC): + invite_menuitem.set_sensitive(True) + # use resource if it's self contact + if contact.jid == gajim.get_jid_from_account(account): + resource = contact.resource + else: + resource = None + invite_to_new_room_menuitem.connect('activate', + roster.on_invite_to_new_room, list_, resource) + else: + invite_menuitem.set_sensitive(False) + # transform None in 'jabber' + c_t = contacts_transport or 'jabber' + muc_jid = {} + for account in connected_accounts: + for t in gajim.connections[account].muc_jid: + muc_jid[t] = gajim.connections[account].muc_jid[t] + if c_t not in muc_jid: + invite_to_new_room_menuitem.set_sensitive(False) + rooms = [] # a list of (room_jid, account) tuple + invite_to_submenu.append(invite_to_new_room_menuitem) + rooms = [] # a list of (room_jid, account) tuple + minimized_controls = [] + for account in connected_accounts: + minimized_controls += gajim.interface.minimized_controls[account].values() + for gc_control in gajim.interface.msg_win_mgr.get_controls( + message_control.TYPE_GC) + minimized_controls: + acct = gc_control.account + room_jid = gc_control.room_jid + if room_jid in gajim.gc_connected[acct] and \ + gajim.gc_connected[acct][room_jid] and \ + contacts_transport == gajim.get_transport_name_from_jid(room_jid): + rooms.append((room_jid, acct)) + if len(rooms): + item = gtk.SeparatorMenuItem() # separator + invite_to_submenu.append(item) + for (room_jid, account) in rooms: + menuitem = gtk.MenuItem(room_jid.split('@')[0]) + if len(contact_list) > 1: # several resources + menuitem.set_submenu(build_resources_submenu( + contact_list, account, roster.on_invite_to_room, room_jid, + account)) + else: + # use resource if it's self contact + if contact.jid == gajim.get_jid_from_account(account): + resource = contact.resource + else: + resource = None + menuitem.connect('activate', roster.on_invite_to_room, list_, + room_jid, account, resource) + invite_to_submenu.append(menuitem) def get_contact_menu(contact, account, use_multiple_contacts=True, - show_start_chat=True, show_encryption=False, show_buttonbar_items=True, - control=None): - """ - Build contact popup menu for roster and chat window. If control is not set, - we hide invite_contacts_menuitem - """ - if not contact: - return + show_start_chat=True, show_encryption=False, show_buttonbar_items=True, + control=None): + """ + Build contact popup menu for roster and chat window. If control is not set, + we hide invite_contacts_menuitem + """ + if not contact: + return - jid = contact.jid - our_jid = jid == gajim.get_jid_from_account(account) - roster = gajim.interface.roster + jid = contact.jid + our_jid = jid == gajim.get_jid_from_account(account) + roster = gajim.interface.roster - xml = gtkgui_helpers.get_gtk_builder('contact_context_menu.ui') - contact_context_menu = xml.get_object('contact_context_menu') + xml = gtkgui_helpers.get_gtk_builder('contact_context_menu.ui') + contact_context_menu = xml.get_object('contact_context_menu') - start_chat_menuitem = xml.get_object('start_chat_menuitem') - execute_command_menuitem = xml.get_object('execute_command_menuitem') - rename_menuitem = xml.get_object('rename_menuitem') - edit_groups_menuitem = xml.get_object('edit_groups_menuitem') - send_file_menuitem = xml.get_object('send_file_menuitem') - assign_openpgp_key_menuitem = xml.get_object('assign_openpgp_key_menuitem') - add_special_notification_menuitem = xml.get_object( - 'add_special_notification_menuitem') - information_menuitem = xml.get_object('information_menuitem') - history_menuitem = xml.get_object('history_menuitem') - send_custom_status_menuitem = xml.get_object('send_custom_status_menuitem') - send_single_message_menuitem = xml.get_object('send_single_message_menuitem') - invite_menuitem = xml.get_object('invite_menuitem') - block_menuitem = xml.get_object('block_menuitem') - unblock_menuitem = xml.get_object('unblock_menuitem') - ignore_menuitem = xml.get_object('ignore_menuitem') - unignore_menuitem = xml.get_object('unignore_menuitem') - set_custom_avatar_menuitem = xml.get_object('set_custom_avatar_menuitem') - # Subscription submenu - subscription_menuitem = xml.get_object('subscription_menuitem') - send_auth_menuitem, ask_auth_menuitem, revoke_auth_menuitem = \ - subscription_menuitem.get_submenu().get_children() - add_to_roster_menuitem = xml.get_object('add_to_roster_menuitem') - remove_from_roster_menuitem = xml.get_object( - 'remove_from_roster_menuitem') - manage_contact_menuitem = xml.get_object('manage_contact') - convert_to_gc_menuitem = xml.get_object('convert_to_groupchat_menuitem') - encryption_separator = xml.get_object('encryption_separator') - toggle_gpg_menuitem = xml.get_object('toggle_gpg_menuitem') - toggle_e2e_menuitem = xml.get_object('toggle_e2e_menuitem') - last_separator = xml.get_object('last_separator') + start_chat_menuitem = xml.get_object('start_chat_menuitem') + execute_command_menuitem = xml.get_object('execute_command_menuitem') + rename_menuitem = xml.get_object('rename_menuitem') + edit_groups_menuitem = xml.get_object('edit_groups_menuitem') + send_file_menuitem = xml.get_object('send_file_menuitem') + assign_openpgp_key_menuitem = xml.get_object('assign_openpgp_key_menuitem') + add_special_notification_menuitem = xml.get_object( + 'add_special_notification_menuitem') + information_menuitem = xml.get_object('information_menuitem') + history_menuitem = xml.get_object('history_menuitem') + send_custom_status_menuitem = xml.get_object('send_custom_status_menuitem') + send_single_message_menuitem = xml.get_object('send_single_message_menuitem') + invite_menuitem = xml.get_object('invite_menuitem') + block_menuitem = xml.get_object('block_menuitem') + unblock_menuitem = xml.get_object('unblock_menuitem') + ignore_menuitem = xml.get_object('ignore_menuitem') + unignore_menuitem = xml.get_object('unignore_menuitem') + set_custom_avatar_menuitem = xml.get_object('set_custom_avatar_menuitem') + # Subscription submenu + subscription_menuitem = xml.get_object('subscription_menuitem') + send_auth_menuitem, ask_auth_menuitem, revoke_auth_menuitem = \ + subscription_menuitem.get_submenu().get_children() + add_to_roster_menuitem = xml.get_object('add_to_roster_menuitem') + remove_from_roster_menuitem = xml.get_object( + 'remove_from_roster_menuitem') + manage_contact_menuitem = xml.get_object('manage_contact') + convert_to_gc_menuitem = xml.get_object('convert_to_groupchat_menuitem') + encryption_separator = xml.get_object('encryption_separator') + toggle_gpg_menuitem = xml.get_object('toggle_gpg_menuitem') + toggle_e2e_menuitem = xml.get_object('toggle_e2e_menuitem') + last_separator = xml.get_object('last_separator') - items_to_hide = [] + items_to_hide = [] - # add a special img for send file menuitem - path_to_upload_img = gtkgui_helpers.get_icon_path('gajim-upload') - img = gtk.Image() - img.set_from_file(path_to_upload_img) - send_file_menuitem.set_image(img) + # add a special img for send file menuitem + path_to_upload_img = gtkgui_helpers.get_icon_path('gajim-upload') + img = gtk.Image() + img.set_from_file(path_to_upload_img) + send_file_menuitem.set_image(img) - if not our_jid: - # add a special img for rename menuitem - gtkgui_helpers.add_image_to_menuitem(rename_menuitem, 'gajim-kbd_input') + if not our_jid: + # add a special img for rename menuitem + gtkgui_helpers.add_image_to_menuitem(rename_menuitem, 'gajim-kbd_input') - muc_icon = gtkgui_helpers.load_icon('muc_active') - if muc_icon: - convert_to_gc_menuitem.set_image(muc_icon) + muc_icon = gtkgui_helpers.load_icon('muc_active') + if muc_icon: + convert_to_gc_menuitem.set_image(muc_icon) - contacts = gajim.contacts.get_contacts(account, jid) - if len(contacts) > 1 and use_multiple_contacts: # several resources - start_chat_menuitem.set_submenu(build_resources_submenu(contacts, - account, gajim.interface.on_open_chat_window)) - send_file_menuitem.set_submenu(build_resources_submenu(contacts, - account, roster.on_send_file_menuitem_activate, cap=NS_FILE)) - execute_command_menuitem.set_submenu(build_resources_submenu( - contacts, account, roster.on_execute_command, cap=NS_COMMANDS)) - else: - start_chat_menuitem.connect('activate', - gajim.interface.on_open_chat_window, contact, account) - if contact.supports(NS_FILE): - send_file_menuitem.set_sensitive(True) - send_file_menuitem.connect('activate', - roster.on_send_file_menuitem_activate, contact, account) - else: - send_file_menuitem.set_sensitive(False) + contacts = gajim.contacts.get_contacts(account, jid) + if len(contacts) > 1 and use_multiple_contacts: # several resources + start_chat_menuitem.set_submenu(build_resources_submenu(contacts, + account, gajim.interface.on_open_chat_window)) + send_file_menuitem.set_submenu(build_resources_submenu(contacts, + account, roster.on_send_file_menuitem_activate, cap=NS_FILE)) + execute_command_menuitem.set_submenu(build_resources_submenu( + contacts, account, roster.on_execute_command, cap=NS_COMMANDS)) + else: + start_chat_menuitem.connect('activate', + gajim.interface.on_open_chat_window, contact, account) + if contact.supports(NS_FILE): + send_file_menuitem.set_sensitive(True) + send_file_menuitem.connect('activate', + roster.on_send_file_menuitem_activate, contact, account) + else: + send_file_menuitem.set_sensitive(False) - if contact.supports(NS_COMMANDS): - execute_command_menuitem.set_sensitive(True) - execute_command_menuitem.connect('activate', roster.on_execute_command, - contact, account, contact.resource) - else: - execute_command_menuitem.set_sensitive(False) + if contact.supports(NS_COMMANDS): + execute_command_menuitem.set_sensitive(True) + execute_command_menuitem.connect('activate', roster.on_execute_command, + contact, account, contact.resource) + else: + execute_command_menuitem.set_sensitive(False) - rename_menuitem.connect('activate', roster.on_rename, 'contact', jid, - account) - history_menuitem.connect('activate', roster.on_history, contact, account) + rename_menuitem.connect('activate', roster.on_rename, 'contact', jid, + account) + history_menuitem.connect('activate', roster.on_history, contact, account) - if control: - convert_to_gc_menuitem.connect('activate', - control._on_convert_to_gc_menuitem_activate) - else: - items_to_hide.append(convert_to_gc_menuitem) + if control: + convert_to_gc_menuitem.connect('activate', + control._on_convert_to_gc_menuitem_activate) + else: + items_to_hide.append(convert_to_gc_menuitem) - if _('Not in Roster') not in contact.get_shown_groups(): - # contact is in normal group - edit_groups_menuitem.connect('activate', roster.on_edit_groups, [(contact, - account)]) + if _('Not in Roster') not in contact.get_shown_groups(): + # contact is in normal group + edit_groups_menuitem.connect('activate', roster.on_edit_groups, [(contact, + account)]) - if gajim.connections[account].gpg: - assign_openpgp_key_menuitem.connect('activate', - roster.on_assign_pgp_key, contact, account) - else: - assign_openpgp_key_menuitem.set_sensitive(False) - else: - # contact is in group 'Not in Roster' - edit_groups_menuitem.set_sensitive(False) - assign_openpgp_key_menuitem.set_sensitive(False) + if gajim.connections[account].gpg: + assign_openpgp_key_menuitem.connect('activate', + roster.on_assign_pgp_key, contact, account) + else: + assign_openpgp_key_menuitem.set_sensitive(False) + else: + # contact is in group 'Not in Roster' + edit_groups_menuitem.set_sensitive(False) + assign_openpgp_key_menuitem.set_sensitive(False) - # Hide items when it's self contact row - if our_jid: - items_to_hide += [rename_menuitem, edit_groups_menuitem] + # Hide items when it's self contact row + if our_jid: + items_to_hide += [rename_menuitem, edit_groups_menuitem] - # Unsensitive many items when account is offline - if gajim.account_is_disconnected(account): - for widget in (start_chat_menuitem, rename_menuitem, - edit_groups_menuitem, send_file_menuitem, convert_to_gc_menuitem): - widget.set_sensitive(False) + # Unsensitive many items when account is offline + if gajim.account_is_disconnected(account): + for widget in (start_chat_menuitem, rename_menuitem, + edit_groups_menuitem, send_file_menuitem, convert_to_gc_menuitem): + widget.set_sensitive(False) - if not show_start_chat: - items_to_hide.append(start_chat_menuitem) + if not show_start_chat: + items_to_hide.append(start_chat_menuitem) - if not show_encryption or not control: - items_to_hide += [encryption_separator, toggle_gpg_menuitem, - toggle_e2e_menuitem] - else: - e2e_is_active = control.session is not None and \ - control.session.enable_encryption + if not show_encryption or not control: + items_to_hide += [encryption_separator, toggle_gpg_menuitem, + toggle_e2e_menuitem] + else: + e2e_is_active = control.session is not None and \ + control.session.enable_encryption - # check if we support and use gpg - if not gajim.config.get_per('accounts', account, 'keyid') or \ - not gajim.connections[account].USE_GPG or gajim.jid_is_transport( - contact.jid): - toggle_gpg_menuitem.set_sensitive(False) - else: - toggle_gpg_menuitem.set_sensitive(control.gpg_is_active or \ - not e2e_is_active) - toggle_gpg_menuitem.set_active(control.gpg_is_active) - toggle_gpg_menuitem.connect('activate', - control._on_toggle_gpg_menuitem_activate) + # check if we support and use gpg + if not gajim.config.get_per('accounts', account, 'keyid') or \ + not gajim.connections[account].USE_GPG or gajim.jid_is_transport( + contact.jid): + toggle_gpg_menuitem.set_sensitive(False) + else: + toggle_gpg_menuitem.set_sensitive(control.gpg_is_active or \ + not e2e_is_active) + toggle_gpg_menuitem.set_active(control.gpg_is_active) + toggle_gpg_menuitem.connect('activate', + control._on_toggle_gpg_menuitem_activate) - # disable esessions if we or the other client don't support them - if not gajim.HAVE_PYCRYPTO or not contact.supports(NS_ESESSION) or \ - not gajim.config.get_per('accounts', account, 'enable_esessions'): - toggle_e2e_menuitem.set_sensitive(False) - else: - toggle_e2e_menuitem.set_active(e2e_is_active) - toggle_e2e_menuitem.set_sensitive(e2e_is_active or \ - not control.gpg_is_active) - toggle_e2e_menuitem.connect('activate', - control._on_toggle_e2e_menuitem_activate) + # disable esessions if we or the other client don't support them + if not gajim.HAVE_PYCRYPTO or not contact.supports(NS_ESESSION) or \ + not gajim.config.get_per('accounts', account, 'enable_esessions'): + toggle_e2e_menuitem.set_sensitive(False) + else: + toggle_e2e_menuitem.set_active(e2e_is_active) + toggle_e2e_menuitem.set_sensitive(e2e_is_active or \ + not control.gpg_is_active) + toggle_e2e_menuitem.connect('activate', + control._on_toggle_e2e_menuitem_activate) - if not show_buttonbar_items: - items_to_hide += [history_menuitem, send_file_menuitem, - information_menuitem, convert_to_gc_menuitem, last_separator] - - if not control: - items_to_hide.append(convert_to_gc_menuitem) + if not show_buttonbar_items: + items_to_hide += [history_menuitem, send_file_menuitem, + information_menuitem, convert_to_gc_menuitem, last_separator] - for item in items_to_hide: - item.set_no_show_all(True) - item.hide() + if not control: + items_to_hide.append(convert_to_gc_menuitem) - # Zeroconf Account - if gajim.config.get_per('accounts', account, 'is_zeroconf'): - for item in (send_custom_status_menuitem, send_single_message_menuitem, - invite_menuitem, block_menuitem, unblock_menuitem, ignore_menuitem, - unignore_menuitem, set_custom_avatar_menuitem, subscription_menuitem, - manage_contact_menuitem, convert_to_gc_menuitem): - item.set_no_show_all(True) - item.hide() + for item in items_to_hide: + item.set_no_show_all(True) + item.hide() - if contact.show in ('offline', 'error'): - information_menuitem.set_sensitive(False) - send_file_menuitem.set_sensitive(False) - else: - information_menuitem.connect('activate', roster.on_info_zeroconf, - contact, account) + # Zeroconf Account + if gajim.config.get_per('accounts', account, 'is_zeroconf'): + for item in (send_custom_status_menuitem, send_single_message_menuitem, + invite_menuitem, block_menuitem, unblock_menuitem, ignore_menuitem, + unignore_menuitem, set_custom_avatar_menuitem, subscription_menuitem, + manage_contact_menuitem, convert_to_gc_menuitem): + item.set_no_show_all(True) + item.hide() - contact_context_menu.connect('selection-done', - gtkgui_helpers.destroy_widget) - contact_context_menu.show_all() - return contact_context_menu + if contact.show in ('offline', 'error'): + information_menuitem.set_sensitive(False) + send_file_menuitem.set_sensitive(False) + else: + information_menuitem.connect('activate', roster.on_info_zeroconf, + contact, account) - # normal account + contact_context_menu.connect('selection-done', + gtkgui_helpers.destroy_widget) + contact_context_menu.show_all() + return contact_context_menu - # send custom status icon - blocked = False - if helpers.jid_is_blocked(account, jid): - blocked = True - else: - for group in contact.get_shown_groups(): - if helpers.group_is_blocked(account, group): - blocked = True - break - if gajim.get_transport_name_from_jid(jid, use_config_setting=False): - # Transport contact, send custom status unavailable - send_custom_status_menuitem.set_sensitive(False) - elif blocked: - send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon('offline')) - send_custom_status_menuitem.set_sensitive(False) - elif account in gajim.interface.status_sent_to_users and \ - jid in gajim.interface.status_sent_to_users[account]: - send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( - gajim.interface.status_sent_to_users[account][jid])) - else: - icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_MENU) - send_custom_status_menuitem.set_image(icon) + # normal account - muc_icon = gtkgui_helpers.load_icon('muc_active') - if muc_icon: - invite_menuitem.set_image(muc_icon) + # send custom status icon + blocked = False + if helpers.jid_is_blocked(account, jid): + blocked = True + else: + for group in contact.get_shown_groups(): + if helpers.group_is_blocked(account, group): + blocked = True + break + if gajim.get_transport_name_from_jid(jid, use_config_setting=False): + # Transport contact, send custom status unavailable + send_custom_status_menuitem.set_sensitive(False) + elif blocked: + send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon('offline')) + send_custom_status_menuitem.set_sensitive(False) + elif account in gajim.interface.status_sent_to_users and \ + jid in gajim.interface.status_sent_to_users[account]: + send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( + gajim.interface.status_sent_to_users[account][jid])) + else: + icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_MENU) + send_custom_status_menuitem.set_image(icon) - build_invite_submenu(invite_menuitem, [(contact, account)]) + muc_icon = gtkgui_helpers.load_icon('muc_active') + if muc_icon: + invite_menuitem.set_image(muc_icon) - # One or several resource, we do the same for send_custom_status - status_menuitems = gtk.Menu() - send_custom_status_menuitem.set_submenu(status_menuitems) - iconset = gajim.config.get('iconset') - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'): - # icon MUST be different instance for every item - state_images = gtkgui_helpers.load_iconset(path) - status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s)) - status_menuitem.connect('activate', roster.on_send_custom_status, - [(contact, account)], s) - icon = state_images[s] - status_menuitem.set_image(icon) - status_menuitems.append(status_menuitem) + build_invite_submenu(invite_menuitem, [(contact, account)]) - send_single_message_menuitem.connect('activate', - roster.on_send_single_message_menuitem_activate, account, contact) + # One or several resource, we do the same for send_custom_status + status_menuitems = gtk.Menu() + send_custom_status_menuitem.set_submenu(status_menuitems) + iconset = gajim.config.get('iconset') + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'): + # icon MUST be different instance for every item + state_images = gtkgui_helpers.load_iconset(path) + status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s)) + status_menuitem.connect('activate', roster.on_send_custom_status, + [(contact, account)], s) + icon = state_images[s] + status_menuitem.set_image(icon) + status_menuitems.append(status_menuitem) - remove_from_roster_menuitem.connect('activate', roster.on_req_usub, - [(contact, account)]) - information_menuitem.connect('activate', roster.on_info, contact, account) + send_single_message_menuitem.connect('activate', + roster.on_send_single_message_menuitem_activate, account, contact) - if _('Not in Roster') not in contact.get_shown_groups(): - # contact is in normal group - add_to_roster_menuitem.hide() - add_to_roster_menuitem.set_no_show_all(True) + remove_from_roster_menuitem.connect('activate', roster.on_req_usub, + [(contact, account)]) + information_menuitem.connect('activate', roster.on_info, contact, account) - if contact.sub in ('from', 'both'): - send_auth_menuitem.set_sensitive(False) - else: - send_auth_menuitem.connect('activate', roster.authorize, jid, account) - if contact.sub in ('to', 'both'): - ask_auth_menuitem.set_sensitive(False) - add_special_notification_menuitem.connect('activate', - roster.on_add_special_notification_menuitem_activate, jid) - else: - ask_auth_menuitem.connect('activate', roster.req_sub, jid, - _('I would like to add you to my roster'), account, - contact.groups, contact.name) - if contact.sub in ('to', 'none') or gajim.get_transport_name_from_jid( - jid, use_config_setting=False): - revoke_auth_menuitem.set_sensitive(False) - else: - revoke_auth_menuitem.connect('activate', roster.revoke_auth, jid, - account) + if _('Not in Roster') not in contact.get_shown_groups(): + # contact is in normal group + add_to_roster_menuitem.hide() + add_to_roster_menuitem.set_no_show_all(True) - else: - # contact is in group 'Not in Roster' - add_to_roster_menuitem.set_no_show_all(False) - subscription_menuitem.set_sensitive(False) + if contact.sub in ('from', 'both'): + send_auth_menuitem.set_sensitive(False) + else: + send_auth_menuitem.connect('activate', roster.authorize, jid, account) + if contact.sub in ('to', 'both'): + ask_auth_menuitem.set_sensitive(False) + add_special_notification_menuitem.connect('activate', + roster.on_add_special_notification_menuitem_activate, jid) + else: + ask_auth_menuitem.connect('activate', roster.req_sub, jid, + _('I would like to add you to my roster'), account, + contact.groups, contact.name) + if contact.sub in ('to', 'none') or gajim.get_transport_name_from_jid( + jid, use_config_setting=False): + revoke_auth_menuitem.set_sensitive(False) + else: + revoke_auth_menuitem.connect('activate', roster.revoke_auth, jid, + account) - add_to_roster_menuitem.connect('activate', roster.on_add_to_roster, - contact, account) + else: + # contact is in group 'Not in Roster' + add_to_roster_menuitem.set_no_show_all(False) + subscription_menuitem.set_sensitive(False) - set_custom_avatar_menuitem.connect('activate', - roster.on_set_custom_avatar_activate, contact, account) + add_to_roster_menuitem.connect('activate', roster.on_add_to_roster, + contact, account) - # Hide items when it's self contact row - if our_jid: - manage_contact_menuitem.set_sensitive(False) + set_custom_avatar_menuitem.connect('activate', + roster.on_set_custom_avatar_activate, contact, account) - # Unsensitive items when account is offline - if gajim.account_is_disconnected(account): - for widget in (send_single_message_menuitem, subscription_menuitem, - add_to_roster_menuitem, remove_from_roster_menuitem, - execute_command_menuitem, send_custom_status_menuitem): - widget.set_sensitive(False) + # Hide items when it's self contact row + if our_jid: + manage_contact_menuitem.set_sensitive(False) - if gajim.connections[account] and gajim.connections[account].\ - privacy_rules_supported: - if helpers.jid_is_blocked(account, jid): - block_menuitem.set_no_show_all(True) - block_menuitem.hide() - if gajim.get_transport_name_from_jid(jid, use_config_setting=False): - unblock_menuitem.set_no_show_all(True) - unblock_menuitem.hide() - unignore_menuitem.set_no_show_all(False) - unignore_menuitem.connect('activate', roster.on_unblock, [(contact, - account)]) - else: - unblock_menuitem.connect('activate', roster.on_unblock, [(contact, - account)]) - else: - unblock_menuitem.set_no_show_all(True) - unblock_menuitem.hide() - if gajim.get_transport_name_from_jid(jid, use_config_setting=False): - block_menuitem.set_no_show_all(True) - block_menuitem.hide() - ignore_menuitem.set_no_show_all(False) - ignore_menuitem.connect('activate', roster.on_block, [(contact, - account)]) - else: - block_menuitem.connect('activate', roster.on_block, [(contact, - account)]) - else: - unblock_menuitem.set_no_show_all(True) - block_menuitem.set_sensitive(False) - unblock_menuitem.hide() + # Unsensitive items when account is offline + if gajim.account_is_disconnected(account): + for widget in (send_single_message_menuitem, subscription_menuitem, + add_to_roster_menuitem, remove_from_roster_menuitem, + execute_command_menuitem, send_custom_status_menuitem): + widget.set_sensitive(False) - contact_context_menu.connect('selection-done', gtkgui_helpers.destroy_widget) - contact_context_menu.show_all() - return contact_context_menu + if gajim.connections[account] and gajim.connections[account].\ + privacy_rules_supported: + if helpers.jid_is_blocked(account, jid): + block_menuitem.set_no_show_all(True) + block_menuitem.hide() + if gajim.get_transport_name_from_jid(jid, use_config_setting=False): + unblock_menuitem.set_no_show_all(True) + unblock_menuitem.hide() + unignore_menuitem.set_no_show_all(False) + unignore_menuitem.connect('activate', roster.on_unblock, [(contact, + account)]) + else: + unblock_menuitem.connect('activate', roster.on_unblock, [(contact, + account)]) + else: + unblock_menuitem.set_no_show_all(True) + unblock_menuitem.hide() + if gajim.get_transport_name_from_jid(jid, use_config_setting=False): + block_menuitem.set_no_show_all(True) + block_menuitem.hide() + ignore_menuitem.set_no_show_all(False) + ignore_menuitem.connect('activate', roster.on_block, [(contact, + account)]) + else: + block_menuitem.connect('activate', roster.on_block, [(contact, + account)]) + else: + unblock_menuitem.set_no_show_all(True) + block_menuitem.set_sensitive(False) + unblock_menuitem.hide() -# vim: se ts=3: + contact_context_menu.connect('selection-done', gtkgui_helpers.destroy_widget) + contact_context_menu.show_all() + return contact_context_menu diff --git a/src/history_manager.py b/src/history_manager.py index 21c46ca0d..5c1456bde 100644 --- a/src/history_manager.py +++ b/src/history_manager.py @@ -30,21 +30,21 @@ import os if os.name == 'nt': - import warnings - warnings.filterwarnings(action='ignore') + import warnings + warnings.filterwarnings(action='ignore') - if os.path.isdir('gtk'): - # Used to create windows installer with GTK included - paths = os.environ['PATH'] - list_ = paths.split(';') - new_list = [] - for p in list_: - if p.find('gtk') < 0 and p.find('GTK') < 0: - new_list.append(p) - new_list.insert(0, 'gtk/lib') - new_list.insert(0, 'gtk/bin') - os.environ['PATH'] = ';'.join(new_list) - os.environ['GTK_BASEPATH'] = 'gtk' + if os.path.isdir('gtk'): + # Used to create windows installer with GTK included + paths = os.environ['PATH'] + list_ = paths.split(';') + new_list = [] + for p in list_: + if p.find('gtk') < 0 and p.find('GTK') < 0: + new_list.append(p) + new_list.insert(0, 'gtk/lib') + new_list.insert(0, 'gtk/bin') + os.environ['PATH'] = ';'.join(new_list) + os.environ['GTK_BASEPATH'] = 'gtk' import sys import signal @@ -57,23 +57,23 @@ import getopt from common import i18n def parseOpts(): - config_path = None + config_path = None - try: - shortargs = 'hc:' - longargs = 'help config_path=' - opts = getopt.getopt(sys.argv[1:], shortargs, longargs.split())[0] - except getopt.error, msg: - print str(msg) - print 'for help use --help' - sys.exit(2) - for o, a in opts: - if o in ('-h', '--help'): - print 'history_manager [--help] [--config-path]' - sys.exit() - elif o in ('-c', '--config-path'): - config_path = a - return config_path + try: + shortargs = 'hc:' + longargs = 'help config_path=' + opts = getopt.getopt(sys.argv[1:], shortargs, longargs.split())[0] + except getopt.error, msg: + print str(msg) + print 'for help use --help' + sys.exit(2) + for o, a in opts: + if o in ('-h', '--help'): + print 'history_manager [--help] [--config-path]' + sys.exit() + elif o in ('-c', '--config-path'): + config_path = a + return config_path config_path = parseOpts() del parseOpts @@ -89,7 +89,7 @@ from common.logger import LOG_DB_PATH, constants #FIXME: constants should implement 2 way mappings status = dict((constants.__dict__[i], i[5:].lower()) for i in \ - constants.__dict__.keys() if i.startswith('SHOW_')) + constants.__dict__.keys() if i.startswith('SHOW_')) from common import helpers import dialogs @@ -106,557 +106,555 @@ import sqlite3 as sqlite class HistoryManager: - def __init__(self): - pix = gtkgui_helpers.get_icon_pixmap('gajim') - gtk.window_set_default_icon(pix) # set the icon to all newly opened windows - - if not os.path.exists(LOG_DB_PATH): - dialogs.ErrorDialog(_('Cannot find history logs database'), - '%s does not exist.' % LOG_DB_PATH) - sys.exit() - - xml = gtkgui_helpers.get_gtk_builder('history_manager.ui') - self.window = xml.get_object('history_manager_window') - self.jids_listview = xml.get_object('jids_listview') - self.logs_listview = xml.get_object('logs_listview') - self.search_results_listview = xml.get_object('search_results_listview') - self.search_entry = xml.get_object('search_entry') - self.logs_scrolledwindow = xml.get_object('logs_scrolledwindow') - self.search_results_scrolledwindow = xml.get_object( - 'search_results_scrolledwindow') - self.welcome_vbox = xml.get_object('welcome_vbox') - - self.jids_already_in = [] # holds jids that we already have in DB - self.AT_LEAST_ONE_DELETION_DONE = False - - self.con = sqlite.connect(LOG_DB_PATH, timeout = 20.0, - isolation_level = 'IMMEDIATE') - self.cur = self.con.cursor() - - self._init_jids_listview() - self._init_logs_listview() - self._init_search_results_listview() - - self._fill_jids_listview() - - self.search_entry.grab_focus() - - self.window.show_all() - - xml.connect_signals(self) - - def _init_jids_listview(self): - self.jids_liststore = gtk.ListStore(str, str) # jid, jid_id - self.jids_listview.set_model(self.jids_liststore) - self.jids_listview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) - - renderer_text = gtk.CellRendererText() # holds jid - col = gtk.TreeViewColumn(_('Contacts'), renderer_text, text = 0) - self.jids_listview.append_column(col) - - self.jids_listview.get_selection().connect('changed', - self.on_jids_listview_selection_changed) - - def _init_logs_listview(self): - # log_line_id (HIDDEN), jid_id (HIDDEN), time, message, subject, nickname - self.logs_liststore = gtk.ListStore(str, str, str, str, str, str) - self.logs_listview.set_model(self.logs_liststore) - self.logs_listview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) - - renderer_text = gtk.CellRendererText() # holds time - col = gtk.TreeViewColumn(_('Date'), renderer_text, text = C_UNIXTIME) - col.set_sort_column_id(C_UNIXTIME) # user can click this header and sort - col.set_resizable(True) - self.logs_listview.append_column(col) - - renderer_text = gtk.CellRendererText() # holds nickname - col = gtk.TreeViewColumn(_('Nickname'), renderer_text, text = C_NICKNAME) - col.set_sort_column_id(C_NICKNAME) # user can click this header and sort - col.set_resizable(True) - col.set_visible(False) - self.nickname_col_for_logs = col - self.logs_listview.append_column(col) - - renderer_text = gtk.CellRendererText() # holds message - col = gtk.TreeViewColumn(_('Message'), renderer_text, markup = C_MESSAGE) - col.set_sort_column_id(C_MESSAGE) # user can click this header and sort - col.set_resizable(True) - self.message_col_for_logs = col - self.logs_listview.append_column(col) - - renderer_text = gtk.CellRendererText() # holds subject - col = gtk.TreeViewColumn(_('Subject'), renderer_text, text = C_SUBJECT) - col.set_sort_column_id(C_SUBJECT) # user can click this header and sort - col.set_resizable(True) - col.set_visible(False) - self.subject_col_for_logs = col - self.logs_listview.append_column(col) - - def _init_search_results_listview(self): - # log_line_id (HIDDEN), jid, time, message, subject, nickname - self.search_results_liststore = gtk.ListStore(str, str, str, str, str, str) - self.search_results_listview.set_model(self.search_results_liststore) - - renderer_text = gtk.CellRendererText() # holds JID (who said this) - col = gtk.TreeViewColumn(_('JID'), renderer_text, text = 1) - col.set_sort_column_id(1) # user can click this header and sort - col.set_resizable(True) - self.search_results_listview.append_column(col) - - renderer_text = gtk.CellRendererText() # holds time - col = gtk.TreeViewColumn(_('Date'), renderer_text, text = C_UNIXTIME) - col.set_sort_column_id(C_UNIXTIME) # user can click this header and sort - col.set_resizable(True) - self.search_results_listview.append_column(col) - - renderer_text = gtk.CellRendererText() # holds message - col = gtk.TreeViewColumn(_('Message'), renderer_text, text = C_MESSAGE) - col.set_sort_column_id(C_MESSAGE) # user can click this header and sort - col.set_resizable(True) - self.search_results_listview.append_column(col) - - renderer_text = gtk.CellRendererText() # holds subject - col = gtk.TreeViewColumn(_('Subject'), renderer_text, text = C_SUBJECT) - col.set_sort_column_id(C_SUBJECT) # user can click this header and sort - col.set_resizable(True) - self.search_results_listview.append_column(col) - - renderer_text = gtk.CellRendererText() # holds nickname - col = gtk.TreeViewColumn(_('Nickname'), renderer_text, text = C_NICKNAME) - col.set_sort_column_id(C_NICKNAME) # user can click this header and sort - col.set_resizable(True) - self.search_results_listview.append_column(col) - - def on_history_manager_window_delete_event(self, widget, event): - if self.AT_LEAST_ONE_DELETION_DONE: - def on_yes(clicked): - self.cur.execute('VACUUM') - self.con.commit() - gtk.main_quit() - - def on_no(): - gtk.main_quit() - - dialogs.YesNoDialog( - _('Do you want to clean up the database? ' - '(STRONGLY NOT RECOMMENDED IF GAJIM IS RUNNING)'), - _('Normally allocated database size will not be freed, ' - 'it will just become reusable. If you really want to reduce ' - 'database filesize, click YES, else click NO.' - '\n\nIn case you click YES, please wait...'), - on_response_yes=on_yes, on_response_no=on_no) - return - - gtk.main_quit() - - def _fill_jids_listview(self): - # get those jids that have at least one entry in logs - self.cur.execute('SELECT jid, jid_id FROM jids WHERE jid_id IN (SELECT ' - 'distinct logs.jid_id FROM logs) ORDER BY jid') - rows = self.cur.fetchall() # list of tupples: [(u'aaa@bbb',), (u'cc@dd',)] - for row in rows: - self.jids_already_in.append(row[0]) # jid - self.jids_liststore.append(row) # jid, jid_id - - def on_jids_listview_selection_changed(self, widget, data = None): - liststore, list_of_paths = self.jids_listview.get_selection()\ - .get_selected_rows() - paths_len = len(list_of_paths) - if paths_len == 0: # nothing is selected - return - - self.logs_liststore.clear() # clear the store - - self.welcome_vbox.hide() - self.search_results_scrolledwindow.hide() - self.logs_scrolledwindow.show() - - list_of_rowrefs = [] - for path in list_of_paths: # make them treerowrefs (it's needed) - list_of_rowrefs.append(gtk.TreeRowReference(liststore, path)) - - for rowref in list_of_rowrefs: # FILL THE STORE, for all rows selected - path = rowref.get_path() - if path is None: - continue - jid = liststore[path][0] # jid - self._fill_logs_listview(jid) - - def _get_jid_id(self, jid): - """ - jids table has jid and jid_id - logs table has log_id, jid_id, contact_name, time, kind, show, message - - So to ask logs we need jid_id that matches our jid in jids table this - method wants jid and returns the jid_id for later sql-ing on logs - """ - if jid.find('/') != -1: # if it has a / - jid_is_from_pm = self._jid_is_from_pm(jid) - if not jid_is_from_pm: # it's normal jid with resource - jid = jid.split('/', 1)[0] # remove the resource - self.cur.execute('SELECT jid_id FROM jids WHERE jid = ?', (jid,)) - jid_id = self.cur.fetchone()[0] - return str(jid_id) - - def _get_jid_from_jid_id(self, jid_id): - """ - jids table has jid and jid_id - - This method accepts jid_id and returns the jid for later sql-ing on logs - """ - self.cur.execute('SELECT jid FROM jids WHERE jid_id = ?', (jid_id,)) - jid = self.cur.fetchone()[0] - return jid - - def _jid_is_from_pm(self, jid): - """ - If jid is gajim@conf/nkour it's likely a pm one, how we know gajim@conf - is not a normal guy and nkour is not his resource? We ask if gajim@conf - is already in jids (with type room jid). This fails if user disables - logging for room and only enables for pm (so higly unlikely) and if we - fail we do not go chaos (user will see the first pm as if it was message - in room's public chat) and after that everything is ok - """ - possible_room_jid = jid.split('/', 1)[0] - - self.cur.execute('SELECT jid_id FROM jids WHERE jid = ? AND type = ?', - (possible_room_jid, constants.JID_ROOM_TYPE)) - row = self.cur.fetchone() - if row is None: - return False - else: - return True - - def _jid_is_room_type(self, jid): - """ - Return True/False if given id is room type or not eg. if it is room - """ - self.cur.execute('SELECT type FROM jids WHERE jid = ?', (jid,)) - row = self.cur.fetchone() - if row is None: - raise - elif row[0] == constants.JID_ROOM_TYPE: - return True - else: # normal type - return False - - def _fill_logs_listview(self, jid): - """ - Fill the listview with all messages that user sent to or received from - JID - """ - # no need to lower jid in this context as jid is already lowered - # as we use those jids from db - jid_id = self._get_jid_id(jid) - self.cur.execute(''' - SELECT log_line_id, jid_id, time, kind, message, subject, contact_name, show - FROM logs - WHERE jid_id = ? - ORDER BY time - ''', (jid_id,)) - - results = self.cur.fetchall() - - if self._jid_is_room_type(jid): # is it room? - self.nickname_col_for_logs.set_visible(True) - self.subject_col_for_logs.set_visible(False) - else: - self.nickname_col_for_logs.set_visible(False) - self.subject_col_for_logs.set_visible(True) - - for row in results: - # exposed in UI (TreeViewColumns) are only - # time, message, subject, nickname - # but store in liststore - # log_line_id, jid_id, time, message, subject, nickname - log_line_id, jid_id, time_, kind, message, subject, nickname, show = row - try: - time_ = time.strftime('%x', time.localtime(float(time_))).decode( - locale.getpreferredencoding()) - except ValueError: - pass - else: - color = None - if kind in (constants.KIND_SINGLE_MSG_RECV, - constants.KIND_CHAT_MSG_RECV, constants.KIND_GC_MSG): - # it is the other side - color = gajim.config.get('inmsgcolor') # so incoming color - elif kind in (constants.KIND_SINGLE_MSG_SENT, - constants.KIND_CHAT_MSG_SENT): # it is us - color = gajim.config.get('outmsgcolor') # so outgoing color - elif kind in (constants.KIND_STATUS, - constants.KIND_GCSTATUS): # is is statuses - color = gajim.config.get('statusmsgcolor') # so status color - # include status into (status) message - if message is None: - message = '' - else: - message = ' : ' + message - message = helpers.get_uf_show(gajim.SHOW_LIST[show]) + message - - message_ = ' every_foo_seconds: - self.last_time_printout = tim - tim = time.strftime('%X ', time.localtime(float(tim))) - buf.insert_with_tags_by_name(end_iter, tim + '\n', - 'time_sometimes') - - tag_name = '' - tag_msg = '' - - show = self._get_string_show_from_constant_int(show) - - if kind == constants.KIND_GC_MSG: - tag_name = 'incoming' - elif kind in (constants.KIND_SINGLE_MSG_RECV, - constants.KIND_CHAT_MSG_RECV): - contact_name = self.completion_dict[self.jid][C_INFO_NAME] - tag_name = 'incoming' - tag_msg = 'incomingtxt' - elif kind in (constants.KIND_SINGLE_MSG_SENT, - constants.KIND_CHAT_MSG_SENT): - if self.account: - contact_name = gajim.nicks[self.account] - else: - # we don't have roster, we don't know our own nick, use first - # account one (urk!) - account = gajim.contacts.get_accounts()[0] - contact_name = gajim.nicks[account] - tag_name = 'outgoing' - tag_msg = 'outgoingtxt' - elif kind == constants.KIND_GCSTATUS: - # message here (if not None) is status message - if message: - message = _('%(nick)s is now %(status)s: %(status_msg)s') %\ - {'nick': contact_name, 'status': helpers.get_uf_show(show), - 'status_msg': message } - else: - message = _('%(nick)s is now %(status)s') % {'nick': contact_name, - 'status': helpers.get_uf_show(show) } - tag_msg = 'status' - else: # 'status' - # message here (if not None) is status message - if show is None: # it means error - if message: - message = _('Error: %s') % message - else: - message = _('Error') - elif message: - message = _('Status is now: %(status)s: %(status_msg)s') % \ - {'status': helpers.get_uf_show(show), 'status_msg': message} - else: - message = _('Status is now: %(status)s') % { 'status': - helpers.get_uf_show(show) } - tag_msg = 'status' - - if message.startswith('/me ') or message.startswith('/me\n'): - tag_msg = tag_name - else: - # do not do this if gcstats, avoid dupping contact_name - # eg. nkour: nkour is now Offline - if contact_name and kind != constants.KIND_GCSTATUS: - # add stuff before and after contact name - before_str = gajim.config.get('before_nickname') - before_str = helpers.from_one_line(before_str) - after_str = gajim.config.get('after_nickname') - after_str = helpers.from_one_line(after_str) - format = before_str + contact_name + after_str + ' ' - buf.insert_with_tags_by_name(end_iter, format, tag_name) - - if subject: - message = _('Subject: %s\n') % subject + message - message += '\n' - if tag_msg: - self.history_textview.print_real_text(message, [tag_msg], - name=contact_name) - else: - self.history_textview.print_real_text(message, name=contact_name) - - def on_query_entry_activate(self, widget): - text = self.query_entry.get_text() - model = self.results_treeview.get_model() - model.clear() - if text == '': - self.results_window.set_property('visible', False) - return - else: - self.results_window.set_property('visible', True) - - # perform search in preselected jids - # jids are preselected with the query_combobox (all, single jid...) - for jid in self.jids_to_search: - account = self.completion_dict[jid][C_INFO_ACCOUNT] - if account is None: - # We do not know an account. This can only happen if the contact is offine, - # or if we browse a groupchat history. The account is not needed, a dummy can - # be set. - # This may leed to wrong self nick in the displayed history (Uggh!) - account = gajim.contacts.get_accounts()[0] - - # contact_name, time, kind, show, message, subject - results = gajim.logger.get_search_results_for_query( - jid, text, account) - #FIXME: - # add "subject: | message: " in message column if kind is single - # also do we need show at all? (we do not search on subject) - for row in results: - contact_name = row[0] - if not contact_name: - kind = row[2] - if kind == constants.KIND_CHAT_MSG_SENT: # it's us! :) - contact_name = gajim.nicks[account] - else: - contact_name = self.completion_dict[jid][C_INFO_NAME] - tim = row[1] - message = row[4] - local_time = time.localtime(tim) - date = time.strftime('%Y-%m-%d', local_time) - - # jid (to which log is assigned to), name, date, message, - # time (full unix time) - model.append((jid, contact_name, date, message, tim)) - - def on_query_combobox_changed(self, widget): - if self.query_combobox.get_active() < 0: - return # custom entry - self.account = None - self.jid = None - self.jids_to_search = [] - self._load_history(None) # clear textview - - if self.query_combobox.get_active() == 0: - # JID or Contact name - self.query_entry.set_sensitive(False) - self.jid_entry.grab_focus() - if self.query_combobox.get_active() == 1: - # Groupchat Histories - self.query_entry.set_sensitive(True) - self.query_entry.grab_focus() - self.jids_to_search = (jid for jid in gajim.logger.get_jids_in_db() - if gajim.logger.jid_is_room_jid(jid)) - if self.query_combobox.get_active() == 2: - # All Chat Histories - self.query_entry.set_sensitive(True) - self.query_entry.grab_focus() - self.jids_to_search = gajim.logger.get_jids_in_db() - - def on_results_treeview_row_activated(self, widget, path, column): - """ - A row was double clicked, get date from row, and select it in calendar - which results to showing conversation logs for that date - """ - # get currently selected date - cur_year, cur_month = self.calendar.get_date()[0:2] - cur_month = gtkgui_helpers.make_gtk_month_python_month(cur_month) - model = widget.get_model() - # make it a tupple (Y, M, D, 0, 0, 0...) - tim = time.strptime(model[path][C_UNIXTIME], '%Y-%m-%d') - year = tim[0] - gtk_month = tim[1] - month = gtkgui_helpers.make_python_month_gtk_month(gtk_month) - day = tim[2] - - # switch to belonging logfile if necessary - log_jid = model[path][C_LOG_JID] - if log_jid != self.jid: - self._load_history(log_jid, None) - - # avoid reruning mark days algo if same month and year! - if year != cur_year or gtk_month != cur_month: - self.calendar.select_month(month, year) - - self.calendar.select_day(day) - unix_time = model[path][C_TIME] - self._scroll_to_result(unix_time) - #FIXME: one day do not search just for unix_time but the whole and user - # specific format of the textbuffer line [time] nick: message - # and highlight all that - - def _scroll_to_result(self, unix_time): - """ - Scroll to the result using unix_time and highlight line - """ - start_iter = self.history_buffer.get_start_iter() - local_time = time.localtime(float(unix_time)) - tim = time.strftime('%X', local_time) - result = start_iter.forward_search(tim, gtk.TEXT_SEARCH_VISIBLE_ONLY, - None) - if result is not None: - match_start_iter, match_end_iter = result - match_start_iter.backward_char() # include '[' or other character before time - match_end_iter.forward_line() # highlight all message not just time - self.history_buffer.apply_tag_by_name('highlight', match_start_iter, - match_end_iter) - - match_start_mark = self.history_buffer.create_mark('match_start', - match_start_iter, True) - self.history_textview.tv.scroll_to_mark(match_start_mark, 0, True) - - def on_log_history_checkbutton_toggled(self, widget): - # log conversation history? - oldlog = True - no_log_for = gajim.config.get_per('accounts', self.account, - 'no_log_for').split() - if self.jid in no_log_for: - oldlog = False - log = widget.get_active() - if not log and not self.jid in no_log_for: - no_log_for.append(self.jid) - if log and self.jid in no_log_for: - no_log_for.remove(self.jid) - if oldlog != log: - gajim.config.set_per('accounts', self.account, 'no_log_for', - ' '.join(no_log_for)) - - def open_history(self, jid, account): - """ - Load chat history of the specified jid - """ - self.jid_entry.set_text(jid) - if account and account not in self.accounts_seen_online: - # Update dict to not only show bare jid - gobject.idle_add(self._fill_completion_dict().next) - else: - # Only in that case because it's called by self._fill_completion_dict() - # otherwise - self._load_history(jid, account) - self.results_window.set_property('visible', False) - - def save_state(self): - x,y = self.window.window.get_root_origin() - width, height = self.window.get_size() - - gajim.config.set('history_window_x-position', x) - gajim.config.set('history_window_y-position', y) - gajim.config.set('history_window_width', width); - gajim.config.set('history_window_height', height); - - gajim.interface.save_config() - -# vim: se ts=3: + """ + Class for browsing logs of conversations with contacts + """ + + def __init__(self, jid = None, account = None): + xml = gtkgui_helpers.get_gtk_builder('history_window.ui') + self.window = xml.get_object('history_window') + self.calendar = xml.get_object('calendar') + scrolledwindow = xml.get_object('scrolledwindow') + self.history_textview = conversation_textview.ConversationTextview( + account, used_in_history_window = True) + scrolledwindow.add(self.history_textview.tv) + self.history_buffer = self.history_textview.tv.get_buffer() + self.history_buffer.create_tag('highlight', background = 'yellow') + self.checkbutton = xml.get_object('log_history_checkbutton') + self.checkbutton.connect('toggled', + self.on_log_history_checkbutton_toggled) + self.query_entry = xml.get_object('query_entry') + self.query_combobox = xml.get_object('query_combobox') + self.jid_entry = self.query_combobox.child + self.jid_entry.connect('activate', self.on_jid_entry_activate) + self.query_combobox.set_active(0) + self.results_treeview = xml.get_object('results_treeview') + self.results_window = xml.get_object('results_scrolledwindow') + + # contact_name, date, message, time + model = gtk.ListStore(str, str, str, str, str) + self.results_treeview.set_model(model) + col = gtk.TreeViewColumn(_('Name')) + self.results_treeview.append_column(col) + renderer = gtk.CellRendererText() + col.pack_start(renderer) + col.set_attributes(renderer, text = C_CONTACT_NAME) + col.set_sort_column_id(C_CONTACT_NAME) # user can click this header and sort + col.set_resizable(True) + + col = gtk.TreeViewColumn(_('Date')) + self.results_treeview.append_column(col) + renderer = gtk.CellRendererText() + col.pack_start(renderer) + col.set_attributes(renderer, text = C_UNIXTIME) + col.set_sort_column_id(C_UNIXTIME) # user can click this header and sort + col.set_resizable(True) + + col = gtk.TreeViewColumn(_('Message')) + self.results_treeview.append_column(col) + renderer = gtk.CellRendererText() + col.pack_start(renderer) + col.set_attributes(renderer, text = C_MESSAGE) + col.set_resizable(True) + + self.jid = None # The history we are currently viewing + self.account = None + self.completion_dict = {} + self.accounts_seen_online = [] # Update dict when new accounts connect + self.jids_to_search = [] + + # This will load history too + gobject.idle_add(self._fill_completion_dict().next) + + if jid: + self.jid_entry.set_text(jid) + else: + self._load_history(None) + + gtkgui_helpers.resize_window(self.window, + gajim.config.get('history_window_width'), + gajim.config.get('history_window_height')) + gtkgui_helpers.move_window(self.window, + gajim.config.get('history_window_x-position'), + gajim.config.get('history_window_y-position')) + + xml.connect_signals(self) + self.window.show_all() + + def _fill_completion_dict(self): + """ + Fill completion_dict for key auto completion. Then load history for + current jid (by calling another function) + + Key will be either jid or full_completion_name (contact name or long + description like "pm-contact from groupchat...."). + + {key : (jid, account, nick_name, full_completion_name} + This is a generator and does pseudo-threading via idle_add(). + """ + liststore = gtkgui_helpers.get_completion_liststore(self.jid_entry) + + # Add all jids in logs.db: + db_jids = gajim.logger.get_jids_in_db() + completion_dict = dict.fromkeys(db_jids) + + self.accounts_seen_online = gajim.contacts.get_accounts()[:] + + # Enhance contacts of online accounts with contact. Needed for mapping below + for account in self.accounts_seen_online: + completion_dict.update(helpers.get_contact_dict_for_account(account)) + + muc_active_img = gtkgui_helpers.load_icon('muc_active') + contact_img = gajim.interface.jabber_state_images['16']['online'] + muc_active_pix = muc_active_img.get_pixbuf() + contact_pix = contact_img.get_pixbuf() + + keys = completion_dict.keys() + # Move the actual jid at first so we load history faster + actual_jid = self.jid_entry.get_text().decode('utf-8') + if actual_jid in keys: + keys.remove(actual_jid) + keys.insert(0, actual_jid) + if None in keys: + keys.remove(None) + # Map jid to info tuple + # Warning : This for is time critical with big DB + for key in keys: + completed = key + contact = completion_dict[completed] + if contact: + info_name = contact.get_shown_name() + info_completion = info_name + info_jid = contact.jid + else: + # Corrensponding account is offline, we know nothing + info_name = completed.split('@')[0] + info_completion = completed + info_jid = completed + + info_acc = self._get_account_for_jid(info_jid) + + if gajim.logger.jid_is_room_jid(completed) or\ + gajim.logger.jid_is_from_pm(completed): + pix = muc_active_pix + if gajim.logger.jid_is_from_pm(completed): + # It's PM. Make it easier to find + room, nick = gajim.get_room_and_nick_from_fjid(completed) + info_completion = '%s from %s' % (nick, room) + completed = info_completion + info_name = nick + else: + pix = contact_pix + + liststore.append((pix, completed)) + self.completion_dict[key] = (info_jid, info_acc, info_name, + info_completion) + self.completion_dict[completed] = (info_jid, info_acc, + info_name, info_completion) + if key == actual_jid: + self._load_history(info_jid, info_acc) + yield True + keys.sort() + yield False + + def _get_account_for_jid(self, jid): + """ + Return the corresponding account of the jid. May be None if an account + could not be found + """ + accounts = gajim.contacts.get_accounts() + account = None + for acc in accounts: + jid_list = gajim.contacts.get_jid_list(acc) + gc_list = gajim.contacts.get_gc_list(acc) + if jid in jid_list or jid in gc_list: + account = acc + break + return account + + def on_history_window_destroy(self, widget): + self.history_textview.del_handlers() + del gajim.interface.instances['logs'] + + def on_history_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: + self.save_state() + self.window.destroy() + + def on_close_button_clicked(self, widget): + self.save_state() + self.window.destroy() + + def on_jid_entry_activate(self, widget): + if not self.query_combobox.get_active() < 0: + # Don't disable querybox when we have changed the combobox + # to GC or All and hit enter + return + jid = self.jid_entry.get_text().decode('utf-8') + account = None # we don't know the account, could be any. Search for it! + self._load_history(jid, account) + self.results_window.set_property('visible', False) + + def on_jid_entry_focus(self, widget, event): + widget.select_region(0, -1) # select text + + def _load_history(self, jid_or_name, account=None): + """ + Load history for the given jid/name and show it + """ + if jid_or_name and jid_or_name in self.completion_dict: + # a full qualified jid or a contact name was entered + info_jid, info_account, info_name, info_completion = self.completion_dict[jid_or_name] + self.jids_to_search = [info_jid] + self.jid = info_jid + + if account: + self.account = account + else: + self.account = info_account + if self.account is None: + # We don't know account. Probably a gc not opened or an + # account not connected. + # Disable possibility to say if we want to log or not + self.checkbutton.set_sensitive(False) + else: + # Are log disabled for account ? + if self.account in gajim.config.get_per('accounts', self.account, + 'no_log_for').split(' '): + self.checkbutton.set_active(False) + self.checkbutton.set_sensitive(False) + else: + # Are log disabled for jid ? + log = True + if self.jid in gajim.config.get_per('accounts', self.account, + 'no_log_for').split(' '): + log = False + self.checkbutton.set_active(log) + self.checkbutton.set_sensitive(True) + + self.jids_to_search = [info_jid] + + # select logs for last date we have logs with contact + self.calendar.set_sensitive(True) + last_log = \ + gajim.logger.get_last_date_that_has_logs(self.jid, self.account) + + date = time.localtime(last_log) + + y, m, d = date[0], date[1], date[2] + gtk_month = gtkgui_helpers.make_python_month_gtk_month(m) + self.calendar.select_month(gtk_month, y) + self.calendar.select_day(d) + + self.query_entry.set_sensitive(True) + self.query_entry.grab_focus() + + title = _('Conversation History with %s') % info_name + self.window.set_title(title) + self.jid_entry.set_text(info_completion) + + else: # neither a valid jid, nor an existing contact name was entered + # we have got nothing to show or to search in + self.jid = None + self.account = None + + self.history_buffer.set_text('') # clear the buffer + self.query_entry.set_sensitive(False) + + self.checkbutton.set_sensitive(False) + self.calendar.set_sensitive(False) + self.calendar.clear_marks() + + self.results_window.set_property('visible', False) + + title = _('Conversation History') + self.window.set_title(title) + + def on_calendar_day_selected(self, widget): + if not self.jid: + return + year, month, day = widget.get_date() # integers + month = gtkgui_helpers.make_gtk_month_python_month(month) + self._add_lines_for_date(year, month, day) + + def on_calendar_month_changed(self, widget): + """ + Ask for days in this month, if they have logs it bolds them (marks them) + """ + if not self.jid: + return + year, month, day = widget.get_date() # integers + # in gtk January is 1, in python January is 0, + # I want the second + # first day of month is 1 not 0 + widget.clear_marks() + month = gtkgui_helpers.make_gtk_month_python_month(month) + days_in_this_month = calendar.monthrange(year, month)[1] + try: + log_days = gajim.logger.get_days_with_logs(self.jid, year, month, + days_in_this_month, self.account) + except exceptions.PysqliteOperationalError, e: + dialogs.ErrorDialog(_('Disk Error'), str(e)) + return + for day in log_days: + widget.mark_day(day) + + def _get_string_show_from_constant_int(self, show): + if show == constants.SHOW_ONLINE: + show = 'online' + elif show == constants.SHOW_CHAT: + show = 'chat' + elif show == constants.SHOW_AWAY: + show = 'away' + elif show == constants.SHOW_XA: + show = 'xa' + elif show == constants.SHOW_DND: + show = 'dnd' + elif show == constants.SHOW_OFFLINE: + show = 'offline' + + return show + + def _add_lines_for_date(self, year, month, day): + """ + Add all the lines for given date in textbuffer + """ + self.history_buffer.set_text('') # clear the buffer first + self.last_time_printout = 0 + + lines = gajim.logger.get_conversation_for_date(self.jid, year, month, day, self.account) + # lines holds list with tupples that have: + # contact_name, time, kind, show, message + for line in lines: + # line[0] is contact_name, line[1] is time of message + # line[2] is kind, line[3] is show, line[4] is message + self._add_new_line(line[0], line[1], line[2], line[3], line[4], + line[5]) + + def _add_new_line(self, contact_name, tim, kind, show, message, subject): + """ + Add a new line in textbuffer + """ + if not message and kind not in (constants.KIND_STATUS, + constants.KIND_GCSTATUS): + return + buf = self.history_buffer + end_iter = buf.get_end_iter() + + if gajim.config.get('print_time') == 'always': + timestamp_str = gajim.config.get('time_stamp') + timestamp_str = helpers.from_one_line(timestamp_str) + tim = time.strftime(timestamp_str, time.localtime(float(tim))) + buf.insert(end_iter, tim) # add time + elif gajim.config.get('print_time') == 'sometimes': + every_foo_seconds = 60 * gajim.config.get( + 'print_ichat_every_foo_minutes') + seconds_passed = tim - self.last_time_printout + if seconds_passed > every_foo_seconds: + self.last_time_printout = tim + tim = time.strftime('%X ', time.localtime(float(tim))) + buf.insert_with_tags_by_name(end_iter, tim + '\n', + 'time_sometimes') + + tag_name = '' + tag_msg = '' + + show = self._get_string_show_from_constant_int(show) + + if kind == constants.KIND_GC_MSG: + tag_name = 'incoming' + elif kind in (constants.KIND_SINGLE_MSG_RECV, + constants.KIND_CHAT_MSG_RECV): + contact_name = self.completion_dict[self.jid][C_INFO_NAME] + tag_name = 'incoming' + tag_msg = 'incomingtxt' + elif kind in (constants.KIND_SINGLE_MSG_SENT, + constants.KIND_CHAT_MSG_SENT): + if self.account: + contact_name = gajim.nicks[self.account] + else: + # we don't have roster, we don't know our own nick, use first + # account one (urk!) + account = gajim.contacts.get_accounts()[0] + contact_name = gajim.nicks[account] + tag_name = 'outgoing' + tag_msg = 'outgoingtxt' + elif kind == constants.KIND_GCSTATUS: + # message here (if not None) is status message + if message: + message = _('%(nick)s is now %(status)s: %(status_msg)s') %\ + {'nick': contact_name, 'status': helpers.get_uf_show(show), + 'status_msg': message } + else: + message = _('%(nick)s is now %(status)s') % {'nick': contact_name, + 'status': helpers.get_uf_show(show) } + tag_msg = 'status' + else: # 'status' + # message here (if not None) is status message + if show is None: # it means error + if message: + message = _('Error: %s') % message + else: + message = _('Error') + elif message: + message = _('Status is now: %(status)s: %(status_msg)s') % \ + {'status': helpers.get_uf_show(show), 'status_msg': message} + else: + message = _('Status is now: %(status)s') % { 'status': + helpers.get_uf_show(show) } + tag_msg = 'status' + + if message.startswith('/me ') or message.startswith('/me\n'): + tag_msg = tag_name + else: + # do not do this if gcstats, avoid dupping contact_name + # eg. nkour: nkour is now Offline + if contact_name and kind != constants.KIND_GCSTATUS: + # add stuff before and after contact name + before_str = gajim.config.get('before_nickname') + before_str = helpers.from_one_line(before_str) + after_str = gajim.config.get('after_nickname') + after_str = helpers.from_one_line(after_str) + format = before_str + contact_name + after_str + ' ' + buf.insert_with_tags_by_name(end_iter, format, tag_name) + + if subject: + message = _('Subject: %s\n') % subject + message + message += '\n' + if tag_msg: + self.history_textview.print_real_text(message, [tag_msg], + name=contact_name) + else: + self.history_textview.print_real_text(message, name=contact_name) + + def on_query_entry_activate(self, widget): + text = self.query_entry.get_text() + model = self.results_treeview.get_model() + model.clear() + if text == '': + self.results_window.set_property('visible', False) + return + else: + self.results_window.set_property('visible', True) + + # perform search in preselected jids + # jids are preselected with the query_combobox (all, single jid...) + for jid in self.jids_to_search: + account = self.completion_dict[jid][C_INFO_ACCOUNT] + if account is None: + # We do not know an account. This can only happen if the contact is offine, + # or if we browse a groupchat history. The account is not needed, a dummy can + # be set. + # This may leed to wrong self nick in the displayed history (Uggh!) + account = gajim.contacts.get_accounts()[0] + + # contact_name, time, kind, show, message, subject + results = gajim.logger.get_search_results_for_query( + jid, text, account) + #FIXME: + # add "subject: | message: " in message column if kind is single + # also do we need show at all? (we do not search on subject) + for row in results: + contact_name = row[0] + if not contact_name: + kind = row[2] + if kind == constants.KIND_CHAT_MSG_SENT: # it's us! :) + contact_name = gajim.nicks[account] + else: + contact_name = self.completion_dict[jid][C_INFO_NAME] + tim = row[1] + message = row[4] + local_time = time.localtime(tim) + date = time.strftime('%Y-%m-%d', local_time) + + # jid (to which log is assigned to), name, date, message, + # time (full unix time) + model.append((jid, contact_name, date, message, tim)) + + def on_query_combobox_changed(self, widget): + if self.query_combobox.get_active() < 0: + return # custom entry + self.account = None + self.jid = None + self.jids_to_search = [] + self._load_history(None) # clear textview + + if self.query_combobox.get_active() == 0: + # JID or Contact name + self.query_entry.set_sensitive(False) + self.jid_entry.grab_focus() + if self.query_combobox.get_active() == 1: + # Groupchat Histories + self.query_entry.set_sensitive(True) + self.query_entry.grab_focus() + self.jids_to_search = (jid for jid in gajim.logger.get_jids_in_db() + if gajim.logger.jid_is_room_jid(jid)) + if self.query_combobox.get_active() == 2: + # All Chat Histories + self.query_entry.set_sensitive(True) + self.query_entry.grab_focus() + self.jids_to_search = gajim.logger.get_jids_in_db() + + def on_results_treeview_row_activated(self, widget, path, column): + """ + A row was double clicked, get date from row, and select it in calendar + which results to showing conversation logs for that date + """ + # get currently selected date + cur_year, cur_month = self.calendar.get_date()[0:2] + cur_month = gtkgui_helpers.make_gtk_month_python_month(cur_month) + model = widget.get_model() + # make it a tupple (Y, M, D, 0, 0, 0...) + tim = time.strptime(model[path][C_UNIXTIME], '%Y-%m-%d') + year = tim[0] + gtk_month = tim[1] + month = gtkgui_helpers.make_python_month_gtk_month(gtk_month) + day = tim[2] + + # switch to belonging logfile if necessary + log_jid = model[path][C_LOG_JID] + if log_jid != self.jid: + self._load_history(log_jid, None) + + # avoid reruning mark days algo if same month and year! + if year != cur_year or gtk_month != cur_month: + self.calendar.select_month(month, year) + + self.calendar.select_day(day) + unix_time = model[path][C_TIME] + self._scroll_to_result(unix_time) + #FIXME: one day do not search just for unix_time but the whole and user + # specific format of the textbuffer line [time] nick: message + # and highlight all that + + def _scroll_to_result(self, unix_time): + """ + Scroll to the result using unix_time and highlight line + """ + start_iter = self.history_buffer.get_start_iter() + local_time = time.localtime(float(unix_time)) + tim = time.strftime('%X', local_time) + result = start_iter.forward_search(tim, gtk.TEXT_SEARCH_VISIBLE_ONLY, + None) + if result is not None: + match_start_iter, match_end_iter = result + match_start_iter.backward_char() # include '[' or other character before time + match_end_iter.forward_line() # highlight all message not just time + self.history_buffer.apply_tag_by_name('highlight', match_start_iter, + match_end_iter) + + match_start_mark = self.history_buffer.create_mark('match_start', + match_start_iter, True) + self.history_textview.tv.scroll_to_mark(match_start_mark, 0, True) + + def on_log_history_checkbutton_toggled(self, widget): + # log conversation history? + oldlog = True + no_log_for = gajim.config.get_per('accounts', self.account, + 'no_log_for').split() + if self.jid in no_log_for: + oldlog = False + log = widget.get_active() + if not log and not self.jid in no_log_for: + no_log_for.append(self.jid) + if log and self.jid in no_log_for: + no_log_for.remove(self.jid) + if oldlog != log: + gajim.config.set_per('accounts', self.account, 'no_log_for', + ' '.join(no_log_for)) + + def open_history(self, jid, account): + """ + Load chat history of the specified jid + """ + self.jid_entry.set_text(jid) + if account and account not in self.accounts_seen_online: + # Update dict to not only show bare jid + gobject.idle_add(self._fill_completion_dict().next) + else: + # Only in that case because it's called by self._fill_completion_dict() + # otherwise + self._load_history(jid, account) + self.results_window.set_property('visible', False) + + def save_state(self): + x, y = self.window.window.get_root_origin() + width, height = self.window.get_size() + + gajim.config.set('history_window_x-position', x) + gajim.config.set('history_window_y-position', y) + gajim.config.set('history_window_width', width); + gajim.config.set('history_window_height', height); + + gajim.interface.save_config() diff --git a/src/htmltextview.py b/src/htmltextview.py index e91af9462..e37556105 100644 --- a/src/htmltextview.py +++ b/src/htmltextview.py @@ -48,9 +48,9 @@ import urllib2 import operator if __name__ == '__main__': - from common import i18n - import common.configpaths - common.configpaths.gajimpaths.init(None) + from common import i18n + import common.configpaths + common.configpaths.gajimpaths.init(None) from common import gajim import tooltips @@ -63,26 +63,26 @@ allwhitespace_rx = re.compile('^\\s*$') # pixels = points * display_resolution display_resolution = 0.3514598*(gtk.gdk.screen_height() / - float(gtk.gdk.screen_height_mm())) + float(gtk.gdk.screen_height_mm())) # embryo of CSS classes classes = { - #'system-message':';display: none', - 'problematic':';color: red', + #'system-message':';display: none', + 'problematic': ';color: red', } # styles for elements element_styles = { - 'u' : ';text-decoration: underline', - 'em' : ';font-style: oblique', - 'cite' : '; background-color:rgb(170,190,250); font-style: oblique', - 'li' : '; margin-left: 1em; margin-right: 10%', - 'strong' : ';font-weight: bold', - 'pre' : '; background-color:rgb(190,190,190); font-family: monospace; white-space: pre; margin-left: 1em; margin-right: 10%', - 'kbd' : ';background-color:rgb(210,210,210);font-family: monospace', - 'blockquote': '; background-color:rgb(170,190,250); margin-left: 2em; margin-right: 10%', - 'dt' : ';font-weight: bold; font-style: oblique', - 'dd' : ';margin-left: 2em; font-style: oblique' + 'u' : ';text-decoration: underline', + 'em' : ';font-style: oblique', + 'cite' : '; background-color:rgb(170,190,250); font-style: oblique', + 'li' : '; margin-left: 1em; margin-right: 10%', + 'strong' : ';font-weight: bold', + 'pre' : '; background-color:rgb(190,190,190); font-family: monospace; white-space: pre; margin-left: 1em; margin-right: 10%', + 'kbd' : ';background-color:rgb(210,210,210);font-family: monospace', + 'blockquote': '; background-color:rgb(170,190,250); margin-left: 2em; margin-right: 10%', + 'dt' : ';font-weight: bold; font-style: oblique', + 'dd' : ';margin-left: 2em; font-style: oblique' } # no difference for the moment element_styles['dfn'] = element_styles['em'] @@ -134,18 +134,18 @@ element_styles['b'] = element_styles['strong'] # Inline.mix # Character-level elements # InlineNoAnchor.class -# Anchor element +# Anchor element # InlinePre.mix # Pre element # # XHTML-IM also uses the following Attribute Groups: # # Core.extra.attrib -# TBD +# TBD # I18n.extra.attrib -# TBD +# TBD # Common.extra -# style +# style # # # ... @@ -178,906 +178,904 @@ INLINE = INLINE_PHRASAL.union(INLINE_PRES).union(INLINE_STRUCT) LIST_ELEMS = set( 'dl, ol, ul'.split(', ')) for name in BLOCK_HEAD: - num = eval(name[1]) - size = (num-1) // 2 - weigth = (num - 1) % 2 - element_styles[name] = '; font-size: %s; %s' % ( ('large', 'medium', 'small')[size], - ('font-weight: bold', 'font-style: oblique')[weigth], - ) + num = eval(name[1]) + size = (num-1) // 2 + weigth = (num - 1) % 2 + element_styles[name] = '; font-size: %s; %s' % ( ('large', 'medium', 'small')[size], + ('font-weight: bold', 'font-style: oblique')[weigth], + ) def _parse_css_color(color): - if color.startswith('rgb(') and color.endswith(')'): - r, g, b = [int(c)*257 for c in color[4:-1].split(',')] - return gtk.gdk.Color(r, g, b) - else: - return gtk.gdk.color_parse(color) + if color.startswith('rgb(') and color.endswith(')'): + r, g, b = [int(c)*257 for c in color[4:-1].split(',')] + return gtk.gdk.Color(r, g, b) + else: + return gtk.gdk.color_parse(color) def style_iter(style): - return ([x.strip() for x in item.split(':', 1)] for item in style.split(';')\ - if len(item.strip())) + return ([x.strip() for x in item.split(':', 1)] for item in style.split(';')\ + if len(item.strip())) class HtmlHandler(xml.sax.handler.ContentHandler): - """ - A handler to display html to a gtk textview + """ + A handler to display html to a gtk textview - It keeps a stack of "style spans" (start/end element pairs) and a stack of - list counters, for nested lists. - """ - def __init__(self, conv_textview, startiter): - xml.sax.handler.ContentHandler.__init__(self) - self.textbuf = conv_textview.tv.get_buffer() - self.textview = conv_textview.tv - self.iter = startiter - self.conv_textview = conv_textview - self.text = '' - self.starting=True - self.preserve = False - self.styles = [] # a gtk.TextTag or None, for each span level - self.list_counters = [] # stack (top at head) of list - # counters, or None for unordered list + It keeps a stack of "style spans" (start/end element pairs) and a stack of + list counters, for nested lists. + """ + def __init__(self, conv_textview, startiter): + xml.sax.handler.ContentHandler.__init__(self) + self.textbuf = conv_textview.tv.get_buffer() + self.textview = conv_textview.tv + self.iter = startiter + self.conv_textview = conv_textview + self.text = '' + self.starting=True + self.preserve = False + self.styles = [] # a gtk.TextTag or None, for each span level + self.list_counters = [] # stack (top at head) of list + # counters, or None for unordered list - def _parse_style_color(self, tag, value): - color = _parse_css_color(value) - tag.set_property('foreground-gdk', color) + def _parse_style_color(self, tag, value): + color = _parse_css_color(value) + tag.set_property('foreground-gdk', color) - def _parse_style_background_color(self, tag, value): - color = _parse_css_color(value) - tag.set_property('background-gdk', color) - tag.set_property('paragraph-background-gdk', color) + def _parse_style_background_color(self, tag, value): + color = _parse_css_color(value) + tag.set_property('background-gdk', color) + tag.set_property('paragraph-background-gdk', color) - def _get_current_attributes(self): - attrs = self.textview.get_default_attributes() - self.iter.backward_char() - self.iter.get_attributes(attrs) - self.iter.forward_char() - return attrs + def _get_current_attributes(self): + attrs = self.textview.get_default_attributes() + self.iter.backward_char() + self.iter.get_attributes(attrs) + self.iter.forward_char() + return attrs - def __parse_length_frac_size_allocate(self, textview, allocation, - frac, callback, args): - callback(allocation.width*frac, *args) + def __parse_length_frac_size_allocate(self, textview, allocation, + frac, callback, args): + callback(allocation.width*frac, *args) - def _parse_length(self, value, font_relative, block_relative, minl, maxl, callback, *args): - """ - Parse/calc length, converting to pixels, calls callback(length, *args) - when the length is first computed or changes - """ - if value.endswith('%'): - val = float(value[:-1]) - sign = cmp(val,0) - # limits: 1% to 500% - val = sign*max(1,min(abs(val),500)) - frac = val/100 - if font_relative: - attrs = self._get_current_attributes() - font_size = attrs.font.get_size() / pango.SCALE - callback(frac*display_resolution*font_size, *args) - elif block_relative: - # CSS says 'Percentage values: refer to width of the closest - # block-level ancestor' - # This is difficult/impossible to implement, so we use - # textview width instead; a reasonable approximation.. - alloc = self.textview.get_allocation() - self.__parse_length_frac_size_allocate(self.textview, alloc, - frac, callback, args) - self.textview.connect('size-allocate', - self.__parse_length_frac_size_allocate, - frac, callback, args) - else: - callback(frac, *args) - return + def _parse_length(self, value, font_relative, block_relative, minl, maxl, callback, *args): + """ + Parse/calc length, converting to pixels, calls callback(length, *args) + when the length is first computed or changes + """ + if value.endswith('%'): + val = float(value[:-1]) + sign = cmp(val, 0) + # limits: 1% to 500% + val = sign*max(1, min(abs(val), 500)) + frac = val/100 + if font_relative: + attrs = self._get_current_attributes() + font_size = attrs.font.get_size() / pango.SCALE + callback(frac*display_resolution*font_size, *args) + elif block_relative: + # CSS says 'Percentage values: refer to width of the closest + # block-level ancestor' + # This is difficult/impossible to implement, so we use + # textview width instead; a reasonable approximation.. + alloc = self.textview.get_allocation() + self.__parse_length_frac_size_allocate(self.textview, alloc, + frac, callback, args) + self.textview.connect('size-allocate', + self.__parse_length_frac_size_allocate, + frac, callback, args) + else: + callback(frac, *args) + return - def get_val(): - val = float(value[:-2]) - sign = cmp(val,0) - # validate length - return sign*max(minl,min(abs(val*display_resolution),maxl)) - if value.endswith('pt'): # points - callback(get_val()*display_resolution, *args) + def get_val(): + val = float(value[:-2]) + sign = cmp(val, 0) + # validate length + return sign*max(minl, min(abs(val*display_resolution), maxl)) + if value.endswith('pt'): # points + callback(get_val()*display_resolution, *args) - elif value.endswith('em'): # ems, the width of the element's font - attrs = self._get_current_attributes() - font_size = attrs.font.get_size() / pango.SCALE - callback(get_val()*display_resolution*font_size, *args) + elif value.endswith('em'): # ems, the width of the element's font + attrs = self._get_current_attributes() + font_size = attrs.font.get_size() / pango.SCALE + callback(get_val()*display_resolution*font_size, *args) - elif value.endswith('ex'): # x-height, ~ the height of the letter 'x' - # FIXME: figure out how to calculate this correctly - # for now 'em' size is used as approximation - attrs = self._get_current_attributes() - font_size = attrs.font.get_size() / pango.SCALE - callback(get_val()*display_resolution*font_size, *args) + elif value.endswith('ex'): # x-height, ~ the height of the letter 'x' + # FIXME: figure out how to calculate this correctly + # for now 'em' size is used as approximation + attrs = self._get_current_attributes() + font_size = attrs.font.get_size() / pango.SCALE + callback(get_val()*display_resolution*font_size, *args) - elif value.endswith('px'): # pixels - callback(get_val(), *args) + elif value.endswith('px'): # pixels + callback(get_val(), *args) - else: - try: - # TODO: isn't "no units" interpreted as pixels? - val = int(value) - sign = cmp(val,0) - # validate length - val = sign*max(minl,min(abs(val),maxl)) - callback(val, *args) - except Exception: - warnings.warn('Unable to parse length value "%s"' % value) + else: + try: + # TODO: isn't "no units" interpreted as pixels? + val = int(value) + sign = cmp(val, 0) + # validate length + val = sign*max(minl, min(abs(val), maxl)) + callback(val, *args) + except Exception: + warnings.warn('Unable to parse length value "%s"' % value) - def __parse_font_size_cb(length, tag): - tag.set_property('size-points', length/display_resolution) - __parse_font_size_cb = staticmethod(__parse_font_size_cb) + def __parse_font_size_cb(length, tag): + tag.set_property('size-points', length/display_resolution) + __parse_font_size_cb = staticmethod(__parse_font_size_cb) - def _parse_style_display(self, tag, value): - if value == 'none': - tag.set_property('invisible','true') - # FIXME: display: block, inline + def _parse_style_display(self, tag, value): + if value == 'none': + tag.set_property('invisible', 'true') + # FIXME: display: block, inline - def _parse_style_font_size(self, tag, value): - try: - scale = { - 'xx-small': pango.SCALE_XX_SMALL, - 'x-small': pango.SCALE_X_SMALL, - 'small': pango.SCALE_SMALL, - 'medium': pango.SCALE_MEDIUM, - 'large': pango.SCALE_LARGE, - 'x-large': pango.SCALE_X_LARGE, - 'xx-large': pango.SCALE_XX_LARGE, - } [value] - except KeyError: - pass - else: - attrs = self._get_current_attributes() - tag.set_property('scale', scale / attrs.font_scale) - return - if value == 'smaller': - tag.set_property('scale', pango.SCALE_SMALL) - return - if value == 'larger': - tag.set_property('scale', pango.SCALE_LARGE) - return - # font relative (5 ~ 4pt, 110 ~ 72pt) - self._parse_length(value, True, False, 5, 110, self.__parse_font_size_cb, tag) + def _parse_style_font_size(self, tag, value): + try: + scale = { + 'xx-small': pango.SCALE_XX_SMALL, + 'x-small': pango.SCALE_X_SMALL, + 'small': pango.SCALE_SMALL, + 'medium': pango.SCALE_MEDIUM, + 'large': pango.SCALE_LARGE, + 'x-large': pango.SCALE_X_LARGE, + 'xx-large': pango.SCALE_XX_LARGE, + } [value] + except KeyError: + pass + else: + attrs = self._get_current_attributes() + tag.set_property('scale', scale / attrs.font_scale) + return + if value == 'smaller': + tag.set_property('scale', pango.SCALE_SMALL) + return + if value == 'larger': + tag.set_property('scale', pango.SCALE_LARGE) + return + # font relative (5 ~ 4pt, 110 ~ 72pt) + self._parse_length(value, True, False, 5, 110, self.__parse_font_size_cb, tag) - def _parse_style_font_style(self, tag, value): - try: - style = { - 'normal': pango.STYLE_NORMAL, - 'italic': pango.STYLE_ITALIC, - 'oblique': pango.STYLE_OBLIQUE, - } [value] - except KeyError: - warnings.warn('unknown font-style %s' % value) - else: - tag.set_property('style', style) + def _parse_style_font_style(self, tag, value): + try: + style = { + 'normal': pango.STYLE_NORMAL, + 'italic': pango.STYLE_ITALIC, + 'oblique': pango.STYLE_OBLIQUE, + } [value] + except KeyError: + warnings.warn('unknown font-style %s' % value) + else: + tag.set_property('style', style) - def __frac_length_tag_cb(self,length, tag, propname): - styles = self._get_style_tags() - if styles: - length += styles[-1].get_property(propname) - tag.set_property(propname, length) - #__frac_length_tag_cb = staticmethod(__frac_length_tag_cb) + def __frac_length_tag_cb(self, length, tag, propname): + styles = self._get_style_tags() + if styles: + length += styles[-1].get_property(propname) + tag.set_property(propname, length) + #__frac_length_tag_cb = staticmethod(__frac_length_tag_cb) - def _parse_style_margin_left(self, tag, value): - # block relative - self._parse_length(value, False, True, 1, 1000, self.__frac_length_tag_cb, - tag, 'left-margin') + def _parse_style_margin_left(self, tag, value): + # block relative + self._parse_length(value, False, True, 1, 1000, self.__frac_length_tag_cb, + tag, 'left-margin') - def _parse_style_margin_right(self, tag, value): - # block relative - self._parse_length(value, False, True, 1, 1000, self.__frac_length_tag_cb, - tag, 'right-margin') + def _parse_style_margin_right(self, tag, value): + # block relative + self._parse_length(value, False, True, 1, 1000, self.__frac_length_tag_cb, + tag, 'right-margin') - def _parse_style_font_weight(self, tag, value): - # TODO: missing 'bolder' and 'lighter' - try: - weight = { - '100': pango.WEIGHT_ULTRALIGHT, - '200': pango.WEIGHT_ULTRALIGHT, - '300': pango.WEIGHT_LIGHT, - '400': pango.WEIGHT_NORMAL, - '500': pango.WEIGHT_NORMAL, - '600': pango.WEIGHT_BOLD, - '700': pango.WEIGHT_BOLD, - '800': pango.WEIGHT_ULTRABOLD, - '900': pango.WEIGHT_HEAVY, - 'normal': pango.WEIGHT_NORMAL, - 'bold': pango.WEIGHT_BOLD, - } [value] - except KeyError: - warnings.warn('unknown font-style %s' % value) - else: - tag.set_property('weight', weight) + def _parse_style_font_weight(self, tag, value): + # TODO: missing 'bolder' and 'lighter' + try: + weight = { + '100': pango.WEIGHT_ULTRALIGHT, + '200': pango.WEIGHT_ULTRALIGHT, + '300': pango.WEIGHT_LIGHT, + '400': pango.WEIGHT_NORMAL, + '500': pango.WEIGHT_NORMAL, + '600': pango.WEIGHT_BOLD, + '700': pango.WEIGHT_BOLD, + '800': pango.WEIGHT_ULTRABOLD, + '900': pango.WEIGHT_HEAVY, + 'normal': pango.WEIGHT_NORMAL, + 'bold': pango.WEIGHT_BOLD, + } [value] + except KeyError: + warnings.warn('unknown font-style %s' % value) + else: + tag.set_property('weight', weight) - def _parse_style_font_family(self, tag, value): - tag.set_property('family', value) + def _parse_style_font_family(self, tag, value): + tag.set_property('family', value) - def _parse_style_text_align(self, tag, value): - try: - align = { - 'left': gtk.JUSTIFY_LEFT, - 'right': gtk.JUSTIFY_RIGHT, - 'center': gtk.JUSTIFY_CENTER, - 'justify': gtk.JUSTIFY_FILL, - } [value] - except KeyError: - warnings.warn('Invalid text-align:%s requested' % value) - else: - tag.set_property('justification', align) + def _parse_style_text_align(self, tag, value): + try: + align = { + 'left': gtk.JUSTIFY_LEFT, + 'right': gtk.JUSTIFY_RIGHT, + 'center': gtk.JUSTIFY_CENTER, + 'justify': gtk.JUSTIFY_FILL, + } [value] + except KeyError: + warnings.warn('Invalid text-align:%s requested' % value) + else: + tag.set_property('justification', align) - def _parse_style_text_decoration(self, tag, value): - values = value.split(' ') - if 'none' in values: - tag.set_property('underline', pango.UNDERLINE_NONE) - tag.set_property('strikethrough', False) - if 'underline' in values: - tag.set_property('underline', pango.UNDERLINE_SINGLE) - else: - tag.set_property('underline', pango.UNDERLINE_NONE) - if 'line-through' in values: - tag.set_property('strikethrough', True) - else: - tag.set_property('strikethrough', False) - if 'blink' in values: - warnings.warn('text-decoration:blink not implemented') - if 'overline' in values: - warnings.warn('text-decoration:overline not implemented') + def _parse_style_text_decoration(self, tag, value): + values = value.split(' ') + if 'none' in values: + tag.set_property('underline', pango.UNDERLINE_NONE) + tag.set_property('strikethrough', False) + if 'underline' in values: + tag.set_property('underline', pango.UNDERLINE_SINGLE) + else: + tag.set_property('underline', pango.UNDERLINE_NONE) + if 'line-through' in values: + tag.set_property('strikethrough', True) + else: + tag.set_property('strikethrough', False) + if 'blink' in values: + warnings.warn('text-decoration:blink not implemented') + if 'overline' in values: + warnings.warn('text-decoration:overline not implemented') - def _parse_style_white_space(self, tag, value): - if value == 'pre': - tag.set_property('wrap_mode', gtk.WRAP_NONE) - elif value == 'normal': - tag.set_property('wrap_mode', gtk.WRAP_WORD) - elif value == 'nowrap': - tag.set_property('wrap_mode', gtk.WRAP_NONE) + def _parse_style_white_space(self, tag, value): + if value == 'pre': + tag.set_property('wrap_mode', gtk.WRAP_NONE) + elif value == 'normal': + tag.set_property('wrap_mode', gtk.WRAP_WORD) + elif value == 'nowrap': + tag.set_property('wrap_mode', gtk.WRAP_NONE) - def __length_tag_cb(self, value, tag, propname): - try: - tag.set_property(propname, value) - except Exception: - gajim.log.warn( "Error with prop: " + propname + " for tag: " + str(tag)) + def __length_tag_cb(self, value, tag, propname): + try: + tag.set_property(propname, value) + except Exception: + gajim.log.warn( "Error with prop: " + propname + " for tag: " + str(tag)) - def _parse_style_width(self, tag, value): - if value == 'auto': - return - self._parse_length(value, False, False, 1, 1000, self.__length_tag_cb, - tag, "width") - def _parse_style_height(self, tag, value): - if value == 'auto': - return - self._parse_length(value, False, False, 1, 1000, self.__length_tag_cb, - tag, "height") + def _parse_style_width(self, tag, value): + if value == 'auto': + return + self._parse_length(value, False, False, 1, 1000, self.__length_tag_cb, + tag, "width") + def _parse_style_height(self, tag, value): + if value == 'auto': + return + self._parse_length(value, False, False, 1, 1000, self.__length_tag_cb, + tag, "height") - # build a dictionary mapping styles to methods, for greater speed - __style_methods = dict() - for style in ('background-color', 'color', 'font-family', 'font-size', - 'font-style', 'font-weight', 'margin-left', 'margin-right', - 'text-align', 'text-decoration', 'white-space', 'display', - 'width', 'height' ): - try: - method = locals()['_parse_style_%s' % style.replace('-', '_')] - except KeyError: - warnings.warn('Style attribute "%s" not yet implemented' % style) - else: - __style_methods[style] = method - del style - # -- + # build a dictionary mapping styles to methods, for greater speed + __style_methods = dict() + for style in ('background-color', 'color', 'font-family', 'font-size', + 'font-style', 'font-weight', 'margin-left', 'margin-right', + 'text-align', 'text-decoration', 'white-space', 'display', + 'width', 'height' ): + try: + method = locals()['_parse_style_%s' % style.replace('-', '_')] + except KeyError: + warnings.warn('Style attribute "%s" not yet implemented' % style) + else: + __style_methods[style] = method + del style + # -- - def _get_style_tags(self): - return [tag for tag in self.styles if tag is not None] + def _get_style_tags(self): + return [tag for tag in self.styles if tag is not None] - def _create_url(self, href, title, type_, id_): - '''Process a url tag. - ''' - tag = self.textbuf.create_tag(id_) - if href and href[0] != '#': - tag.href = href - tag.type_ = type_ # to be used by the URL handler - tag.connect('event', self.textview.html_hyperlink_handler, 'url', href) - tag.set_property('foreground', gajim.config.get('urlmsgcolor')) - tag.set_property('underline', pango.UNDERLINE_SINGLE) - tag.is_anchor = True - if title: - tag.title = title - return tag + def _create_url(self, href, title, type_, id_): + '''Process a url tag. + ''' + tag = self.textbuf.create_tag(id_) + if href and href[0] != '#': + tag.href = href + tag.type_ = type_ # to be used by the URL handler + tag.connect('event', self.textview.html_hyperlink_handler, 'url', href) + tag.set_property('foreground', gajim.config.get('urlmsgcolor')) + tag.set_property('underline', pango.UNDERLINE_SINGLE) + tag.is_anchor = True + if title: + tag.title = title + return tag - def _process_img(self, attrs): - '''Process a img tag. - ''' - mem = '' - try: - # Wait maximum 1s for connection - socket.setdefaulttimeout(1) - try: - req = urllib2.Request(attrs['src']) - req.add_header('User-Agent', 'Gajim ' + gajim.version) - f = urllib2.urlopen(req) - except Exception, ex: - gajim.log.debug('Error loading image %s ' % attrs['src'] + str(ex)) - pixbuf = None - alt = attrs.get('alt', 'Broken image') - else: - # Wait 0.1s between each byte - try: - f.fp._sock.fp._sock.settimeout(0.5) - except Exception: - pass - # Max image size = 2 MB (to try to prevent DoS) - deadline = time.time() + 3 - while True: - if time.time() > deadline: - gajim.log.debug(str('Timeout loading image %s ' % \ - attrs['src'] + ex)) - mem = '' - alt = attrs.get('alt', '') - if alt: - alt += '\n' - alt += _('Timeout loading image') - break - try: - temp = f.read(100) - except socket.timeout, ex: - gajim.log.debug('Timeout loading image %s ' % attrs['src'] + \ - str(ex)) - alt = attrs.get('alt', '') - if alt: - alt += '\n' - alt += _('Timeout loading image') - break - if temp: - mem += temp - else: - break - if len(mem) > 2*1024*1024: - alt = attrs.get('alt', '') - if alt: - alt += '\n' - alt += _('Image is too big') - break - pixbuf = None - if mem: - # Caveat: GdkPixbuf is known not to be safe to load - # images from network... this program is now potentially - # hackable ;) - loader = gtk.gdk.PixbufLoader() - dims = [0,0] - def height_cb(length): - dims[1] = length - def width_cb(length): - dims[0] = length - # process width and height attributes - w = attrs.get('width') - h = attrs.get('height') - # override with width and height styles - for attr, val in style_iter(attrs.get('style', '')): - if attr == 'width': - w = val - elif attr == 'height': - h = val - if w: - self._parse_length(w, False, False, 1, 1000, width_cb) - if h: - self._parse_length(h, False, False, 1, 1000, height_cb) - def set_size(pixbuf, w, h, dims): - """ - FIXME: Floats should be relative to the whole textview, and - resize with it. This needs new pifbufs for every resize, - gtk.gdk.Pixbuf.scale_simple or similar. - """ - if isinstance(dims[0], float): - dims[0] = int(dims[0]*w) - elif not dims[0]: - dims[0] = w - if isinstance(dims[1], float): - dims[1] = int(dims[1]*h) - if not dims[1]: - dims[1] = h - loader.set_size(*dims) - if w or h: - loader.connect('size-prepared', set_size, dims) - loader.write(mem) - loader.close() - pixbuf = loader.get_pixbuf() - alt = attrs.get('alt', '') - if pixbuf is not None: - tags = self._get_style_tags() - if tags: - tmpmark = self.textbuf.create_mark(None, self.iter, True) - self.textbuf.insert_pixbuf(self.iter, pixbuf) - self.starting = False - if tags: - start = self.textbuf.get_iter_at_mark(tmpmark) - for tag in tags: - self.textbuf.apply_tag(tag, start, self.iter) - self.textbuf.delete_mark(tmpmark) - else: - self._insert_text('[IMG: %s]' % alt) - except Exception, ex: - gajim.log.error('Error loading image ' + str(ex)) - pixbuf = None - alt = attrs.get('alt', 'Broken image') - try: - loader.close() - except Exception: - pass - return pixbuf + def _process_img(self, attrs): + '''Process a img tag. + ''' + mem = '' + try: + # Wait maximum 1s for connection + socket.setdefaulttimeout(1) + try: + req = urllib2.Request(attrs['src']) + req.add_header('User-Agent', 'Gajim ' + gajim.version) + f = urllib2.urlopen(req) + except Exception, ex: + gajim.log.debug('Error loading image %s ' % attrs['src'] + str(ex)) + pixbuf = None + alt = attrs.get('alt', 'Broken image') + else: + # Wait 0.1s between each byte + try: + f.fp._sock.fp._sock.settimeout(0.5) + except Exception: + pass + # Max image size = 2 MB (to try to prevent DoS) + deadline = time.time() + 3 + while True: + if time.time() > deadline: + gajim.log.debug(str('Timeout loading image %s ' % \ + attrs['src'] + ex)) + mem = '' + alt = attrs.get('alt', '') + if alt: + alt += '\n' + alt += _('Timeout loading image') + break + try: + temp = f.read(100) + except socket.timeout, ex: + gajim.log.debug('Timeout loading image %s ' % attrs['src'] + \ + str(ex)) + alt = attrs.get('alt', '') + if alt: + alt += '\n' + alt += _('Timeout loading image') + break + if temp: + mem += temp + else: + break + if len(mem) > 2*1024*1024: + alt = attrs.get('alt', '') + if alt: + alt += '\n' + alt += _('Image is too big') + break + pixbuf = None + if mem: + # Caveat: GdkPixbuf is known not to be safe to load + # images from network... this program is now potentially + # hackable ;) + loader = gtk.gdk.PixbufLoader() + dims = [0, 0] + def height_cb(length): + dims[1] = length + def width_cb(length): + dims[0] = length + # process width and height attributes + w = attrs.get('width') + h = attrs.get('height') + # override with width and height styles + for attr, val in style_iter(attrs.get('style', '')): + if attr == 'width': + w = val + elif attr == 'height': + h = val + if w: + self._parse_length(w, False, False, 1, 1000, width_cb) + if h: + self._parse_length(h, False, False, 1, 1000, height_cb) + def set_size(pixbuf, w, h, dims): + """ + FIXME: Floats should be relative to the whole textview, and + resize with it. This needs new pifbufs for every resize, + gtk.gdk.Pixbuf.scale_simple or similar. + """ + if isinstance(dims[0], float): + dims[0] = int(dims[0]*w) + elif not dims[0]: + dims[0] = w + if isinstance(dims[1], float): + dims[1] = int(dims[1]*h) + if not dims[1]: + dims[1] = h + loader.set_size(*dims) + if w or h: + loader.connect('size-prepared', set_size, dims) + loader.write(mem) + loader.close() + pixbuf = loader.get_pixbuf() + alt = attrs.get('alt', '') + if pixbuf is not None: + tags = self._get_style_tags() + if tags: + tmpmark = self.textbuf.create_mark(None, self.iter, True) + self.textbuf.insert_pixbuf(self.iter, pixbuf) + self.starting = False + if tags: + start = self.textbuf.get_iter_at_mark(tmpmark) + for tag in tags: + self.textbuf.apply_tag(tag, start, self.iter) + self.textbuf.delete_mark(tmpmark) + else: + self._insert_text('[IMG: %s]' % alt) + except Exception, ex: + gajim.log.error('Error loading image ' + str(ex)) + pixbuf = None + alt = attrs.get('alt', 'Broken image') + try: + loader.close() + except Exception: + pass + return pixbuf - def _begin_span(self, style, tag=None, id_=None): - if style is None: - self.styles.append(tag) - return None - if tag is None: - if id_: - tag = self.textbuf.create_tag(id_) - else: - tag = self.textbuf.create_tag() # we create anonymous tag - for attr, val in style_iter(style): - attr = attr.lower() - val = val - try: - method = self.__style_methods[attr] - except KeyError: - warnings.warn('Style attribute "%s" requested ' - 'but not yet implemented' % attr) - else: - method(self, tag, val) - self.styles.append(tag) + def _begin_span(self, style, tag=None, id_=None): + if style is None: + self.styles.append(tag) + return None + if tag is None: + if id_: + tag = self.textbuf.create_tag(id_) + else: + tag = self.textbuf.create_tag() # we create anonymous tag + for attr, val in style_iter(style): + attr = attr.lower() + val = val + try: + method = self.__style_methods[attr] + except KeyError: + warnings.warn('Style attribute "%s" requested ' + 'but not yet implemented' % attr) + else: + method(self, tag, val) + self.styles.append(tag) - def _end_span(self): - self.styles.pop() + def _end_span(self): + self.styles.pop() - def _jump_line(self): - self.textbuf.insert_with_tags_by_name(self.iter, '\n', 'eol') - self.starting = True + def _jump_line(self): + self.textbuf.insert_with_tags_by_name(self.iter, '\n', 'eol') + self.starting = True - def _insert_text(self, text): - if self.starting and text != '\n': - self.starting = (text[-1] == '\n') - tags = self._get_style_tags() - if tags: - self.textbuf.insert_with_tags(self.iter, text, *tags) - else: - self.textbuf.insert(self.iter, text) + def _insert_text(self, text): + if self.starting and text != '\n': + self.starting = (text[-1] == '\n') + tags = self._get_style_tags() + if tags: + self.textbuf.insert_with_tags(self.iter, text, *tags) + else: + self.textbuf.insert(self.iter, text) - def _starts_line(self): - return self.starting or self.iter.starts_line() + def _starts_line(self): + return self.starting or self.iter.starts_line() - def _flush_text(self): - if not self.text: return - text, self.text = self.text, '' - if not self.preserve: - text = text.replace('\n', ' ') - self.handle_specials(whitespace_rx.sub(' ', text)) - else: - self._insert_text(text.strip('\n')) + def _flush_text(self): + if not self.text: return + text, self.text = self.text, '' + if not self.preserve: + text = text.replace('\n', ' ') + self.handle_specials(whitespace_rx.sub(' ', text)) + else: + self._insert_text(text.strip('\n')) - def _anchor_event(self, tag, textview, event, iter_, href, type_): - if event.type == gtk.gdk.BUTTON_PRESS: - self.textview.emit('url-clicked', href, type_) - return True - return False + def _anchor_event(self, tag, textview, event, iter_, href, type_): + if event.type == gtk.gdk.BUTTON_PRESS: + self.textview.emit('url-clicked', href, type_) + return True + return False - def handle_specials(self, text): - self.iter = self.conv_textview.detect_and_print_special_text(text, self._get_style_tags()) + def handle_specials(self, text): + self.iter = self.conv_textview.detect_and_print_special_text(text, self._get_style_tags()) - def characters(self, content): - if self.preserve: - self.text += content - return - if allwhitespace_rx.match(content) is not None and self._starts_line(): - self.text += ' ' - return - self.text += content - self.starting = False + def characters(self, content): + if self.preserve: + self.text += content + return + if allwhitespace_rx.match(content) is not None and self._starts_line(): + self.text += ' ' + return + self.text += content + self.starting = False - def startElement(self, name, attrs): - self._flush_text() - klass = [i for i in attrs.get('class',' ').split(' ') if i] - style = '' - #Add styles defined for classes - for k in klass: - if k in classes: - style += classes[k] + def startElement(self, name, attrs): + self._flush_text() + klass = [i for i in attrs.get('class', ' ').split(' ') if i] + style = '' + #Add styles defined for classes + for k in klass: + if k in classes: + style += classes[k] - tag = None - #FIXME: if we want to use id, it needs to be unique across - # the whole textview, so we need to add something like the - # message-id to it. - #id_ = attrs.get('id',None) - id_ = None - if name == 'a': - #TODO: accesskey, charset, hreflang, rel, rev, tabindex, type - href = attrs.get('href', None) - if not href: - href = attrs.get('HREF', None) - # Gaim sends HREF instead of href - title = attrs.get('title', attrs.get('rel',href)) - type_ = attrs.get('type', None) - tag = self._create_url(href, title, type_, id_) - elif name == 'blockquote': - cite = attrs.get('cite', None) - if cite: - tag = self.textbuf.create_tag(id_) - tag.title = title - tag.is_anchor = True - elif name in LIST_ELEMS: - style += ';margin-left: 2em' - elif name == 'img': - tag = self._process_img(attrs) - if name in element_styles: - style += element_styles[name] - # so that explicit styles override implicit ones, - # we add the attribute last - style += ";"+attrs.get('style','') - if style == '': - style = None - self._begin_span(style, tag, id_) + tag = None + #FIXME: if we want to use id, it needs to be unique across + # the whole textview, so we need to add something like the + # message-id to it. + #id_ = attrs.get('id',None) + id_ = None + if name == 'a': + #TODO: accesskey, charset, hreflang, rel, rev, tabindex, type + href = attrs.get('href', None) + if not href: + href = attrs.get('HREF', None) + # Gaim sends HREF instead of href + title = attrs.get('title', attrs.get('rel', href)) + type_ = attrs.get('type', None) + tag = self._create_url(href, title, type_, id_) + elif name == 'blockquote': + cite = attrs.get('cite', None) + if cite: + tag = self.textbuf.create_tag(id_) + tag.title = title + tag.is_anchor = True + elif name in LIST_ELEMS: + style += ';margin-left: 2em' + elif name == 'img': + tag = self._process_img(attrs) + if name in element_styles: + style += element_styles[name] + # so that explicit styles override implicit ones, + # we add the attribute last + style += ";"+attrs.get('style', '') + if style == '': + style = None + self._begin_span(style, tag, id_) - if name == 'br': - pass # handled in endElement - elif name == 'hr': - pass # handled in endElement - elif name in BLOCK: - if not self._starts_line(): - self._jump_line() - if name == 'pre': - self.preserve = True - elif name == 'span': - pass - elif name in ('dl', 'ul'): - if not self._starts_line(): - self._jump_line() - self.list_counters.append(None) - elif name == 'ol': - if not self._starts_line(): - self._jump_line() - self.list_counters.append(0) - elif name == 'li': - if self.list_counters[-1] is None: - li_head = unichr(0x2022) - else: - self.list_counters[-1] += 1 - li_head = '%i.' % self.list_counters[-1] - self.text = ' '*len(self.list_counters)*4 + li_head + ' ' - self._flush_text() - self.starting = True - elif name == 'dd': - self._jump_line() - elif name == 'dt': - if not self.starting: - self._jump_line() - elif name in ('a', 'img', 'body', 'html'): - pass - elif name in INLINE: - pass - else: - warnings.warn('Unhandled element "%s"' % name) + if name == 'br': + pass # handled in endElement + elif name == 'hr': + pass # handled in endElement + elif name in BLOCK: + if not self._starts_line(): + self._jump_line() + if name == 'pre': + self.preserve = True + elif name == 'span': + pass + elif name in ('dl', 'ul'): + if not self._starts_line(): + self._jump_line() + self.list_counters.append(None) + elif name == 'ol': + if not self._starts_line(): + self._jump_line() + self.list_counters.append(0) + elif name == 'li': + if self.list_counters[-1] is None: + li_head = unichr(0x2022) + else: + self.list_counters[-1] += 1 + li_head = '%i.' % self.list_counters[-1] + self.text = ' '*len(self.list_counters)*4 + li_head + ' ' + self._flush_text() + self.starting = True + elif name == 'dd': + self._jump_line() + elif name == 'dt': + if not self.starting: + self._jump_line() + elif name in ('a', 'img', 'body', 'html'): + pass + elif name in INLINE: + pass + else: + warnings.warn('Unhandled element "%s"' % name) - def endElement(self, name): - endPreserving = False - newLine = False - if name == 'br': - newLine = True - elif name == 'hr': - #FIXME: plenty of unused attributes (width, height,...) :) - self._jump_line() - try: - self.textbuf.insert_pixbuf(self.iter, self.textview.focus_out_line_pixbuf) - #self._insert_text(u'\u2550'*40) - self._jump_line() - except Exception, e: - gajim.log.debug(str('Error in hr'+e)) - elif name in LIST_ELEMS: - self.list_counters.pop() - elif name == 'li': - newLine = True - elif name == 'img': - pass - elif name == 'body' or name == 'html': - pass - elif name == 'a': - pass - elif name in INLINE: - pass - elif name in ('dd', 'dt', ): - pass - elif name in BLOCK: - if name == 'pre': - endPreserving = True - else: - warnings.warn("Unhandled element '%s'" % name) - self._flush_text() - if endPreserving: - self.preserve = False - if newLine: - self._jump_line() - self._end_span() - #if not self._starts_line(): - # self.text = ' ' + def endElement(self, name): + endPreserving = False + newLine = False + if name == 'br': + newLine = True + elif name == 'hr': + #FIXME: plenty of unused attributes (width, height,...) :) + self._jump_line() + try: + self.textbuf.insert_pixbuf(self.iter, self.textview.focus_out_line_pixbuf) + #self._insert_text(u'\u2550'*40) + self._jump_line() + except Exception, e: + gajim.log.debug(str('Error in hr'+e)) + elif name in LIST_ELEMS: + self.list_counters.pop() + elif name == 'li': + newLine = True + elif name == 'img': + pass + elif name == 'body' or name == 'html': + pass + elif name == 'a': + pass + elif name in INLINE: + pass + elif name in ('dd', 'dt', ): + pass + elif name in BLOCK: + if name == 'pre': + endPreserving = True + else: + warnings.warn("Unhandled element '%s'" % name) + self._flush_text() + if endPreserving: + self.preserve = False + if newLine: + self._jump_line() + self._end_span() + #if not self._starts_line(): + # self.text = ' ' class HtmlTextView(gtk.TextView): - def __init__(self): - gobject.GObject.__init__(self) - self.set_wrap_mode(gtk.WRAP_CHAR) - self.set_editable(False) - self._changed_cursor = False - self.connect('destroy', self.__destroy_event) - self.connect('motion-notify-event', self.__motion_notify_event) - self.connect('leave-notify-event', self.__leave_event) - self.connect('enter-notify-event', self.__motion_notify_event) - self.connect('realize', self.on_html_text_view_realized) - self.connect('unrealize', self.on_html_text_view_unrealized) - self.connect('copy-clipboard', self.on_html_text_view_copy_clipboard) - self.get_buffer().connect_after('mark-set', self.on_text_buffer_mark_set) - self.get_buffer().create_tag('eol', scale = pango.SCALE_XX_SMALL) - self.tooltip = tooltips.BaseTooltip() - self.config = gajim.config - self.interface = gajim.interface - # end big hack + def __init__(self): + gobject.GObject.__init__(self) + self.set_wrap_mode(gtk.WRAP_CHAR) + self.set_editable(False) + self._changed_cursor = False + self.connect('destroy', self.__destroy_event) + self.connect('motion-notify-event', self.__motion_notify_event) + self.connect('leave-notify-event', self.__leave_event) + self.connect('enter-notify-event', self.__motion_notify_event) + self.connect('realize', self.on_html_text_view_realized) + self.connect('unrealize', self.on_html_text_view_unrealized) + self.connect('copy-clipboard', self.on_html_text_view_copy_clipboard) + self.get_buffer().connect_after('mark-set', self.on_text_buffer_mark_set) + self.get_buffer().create_tag('eol', scale = pango.SCALE_XX_SMALL) + self.tooltip = tooltips.BaseTooltip() + self.config = gajim.config + self.interface = gajim.interface + # end big hack - def __destroy_event(self, widget): - if self.tooltip.timeout != 0: - self.tooltip.hide_tooltip() + def __destroy_event(self, widget): + if self.tooltip.timeout != 0: + self.tooltip.hide_tooltip() - def __leave_event(self, widget, event): - if self._changed_cursor: - window = widget.get_window(gtk.TEXT_WINDOW_TEXT) - window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM)) - self._changed_cursor = False + def __leave_event(self, widget, event): + if self._changed_cursor: + window = widget.get_window(gtk.TEXT_WINDOW_TEXT) + window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM)) + self._changed_cursor = False - def show_tooltip(self, tag): - if not self.tooltip.win: - # check if the current pointer is still over the line - x, y, _ = self.window.get_pointer() - x, y = self.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y) - tags = self.get_iter_at_location(x, y).get_tags() - is_over_anchor = False - for tag_ in tags: - if getattr(tag_, 'is_anchor', False): - is_over_anchor = True - break - if not is_over_anchor: - return - text = getattr(tag, 'title', False) - if text: - pointer = self.get_pointer() - position = self.window.get_origin() - self.tooltip.show_tooltip(text, 8, position[1] + pointer[1]) + def show_tooltip(self, tag): + if not self.tooltip.win: + # check if the current pointer is still over the line + x, y, _ = self.window.get_pointer() + x, y = self.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y) + tags = self.get_iter_at_location(x, y).get_tags() + is_over_anchor = False + for tag_ in tags: + if getattr(tag_, 'is_anchor', False): + is_over_anchor = True + break + if not is_over_anchor: + return + text = getattr(tag, 'title', False) + if text: + pointer = self.get_pointer() + position = self.window.get_origin() + self.tooltip.show_tooltip(text, 8, position[1] + pointer[1]) - def __motion_notify_event(self, widget, event): - x, y, _ = widget.window.get_pointer() - x, y = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y) - tags = widget.get_iter_at_location(x, y).get_tags() - anchor_tags = [tag for tag in tags if getattr(tag, 'is_anchor', False)] - if self.tooltip.timeout != 0: - # Check if we should hide the line tooltip - if not anchor_tags: - self.tooltip.hide_tooltip() - if not self._changed_cursor and anchor_tags: - window = widget.get_window(gtk.TEXT_WINDOW_TEXT) - window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2)) - self._changed_cursor = True - self.tooltip.timeout = gobject.timeout_add(500, self.show_tooltip, anchor_tags[0]) - elif self._changed_cursor and not anchor_tags: - window = widget.get_window(gtk.TEXT_WINDOW_TEXT) - window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM)) - self._changed_cursor = False - return False + def __motion_notify_event(self, widget, event): + x, y, _ = widget.window.get_pointer() + x, y = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y) + tags = widget.get_iter_at_location(x, y).get_tags() + anchor_tags = [tag for tag in tags if getattr(tag, 'is_anchor', False)] + if self.tooltip.timeout != 0: + # Check if we should hide the line tooltip + if not anchor_tags: + self.tooltip.hide_tooltip() + if not self._changed_cursor and anchor_tags: + window = widget.get_window(gtk.TEXT_WINDOW_TEXT) + window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2)) + self._changed_cursor = True + self.tooltip.timeout = gobject.timeout_add(500, self.show_tooltip, anchor_tags[0]) + elif self._changed_cursor and not anchor_tags: + window = widget.get_window(gtk.TEXT_WINDOW_TEXT) + window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM)) + self._changed_cursor = False + return False - def display_html(self, html, conv_textview): - buffer_ = self.get_buffer() - eob = buffer_.get_end_iter() - ## this works too if libxml2 is not available - # parser = xml.sax.make_parser(['drv_libxml2']) - # parser.setFeature(xml.sax.handler.feature_validation, True) - parser = xml.sax.make_parser() - parser.setContentHandler(HtmlHandler(conv_textview, eob)) - parser.parse(StringIO(html)) + def display_html(self, html, conv_textview): + buffer_ = self.get_buffer() + eob = buffer_.get_end_iter() + ## this works too if libxml2 is not available + # parser = xml.sax.make_parser(['drv_libxml2']) + # parser.setFeature(xml.sax.handler.feature_validation, True) + parser = xml.sax.make_parser() + parser.setContentHandler(HtmlHandler(conv_textview, eob)) + parser.parse(StringIO(html)) - # too much space after :) - #if not eob.starts_line(): - # buffer_.insert(eob, '\n') + # too much space after :) + #if not eob.starts_line(): + # buffer_.insert(eob, '\n') - def on_html_text_view_copy_clipboard(self, unused_data): - clipboard = self.get_clipboard(gtk.gdk.SELECTION_CLIPBOARD) - clipboard.set_text(self.get_selected_text()) - self.emit_stop_by_name('copy-clipboard') + def on_html_text_view_copy_clipboard(self, unused_data): + clipboard = self.get_clipboard(gtk.gdk.SELECTION_CLIPBOARD) + clipboard.set_text(self.get_selected_text()) + self.emit_stop_by_name('copy-clipboard') - def on_html_text_view_realized(self, unused_data): - self.get_buffer().remove_selection_clipboard(self.get_clipboard(gtk.gdk.SELECTION_PRIMARY)) + def on_html_text_view_realized(self, unused_data): + self.get_buffer().remove_selection_clipboard(self.get_clipboard(gtk.gdk.SELECTION_PRIMARY)) - def on_html_text_view_unrealized(self, unused_data): - self.get_buffer().add_selection_clipboard(self.get_clipboard(gtk.gdk.SELECTION_PRIMARY)) + def on_html_text_view_unrealized(self, unused_data): + self.get_buffer().add_selection_clipboard(self.get_clipboard(gtk.gdk.SELECTION_PRIMARY)) - def on_text_buffer_mark_set(self, location, mark, unused_data): - bounds = self.get_buffer().get_selection_bounds() - if bounds: - # textview can be hidden while we add a new line in it. - if self.has_screen(): - clipboard = self.get_clipboard(gtk.gdk.SELECTION_PRIMARY) - clipboard.set_text(self.get_selected_text()) + def on_text_buffer_mark_set(self, location, mark, unused_data): + bounds = self.get_buffer().get_selection_bounds() + if bounds: + # textview can be hidden while we add a new line in it. + if self.has_screen(): + clipboard = self.get_clipboard(gtk.gdk.SELECTION_PRIMARY) + clipboard.set_text(self.get_selected_text()) - def get_selected_text(self): - bounds = self.get_buffer().get_selection_bounds() - selection = '' - if bounds: - (search_iter, end) = bounds + def get_selected_text(self): + bounds = self.get_buffer().get_selection_bounds() + selection = '' + if bounds: + (search_iter, end) = bounds - while (search_iter.compare(end)): - character = search_iter.get_char() - if character == u'\ufffc': - anchor = search_iter.get_child_anchor() - if anchor: - text = anchor.get_data('plaintext') - if text: - selection+=text - else: - selection+=character - else: - selection+=character - search_iter.forward_char() - return selection + while (search_iter.compare(end)): + character = search_iter.get_char() + if character == u'\ufffc': + anchor = search_iter.get_child_anchor() + if anchor: + text = anchor.get_data('plaintext') + if text: + selection+=text + else: + selection+=character + else: + selection+=character + search_iter.forward_char() + return selection change_cursor = None if __name__ == '__main__': - import os + import os - from conversation_textview import ConversationTextview - import gajim as gaj + from conversation_textview import ConversationTextview + import gajim as gaj - class log(object): + class log(object): - def debug(self, text): - print "debug:", text - def warn(self, text): - print "warn;", text - def error(self,text): - print "error;", text + def debug(self, text): + print "debug:", text + def warn(self, text): + print "warn;", text + def error(self, text): + print "error;", text - gajim.log=log() + gajim.log=log() - gaj.Interface() + gaj.Interface() - htmlview = ConversationTextview(None) + htmlview = ConversationTextview(None) - path = gtkgui_helpers.get_icon_path('gajim-muc_separator') - # use this for hr - htmlview.tv.focus_out_line_pixbuf = gtk.gdk.pixbuf_new_from_file(path) + path = gtkgui_helpers.get_icon_path('gajim-muc_separator') + # use this for hr + htmlview.tv.focus_out_line_pixbuf = gtk.gdk.pixbuf_new_from_file(path) - tooltip = tooltips.BaseTooltip() + tooltip = tooltips.BaseTooltip() - def on_textview_motion_notify_event(widget, event): - """ - Change the cursor to a hand when we are over a mail or an url - """ - global change_cursor - pointer_x, pointer_y = htmlview.tv.window.get_pointer()[0:2] - x, y = htmlview.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer_x, - pointer_y) - tags = htmlview.tv.get_iter_at_location(x, y).get_tags() - if change_cursor: - htmlview.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( - gtk.gdk.Cursor(gtk.gdk.XTERM)) - change_cursor = None - tag_table = htmlview.tv.get_buffer().get_tag_table() - for tag in tags: - try: - if tag.is_anchor: - htmlview.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( - gtk.gdk.Cursor(gtk.gdk.HAND2)) - change_cursor = tag - elif tag == tag_table.lookup('focus-out-line'): - over_line = True - except Exception: - pass + def on_textview_motion_notify_event(widget, event): + """ + Change the cursor to a hand when we are over a mail or an url + """ + global change_cursor + pointer_x, pointer_y = htmlview.tv.window.get_pointer()[0:2] + x, y = htmlview.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer_x, + pointer_y) + tags = htmlview.tv.get_iter_at_location(x, y).get_tags() + if change_cursor: + htmlview.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( + gtk.gdk.Cursor(gtk.gdk.XTERM)) + change_cursor = None + tag_table = htmlview.tv.get_buffer().get_tag_table() + for tag in tags: + try: + if tag.is_anchor: + htmlview.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( + gtk.gdk.Cursor(gtk.gdk.HAND2)) + change_cursor = tag + elif tag == tag_table.lookup('focus-out-line'): + over_line = True + except Exception: + pass - #if line_tooltip.timeout != 0: - # Check if we should hide the line tooltip - # if not over_line: - # line_tooltip.hide_tooltip() - #if over_line and not line_tooltip.win: - # line_tooltip.timeout = gobject.timeout_add(500, - # show_line_tooltip) - # htmlview.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( - # gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)) - # change_cursor = tag + #if line_tooltip.timeout != 0: + # Check if we should hide the line tooltip + # if not over_line: + # line_tooltip.hide_tooltip() + #if over_line and not line_tooltip.win: + # line_tooltip.timeout = gobject.timeout_add(500, + # show_line_tooltip) + # htmlview.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( + # gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)) + # change_cursor = tag - htmlview.tv.connect('motion_notify_event', on_textview_motion_notify_event) + htmlview.tv.connect('motion_notify_event', on_textview_motion_notify_event) - def handler(texttag, widget, event, iter_, kind, href): - if event.type == gtk.gdk.BUTTON_PRESS: - print href + def handler(texttag, widget, event, iter_, kind, href): + if event.type == gtk.gdk.BUTTON_PRESS: + print href - htmlview.tv.html_hyperlink_handler = handler + htmlview.tv.html_hyperlink_handler = handler - htmlview.print_real_text(None, xhtml='
Hello
\n' - '
\n' - ' World\n' - '
\n') - htmlview.print_real_text(None, xhtml='
') - htmlview.print_real_text(None, xhtml='''

a:bGoogle


''') - htmlview.print_real_text(None, xhtml=''' - -

- OMG, - I'm green - with envy! -

- - ''') - htmlview.print_real_text(None, xhtml='
') - htmlview.print_real_text(None, xhtml=''' - - http://test.com/ testing links autolinkifying - - ''') - htmlview.print_real_text(None, xhtml='
') - htmlview.print_real_text(None, xhtml=''' - -

As Emerson said in his essay Self-Reliance:

-

- "A foolish consistency is the hobgoblin of little minds." -

- - ''') - htmlview.print_real_text(None, xhtml='
') - htmlview.print_real_text(None, xhtml=''' - -

Hey, are you licensed to Jabber?

-

A License to Jabber

- - ''') - htmlview.print_real_text(None, xhtml='
') - htmlview.print_real_text(None, xhtml=''' - -
    -
  • One
  • -
  • Two
  • -
  • Three
  • -

def fac(n):
-  def faciter(n,acc):
-	if n==0: return acc
-	return faciter(n-1, acc*n)
-  if n<0: raise ValueError('Must be non-negative')
-  return faciter(n,1)
- - ''') - htmlview.print_real_text(None, xhtml='
') - htmlview.print_real_text(None, xhtml=''' - -
    -
  1. One
  2. -
  3. Two is nested:
      -
    • One
    • -
    • Two
    • -
    • Three
    • -
    • Four
    • -
  4. -
  5. Three
- - ''') - htmlview.tv.show() - sw = gtk.ScrolledWindow() - sw.set_property('hscrollbar-policy', gtk.POLICY_AUTOMATIC) - sw.set_property('vscrollbar-policy', gtk.POLICY_AUTOMATIC) - sw.set_property('border-width', 0) - sw.add(htmlview.tv) - sw.show() - frame = gtk.Frame() - frame.set_shadow_type(gtk.SHADOW_IN) - frame.show() - frame.add(sw) - w = gtk.Window() - w.add(frame) - w.set_default_size(400, 300) - w.show_all() - w.connect('destroy', lambda w: gtk.main_quit()) - gtk.main() - -# vim: se ts=3: + htmlview.print_real_text(None, xhtml='
Hello
\n' + '
\n' + ' World\n' + '
\n') + htmlview.print_real_text(None, xhtml='
') + htmlview.print_real_text(None, xhtml='''

a:bGoogle


''') + htmlview.print_real_text(None, xhtml=''' + +

+ OMG, + I'm green + with envy! +

+ + ''') + htmlview.print_real_text(None, xhtml='
') + htmlview.print_real_text(None, xhtml=''' + + http://test.com/ testing links autolinkifying + + ''') + htmlview.print_real_text(None, xhtml='
') + htmlview.print_real_text(None, xhtml=''' + +

As Emerson said in his essay Self-Reliance:

+

+ "A foolish consistency is the hobgoblin of little minds." +

+ + ''') + htmlview.print_real_text(None, xhtml='
') + htmlview.print_real_text(None, xhtml=''' + +

Hey, are you licensed to Jabber?

+

A License to Jabber

+ + ''') + htmlview.print_real_text(None, xhtml='
') + htmlview.print_real_text(None, xhtml=''' + +
    +
  • One
  • +
  • Two
  • +
  • Three
  • +

def fac(n):
+def faciter(n,acc):
+    if n==0: return acc
+    return faciter(n-1, acc*n)
+if n<0: raise ValueError('Must be non-negative')
+return faciter(n,1)
+ + ''') + htmlview.print_real_text(None, xhtml='
') + htmlview.print_real_text(None, xhtml=''' + +
    +
  1. One
  2. +
  3. Two is nested:
      +
    • One
    • +
    • Two
    • +
    • Three
    • +
    • Four
    • +
  4. +
  5. Three
+ + ''') + htmlview.tv.show() + sw = gtk.ScrolledWindow() + sw.set_property('hscrollbar-policy', gtk.POLICY_AUTOMATIC) + sw.set_property('vscrollbar-policy', gtk.POLICY_AUTOMATIC) + sw.set_property('border-width', 0) + sw.add(htmlview.tv) + sw.show() + frame = gtk.Frame() + frame.set_shadow_type(gtk.SHADOW_IN) + frame.show() + frame.add(sw) + w = gtk.Window() + w.add(frame) + w.set_default_size(400, 300) + w.show_all() + w.connect('destroy', lambda w: gtk.main_quit()) + gtk.main() diff --git a/src/ipython_view.py b/src/ipython_view.py index a901643f6..49e98fad4 100644 --- a/src/ipython_view.py +++ b/src/ipython_view.py @@ -50,490 +50,487 @@ import pango from StringIO import StringIO try: - import IPython + import IPython except ImportError: - IPython = None + IPython = None class IterableIPShell: - """ - Create an IPython instance. Does not start a blocking event loop, - instead allow single iterations. This allows embedding in GTK+ - without blockage - - @ivar IP: IPython instance. - @type IP: IPython.iplib.InteractiveShell - @ivar iter_more: Indicates if the line executed was a complete command, - or we should wait for more. - @type iter_more: integer - @ivar history_level: The place in history where we currently are - when pressing up/down. - @type history_level: integer - @ivar complete_sep: Seperation delimeters for completion function. - @type complete_sep: _sre.SRE_Pattern - """ - def __init__(self,argv=[],user_ns=None,user_global_ns=None, cin=None, - cout=None,cerr=None, input_func=None): """ - @param argv: Command line options for IPython - @type argv: list - @param user_ns: User namespace. - @type user_ns: dictionary - @param user_global_ns: User global namespace. - @type user_global_ns: dictionary. - @param cin: Console standard input. - @type cin: IO stream - @param cout: Console standard output. - @type cout: IO stream - @param cerr: Console standard error. - @type cerr: IO stream - @param input_func: Replacement for builtin raw_input() - @type input_func: function + Create an IPython instance. Does not start a blocking event loop, + instead allow single iterations. This allows embedding in GTK+ + without blockage + + @ivar IP: IPython instance. + @type IP: IPython.iplib.InteractiveShell + @ivar iter_more: Indicates if the line executed was a complete command, + or we should wait for more. + @type iter_more: integer + @ivar history_level: The place in history where we currently are + when pressing up/down. + @type history_level: integer + @ivar complete_sep: Seperation delimeters for completion function. + @type complete_sep: _sre.SRE_Pattern """ - if input_func: - IPython.iplib.raw_input_original = input_func - if cin: - IPython.Shell.Term.cin = cin - if cout: - IPython.Shell.Term.cout = cout - if cerr: - IPython.Shell.Term.cerr = cerr - - # This is to get rid of the blockage that accurs during - # IPython.Shell.InteractiveShell.user_setup() - IPython.iplib.raw_input = lambda x: None - - self.term = IPython.genutils.IOTerm(cin=cin, cout=cout, cerr=cerr) - os.environ['TERM'] = 'dumb' - excepthook = sys.excepthook - self.IP = IPython.Shell.make_IPython( - argv,user_ns=user_ns, - user_global_ns=user_global_ns, - embedded=True, - shell_class=IPython.Shell.InteractiveShell) - self.IP.system = lambda cmd: self.shell(self.IP.var_expand(cmd), - header='IPython system call: ', - verbose=self.IP.rc.system_verbose) - sys.excepthook = excepthook - self.iter_more = 0 - self.history_level = 0 - self.complete_sep = re.compile('[\s\{\}\[\]\(\)]') - - def execute(self): - """ - Execute the current line provided by the shell object - """ - self.history_level = 0 - orig_stdout = sys.stdout - sys.stdout = IPython.Shell.Term.cout - try: - line = self.IP.raw_input(None, self.iter_more) - if self.IP.autoindent: - self.IP.readline_startup_hook(None) - except KeyboardInterrupt: - self.IP.write('\nKeyboardInterrupt\n') - self.IP.resetbuffer() - # keep cache in sync with the prompt counter: - self.IP.outputcache.prompt_count -= 1 - - if self.IP.autoindent: - self.IP.indent_current_nsp = 0 - self.iter_more = 0 - except: - self.IP.showtraceback() - else: - self.iter_more = self.IP.push(line) - if (self.IP.SyntaxTB.last_syntax_error and - self.IP.rc.autoedit_syntax): - self.IP.edit_syntax_error() - if self.iter_more: - self.prompt = str(self.IP.outputcache.prompt2).strip() - if self.IP.autoindent: - self.IP.readline_startup_hook(self.IP.pre_readline) - else: - self.prompt = str(self.IP.outputcache.prompt1).strip() - sys.stdout = orig_stdout - - def historyBack(self): - """ - Provide one history command back - - @return: The command string. - @rtype: string - """ - self.history_level -= 1 - return self._getHistory() - - def historyForward(self): - """ - Provide one history command forward - - @return: The command string. - @rtype: string - """ - self.history_level += 1 - return self._getHistory() - - def _getHistory(self): - """ - Get the command string of the current history level - - @return: Historic command string. - @rtype: string - """ - try: - rv = self.IP.user_ns['In'][self.history_level].strip('\n') - except IndexError: - self.history_level = 0 - rv = '' - return rv - - def updateNamespace(self, ns_dict): - """ - Add the current dictionary to the shell namespace - - @param ns_dict: A dictionary of symbol-values. - @type ns_dict: dictionary - """ - self.IP.user_ns.update(ns_dict) - - def complete(self, line): - """ - Returns an auto completed line and/or posibilities for completion - - @param line: Given line so far. - @type line: string - - @return: Line completed as for as possible, - and possible further completions. - @rtype: tuple - """ - split_line = self.complete_sep.split(line) - possibilities = self.IP.complete(split_line[-1]) - - try: - __builtins__.all() - except AttributeError: - def all(iterable): - for element in iterable: - if not element: - return False - return True - - def common_prefix(seq): + def __init__(self,argv=[],user_ns=None,user_global_ns=None, cin=None, + cout=None,cerr=None, input_func=None): """ - Return the common prefix of a sequence of strings - """ - return "".join(c for i, c in enumerate(seq[0]) - if all(s.startswith(c, i) for s in seq)) - if possibilities: - completed = line[:-len(split_line[-1])]+common_prefix(possibilities) - else: - completed = line - return completed, possibilities + @param argv: Command line options for IPython + @type argv: list + @param user_ns: User namespace. + @type user_ns: dictionary + @param user_global_ns: User global namespace. + @type user_global_ns: dictionary. + @param cin: Console standard input. + @type cin: IO stream + @param cout: Console standard output. + @type cout: IO stream + @param cerr: Console standard error. + @type cerr: IO stream + @param input_func: Replacement for builtin raw_input() + @type input_func: function + """ + if input_func: + IPython.iplib.raw_input_original = input_func + if cin: + IPython.Shell.Term.cin = cin + if cout: + IPython.Shell.Term.cout = cout + if cerr: + IPython.Shell.Term.cerr = cerr + + # This is to get rid of the blockage that accurs during + # IPython.Shell.InteractiveShell.user_setup() + IPython.iplib.raw_input = lambda x: None + + self.term = IPython.genutils.IOTerm(cin=cin, cout=cout, cerr=cerr) + os.environ['TERM'] = 'dumb' + excepthook = sys.excepthook + self.IP = IPython.Shell.make_IPython( + argv, user_ns=user_ns, + user_global_ns=user_global_ns, + embedded=True, + shell_class=IPython.Shell.InteractiveShell) + self.IP.system = lambda cmd: self.shell(self.IP.var_expand(cmd), + header='IPython system call: ', + verbose=self.IP.rc.system_verbose) + sys.excepthook = excepthook + self.iter_more = 0 + self.history_level = 0 + self.complete_sep = re.compile('[\s\{\}\[\]\(\)]') + + def execute(self): + """ + Execute the current line provided by the shell object + """ + self.history_level = 0 + orig_stdout = sys.stdout + sys.stdout = IPython.Shell.Term.cout + try: + line = self.IP.raw_input(None, self.iter_more) + if self.IP.autoindent: + self.IP.readline_startup_hook(None) + except KeyboardInterrupt: + self.IP.write('\nKeyboardInterrupt\n') + self.IP.resetbuffer() + # keep cache in sync with the prompt counter: + self.IP.outputcache.prompt_count -= 1 + + if self.IP.autoindent: + self.IP.indent_current_nsp = 0 + self.iter_more = 0 + except: + self.IP.showtraceback() + else: + self.iter_more = self.IP.push(line) + if (self.IP.SyntaxTB.last_syntax_error and + self.IP.rc.autoedit_syntax): + self.IP.edit_syntax_error() + if self.iter_more: + self.prompt = str(self.IP.outputcache.prompt2).strip() + if self.IP.autoindent: + self.IP.readline_startup_hook(self.IP.pre_readline) + else: + self.prompt = str(self.IP.outputcache.prompt1).strip() + sys.stdout = orig_stdout + + def historyBack(self): + """ + Provide one history command back + + @return: The command string. + @rtype: string + """ + self.history_level -= 1 + return self._getHistory() + + def historyForward(self): + """ + Provide one history command forward + + @return: The command string. + @rtype: string + """ + self.history_level += 1 + return self._getHistory() + + def _getHistory(self): + """ + Get the command string of the current history level + + @return: Historic command string. + @rtype: string + """ + try: + rv = self.IP.user_ns['In'][self.history_level].strip('\n') + except IndexError: + self.history_level = 0 + rv = '' + return rv + + def updateNamespace(self, ns_dict): + """ + Add the current dictionary to the shell namespace + + @param ns_dict: A dictionary of symbol-values. + @type ns_dict: dictionary + """ + self.IP.user_ns.update(ns_dict) + + def complete(self, line): + """ + Returns an auto completed line and/or posibilities for completion + + @param line: Given line so far. + @type line: string + + @return: Line completed as for as possible, + and possible further completions. + @rtype: tuple + """ + split_line = self.complete_sep.split(line) + possibilities = self.IP.complete(split_line[-1]) + + try: + __builtins__.all() + except AttributeError: + def all(iterable): + for element in iterable: + if not element: + return False + return True + + def common_prefix(seq): + """ + Return the common prefix of a sequence of strings + """ + return "".join(c for i, c in enumerate(seq[0]) + if all(s.startswith(c, i) for s in seq)) + if possibilities: + completed = line[:-len(split_line[-1])]+common_prefix(possibilities) + else: + completed = line + return completed, possibilities - def shell(self, cmd,verbose=0,debug=0,header=''): - """ - Replacement method to allow shell commands without them blocking + def shell(self, cmd,verbose=0,debug=0,header=''): + """ + Replacement method to allow shell commands without them blocking - @param cmd: Shell command to execute. - @type cmd: string - @param verbose: Verbosity - @type verbose: integer - @param debug: Debug level - @type debug: integer - @param header: Header to be printed before output - @type header: string - """ - if verbose or debug: print header+cmd - # flush stdout so we don't mangle python's buffering - if not debug: - input_, output = os.popen4(cmd) - print output.read() - output.close() - input_.close() + @param cmd: Shell command to execute. + @type cmd: string + @param verbose: Verbosity + @type verbose: integer + @param debug: Debug level + @type debug: integer + @param header: Header to be printed before output + @type header: string + """ + if verbose or debug: print header+cmd + # flush stdout so we don't mangle python's buffering + if not debug: + input_, output = os.popen4(cmd) + print output.read() + output.close() + input_.close() class ConsoleView(gtk.TextView): - """ - Specialized text view for console-like workflow - - @cvar ANSI_COLORS: Mapping of terminal colors to X11 names. - @type ANSI_COLORS: dictionary - - @ivar text_buffer: Widget's text buffer. - @type text_buffer: gtk.TextBuffer - @ivar color_pat: Regex of terminal color pattern - @type color_pat: _sre.SRE_Pattern - @ivar mark: Scroll mark for automatic scrolling on input. - @type mark: gtk.TextMark - @ivar line_start: Start of command line mark. - @type line_start: gtk.TextMark - """ - - ANSI_COLORS = {'0;30': 'Black', '0;31': 'Red', - '0;32': 'Green', '0;33': 'Brown', - '0;34': 'Blue', '0;35': 'Purple', - '0;36': 'Cyan', '0;37': 'LightGray', - '1;30': 'DarkGray', '1;31': 'DarkRed', - '1;32': 'SeaGreen', '1;33': 'Yellow', - '1;34': 'LightBlue', '1;35': 'MediumPurple', - '1;36': 'LightCyan', '1;37': 'White'} - - def __init__(self): """ - Initialize console view + Specialized text view for console-like workflow + + @cvar ANSI_COLORS: Mapping of terminal colors to X11 names. + @type ANSI_COLORS: dictionary + + @ivar text_buffer: Widget's text buffer. + @type text_buffer: gtk.TextBuffer + @ivar color_pat: Regex of terminal color pattern + @type color_pat: _sre.SRE_Pattern + @ivar mark: Scroll mark for automatic scrolling on input. + @type mark: gtk.TextMark + @ivar line_start: Start of command line mark. + @type line_start: gtk.TextMark """ - gtk.TextView.__init__(self) - self.modify_font(pango.FontDescription('Mono')) - self.set_cursor_visible(True) - self.text_buffer = self.get_buffer() - self.mark = self.text_buffer.create_mark('scroll_mark', - self.text_buffer.get_end_iter(), - False) - for code in self.ANSI_COLORS: - self.text_buffer.create_tag(code, - foreground=self.ANSI_COLORS[code], - weight=700) - self.text_buffer.create_tag('0') - self.text_buffer.create_tag('notouch', editable=False) - self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?') - self.line_start = \ - self.text_buffer.create_mark('line_start', - self.text_buffer.get_end_iter(), True) - self.connect('key-press-event', self.onKeyPress) - def write(self, text, editable=False): - gobject.idle_add(self._write, text, editable) + ANSI_COLORS = {'0;30': 'Black', '0;31': 'Red', + '0;32': 'Green', '0;33': 'Brown', + '0;34': 'Blue', '0;35': 'Purple', + '0;36': 'Cyan', '0;37': 'LightGray', + '1;30': 'DarkGray', '1;31': 'DarkRed', + '1;32': 'SeaGreen', '1;33': 'Yellow', + '1;34': 'LightBlue', '1;35': 'MediumPurple', + '1;36': 'LightCyan', '1;37': 'White'} - def _write(self, text, editable=False): - """ - Write given text to buffer + def __init__(self): + """ + Initialize console view + """ + gtk.TextView.__init__(self) + self.modify_font(pango.FontDescription('Mono')) + self.set_cursor_visible(True) + self.text_buffer = self.get_buffer() + self.mark = self.text_buffer.create_mark('scroll_mark', + self.text_buffer.get_end_iter(), + False) + for code in self.ANSI_COLORS: + self.text_buffer.create_tag(code, + foreground=self.ANSI_COLORS[code], + weight=700) + self.text_buffer.create_tag('0') + self.text_buffer.create_tag('notouch', editable=False) + self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?') + self.line_start = \ + self.text_buffer.create_mark('line_start', + self.text_buffer.get_end_iter(), True) + self.connect('key-press-event', self.onKeyPress) - @param text: Text to append. - @type text: string - @param editable: If true, added text is editable. - @type editable: boolean - """ - segments = self.color_pat.split(text) - segment = segments.pop(0) - start_mark = self.text_buffer.create_mark(None, - self.text_buffer.get_end_iter(), - True) - self.text_buffer.insert(self.text_buffer.get_end_iter(), segment) + def write(self, text, editable=False): + gobject.idle_add(self._write, text, editable) - if segments: - ansi_tags = self.color_pat.findall(text) - for tag in ansi_tags: - i = segments.index(tag) - self.text_buffer.insert_with_tags_by_name(self.text_buffer.get_end_iter(), - segments[i+1], tag) - segments.pop(i) - if not editable: - self.text_buffer.apply_tag_by_name('notouch', - self.text_buffer.get_iter_at_mark(start_mark), - self.text_buffer.get_end_iter()) - self.text_buffer.delete_mark(start_mark) - self.scroll_mark_onscreen(self.mark) + def _write(self, text, editable=False): + """ + Write given text to buffer + + @param text: Text to append. + @type text: string + @param editable: If true, added text is editable. + @type editable: boolean + """ + segments = self.color_pat.split(text) + segment = segments.pop(0) + start_mark = self.text_buffer.create_mark(None, + self.text_buffer.get_end_iter(), + True) + self.text_buffer.insert(self.text_buffer.get_end_iter(), segment) + + if segments: + ansi_tags = self.color_pat.findall(text) + for tag in ansi_tags: + i = segments.index(tag) + self.text_buffer.insert_with_tags_by_name(self.text_buffer.get_end_iter(), + segments[i+1], tag) + segments.pop(i) + if not editable: + self.text_buffer.apply_tag_by_name('notouch', + self.text_buffer.get_iter_at_mark(start_mark), + self.text_buffer.get_end_iter()) + self.text_buffer.delete_mark(start_mark) + self.scroll_mark_onscreen(self.mark) - def showPrompt(self, prompt): - gobject.idle_add(self._showPrompt, prompt) + def showPrompt(self, prompt): + gobject.idle_add(self._showPrompt, prompt) - def _showPrompt(self, prompt): - """ - Print prompt at start of line + def _showPrompt(self, prompt): + """ + Print prompt at start of line - @param prompt: Prompt to print. - @type prompt: string - """ - self._write(prompt) - self.text_buffer.move_mark(self.line_start, - self.text_buffer.get_end_iter()) + @param prompt: Prompt to print. + @type prompt: string + """ + self._write(prompt) + self.text_buffer.move_mark(self.line_start, + self.text_buffer.get_end_iter()) - def changeLine(self, text): - gobject.idle_add(self._changeLine, text) + def changeLine(self, text): + gobject.idle_add(self._changeLine, text) - def _changeLine(self, text): - """ - Replace currently entered command line with given text + def _changeLine(self, text): + """ + Replace currently entered command line with given text - @param text: Text to use as replacement. - @type text: string - """ - iter_ = self.text_buffer.get_iter_at_mark(self.line_start) - iter_.forward_to_line_end() - self.text_buffer.delete(self.text_buffer.get_iter_at_mark(self.line_start), iter_) - self._write(text, True) + @param text: Text to use as replacement. + @type text: string + """ + iter_ = self.text_buffer.get_iter_at_mark(self.line_start) + iter_.forward_to_line_end() + self.text_buffer.delete(self.text_buffer.get_iter_at_mark(self.line_start), iter_) + self._write(text, True) - def getCurrentLine(self): - """ - Get text in current command line + def getCurrentLine(self): + """ + Get text in current command line - @return: Text of current command line. - @rtype: string - """ - rv = self.text_buffer.get_slice( - self.text_buffer.get_iter_at_mark(self.line_start), - self.text_buffer.get_end_iter(), False) - return rv + @return: Text of current command line. + @rtype: string + """ + rv = self.text_buffer.get_slice( + self.text_buffer.get_iter_at_mark(self.line_start), + self.text_buffer.get_end_iter(), False) + return rv - def showReturned(self, text): - gobject.idle_add(self._showReturned, text) + def showReturned(self, text): + gobject.idle_add(self._showReturned, text) - def _showReturned(self, text): - """ - Show returned text from last command and print new prompt + def _showReturned(self, text): + """ + Show returned text from last command and print new prompt - @param text: Text to show. - @type text: string - """ - iter_ = self.text_buffer.get_iter_at_mark(self.line_start) - iter_.forward_to_line_end() - self.text_buffer.apply_tag_by_name( - 'notouch', - self.text_buffer.get_iter_at_mark(self.line_start), - iter_) - self._write('\n'+text) - if text: - self._write('\n') - self._showPrompt(self.prompt) - self.text_buffer.move_mark(self.line_start,self.text_buffer.get_end_iter()) - self.text_buffer.place_cursor(self.text_buffer.get_end_iter()) + @param text: Text to show. + @type text: string + """ + iter_ = self.text_buffer.get_iter_at_mark(self.line_start) + iter_.forward_to_line_end() + self.text_buffer.apply_tag_by_name( + 'notouch', + self.text_buffer.get_iter_at_mark(self.line_start), + iter_) + self._write('\n'+text) + if text: + self._write('\n') + self._showPrompt(self.prompt) + self.text_buffer.move_mark(self.line_start, self.text_buffer.get_end_iter()) + self.text_buffer.place_cursor(self.text_buffer.get_end_iter()) - def onKeyPress(self, widget, event): - """ - Key press callback used for correcting behavior for console-like - interfaces. For example 'home' should go to prompt, not to begining of - line + def onKeyPress(self, widget, event): + """ + Key press callback used for correcting behavior for console-like + interfaces. For example 'home' should go to prompt, not to begining of + line - @param widget: Widget that key press accored in. - @type widget: gtk.Widget - @param event: Event object - @type event: gtk.gdk.Event + @param widget: Widget that key press accored in. + @type widget: gtk.Widget + @param event: Event object + @type event: gtk.gdk.Event - @return: Return True if event should not trickle. - @rtype: boolean - """ - insert_mark = self.text_buffer.get_insert() - insert_iter = self.text_buffer.get_iter_at_mark(insert_mark) - selection_mark = self.text_buffer.get_selection_bound() - selection_iter = self.text_buffer.get_iter_at_mark(selection_mark) - start_iter = self.text_buffer.get_iter_at_mark(self.line_start) - if event.keyval == gtk.keysyms.Home: - if event.state == 0: - self.text_buffer.place_cursor(start_iter) - return True - elif event.state == gtk.gdk.SHIFT_MASK: - self.text_buffer.move_mark(insert_mark, start_iter) - return True - elif event.keyval == gtk.keysyms.Left: - insert_iter.backward_cursor_position() - if not insert_iter.editable(True): - return True - elif not event.string: - pass - elif start_iter.compare(insert_iter) <= 0 and \ - start_iter.compare(selection_iter) <= 0: - pass - elif start_iter.compare(insert_iter) > 0 and \ - start_iter.compare(selection_iter) > 0: - self.text_buffer.place_cursor(start_iter) - elif insert_iter.compare(selection_iter) < 0: - self.text_buffer.move_mark(insert_mark, start_iter) - elif insert_iter.compare(selection_iter) > 0: - self.text_buffer.move_mark(selection_mark, start_iter) + @return: Return True if event should not trickle. + @rtype: boolean + """ + insert_mark = self.text_buffer.get_insert() + insert_iter = self.text_buffer.get_iter_at_mark(insert_mark) + selection_mark = self.text_buffer.get_selection_bound() + selection_iter = self.text_buffer.get_iter_at_mark(selection_mark) + start_iter = self.text_buffer.get_iter_at_mark(self.line_start) + if event.keyval == gtk.keysyms.Home: + if event.state == 0: + self.text_buffer.place_cursor(start_iter) + return True + elif event.state == gtk.gdk.SHIFT_MASK: + self.text_buffer.move_mark(insert_mark, start_iter) + return True + elif event.keyval == gtk.keysyms.Left: + insert_iter.backward_cursor_position() + if not insert_iter.editable(True): + return True + elif not event.string: + pass + elif start_iter.compare(insert_iter) <= 0 and \ + start_iter.compare(selection_iter) <= 0: + pass + elif start_iter.compare(insert_iter) > 0 and \ + start_iter.compare(selection_iter) > 0: + self.text_buffer.place_cursor(start_iter) + elif insert_iter.compare(selection_iter) < 0: + self.text_buffer.move_mark(insert_mark, start_iter) + elif insert_iter.compare(selection_iter) > 0: + self.text_buffer.move_mark(selection_mark, start_iter) - return self.onKeyPressExtend(event) + return self.onKeyPressExtend(event) - def onKeyPressExtend(self, event): - """ - For some reason we can't extend onKeyPress directly (bug #500900) - """ - pass + def onKeyPressExtend(self, event): + """ + For some reason we can't extend onKeyPress directly (bug #500900) + """ + pass class IPythonView(ConsoleView, IterableIPShell): - ''' - Sub-class of both modified IPython shell and L{ConsoleView} this makes - a GTK+ IPython console. - ''' - def __init__(self): - """ - Initialize. Redirect I/O to console - """ - ConsoleView.__init__(self) - self.cout = StringIO() - IterableIPShell.__init__(self, cout=self.cout,cerr=self.cout, - input_func=self.raw_input) + ''' + Sub-class of both modified IPython shell and L{ConsoleView} this makes + a GTK+ IPython console. + ''' + def __init__(self): + """ + Initialize. Redirect I/O to console + """ + ConsoleView.__init__(self) + self.cout = StringIO() + IterableIPShell.__init__(self, cout=self.cout, cerr=self.cout, + input_func=self.raw_input) # self.connect('key_press_event', self.keyPress) - self.execute() - self.cout.truncate(0) - self.showPrompt(self.prompt) - self.interrupt = False - - def raw_input(self, prompt=''): - """ - Custom raw_input() replacement. Get's current line from console buffer - - @param prompt: Prompt to print. Here for compatability as replacement. - @type prompt: string - - @return: The current command line text. - @rtype: string - """ - if self.interrupt: - self.interrupt = False - raise KeyboardInterrupt - return self.getCurrentLine() - - def onKeyPressExtend(self, event): - """ - Key press callback with plenty of shell goodness, like history, - autocompletions, etc - - @param widget: Widget that key press occured in. - @type widget: gtk.Widget - @param event: Event object. - @type event: gtk.gdk.Event - - @return: True if event should not trickle. - @rtype: boolean - """ - if event.state & gtk.gdk.CONTROL_MASK and event.keyval == 99: - self.interrupt = True - self._processLine() - return True - elif event.keyval == gtk.keysyms.Return: - self._processLine() - return True - elif event.keyval == gtk.keysyms.Up: - self.changeLine(self.historyBack()) - return True - elif event.keyval == gtk.keysyms.Down: - self.changeLine(self.historyForward()) - return True - elif event.keyval == gtk.keysyms.Tab: - if not self.getCurrentLine().strip(): - return False - completed, possibilities = self.complete(self.getCurrentLine()) - if len(possibilities) > 1: - slice_ = self.getCurrentLine() - self.write('\n') - for symbol in possibilities: - self.write(symbol+'\n') + self.execute() + self.cout.truncate(0) self.showPrompt(self.prompt) - self.changeLine(completed or slice_) - return True + self.interrupt = False - def _processLine(self): - """ - Process current command line - """ - self.history_pos = 0 - self.execute() - rv = self.cout.getvalue() - if rv: rv = rv.strip('\n') - self.showReturned(rv) - self.cout.truncate(0) + def raw_input(self, prompt=''): + """ + Custom raw_input() replacement. Get's current line from console buffer + @param prompt: Prompt to print. Here for compatability as replacement. + @type prompt: string -# vim: se ts=3: + @return: The current command line text. + @rtype: string + """ + if self.interrupt: + self.interrupt = False + raise KeyboardInterrupt + return self.getCurrentLine() + + def onKeyPressExtend(self, event): + """ + Key press callback with plenty of shell goodness, like history, + autocompletions, etc + + @param widget: Widget that key press occured in. + @type widget: gtk.Widget + @param event: Event object. + @type event: gtk.gdk.Event + + @return: True if event should not trickle. + @rtype: boolean + """ + if event.state & gtk.gdk.CONTROL_MASK and event.keyval == 99: + self.interrupt = True + self._processLine() + return True + elif event.keyval == gtk.keysyms.Return: + self._processLine() + return True + elif event.keyval == gtk.keysyms.Up: + self.changeLine(self.historyBack()) + return True + elif event.keyval == gtk.keysyms.Down: + self.changeLine(self.historyForward()) + return True + elif event.keyval == gtk.keysyms.Tab: + if not self.getCurrentLine().strip(): + return False + completed, possibilities = self.complete(self.getCurrentLine()) + if len(possibilities) > 1: + slice_ = self.getCurrentLine() + self.write('\n') + for symbol in possibilities: + self.write(symbol+'\n') + self.showPrompt(self.prompt) + self.changeLine(completed or slice_) + return True + + def _processLine(self): + """ + Process current command line + """ + self.history_pos = 0 + self.execute() + rv = self.cout.getvalue() + if rv: rv = rv.strip('\n') + self.showReturned(rv) + self.cout.truncate(0) diff --git a/src/message_control.py b/src/message_control.py index 1af5cc765..86205c50f 100644 --- a/src/message_control.py +++ b/src/message_control.py @@ -39,209 +39,207 @@ TYPE_PM = 'pm' #################### class MessageControl(object): - """ - An abstract base widget that can embed in the gtk.Notebook of a - MessageWindow - """ + """ + An abstract base widget that can embed in the gtk.Notebook of a + MessageWindow + """ - def __init__(self, type_id, parent_win, widget_name, contact, account, resource = None): - # dict { cb id : widget} - # keep all registered callbacks of widgets, created by self.xml - self.handlers = {} - self.type_id = type_id - self.parent_win = parent_win - self.widget_name = widget_name - self.contact = contact - self.account = account - self.hide_chat_buttons = False - self.resource = resource + def __init__(self, type_id, parent_win, widget_name, contact, account, resource = None): + # dict { cb id : widget} + # keep all registered callbacks of widgets, created by self.xml + self.handlers = {} + self.type_id = type_id + self.parent_win = parent_win + self.widget_name = widget_name + self.contact = contact + self.account = account + self.hide_chat_buttons = False + self.resource = resource - self.session = None + self.session = None - gajim.last_message_time[self.account][self.get_full_jid()] = 0 + gajim.last_message_time[self.account][self.get_full_jid()] = 0 - self.xml = gtkgui_helpers.get_gtk_builder('%s.ui' % widget_name) - self.widget = self.xml.get_object('%s_vbox' % widget_name) + self.xml = gtkgui_helpers.get_gtk_builder('%s.ui' % widget_name) + self.widget = self.xml.get_object('%s_vbox' % widget_name) - def get_full_jid(self): - fjid = self.contact.jid - if self.resource: - fjid += '/' + self.resource - return fjid + def get_full_jid(self): + fjid = self.contact.jid + if self.resource: + fjid += '/' + self.resource + return fjid - def set_control_active(self, state): - """ - Called when the control becomes active (state is True) or inactive (state - is False) - """ - pass # Derived classes MUST implement this method + def set_control_active(self, state): + """ + Called when the control becomes active (state is True) or inactive (state + is False) + """ + pass # Derived classes MUST implement this method - def minimizable(self): - """ - Called to check if control can be minimized + def minimizable(self): + """ + Called to check if control can be minimized - Derived classes MAY implement this. - """ - return False + Derived classes MAY implement this. + """ + return False - def safe_shutdown(self): - """ - Called to check if control can be closed without loosing data. - returns True if control can be closed safely else False + def safe_shutdown(self): + """ + Called to check if control can be closed without loosing data. + returns True if control can be closed safely else False - Derived classes MAY implement this. - """ - return True + Derived classes MAY implement this. + """ + return True - def allow_shutdown(self, method, on_response_yes, on_response_no, - on_response_minimize): - """ - Called to check is a control is allowed to shutdown. - If a control is not in a suitable shutdown state this method - should call on_response_no, else on_response_yes or - on_response_minimize + def allow_shutdown(self, method, on_response_yes, on_response_no, + on_response_minimize): + """ + Called to check is a control is allowed to shutdown. + If a control is not in a suitable shutdown state this method + should call on_response_no, else on_response_yes or + on_response_minimize - Derived classes MAY implement this. - """ - on_response_yes(self) + Derived classes MAY implement this. + """ + on_response_yes(self) - def shutdown(self): - """ - Derived classes MUST implement this - """ - pass + def shutdown(self): + """ + Derived classes MUST implement this + """ + pass - def repaint_themed_widgets(self): - """ - Derived classes SHOULD implement this - """ - pass + def repaint_themed_widgets(self): + """ + Derived classes SHOULD implement this + """ + pass - def update_ui(self): - """ - Derived classes SHOULD implement this - """ - pass + def update_ui(self): + """ + Derived classes SHOULD implement this + """ + pass - def toggle_emoticons(self): - """ - Derived classes MAY implement this - """ - pass + def toggle_emoticons(self): + """ + Derived classes MAY implement this + """ + pass - def update_font(self): - """ - Derived classes SHOULD implement this - """ - pass + def update_font(self): + """ + Derived classes SHOULD implement this + """ + pass - def update_tags(self): - """ - Derived classes SHOULD implement this - """ - pass + def update_tags(self): + """ + Derived classes SHOULD implement this + """ + pass - def get_tab_label(self, chatstate): - """ - Return a suitable tab label string. Returns a tuple such as: (label_str, - color) either of which can be None if chatstate is given that means we - have HE SENT US a chatstate and we want it displayed + def get_tab_label(self, chatstate): + """ + Return a suitable tab label string. Returns a tuple such as: (label_str, + color) either of which can be None if chatstate is given that means we + have HE SENT US a chatstate and we want it displayed - Derivded classes MUST implement this. - """ - # Return a markup'd label and optional gtk.Color in a tupple like: - # return (label_str, None) - pass + Derivded classes MUST implement this. + """ + # Return a markup'd label and optional gtk.Color in a tupple like: + # return (label_str, None) + pass - def get_tab_image(self, count_unread=True): - # Return a suitable tab image for display. - # None clears any current label. - return None + def get_tab_image(self, count_unread=True): + # Return a suitable tab image for display. + # None clears any current label. + return None - def prepare_context_menu(self): - """ - Derived classes SHOULD implement this - """ - return None + def prepare_context_menu(self): + """ + Derived classes SHOULD implement this + """ + return None - def chat_buttons_set_visible(self, state): - """ - Derived classes MAY implement this - """ - self.hide_chat_buttons = state + def chat_buttons_set_visible(self, state): + """ + Derived classes MAY implement this + """ + self.hide_chat_buttons = state - def got_connected(self): - pass + def got_connected(self): + pass - def got_disconnected(self): - pass + def got_disconnected(self): + pass - def get_specific_unread(self): - return len(gajim.events.get_events(self.account, - self.contact.jid)) + def get_specific_unread(self): + return len(gajim.events.get_events(self.account, + self.contact.jid)) - def set_session(self, session): - oldsession = None - if hasattr(self, 'session'): - oldsession = self.session + def set_session(self, session): + oldsession = None + if hasattr(self, 'session'): + oldsession = self.session - if oldsession and session == oldsession: - return + if oldsession and session == oldsession: + return - self.session = session + self.session = session - if session: - session.control = self + if session: + session.control = self - if oldsession: - oldsession.control = None + if oldsession: + oldsession.control = None - jid = self.contact.jid - if self.resource: - jid += '/' + self.resource + jid = self.contact.jid + if self.resource: + jid += '/' + self.resource - crypto_changed = bool(session and session.enable_encryption) != \ - bool(oldsession and oldsession.enable_encryption) + crypto_changed = bool(session and session.enable_encryption) != \ + bool(oldsession and oldsession.enable_encryption) - if crypto_changed: - self.print_esession_details() + if crypto_changed: + self.print_esession_details() - def send_message(self, message, keyID='', type_='chat', chatstate=None, - msg_id=None, composing_xep=None, resource=None, user_nick=None, - xhtml=None, callback=None, callback_args=[]): - # Send the given message to the active tab. - # Doesn't return None if error - jid = self.contact.jid + def send_message(self, message, keyID='', type_='chat', chatstate=None, + msg_id=None, composing_xep=None, resource=None, user_nick=None, + xhtml=None, callback=None, callback_args=[]): + # Send the given message to the active tab. + # Doesn't return None if error + jid = self.contact.jid - message = helpers.remove_invalid_xml_chars(message) + message = helpers.remove_invalid_xml_chars(message) - original_message = message - conn = gajim.connections[self.account] + original_message = message + conn = gajim.connections[self.account] - if not self.session: - if not resource: - if self.resource: - resource = self.resource - else: - resource = self.contact.resource - sess = conn.find_controlless_session(jid, resource=resource) + if not self.session: + if not resource: + if self.resource: + resource = self.resource + else: + resource = self.contact.resource + sess = conn.find_controlless_session(jid, resource=resource) - if self.resource: - jid += '/' + self.resource + if self.resource: + jid += '/' + self.resource - if not sess: - if self.type_id == TYPE_PM: - sess = conn.make_new_session(jid, type_='pm') - else: - sess = conn.make_new_session(jid) + if not sess: + if self.type_id == TYPE_PM: + sess = conn.make_new_session(jid, type_='pm') + else: + sess = conn.make_new_session(jid) - self.set_session(sess) + self.set_session(sess) - # Send and update history - conn.send_message(jid, message, keyID, type_=type_, chatstate=chatstate, - msg_id=msg_id, composing_xep=composing_xep, resource=self.resource, - user_nick=user_nick, session=self.session, - original_message=original_message, xhtml=xhtml, callback=callback, - callback_args=callback_args) - -# vim: se ts=3: + # Send and update history + conn.send_message(jid, message, keyID, type_=type_, chatstate=chatstate, + msg_id=msg_id, composing_xep=composing_xep, resource=self.resource, + user_nick=user_nick, session=self.session, + original_message=original_message, xhtml=xhtml, callback=callback, + callback_args=callback_args) diff --git a/src/message_textview.py b/src/message_textview.py index 37c865043..1b568af6b 100644 --- a/src/message_textview.py +++ b/src/message_textview.py @@ -31,281 +31,281 @@ import gtkgui_helpers from common import gajim class MessageTextView(gtk.TextView): - """ - Class for the message textview (where user writes new messages) for - chat/groupchat windows - """ - UNDO_LIMIT = 20 - __gsignals__ = dict( - mykeypress = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION, - None, # return value - (int, gtk.gdk.ModifierType ) # arguments - ) - ) + """ + Class for the message textview (where user writes new messages) for + chat/groupchat windows + """ + UNDO_LIMIT = 20 + __gsignals__ = dict( + mykeypress = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION, + None, # return value + (int, gtk.gdk.ModifierType ) # arguments + ) + ) - def __init__(self): - gtk.TextView.__init__(self) + def __init__(self): + gtk.TextView.__init__(self) - # set properties - self.set_border_width(1) - self.set_accepts_tab(True) - self.set_editable(True) - self.set_cursor_visible(True) - self.set_wrap_mode(gtk.WRAP_WORD_CHAR) - self.set_left_margin(2) - self.set_right_margin(2) - self.set_pixels_above_lines(2) - self.set_pixels_below_lines(2) + # set properties + self.set_border_width(1) + self.set_accepts_tab(True) + self.set_editable(True) + self.set_cursor_visible(True) + self.set_wrap_mode(gtk.WRAP_WORD_CHAR) + self.set_left_margin(2) + self.set_right_margin(2) + self.set_pixels_above_lines(2) + self.set_pixels_below_lines(2) - # set undo list - self.undo_list = [] - # needed to know if we undid something - self.undo_pressed = False - self.lang = None # Lang used for spell checking - _buffer = self.get_buffer() - self.begin_tags = {} - self.end_tags = {} - self.color_tags = [] - self.fonts_tags = [] - self.other_tags = {} - self.other_tags['bold'] = _buffer.create_tag('bold') - self.other_tags['bold'].set_property('weight', pango.WEIGHT_BOLD) - self.begin_tags['bold'] = '' - self.end_tags['bold'] = '' - self.other_tags['italic'] = _buffer.create_tag('italic') - self.other_tags['italic'].set_property('style', pango.STYLE_ITALIC) - self.begin_tags['italic'] = '' - self.end_tags['italic'] = '' - self.other_tags['underline'] = _buffer.create_tag('underline') - self.other_tags['underline'].set_property('underline', pango.UNDERLINE_SINGLE) - self.begin_tags['underline'] = '' - self.end_tags['underline'] = '' - self.other_tags['strike'] = _buffer.create_tag('strike') - self.other_tags['strike'].set_property('strikethrough', True) - self.begin_tags['strike'] = '' - self.end_tags['strike'] = '' + # set undo list + self.undo_list = [] + # needed to know if we undid something + self.undo_pressed = False + self.lang = None # Lang used for spell checking + _buffer = self.get_buffer() + self.begin_tags = {} + self.end_tags = {} + self.color_tags = [] + self.fonts_tags = [] + self.other_tags = {} + self.other_tags['bold'] = _buffer.create_tag('bold') + self.other_tags['bold'].set_property('weight', pango.WEIGHT_BOLD) + self.begin_tags['bold'] = '' + self.end_tags['bold'] = '' + self.other_tags['italic'] = _buffer.create_tag('italic') + self.other_tags['italic'].set_property('style', pango.STYLE_ITALIC) + self.begin_tags['italic'] = '' + self.end_tags['italic'] = '' + self.other_tags['underline'] = _buffer.create_tag('underline') + self.other_tags['underline'].set_property('underline', pango.UNDERLINE_SINGLE) + self.begin_tags['underline'] = '' + self.end_tags['underline'] = '' + self.other_tags['strike'] = _buffer.create_tag('strike') + self.other_tags['strike'].set_property('strikethrough', True) + self.begin_tags['strike'] = '' + self.end_tags['strike'] = '' - def make_clickable_urls(self, text): - _buffer = self.get_buffer() + def make_clickable_urls(self, text): + _buffer = self.get_buffer() - start = 0 - end = 0 - index = 0 + start = 0 + end = 0 + index = 0 - new_text = '' - iterator = gajim.interface.link_pattern_re.finditer(text) - for match in iterator: - start, end = match.span() - url = text[start:end] - if start != 0: - text_before_special_text = text[index:start] - else: - text_before_special_text = '' - end_iter = _buffer.get_end_iter() - # we insert normal text - new_text += text_before_special_text + \ - '' + url + '' + new_text = '' + iterator = gajim.interface.link_pattern_re.finditer(text) + for match in iterator: + start, end = match.span() + url = text[start:end] + if start != 0: + text_before_special_text = text[index:start] + else: + text_before_special_text = '' + end_iter = _buffer.get_end_iter() + # we insert normal text + new_text += text_before_special_text + \ + '' + url + '' - index = end # update index + index = end # update index - if end < len(text): - new_text += text[end:] + if end < len(text): + new_text += text[end:] - return new_text # the position after *last* special text + return new_text # the position after *last* special text - def get_active_tags(self): - start, finish = self.get_active_iters() - active_tags = [] - for tag in start.get_tags(): - active_tags.append(tag.get_property('name')) - return active_tags + def get_active_tags(self): + start, finish = self.get_active_iters() + active_tags = [] + for tag in start.get_tags(): + active_tags.append(tag.get_property('name')) + return active_tags - def get_active_iters(self): - _buffer = self.get_buffer() - return_val = _buffer.get_selection_bounds() - if return_val: # if sth was selected - start, finish = return_val[0], return_val[1] - else: - start, finish = _buffer.get_bounds() - return (start, finish) + def get_active_iters(self): + _buffer = self.get_buffer() + return_val = _buffer.get_selection_bounds() + if return_val: # if sth was selected + start, finish = return_val[0], return_val[1] + else: + start, finish = _buffer.get_bounds() + return (start, finish) - def set_tag(self, widget, tag): - _buffer = self.get_buffer() - start, finish = self.get_active_iters() - if start.has_tag(self.other_tags[tag]): - _buffer.remove_tag_by_name(tag, start, finish) - else: - if tag == 'underline': - _buffer.remove_tag_by_name('strike', start, finish) - elif tag == 'strike': - _buffer.remove_tag_by_name('underline', start, finish) - _buffer.apply_tag_by_name(tag, start, finish) + def set_tag(self, widget, tag): + _buffer = self.get_buffer() + start, finish = self.get_active_iters() + if start.has_tag(self.other_tags[tag]): + _buffer.remove_tag_by_name(tag, start, finish) + else: + if tag == 'underline': + _buffer.remove_tag_by_name('strike', start, finish) + elif tag == 'strike': + _buffer.remove_tag_by_name('underline', start, finish) + _buffer.apply_tag_by_name(tag, start, finish) - def clear_tags(self, widget): - _buffer = self.get_buffer() - start, finish = self.get_active_iters() - _buffer.remove_all_tags(start, finish) + def clear_tags(self, widget): + _buffer = self.get_buffer() + start, finish = self.get_active_iters() + _buffer.remove_all_tags(start, finish) - def color_set(self, widget, response, color): - if response == -6: - widget.destroy() - return - _buffer = self.get_buffer() - color = color.get_current_color() - widget.destroy() - color_string = gtkgui_helpers.make_color_string(color) - tag_name = 'color' + color_string - if not tag_name in self.color_tags: - tagColor = _buffer.create_tag(tag_name) - tagColor.set_property('foreground', color_string) - self.begin_tags[tag_name] = '' - self.end_tags[tag_name] = '' - self.color_tags.append(tag_name) + def color_set(self, widget, response, color): + if response == -6: + widget.destroy() + return + _buffer = self.get_buffer() + color = color.get_current_color() + widget.destroy() + color_string = gtkgui_helpers.make_color_string(color) + tag_name = 'color' + color_string + if not tag_name in self.color_tags: + tagColor = _buffer.create_tag(tag_name) + tagColor.set_property('foreground', color_string) + self.begin_tags[tag_name] = '' + self.end_tags[tag_name] = '' + self.color_tags.append(tag_name) - start, finish = self.get_active_iters() + start, finish = self.get_active_iters() - for tag in self.color_tags: - _buffer.remove_tag_by_name(tag, start, finish) + for tag in self.color_tags: + _buffer.remove_tag_by_name(tag, start, finish) - _buffer.apply_tag_by_name(tag_name, start, finish) + _buffer.apply_tag_by_name(tag_name, start, finish) - def font_set(self, widget, response, font): - if response == -6: - widget.destroy() - return + def font_set(self, widget, response, font): + if response == -6: + widget.destroy() + return - _buffer = self.get_buffer() + _buffer = self.get_buffer() - font = font.get_font_name() - font_desc = pango.FontDescription(font) - family = font_desc.get_family() - size = font_desc.get_size() - size = size / pango.SCALE - weight = font_desc.get_weight() - style = font_desc.get_style() + font = font.get_font_name() + font_desc = pango.FontDescription(font) + family = font_desc.get_family() + size = font_desc.get_size() + size = size / pango.SCALE + weight = font_desc.get_weight() + style = font_desc.get_style() - widget.destroy() + widget.destroy() - tag_name = 'font' + font - if not tag_name in self.fonts_tags: - tagFont = _buffer.create_tag(tag_name) - tagFont.set_property('font', family + ' ' + str(size)) - self.begin_tags[tag_name] = \ - '' - self.end_tags[tag_name] = '' - self.fonts_tags.append(tag_name) + tag_name = 'font' + font + if not tag_name in self.fonts_tags: + tagFont = _buffer.create_tag(tag_name) + tagFont.set_property('font', family + ' ' + str(size)) + self.begin_tags[tag_name] = \ + '' + self.end_tags[tag_name] = '' + self.fonts_tags.append(tag_name) - start, finish = self.get_active_iters() + start, finish = self.get_active_iters() - for tag in self.fonts_tags: - _buffer.remove_tag_by_name(tag, start, finish) + for tag in self.fonts_tags: + _buffer.remove_tag_by_name(tag, start, finish) - _buffer.apply_tag_by_name(tag_name, start, finish) + _buffer.apply_tag_by_name(tag_name, start, finish) - if weight == pango.WEIGHT_BOLD: - _buffer.apply_tag_by_name('bold', start, finish) - else: - _buffer.remove_tag_by_name('bold', start, finish) + if weight == pango.WEIGHT_BOLD: + _buffer.apply_tag_by_name('bold', start, finish) + else: + _buffer.remove_tag_by_name('bold', start, finish) - if style == pango.STYLE_ITALIC: - _buffer.apply_tag_by_name('italic', start, finish) - else: - _buffer.remove_tag_by_name('italic', start, finish) + if style == pango.STYLE_ITALIC: + _buffer.apply_tag_by_name('italic', start, finish) + else: + _buffer.remove_tag_by_name('italic', start, finish) - def get_xhtml(self): - _buffer = self.get_buffer() - old = _buffer.get_start_iter() - tags = {} - tags['bold'] = False - iter = _buffer.get_start_iter() - old = _buffer.get_start_iter() - text = '' - modified = False + def get_xhtml(self): + _buffer = self.get_buffer() + old = _buffer.get_start_iter() + tags = {} + tags['bold'] = False + iter = _buffer.get_start_iter() + old = _buffer.get_start_iter() + text = '' + modified = False - def xhtml_special(text): - text = text.replace('<', '<') - text = text.replace('>', '>') - text = text.replace('&', '&') - text = text.replace('\n', '
') - return text + def xhtml_special(text): + text = text.replace('<', '<') + text = text.replace('>', '>') + text = text.replace('&', '&') + text = text.replace('\n', '
') + return text - for tag in iter.get_toggled_tags(True): - tag_name = tag.get_property('name') - if tag_name not in self.begin_tags: - continue - text += self.begin_tags[tag_name] - modified = True - while (iter.forward_to_tag_toggle(None) and not iter.is_end()): - text += xhtml_special(_buffer.get_text(old, iter)) - old.forward_to_tag_toggle(None) - new_tags, old_tags, end_tags = [], [], [] - for tag in iter.get_toggled_tags(True): - tag_name = tag.get_property('name') - if tag_name not in self.begin_tags: - continue - new_tags.append(tag_name) - modified = True + for tag in iter.get_toggled_tags(True): + tag_name = tag.get_property('name') + if tag_name not in self.begin_tags: + continue + text += self.begin_tags[tag_name] + modified = True + while (iter.forward_to_tag_toggle(None) and not iter.is_end()): + text += xhtml_special(_buffer.get_text(old, iter)) + old.forward_to_tag_toggle(None) + new_tags, old_tags, end_tags = [], [], [] + for tag in iter.get_toggled_tags(True): + tag_name = tag.get_property('name') + if tag_name not in self.begin_tags: + continue + new_tags.append(tag_name) + modified = True - for tag in iter.get_tags(): - tag_name = tag.get_property('name') - if tag_name not in self.begin_tags or tag_name not in self.end_tags: - continue - if tag_name not in new_tags: - old_tags.append(tag_name) + for tag in iter.get_tags(): + tag_name = tag.get_property('name') + if tag_name not in self.begin_tags or tag_name not in self.end_tags: + continue + if tag_name not in new_tags: + old_tags.append(tag_name) - for tag in iter.get_toggled_tags(False): - tag_name = tag.get_property('name') - if tag_name not in self.end_tags: - continue - end_tags.append(tag_name) + for tag in iter.get_toggled_tags(False): + tag_name = tag.get_property('name') + if tag_name not in self.end_tags: + continue + end_tags.append(tag_name) - for tag in old_tags: - text += self.end_tags[tag] - for tag in end_tags: - text += self.end_tags[tag] - for tag in new_tags: - text += self.begin_tags[tag] - for tag in old_tags: - text += self.begin_tags[tag] + for tag in old_tags: + text += self.end_tags[tag] + for tag in end_tags: + text += self.end_tags[tag] + for tag in new_tags: + text += self.begin_tags[tag] + for tag in old_tags: + text += self.begin_tags[tag] - text += xhtml_special(_buffer.get_text(old, _buffer.get_end_iter())) - for tag in iter.get_toggled_tags(False): - tag_name = tag.get_property('name') - if tag_name not in self.end_tags: - continue - text += self.end_tags[tag_name] + text += xhtml_special(_buffer.get_text(old, _buffer.get_end_iter())) + for tag in iter.get_toggled_tags(False): + tag_name = tag.get_property('name') + if tag_name not in self.end_tags: + continue + text += self.end_tags[tag_name] - if modified: - return '

' + self.make_clickable_urls(text) + '

' - else: - return None + if modified: + return '

' + self.make_clickable_urls(text) + '

' + else: + return None - def destroy(self): - gobject.idle_add(gc.collect) + def destroy(self): + gobject.idle_add(gc.collect) - def clear(self, widget = None): - """ - Clear text in the textview - """ - _buffer = self.get_buffer() - start, end = _buffer.get_bounds() - _buffer.delete(start, end) + def clear(self, widget = None): + """ + Clear text in the textview + """ + _buffer = self.get_buffer() + start, end = _buffer.get_bounds() + _buffer.delete(start, end) - def save_undo(self, text): - self.undo_list.append(text) - if len(self.undo_list) > self.UNDO_LIMIT: - del self.undo_list[0] - self.undo_pressed = False + def save_undo(self, text): + self.undo_list.append(text) + if len(self.undo_list) > self.UNDO_LIMIT: + del self.undo_list[0] + self.undo_pressed = False - def undo(self, widget=None): - """ - Undo text in the textview - """ - _buffer = self.get_buffer() - if self.undo_list: - _buffer.set_text(self.undo_list.pop()) - self.undo_pressed = True + def undo(self, widget=None): + """ + Undo text in the textview + """ + _buffer = self.get_buffer() + if self.undo_list: + _buffer.set_text(self.undo_list.pop()) + self.undo_pressed = True # We register depending on keysym and modifier some bindings # but we also pass those as param so we can construct fake Event @@ -317,50 +317,49 @@ class MessageTextView(gtk.TextView): # CTRL + SHIFT + TAB gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.ISO_Left_Tab, - gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.ISO_Left_Tab, - gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) + gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.ISO_Left_Tab, + gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) # CTRL + TAB gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Tab, - gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Tab, - gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) + gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Tab, + gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) # TAB gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Tab, - 0, 'mykeypress', int, gtk.keysyms.Tab, gtk.gdk.ModifierType, 0) + 0, 'mykeypress', int, gtk.keysyms.Tab, gtk.gdk.ModifierType, 0) # CTRL + UP gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Up, - gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Up, - gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) + gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Up, + gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) # CTRL + DOWN gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Down, - gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Down, - gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) + gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Down, + gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) # ENTER gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Return, - 0, 'mykeypress', int, gtk.keysyms.Return, - gtk.gdk.ModifierType, 0) + 0, 'mykeypress', int, gtk.keysyms.Return, + gtk.gdk.ModifierType, 0) # Ctrl + Enter gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Return, - gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Return, - gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) + gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Return, + gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) # Keypad Enter gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.KP_Enter, - 0, 'mykeypress', int, gtk.keysyms.KP_Enter, - gtk.gdk.ModifierType, 0) + 0, 'mykeypress', int, gtk.keysyms.KP_Enter, + gtk.gdk.ModifierType, 0) # Ctrl + Keypad Enter gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.KP_Enter, - gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.KP_Enter, - gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) + gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.KP_Enter, + gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) # Ctrl + z gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.z, - gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.z, - gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) -# vim: se ts=3: + gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.z, + gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK) diff --git a/src/message_window.py b/src/message_window.py index 82417fd34..22c14c327 100644 --- a/src/message_window.py +++ b/src/message_window.py @@ -42,1167 +42,1165 @@ from common import gajim #################### class MessageWindow(object): - """ - Class for windows which contain message like things; chats, groupchats, etc - """ - - # DND_TARGETS is the targets needed by drag_source_set and drag_dest_set - DND_TARGETS = [('GAJIM_TAB', 0, 81)] - hid = 0 # drag_data_received handler id - ( - CLOSE_TAB_MIDDLE_CLICK, - CLOSE_ESC, - CLOSE_CLOSE_BUTTON, - CLOSE_COMMAND, - CLOSE_CTRL_KEY - ) = range(5) - - def __init__(self, acct, type_, parent_window=None, parent_paned=None): - # A dictionary of dictionaries - # where _contacts[account][jid] == A MessageControl - self._controls = {} - - # If None, the window is not tied to any specific account - self.account = acct - # If None, the window is not tied to any specific type - self.type_ = type_ - # dict { handler id: widget}. Keeps callbacks, which - # lead to cylcular references - self.handlers = {} - # Don't show warning dialogs when we want to delete the window - self.dont_warn_on_delete = False - - self.widget_name = 'message_window' - self.xml = gtkgui_helpers.get_gtk_builder('%s.ui' % self.widget_name) - self.window = self.xml.get_object(self.widget_name) - self.notebook = self.xml.get_object('notebook') - self.parent_paned = None - - if parent_window: - orig_window = self.window - self.window = parent_window - self.parent_paned = parent_paned - self.notebook.reparent(self.parent_paned) - self.parent_paned.pack2(self.notebook, resize=True, shrink=True) - orig_window.destroy() - del orig_window - - # NOTE: we use 'connect_after' here because in - # MessageWindowMgr._new_window we register handler that saves window - # state when closing it, and it should be called before - # MessageWindow._on_window_delete, which manually destroys window - # through win.destroy() - this means no additional handlers for - # 'delete-event' are called. - id_ = self.window.connect_after('delete-event', self._on_window_delete) - self.handlers[id_] = self.window - id_ = self.window.connect('destroy', self._on_window_destroy) - self.handlers[id_] = self.window - id_ = self.window.connect('focus-in-event', self._on_window_focus) - self.handlers[id_] = self.window - - keys=['f', 'g', 'h', 'i', - 'l', 'L', 'n', 'u', - 'b', 'F4', - 'w', 'Page_Up', 'Page_Down', 'Right', - 'Left', 'd', 'c', 'm', 't', 'Escape'] + \ - [''+str(i) for i in xrange(10)] - accel_group = gtk.AccelGroup() - for key in keys: - keyval, mod = gtk.accelerator_parse(key) - accel_group.connect_group(keyval, mod, gtk.ACCEL_VISIBLE, - self.accel_group_func) - self.window.add_accel_group(accel_group) - - # gtk+ doesn't make use of the motion notify on gtkwindow by default - # so this line adds that - self.window.add_events(gtk.gdk.POINTER_MOTION_MASK) - - id_ = self.notebook.connect('switch-page', - self._on_notebook_switch_page) - self.handlers[id_] = self.notebook - id_ = self.notebook.connect('key-press-event', - self._on_notebook_key_press) - self.handlers[id_] = self.notebook - - # Tab customizations - pref_pos = gajim.config.get('tabs_position') - if pref_pos == 'bottom': - nb_pos = gtk.POS_BOTTOM - elif pref_pos == 'left': - nb_pos = gtk.POS_LEFT - elif pref_pos == 'right': - nb_pos = gtk.POS_RIGHT - else: - nb_pos = gtk.POS_TOP - self.notebook.set_tab_pos(nb_pos) - window_mode = gajim.interface.msg_win_mgr.mode - if gajim.config.get('tabs_always_visible') or \ - window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: - self.notebook.set_show_tabs(True) - else: - self.notebook.set_show_tabs(False) - self.notebook.set_show_border(gajim.config.get('tabs_border')) - self.show_icon() - - def change_account_name(self, old_name, new_name): - if old_name in self._controls: - self._controls[new_name] = self._controls[old_name] - del self._controls[old_name] - - for ctrl in self.controls(): - if ctrl.account == old_name: - ctrl.account = new_name - if self.account == old_name: - self.account = new_name - - def change_jid(self, account, old_jid, new_jid): - """ - Called when the full jid of the control is changed - """ - if account not in self._controls: - return - if old_jid not in self._controls[account]: - return - if old_jid == new_jid: - return - self._controls[account][new_jid] = self._controls[account][old_jid] - del self._controls[account][old_jid] - - def get_num_controls(self): - return sum(len(d) for d in self._controls.values()) - - def resize(self, width, height): - gtkgui_helpers.resize_window(self.window, width, height) - - def _on_window_focus(self, widget, event): - # window received focus, so if we had urgency REMOVE IT - # NOTE: we do not have to read the message (it maybe in a bg tab) - # to remove urgency hint so this functions does that - gtkgui_helpers.set_unset_urgency_hint(self.window, False) - - ctrl = self.get_active_control() - if ctrl: - ctrl.set_control_active(True) - # Undo "unread" state display, etc. - if ctrl.type_id == message_control.TYPE_GC: - self.redraw_tab(ctrl, 'active') - else: - # NOTE: we do not send any chatstate to preserve - # inactive, gone, etc. - self.redraw_tab(ctrl) - - def _on_window_delete(self, win, event): - if self.dont_warn_on_delete: - # Destroy the window - return False - - # Number of controls that will be closed and for which we'll loose data: - # chat, pm, gc that won't go in roster - number_of_closed_control = 0 - for ctrl in self.controls(): - if not ctrl.safe_shutdown(): - number_of_closed_control += 1 - - if number_of_closed_control > 1: - def on_yes1(checked): - if checked: - gajim.config.set('confirm_close_multiple_tabs', False) - self.dont_warn_on_delete = True - for ctrl in self.controls(): - if ctrl.minimizable(): - ctrl.minimize() - win.destroy() - - if not gajim.config.get('confirm_close_multiple_tabs'): - # destroy window - return False - dialogs.YesNoDialog( - _('You are going to close several tabs'), - _('Do you really want to close them all?'), - checktext=_('Do _not ask me again'), on_response_yes=on_yes1) - return True - - def on_yes(ctrl): - if self.on_delete_ok == 1: - self.dont_warn_on_delete = True - win.destroy() - self.on_delete_ok -= 1 - - def on_no(ctrl): - return - - def on_minimize(ctrl): - ctrl.minimize() - if self.on_delete_ok == 1: - self.dont_warn_on_delete = True - win.destroy() - self.on_delete_ok -= 1 - - # Make sure all controls are okay with being deleted - self.on_delete_ok = self.get_nb_controls() - for ctrl in self.controls(): - ctrl.allow_shutdown(self.CLOSE_CLOSE_BUTTON, on_yes, on_no, - on_minimize) - return True # halt the delete for the moment - - def _on_window_destroy(self, win): - for ctrl in self.controls(): - ctrl.shutdown() - self._controls.clear() - # Clean up handlers connected to the parent window, this is important since - # self.window may be the RosterWindow - for i in self.handlers.keys(): - if self.handlers[i].handler_is_connected(i): - self.handlers[i].disconnect(i) - del self.handlers[i] - del self.handlers - - def new_tab(self, control): - fjid = control.get_full_jid() - - if control.account not in self._controls: - self._controls[control.account] = {} - - self._controls[control.account][fjid] = control - - if self.get_num_controls() == 2: - # is first conversation_textview scrolled down ? - scrolled = False - first_widget = self.notebook.get_nth_page(0) - ctrl = self._widget_to_control(first_widget) - conv_textview = ctrl.conv_textview - if conv_textview.at_the_end(): - scrolled = True - self.notebook.set_show_tabs(True) - if scrolled: - gobject.idle_add(conv_textview.scroll_to_end_iter) - - # Add notebook page and connect up to the tab's close button - xml = gtkgui_helpers.get_gtk_builder('message_window.ui', 'chat_tab_ebox') - tab_label_box = xml.get_object('chat_tab_ebox') - widget = xml.get_object('tab_close_button') - id_ = widget.connect('clicked', self._on_close_button_clicked, control) - control.handlers[id_] = widget - - id_ = tab_label_box.connect('button-press-event', self.on_tab_eventbox_button_press_event, - control.widget) - control.handlers[id_] = tab_label_box - self.notebook.append_page(control.widget, tab_label_box) - - self.notebook.set_tab_reorderable(control.widget, True) - - self.redraw_tab(control) - if self.parent_paned: - self.notebook.show_all() - else: - self.window.show_all() - # NOTE: we do not call set_control_active(True) since we don't know whether - # the tab is the active one. - self.show_title() - - def on_tab_eventbox_button_press_event(self, widget, event, child): - if event.button == 3: # right click - n = self.notebook.page_num(child) - self.notebook.set_current_page(n) - self.popup_menu(event) - elif event.button == 2: # middle click - ctrl = self._widget_to_control(child) - self.remove_tab(ctrl, self.CLOSE_TAB_MIDDLE_CLICK) - - def _on_message_textview_mykeypress_event(self, widget, event_keyval, - event_keymod): - # NOTE: handles mykeypress which is custom signal; see message_textview.py - - # construct event instance from binding - event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here - event.keyval = event_keyval - event.state = event_keymod - event.time = 0 # assign current time - - if event.state & gtk.gdk.CONTROL_MASK: - # Tab switch bindings - if event.keyval == gtk.keysyms.Tab: # CTRL + TAB - self.move_to_next_unread_tab(True) - elif event.keyval == gtk.keysyms.ISO_Left_Tab: # CTRL + SHIFT + TAB - self.move_to_next_unread_tab(False) - elif event.keyval == gtk.keysyms.Page_Down: # CTRL + PAGE DOWN - self.notebook.emit('key_press_event', event) - elif event.keyval == gtk.keysyms.Page_Up: # CTRL + PAGE UP - self.notebook.emit('key_press_event', event) - - def accel_group_func(self, accel_group, acceleratable, keyval, modifier): - st = '1234567890' # alt+1 means the first tab (tab 0) - control = self.get_active_control() - if not control: - # No more control in this window - return - - # CTRL mask - if modifier & gtk.gdk.CONTROL_MASK: - if keyval == gtk.keysyms.h: # CTRL + h - control._on_history_menuitem_activate() - elif control.type_id == message_control.TYPE_CHAT and \ - keyval == gtk.keysyms.f: # CTRL + f - control._on_send_file_menuitem_activate(None) - elif control.type_id == message_control.TYPE_CHAT and \ - keyval == gtk.keysyms.g: # CTRL + g - control._on_convert_to_gc_menuitem_activate(None) - elif control.type_id in (message_control.TYPE_CHAT, - message_control.TYPE_PM) and keyval == gtk.keysyms.i: # CTRL + i - control._on_contact_information_menuitem_activate(None) - elif keyval == gtk.keysyms.l or keyval == gtk.keysyms.L: # CTRL + l|L - control.conv_textview.clear() - elif keyval == gtk.keysyms.u: # CTRL + u: emacs style clear line - control.clear(control.msg_textview) - elif control.type_id == message_control.TYPE_GC and \ - keyval == gtk.keysyms.b: # CTRL + b - control._on_bookmark_room_menuitem_activate(None) - # Tab switch bindings - elif keyval == gtk.keysyms.F4: # CTRL + F4 - self.remove_tab(control, self.CLOSE_CTRL_KEY) - elif keyval == gtk.keysyms.w: # CTRL + w - # CTRL + w removes latest word before sursor when User uses emacs - # theme - if not gtk.settings_get_default().get_property( - 'gtk-key-theme-name') == 'Emacs': - self.remove_tab(control, self.CLOSE_CTRL_KEY) - elif keyval in (gtk.keysyms.Page_Up, gtk.keysyms.Page_Down): - # CTRL + PageUp | PageDown - # Create event and send it to notebook - event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) - event.window = self.window.window - event.time = int(time.time()) - event.state = gtk.gdk.CONTROL_MASK - event.keyval = int(keyval) - self.notebook.emit('key_press_event', event) - - if modifier & gtk.gdk.SHIFT_MASK: - # CTRL + SHIFT - if control.type_id == message_control.TYPE_GC and \ - keyval == gtk.keysyms.n: # CTRL + SHIFT + n - control._on_change_nick_menuitem_activate(None) - # MOD1 (ALT) mask - elif modifier & gtk.gdk.MOD1_MASK: - # Tab switch bindings - if keyval == gtk.keysyms.Right: # ALT + RIGHT - new = self.notebook.get_current_page() + 1 - if new >= self.notebook.get_n_pages(): - new = 0 - self.notebook.set_current_page(new) - elif keyval == gtk.keysyms.Left: # ALT + LEFT - new = self.notebook.get_current_page() - 1 - if new < 0: - new = self.notebook.get_n_pages() - 1 - self.notebook.set_current_page(new) - elif chr(keyval) in st: # ALT + 1,2,3.. - self.notebook.set_current_page(st.index(chr(keyval))) - elif keyval == gtk.keysyms.c: # ALT + C toggles chat buttons - control.chat_buttons_set_visible(not control.hide_chat_buttons) - elif keyval == gtk.keysyms.m: # ALT + M show emoticons menu - control.show_emoticons_menu() - elif keyval == gtk.keysyms.d: # ALT + D show actions menu - control.on_actions_button_clicked(control.actions_button) - elif control.type_id == message_control.TYPE_GC and \ - keyval == gtk.keysyms.t: # ALT + t - control._on_change_subject_menuitem_activate(None) - # Close tab bindings - elif keyval == gtk.keysyms.Escape and \ - gajim.config.get('escape_key_closes'): # Escape - self.remove_tab(control, self.CLOSE_ESC) - return True - - def _on_close_button_clicked(self, button, control): - """ - When close button is pressed: close a tab - """ - self.remove_tab(control, self.CLOSE_CLOSE_BUTTON) - - def show_icon(self): - window_mode = gajim.interface.msg_win_mgr.mode - icon = None - if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_NEVER: - ctrl = self.get_active_control() - if not ctrl: - return - icon = ctrl.get_tab_image(count_unread=False) - elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS: - pass # keep default icon - elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: - pass # keep default icon - elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERACCT: - pass # keep default icon - elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERTYPE: - if self.type_ == 'gc': - icon = gtkgui_helpers.load_icon('muc_active') - else: - # chat, pm - icon = gtkgui_helpers.load_icon('online') - if icon: - self.window.set_icon(icon.get_pixbuf()) - - def show_title(self, urgent=True, control=None): - """ - Redraw the window's title - """ - if not control: - control = self.get_active_control() - if not control: - # No more control in this window - return - unread = 0 - for ctrl in self.controls(): - if ctrl.type_id == message_control.TYPE_GC and not \ - gajim.config.get('notify_on_all_muc_messages') and not \ - ctrl.attention_flag: - # count only pm messages - unread += ctrl.get_nb_unread_pm() - continue - unread += ctrl.get_nb_unread() - - unread_str = '' - if unread > 1: - unread_str = '[' + unicode(unread) + '] ' - elif unread == 1: - unread_str = '* ' - else: - urgent = False - - if control.type_id == message_control.TYPE_GC: - name = control.room_jid.split('@')[0] - urgent = control.attention_flag - else: - name = control.contact.get_shown_name() - if control.resource: - name += '/' + control.resource - - window_mode = gajim.interface.msg_win_mgr.mode - if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERTYPE: - # Show the plural form since number of tabs > 1 - if self.type_ == 'chat': - label = _('Chats') - elif self.type_ == 'gc': - label = _('Group Chats') - else: - label = _('Private Chats') - elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: - label = None - elif self.get_num_controls() == 1: - label = name - else: - label = _('Messages') - - title = 'Gajim' - if label: - title = '%s - %s' % (label, title) - - if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERACCT: - title = title + ": " + control.account - - self.window.set_title(unread_str + title) - - if urgent: - gtkgui_helpers.set_unset_urgency_hint(self.window, unread) - else: - gtkgui_helpers.set_unset_urgency_hint(self.window, False) - - def set_active_tab(self, ctrl): - ctrl_page = self.notebook.page_num(ctrl.widget) - self.notebook.set_current_page(ctrl_page) - self.window.present() - - def remove_tab(self, ctrl, method, reason = None, force = False): - """ - Reason is only for gc (offline status message) if force is True, do not - ask any confirmation - """ - def close(ctrl): - if reason is not None: # We are leaving gc with a status message - ctrl.shutdown(reason) - else: # We are leaving gc without status message or it's a chat - ctrl.shutdown() - # Update external state - gajim.events.remove_events(ctrl.account, ctrl.get_full_jid, - types = ['printed_msg', 'chat', 'gc_msg']) - - fjid = ctrl.get_full_jid() - jid = gajim.get_jid_without_resource(fjid) - - fctrl = self.get_control(fjid, ctrl.account) - bctrl = self.get_control(jid, ctrl.account) - # keep last_message_time around unless this was our last control with - # that jid - if not fctrl and not bctrl and \ - fjid in gajim.last_message_time[ctrl.account]: - del gajim.last_message_time[ctrl.account][fjid] - - self.notebook.remove_page(self.notebook.page_num(ctrl.widget)) - - del self._controls[ctrl.account][fjid] - - if len(self._controls[ctrl.account]) == 0: - del self._controls[ctrl.account] - - self.check_tabs() - self.show_title() - - def on_yes(ctrl): - close(ctrl) - - def on_no(ctrl): - return - - def on_minimize(ctrl): - if method != self.CLOSE_COMMAND: - ctrl.minimize() - self.check_tabs() - return - close(ctrl) - - # Shutdown the MessageControl - if force: - close(ctrl) - else: - ctrl.allow_shutdown(method, on_yes, on_no, on_minimize) - - def check_tabs(self): - if self.get_num_controls() == 0: - # These are not called when the window is destroyed like this, fake it - gajim.interface.msg_win_mgr._on_window_delete(self.window, None) - gajim.interface.msg_win_mgr._on_window_destroy(self.window) - # dnd clean up - self.notebook.drag_dest_unset() - if self.parent_paned: - # Don't close parent window, just remove the child - child = self.parent_paned.get_child2() - self.parent_paned.remove(child) - else: - self.window.destroy() - return # don't show_title, we are dead - elif self.get_num_controls() == 1: # we are going from two tabs to one - window_mode = gajim.interface.msg_win_mgr.mode - show_tabs_if_one_tab = gajim.config.get('tabs_always_visible') or \ - window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER - self.notebook.set_show_tabs(show_tabs_if_one_tab) - - def redraw_tab(self, ctrl, chatstate = None): - hbox = self.notebook.get_tab_label(ctrl.widget).get_children()[0] - status_img = hbox.get_children()[0] - nick_label = hbox.get_children()[1] - - # Optionally hide close button - close_button = hbox.get_children()[2] - if gajim.config.get('tabs_close_button'): - close_button.show() - else: - close_button.hide() - - # Update nick - nick_label.set_max_width_chars(10) - (tab_label_str, tab_label_color) = ctrl.get_tab_label(chatstate) - nick_label.set_markup(tab_label_str) - if tab_label_color: - nick_label.modify_fg(gtk.STATE_NORMAL, tab_label_color) - nick_label.modify_fg(gtk.STATE_ACTIVE, tab_label_color) - - tab_img = ctrl.get_tab_image() - if tab_img: - if tab_img.get_storage_type() == gtk.IMAGE_ANIMATION: - status_img.set_from_animation(tab_img.get_animation()) - else: - status_img.set_from_pixbuf(tab_img.get_pixbuf()) - - self.show_icon() - - def repaint_themed_widgets(self): - """ - Repaint controls in the window with theme color - """ - # iterate through controls and repaint - for ctrl in self.controls(): - ctrl.repaint_themed_widgets() - - def _widget_to_control(self, widget): - for ctrl in self.controls(): - if ctrl.widget == widget: - return ctrl - return None - - def get_active_control(self): - notebook = self.notebook - active_widget = notebook.get_nth_page(notebook.get_current_page()) - return self._widget_to_control(active_widget) - - def get_active_contact(self): - ctrl = self.get_active_control() - if ctrl: - return ctrl.contact - return None - - def get_active_jid(self): - contact = self.get_active_contact() - if contact: - return contact.jid - return None - - def is_active(self): - return self.window.is_active() - - def get_origin(self): - return self.window.window.get_origin() - - def get_control(self, key, acct): - """ - Return the MessageControl for jid or n, where n is a notebook page index. - When key is an int index acct may be None - """ - if isinstance(key, str): - key = unicode(key, 'utf-8') - - if isinstance(key, unicode): - jid = key - try: - return self._controls[acct][jid] - except Exception: - return None - else: - page_num = key - notebook = self.notebook - if page_num is None: - page_num = notebook.get_current_page() - nth_child = notebook.get_nth_page(page_num) - return self._widget_to_control(nth_child) - - def has_control(self, jid, acct): - return (acct in self._controls and jid in self._controls[acct]) - - def change_key(self, old_jid, new_jid, acct): - """ - Change the JID key of a control - """ - try: - # Check if controls exists - ctrl = self._controls[acct][old_jid] - except KeyError: - return - - self._controls[acct][new_jid] = ctrl - del self._controls[acct][old_jid] - - if old_jid in gajim.last_message_time[acct]: - gajim.last_message_time[acct][new_jid] = \ - gajim.last_message_time[acct][old_jid] - del gajim.last_message_time[acct][old_jid] - - def controls(self): - for jid_dict in self._controls.values(): - for ctrl in jid_dict.values(): - yield ctrl - - def get_nb_controls(self): - return sum(len(jid_dict) for jid_dict in self._controls.values()) - - def move_to_next_unread_tab(self, forward): - ind = self.notebook.get_current_page() - current = ind - found = False - first_composing_ind = -1 # id of first composing ctrl to switch to - # if no others controls have awaiting events - # loop until finding an unread tab or having done a complete cycle - while True: - if forward == True: # look for the first unread tab on the right - ind = ind + 1 - if ind >= self.notebook.get_n_pages(): - ind = 0 - else: # look for the first unread tab on the right - ind = ind - 1 - if ind < 0: - ind = self.notebook.get_n_pages() - 1 - ctrl = self.get_control(ind, None) - if ctrl.get_nb_unread() > 0: - found = True - break # found - elif gajim.config.get('ctrl_tab_go_to_next_composing') : # Search for a composing contact - contact = ctrl.contact - if first_composing_ind == -1 and contact.chatstate == 'composing': - # If no composing contact found yet, check if this one is composing - first_composing_ind = ind - if ind == current: - break # a complete cycle without finding an unread tab - if found: - self.notebook.set_current_page(ind) - elif first_composing_ind != -1: - self.notebook.set_current_page(first_composing_ind) - else: # not found and nobody composing - if forward: # CTRL + TAB - if current < (self.notebook.get_n_pages() - 1): - self.notebook.next_page() - else: # traverse for ever (eg. don't stop at last tab) - self.notebook.set_current_page(0) - else: # CTRL + SHIFT + TAB - if current > 0: - self.notebook.prev_page() - else: # traverse for ever (eg. don't stop at first tab) - self.notebook.set_current_page( - self.notebook.get_n_pages() - 1) - - def popup_menu(self, event): - menu = self.get_active_control().prepare_context_menu() - # show the menu - menu.popup(None, None, None, event.button, event.time) - menu.show_all() - - def _on_notebook_switch_page(self, notebook, page, page_num): - old_no = notebook.get_current_page() - if old_no >= 0: - old_ctrl = self._widget_to_control(notebook.get_nth_page(old_no)) - old_ctrl.set_control_active(False) - - new_ctrl = self._widget_to_control(notebook.get_nth_page(page_num)) - new_ctrl.set_control_active(True) - self.show_title(control = new_ctrl) - - control = self.get_active_control() - if isinstance(control, ChatControlBase): - control.msg_textview.grab_focus() - - def _on_notebook_key_press(self, widget, event): - # when tab itself is selected, make sure <- and -> are allowed for navigating between tabs - if event.keyval in (gtk.keysyms.Left, gtk.keysyms.Right): - return False - - control = self.get_active_control() - - if event.state & gtk.gdk.SHIFT_MASK: - # CTRL + SHIFT + TAB - if event.state & gtk.gdk.CONTROL_MASK and \ - event.keyval == gtk.keysyms.ISO_Left_Tab: - self.move_to_next_unread_tab(False) - return True - # SHIFT + PAGE_[UP|DOWN]: send to conv_textview - elif event.keyval in (gtk.keysyms.Page_Down, gtk.keysyms.Page_Up): - control.conv_textview.tv.emit('key_press_event', event) - return True - elif event.state & gtk.gdk.CONTROL_MASK: - if event.keyval == gtk.keysyms.Tab: # CTRL + TAB - self.move_to_next_unread_tab(True) - return True - # Ctrl+PageUP / DOWN has to be handled by notebook - elif event.keyval == gtk.keysyms.Page_Down: - self.move_to_next_unread_tab(True) - return True - elif event.keyval == gtk.keysyms.Page_Up: - self.move_to_next_unread_tab(False) - return True - if event.keyval in (gtk.keysyms.Shift_L, gtk.keysyms.Shift_R, - gtk.keysyms.Control_L, gtk.keysyms.Control_R, gtk.keysyms.Caps_Lock, - gtk.keysyms.Shift_Lock, gtk.keysyms.Meta_L, gtk.keysyms.Meta_R, - gtk.keysyms.Alt_L, gtk.keysyms.Alt_R, gtk.keysyms.Super_L, - gtk.keysyms.Super_R, gtk.keysyms.Hyper_L, gtk.keysyms.Hyper_R): - return True - - if isinstance(control, ChatControlBase): - # we forwarded it to message textview - control.msg_textview.emit('key_press_event', event) - control.msg_textview.grab_focus() - - def get_tab_at_xy(self, x, y): - """ - Return the tab under xy and if its nearer from left or right side of the - tab - """ - page_num = -1 - to_right = False - horiz = self.notebook.get_tab_pos() == gtk.POS_TOP or \ - self.notebook.get_tab_pos() == gtk.POS_BOTTOM - for i in xrange(self.notebook.get_n_pages()): - page = self.notebook.get_nth_page(i) - tab = self.notebook.get_tab_label(page) - tab_alloc = tab.get_allocation() - if horiz: - if (x >= tab_alloc.x) and \ - (x <= (tab_alloc.x + tab_alloc.width)): - page_num = i - if x >= tab_alloc.x + (tab_alloc.width / 2.0): - to_right = True - break - else: - if (y >= tab_alloc.y) and \ - (y <= (tab_alloc.y + tab_alloc.height)): - page_num = i - - if y > tab_alloc.y + (tab_alloc.height / 2.0): - to_right = True - break - return (page_num, to_right) - - def find_page_num_according_to_tab_label(self, tab_label): - """ - Find the page num of the tab label - """ - page_num = -1 - for i in xrange(self.notebook.get_n_pages()): - page = self.notebook.get_nth_page(i) - tab = self.notebook.get_tab_label(page) - if tab == tab_label: - page_num = i - break - return page_num + """ + Class for windows which contain message like things; chats, groupchats, etc + """ + + # DND_TARGETS is the targets needed by drag_source_set and drag_dest_set + DND_TARGETS = [('GAJIM_TAB', 0, 81)] + hid = 0 # drag_data_received handler id + ( + CLOSE_TAB_MIDDLE_CLICK, + CLOSE_ESC, + CLOSE_CLOSE_BUTTON, + CLOSE_COMMAND, + CLOSE_CTRL_KEY + ) = range(5) + + def __init__(self, acct, type_, parent_window=None, parent_paned=None): + # A dictionary of dictionaries + # where _contacts[account][jid] == A MessageControl + self._controls = {} + + # If None, the window is not tied to any specific account + self.account = acct + # If None, the window is not tied to any specific type + self.type_ = type_ + # dict { handler id: widget}. Keeps callbacks, which + # lead to cylcular references + self.handlers = {} + # Don't show warning dialogs when we want to delete the window + self.dont_warn_on_delete = False + + self.widget_name = 'message_window' + self.xml = gtkgui_helpers.get_gtk_builder('%s.ui' % self.widget_name) + self.window = self.xml.get_object(self.widget_name) + self.notebook = self.xml.get_object('notebook') + self.parent_paned = None + + if parent_window: + orig_window = self.window + self.window = parent_window + self.parent_paned = parent_paned + self.notebook.reparent(self.parent_paned) + self.parent_paned.pack2(self.notebook, resize=True, shrink=True) + orig_window.destroy() + del orig_window + + # NOTE: we use 'connect_after' here because in + # MessageWindowMgr._new_window we register handler that saves window + # state when closing it, and it should be called before + # MessageWindow._on_window_delete, which manually destroys window + # through win.destroy() - this means no additional handlers for + # 'delete-event' are called. + id_ = self.window.connect_after('delete-event', self._on_window_delete) + self.handlers[id_] = self.window + id_ = self.window.connect('destroy', self._on_window_destroy) + self.handlers[id_] = self.window + id_ = self.window.connect('focus-in-event', self._on_window_focus) + self.handlers[id_] = self.window + + keys=['f', 'g', 'h', 'i', + 'l', 'L', 'n', 'u', + 'b', 'F4', + 'w', 'Page_Up', 'Page_Down', 'Right', + 'Left', 'd', 'c', 'm', 't', 'Escape'] + \ + [''+str(i) for i in xrange(10)] + accel_group = gtk.AccelGroup() + for key in keys: + keyval, mod = gtk.accelerator_parse(key) + accel_group.connect_group(keyval, mod, gtk.ACCEL_VISIBLE, + self.accel_group_func) + self.window.add_accel_group(accel_group) + + # gtk+ doesn't make use of the motion notify on gtkwindow by default + # so this line adds that + self.window.add_events(gtk.gdk.POINTER_MOTION_MASK) + + id_ = self.notebook.connect('switch-page', + self._on_notebook_switch_page) + self.handlers[id_] = self.notebook + id_ = self.notebook.connect('key-press-event', + self._on_notebook_key_press) + self.handlers[id_] = self.notebook + + # Tab customizations + pref_pos = gajim.config.get('tabs_position') + if pref_pos == 'bottom': + nb_pos = gtk.POS_BOTTOM + elif pref_pos == 'left': + nb_pos = gtk.POS_LEFT + elif pref_pos == 'right': + nb_pos = gtk.POS_RIGHT + else: + nb_pos = gtk.POS_TOP + self.notebook.set_tab_pos(nb_pos) + window_mode = gajim.interface.msg_win_mgr.mode + if gajim.config.get('tabs_always_visible') or \ + window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: + self.notebook.set_show_tabs(True) + else: + self.notebook.set_show_tabs(False) + self.notebook.set_show_border(gajim.config.get('tabs_border')) + self.show_icon() + + def change_account_name(self, old_name, new_name): + if old_name in self._controls: + self._controls[new_name] = self._controls[old_name] + del self._controls[old_name] + + for ctrl in self.controls(): + if ctrl.account == old_name: + ctrl.account = new_name + if self.account == old_name: + self.account = new_name + + def change_jid(self, account, old_jid, new_jid): + """ + Called when the full jid of the control is changed + """ + if account not in self._controls: + return + if old_jid not in self._controls[account]: + return + if old_jid == new_jid: + return + self._controls[account][new_jid] = self._controls[account][old_jid] + del self._controls[account][old_jid] + + def get_num_controls(self): + return sum(len(d) for d in self._controls.values()) + + def resize(self, width, height): + gtkgui_helpers.resize_window(self.window, width, height) + + def _on_window_focus(self, widget, event): + # window received focus, so if we had urgency REMOVE IT + # NOTE: we do not have to read the message (it maybe in a bg tab) + # to remove urgency hint so this functions does that + gtkgui_helpers.set_unset_urgency_hint(self.window, False) + + ctrl = self.get_active_control() + if ctrl: + ctrl.set_control_active(True) + # Undo "unread" state display, etc. + if ctrl.type_id == message_control.TYPE_GC: + self.redraw_tab(ctrl, 'active') + else: + # NOTE: we do not send any chatstate to preserve + # inactive, gone, etc. + self.redraw_tab(ctrl) + + def _on_window_delete(self, win, event): + if self.dont_warn_on_delete: + # Destroy the window + return False + + # Number of controls that will be closed and for which we'll loose data: + # chat, pm, gc that won't go in roster + number_of_closed_control = 0 + for ctrl in self.controls(): + if not ctrl.safe_shutdown(): + number_of_closed_control += 1 + + if number_of_closed_control > 1: + def on_yes1(checked): + if checked: + gajim.config.set('confirm_close_multiple_tabs', False) + self.dont_warn_on_delete = True + for ctrl in self.controls(): + if ctrl.minimizable(): + ctrl.minimize() + win.destroy() + + if not gajim.config.get('confirm_close_multiple_tabs'): + # destroy window + return False + dialogs.YesNoDialog( + _('You are going to close several tabs'), +_('Do you really want to close them all?'), + checktext=_('Do _not ask me again'), on_response_yes=on_yes1) + return True + + def on_yes(ctrl): + if self.on_delete_ok == 1: + self.dont_warn_on_delete = True + win.destroy() + self.on_delete_ok -= 1 + + def on_no(ctrl): + return + + def on_minimize(ctrl): + ctrl.minimize() + if self.on_delete_ok == 1: + self.dont_warn_on_delete = True + win.destroy() + self.on_delete_ok -= 1 + + # Make sure all controls are okay with being deleted + self.on_delete_ok = self.get_nb_controls() + for ctrl in self.controls(): + ctrl.allow_shutdown(self.CLOSE_CLOSE_BUTTON, on_yes, on_no, + on_minimize) + return True # halt the delete for the moment + + def _on_window_destroy(self, win): + for ctrl in self.controls(): + ctrl.shutdown() + self._controls.clear() + # Clean up handlers connected to the parent window, this is important since + # self.window may be the RosterWindow + for i in self.handlers.keys(): + if self.handlers[i].handler_is_connected(i): + self.handlers[i].disconnect(i) + del self.handlers[i] + del self.handlers + + def new_tab(self, control): + fjid = control.get_full_jid() + + if control.account not in self._controls: + self._controls[control.account] = {} + + self._controls[control.account][fjid] = control + + if self.get_num_controls() == 2: + # is first conversation_textview scrolled down ? + scrolled = False + first_widget = self.notebook.get_nth_page(0) + ctrl = self._widget_to_control(first_widget) + conv_textview = ctrl.conv_textview + if conv_textview.at_the_end(): + scrolled = True + self.notebook.set_show_tabs(True) + if scrolled: + gobject.idle_add(conv_textview.scroll_to_end_iter) + + # Add notebook page and connect up to the tab's close button + xml = gtkgui_helpers.get_gtk_builder('message_window.ui', 'chat_tab_ebox') + tab_label_box = xml.get_object('chat_tab_ebox') + widget = xml.get_object('tab_close_button') + id_ = widget.connect('clicked', self._on_close_button_clicked, control) + control.handlers[id_] = widget + + id_ = tab_label_box.connect('button-press-event', self.on_tab_eventbox_button_press_event, + control.widget) + control.handlers[id_] = tab_label_box + self.notebook.append_page(control.widget, tab_label_box) + + self.notebook.set_tab_reorderable(control.widget, True) + + self.redraw_tab(control) + if self.parent_paned: + self.notebook.show_all() + else: + self.window.show_all() + # NOTE: we do not call set_control_active(True) since we don't know whether + # the tab is the active one. + self.show_title() + + def on_tab_eventbox_button_press_event(self, widget, event, child): + if event.button == 3: # right click + n = self.notebook.page_num(child) + self.notebook.set_current_page(n) + self.popup_menu(event) + elif event.button == 2: # middle click + ctrl = self._widget_to_control(child) + self.remove_tab(ctrl, self.CLOSE_TAB_MIDDLE_CLICK) + + def _on_message_textview_mykeypress_event(self, widget, event_keyval, + event_keymod): + # NOTE: handles mykeypress which is custom signal; see message_textview.py + + # construct event instance from binding + event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here + event.keyval = event_keyval + event.state = event_keymod + event.time = 0 # assign current time + + if event.state & gtk.gdk.CONTROL_MASK: + # Tab switch bindings + if event.keyval == gtk.keysyms.Tab: # CTRL + TAB + self.move_to_next_unread_tab(True) + elif event.keyval == gtk.keysyms.ISO_Left_Tab: # CTRL + SHIFT + TAB + self.move_to_next_unread_tab(False) + elif event.keyval == gtk.keysyms.Page_Down: # CTRL + PAGE DOWN + self.notebook.emit('key_press_event', event) + elif event.keyval == gtk.keysyms.Page_Up: # CTRL + PAGE UP + self.notebook.emit('key_press_event', event) + + def accel_group_func(self, accel_group, acceleratable, keyval, modifier): + st = '1234567890' # alt+1 means the first tab (tab 0) + control = self.get_active_control() + if not control: + # No more control in this window + return + + # CTRL mask + if modifier & gtk.gdk.CONTROL_MASK: + if keyval == gtk.keysyms.h: # CTRL + h + control._on_history_menuitem_activate() + elif control.type_id == message_control.TYPE_CHAT and \ + keyval == gtk.keysyms.f: # CTRL + f + control._on_send_file_menuitem_activate(None) + elif control.type_id == message_control.TYPE_CHAT and \ + keyval == gtk.keysyms.g: # CTRL + g + control._on_convert_to_gc_menuitem_activate(None) + elif control.type_id in (message_control.TYPE_CHAT, + message_control.TYPE_PM) and keyval == gtk.keysyms.i: # CTRL + i + control._on_contact_information_menuitem_activate(None) + elif keyval == gtk.keysyms.l or keyval == gtk.keysyms.L: # CTRL + l|L + control.conv_textview.clear() + elif keyval == gtk.keysyms.u: # CTRL + u: emacs style clear line + control.clear(control.msg_textview) + elif control.type_id == message_control.TYPE_GC and \ + keyval == gtk.keysyms.b: # CTRL + b + control._on_bookmark_room_menuitem_activate(None) + # Tab switch bindings + elif keyval == gtk.keysyms.F4: # CTRL + F4 + self.remove_tab(control, self.CLOSE_CTRL_KEY) + elif keyval == gtk.keysyms.w: # CTRL + w + # CTRL + w removes latest word before sursor when User uses emacs + # theme + if not gtk.settings_get_default().get_property( + 'gtk-key-theme-name') == 'Emacs': + self.remove_tab(control, self.CLOSE_CTRL_KEY) + elif keyval in (gtk.keysyms.Page_Up, gtk.keysyms.Page_Down): + # CTRL + PageUp | PageDown + # Create event and send it to notebook + event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) + event.window = self.window.window + event.time = int(time.time()) + event.state = gtk.gdk.CONTROL_MASK + event.keyval = int(keyval) + self.notebook.emit('key_press_event', event) + + if modifier & gtk.gdk.SHIFT_MASK: + # CTRL + SHIFT + if control.type_id == message_control.TYPE_GC and \ + keyval == gtk.keysyms.n: # CTRL + SHIFT + n + control._on_change_nick_menuitem_activate(None) + # MOD1 (ALT) mask + elif modifier & gtk.gdk.MOD1_MASK: + # Tab switch bindings + if keyval == gtk.keysyms.Right: # ALT + RIGHT + new = self.notebook.get_current_page() + 1 + if new >= self.notebook.get_n_pages(): + new = 0 + self.notebook.set_current_page(new) + elif keyval == gtk.keysyms.Left: # ALT + LEFT + new = self.notebook.get_current_page() - 1 + if new < 0: + new = self.notebook.get_n_pages() - 1 + self.notebook.set_current_page(new) + elif chr(keyval) in st: # ALT + 1,2,3.. + self.notebook.set_current_page(st.index(chr(keyval))) + elif keyval == gtk.keysyms.c: # ALT + C toggles chat buttons + control.chat_buttons_set_visible(not control.hide_chat_buttons) + elif keyval == gtk.keysyms.m: # ALT + M show emoticons menu + control.show_emoticons_menu() + elif keyval == gtk.keysyms.d: # ALT + D show actions menu + control.on_actions_button_clicked(control.actions_button) + elif control.type_id == message_control.TYPE_GC and \ + keyval == gtk.keysyms.t: # ALT + t + control._on_change_subject_menuitem_activate(None) + # Close tab bindings + elif keyval == gtk.keysyms.Escape and \ + gajim.config.get('escape_key_closes'): # Escape + self.remove_tab(control, self.CLOSE_ESC) + return True + + def _on_close_button_clicked(self, button, control): + """ + When close button is pressed: close a tab + """ + self.remove_tab(control, self.CLOSE_CLOSE_BUTTON) + + def show_icon(self): + window_mode = gajim.interface.msg_win_mgr.mode + icon = None + if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_NEVER: + ctrl = self.get_active_control() + if not ctrl: + return + icon = ctrl.get_tab_image(count_unread=False) + elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS: + pass # keep default icon + elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: + pass # keep default icon + elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERACCT: + pass # keep default icon + elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERTYPE: + if self.type_ == 'gc': + icon = gtkgui_helpers.load_icon('muc_active') + else: + # chat, pm + icon = gtkgui_helpers.load_icon('online') + if icon: + self.window.set_icon(icon.get_pixbuf()) + + def show_title(self, urgent=True, control=None): + """ + Redraw the window's title + """ + if not control: + control = self.get_active_control() + if not control: + # No more control in this window + return + unread = 0 + for ctrl in self.controls(): + if ctrl.type_id == message_control.TYPE_GC and not \ + gajim.config.get('notify_on_all_muc_messages') and not \ + ctrl.attention_flag: + # count only pm messages + unread += ctrl.get_nb_unread_pm() + continue + unread += ctrl.get_nb_unread() + + unread_str = '' + if unread > 1: + unread_str = '[' + unicode(unread) + '] ' + elif unread == 1: + unread_str = '* ' + else: + urgent = False + + if control.type_id == message_control.TYPE_GC: + name = control.room_jid.split('@')[0] + urgent = control.attention_flag + else: + name = control.contact.get_shown_name() + if control.resource: + name += '/' + control.resource + + window_mode = gajim.interface.msg_win_mgr.mode + if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERTYPE: + # Show the plural form since number of tabs > 1 + if self.type_ == 'chat': + label = _('Chats') + elif self.type_ == 'gc': + label = _('Group Chats') + else: + label = _('Private Chats') + elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: + label = None + elif self.get_num_controls() == 1: + label = name + else: + label = _('Messages') + + title = 'Gajim' + if label: + title = '%s - %s' % (label, title) + + if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERACCT: + title = title + ": " + control.account + + self.window.set_title(unread_str + title) + + if urgent: + gtkgui_helpers.set_unset_urgency_hint(self.window, unread) + else: + gtkgui_helpers.set_unset_urgency_hint(self.window, False) + + def set_active_tab(self, ctrl): + ctrl_page = self.notebook.page_num(ctrl.widget) + self.notebook.set_current_page(ctrl_page) + self.window.present() + + def remove_tab(self, ctrl, method, reason = None, force = False): + """ + Reason is only for gc (offline status message) if force is True, do not + ask any confirmation + """ + def close(ctrl): + if reason is not None: # We are leaving gc with a status message + ctrl.shutdown(reason) + else: # We are leaving gc without status message or it's a chat + ctrl.shutdown() + # Update external state + gajim.events.remove_events(ctrl.account, ctrl.get_full_jid, + types = ['printed_msg', 'chat', 'gc_msg']) + + fjid = ctrl.get_full_jid() + jid = gajim.get_jid_without_resource(fjid) + + fctrl = self.get_control(fjid, ctrl.account) + bctrl = self.get_control(jid, ctrl.account) + # keep last_message_time around unless this was our last control with + # that jid + if not fctrl and not bctrl and \ + fjid in gajim.last_message_time[ctrl.account]: + del gajim.last_message_time[ctrl.account][fjid] + + self.notebook.remove_page(self.notebook.page_num(ctrl.widget)) + + del self._controls[ctrl.account][fjid] + + if len(self._controls[ctrl.account]) == 0: + del self._controls[ctrl.account] + + self.check_tabs() + self.show_title() + + def on_yes(ctrl): + close(ctrl) + + def on_no(ctrl): + return + + def on_minimize(ctrl): + if method != self.CLOSE_COMMAND: + ctrl.minimize() + self.check_tabs() + return + close(ctrl) + + # Shutdown the MessageControl + if force: + close(ctrl) + else: + ctrl.allow_shutdown(method, on_yes, on_no, on_minimize) + + def check_tabs(self): + if self.get_num_controls() == 0: + # These are not called when the window is destroyed like this, fake it + gajim.interface.msg_win_mgr._on_window_delete(self.window, None) + gajim.interface.msg_win_mgr._on_window_destroy(self.window) + # dnd clean up + self.notebook.drag_dest_unset() + if self.parent_paned: + # Don't close parent window, just remove the child + child = self.parent_paned.get_child2() + self.parent_paned.remove(child) + else: + self.window.destroy() + return # don't show_title, we are dead + elif self.get_num_controls() == 1: # we are going from two tabs to one + window_mode = gajim.interface.msg_win_mgr.mode + show_tabs_if_one_tab = gajim.config.get('tabs_always_visible') or \ + window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER + self.notebook.set_show_tabs(show_tabs_if_one_tab) + + def redraw_tab(self, ctrl, chatstate = None): + hbox = self.notebook.get_tab_label(ctrl.widget).get_children()[0] + status_img = hbox.get_children()[0] + nick_label = hbox.get_children()[1] + + # Optionally hide close button + close_button = hbox.get_children()[2] + if gajim.config.get('tabs_close_button'): + close_button.show() + else: + close_button.hide() + + # Update nick + nick_label.set_max_width_chars(10) + (tab_label_str, tab_label_color) = ctrl.get_tab_label(chatstate) + nick_label.set_markup(tab_label_str) + if tab_label_color: + nick_label.modify_fg(gtk.STATE_NORMAL, tab_label_color) + nick_label.modify_fg(gtk.STATE_ACTIVE, tab_label_color) + + tab_img = ctrl.get_tab_image() + if tab_img: + if tab_img.get_storage_type() == gtk.IMAGE_ANIMATION: + status_img.set_from_animation(tab_img.get_animation()) + else: + status_img.set_from_pixbuf(tab_img.get_pixbuf()) + + self.show_icon() + + def repaint_themed_widgets(self): + """ + Repaint controls in the window with theme color + """ + # iterate through controls and repaint + for ctrl in self.controls(): + ctrl.repaint_themed_widgets() + + def _widget_to_control(self, widget): + for ctrl in self.controls(): + if ctrl.widget == widget: + return ctrl + return None + + def get_active_control(self): + notebook = self.notebook + active_widget = notebook.get_nth_page(notebook.get_current_page()) + return self._widget_to_control(active_widget) + + def get_active_contact(self): + ctrl = self.get_active_control() + if ctrl: + return ctrl.contact + return None + + def get_active_jid(self): + contact = self.get_active_contact() + if contact: + return contact.jid + return None + + def is_active(self): + return self.window.is_active() + + def get_origin(self): + return self.window.window.get_origin() + + def get_control(self, key, acct): + """ + Return the MessageControl for jid or n, where n is a notebook page index. + When key is an int index acct may be None + """ + if isinstance(key, str): + key = unicode(key, 'utf-8') + + if isinstance(key, unicode): + jid = key + try: + return self._controls[acct][jid] + except Exception: + return None + else: + page_num = key + notebook = self.notebook + if page_num is None: + page_num = notebook.get_current_page() + nth_child = notebook.get_nth_page(page_num) + return self._widget_to_control(nth_child) + + def has_control(self, jid, acct): + return (acct in self._controls and jid in self._controls[acct]) + + def change_key(self, old_jid, new_jid, acct): + """ + Change the JID key of a control + """ + try: + # Check if controls exists + ctrl = self._controls[acct][old_jid] + except KeyError: + return + + self._controls[acct][new_jid] = ctrl + del self._controls[acct][old_jid] + + if old_jid in gajim.last_message_time[acct]: + gajim.last_message_time[acct][new_jid] = \ + gajim.last_message_time[acct][old_jid] + del gajim.last_message_time[acct][old_jid] + + def controls(self): + for jid_dict in self._controls.values(): + for ctrl in jid_dict.values(): + yield ctrl + + def get_nb_controls(self): + return sum(len(jid_dict) for jid_dict in self._controls.values()) + + def move_to_next_unread_tab(self, forward): + ind = self.notebook.get_current_page() + current = ind + found = False + first_composing_ind = -1 # id of first composing ctrl to switch to + # if no others controls have awaiting events + # loop until finding an unread tab or having done a complete cycle + while True: + if forward == True: # look for the first unread tab on the right + ind = ind + 1 + if ind >= self.notebook.get_n_pages(): + ind = 0 + else: # look for the first unread tab on the right + ind = ind - 1 + if ind < 0: + ind = self.notebook.get_n_pages() - 1 + ctrl = self.get_control(ind, None) + if ctrl.get_nb_unread() > 0: + found = True + break # found + elif gajim.config.get('ctrl_tab_go_to_next_composing') : # Search for a composing contact + contact = ctrl.contact + if first_composing_ind == -1 and contact.chatstate == 'composing': + # If no composing contact found yet, check if this one is composing + first_composing_ind = ind + if ind == current: + break # a complete cycle without finding an unread tab + if found: + self.notebook.set_current_page(ind) + elif first_composing_ind != -1: + self.notebook.set_current_page(first_composing_ind) + else: # not found and nobody composing + if forward: # CTRL + TAB + if current < (self.notebook.get_n_pages() - 1): + self.notebook.next_page() + else: # traverse for ever (eg. don't stop at last tab) + self.notebook.set_current_page(0) + else: # CTRL + SHIFT + TAB + if current > 0: + self.notebook.prev_page() + else: # traverse for ever (eg. don't stop at first tab) + self.notebook.set_current_page( + self.notebook.get_n_pages() - 1) + + def popup_menu(self, event): + menu = self.get_active_control().prepare_context_menu() + # show the menu + menu.popup(None, None, None, event.button, event.time) + menu.show_all() + + def _on_notebook_switch_page(self, notebook, page, page_num): + old_no = notebook.get_current_page() + if old_no >= 0: + old_ctrl = self._widget_to_control(notebook.get_nth_page(old_no)) + old_ctrl.set_control_active(False) + + new_ctrl = self._widget_to_control(notebook.get_nth_page(page_num)) + new_ctrl.set_control_active(True) + self.show_title(control = new_ctrl) + + control = self.get_active_control() + if isinstance(control, ChatControlBase): + control.msg_textview.grab_focus() + + def _on_notebook_key_press(self, widget, event): + # when tab itself is selected, make sure <- and -> are allowed for navigating between tabs + if event.keyval in (gtk.keysyms.Left, gtk.keysyms.Right): + return False + + control = self.get_active_control() + + if event.state & gtk.gdk.SHIFT_MASK: + # CTRL + SHIFT + TAB + if event.state & gtk.gdk.CONTROL_MASK and \ + event.keyval == gtk.keysyms.ISO_Left_Tab: + self.move_to_next_unread_tab(False) + return True + # SHIFT + PAGE_[UP|DOWN]: send to conv_textview + elif event.keyval in (gtk.keysyms.Page_Down, gtk.keysyms.Page_Up): + control.conv_textview.tv.emit('key_press_event', event) + return True + elif event.state & gtk.gdk.CONTROL_MASK: + if event.keyval == gtk.keysyms.Tab: # CTRL + TAB + self.move_to_next_unread_tab(True) + return True + # Ctrl+PageUP / DOWN has to be handled by notebook + elif event.keyval == gtk.keysyms.Page_Down: + self.move_to_next_unread_tab(True) + return True + elif event.keyval == gtk.keysyms.Page_Up: + self.move_to_next_unread_tab(False) + return True + if event.keyval in (gtk.keysyms.Shift_L, gtk.keysyms.Shift_R, + gtk.keysyms.Control_L, gtk.keysyms.Control_R, gtk.keysyms.Caps_Lock, + gtk.keysyms.Shift_Lock, gtk.keysyms.Meta_L, gtk.keysyms.Meta_R, + gtk.keysyms.Alt_L, gtk.keysyms.Alt_R, gtk.keysyms.Super_L, + gtk.keysyms.Super_R, gtk.keysyms.Hyper_L, gtk.keysyms.Hyper_R): + return True + + if isinstance(control, ChatControlBase): + # we forwarded it to message textview + control.msg_textview.emit('key_press_event', event) + control.msg_textview.grab_focus() + + def get_tab_at_xy(self, x, y): + """ + Return the tab under xy and if its nearer from left or right side of the + tab + """ + page_num = -1 + to_right = False + horiz = self.notebook.get_tab_pos() == gtk.POS_TOP or \ + self.notebook.get_tab_pos() == gtk.POS_BOTTOM + for i in xrange(self.notebook.get_n_pages()): + page = self.notebook.get_nth_page(i) + tab = self.notebook.get_tab_label(page) + tab_alloc = tab.get_allocation() + if horiz: + if (x >= tab_alloc.x) and \ + (x <= (tab_alloc.x + tab_alloc.width)): + page_num = i + if x >= tab_alloc.x + (tab_alloc.width / 2.0): + to_right = True + break + else: + if (y >= tab_alloc.y) and \ + (y <= (tab_alloc.y + tab_alloc.height)): + page_num = i + + if y > tab_alloc.y + (tab_alloc.height / 2.0): + to_right = True + break + return (page_num, to_right) + + def find_page_num_according_to_tab_label(self, tab_label): + """ + Find the page num of the tab label + """ + page_num = -1 + for i in xrange(self.notebook.get_n_pages()): + page = self.notebook.get_nth_page(i) + tab = self.notebook.get_tab_label(page) + if tab == tab_label: + page_num = i + break + return page_num ################################################################################ class MessageWindowMgr(gobject.GObject): - """ - A manager and factory for MessageWindow objects - """ + """ + A manager and factory for MessageWindow objects + """ - __gsignals__ = { - 'window-delete': (gobject.SIGNAL_RUN_LAST, None, (object,)), - } + __gsignals__ = { + 'window-delete': (gobject.SIGNAL_RUN_LAST, None, (object,)), + } - # These constants map to common.config.opt_one_window_types indices - ( - ONE_MSG_WINDOW_NEVER, - ONE_MSG_WINDOW_ALWAYS, - ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER, - ONE_MSG_WINDOW_PERACCT, - ONE_MSG_WINDOW_PERTYPE, - ) = range(5) - # A key constant for the main window in ONE_MSG_WINDOW_ALWAYS mode - MAIN_WIN = 'main' - # A key constant for the main window in ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER mode - ROSTER_MAIN_WIN = 'roster' + # These constants map to common.config.opt_one_window_types indices + ( + ONE_MSG_WINDOW_NEVER, + ONE_MSG_WINDOW_ALWAYS, + ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER, + ONE_MSG_WINDOW_PERACCT, + ONE_MSG_WINDOW_PERTYPE, + ) = range(5) + # A key constant for the main window in ONE_MSG_WINDOW_ALWAYS mode + MAIN_WIN = 'main' + # A key constant for the main window in ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER mode + ROSTER_MAIN_WIN = 'roster' - def __init__(self, parent_window, parent_paned): - """ - A dictionary of windows; the key depends on the config: - ONE_MSG_WINDOW_NEVER: The key is the contact JID - ONE_MSG_WINDOW_ALWAYS: The key is MessageWindowMgr.MAIN_WIN - ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: The key is MessageWindowMgr.MAIN_WIN - ONE_MSG_WINDOW_PERACCT: The key is the account name - ONE_MSG_WINDOW_PERTYPE: The key is a message type constant - """ - gobject.GObject.__init__(self) - self._windows = {} + def __init__(self, parent_window, parent_paned): + """ + A dictionary of windows; the key depends on the config: + ONE_MSG_WINDOW_NEVER: The key is the contact JID + ONE_MSG_WINDOW_ALWAYS: The key is MessageWindowMgr.MAIN_WIN + ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: The key is MessageWindowMgr.MAIN_WIN + ONE_MSG_WINDOW_PERACCT: The key is the account name + ONE_MSG_WINDOW_PERTYPE: The key is a message type constant + """ + gobject.GObject.__init__(self) + self._windows = {} - # Map the mode to a int constant for frequent compares - mode = gajim.config.get('one_message_window') - self.mode = common.config.opt_one_window_types.index(mode) + # Map the mode to a int constant for frequent compares + mode = gajim.config.get('one_message_window') + self.mode = common.config.opt_one_window_types.index(mode) - self.parent_win = parent_window - self.parent_paned = parent_paned + self.parent_win = parent_window + self.parent_paned = parent_paned - def change_account_name(self, old_name, new_name): - for win in self.windows(): - win.change_account_name(old_name, new_name) + def change_account_name(self, old_name, new_name): + for win in self.windows(): + win.change_account_name(old_name, new_name) - def _new_window(self, acct, type_): - parent_win = None - parent_paned = None - if self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: - parent_win = self.parent_win - parent_paned = self.parent_paned - win = MessageWindow(acct, type_, parent_win, parent_paned) - # we track the lifetime of this window - win.window.connect('delete-event', self._on_window_delete) - win.window.connect('destroy', self._on_window_destroy) - return win + def _new_window(self, acct, type_): + parent_win = None + parent_paned = None + if self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: + parent_win = self.parent_win + parent_paned = self.parent_paned + win = MessageWindow(acct, type_, parent_win, parent_paned) + # we track the lifetime of this window + win.window.connect('delete-event', self._on_window_delete) + win.window.connect('destroy', self._on_window_destroy) + return win - def _gtk_win_to_msg_win(self, gtk_win): - for w in self.windows(): - if w.window == gtk_win: - return w - return None + def _gtk_win_to_msg_win(self, gtk_win): + for w in self.windows(): + if w.window == gtk_win: + return w + return None - def get_window(self, jid, acct): - for win in self.windows(): - if win.has_control(jid, acct): - return win + def get_window(self, jid, acct): + for win in self.windows(): + if win.has_control(jid, acct): + return win - return None + return None - def has_window(self, jid, acct): - return self.get_window(jid, acct) is not None + def has_window(self, jid, acct): + return self.get_window(jid, acct) is not None - def one_window_opened(self, contact=None, acct=None, type_=None): - try: - return \ - self._windows[self._mode_to_key(contact, acct, type_)] is not None - except KeyError: - return False + def one_window_opened(self, contact=None, acct=None, type_=None): + try: + return \ + self._windows[self._mode_to_key(contact, acct, type_)] is not None + except KeyError: + return False - def _resize_window(self, win, acct, type_): - """ - Resizes window according to config settings - """ - if self.mode in (self.ONE_MSG_WINDOW_ALWAYS, - self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER): - size = (gajim.config.get('msgwin-width'), - gajim.config.get('msgwin-height')) - if self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: - parent_size = win.window.get_size() - # Need to add the size of the now visible paned handle, otherwise - # the saved width of the message window decreases by this amount - handle_size = win.parent_paned.style_get_property('handle-size') - size = (parent_size[0] + size[0] + handle_size, size[1]) - elif self.mode == self.ONE_MSG_WINDOW_PERACCT: - size = (gajim.config.get_per('accounts', acct, 'msgwin-width'), - gajim.config.get_per('accounts', acct, 'msgwin-height')) - elif self.mode in (self.ONE_MSG_WINDOW_NEVER, self.ONE_MSG_WINDOW_PERTYPE): - if type_ == message_control.TYPE_PM: - type_ = message_control.TYPE_CHAT - opt_width = type_ + '-msgwin-width' - opt_height = type_ + '-msgwin-height' - size = (gajim.config.get(opt_width), gajim.config.get(opt_height)) - else: - return - win.resize(size[0], size[1]) - if win.parent_paned: - win.parent_paned.set_position(parent_size[0]) + def _resize_window(self, win, acct, type_): + """ + Resizes window according to config settings + """ + if self.mode in (self.ONE_MSG_WINDOW_ALWAYS, + self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER): + size = (gajim.config.get('msgwin-width'), + gajim.config.get('msgwin-height')) + if self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: + parent_size = win.window.get_size() + # Need to add the size of the now visible paned handle, otherwise + # the saved width of the message window decreases by this amount + handle_size = win.parent_paned.style_get_property('handle-size') + size = (parent_size[0] + size[0] + handle_size, size[1]) + elif self.mode == self.ONE_MSG_WINDOW_PERACCT: + size = (gajim.config.get_per('accounts', acct, 'msgwin-width'), + gajim.config.get_per('accounts', acct, 'msgwin-height')) + elif self.mode in (self.ONE_MSG_WINDOW_NEVER, self.ONE_MSG_WINDOW_PERTYPE): + if type_ == message_control.TYPE_PM: + type_ = message_control.TYPE_CHAT + opt_width = type_ + '-msgwin-width' + opt_height = type_ + '-msgwin-height' + size = (gajim.config.get(opt_width), gajim.config.get(opt_height)) + else: + return + win.resize(size[0], size[1]) + if win.parent_paned: + win.parent_paned.set_position(parent_size[0]) - def _position_window(self, win, acct, type_): - """ - Moves window according to config settings - """ - if (self.mode in [self.ONE_MSG_WINDOW_NEVER, - self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER]): - return + def _position_window(self, win, acct, type_): + """ + Moves window according to config settings + """ + if (self.mode in [self.ONE_MSG_WINDOW_NEVER, + self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER]): + return - if self.mode == self.ONE_MSG_WINDOW_ALWAYS: - pos = (gajim.config.get('msgwin-x-position'), - gajim.config.get('msgwin-y-position')) - elif self.mode == self.ONE_MSG_WINDOW_PERACCT: - pos = (gajim.config.get_per('accounts', acct, 'msgwin-x-position'), - gajim.config.get_per('accounts', acct, 'msgwin-y-position')) - elif self.mode == self.ONE_MSG_WINDOW_PERTYPE: - pos = (gajim.config.get(type_ + '-msgwin-x-position'), - gajim.config.get(type_ + '-msgwin-y-position')) - else: - return + if self.mode == self.ONE_MSG_WINDOW_ALWAYS: + pos = (gajim.config.get('msgwin-x-position'), + gajim.config.get('msgwin-y-position')) + elif self.mode == self.ONE_MSG_WINDOW_PERACCT: + pos = (gajim.config.get_per('accounts', acct, 'msgwin-x-position'), + gajim.config.get_per('accounts', acct, 'msgwin-y-position')) + elif self.mode == self.ONE_MSG_WINDOW_PERTYPE: + pos = (gajim.config.get(type_ + '-msgwin-x-position'), + gajim.config.get(type_ + '-msgwin-y-position')) + else: + return - gtkgui_helpers.move_window(win.window, pos[0], pos[1]) + gtkgui_helpers.move_window(win.window, pos[0], pos[1]) - def _mode_to_key(self, contact, acct, type_, resource = None): - if self.mode == self.ONE_MSG_WINDOW_NEVER: - key = acct + contact.jid - if resource: - key += '/' + resource - return key - elif self.mode == self.ONE_MSG_WINDOW_ALWAYS: - return self.MAIN_WIN - elif self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: - return self.ROSTER_MAIN_WIN - elif self.mode == self.ONE_MSG_WINDOW_PERACCT: - return acct - elif self.mode == self.ONE_MSG_WINDOW_PERTYPE: - return type_ + def _mode_to_key(self, contact, acct, type_, resource = None): + if self.mode == self.ONE_MSG_WINDOW_NEVER: + key = acct + contact.jid + if resource: + key += '/' + resource + return key + elif self.mode == self.ONE_MSG_WINDOW_ALWAYS: + return self.MAIN_WIN + elif self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: + return self.ROSTER_MAIN_WIN + elif self.mode == self.ONE_MSG_WINDOW_PERACCT: + return acct + elif self.mode == self.ONE_MSG_WINDOW_PERTYPE: + return type_ - def create_window(self, contact, acct, type_, resource = None): - win_acct = None - win_type = None - win_role = None # X11 window role + def create_window(self, contact, acct, type_, resource = None): + win_acct = None + win_type = None + win_role = None # X11 window role - win_key = self._mode_to_key(contact, acct, type_, resource) - if self.mode == self.ONE_MSG_WINDOW_PERACCT: - win_acct = acct - win_role = acct - elif self.mode == self.ONE_MSG_WINDOW_PERTYPE: - win_type = type_ - win_role = type_ - elif self.mode == self.ONE_MSG_WINDOW_NEVER: - win_type = type_ - win_role = contact.jid - elif self.mode == self.ONE_MSG_WINDOW_ALWAYS: - win_role = 'messages' + win_key = self._mode_to_key(contact, acct, type_, resource) + if self.mode == self.ONE_MSG_WINDOW_PERACCT: + win_acct = acct + win_role = acct + elif self.mode == self.ONE_MSG_WINDOW_PERTYPE: + win_type = type_ + win_role = type_ + elif self.mode == self.ONE_MSG_WINDOW_NEVER: + win_type = type_ + win_role = contact.jid + elif self.mode == self.ONE_MSG_WINDOW_ALWAYS: + win_role = 'messages' - win = None - try: - win = self._windows[win_key] - except KeyError: - win = self._new_window(win_acct, win_type) + win = None + try: + win = self._windows[win_key] + except KeyError: + win = self._new_window(win_acct, win_type) - if win_role: - win.window.set_role(win_role) + if win_role: + win.window.set_role(win_role) - # Position and size window based on saved state and window mode - if not self.one_window_opened(contact, acct, type_): - if gajim.config.get('msgwin-max-state'): - win.window.maximize() - else: - self._resize_window(win, acct, type_) - self._position_window(win, acct, type_) + # Position and size window based on saved state and window mode + if not self.one_window_opened(contact, acct, type_): + if gajim.config.get('msgwin-max-state'): + win.window.maximize() + else: + self._resize_window(win, acct, type_) + self._position_window(win, acct, type_) - self._windows[win_key] = win - return win + self._windows[win_key] = win + return win - def change_key(self, old_jid, new_jid, acct): - win = self.get_window(old_jid, acct) - if self.mode == self.ONE_MSG_WINDOW_NEVER: - old_key = acct + old_jid - if old_jid not in self._windows: - return - new_key = acct + new_jid - self._windows[new_key] = self._windows[old_key] - del self._windows[old_key] - win.change_key(old_jid, new_jid, acct) + def change_key(self, old_jid, new_jid, acct): + win = self.get_window(old_jid, acct) + if self.mode == self.ONE_MSG_WINDOW_NEVER: + old_key = acct + old_jid + if old_jid not in self._windows: + return + new_key = acct + new_jid + self._windows[new_key] = self._windows[old_key] + del self._windows[old_key] + win.change_key(old_jid, new_jid, acct) - def _on_window_delete(self, win, event): - self.save_state(self._gtk_win_to_msg_win(win)) - gajim.interface.save_config() - return False + def _on_window_delete(self, win, event): + self.save_state(self._gtk_win_to_msg_win(win)) + gajim.interface.save_config() + return False - def _on_window_destroy(self, win): - for k in self._windows.keys(): - if self._windows[k].window == win: - self.emit('window-delete', self._windows[k]) - del self._windows[k] - return + def _on_window_destroy(self, win): + for k in self._windows.keys(): + if self._windows[k].window == win: + self.emit('window-delete', self._windows[k]) + del self._windows[k] + return - def get_control(self, jid, acct): - """ - Amongst all windows, return the MessageControl for jid - """ - win = self.get_window(jid, acct) - if win: - return win.get_control(jid, acct) - return None + def get_control(self, jid, acct): + """ + Amongst all windows, return the MessageControl for jid + """ + win = self.get_window(jid, acct) + if win: + return win.get_control(jid, acct) + return None - def get_gc_control(self, jid, acct): - """ - Same as get_control. Was briefly required, is not any more. May be useful - some day in the future? - """ - ctrl = self.get_control(jid, acct) - if ctrl and ctrl.type_id == message_control.TYPE_GC: - return ctrl - return None + def get_gc_control(self, jid, acct): + """ + Same as get_control. Was briefly required, is not any more. May be useful + some day in the future? + """ + ctrl = self.get_control(jid, acct) + if ctrl and ctrl.type_id == message_control.TYPE_GC: + return ctrl + return None - def get_controls(self, type_=None, acct=None): - ctrls = [] - for c in self.controls(): - if acct and c.account != acct: - continue - if not type_ or c.type_id == type_: - ctrls.append(c) - return ctrls + def get_controls(self, type_=None, acct=None): + ctrls = [] + for c in self.controls(): + if acct and c.account != acct: + continue + if not type_ or c.type_id == type_: + ctrls.append(c) + return ctrls - def windows(self): - for w in self._windows.values(): - yield w + def windows(self): + for w in self._windows.values(): + yield w - def controls(self): - for w in self._windows.values(): - for c in w.controls(): - yield c + def controls(self): + for w in self._windows.values(): + for c in w.controls(): + yield c - def shutdown(self, width_adjust=0): - for w in self.windows(): - self.save_state(w, width_adjust) - if not w.parent_paned: - w.window.hide() - w.window.destroy() + def shutdown(self, width_adjust=0): + for w in self.windows(): + self.save_state(w, width_adjust) + if not w.parent_paned: + w.window.hide() + w.window.destroy() - gajim.interface.save_config() + gajim.interface.save_config() - def save_state(self, msg_win, width_adjust=0): - # Save window size and position - max_win_key = 'msgwin-max-state' - pos_x_key = 'msgwin-x-position' - pos_y_key = 'msgwin-y-position' - size_width_key = 'msgwin-width' - size_height_key = 'msgwin-height' + def save_state(self, msg_win, width_adjust=0): + # Save window size and position + max_win_key = 'msgwin-max-state' + pos_x_key = 'msgwin-x-position' + pos_y_key = 'msgwin-y-position' + size_width_key = 'msgwin-width' + size_height_key = 'msgwin-height' - acct = None - x, y = msg_win.window.get_position() - width, height = msg_win.window.get_size() + acct = None + x, y = msg_win.window.get_position() + width, height = msg_win.window.get_size() - # If any of these values seem bogus don't update. - if x < 0 or y < 0 or width < 0 or height < 0: - return + # If any of these values seem bogus don't update. + if x < 0 or y < 0 or width < 0 or height < 0: + return - elif self.mode == self.ONE_MSG_WINDOW_PERACCT: - acct = msg_win.account - elif self.mode == self.ONE_MSG_WINDOW_PERTYPE: - type_ = msg_win.type_ - pos_x_key = type_ + '-msgwin-x-position' - pos_y_key = type_ + '-msgwin-y-position' - size_width_key = type_ + '-msgwin-width' - size_height_key = type_ + '-msgwin-height' - elif self.mode == self.ONE_MSG_WINDOW_NEVER: - type_ = msg_win.type_ - size_width_key = type_ + '-msgwin-width' - size_height_key = type_ + '-msgwin-height' - elif self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: - # Ignore any hpaned width - width = msg_win.notebook.allocation.width + elif self.mode == self.ONE_MSG_WINDOW_PERACCT: + acct = msg_win.account + elif self.mode == self.ONE_MSG_WINDOW_PERTYPE: + type_ = msg_win.type_ + pos_x_key = type_ + '-msgwin-x-position' + pos_y_key = type_ + '-msgwin-y-position' + size_width_key = type_ + '-msgwin-width' + size_height_key = type_ + '-msgwin-height' + elif self.mode == self.ONE_MSG_WINDOW_NEVER: + type_ = msg_win.type_ + size_width_key = type_ + '-msgwin-width' + size_height_key = type_ + '-msgwin-height' + elif self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: + # Ignore any hpaned width + width = msg_win.notebook.allocation.width - if acct: - gajim.config.set_per('accounts', acct, size_width_key, width) - gajim.config.set_per('accounts', acct, size_height_key, height) + if acct: + gajim.config.set_per('accounts', acct, size_width_key, width) + gajim.config.set_per('accounts', acct, size_height_key, height) - if self.mode != self.ONE_MSG_WINDOW_NEVER: - gajim.config.set_per('accounts', acct, pos_x_key, x) - gajim.config.set_per('accounts', acct, pos_y_key, y) + if self.mode != self.ONE_MSG_WINDOW_NEVER: + gajim.config.set_per('accounts', acct, pos_x_key, x) + gajim.config.set_per('accounts', acct, pos_y_key, y) - else: - win_maximized = msg_win.window.window.get_state() == \ - gtk.gdk.WINDOW_STATE_MAXIMIZED - gajim.config.set(max_win_key, win_maximized) - width += width_adjust - gajim.config.set(size_width_key, width) - gajim.config.set(size_height_key, height) + else: + win_maximized = msg_win.window.window.get_state() == \ + gtk.gdk.WINDOW_STATE_MAXIMIZED + gajim.config.set(max_win_key, win_maximized) + width += width_adjust + gajim.config.set(size_width_key, width) + gajim.config.set(size_height_key, height) - if self.mode != self.ONE_MSG_WINDOW_NEVER: - gajim.config.set(pos_x_key, x) - gajim.config.set(pos_y_key, y) + if self.mode != self.ONE_MSG_WINDOW_NEVER: + gajim.config.set(pos_x_key, x) + gajim.config.set(pos_y_key, y) - def reconfig(self): - for w in self.windows(): - self.save_state(w) - gajim.interface.save_config() - mode = gajim.config.get('one_message_window') - if self.mode == common.config.opt_one_window_types.index(mode): - # No change - return - self.mode = common.config.opt_one_window_types.index(mode) + def reconfig(self): + for w in self.windows(): + self.save_state(w) + gajim.interface.save_config() + mode = gajim.config.get('one_message_window') + if self.mode == common.config.opt_one_window_types.index(mode): + # No change + return + self.mode = common.config.opt_one_window_types.index(mode) - controls = [] - for w in self.windows(): - # Note, we are taking care not to hide/delete the roster window when the - # MessageWindow is embedded. - if not w.parent_paned: - w.window.hide() - else: - # Stash current size so it can be restored if the MessageWindow - # is not longer embedded - roster_width = w.parent_paned.get_child1().allocation.width - gajim.config.set('roster_width', roster_width) + controls = [] + for w in self.windows(): + # Note, we are taking care not to hide/delete the roster window when the + # MessageWindow is embedded. + if not w.parent_paned: + w.window.hide() + else: + # Stash current size so it can be restored if the MessageWindow + # is not longer embedded + roster_width = w.parent_paned.get_child1().allocation.width + gajim.config.set('roster_width', roster_width) - while w.notebook.get_n_pages(): - page = w.notebook.get_nth_page(0) - ctrl = w._widget_to_control(page) - w.notebook.remove_page(0) - page.unparent() - controls.append(ctrl) + while w.notebook.get_n_pages(): + page = w.notebook.get_nth_page(0) + ctrl = w._widget_to_control(page) + w.notebook.remove_page(0) + page.unparent() + controls.append(ctrl) - # Must clear _controls to prevent MessageControl.shutdown calls - w._controls = {} - if not w.parent_paned: - w.window.destroy() - else: - # Don't close parent window, just remove the child - child = w.parent_paned.get_child2() - w.parent_paned.remove(child) - gtkgui_helpers.resize_window(w.window, - gajim.config.get('roster_width'), - gajim.config.get('roster_height')) + # Must clear _controls to prevent MessageControl.shutdown calls + w._controls = {} + if not w.parent_paned: + w.window.destroy() + else: + # Don't close parent window, just remove the child + child = w.parent_paned.get_child2() + w.parent_paned.remove(child) + gtkgui_helpers.resize_window(w.window, + gajim.config.get('roster_width'), + gajim.config.get('roster_height')) - self._windows = {} + self._windows = {} - for ctrl in controls: - mw = self.get_window(ctrl.contact.jid, ctrl.account) - if not mw: - mw = self.create_window(ctrl.contact, ctrl.account, - ctrl.type_id) - ctrl.parent_win = mw - mw.new_tab(ctrl) - -# vim: se ts=3: + for ctrl in controls: + mw = self.get_window(ctrl.contact.jid, ctrl.account) + if not mw: + mw = self.create_window(ctrl.contact, ctrl.account, + ctrl.type_id) + ctrl.parent_win = mw + mw.new_tab(ctrl) diff --git a/src/music_track_listener.py b/src/music_track_listener.py index 07105a947..9de01dd02 100644 --- a/src/music_track_listener.py +++ b/src/music_track_listener.py @@ -25,282 +25,280 @@ import gobject if __name__ == '__main__': - # install _() func before importing dbus_support - from common import i18n + # install _() func before importing dbus_support + from common import i18n from common import dbus_support if dbus_support.supported: - import dbus - import dbus.glib + import dbus + import dbus.glib class MusicTrackInfo(object): - __slots__ = ['title', 'album', 'artist', 'duration', 'track_number', - 'paused'] + __slots__ = ['title', 'album', 'artist', 'duration', 'track_number', + 'paused'] class MusicTrackListener(gobject.GObject): - __gsignals__ = { - 'music-track-changed': (gobject.SIGNAL_RUN_LAST, None, (object,)), - } + __gsignals__ = { + 'music-track-changed': (gobject.SIGNAL_RUN_LAST, None, (object,)), + } - _instance = None - @classmethod - def get(cls): - if cls._instance is None: - cls._instance = cls() - return cls._instance + _instance = None + @classmethod + def get(cls): + if cls._instance is None: + cls._instance = cls() + return cls._instance - def __init__(self): - super(MusicTrackListener, self).__init__() - self._last_playing_music = None + def __init__(self): + super(MusicTrackListener, self).__init__() + self._last_playing_music = None - bus = dbus.SessionBus() + bus = dbus.SessionBus() - ## MPRIS - bus.add_signal_receiver(self._mpris_music_track_change_cb, 'TrackChange', - 'org.freedesktop.MediaPlayer') - bus.add_signal_receiver(self._mpris_playing_changed_cb, 'StatusChange', - 'org.freedesktop.MediaPlayer') - bus.add_signal_receiver(self._player_name_owner_changed, - 'NameOwnerChanged', 'org.freedesktop.DBus', - arg0='org.freedesktop.MediaPlayer') + ## MPRIS + bus.add_signal_receiver(self._mpris_music_track_change_cb, 'TrackChange', + 'org.freedesktop.MediaPlayer') + bus.add_signal_receiver(self._mpris_playing_changed_cb, 'StatusChange', + 'org.freedesktop.MediaPlayer') + bus.add_signal_receiver(self._player_name_owner_changed, + 'NameOwnerChanged', 'org.freedesktop.DBus', + arg0='org.freedesktop.MediaPlayer') - ## Muine - bus.add_signal_receiver(self._muine_music_track_change_cb, 'SongChanged', - 'org.gnome.Muine.Player') - bus.add_signal_receiver(self._player_name_owner_changed, - 'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Muine') - bus.add_signal_receiver(self._player_playing_changed_cb, 'StateChanged', - 'org.gnome.Muine.Player') + ## Muine + bus.add_signal_receiver(self._muine_music_track_change_cb, 'SongChanged', + 'org.gnome.Muine.Player') + bus.add_signal_receiver(self._player_name_owner_changed, + 'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Muine') + bus.add_signal_receiver(self._player_playing_changed_cb, 'StateChanged', + 'org.gnome.Muine.Player') - ## Rhythmbox - bus.add_signal_receiver(self._player_name_owner_changed, - 'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Rhythmbox') - bus.add_signal_receiver(self._rhythmbox_playing_changed_cb, - 'playingChanged', 'org.gnome.Rhythmbox.Player') - bus.add_signal_receiver(self._player_playing_song_property_changed_cb, - 'playingSongPropertyChanged', 'org.gnome.Rhythmbox.Player') + ## Rhythmbox + bus.add_signal_receiver(self._player_name_owner_changed, + 'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Rhythmbox') + bus.add_signal_receiver(self._rhythmbox_playing_changed_cb, + 'playingChanged', 'org.gnome.Rhythmbox.Player') + bus.add_signal_receiver(self._player_playing_song_property_changed_cb, + 'playingSongPropertyChanged', 'org.gnome.Rhythmbox.Player') - ## Banshee - bus.add_signal_receiver(self._banshee_state_changed_cb, - 'StateChanged', 'org.bansheeproject.Banshee.PlayerEngine') - bus.add_signal_receiver(self._player_name_owner_changed, - 'NameOwnerChanged', 'org.freedesktop.DBus', - arg0='org.bansheeproject.Banshee') + ## Banshee + bus.add_signal_receiver(self._banshee_state_changed_cb, + 'StateChanged', 'org.bansheeproject.Banshee.PlayerEngine') + bus.add_signal_receiver(self._player_name_owner_changed, + 'NameOwnerChanged', 'org.freedesktop.DBus', + arg0='org.bansheeproject.Banshee') - ## Quod Libet - bus.add_signal_receiver(self._quodlibet_state_change_cb, - 'SongStarted', 'net.sacredchao.QuodLibet') - bus.add_signal_receiver(self._quodlibet_state_change_cb, - 'Paused', 'net.sacredchao.QuodLibet') - bus.add_signal_receiver(self._quodlibet_state_change_cb, - 'Unpaused', 'net.sacredchao.QuodLibet') - bus.add_signal_receiver(self._player_name_owner_changed, - 'NameOwnerChanged', 'org.freedesktop.DBus', - arg0='net.sacredchao.QuodLibet') + ## Quod Libet + bus.add_signal_receiver(self._quodlibet_state_change_cb, + 'SongStarted', 'net.sacredchao.QuodLibet') + bus.add_signal_receiver(self._quodlibet_state_change_cb, + 'Paused', 'net.sacredchao.QuodLibet') + bus.add_signal_receiver(self._quodlibet_state_change_cb, + 'Unpaused', 'net.sacredchao.QuodLibet') + bus.add_signal_receiver(self._player_name_owner_changed, + 'NameOwnerChanged', 'org.freedesktop.DBus', + arg0='net.sacredchao.QuodLibet') - def _player_name_owner_changed(self, name, old, new): - if not new: - self.emit('music-track-changed', None) + def _player_name_owner_changed(self, name, old, new): + if not new: + self.emit('music-track-changed', None) - def _player_playing_changed_cb(self, playing): - if playing: - self.emit('music-track-changed', self._last_playing_music) - else: - self.emit('music-track-changed', None) + def _player_playing_changed_cb(self, playing): + if playing: + self.emit('music-track-changed', self._last_playing_music) + else: + self.emit('music-track-changed', None) - def _player_playing_song_property_changed_cb(self, a, b, c, d): - if b == 'rb:stream-song-title': - self.emit('music-track-changed', self._last_playing_music) + def _player_playing_song_property_changed_cb(self, a, b, c, d): + if b == 'rb:stream-song-title': + self.emit('music-track-changed', self._last_playing_music) - def _mpris_properties_extract(self, song): - info = MusicTrackInfo() - info.title = song.get('title', '') - info.album = song.get('album', '') - info.artist = song.get('artist', '') - info.duration = int(song.get('length', 0)) - return info + def _mpris_properties_extract(self, song): + info = MusicTrackInfo() + info.title = song.get('title', '') + info.album = song.get('album', '') + info.artist = song.get('artist', '') + info.duration = int(song.get('length', 0)) + return info - def _mpris_playing_changed_cb(self, playing): - if type(playing) is dbus.Struct: - if playing[0]: - self.emit('music-track-changed', None) - else: - self.emit('music-track-changed', self._last_playing_music) - else: # Workaround for e.g. Audacious - if playing: - self.emit('music-track-changed', None) - else: - self.emit('music-track-changed', self._last_playing_music) + def _mpris_playing_changed_cb(self, playing): + if type(playing) is dbus.Struct: + if playing[0]: + self.emit('music-track-changed', None) + else: + self.emit('music-track-changed', self._last_playing_music) + else: # Workaround for e.g. Audacious + if playing: + self.emit('music-track-changed', None) + else: + self.emit('music-track-changed', self._last_playing_music) - def _mpris_music_track_change_cb(self, arg): - self._last_playing_music = self._mpris_properties_extract(arg) - self.emit('music-track-changed', self._last_playing_music) + def _mpris_music_track_change_cb(self, arg): + self._last_playing_music = self._mpris_properties_extract(arg) + self.emit('music-track-changed', self._last_playing_music) - def _muine_properties_extract(self, song_string): - d = dict((x.strip() for x in s1.split(':', 1)) for s1 in \ - song_string.split('\n')) - info = MusicTrackInfo() - info.title = d['title'] - info.album = d['album'] - info.artist = d['artist'] - info.duration = int(d['duration']) - info.track_number = int(d['track_number']) - return info + def _muine_properties_extract(self, song_string): + d = dict((x.strip() for x in s1.split(':', 1)) for s1 in \ + song_string.split('\n')) + info = MusicTrackInfo() + info.title = d['title'] + info.album = d['album'] + info.artist = d['artist'] + info.duration = int(d['duration']) + info.track_number = int(d['track_number']) + return info - def _muine_music_track_change_cb(self, arg): - info = self._muine_properties_extract(arg) - self.emit('music-track-changed', info) + def _muine_music_track_change_cb(self, arg): + info = self._muine_properties_extract(arg) + self.emit('music-track-changed', info) - def _rhythmbox_playing_changed_cb(self, playing): - if playing: - info = self.get_playing_track() - self.emit('music-track-changed', info) - else: - self.emit('music-track-changed', None) + def _rhythmbox_playing_changed_cb(self, playing): + if playing: + info = self.get_playing_track() + self.emit('music-track-changed', info) + else: + self.emit('music-track-changed', None) - def _rhythmbox_properties_extract(self, props): - info = MusicTrackInfo() - info.title = props.get('title', None) - info.album = props.get('album', None) - info.artist = props.get('artist', None) - info.duration = int(props.get('duration', 0)) - info.track_number = int(props.get('track-number', 0)) - return info + def _rhythmbox_properties_extract(self, props): + info = MusicTrackInfo() + info.title = props.get('title', None) + info.album = props.get('album', None) + info.artist = props.get('artist', None) + info.duration = int(props.get('duration', 0)) + info.track_number = int(props.get('track-number', 0)) + return info - def _banshee_state_changed_cb(self, state): - if state == 'playing': - bus = dbus.SessionBus() - banshee = bus.get_object('org.bansheeproject.Banshee', - '/org/bansheeproject/Banshee/PlayerEngine') - currentTrack = banshee.GetCurrentTrack() - self._last_playing_music = self._banshee_properties_extract( - currentTrack) - self.emit('music-track-changed', self._last_playing_music) - elif state == 'paused': - self.emit('music-track-changed', None) + def _banshee_state_changed_cb(self, state): + if state == 'playing': + bus = dbus.SessionBus() + banshee = bus.get_object('org.bansheeproject.Banshee', + '/org/bansheeproject/Banshee/PlayerEngine') + currentTrack = banshee.GetCurrentTrack() + self._last_playing_music = self._banshee_properties_extract( + currentTrack) + self.emit('music-track-changed', self._last_playing_music) + elif state == 'paused': + self.emit('music-track-changed', None) - def _banshee_properties_extract(self, props): - info = MusicTrackInfo() - info.title = props.get('name', None) - info.album = props.get('album', None) - info.artist = props.get('artist', None) - info.duration = int(props.get('length', 0)) - return info + def _banshee_properties_extract(self, props): + info = MusicTrackInfo() + info.title = props.get('name', None) + info.album = props.get('album', None) + info.artist = props.get('artist', None) + info.duration = int(props.get('length', 0)) + return info - def _quodlibet_state_change_cb(self, state=None): - info = self.get_playing_track() - if info: - self.emit('music-track-changed', info) - else: - self.emit('music-track-changed', None) + def _quodlibet_state_change_cb(self, state=None): + info = self.get_playing_track() + if info: + self.emit('music-track-changed', info) + else: + self.emit('music-track-changed', None) - def _quodlibet_properties_extract(self, props): - info = MusicTrackInfo() - info.title = props.get('title', None) - info.album = props.get('album', None) - info.artist = props.get('artist', None) - info.duration = int(props.get('~#length', 0)) - return info + def _quodlibet_properties_extract(self, props): + info = MusicTrackInfo() + info.title = props.get('title', None) + info.album = props.get('album', None) + info.artist = props.get('artist', None) + info.duration = int(props.get('~#length', 0)) + return info - def get_playing_track(self): - '''Return a MusicTrackInfo for the currently playing - song, or None if no song is playing''' + def get_playing_track(self): + '''Return a MusicTrackInfo for the currently playing + song, or None if no song is playing''' - bus = dbus.SessionBus() + bus = dbus.SessionBus() - ## Check Muine playing track - test = False - if hasattr(bus, 'name_has_owner'): - if bus.name_has_owner('org.gnome.Muine'): - test = True - elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(), - 'org.gnome.Muine'): - test = True - if test: - obj = bus.get_object('org.gnome.Muine', '/org/gnome/Muine/Player') - player = dbus.Interface(obj, 'org.gnome.Muine.Player') - if player.GetPlaying(): - song_string = player.GetCurrentSong() - song = self._muine_properties_extract(song_string) - self._last_playing_music = song - return song + ## Check Muine playing track + test = False + if hasattr(bus, 'name_has_owner'): + if bus.name_has_owner('org.gnome.Muine'): + test = True + elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(), + 'org.gnome.Muine'): + test = True + if test: + obj = bus.get_object('org.gnome.Muine', '/org/gnome/Muine/Player') + player = dbus.Interface(obj, 'org.gnome.Muine.Player') + if player.GetPlaying(): + song_string = player.GetCurrentSong() + song = self._muine_properties_extract(song_string) + self._last_playing_music = song + return song - ## Check Rhythmbox playing song - test = False - if hasattr(bus, 'name_has_owner'): - if bus.name_has_owner('org.gnome.Rhythmbox'): - test = True - elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(), - 'org.gnome.Rhythmbox'): - test = True - if test: - rbshellobj = bus.get_object('org.gnome.Rhythmbox', - '/org/gnome/Rhythmbox/Shell') - player = dbus.Interface( - bus.get_object('org.gnome.Rhythmbox', - '/org/gnome/Rhythmbox/Player'), 'org.gnome.Rhythmbox.Player') - rbshell = dbus.Interface(rbshellobj, 'org.gnome.Rhythmbox.Shell') - try: - uri = player.getPlayingUri() - except dbus.DBusException: - uri = None - if not uri: - return None - props = rbshell.getSongProperties(uri) - info = self._rhythmbox_properties_extract(props) - self._last_playing_music = info - return info + ## Check Rhythmbox playing song + test = False + if hasattr(bus, 'name_has_owner'): + if bus.name_has_owner('org.gnome.Rhythmbox'): + test = True + elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(), + 'org.gnome.Rhythmbox'): + test = True + if test: + rbshellobj = bus.get_object('org.gnome.Rhythmbox', + '/org/gnome/Rhythmbox/Shell') + player = dbus.Interface( + bus.get_object('org.gnome.Rhythmbox', + '/org/gnome/Rhythmbox/Player'), 'org.gnome.Rhythmbox.Player') + rbshell = dbus.Interface(rbshellobj, 'org.gnome.Rhythmbox.Shell') + try: + uri = player.getPlayingUri() + except dbus.DBusException: + uri = None + if not uri: + return None + props = rbshell.getSongProperties(uri) + info = self._rhythmbox_properties_extract(props) + self._last_playing_music = info + return info - ## Check Banshee playing track - test = False - if hasattr(bus, 'name_has_owner'): - if bus.name_has_owner('org.bansheeproject.Banshee'): - test = True - elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(), - 'org.bansheeproject.Banshee'): - test = True - if test: - banshee = bus.get_object('org.bansheeproject.Banshee', - '/org/bansheeproject/Banshee/PlayerEngine') - currentTrack = banshee.GetCurrentTrack() - if currentTrack: - song = self._banshee_properties_extract(currentTrack) - self._last_playing_music = song - return song + ## Check Banshee playing track + test = False + if hasattr(bus, 'name_has_owner'): + if bus.name_has_owner('org.bansheeproject.Banshee'): + test = True + elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(), + 'org.bansheeproject.Banshee'): + test = True + if test: + banshee = bus.get_object('org.bansheeproject.Banshee', + '/org/bansheeproject/Banshee/PlayerEngine') + currentTrack = banshee.GetCurrentTrack() + if currentTrack: + song = self._banshee_properties_extract(currentTrack) + self._last_playing_music = song + return song - ## Check Quod Libet playing track - test = False - if hasattr(bus, 'name_has_owner'): - if bus.name_has_owner('net.sacredchao.QuodLibet'): - test = True - elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(), - 'net.sacredchao.QuodLibet'): - test = True - if test: - quodlibet = bus.get_object('net.sacredchao.QuodLibet', - '/net/sacredchao/QuodLibet') - if quodlibet.IsPlaying(): - currentTrack = quodlibet.CurrentSong() - song = self._quodlibet_properties_extract(currentTrack) - self._last_playing_music = song - return song + ## Check Quod Libet playing track + test = False + if hasattr(bus, 'name_has_owner'): + if bus.name_has_owner('net.sacredchao.QuodLibet'): + test = True + elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(), + 'net.sacredchao.QuodLibet'): + test = True + if test: + quodlibet = bus.get_object('net.sacredchao.QuodLibet', + '/net/sacredchao/QuodLibet') + if quodlibet.IsPlaying(): + currentTrack = quodlibet.CurrentSong() + song = self._quodlibet_properties_extract(currentTrack) + self._last_playing_music = song + return song - return None + return None # here we test :) if __name__ == '__main__': - def music_track_change_cb(listener, music_track_info): - if music_track_info is None: - print 'Stop!' - else: - print music_track_info.title - listener = MusicTrackListener.get() - listener.connect('music-track-changed', music_track_change_cb) - track = listener.get_playing_track() - if track is None: - print 'Now not playing anything' - else: - print 'Now playing: "%s" by %s' % (track.title, track.artist) - gobject.MainLoop().run() - -# vim: se ts=3: + def music_track_change_cb(listener, music_track_info): + if music_track_info is None: + print 'Stop!' + else: + print music_track_info.title + listener = MusicTrackListener.get() + listener.connect('music-track-changed', music_track_change_cb) + track = listener.get_playing_track() + if track is None: + print 'Now not playing anything' + else: + print 'Now playing: "%s" by %s' % (track.title, track.artist) + gobject.MainLoop().run() diff --git a/src/negotiation.py b/src/negotiation.py index 6e8161983..d5d974b79 100644 --- a/src/negotiation.py +++ b/src/negotiation.py @@ -27,62 +27,60 @@ from common import gajim from common import xmpp def describe_features(features): - """ - A human-readable description of the features that have been negotiated - """ - if features['logging'] == 'may': - return _('- messages will be logged') - elif features['logging'] == 'mustnot': - return _('- messages will not be logged') + """ + A human-readable description of the features that have been negotiated + """ + if features['logging'] == 'may': + return _('- messages will be logged') + elif features['logging'] == 'mustnot': + return _('- messages will not be logged') class FeatureNegotiationWindow: - def __init__(self, account, jid, session, form): - self.account = account - self.jid = jid - self.form = form - self.session = session + def __init__(self, account, jid, session, form): + self.account = account + self.jid = jid + self.form = form + self.session = session - self.xml = gtkgui_helpers.get_gtk_builder('data_form_window.ui', 'data_form_window') - self.window = self.xml.get_object('data_form_window') + self.xml = gtkgui_helpers.get_gtk_builder('data_form_window.ui', 'data_form_window') + self.window = self.xml.get_object('data_form_window') - config_vbox = self.xml.get_object('config_vbox') - dataform = dataforms.ExtendForm(node = self.form) - self.data_form_widget = dataforms_widget.DataFormWidget(dataform) - self.data_form_widget.show() - config_vbox.pack_start(self.data_form_widget) + config_vbox = self.xml.get_object('config_vbox') + dataform = dataforms.ExtendForm(node = self.form) + self.data_form_widget = dataforms_widget.DataFormWidget(dataform) + self.data_form_widget.show() + config_vbox.pack_start(self.data_form_widget) - self.xml.connect_signals(self) - self.window.show_all() + self.xml.connect_signals(self) + self.window.show_all() - def on_ok_button_clicked(self, widget): - acceptance = xmpp.Message(self.jid) - acceptance.setThread(self.session.thread_id) - feature = acceptance.NT.feature - feature.setNamespace(xmpp.NS_FEATURE) + def on_ok_button_clicked(self, widget): + acceptance = xmpp.Message(self.jid) + acceptance.setThread(self.session.thread_id) + feature = acceptance.NT.feature + feature.setNamespace(xmpp.NS_FEATURE) - form = self.data_form_widget.data_form - form.setAttr('type', 'submit') + form = self.data_form_widget.data_form + form.setAttr('type', 'submit') - feature.addChild(node=form) + feature.addChild(node=form) - gajim.connections[self.account].send_stanza(acceptance) + gajim.connections[self.account].send_stanza(acceptance) - self.window.destroy() + self.window.destroy() - def on_cancel_button_clicked(self, widget): - rejection = xmpp.Message(self.jid) - rejection.setThread(self.session.thread_id) - feature = rejection.NT.feature - feature.setNamespace(xmpp.NS_FEATURE) + def on_cancel_button_clicked(self, widget): + rejection = xmpp.Message(self.jid) + rejection.setThread(self.session.thread_id) + feature = rejection.NT.feature + feature.setNamespace(xmpp.NS_FEATURE) - x = xmpp.DataForm(typ='submit') - x.addChild(node=xmpp.DataField('FORM_TYPE', value='urn:xmpp:ssn')) - x.addChild(node=xmpp.DataField('accept', value='false', typ='boolean')) + x = xmpp.DataForm(typ='submit') + x.addChild(node=xmpp.DataField('FORM_TYPE', value='urn:xmpp:ssn')) + x.addChild(node=xmpp.DataField('accept', value='false', typ='boolean')) - feature.addChild(node=x) + feature.addChild(node=x) - gajim.connections[self.account].send_stanza(rejection) + gajim.connections[self.account].send_stanza(rejection) - self.window.destroy() - -# vim: se ts=3: + self.window.destroy() diff --git a/src/network_manager_listener.py b/src/network_manager_listener.py index 3f1f774ca..5239015fe 100644 --- a/src/network_manager_listener.py +++ b/src/network_manager_listener.py @@ -26,79 +26,77 @@ from common import gajim def device_now_active(self, *args): - """ - For Network Manager 0.6 - """ - for connection in gajim.connections.itervalues(): - if gajim.config.get_per('accounts', connection.name, - 'listen_to_network_manager') and connection.time_to_reconnect: - connection._reconnect() + """ + For Network Manager 0.6 + """ + for connection in gajim.connections.itervalues(): + if gajim.config.get_per('accounts', connection.name, + 'listen_to_network_manager') and connection.time_to_reconnect: + connection._reconnect() def device_no_longer_active(self, *args): - """ - For Network Manager 0.6 - """ - for connection in gajim.connections.itervalues(): - if gajim.config.get_per('accounts', connection.name, - 'listen_to_network_manager') and connection.connected > 1: - connection._disconnectedReconnCB() + """ + For Network Manager 0.6 + """ + for connection in gajim.connections.itervalues(): + if gajim.config.get_per('accounts', connection.name, + 'listen_to_network_manager') and connection.connected > 1: + connection._disconnectedReconnCB() def state_changed(state): - """ - For Network Manager 0.7 - """ - if props.Get("org.freedesktop.NetworkManager", "State") == 3: - for connection in gajim.connections.itervalues(): - if gajim.config.get_per('accounts', connection.name, - 'listen_to_network_manager') and connection.time_to_reconnect: - connection._reconnect() - else: - for connection in gajim.connections.itervalues(): - if gajim.config.get_per('accounts', connection.name, - 'listen_to_network_manager') and connection.connected > 1: - connection._disconnectedReconnCB() + """ + For Network Manager 0.7 + """ + if props.Get("org.freedesktop.NetworkManager", "State") == 3: + for connection in gajim.connections.itervalues(): + if gajim.config.get_per('accounts', connection.name, + 'listen_to_network_manager') and connection.time_to_reconnect: + connection._reconnect() + else: + for connection in gajim.connections.itervalues(): + if gajim.config.get_per('accounts', connection.name, + 'listen_to_network_manager') and connection.connected > 1: + connection._disconnectedReconnCB() supported = False from common import dbus_support if dbus_support.supported: - import dbus - import dbus.glib + import dbus + import dbus.glib - try: - from common.dbus_support import system_bus + try: + from common.dbus_support import system_bus - bus = system_bus.bus() + bus = system_bus.bus() - if 'org.freedesktop.NetworkManager' in bus.list_names(): - nm_object = bus.get_object('org.freedesktop.NetworkManager', - '/org/freedesktop/NetworkManager') - props = dbus.Interface(nm_object,"org.freedesktop.DBus.Properties") - bus.add_signal_receiver(state_changed, - 'StateChanged', - 'org.freedesktop.NetworkManager', - 'org.freedesktop.NetworkManager', - '/org/freedesktop/NetworkManager') - supported = True + if 'org.freedesktop.NetworkManager' in bus.list_names(): + nm_object = bus.get_object('org.freedesktop.NetworkManager', + '/org/freedesktop/NetworkManager') + props = dbus.Interface(nm_object, "org.freedesktop.DBus.Properties") + bus.add_signal_receiver(state_changed, + 'StateChanged', + 'org.freedesktop.NetworkManager', + 'org.freedesktop.NetworkManager', + '/org/freedesktop/NetworkManager') + supported = True - except dbus.DBusException: - try: - if 'org.freedesktop.NetworkManager' in bus.list_names(): - supported = True + except dbus.DBusException: + try: + if 'org.freedesktop.NetworkManager' in bus.list_names(): + supported = True - bus.add_signal_receiver(device_no_longer_active, - 'DeviceNoLongerActive', - 'org.freedesktop.NetworkManager', - 'org.freedesktop.NetworkManager', - '/org/freedesktop/NetworkManager') + bus.add_signal_receiver(device_no_longer_active, + 'DeviceNoLongerActive', + 'org.freedesktop.NetworkManager', + 'org.freedesktop.NetworkManager', + '/org/freedesktop/NetworkManager') - bus.add_signal_receiver(device_now_active, - 'DeviceNowActive', - 'org.freedesktop.NetworkManager', - 'org.freedesktop.NetworkManager', - '/org/freedesktop/NetworkManager') - except Exception: - pass - -# vim: se ts=3: + bus.add_signal_receiver(device_now_active, + 'DeviceNowActive', + 'org.freedesktop.NetworkManager', + 'org.freedesktop.NetworkManager', + '/org/freedesktop/NetworkManager') + except Exception: + pass diff --git a/src/notify.py b/src/notify.py index 37a7fd267..92cc63b5f 100644 --- a/src/notify.py +++ b/src/notify.py @@ -39,644 +39,642 @@ from common import helpers from common import dbus_support if dbus_support.supported: - import dbus - import dbus.glib + import dbus + import dbus.glib USER_HAS_PYNOTIFY = True # user has pynotify module try: - import pynotify - pynotify.init('Gajim Notification') + import pynotify + pynotify.init('Gajim Notification') except ImportError: - USER_HAS_PYNOTIFY = False + USER_HAS_PYNOTIFY = False if gajim.HAVE_INDICATOR: - import indicate + import indicate def setup_indicator_server(): - server = indicate.indicate_server_ref_default() - server.set_type('message.im') - server.set_desktop_file('/usr/share/applications/gajim.desktop') - server.connect('server-display', server_display) - server.show() + server = indicate.indicate_server_ref_default() + server.set_type('message.im') + server.set_desktop_file('/usr/share/applications/gajim.desktop') + server.connect('server-display', server_display) + server.show() def display(indicator, account, jid, msg_type): - gajim.interface.handle_event(account, jid, msg_type) - indicator.hide() + gajim.interface.handle_event(account, jid, msg_type) + indicator.hide() def server_display(server): - win = gajim.interface.roster.window - win.present() + win = gajim.interface.roster.window + win.present() def get_show_in_roster(event, account, contact, session=None): - """ - Return True if this event must be shown in roster, else False - """ - if event == 'gc_message_received': - return True - num = get_advanced_notification(event, account, contact) - if num is not None: - if gajim.config.get_per('notifications', str(num), 'roster') == 'yes': - return True - if gajim.config.get_per('notifications', str(num), 'roster') == 'no': - return False - if event == 'message_received': - if session and session.control: - return False - return True + """ + Return True if this event must be shown in roster, else False + """ + if event == 'gc_message_received': + return True + num = get_advanced_notification(event, account, contact) + if num is not None: + if gajim.config.get_per('notifications', str(num), 'roster') == 'yes': + return True + if gajim.config.get_per('notifications', str(num), 'roster') == 'no': + return False + if event == 'message_received': + if session and session.control: + return False + return True def get_show_in_systray(event, account, contact, type_=None): - """ - Return True if this event must be shown in systray, else False - """ - num = get_advanced_notification(event, account, contact) - if num is not None: - if gajim.config.get_per('notifications', str(num), 'systray') == 'yes': - return True - if gajim.config.get_per('notifications', str(num), 'systray') == 'no': - return False - if type_ == 'printed_gc_msg' and not gajim.config.get( - 'notify_on_all_muc_messages'): - # it's not an highlighted message, don't show in systray - return False - return gajim.config.get('trayicon_notification_on_events') + """ + Return True if this event must be shown in systray, else False + """ + num = get_advanced_notification(event, account, contact) + if num is not None: + if gajim.config.get_per('notifications', str(num), 'systray') == 'yes': + return True + if gajim.config.get_per('notifications', str(num), 'systray') == 'no': + return False + if type_ == 'printed_gc_msg' and not gajim.config.get( + 'notify_on_all_muc_messages'): + # it's not an highlighted message, don't show in systray + return False + return gajim.config.get('trayicon_notification_on_events') def get_advanced_notification(event, account, contact): - """ - Returns the number of the first (top most) advanced notification else None - """ - num = 0 - notif = gajim.config.get_per('notifications', str(num)) - while notif: - recipient_ok = False - status_ok = False - tab_opened_ok = False - # test event - if gajim.config.get_per('notifications', str(num), 'event') == event: - # test recipient - recipient_type = gajim.config.get_per('notifications', str(num), - 'recipient_type') - recipients = gajim.config.get_per('notifications', str(num), - 'recipients').split() - if recipient_type == 'all': - recipient_ok = True - elif recipient_type == 'contact' and contact.jid in recipients: - recipient_ok = True - elif recipient_type == 'group': - for group in contact.groups: - if group in contact.groups: - recipient_ok = True - break - if recipient_ok: - # test status - our_status = gajim.SHOW_LIST[gajim.connections[account].connected] - status = gajim.config.get_per('notifications', str(num), 'status') - if status == 'all' or our_status in status.split(): - status_ok = True - if status_ok: - # test window_opened - tab_opened = gajim.config.get_per('notifications', str(num), - 'tab_opened') - if tab_opened == 'both': - tab_opened_ok = True - else: - chat_control = helpers.get_chat_control(account, contact) - if (chat_control and tab_opened == 'yes') or (not chat_control and \ - tab_opened == 'no'): - tab_opened_ok = True - if tab_opened_ok: - return num + """ + Returns the number of the first (top most) advanced notification else None + """ + num = 0 + notif = gajim.config.get_per('notifications', str(num)) + while notif: + recipient_ok = False + status_ok = False + tab_opened_ok = False + # test event + if gajim.config.get_per('notifications', str(num), 'event') == event: + # test recipient + recipient_type = gajim.config.get_per('notifications', str(num), + 'recipient_type') + recipients = gajim.config.get_per('notifications', str(num), + 'recipients').split() + if recipient_type == 'all': + recipient_ok = True + elif recipient_type == 'contact' and contact.jid in recipients: + recipient_ok = True + elif recipient_type == 'group': + for group in contact.groups: + if group in contact.groups: + recipient_ok = True + break + if recipient_ok: + # test status + our_status = gajim.SHOW_LIST[gajim.connections[account].connected] + status = gajim.config.get_per('notifications', str(num), 'status') + if status == 'all' or our_status in status.split(): + status_ok = True + if status_ok: + # test window_opened + tab_opened = gajim.config.get_per('notifications', str(num), + 'tab_opened') + if tab_opened == 'both': + tab_opened_ok = True + else: + chat_control = helpers.get_chat_control(account, contact) + if (chat_control and tab_opened == 'yes') or (not chat_control and \ + tab_opened == 'no'): + tab_opened_ok = True + if tab_opened_ok: + return num - num += 1 - notif = gajim.config.get_per('notifications', str(num)) + num += 1 + notif = gajim.config.get_per('notifications', str(num)) def notify(event, jid, account, parameters, advanced_notif_num=None): - """ - Check what type of notifications we want, depending on basic and the advanced - configuration of notifications and do these notifications; advanced_notif_num - holds the number of the first (top most) advanced notification - """ - # First, find what notifications we want - do_popup = False - do_sound = False - do_cmd = False - if event == 'status_change': - new_show = parameters[0] - status_message = parameters[1] - # Default: No popup for status change - elif event == 'contact_connected': - status_message = parameters - j = gajim.get_jid_without_resource(jid) - server = gajim.get_server_from_jid(j) - account_server = account + '/' + server - block_transport = False - if account_server in gajim.block_signed_in_notifications and \ - gajim.block_signed_in_notifications[account_server]: - block_transport = True - if helpers.allow_showing_notification(account, 'notify_on_signin') and \ - not gajim.block_signed_in_notifications[account] and not block_transport: - do_popup = True - if gajim.config.get_per('soundevents', 'contact_connected', - 'enabled') and not gajim.block_signed_in_notifications[account] and \ - not block_transport: - do_sound = True - elif event == 'contact_disconnected': - status_message = parameters - if helpers.allow_showing_notification(account, 'notify_on_signout'): - do_popup = True - if gajim.config.get_per('soundevents', 'contact_disconnected', - 'enabled'): - do_sound = True - elif event == 'new_message': - message_type = parameters[0] - is_first_message = parameters[1] - nickname = parameters[2] - if gajim.config.get('notification_preview_message'): - message = parameters[3] - if message.startswith('/me ') or message.startswith('/me\n'): - message = '* ' + nickname + message[3:] - else: - # We don't want message preview, do_preview = False - message = '' - focused = parameters[4] - if helpers.allow_showing_notification(account, 'notify_on_new_message', - advanced_notif_num, is_first_message): - do_popup = True - if is_first_message and helpers.allow_sound_notification(account, - 'first_message_received', advanced_notif_num): - do_sound = True - elif not is_first_message and focused and \ - helpers.allow_sound_notification(account, 'next_message_received_focused', - advanced_notif_num): - do_sound = True - elif not is_first_message and not focused and \ - helpers.allow_sound_notification(account, - 'next_message_received_unfocused', advanced_notif_num): - do_sound = True - else: - print '*Event not implemeted yet*' + """ + Check what type of notifications we want, depending on basic and the advanced + configuration of notifications and do these notifications; advanced_notif_num + holds the number of the first (top most) advanced notification + """ + # First, find what notifications we want + do_popup = False + do_sound = False + do_cmd = False + if event == 'status_change': + new_show = parameters[0] + status_message = parameters[1] + # Default: No popup for status change + elif event == 'contact_connected': + status_message = parameters + j = gajim.get_jid_without_resource(jid) + server = gajim.get_server_from_jid(j) + account_server = account + '/' + server + block_transport = False + if account_server in gajim.block_signed_in_notifications and \ + gajim.block_signed_in_notifications[account_server]: + block_transport = True + if helpers.allow_showing_notification(account, 'notify_on_signin') and \ + not gajim.block_signed_in_notifications[account] and not block_transport: + do_popup = True + if gajim.config.get_per('soundevents', 'contact_connected', + 'enabled') and not gajim.block_signed_in_notifications[account] and \ + not block_transport: + do_sound = True + elif event == 'contact_disconnected': + status_message = parameters + if helpers.allow_showing_notification(account, 'notify_on_signout'): + do_popup = True + if gajim.config.get_per('soundevents', 'contact_disconnected', + 'enabled'): + do_sound = True + elif event == 'new_message': + message_type = parameters[0] + is_first_message = parameters[1] + nickname = parameters[2] + if gajim.config.get('notification_preview_message'): + message = parameters[3] + if message.startswith('/me ') or message.startswith('/me\n'): + message = '* ' + nickname + message[3:] + else: + # We don't want message preview, do_preview = False + message = '' + focused = parameters[4] + if helpers.allow_showing_notification(account, 'notify_on_new_message', + advanced_notif_num, is_first_message): + do_popup = True + if is_first_message and helpers.allow_sound_notification(account, + 'first_message_received', advanced_notif_num): + do_sound = True + elif not is_first_message and focused and \ + helpers.allow_sound_notification(account, 'next_message_received_focused', + advanced_notif_num): + do_sound = True + elif not is_first_message and not focused and \ + helpers.allow_sound_notification(account, + 'next_message_received_unfocused', advanced_notif_num): + do_sound = True + else: + print '*Event not implemeted yet*' - if advanced_notif_num is not None and gajim.config.get_per('notifications', - str(advanced_notif_num), 'run_command'): - do_cmd = True + if advanced_notif_num is not None and gajim.config.get_per('notifications', + str(advanced_notif_num), 'run_command'): + do_cmd = True - # Do the wanted notifications - if do_popup: - if event in ('contact_connected', 'contact_disconnected', - 'status_change'): # Common code for popup for these three events - if event == 'contact_disconnected': - show_image = 'offline.png' - suffix = '_notif_size_bw' - else: #Status Change or Connected - # FIXME: for status change, - # we don't always 'online.png', but we - # first need 48x48 for all status - show_image = 'online.png' - suffix = '_notif_size_colored' - transport_name = gajim.get_transport_name_from_jid(jid) - img_path = None - if transport_name: - img_path = os.path.join(helpers.get_transport_path(transport_name), - '48x48', show_image) - if not img_path or not os.path.isfile(img_path): - iconset = gajim.config.get('iconset') - img_path = os.path.join(helpers.get_iconset_path(iconset), '48x48', - show_image) - path = gtkgui_helpers.get_path_to_generic_or_avatar(img_path, jid=jid, - suffix=suffix) - if event == 'status_change': - title = _('%(nick)s Changed Status') % \ - {'nick': gajim.get_name_from_jid(account, jid)} - text = _('%(nick)s is now %(status)s') % \ - {'nick': gajim.get_name_from_jid(account, jid),\ - 'status': helpers.get_uf_show(gajim.SHOW_LIST[new_show])} - if status_message: - text = text + " : " + status_message - popup(_('Contact Changed Status'), jid, account, - path_to_image=path, title=title, text=text) - elif event == 'contact_connected': - title = _('%(nickname)s Signed In') % \ - {'nickname': gajim.get_name_from_jid(account, jid)} - text = '' - if status_message: - text = status_message - popup(_('Contact Signed In'), jid, account, - path_to_image=path, title=title, text=text) - elif event == 'contact_disconnected': - title = _('%(nickname)s Signed Out') % \ - {'nickname': gajim.get_name_from_jid(account, jid)} - text = '' - if status_message: - text = status_message - popup(_('Contact Signed Out'), jid, account, - path_to_image=path, title=title, text=text) - elif event == 'new_message': - if message_type == 'normal': # single message - event_type = _('New Single Message') - img_name = 'gajim-single_msg_recv' - title = _('New Single Message from %(nickname)s') % \ - {'nickname': nickname} - text = message - elif message_type == 'pm': # private message - event_type = _('New Private Message') - room_name = gajim.get_nick_from_jid(jid) - img_name = 'gajim-priv_msg_recv' - title = _('New Private Message from group chat %s') % room_name - if message: - text = _('%(nickname)s: %(message)s') % {'nickname': nickname, - 'message': message} - else: - text = _('Messaged by %(nickname)s') % {'nickname': nickname} + # Do the wanted notifications + if do_popup: + if event in ('contact_connected', 'contact_disconnected', + 'status_change'): # Common code for popup for these three events + if event == 'contact_disconnected': + show_image = 'offline.png' + suffix = '_notif_size_bw' + else: #Status Change or Connected + # FIXME: for status change, + # we don't always 'online.png', but we + # first need 48x48 for all status + show_image = 'online.png' + suffix = '_notif_size_colored' + transport_name = gajim.get_transport_name_from_jid(jid) + img_path = None + if transport_name: + img_path = os.path.join(helpers.get_transport_path(transport_name), + '48x48', show_image) + if not img_path or not os.path.isfile(img_path): + iconset = gajim.config.get('iconset') + img_path = os.path.join(helpers.get_iconset_path(iconset), '48x48', + show_image) + path = gtkgui_helpers.get_path_to_generic_or_avatar(img_path, jid=jid, + suffix=suffix) + if event == 'status_change': + title = _('%(nick)s Changed Status') % \ + {'nick': gajim.get_name_from_jid(account, jid)} + text = _('%(nick)s is now %(status)s') % \ + {'nick': gajim.get_name_from_jid(account, jid),\ + 'status': helpers.get_uf_show(gajim.SHOW_LIST[new_show])} + if status_message: + text = text + " : " + status_message + popup(_('Contact Changed Status'), jid, account, + path_to_image=path, title=title, text=text) + elif event == 'contact_connected': + title = _('%(nickname)s Signed In') % \ + {'nickname': gajim.get_name_from_jid(account, jid)} + text = '' + if status_message: + text = status_message + popup(_('Contact Signed In'), jid, account, + path_to_image=path, title=title, text=text) + elif event == 'contact_disconnected': + title = _('%(nickname)s Signed Out') % \ + {'nickname': gajim.get_name_from_jid(account, jid)} + text = '' + if status_message: + text = status_message + popup(_('Contact Signed Out'), jid, account, + path_to_image=path, title=title, text=text) + elif event == 'new_message': + if message_type == 'normal': # single message + event_type = _('New Single Message') + img_name = 'gajim-single_msg_recv' + title = _('New Single Message from %(nickname)s') % \ + {'nickname': nickname} + text = message + elif message_type == 'pm': # private message + event_type = _('New Private Message') + room_name = gajim.get_nick_from_jid(jid) + img_name = 'gajim-priv_msg_recv' + title = _('New Private Message from group chat %s') % room_name + if message: + text = _('%(nickname)s: %(message)s') % {'nickname': nickname, + 'message': message} + else: + text = _('Messaged by %(nickname)s') % {'nickname': nickname} - else: # chat message - event_type = _('New Message') - img_name = 'gajim-chat_msg_recv' - title = _('New Message from %(nickname)s') % \ - {'nickname': nickname} - text = message - img_path = gtkgui_helpers.get_icon_path(img_name, 48) - popup(event_type, jid, account, message_type, - path_to_image=img_path, title=title, text=text) + else: # chat message + event_type = _('New Message') + img_name = 'gajim-chat_msg_recv' + title = _('New Message from %(nickname)s') % \ + {'nickname': nickname} + text = message + img_path = gtkgui_helpers.get_icon_path(img_name, 48) + popup(event_type, jid, account, message_type, + path_to_image=img_path, title=title, text=text) - if do_sound: - snd_file = None - snd_event = None # If not snd_file, play the event - if event == 'new_message': - if advanced_notif_num is not None and gajim.config.get_per( - 'notifications', str(advanced_notif_num), 'sound') == 'yes': - snd_file = gajim.config.get_per('notifications', - str(advanced_notif_num), 'sound_file') - elif advanced_notif_num is not None and gajim.config.get_per( - 'notifications', str(advanced_notif_num), 'sound') == 'no': - pass # do not set snd_event - elif is_first_message: - snd_event = 'first_message_received' - elif focused: - snd_event = 'next_message_received_focused' - else: - snd_event = 'next_message_received_unfocused' - elif event in ('contact_connected', 'contact_disconnected'): - snd_event = event - if snd_file: - helpers.play_sound_file(snd_file) - if snd_event: - helpers.play_sound(snd_event) + if do_sound: + snd_file = None + snd_event = None # If not snd_file, play the event + if event == 'new_message': + if advanced_notif_num is not None and gajim.config.get_per( + 'notifications', str(advanced_notif_num), 'sound') == 'yes': + snd_file = gajim.config.get_per('notifications', + str(advanced_notif_num), 'sound_file') + elif advanced_notif_num is not None and gajim.config.get_per( + 'notifications', str(advanced_notif_num), 'sound') == 'no': + pass # do not set snd_event + elif is_first_message: + snd_event = 'first_message_received' + elif focused: + snd_event = 'next_message_received_focused' + else: + snd_event = 'next_message_received_unfocused' + elif event in ('contact_connected', 'contact_disconnected'): + snd_event = event + if snd_file: + helpers.play_sound_file(snd_file) + if snd_event: + helpers.play_sound(snd_event) - if do_cmd: - command = gajim.config.get_per('notifications', str(advanced_notif_num), - 'command') - try: - helpers.exec_command(command) - except Exception: - pass + if do_cmd: + command = gajim.config.get_per('notifications', str(advanced_notif_num), + 'command') + try: + helpers.exec_command(command) + except Exception: + pass def popup(event_type, jid, account, msg_type='', path_to_image=None, title=None, - text=None): - """ - Notify a user of an event. It first tries to a valid implementation of - the Desktop Notification Specification. If that fails, then we fall back to - the older style PopupNotificationWindow method - """ - # default image - if not path_to_image: - path_to_image = gtkgui_helpers.get_icon_path('gajim-chat_msg_recv', 48) + text=None): + """ + Notify a user of an event. It first tries to a valid implementation of + the Desktop Notification Specification. If that fails, then we fall back to + the older style PopupNotificationWindow method + """ + # default image + if not path_to_image: + path_to_image = gtkgui_helpers.get_icon_path('gajim-chat_msg_recv', 48) - if gajim.HAVE_INDICATOR and event_type in (_('New Message'), - _('New Single Message'), _('New Private Message')): - indicator = indicate.Indicator() - indicator.set_property('subtype', 'im') - indicator.set_property('sender', jid) - indicator.set_property('body', text) - indicator.set_property_time('time', time.time()) - pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_image) - indicator.set_property_icon('icon', pixbuf) - indicator.connect('user-display', display, account, jid, msg_type) - indicator.show() + if gajim.HAVE_INDICATOR and event_type in (_('New Message'), + _('New Single Message'), _('New Private Message')): + indicator = indicate.Indicator() + indicator.set_property('subtype', 'im') + indicator.set_property('sender', jid) + indicator.set_property('body', text) + indicator.set_property_time('time', time.time()) + pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_image) + indicator.set_property_icon('icon', pixbuf) + indicator.connect('user-display', display, account, jid, msg_type) + indicator.show() - # Try to show our popup via D-Bus and notification daemon - if gajim.config.get('use_notif_daemon') and dbus_support.supported: - try: - DesktopNotification(event_type, jid, account, msg_type, - path_to_image, title, gobject.markup_escape_text(text)) - return # sucessfully did D-Bus Notification procedure! - except dbus.DBusException, e: - # Connection to D-Bus failed - gajim.log.debug(str(e)) - except TypeError, e: - # This means that we sent the message incorrectly - gajim.log.debug(str(e)) + # Try to show our popup via D-Bus and notification daemon + if gajim.config.get('use_notif_daemon') and dbus_support.supported: + try: + DesktopNotification(event_type, jid, account, msg_type, + path_to_image, title, gobject.markup_escape_text(text)) + return # sucessfully did D-Bus Notification procedure! + except dbus.DBusException, e: + # Connection to D-Bus failed + gajim.log.debug(str(e)) + except TypeError, e: + # This means that we sent the message incorrectly + gajim.log.debug(str(e)) - # Ok, that failed. Let's try pynotify, which also uses notification daemon - if gajim.config.get('use_notif_daemon') and USER_HAS_PYNOTIFY: - if not text and event_type == 'new_message': - # empty text for new_message means do_preview = False - # -> default value for text - _text = gobject.markup_escape_text( - gajim.get_name_from_jid(account, jid)) - else: - _text = gobject.markup_escape_text(text) + # Ok, that failed. Let's try pynotify, which also uses notification daemon + if gajim.config.get('use_notif_daemon') and USER_HAS_PYNOTIFY: + if not text and event_type == 'new_message': + # empty text for new_message means do_preview = False + # -> default value for text + _text = gobject.markup_escape_text( + gajim.get_name_from_jid(account, jid)) + else: + _text = gobject.markup_escape_text(text) - if not title: - _title = '' - else: - _title = title + if not title: + _title = '' + else: + _title = title - notification = pynotify.Notification(_title, _text) - timeout = gajim.config.get('notification_timeout') * 1000 # make it ms - notification.set_timeout(timeout) + notification = pynotify.Notification(_title, _text) + timeout = gajim.config.get('notification_timeout') * 1000 # make it ms + notification.set_timeout(timeout) - notification.set_category(event_type) - notification.set_data('event_type', event_type) - notification.set_data('jid', jid) - notification.set_data('account', account) - notification.set_data('msg_type', msg_type) - notification.set_property('icon-name', path_to_image) - if 'actions' in pynotify.get_server_caps(): - notification.add_action('default', 'Default Action', - on_pynotify_notification_clicked) + notification.set_category(event_type) + notification.set_data('event_type', event_type) + notification.set_data('jid', jid) + notification.set_data('account', account) + notification.set_data('msg_type', msg_type) + notification.set_property('icon-name', path_to_image) + if 'actions' in pynotify.get_server_caps(): + notification.add_action('default', 'Default Action', + on_pynotify_notification_clicked) - try: - notification.show() - return - except gobject.GError, e: - # Connection to notification-daemon failed, see #2893 - gajim.log.debug(str(e)) + try: + notification.show() + return + except gobject.GError, e: + # Connection to notification-daemon failed, see #2893 + gajim.log.debug(str(e)) - # Either nothing succeeded or the user wants old-style notifications - instance = dialogs.PopupNotificationWindow(event_type, jid, account, - msg_type, path_to_image, title, text) - gajim.interface.roster.popup_notification_windows.append(instance) + # Either nothing succeeded or the user wants old-style notifications + instance = dialogs.PopupNotificationWindow(event_type, jid, account, + msg_type, path_to_image, title, text) + gajim.interface.roster.popup_notification_windows.append(instance) def on_pynotify_notification_clicked(notification, action): - jid = notification.get_data('jid') - account = notification.get_data('account') - msg_type = notification.get_data('msg_type') + jid = notification.get_data('jid') + account = notification.get_data('account') + msg_type = notification.get_data('msg_type') - notification.close() - gajim.interface.handle_event(account, jid, msg_type) + notification.close() + gajim.interface.handle_event(account, jid, msg_type) class NotificationResponseManager: - """ - Collect references to pending DesktopNotifications and manages there - signalling. This is necessary due to a bug in DBus where you can't remove a - signal from an interface once it's connected - """ + """ + Collect references to pending DesktopNotifications and manages there + signalling. This is necessary due to a bug in DBus where you can't remove a + signal from an interface once it's connected + """ - def __init__(self): - self.pending = {} - self.received = [] - self.interface = None + def __init__(self): + self.pending = {} + self.received = [] + self.interface = None - def attach_to_interface(self): - if self.interface is not None: - return - self.interface = dbus_support.get_notifications_interface() - self.interface.connect_to_signal('ActionInvoked', self.on_action_invoked) - self.interface.connect_to_signal('NotificationClosed', self.on_closed) + def attach_to_interface(self): + if self.interface is not None: + return + self.interface = dbus_support.get_notifications_interface() + self.interface.connect_to_signal('ActionInvoked', self.on_action_invoked) + self.interface.connect_to_signal('NotificationClosed', self.on_closed) - def on_action_invoked(self, id_, reason): - self.received.append((id_, time.time(), reason)) - if id_ in self.pending: - notification = self.pending[id_] - notification.on_action_invoked(id_, reason) - del self.pending[id_] - if len(self.received) > 20: - curt = time.time() - for rec in self.received: - diff = curt - rec[1] - if diff > 10: - self.received.remove(rec) + def on_action_invoked(self, id_, reason): + self.received.append((id_, time.time(), reason)) + if id_ in self.pending: + notification = self.pending[id_] + notification.on_action_invoked(id_, reason) + del self.pending[id_] + if len(self.received) > 20: + curt = time.time() + for rec in self.received: + diff = curt - rec[1] + if diff > 10: + self.received.remove(rec) - def on_closed(self, id_, reason=None): - if id_ in self.pending: - del self.pending[id_] + def on_closed(self, id_, reason=None): + if id_ in self.pending: + del self.pending[id_] - def add_pending(self, id_, object_): - # Check to make sure that we handle an event immediately if we're adding - # an id that's already been triggered - for rec in self.received: - if rec[0] == id_: - object_.on_action_invoked(id_, rec[2]) - self.received.remove(rec) - return - if id_ not in self.pending: - # Add it - self.pending[id_] = object_ - else: - # We've triggered an event that has a duplicate ID! - gajim.log.debug('Duplicate ID of notification. Can\'t handle this.') + def add_pending(self, id_, object_): + # Check to make sure that we handle an event immediately if we're adding + # an id that's already been triggered + for rec in self.received: + if rec[0] == id_: + object_.on_action_invoked(id_, rec[2]) + self.received.remove(rec) + return + if id_ not in self.pending: + # Add it + self.pending[id_] = object_ + else: + # We've triggered an event that has a duplicate ID! + gajim.log.debug('Duplicate ID of notification. Can\'t handle this.') notification_response_manager = NotificationResponseManager() class DesktopNotification: - """ - A DesktopNotification that interfaces with D-Bus via the Desktop Notification - specification - """ + """ + A DesktopNotification that interfaces with D-Bus via the Desktop Notification + specification + """ - def __init__(self, event_type, jid, account, msg_type='', - path_to_image=None, title=None, text=None): - self.path_to_image = path_to_image - self.event_type = event_type - self.title = title - self.text = text - # 0.3.1 is the only version of notification daemon that has no way - # to determine which version it is. If no method exists, it means - # they're using that one. - self.default_version = [0, 3, 1] - self.account = account - self.jid = jid - self.msg_type = msg_type + def __init__(self, event_type, jid, account, msg_type='', + path_to_image=None, title=None, text=None): + self.path_to_image = path_to_image + self.event_type = event_type + self.title = title + self.text = text + # 0.3.1 is the only version of notification daemon that has no way + # to determine which version it is. If no method exists, it means + # they're using that one. + self.default_version = [0, 3, 1] + self.account = account + self.jid = jid + self.msg_type = msg_type - # default value of text - if not text and event_type == 'new_message': - # empty text for new_message means do_preview = False - self.text = gajim.get_name_from_jid(account, jid) + # default value of text + if not text and event_type == 'new_message': + # empty text for new_message means do_preview = False + self.text = gajim.get_name_from_jid(account, jid) - if not title: - self.title = event_type # default value + if not title: + self.title = event_type # default value - if event_type == _('Contact Signed In'): - ntype = 'presence.online' - elif event_type == _('Contact Signed Out'): - ntype = 'presence.offline' - elif event_type in (_('New Message'), _('New Single Message'), - _('New Private Message')): - ntype = 'im.received' - elif event_type == _('File Transfer Request'): - ntype = 'transfer' - elif event_type == _('File Transfer Error'): - ntype = 'transfer.error' - elif event_type in (_('File Transfer Completed'), - _('File Transfer Stopped')): - ntype = 'transfer.complete' - elif event_type == _('New E-mail'): - ntype = 'email.arrived' - elif event_type == _('Groupchat Invitation'): - ntype = 'im.invitation' - elif event_type == _('Contact Changed Status'): - ntype = 'presence.status' - elif event_type == _('Connection Failed'): - ntype = 'connection.failed' - elif event_type == _('Subscription request'): - ntype = 'subscription.request' - elif event_type == _('Unsubscribed'): - ntype = 'unsubscribed' - else: - # default failsafe values - self.path_to_image = gtkgui_helpers.get_icon_path( - 'gajim-chat_msg_recv', 48) - ntype = 'im' # Notification Type + if event_type == _('Contact Signed In'): + ntype = 'presence.online' + elif event_type == _('Contact Signed Out'): + ntype = 'presence.offline' + elif event_type in (_('New Message'), _('New Single Message'), + _('New Private Message')): + ntype = 'im.received' + elif event_type == _('File Transfer Request'): + ntype = 'transfer' + elif event_type == _('File Transfer Error'): + ntype = 'transfer.error' + elif event_type in (_('File Transfer Completed'), + _('File Transfer Stopped')): + ntype = 'transfer.complete' + elif event_type == _('New E-mail'): + ntype = 'email.arrived' + elif event_type == _('Groupchat Invitation'): + ntype = 'im.invitation' + elif event_type == _('Contact Changed Status'): + ntype = 'presence.status' + elif event_type == _('Connection Failed'): + ntype = 'connection.failed' + elif event_type == _('Subscription request'): + ntype = 'subscription.request' + elif event_type == _('Unsubscribed'): + ntype = 'unsubscribed' + else: + # default failsafe values + self.path_to_image = gtkgui_helpers.get_icon_path( + 'gajim-chat_msg_recv', 48) + ntype = 'im' # Notification Type - self.notif = dbus_support.get_notifications_interface(self) - if self.notif is None: - raise dbus.DBusException('unable to get notifications interface') - self.ntype = ntype + self.notif = dbus_support.get_notifications_interface(self) + if self.notif is None: + raise dbus.DBusException('unable to get notifications interface') + self.ntype = ntype - if self.kde_notifications: - self.attempt_notify() - else: - self.capabilities = self.notif.GetCapabilities() - if self.capabilities is None: - self.capabilities = ['actions'] - self.get_version() + if self.kde_notifications: + self.attempt_notify() + else: + self.capabilities = self.notif.GetCapabilities() + if self.capabilities is None: + self.capabilities = ['actions'] + self.get_version() - def attempt_notify(self): - timeout = gajim.config.get('notification_timeout') # in seconds - ntype = self.ntype - if self.kde_notifications: - notification_text = ('' \ - '%(title)s
%(text)s') % {'title': self.title, - 'text': self.text, 'image': self.path_to_image} - gajim_icon = gtkgui_helpers.get_icon_path('gajim', 48) - self.notif.Notify( - dbus.String(_('Gajim')), # app_name (string) - dbus.UInt32(0), # replaces_id (uint) - ntype, # event_id (string) - dbus.String(gajim_icon), # app_icon (string) - dbus.String(''), # summary (string) - dbus.String(notification_text), # body (string) - # actions (stringlist) - (dbus.String('default'), dbus.String(self.event_type), - dbus.String('ignore'), dbus.String(_('Ignore'))), - [], # hints (not used in KDE yet) - dbus.UInt32(timeout*1000), # timeout (int), in ms - reply_handler=self.attach_by_id, - error_handler=self.notify_another_way) - return - version = self.version - if version[:2] == [0, 2]: - actions = {} - if 'actions' in self.capabilities: - actions = {'default': 0} - try: - self.notif.Notify( - dbus.String(_('Gajim')), - dbus.String(self.path_to_image), - dbus.UInt32(0), - ntype, - dbus.Byte(0), - dbus.String(self.title), - dbus.String(self.text), - [dbus.String(self.path_to_image)], - actions, - [''], - True, - dbus.UInt32(timeout), - reply_handler=self.attach_by_id, - error_handler=self.notify_another_way) - except AttributeError: - version = [0, 3, 1] # we're actually dealing with the newer version - if version > [0, 3]: - if gajim.interface.systray_enabled and \ - gajim.config.get('attach_notifications_to_systray'): - status_icon = gajim.interface.systray.status_icon - x, y, width, height = status_icon.get_geometry()[1] - pos_x = x + (width / 2) - pos_y = y + (height / 2) - hints = {'x': pos_x, 'y': pos_y} - else: - hints = {} - if version >= [0, 3, 2]: - hints['urgency'] = dbus.Byte(0) # Low Urgency - hints['category'] = dbus.String(ntype) - # it seems notification-daemon doesn't like empty text - if self.text: - text = self.text - else: - text = ' ' - actions = () - if 'actions' in self.capabilities: - actions = (dbus.String('default'), dbus.String(self.event_type)) - self.notif.Notify( - dbus.String(_('Gajim')), - dbus.UInt32(0), # this notification does not replace other - dbus.String(self.path_to_image), - dbus.String(self.title), - dbus.String(text), - actions, - hints, - dbus.UInt32(timeout*1000), - reply_handler=self.attach_by_id, - error_handler=self.notify_another_way) - else: - self.notif.Notify( - dbus.String(_('Gajim')), - dbus.String(self.path_to_image), - dbus.UInt32(0), - dbus.String(self.title), - dbus.String(self.text), - dbus.String(''), - hints, - dbus.UInt32(timeout*1000), - reply_handler=self.attach_by_id, - error_handler=self.notify_another_way) + def attempt_notify(self): + timeout = gajim.config.get('notification_timeout') # in seconds + ntype = self.ntype + if self.kde_notifications: + notification_text = ('' \ + '%(title)s
%(text)s') % {'title': self.title, + 'text': self.text, 'image': self.path_to_image} + gajim_icon = gtkgui_helpers.get_icon_path('gajim', 48) + self.notif.Notify( + dbus.String(_('Gajim')), # app_name (string) + dbus.UInt32(0), # replaces_id (uint) + ntype, # event_id (string) + dbus.String(gajim_icon), # app_icon (string) + dbus.String(''), # summary (string) + dbus.String(notification_text), # body (string) + # actions (stringlist) + (dbus.String('default'), dbus.String(self.event_type), + dbus.String('ignore'), dbus.String(_('Ignore'))), + [], # hints (not used in KDE yet) + dbus.UInt32(timeout*1000), # timeout (int), in ms + reply_handler=self.attach_by_id, + error_handler=self.notify_another_way) + return + version = self.version + if version[:2] == [0, 2]: + actions = {} + if 'actions' in self.capabilities: + actions = {'default': 0} + try: + self.notif.Notify( + dbus.String(_('Gajim')), + dbus.String(self.path_to_image), + dbus.UInt32(0), + ntype, + dbus.Byte(0), + dbus.String(self.title), + dbus.String(self.text), + [dbus.String(self.path_to_image)], + actions, + [''], + True, + dbus.UInt32(timeout), + reply_handler=self.attach_by_id, + error_handler=self.notify_another_way) + except AttributeError: + version = [0, 3, 1] # we're actually dealing with the newer version + if version > [0, 3]: + if gajim.interface.systray_enabled and \ + gajim.config.get('attach_notifications_to_systray'): + status_icon = gajim.interface.systray.status_icon + x, y, width, height = status_icon.get_geometry()[1] + pos_x = x + (width / 2) + pos_y = y + (height / 2) + hints = {'x': pos_x, 'y': pos_y} + else: + hints = {} + if version >= [0, 3, 2]: + hints['urgency'] = dbus.Byte(0) # Low Urgency + hints['category'] = dbus.String(ntype) + # it seems notification-daemon doesn't like empty text + if self.text: + text = self.text + else: + text = ' ' + actions = () + if 'actions' in self.capabilities: + actions = (dbus.String('default'), dbus.String(self.event_type)) + self.notif.Notify( + dbus.String(_('Gajim')), + dbus.UInt32(0), # this notification does not replace other + dbus.String(self.path_to_image), + dbus.String(self.title), + dbus.String(text), + actions, + hints, + dbus.UInt32(timeout*1000), + reply_handler=self.attach_by_id, + error_handler=self.notify_another_way) + else: + self.notif.Notify( + dbus.String(_('Gajim')), + dbus.String(self.path_to_image), + dbus.UInt32(0), + dbus.String(self.title), + dbus.String(self.text), + dbus.String(''), + hints, + dbus.UInt32(timeout*1000), + reply_handler=self.attach_by_id, + error_handler=self.notify_another_way) - def attach_by_id(self, id_): - self.id = id_ - notification_response_manager.attach_to_interface() - notification_response_manager.add_pending(self.id, self) + def attach_by_id(self, id_): + self.id = id_ + notification_response_manager.attach_to_interface() + notification_response_manager.add_pending(self.id, self) - def notify_another_way(self,e): - gajim.log.debug(str(e)) - gajim.log.debug('Need to implement a new way of falling back') + def notify_another_way(self, e): + gajim.log.debug(str(e)) + gajim.log.debug('Need to implement a new way of falling back') - def on_action_invoked(self, id_, reason): - if self.notif is None: - return - self.notif.CloseNotification(dbus.UInt32(id_)) - self.notif = None + def on_action_invoked(self, id_, reason): + if self.notif is None: + return + self.notif.CloseNotification(dbus.UInt32(id_)) + self.notif = None - if reason == 'ignore': - return + if reason == 'ignore': + return - gajim.interface.handle_event(self.account, self.jid, self.msg_type) + gajim.interface.handle_event(self.account, self.jid, self.msg_type) - def version_reply_handler(self, name, vendor, version, spec_version=None): - if spec_version: - version = spec_version - elif vendor == 'Xfce' and version.startswith('0.1.0'): - version = '0.9' - version_list = version.split('.') - self.version = [] - try: - while len(version_list): - self.version.append(int(version_list.pop(0))) - except ValueError: - self.version_error_handler_3_x_try(None) - self.attempt_notify() + def version_reply_handler(self, name, vendor, version, spec_version=None): + if spec_version: + version = spec_version + elif vendor == 'Xfce' and version.startswith('0.1.0'): + version = '0.9' + version_list = version.split('.') + self.version = [] + try: + while len(version_list): + self.version.append(int(version_list.pop(0))) + except ValueError: + self.version_error_handler_3_x_try(None) + self.attempt_notify() - def get_version(self): - self.notif.GetServerInfo( - reply_handler=self.version_reply_handler, - error_handler=self.version_error_handler_2_x_try) + def get_version(self): + self.notif.GetServerInfo( + reply_handler=self.version_reply_handler, + error_handler=self.version_error_handler_2_x_try) - def version_error_handler_2_x_try(self, e): - self.notif.GetServerInformation(reply_handler=self.version_reply_handler, - error_handler=self.version_error_handler_3_x_try) + def version_error_handler_2_x_try(self, e): + self.notif.GetServerInformation(reply_handler=self.version_reply_handler, + error_handler=self.version_error_handler_3_x_try) - def version_error_handler_3_x_try(self, e): - self.version = self.default_version - self.attempt_notify() - -# vim: se ts=3: + def version_error_handler_3_x_try(self, e): + self.version = self.default_version + self.attempt_notify() diff --git a/src/plugins/gui.py b/src/plugins/gui.py index b183f7371..ab0281e1f 100644 --- a/src/plugins/gui.py +++ b/src/plugins/gui.py @@ -35,186 +35,186 @@ from common import gajim from plugins.helpers import log_calls, log class PluginsWindow(object): - '''Class for Plugins window''' + '''Class for Plugins window''' - @log_calls('PluginsWindow') - def __init__(self): - '''Initialize Plugins window''' - self.xml = gtkgui_helpers.get_gtk_builder('plugins_window.ui') - self.window = self.xml.get_object('plugins_window') - self.window.set_transient_for(gajim.interface.roster.window) - - widgets_to_extract = ('plugins_notebook', - 'plugin_name_label', - 'plugin_version_label', - 'plugin_authors_label', - 'plugin_homepage_linkbutton', - 'plugin_description_textview', - 'uninstall_plugin_button', - 'configure_plugin_button', - 'installed_plugins_treeview') - - for widget_name in widgets_to_extract: - setattr(self, widget_name, self.xml.get_object(widget_name)) + @log_calls('PluginsWindow') + def __init__(self): + '''Initialize Plugins window''' + self.xml = gtkgui_helpers.get_gtk_builder('plugins_window.ui') + self.window = self.xml.get_object('plugins_window') + self.window.set_transient_for(gajim.interface.roster.window) - attr_list = pango.AttrList() - attr_list.insert(pango.AttrWeight(pango.WEIGHT_BOLD, 0, -1)) - self.plugin_name_label.set_attributes(attr_list) - - self.installed_plugins_model = gtk.ListStore(gobject.TYPE_PYOBJECT, - gobject.TYPE_STRING, - gobject.TYPE_BOOLEAN) - self.installed_plugins_treeview.set_model(self.installed_plugins_model) - - renderer = gtk.CellRendererText() - col = gtk.TreeViewColumn(_('Plugin'), renderer, text=1) - self.installed_plugins_treeview.append_column(col) - - renderer = gtk.CellRendererToggle() - renderer.set_property('activatable', True) - renderer.connect('toggled', self.installed_plugins_toggled_cb) - col = gtk.TreeViewColumn(_('Active'), renderer, active=2) - self.installed_plugins_treeview.append_column(col) - - # connect signal for selection change - selection = self.installed_plugins_treeview.get_selection() - selection.connect('changed', - self.installed_plugins_treeview_selection_changed) - selection.set_mode(gtk.SELECTION_SINGLE) - - self._clear_installed_plugin_info() - - self.fill_installed_plugins_model() - - self.xml.connect_signals(self) + widgets_to_extract = ('plugins_notebook', + 'plugin_name_label', + 'plugin_version_label', + 'plugin_authors_label', + 'plugin_homepage_linkbutton', + 'plugin_description_textview', + 'uninstall_plugin_button', + 'configure_plugin_button', + 'installed_plugins_treeview') - self.plugins_notebook.set_current_page(0) - - self.window.show_all() - gtkgui_helpers.possibly_move_window_in_current_desktop(self.window) - - @log_calls('PluginsWindow') - def installed_plugins_treeview_selection_changed(self, treeview_selection): - model, iter = treeview_selection.get_selected() - if iter: - plugin = model.get_value(iter, 0) - plugin_name = model.get_value(iter, 1) - is_active = model.get_value(iter, 2) - - self._display_installed_plugin_info(plugin) - else: - self._clear_installed_plugin_info() - - def _display_installed_plugin_info(self, plugin): - self.plugin_name_label.set_text(plugin.name) - self.plugin_version_label.set_text(plugin.version) - self.plugin_authors_label.set_text(", ".join(plugin.authors)) - self.plugin_homepage_linkbutton.set_uri(plugin.homepage) - self.plugin_homepage_linkbutton.set_label(plugin.homepage) - self.plugin_homepage_linkbutton.set_property('sensitive', True) - - desc_textbuffer = self.plugin_description_textview.get_buffer() - desc_textbuffer.set_text(plugin.description) - self.plugin_description_textview.set_property('sensitive', True) - self.uninstall_plugin_button.set_property('sensitive', True) - if plugin.config_dialog is None: - self.configure_plugin_button.set_property('sensitive', False) - else: - self.configure_plugin_button.set_property('sensitive', True) - - def _clear_installed_plugin_info(self): - self.plugin_name_label.set_text('') - self.plugin_version_label.set_text('') - self.plugin_authors_label.set_text('') - self.plugin_homepage_linkbutton.set_uri('') - self.plugin_homepage_linkbutton.set_label('') - self.plugin_homepage_linkbutton.set_property('sensitive', False) - - desc_textbuffer = self.plugin_description_textview.get_buffer() - desc_textbuffer.set_text('') - self.plugin_description_textview.set_property('sensitive', False) - self.uninstall_plugin_button.set_property('sensitive', False) - self.configure_plugin_button.set_property('sensitive', False) - - @log_calls('PluginsWindow') - def fill_installed_plugins_model(self): - pm = gajim.plugin_manager - self.installed_plugins_model.clear() - self.installed_plugins_model.set_sort_column_id(1, gtk.SORT_ASCENDING) - - for plugin in pm.plugins: - self.installed_plugins_model.append([plugin, - plugin.name, - plugin.active]) - - @log_calls('PluginsWindow') - def installed_plugins_toggled_cb(self, cell, path): - is_active = self.installed_plugins_model[path][2] - plugin = self.installed_plugins_model[path][0] + for widget_name in widgets_to_extract: + setattr(self, widget_name, self.xml.get_object(widget_name)) - if is_active: - gajim.plugin_manager.deactivate_plugin(plugin) - else: - gajim.plugin_manager.activate_plugin(plugin) - - self.installed_plugins_model[path][2] = not is_active - - @log_calls('PluginsWindow') - def on_plugins_window_destroy(self, widget): - '''Close window''' - del gajim.interface.instances['plugins'] + attr_list = pango.AttrList() + attr_list.insert(pango.AttrWeight(pango.WEIGHT_BOLD, 0, -1)) + self.plugin_name_label.set_attributes(attr_list) - @log_calls('PluginsWindow') - def on_close_button_clicked(self, widget): - self.window.destroy() - - @log_calls('PluginsWindow') - def on_configure_plugin_button_clicked(self, widget): - #log.debug('widget: %s'%(widget)) - selection = self.installed_plugins_treeview.get_selection() - model, iter = selection.get_selected() - if iter: - plugin = model.get_value(iter, 0) - plugin_name = model.get_value(iter, 1) - is_active = model.get_value(iter, 2) - + self.installed_plugins_model = gtk.ListStore(gobject.TYPE_PYOBJECT, + gobject.TYPE_STRING, + gobject.TYPE_BOOLEAN) + self.installed_plugins_treeview.set_model(self.installed_plugins_model) - result = plugin.config_dialog.run(self.window) + renderer = gtk.CellRendererText() + col = gtk.TreeViewColumn(_('Plugin'), renderer, text=1) + self.installed_plugins_treeview.append_column(col) + + renderer = gtk.CellRendererToggle() + renderer.set_property('activatable', True) + renderer.connect('toggled', self.installed_plugins_toggled_cb) + col = gtk.TreeViewColumn(_('Active'), renderer, active=2) + self.installed_plugins_treeview.append_column(col) + + # connect signal for selection change + selection = self.installed_plugins_treeview.get_selection() + selection.connect('changed', + self.installed_plugins_treeview_selection_changed) + selection.set_mode(gtk.SELECTION_SINGLE) + + self._clear_installed_plugin_info() + + self.fill_installed_plugins_model() + + self.xml.connect_signals(self) + + self.plugins_notebook.set_current_page(0) + + self.window.show_all() + gtkgui_helpers.possibly_move_window_in_current_desktop(self.window) + + @log_calls('PluginsWindow') + def installed_plugins_treeview_selection_changed(self, treeview_selection): + model, iter = treeview_selection.get_selected() + if iter: + plugin = model.get_value(iter, 0) + plugin_name = model.get_value(iter, 1) + is_active = model.get_value(iter, 2) + + self._display_installed_plugin_info(plugin) + else: + self._clear_installed_plugin_info() + + def _display_installed_plugin_info(self, plugin): + self.plugin_name_label.set_text(plugin.name) + self.plugin_version_label.set_text(plugin.version) + self.plugin_authors_label.set_text(", ".join(plugin.authors)) + self.plugin_homepage_linkbutton.set_uri(plugin.homepage) + self.plugin_homepage_linkbutton.set_label(plugin.homepage) + self.plugin_homepage_linkbutton.set_property('sensitive', True) + + desc_textbuffer = self.plugin_description_textview.get_buffer() + desc_textbuffer.set_text(plugin.description) + self.plugin_description_textview.set_property('sensitive', True) + self.uninstall_plugin_button.set_property('sensitive', True) + if plugin.config_dialog is None: + self.configure_plugin_button.set_property('sensitive', False) + else: + self.configure_plugin_button.set_property('sensitive', True) + + def _clear_installed_plugin_info(self): + self.plugin_name_label.set_text('') + self.plugin_version_label.set_text('') + self.plugin_authors_label.set_text('') + self.plugin_homepage_linkbutton.set_uri('') + self.plugin_homepage_linkbutton.set_label('') + self.plugin_homepage_linkbutton.set_property('sensitive', False) + + desc_textbuffer = self.plugin_description_textview.get_buffer() + desc_textbuffer.set_text('') + self.plugin_description_textview.set_property('sensitive', False) + self.uninstall_plugin_button.set_property('sensitive', False) + self.configure_plugin_button.set_property('sensitive', False) + + @log_calls('PluginsWindow') + def fill_installed_plugins_model(self): + pm = gajim.plugin_manager + self.installed_plugins_model.clear() + self.installed_plugins_model.set_sort_column_id(1, gtk.SORT_ASCENDING) + + for plugin in pm.plugins: + self.installed_plugins_model.append([plugin, + plugin.name, + plugin.active]) + + @log_calls('PluginsWindow') + def installed_plugins_toggled_cb(self, cell, path): + is_active = self.installed_plugins_model[path][2] + plugin = self.installed_plugins_model[path][0] + + if is_active: + gajim.plugin_manager.deactivate_plugin(plugin) + else: + gajim.plugin_manager.activate_plugin(plugin) + + self.installed_plugins_model[path][2] = not is_active + + @log_calls('PluginsWindow') + def on_plugins_window_destroy(self, widget): + '''Close window''' + del gajim.interface.instances['plugins'] + + @log_calls('PluginsWindow') + def on_close_button_clicked(self, widget): + self.window.destroy() + + @log_calls('PluginsWindow') + def on_configure_plugin_button_clicked(self, widget): + #log.debug('widget: %s'%(widget)) + selection = self.installed_plugins_treeview.get_selection() + model, iter = selection.get_selected() + if iter: + plugin = model.get_value(iter, 0) + plugin_name = model.get_value(iter, 1) + is_active = model.get_value(iter, 2) + + + result = plugin.config_dialog.run(self.window) + + else: + # No plugin selected. this should never be reached. As configure + # plugin button should only be clickable when plugin is selected. + # XXX: maybe throw exception here? + pass + + @log_calls('PluginsWindow') + def on_uninstall_plugin_button_clicked(self, widget): + pass - else: - # No plugin selected. this should never be reached. As configure - # plugin button should only be clickable when plugin is selected. - # XXX: maybe throw exception here? - pass - - @log_calls('PluginsWindow') - def on_uninstall_plugin_button_clicked(self, widget): - pass - class GajimPluginConfigDialog(gtk.Dialog): - - @log_calls('GajimPluginConfigDialog') - def __init__(self, plugin, **kwargs): - gtk.Dialog.__init__(self, '%s %s'%(plugin.name, _('Configuration')), **kwargs) - self.plugin = plugin - self.add_button('gtk-close', gtk.RESPONSE_CLOSE) - - self.child.set_spacing(3) - - self.init() - - @log_calls('GajimPluginConfigDialog') - def run(self, parent=None): - self.set_transient_for(parent) - self.on_run() - self.show_all() - result = super(GajimPluginConfigDialog, self).run() - self.hide() - return result - def init(self): - pass - - def on_run(self): - pass + @log_calls('GajimPluginConfigDialog') + def __init__(self, plugin, **kwargs): + gtk.Dialog.__init__(self, '%s %s'%(plugin.name, _('Configuration')), **kwargs) + self.plugin = plugin + self.add_button('gtk-close', gtk.RESPONSE_CLOSE) + + self.child.set_spacing(3) + + self.init() + + @log_calls('GajimPluginConfigDialog') + def run(self, parent=None): + self.set_transient_for(parent) + self.on_run() + self.show_all() + result = super(GajimPluginConfigDialog, self).run() + self.hide() + return result + + def init(self): + pass + + def on_run(self): + pass diff --git a/src/plugins/helpers.py b/src/plugins/helpers.py index ac58f06db..6b62fa3ec 100644 --- a/src/plugins/helpers.py +++ b/src/plugins/helpers.py @@ -49,14 +49,14 @@ class log_calls(object): ''' Decorator class for functions to easily log when they are entered and left. ''' - + filter_out_classes = ['GajimPluginConfig', 'PluginManager', 'GajimPluginConfigDialog', 'PluginsWindow'] ''' List of classes from which no logs should be emited when methods are called, eventhough `log_calls` decorator is used. ''' - + def __init__(self, classname='', log=log): ''' :Keywords: @@ -65,46 +65,46 @@ class log_calls(object): log : logging.Logger Logger to use when outputing debug information on when function has been entered and when left. By default: `plugins.helpers.log` - is used. + is used. ''' - + self.full_func_name = '' ''' - Full name of function, with class name (as prefix) if given + Full name of function, with class name (as prefix) if given to decorator. - + Otherwise, it's only function name retrieved from function object for which decorator was called. - + :type: str ''' self.log_this_class = True ''' Determines whether wrapper of given function should log calls of this function or not. - + :type: bool ''' - + if classname: self.full_func_name = classname+'.' - + if classname in self.filter_out_classes: self.log_this_class = False - + def __call__(self, f): ''' :param f: function to be wrapped with logging statements - + :return: given function wrapped by *log.debug* statements :rtype: function ''' - + self.full_func_name += f.func_name if self.log_this_class: @functools.wraps(f) def wrapper(*args, **kwargs): - + log.debug('%(funcname)s() '%{ 'funcname': self.full_func_name}) result = f(*args, **kwargs) @@ -116,25 +116,25 @@ class log_calls(object): def wrapper(*args, **kwargs): result = f(*args, **kwargs) return result - + return wrapper class Singleton(type): ''' Singleton metaclass. ''' - def __init__(cls,name,bases,dic): - super(Singleton,cls).__init__(name,bases,dic) + def __init__(cls, name, bases, dic): + super(Singleton, cls).__init__(name, bases, dic) cls.instance=None - + def __call__(cls,*args,**kw): if cls.instance is None: - cls.instance=super(Singleton,cls).__call__(*args,**kw) + cls.instance=super(Singleton, cls).__call__(*args,**kw) #log.debug('%(classname)s - new instance created'%{ #'classname' : cls.__name__}) else: pass #log.debug('%(classname)s - returning already existing instance'%{ #'classname' : cls.__name__}) - - return cls.instance \ No newline at end of file + + return cls.instance diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py index b30d7c7e7..19ca1ce5e 100644 --- a/src/plugins/plugin.py +++ b/src/plugins/plugin.py @@ -33,202 +33,202 @@ from plugins.gui import GajimPluginConfigDialog class GajimPlugin(object): - ''' - Base class for implementing Gajim plugins. - ''' - name = u'' - ''' - Name of plugin. - - Will be shown in plugins management GUI. - - :type: unicode - ''' - short_name = u'' - ''' - Short name of plugin. - - Used for quick indentification of plugin. + ''' + Base class for implementing Gajim plugins. + ''' + name = u'' + ''' + Name of plugin. - :type: unicode - - :todo: decide whether we really need this one, because class name (with - module name) can act as such short name - ''' - version = u'' - ''' - Version of plugin. - - :type: unicode - - :todo: decide how to compare version between each other (which one - is higher). Also rethink: do we really need to compare versions - of plugins between each other? This would be only useful if we detect - same plugin class but with different version and we want only the newest - one to be active - is such policy good? - ''' - description = u'' - ''' - Plugin description. - - :type: unicode - - :todo: should be allow rich text here (like HTML or reStructuredText)? - ''' - authors = [] - ''' - Plugin authors. - - :type: [] of unicode - - :todo: should we decide on any particular format of author strings? - Especially: should we force format of giving author's e-mail? - ''' - homepage = u'' - ''' - URL to plug-in's homepage. - - :type: unicode - - :todo: should we check whether provided string is valid URI? (Maybe - using 'property') - ''' - gui_extension_points = {} - ''' - Extension points that plugin wants to connect with and handlers to be used. - - Keys of this string should be strings with name of GUI extension point - to handles. Values should be 2-element tuples with references to handling - functions. First function will be used to connect plugin with extpoint, - the second one to successfuly disconnect from it. Connecting takes places - when plugin is activated and extpoint already exists, or when plugin is - already activated but extpoint is being created (eg. chat window opens). - Disconnecting takes place when plugin is deactivated and extpoint exists - or when extpoint is destroyed and plugin is activate (eg. chat window - closed). - ''' - config_default_values = {} - ''' - Default values for keys that should be stored in plug-in config. - - This dict is used when when someone calls for config option but it has not - been set yet. - - Values are tuples: (default_value, option_description). The first one can - be anything (this is the advantage of using shelve/pickle instead of - custom-made config I/O handling); the second one should be unicode (gettext - can be used if need and/or translation is planned). - - :type: {} of 2-element tuples - ''' - events_handlers = {} - ''' - Dictionary with events handlers. - - Keys are event names. Values should be 2-element tuples with handler - priority as first element and reference to handler function as second - element. Priority is integer. See `ged` module for predefined priorities - like `ged.PRECORE`, `ged.CORE` or `ged.POSTCORE`. - - :type: {} with 2-element tuples - ''' - events = [] - ''' - New network event classes to be registered in Network Events Controller. - - :type: [] of `nec.NetworkIncomingEvent` or `nec.NetworkOutgoingEvent` - subclasses. - ''' - - @log_calls('GajimPlugin') - def __init__(self): - self.config = GajimPluginConfig(self) - ''' - Plug-in configuration dictionary. - - Automatically saved and loaded and plug-in (un)load. - - :type: `plugins.plugin.GajimPluginConfig` - ''' - self.load_config() - self.config_dialog = GajimPluginConfigDialog(self) - self.init() - - @log_calls('GajimPlugin') - def save_config(self): - self.config.save() - - @log_calls('GajimPlugin') - def load_config(self): - self.config.load() - - def __eq__(self, plugin): - if self.short_name == plugin.short_name: - return True - - return False - - def __ne__(self, plugin): - if self.short_name != plugin.short_name: - return True - - return False - - @log_calls('GajimPlugin') - def local_file_path(self, file_name): - return os.path.join(self.__path__, file_name) + Will be shown in plugins management GUI. + + :type: unicode + ''' + short_name = u'' + ''' + Short name of plugin. + + Used for quick indentification of plugin. + + :type: unicode + + :todo: decide whether we really need this one, because class name (with + module name) can act as such short name + ''' + version = u'' + ''' + Version of plugin. + + :type: unicode + + :todo: decide how to compare version between each other (which one + is higher). Also rethink: do we really need to compare versions + of plugins between each other? This would be only useful if we detect + same plugin class but with different version and we want only the newest + one to be active - is such policy good? + ''' + description = u'' + ''' + Plugin description. + + :type: unicode + + :todo: should be allow rich text here (like HTML or reStructuredText)? + ''' + authors = [] + ''' + Plugin authors. + + :type: [] of unicode + + :todo: should we decide on any particular format of author strings? + Especially: should we force format of giving author's e-mail? + ''' + homepage = u'' + ''' + URL to plug-in's homepage. + + :type: unicode + + :todo: should we check whether provided string is valid URI? (Maybe + using 'property') + ''' + gui_extension_points = {} + ''' + Extension points that plugin wants to connect with and handlers to be used. + + Keys of this string should be strings with name of GUI extension point + to handles. Values should be 2-element tuples with references to handling + functions. First function will be used to connect plugin with extpoint, + the second one to successfuly disconnect from it. Connecting takes places + when plugin is activated and extpoint already exists, or when plugin is + already activated but extpoint is being created (eg. chat window opens). + Disconnecting takes place when plugin is deactivated and extpoint exists + or when extpoint is destroyed and plugin is activate (eg. chat window + closed). + ''' + config_default_values = {} + ''' + Default values for keys that should be stored in plug-in config. + + This dict is used when when someone calls for config option but it has not + been set yet. + + Values are tuples: (default_value, option_description). The first one can + be anything (this is the advantage of using shelve/pickle instead of + custom-made config I/O handling); the second one should be unicode (gettext + can be used if need and/or translation is planned). + + :type: {} of 2-element tuples + ''' + events_handlers = {} + ''' + Dictionary with events handlers. + + Keys are event names. Values should be 2-element tuples with handler + priority as first element and reference to handler function as second + element. Priority is integer. See `ged` module for predefined priorities + like `ged.PRECORE`, `ged.CORE` or `ged.POSTCORE`. + + :type: {} with 2-element tuples + ''' + events = [] + ''' + New network event classes to be registered in Network Events Controller. + + :type: [] of `nec.NetworkIncomingEvent` or `nec.NetworkOutgoingEvent` + subclasses. + ''' + + @log_calls('GajimPlugin') + def __init__(self): + self.config = GajimPluginConfig(self) + ''' + Plug-in configuration dictionary. + + Automatically saved and loaded and plug-in (un)load. + + :type: `plugins.plugin.GajimPluginConfig` + ''' + self.load_config() + self.config_dialog = GajimPluginConfigDialog(self) + self.init() + + @log_calls('GajimPlugin') + def save_config(self): + self.config.save() + + @log_calls('GajimPlugin') + def load_config(self): + self.config.load() + + def __eq__(self, plugin): + if self.short_name == plugin.short_name: + return True + + return False + + def __ne__(self, plugin): + if self.short_name != plugin.short_name: + return True + + return False + + @log_calls('GajimPlugin') + def local_file_path(self, file_name): + return os.path.join(self.__path__, file_name) + + @log_calls('GajimPlugin') + def init(self): + pass + + @log_calls('GajimPlugin') + def activate(self): + pass + + @log_calls('GajimPlugin') + def deactivate(self): + pass - @log_calls('GajimPlugin') - def init(self): - pass - - @log_calls('GajimPlugin') - def activate(self): - pass - - @log_calls('GajimPlugin') - def deactivate(self): - pass - import shelve import UserDict class GajimPluginConfig(UserDict.DictMixin): - @log_calls('GajimPluginConfig') - def __init__(self, plugin): - self.plugin = plugin - self.FILE_PATH = os.path.join(gajim.PLUGINS_CONFIG_DIR, self.plugin.short_name) - #log.debug('FILE_PATH = %s'%(self.FILE_PATH)) - self.data = None - self.load() - - @log_calls('GajimPluginConfig') - def __getitem__(self, key): - if not key in self.data: - self.data[key] = self.plugin.config_default_values[key][0] - self.save() - - return self.data[key] - - @log_calls('GajimPluginConfig') - def __setitem__(self, key, value): - self.data[key] = value - self.save() - - def keys(self): - return self.data.keys() - - @log_calls('GajimPluginConfig') - def save(self): - self.data.sync() - #log.debug(str(self.data)) + @log_calls('GajimPluginConfig') + def __init__(self, plugin): + self.plugin = plugin + self.FILE_PATH = os.path.join(gajim.PLUGINS_CONFIG_DIR, self.plugin.short_name) + #log.debug('FILE_PATH = %s'%(self.FILE_PATH)) + self.data = None + self.load() - @log_calls('GajimPluginConfig') - def load(self): - self.data = shelve.open(self.FILE_PATH) + @log_calls('GajimPluginConfig') + def __getitem__(self, key): + if not key in self.data: + self.data[key] = self.plugin.config_default_values[key][0] + self.save() + + return self.data[key] + + @log_calls('GajimPluginConfig') + def __setitem__(self, key, value): + self.data[key] = value + self.save() + + def keys(self): + return self.data.keys() + + @log_calls('GajimPluginConfig') + def save(self): + self.data.sync() + #log.debug(str(self.data)) + + @log_calls('GajimPluginConfig') + def load(self): + self.data = shelve.open(self.FILE_PATH) class GajimPluginException(Exception): - pass + pass class GajimPluginInitError(GajimPluginException): - pass + pass diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py index 1b1eb7cf3..a0708c89c 100644 --- a/src/plugins/pluginmanager.py +++ b/src/plugins/pluginmanager.py @@ -37,420 +37,420 @@ from plugins.helpers import log, log_calls, Singleton from plugins.plugin import GajimPlugin class PluginManager(object): - ''' - Main plug-in management class. - - Currently: - - scans for plugins - - activates them - - handles GUI extension points, when called by GUI objects after plugin - is activated (by dispatching info about call to handlers in plugins) - - :todo: add more info about how GUI extension points work - :todo: add list of available GUI extension points - :todo: implement mechanism to dynamically load plugins where GUI extension - points have been already called (i.e. when plugin is activated - after GUI object creation). [DONE?] - :todo: implement mechanism to dynamically deactive plugins (call plugin's - deactivation handler) [DONE?] - :todo: when plug-in is deactivated all GUI extension points are removed - from `PluginManager.gui_extension_points_handlers`. But when - object that invoked GUI extension point is abandoned by Gajim, eg. - closed ChatControl object, the reference to called GUI extension - points is still in `PluginManager.gui_extension_points`. These - should be removed, so that object can be destroyed by Python. - Possible solution: add call to clean up method in classes - 'destructors' (classes that register GUI extension points) - ''' - - __metaclass__ = Singleton + ''' + Main plug-in management class. - #@log_calls('PluginManager') - def __init__(self): - self.plugins = [] - ''' - Detected plugin classes. - - Each class object in list is `GajimPlugin` subclass. - - :type: [] of class objects - ''' - self.active_plugins = [] - ''' - Instance objects of active plugins. - - These are object instances of classes held `plugins`, but only those - that were activated. - - :type: [] of `GajimPlugin` based objects - ''' - self.gui_extension_points = {} - ''' - Registered GUI extension points. - ''' - - self.gui_extension_points_handlers = {} - ''' - Registered handlers of GUI extension points. - ''' + Currently: + - scans for plugins + - activates them + - handles GUI extension points, when called by GUI objects after plugin + is activated (by dispatching info about call to handlers in plugins) - for path in gajim.PLUGINS_DIRS: - self.add_plugins(PluginManager.scan_dir_for_plugins(path)) + :todo: add more info about how GUI extension points work + :todo: add list of available GUI extension points + :todo: implement mechanism to dynamically load plugins where GUI extension + points have been already called (i.e. when plugin is activated + after GUI object creation). [DONE?] + :todo: implement mechanism to dynamically deactive plugins (call plugin's + deactivation handler) [DONE?] + :todo: when plug-in is deactivated all GUI extension points are removed + from `PluginManager.gui_extension_points_handlers`. But when + object that invoked GUI extension point is abandoned by Gajim, eg. + closed ChatControl object, the reference to called GUI extension + points is still in `PluginManager.gui_extension_points`. These + should be removed, so that object can be destroyed by Python. + Possible solution: add call to clean up method in classes + 'destructors' (classes that register GUI extension points) + ''' - #log.debug('plugins: %s'%(self.plugins)) + __metaclass__ = Singleton - self._activate_all_plugins_from_global_config() + #@log_calls('PluginManager') + def __init__(self): + self.plugins = [] + ''' + Detected plugin classes. - #log.debug('active: %s'%(self.active_plugins)) + Each class object in list is `GajimPlugin` subclass. - @log_calls('PluginManager') - def _plugin_has_entry_in_global_config(self, plugin): - if gajim.config.get_per('plugins', plugin.short_name) is None: - return False - else: - return True - - @log_calls('PluginManager') - def _create_plugin_entry_in_global_config(self, plugin): - gajim.config.add_per('plugins', plugin.short_name) - - @log_calls('PluginManager') - def add_plugin(self, plugin_class): - ''' - :todo: what about adding plug-ins that are already added? Module reload - and adding class from reloaded module or ignoring adding plug-in? - ''' - plugin = plugin_class() - - if plugin not in self.plugins: - if not self._plugin_has_entry_in_global_config(plugin): - self._create_plugin_entry_in_global_config(plugin) - - self.plugins.append(plugin) - plugin.active = False - else: - log.info('Not loading plugin %s v%s from module %s (identified by short name: %s). Plugin already loaded.'%( - plugin.name, plugin.version, plugin.__module__, plugin.short_name)) - - @log_calls('PluginManager') - def add_plugins(self, plugin_classes): - for plugin_class in plugin_classes: - self.add_plugin(plugin_class) - - @log_calls('PluginManager') - def gui_extension_point(self, gui_extpoint_name, *args): - ''' - Invokes all handlers (from plugins) for particular GUI extension point - and adds it to collection for further processing (eg. by plugins not active - yet). - - :param gui_extpoint_name: name of GUI extension point. - :type gui_extpoint_name: unicode - :param args: parameters to be passed to extension point handlers - (typically and object that invokes `gui_extension_point`; however, - this can be practically anything) - :type args: tuple + :type: [] of class objects + ''' + self.active_plugins = [] + ''' + Instance objects of active plugins. - :todo: GUI extension points must be documented well - names with - parameters that will be passed to handlers (in plugins). Such - documentation must be obeyed both in core and in plugins. This - is a loosely coupled approach and is pretty natural in Python. - - :bug: what if only some handlers are successfully connected? we should - revert all those connections that where successfully made. Maybe - call 'self._deactivate_plugin()' or sth similar. - Looking closer - we only rewrite tuples here. Real check should be - made in method that invokes gui_extpoints handlers. - ''' + These are object instances of classes held `plugins`, but only those + that were activated. - self._add_gui_extension_point_call_to_list(gui_extpoint_name, *args) - self._execute_all_handlers_of_gui_extension_point(gui_extpoint_name, *args) - - @log_calls('PluginManager') - def remove_gui_extension_point(self, gui_extpoint_name, *args): - ''' - Removes GUI extension point from collection held by `PluginManager`. - - From this point this particular extension point won't be visible - to plugins (eg. it won't invoke any handlers when plugin is activated). - - GUI extension point is removed completely (there is no way to recover it - from inside `PluginManager`). - - Removal is needed when instance object that given extension point was - connect with is destroyed (eg. ChatControl is closed or context menu - is hidden). - - Each `PluginManager.gui_extension_point` call should have a call of - `PluginManager.remove_gui_extension_point` related to it. + :type: [] of `GajimPlugin` based objects + ''' + self.gui_extension_points = {} + ''' + Registered GUI extension points. + ''' - :note: in current implementation different arguments mean different - extension points. The same arguments and the same name mean - the same extension point. - :todo: instead of using argument to identify which extpoint should be - removed, maybe add additional 'id' argument - this would work similar - hash in Python objects. 'id' would be calculated based on arguments - passed or on anything else (even could be constant). This would give - core developers (that add new extpoints) more freedom, but is this - necessary? - - :param gui_extpoint_name: name of GUI extension point. - :type gui_extpoint_name: unicode - :param args: arguments that `PluginManager.gui_extension_point` was - called with for this extension point. This is used (along with - extension point name) to identify element to be removed. - :type args: tuple - ''' + self.gui_extension_points_handlers = {} + ''' + Registered handlers of GUI extension points. + ''' - if gui_extpoint_name in self.gui_extension_points: - #log.debug('Removing GUI extpoint\n name: %s\n args: %s'%(gui_extpoint_name, args)) - self.gui_extension_points[gui_extpoint_name].remove(args) - - - @log_calls('PluginManager') - def _add_gui_extension_point_call_to_list(self, gui_extpoint_name, *args): - ''' - Adds GUI extension point call to list of calls. - - This is done only if such call hasn't been added already - (same extension point name and same arguments). - - :note: This is assumption that GUI extension points are different only - if they have different name or different arguments. - - :param gui_extpoint_name: GUI extension point name used to identify it - by plugins. - :type gui_extpoint_name: str - - :param args: parameters to be passed to extension point handlers - (typically and object that invokes `gui_extension_point`; however, - this can be practically anything) - :type args: tuple - - ''' - if ((gui_extpoint_name not in self.gui_extension_points) - or (args not in self.gui_extension_points[gui_extpoint_name])): - self.gui_extension_points.setdefault(gui_extpoint_name, []).append(args) - - @log_calls('PluginManager') - def _execute_all_handlers_of_gui_extension_point(self, gui_extpoint_name, *args): - if gui_extpoint_name in self.gui_extension_points_handlers: - for handlers in self.gui_extension_points_handlers[gui_extpoint_name]: - handlers[0](*args) - - def _register_events_handlers_in_ged(self, plugin): - for event_name, handler in plugin.events_handlers.iteritems(): - priority = handler[0] - handler_function = handler[1] - gajim.ged.register_event_handler(event_name, - priority, - handler_function) - - def _remove_events_handler_from_ged(self, plugin): - for event_name, handler in plugin.events_handlers.iteritems(): - priority = handler[0] - handler_function = handler[1] - gajim.ged.remove_event_handler(event_name, - priority, - handler_function) - - def _register_network_events_in_nec(self, plugin): - for event_class in plugin.events: - setattr(event_class, 'plugin', plugin) - if issubclass(event_class, nec.NetworkIncomingEvent): - gajim.nec.register_incoming_event(event_class) - elif issubclass(event_class, nec.NetworkOutgoingEvent): - gajim.nec.register_outgoing_event(event_class) - - def _remove_network_events_from_nec(self, plugin): - for event_class in plugin.events: - if issubclass(event_class, nec.NetworkIncomingEvent): - gajim.nec.unregister_incoming_event(event_class) - elif issubclass(event_class, nec.NetworkOutgoingEvent): - gajim.nec.unregister_outgoing_event(event_class) + for path in gajim.PLUGINS_DIRS: + self.add_plugins(PluginManager.scan_dir_for_plugins(path)) - @log_calls('PluginManager') - def activate_plugin(self, plugin): - ''' - :param plugin: plugin to be activated - :type plugin: class object of `GajimPlugin` subclass - - :todo: success checks should be implemented using exceptions. Such - control should also be implemented in deactivation. Exceptions - should be shown to user inside popup dialog, so the reason - for not activating plugin is known. - ''' - success = False - if not plugin.active: - - self._add_gui_extension_points_handlers_from_plugin(plugin) - self._handle_all_gui_extension_points_with_plugin(plugin) - self._register_events_handlers_in_ged(plugin) - self._register_network_events_in_nec(plugin) - - success = True - - if success: - self.active_plugins.append(plugin) - plugin.activate() - self._set_plugin_active_in_global_config(plugin) - plugin.active = True + #log.debug('plugins: %s'%(self.plugins)) - return success - - def deactivate_plugin(self, plugin): - # remove GUI extension points handlers (provided by plug-in) from - # handlers list - for gui_extpoint_name, gui_extpoint_handlers in \ - plugin.gui_extension_points.iteritems(): - self.gui_extension_points_handlers[gui_extpoint_name].remove(gui_extpoint_handlers) - - # detaching plug-in from handler GUI extension points (calling - # cleaning up method that must be provided by plug-in developer - # for each handled GUI extension point) - for gui_extpoint_name, gui_extpoint_handlers in \ - plugin.gui_extension_points.iteritems(): - if gui_extpoint_name in self.gui_extension_points: - for gui_extension_point_args in self.gui_extension_points[gui_extpoint_name]: - handler = gui_extpoint_handlers[1] - if handler: - handler(*gui_extension_point_args) - - self._remove_events_handler_from_ged(plugin) - self._remove_network_events_from_nec(plugin) - - # removing plug-in from active plug-ins list - plugin.deactivate() - self.active_plugins.remove(plugin) - self._set_plugin_active_in_global_config(plugin, False) - plugin.active = False - - def _deactivate_all_plugins(self): - for plugin_object in self.active_plugins: - self.deactivate_plugin(plugin_object) - - @log_calls('PluginManager') - def _add_gui_extension_points_handlers_from_plugin(self, plugin): - for gui_extpoint_name, gui_extpoint_handlers in \ - plugin.gui_extension_points.iteritems(): - self.gui_extension_points_handlers.setdefault(gui_extpoint_name, []).append( - gui_extpoint_handlers) - - @log_calls('PluginManager') - def _handle_all_gui_extension_points_with_plugin(self, plugin): - for gui_extpoint_name, gui_extpoint_handlers in \ - plugin.gui_extension_points.iteritems(): - if gui_extpoint_name in self.gui_extension_points: - for gui_extension_point_args in self.gui_extension_points[gui_extpoint_name]: - handler = gui_extpoint_handlers[0] - if handler: - handler(*gui_extension_point_args) + self._activate_all_plugins_from_global_config() - @log_calls('PluginManager') - def _activate_all_plugins(self): - ''' - Activates all plugins in `plugins`. - - Activated plugins are appended to `active_plugins` list. - ''' - #self.active_plugins = [] - for plugin in self.plugins: - self.activate_plugin(plugin) - - def _activate_all_plugins_from_global_config(self): - for plugin in self.plugins: - if self._plugin_is_active_in_global_config(plugin): - self.activate_plugin(plugin) - - def _plugin_is_active_in_global_config(self, plugin): - return gajim.config.get_per('plugins', plugin.short_name, 'active') - - def _set_plugin_active_in_global_config(self, plugin, active=True): - gajim.config.set_per('plugins', plugin.short_name, 'active', active) + #log.debug('active: %s'%(self.active_plugins)) - @staticmethod - @log_calls('PluginManager') - def scan_dir_for_plugins(path): - ''' - Scans given directory for plugin classes. - - :param path: directory to scan for plugins - :type path: unicode - - :return: list of found plugin classes (subclasses of `GajimPlugin` - :rtype: [] of class objects - - :note: currently it only searches for plugin classes in '\*.py' files - present in given direcotory `path` (no recursion here) - - :todo: add scanning packages - :todo: add scanning zipped modules - ''' - plugins_found = [] - if os.path.isdir(path): - dir_list = os.listdir(path) - #log.debug(dir_list) + @log_calls('PluginManager') + def _plugin_has_entry_in_global_config(self, plugin): + if gajim.config.get_per('plugins', plugin.short_name) is None: + return False + else: + return True - sys.path.insert(0, path) - #log.debug(sys.path) + @log_calls('PluginManager') + def _create_plugin_entry_in_global_config(self, plugin): + gajim.config.add_per('plugins', plugin.short_name) - for elem_name in dir_list: - #log.debug('- "%s"'%(elem_name)) - file_path = os.path.join(path, elem_name) - #log.debug(' "%s"'%(file_path)) - - module = None - - if os.path.isfile(file_path) and fnmatch.fnmatch(file_path,'*.py'): - module_name = os.path.splitext(elem_name)[0] - #log.debug('Possible module detected.') - try: - module = __import__(module_name) - #log.debug('Module imported.') - except ValueError, value_error: - pass - #log.debug('Module not imported successfully. ValueError: %s'%(value_error)) - except ImportError, import_error: - pass - #log.debug('Module not imported successfully. ImportError: %s'%(import_error)) - - elif os.path.isdir(file_path): - module_name = elem_name - file_path += os.path.sep - #log.debug('Possible package detected.') - try: - module = __import__(module_name) - #log.debug('Package imported.') - except ValueError, value_error: - pass - #log.debug('Package not imported successfully. ValueError: %s'%(value_error)) - except ImportError, import_error: - pass - #log.debug('Package not imported successfully. ImportError: %s'%(import_error)) - - - if module: - log.debug('Attributes processing started') - for module_attr_name in [attr_name for attr_name in dir(module) - if not (attr_name.startswith('__') or - attr_name.endswith('__'))]: - module_attr = getattr(module, module_attr_name) - log.debug('%s : %s'%(module_attr_name, module_attr)) - - try: - if issubclass(module_attr, GajimPlugin) and \ - not module_attr is GajimPlugin: - log.debug('is subclass of GajimPlugin') - #log.debug('file_path: %s\nabspath: %s\ndirname: %s'%(file_path, os.path.abspath(file_path), os.path.dirname(os.path.abspath(file_path)))) - #log.debug('file_path: %s\ndirname: %s\nabspath: %s'%(file_path, os.path.dirname(file_path), os.path.abspath(os.path.dirname(file_path)))) - module_attr.__path__ = os.path.abspath(os.path.dirname(file_path)) - plugins_found.append(module_attr) - except TypeError, type_error: - pass - #log.debug('module_attr: %s, error : %s'%( - #module_name+'.'+module_attr_name, - #type_error)) + @log_calls('PluginManager') + def add_plugin(self, plugin_class): + ''' + :todo: what about adding plug-ins that are already added? Module reload + and adding class from reloaded module or ignoring adding plug-in? + ''' + plugin = plugin_class() - #log.debug(module) + if plugin not in self.plugins: + if not self._plugin_has_entry_in_global_config(plugin): + self._create_plugin_entry_in_global_config(plugin) - return plugins_found \ No newline at end of file + self.plugins.append(plugin) + plugin.active = False + else: + log.info('Not loading plugin %s v%s from module %s (identified by short name: %s). Plugin already loaded.'%( + plugin.name, plugin.version, plugin.__module__, plugin.short_name)) + + @log_calls('PluginManager') + def add_plugins(self, plugin_classes): + for plugin_class in plugin_classes: + self.add_plugin(plugin_class) + + @log_calls('PluginManager') + def gui_extension_point(self, gui_extpoint_name, *args): + ''' + Invokes all handlers (from plugins) for particular GUI extension point + and adds it to collection for further processing (eg. by plugins not active + yet). + + :param gui_extpoint_name: name of GUI extension point. + :type gui_extpoint_name: unicode + :param args: parameters to be passed to extension point handlers + (typically and object that invokes `gui_extension_point`; however, + this can be practically anything) + :type args: tuple + + :todo: GUI extension points must be documented well - names with + parameters that will be passed to handlers (in plugins). Such + documentation must be obeyed both in core and in plugins. This + is a loosely coupled approach and is pretty natural in Python. + + :bug: what if only some handlers are successfully connected? we should + revert all those connections that where successfully made. Maybe + call 'self._deactivate_plugin()' or sth similar. + Looking closer - we only rewrite tuples here. Real check should be + made in method that invokes gui_extpoints handlers. + ''' + + self._add_gui_extension_point_call_to_list(gui_extpoint_name, *args) + self._execute_all_handlers_of_gui_extension_point(gui_extpoint_name, *args) + + @log_calls('PluginManager') + def remove_gui_extension_point(self, gui_extpoint_name, *args): + ''' + Removes GUI extension point from collection held by `PluginManager`. + + From this point this particular extension point won't be visible + to plugins (eg. it won't invoke any handlers when plugin is activated). + + GUI extension point is removed completely (there is no way to recover it + from inside `PluginManager`). + + Removal is needed when instance object that given extension point was + connect with is destroyed (eg. ChatControl is closed or context menu + is hidden). + + Each `PluginManager.gui_extension_point` call should have a call of + `PluginManager.remove_gui_extension_point` related to it. + + :note: in current implementation different arguments mean different + extension points. The same arguments and the same name mean + the same extension point. + :todo: instead of using argument to identify which extpoint should be + removed, maybe add additional 'id' argument - this would work similar + hash in Python objects. 'id' would be calculated based on arguments + passed or on anything else (even could be constant). This would give + core developers (that add new extpoints) more freedom, but is this + necessary? + + :param gui_extpoint_name: name of GUI extension point. + :type gui_extpoint_name: unicode + :param args: arguments that `PluginManager.gui_extension_point` was + called with for this extension point. This is used (along with + extension point name) to identify element to be removed. + :type args: tuple + ''' + + if gui_extpoint_name in self.gui_extension_points: + #log.debug('Removing GUI extpoint\n name: %s\n args: %s'%(gui_extpoint_name, args)) + self.gui_extension_points[gui_extpoint_name].remove(args) + + + @log_calls('PluginManager') + def _add_gui_extension_point_call_to_list(self, gui_extpoint_name, *args): + ''' + Adds GUI extension point call to list of calls. + + This is done only if such call hasn't been added already + (same extension point name and same arguments). + + :note: This is assumption that GUI extension points are different only + if they have different name or different arguments. + + :param gui_extpoint_name: GUI extension point name used to identify it + by plugins. + :type gui_extpoint_name: str + + :param args: parameters to be passed to extension point handlers + (typically and object that invokes `gui_extension_point`; however, + this can be practically anything) + :type args: tuple + + ''' + if ((gui_extpoint_name not in self.gui_extension_points) + or (args not in self.gui_extension_points[gui_extpoint_name])): + self.gui_extension_points.setdefault(gui_extpoint_name, []).append(args) + + @log_calls('PluginManager') + def _execute_all_handlers_of_gui_extension_point(self, gui_extpoint_name, *args): + if gui_extpoint_name in self.gui_extension_points_handlers: + for handlers in self.gui_extension_points_handlers[gui_extpoint_name]: + handlers[0](*args) + + def _register_events_handlers_in_ged(self, plugin): + for event_name, handler in plugin.events_handlers.iteritems(): + priority = handler[0] + handler_function = handler[1] + gajim.ged.register_event_handler(event_name, + priority, + handler_function) + + def _remove_events_handler_from_ged(self, plugin): + for event_name, handler in plugin.events_handlers.iteritems(): + priority = handler[0] + handler_function = handler[1] + gajim.ged.remove_event_handler(event_name, + priority, + handler_function) + + def _register_network_events_in_nec(self, plugin): + for event_class in plugin.events: + setattr(event_class, 'plugin', plugin) + if issubclass(event_class, nec.NetworkIncomingEvent): + gajim.nec.register_incoming_event(event_class) + elif issubclass(event_class, nec.NetworkOutgoingEvent): + gajim.nec.register_outgoing_event(event_class) + + def _remove_network_events_from_nec(self, plugin): + for event_class in plugin.events: + if issubclass(event_class, nec.NetworkIncomingEvent): + gajim.nec.unregister_incoming_event(event_class) + elif issubclass(event_class, nec.NetworkOutgoingEvent): + gajim.nec.unregister_outgoing_event(event_class) + + @log_calls('PluginManager') + def activate_plugin(self, plugin): + ''' + :param plugin: plugin to be activated + :type plugin: class object of `GajimPlugin` subclass + + :todo: success checks should be implemented using exceptions. Such + control should also be implemented in deactivation. Exceptions + should be shown to user inside popup dialog, so the reason + for not activating plugin is known. + ''' + success = False + if not plugin.active: + + self._add_gui_extension_points_handlers_from_plugin(plugin) + self._handle_all_gui_extension_points_with_plugin(plugin) + self._register_events_handlers_in_ged(plugin) + self._register_network_events_in_nec(plugin) + + success = True + + if success: + self.active_plugins.append(plugin) + plugin.activate() + self._set_plugin_active_in_global_config(plugin) + plugin.active = True + + return success + + def deactivate_plugin(self, plugin): + # remove GUI extension points handlers (provided by plug-in) from + # handlers list + for gui_extpoint_name, gui_extpoint_handlers in \ + plugin.gui_extension_points.iteritems(): + self.gui_extension_points_handlers[gui_extpoint_name].remove(gui_extpoint_handlers) + + # detaching plug-in from handler GUI extension points (calling + # cleaning up method that must be provided by plug-in developer + # for each handled GUI extension point) + for gui_extpoint_name, gui_extpoint_handlers in \ + plugin.gui_extension_points.iteritems(): + if gui_extpoint_name in self.gui_extension_points: + for gui_extension_point_args in self.gui_extension_points[gui_extpoint_name]: + handler = gui_extpoint_handlers[1] + if handler: + handler(*gui_extension_point_args) + + self._remove_events_handler_from_ged(plugin) + self._remove_network_events_from_nec(plugin) + + # removing plug-in from active plug-ins list + plugin.deactivate() + self.active_plugins.remove(plugin) + self._set_plugin_active_in_global_config(plugin, False) + plugin.active = False + + def _deactivate_all_plugins(self): + for plugin_object in self.active_plugins: + self.deactivate_plugin(plugin_object) + + @log_calls('PluginManager') + def _add_gui_extension_points_handlers_from_plugin(self, plugin): + for gui_extpoint_name, gui_extpoint_handlers in \ + plugin.gui_extension_points.iteritems(): + self.gui_extension_points_handlers.setdefault(gui_extpoint_name, []).append( + gui_extpoint_handlers) + + @log_calls('PluginManager') + def _handle_all_gui_extension_points_with_plugin(self, plugin): + for gui_extpoint_name, gui_extpoint_handlers in \ + plugin.gui_extension_points.iteritems(): + if gui_extpoint_name in self.gui_extension_points: + for gui_extension_point_args in self.gui_extension_points[gui_extpoint_name]: + handler = gui_extpoint_handlers[0] + if handler: + handler(*gui_extension_point_args) + + @log_calls('PluginManager') + def _activate_all_plugins(self): + ''' + Activates all plugins in `plugins`. + + Activated plugins are appended to `active_plugins` list. + ''' + #self.active_plugins = [] + for plugin in self.plugins: + self.activate_plugin(plugin) + + def _activate_all_plugins_from_global_config(self): + for plugin in self.plugins: + if self._plugin_is_active_in_global_config(plugin): + self.activate_plugin(plugin) + + def _plugin_is_active_in_global_config(self, plugin): + return gajim.config.get_per('plugins', plugin.short_name, 'active') + + def _set_plugin_active_in_global_config(self, plugin, active=True): + gajim.config.set_per('plugins', plugin.short_name, 'active', active) + + @staticmethod + @log_calls('PluginManager') + def scan_dir_for_plugins(path): + ''' + Scans given directory for plugin classes. + + :param path: directory to scan for plugins + :type path: unicode + + :return: list of found plugin classes (subclasses of `GajimPlugin` + :rtype: [] of class objects + + :note: currently it only searches for plugin classes in '\*.py' files + present in given direcotory `path` (no recursion here) + + :todo: add scanning packages + :todo: add scanning zipped modules + ''' + plugins_found = [] + if os.path.isdir(path): + dir_list = os.listdir(path) + #log.debug(dir_list) + + sys.path.insert(0, path) + #log.debug(sys.path) + + for elem_name in dir_list: + #log.debug('- "%s"'%(elem_name)) + file_path = os.path.join(path, elem_name) + #log.debug(' "%s"'%(file_path)) + + module = None + + if os.path.isfile(file_path) and fnmatch.fnmatch(file_path, '*.py'): + module_name = os.path.splitext(elem_name)[0] + #log.debug('Possible module detected.') + try: + module = __import__(module_name) + #log.debug('Module imported.') + except ValueError, value_error: + pass + #log.debug('Module not imported successfully. ValueError: %s'%(value_error)) + except ImportError, import_error: + pass + #log.debug('Module not imported successfully. ImportError: %s'%(import_error)) + + elif os.path.isdir(file_path): + module_name = elem_name + file_path += os.path.sep + #log.debug('Possible package detected.') + try: + module = __import__(module_name) + #log.debug('Package imported.') + except ValueError, value_error: + pass + #log.debug('Package not imported successfully. ValueError: %s'%(value_error)) + except ImportError, import_error: + pass + #log.debug('Package not imported successfully. ImportError: %s'%(import_error)) + + + if module: + log.debug('Attributes processing started') + for module_attr_name in [attr_name for attr_name in dir(module) + if not (attr_name.startswith('__') or + attr_name.endswith('__'))]: + module_attr = getattr(module, module_attr_name) + log.debug('%s : %s'%(module_attr_name, module_attr)) + + try: + if issubclass(module_attr, GajimPlugin) and \ + not module_attr is GajimPlugin: + log.debug('is subclass of GajimPlugin') + #log.debug('file_path: %s\nabspath: %s\ndirname: %s'%(file_path, os.path.abspath(file_path), os.path.dirname(os.path.abspath(file_path)))) + #log.debug('file_path: %s\ndirname: %s\nabspath: %s'%(file_path, os.path.dirname(file_path), os.path.abspath(os.path.dirname(file_path)))) + module_attr.__path__ = os.path.abspath(os.path.dirname(file_path)) + plugins_found.append(module_attr) + except TypeError, type_error: + pass + #log.debug('module_attr: %s, error : %s'%( + #module_name+'.'+module_attr_name, + #type_error)) + + #log.debug(module) + + return plugins_found diff --git a/src/profile_window.py b/src/profile_window.py index 834f1e59e..577f50fa4 100644 --- a/src/profile_window.py +++ b/src/profile_window.py @@ -36,333 +36,331 @@ from common import gajim class ProfileWindow: - """ - Class for our information window - """ + """ + Class for our information window + """ - def __init__(self, account): - self.xml = gtkgui_helpers.get_gtk_builder('profile_window.ui') - self.window = self.xml.get_object('profile_window') - self.progressbar = self.xml.get_object('progressbar') - self.statusbar = self.xml.get_object('statusbar') - self.context_id = self.statusbar.get_context_id('profile') + def __init__(self, account): + self.xml = gtkgui_helpers.get_gtk_builder('profile_window.ui') + self.window = self.xml.get_object('profile_window') + self.progressbar = self.xml.get_object('progressbar') + self.statusbar = self.xml.get_object('statusbar') + self.context_id = self.statusbar.get_context_id('profile') - self.account = account - self.jid = gajim.get_jid_from_account(account) + self.account = account + self.jid = gajim.get_jid_from_account(account) - self.dialog = None - self.avatar_mime_type = None - self.avatar_encoded = None - self.message_id = self.statusbar.push(self.context_id, - _('Retrieving profile...')) - self.update_progressbar_timeout_id = gobject.timeout_add(100, - self.update_progressbar) - self.remove_statusbar_timeout_id = None + self.dialog = None + self.avatar_mime_type = None + self.avatar_encoded = None + self.message_id = self.statusbar.push(self.context_id, + _('Retrieving profile...')) + self.update_progressbar_timeout_id = gobject.timeout_add(100, + self.update_progressbar) + self.remove_statusbar_timeout_id = None - # Create Image for avatar button - image = gtk.Image() - self.xml.get_object('PHOTO_button').set_image(image) - self.xml.connect_signals(self) - self.window.show_all() + # Create Image for avatar button + image = gtk.Image() + self.xml.get_object('PHOTO_button').set_image(image) + self.xml.connect_signals(self) + self.window.show_all() - def update_progressbar(self): - self.progressbar.pulse() - return True # loop forever + def update_progressbar(self): + self.progressbar.pulse() + return True # loop forever - def remove_statusbar(self, message_id): - self.statusbar.remove_message(self.context_id, message_id) - self.remove_statusbar_timeout_id = None + def remove_statusbar(self, message_id): + self.statusbar.remove_message(self.context_id, message_id) + self.remove_statusbar_timeout_id = None - def on_profile_window_destroy(self, widget): - if self.update_progressbar_timeout_id is not None: - gobject.source_remove(self.update_progressbar_timeout_id) - if self.remove_statusbar_timeout_id is not None: - gobject.source_remove(self.remove_statusbar_timeout_id) - del gajim.interface.instances[self.account]['profile'] - if self.dialog: # Image chooser dialog - self.dialog.destroy() + def on_profile_window_destroy(self, widget): + if self.update_progressbar_timeout_id is not None: + gobject.source_remove(self.update_progressbar_timeout_id) + if self.remove_statusbar_timeout_id is not None: + gobject.source_remove(self.remove_statusbar_timeout_id) + del gajim.interface.instances[self.account]['profile'] + if self.dialog: # Image chooser dialog + self.dialog.destroy() - def on_profile_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: - self.window.destroy() + def on_profile_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: + self.window.destroy() - def on_clear_button_clicked(self, widget): - # empty the image - button = self.xml.get_object('PHOTO_button') - image = button.get_image() - image.set_from_pixbuf(None) - button.hide() - text_button = self.xml.get_object('NOPHOTO_button') - text_button.show() - self.avatar_encoded = None - self.avatar_mime_type = None + def on_clear_button_clicked(self, widget): + # empty the image + button = self.xml.get_object('PHOTO_button') + image = button.get_image() + image.set_from_pixbuf(None) + button.hide() + text_button = self.xml.get_object('NOPHOTO_button') + text_button.show() + self.avatar_encoded = None + self.avatar_mime_type = None - def on_set_avatar_button_clicked(self, widget): - def on_ok(widget, path_to_file): - must_delete = False - filesize = os.path.getsize(path_to_file) # in bytes - invalid_file = False - msg = '' - if os.path.isfile(path_to_file): - stat = os.stat(path_to_file) - if stat[6] == 0: - invalid_file = True - msg = _('File is empty') - else: - invalid_file = True - msg = _('File does not exist') - if not invalid_file and filesize > 16384: # 16 kb - try: - pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file) - # get the image at 'notification size' - # and hope that user did not specify in ACE crazy size - scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, - 'tooltip') - except gobject.GError, msg: # unknown format - # msg should be string, not object instance - msg = str(msg) - invalid_file = True - if invalid_file: - if True: # keep identation - dialogs.ErrorDialog(_('Could not load image'), msg) - return - if filesize > 16384: - if scaled_pixbuf: - path_to_file = os.path.join(gajim.TMP, - 'avatar_scaled.png') - scaled_pixbuf.save(path_to_file, 'png') - must_delete = True + def on_set_avatar_button_clicked(self, widget): + def on_ok(widget, path_to_file): + must_delete = False + filesize = os.path.getsize(path_to_file) # in bytes + invalid_file = False + msg = '' + if os.path.isfile(path_to_file): + stat = os.stat(path_to_file) + if stat[6] == 0: + invalid_file = True + msg = _('File is empty') + else: + invalid_file = True + msg = _('File does not exist') + if not invalid_file and filesize > 16384: # 16 kb + try: + pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file) + # get the image at 'notification size' + # and hope that user did not specify in ACE crazy size + scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, + 'tooltip') + except gobject.GError, msg: # unknown format + # msg should be string, not object instance + msg = str(msg) + invalid_file = True + if invalid_file: + if True: # keep identation + dialogs.ErrorDialog(_('Could not load image'), msg) + return + if filesize > 16384: + if scaled_pixbuf: + path_to_file = os.path.join(gajim.TMP, + 'avatar_scaled.png') + scaled_pixbuf.save(path_to_file, 'png') + must_delete = True - fd = open(path_to_file, 'rb') - data = fd.read() - pixbuf = gtkgui_helpers.get_pixbuf_from_data(data) - try: - # rescale it - pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard') - except AttributeError: # unknown format - dialogs.ErrorDialog(_('Could not load image')) - return - self.dialog.destroy() - self.dialog = None - button = self.xml.get_object('PHOTO_button') - image = button.get_image() - image.set_from_pixbuf(pixbuf) - button.show() - text_button = self.xml.get_object('NOPHOTO_button') - text_button.hide() - self.avatar_encoded = base64.encodestring(data) - # returns None if unknown type - self.avatar_mime_type = mimetypes.guess_type(path_to_file)[0] - if must_delete: - try: - os.remove(path_to_file) - except OSError: - gajim.log.debug('Cannot remove %s' % path_to_file) + fd = open(path_to_file, 'rb') + data = fd.read() + pixbuf = gtkgui_helpers.get_pixbuf_from_data(data) + try: + # rescale it + pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard') + except AttributeError: # unknown format + dialogs.ErrorDialog(_('Could not load image')) + return + self.dialog.destroy() + self.dialog = None + button = self.xml.get_object('PHOTO_button') + image = button.get_image() + image.set_from_pixbuf(pixbuf) + button.show() + text_button = self.xml.get_object('NOPHOTO_button') + text_button.hide() + self.avatar_encoded = base64.encodestring(data) + # returns None if unknown type + self.avatar_mime_type = mimetypes.guess_type(path_to_file)[0] + if must_delete: + try: + os.remove(path_to_file) + except OSError: + gajim.log.debug('Cannot remove %s' % path_to_file) - def on_clear(widget): - self.dialog.destroy() - self.dialog = None - self.on_clear_button_clicked(widget) + def on_clear(widget): + self.dialog.destroy() + self.dialog = None + self.on_clear_button_clicked(widget) - def on_cancel(widget): - self.dialog.destroy() - self.dialog = None + def on_cancel(widget): + self.dialog.destroy() + self.dialog = None - if self.dialog: - self.dialog.present() - else: - self.dialog = dialogs.AvatarChooserDialog(on_response_ok = on_ok, - on_response_cancel = on_cancel, on_response_clear = on_clear) + if self.dialog: + self.dialog.present() + else: + self.dialog = dialogs.AvatarChooserDialog(on_response_ok = on_ok, + on_response_cancel = on_cancel, on_response_clear = on_clear) - def on_PHOTO_button_press_event(self, widget, event): - """ - If right-clicked, show popup - """ - if event.button == 3 and self.avatar_encoded: # right click - menu = gtk.Menu() + def on_PHOTO_button_press_event(self, widget, event): + """ + If right-clicked, show popup + """ + if event.button == 3 and self.avatar_encoded: # right click + menu = gtk.Menu() - # Try to get pixbuf - pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(self.jid, - use_local=False) + # Try to get pixbuf + pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(self.jid, + use_local=False) - if pixbuf not in (None, 'ask'): - nick = gajim.config.get_per('accounts', self.account, 'name') - menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS) - menuitem.connect('activate', - gtkgui_helpers.on_avatar_save_as_menuitem_activate, - self.jid, self.account, nick) - menu.append(menuitem) - # show clear - menuitem = gtk.ImageMenuItem(gtk.STOCK_CLEAR) - menuitem.connect('activate', self.on_clear_button_clicked) - menu.append(menuitem) - menu.connect('selection-done', lambda w:w.destroy()) - # show the menu - menu.show_all() - menu.popup(None, None, None, event.button, event.time) - elif event.button == 1: # left click - self.on_set_avatar_button_clicked(widget) + if pixbuf not in (None, 'ask'): + nick = gajim.config.get_per('accounts', self.account, 'name') + menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS) + menuitem.connect('activate', + gtkgui_helpers.on_avatar_save_as_menuitem_activate, + self.jid, self.account, nick) + menu.append(menuitem) + # show clear + menuitem = gtk.ImageMenuItem(gtk.STOCK_CLEAR) + menuitem.connect('activate', self.on_clear_button_clicked) + menu.append(menuitem) + menu.connect('selection-done', lambda w:w.destroy()) + # show the menu + menu.show_all() + menu.popup(None, None, None, event.button, event.time) + elif event.button == 1: # left click + self.on_set_avatar_button_clicked(widget) - def set_value(self, entry_name, value): - try: - self.xml.get_object(entry_name).set_text(value) - except AttributeError: - pass + def set_value(self, entry_name, value): + try: + self.xml.get_object(entry_name).set_text(value) + except AttributeError: + pass - def set_values(self, vcard_): - button = self.xml.get_object('PHOTO_button') - image = button.get_image() - text_button = self.xml.get_object('NOPHOTO_button') - if not 'PHOTO' in vcard_: - # set default image - image.set_from_pixbuf(None) - button.hide() - text_button.show() - for i in vcard_.keys(): - if i == 'PHOTO': - pixbuf, self.avatar_encoded, self.avatar_mime_type = \ - vcard.get_avatar_pixbuf_encoded_mime(vcard_[i]) - if not pixbuf: - image.set_from_pixbuf(None) - button.hide() - text_button.show() - continue - pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard') - image.set_from_pixbuf(pixbuf) - button.show() - text_button.hide() - continue - if i == 'ADR' or i == 'TEL' or i == 'EMAIL': - for entry in vcard_[i]: - add_on = '_HOME' - if 'WORK' in entry: - add_on = '_WORK' - for j in entry.keys(): - self.set_value(i + add_on + '_' + j + '_entry', entry[j]) - if isinstance(vcard_[i], dict): - for j in vcard_[i].keys(): - self.set_value(i + '_' + j + '_entry', vcard_[i][j]) - else: - if i == 'DESC': - self.xml.get_object('DESC_textview').get_buffer().set_text( - vcard_[i], 0) - else: - self.set_value(i + '_entry', vcard_[i]) - if self.update_progressbar_timeout_id is not None: - if self.message_id: - self.statusbar.remove_message(self.context_id, self.message_id) - self.message_id = self.statusbar.push(self.context_id, - _('Information received')) - self.remove_statusbar_timeout_id = gobject.timeout_add_seconds(3, - self.remove_statusbar, self.message_id) - gobject.source_remove(self.update_progressbar_timeout_id) - self.progressbar.hide() - self.progressbar.set_fraction(0) - self.update_progressbar_timeout_id = None + def set_values(self, vcard_): + button = self.xml.get_object('PHOTO_button') + image = button.get_image() + text_button = self.xml.get_object('NOPHOTO_button') + if not 'PHOTO' in vcard_: + # set default image + image.set_from_pixbuf(None) + button.hide() + text_button.show() + for i in vcard_.keys(): + if i == 'PHOTO': + pixbuf, self.avatar_encoded, self.avatar_mime_type = \ + vcard.get_avatar_pixbuf_encoded_mime(vcard_[i]) + if not pixbuf: + image.set_from_pixbuf(None) + button.hide() + text_button.show() + continue + pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard') + image.set_from_pixbuf(pixbuf) + button.show() + text_button.hide() + continue + if i == 'ADR' or i == 'TEL' or i == 'EMAIL': + for entry in vcard_[i]: + add_on = '_HOME' + if 'WORK' in entry: + add_on = '_WORK' + for j in entry.keys(): + self.set_value(i + add_on + '_' + j + '_entry', entry[j]) + if isinstance(vcard_[i], dict): + for j in vcard_[i].keys(): + self.set_value(i + '_' + j + '_entry', vcard_[i][j]) + else: + if i == 'DESC': + self.xml.get_object('DESC_textview').get_buffer().set_text( + vcard_[i], 0) + else: + self.set_value(i + '_entry', vcard_[i]) + if self.update_progressbar_timeout_id is not None: + if self.message_id: + self.statusbar.remove_message(self.context_id, self.message_id) + self.message_id = self.statusbar.push(self.context_id, + _('Information received')) + self.remove_statusbar_timeout_id = gobject.timeout_add_seconds(3, + self.remove_statusbar, self.message_id) + gobject.source_remove(self.update_progressbar_timeout_id) + self.progressbar.hide() + self.progressbar.set_fraction(0) + self.update_progressbar_timeout_id = None - def add_to_vcard(self, vcard_, entry, txt): - """ - Add an information to the vCard dictionary - """ - entries = entry.split('_') - loc = vcard_ - if len(entries) == 3: # We need to use lists - if entries[0] not in loc: - loc[entries[0]] = [] - found = False - for e in loc[entries[0]]: - if entries[1] in e: - e[entries[2]] = txt - break - else: - loc[entries[0]].append({entries[1]: '', entries[2]: txt}) - return vcard_ - while len(entries) > 1: - if entries[0] not in loc: - loc[entries[0]] = {} - loc = loc[entries[0]] - del entries[0] - loc[entries[0]] = txt - return vcard_ + def add_to_vcard(self, vcard_, entry, txt): + """ + Add an information to the vCard dictionary + """ + entries = entry.split('_') + loc = vcard_ + if len(entries) == 3: # We need to use lists + if entries[0] not in loc: + loc[entries[0]] = [] + found = False + for e in loc[entries[0]]: + if entries[1] in e: + e[entries[2]] = txt + break + else: + loc[entries[0]].append({entries[1]: '', entries[2]: txt}) + return vcard_ + while len(entries) > 1: + if entries[0] not in loc: + loc[entries[0]] = {} + loc = loc[entries[0]] + del entries[0] + loc[entries[0]] = txt + return vcard_ - def make_vcard(self): - """ - Make the vCard dictionary - """ - entries = ['FN', 'NICKNAME', 'BDAY', 'EMAIL_HOME_USERID', 'URL', - 'TEL_HOME_NUMBER', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX', - 'N_SUFFIX', 'ADR_HOME_STREET', 'ADR_HOME_EXTADR', 'ADR_HOME_LOCALITY', - 'ADR_HOME_REGION', 'ADR_HOME_PCODE', 'ADR_HOME_CTRY', 'ORG_ORGNAME', - 'ORG_ORGUNIT', 'TITLE', 'ROLE', 'TEL_WORK_NUMBER', 'EMAIL_WORK_USERID', - 'ADR_WORK_STREET', 'ADR_WORK_EXTADR', 'ADR_WORK_LOCALITY', - 'ADR_WORK_REGION', 'ADR_WORK_PCODE', 'ADR_WORK_CTRY'] - vcard_ = {} - for e in entries: - txt = self.xml.get_object(e + '_entry').get_text().decode('utf-8') - if txt != '': - vcard_ = self.add_to_vcard(vcard_, e, txt) + def make_vcard(self): + """ + Make the vCard dictionary + """ + entries = ['FN', 'NICKNAME', 'BDAY', 'EMAIL_HOME_USERID', 'URL', + 'TEL_HOME_NUMBER', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX', + 'N_SUFFIX', 'ADR_HOME_STREET', 'ADR_HOME_EXTADR', 'ADR_HOME_LOCALITY', + 'ADR_HOME_REGION', 'ADR_HOME_PCODE', 'ADR_HOME_CTRY', 'ORG_ORGNAME', + 'ORG_ORGUNIT', 'TITLE', 'ROLE', 'TEL_WORK_NUMBER', 'EMAIL_WORK_USERID', + 'ADR_WORK_STREET', 'ADR_WORK_EXTADR', 'ADR_WORK_LOCALITY', + 'ADR_WORK_REGION', 'ADR_WORK_PCODE', 'ADR_WORK_CTRY'] + vcard_ = {} + for e in entries: + txt = self.xml.get_object(e + '_entry').get_text().decode('utf-8') + if txt != '': + vcard_ = self.add_to_vcard(vcard_, e, txt) - # DESC textview - buff = self.xml.get_object('DESC_textview').get_buffer() - start_iter = buff.get_start_iter() - end_iter = buff.get_end_iter() - txt = buff.get_text(start_iter, end_iter, 0) - if txt != '': - vcard_['DESC'] = txt.decode('utf-8') + # DESC textview + buff = self.xml.get_object('DESC_textview').get_buffer() + start_iter = buff.get_start_iter() + end_iter = buff.get_end_iter() + txt = buff.get_text(start_iter, end_iter, 0) + if txt != '': + vcard_['DESC'] = txt.decode('utf-8') - # Avatar - if self.avatar_encoded: - vcard_['PHOTO'] = {'BINVAL': self.avatar_encoded} - if self.avatar_mime_type: - vcard_['PHOTO']['TYPE'] = self.avatar_mime_type - return vcard_ + # Avatar + if self.avatar_encoded: + vcard_['PHOTO'] = {'BINVAL': self.avatar_encoded} + if self.avatar_mime_type: + vcard_['PHOTO']['TYPE'] = self.avatar_mime_type + return vcard_ - def on_ok_button_clicked(self, widget): - if self.update_progressbar_timeout_id: - # Operation in progress - return - if gajim.connections[self.account].connected < 2: - dialogs.ErrorDialog(_('You are not connected to the server'), - _('Without a connection you can not publish your contact ' - 'information.')) - return - vcard_ = self.make_vcard() - nick = '' - if 'NICKNAME' in vcard_: - nick = vcard_['NICKNAME'] - gajim.connections[self.account].send_nickname(nick) - if nick == '': - nick = gajim.config.get_per('accounts', self.account, 'name') - gajim.nicks[self.account] = nick - gajim.connections[self.account].send_vcard(vcard_) - self.message_id = self.statusbar.push(self.context_id, - _('Sending profile...')) - self.progressbar.show() - self.update_progressbar_timeout_id = gobject.timeout_add(100, - self.update_progressbar) + def on_ok_button_clicked(self, widget): + if self.update_progressbar_timeout_id: + # Operation in progress + return + if gajim.connections[self.account].connected < 2: + dialogs.ErrorDialog(_('You are not connected to the server'), + _('Without a connection you can not publish your contact ' + 'information.')) + return + vcard_ = self.make_vcard() + nick = '' + if 'NICKNAME' in vcard_: + nick = vcard_['NICKNAME'] + gajim.connections[self.account].send_nickname(nick) + if nick == '': + nick = gajim.config.get_per('accounts', self.account, 'name') + gajim.nicks[self.account] = nick + gajim.connections[self.account].send_vcard(vcard_) + self.message_id = self.statusbar.push(self.context_id, + _('Sending profile...')) + self.progressbar.show() + self.update_progressbar_timeout_id = gobject.timeout_add(100, + self.update_progressbar) - def vcard_published(self): - if self.update_progressbar_timeout_id is not None: - gobject.source_remove(self.update_progressbar_timeout_id) - self.update_progressbar_timeout_id = None - self.window.destroy() + def vcard_published(self): + if self.update_progressbar_timeout_id is not None: + gobject.source_remove(self.update_progressbar_timeout_id) + self.update_progressbar_timeout_id = None + self.window.destroy() - def vcard_not_published(self): - if self.message_id: - self.statusbar.remove_message(self.context_id, self.message_id) - self.message_id = self.statusbar.push(self.context_id, - _('Information NOT published')) - self.remove_statusbar_timeout_id = gobject.timeout_add_seconds(3, - self.remove_statusbar, self.message_id) - if self.update_progressbar_timeout_id is not None: - gobject.source_remove(self.update_progressbar_timeout_id) - self.progressbar.set_fraction(0) - self.update_progressbar_timeout_id = None - dialogs.InformationDialog(_('vCard publication failed'), - _('There was an error while publishing your personal information, ' - 'try again later.')) + def vcard_not_published(self): + if self.message_id: + self.statusbar.remove_message(self.context_id, self.message_id) + self.message_id = self.statusbar.push(self.context_id, + _('Information NOT published')) + self.remove_statusbar_timeout_id = gobject.timeout_add_seconds(3, + self.remove_statusbar, self.message_id) + if self.update_progressbar_timeout_id is not None: + gobject.source_remove(self.update_progressbar_timeout_id) + self.progressbar.set_fraction(0) + self.update_progressbar_timeout_id = None + dialogs.InformationDialog(_('vCard publication failed'), + _('There was an error while publishing your personal information, ' + 'try again later.')) - def on_cancel_button_clicked(self, widget): - self.window.destroy() - -# vim: se ts=3: + def on_cancel_button_clicked(self, widget): + self.window.destroy() diff --git a/src/pycallgraph.py b/src/pycallgraph.py index 92c6bbdd3..3a5fd7ecd 100644 --- a/src/pycallgraph.py +++ b/src/pycallgraph.py @@ -402,11 +402,9 @@ def simple_memoize(callable_object): return wrapper - + settings = {} graph_attributes = {} reset_settings() reset_trace() inspect.getmodule = simple_memoize(inspect.getmodule) - -# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/src/remote_control.py b/src/remote_control.py index 377b9e1e8..71cab3aa8 100644 --- a/src/remote_control.py +++ b/src/remote_control.py @@ -39,10 +39,10 @@ from dialogs import AddNewContactWindow, NewChatDialog, JoinGroupchatWindow from common import dbus_support if dbus_support.supported: - import dbus - if dbus_support: - import dbus.service - import dbus.glib + import dbus + if dbus_support: + import dbus.service + import dbus.glib INTERFACE = 'org.gajim.dbus.RemoteInterface' OBJ_PATH = '/org/gajim/dbus/RemoteObject' @@ -66,729 +66,727 @@ DBUS_DICT_SS = lambda : dbus.Dictionary({}, signature="ss") DBUS_NONE = lambda : dbus.Int32(0) def get_dbus_struct(obj): - """ - Recursively go through all the items and replace them with their casted dbus - equivalents - """ - if obj is None: - return DBUS_NONE() - if isinstance(obj, (unicode, str)): - return DBUS_STRING(obj) - if isinstance(obj, int): - return DBUS_INT32(obj) - if isinstance(obj, float): - return DBUS_DOUBLE(obj) - if isinstance(obj, bool): - return DBUS_BOOLEAN(obj) - if isinstance(obj, (list, tuple)): - result = dbus.Array([get_dbus_struct(i) for i in obj], - signature='v') - if result == []: - return DBUS_NONE() - return result - if isinstance(obj, dict): - result = DBUS_DICT_SV() - for key, value in obj.items(): - result[DBUS_STRING(key)] = get_dbus_struct(value) - if result == {}: - return DBUS_NONE() - return result - # unknown type - return DBUS_NONE() + """ + Recursively go through all the items and replace them with their casted dbus + equivalents + """ + if obj is None: + return DBUS_NONE() + if isinstance(obj, (unicode, str)): + return DBUS_STRING(obj) + if isinstance(obj, int): + return DBUS_INT32(obj) + if isinstance(obj, float): + return DBUS_DOUBLE(obj) + if isinstance(obj, bool): + return DBUS_BOOLEAN(obj) + if isinstance(obj, (list, tuple)): + result = dbus.Array([get_dbus_struct(i) for i in obj], + signature='v') + if result == []: + return DBUS_NONE() + return result + if isinstance(obj, dict): + result = DBUS_DICT_SV() + for key, value in obj.items(): + result[DBUS_STRING(key)] = get_dbus_struct(value) + if result == {}: + return DBUS_NONE() + return result + # unknown type + return DBUS_NONE() class Remote: - def __init__(self): - self.signal_object = None - session_bus = dbus_support.session_bus.SessionBus() + def __init__(self): + self.signal_object = None + session_bus = dbus_support.session_bus.SessionBus() - bus_name = dbus.service.BusName(SERVICE, bus=session_bus) - self.signal_object = SignalObject(bus_name) + bus_name = dbus.service.BusName(SERVICE, bus=session_bus) + self.signal_object = SignalObject(bus_name) - def raise_signal(self, signal, arg): - if self.signal_object: - try: - getattr(self.signal_object, signal)(get_dbus_struct(arg)) - except UnicodeDecodeError: - pass # ignore error when we fail to announce on dbus + def raise_signal(self, signal, arg): + if self.signal_object: + try: + getattr(self.signal_object, signal)(get_dbus_struct(arg)) + except UnicodeDecodeError: + pass # ignore error when we fail to announce on dbus class SignalObject(dbus.service.Object): - """ - Local object definition for /org/gajim/dbus/RemoteObject - - This docstring is not be visible, because the clients can access only the - remote object. - """ - - def __init__(self, bus_name): - self.first_show = True - self.vcard_account = None - - # register our dbus API - dbus.service.Object.__init__(self, bus_name, OBJ_PATH) - - @dbus.service.signal(INTERFACE, signature='av') - def Roster(self, account_and_data): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def AccountPresence(self, status_and_account): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def ContactPresence(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def ContactAbsence(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def ContactStatus(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def NewMessage(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def Subscribe(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def Subscribed(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def Unsubscribed(self, account_and_jid): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def NewAccount(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def VcardInfo(self, account_and_vcard): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def LastStatusTime(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def OsInfo(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def EntityTime(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def GCPresence(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def GCMessage(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def RosterInfo(self, account_and_array): - pass - - @dbus.service.signal(INTERFACE, signature='av') - def NewGmail(self, account_and_array): - pass - - def raise_signal(self, signal, arg): - """ - Raise a signal, with a single argument of unspecified type Instead of - obj.raise_signal("Foo", bar), use obj.Foo(bar) - """ - getattr(self, signal)(arg) - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='s') - def get_status(self, account): - """ - Return status (show to be exact) which is the global one unless account is - given - """ - if not account: - # If user did not ask for account, returns the global status - return DBUS_STRING(helpers.get_global_show()) - # return show for the given account - index = gajim.connections[account].connected - return DBUS_STRING(gajim.SHOW_LIST[index]) - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='s') - def get_status_message(self, account): - """ - Return status which is the global one unless account is given - """ - if not account: - # If user did not ask for account, returns the global status - return DBUS_STRING(str(helpers.get_global_status())) - # return show for the given account - status = gajim.connections[account].status - return DBUS_STRING(status) - - def _get_account_and_contact(self, account, jid): - """ - Get the account (if not given) and contact instance from jid - """ - connected_account = None - contact = None - accounts = gajim.contacts.get_accounts() - # if there is only one account in roster, take it as default - # if user did not ask for account - if not account and len(accounts) == 1: - account = accounts[0] - if account: - if gajim.connections[account].connected > 1: # account is connected - connected_account = account - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - else: - for account in accounts: - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - if contact and gajim.connections[account].connected > 1: - # account is connected - connected_account = account - break - if not contact: - contact = jid - - return connected_account, contact - - def _get_account_for_groupchat(self, account, room_jid): - """ - Get the account which is connected to groupchat (if not given) - or check if the given account is connected to the groupchat - """ - connected_account = None - accounts = gajim.contacts.get_accounts() - # if there is only one account in roster, take it as default - # if user did not ask for account - if not account and len(accounts) == 1: - account = accounts[0] - if account: - if gajim.connections[account].connected > 1 and \ - room_jid in gajim.gc_connected[account] and \ - gajim.gc_connected[account][room_jid]: - # account and groupchat are connected - connected_account = account - else: - for account in accounts: - if gajim.connections[account].connected > 1 and \ - room_jid in gajim.gc_connected[account] and \ - gajim.gc_connected[account][room_jid]: - # account and groupchat are connected - connected_account = account - break - return connected_account - - @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') - def send_file(self, file_path, jid, account): - """ - Send file, located at 'file_path' to 'jid', using account (optional) - 'account' - """ - jid = self._get_real_jid(jid, account) - connected_account, contact = self._get_account_and_contact(account, jid) - - if connected_account: - if file_path.startswith('file://'): - file_path=file_path[7:] - if os.path.isfile(file_path): # is it file? - gajim.interface.instances['file_transfers'].send_file( - connected_account, contact, file_path) - return DBUS_BOOLEAN(True) - return DBUS_BOOLEAN(False) - - def _send_message(self, jid, message, keyID, account, type_ = 'chat', - subject = None): - """ - Can be called from send_chat_message (default when send_message) or - send_single_message - """ - if not jid or not message: - return DBUS_BOOLEAN(False) - if not keyID: - keyID = '' - - connected_account, contact = self._get_account_and_contact(account, jid) - if connected_account: - connection = gajim.connections[connected_account] - connection.send_message(jid, message, keyID, type_, subject) - return DBUS_BOOLEAN(True) - return DBUS_BOOLEAN(False) - - @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='b') - def send_chat_message(self, jid, message, keyID, account): - """ - Send chat 'message' to 'jid', using account (optional) 'account'. If keyID - is specified, encrypt the message with the pgp key - """ - jid = self._get_real_jid(jid, account) - return self._send_message(jid, message, keyID, account) - - @dbus.service.method(INTERFACE, in_signature='sssss', out_signature='b') - def send_single_message(self, jid, subject, message, keyID, account): - """ - Send single 'message' to 'jid', using account (optional) 'account'. If - keyID is specified, encrypt the message with the pgp key - """ - jid = self._get_real_jid(jid, account) - return self._send_message(jid, message, keyID, account, type, subject) - - @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') - def send_groupchat_message(self, room_jid, message, account): - """ - Send 'message' to groupchat 'room_jid', using account (optional) 'account' - """ - if not room_jid or not message: - return DBUS_BOOLEAN(False) - connected_account = self._get_account_for_groupchat(account, room_jid) - if connected_account: - connection = gajim.connections[connected_account] - connection.send_gc_message(room_jid, message) - return DBUS_BOOLEAN(True) - return DBUS_BOOLEAN(False) - - @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') - def open_chat(self, jid, account, message): - """ - Shows the tabbed window for new message to 'jid', using account (optional) - 'account' - """ - if not jid: - raise dbus_support.MissingArgument() - jid = self._get_real_jid(jid, account) - try: - jid = helpers.parse_jid(jid) - except Exception: - # Jid is not conform, ignore it - return DBUS_BOOLEAN(False) - - if account: - accounts = [account] - else: - accounts = gajim.connections.keys() - if len(accounts) == 1: - account = accounts[0] - connected_account = None - first_connected_acct = None - for acct in accounts: - if gajim.connections[acct].connected > 1: # account is online - contact = gajim.contacts.get_first_contact_from_jid(acct, jid) - if gajim.interface.msg_win_mgr.has_window(jid, acct): - connected_account = acct - break - # jid is in roster - elif contact: - connected_account = acct - break - # we send the message to jid not in roster, because account is - # specified, or there is only one account - elif account: - connected_account = acct - elif first_connected_acct is None: - first_connected_acct = acct - - # if jid is not a conntact, open-chat with first connected account - if connected_account is None and first_connected_acct: - connected_account = first_connected_acct - - if connected_account: - gajim.interface.new_chat_from_jid(connected_account, jid, message) - # preserve the 'steal focus preservation' - win = gajim.interface.msg_win_mgr.get_window(jid, - connected_account).window - if win.get_property('visible'): - win.window.focus(gtk.get_current_event_time()) - return DBUS_BOOLEAN(True) - return DBUS_BOOLEAN(False) - - @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') - def change_status(self, status, message, account): - """ - change_status(status, message, account). Account is optional - if not - specified status is changed for all accounts - """ - if status not in ('offline', 'online', 'chat', - 'away', 'xa', 'dnd', 'invisible'): - status = '' - if account: - if not status: - if account not in gajim.connections: - return DBUS_BOOLEAN(False) - status = gajim.SHOW_LIST[gajim.connections[account].connected] - gobject.idle_add(gajim.interface.roster.send_status, account, - status, message) - else: - # account not specified, so change the status of all accounts - for acc in gajim.contacts.get_accounts(): - if not gajim.config.get_per('accounts', acc, - 'sync_with_global_status'): - continue - if status: - status_ = status - else: - if acc not in gajim.connections: - continue - status_ = gajim.SHOW_LIST[gajim.connections[acc].connected] - gobject.idle_add(gajim.interface.roster.send_status, acc, - status_, message) - return DBUS_BOOLEAN(False) - - @dbus.service.method(INTERFACE, in_signature='ss', out_signature='') - def set_priority(self, prio, account): - """ - set_priority(prio, account). Account is optional - if not specified - priority is changed for all accounts. That are synced with global status - """ - if account: - gajim.config.set_per('accounts', account, 'priority', prio) - show = gajim.SHOW_LIST[gajim.connections[account].connected] - status = gajim.connections[account].status - gobject.idle_add(gajim.connections[account].change_status, show, - status) - else: - # account not specified, so change prio of all accounts - for acc in gajim.contacts.get_accounts(): - if not gajim.account_is_connected(acc): - continue - if not gajim.config.get_per('accounts', acc, - 'sync_with_global_status'): - continue - gajim.config.set_per('accounts', acc, 'priority', prio) - show = gajim.SHOW_LIST[gajim.connections[acc].connected] - status = gajim.connections[acc].status - gobject.idle_add(gajim.connections[acc].change_status, show, - status) - - @dbus.service.method(INTERFACE, in_signature='', out_signature='') - def show_next_pending_event(self): - """ - Show the window(s) with next pending event in tabbed/group chats - """ - if gajim.events.get_nb_events(): - gajim.interface.systray.handle_first_event() - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{sv}') - def contact_info(self, jid): - """ - Get vcard info for a contact. Return cached value of the vcard - """ - if not isinstance(jid, unicode): - jid = unicode(jid) - if not jid: - raise dbus_support.MissingArgument() - jid = self._get_real_jid(jid) - - cached_vcard = gajim.connections.values()[0].get_cached_vcard(jid) - if cached_vcard: - return get_dbus_struct(cached_vcard) - - # return empty dict - return DBUS_DICT_SV() - - @dbus.service.method(INTERFACE, in_signature='', out_signature='as') - def list_accounts(self): - """ - List register accounts - """ - result = gajim.contacts.get_accounts() - result_array = dbus.Array([], signature='s') - if result and len(result) > 0: - for account in result: - result_array.append(DBUS_STRING(account)) - return result_array - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{ss}') - def account_info(self, account): - """ - Show info on account: resource, jid, nick, prio, message - """ - result = DBUS_DICT_SS() - if account in gajim.connections: - # account is valid - con = gajim.connections[account] - index = con.connected - result['status'] = DBUS_STRING(gajim.SHOW_LIST[index]) - result['name'] = DBUS_STRING(con.name) - result['jid'] = DBUS_STRING(gajim.get_jid_from_account(con.name)) - result['message'] = DBUS_STRING(con.status) - result['priority'] = DBUS_STRING(unicode(con.priority)) - result['resource'] = DBUS_STRING(unicode(gajim.config.get_per( - 'accounts', con.name, 'resource'))) - return result - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='aa{sv}') - def list_contacts(self, account): - """ - List all contacts in the roster. If the first argument is specified, then - return the contacts for the specified account - """ - result = dbus.Array([], signature='aa{sv}') - accounts = gajim.contacts.get_accounts() - if len(accounts) == 0: - return result - if account: - accounts_to_search = [account] - else: - accounts_to_search = accounts - for acct in accounts_to_search: - if acct in accounts: - for jid in gajim.contacts.get_jid_list(acct): - item = self._contacts_as_dbus_structure( - gajim.contacts.get_contacts(acct, jid)) - if item: - result.append(item) - return result - - @dbus.service.method(INTERFACE, in_signature='', out_signature='') - def toggle_roster_appearance(self): - """ - Show/hide the roster window - """ - win = gajim.interface.roster.window - if win.get_property('visible'): - gobject.idle_add(win.hide) - else: - win.present() - # preserve the 'steal focus preservation' - if self._is_first(): - win.window.focus(gtk.get_current_event_time()) - else: - win.window.focus(long(time())) - - @dbus.service.method(INTERFACE, in_signature='', out_signature='') - def toggle_ipython(self): - """ - Show/hide the ipython window - """ - win = gajim.ipython_window - if win: - if win.window.is_visible(): - gobject.idle_add(win.hide) - else: - win.show_all() - win.present() - else: - gajim.interface.create_ipython_window() - - @dbus.service.method(INTERFACE, in_signature='', out_signature='a{ss}') - def prefs_list(self): - prefs_dict = DBUS_DICT_SS() - def get_prefs(data, name, path, value): - if value is None: - return - key = '' - if path is not None: - for node in path: - key += node + '#' - key += name - prefs_dict[DBUS_STRING(key)] = DBUS_STRING(value[1]) - gajim.config.foreach(get_prefs) - return prefs_dict - - @dbus.service.method(INTERFACE, in_signature='', out_signature='b') - def prefs_store(self): - try: - gajim.interface.save_config() - except Exception, e: - return DBUS_BOOLEAN(False) - return DBUS_BOOLEAN(True) - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') - def prefs_del(self, key): - if not key: - return DBUS_BOOLEAN(False) - key_path = key.split('#', 2) - if len(key_path) != 3: - return DBUS_BOOLEAN(False) - if key_path[2] == '*': - gajim.config.del_per(key_path[0], key_path[1]) - else: - gajim.config.del_per(key_path[0], key_path[1], key_path[2]) - return DBUS_BOOLEAN(True) - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') - def prefs_put(self, key): - if not key: - return DBUS_BOOLEAN(False) - key_path = key.split('#', 2) - if len(key_path) < 3: - subname, value = key.split('=', 1) - gajim.config.set(subname, value) - return DBUS_BOOLEAN(True) - subname, value = key_path[2].split('=', 1) - gajim.config.set_per(key_path[0], key_path[1], subname, value) - return DBUS_BOOLEAN(True) - - @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b') - def add_contact(self, jid, account): - if account: - if account in gajim.connections and \ - gajim.connections[account].connected > 1: - # if given account is active, use it - AddNewContactWindow(account = account, jid = jid) - else: - # wrong account - return DBUS_BOOLEAN(False) - else: - # if account is not given, show account combobox - AddNewContactWindow(account = None, jid = jid) - return DBUS_BOOLEAN(True) - - @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b') - def remove_contact(self, jid, account): - jid = self._get_real_jid(jid, account) - accounts = gajim.contacts.get_accounts() - - # if there is only one account in roster, take it as default - if account: - accounts = [account] - contact_exists = False - for account in accounts: - contacts = gajim.contacts.get_contacts(account, jid) - if contacts: - gajim.connections[account].unsubscribe(jid) - for contact in contacts: - gajim.interface.roster.remove_contact(contact, account) - gajim.contacts.remove_jid(account, jid) - contact_exists = True - return DBUS_BOOLEAN(contact_exists) - - def _is_first(self): - if self.first_show: - self.first_show = False - return True - return False - - def _get_real_jid(self, jid, account = None): - """ - Get the real jid from the given one: removes xmpp: or get jid from nick if - account is specified, search only in this account - """ - if account: - accounts = [account] - else: - accounts = gajim.connections.keys() - if jid.startswith('xmpp:'): - return jid[5:] # len('xmpp:') = 5 - nick_in_roster = None # Is jid a nick ? - for account in accounts: - # Does jid exists in roster of one account ? - if gajim.contacts.get_contacts(account, jid): - return jid - if not nick_in_roster: - # look in all contact if one has jid as nick - for jid_ in gajim.contacts.get_jid_list(account): - c = gajim.contacts.get_contacts(account, jid_) - if c[0].name == jid: - nick_in_roster = jid_ - break - if nick_in_roster: - # We have not found jid in roster, but we found is as a nick - return nick_in_roster - # We have not found it as jid nor as nick, probably a not in roster jid - return jid - - def _contacts_as_dbus_structure(self, contacts): - """ - Get info from list of Contact objects and create dbus dict - """ - if not contacts: - return None - prim_contact = None # primary contact - for contact in contacts: - if prim_contact is None or contact.priority > prim_contact.priority: - prim_contact = contact - contact_dict = DBUS_DICT_SV() - contact_dict['name'] = DBUS_STRING(prim_contact.name) - contact_dict['show'] = DBUS_STRING(prim_contact.show) - contact_dict['jid'] = DBUS_STRING(prim_contact.jid) - if prim_contact.keyID: - keyID = None - if len(prim_contact.keyID) == 8: - keyID = prim_contact.keyID - elif len(prim_contact.keyID) == 16: - keyID = prim_contact.keyID[8:] - if keyID: - contact_dict['openpgp'] = keyID - contact_dict['resources'] = dbus.Array([], signature='(sis)') - for contact in contacts: - resource_props = dbus.Struct((DBUS_STRING(contact.resource), - dbus.Int32(contact.priority), DBUS_STRING(contact.status))) - contact_dict['resources'].append(resource_props) - contact_dict['groups'] = dbus.Array([], signature='(s)') - for group in prim_contact.groups: - contact_dict['groups'].append((DBUS_STRING(group),)) - return contact_dict - - @dbus.service.method(INTERFACE, in_signature='', out_signature='s') - def get_unread_msgs_number(self): - return DBUS_STRING(str(gajim.events.get_nb_events())) - - @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') - def start_chat(self, account): - if not account: - # error is shown in gajim-remote check_arguments(..) - return DBUS_BOOLEAN(False) - NewChatDialog(account) - return DBUS_BOOLEAN(True) - - @dbus.service.method(INTERFACE, in_signature='ss', out_signature='') - def send_xml(self, xml, account): - if account: - gajim.connections[account].send_stanza(str(xml)) - else: - for acc in gajim.contacts.get_accounts(): - gajim.connections[acc].send_stanza(str(xml)) - - @dbus.service.method(INTERFACE, in_signature='ss', out_signature='') - def change_avatar(self, picture, account): - filesize = os.path.getsize(picture) - invalid_file = False - if os.path.isfile(picture): - stat = os.stat(picture) - if stat[6] == 0: - invalid_file = True - else: - invalid_file = True - if not invalid_file and filesize < 16384: - fd = open(picture, 'rb') - data = fd.read() - avatar = base64.encodestring(data) - avatar_mime_type = mimetypes.guess_type(picture)[0] - vcard={} - vcard['PHOTO'] = {'BINVAL': avatar} - if avatar_mime_type: - vcard['PHOTO']['TYPE'] = avatar_mime_type - if account: - gajim.connections[account].send_vcard(vcard) - else: - for acc in gajim.connections: - gajim.connections[acc].send_vcard(vcard) - - @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='') - def join_room(self, room_jid, nick, password, account): - if not account: - # get the first connected account - accounts = gajim.connections.keys() - for acct in accounts: - if gajim.account_is_connected(acct): - account = acct - break - if not account: - return - if not nick: - nick = '' - gajim.interface.instances[account]['join_gc'] = \ - JoinGroupchatWindow(account, room_jid, nick) - else: - gajim.interface.join_gc_room(account, room_jid, nick, password) - -# vim: se ts=3: + """ + Local object definition for /org/gajim/dbus/RemoteObject + + This docstring is not be visible, because the clients can access only the + remote object. + """ + + def __init__(self, bus_name): + self.first_show = True + self.vcard_account = None + + # register our dbus API + dbus.service.Object.__init__(self, bus_name, OBJ_PATH) + + @dbus.service.signal(INTERFACE, signature='av') + def Roster(self, account_and_data): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def AccountPresence(self, status_and_account): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def ContactPresence(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def ContactAbsence(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def ContactStatus(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def NewMessage(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def Subscribe(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def Subscribed(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def Unsubscribed(self, account_and_jid): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def NewAccount(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def VcardInfo(self, account_and_vcard): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def LastStatusTime(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def OsInfo(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def EntityTime(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def GCPresence(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def GCMessage(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def RosterInfo(self, account_and_array): + pass + + @dbus.service.signal(INTERFACE, signature='av') + def NewGmail(self, account_and_array): + pass + + def raise_signal(self, signal, arg): + """ + Raise a signal, with a single argument of unspecified type Instead of + obj.raise_signal("Foo", bar), use obj.Foo(bar) + """ + getattr(self, signal)(arg) + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='s') + def get_status(self, account): + """ + Return status (show to be exact) which is the global one unless account is + given + """ + if not account: + # If user did not ask for account, returns the global status + return DBUS_STRING(helpers.get_global_show()) + # return show for the given account + index = gajim.connections[account].connected + return DBUS_STRING(gajim.SHOW_LIST[index]) + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='s') + def get_status_message(self, account): + """ + Return status which is the global one unless account is given + """ + if not account: + # If user did not ask for account, returns the global status + return DBUS_STRING(str(helpers.get_global_status())) + # return show for the given account + status = gajim.connections[account].status + return DBUS_STRING(status) + + def _get_account_and_contact(self, account, jid): + """ + Get the account (if not given) and contact instance from jid + """ + connected_account = None + contact = None + accounts = gajim.contacts.get_accounts() + # if there is only one account in roster, take it as default + # if user did not ask for account + if not account and len(accounts) == 1: + account = accounts[0] + if account: + if gajim.connections[account].connected > 1: # account is connected + connected_account = account + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + else: + for account in accounts: + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + if contact and gajim.connections[account].connected > 1: + # account is connected + connected_account = account + break + if not contact: + contact = jid + + return connected_account, contact + + def _get_account_for_groupchat(self, account, room_jid): + """ + Get the account which is connected to groupchat (if not given) + or check if the given account is connected to the groupchat + """ + connected_account = None + accounts = gajim.contacts.get_accounts() + # if there is only one account in roster, take it as default + # if user did not ask for account + if not account and len(accounts) == 1: + account = accounts[0] + if account: + if gajim.connections[account].connected > 1 and \ + room_jid in gajim.gc_connected[account] and \ + gajim.gc_connected[account][room_jid]: + # account and groupchat are connected + connected_account = account + else: + for account in accounts: + if gajim.connections[account].connected > 1 and \ + room_jid in gajim.gc_connected[account] and \ + gajim.gc_connected[account][room_jid]: + # account and groupchat are connected + connected_account = account + break + return connected_account + + @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') + def send_file(self, file_path, jid, account): + """ + Send file, located at 'file_path' to 'jid', using account (optional) + 'account' + """ + jid = self._get_real_jid(jid, account) + connected_account, contact = self._get_account_and_contact(account, jid) + + if connected_account: + if file_path.startswith('file://'): + file_path=file_path[7:] + if os.path.isfile(file_path): # is it file? + gajim.interface.instances['file_transfers'].send_file( + connected_account, contact, file_path) + return DBUS_BOOLEAN(True) + return DBUS_BOOLEAN(False) + + def _send_message(self, jid, message, keyID, account, type_ = 'chat', + subject = None): + """ + Can be called from send_chat_message (default when send_message) or + send_single_message + """ + if not jid or not message: + return DBUS_BOOLEAN(False) + if not keyID: + keyID = '' + + connected_account, contact = self._get_account_and_contact(account, jid) + if connected_account: + connection = gajim.connections[connected_account] + connection.send_message(jid, message, keyID, type_, subject) + return DBUS_BOOLEAN(True) + return DBUS_BOOLEAN(False) + + @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='b') + def send_chat_message(self, jid, message, keyID, account): + """ + Send chat 'message' to 'jid', using account (optional) 'account'. If keyID + is specified, encrypt the message with the pgp key + """ + jid = self._get_real_jid(jid, account) + return self._send_message(jid, message, keyID, account) + + @dbus.service.method(INTERFACE, in_signature='sssss', out_signature='b') + def send_single_message(self, jid, subject, message, keyID, account): + """ + Send single 'message' to 'jid', using account (optional) 'account'. If + keyID is specified, encrypt the message with the pgp key + """ + jid = self._get_real_jid(jid, account) + return self._send_message(jid, message, keyID, account, type, subject) + + @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') + def send_groupchat_message(self, room_jid, message, account): + """ + Send 'message' to groupchat 'room_jid', using account (optional) 'account' + """ + if not room_jid or not message: + return DBUS_BOOLEAN(False) + connected_account = self._get_account_for_groupchat(account, room_jid) + if connected_account: + connection = gajim.connections[connected_account] + connection.send_gc_message(room_jid, message) + return DBUS_BOOLEAN(True) + return DBUS_BOOLEAN(False) + + @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') + def open_chat(self, jid, account, message): + """ + Shows the tabbed window for new message to 'jid', using account (optional) + 'account' + """ + if not jid: + raise dbus_support.MissingArgument() + jid = self._get_real_jid(jid, account) + try: + jid = helpers.parse_jid(jid) + except Exception: + # Jid is not conform, ignore it + return DBUS_BOOLEAN(False) + + if account: + accounts = [account] + else: + accounts = gajim.connections.keys() + if len(accounts) == 1: + account = accounts[0] + connected_account = None + first_connected_acct = None + for acct in accounts: + if gajim.connections[acct].connected > 1: # account is online + contact = gajim.contacts.get_first_contact_from_jid(acct, jid) + if gajim.interface.msg_win_mgr.has_window(jid, acct): + connected_account = acct + break + # jid is in roster + elif contact: + connected_account = acct + break + # we send the message to jid not in roster, because account is + # specified, or there is only one account + elif account: + connected_account = acct + elif first_connected_acct is None: + first_connected_acct = acct + + # if jid is not a conntact, open-chat with first connected account + if connected_account is None and first_connected_acct: + connected_account = first_connected_acct + + if connected_account: + gajim.interface.new_chat_from_jid(connected_account, jid, message) + # preserve the 'steal focus preservation' + win = gajim.interface.msg_win_mgr.get_window(jid, + connected_account).window + if win.get_property('visible'): + win.window.focus(gtk.get_current_event_time()) + return DBUS_BOOLEAN(True) + return DBUS_BOOLEAN(False) + + @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b') + def change_status(self, status, message, account): + """ + change_status(status, message, account). Account is optional - if not + specified status is changed for all accounts + """ + if status not in ('offline', 'online', 'chat', + 'away', 'xa', 'dnd', 'invisible'): + status = '' + if account: + if not status: + if account not in gajim.connections: + return DBUS_BOOLEAN(False) + status = gajim.SHOW_LIST[gajim.connections[account].connected] + gobject.idle_add(gajim.interface.roster.send_status, account, + status, message) + else: + # account not specified, so change the status of all accounts + for acc in gajim.contacts.get_accounts(): + if not gajim.config.get_per('accounts', acc, + 'sync_with_global_status'): + continue + if status: + status_ = status + else: + if acc not in gajim.connections: + continue + status_ = gajim.SHOW_LIST[gajim.connections[acc].connected] + gobject.idle_add(gajim.interface.roster.send_status, acc, + status_, message) + return DBUS_BOOLEAN(False) + + @dbus.service.method(INTERFACE, in_signature='ss', out_signature='') + def set_priority(self, prio, account): + """ + set_priority(prio, account). Account is optional - if not specified + priority is changed for all accounts. That are synced with global status + """ + if account: + gajim.config.set_per('accounts', account, 'priority', prio) + show = gajim.SHOW_LIST[gajim.connections[account].connected] + status = gajim.connections[account].status + gobject.idle_add(gajim.connections[account].change_status, show, + status) + else: + # account not specified, so change prio of all accounts + for acc in gajim.contacts.get_accounts(): + if not gajim.account_is_connected(acc): + continue + if not gajim.config.get_per('accounts', acc, + 'sync_with_global_status'): + continue + gajim.config.set_per('accounts', acc, 'priority', prio) + show = gajim.SHOW_LIST[gajim.connections[acc].connected] + status = gajim.connections[acc].status + gobject.idle_add(gajim.connections[acc].change_status, show, + status) + + @dbus.service.method(INTERFACE, in_signature='', out_signature='') + def show_next_pending_event(self): + """ + Show the window(s) with next pending event in tabbed/group chats + """ + if gajim.events.get_nb_events(): + gajim.interface.systray.handle_first_event() + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{sv}') + def contact_info(self, jid): + """ + Get vcard info for a contact. Return cached value of the vcard + """ + if not isinstance(jid, unicode): + jid = unicode(jid) + if not jid: + raise dbus_support.MissingArgument() + jid = self._get_real_jid(jid) + + cached_vcard = gajim.connections.values()[0].get_cached_vcard(jid) + if cached_vcard: + return get_dbus_struct(cached_vcard) + + # return empty dict + return DBUS_DICT_SV() + + @dbus.service.method(INTERFACE, in_signature='', out_signature='as') + def list_accounts(self): + """ + List register accounts + """ + result = gajim.contacts.get_accounts() + result_array = dbus.Array([], signature='s') + if result and len(result) > 0: + for account in result: + result_array.append(DBUS_STRING(account)) + return result_array + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{ss}') + def account_info(self, account): + """ + Show info on account: resource, jid, nick, prio, message + """ + result = DBUS_DICT_SS() + if account in gajim.connections: + # account is valid + con = gajim.connections[account] + index = con.connected + result['status'] = DBUS_STRING(gajim.SHOW_LIST[index]) + result['name'] = DBUS_STRING(con.name) + result['jid'] = DBUS_STRING(gajim.get_jid_from_account(con.name)) + result['message'] = DBUS_STRING(con.status) + result['priority'] = DBUS_STRING(unicode(con.priority)) + result['resource'] = DBUS_STRING(unicode(gajim.config.get_per( + 'accounts', con.name, 'resource'))) + return result + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='aa{sv}') + def list_contacts(self, account): + """ + List all contacts in the roster. If the first argument is specified, then + return the contacts for the specified account + """ + result = dbus.Array([], signature='aa{sv}') + accounts = gajim.contacts.get_accounts() + if len(accounts) == 0: + return result + if account: + accounts_to_search = [account] + else: + accounts_to_search = accounts + for acct in accounts_to_search: + if acct in accounts: + for jid in gajim.contacts.get_jid_list(acct): + item = self._contacts_as_dbus_structure( + gajim.contacts.get_contacts(acct, jid)) + if item: + result.append(item) + return result + + @dbus.service.method(INTERFACE, in_signature='', out_signature='') + def toggle_roster_appearance(self): + """ + Show/hide the roster window + """ + win = gajim.interface.roster.window + if win.get_property('visible'): + gobject.idle_add(win.hide) + else: + win.present() + # preserve the 'steal focus preservation' + if self._is_first(): + win.window.focus(gtk.get_current_event_time()) + else: + win.window.focus(long(time())) + + @dbus.service.method(INTERFACE, in_signature='', out_signature='') + def toggle_ipython(self): + """ + Show/hide the ipython window + """ + win = gajim.ipython_window + if win: + if win.window.is_visible(): + gobject.idle_add(win.hide) + else: + win.show_all() + win.present() + else: + gajim.interface.create_ipython_window() + + @dbus.service.method(INTERFACE, in_signature='', out_signature='a{ss}') + def prefs_list(self): + prefs_dict = DBUS_DICT_SS() + def get_prefs(data, name, path, value): + if value is None: + return + key = '' + if path is not None: + for node in path: + key += node + '#' + key += name + prefs_dict[DBUS_STRING(key)] = DBUS_STRING(value[1]) + gajim.config.foreach(get_prefs) + return prefs_dict + + @dbus.service.method(INTERFACE, in_signature='', out_signature='b') + def prefs_store(self): + try: + gajim.interface.save_config() + except Exception, e: + return DBUS_BOOLEAN(False) + return DBUS_BOOLEAN(True) + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') + def prefs_del(self, key): + if not key: + return DBUS_BOOLEAN(False) + key_path = key.split('#', 2) + if len(key_path) != 3: + return DBUS_BOOLEAN(False) + if key_path[2] == '*': + gajim.config.del_per(key_path[0], key_path[1]) + else: + gajim.config.del_per(key_path[0], key_path[1], key_path[2]) + return DBUS_BOOLEAN(True) + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') + def prefs_put(self, key): + if not key: + return DBUS_BOOLEAN(False) + key_path = key.split('#', 2) + if len(key_path) < 3: + subname, value = key.split('=', 1) + gajim.config.set(subname, value) + return DBUS_BOOLEAN(True) + subname, value = key_path[2].split('=', 1) + gajim.config.set_per(key_path[0], key_path[1], subname, value) + return DBUS_BOOLEAN(True) + + @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b') + def add_contact(self, jid, account): + if account: + if account in gajim.connections and \ + gajim.connections[account].connected > 1: + # if given account is active, use it + AddNewContactWindow(account = account, jid = jid) + else: + # wrong account + return DBUS_BOOLEAN(False) + else: + # if account is not given, show account combobox + AddNewContactWindow(account = None, jid = jid) + return DBUS_BOOLEAN(True) + + @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b') + def remove_contact(self, jid, account): + jid = self._get_real_jid(jid, account) + accounts = gajim.contacts.get_accounts() + + # if there is only one account in roster, take it as default + if account: + accounts = [account] + contact_exists = False + for account in accounts: + contacts = gajim.contacts.get_contacts(account, jid) + if contacts: + gajim.connections[account].unsubscribe(jid) + for contact in contacts: + gajim.interface.roster.remove_contact(contact, account) + gajim.contacts.remove_jid(account, jid) + contact_exists = True + return DBUS_BOOLEAN(contact_exists) + + def _is_first(self): + if self.first_show: + self.first_show = False + return True + return False + + def _get_real_jid(self, jid, account = None): + """ + Get the real jid from the given one: removes xmpp: or get jid from nick if + account is specified, search only in this account + """ + if account: + accounts = [account] + else: + accounts = gajim.connections.keys() + if jid.startswith('xmpp:'): + return jid[5:] # len('xmpp:') = 5 + nick_in_roster = None # Is jid a nick ? + for account in accounts: + # Does jid exists in roster of one account ? + if gajim.contacts.get_contacts(account, jid): + return jid + if not nick_in_roster: + # look in all contact if one has jid as nick + for jid_ in gajim.contacts.get_jid_list(account): + c = gajim.contacts.get_contacts(account, jid_) + if c[0].name == jid: + nick_in_roster = jid_ + break + if nick_in_roster: + # We have not found jid in roster, but we found is as a nick + return nick_in_roster + # We have not found it as jid nor as nick, probably a not in roster jid + return jid + + def _contacts_as_dbus_structure(self, contacts): + """ + Get info from list of Contact objects and create dbus dict + """ + if not contacts: + return None + prim_contact = None # primary contact + for contact in contacts: + if prim_contact is None or contact.priority > prim_contact.priority: + prim_contact = contact + contact_dict = DBUS_DICT_SV() + contact_dict['name'] = DBUS_STRING(prim_contact.name) + contact_dict['show'] = DBUS_STRING(prim_contact.show) + contact_dict['jid'] = DBUS_STRING(prim_contact.jid) + if prim_contact.keyID: + keyID = None + if len(prim_contact.keyID) == 8: + keyID = prim_contact.keyID + elif len(prim_contact.keyID) == 16: + keyID = prim_contact.keyID[8:] + if keyID: + contact_dict['openpgp'] = keyID + contact_dict['resources'] = dbus.Array([], signature='(sis)') + for contact in contacts: + resource_props = dbus.Struct((DBUS_STRING(contact.resource), + dbus.Int32(contact.priority), DBUS_STRING(contact.status))) + contact_dict['resources'].append(resource_props) + contact_dict['groups'] = dbus.Array([], signature='(s)') + for group in prim_contact.groups: + contact_dict['groups'].append((DBUS_STRING(group),)) + return contact_dict + + @dbus.service.method(INTERFACE, in_signature='', out_signature='s') + def get_unread_msgs_number(self): + return DBUS_STRING(str(gajim.events.get_nb_events())) + + @dbus.service.method(INTERFACE, in_signature='s', out_signature='b') + def start_chat(self, account): + if not account: + # error is shown in gajim-remote check_arguments(..) + return DBUS_BOOLEAN(False) + NewChatDialog(account) + return DBUS_BOOLEAN(True) + + @dbus.service.method(INTERFACE, in_signature='ss', out_signature='') + def send_xml(self, xml, account): + if account: + gajim.connections[account].send_stanza(str(xml)) + else: + for acc in gajim.contacts.get_accounts(): + gajim.connections[acc].send_stanza(str(xml)) + + @dbus.service.method(INTERFACE, in_signature='ss', out_signature='') + def change_avatar(self, picture, account): + filesize = os.path.getsize(picture) + invalid_file = False + if os.path.isfile(picture): + stat = os.stat(picture) + if stat[6] == 0: + invalid_file = True + else: + invalid_file = True + if not invalid_file and filesize < 16384: + fd = open(picture, 'rb') + data = fd.read() + avatar = base64.encodestring(data) + avatar_mime_type = mimetypes.guess_type(picture)[0] + vcard={} + vcard['PHOTO'] = {'BINVAL': avatar} + if avatar_mime_type: + vcard['PHOTO']['TYPE'] = avatar_mime_type + if account: + gajim.connections[account].send_vcard(vcard) + else: + for acc in gajim.connections: + gajim.connections[acc].send_vcard(vcard) + + @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='') + def join_room(self, room_jid, nick, password, account): + if not account: + # get the first connected account + accounts = gajim.connections.keys() + for acct in accounts: + if gajim.account_is_connected(acct): + account = acct + break + if not account: + return + if not nick: + nick = '' + gajim.interface.instances[account]['join_gc'] = \ + JoinGroupchatWindow(account, room_jid, nick) + else: + gajim.interface.join_gc_room(account, room_jid, nick, password) diff --git a/src/roster_window.py b/src/roster_window.py index ce41f3570..d6716fb61 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -67,5946 +67,5944 @@ from message_window import MessageWindowMgr from common import dbus_support if dbus_support.supported: - import dbus + import dbus from common.xmpp.protocol import NS_FILE from common.pep import MOODS, ACTIVITIES #(icon, name, type, jid, account, editable, second pixbuf) ( - C_IMG, # image to show state (online, new message etc) - C_NAME, # cellrenderer text that holds contact nickame - C_TYPE, # account, group or contact? - C_JID, # the jid of the row - C_ACCOUNT, # cellrenderer text that holds account name - C_MOOD_PIXBUF, - C_ACTIVITY_PIXBUF, - C_TUNE_PIXBUF, - C_LOCATION_PIXBUF, - C_AVATAR_PIXBUF, # avatar_pixbuf - C_PADLOCK_PIXBUF, # use for account row only + C_IMG, # image to show state (online, new message etc) + C_NAME, # cellrenderer text that holds contact nickame + C_TYPE, # account, group or contact? + C_JID, # the jid of the row + C_ACCOUNT, # cellrenderer text that holds account name + C_MOOD_PIXBUF, + C_ACTIVITY_PIXBUF, + C_TUNE_PIXBUF, + C_LOCATION_PIXBUF, + C_AVATAR_PIXBUF, # avatar_pixbuf + C_PADLOCK_PIXBUF, # use for account row only ) = range(11) class RosterWindow: - """ - Class for main window of the GTK+ interface - """ + """ + Class for main window of the GTK+ interface + """ - def _get_account_iter(self, name, model=None): - """ - Return the gtk.TreeIter of the given account or None if not found + def _get_account_iter(self, name, model=None): + """ + Return the gtk.TreeIter of the given account or None if not found - Keyword arguments: - name -- the account name - model -- the data model (default TreeFilterModel) - """ - if not model: - model = self.modelfilter - if model is None: - return - account_iter = model.get_iter_root() - if self.regroup: - return account_iter - while account_iter: - account_name = model[account_iter][C_ACCOUNT] - if account_name and name == account_name.decode('utf-8'): - break - account_iter = model.iter_next(account_iter) - return account_iter + Keyword arguments: + name -- the account name + model -- the data model (default TreeFilterModel) + """ + if not model: + model = self.modelfilter + if model is None: + return + account_iter = model.get_iter_root() + if self.regroup: + return account_iter + while account_iter: + account_name = model[account_iter][C_ACCOUNT] + if account_name and name == account_name.decode('utf-8'): + break + account_iter = model.iter_next(account_iter) + return account_iter - def _get_group_iter(self, name, account, account_iter=None, model=None): - """ - Return the gtk.TreeIter of the given group or None if not found + def _get_group_iter(self, name, account, account_iter=None, model=None): + """ + Return the gtk.TreeIter of the given group or None if not found - Keyword arguments: - name -- the group name - account -- the account name - account_iter -- the iter of the account the model (default None) - model -- the data model (default TreeFilterModel) - """ - if not model: - model = self.modelfilter - if not account_iter: - account_iter = self._get_account_iter(account, model) - group_iter = model.iter_children(account_iter) - # C_NAME column contacts the pango escaped group name - while group_iter: - group_name = model[group_iter][C_JID].decode('utf-8') - if name == group_name: - break - group_iter = model.iter_next(group_iter) - return group_iter + Keyword arguments: + name -- the group name + account -- the account name + account_iter -- the iter of the account the model (default None) + model -- the data model (default TreeFilterModel) + """ + if not model: + model = self.modelfilter + if not account_iter: + account_iter = self._get_account_iter(account, model) + group_iter = model.iter_children(account_iter) + # C_NAME column contacts the pango escaped group name + while group_iter: + group_name = model[group_iter][C_JID].decode('utf-8') + if name == group_name: + break + group_iter = model.iter_next(group_iter) + return group_iter - def _get_self_contact_iter(self, account, model=None): - """ - Return the gtk.TreeIter of SelfContact or None if not found + def _get_self_contact_iter(self, account, model=None): + """ + Return the gtk.TreeIter of SelfContact or None if not found - Keyword arguments: - account -- the account of SelfContact - model -- the data model (default TreeFilterModel) - """ - if not model: - model = self.modelfilter - iterAcct = self._get_account_iter(account, model) - iterC = model.iter_children(iterAcct) + Keyword arguments: + account -- the account of SelfContact + model -- the data model (default TreeFilterModel) + """ + if not model: + model = self.modelfilter + iterAcct = self._get_account_iter(account, model) + iterC = model.iter_children(iterAcct) - # There might be several SelfContacts in merged account view - while iterC: - if model[iterC][C_TYPE] != 'self_contact': - break - iter_account = model[iterC][C_ACCOUNT] - if account == iter_account.decode('utf-8'): - return iterC - iterC = model.iter_next(iterC) - return None + # There might be several SelfContacts in merged account view + while iterC: + if model[iterC][C_TYPE] != 'self_contact': + break + iter_account = model[iterC][C_ACCOUNT] + if account == iter_account.decode('utf-8'): + return iterC + iterC = model.iter_next(iterC) + return None - def _get_contact_iter(self, jid, account, contact=None, model=None): - """ - Return a list of gtk.TreeIter of the given contact + def _get_contact_iter(self, jid, account, contact=None, model=None): + """ + Return a list of gtk.TreeIter of the given contact - Keyword arguments: - jid -- the jid without resource - account -- the account - contact -- the contact (default None) - model -- the data model (default TreeFilterModel) - """ - if not model: - model = self.modelfilter - # when closing Gajim model can be none (async pbs?) - if model is None: - return [] + Keyword arguments: + jid -- the jid without resource + account -- the account + contact -- the contact (default None) + model -- the data model (default TreeFilterModel) + """ + if not model: + model = self.modelfilter + # when closing Gajim model can be none (async pbs?) + if model is None: + return [] - if jid == gajim.get_jid_from_account(account): - contact_iter = self._get_self_contact_iter(account, model) - if contact_iter: - return [contact_iter] - else: - return [] + if jid == gajim.get_jid_from_account(account): + contact_iter = self._get_self_contact_iter(account, model) + if contact_iter: + return [contact_iter] + else: + return [] - if not contact: - contact = gajim.contacts.get_first_contact_from_jid(account, jid) - if not contact: - # We don't know this contact - return [] + if not contact: + contact = gajim.contacts.get_first_contact_from_jid(account, jid) + if not contact: + # We don't know this contact + return [] - acct = self._get_account_iter(account, model) - found = [] # the contact iters. One per group - for group in contact.get_shown_groups(): - group_iter = self._get_group_iter(group, account, acct, model) - contact_iter = model.iter_children(group_iter) + acct = self._get_account_iter(account, model) + found = [] # the contact iters. One per group + for group in contact.get_shown_groups(): + group_iter = self._get_group_iter(group, account, acct, model) + contact_iter = model.iter_children(group_iter) - while contact_iter: - # Loop over all contacts in this group - iter_jid = model[contact_iter][C_JID] - if iter_jid and jid == iter_jid.decode('utf-8') and \ - account == model[contact_iter][C_ACCOUNT].decode('utf-8'): - # only one iter per group - found.append(contact_iter) - contact_iter = None - elif model.iter_has_child(contact_iter): - # it's a big brother and has children - contact_iter = model.iter_children(contact_iter) - else: - # try to find next contact: - # other contact in this group or - # brother contact - next_contact_iter = model.iter_next(contact_iter) - if next_contact_iter: - contact_iter = next_contact_iter - else: - # It's the last one. - # Go up if we are big brother - parent_iter = model.iter_parent(contact_iter) - if parent_iter and model[parent_iter][C_TYPE] == 'contact': - contact_iter = model.iter_next(parent_iter) - else: - # we tested all - # contacts in this group - contact_iter = None - return found + while contact_iter: + # Loop over all contacts in this group + iter_jid = model[contact_iter][C_JID] + if iter_jid and jid == iter_jid.decode('utf-8') and \ + account == model[contact_iter][C_ACCOUNT].decode('utf-8'): + # only one iter per group + found.append(contact_iter) + contact_iter = None + elif model.iter_has_child(contact_iter): + # it's a big brother and has children + contact_iter = model.iter_children(contact_iter) + else: + # try to find next contact: + # other contact in this group or + # brother contact + next_contact_iter = model.iter_next(contact_iter) + if next_contact_iter: + contact_iter = next_contact_iter + else: + # It's the last one. + # Go up if we are big brother + parent_iter = model.iter_parent(contact_iter) + if parent_iter and model[parent_iter][C_TYPE] == 'contact': + contact_iter = model.iter_next(parent_iter) + else: + # we tested all + # contacts in this group + contact_iter = None + return found - def _iter_is_separator(self, model, titer): - """ - Return True if the given iter is a separator + def _iter_is_separator(self, model, titer): + """ + Return True if the given iter is a separator - Keyword arguments: - model -- the data model - iter -- the gtk.TreeIter to test - """ - if model[titer][0] == 'SEPARATOR': - return True - return False + Keyword arguments: + model -- the data model + iter -- the gtk.TreeIter to test + """ + if model[titer][0] == 'SEPARATOR': + return True + return False - def _iter_contact_rows(self, model=None): - """ - Iterate over all contact rows in given model + def _iter_contact_rows(self, model=None): + """ + Iterate over all contact rows in given model - Keyword argument - model -- the data model (default TreeFilterModel) - """ - if not model: - model = self.modelfilter - account_iter = model.get_iter_root() - while account_iter: - group_iter = model.iter_children(account_iter) - while group_iter: - contact_iter = model.iter_children(group_iter) - while contact_iter: - yield model[contact_iter] - contact_iter = model.iter_next( - contact_iter) - group_iter = model.iter_next(group_iter) - account_iter = model.iter_next(account_iter) + Keyword argument + model -- the data model (default TreeFilterModel) + """ + if not model: + model = self.modelfilter + account_iter = model.get_iter_root() + while account_iter: + group_iter = model.iter_children(account_iter) + while group_iter: + contact_iter = model.iter_children(group_iter) + while contact_iter: + yield model[contact_iter] + contact_iter = model.iter_next( + contact_iter) + group_iter = model.iter_next(group_iter) + account_iter = model.iter_next(account_iter) ############################################################################# ### Methods for adding and removing roster window items ############################################################################# - def add_account(self, account): - """ - Add account to roster and draw it. Do nothing if it is already in - """ - if self._get_account_iter(account): - # Will happen on reconnect or for merged accounts - return - - if self.regroup: - # Merged accounts view - show = helpers.get_global_show() - self.model.append(None, [ - gajim.interface.jabber_state_images['16'][show], - _('Merged accounts'), 'account', '', 'all', - None, None, None, None, None, None]) - else: - show = gajim.SHOW_LIST[gajim.connections[account].connected] - our_jid = gajim.get_jid_from_account(account) - - tls_pixbuf = None - if gajim.account_is_securely_connected(account): - # the only way to create a pixbuf from stock - tls_pixbuf = self.window.render_icon( - gtk.STOCK_DIALOG_AUTHENTICATION, - gtk.ICON_SIZE_MENU) - - self.model.append(None, [ - gajim.interface.jabber_state_images['16'][show], - gobject.markup_escape_text(account), 'account', - our_jid, account, None, None, None, None, None, - tls_pixbuf]) - - self.draw_account(account) - - - def add_account_contacts(self, account): - """ - Add all contacts and groups of the given account to roster, draw them and - account - """ - self.starting = True - jids = gajim.contacts.get_jid_list(account) - - self.tree.freeze_child_notify() - for jid in jids: - self.add_contact(jid, account) - - # Do not freeze the GUI when drawing the contacts - if jids: - # Overhead is big, only invoke when needed - self._idle_draw_jids_of_account(jids, account) - - # Draw all known groups - for group in gajim.groups[account]: - self.draw_group(group, account) - self.draw_account(account) - - self.tree.thaw_child_notify() - self.starting = False - - - def _add_entity(self, contact, account, groups=None, - big_brother_contact=None, big_brother_account=None): - """ - Add the given contact to roster data model - - Contact is added regardless if he is already in roster or not. Return - list of newly added iters. - - Keyword arguments: - contact -- the contact to add - account -- the contacts account - groups -- list of groups to add the contact to. - (default groups in contact.get_shown_groups()). - Parameter ignored when big_brother_contact is specified. - big_brother_contact -- if specified contact is added as child - big_brother_contact. (default None) - """ - added_iters = [] - if big_brother_contact: - # Add contact under big brother - - parent_iters = self._get_contact_iter( - big_brother_contact.jid, big_brother_account, - big_brother_contact, self.model) - assert len(parent_iters) > 0, 'Big brother is not yet in roster!' - - # Do not confuse get_contact_iter: Sync groups of family members - contact.groups = big_brother_contact.get_shown_groups()[:] - - for child_iter in parent_iters: - it = self.model.append(child_iter, (None, contact.get_shown_name(), - 'contact', contact.jid, account, None, None, None, None, None, - None)) - added_iters.append(it) - else: - # We are a normal contact. Add us to our groups. - if not groups: - groups = contact.get_shown_groups() - for group in groups: - child_iterG = self._get_group_iter(group, account, - model = self.model) - if not child_iterG: - # Group is not yet in roster, add it! - child_iterA = self._get_account_iter(account, self.model) - child_iterG = self.model.append(child_iterA, - [gajim.interface.jabber_state_images['16']['closed'], - gobject.markup_escape_text(group), - 'group', group, account, None, None, None, None, None, None]) - self.draw_group(group, account) - - if contact.is_transport(): - typestr = 'agent' - elif contact.is_groupchat(): - typestr = 'groupchat' - else: - typestr = 'contact' - - # we add some values here. see draw_contact - # for more - i_ = self.model.append(child_iterG, (None, - contact.get_shown_name(), typestr, - contact.jid, account, None, None, None, - None, None, None)) - added_iters.append(i_) - - # Restore the group expand state - if account + group in self.collapsed_rows: - is_expanded = False - else: - is_expanded = True - if group not in gajim.groups[account]: - gajim.groups[account][group] = {'expand': is_expanded} - - assert len(added_iters), '%s has not been added to roster!' % contact.jid - return added_iters - - def _remove_entity(self, contact, account, groups=None): - """ - Remove the given contact from roster data model - - Empty groups after contact removal are removed too. - Return False if contact still has children and deletion was - not performed. - Return True on success. - - Keyword arguments: - contact -- the contact to add - account -- the contacts account - groups -- list of groups to remove the contact from. - """ - iters = self._get_contact_iter(contact.jid, account, contact, self.model) - assert iters, '%s shall be removed but is not in roster' % contact.jid - - parent_iter = self.model.iter_parent(iters[0]) - parent_type = self.model[parent_iter][C_TYPE] - - if groups: - # Only remove from specified groups - all_iters = iters[:] - group_iters = [self._get_group_iter(group, account) - for group in groups] - iters = [titer for titer in all_iters - if self.model.iter_parent(titer) in group_iters] - - iter_children = self.model.iter_children(iters[0]) - - if iter_children: - # We have children. We cannot be removed! - return False - else: - # Remove us and empty groups from the model - for i in iters: - assert self.model[i][C_JID] == contact.jid and \ - self.model[i][C_ACCOUNT] == account, \ - "Invalidated iters of %s" % contact.jid - - parent_i = self.model.iter_parent(i) - - if parent_type == 'group' and \ - self.model.iter_n_children(parent_i) == 1: - group = self.model[parent_i][C_JID].decode('utf-8') - if group in gajim.groups[account]: - del gajim.groups[account][group] - self.model.remove(parent_i) - else: - self.model.remove(i) - return True - - def _add_metacontact_family(self, family, account): - """ - Add the give Metacontact family to roster data model - - Add Big Brother to his groups and all others under him. - Return list of all added (contact, account) tuples with - Big Brother as first element. - - Keyword arguments: - family -- the family, see Contacts.get_metacontacts_family() - """ - - nearby_family, big_brother_jid, big_brother_account = \ - self._get_nearby_family_and_big_brother(family, account) - big_brother_contact = gajim.contacts.get_first_contact_from_jid( - big_brother_account, big_brother_jid) - - assert len(self._get_contact_iter(big_brother_jid, - big_brother_account, big_brother_contact, self.model)) == 0, \ - 'Big brother %s already in roster\n Family: %s' \ - % (big_brother_jid, family) - self._add_entity(big_brother_contact, big_brother_account) - - brothers = [] - # Filter family members - for data in nearby_family: - _account = data['account'] - _jid = data['jid'] - _contact = gajim.contacts.get_first_contact_from_jid( - _account, _jid) - - if not _contact or _contact == big_brother_contact: - # Corresponding account is not connected - # or brother already added - continue - - assert len(self._get_contact_iter(_jid, _account, - _contact, self.model)) == 0, \ - "%s already in roster.\n Family: %s" % (_jid, nearby_family) - self._add_entity(_contact, _account, - big_brother_contact = big_brother_contact, - big_brother_account = big_brother_account) - brothers.append((_contact, _account)) - - brothers.insert(0, (big_brother_contact, big_brother_account)) - return brothers - - def _remove_metacontact_family(self, family, account): - """ - Remove the given Metacontact family from roster data model - - See Contacts.get_metacontacts_family() and - RosterWindow._remove_entity() - """ - nearby_family = self._get_nearby_family_and_big_brother( - family, account)[0] - - # Family might has changed (actual big brother not on top). - # Remove childs first then big brother - family_in_roster = False - for data in nearby_family: - _account = data['account'] - _jid = data['jid'] - _contact = gajim.contacts.get_first_contact_from_jid(_account, _jid) - - iters = self._get_contact_iter(_jid, _account, _contact, self.model) - if not iters or not _contact: - # Family might not be up to date. - # Only try to remove what is actually in the roster - continue - assert iters, '%s shall be removed but is not in roster \ - \n Family: %s' % (_jid, family) - - family_in_roster = True - - parent_iter = self.model.iter_parent(iters[0]) - parent_type = self.model[parent_iter][C_TYPE] - - if parent_type != 'contact': - # The contact on top - old_big_account = _account - old_big_contact = _contact - old_big_jid = _jid - continue - - ok = self._remove_entity(_contact, _account) - assert ok, '%s was not removed' % _jid - assert len(self._get_contact_iter(_jid, _account, _contact, - self.model)) == 0, '%s is removed but still in roster' % _jid - - if not family_in_roster: - return False - - assert old_big_jid, 'No Big Brother in nearby family % (Family: %)' % \ - (nearby_family, family) - iters = self._get_contact_iter(old_big_jid, old_big_account, - old_big_contact, self.model) - assert len(iters) > 0, 'Old Big Brother %s is not in roster anymore' % \ - old_big_jid - assert not self.model.iter_children(iters[0]),\ - 'Old Big Brother %s still has children' % old_big_jid - - ok = self._remove_entity(old_big_contact, old_big_account) - assert ok, "Old Big Brother %s not removed" % old_big_jid - assert len(self._get_contact_iter(old_big_jid, old_big_account, - old_big_contact, self.model)) == 0,\ - 'Old Big Brother %s is removed but still in roster' % old_big_jid - - return True - - - def _recalibrate_metacontact_family(self, family, account): - """ - Regroup metacontact family if necessary - """ - - brothers = [] - nearby_family, big_brother_jid, big_brother_account = \ - self._get_nearby_family_and_big_brother(family, account) - big_brother_contact = gajim.contacts.get_contact(big_brother_account, - big_brother_jid) - child_iters = self._get_contact_iter(big_brother_jid, big_brother_account, - model=self.model) - if child_iters: - parent_iter = self.model.iter_parent(child_iters[0]) - parent_type = self.model[parent_iter][C_TYPE] - - # Check if the current BigBrother has even been before. - if parent_type == 'contact': - for data in nearby_family: - # recalibrate after remove to keep highlight - if data['jid'] in gajim.to_be_removed[data['account']]: - return - - self._remove_metacontact_family(family, account) - brothers = self._add_metacontact_family(family, account) - - for c, acc in brothers: - self.draw_completely(c.jid, acc) - - # Check is small brothers are under the big brother - for child in nearby_family: - _jid = child['jid'] - _account = child['account'] - if _account == big_brother_account and _jid == big_brother_jid: - continue - child_iters = self._get_contact_iter(_jid, _account, model=self.model) - if not child_iters: - continue - parent_iter = self.model.iter_parent(child_iters[0]) - parent_type = self.model[parent_iter][C_TYPE] - if parent_type != 'contact': - _contact = gajim.contacts.get_contact(_account, _jid) - self._remove_entity(_contact, _account) - self._add_entity(_contact, _account, groups=None, - big_brother_contact=big_brother_contact, - big_brother_account=big_brother_account) - - def _get_nearby_family_and_big_brother(self, family, account): - return gajim.contacts.get_nearby_family_and_big_brother(family, account) - - def _add_self_contact(self, account): - """ - Add account's SelfContact to roster and draw it and the account - - Return the SelfContact contact instance - """ - jid = gajim.get_jid_from_account(account) - contact = gajim.contacts.get_first_contact_from_jid(account, jid) - - assert len(self._get_contact_iter(jid, account, contact, self.model)) == \ - 0, 'Self contact %s already in roster' % jid - - child_iterA = self._get_account_iter(account, self.model) - self.model.append(child_iterA, (None, gajim.nicks[account], - 'self_contact', jid, account, None, None, None, None, - None, None)) - - self.draw_completely(jid, account) - self.draw_account(account) - - return contact - - def redraw_metacontacts(self, account): - for family in gajim.contacts.iter_metacontacts_families(account): - self._recalibrate_metacontact_family(family, account) - - def add_contact(self, jid, account): - """ - Add contact to roster and draw him - - Add contact to all its group and redraw the groups, the contact and the - account. If it's a Metacontact, add and draw the whole family. - Do nothing if the contact is already in roster. - - Return the added contact instance. If it is a Metacontact return - Big Brother. - - Keyword arguments: - jid -- the contact's jid or SelfJid to add SelfContact - account -- the corresponding account. - """ - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - if len(self._get_contact_iter(jid, account, contact, self.model)): - # If contact already in roster, do nothing - return - - if jid == gajim.get_jid_from_account(account): - show_self_contact = gajim.config.get('show_self_contact') - if show_self_contact == 'never': - return - if (contact.resource != gajim.connections[account].server_resource and\ - show_self_contact == 'when_other_resource') or show_self_contact == \ - 'always': - return self._add_self_contact(account) - return - - is_observer = contact.is_observer() - if is_observer: - # if he has a tag, remove it - gajim.contacts.remove_metacontact(account, jid) - - # Add contact to roster - family = gajim.contacts.get_metacontacts_family(account, jid) - contacts = [] - if family: - # We have a family. So we are a metacontact. - # Add all family members that we shall be grouped with - if self.regroup: - # remove existing family members to regroup them - self._remove_metacontact_family(family, account) - contacts = self._add_metacontact_family(family, account) - else: - # We are a normal contact - contacts = [(contact, account),] - self._add_entity(contact, account) - - # Draw the contact and its groups contact - if not self.starting: - for c, acc in contacts: - self.draw_completely(c.jid, acc) - for group in contact.get_shown_groups(): - self.draw_group(group, account) - self._adjust_group_expand_collapse_state(group, account) - self.draw_account(account) - - return contacts[0][0] # it's contact/big brother with highest priority - - def remove_contact(self, jid, account, force=False, backend=False): - """ - Remove contact from roster - - Remove contact from all its group. Remove empty groups or redraw - otherwise. - Draw the account. - If it's a Metacontact, remove the whole family. - Do nothing if the contact is not in roster. - - Keyword arguments: - jid -- the contact's jid or SelfJid to remove SelfContact - account -- the corresponding account. - force -- remove contact even it has pending evens (Default False) - backend -- also remove contact instance (Default False) - """ - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - if not contact: - return - - if not force and (self.contact_has_pending_roster_events(contact, - account) or gajim.interface.msg_win_mgr.get_control(jid, account)): - # Contact has pending events or window - #TODO: or single message windows? Bur they are not listed for the - # moment - key = (jid, account) - if not key in self.contacts_to_be_removed: - self.contacts_to_be_removed[key] = {'backend': backend} - # if more pending event, don't remove from roster - if self.contact_has_pending_roster_events(contact, account): - return False - - iters = self._get_contact_iter(jid, account, contact, self.model) - if iters: - # no more pending events - # Remove contact from roster directly - family = gajim.contacts.get_metacontacts_family(account, jid) - if family: - # We have a family. So we are a metacontact. - self._remove_metacontact_family(family, account) - else: - self._remove_entity(contact, account) - - if backend and (not gajim.interface.msg_win_mgr.get_control(jid, account)\ - or force): - # If a window is still opened: don't remove contact instance - # Remove contact before redrawing, otherwise the old - # numbers will still be show - gajim.contacts.remove_jid(account, jid, remove_meta=True) - if iters: - rest_of_family = [data for data in family - if account != data['account'] or jid != data['jid']] - if rest_of_family: - # reshow the rest of the family - brothers = self._add_metacontact_family(rest_of_family, account) - for c, acc in brothers: - self.draw_completely(c.jid, acc) - - if iters: - # Draw all groups of the contact - for group in contact.get_shown_groups(): - self.draw_group(group, account) - self.draw_account(account) - - return True - - def rename_self_contact(self, old_jid, new_jid, account): - """ - Rename the self_contact jid - - Keyword arguments: - old_jid -- our old jid - new_jid -- our new jid - account -- the corresponding account. - """ - gajim.contacts.change_contact_jid(old_jid, new_jid, account) - self_iter = self._get_self_contact_iter(account, model=self.model) - if not self_iter: - return - self.model[self_iter][C_JID] = new_jid - self.draw_contact(new_jid, account) - - def add_groupchat(self, jid, account, status=''): - """ - Add groupchat to roster and draw it. Return the added contact instance - """ - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - # Do not show gc if we are disconnected and minimize it - if gajim.account_is_connected(account): - show = 'online' - else: - show = 'offline' - status = '' - - if contact is None: - gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid, account) - if gc_control: - # there is a window that we can minimize - gajim.interface.minimized_controls[account][jid] = gc_control - name = gc_control.name - elif jid in gajim.interface.minimized_controls[account]: - name = gajim.interface.minimized_controls[account][jid].name - else: - name = jid.split('@')[0] - # New groupchat - #GCMIN - contact = gajim.contacts.create_contact(jid=jid, account=account, name=name, - groups=[_('Groupchats')], show=show, status=status, sub='none') - gajim.contacts.add_contact(account, contact) - self.add_contact(jid, account) - else: - if jid not in gajim.interface.minimized_controls[account]: - # there is a window that we can minimize - gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid, - account) - gajim.interface.minimized_controls[account][jid] = gc_control - contact.show = show - contact.status = status - self.adjust_and_draw_contact_context(jid, account) - - return contact - - - def remove_groupchat(self, jid, account): - """ - Remove groupchat from roster and redraw account and group - """ - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - if contact.is_groupchat(): - if jid in gajim.interface.minimized_controls[account]: - del gajim.interface.minimized_controls[account][jid] - self.remove_contact(jid, account, force=True, backend=True) - return True - else: - return False - - - # FIXME: This function is yet unused! Port to new API - def add_transport(self, jid, account): - """ - Add transport to roster and draw it. Return the added contact instance - """ - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - if contact is None: - #TRANSP - contact = gajim.contacts.create_contact(jid=jid, account=account, name=jid, - groups=[_('Transports')], show='offline', status='offline', - sub='from') - gajim.contacts.add_contact(account, contact) - self.add_contact(jid, account) - return contact - - def remove_transport(self, jid, account): - """ - Remove transport from roster and redraw account and group - """ - self.remove_contact(jid, account, force=True, backend=True) - return True - - def rename_group(self, old_name, new_name, account): - """ - Rename a roster group - """ - if old_name == new_name: - return - - # Groups may not change name from or to a special groups - for g in helpers.special_groups: - if g in (new_name, old_name): - return - - # update all contacts in the given group - if self.regroup: - accounts = gajim.connections.keys() - else: - accounts = [account,] - - for acc in accounts: - changed_contacts = [] - for jid in gajim.contacts.get_jid_list(acc): - contact = gajim.contacts.get_first_contact_from_jid(acc, jid) - if old_name not in contact.groups: - continue - - self.remove_contact(jid, acc, force=True) - - contact.groups.remove(old_name) - if new_name not in contact.groups: - contact.groups.append(new_name) - - changed_contacts.append({'jid':jid, 'name':contact.name, - 'groups':contact.groups}) - - gajim.connections[acc].update_contacts(changed_contacts) - - for c in changed_contacts: - self.add_contact(c['jid'], acc) - - self._adjust_group_expand_collapse_state(new_name, acc) - - self.draw_group(old_name, acc) - self.draw_group(new_name, acc) - - - def add_contact_to_groups(self, jid, account, groups, update=True): - """ - Add contact to given groups and redraw them - - Contact on server is updated too. When the contact has a family, - the action will be performed for all members. - - Keyword Arguments: - jid -- the jid - account -- the corresponding account - groups -- list of Groups to add the contact to. - update -- update contact on the server - """ - self.remove_contact(jid, account, force=True) - for contact in gajim.contacts.get_contacts(account, jid): - for group in groups: - if group not in contact.groups: - # we might be dropped from meta to group - contact.groups.append(group) - if update: - gajim.connections[account].update_contact(jid, contact.name, - contact.groups) - - self.add_contact(jid, account) - - for group in groups: - self._adjust_group_expand_collapse_state(group, account) - - def remove_contact_from_groups(self, jid, account, groups, update=True): - """ - Remove contact from given groups and redraw them - - Contact on server is updated too. When the contact has a family, - the action will be performed for all members. - - Keyword Arguments: - jid -- the jid - account -- the corresponding account - groups -- list of Groups to remove the contact from - update -- update contact on the server - """ - self.remove_contact(jid, account, force=True) - for contact in gajim.contacts.get_contacts(account, jid): - for group in groups: - if group in contact.groups: - # Needed when we remove from "General" or "Observers" - contact.groups.remove(group) - if update: - gajim.connections[account].update_contact(jid, contact.name, - contact.groups) - self.add_contact(jid, account) - - # Also redraw old groups - for group in groups: - self.draw_group(group, account) - - # FIXME: maybe move to gajim.py - def remove_newly_added(self, jid, account): - if jid in gajim.newly_added[account]: - gajim.newly_added[account].remove(jid) - self.draw_contact(jid, account) - - # FIXME: maybe move to gajim.py - def remove_to_be_removed(self, jid, account): - if account not in gajim.interface.instances: - # Account has been deleted during the timeout that called us - return - if jid in gajim.newly_added[account]: - return - if jid in gajim.to_be_removed[account]: - gajim.to_be_removed[account].remove(jid) - family = gajim.contacts.get_metacontacts_family(account, jid) - if family: - # Peform delayed recalibration - self._recalibrate_metacontact_family(family, account) - self.draw_contact(jid, account) - - # FIXME: integrate into add_contact() - def add_to_not_in_the_roster(self, account, jid, nick='', resource=''): - keyID = '' - attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() - if jid in attached_keys: - keyID = attached_keys[attached_keys.index(jid) + 1] - contact = gajim.contacts.create_not_in_roster_contact(jid=jid, - account=account, resource=resource, name=nick, keyID=keyID) - gajim.contacts.add_contact(account, contact) - self.add_contact(contact.jid, account) - return contact + def add_account(self, account): + """ + Add account to roster and draw it. Do nothing if it is already in + """ + if self._get_account_iter(account): + # Will happen on reconnect or for merged accounts + return + + if self.regroup: + # Merged accounts view + show = helpers.get_global_show() + self.model.append(None, [ + gajim.interface.jabber_state_images['16'][show], + _('Merged accounts'), 'account', '', 'all', + None, None, None, None, None, None]) + else: + show = gajim.SHOW_LIST[gajim.connections[account].connected] + our_jid = gajim.get_jid_from_account(account) + + tls_pixbuf = None + if gajim.account_is_securely_connected(account): + # the only way to create a pixbuf from stock + tls_pixbuf = self.window.render_icon( + gtk.STOCK_DIALOG_AUTHENTICATION, + gtk.ICON_SIZE_MENU) + + self.model.append(None, [ + gajim.interface.jabber_state_images['16'][show], + gobject.markup_escape_text(account), 'account', + our_jid, account, None, None, None, None, None, + tls_pixbuf]) + + self.draw_account(account) + + + def add_account_contacts(self, account): + """ + Add all contacts and groups of the given account to roster, draw them and + account + """ + self.starting = True + jids = gajim.contacts.get_jid_list(account) + + self.tree.freeze_child_notify() + for jid in jids: + self.add_contact(jid, account) + + # Do not freeze the GUI when drawing the contacts + if jids: + # Overhead is big, only invoke when needed + self._idle_draw_jids_of_account(jids, account) + + # Draw all known groups + for group in gajim.groups[account]: + self.draw_group(group, account) + self.draw_account(account) + + self.tree.thaw_child_notify() + self.starting = False + + + def _add_entity(self, contact, account, groups=None, + big_brother_contact=None, big_brother_account=None): + """ + Add the given contact to roster data model + + Contact is added regardless if he is already in roster or not. Return + list of newly added iters. + + Keyword arguments: + contact -- the contact to add + account -- the contacts account + groups -- list of groups to add the contact to. + (default groups in contact.get_shown_groups()). + Parameter ignored when big_brother_contact is specified. + big_brother_contact -- if specified contact is added as child + big_brother_contact. (default None) + """ + added_iters = [] + if big_brother_contact: + # Add contact under big brother + + parent_iters = self._get_contact_iter( + big_brother_contact.jid, big_brother_account, + big_brother_contact, self.model) + assert len(parent_iters) > 0, 'Big brother is not yet in roster!' + + # Do not confuse get_contact_iter: Sync groups of family members + contact.groups = big_brother_contact.get_shown_groups()[:] + + for child_iter in parent_iters: + it = self.model.append(child_iter, (None, contact.get_shown_name(), + 'contact', contact.jid, account, None, None, None, None, None, + None)) + added_iters.append(it) + else: + # We are a normal contact. Add us to our groups. + if not groups: + groups = contact.get_shown_groups() + for group in groups: + child_iterG = self._get_group_iter(group, account, + model = self.model) + if not child_iterG: + # Group is not yet in roster, add it! + child_iterA = self._get_account_iter(account, self.model) + child_iterG = self.model.append(child_iterA, + [gajim.interface.jabber_state_images['16']['closed'], + gobject.markup_escape_text(group), + 'group', group, account, None, None, None, None, None, None]) + self.draw_group(group, account) + + if contact.is_transport(): + typestr = 'agent' + elif contact.is_groupchat(): + typestr = 'groupchat' + else: + typestr = 'contact' + + # we add some values here. see draw_contact + # for more + i_ = self.model.append(child_iterG, (None, + contact.get_shown_name(), typestr, + contact.jid, account, None, None, None, + None, None, None)) + added_iters.append(i_) + + # Restore the group expand state + if account + group in self.collapsed_rows: + is_expanded = False + else: + is_expanded = True + if group not in gajim.groups[account]: + gajim.groups[account][group] = {'expand': is_expanded} + + assert len(added_iters), '%s has not been added to roster!' % contact.jid + return added_iters + + def _remove_entity(self, contact, account, groups=None): + """ + Remove the given contact from roster data model + + Empty groups after contact removal are removed too. + Return False if contact still has children and deletion was + not performed. + Return True on success. + + Keyword arguments: + contact -- the contact to add + account -- the contacts account + groups -- list of groups to remove the contact from. + """ + iters = self._get_contact_iter(contact.jid, account, contact, self.model) + assert iters, '%s shall be removed but is not in roster' % contact.jid + + parent_iter = self.model.iter_parent(iters[0]) + parent_type = self.model[parent_iter][C_TYPE] + + if groups: + # Only remove from specified groups + all_iters = iters[:] + group_iters = [self._get_group_iter(group, account) + for group in groups] + iters = [titer for titer in all_iters + if self.model.iter_parent(titer) in group_iters] + + iter_children = self.model.iter_children(iters[0]) + + if iter_children: + # We have children. We cannot be removed! + return False + else: + # Remove us and empty groups from the model + for i in iters: + assert self.model[i][C_JID] == contact.jid and \ + self.model[i][C_ACCOUNT] == account, \ + "Invalidated iters of %s" % contact.jid + + parent_i = self.model.iter_parent(i) + + if parent_type == 'group' and \ + self.model.iter_n_children(parent_i) == 1: + group = self.model[parent_i][C_JID].decode('utf-8') + if group in gajim.groups[account]: + del gajim.groups[account][group] + self.model.remove(parent_i) + else: + self.model.remove(i) + return True + + def _add_metacontact_family(self, family, account): + """ + Add the give Metacontact family to roster data model + + Add Big Brother to his groups and all others under him. + Return list of all added (contact, account) tuples with + Big Brother as first element. + + Keyword arguments: + family -- the family, see Contacts.get_metacontacts_family() + """ + + nearby_family, big_brother_jid, big_brother_account = \ + self._get_nearby_family_and_big_brother(family, account) + big_brother_contact = gajim.contacts.get_first_contact_from_jid( + big_brother_account, big_brother_jid) + + assert len(self._get_contact_iter(big_brother_jid, + big_brother_account, big_brother_contact, self.model)) == 0, \ + 'Big brother %s already in roster\n Family: %s' \ + % (big_brother_jid, family) + self._add_entity(big_brother_contact, big_brother_account) + + brothers = [] + # Filter family members + for data in nearby_family: + _account = data['account'] + _jid = data['jid'] + _contact = gajim.contacts.get_first_contact_from_jid( + _account, _jid) + + if not _contact or _contact == big_brother_contact: + # Corresponding account is not connected + # or brother already added + continue + + assert len(self._get_contact_iter(_jid, _account, + _contact, self.model)) == 0, \ + "%s already in roster.\n Family: %s" % (_jid, nearby_family) + self._add_entity(_contact, _account, + big_brother_contact = big_brother_contact, + big_brother_account = big_brother_account) + brothers.append((_contact, _account)) + + brothers.insert(0, (big_brother_contact, big_brother_account)) + return brothers + + def _remove_metacontact_family(self, family, account): + """ + Remove the given Metacontact family from roster data model + + See Contacts.get_metacontacts_family() and + RosterWindow._remove_entity() + """ + nearby_family = self._get_nearby_family_and_big_brother( + family, account)[0] + + # Family might has changed (actual big brother not on top). + # Remove childs first then big brother + family_in_roster = False + for data in nearby_family: + _account = data['account'] + _jid = data['jid'] + _contact = gajim.contacts.get_first_contact_from_jid(_account, _jid) + + iters = self._get_contact_iter(_jid, _account, _contact, self.model) + if not iters or not _contact: + # Family might not be up to date. + # Only try to remove what is actually in the roster + continue + assert iters, '%s shall be removed but is not in roster \ + \n Family: %s' % (_jid, family) + + family_in_roster = True + + parent_iter = self.model.iter_parent(iters[0]) + parent_type = self.model[parent_iter][C_TYPE] + + if parent_type != 'contact': + # The contact on top + old_big_account = _account + old_big_contact = _contact + old_big_jid = _jid + continue + + ok = self._remove_entity(_contact, _account) + assert ok, '%s was not removed' % _jid + assert len(self._get_contact_iter(_jid, _account, _contact, + self.model)) == 0, '%s is removed but still in roster' % _jid + + if not family_in_roster: + return False + + assert old_big_jid, 'No Big Brother in nearby family % (Family: %)' % \ + (nearby_family, family) + iters = self._get_contact_iter(old_big_jid, old_big_account, + old_big_contact, self.model) + assert len(iters) > 0, 'Old Big Brother %s is not in roster anymore' % \ + old_big_jid + assert not self.model.iter_children(iters[0]),\ + 'Old Big Brother %s still has children' % old_big_jid + + ok = self._remove_entity(old_big_contact, old_big_account) + assert ok, "Old Big Brother %s not removed" % old_big_jid + assert len(self._get_contact_iter(old_big_jid, old_big_account, + old_big_contact, self.model)) == 0,\ + 'Old Big Brother %s is removed but still in roster' % old_big_jid + + return True + + + def _recalibrate_metacontact_family(self, family, account): + """ + Regroup metacontact family if necessary + """ + + brothers = [] + nearby_family, big_brother_jid, big_brother_account = \ + self._get_nearby_family_and_big_brother(family, account) + big_brother_contact = gajim.contacts.get_contact(big_brother_account, + big_brother_jid) + child_iters = self._get_contact_iter(big_brother_jid, big_brother_account, + model=self.model) + if child_iters: + parent_iter = self.model.iter_parent(child_iters[0]) + parent_type = self.model[parent_iter][C_TYPE] + + # Check if the current BigBrother has even been before. + if parent_type == 'contact': + for data in nearby_family: + # recalibrate after remove to keep highlight + if data['jid'] in gajim.to_be_removed[data['account']]: + return + + self._remove_metacontact_family(family, account) + brothers = self._add_metacontact_family(family, account) + + for c, acc in brothers: + self.draw_completely(c.jid, acc) + + # Check is small brothers are under the big brother + for child in nearby_family: + _jid = child['jid'] + _account = child['account'] + if _account == big_brother_account and _jid == big_brother_jid: + continue + child_iters = self._get_contact_iter(_jid, _account, model=self.model) + if not child_iters: + continue + parent_iter = self.model.iter_parent(child_iters[0]) + parent_type = self.model[parent_iter][C_TYPE] + if parent_type != 'contact': + _contact = gajim.contacts.get_contact(_account, _jid) + self._remove_entity(_contact, _account) + self._add_entity(_contact, _account, groups=None, + big_brother_contact=big_brother_contact, + big_brother_account=big_brother_account) + + def _get_nearby_family_and_big_brother(self, family, account): + return gajim.contacts.get_nearby_family_and_big_brother(family, account) + + def _add_self_contact(self, account): + """ + Add account's SelfContact to roster and draw it and the account + + Return the SelfContact contact instance + """ + jid = gajim.get_jid_from_account(account) + contact = gajim.contacts.get_first_contact_from_jid(account, jid) + + assert len(self._get_contact_iter(jid, account, contact, self.model)) == \ + 0, 'Self contact %s already in roster' % jid + + child_iterA = self._get_account_iter(account, self.model) + self.model.append(child_iterA, (None, gajim.nicks[account], + 'self_contact', jid, account, None, None, None, None, + None, None)) + + self.draw_completely(jid, account) + self.draw_account(account) + + return contact + + def redraw_metacontacts(self, account): + for family in gajim.contacts.iter_metacontacts_families(account): + self._recalibrate_metacontact_family(family, account) + + def add_contact(self, jid, account): + """ + Add contact to roster and draw him + + Add contact to all its group and redraw the groups, the contact and the + account. If it's a Metacontact, add and draw the whole family. + Do nothing if the contact is already in roster. + + Return the added contact instance. If it is a Metacontact return + Big Brother. + + Keyword arguments: + jid -- the contact's jid or SelfJid to add SelfContact + account -- the corresponding account. + """ + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + if len(self._get_contact_iter(jid, account, contact, self.model)): + # If contact already in roster, do nothing + return + + if jid == gajim.get_jid_from_account(account): + show_self_contact = gajim.config.get('show_self_contact') + if show_self_contact == 'never': + return + if (contact.resource != gajim.connections[account].server_resource and\ + show_self_contact == 'when_other_resource') or show_self_contact == \ + 'always': + return self._add_self_contact(account) + return + + is_observer = contact.is_observer() + if is_observer: + # if he has a tag, remove it + gajim.contacts.remove_metacontact(account, jid) + + # Add contact to roster + family = gajim.contacts.get_metacontacts_family(account, jid) + contacts = [] + if family: + # We have a family. So we are a metacontact. + # Add all family members that we shall be grouped with + if self.regroup: + # remove existing family members to regroup them + self._remove_metacontact_family(family, account) + contacts = self._add_metacontact_family(family, account) + else: + # We are a normal contact + contacts = [(contact, account),] + self._add_entity(contact, account) + + # Draw the contact and its groups contact + if not self.starting: + for c, acc in contacts: + self.draw_completely(c.jid, acc) + for group in contact.get_shown_groups(): + self.draw_group(group, account) + self._adjust_group_expand_collapse_state(group, account) + self.draw_account(account) + + return contacts[0][0] # it's contact/big brother with highest priority + + def remove_contact(self, jid, account, force=False, backend=False): + """ + Remove contact from roster + + Remove contact from all its group. Remove empty groups or redraw + otherwise. + Draw the account. + If it's a Metacontact, remove the whole family. + Do nothing if the contact is not in roster. + + Keyword arguments: + jid -- the contact's jid or SelfJid to remove SelfContact + account -- the corresponding account. + force -- remove contact even it has pending evens (Default False) + backend -- also remove contact instance (Default False) + """ + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + if not contact: + return + + if not force and (self.contact_has_pending_roster_events(contact, + account) or gajim.interface.msg_win_mgr.get_control(jid, account)): + # Contact has pending events or window + #TODO: or single message windows? Bur they are not listed for the + # moment + key = (jid, account) + if not key in self.contacts_to_be_removed: + self.contacts_to_be_removed[key] = {'backend': backend} + # if more pending event, don't remove from roster + if self.contact_has_pending_roster_events(contact, account): + return False + + iters = self._get_contact_iter(jid, account, contact, self.model) + if iters: + # no more pending events + # Remove contact from roster directly + family = gajim.contacts.get_metacontacts_family(account, jid) + if family: + # We have a family. So we are a metacontact. + self._remove_metacontact_family(family, account) + else: + self._remove_entity(contact, account) + + if backend and (not gajim.interface.msg_win_mgr.get_control(jid, account)\ + or force): + # If a window is still opened: don't remove contact instance + # Remove contact before redrawing, otherwise the old + # numbers will still be show + gajim.contacts.remove_jid(account, jid, remove_meta=True) + if iters: + rest_of_family = [data for data in family + if account != data['account'] or jid != data['jid']] + if rest_of_family: + # reshow the rest of the family + brothers = self._add_metacontact_family(rest_of_family, account) + for c, acc in brothers: + self.draw_completely(c.jid, acc) + + if iters: + # Draw all groups of the contact + for group in contact.get_shown_groups(): + self.draw_group(group, account) + self.draw_account(account) + + return True + + def rename_self_contact(self, old_jid, new_jid, account): + """ + Rename the self_contact jid + + Keyword arguments: + old_jid -- our old jid + new_jid -- our new jid + account -- the corresponding account. + """ + gajim.contacts.change_contact_jid(old_jid, new_jid, account) + self_iter = self._get_self_contact_iter(account, model=self.model) + if not self_iter: + return + self.model[self_iter][C_JID] = new_jid + self.draw_contact(new_jid, account) + + def add_groupchat(self, jid, account, status=''): + """ + Add groupchat to roster and draw it. Return the added contact instance + """ + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + # Do not show gc if we are disconnected and minimize it + if gajim.account_is_connected(account): + show = 'online' + else: + show = 'offline' + status = '' + + if contact is None: + gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid, account) + if gc_control: + # there is a window that we can minimize + gajim.interface.minimized_controls[account][jid] = gc_control + name = gc_control.name + elif jid in gajim.interface.minimized_controls[account]: + name = gajim.interface.minimized_controls[account][jid].name + else: + name = jid.split('@')[0] + # New groupchat + #GCMIN + contact = gajim.contacts.create_contact(jid=jid, account=account, name=name, + groups=[_('Groupchats')], show=show, status=status, sub='none') + gajim.contacts.add_contact(account, contact) + self.add_contact(jid, account) + else: + if jid not in gajim.interface.minimized_controls[account]: + # there is a window that we can minimize + gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid, + account) + gajim.interface.minimized_controls[account][jid] = gc_control + contact.show = show + contact.status = status + self.adjust_and_draw_contact_context(jid, account) + + return contact + + + def remove_groupchat(self, jid, account): + """ + Remove groupchat from roster and redraw account and group + """ + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + if contact.is_groupchat(): + if jid in gajim.interface.minimized_controls[account]: + del gajim.interface.minimized_controls[account][jid] + self.remove_contact(jid, account, force=True, backend=True) + return True + else: + return False + + + # FIXME: This function is yet unused! Port to new API + def add_transport(self, jid, account): + """ + Add transport to roster and draw it. Return the added contact instance + """ + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + if contact is None: + #TRANSP + contact = gajim.contacts.create_contact(jid=jid, account=account, name=jid, + groups=[_('Transports')], show='offline', status='offline', + sub='from') + gajim.contacts.add_contact(account, contact) + self.add_contact(jid, account) + return contact + + def remove_transport(self, jid, account): + """ + Remove transport from roster and redraw account and group + """ + self.remove_contact(jid, account, force=True, backend=True) + return True + + def rename_group(self, old_name, new_name, account): + """ + Rename a roster group + """ + if old_name == new_name: + return + + # Groups may not change name from or to a special groups + for g in helpers.special_groups: + if g in (new_name, old_name): + return + + # update all contacts in the given group + if self.regroup: + accounts = gajim.connections.keys() + else: + accounts = [account,] + + for acc in accounts: + changed_contacts = [] + for jid in gajim.contacts.get_jid_list(acc): + contact = gajim.contacts.get_first_contact_from_jid(acc, jid) + if old_name not in contact.groups: + continue + + self.remove_contact(jid, acc, force=True) + + contact.groups.remove(old_name) + if new_name not in contact.groups: + contact.groups.append(new_name) + + changed_contacts.append({'jid':jid, 'name':contact.name, + 'groups':contact.groups}) + + gajim.connections[acc].update_contacts(changed_contacts) + + for c in changed_contacts: + self.add_contact(c['jid'], acc) + + self._adjust_group_expand_collapse_state(new_name, acc) + + self.draw_group(old_name, acc) + self.draw_group(new_name, acc) + + + def add_contact_to_groups(self, jid, account, groups, update=True): + """ + Add contact to given groups and redraw them + + Contact on server is updated too. When the contact has a family, + the action will be performed for all members. + + Keyword Arguments: + jid -- the jid + account -- the corresponding account + groups -- list of Groups to add the contact to. + update -- update contact on the server + """ + self.remove_contact(jid, account, force=True) + for contact in gajim.contacts.get_contacts(account, jid): + for group in groups: + if group not in contact.groups: + # we might be dropped from meta to group + contact.groups.append(group) + if update: + gajim.connections[account].update_contact(jid, contact.name, + contact.groups) + + self.add_contact(jid, account) + + for group in groups: + self._adjust_group_expand_collapse_state(group, account) + + def remove_contact_from_groups(self, jid, account, groups, update=True): + """ + Remove contact from given groups and redraw them + + Contact on server is updated too. When the contact has a family, + the action will be performed for all members. + + Keyword Arguments: + jid -- the jid + account -- the corresponding account + groups -- list of Groups to remove the contact from + update -- update contact on the server + """ + self.remove_contact(jid, account, force=True) + for contact in gajim.contacts.get_contacts(account, jid): + for group in groups: + if group in contact.groups: + # Needed when we remove from "General" or "Observers" + contact.groups.remove(group) + if update: + gajim.connections[account].update_contact(jid, contact.name, + contact.groups) + self.add_contact(jid, account) + + # Also redraw old groups + for group in groups: + self.draw_group(group, account) + + # FIXME: maybe move to gajim.py + def remove_newly_added(self, jid, account): + if jid in gajim.newly_added[account]: + gajim.newly_added[account].remove(jid) + self.draw_contact(jid, account) + + # FIXME: maybe move to gajim.py + def remove_to_be_removed(self, jid, account): + if account not in gajim.interface.instances: + # Account has been deleted during the timeout that called us + return + if jid in gajim.newly_added[account]: + return + if jid in gajim.to_be_removed[account]: + gajim.to_be_removed[account].remove(jid) + family = gajim.contacts.get_metacontacts_family(account, jid) + if family: + # Peform delayed recalibration + self._recalibrate_metacontact_family(family, account) + self.draw_contact(jid, account) + + # FIXME: integrate into add_contact() + def add_to_not_in_the_roster(self, account, jid, nick='', resource=''): + keyID = '' + attached_keys = gajim.config.get_per('accounts', account, + 'attached_gpg_keys').split() + if jid in attached_keys: + keyID = attached_keys[attached_keys.index(jid) + 1] + contact = gajim.contacts.create_not_in_roster_contact(jid=jid, + account=account, resource=resource, name=nick, keyID=keyID) + gajim.contacts.add_contact(account, contact) + self.add_contact(contact.jid, account) + return contact ################################################################################ ### Methods for adding and removing roster window items ################################################################################ - def draw_account(self, account): - child_iter = self._get_account_iter(account, self.model) - if not child_iter: - assert False, 'Account iter of %s could not be found.' % account - return + def draw_account(self, account): + child_iter = self._get_account_iter(account, self.model) + if not child_iter: + assert False, 'Account iter of %s could not be found.' % account + return - num_of_accounts = gajim.get_number_of_connected_accounts() - num_of_secured = gajim.get_number_of_securely_connected_accounts() + num_of_accounts = gajim.get_number_of_connected_accounts() + num_of_secured = gajim.get_number_of_securely_connected_accounts() - if gajim.account_is_securely_connected(account) and not self.regroup or \ - self.regroup and num_of_secured and num_of_secured == num_of_accounts: - tls_pixbuf = self.window.render_icon(gtk.STOCK_DIALOG_AUTHENTICATION, - gtk.ICON_SIZE_MENU) # the only way to create a pixbuf from stock - self.model[child_iter][C_PADLOCK_PIXBUF] = tls_pixbuf - else: - self.model[child_iter][C_PADLOCK_PIXBUF] = None + if gajim.account_is_securely_connected(account) and not self.regroup or \ + self.regroup and num_of_secured and num_of_secured == num_of_accounts: + tls_pixbuf = self.window.render_icon(gtk.STOCK_DIALOG_AUTHENTICATION, + gtk.ICON_SIZE_MENU) # the only way to create a pixbuf from stock + self.model[child_iter][C_PADLOCK_PIXBUF] = tls_pixbuf + else: + self.model[child_iter][C_PADLOCK_PIXBUF] = None - if self.regroup: - account_name = _('Merged accounts') - accounts = [] - else: - account_name = account - accounts = [account] + if self.regroup: + account_name = _('Merged accounts') + accounts = [] + else: + account_name = account + accounts = [account] - if account in self.collapsed_rows and \ - self.model.iter_has_child(child_iter): - account_name = '[%s]' % account_name + if account in self.collapsed_rows and \ + self.model.iter_has_child(child_iter): + account_name = '[%s]' % account_name - if (gajim.account_is_connected(account) or (self.regroup and \ - gajim.get_number_of_connected_accounts())) and gajim.config.get( - 'show_contacts_number'): - nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts( - accounts = accounts) - account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total)) + if (gajim.account_is_connected(account) or (self.regroup and \ + gajim.get_number_of_connected_accounts())) and gajim.config.get( + 'show_contacts_number'): + nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts( + accounts = accounts) + account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total)) - self.model[child_iter][C_NAME] = account_name + self.model[child_iter][C_NAME] = account_name - pep = gajim.connections[account].pep - if gajim.config.get('show_mood_in_roster') and 'mood' in pep: - self.model[child_iter][C_MOOD_PIXBUF] = pep['mood'].asPixbufIcon() - else: - self.model[child_iter][C_MOOD_PIXBUF] = None + pep = gajim.connections[account].pep + if gajim.config.get('show_mood_in_roster') and 'mood' in pep: + self.model[child_iter][C_MOOD_PIXBUF] = pep['mood'].asPixbufIcon() + else: + self.model[child_iter][C_MOOD_PIXBUF] = None - if gajim.config.get('show_activity_in_roster') and 'activity' in pep: - self.model[child_iter][C_ACTIVITY_PIXBUF] = pep['activity'].asPixbufIcon() - else: - self.model[child_iter][C_ACTIVITY_PIXBUF] = None + if gajim.config.get('show_activity_in_roster') and 'activity' in pep: + self.model[child_iter][C_ACTIVITY_PIXBUF] = pep['activity'].asPixbufIcon() + else: + self.model[child_iter][C_ACTIVITY_PIXBUF] = None - if gajim.config.get('show_tunes_in_roster') and 'tune' in pep: - self.model[child_iter][C_TUNE_PIXBUF] = pep['tune'].asPixbufIcon() - else: - self.model[child_iter][C_TUNE_PIXBUF] = None + if gajim.config.get('show_tunes_in_roster') and 'tune' in pep: + self.model[child_iter][C_TUNE_PIXBUF] = pep['tune'].asPixbufIcon() + else: + self.model[child_iter][C_TUNE_PIXBUF] = None - if gajim.config.get('show_location_in_roster') and 'location' in pep: - self.model[child_iter][C_LOCATION_PIXBUF] = pep['location'].asPixbufIcon() - else: - self.model[child_iter][C_LOCATION_PIXBUF] = None - return False + if gajim.config.get('show_location_in_roster') and 'location' in pep: + self.model[child_iter][C_LOCATION_PIXBUF] = pep['location'].asPixbufIcon() + else: + self.model[child_iter][C_LOCATION_PIXBUF] = None + return False - def draw_group(self, group, account): - child_iter = self._get_group_iter(group, account, model=self.model) - if not child_iter: - # Eg. We redraw groups after we removed a entitiy - # and its empty groups - return - if self.regroup: - accounts = [] - else: - accounts = [account] - text = gobject.markup_escape_text(group) - if helpers.group_is_blocked(account, group): - text = '%s' % text - if gajim.config.get('show_contacts_number'): - nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts( - accounts = accounts, groups = [group]) - text += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total)) + def draw_group(self, group, account): + child_iter = self._get_group_iter(group, account, model=self.model) + if not child_iter: + # Eg. We redraw groups after we removed a entitiy + # and its empty groups + return + if self.regroup: + accounts = [] + else: + accounts = [account] + text = gobject.markup_escape_text(group) + if helpers.group_is_blocked(account, group): + text = '%s' % text + if gajim.config.get('show_contacts_number'): + nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts( + accounts = accounts, groups = [group]) + text += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total)) - self.model[child_iter][C_NAME] = text - return False + self.model[child_iter][C_NAME] = text + return False - def draw_parent_contact(self, jid, account): - child_iters = self._get_contact_iter(jid, account, model=self.model) - if not child_iters: - return False - parent_iter = self.model.iter_parent(child_iters[0]) - if self.model[parent_iter][C_TYPE] != 'contact': - # parent is not a contact - return - parent_jid = self.model[parent_iter][C_JID].decode('utf-8') - parent_account = self.model[parent_iter][C_ACCOUNT].decode('utf-8') - self.draw_contact(parent_jid, parent_account) - return False + def draw_parent_contact(self, jid, account): + child_iters = self._get_contact_iter(jid, account, model=self.model) + if not child_iters: + return False + parent_iter = self.model.iter_parent(child_iters[0]) + if self.model[parent_iter][C_TYPE] != 'contact': + # parent is not a contact + return + parent_jid = self.model[parent_iter][C_JID].decode('utf-8') + parent_account = self.model[parent_iter][C_ACCOUNT].decode('utf-8') + self.draw_contact(parent_jid, parent_account) + return False - def draw_contact(self, jid, account, selected=False, focus=False): - """ - Draw the correct state image, name BUT not avatar - """ - # focus is about if the roster window has toplevel-focus or not - # FIXME: We really need a custom cell_renderer + def draw_contact(self, jid, account, selected=False, focus=False): + """ + Draw the correct state image, name BUT not avatar + """ + # focus is about if the roster window has toplevel-focus or not + # FIXME: We really need a custom cell_renderer - contact_instances = gajim.contacts.get_contacts(account, jid) - contact = gajim.contacts.get_highest_prio_contact_from_contacts( - contact_instances) + contact_instances = gajim.contacts.get_contacts(account, jid) + contact = gajim.contacts.get_highest_prio_contact_from_contacts( + contact_instances) - child_iters = self._get_contact_iter(jid, account, contact, self.model) - if not child_iters: - return False + child_iters = self._get_contact_iter(jid, account, contact, self.model) + if not child_iters: + return False - name = gobject.markup_escape_text(contact.get_shown_name()) + name = gobject.markup_escape_text(contact.get_shown_name()) - # gets number of unread gc marked messages - if jid in gajim.interface.minimized_controls[account] and \ - gajim.interface.minimized_controls[account][jid]: - nb_unread = len(gajim.events.get_events(account, jid, - ['printed_marked_gc_msg'])) - nb_unread += gajim.interface.minimized_controls \ - [account][jid].get_nb_unread_pm() + # gets number of unread gc marked messages + if jid in gajim.interface.minimized_controls[account] and \ + gajim.interface.minimized_controls[account][jid]: + nb_unread = len(gajim.events.get_events(account, jid, + ['printed_marked_gc_msg'])) + nb_unread += gajim.interface.minimized_controls \ + [account][jid].get_nb_unread_pm() - if nb_unread == 1: - name = '%s *' % name - elif nb_unread > 1: - name = '%s [%s]' % (name, str(nb_unread)) + if nb_unread == 1: + name = '%s *' % name + elif nb_unread > 1: + name = '%s [%s]' % (name, str(nb_unread)) - # Strike name if blocked - strike = False - if helpers.jid_is_blocked(account, jid): - strike = True - else: - for group in contact.get_shown_groups(): - if helpers.group_is_blocked(account, group): - strike = True - break - if strike: - name = '%s' % name + # Strike name if blocked + strike = False + if helpers.jid_is_blocked(account, jid): + strike = True + else: + for group in contact.get_shown_groups(): + if helpers.group_is_blocked(account, group): + strike = True + break + if strike: + name = '%s' % name - # Show resource counter - nb_connected_contact = 0 - for c in contact_instances: - if c.show not in ('error', 'offline'): - nb_connected_contact += 1 - if nb_connected_contact > 1: - # switch back to default writing direction - name += i18n.paragraph_direction_mark(unicode(name)) - name += u' (%d)' % nb_connected_contact + # Show resource counter + nb_connected_contact = 0 + for c in contact_instances: + if c.show not in ('error', 'offline'): + nb_connected_contact += 1 + if nb_connected_contact > 1: + # switch back to default writing direction + name += i18n.paragraph_direction_mark(unicode(name)) + name += u' (%d)' % nb_connected_contact - # show (account_name) if there are 2 contact with same jid - # in merged mode - if self.regroup: - add_acct = False - # look through all contacts of all accounts - for account_ in gajim.connections: - # useless to add account name - if account_ == account: - continue - for jid_ in gajim.contacts.get_jid_list(account_): - contact_ = gajim.contacts.get_first_contact_from_jid( - account_, jid_) - if contact_.get_shown_name() == contact.get_shown_name() and \ - (jid_, account_) != (jid, account): - add_acct = True - break - if add_acct: - # No need to continue in other account - # if we already found one - break - if add_acct: - name += ' (' + account + ')' + # show (account_name) if there are 2 contact with same jid + # in merged mode + if self.regroup: + add_acct = False + # look through all contacts of all accounts + for account_ in gajim.connections: + # useless to add account name + if account_ == account: + continue + for jid_ in gajim.contacts.get_jid_list(account_): + contact_ = gajim.contacts.get_first_contact_from_jid( + account_, jid_) + if contact_.get_shown_name() == contact.get_shown_name() and \ + (jid_, account_) != (jid, account): + add_acct = True + break + if add_acct: + # No need to continue in other account + # if we already found one + break + if add_acct: + name += ' (' + account + ')' - # add status msg, if not empty, under contact name in - # the treeview - if contact.status and gajim.config.get('show_status_msgs_in_roster'): - status = contact.status.strip() - if status != '': - status = helpers.reduce_chars_newlines(status, - max_lines = 1) - # escape markup entities and make them small - # italic and fg color color is calcuted to be - # always readable - color = gtkgui_helpers._get_fade_color(self.tree, selected, focus) - colorstring = '#%04x%04x%04x' % (color.red, color.green, color.blue) - name += '\n%s' % ( - colorstring, - gobject.markup_escape_text(status)) + # add status msg, if not empty, under contact name in + # the treeview + if contact.status and gajim.config.get('show_status_msgs_in_roster'): + status = contact.status.strip() + if status != '': + status = helpers.reduce_chars_newlines(status, + max_lines = 1) + # escape markup entities and make them small + # italic and fg color color is calcuted to be + # always readable + color = gtkgui_helpers._get_fade_color(self.tree, selected, focus) + colorstring = '#%04x%04x%04x' % (color.red, color.green, color.blue) + name += '\n%s' % ( + colorstring, + gobject.markup_escape_text(status)) - icon_name = helpers.get_icon_name_to_show(contact, account) - # look if another resource has awaiting events - for c in contact_instances: - c_icon_name = helpers.get_icon_name_to_show(c, account) - if c_icon_name in ('event', 'muc_active', 'muc_inactive'): - icon_name = c_icon_name - break + icon_name = helpers.get_icon_name_to_show(contact, account) + # look if another resource has awaiting events + for c in contact_instances: + c_icon_name = helpers.get_icon_name_to_show(c, account) + if c_icon_name in ('event', 'muc_active', 'muc_inactive'): + icon_name = c_icon_name + break - # Check for events of collapsed (hidden) brothers - family = gajim.contacts.get_metacontacts_family(account, jid) - is_big_brother = False - have_visible_children = False - if family: - bb_jid, bb_account = \ - self._get_nearby_family_and_big_brother(family, account)[1:] - is_big_brother = (jid, account) == (bb_jid, bb_account) - iters = self._get_contact_iter(jid, account) - have_visible_children = iters \ - and self.modelfilter.iter_has_child(iters[0]) + # Check for events of collapsed (hidden) brothers + family = gajim.contacts.get_metacontacts_family(account, jid) + is_big_brother = False + have_visible_children = False + if family: + bb_jid, bb_account = \ + self._get_nearby_family_and_big_brother(family, account)[1:] + is_big_brother = (jid, account) == (bb_jid, bb_account) + iters = self._get_contact_iter(jid, account) + have_visible_children = iters \ + and self.modelfilter.iter_has_child(iters[0]) - if have_visible_children: - # We are the big brother and have a visible family - for child_iter in child_iters: - child_path = self.model.get_path(child_iter) - path = self.modelfilter.convert_child_path_to_path(child_path) + if have_visible_children: + # We are the big brother and have a visible family + for child_iter in child_iters: + child_path = self.model.get_path(child_iter) + path = self.modelfilter.convert_child_path_to_path(child_path) - if not path: - continue + if not path: + continue - if not self.tree.row_expanded(path) and icon_name != 'event': - iterC = self.model.iter_children(child_iter) - while iterC: - # a child has awaiting messages? - jidC = self.model[iterC][C_JID].decode('utf-8') - accountC = self.model[iterC][C_ACCOUNT].decode('utf-8') - if len(gajim.events.get_events(accountC, jidC)): - icon_name = 'event' - break - iterC = self.model.iter_next(iterC) + if not self.tree.row_expanded(path) and icon_name != 'event': + iterC = self.model.iter_children(child_iter) + while iterC: + # a child has awaiting messages? + jidC = self.model[iterC][C_JID].decode('utf-8') + accountC = self.model[iterC][C_ACCOUNT].decode('utf-8') + if len(gajim.events.get_events(accountC, jidC)): + icon_name = 'event' + break + iterC = self.model.iter_next(iterC) - if self.tree.row_expanded(path): - state_images = self.get_appropriate_state_images( - jid, size = 'opened', - icon_name = icon_name) - else: - state_images = self.get_appropriate_state_images( - jid, size = 'closed', - icon_name = icon_name) + if self.tree.row_expanded(path): + state_images = self.get_appropriate_state_images( + jid, size = 'opened', + icon_name = icon_name) + else: + state_images = self.get_appropriate_state_images( + jid, size = 'closed', + icon_name = icon_name) - # Expand/collapse icon might differ per iter - # (group) - img = state_images[icon_name] - self.model[child_iter][C_IMG] = img - self.model[child_iter][C_NAME] = name - else: - # A normal contact or little brother - state_images = self.get_appropriate_state_images(jid, - icon_name = icon_name) + # Expand/collapse icon might differ per iter + # (group) + img = state_images[icon_name] + self.model[child_iter][C_IMG] = img + self.model[child_iter][C_NAME] = name + else: + # A normal contact or little brother + state_images = self.get_appropriate_state_images(jid, + icon_name = icon_name) - # All iters have the same icon (no expand/collapse) - img = state_images[icon_name] - for child_iter in child_iters: - self.model[child_iter][C_IMG] = img - self.model[child_iter][C_NAME] = name + # All iters have the same icon (no expand/collapse) + img = state_images[icon_name] + for child_iter in child_iters: + self.model[child_iter][C_IMG] = img + self.model[child_iter][C_NAME] = name - # We are a little brother - if family and not is_big_brother and not self.starting: - self.draw_parent_contact(jid, account) + # We are a little brother + if family and not is_big_brother and not self.starting: + self.draw_parent_contact(jid, account) - for group in contact.get_shown_groups(): - # We need to make sure that _visible_func is called for - # our groups otherwise we might not be shown - iterG = self._get_group_iter(group, account, model=self.model) - if iterG: - # it's not self contact - self.model[iterG][C_JID] = self.model[iterG][C_JID] + for group in contact.get_shown_groups(): + # We need to make sure that _visible_func is called for + # our groups otherwise we might not be shown + iterG = self._get_group_iter(group, account, model=self.model) + if iterG: + # it's not self contact + self.model[iterG][C_JID] = self.model[iterG][C_JID] - return False + return False - def _is_pep_shown_in_roster(self, pep_type): - if pep_type == 'mood': - return gajim.config.get('show_mood_in_roster') - elif pep_type == 'activity': - return gajim.config.get('show_activity_in_roster') - elif pep_type == 'tune': - return gajim.config.get('show_tunes_in_roster') - elif pep_type == 'location': - return gajim.config.get('show_location_in_roster') - else: - return False + def _is_pep_shown_in_roster(self, pep_type): + if pep_type == 'mood': + return gajim.config.get('show_mood_in_roster') + elif pep_type == 'activity': + return gajim.config.get('show_activity_in_roster') + elif pep_type == 'tune': + return gajim.config.get('show_tunes_in_roster') + elif pep_type == 'location': + return gajim.config.get('show_location_in_roster') + else: + return False - def draw_all_pep_types(self, jid, account): - for pep_type in self._pep_type_to_model_column: - self.draw_pep(jid, account, pep_type) + def draw_all_pep_types(self, jid, account): + for pep_type in self._pep_type_to_model_column: + self.draw_pep(jid, account, pep_type) - def draw_pep(self, jid, account, pep_type): - if pep_type not in self._pep_type_to_model_column: - return - if not self._is_pep_shown_in_roster(pep_type): - return + def draw_pep(self, jid, account, pep_type): + if pep_type not in self._pep_type_to_model_column: + return + if not self._is_pep_shown_in_roster(pep_type): + return - model_column = self._pep_type_to_model_column[pep_type] - iters = self._get_contact_iter(jid, account, model=self.model) - if not iters: - return - jid = self.model[iters[0]][C_JID].decode('utf-8') - contact = gajim.contacts.get_contact(account, jid) - if pep_type in contact.pep: - pixbuf = contact.pep[pep_type].asPixbufIcon() - else: - pixbuf = None - for child_iter in iters: - self.model[child_iter][model_column] = pixbuf + model_column = self._pep_type_to_model_column[pep_type] + iters = self._get_contact_iter(jid, account, model=self.model) + if not iters: + return + jid = self.model[iters[0]][C_JID].decode('utf-8') + contact = gajim.contacts.get_contact(account, jid) + if pep_type in contact.pep: + pixbuf = contact.pep[pep_type].asPixbufIcon() + else: + pixbuf = None + for child_iter in iters: + self.model[child_iter][model_column] = pixbuf - def draw_avatar(self, jid, account): - iters = self._get_contact_iter(jid, account, model=self.model) - if not iters or not gajim.config.get('show_avatars_in_roster'): - return - jid = self.model[iters[0]][C_JID].decode('utf-8') - pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid) - if pixbuf in (None, 'ask'): - scaled_pixbuf = None - else: - scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'roster') - for child_iter in iters: - self.model[child_iter][C_AVATAR_PIXBUF] = scaled_pixbuf - return False + def draw_avatar(self, jid, account): + iters = self._get_contact_iter(jid, account, model=self.model) + if not iters or not gajim.config.get('show_avatars_in_roster'): + return + jid = self.model[iters[0]][C_JID].decode('utf-8') + pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid) + if pixbuf in (None, 'ask'): + scaled_pixbuf = None + else: + scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'roster') + for child_iter in iters: + self.model[child_iter][C_AVATAR_PIXBUF] = scaled_pixbuf + return False - def draw_completely(self, jid, account): - self.draw_contact(jid, account) - self.draw_all_pep_types(jid, account) - self.draw_avatar(jid, account) + def draw_completely(self, jid, account): + self.draw_contact(jid, account) + self.draw_all_pep_types(jid, account) + self.draw_avatar(jid, account) - def adjust_and_draw_contact_context(self, jid, account): - """ - Draw contact, account and groups of given jid Show contact if it has - pending events - """ - contact = gajim.contacts.get_first_contact_from_jid(account, jid) - if not contact: - # idle draw or just removed SelfContact - return + def adjust_and_draw_contact_context(self, jid, account): + """ + Draw contact, account and groups of given jid Show contact if it has + pending events + """ + contact = gajim.contacts.get_first_contact_from_jid(account, jid) + if not contact: + # idle draw or just removed SelfContact + return - family = gajim.contacts.get_metacontacts_family(account, jid) - if family: - # There might be a new big brother - self._recalibrate_metacontact_family(family, account) - self.draw_contact(jid, account) - self.draw_account(account) + family = gajim.contacts.get_metacontacts_family(account, jid) + if family: + # There might be a new big brother + self._recalibrate_metacontact_family(family, account) + self.draw_contact(jid, account) + self.draw_account(account) - for group in contact.get_shown_groups(): - self.draw_group(group, account) - self._adjust_group_expand_collapse_state(group, account) + for group in contact.get_shown_groups(): + self.draw_group(group, account) + self._adjust_group_expand_collapse_state(group, account) - def _idle_draw_jids_of_account(self, jids, account): - """ - Draw given contacts and their avatars in a lazy fashion + def _idle_draw_jids_of_account(self, jids, account): + """ + Draw given contacts and their avatars in a lazy fashion - Keyword arguments: - jids -- a list of jids to draw - account -- the corresponding account - """ - def _draw_all_contacts(jids, account): - for jid in jids: - family = gajim.contacts.get_metacontacts_family(account, jid) - if family: - # For metacontacts over several accounts: - # When we connect a new account existing brothers - # must be redrawn (got removed and readded) - for data in family: - self.draw_completely(data['jid'], data['account']) - else: - self.draw_completely(jid, account) - yield True - yield False + Keyword arguments: + jids -- a list of jids to draw + account -- the corresponding account + """ + def _draw_all_contacts(jids, account): + for jid in jids: + family = gajim.contacts.get_metacontacts_family(account, jid) + if family: + # For metacontacts over several accounts: + # When we connect a new account existing brothers + # must be redrawn (got removed and readded) + for data in family: + self.draw_completely(data['jid'], data['account']) + else: + self.draw_completely(jid, account) + yield True + yield False - task = _draw_all_contacts(jids, account) - gobject.idle_add(task.next) + task = _draw_all_contacts(jids, account) + gobject.idle_add(task.next) - def setup_and_draw_roster(self): - """ - Create new empty model and draw roster - """ - self.modelfilter = None - # (icon, name, type, jid, account, editable, mood_pixbuf, - # activity_pixbuf, tune_pixbuf, location_pixbuf, avatar_pixbuf, - # padlock_pixbuf) - self.model = gtk.TreeStore(gtk.Image, str, str, str, str, - gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, - gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, gtk.gdk.Pixbuf) + def setup_and_draw_roster(self): + """ + Create new empty model and draw roster + """ + self.modelfilter = None + # (icon, name, type, jid, account, editable, mood_pixbuf, + # activity_pixbuf, tune_pixbuf, location_pixbuf, avatar_pixbuf, + # padlock_pixbuf) + self.model = gtk.TreeStore(gtk.Image, str, str, str, str, + gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, + gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, gtk.gdk.Pixbuf) - self.model.set_sort_func(1, self._compareIters) - self.model.set_sort_column_id(1, gtk.SORT_ASCENDING) - self.modelfilter = self.model.filter_new() - self.modelfilter.set_visible_func(self._visible_func) - self.modelfilter.connect('row-has-child-toggled', - self.on_modelfilter_row_has_child_toggled) - self.tree.set_model(self.modelfilter) + self.model.set_sort_func(1, self._compareIters) + self.model.set_sort_column_id(1, gtk.SORT_ASCENDING) + self.modelfilter = self.model.filter_new() + self.modelfilter.set_visible_func(self._visible_func) + self.modelfilter.connect('row-has-child-toggled', + self.on_modelfilter_row_has_child_toggled) + self.tree.set_model(self.modelfilter) - for acct in gajim.connections: - self.add_account(acct) - self.add_account_contacts(acct) - # Recalculate column width for ellipsizing - self.tree.columns_autosize() + for acct in gajim.connections: + self.add_account(acct) + self.add_account_contacts(acct) + # Recalculate column width for ellipsizing + self.tree.columns_autosize() - def select_contact(self, jid, account): - """ - Select contact in roster. If contact is hidden but has events, show him - """ - # Refiltering SHOULD NOT be needed: - # When a contact gets a new event he will be redrawn and his - # icon changes, so _visible_func WILL be called on him anyway - iters = self._get_contact_iter(jid, account) - if not iters: - # Not visible in roster - return - path = self.modelfilter.get_path(iters[0]) - if self.dragging or not gajim.config.get('scroll_roster_to_last_message'): - # do not change selection while DND'ing - return - # Expand his parent, so this path is visible, don't expand it. - self.tree.expand_to_path(path[:-1]) - self.tree.scroll_to_cell(path) - self.tree.set_cursor(path) + def select_contact(self, jid, account): + """ + Select contact in roster. If contact is hidden but has events, show him + """ + # Refiltering SHOULD NOT be needed: + # When a contact gets a new event he will be redrawn and his + # icon changes, so _visible_func WILL be called on him anyway + iters = self._get_contact_iter(jid, account) + if not iters: + # Not visible in roster + return + path = self.modelfilter.get_path(iters[0]) + if self.dragging or not gajim.config.get('scroll_roster_to_last_message'): + # do not change selection while DND'ing + return + # Expand his parent, so this path is visible, don't expand it. + self.tree.expand_to_path(path[:-1]) + self.tree.scroll_to_cell(path) + self.tree.set_cursor(path) - def _adjust_account_expand_collapse_state(self, account): - """ - Expand/collapse account row based on self.collapsed_rows - """ - iterA = self._get_account_iter(account) - if not iterA: - # thank you modelfilter - return - path = self.modelfilter.get_path(iterA) - if account in self.collapsed_rows: - self.tree.collapse_row(path) - else: - self.tree.expand_row(path, False) - return False + def _adjust_account_expand_collapse_state(self, account): + """ + Expand/collapse account row based on self.collapsed_rows + """ + iterA = self._get_account_iter(account) + if not iterA: + # thank you modelfilter + return + path = self.modelfilter.get_path(iterA) + if account in self.collapsed_rows: + self.tree.collapse_row(path) + else: + self.tree.expand_row(path, False) + return False - def _adjust_group_expand_collapse_state(self, group, account): - """ - Expand/collapse group row based on self.collapsed_rows - """ - iterG = self._get_group_iter(group, account) - if not iterG: - # Group not visible - return - path = self.modelfilter.get_path(iterG) - if account + group in self.collapsed_rows: - self.tree.collapse_row(path) - else: - self.tree.expand_row(path, False) - return False + def _adjust_group_expand_collapse_state(self, group, account): + """ + Expand/collapse group row based on self.collapsed_rows + """ + iterG = self._get_group_iter(group, account) + if not iterG: + # Group not visible + return + path = self.modelfilter.get_path(iterG) + if account + group in self.collapsed_rows: + self.tree.collapse_row(path) + else: + self.tree.expand_row(path, False) + return False ############################################################################## ### Roster and Modelfilter handling ############################################################################## - def _search_roster_func(self, model, column, key, titer): - key = key.decode('utf-8').lower() - name = model[titer][C_NAME].decode('utf-8').lower() - return not (key in name) + def _search_roster_func(self, model, column, key, titer): + key = key.decode('utf-8').lower() + name = model[titer][C_NAME].decode('utf-8').lower() + return not (key in name) - def refilter_shown_roster_items(self): - self.filtering = True - self.modelfilter.refilter() - self.filtering = False + def refilter_shown_roster_items(self): + self.filtering = True + self.modelfilter.refilter() + self.filtering = False - def contact_has_pending_roster_events(self, contact, account): - """ - Return True if the contact or one if it resources has pending events - """ - # jid has pending events - if gajim.events.get_nb_roster_events(account, contact.jid) > 0: - return True - # check events of all resources - for contact_ in gajim.contacts.get_contacts(account, contact.jid): - if contact_.resource and gajim.events.get_nb_roster_events(account, - contact_.get_full_jid()) > 0: - return True - return False + def contact_has_pending_roster_events(self, contact, account): + """ + Return True if the contact or one if it resources has pending events + """ + # jid has pending events + if gajim.events.get_nb_roster_events(account, contact.jid) > 0: + return True + # check events of all resources + for contact_ in gajim.contacts.get_contacts(account, contact.jid): + if contact_.resource and gajim.events.get_nb_roster_events(account, + contact_.get_full_jid()) > 0: + return True + return False - def contact_is_visible(self, contact, account): - if self.contact_has_pending_roster_events(contact, account): - return True + def contact_is_visible(self, contact, account): + if self.contact_has_pending_roster_events(contact, account): + return True - if contact.show in ('offline', 'error'): - if contact.jid in gajim.to_be_removed[account]: - return True - return False - if gajim.config.get('show_only_chat_and_online') and contact.show in ( - 'away', 'xa', 'busy'): - return False - return True + if contact.show in ('offline', 'error'): + if contact.jid in gajim.to_be_removed[account]: + return True + return False + if gajim.config.get('show_only_chat_and_online') and contact.show in ( + 'away', 'xa', 'busy'): + return False + return True - def _visible_func(self, model, titer): - """ - Determine whether iter should be visible in the treeview - """ - type_ = model[titer][C_TYPE] - if not type_: - return False - if type_ == 'account': - # Always show account - return True + def _visible_func(self, model, titer): + """ + Determine whether iter should be visible in the treeview + """ + type_ = model[titer][C_TYPE] + if not type_: + return False + if type_ == 'account': + # Always show account + return True - account = model[titer][C_ACCOUNT] - if not account: - return False + account = model[titer][C_ACCOUNT] + if not account: + return False - account = account.decode('utf-8') - jid = model[titer][C_JID] - if not jid: - return False - jid = jid.decode('utf-8') - if type_ == 'group': - group = jid - if group == _('Transports'): - if self.regroup: - accounts = gajim.contacts.get_accounts() - else: - accounts = [account] - for _acc in accounts: - for contact in gajim.contacts.iter_contacts(_acc): - if group in contact.get_shown_groups() and \ - self.contact_has_pending_roster_events(contact, _acc): - return True - return gajim.config.get('show_transports_group') and \ - (gajim.account_is_connected(account) or \ - gajim.config.get('showoffline')) - if gajim.config.get('showoffline'): - return True + account = account.decode('utf-8') + jid = model[titer][C_JID] + if not jid: + return False + jid = jid.decode('utf-8') + if type_ == 'group': + group = jid + if group == _('Transports'): + if self.regroup: + accounts = gajim.contacts.get_accounts() + else: + accounts = [account] + for _acc in accounts: + for contact in gajim.contacts.iter_contacts(_acc): + if group in contact.get_shown_groups() and \ + self.contact_has_pending_roster_events(contact, _acc): + return True + return gajim.config.get('show_transports_group') and \ + (gajim.account_is_connected(account) or \ + gajim.config.get('showoffline')) + if gajim.config.get('showoffline'): + return True - if self.regroup: - # C_ACCOUNT for groups depends on the order - # accounts were connected - # Check all accounts for online group contacts - accounts = gajim.contacts.get_accounts() - else: - accounts = [account] - for _acc in accounts: - for contact in gajim.contacts.iter_contacts(_acc): - # Is this contact in this group ? (last part of if check if it's - # self contact) - if group in contact.get_shown_groups(): - if self.contact_is_visible(contact, _acc): - return True - return False - if type_ == 'contact': - if gajim.config.get('showoffline'): - return True - bb_jid = None - bb_account = None - family = gajim.contacts.get_metacontacts_family(account, jid) - if family: - nearby_family, bb_jid, bb_account = \ - self._get_nearby_family_and_big_brother(family, account) - if (bb_jid, bb_account) == (jid, account): - # Show the big brother if a child has pending events - for data in nearby_family: - jid = data['jid'] - account = data['account'] - contact = gajim.contacts.get_contact_with_highest_priority( - account, jid) - if contact and self.contact_is_visible(contact, account): - return True - return False - else: - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - return self.contact_is_visible(contact, account) - if type_ == 'agent': - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - return self.contact_has_pending_roster_events(contact, account) or \ - (gajim.config.get('show_transports_group') and \ - (gajim.account_is_connected(account) or \ - gajim.config.get('showoffline'))) - return True + if self.regroup: + # C_ACCOUNT for groups depends on the order + # accounts were connected + # Check all accounts for online group contacts + accounts = gajim.contacts.get_accounts() + else: + accounts = [account] + for _acc in accounts: + for contact in gajim.contacts.iter_contacts(_acc): + # Is this contact in this group ? (last part of if check if it's + # self contact) + if group in contact.get_shown_groups(): + if self.contact_is_visible(contact, _acc): + return True + return False + if type_ == 'contact': + if gajim.config.get('showoffline'): + return True + bb_jid = None + bb_account = None + family = gajim.contacts.get_metacontacts_family(account, jid) + if family: + nearby_family, bb_jid, bb_account = \ + self._get_nearby_family_and_big_brother(family, account) + if (bb_jid, bb_account) == (jid, account): + # Show the big brother if a child has pending events + for data in nearby_family: + jid = data['jid'] + account = data['account'] + contact = gajim.contacts.get_contact_with_highest_priority( + account, jid) + if contact and self.contact_is_visible(contact, account): + return True + return False + else: + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + return self.contact_is_visible(contact, account) + if type_ == 'agent': + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + return self.contact_has_pending_roster_events(contact, account) or \ + (gajim.config.get('show_transports_group') and \ + (gajim.account_is_connected(account) or \ + gajim.config.get('showoffline'))) + return True - def _compareIters(self, model, iter1, iter2, data=None): - """ - Compare two iters to sort them - """ - name1 = model[iter1][C_NAME] - name2 = model[iter2][C_NAME] - if not name1 or not name2: - return 0 - name1 = name1.decode('utf-8') - name2 = name2.decode('utf-8') - type1 = model[iter1][C_TYPE] - type2 = model[iter2][C_TYPE] - if type1 == 'self_contact': - return -1 - if type2 == 'self_contact': - return 1 - if type1 == 'group': - name1 = model[iter1][C_JID] - name2 = model[iter2][C_JID] - if name1 == _('Transports'): - return 1 - if name2 == _('Transports'): - return -1 - if name1 == _('Not in Roster'): - return 1 - if name2 == _('Not in Roster'): - return -1 - if name1 == _('Groupchats'): - return 1 - if name2 == _('Groupchats'): - return -1 - account1 = model[iter1][C_ACCOUNT] - account2 = model[iter2][C_ACCOUNT] - if not account1 or not account2: - return 0 - account1 = account1.decode('utf-8') - account2 = account2.decode('utf-8') - if type1 == 'account': - return locale.strcoll(account1, account2) - jid1 = model[iter1][C_JID].decode('utf-8') - jid2 = model[iter2][C_JID].decode('utf-8') - if type1 == 'contact': - lcontact1 = gajim.contacts.get_contacts(account1, jid1) - contact1 = gajim.contacts.get_first_contact_from_jid(account1, jid1) - if not contact1: - return 0 - name1 = contact1.get_shown_name() - if type2 == 'contact': - lcontact2 = gajim.contacts.get_contacts(account2, jid2) - contact2 = gajim.contacts.get_first_contact_from_jid(account2, jid2) - if not contact2: - return 0 - name2 = contact2.get_shown_name() - # We first compare by show if sort_by_show_in_roster is True or if it's a - # child contact - if type1 == 'contact' and type2 == 'contact' and \ - gajim.config.get('sort_by_show_in_roster'): - cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4, - 'invisible': 5, 'offline': 6, 'not in roster': 7, 'error': 8} - s = self.get_show(lcontact1) - show1 = cshow.get(s, 9) - s = self.get_show(lcontact2) - show2 = cshow.get(s, 9) - removing1 = False - removing2 = False - if show1 == 6 and jid1 in gajim.to_be_removed[account1]: - removing1 = True - if show2 == 6 and jid2 in gajim.to_be_removed[account2]: - removing2 = True - if removing1 and not removing2: - return 1 - if removing2 and not removing1: - return -1 - sub1 = contact1.sub - sub2 = contact2.sub - # none and from goes after - if sub1 not in ['none', 'from'] and sub2 in ['none', 'from']: - return -1 - if sub1 in ['none', 'from'] and sub2 not in ['none', 'from']: - return 1 - if show1 < show2: - return -1 - elif show1 > show2: - return 1 - # We compare names - cmp_result = locale.strcoll(name1.lower(), name2.lower()) - if cmp_result < 0: - return -1 - if cmp_result > 0: - return 1 - if type1 == 'contact' and type2 == 'contact': - # We compare account names - cmp_result = locale.strcoll(account1.lower(), account2.lower()) - if cmp_result < 0: - return -1 - if cmp_result > 0: - return 1 - # We compare jids - cmp_result = locale.strcoll(jid1.lower(), jid2.lower()) - if cmp_result < 0: - return -1 - if cmp_result > 0: - return 1 - return 0 + def _compareIters(self, model, iter1, iter2, data=None): + """ + Compare two iters to sort them + """ + name1 = model[iter1][C_NAME] + name2 = model[iter2][C_NAME] + if not name1 or not name2: + return 0 + name1 = name1.decode('utf-8') + name2 = name2.decode('utf-8') + type1 = model[iter1][C_TYPE] + type2 = model[iter2][C_TYPE] + if type1 == 'self_contact': + return -1 + if type2 == 'self_contact': + return 1 + if type1 == 'group': + name1 = model[iter1][C_JID] + name2 = model[iter2][C_JID] + if name1 == _('Transports'): + return 1 + if name2 == _('Transports'): + return -1 + if name1 == _('Not in Roster'): + return 1 + if name2 == _('Not in Roster'): + return -1 + if name1 == _('Groupchats'): + return 1 + if name2 == _('Groupchats'): + return -1 + account1 = model[iter1][C_ACCOUNT] + account2 = model[iter2][C_ACCOUNT] + if not account1 or not account2: + return 0 + account1 = account1.decode('utf-8') + account2 = account2.decode('utf-8') + if type1 == 'account': + return locale.strcoll(account1, account2) + jid1 = model[iter1][C_JID].decode('utf-8') + jid2 = model[iter2][C_JID].decode('utf-8') + if type1 == 'contact': + lcontact1 = gajim.contacts.get_contacts(account1, jid1) + contact1 = gajim.contacts.get_first_contact_from_jid(account1, jid1) + if not contact1: + return 0 + name1 = contact1.get_shown_name() + if type2 == 'contact': + lcontact2 = gajim.contacts.get_contacts(account2, jid2) + contact2 = gajim.contacts.get_first_contact_from_jid(account2, jid2) + if not contact2: + return 0 + name2 = contact2.get_shown_name() + # We first compare by show if sort_by_show_in_roster is True or if it's a + # child contact + if type1 == 'contact' and type2 == 'contact' and \ + gajim.config.get('sort_by_show_in_roster'): + cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4, + 'invisible': 5, 'offline': 6, 'not in roster': 7, 'error': 8} + s = self.get_show(lcontact1) + show1 = cshow.get(s, 9) + s = self.get_show(lcontact2) + show2 = cshow.get(s, 9) + removing1 = False + removing2 = False + if show1 == 6 and jid1 in gajim.to_be_removed[account1]: + removing1 = True + if show2 == 6 and jid2 in gajim.to_be_removed[account2]: + removing2 = True + if removing1 and not removing2: + return 1 + if removing2 and not removing1: + return -1 + sub1 = contact1.sub + sub2 = contact2.sub + # none and from goes after + if sub1 not in ['none', 'from'] and sub2 in ['none', 'from']: + return -1 + if sub1 in ['none', 'from'] and sub2 not in ['none', 'from']: + return 1 + if show1 < show2: + return -1 + elif show1 > show2: + return 1 + # We compare names + cmp_result = locale.strcoll(name1.lower(), name2.lower()) + if cmp_result < 0: + return -1 + if cmp_result > 0: + return 1 + if type1 == 'contact' and type2 == 'contact': + # We compare account names + cmp_result = locale.strcoll(account1.lower(), account2.lower()) + if cmp_result < 0: + return -1 + if cmp_result > 0: + return 1 + # We compare jids + cmp_result = locale.strcoll(jid1.lower(), jid2.lower()) + if cmp_result < 0: + return -1 + if cmp_result > 0: + return 1 + return 0 ################################################################################ ### FIXME: Methods that don't belong to roster window... -### ... atleast not in there current form +### ... atleast not in there current form ################################################################################ - def fire_up_unread_messages_events(self, account): - """ - Read from db the unread messages, and fire them up, and if we find very - old unread messages, delete them from unread table - """ - results = gajim.logger.get_unread_msgs() - for result in results: - jid = result[4] - shown = result[5] - if gajim.contacts.get_first_contact_from_jid(account, jid) and not \ - shown: - # We have this jid in our contacts list - # XXX unread messages should probably have their session saved with - # them - session = gajim.connections[account].make_new_session(jid) + def fire_up_unread_messages_events(self, account): + """ + Read from db the unread messages, and fire them up, and if we find very + old unread messages, delete them from unread table + """ + results = gajim.logger.get_unread_msgs() + for result in results: + jid = result[4] + shown = result[5] + if gajim.contacts.get_first_contact_from_jid(account, jid) and not \ + shown: + # We have this jid in our contacts list + # XXX unread messages should probably have their session saved with + # them + session = gajim.connections[account].make_new_session(jid) - tim = time.localtime(float(result[2])) - session.roster_message(jid, result[1], tim, msg_type='chat', - msg_id=result[0]) - gajim.logger.set_shown_unread_msgs(result[0]) + tim = time.localtime(float(result[2])) + session.roster_message(jid, result[1], tim, msg_type='chat', + msg_id=result[0]) + gajim.logger.set_shown_unread_msgs(result[0]) - elif (time.time() - result[2]) > 2592000: - # ok, here we see that we have a message in unread messages table - # that is older than a month. It is probably from someone not in our - # roster for accounts we usually launch, so we will delete this id - # from unread message tables. - gajim.logger.set_read_messages([result[0]]) + elif (time.time() - result[2]) > 2592000: + # ok, here we see that we have a message in unread messages table + # that is older than a month. It is probably from someone not in our + # roster for accounts we usually launch, so we will delete this id + # from unread message tables. + gajim.logger.set_read_messages([result[0]]) - def fill_contacts_and_groups_dicts(self, array, account): - """ - Fill gajim.contacts and gajim.groups - """ - # FIXME: This function needs to be splitted - # Most of the logic SHOULD NOT be done at GUI level - if account not in gajim.contacts.get_accounts(): - gajim.contacts.add_account(account) - if account not in gajim.groups: - gajim.groups[account] = {} - if gajim.config.get('show_self_contact') == 'always': - self_jid = gajim.get_jid_from_account(account) - if gajim.connections[account].server_resource: - self_jid += '/' + gajim.connections[account].server_resource - array[self_jid] = {'name': gajim.nicks[account], - 'groups': ['self_contact'], 'subscription': 'both', 'ask': 'none'} - # .keys() is needed - for jid in array.keys(): - # Remove the contact in roster. It might has changed - self.remove_contact(jid, account, force=True) - # Remove old Contact instances - gajim.contacts.remove_jid(account, jid, remove_meta=False) - jids = jid.split('/') - # get jid - ji = jids[0] - # get resource - resource = '' - if len(jids) > 1: - resource = '/'.join(jids[1:]) - # get name - name = array[jid]['name'] or '' - show = 'offline' # show is offline by default - status = '' # no status message by default + def fill_contacts_and_groups_dicts(self, array, account): + """ + Fill gajim.contacts and gajim.groups + """ + # FIXME: This function needs to be splitted + # Most of the logic SHOULD NOT be done at GUI level + if account not in gajim.contacts.get_accounts(): + gajim.contacts.add_account(account) + if account not in gajim.groups: + gajim.groups[account] = {} + if gajim.config.get('show_self_contact') == 'always': + self_jid = gajim.get_jid_from_account(account) + if gajim.connections[account].server_resource: + self_jid += '/' + gajim.connections[account].server_resource + array[self_jid] = {'name': gajim.nicks[account], + 'groups': ['self_contact'], 'subscription': 'both', 'ask': 'none'} + # .keys() is needed + for jid in array.keys(): + # Remove the contact in roster. It might has changed + self.remove_contact(jid, account, force=True) + # Remove old Contact instances + gajim.contacts.remove_jid(account, jid, remove_meta=False) + jids = jid.split('/') + # get jid + ji = jids[0] + # get resource + resource = '' + if len(jids) > 1: + resource = '/'.join(jids[1:]) + # get name + name = array[jid]['name'] or '' + show = 'offline' # show is offline by default + status = '' # no status message by default - keyID = '' - attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() - if jid in attached_keys: - keyID = attached_keys[attached_keys.index(jid) + 1] + keyID = '' + attached_keys = gajim.config.get_per('accounts', account, + 'attached_gpg_keys').split() + if jid in attached_keys: + keyID = attached_keys[attached_keys.index(jid) + 1] - if gajim.jid_is_transport(jid): - array[jid]['groups'] = [_('Transports')] - #TRANSP - potential - contact1 = gajim.contacts.create_contact(jid=ji, account=account, name=name, - groups=array[jid]['groups'], show=show, status=status, - sub=array[jid]['subscription'], ask=array[jid]['ask'], - resource=resource, keyID=keyID) - gajim.contacts.add_contact(account, contact1) + if gajim.jid_is_transport(jid): + array[jid]['groups'] = [_('Transports')] + #TRANSP - potential + contact1 = gajim.contacts.create_contact(jid=ji, account=account, name=name, + groups=array[jid]['groups'], show=show, status=status, + sub=array[jid]['subscription'], ask=array[jid]['ask'], + resource=resource, keyID=keyID) + gajim.contacts.add_contact(account, contact1) - if gajim.config.get('ask_avatars_on_startup'): - pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(ji) - if pixbuf == 'ask': - transport = gajim.get_transport_name_from_jid(contact1.jid) - if not transport or gajim.jid_is_transport(contact1.jid): - jid_with_resource = contact1.jid - if contact1.resource: - jid_with_resource += '/' + contact1.resource - gajim.connections[account].request_vcard(jid_with_resource) - else: - host = gajim.get_server_from_jid(contact1.jid) - if host not in gajim.transport_avatar[account]: - gajim.transport_avatar[account][host] = [contact1.jid] - else: - gajim.transport_avatar[account][host].append(contact1.jid) + if gajim.config.get('ask_avatars_on_startup'): + pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(ji) + if pixbuf == 'ask': + transport = gajim.get_transport_name_from_jid(contact1.jid) + if not transport or gajim.jid_is_transport(contact1.jid): + jid_with_resource = contact1.jid + if contact1.resource: + jid_with_resource += '/' + contact1.resource + gajim.connections[account].request_vcard(jid_with_resource) + else: + host = gajim.get_server_from_jid(contact1.jid) + if host not in gajim.transport_avatar[account]: + gajim.transport_avatar[account][host] = [contact1.jid] + else: + gajim.transport_avatar[account][host].append(contact1.jid) - # If we already have chat windows opened, update them with new contact - # instance - chat_control = gajim.interface.msg_win_mgr.get_control(ji, account) - if chat_control: - chat_control.contact = contact1 + # If we already have chat windows opened, update them with new contact + # instance + chat_control = gajim.interface.msg_win_mgr.get_control(ji, account) + if chat_control: + chat_control.contact = contact1 - def connected_rooms(self, account): - if account in gajim.gc_connected[account].values(): - return True - return False + def connected_rooms(self, account): + if account in gajim.gc_connected[account].values(): + return True + return False - def on_event_removed(self, event_list): - """ - Remove contacts on last events removed + def on_event_removed(self, event_list): + """ + Remove contacts on last events removed - Only performed if removal was requested before but the contact still had - pending events - """ - contact_list = ((event.jid.split('/')[0], event.account) for event in \ - event_list) + Only performed if removal was requested before but the contact still had + pending events + """ + contact_list = ((event.jid.split('/')[0], event.account) for event in \ + event_list) - for jid, account in contact_list: - self.draw_contact(jid, account) - # Remove contacts in roster if removal was requested - key = (jid, account) - if key in self.contacts_to_be_removed.keys(): - backend = self.contacts_to_be_removed[key]['backend'] - del self.contacts_to_be_removed[key] - # Remove contact will delay removal if there are more events pending - self.remove_contact(jid, account, backend=backend) - self.show_title() + for jid, account in contact_list: + self.draw_contact(jid, account) + # Remove contacts in roster if removal was requested + key = (jid, account) + if key in self.contacts_to_be_removed.keys(): + backend = self.contacts_to_be_removed[key]['backend'] + del self.contacts_to_be_removed[key] + # Remove contact will delay removal if there are more events pending + self.remove_contact(jid, account, backend=backend) + self.show_title() - def open_event(self, account, jid, event): - """ - If an event was handled, return True, else return False - """ - data = event.parameters - ft = gajim.interface.instances['file_transfers'] - event = gajim.events.get_first_event(account, jid, event.type_) - if event.type_ == 'normal': - dialogs.SingleMessageWindow(account, jid, - action='receive', from_whom=jid, subject=data[1], message=data[0], - resource=data[5], session=data[8], form_node=data[9]) - gajim.events.remove_events(account, jid, event) - return True - elif event.type_ == 'file-request': - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - ft.show_file_request(account, contact, data) - gajim.events.remove_events(account, jid, event) - return True - elif event.type_ in ('file-request-error', 'file-send-error'): - ft.show_send_error(data) - gajim.events.remove_events(account, jid, event) - return True - elif event.type_ in ('file-error', 'file-stopped'): - msg_err = '' - if data['error'] == -1: - msg_err = _('Remote contact stopped transfer') - elif data['error'] == -6: - msg_err = _('Error opening file') - ft.show_stopped(jid, data, error_msg=msg_err) - gajim.events.remove_events(account, jid, event) - return True - elif event.type_ == 'file-completed': - ft.show_completed(jid, data) - gajim.events.remove_events(account, jid, event) - return True - elif event.type_ == 'gc-invitation': - dialogs.InvitationReceivedDialog(account, data[0], jid, data[2], - data[1]) - gajim.events.remove_events(account, jid, event) - return True - elif event.type_ == 'subscription_request': - dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1]) - gajim.events.remove_events(account, jid, event) - return True - elif event.type_ == 'unsubscribed': - gajim.interface.show_unsubscribed_dialog(account, data) - gajim.events.remove_events(account, jid, event) - return True - elif event.type_ == 'jingle-incoming': - peerjid, sid, content_types = data - dialogs.VoIPCallReceivedDialog(account, peerjid, sid, content_types) - gajim.events.remove_events(account, jid, event) - return True - return False + def open_event(self, account, jid, event): + """ + If an event was handled, return True, else return False + """ + data = event.parameters + ft = gajim.interface.instances['file_transfers'] + event = gajim.events.get_first_event(account, jid, event.type_) + if event.type_ == 'normal': + dialogs.SingleMessageWindow(account, jid, + action='receive', from_whom=jid, subject=data[1], message=data[0], + resource=data[5], session=data[8], form_node=data[9]) + gajim.events.remove_events(account, jid, event) + return True + elif event.type_ == 'file-request': + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + ft.show_file_request(account, contact, data) + gajim.events.remove_events(account, jid, event) + return True + elif event.type_ in ('file-request-error', 'file-send-error'): + ft.show_send_error(data) + gajim.events.remove_events(account, jid, event) + return True + elif event.type_ in ('file-error', 'file-stopped'): + msg_err = '' + if data['error'] == -1: + msg_err = _('Remote contact stopped transfer') + elif data['error'] == -6: + msg_err = _('Error opening file') + ft.show_stopped(jid, data, error_msg=msg_err) + gajim.events.remove_events(account, jid, event) + return True + elif event.type_ == 'file-completed': + ft.show_completed(jid, data) + gajim.events.remove_events(account, jid, event) + return True + elif event.type_ == 'gc-invitation': + dialogs.InvitationReceivedDialog(account, data[0], jid, data[2], + data[1]) + gajim.events.remove_events(account, jid, event) + return True + elif event.type_ == 'subscription_request': + dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1]) + gajim.events.remove_events(account, jid, event) + return True + elif event.type_ == 'unsubscribed': + gajim.interface.show_unsubscribed_dialog(account, data) + gajim.events.remove_events(account, jid, event) + return True + elif event.type_ == 'jingle-incoming': + peerjid, sid, content_types = data + dialogs.VoIPCallReceivedDialog(account, peerjid, sid, content_types) + gajim.events.remove_events(account, jid, event) + return True + return False ################################################################################ ### This and that... random. ################################################################################ - def show_roster_vbox(self, active): - if active: - self.xml.get_object('roster_vbox2').show() - else: - self.xml.get_object('roster_vbox2').hide() + def show_roster_vbox(self, active): + if active: + self.xml.get_object('roster_vbox2').show() + else: + self.xml.get_object('roster_vbox2').hide() - def show_tooltip(self, contact): - pointer = self.tree.get_pointer() - props = self.tree.get_path_at_pos(pointer[0], pointer[1]) - # check if the current pointer is at the same path - # as it was before setting the timeout - if props and self.tooltip.id == props[0]: - # bounding rectangle of coordinates for the cell within the treeview - rect = self.tree.get_cell_area(props[0], props[1]) + def show_tooltip(self, contact): + pointer = self.tree.get_pointer() + props = self.tree.get_path_at_pos(pointer[0], pointer[1]) + # check if the current pointer is at the same path + # as it was before setting the timeout + if props and self.tooltip.id == props[0]: + # bounding rectangle of coordinates for the cell within the treeview + rect = self.tree.get_cell_area(props[0], props[1]) - # position of the treeview on the screen - position = self.tree.window.get_origin() - self.tooltip.show_tooltip(contact, rect.height, position[1] + rect.y) - else: - self.tooltip.hide_tooltip() + # position of the treeview on the screen + position = self.tree.window.get_origin() + self.tooltip.show_tooltip(contact, rect.height, position[1] + rect.y) + else: + self.tooltip.hide_tooltip() - def authorize(self, widget, jid, account): - """ - Authorize a contact (by re-sending auth menuitem) - """ - gajim.connections[account].send_authorization(jid) - dialogs.InformationDialog(_('Authorization has been sent'), - _('Now "%s" will know your status.') %jid) + def authorize(self, widget, jid, account): + """ + Authorize a contact (by re-sending auth menuitem) + """ + gajim.connections[account].send_authorization(jid) + dialogs.InformationDialog(_('Authorization has been sent'), + _('Now "%s" will know your status.') %jid) - def req_sub(self, widget, jid, txt, account, groups=[], nickname=None, - auto_auth=False): - """ - Request subscription to a contact - """ - gajim.connections[account].request_subscription(jid, txt, nickname, - groups, auto_auth, gajim.nicks[account]) - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - if not contact: - keyID = '' - attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() - if jid in attached_keys: - keyID = attached_keys[attached_keys.index(jid) + 1] - contact = gajim.contacts.create_contact(jid=jid, account=account, name=nickname, - groups=groups, show='requested', status='', ask='none', - sub='subscribe', keyID=keyID) - gajim.contacts.add_contact(account, contact) - else: - if not _('Not in Roster') in contact.get_shown_groups(): - dialogs.InformationDialog(_('Subscription request has been sent'), - _('If "%s" accepts this request you will know his or her status.' - ) % jid) - return - self.remove_contact(contact.jid, account, force=True) - contact.groups = groups - if nickname: - contact.name = nickname - self.add_contact(jid, account) + def req_sub(self, widget, jid, txt, account, groups=[], nickname=None, + auto_auth=False): + """ + Request subscription to a contact + """ + gajim.connections[account].request_subscription(jid, txt, nickname, + groups, auto_auth, gajim.nicks[account]) + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + if not contact: + keyID = '' + attached_keys = gajim.config.get_per('accounts', account, + 'attached_gpg_keys').split() + if jid in attached_keys: + keyID = attached_keys[attached_keys.index(jid) + 1] + contact = gajim.contacts.create_contact(jid=jid, account=account, name=nickname, + groups=groups, show='requested', status='', ask='none', + sub='subscribe', keyID=keyID) + gajim.contacts.add_contact(account, contact) + else: + if not _('Not in Roster') in contact.get_shown_groups(): + dialogs.InformationDialog(_('Subscription request has been sent'), + _('If "%s" accepts this request you will know his or her status.' + ) % jid) + return + self.remove_contact(contact.jid, account, force=True) + contact.groups = groups + if nickname: + contact.name = nickname + self.add_contact(jid, account) - def revoke_auth(self, widget, jid, account): - """ - Revoke a contact's authorization - """ - gajim.connections[account].refuse_authorization(jid) - dialogs.InformationDialog(_('Authorization has been removed'), - _('Now "%s" will always see you as offline.') %jid) + def revoke_auth(self, widget, jid, account): + """ + Revoke a contact's authorization + """ + gajim.connections[account].refuse_authorization(jid) + dialogs.InformationDialog(_('Authorization has been removed'), + _('Now "%s" will always see you as offline.') %jid) - def set_state(self, account, state): - child_iterA = self._get_account_iter(account, self.model) - if child_iterA: - self.model[child_iterA][0] = \ - gajim.interface.jabber_state_images['16'][state] - if gajim.interface.systray_enabled: - gajim.interface.systray.change_status(state) + def set_state(self, account, state): + child_iterA = self._get_account_iter(account, self.model) + if child_iterA: + self.model[child_iterA][0] = \ + gajim.interface.jabber_state_images['16'][state] + if gajim.interface.systray_enabled: + gajim.interface.systray.change_status(state) - def set_connecting_state(self, account): - self.set_state(account, 'connecting') + def set_connecting_state(self, account): + self.set_state(account, 'connecting') - def send_status(self, account, status, txt, auto=False, to=None): - child_iterA = self._get_account_iter(account, self.model) - if status != 'offline': - if to is None: - if status == gajim.connections[account].get_status() and \ - txt == gajim.connections[account].status: - return - gajim.config.set_per('accounts', account, 'last_status', status) - gajim.config.set_per('accounts', account, 'last_status_msg', - helpers.to_one_line(txt)) - if gajim.connections[account].connected < 2: - self.set_connecting_state(account) + def send_status(self, account, status, txt, auto=False, to=None): + child_iterA = self._get_account_iter(account, self.model) + if status != 'offline': + if to is None: + if status == gajim.connections[account].get_status() and \ + txt == gajim.connections[account].status: + return + gajim.config.set_per('accounts', account, 'last_status', status) + gajim.config.set_per('accounts', account, 'last_status_msg', + helpers.to_one_line(txt)) + if gajim.connections[account].connected < 2: + self.set_connecting_state(account) - keyid = gajim.config.get_per('accounts', account, 'keyid') - if keyid and not gajim.connections[account].gpg: - dialogs.WarningDialog(_('GPG is not usable'), - _('You will be connected to %s without OpenPGP.') % account) + keyid = gajim.config.get_per('accounts', account, 'keyid') + if keyid and not gajim.connections[account].gpg: + dialogs.WarningDialog(_('GPG is not usable'), + _('You will be connected to %s without OpenPGP.') % account) - self.send_status_continue(account, status, txt, auto, to) + self.send_status_continue(account, status, txt, auto, to) - def send_pep(self, account, pep_dict): - connection = gajim.connections[account] + def send_pep(self, account, pep_dict): + connection = gajim.connections[account] - if 'activity' in pep_dict: - activity = pep_dict['activity'] - subactivity = pep_dict.get('subactivity', None) - activity_text = pep_dict.get('activity_text', None) - connection.send_activity(activity, subactivity, activity_text) - else: - connection.retract_activity() + if 'activity' in pep_dict: + activity = pep_dict['activity'] + subactivity = pep_dict.get('subactivity', None) + activity_text = pep_dict.get('activity_text', None) + connection.send_activity(activity, subactivity, activity_text) + else: + connection.retract_activity() - if 'mood' in pep_dict: - mood = pep_dict['mood'] - mood_text = pep_dict.get('mood_text', None) - connection.send_mood(mood, mood_text) - else: - connection.retract_mood() + if 'mood' in pep_dict: + mood = pep_dict['mood'] + mood_text = pep_dict.get('mood_text', None) + connection.send_mood(mood, mood_text) + else: + connection.retract_mood() - def delete_pep(self, jid, account): - if jid == gajim.get_jid_from_account(account): - gajim.connections[account].pep = {} - self.draw_account(account) + def delete_pep(self, jid, account): + if jid == gajim.get_jid_from_account(account): + gajim.connections[account].pep = {} + self.draw_account(account) - for contact in gajim.contacts.get_contacts(account, jid): - contact.pep = {} + for contact in gajim.contacts.get_contacts(account, jid): + contact.pep = {} - self.draw_all_pep_types(jid, account) - ctrl = gajim.interface.msg_win_mgr.get_control(jid, account) - if ctrl: - ctrl.update_all_pep_types() + self.draw_all_pep_types(jid, account) + ctrl = gajim.interface.msg_win_mgr.get_control(jid, account) + if ctrl: + ctrl.update_all_pep_types() - def send_status_continue(self, account, status, txt, auto, to): - if gajim.account_is_connected(account) and not to: - if status == 'online' and gajim.interface.sleeper.getState() != \ - common.sleepy.STATE_UNKNOWN: - gajim.sleeper_state[account] = 'online' - elif gajim.sleeper_state[account] not in ('autoaway', 'autoxa') or \ - status == 'offline': - gajim.sleeper_state[account] = 'off' + def send_status_continue(self, account, status, txt, auto, to): + if gajim.account_is_connected(account) and not to: + if status == 'online' and gajim.interface.sleeper.getState() != \ + common.sleepy.STATE_UNKNOWN: + gajim.sleeper_state[account] = 'online' + elif gajim.sleeper_state[account] not in ('autoaway', 'autoxa') or \ + status == 'offline': + gajim.sleeper_state[account] = 'off' - if to: - gajim.connections[account].send_custom_status(status, txt, to) - else: - if status in ('invisible', 'offline'): - self.delete_pep(gajim.get_jid_from_account(account), account) - was_invisible = gajim.connections[account].connected == \ - gajim.SHOW_LIST.index('invisible') - gajim.connections[account].change_status(status, txt, auto) + if to: + gajim.connections[account].send_custom_status(status, txt, to) + else: + if status in ('invisible', 'offline'): + self.delete_pep(gajim.get_jid_from_account(account), account) + was_invisible = gajim.connections[account].connected == \ + gajim.SHOW_LIST.index('invisible') + gajim.connections[account].change_status(status, txt, auto) - if account in gajim.interface.status_sent_to_users: - gajim.interface.status_sent_to_users[account] = {} - if account in gajim.interface.status_sent_to_groups: - gajim.interface.status_sent_to_groups[account] = {} - for gc_control in gajim.interface.msg_win_mgr.get_controls( - message_control.TYPE_GC) + \ - gajim.interface.minimized_controls[account].values(): - if gc_control.account == account: - if gajim.gc_connected[account][gc_control.room_jid]: - gajim.connections[account].send_gc_status(gc_control.nick, - gc_control.room_jid, status, txt) - else: - # for some reason, we are not connected to the room even if - # tab is opened, send initial join_gc() - gajim.connections[account].join_gc(gc_control.nick, - gc_control.room_jid, None) - if was_invisible and status != 'offline': - # We come back from invisible, join bookmarks - gajim.interface.auto_join_bookmarks(account) + if account in gajim.interface.status_sent_to_users: + gajim.interface.status_sent_to_users[account] = {} + if account in gajim.interface.status_sent_to_groups: + gajim.interface.status_sent_to_groups[account] = {} + for gc_control in gajim.interface.msg_win_mgr.get_controls( + message_control.TYPE_GC) + \ + gajim.interface.minimized_controls[account].values(): + if gc_control.account == account: + if gajim.gc_connected[account][gc_control.room_jid]: + gajim.connections[account].send_gc_status(gc_control.nick, + gc_control.room_jid, status, txt) + else: + # for some reason, we are not connected to the room even if + # tab is opened, send initial join_gc() + gajim.connections[account].join_gc(gc_control.nick, + gc_control.room_jid, None) + if was_invisible and status != 'offline': + # We come back from invisible, join bookmarks + gajim.interface.auto_join_bookmarks(account) - def chg_contact_status(self, contact, show, status, account): - """ - When a contact changes his or her status - """ - contact_instances = gajim.contacts.get_contacts(account, contact.jid) - contact.show = show - contact.status = status - # name is to show in conversation window - name = contact.get_shown_name() - fjid = contact.get_full_jid() + def chg_contact_status(self, contact, show, status, account): + """ + When a contact changes his or her status + """ + contact_instances = gajim.contacts.get_contacts(account, contact.jid) + contact.show = show + contact.status = status + # name is to show in conversation window + name = contact.get_shown_name() + fjid = contact.get_full_jid() - # The contact has several resources - if len(contact_instances) > 1: - if contact.resource != '': - name += '/' + contact.resource + # The contact has several resources + if len(contact_instances) > 1: + if contact.resource != '': + name += '/' + contact.resource - # Remove resource when going offline - if show in ('offline', 'error') and \ - not self.contact_has_pending_roster_events(contact, account): - ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account) - if ctrl: - ctrl.update_ui() - ctrl.parent_win.redraw_tab(ctrl) - # keep the contact around, since it's - # already attached to the control - else: - gajim.contacts.remove_contact(account, contact) + # Remove resource when going offline + if show in ('offline', 'error') and \ + not self.contact_has_pending_roster_events(contact, account): + ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account) + if ctrl: + ctrl.update_ui() + ctrl.parent_win.redraw_tab(ctrl) + # keep the contact around, since it's + # already attached to the control + else: + gajim.contacts.remove_contact(account, contact) - elif contact.jid == gajim.get_jid_from_account(account) and \ - show in ('offline', 'error'): - if gajim.config.get('show_self_contact') != 'never': - # SelfContact went offline. Remove him when last pending - # message was read - self.remove_contact(contact.jid, account, backend=True) + elif contact.jid == gajim.get_jid_from_account(account) and \ + show in ('offline', 'error'): + if gajim.config.get('show_self_contact') != 'never': + # SelfContact went offline. Remove him when last pending + # message was read + self.remove_contact(contact.jid, account, backend=True) - uf_show = helpers.get_uf_show(show) + uf_show = helpers.get_uf_show(show) - # print status in chat window and update status/GPG image - ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account) - if ctrl and ctrl.type_id != message_control.TYPE_GC: - ctrl.contact = gajim.contacts.get_contact_with_highest_priority( - account, contact.jid) - ctrl.update_status_display(name, uf_show, status) + # print status in chat window and update status/GPG image + ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account) + if ctrl and ctrl.type_id != message_control.TYPE_GC: + ctrl.contact = gajim.contacts.get_contact_with_highest_priority( + account, contact.jid) + ctrl.update_status_display(name, uf_show, status) - if contact.resource: - ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account) - if ctrl: - ctrl.update_status_display(name, uf_show, status) + if contact.resource: + ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account) + if ctrl: + ctrl.update_status_display(name, uf_show, status) - # Delete pep if needed - keep_pep = any(c.show not in ('error', 'offline') for c in - contact_instances) - if not keep_pep and contact.jid != gajim.get_jid_from_account(account) \ - and not contact.is_groupchat(): - self.delete_pep(contact.jid, account) + # Delete pep if needed + keep_pep = any(c.show not in ('error', 'offline') for c in + contact_instances) + if not keep_pep and contact.jid != gajim.get_jid_from_account(account) \ + and not contact.is_groupchat(): + self.delete_pep(contact.jid, account) - # Redraw everything and select the sender - self.adjust_and_draw_contact_context(contact.jid, account) + # Redraw everything and select the sender + self.adjust_and_draw_contact_context(contact.jid, account) - def on_status_changed(self, account, show): - """ - The core tells us that our status has changed - """ - if account not in gajim.contacts.get_accounts(): - return - child_iterA = self._get_account_iter(account, self.model) - if gajim.config.get('show_self_contact') == 'always': - self_resource = gajim.connections[account].server_resource - self_contact = gajim.contacts.get_contact(account, - gajim.get_jid_from_account(account), resource=self_resource) - if self_contact: - status = gajim.connections[account].status - self.chg_contact_status(self_contact, show, status, account) - self.set_account_status_icon(account) - if show == 'offline': - if self.quit_on_next_offline > -1: - # we want to quit, we are waiting for all accounts to be offline - self.quit_on_next_offline -= 1 - if self.quit_on_next_offline < 1: - # all accounts offline, quit - self.quit_gtkgui_interface() - else: - # No need to redraw contacts if we're quitting - if child_iterA: - self.model[child_iterA][C_AVATAR_PIXBUF] = None - if account in gajim.con_types: - gajim.con_types[account] = None - for jid in gajim.contacts.get_jid_list(account): - lcontact = gajim.contacts.get_contacts(account, jid) - ctrl = gajim.interface.msg_win_mgr.get_gc_control(jid, account) - for contact in [c for c in lcontact if ((c.show != 'offline' or \ - c.is_transport()) and not ctrl)]: - self.chg_contact_status(contact, 'offline', '', account) - self.actions_menu_needs_rebuild = True - self.update_status_combobox() + def on_status_changed(self, account, show): + """ + The core tells us that our status has changed + """ + if account not in gajim.contacts.get_accounts(): + return + child_iterA = self._get_account_iter(account, self.model) + if gajim.config.get('show_self_contact') == 'always': + self_resource = gajim.connections[account].server_resource + self_contact = gajim.contacts.get_contact(account, + gajim.get_jid_from_account(account), resource=self_resource) + if self_contact: + status = gajim.connections[account].status + self.chg_contact_status(self_contact, show, status, account) + self.set_account_status_icon(account) + if show == 'offline': + if self.quit_on_next_offline > -1: + # we want to quit, we are waiting for all accounts to be offline + self.quit_on_next_offline -= 1 + if self.quit_on_next_offline < 1: + # all accounts offline, quit + self.quit_gtkgui_interface() + else: + # No need to redraw contacts if we're quitting + if child_iterA: + self.model[child_iterA][C_AVATAR_PIXBUF] = None + if account in gajim.con_types: + gajim.con_types[account] = None + for jid in gajim.contacts.get_jid_list(account): + lcontact = gajim.contacts.get_contacts(account, jid) + ctrl = gajim.interface.msg_win_mgr.get_gc_control(jid, account) + for contact in [c for c in lcontact if ((c.show != 'offline' or \ + c.is_transport()) and not ctrl)]: + self.chg_contact_status(contact, 'offline', '', account) + self.actions_menu_needs_rebuild = True + self.update_status_combobox() - def get_status_message(self, show, on_response, show_pep=True, - always_ask=False): - """ - Get the status message by: + def get_status_message(self, show, on_response, show_pep=True, + always_ask=False): + """ + Get the status message by: - 1/ looking in default status message - 2/ asking to user if needed depending on ask_on(ff)line_status and - always_ask - show_pep can be False to hide pep things from status message or True - """ - empty_pep = {'activity': '', 'subactivity': '', 'activity_text': '', - 'mood': '', 'mood_text': ''} - if show in gajim.config.get_per('defaultstatusmsg'): - if gajim.config.get_per('defaultstatusmsg', show, 'enabled'): - msg = gajim.config.get_per('defaultstatusmsg', show, 'message') - msg = helpers.from_one_line(msg) - on_response(msg, empty_pep) - return - if not always_ask and ((show == 'online' and not gajim.config.get( - 'ask_online_status')) or (show in ('offline', 'invisible') and not \ - gajim.config.get('ask_offline_status'))): - on_response('', empty_pep) - return + 1/ looking in default status message + 2/ asking to user if needed depending on ask_on(ff)line_status and + always_ask + show_pep can be False to hide pep things from status message or True + """ + empty_pep = {'activity': '', 'subactivity': '', 'activity_text': '', + 'mood': '', 'mood_text': ''} + if show in gajim.config.get_per('defaultstatusmsg'): + if gajim.config.get_per('defaultstatusmsg', show, 'enabled'): + msg = gajim.config.get_per('defaultstatusmsg', show, 'message') + msg = helpers.from_one_line(msg) + on_response(msg, empty_pep) + return + if not always_ask and ((show == 'online' and not gajim.config.get( + 'ask_online_status')) or (show in ('offline', 'invisible') and not \ + gajim.config.get('ask_offline_status'))): + on_response('', empty_pep) + return - dlg = dialogs.ChangeStatusMessageDialog(on_response, show, show_pep) - dlg.dialog.present() # show it on current workspace + dlg = dialogs.ChangeStatusMessageDialog(on_response, show, show_pep) + dlg.dialog.present() # show it on current workspace - def change_status(self, widget, account, status): - def change(account, status): - def on_response(message, pep_dict): - if message is None: - # user pressed Cancel to change status message dialog - return - self.send_status(account, status, message) - self.send_pep(account, pep_dict) - self.get_status_message(status, on_response) + def change_status(self, widget, account, status): + def change(account, status): + def on_response(message, pep_dict): + if message is None: + # user pressed Cancel to change status message dialog + return + self.send_status(account, status, message) + self.send_pep(account, pep_dict) + self.get_status_message(status, on_response) - if status == 'invisible' and self.connected_rooms(account): - dialogs.ConfirmationDialog( - _('You are participating in one or more group chats'), - _('Changing your status to invisible will result in disconnection ' - 'from those group chats. Are you sure you want to go invisible?'), - on_response_ok = (change, account, status)) - else: - change(account, status) + if status == 'invisible' and self.connected_rooms(account): + dialogs.ConfirmationDialog( + _('You are participating in one or more group chats'), + _('Changing your status to invisible will result in disconnection ' + 'from those group chats. Are you sure you want to go invisible?'), + on_response_ok = (change, account, status)) + else: + change(account, status) - def update_status_combobox(self): - # table to change index in connection.connected to index in combobox - table = {'offline':9, 'connecting':9, 'online':0, 'chat':1, 'away':2, - 'xa':3, 'dnd':4, 'invisible':5} + def update_status_combobox(self): + # table to change index in connection.connected to index in combobox + table = {'offline':9, 'connecting':9, 'online':0, 'chat':1, 'away':2, + 'xa':3, 'dnd':4, 'invisible':5} - # we check if there are more options in the combobox that it should - # if yes, we remove the first ones - while len(self.status_combobox.get_model()) > len(table)+2: - self.status_combobox.remove_text(0) + # we check if there are more options in the combobox that it should + # if yes, we remove the first ones + while len(self.status_combobox.get_model()) > len(table)+2: + self.status_combobox.remove_text(0) - show = helpers.get_global_show() - # temporarily block signal in order not to send status that we show - # in the combobox - self.combobox_callback_active = False - if helpers.statuses_unified(): - self.status_combobox.set_active(table[show]) - else: - uf_show = helpers.get_uf_show(show) - liststore = self.status_combobox.get_model() - liststore.prepend(['SEPARATOR', None, '', True]) - status_combobox_text = uf_show + ' (' + _("desync'ed") +')' - liststore.prepend([status_combobox_text, - gajim.interface.jabber_state_images['16'][show], show, False]) - self.status_combobox.set_active(0) - gajim.interface._change_awn_icon_status(show) - self.combobox_callback_active = True - if gajim.interface.systray_enabled: - gajim.interface.systray.change_status(show) + show = helpers.get_global_show() + # temporarily block signal in order not to send status that we show + # in the combobox + self.combobox_callback_active = False + if helpers.statuses_unified(): + self.status_combobox.set_active(table[show]) + else: + uf_show = helpers.get_uf_show(show) + liststore = self.status_combobox.get_model() + liststore.prepend(['SEPARATOR', None, '', True]) + status_combobox_text = uf_show + ' (' + _("desync'ed") +')' + liststore.prepend([status_combobox_text, + gajim.interface.jabber_state_images['16'][show], show, False]) + self.status_combobox.set_active(0) + gajim.interface._change_awn_icon_status(show) + self.combobox_callback_active = True + if gajim.interface.systray_enabled: + gajim.interface.systray.change_status(show) - def get_show(self, lcontact): - prio = lcontact[0].priority - show = lcontact[0].show - for u in lcontact: - if u.priority > prio: - prio = u.priority - show = u.show - return show + def get_show(self, lcontact): + prio = lcontact[0].priority + show = lcontact[0].show + for u in lcontact: + if u.priority > prio: + prio = u.priority + show = u.show + return show - def on_message_window_delete(self, win_mgr, msg_win): - if gajim.config.get('one_message_window') == 'always_with_roster': - self.show_roster_vbox(True) - gtkgui_helpers.resize_window(self.window, - gajim.config.get('roster_width'), - gajim.config.get('roster_height')) + def on_message_window_delete(self, win_mgr, msg_win): + if gajim.config.get('one_message_window') == 'always_with_roster': + self.show_roster_vbox(True) + gtkgui_helpers.resize_window(self.window, + gajim.config.get('roster_width'), + gajim.config.get('roster_height')) - def close_all_from_dict(self, dic): - """ - Close all the windows in the given dictionary - """ - for w in dic.values(): - if isinstance(w, dict): - self.close_all_from_dict(w) - else: - w.window.destroy() + def close_all_from_dict(self, dic): + """ + Close all the windows in the given dictionary + """ + for w in dic.values(): + if isinstance(w, dict): + self.close_all_from_dict(w) + else: + w.window.destroy() - def close_all(self, account, force=False): - """ - Close all the windows from an account. If force is True, do not ask - confirmation before closing chat/gc windows - """ - if account in gajim.interface.instances: - self.close_all_from_dict(gajim.interface.instances[account]) - for ctrl in gajim.interface.msg_win_mgr.get_controls(acct=account): - ctrl.parent_win.remove_tab(ctrl, ctrl.parent_win.CLOSE_CLOSE_BUTTON, - force = force) + def close_all(self, account, force=False): + """ + Close all the windows from an account. If force is True, do not ask + confirmation before closing chat/gc windows + """ + if account in gajim.interface.instances: + self.close_all_from_dict(gajim.interface.instances[account]) + for ctrl in gajim.interface.msg_win_mgr.get_controls(acct=account): + ctrl.parent_win.remove_tab(ctrl, ctrl.parent_win.CLOSE_CLOSE_BUTTON, + force = force) - def on_roster_window_delete_event(self, widget, event): - """ - Main window X button was clicked - """ - if gajim.interface.systray_enabled and not gajim.config.get( - 'quit_on_roster_x_button') and gajim.config.get('trayicon') != 'on_event': - self.tooltip.hide_tooltip() - self.window.hide() - elif gajim.config.get('quit_on_roster_x_button'): - self.on_quit_request() - else: - def on_ok(checked): - if checked: - gajim.config.set('quit_on_roster_x_button', True) - self.on_quit_request() - dialogs.ConfirmationDialogCheck(_('Really quit Gajim?'), - _('Are you sure you want to quit Gajim?'), - _('Always close Gajim'), on_response_ok=on_ok) - return True # do NOT destroy the window + def on_roster_window_delete_event(self, widget, event): + """ + Main window X button was clicked + """ + if gajim.interface.systray_enabled and not gajim.config.get( + 'quit_on_roster_x_button') and gajim.config.get('trayicon') != 'on_event': + self.tooltip.hide_tooltip() + self.window.hide() + elif gajim.config.get('quit_on_roster_x_button'): + self.on_quit_request() + else: + def on_ok(checked): + if checked: + gajim.config.set('quit_on_roster_x_button', True) + self.on_quit_request() + dialogs.ConfirmationDialogCheck(_('Really quit Gajim?'), + _('Are you sure you want to quit Gajim?'), + _('Always close Gajim'), on_response_ok=on_ok) + return True # do NOT destroy the window - def prepare_quit(self): - msgwin_width_adjust = 0 + def prepare_quit(self): + msgwin_width_adjust = 0 - # in case show_roster_on_start is False and roster is never shown - # window.window is None - if self.window.window is not None: - x, y = self.window.window.get_root_origin() - gajim.config.set('roster_x-position', x) - gajim.config.set('roster_y-position', y) - width, height = self.window.get_size() - # For the width use the size of the vbox containing the tree and - # status combo, this will cancel out any hpaned width - width = self.xml.get_object('roster_vbox2').allocation.width - gajim.config.set('roster_width', width) - gajim.config.set('roster_height', height) - if not self.xml.get_object('roster_vbox2').get_property('visible'): - # The roster vbox is hidden, so the message window is larger - # then we want to save (i.e. the window will grow every startup) - # so adjust. - msgwin_width_adjust = -1 * width - gajim.config.set('show_roster_on_startup', - self.window.get_property('visible')) - gajim.interface.msg_win_mgr.shutdown(msgwin_width_adjust) + # in case show_roster_on_start is False and roster is never shown + # window.window is None + if self.window.window is not None: + x, y = self.window.window.get_root_origin() + gajim.config.set('roster_x-position', x) + gajim.config.set('roster_y-position', y) + width, height = self.window.get_size() + # For the width use the size of the vbox containing the tree and + # status combo, this will cancel out any hpaned width + width = self.xml.get_object('roster_vbox2').allocation.width + gajim.config.set('roster_width', width) + gajim.config.set('roster_height', height) + if not self.xml.get_object('roster_vbox2').get_property('visible'): + # The roster vbox is hidden, so the message window is larger + # then we want to save (i.e. the window will grow every startup) + # so adjust. + msgwin_width_adjust = -1 * width + gajim.config.set('show_roster_on_startup', + self.window.get_property('visible')) + gajim.interface.msg_win_mgr.shutdown(msgwin_width_adjust) - gajim.config.set('collapsed_rows', '\t'.join(self.collapsed_rows)) - gajim.interface.save_config() - for account in gajim.connections: - gajim.connections[account].quit(True) - self.close_all(account) - if gajim.interface.systray_enabled: - gajim.interface.hide_systray() + gajim.config.set('collapsed_rows', '\t'.join(self.collapsed_rows)) + gajim.interface.save_config() + for account in gajim.connections: + gajim.connections[account].quit(True) + self.close_all(account) + if gajim.interface.systray_enabled: + gajim.interface.hide_systray() - def quit_gtkgui_interface(self): - """ - When we quit the gtk interface - exit gtk - """ - self.prepare_quit() - gtk.main_quit() + def quit_gtkgui_interface(self): + """ + When we quit the gtk interface - exit gtk + """ + self.prepare_quit() + gtk.main_quit() - def on_quit_request(self, widget=None): - """ - User wants to quit. Check if he should be warned about messages pending. - Terminate all sessions and send offline to all connected account. We do - NOT really quit gajim here - """ - accounts = gajim.connections.keys() - get_msg = False - for acct in accounts: - if gajim.connections[acct].connected: - get_msg = True - break + def on_quit_request(self, widget=None): + """ + User wants to quit. Check if he should be warned about messages pending. + Terminate all sessions and send offline to all connected account. We do + NOT really quit gajim here + """ + accounts = gajim.connections.keys() + get_msg = False + for acct in accounts: + if gajim.connections[acct].connected: + get_msg = True + break - def on_continue3(message, pep_dict): - self.quit_on_next_offline = 0 - accounts_to_disconnect = [] - for acct in accounts: - if gajim.connections[acct].connected: - self.quit_on_next_offline += 1 - accounts_to_disconnect.append(acct) + def on_continue3(message, pep_dict): + self.quit_on_next_offline = 0 + accounts_to_disconnect = [] + for acct in accounts: + if gajim.connections[acct].connected: + self.quit_on_next_offline += 1 + accounts_to_disconnect.append(acct) - for acct in accounts_to_disconnect: - self.send_status(acct, 'offline', message) - self.send_pep(acct, pep_dict) + for acct in accounts_to_disconnect: + self.send_status(acct, 'offline', message) + self.send_pep(acct, pep_dict) - if not self.quit_on_next_offline: - self.quit_gtkgui_interface() + if not self.quit_on_next_offline: + self.quit_gtkgui_interface() - def on_continue2(message, pep_dict): - # check if there is an active file transfer - from common.protocol.bytestream import (is_transfer_active) - files_props = gajim.interface.instances['file_transfers'].files_props - transfer_active = False - for x in files_props: - for y in files_props[x]: - if is_transfer_active(files_props[x][y]): - transfer_active = True - break + def on_continue2(message, pep_dict): + # check if there is an active file transfer + from common.protocol.bytestream import (is_transfer_active) + files_props = gajim.interface.instances['file_transfers'].files_props + transfer_active = False + for x in files_props: + for y in files_props[x]: + if is_transfer_active(files_props[x][y]): + transfer_active = True + break - if transfer_active: - dialogs.ConfirmationDialog(_('You have running file transfers'), - _('If you quit now, the file(s) being transfered will be ' - 'stopped. Do you still want to quit?'), - on_response_ok=(on_continue3, message, pep_dict)) - return - on_continue3(message, pep_dict) + if transfer_active: + dialogs.ConfirmationDialog(_('You have running file transfers'), + _('If you quit now, the file(s) being transfered will be ' + 'stopped. Do you still want to quit?'), + on_response_ok=(on_continue3, message, pep_dict)) + return + on_continue3(message, pep_dict) - def on_continue(message, pep_dict): - if message is None: - # user pressed Cancel to change status message dialog - return - # check if we have unread messages - unread = gajim.events.get_nb_events() - if not gajim.config.get('notify_on_all_muc_messages'): - unread_not_to_notify = gajim.events.get_nb_events( - ['printed_gc_msg']) - unread -= unread_not_to_notify + def on_continue(message, pep_dict): + if message is None: + # user pressed Cancel to change status message dialog + return + # check if we have unread messages + unread = gajim.events.get_nb_events() + if not gajim.config.get('notify_on_all_muc_messages'): + unread_not_to_notify = gajim.events.get_nb_events( + ['printed_gc_msg']) + unread -= unread_not_to_notify - # check if we have recent messages - recent = False - for win in gajim.interface.msg_win_mgr.windows(): - for ctrl in win.controls(): - fjid = ctrl.get_full_jid() - if fjid in gajim.last_message_time[ctrl.account]: - if time.time() - gajim.last_message_time[ctrl.account][fjid] \ - < 2: - recent = True - break - if recent: - break + # check if we have recent messages + recent = False + for win in gajim.interface.msg_win_mgr.windows(): + for ctrl in win.controls(): + fjid = ctrl.get_full_jid() + if fjid in gajim.last_message_time[ctrl.account]: + if time.time() - gajim.last_message_time[ctrl.account][fjid] \ + < 2: + recent = True + break + if recent: + break - if unread or recent: - dialogs.ConfirmationDialog(_('You have unread messages'), - _('Messages will only be available for reading them later if you' - ' have history enabled and contact is in your roster.'), - on_response_ok=(on_continue2, message, pep_dict)) - return - on_continue2(message, pep_dict) + if unread or recent: + dialogs.ConfirmationDialog(_('You have unread messages'), + _('Messages will only be available for reading them later if you' + ' have history enabled and contact is in your roster.'), + on_response_ok=(on_continue2, message, pep_dict)) + return + on_continue2(message, pep_dict) - if get_msg: - self.get_status_message('offline', on_continue, show_pep=False) - else: - on_continue('', None) + if get_msg: + self.get_status_message('offline', on_continue, show_pep=False) + else: + on_continue('', None) ################################################################################ ### Menu and GUI callbacks ### FIXME: order callbacks in itself... ################################################################################ - def on_actions_menuitem_activate(self, widget): - self.make_menu() - - def on_edit_menuitem_activate(self, widget): - """ - Need to call make_menu to build profile, avatar item - """ - self.make_menu() - - def on_bookmark_menuitem_activate(self, widget, account, bookmark): - gajim.interface.join_gc_room(account, bookmark['jid'], bookmark['nick'], - bookmark['password']) - - def on_send_server_message_menuitem_activate(self, widget, account): - server = gajim.config.get_per('accounts', account, 'hostname') - server += '/announce/online' - dialogs.SingleMessageWindow(account, server, 'send') - - def on_xml_console_menuitem_activate(self, widget, account): - if 'xml_console' in gajim.interface.instances[account]: - gajim.interface.instances[account]['xml_console'].window.present() - else: - gajim.interface.instances[account]['xml_console'] = \ - dialogs.XMLConsoleWindow(account) - - def on_privacy_lists_menuitem_activate(self, widget, account): - if 'privacy_lists' in gajim.interface.instances[account]: - gajim.interface.instances[account]['privacy_lists'].window.present() - else: - gajim.interface.instances[account]['privacy_lists'] = \ - dialogs.PrivacyListsWindow(account) - - def on_set_motd_menuitem_activate(self, widget, account): - server = gajim.config.get_per('accounts', account, 'hostname') - server += '/announce/motd' - dialogs.SingleMessageWindow(account, server, 'send') - - def on_update_motd_menuitem_activate(self, widget, account): - server = gajim.config.get_per('accounts', account, 'hostname') - server += '/announce/motd/update' - dialogs.SingleMessageWindow(account, server, 'send') - - def on_delete_motd_menuitem_activate(self, widget, account): - server = gajim.config.get_per('accounts', account, 'hostname') - server += '/announce/motd/delete' - gajim.connections[account].send_motd(server) - - def on_history_manager_menuitem_activate(self, widget): - if os.name == 'nt': - if os.path.exists('history_manager.exe'): # user is running stable - helpers.exec_command('history_manager.exe') - else: # user is running svn - helpers.exec_command('%s history_manager.py' % sys.executable) - else: # Unix user - helpers.exec_command('%s history_manager.py' % sys.executable) - - def on_info(self, widget, contact, account): - """ - Call vcard_information_window class to display contact's information - """ - if gajim.connections[account].is_zeroconf: - self.on_info_zeroconf(widget, contact, account) - return - - info = gajim.interface.instances[account]['infos'] - if contact.jid in info: - info[contact.jid].window.present() - else: - info[contact.jid] = vcard.VcardWindow(contact, account) - - def on_info_zeroconf(self, widget, contact, account): - info = gajim.interface.instances[account]['infos'] - if contact.jid in info: - info[contact.jid].window.present() - else: - contact = gajim.contacts.get_first_contact_from_jid(account, - contact.jid) - if contact.show in ('offline', 'error'): - # don't show info on offline contacts - return - info[contact.jid] = vcard.ZeroconfVcardWindow(contact, account) - - def on_roster_treeview_leave_notify_event(self, widget, event): - props = widget.get_path_at_pos(int(event.x), int(event.y)) - if self.tooltip.timeout > 0: - if not props or self.tooltip.id == props[0]: - self.tooltip.hide_tooltip() - - def on_roster_treeview_motion_notify_event(self, widget, event): - model = widget.get_model() - props = widget.get_path_at_pos(int(event.x), int(event.y)) - if self.tooltip.timeout > 0: - if not props or self.tooltip.id != props[0]: - self.tooltip.hide_tooltip() - if props: - row = props[0] - titer = None - try: - titer = model.get_iter(row) - except Exception: - self.tooltip.hide_tooltip() - return - if model[titer][C_TYPE] in ('contact', 'self_contact'): - # we're on a contact entry in the roster - account = model[titer][C_ACCOUNT].decode('utf-8') - jid = model[titer][C_JID].decode('utf-8') - if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: - self.tooltip.id = row - contacts = gajim.contacts.get_contacts(account, jid) - connected_contacts = [] - for c in contacts: - if c.show not in ('offline', 'error'): - connected_contacts.append(c) - if not connected_contacts: - # no connected contacts, show the ofline one - connected_contacts = contacts - self.tooltip.account = account - self.tooltip.timeout = gobject.timeout_add(500, - self.show_tooltip, connected_contacts) - elif model[titer][C_TYPE] == 'groupchat': - account = model[titer][C_ACCOUNT].decode('utf-8') - jid = model[titer][C_JID].decode('utf-8') - if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: - self.tooltip.id = row - contact = gajim.contacts.get_contacts(account, jid) - self.tooltip.account = account - self.tooltip.timeout = gobject.timeout_add(500, - self.show_tooltip, contact) - elif model[titer][C_TYPE] == 'account': - # we're on an account entry in the roster - account = model[titer][C_ACCOUNT].decode('utf-8') - if account == 'all': - if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: - self.tooltip.id = row - self.tooltip.account = None - self.tooltip.timeout = gobject.timeout_add(500, - self.show_tooltip, []) - return - jid = gajim.get_jid_from_account(account) - contacts = [] - connection = gajim.connections[account] - # get our current contact info - - nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts( - accounts = [account]) - account_name = account - if gajim.account_is_connected(account): - account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total)) - contact = gajim.contacts.create_self_contact(jid=jid, account=account, - name=account_name, show=connection.get_status(), - status=connection.status, resource=connection.server_resource, - priority=connection.priority) - if gajim.connections[account].gpg: - contact.keyID = gajim.config.get_per('accounts', connection.name, - 'keyid') - contacts.append(contact) - # if we're online ... - if connection.connection: - roster = connection.connection.getRoster() - # in threadless connection when no roster stanza is sent, - # 'roster' is None - if roster and roster.getItem(jid): - resources = roster.getResources(jid) - # ...get the contact info for our other online resources - for resource in resources: - # Check if we already have this resource - found = False - for contact_ in contacts: - if contact_.resource == resource: - found = True - break - if found: - continue - show = roster.getShow(jid+'/'+resource) - if not show: - show = 'online' - contact = gajim.contacts.create_self_contact(jid=jid, - account=account, show=show, status=roster.getStatus(jid + '/' + resource), - priority=roster.getPriority(jid + '/' + resource), - resource=resource) - contacts.append(contact) - if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: - self.tooltip.id = row - self.tooltip.account = None - self.tooltip.timeout = gobject.timeout_add(500, - self.show_tooltip, contacts) - - def on_agent_logging(self, widget, jid, state, account): - """ - When an agent is requested to log in or off - """ - gajim.connections[account].send_agent_status(jid, state) - - def on_edit_agent(self, widget, contact, account): - """ - When we want to modify the agent registration - """ - gajim.connections[account].request_register_agent_info(contact.jid) - - def on_remove_agent(self, widget, list_): - """ - When an agent is requested to be removed. list_ is a list of (contact, - account) tuple - """ - for (contact, account) in list_: - if gajim.config.get_per('accounts', account, 'hostname') == \ - contact.jid: - # We remove the server contact - # remove it from treeview - gajim.connections[account].unsubscribe(contact.jid) - self.remove_contact(contact.jid, account, backend=True) - return - - def remove(list_): - for (contact, account) in list_: - full_jid = contact.get_full_jid() - gajim.connections[account].unsubscribe_agent(full_jid) - # remove transport from treeview - self.remove_contact(contact.jid, account, backend=True) - - # Check if there are unread events from some contacts - has_unread_events = False - for (contact, account) in list_: - for jid in gajim.events.get_events(account): - if jid.endswith(contact.jid): - has_unread_events = True - break - if has_unread_events: - dialogs.ErrorDialog(_('You have unread messages'), - _('You must read them before removing this transport.')) - return - if len(list_) == 1: - pritext = _('Transport "%s" will be removed') % list_[0][0].jid - sectext = _('You will no longer be able to send and receive messages ' - 'from contacts using this transport.') - else: - pritext = _('Transports will be removed') - jids = '' - for (contact, account) in list_: - jids += '\n ' + contact.get_shown_name() + ',' - jids = jids[:-1] + '.' - sectext = _('You will no longer be able to send and receive messages ' - 'to contacts from these transports: %s') % jids - dialogs.ConfirmationDialog(pritext, sectext, - on_response_ok = (remove, list_)) - - def on_block(self, widget, list_, group=None): - """ - When clicked on the 'block' button in context menu. list_ is a list of - (contact, account) - """ - def on_continue(msg, pep_dict): - if msg is None: - # user pressed Cancel to change status message dialog - return - accounts = [] - if group is None: - for (contact, account) in list_: - if account not in accounts: - if not gajim.connections[account].privacy_rules_supported: - continue - accounts.append(account) - self.send_status(account, 'offline', msg, to=contact.jid) - new_rule = {'order': u'1', 'type': u'jid', 'action': u'deny', - 'value' : contact.jid, 'child': [u'message', u'iq', - u'presence-out']} - gajim.connections[account].blocked_list.append(new_rule) - # needed for draw_contact: - gajim.connections[account].blocked_contacts.append( - contact.jid) - self.draw_contact(contact.jid, account) - else: - for (contact, account) in list_: - if account not in accounts: - if not gajim.connections[account].privacy_rules_supported: - continue - accounts.append(account) - # needed for draw_group: - gajim.connections[account].blocked_groups.append(group) - self.draw_group(group, account) - self.send_status(account, 'offline', msg, to=contact.jid) - self.draw_contact(contact.jid, account) - new_rule = {'order': u'1', 'type': u'group', 'action': u'deny', - 'value' : group, 'child': [u'message', u'iq', u'presence-out']} - gajim.connections[account].blocked_list.append(new_rule) - for account in accounts: - connection = gajim.connections[account] - connection.set_privacy_list('block', connection.blocked_list) - if len(connection.blocked_list) == 1: - connection.set_active_list('block') - connection.set_default_list('block') - connection.get_privacy_list('block') - - def _block_it(is_checked=None): - if is_checked is not None: # dialog has been shown - if is_checked: # user does not want to be asked again - gajim.config.set('confirm_block', 'no') - else: - gajim.config.set('confirm_block', 'yes') - self.get_status_message('offline', on_continue, show_pep=False) - - confirm_block = gajim.config.get('confirm_block') - if confirm_block == 'no': - _block_it() - return - pritext = _('You are about to block a contact. Are you sure you want' - ' to continue?') - sectext = _('This contact will see you offline and you will not receive ' - 'messages he will send you.') - dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, - _('Do _not ask me again'), on_response_ok=_block_it) - - def on_unblock(self, widget, list_, group=None): - """ - When clicked on the 'unblock' button in context menu. - """ - accounts = [] - if group is None: - for (contact, account) in list_: - if account not in accounts: - if gajim.connections[account].privacy_rules_supported: - accounts.append(account) - gajim.connections[account].new_blocked_list = [] - gajim.connections[account].to_unblock = [] - gajim.connections[account].to_unblock.append(contact.jid) - else: - gajim.connections[account].to_unblock.append(contact.jid) - # needed for draw_contact: - if contact.jid in gajim.connections[account].blocked_contacts: - gajim.connections[account].blocked_contacts.remove(contact.jid) - self.draw_contact(contact.jid, account) - for account in accounts: - for rule in gajim.connections[account].blocked_list: - if rule['action'] != 'deny' or rule['type'] != 'jid' \ - or rule['value'] not in gajim.connections[account].to_unblock: - gajim.connections[account].new_blocked_list.append(rule) - else: - for (contact, account) in list_: - if account not in accounts: - if gajim.connections[account].privacy_rules_supported: - accounts.append(account) - # needed for draw_group: - if group in gajim.connections[account].blocked_groups: - gajim.connections[account].blocked_groups.remove(group) - self.draw_group(group, account) - gajim.connections[account].new_blocked_list = [] - for rule in gajim.connections[account].blocked_list: - if rule['action'] != 'deny' or rule['type'] != 'group' \ - or rule['value'] != group: - gajim.connections[account].new_blocked_list.append(rule) - self.draw_contact(contact.jid, account) - for account in accounts: - gajim.connections[account].set_privacy_list('block', - gajim.connections[account].new_blocked_list) - gajim.connections[account].get_privacy_list('block') - if len(gajim.connections[account].new_blocked_list) == 0: - gajim.connections[account].blocked_list = [] - gajim.connections[account].blocked_contacts = [] - gajim.connections[account].blocked_groups = [] - gajim.connections[account].set_default_list('') - gajim.connections[account].set_active_list('') - gajim.connections[account].del_privacy_list('block') - if 'blocked_contacts' in gajim.interface.instances[account]: - gajim.interface.instances[account]['blocked_contacts'].\ - privacy_list_received([]) - for (contact, account) in list_: - if not self.regroup: - show = gajim.SHOW_LIST[gajim.connections[account].connected] - else: # accounts merged - show = helpers.get_global_show() - if show == 'invisible': - # Don't send our presence if we're invisible - continue - if account not in accounts: - accounts.append(account) - if gajim.connections[account].privacy_rules_supported: - self.send_status(account, show, - gajim.connections[account].status, to=contact.jid) - else: - self.send_status(account, show, - gajim.connections[account].status, to=contact.jid) - - def on_rename(self, widget, row_type, jid, account): - # this function is called either by F2 or by Rename menuitem - if 'rename' in gajim.interface.instances: - gajim.interface.instances['rename'].dialog.present() - return - - # account is offline, don't allow to rename - if gajim.connections[account].connected < 2: - return - if row_type in ('contact', 'agent'): - # it's jid - title = _('Rename Contact') - message = _('Enter a new nickname for contact %s') % jid - old_text = gajim.contacts.get_contact_with_highest_priority(account, - jid).name - elif row_type == 'group': - if jid in helpers.special_groups + (_('General'),): - return - old_text = jid - title = _('Rename Group') - message = _('Enter a new name for group %s') % \ - gobject.markup_escape_text(jid) - - def on_renamed(new_text, account, row_type, jid, old_text): - if 'rename' in gajim.interface.instances: - del gajim.interface.instances['rename'] - if row_type in ('contact', 'agent'): - if old_text == new_text: - return - for contact in gajim.contacts.get_contacts(account, jid): - contact.name = new_text - gajim.connections[account].update_contact(jid, new_text, \ - contact.groups) - self.draw_contact(jid, account) - # Update opened chats - for ctrl in gajim.interface.msg_win_mgr.get_controls(jid, account): - ctrl.update_ui() - win = gajim.interface.msg_win_mgr.get_window(jid, account) - win.redraw_tab(ctrl) - win.show_title() - elif row_type == 'group': - # in C_JID column, we hold the group name (which is not escaped) - self.rename_group(old_text, new_text, account) - - def on_canceled(): - if 'rename' in gajim.interface.instances: - del gajim.interface.instances['rename'] - - gajim.interface.instances['rename'] = dialogs.InputDialog(title, message, - old_text, False, (on_renamed, account, row_type, jid, old_text), - on_canceled) - - def on_remove_group_item_activated(self, widget, group, account): - def on_ok(checked): - for contact in gajim.contacts.get_contacts_from_group(account, group): - if not checked: - self.remove_contact_from_groups(contact.jid,account, [group]) - else: - gajim.connections[account].unsubscribe(contact.jid) - self.remove_contact(contact.jid, account, backend=True) - - dialogs.ConfirmationDialogCheck(_('Remove Group'), - _('Do you want to remove group %s from the roster?') % group, - _('Also remove all contacts in this group from your roster'), - on_response_ok=on_ok) - - def on_assign_pgp_key(self, widget, contact, account): - attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() - keys = {} - keyID = _('None') - for i in xrange(len(attached_keys)/2): - keys[attached_keys[2*i]] = attached_keys[2*i+1] - if attached_keys[2*i] == contact.jid: - keyID = attached_keys[2*i+1] - public_keys = gajim.connections[account].ask_gpg_keys() - public_keys[_('None')] = _('None') - - def on_key_selected(keyID): - if keyID is None: - return - if keyID[0] == _('None'): - if contact.jid in keys: - del keys[contact.jid] - keyID = '' - else: - keyID = keyID[0] - keys[contact.jid] = keyID - - ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account) - if ctrl: - ctrl.update_ui() - - keys_str = '' - for jid in keys: - keys_str += jid + ' ' + keys[jid] + ' ' - gajim.config.set_per('accounts', account, 'attached_gpg_keys', - keys_str) - for u in gajim.contacts.get_contacts(account, contact.jid): - u.keyID = helpers.prepare_and_validate_gpg_keyID(account, - contact.jid, keyID) - - dialogs.ChooseGPGKeyDialog(_('Assign OpenPGP Key'), - _('Select a key to apply to the contact'), public_keys, - on_key_selected, selected=keyID) - - def on_set_custom_avatar_activate(self, widget, contact, account): - def on_ok(widget, path_to_file): - filesize = os.path.getsize(path_to_file) # in bytes - invalid_file = False - msg = '' - if os.path.isfile(path_to_file): - stat = os.stat(path_to_file) - if stat[6] == 0: - invalid_file = True - msg = _('File is empty') - else: - invalid_file = True - msg = _('File does not exist') - if invalid_file: - dialogs.ErrorDialog(_('Could not load image'), msg) - return - try: - pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file) - if filesize > 16384: # 16 kb - # get the image at 'tooltip size' - # and hope that user did not specify in ACE crazy size - pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'tooltip') - except gobject.GError, msg: # unknown format - # msg should be string, not object instance - msg = str(msg) - dialogs.ErrorDialog(_('Could not load image'), msg) - return - gajim.interface.save_avatar_files(contact.jid, pixbuf, local=True) - dlg.destroy() - self.update_avatar_in_gui(contact.jid, account) - - def on_clear(widget): - dlg.destroy() - # Delete file: - gajim.interface.remove_avatar_files(contact.jid, local=True) - self.update_avatar_in_gui(contact.jid, account) - - dlg = dialogs.AvatarChooserDialog(on_response_ok=on_ok, - on_response_clear=on_clear) - - def on_edit_groups(self, widget, list_): - dialogs.EditGroupsDialog(list_) - - def on_history(self, widget, contact, account): - """ - When history menuitem is activated: call log window - """ - if 'logs' in gajim.interface.instances: - gajim.interface.instances['logs'].window.present() - gajim.interface.instances['logs'].open_history(contact.jid, account) - else: - gajim.interface.instances['logs'] = history_window.\ - HistoryWindow(contact.jid, account) - - def on_disconnect(self, widget, jid, account): - """ - When disconnect menuitem is activated: disconect from room - """ - if jid in gajim.interface.minimized_controls[account]: - ctrl = gajim.interface.minimized_controls[account][jid] - ctrl.shutdown() - ctrl.got_disconnected() - self.remove_groupchat(jid, account) - - def on_reconnect(self, widget, jid, account): - """ - When disconnect menuitem is activated: disconect from room - """ - if jid in gajim.interface.minimized_controls[account]: - ctrl = gajim.interface.minimized_controls[account][jid] - gajim.interface.join_gc_room(account, jid, ctrl.nick, - gajim.gc_passwords.get(jid, '')) - - def on_send_single_message_menuitem_activate(self, widget, account, - contact=None): - if contact is None: - dialogs.SingleMessageWindow(account, action='send') - elif isinstance(contact, list): - dialogs.SingleMessageWindow(account, contact, 'send') - else: - jid = contact.jid - if contact.jid == gajim.get_jid_from_account(account): - jid += '/' + contact.resource - dialogs.SingleMessageWindow(account, jid, 'send') - - def on_send_file_menuitem_activate(self, widget, contact, account, - resource=None): - gajim.interface.instances['file_transfers'].show_file_send_request( - account, contact) - - def on_add_special_notification_menuitem_activate(self, widget, jid): - dialogs.AddSpecialNotificationDialog(jid) - - def on_invite_to_new_room(self, widget, list_, resource=None): - """ - Resource parameter MUST NOT be used if more than one contact in list - """ - account_list = [] - jid_list = [] - for (contact, account) in list_: - if contact.jid not in jid_list: - if resource: # we MUST have one contact only in list_ - fjid = contact.jid + '/' + resource - jid_list.append(fjid) - else: - jid_list.append(contact.jid) - if account not in account_list: - account_list.append(account) - # transform None in 'jabber' - type_ = gajim.get_transport_name_from_jid(jid_list[0]) or 'jabber' - for account in account_list: - if gajim.connections[account].muc_jid[type_]: - # create the room on this muc server - if 'join_gc' in gajim.interface.instances[account]: - gajim.interface.instances[account]['join_gc'].window.destroy() - try: - gajim.interface.instances[account]['join_gc'] = \ - dialogs.JoinGroupchatWindow(account, - gajim.connections[account].muc_jid[type_], - automatic = {'invities': jid_list}) - except GajimGeneralException: - continue - break - - def on_invite_to_room(self, widget, list_, room_jid, room_account, - resource=None): - """ - Resource parameter MUST NOT be used if more than one contact in list - """ - for e in list_: - contact = e[0] - contact_jid = contact.jid - if resource: # we MUST have one contact only in list_ - contact_jid += '/' + resource - gajim.connections[room_account].send_invite(room_jid, contact_jid) - - def on_all_groupchat_maximized(self, widget, group_list): - for (contact, account) in group_list: - self.on_groupchat_maximized(widget, contact.jid, account) - - def on_groupchat_maximized(self, widget, jid, account): - """ - When a groupchat is maximized - """ - if not jid in gajim.interface.minimized_controls[account]: - # Already opened? - gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid, account) - if gc_control: - mw = gajim.interface.msg_win_mgr.get_window(jid, account) - mw.set_active_tab(gc_control) - mw.window.window.focus(gtk.get_current_event_time()) - return - ctrl = gajim.interface.minimized_controls[account][jid] - mw = gajim.interface.msg_win_mgr.get_window(jid, account) - if not mw: - mw = gajim.interface.msg_win_mgr.create_window(ctrl.contact, - ctrl.account, ctrl.type_id) - ctrl.parent_win = mw - mw.new_tab(ctrl) - mw.set_active_tab(ctrl) - mw.window.window.focus(gtk.get_current_event_time()) - self.remove_groupchat(jid, account) - - def on_edit_account(self, widget, account): - if 'accounts' in gajim.interface.instances: - gajim.interface.instances['accounts'].window.present() - else: - gajim.interface.instances['accounts'] = config.AccountsWindow() - gajim.interface.instances['accounts'].select_account(account) - - def on_zeroconf_properties(self, widget, account): - if 'accounts' in gajim.interface.instances: - gajim.interface.instances['accounts'].window.present() - else: - gajim.interface.instances['accounts'] = config.AccountsWindow() - gajim.interface.instances['accounts'].select_account(account) - - def on_open_gmail_inbox(self, widget, account): - url = gajim.connections[account].gmail_url - if url: - helpers.launch_browser_mailer('url', url) - - def on_change_status_message_activate(self, widget, account): - show = gajim.SHOW_LIST[gajim.connections[account].connected] - def on_response(message, pep_dict): - if message is None: # None is if user pressed Cancel - return - self.send_status(account, show, message) - self.send_pep(account, pep_dict) - dialogs.ChangeStatusMessageDialog(on_response, show) - - def on_add_to_roster(self, widget, contact, account): - dialogs.AddNewContactWindow(account, contact.jid, contact.name) - - - def on_roster_treeview_scroll_event(self, widget, event): - self.tooltip.hide_tooltip() - - def on_roster_treeview_key_press_event(self, widget, event): - """ - When a key is pressed in the treeviews - """ - self.tooltip.hide_tooltip() - if event.keyval == gtk.keysyms.Escape: - self.tree.get_selection().unselect_all() - elif event.keyval == gtk.keysyms.F2: - treeselection = self.tree.get_selection() - model, list_of_paths = treeselection.get_selected_rows() - if len(list_of_paths) != 1: - return - path = list_of_paths[0] - type_ = model[path][C_TYPE] - if type_ in ('contact', 'group', 'agent'): - jid = model[path][C_JID].decode('utf-8') - account = model[path][C_ACCOUNT].decode('utf-8') - self.on_rename(widget, type_, jid, account) - - elif event.keyval == gtk.keysyms.Delete: - treeselection = self.tree.get_selection() - model, list_of_paths = treeselection.get_selected_rows() - if not len(list_of_paths): - return - type_ = model[list_of_paths[0]][C_TYPE] - account = model[list_of_paths[0]][C_ACCOUNT].decode('utf-8') - if type_ in ('account', 'group', 'self_contact') or \ - account == gajim.ZEROCONF_ACC_NAME: - return - list_ = [] - for path in list_of_paths: - if model[path][C_TYPE] != type_: - return - jid = model[path][C_JID].decode('utf-8') - account = model[path][C_ACCOUNT].decode('utf-8') - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - list_.append((contact, account)) - if type_ == 'contact': - self.on_req_usub(widget, list_) - elif type_ == 'agent': - self.on_remove_agent(widget, list_) - - def on_roster_treeview_button_release_event(self, widget, event): - try: - path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0] - except TypeError: - return False - - if event.button == 1: # Left click - if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \ - not event.state & gtk.gdk.CONTROL_MASK: - # Check if button has been pressed on the same row - if self.clicked_path == path: - self.on_row_activated(widget, path) - self.clicked_path = None - - def on_roster_treeview_button_press_event(self, widget, event): - # hide tooltip, no matter the button is pressed - self.tooltip.hide_tooltip() - try: - pos = self.tree.get_path_at_pos(int(event.x), int(event.y)) - path, x = pos[0], pos[2] - except TypeError: - self.tree.get_selection().unselect_all() - return False - - if event.button == 3: # Right click - try: - model, list_of_paths = self.tree.get_selection().get_selected_rows() - except TypeError: - list_of_paths = [] - if path not in list_of_paths: - self.tree.get_selection().unselect_all() - self.tree.get_selection().select_path(path) - return self.show_treeview_menu(event) - - elif event.button == 2: # Middle click - try: - model, list_of_paths = self.tree.get_selection().get_selected_rows() - except TypeError: - list_of_paths = [] - if list_of_paths != [path]: - self.tree.get_selection().unselect_all() - self.tree.get_selection().select_path(path) - type_ = model[path][C_TYPE] - if type_ in ('agent', 'contact', 'self_contact', 'groupchat'): - self.on_row_activated(widget, path) - elif type_ == 'account': - account = model[path][C_ACCOUNT].decode('utf-8') - if account != 'all': - show = gajim.connections[account].connected - if show > 1: # We are connected - self.on_change_status_message_activate(widget, account) - return True - show = helpers.get_global_show() - if show == 'offline': - return True - def on_response(message, pep_dict): - if message is None: - return True - for acct in gajim.connections: - if not gajim.config.get_per('accounts', acct, - 'sync_with_global_status'): - continue - current_show = gajim.SHOW_LIST[gajim.connections[acct].\ - connected] - self.send_status(acct, current_show, message) - self.send_pep(acct, pep_dict) - dialogs.ChangeStatusMessageDialog(on_response, show) - return True - - elif event.button == 1: # Left click - model = self.modelfilter - type_ = model[path][C_TYPE] - # x_min is the x start position of status icon column - if gajim.config.get('avatar_position_in_roster') == 'left': - x_min = gajim.config.get('roster_avatar_width') - else: - x_min = 0 - if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \ - not event.state & gtk.gdk.CONTROL_MASK: - # Don't handle double click if we press icon of a metacontact - titer = model.get_iter(path) - if x > x_min and x < x_min + 27 and type_ == 'contact' and \ - model.iter_has_child(titer): - if (self.tree.row_expanded(path)): - self.tree.collapse_row(path) - else: - self.tree.expand_row(path, False) - return - # We just save on which row we press button, and open chat window on - # button release to be able to do DND without opening chat window - self.clicked_path = path - return - else: - if type_ == 'group' and x < 27: - # first cell in 1st column (the arrow SINGLE clicked) - if (self.tree.row_expanded(path)): - self.tree.collapse_row(path) - else: - self.tree.expand_row(path, False) - - elif type_ == 'contact' and x > x_min and x < x_min + 27: - if (self.tree.row_expanded(path)): - self.tree.collapse_row(path) - else: - self.tree.expand_row(path, False) - - def on_req_usub(self, widget, list_): - """ - Remove a contact. list_ is a list of (contact, account) tuples - """ - def on_ok(is_checked, list_): - remove_auth = True - if len(list_) == 1: - contact = list_[0][0] - if contact.sub != 'to' and is_checked: - remove_auth = False - for (contact, account) in list_: - if _('Not in Roster') not in contact.get_shown_groups(): - gajim.connections[account].unsubscribe(contact.jid, remove_auth) - self.remove_contact(contact.jid, account, backend=True) - if not remove_auth and contact.sub == 'both': - contact.name = '' - contact.groups = [] - contact.sub = 'from' - # we can't see him, but have to set it manually in contact - contact.show = 'offline' - gajim.contacts.add_contact(account, contact) - self.add_contact(contact.jid, account) - def on_ok2(list_): - on_ok(False, list_) - - if len(list_) == 1: - contact = list_[0][0] - pritext = _('Contact "%s" will be removed from your roster') % \ - contact.get_shown_name() - sectext = _('You are about to remove "%(name)s" (%(jid)s) from your ' - 'roster.\n') % {'name': contact.get_shown_name(), - 'jid': contact.jid} - if contact.sub == 'to': - dialogs.ConfirmationDialog(pritext, sectext + \ - _('By removing this contact you also remove authorization ' - 'resulting in him or her always seeing you as offline.'), - on_response_ok = (on_ok2, list_)) - elif _('Not in Roster') in contact.get_shown_groups(): - # Contact is not in roster - dialogs.ConfirmationDialog(pritext, sectext + \ - _('Do you want to continue?'), on_response_ok = (on_ok2, list_)) - else: - dialogs.ConfirmationDialogCheck(pritext, sectext + \ - _('By removing this contact you also by default remove ' - 'authorization resulting in him or her always seeing you as ' - 'offline.'), - _('I want this contact to know my status after removal'), - on_response_ok = (on_ok, list_)) - else: - # several contact to remove at the same time - pritext = _('Contacts will be removed from your roster') - jids = '' - for (contact, account) in list_: - jids += '\n ' + contact.get_shown_name() + ' (%s)' % contact.jid +\ - ',' - sectext = _('By removing these contacts:%s\nyou also remove ' - 'authorization resulting in them always seeing you as offline.') % \ - jids - dialogs.ConfirmationDialog(pritext, sectext, - on_response_ok = (on_ok2, list_)) - - def on_send_custom_status(self, widget, contact_list, show, group=None): - """ - Send custom status - """ - # contact_list has only one element except if group != None - def on_response(message, pep_dict): - if message is None: # None if user pressed Cancel - return - account_list = [] - for (contact, account) in contact_list: - if account not in account_list: - account_list.append(account) - # 1. update status_sent_to_[groups|users] list - if group: - for account in account_list: - if account not in gajim.interface.status_sent_to_groups: - gajim.interface.status_sent_to_groups[account] = {} - gajim.interface.status_sent_to_groups[account][group] = show - else: - for (contact, account) in contact_list: - if account not in gajim.interface.status_sent_to_users: - gajim.interface.status_sent_to_users[account] = {} - gajim.interface.status_sent_to_users[account][contact.jid] = show - - # 2. update privacy lists if main status is invisible - for account in account_list: - if gajim.SHOW_LIST[gajim.connections[account].connected] == \ - 'invisible': - gajim.connections[account].set_invisible_rule() - - # 3. send directed presence - for (contact, account) in contact_list: - our_jid = gajim.get_jid_from_account(account) - jid = contact.jid - if jid == our_jid: - jid += '/' + contact.resource - self.send_status(account, show, message, to=jid) - - def send_it(is_checked=None): - if is_checked is not None: # dialog has been shown - if is_checked: # user does not want to be asked again - gajim.config.set('confirm_custom_status', 'no') - else: - gajim.config.set('confirm_custom_status', 'yes') - self.get_status_message(show, on_response, show_pep=False, - always_ask=True) - - confirm_custom_status = gajim.config.get('confirm_custom_status') - if confirm_custom_status == 'no': - send_it() - return - pritext = _('You are about to send a custom status. Are you sure you want' - ' to continue?') - sectext = _('This contact will temporarily see you as %(status)s, ' - 'but only until you change your status. Then he or she will see your ' - 'global status.') % {'status': show} - dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, - _('Do _not ask me again'), on_response_ok=send_it) - - def on_status_combobox_changed(self, widget): - """ - When we change our status via the combobox - """ - model = self.status_combobox.get_model() - active = self.status_combobox.get_active() - if active == -1: # no active item - return - if not self.combobox_callback_active: - self.previous_status_combobox_active = active - return - accounts = gajim.connections.keys() - if len(accounts) == 0: - dialogs.ErrorDialog(_('No account available'), - _('You must create an account before you can chat with other contacts.')) - self.update_status_combobox() - return - status = model[active][2].decode('utf-8') - statuses_unified = helpers.statuses_unified() # status "desync'ed" or not - if (active == 7 and statuses_unified) or (active == 9 and \ - not statuses_unified): - # 'Change status message' selected: - # do not change show, just show change status dialog - status = model[self.previous_status_combobox_active][2].decode('utf-8') - def on_response(message, pep_dict): - if message is not None: # None if user pressed Cancel - for account in accounts: - if not gajim.config.get_per('accounts', account, - 'sync_with_global_status'): - continue - current_show = gajim.SHOW_LIST[ - gajim.connections[account].connected] - self.send_status(account, current_show, message) - self.send_pep(account, pep_dict) - self.combobox_callback_active = False - self.status_combobox.set_active( - self.previous_status_combobox_active) - self.combobox_callback_active = True - dialogs.ChangeStatusMessageDialog(on_response, status) - return - # we are about to change show, so save this new show so in case - # after user chooses "Change status message" menuitem - # we can return to this show - self.previous_status_combobox_active = active - connected_accounts = gajim.get_number_of_connected_accounts() - - def on_continue(message, pep_dict): - if message is None: - # user pressed Cancel to change status message dialog - self.update_status_combobox() - return - global_sync_accounts = [] - for acct in accounts: - if gajim.config.get_per('accounts', acct, - 'sync_with_global_status'): - global_sync_accounts.append(acct) - global_sync_connected_accounts = \ - gajim.get_number_of_connected_accounts(global_sync_accounts) - for account in accounts: - if not gajim.config.get_per('accounts', account, - 'sync_with_global_status'): - continue - # we are connected (so we wanna change show and status) - # or no account is connected and we want to connect with new show - # and status - - if not global_sync_connected_accounts > 0 or \ - gajim.connections[account].connected > 0: - self.send_status(account, status, message) - self.send_pep(account, pep_dict) - self.update_status_combobox() - - if status == 'invisible': - bug_user = False - for account in accounts: - if connected_accounts < 1 or gajim.account_is_connected(account): - if not gajim.config.get_per('accounts', account, - 'sync_with_global_status'): - continue - # We're going to change our status to invisible - if self.connected_rooms(account): - bug_user = True - break - if bug_user: - def on_ok(): - self.get_status_message(status, on_continue, show_pep=False) - - def on_cancel(): - self.update_status_combobox() - - dialogs.ConfirmationDialog( - _('You are participating in one or more group chats'), - _('Changing your status to invisible will result in ' - 'disconnection from those group chats. Are you sure you want to ' - 'go invisible?'), on_reponse_ok=on_ok, - on_response_cancel=on_cancel) - return - - self.get_status_message(status, on_continue) - - def on_preferences_menuitem_activate(self, widget): - if 'preferences' in gajim.interface.instances: - gajim.interface.instances['preferences'].window.present() - else: - gajim.interface.instances['preferences'] = config.PreferencesWindow() - - def on_plugins_menuitem_activate(self, widget): - if gajim.interface.instances.has_key('plugins'): - gajim.interface.instances['plugins'].window.present() - else: - gajim.interface.instances['plugins'] = plugins.gui.PluginsWindow() - - def on_publish_tune_toggled(self, widget, account): - active = widget.get_active() - gajim.config.set_per('accounts', account, 'publish_tune', active) - if active: - gajim.interface.enable_music_listener() - else: - gajim.connections[account].retract_tune() - # disable music listener only if no other account uses it - for acc in gajim.connections: - if gajim.config.get_per('accounts', acc, 'publish_tune'): - break - else: - gajim.interface.disable_music_listener() - - helpers.update_optional_features(account) - - def on_publish_location_toggled(self, widget, account): - active = widget.get_active() - gajim.config.set_per('accounts', account, 'publish_location', active) - if active: - location_listener.enable() - else: - gajim.connections[account].retract_location() - # disable music listener only if no other account uses it - for acc in gajim.connections: - if gajim.config.get_per('accounts', acc, 'publish_location'): - break - else: - location_listener.disable() - - helpers.update_optional_features(account) - - def on_pep_services_menuitem_activate(self, widget, account): - if 'pep_services' in gajim.interface.instances[account]: - gajim.interface.instances[account]['pep_services'].window.present() - else: - gajim.interface.instances[account]['pep_services'] = \ - config.ManagePEPServicesWindow(account) - - def on_add_new_contact(self, widget, account): - dialogs.AddNewContactWindow(account) - - def on_join_gc_activate(self, widget, account): - """ - When the join gc menuitem is clicked, show the join gc window - """ - invisible_show = gajim.SHOW_LIST.index('invisible') - if gajim.connections[account].connected == invisible_show: - dialogs.ErrorDialog(_('You cannot join a group chat while you are ' - 'invisible')) - return - if 'join_gc' in gajim.interface.instances[account]: - gajim.interface.instances[account]['join_gc'].window.present() - else: - try: - gajim.interface.instances[account]['join_gc'] = \ - dialogs.JoinGroupchatWindow(account) - except GajimGeneralException: - pass - - def on_new_chat_menuitem_activate(self, widget, account): - dialogs.NewChatDialog(account) - - def on_contents_menuitem_activate(self, widget): - helpers.launch_browser_mailer('url', 'http://trac.gajim.org/wiki') - - def on_faq_menuitem_activate(self, widget): - helpers.launch_browser_mailer('url', - 'http://trac.gajim.org/wiki/GajimFaq') - - def on_features_menuitem_activate(self, widget): - features_window.FeaturesWindow() - - def on_about_menuitem_activate(self, widget): - dialogs.AboutDialog() - - def on_accounts_menuitem_activate(self, widget): - if 'accounts' in gajim.interface.instances: - gajim.interface.instances['accounts'].window.present() - else: - gajim.interface.instances['accounts'] = config.AccountsWindow() - - def on_file_transfers_menuitem_activate(self, widget): - if gajim.interface.instances['file_transfers'].window.get_property( - 'visible'): - gajim.interface.instances['file_transfers'].window.present() - else: - gajim.interface.instances['file_transfers'].window.show_all() - - def on_history_menuitem_activate(self, widget): - if 'logs' in gajim.interface.instances: - gajim.interface.instances['logs'].window.present() - else: - gajim.interface.instances['logs'] = history_window.\ - HistoryWindow() - - def on_show_transports_menuitem_activate(self, widget): - gajim.config.set('show_transports_group', widget.get_active()) - self.refilter_shown_roster_items() - - def on_manage_bookmarks_menuitem_activate(self, widget): - config.ManageBookmarksWindow() - - def on_profile_avatar_menuitem_activate(self, widget, account): - gajim.interface.edit_own_details(account) - - def on_execute_command(self, widget, contact, account, resource=None): - """ - Execute command. Full JID needed; if it is other contact, resource is - necessary. Widget is unnecessary, only to be able to make this a callback - """ - jid = contact.jid - if resource is not None: - jid = jid + u'/' + resource - adhoc_commands.CommandWindow(account, jid) - - def on_roster_window_focus_in_event(self, widget, event): - # roster received focus, so if we had urgency REMOVE IT - # NOTE: we do not have to read the message to remove urgency - # so this functions does that - gtkgui_helpers.set_unset_urgency_hint(widget, False) - - # if a contact row is selected, update colors (eg. for status msg) - # because gtk engines may differ in bg when window is selected - # or not - if len(self._last_selected_contact): - for (jid, account) in self._last_selected_contact: - self.draw_contact(jid, account, selected=True, focus=True) - - def on_roster_window_focus_out_event(self, widget, event): - # if a contact row is selected, update colors (eg. for status msg) - # because gtk engines may differ in bg when window is selected - # or not - if len(self._last_selected_contact): - for (jid, account) in self._last_selected_contact: - self.draw_contact(jid, account, selected=True, focus=False) - - def on_roster_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: - if gajim.interface.msg_win_mgr.mode == \ - MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER and \ - gajim.interface.msg_win_mgr.one_window_opened(): - # let message window close the tab - return - list_of_paths = self.tree.get_selection().get_selected_rows()[1] - if not len(list_of_paths) and gajim.interface.systray_enabled and \ - not gajim.config.get('quit_on_roster_x_button'): - self.tooltip.hide_tooltip() - self.window.hide() - elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == gtk.keysyms.i: - treeselection = self.tree.get_selection() - model, list_of_paths = treeselection.get_selected_rows() - for path in list_of_paths: - type_ = model[path][C_TYPE] - if type_ in ('contact', 'agent'): - jid = model[path][C_JID].decode('utf-8') - account = model[path][C_ACCOUNT].decode('utf-8') - contact = gajim.contacts.get_first_contact_from_jid(account, jid) - self.on_info(widget, contact, account) - elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == gtk.keysyms.h: - treeselection = self.tree.get_selection() - model, list_of_paths = treeselection.get_selected_rows() - if len(list_of_paths) != 1: - return - path = list_of_paths[0] - type_ = model[path][C_TYPE] - if type_ in ('contact', 'agent'): - jid = model[path][C_JID].decode('utf-8') - account = model[path][C_ACCOUNT].decode('utf-8') - contact = gajim.contacts.get_first_contact_from_jid(account, jid) - self.on_history(widget, contact, account) - - def on_roster_window_popup_menu(self, widget): - event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) - self.show_treeview_menu(event) - - def on_row_activated(self, widget, path): - """ - When an iter is activated (double-click or single click if gnome is set - this way) - """ - model = self.modelfilter - account = model[path][C_ACCOUNT].decode('utf-8') - type_ = model[path][C_TYPE] - if type_ in ('group', 'account'): - if self.tree.row_expanded(path): - self.tree.collapse_row(path) - else: - self.tree.expand_row(path, False) - return - jid = model[path][C_JID].decode('utf-8') - resource = None - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - titer = model.get_iter(path) - if contact.is_groupchat(): - first_ev = gajim.events.get_first_event(account, jid) - if first_ev and self.open_event(account, jid, first_ev): - # We are invited to a GC - # open event cares about connecting to it - self.remove_groupchat(jid, account) - else: - self.on_groupchat_maximized(None, jid, account) - return - - # else - first_ev = gajim.events.get_first_event(account, jid) - if not first_ev: - # look in other resources - for c in gajim.contacts.get_contacts(account, jid): - fjid = c.get_full_jid() - first_ev = gajim.events.get_first_event(account, fjid) - if first_ev: - resource = c.resource - break - if not first_ev and model.iter_has_child(titer): - child_iter = model.iter_children(titer) - while not first_ev and child_iter: - child_jid = model[child_iter][C_JID].decode('utf-8') - first_ev = gajim.events.get_first_event(account, child_jid) - if first_ev: - jid = child_jid - else: - child_iter = model.iter_next(child_iter) - session = None - if first_ev: - if first_ev.type_ in ('chat', 'normal'): - session = first_ev.parameters[8] - fjid = jid - if resource: - fjid += '/' + resource - if self.open_event(account, fjid, first_ev): - return - # else - contact = gajim.contacts.get_contact(account, jid, resource) - if not contact or isinstance(contact, list): - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - if jid == gajim.get_jid_from_account(account): - resource = contact.resource - - gajim.interface.on_open_chat_window(None, contact, account, \ - resource=resource, session=session) - - def on_roster_treeview_row_activated(self, widget, path, col=0): - """ - When an iter is double clicked: open the first event window - """ - if not gajim.single_click: - self.on_row_activated(widget, path) - - def on_roster_treeview_row_expanded(self, widget, titer, path): - """ - When a row is expanded change the icon of the arrow - """ - self._toggeling_row = True - model = widget.get_model() - child_model = model.get_model() - child_iter = model.convert_iter_to_child_iter(titer) - - if self.regroup: # merged accounts - accounts = gajim.connections.keys() - else: - accounts = [model[titer][C_ACCOUNT].decode('utf-8')] - - type_ = model[titer][C_TYPE] - if type_ == 'group': - group = model[titer][C_JID].decode('utf-8') - child_model[child_iter][C_IMG] = gajim.interface.jabber_state_images[ - '16']['opened'] - for account in accounts: - if group in gajim.groups[account]: # This account has this group - gajim.groups[account][group]['expand'] = True - if account + group in self.collapsed_rows: - self.collapsed_rows.remove(account + group) - for contact in gajim.contacts.iter_contacts(account): - jid = contact.jid - if group in contact.groups and gajim.contacts.is_big_brother( - account, jid, accounts) and account + group + jid \ - not in self.collapsed_rows: - titers = self._get_contact_iter(jid, account) - for titer in titers: - path = model.get_path(titer) - self.tree.expand_row(path, False) - elif type_ == 'account': - account = accounts[0] # There is only one cause we don't use merge - if account in self.collapsed_rows: - self.collapsed_rows.remove(account) - self.draw_account(account) - # When we expand, groups are collapsed. Restore expand state - for group in gajim.groups[account]: - if gajim.groups[account][group]['expand']: - titer = self._get_group_iter(group, account) - if titer: - path = model.get_path(titer) - self.tree.expand_row(path, False) - elif type_ == 'contact': - # Metacontact got toggled, update icon - jid = model[titer][C_JID].decode('utf-8') - account = model[titer][C_ACCOUNT].decode('utf-8') - contact = gajim.contacts.get_contact(account, jid) - for group in contact.groups: - if account + group + jid in self.collapsed_rows: - self.collapsed_rows.remove(account + group + jid) - family = gajim.contacts.get_metacontacts_family(account, jid) - nearby_family = \ - self._get_nearby_family_and_big_brother(family, account)[0] - # Redraw all brothers to show pending events - for data in nearby_family: - self.draw_contact(data['jid'], data['account']) - - self._toggeling_row = False - - def on_roster_treeview_row_collapsed(self, widget, titer, path): - """ - When a row is collapsed change the icon of the arrow - """ - self._toggeling_row = True - model = widget.get_model() - child_model = model.get_model() - child_iter = model.convert_iter_to_child_iter(titer) - - if self.regroup: # merged accounts - accounts = gajim.connections.keys() - else: - accounts = [model[titer][C_ACCOUNT].decode('utf-8')] - - type_ = model[titer][C_TYPE] - if type_ == 'group': - child_model[child_iter][C_IMG] = gajim.interface.jabber_state_images[ - '16']['closed'] - group = model[titer][C_JID].decode('utf-8') - for account in accounts: - if group in gajim.groups[account]: # This account has this group - gajim.groups[account][group]['expand'] = False - if account + group not in self.collapsed_rows: - self.collapsed_rows.append(account + group) - elif type_ == 'account': - account = accounts[0] # There is only one cause we don't use merge - if account not in self.collapsed_rows: - self.collapsed_rows.append(account) - self.draw_account(account) - elif type_ == 'contact': - # Metacontact got toggled, update icon - jid = model[titer][C_JID].decode('utf-8') - account = model[titer][C_ACCOUNT].decode('utf-8') - contact = gajim.contacts.get_contact(account, jid) - for group in contact.groups: - if account + group + jid not in self.collapsed_rows: - self.collapsed_rows.append(account + group + jid) - family = gajim.contacts.get_metacontacts_family(account, jid) - nearby_family = \ - self._get_nearby_family_and_big_brother(family, account)[0] - # Redraw all brothers to show pending events - for data in nearby_family: - self.draw_contact(data['jid'], data['account']) - - self._toggeling_row = False - - def on_modelfilter_row_has_child_toggled(self, model, path, titer): - """ - Called when a row has gotten the first or lost its last child row - - Expand Parent if necessary. - """ - if self._toggeling_row: - # Signal is emitted when we write to our model - return - - type_ = model[titer][C_TYPE] - account = model[titer][C_ACCOUNT] - if not account: - return - - account = account.decode('utf-8') - - if type_ == 'contact': - child_iter = model.convert_iter_to_child_iter(titer) - if self.model.iter_has_child(child_iter): - # we are a bigbrother metacontact - # redraw us to show/hide expand icon - if self.filtering: - # Prevent endless loops - jid = model[titer][C_JID].decode('utf-8') - gobject.idle_add(self.draw_contact, jid, account) - elif type_ == 'group': - group = model[titer][C_JID].decode('utf-8') - self._adjust_group_expand_collapse_state(group, account) - elif type_ == 'account': - self._adjust_account_expand_collapse_state(account) + def on_actions_menuitem_activate(self, widget): + self.make_menu() + + def on_edit_menuitem_activate(self, widget): + """ + Need to call make_menu to build profile, avatar item + """ + self.make_menu() + + def on_bookmark_menuitem_activate(self, widget, account, bookmark): + gajim.interface.join_gc_room(account, bookmark['jid'], bookmark['nick'], + bookmark['password']) + + def on_send_server_message_menuitem_activate(self, widget, account): + server = gajim.config.get_per('accounts', account, 'hostname') + server += '/announce/online' + dialogs.SingleMessageWindow(account, server, 'send') + + def on_xml_console_menuitem_activate(self, widget, account): + if 'xml_console' in gajim.interface.instances[account]: + gajim.interface.instances[account]['xml_console'].window.present() + else: + gajim.interface.instances[account]['xml_console'] = \ + dialogs.XMLConsoleWindow(account) + + def on_privacy_lists_menuitem_activate(self, widget, account): + if 'privacy_lists' in gajim.interface.instances[account]: + gajim.interface.instances[account]['privacy_lists'].window.present() + else: + gajim.interface.instances[account]['privacy_lists'] = \ + dialogs.PrivacyListsWindow(account) + + def on_set_motd_menuitem_activate(self, widget, account): + server = gajim.config.get_per('accounts', account, 'hostname') + server += '/announce/motd' + dialogs.SingleMessageWindow(account, server, 'send') + + def on_update_motd_menuitem_activate(self, widget, account): + server = gajim.config.get_per('accounts', account, 'hostname') + server += '/announce/motd/update' + dialogs.SingleMessageWindow(account, server, 'send') + + def on_delete_motd_menuitem_activate(self, widget, account): + server = gajim.config.get_per('accounts', account, 'hostname') + server += '/announce/motd/delete' + gajim.connections[account].send_motd(server) + + def on_history_manager_menuitem_activate(self, widget): + if os.name == 'nt': + if os.path.exists('history_manager.exe'): # user is running stable + helpers.exec_command('history_manager.exe') + else: # user is running svn + helpers.exec_command('%s history_manager.py' % sys.executable) + else: # Unix user + helpers.exec_command('%s history_manager.py' % sys.executable) + + def on_info(self, widget, contact, account): + """ + Call vcard_information_window class to display contact's information + """ + if gajim.connections[account].is_zeroconf: + self.on_info_zeroconf(widget, contact, account) + return + + info = gajim.interface.instances[account]['infos'] + if contact.jid in info: + info[contact.jid].window.present() + else: + info[contact.jid] = vcard.VcardWindow(contact, account) + + def on_info_zeroconf(self, widget, contact, account): + info = gajim.interface.instances[account]['infos'] + if contact.jid in info: + info[contact.jid].window.present() + else: + contact = gajim.contacts.get_first_contact_from_jid(account, + contact.jid) + if contact.show in ('offline', 'error'): + # don't show info on offline contacts + return + info[contact.jid] = vcard.ZeroconfVcardWindow(contact, account) + + def on_roster_treeview_leave_notify_event(self, widget, event): + props = widget.get_path_at_pos(int(event.x), int(event.y)) + if self.tooltip.timeout > 0: + if not props or self.tooltip.id == props[0]: + self.tooltip.hide_tooltip() + + def on_roster_treeview_motion_notify_event(self, widget, event): + model = widget.get_model() + props = widget.get_path_at_pos(int(event.x), int(event.y)) + if self.tooltip.timeout > 0: + if not props or self.tooltip.id != props[0]: + self.tooltip.hide_tooltip() + if props: + row = props[0] + titer = None + try: + titer = model.get_iter(row) + except Exception: + self.tooltip.hide_tooltip() + return + if model[titer][C_TYPE] in ('contact', 'self_contact'): + # we're on a contact entry in the roster + account = model[titer][C_ACCOUNT].decode('utf-8') + jid = model[titer][C_JID].decode('utf-8') + if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: + self.tooltip.id = row + contacts = gajim.contacts.get_contacts(account, jid) + connected_contacts = [] + for c in contacts: + if c.show not in ('offline', 'error'): + connected_contacts.append(c) + if not connected_contacts: + # no connected contacts, show the ofline one + connected_contacts = contacts + self.tooltip.account = account + self.tooltip.timeout = gobject.timeout_add(500, + self.show_tooltip, connected_contacts) + elif model[titer][C_TYPE] == 'groupchat': + account = model[titer][C_ACCOUNT].decode('utf-8') + jid = model[titer][C_JID].decode('utf-8') + if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: + self.tooltip.id = row + contact = gajim.contacts.get_contacts(account, jid) + self.tooltip.account = account + self.tooltip.timeout = gobject.timeout_add(500, + self.show_tooltip, contact) + elif model[titer][C_TYPE] == 'account': + # we're on an account entry in the roster + account = model[titer][C_ACCOUNT].decode('utf-8') + if account == 'all': + if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: + self.tooltip.id = row + self.tooltip.account = None + self.tooltip.timeout = gobject.timeout_add(500, + self.show_tooltip, []) + return + jid = gajim.get_jid_from_account(account) + contacts = [] + connection = gajim.connections[account] + # get our current contact info + + nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts( + accounts = [account]) + account_name = account + if gajim.account_is_connected(account): + account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total)) + contact = gajim.contacts.create_self_contact(jid=jid, account=account, + name=account_name, show=connection.get_status(), + status=connection.status, resource=connection.server_resource, + priority=connection.priority) + if gajim.connections[account].gpg: + contact.keyID = gajim.config.get_per('accounts', connection.name, + 'keyid') + contacts.append(contact) + # if we're online ... + if connection.connection: + roster = connection.connection.getRoster() + # in threadless connection when no roster stanza is sent, + # 'roster' is None + if roster and roster.getItem(jid): + resources = roster.getResources(jid) + # ...get the contact info for our other online resources + for resource in resources: + # Check if we already have this resource + found = False + for contact_ in contacts: + if contact_.resource == resource: + found = True + break + if found: + continue + show = roster.getShow(jid+'/'+resource) + if not show: + show = 'online' + contact = gajim.contacts.create_self_contact(jid=jid, + account=account, show=show, status=roster.getStatus(jid + '/' + resource), + priority=roster.getPriority(jid + '/' + resource), + resource=resource) + contacts.append(contact) + if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: + self.tooltip.id = row + self.tooltip.account = None + self.tooltip.timeout = gobject.timeout_add(500, + self.show_tooltip, contacts) + + def on_agent_logging(self, widget, jid, state, account): + """ + When an agent is requested to log in or off + """ + gajim.connections[account].send_agent_status(jid, state) + + def on_edit_agent(self, widget, contact, account): + """ + When we want to modify the agent registration + """ + gajim.connections[account].request_register_agent_info(contact.jid) + + def on_remove_agent(self, widget, list_): + """ + When an agent is requested to be removed. list_ is a list of (contact, + account) tuple + """ + for (contact, account) in list_: + if gajim.config.get_per('accounts', account, 'hostname') == \ + contact.jid: + # We remove the server contact + # remove it from treeview + gajim.connections[account].unsubscribe(contact.jid) + self.remove_contact(contact.jid, account, backend=True) + return + + def remove(list_): + for (contact, account) in list_: + full_jid = contact.get_full_jid() + gajim.connections[account].unsubscribe_agent(full_jid) + # remove transport from treeview + self.remove_contact(contact.jid, account, backend=True) + + # Check if there are unread events from some contacts + has_unread_events = False + for (contact, account) in list_: + for jid in gajim.events.get_events(account): + if jid.endswith(contact.jid): + has_unread_events = True + break + if has_unread_events: + dialogs.ErrorDialog(_('You have unread messages'), + _('You must read them before removing this transport.')) + return + if len(list_) == 1: + pritext = _('Transport "%s" will be removed') % list_[0][0].jid + sectext = _('You will no longer be able to send and receive messages ' + 'from contacts using this transport.') + else: + pritext = _('Transports will be removed') + jids = '' + for (contact, account) in list_: + jids += '\n ' + contact.get_shown_name() + ',' + jids = jids[:-1] + '.' + sectext = _('You will no longer be able to send and receive messages ' + 'to contacts from these transports: %s') % jids + dialogs.ConfirmationDialog(pritext, sectext, + on_response_ok = (remove, list_)) + + def on_block(self, widget, list_, group=None): + """ + When clicked on the 'block' button in context menu. list_ is a list of + (contact, account) + """ + def on_continue(msg, pep_dict): + if msg is None: + # user pressed Cancel to change status message dialog + return + accounts = [] + if group is None: + for (contact, account) in list_: + if account not in accounts: + if not gajim.connections[account].privacy_rules_supported: + continue + accounts.append(account) + self.send_status(account, 'offline', msg, to=contact.jid) + new_rule = {'order': u'1', 'type': u'jid', 'action': u'deny', + 'value' : contact.jid, 'child': [u'message', u'iq', + u'presence-out']} + gajim.connections[account].blocked_list.append(new_rule) + # needed for draw_contact: + gajim.connections[account].blocked_contacts.append( + contact.jid) + self.draw_contact(contact.jid, account) + else: + for (contact, account) in list_: + if account not in accounts: + if not gajim.connections[account].privacy_rules_supported: + continue + accounts.append(account) + # needed for draw_group: + gajim.connections[account].blocked_groups.append(group) + self.draw_group(group, account) + self.send_status(account, 'offline', msg, to=contact.jid) + self.draw_contact(contact.jid, account) + new_rule = {'order': u'1', 'type': u'group', 'action': u'deny', + 'value' : group, 'child': [u'message', u'iq', u'presence-out']} + gajim.connections[account].blocked_list.append(new_rule) + for account in accounts: + connection = gajim.connections[account] + connection.set_privacy_list('block', connection.blocked_list) + if len(connection.blocked_list) == 1: + connection.set_active_list('block') + connection.set_default_list('block') + connection.get_privacy_list('block') + + def _block_it(is_checked=None): + if is_checked is not None: # dialog has been shown + if is_checked: # user does not want to be asked again + gajim.config.set('confirm_block', 'no') + else: + gajim.config.set('confirm_block', 'yes') + self.get_status_message('offline', on_continue, show_pep=False) + + confirm_block = gajim.config.get('confirm_block') + if confirm_block == 'no': + _block_it() + return + pritext = _('You are about to block a contact. Are you sure you want' + ' to continue?') + sectext = _('This contact will see you offline and you will not receive ' + 'messages he will send you.') + dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, + _('Do _not ask me again'), on_response_ok=_block_it) + + def on_unblock(self, widget, list_, group=None): + """ + When clicked on the 'unblock' button in context menu. + """ + accounts = [] + if group is None: + for (contact, account) in list_: + if account not in accounts: + if gajim.connections[account].privacy_rules_supported: + accounts.append(account) + gajim.connections[account].new_blocked_list = [] + gajim.connections[account].to_unblock = [] + gajim.connections[account].to_unblock.append(contact.jid) + else: + gajim.connections[account].to_unblock.append(contact.jid) + # needed for draw_contact: + if contact.jid in gajim.connections[account].blocked_contacts: + gajim.connections[account].blocked_contacts.remove(contact.jid) + self.draw_contact(contact.jid, account) + for account in accounts: + for rule in gajim.connections[account].blocked_list: + if rule['action'] != 'deny' or rule['type'] != 'jid' \ + or rule['value'] not in gajim.connections[account].to_unblock: + gajim.connections[account].new_blocked_list.append(rule) + else: + for (contact, account) in list_: + if account not in accounts: + if gajim.connections[account].privacy_rules_supported: + accounts.append(account) + # needed for draw_group: + if group in gajim.connections[account].blocked_groups: + gajim.connections[account].blocked_groups.remove(group) + self.draw_group(group, account) + gajim.connections[account].new_blocked_list = [] + for rule in gajim.connections[account].blocked_list: + if rule['action'] != 'deny' or rule['type'] != 'group' \ + or rule['value'] != group: + gajim.connections[account].new_blocked_list.append(rule) + self.draw_contact(contact.jid, account) + for account in accounts: + gajim.connections[account].set_privacy_list('block', + gajim.connections[account].new_blocked_list) + gajim.connections[account].get_privacy_list('block') + if len(gajim.connections[account].new_blocked_list) == 0: + gajim.connections[account].blocked_list = [] + gajim.connections[account].blocked_contacts = [] + gajim.connections[account].blocked_groups = [] + gajim.connections[account].set_default_list('') + gajim.connections[account].set_active_list('') + gajim.connections[account].del_privacy_list('block') + if 'blocked_contacts' in gajim.interface.instances[account]: + gajim.interface.instances[account]['blocked_contacts'].\ + privacy_list_received([]) + for (contact, account) in list_: + if not self.regroup: + show = gajim.SHOW_LIST[gajim.connections[account].connected] + else: # accounts merged + show = helpers.get_global_show() + if show == 'invisible': + # Don't send our presence if we're invisible + continue + if account not in accounts: + accounts.append(account) + if gajim.connections[account].privacy_rules_supported: + self.send_status(account, show, + gajim.connections[account].status, to=contact.jid) + else: + self.send_status(account, show, + gajim.connections[account].status, to=contact.jid) + + def on_rename(self, widget, row_type, jid, account): + # this function is called either by F2 or by Rename menuitem + if 'rename' in gajim.interface.instances: + gajim.interface.instances['rename'].dialog.present() + return + + # account is offline, don't allow to rename + if gajim.connections[account].connected < 2: + return + if row_type in ('contact', 'agent'): + # it's jid + title = _('Rename Contact') + message = _('Enter a new nickname for contact %s') % jid + old_text = gajim.contacts.get_contact_with_highest_priority(account, + jid).name + elif row_type == 'group': + if jid in helpers.special_groups + (_('General'),): + return + old_text = jid + title = _('Rename Group') + message = _('Enter a new name for group %s') % \ + gobject.markup_escape_text(jid) + + def on_renamed(new_text, account, row_type, jid, old_text): + if 'rename' in gajim.interface.instances: + del gajim.interface.instances['rename'] + if row_type in ('contact', 'agent'): + if old_text == new_text: + return + for contact in gajim.contacts.get_contacts(account, jid): + contact.name = new_text + gajim.connections[account].update_contact(jid, new_text, \ + contact.groups) + self.draw_contact(jid, account) + # Update opened chats + for ctrl in gajim.interface.msg_win_mgr.get_controls(jid, account): + ctrl.update_ui() + win = gajim.interface.msg_win_mgr.get_window(jid, account) + win.redraw_tab(ctrl) + win.show_title() + elif row_type == 'group': + # in C_JID column, we hold the group name (which is not escaped) + self.rename_group(old_text, new_text, account) + + def on_canceled(): + if 'rename' in gajim.interface.instances: + del gajim.interface.instances['rename'] + + gajim.interface.instances['rename'] = dialogs.InputDialog(title, message, + old_text, False, (on_renamed, account, row_type, jid, old_text), + on_canceled) + + def on_remove_group_item_activated(self, widget, group, account): + def on_ok(checked): + for contact in gajim.contacts.get_contacts_from_group(account, group): + if not checked: + self.remove_contact_from_groups(contact.jid, account, [group]) + else: + gajim.connections[account].unsubscribe(contact.jid) + self.remove_contact(contact.jid, account, backend=True) + + dialogs.ConfirmationDialogCheck(_('Remove Group'), + _('Do you want to remove group %s from the roster?') % group, + _('Also remove all contacts in this group from your roster'), + on_response_ok=on_ok) + + def on_assign_pgp_key(self, widget, contact, account): + attached_keys = gajim.config.get_per('accounts', account, + 'attached_gpg_keys').split() + keys = {} + keyID = _('None') + for i in xrange(len(attached_keys)/2): + keys[attached_keys[2*i]] = attached_keys[2*i+1] + if attached_keys[2*i] == contact.jid: + keyID = attached_keys[2*i+1] + public_keys = gajim.connections[account].ask_gpg_keys() + public_keys[_('None')] = _('None') + + def on_key_selected(keyID): + if keyID is None: + return + if keyID[0] == _('None'): + if contact.jid in keys: + del keys[contact.jid] + keyID = '' + else: + keyID = keyID[0] + keys[contact.jid] = keyID + + ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account) + if ctrl: + ctrl.update_ui() + + keys_str = '' + for jid in keys: + keys_str += jid + ' ' + keys[jid] + ' ' + gajim.config.set_per('accounts', account, 'attached_gpg_keys', + keys_str) + for u in gajim.contacts.get_contacts(account, contact.jid): + u.keyID = helpers.prepare_and_validate_gpg_keyID(account, + contact.jid, keyID) + + dialogs.ChooseGPGKeyDialog(_('Assign OpenPGP Key'), + _('Select a key to apply to the contact'), public_keys, + on_key_selected, selected=keyID) + + def on_set_custom_avatar_activate(self, widget, contact, account): + def on_ok(widget, path_to_file): + filesize = os.path.getsize(path_to_file) # in bytes + invalid_file = False + msg = '' + if os.path.isfile(path_to_file): + stat = os.stat(path_to_file) + if stat[6] == 0: + invalid_file = True + msg = _('File is empty') + else: + invalid_file = True + msg = _('File does not exist') + if invalid_file: + dialogs.ErrorDialog(_('Could not load image'), msg) + return + try: + pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file) + if filesize > 16384: # 16 kb + # get the image at 'tooltip size' + # and hope that user did not specify in ACE crazy size + pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'tooltip') + except gobject.GError, msg: # unknown format + # msg should be string, not object instance + msg = str(msg) + dialogs.ErrorDialog(_('Could not load image'), msg) + return + gajim.interface.save_avatar_files(contact.jid, pixbuf, local=True) + dlg.destroy() + self.update_avatar_in_gui(contact.jid, account) + + def on_clear(widget): + dlg.destroy() + # Delete file: + gajim.interface.remove_avatar_files(contact.jid, local=True) + self.update_avatar_in_gui(contact.jid, account) + + dlg = dialogs.AvatarChooserDialog(on_response_ok=on_ok, + on_response_clear=on_clear) + + def on_edit_groups(self, widget, list_): + dialogs.EditGroupsDialog(list_) + + def on_history(self, widget, contact, account): + """ + When history menuitem is activated: call log window + """ + if 'logs' in gajim.interface.instances: + gajim.interface.instances['logs'].window.present() + gajim.interface.instances['logs'].open_history(contact.jid, account) + else: + gajim.interface.instances['logs'] = history_window.\ + HistoryWindow(contact.jid, account) + + def on_disconnect(self, widget, jid, account): + """ + When disconnect menuitem is activated: disconect from room + """ + if jid in gajim.interface.minimized_controls[account]: + ctrl = gajim.interface.minimized_controls[account][jid] + ctrl.shutdown() + ctrl.got_disconnected() + self.remove_groupchat(jid, account) + + def on_reconnect(self, widget, jid, account): + """ + When disconnect menuitem is activated: disconect from room + """ + if jid in gajim.interface.minimized_controls[account]: + ctrl = gajim.interface.minimized_controls[account][jid] + gajim.interface.join_gc_room(account, jid, ctrl.nick, + gajim.gc_passwords.get(jid, '')) + + def on_send_single_message_menuitem_activate(self, widget, account, + contact=None): + if contact is None: + dialogs.SingleMessageWindow(account, action='send') + elif isinstance(contact, list): + dialogs.SingleMessageWindow(account, contact, 'send') + else: + jid = contact.jid + if contact.jid == gajim.get_jid_from_account(account): + jid += '/' + contact.resource + dialogs.SingleMessageWindow(account, jid, 'send') + + def on_send_file_menuitem_activate(self, widget, contact, account, + resource=None): + gajim.interface.instances['file_transfers'].show_file_send_request( + account, contact) + + def on_add_special_notification_menuitem_activate(self, widget, jid): + dialogs.AddSpecialNotificationDialog(jid) + + def on_invite_to_new_room(self, widget, list_, resource=None): + """ + Resource parameter MUST NOT be used if more than one contact in list + """ + account_list = [] + jid_list = [] + for (contact, account) in list_: + if contact.jid not in jid_list: + if resource: # we MUST have one contact only in list_ + fjid = contact.jid + '/' + resource + jid_list.append(fjid) + else: + jid_list.append(contact.jid) + if account not in account_list: + account_list.append(account) + # transform None in 'jabber' + type_ = gajim.get_transport_name_from_jid(jid_list[0]) or 'jabber' + for account in account_list: + if gajim.connections[account].muc_jid[type_]: + # create the room on this muc server + if 'join_gc' in gajim.interface.instances[account]: + gajim.interface.instances[account]['join_gc'].window.destroy() + try: + gajim.interface.instances[account]['join_gc'] = \ + dialogs.JoinGroupchatWindow(account, + gajim.connections[account].muc_jid[type_], + automatic = {'invities': jid_list}) + except GajimGeneralException: + continue + break + + def on_invite_to_room(self, widget, list_, room_jid, room_account, + resource=None): + """ + Resource parameter MUST NOT be used if more than one contact in list + """ + for e in list_: + contact = e[0] + contact_jid = contact.jid + if resource: # we MUST have one contact only in list_ + contact_jid += '/' + resource + gajim.connections[room_account].send_invite(room_jid, contact_jid) + + def on_all_groupchat_maximized(self, widget, group_list): + for (contact, account) in group_list: + self.on_groupchat_maximized(widget, contact.jid, account) + + def on_groupchat_maximized(self, widget, jid, account): + """ + When a groupchat is maximized + """ + if not jid in gajim.interface.minimized_controls[account]: + # Already opened? + gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid, account) + if gc_control: + mw = gajim.interface.msg_win_mgr.get_window(jid, account) + mw.set_active_tab(gc_control) + mw.window.window.focus(gtk.get_current_event_time()) + return + ctrl = gajim.interface.minimized_controls[account][jid] + mw = gajim.interface.msg_win_mgr.get_window(jid, account) + if not mw: + mw = gajim.interface.msg_win_mgr.create_window(ctrl.contact, + ctrl.account, ctrl.type_id) + ctrl.parent_win = mw + mw.new_tab(ctrl) + mw.set_active_tab(ctrl) + mw.window.window.focus(gtk.get_current_event_time()) + self.remove_groupchat(jid, account) + + def on_edit_account(self, widget, account): + if 'accounts' in gajim.interface.instances: + gajim.interface.instances['accounts'].window.present() + else: + gajim.interface.instances['accounts'] = config.AccountsWindow() + gajim.interface.instances['accounts'].select_account(account) + + def on_zeroconf_properties(self, widget, account): + if 'accounts' in gajim.interface.instances: + gajim.interface.instances['accounts'].window.present() + else: + gajim.interface.instances['accounts'] = config.AccountsWindow() + gajim.interface.instances['accounts'].select_account(account) + + def on_open_gmail_inbox(self, widget, account): + url = gajim.connections[account].gmail_url + if url: + helpers.launch_browser_mailer('url', url) + + def on_change_status_message_activate(self, widget, account): + show = gajim.SHOW_LIST[gajim.connections[account].connected] + def on_response(message, pep_dict): + if message is None: # None is if user pressed Cancel + return + self.send_status(account, show, message) + self.send_pep(account, pep_dict) + dialogs.ChangeStatusMessageDialog(on_response, show) + + def on_add_to_roster(self, widget, contact, account): + dialogs.AddNewContactWindow(account, contact.jid, contact.name) + + + def on_roster_treeview_scroll_event(self, widget, event): + self.tooltip.hide_tooltip() + + def on_roster_treeview_key_press_event(self, widget, event): + """ + When a key is pressed in the treeviews + """ + self.tooltip.hide_tooltip() + if event.keyval == gtk.keysyms.Escape: + self.tree.get_selection().unselect_all() + elif event.keyval == gtk.keysyms.F2: + treeselection = self.tree.get_selection() + model, list_of_paths = treeselection.get_selected_rows() + if len(list_of_paths) != 1: + return + path = list_of_paths[0] + type_ = model[path][C_TYPE] + if type_ in ('contact', 'group', 'agent'): + jid = model[path][C_JID].decode('utf-8') + account = model[path][C_ACCOUNT].decode('utf-8') + self.on_rename(widget, type_, jid, account) + + elif event.keyval == gtk.keysyms.Delete: + treeselection = self.tree.get_selection() + model, list_of_paths = treeselection.get_selected_rows() + if not len(list_of_paths): + return + type_ = model[list_of_paths[0]][C_TYPE] + account = model[list_of_paths[0]][C_ACCOUNT].decode('utf-8') + if type_ in ('account', 'group', 'self_contact') or \ + account == gajim.ZEROCONF_ACC_NAME: + return + list_ = [] + for path in list_of_paths: + if model[path][C_TYPE] != type_: + return + jid = model[path][C_JID].decode('utf-8') + account = model[path][C_ACCOUNT].decode('utf-8') + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + list_.append((contact, account)) + if type_ == 'contact': + self.on_req_usub(widget, list_) + elif type_ == 'agent': + self.on_remove_agent(widget, list_) + + def on_roster_treeview_button_release_event(self, widget, event): + try: + path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0] + except TypeError: + return False + + if event.button == 1: # Left click + if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \ + not event.state & gtk.gdk.CONTROL_MASK: + # Check if button has been pressed on the same row + if self.clicked_path == path: + self.on_row_activated(widget, path) + self.clicked_path = None + + def on_roster_treeview_button_press_event(self, widget, event): + # hide tooltip, no matter the button is pressed + self.tooltip.hide_tooltip() + try: + pos = self.tree.get_path_at_pos(int(event.x), int(event.y)) + path, x = pos[0], pos[2] + except TypeError: + self.tree.get_selection().unselect_all() + return False + + if event.button == 3: # Right click + try: + model, list_of_paths = self.tree.get_selection().get_selected_rows() + except TypeError: + list_of_paths = [] + if path not in list_of_paths: + self.tree.get_selection().unselect_all() + self.tree.get_selection().select_path(path) + return self.show_treeview_menu(event) + + elif event.button == 2: # Middle click + try: + model, list_of_paths = self.tree.get_selection().get_selected_rows() + except TypeError: + list_of_paths = [] + if list_of_paths != [path]: + self.tree.get_selection().unselect_all() + self.tree.get_selection().select_path(path) + type_ = model[path][C_TYPE] + if type_ in ('agent', 'contact', 'self_contact', 'groupchat'): + self.on_row_activated(widget, path) + elif type_ == 'account': + account = model[path][C_ACCOUNT].decode('utf-8') + if account != 'all': + show = gajim.connections[account].connected + if show > 1: # We are connected + self.on_change_status_message_activate(widget, account) + return True + show = helpers.get_global_show() + if show == 'offline': + return True + def on_response(message, pep_dict): + if message is None: + return True + for acct in gajim.connections: + if not gajim.config.get_per('accounts', acct, + 'sync_with_global_status'): + continue + current_show = gajim.SHOW_LIST[gajim.connections[acct].\ + connected] + self.send_status(acct, current_show, message) + self.send_pep(acct, pep_dict) + dialogs.ChangeStatusMessageDialog(on_response, show) + return True + + elif event.button == 1: # Left click + model = self.modelfilter + type_ = model[path][C_TYPE] + # x_min is the x start position of status icon column + if gajim.config.get('avatar_position_in_roster') == 'left': + x_min = gajim.config.get('roster_avatar_width') + else: + x_min = 0 + if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \ + not event.state & gtk.gdk.CONTROL_MASK: + # Don't handle double click if we press icon of a metacontact + titer = model.get_iter(path) + if x > x_min and x < x_min + 27 and type_ == 'contact' and \ + model.iter_has_child(titer): + if (self.tree.row_expanded(path)): + self.tree.collapse_row(path) + else: + self.tree.expand_row(path, False) + return + # We just save on which row we press button, and open chat window on + # button release to be able to do DND without opening chat window + self.clicked_path = path + return + else: + if type_ == 'group' and x < 27: + # first cell in 1st column (the arrow SINGLE clicked) + if (self.tree.row_expanded(path)): + self.tree.collapse_row(path) + else: + self.tree.expand_row(path, False) + + elif type_ == 'contact' and x > x_min and x < x_min + 27: + if (self.tree.row_expanded(path)): + self.tree.collapse_row(path) + else: + self.tree.expand_row(path, False) + + def on_req_usub(self, widget, list_): + """ + Remove a contact. list_ is a list of (contact, account) tuples + """ + def on_ok(is_checked, list_): + remove_auth = True + if len(list_) == 1: + contact = list_[0][0] + if contact.sub != 'to' and is_checked: + remove_auth = False + for (contact, account) in list_: + if _('Not in Roster') not in contact.get_shown_groups(): + gajim.connections[account].unsubscribe(contact.jid, remove_auth) + self.remove_contact(contact.jid, account, backend=True) + if not remove_auth and contact.sub == 'both': + contact.name = '' + contact.groups = [] + contact.sub = 'from' + # we can't see him, but have to set it manually in contact + contact.show = 'offline' + gajim.contacts.add_contact(account, contact) + self.add_contact(contact.jid, account) + def on_ok2(list_): + on_ok(False, list_) + + if len(list_) == 1: + contact = list_[0][0] + pritext = _('Contact "%s" will be removed from your roster') % \ + contact.get_shown_name() + sectext = _('You are about to remove "%(name)s" (%(jid)s) from your ' + 'roster.\n') % {'name': contact.get_shown_name(), + 'jid': contact.jid} + if contact.sub == 'to': + dialogs.ConfirmationDialog(pritext, sectext + \ + _('By removing this contact you also remove authorization ' + 'resulting in him or her always seeing you as offline.'), + on_response_ok = (on_ok2, list_)) + elif _('Not in Roster') in contact.get_shown_groups(): + # Contact is not in roster + dialogs.ConfirmationDialog(pritext, sectext + \ + _('Do you want to continue?'), on_response_ok = (on_ok2, list_)) + else: + dialogs.ConfirmationDialogCheck(pritext, sectext + \ + _('By removing this contact you also by default remove ' + 'authorization resulting in him or her always seeing you as ' + 'offline.'), + _('I want this contact to know my status after removal'), + on_response_ok = (on_ok, list_)) + else: + # several contact to remove at the same time + pritext = _('Contacts will be removed from your roster') + jids = '' + for (contact, account) in list_: + jids += '\n ' + contact.get_shown_name() + ' (%s)' % contact.jid +\ + ',' + sectext = _('By removing these contacts:%s\nyou also remove ' + 'authorization resulting in them always seeing you as offline.') % \ + jids + dialogs.ConfirmationDialog(pritext, sectext, + on_response_ok = (on_ok2, list_)) + + def on_send_custom_status(self, widget, contact_list, show, group=None): + """ + Send custom status + """ + # contact_list has only one element except if group != None + def on_response(message, pep_dict): + if message is None: # None if user pressed Cancel + return + account_list = [] + for (contact, account) in contact_list: + if account not in account_list: + account_list.append(account) + # 1. update status_sent_to_[groups|users] list + if group: + for account in account_list: + if account not in gajim.interface.status_sent_to_groups: + gajim.interface.status_sent_to_groups[account] = {} + gajim.interface.status_sent_to_groups[account][group] = show + else: + for (contact, account) in contact_list: + if account not in gajim.interface.status_sent_to_users: + gajim.interface.status_sent_to_users[account] = {} + gajim.interface.status_sent_to_users[account][contact.jid] = show + + # 2. update privacy lists if main status is invisible + for account in account_list: + if gajim.SHOW_LIST[gajim.connections[account].connected] == \ + 'invisible': + gajim.connections[account].set_invisible_rule() + + # 3. send directed presence + for (contact, account) in contact_list: + our_jid = gajim.get_jid_from_account(account) + jid = contact.jid + if jid == our_jid: + jid += '/' + contact.resource + self.send_status(account, show, message, to=jid) + + def send_it(is_checked=None): + if is_checked is not None: # dialog has been shown + if is_checked: # user does not want to be asked again + gajim.config.set('confirm_custom_status', 'no') + else: + gajim.config.set('confirm_custom_status', 'yes') + self.get_status_message(show, on_response, show_pep=False, + always_ask=True) + + confirm_custom_status = gajim.config.get('confirm_custom_status') + if confirm_custom_status == 'no': + send_it() + return + pritext = _('You are about to send a custom status. Are you sure you want' + ' to continue?') + sectext = _('This contact will temporarily see you as %(status)s, ' + 'but only until you change your status. Then he or she will see your ' + 'global status.') % {'status': show} + dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, + _('Do _not ask me again'), on_response_ok=send_it) + + def on_status_combobox_changed(self, widget): + """ + When we change our status via the combobox + """ + model = self.status_combobox.get_model() + active = self.status_combobox.get_active() + if active == -1: # no active item + return + if not self.combobox_callback_active: + self.previous_status_combobox_active = active + return + accounts = gajim.connections.keys() + if len(accounts) == 0: + dialogs.ErrorDialog(_('No account available'), + _('You must create an account before you can chat with other contacts.')) + self.update_status_combobox() + return + status = model[active][2].decode('utf-8') + statuses_unified = helpers.statuses_unified() # status "desync'ed" or not + if (active == 7 and statuses_unified) or (active == 9 and \ + not statuses_unified): + # 'Change status message' selected: + # do not change show, just show change status dialog + status = model[self.previous_status_combobox_active][2].decode('utf-8') + def on_response(message, pep_dict): + if message is not None: # None if user pressed Cancel + for account in accounts: + if not gajim.config.get_per('accounts', account, + 'sync_with_global_status'): + continue + current_show = gajim.SHOW_LIST[ + gajim.connections[account].connected] + self.send_status(account, current_show, message) + self.send_pep(account, pep_dict) + self.combobox_callback_active = False + self.status_combobox.set_active( + self.previous_status_combobox_active) + self.combobox_callback_active = True + dialogs.ChangeStatusMessageDialog(on_response, status) + return + # we are about to change show, so save this new show so in case + # after user chooses "Change status message" menuitem + # we can return to this show + self.previous_status_combobox_active = active + connected_accounts = gajim.get_number_of_connected_accounts() + + def on_continue(message, pep_dict): + if message is None: + # user pressed Cancel to change status message dialog + self.update_status_combobox() + return + global_sync_accounts = [] + for acct in accounts: + if gajim.config.get_per('accounts', acct, + 'sync_with_global_status'): + global_sync_accounts.append(acct) + global_sync_connected_accounts = \ + gajim.get_number_of_connected_accounts(global_sync_accounts) + for account in accounts: + if not gajim.config.get_per('accounts', account, + 'sync_with_global_status'): + continue + # we are connected (so we wanna change show and status) + # or no account is connected and we want to connect with new show + # and status + + if not global_sync_connected_accounts > 0 or \ + gajim.connections[account].connected > 0: + self.send_status(account, status, message) + self.send_pep(account, pep_dict) + self.update_status_combobox() + + if status == 'invisible': + bug_user = False + for account in accounts: + if connected_accounts < 1 or gajim.account_is_connected(account): + if not gajim.config.get_per('accounts', account, + 'sync_with_global_status'): + continue + # We're going to change our status to invisible + if self.connected_rooms(account): + bug_user = True + break + if bug_user: + def on_ok(): + self.get_status_message(status, on_continue, show_pep=False) + + def on_cancel(): + self.update_status_combobox() + + dialogs.ConfirmationDialog( + _('You are participating in one or more group chats'), + _('Changing your status to invisible will result in ' + 'disconnection from those group chats. Are you sure you want to ' + 'go invisible?'), on_reponse_ok=on_ok, + on_response_cancel=on_cancel) + return + + self.get_status_message(status, on_continue) + + def on_preferences_menuitem_activate(self, widget): + if 'preferences' in gajim.interface.instances: + gajim.interface.instances['preferences'].window.present() + else: + gajim.interface.instances['preferences'] = config.PreferencesWindow() + + def on_plugins_menuitem_activate(self, widget): + if gajim.interface.instances.has_key('plugins'): + gajim.interface.instances['plugins'].window.present() + else: + gajim.interface.instances['plugins'] = plugins.gui.PluginsWindow() + + def on_publish_tune_toggled(self, widget, account): + active = widget.get_active() + gajim.config.set_per('accounts', account, 'publish_tune', active) + if active: + gajim.interface.enable_music_listener() + else: + gajim.connections[account].retract_tune() + # disable music listener only if no other account uses it + for acc in gajim.connections: + if gajim.config.get_per('accounts', acc, 'publish_tune'): + break + else: + gajim.interface.disable_music_listener() + + helpers.update_optional_features(account) + + def on_publish_location_toggled(self, widget, account): + active = widget.get_active() + gajim.config.set_per('accounts', account, 'publish_location', active) + if active: + location_listener.enable() + else: + gajim.connections[account].retract_location() + # disable music listener only if no other account uses it + for acc in gajim.connections: + if gajim.config.get_per('accounts', acc, 'publish_location'): + break + else: + location_listener.disable() + + helpers.update_optional_features(account) + + def on_pep_services_menuitem_activate(self, widget, account): + if 'pep_services' in gajim.interface.instances[account]: + gajim.interface.instances[account]['pep_services'].window.present() + else: + gajim.interface.instances[account]['pep_services'] = \ + config.ManagePEPServicesWindow(account) + + def on_add_new_contact(self, widget, account): + dialogs.AddNewContactWindow(account) + + def on_join_gc_activate(self, widget, account): + """ + When the join gc menuitem is clicked, show the join gc window + """ + invisible_show = gajim.SHOW_LIST.index('invisible') + if gajim.connections[account].connected == invisible_show: + dialogs.ErrorDialog(_('You cannot join a group chat while you are ' + 'invisible')) + return + if 'join_gc' in gajim.interface.instances[account]: + gajim.interface.instances[account]['join_gc'].window.present() + else: + try: + gajim.interface.instances[account]['join_gc'] = \ + dialogs.JoinGroupchatWindow(account) + except GajimGeneralException: + pass + + def on_new_chat_menuitem_activate(self, widget, account): + dialogs.NewChatDialog(account) + + def on_contents_menuitem_activate(self, widget): + helpers.launch_browser_mailer('url', 'http://trac.gajim.org/wiki') + + def on_faq_menuitem_activate(self, widget): + helpers.launch_browser_mailer('url', + 'http://trac.gajim.org/wiki/GajimFaq') + + def on_features_menuitem_activate(self, widget): + features_window.FeaturesWindow() + + def on_about_menuitem_activate(self, widget): + dialogs.AboutDialog() + + def on_accounts_menuitem_activate(self, widget): + if 'accounts' in gajim.interface.instances: + gajim.interface.instances['accounts'].window.present() + else: + gajim.interface.instances['accounts'] = config.AccountsWindow() + + def on_file_transfers_menuitem_activate(self, widget): + if gajim.interface.instances['file_transfers'].window.get_property( + 'visible'): + gajim.interface.instances['file_transfers'].window.present() + else: + gajim.interface.instances['file_transfers'].window.show_all() + + def on_history_menuitem_activate(self, widget): + if 'logs' in gajim.interface.instances: + gajim.interface.instances['logs'].window.present() + else: + gajim.interface.instances['logs'] = history_window.\ + HistoryWindow() + + def on_show_transports_menuitem_activate(self, widget): + gajim.config.set('show_transports_group', widget.get_active()) + self.refilter_shown_roster_items() + + def on_manage_bookmarks_menuitem_activate(self, widget): + config.ManageBookmarksWindow() + + def on_profile_avatar_menuitem_activate(self, widget, account): + gajim.interface.edit_own_details(account) + + def on_execute_command(self, widget, contact, account, resource=None): + """ + Execute command. Full JID needed; if it is other contact, resource is + necessary. Widget is unnecessary, only to be able to make this a callback + """ + jid = contact.jid + if resource is not None: + jid = jid + u'/' + resource + adhoc_commands.CommandWindow(account, jid) + + def on_roster_window_focus_in_event(self, widget, event): + # roster received focus, so if we had urgency REMOVE IT + # NOTE: we do not have to read the message to remove urgency + # so this functions does that + gtkgui_helpers.set_unset_urgency_hint(widget, False) + + # if a contact row is selected, update colors (eg. for status msg) + # because gtk engines may differ in bg when window is selected + # or not + if len(self._last_selected_contact): + for (jid, account) in self._last_selected_contact: + self.draw_contact(jid, account, selected=True, focus=True) + + def on_roster_window_focus_out_event(self, widget, event): + # if a contact row is selected, update colors (eg. for status msg) + # because gtk engines may differ in bg when window is selected + # or not + if len(self._last_selected_contact): + for (jid, account) in self._last_selected_contact: + self.draw_contact(jid, account, selected=True, focus=False) + + def on_roster_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: + if gajim.interface.msg_win_mgr.mode == \ + MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER and \ + gajim.interface.msg_win_mgr.one_window_opened(): + # let message window close the tab + return + list_of_paths = self.tree.get_selection().get_selected_rows()[1] + if not len(list_of_paths) and gajim.interface.systray_enabled and \ + not gajim.config.get('quit_on_roster_x_button'): + self.tooltip.hide_tooltip() + self.window.hide() + elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == gtk.keysyms.i: + treeselection = self.tree.get_selection() + model, list_of_paths = treeselection.get_selected_rows() + for path in list_of_paths: + type_ = model[path][C_TYPE] + if type_ in ('contact', 'agent'): + jid = model[path][C_JID].decode('utf-8') + account = model[path][C_ACCOUNT].decode('utf-8') + contact = gajim.contacts.get_first_contact_from_jid(account, jid) + self.on_info(widget, contact, account) + elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == gtk.keysyms.h: + treeselection = self.tree.get_selection() + model, list_of_paths = treeselection.get_selected_rows() + if len(list_of_paths) != 1: + return + path = list_of_paths[0] + type_ = model[path][C_TYPE] + if type_ in ('contact', 'agent'): + jid = model[path][C_JID].decode('utf-8') + account = model[path][C_ACCOUNT].decode('utf-8') + contact = gajim.contacts.get_first_contact_from_jid(account, jid) + self.on_history(widget, contact, account) + + def on_roster_window_popup_menu(self, widget): + event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) + self.show_treeview_menu(event) + + def on_row_activated(self, widget, path): + """ + When an iter is activated (double-click or single click if gnome is set + this way) + """ + model = self.modelfilter + account = model[path][C_ACCOUNT].decode('utf-8') + type_ = model[path][C_TYPE] + if type_ in ('group', 'account'): + if self.tree.row_expanded(path): + self.tree.collapse_row(path) + else: + self.tree.expand_row(path, False) + return + jid = model[path][C_JID].decode('utf-8') + resource = None + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + titer = model.get_iter(path) + if contact.is_groupchat(): + first_ev = gajim.events.get_first_event(account, jid) + if first_ev and self.open_event(account, jid, first_ev): + # We are invited to a GC + # open event cares about connecting to it + self.remove_groupchat(jid, account) + else: + self.on_groupchat_maximized(None, jid, account) + return + + # else + first_ev = gajim.events.get_first_event(account, jid) + if not first_ev: + # look in other resources + for c in gajim.contacts.get_contacts(account, jid): + fjid = c.get_full_jid() + first_ev = gajim.events.get_first_event(account, fjid) + if first_ev: + resource = c.resource + break + if not first_ev and model.iter_has_child(titer): + child_iter = model.iter_children(titer) + while not first_ev and child_iter: + child_jid = model[child_iter][C_JID].decode('utf-8') + first_ev = gajim.events.get_first_event(account, child_jid) + if first_ev: + jid = child_jid + else: + child_iter = model.iter_next(child_iter) + session = None + if first_ev: + if first_ev.type_ in ('chat', 'normal'): + session = first_ev.parameters[8] + fjid = jid + if resource: + fjid += '/' + resource + if self.open_event(account, fjid, first_ev): + return + # else + contact = gajim.contacts.get_contact(account, jid, resource) + if not contact or isinstance(contact, list): + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + if jid == gajim.get_jid_from_account(account): + resource = contact.resource + + gajim.interface.on_open_chat_window(None, contact, account, \ + resource=resource, session=session) + + def on_roster_treeview_row_activated(self, widget, path, col=0): + """ + When an iter is double clicked: open the first event window + """ + if not gajim.single_click: + self.on_row_activated(widget, path) + + def on_roster_treeview_row_expanded(self, widget, titer, path): + """ + When a row is expanded change the icon of the arrow + """ + self._toggeling_row = True + model = widget.get_model() + child_model = model.get_model() + child_iter = model.convert_iter_to_child_iter(titer) + + if self.regroup: # merged accounts + accounts = gajim.connections.keys() + else: + accounts = [model[titer][C_ACCOUNT].decode('utf-8')] + + type_ = model[titer][C_TYPE] + if type_ == 'group': + group = model[titer][C_JID].decode('utf-8') + child_model[child_iter][C_IMG] = gajim.interface.jabber_state_images[ + '16']['opened'] + for account in accounts: + if group in gajim.groups[account]: # This account has this group + gajim.groups[account][group]['expand'] = True + if account + group in self.collapsed_rows: + self.collapsed_rows.remove(account + group) + for contact in gajim.contacts.iter_contacts(account): + jid = contact.jid + if group in contact.groups and gajim.contacts.is_big_brother( + account, jid, accounts) and account + group + jid \ + not in self.collapsed_rows: + titers = self._get_contact_iter(jid, account) + for titer in titers: + path = model.get_path(titer) + self.tree.expand_row(path, False) + elif type_ == 'account': + account = accounts[0] # There is only one cause we don't use merge + if account in self.collapsed_rows: + self.collapsed_rows.remove(account) + self.draw_account(account) + # When we expand, groups are collapsed. Restore expand state + for group in gajim.groups[account]: + if gajim.groups[account][group]['expand']: + titer = self._get_group_iter(group, account) + if titer: + path = model.get_path(titer) + self.tree.expand_row(path, False) + elif type_ == 'contact': + # Metacontact got toggled, update icon + jid = model[titer][C_JID].decode('utf-8') + account = model[titer][C_ACCOUNT].decode('utf-8') + contact = gajim.contacts.get_contact(account, jid) + for group in contact.groups: + if account + group + jid in self.collapsed_rows: + self.collapsed_rows.remove(account + group + jid) + family = gajim.contacts.get_metacontacts_family(account, jid) + nearby_family = \ + self._get_nearby_family_and_big_brother(family, account)[0] + # Redraw all brothers to show pending events + for data in nearby_family: + self.draw_contact(data['jid'], data['account']) + + self._toggeling_row = False + + def on_roster_treeview_row_collapsed(self, widget, titer, path): + """ + When a row is collapsed change the icon of the arrow + """ + self._toggeling_row = True + model = widget.get_model() + child_model = model.get_model() + child_iter = model.convert_iter_to_child_iter(titer) + + if self.regroup: # merged accounts + accounts = gajim.connections.keys() + else: + accounts = [model[titer][C_ACCOUNT].decode('utf-8')] + + type_ = model[titer][C_TYPE] + if type_ == 'group': + child_model[child_iter][C_IMG] = gajim.interface.jabber_state_images[ + '16']['closed'] + group = model[titer][C_JID].decode('utf-8') + for account in accounts: + if group in gajim.groups[account]: # This account has this group + gajim.groups[account][group]['expand'] = False + if account + group not in self.collapsed_rows: + self.collapsed_rows.append(account + group) + elif type_ == 'account': + account = accounts[0] # There is only one cause we don't use merge + if account not in self.collapsed_rows: + self.collapsed_rows.append(account) + self.draw_account(account) + elif type_ == 'contact': + # Metacontact got toggled, update icon + jid = model[titer][C_JID].decode('utf-8') + account = model[titer][C_ACCOUNT].decode('utf-8') + contact = gajim.contacts.get_contact(account, jid) + for group in contact.groups: + if account + group + jid not in self.collapsed_rows: + self.collapsed_rows.append(account + group + jid) + family = gajim.contacts.get_metacontacts_family(account, jid) + nearby_family = \ + self._get_nearby_family_and_big_brother(family, account)[0] + # Redraw all brothers to show pending events + for data in nearby_family: + self.draw_contact(data['jid'], data['account']) + + self._toggeling_row = False + + def on_modelfilter_row_has_child_toggled(self, model, path, titer): + """ + Called when a row has gotten the first or lost its last child row + + Expand Parent if necessary. + """ + if self._toggeling_row: + # Signal is emitted when we write to our model + return + + type_ = model[titer][C_TYPE] + account = model[titer][C_ACCOUNT] + if not account: + return + + account = account.decode('utf-8') + + if type_ == 'contact': + child_iter = model.convert_iter_to_child_iter(titer) + if self.model.iter_has_child(child_iter): + # we are a bigbrother metacontact + # redraw us to show/hide expand icon + if self.filtering: + # Prevent endless loops + jid = model[titer][C_JID].decode('utf-8') + gobject.idle_add(self.draw_contact, jid, account) + elif type_ == 'group': + group = model[titer][C_JID].decode('utf-8') + self._adjust_group_expand_collapse_state(group, account) + elif type_ == 'account': + self._adjust_account_expand_collapse_state(account) # Selection can change when the model is filtered # Only write to the model when filtering is finished! # # FIXME: When we are filtering our custom colors are somehow lost # -# def on_treeview_selection_changed(self, selection): -# '''Called when selection in TreeView has changed. +# def on_treeview_selection_changed(self, selection): +# '''Called when selection in TreeView has changed. # -# Redraw unselected rows to make status message readable -# on all possible backgrounds. -# ''' -# model, list_of_paths = selection.get_selected_rows() -# if len(self._last_selected_contact): -# # update unselected rows -# for (jid, account) in self._last_selected_contact: -# gobject.idle_add(self.draw_contact, jid, account) -# self._last_selected_contact = [] -# if len(list_of_paths) == 0: -# return -# for path in list_of_paths: -# row = model[path] -# if row[C_TYPE] != 'contact': -# self._last_selected_contact = [] -# return -# jid = row[C_JID].decode('utf-8') -# account = row[C_ACCOUNT].decode('utf-8') -# self._last_selected_contact.append((jid, account)) -# gobject.idle_add(self.draw_contact, jid, account, True) +# Redraw unselected rows to make status message readable +# on all possible backgrounds. +# ''' +# model, list_of_paths = selection.get_selected_rows() +# if len(self._last_selected_contact): +# # update unselected rows +# for (jid, account) in self._last_selected_contact: +# gobject.idle_add(self.draw_contact, jid, account) +# self._last_selected_contact = [] +# if len(list_of_paths) == 0: +# return +# for path in list_of_paths: +# row = model[path] +# if row[C_TYPE] != 'contact': +# self._last_selected_contact = [] +# return +# jid = row[C_JID].decode('utf-8') +# account = row[C_ACCOUNT].decode('utf-8') +# self._last_selected_contact.append((jid, account)) +# gobject.idle_add(self.draw_contact, jid, account, True) - def on_service_disco_menuitem_activate(self, widget, account): - server_jid = gajim.config.get_per('accounts', account, 'hostname') - if server_jid in gajim.interface.instances[account]['disco']: - gajim.interface.instances[account]['disco'][server_jid].\ - window.present() - else: - try: - # Object will add itself to the window dict - disco.ServiceDiscoveryWindow(account, address_entry=True) - except GajimGeneralException: - pass + def on_service_disco_menuitem_activate(self, widget, account): + server_jid = gajim.config.get_per('accounts', account, 'hostname') + if server_jid in gajim.interface.instances[account]['disco']: + gajim.interface.instances[account]['disco'][server_jid].\ + window.present() + else: + try: + # Object will add itself to the window dict + disco.ServiceDiscoveryWindow(account, address_entry=True) + except GajimGeneralException: + pass - def on_show_offline_contacts_menuitem_activate(self, widget): - """ - When show offline option is changed: redraw the treeview - """ - gajim.config.set('showoffline', not gajim.config.get('showoffline')) - self.refilter_shown_roster_items() - w = self.xml.get_object('show_only_active_contacts_menuitem') - if gajim.config.get('showoffline'): - # We need to filter twice to show groups with no contacts inside - # in the correct expand state - self.refilter_shown_roster_items() - w.set_sensitive(False) - else: - w.set_sensitive(True) + def on_show_offline_contacts_menuitem_activate(self, widget): + """ + When show offline option is changed: redraw the treeview + """ + gajim.config.set('showoffline', not gajim.config.get('showoffline')) + self.refilter_shown_roster_items() + w = self.xml.get_object('show_only_active_contacts_menuitem') + if gajim.config.get('showoffline'): + # We need to filter twice to show groups with no contacts inside + # in the correct expand state + self.refilter_shown_roster_items() + w.set_sensitive(False) + else: + w.set_sensitive(True) - def on_show_only_active_contacts_menuitem_activate(self, widget): - """ - When show only active contact option is changed: redraw the treeview - """ - gajim.config.set('show_only_chat_and_online', not gajim.config.get( - 'show_only_chat_and_online')) - self.refilter_shown_roster_items() - w = self.xml.get_object('show_offline_contacts_menuitem') - if gajim.config.get('show_only_chat_and_online'): - # We need to filter twice to show groups with no contacts inside - # in the correct expand state - self.refilter_shown_roster_items() - w.set_sensitive(False) - else: - w.set_sensitive(True) + def on_show_only_active_contacts_menuitem_activate(self, widget): + """ + When show only active contact option is changed: redraw the treeview + """ + gajim.config.set('show_only_chat_and_online', not gajim.config.get( + 'show_only_chat_and_online')) + self.refilter_shown_roster_items() + w = self.xml.get_object('show_offline_contacts_menuitem') + if gajim.config.get('show_only_chat_and_online'): + # We need to filter twice to show groups with no contacts inside + # in the correct expand state + self.refilter_shown_roster_items() + w.set_sensitive(False) + else: + w.set_sensitive(True) - def on_view_menu_activate(self, widget): - # Hide the show roster menu if we are not in the right windowing mode. - if self.hpaned.get_child2() is not None: - self.xml.get_object('show_roster_menuitem').show() - else: - self.xml.get_object('show_roster_menuitem').hide() + def on_view_menu_activate(self, widget): + # Hide the show roster menu if we are not in the right windowing mode. + if self.hpaned.get_child2() is not None: + self.xml.get_object('show_roster_menuitem').show() + else: + self.xml.get_object('show_roster_menuitem').hide() - def on_show_roster_menuitem_toggled(self, widget): - # when num controls is 0 this menuitem is hidden, but still need to - # disable keybinding - if self.hpaned.get_child2() is not None: - self.show_roster_vbox(widget.get_active()) + def on_show_roster_menuitem_toggled(self, widget): + # when num controls is 0 this menuitem is hidden, but still need to + # disable keybinding + if self.hpaned.get_child2() is not None: + self.show_roster_vbox(widget.get_active()) ################################################################################ ### Drag and Drop handling ################################################################################ - def drag_data_get_data(self, treeview, context, selection, target_id, etime): - model, list_of_paths = self.tree.get_selection().get_selected_rows() - if len(list_of_paths) != 1: - return - path = list_of_paths[0] - data = '' - if len(path) >= 3: - data = model[path][C_JID] - selection.set(selection.target, 8, data) + def drag_data_get_data(self, treeview, context, selection, target_id, etime): + model, list_of_paths = self.tree.get_selection().get_selected_rows() + if len(list_of_paths) != 1: + return + path = list_of_paths[0] + data = '' + if len(path) >= 3: + data = model[path][C_JID] + selection.set(selection.target, 8, data) - def drag_begin(self, treeview, context): - self.dragging = True + def drag_begin(self, treeview, context): + self.dragging = True - def drag_end(self, treeview, context): - self.dragging = False + def drag_end(self, treeview, context): + self.dragging = False - def on_drop_rosterx(self, widget, account_source, c_source, account_dest, - c_dest, was_big_brother, context, etime): - gajim.connections[account_dest].send_contacts([c_source], c_dest.jid) + def on_drop_rosterx(self, widget, account_source, c_source, account_dest, + c_dest, was_big_brother, context, etime): + gajim.connections[account_dest].send_contacts([c_source], c_dest.jid) - def on_drop_in_contact(self, widget, account_source, c_source, account_dest, - c_dest, was_big_brother, context, etime): + def on_drop_in_contact(self, widget, account_source, c_source, account_dest, + c_dest, was_big_brother, context, etime): - if not gajim.connections[account_source].private_storage_supported or not\ - gajim.connections[account_dest].private_storage_supported: - dialogs.WarningDialog(_('Metacontacts storage not supported by your ' - 'server'), - _('Your server does not support storing metacontacts information. ' - 'So those information will not be saved on next reconnection.')) + if not gajim.connections[account_source].private_storage_supported or not\ + gajim.connections[account_dest].private_storage_supported: + dialogs.WarningDialog(_('Metacontacts storage not supported by your ' + 'server'), + _('Your server does not support storing metacontacts information. ' + 'So those information will not be saved on next reconnection.')) - def merge_contacts(is_checked=None): - contacts = 0 - if is_checked is not None: # dialog has been shown - if is_checked: # user does not want to be asked again - gajim.config.set('confirm_metacontacts', 'no') - else: - gajim.config.set('confirm_metacontacts', 'yes') + def merge_contacts(is_checked=None): + contacts = 0 + if is_checked is not None: # dialog has been shown + if is_checked: # user does not want to be asked again + gajim.config.set('confirm_metacontacts', 'no') + else: + gajim.config.set('confirm_metacontacts', 'yes') - # We might have dropped on a metacontact. - # Remove it and readd later with updated family info - dest_family = gajim.contacts.get_metacontacts_family(account_dest, - c_dest.jid) - if dest_family: - self._remove_metacontact_family(dest_family, account_dest) - source_family = gajim.contacts.get_metacontacts_family(account_source, c_source.jid) - if dest_family == source_family: - n = contacts = len(dest_family) - for tag in source_family: - if tag['jid'] == c_source.jid: - tag['order'] = contacts - continue - if 'order' in tag: - n -= 1 - tag['order'] = n - else: - self._remove_entity(c_dest, account_dest) + # We might have dropped on a metacontact. + # Remove it and readd later with updated family info + dest_family = gajim.contacts.get_metacontacts_family(account_dest, + c_dest.jid) + if dest_family: + self._remove_metacontact_family(dest_family, account_dest) + source_family = gajim.contacts.get_metacontacts_family(account_source, c_source.jid) + if dest_family == source_family: + n = contacts = len(dest_family) + for tag in source_family: + if tag['jid'] == c_source.jid: + tag['order'] = contacts + continue + if 'order' in tag: + n -= 1 + tag['order'] = n + else: + self._remove_entity(c_dest, account_dest) - old_family = gajim.contacts.get_metacontacts_family(account_source, - c_source.jid) - old_groups = c_source.groups + old_family = gajim.contacts.get_metacontacts_family(account_source, + c_source.jid) + old_groups = c_source.groups - # Remove old source contact(s) - if was_big_brother: - # We have got little brothers. Readd them all - self._remove_metacontact_family(old_family, account_source) - else: - # We are only a litle brother. Simply remove us from our big brother - if self._get_contact_iter(c_source.jid, account_source): - # When we have been in the group before. - # Do not try to remove us again - self._remove_entity(c_source, account_source) + # Remove old source contact(s) + if was_big_brother: + # We have got little brothers. Readd them all + self._remove_metacontact_family(old_family, account_source) + else: + # We are only a litle brother. Simply remove us from our big brother + if self._get_contact_iter(c_source.jid, account_source): + # When we have been in the group before. + # Do not try to remove us again + self._remove_entity(c_source, account_source) - own_data = {} - own_data['jid'] = c_source.jid - own_data['account'] = account_source - # Don't touch the rest of the family - old_family = [own_data] + own_data = {} + own_data['jid'] = c_source.jid + own_data['account'] = account_source + # Don't touch the rest of the family + old_family = [own_data] - # Apply new tag and update contact - for data in old_family: - if account_source != data['account'] and not self.regroup: - continue + # Apply new tag and update contact + for data in old_family: + if account_source != data['account'] and not self.regroup: + continue - _account = data['account'] - _jid = data['jid'] - _contact = gajim.contacts.get_first_contact_from_jid(_account, _jid) - if not _contact: - # One of the metacontacts may be not connected. - continue + _account = data['account'] + _jid = data['jid'] + _contact = gajim.contacts.get_first_contact_from_jid(_account, _jid) + if not _contact: + # One of the metacontacts may be not connected. + continue - _contact.groups = c_dest.groups[:] - gajim.contacts.add_metacontact(account_dest, c_dest.jid, - _account, _contact.jid, contacts) - gajim.connections[account_source].update_contact(_contact.jid, - _contact.name, _contact.groups) + _contact.groups = c_dest.groups[:] + gajim.contacts.add_metacontact(account_dest, c_dest.jid, + _account, _contact.jid, contacts) + gajim.connections[account_source].update_contact(_contact.jid, + _contact.name, _contact.groups) - # Re-add all and update GUI - new_family = gajim.contacts.get_metacontacts_family(account_source, - c_source.jid) - brothers = self._add_metacontact_family(new_family, account_source) + # Re-add all and update GUI + new_family = gajim.contacts.get_metacontacts_family(account_source, + c_source.jid) + brothers = self._add_metacontact_family(new_family, account_source) - for c, acc in brothers: - self.draw_completely(c.jid, acc) + for c, acc in brothers: + self.draw_completely(c.jid, acc) - old_groups.extend(c_dest.groups) - for g in old_groups: - self.draw_group(g, account_source) + old_groups.extend(c_dest.groups) + for g in old_groups: + self.draw_group(g, account_source) - self.draw_account(account_source) - context.finish(True, True, etime) + self.draw_account(account_source) + context.finish(True, True, etime) - confirm_metacontacts = gajim.config.get('confirm_metacontacts') - if confirm_metacontacts == 'no': - merge_contacts() - return - pritext = _('You are about to create a metacontact. Are you sure you want' - ' to continue?') - sectext = _('Metacontacts are a way to regroup several contacts in one ' - 'line. Generally it is used when the same person has several Jabber ' - 'accounts or transport accounts.') - dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, - _('Do _not ask me again'), on_response_ok=merge_contacts) - if not confirm_metacontacts: # First time we see this window - dlg.checkbutton.set_active(True) + confirm_metacontacts = gajim.config.get('confirm_metacontacts') + if confirm_metacontacts == 'no': + merge_contacts() + return + pritext = _('You are about to create a metacontact. Are you sure you want' + ' to continue?') + sectext = _('Metacontacts are a way to regroup several contacts in one ' + 'line. Generally it is used when the same person has several Jabber ' + 'accounts or transport accounts.') + dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, + _('Do _not ask me again'), on_response_ok=merge_contacts) + if not confirm_metacontacts: # First time we see this window + dlg.checkbutton.set_active(True) - def on_drop_in_group(self, widget, account, c_source, grp_dest, - is_big_brother, context, etime, grp_source = None): - if is_big_brother: - # add whole metacontact to new group - self.add_contact_to_groups(c_source.jid, account, [grp_dest,]) - # remove afterwards so the contact is not moved to General in the - # meantime - if grp_dest != grp_source: - self.remove_contact_from_groups(c_source.jid, account, [grp_source]) - else: - # Normal contact or little brother - family = gajim.contacts.get_metacontacts_family(account, - c_source.jid) - if family: - # Little brother - # Remove whole family. Remove us from the family. - # Then re-add other family members. - self._remove_metacontact_family(family, account) - gajim.contacts.remove_metacontact(account, c_source.jid) - for data in family: - if account != data['account'] and not self.regroup: - continue - if data['jid'] == c_source.jid and\ - data['account'] == account: - continue - self.add_contact(data['jid'], data['account']) - break + def on_drop_in_group(self, widget, account, c_source, grp_dest, + is_big_brother, context, etime, grp_source = None): + if is_big_brother: + # add whole metacontact to new group + self.add_contact_to_groups(c_source.jid, account, [grp_dest,]) + # remove afterwards so the contact is not moved to General in the + # meantime + if grp_dest != grp_source: + self.remove_contact_from_groups(c_source.jid, account, [grp_source]) + else: + # Normal contact or little brother + family = gajim.contacts.get_metacontacts_family(account, + c_source.jid) + if family: + # Little brother + # Remove whole family. Remove us from the family. + # Then re-add other family members. + self._remove_metacontact_family(family, account) + gajim.contacts.remove_metacontact(account, c_source.jid) + for data in family: + if account != data['account'] and not self.regroup: + continue + if data['jid'] == c_source.jid and\ + data['account'] == account: + continue + self.add_contact(data['jid'], data['account']) + break - self.add_contact_to_groups(c_source.jid, account, [grp_dest,]) + self.add_contact_to_groups(c_source.jid, account, [grp_dest,]) - else: - # Normal contact - self.add_contact_to_groups(c_source.jid, account, [grp_dest,]) - # remove afterwards so the contact is not moved to General in the - # meantime - if grp_dest != grp_source: - self.remove_contact_from_groups(c_source.jid, account, - [grp_source]) + else: + # Normal contact + self.add_contact_to_groups(c_source.jid, account, [grp_dest,]) + # remove afterwards so the contact is not moved to General in the + # meantime + if grp_dest != grp_source: + self.remove_contact_from_groups(c_source.jid, account, + [grp_source]) - if context.action in (gtk.gdk.ACTION_MOVE, gtk.gdk.ACTION_COPY): - context.finish(True, True, etime) + if context.action in (gtk.gdk.ACTION_MOVE, gtk.gdk.ACTION_COPY): + context.finish(True, True, etime) - def drag_drop(self, treeview, context, x, y, timestamp): - target_list = treeview.drag_dest_get_target_list() - target = treeview.drag_dest_find_target(context, target_list) - treeview.drag_get_data(context, target) - context.finish(False, True) - return True + def drag_drop(self, treeview, context, x, y, timestamp): + target_list = treeview.drag_dest_get_target_list() + target = treeview.drag_dest_find_target(context, target_list) + treeview.drag_get_data(context, target) + context.finish(False, True) + return True - def drag_data_received_data(self, treeview, context, x, y, selection, info, - etime): - treeview.stop_emission('drag_data_received') - drop_info = treeview.get_dest_row_at_pos(x, y) - if not drop_info: - return - if not selection.data: - return # prevents tb when several entrys are dragged - model = treeview.get_model() - data = selection.data - path_dest, position = drop_info + def drag_data_received_data(self, treeview, context, x, y, selection, info, + etime): + treeview.stop_emission('drag_data_received') + drop_info = treeview.get_dest_row_at_pos(x, y) + if not drop_info: + return + if not selection.data: + return # prevents tb when several entrys are dragged + model = treeview.get_model() + data = selection.data + path_dest, position = drop_info - if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2 \ - and path_dest[1] == 0: # dropped before the first group - return - if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2: - # dropped before a group: we drop it in the previous group every time - path_dest = (path_dest[0], path_dest[1]-1) - # destination: the row something got dropped on - iter_dest = model.get_iter(path_dest) - type_dest = model[iter_dest][C_TYPE].decode('utf-8') - jid_dest = model[iter_dest][C_JID].decode('utf-8') - account_dest = model[iter_dest][C_ACCOUNT].decode('utf-8') + if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2 \ + and path_dest[1] == 0: # dropped before the first group + return + if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2: + # dropped before a group: we drop it in the previous group every time + path_dest = (path_dest[0], path_dest[1]-1) + # destination: the row something got dropped on + iter_dest = model.get_iter(path_dest) + type_dest = model[iter_dest][C_TYPE].decode('utf-8') + jid_dest = model[iter_dest][C_JID].decode('utf-8') + account_dest = model[iter_dest][C_ACCOUNT].decode('utf-8') - # drop on account row in merged mode, we cannot know the desired account - if account_dest == 'all': - return - # nothing can be done, if destination account is offline - if gajim.connections[account_dest].connected < 2: - return + # drop on account row in merged mode, we cannot know the desired account + if account_dest == 'all': + return + # nothing can be done, if destination account is offline + if gajim.connections[account_dest].connected < 2: + return - # A file got dropped on the roster - if info == self.TARGET_TYPE_URI_LIST: - if len(path_dest) < 3: - return - if type_dest != 'contact': - return - c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest, - jid_dest) - if not c_dest.supports(NS_FILE): - return - uri = data.strip() - uri_splitted = uri.split() # we may have more than one file dropped - try: - # This is always the last element in windows - uri_splitted.remove('\0') - except ValueError: - pass - nb_uri = len(uri_splitted) - # Check the URIs - bad_uris = [] - for a_uri in uri_splitted: - path = helpers.get_file_path_from_dnd_dropped_uri(a_uri) - if not os.path.isfile(path): - bad_uris.append(a_uri) - if len(bad_uris): - dialogs.ErrorDialog(_('Invalid file URI:'), '\n'.join(bad_uris)) - return - def _on_send_files(account, jid, uris): - c = gajim.contacts.get_contact_with_highest_priority(account, jid) - for uri in uris: - path = helpers.get_file_path_from_dnd_dropped_uri(uri) - if os.path.isfile(path): # is it file? - gajim.interface.instances['file_transfers'].send_file( - account, c, path) - # Popup dialog to confirm sending - prim_text = 'Send file?' - sec_text = i18n.ngettext('Do you want to send this file to %s:', - 'Do you want to send these files to %s:', nb_uri) %\ - c_dest.get_shown_name() - for uri in uri_splitted: - path = helpers.get_file_path_from_dnd_dropped_uri(uri) - sec_text += '\n' + os.path.basename(path) - dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text, - on_response_ok = (_on_send_files, account_dest, jid_dest, - uri_splitted)) - dialog.popup() - return + # A file got dropped on the roster + if info == self.TARGET_TYPE_URI_LIST: + if len(path_dest) < 3: + return + if type_dest != 'contact': + return + c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest, + jid_dest) + if not c_dest.supports(NS_FILE): + return + uri = data.strip() + uri_splitted = uri.split() # we may have more than one file dropped + try: + # This is always the last element in windows + uri_splitted.remove('\0') + except ValueError: + pass + nb_uri = len(uri_splitted) + # Check the URIs + bad_uris = [] + for a_uri in uri_splitted: + path = helpers.get_file_path_from_dnd_dropped_uri(a_uri) + if not os.path.isfile(path): + bad_uris.append(a_uri) + if len(bad_uris): + dialogs.ErrorDialog(_('Invalid file URI:'), '\n'.join(bad_uris)) + return + def _on_send_files(account, jid, uris): + c = gajim.contacts.get_contact_with_highest_priority(account, jid) + for uri in uris: + path = helpers.get_file_path_from_dnd_dropped_uri(uri) + if os.path.isfile(path): # is it file? + gajim.interface.instances['file_transfers'].send_file( + account, c, path) + # Popup dialog to confirm sending + prim_text = 'Send file?' + sec_text = i18n.ngettext('Do you want to send this file to %s:', + 'Do you want to send these files to %s:', nb_uri) %\ + c_dest.get_shown_name() + for uri in uri_splitted: + path = helpers.get_file_path_from_dnd_dropped_uri(uri) + sec_text += '\n' + os.path.basename(path) + dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text, + on_response_ok = (_on_send_files, account_dest, jid_dest, + uri_splitted)) + dialog.popup() + return - # a roster entry was dragged and dropped somewhere in the roster + # a roster entry was dragged and dropped somewhere in the roster - # source: the row that was dragged - path_source = treeview.get_selection().get_selected_rows()[1][0] - iter_source = model.get_iter(path_source) - type_source = model[iter_source][C_TYPE] - account_source = model[iter_source][C_ACCOUNT].decode('utf-8') + # source: the row that was dragged + path_source = treeview.get_selection().get_selected_rows()[1][0] + iter_source = model.get_iter(path_source) + type_source = model[iter_source][C_TYPE] + account_source = model[iter_source][C_ACCOUNT].decode('utf-8') - # Only normal contacts can be dragged - if type_source != 'contact': - return - if gajim.config.get_per('accounts', account_source, 'is_zeroconf'): - return + # Only normal contacts can be dragged + if type_source != 'contact': + return + if gajim.config.get_per('accounts', account_source, 'is_zeroconf'): + return - # A contact was dropped - if gajim.config.get_per('accounts', account_dest, 'is_zeroconf'): - # drop on zeroconf account, adding not possible - return - if type_dest == 'self_contact': - # drop on self contact row - return - if type_dest == 'account' and account_source == account_dest: - # drop on the account it was dragged from - return - if type_dest == 'groupchat': - # drop on a minimized groupchat - # TODO: Invite to groupchat - return + # A contact was dropped + if gajim.config.get_per('accounts', account_dest, 'is_zeroconf'): + # drop on zeroconf account, adding not possible + return + if type_dest == 'self_contact': + # drop on self contact row + return + if type_dest == 'account' and account_source == account_dest: + # drop on the account it was dragged from + return + if type_dest == 'groupchat': + # drop on a minimized groupchat + # TODO: Invite to groupchat + return - # Get valid source group, jid and contact - it = iter_source - while model[it][C_TYPE] == 'contact': - it = model.iter_parent(it) - grp_source = model[it][C_JID].decode('utf-8') - if grp_source in helpers.special_groups and \ - grp_source not in ('Not in Roster', 'Observers'): - # a transport or a minimized groupchat was dragged - # we can add it to other accounts but not move it to another group, - # see below - return - jid_source = data.decode('utf-8') - c_source = gajim.contacts.get_contact_with_highest_priority( - account_source, jid_source) + # Get valid source group, jid and contact + it = iter_source + while model[it][C_TYPE] == 'contact': + it = model.iter_parent(it) + grp_source = model[it][C_JID].decode('utf-8') + if grp_source in helpers.special_groups and \ + grp_source not in ('Not in Roster', 'Observers'): + # a transport or a minimized groupchat was dragged + # we can add it to other accounts but not move it to another group, + # see below + return + jid_source = data.decode('utf-8') + c_source = gajim.contacts.get_contact_with_highest_priority( + account_source, jid_source) - # Get destination group - grp_dest = None - if type_dest == 'group': - grp_dest = model[iter_dest][C_JID].decode('utf-8') - elif type_dest in ('contact', 'agent'): - it = iter_dest - while model[it][C_TYPE] != 'group': - it = model.iter_parent(it) - grp_dest = model[it][C_JID].decode('utf-8') - if grp_dest in helpers.special_groups: - return + # Get destination group + grp_dest = None + if type_dest == 'group': + grp_dest = model[iter_dest][C_JID].decode('utf-8') + elif type_dest in ('contact', 'agent'): + it = iter_dest + while model[it][C_TYPE] != 'group': + it = model.iter_parent(it) + grp_dest = model[it][C_JID].decode('utf-8') + if grp_dest in helpers.special_groups: + return - if jid_source == jid_dest: - if grp_source == grp_dest and account_source == account_dest: - # Drop on self - return + if jid_source == jid_dest: + if grp_source == grp_dest and account_source == account_dest: + # Drop on self + return - # contact drop somewhere in or on a foreign account - if (type_dest == 'account' or not self.regroup) and \ - account_source != account_dest: - # add to account in specified group - dialogs.AddNewContactWindow(account=account_dest, jid=jid_source, - user_nick=c_source.name, group=grp_dest) - return + # contact drop somewhere in or on a foreign account + if (type_dest == 'account' or not self.regroup) and \ + account_source != account_dest: + # add to account in specified group + dialogs.AddNewContactWindow(account=account_dest, jid=jid_source, + user_nick=c_source.name, group=grp_dest) + return - # we may not add contacts from special_groups - if grp_source in helpers.special_groups : - return + # we may not add contacts from special_groups + if grp_source in helpers.special_groups : + return - # Is the contact we drag a meta contact? - accounts = (self.regroup and gajim.contacts.get_accounts()) or \ - account_source - is_big_brother = gajim.contacts.is_big_brother(account_source, jid_source, - accounts) + # Is the contact we drag a meta contact? + accounts = (self.regroup and gajim.contacts.get_accounts()) or \ + account_source + is_big_brother = gajim.contacts.is_big_brother(account_source, jid_source, + accounts) - drop_in_middle_of_meta = False - if type_dest == 'contact': - if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 4: - drop_in_middle_of_meta = True - if position == gtk.TREE_VIEW_DROP_AFTER and (len(path_dest) == 4 or \ - self.modelfilter.iter_has_child(iter_dest)): - drop_in_middle_of_meta = True - # Contact drop on group row or between two contacts that are - # not metacontacts - if (type_dest == 'group' or position in (gtk.TREE_VIEW_DROP_BEFORE, - gtk.TREE_VIEW_DROP_AFTER)) and not drop_in_middle_of_meta: - self.on_drop_in_group(None, account_source, c_source, grp_dest, - is_big_brother, context, etime, grp_source) - return + drop_in_middle_of_meta = False + if type_dest == 'contact': + if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 4: + drop_in_middle_of_meta = True + if position == gtk.TREE_VIEW_DROP_AFTER and (len(path_dest) == 4 or \ + self.modelfilter.iter_has_child(iter_dest)): + drop_in_middle_of_meta = True + # Contact drop on group row or between two contacts that are + # not metacontacts + if (type_dest == 'group' or position in (gtk.TREE_VIEW_DROP_BEFORE, + gtk.TREE_VIEW_DROP_AFTER)) and not drop_in_middle_of_meta: + self.on_drop_in_group(None, account_source, c_source, grp_dest, + is_big_brother, context, etime, grp_source) + return - # Contact drop on another contact, make meta contacts - if position == gtk.TREE_VIEW_DROP_INTO_OR_AFTER or \ - position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE or drop_in_middle_of_meta: - c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest, - jid_dest) - if not c_dest: - # c_dest is None if jid_dest doesn't belong to account - return - menu = gtk.Menu() - item = gtk.MenuItem(_('Send %s to %s') % (c_source.get_shown_name(), - c_dest.get_shown_name())) - item.connect('activate', self.on_drop_rosterx, account_source, - c_source, account_dest, c_dest, is_big_brother, context, etime) - menu.append(item) + # Contact drop on another contact, make meta contacts + if position == gtk.TREE_VIEW_DROP_INTO_OR_AFTER or \ + position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE or drop_in_middle_of_meta: + c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest, + jid_dest) + if not c_dest: + # c_dest is None if jid_dest doesn't belong to account + return + menu = gtk.Menu() + item = gtk.MenuItem(_('Send %s to %s') % (c_source.get_shown_name(), + c_dest.get_shown_name())) + item.connect('activate', self.on_drop_rosterx, account_source, + c_source, account_dest, c_dest, is_big_brother, context, etime) + menu.append(item) - item = gtk.MenuItem(_('Make %s and %s metacontacts') % ( - c_source.get_shown_name(), c_dest.get_shown_name())) - item.connect('activate', self.on_drop_in_contact, account_source, - c_source, account_dest, c_dest, is_big_brother, context, etime) + item = gtk.MenuItem(_('Make %s and %s metacontacts') % ( + c_source.get_shown_name(), c_dest.get_shown_name())) + item.connect('activate', self.on_drop_in_contact, account_source, + c_source, account_dest, c_dest, is_big_brother, context, etime) - menu.append(item) + menu.append(item) - menu.attach_to_widget(self.tree, None) - menu.connect('selection-done', gtkgui_helpers.destroy_widget) - menu.show_all() - menu.popup(None, None, None, 1, etime) + menu.attach_to_widget(self.tree, None) + menu.connect('selection-done', gtkgui_helpers.destroy_widget) + menu.show_all() + menu.popup(None, None, None, 1, etime) ################################################################################ ### Everything about images and icons.... ### Cleanup assigned to Jim++ :-) ################################################################################ - def get_appropriate_state_images(self, jid, size='16', icon_name='online'): - """ - Check jid and return the appropriate state images dict for the demanded - size. icon_name is taken into account when jid is from transport: - transport iconset doesn't contain all icons, so we fall back to jabber - one - """ - transport = gajim.get_transport_name_from_jid(jid) - if transport and size in self.transports_state_images: - if transport not in self.transports_state_images[size]: - # we don't have iconset for this transport loaded yet. Let's do it - self.make_transport_state_images(transport) - if transport in self.transports_state_images[size] and \ - icon_name in self.transports_state_images[size][transport]: - return self.transports_state_images[size][transport] - return gajim.interface.jabber_state_images[size] + def get_appropriate_state_images(self, jid, size='16', icon_name='online'): + """ + Check jid and return the appropriate state images dict for the demanded + size. icon_name is taken into account when jid is from transport: + transport iconset doesn't contain all icons, so we fall back to jabber + one + """ + transport = gajim.get_transport_name_from_jid(jid) + if transport and size in self.transports_state_images: + if transport not in self.transports_state_images[size]: + # we don't have iconset for this transport loaded yet. Let's do it + self.make_transport_state_images(transport) + if transport in self.transports_state_images[size] and \ + icon_name in self.transports_state_images[size][transport]: + return self.transports_state_images[size][transport] + return gajim.interface.jabber_state_images[size] - def make_transport_state_images(self, transport): - """ - Initialize opened and closed 'transport' iconset dict - """ - if gajim.config.get('use_transports_iconsets'): - folder = os.path.join(helpers.get_transport_path(transport), - '16x16') - pixo, pixc = gtkgui_helpers.load_icons_meta() - self.transports_state_images['opened'][transport] = \ - gtkgui_helpers.load_iconset(folder, pixo, transport=True) - self.transports_state_images['closed'][transport] = \ - gtkgui_helpers.load_iconset(folder, pixc, transport=True) - folder = os.path.join(helpers.get_transport_path(transport), '32x32') - self.transports_state_images['32'][transport] = \ - gtkgui_helpers.load_iconset(folder, transport=True) - folder = os.path.join(helpers.get_transport_path(transport), '16x16') - self.transports_state_images['16'][transport] = \ - gtkgui_helpers.load_iconset(folder, transport=True) + def make_transport_state_images(self, transport): + """ + Initialize opened and closed 'transport' iconset dict + """ + if gajim.config.get('use_transports_iconsets'): + folder = os.path.join(helpers.get_transport_path(transport), + '16x16') + pixo, pixc = gtkgui_helpers.load_icons_meta() + self.transports_state_images['opened'][transport] = \ + gtkgui_helpers.load_iconset(folder, pixo, transport=True) + self.transports_state_images['closed'][transport] = \ + gtkgui_helpers.load_iconset(folder, pixc, transport=True) + folder = os.path.join(helpers.get_transport_path(transport), '32x32') + self.transports_state_images['32'][transport] = \ + gtkgui_helpers.load_iconset(folder, transport=True) + folder = os.path.join(helpers.get_transport_path(transport), '16x16') + self.transports_state_images['16'][transport] = \ + gtkgui_helpers.load_iconset(folder, transport=True) - def update_jabber_state_images(self): - # Update the roster - self.setup_and_draw_roster() - # Update the status combobox - model = self.status_combobox.get_model() - titer = model.get_iter_root() - while titer: - if model[titer][2] != '': - # If it's not change status message iter - # eg. if it has show parameter not '' - model[titer][1] = gajim.interface.jabber_state_images['16'][model[ - titer][2]] - titer = model.iter_next(titer) - # Update the systray - if gajim.interface.systray_enabled: - gajim.interface.systray.set_img() + def update_jabber_state_images(self): + # Update the roster + self.setup_and_draw_roster() + # Update the status combobox + model = self.status_combobox.get_model() + titer = model.get_iter_root() + while titer: + if model[titer][2] != '': + # If it's not change status message iter + # eg. if it has show parameter not '' + model[titer][1] = gajim.interface.jabber_state_images['16'][model[ + titer][2]] + titer = model.iter_next(titer) + # Update the systray + if gajim.interface.systray_enabled: + gajim.interface.systray.set_img() - for win in gajim.interface.msg_win_mgr.windows(): - for ctrl in win.controls(): - ctrl.update_ui() - win.redraw_tab(ctrl) + for win in gajim.interface.msg_win_mgr.windows(): + for ctrl in win.controls(): + ctrl.update_ui() + win.redraw_tab(ctrl) - self.update_status_combobox() + self.update_status_combobox() - def set_account_status_icon(self, account): - status = gajim.connections[account].connected - child_iterA = self._get_account_iter(account, self.model) - if not child_iterA: - return - if not self.regroup: - show = gajim.SHOW_LIST[status] - else: # accounts merged - show = helpers.get_global_show() - self.model[child_iterA][C_IMG] = gajim.interface.jabber_state_images[ - '16'][show] + def set_account_status_icon(self, account): + status = gajim.connections[account].connected + child_iterA = self._get_account_iter(account, self.model) + if not child_iterA: + return + if not self.regroup: + show = gajim.SHOW_LIST[status] + else: # accounts merged + show = helpers.get_global_show() + self.model[child_iterA][C_IMG] = gajim.interface.jabber_state_images[ + '16'][show] ################################################################################ ### Style and theme related methods ################################################################################ - def show_title(self): - change_title_allowed = gajim.config.get('change_roster_title') - if not change_title_allowed: - return + def show_title(self): + change_title_allowed = gajim.config.get('change_roster_title') + if not change_title_allowed: + return - if gajim.config.get('one_message_window') == 'always_with_roster': - # always_with_roster mode defers to the MessageWindow - if not gajim.interface.msg_win_mgr.one_window_opened(): - # No MessageWindow to defer to - self.window.set_title('Gajim') - return + if gajim.config.get('one_message_window') == 'always_with_roster': + # always_with_roster mode defers to the MessageWindow + if not gajim.interface.msg_win_mgr.one_window_opened(): + # No MessageWindow to defer to + self.window.set_title('Gajim') + return - nb_unread = 0 - start = '' - for account in gajim.connections: - # Count events in roster title only if we don't auto open them - if not helpers.allow_popup_window(account): - nb_unread += gajim.events.get_nb_events(['chat', 'normal', - 'file-request', 'file-error', 'file-completed', - 'file-request-error', 'file-send-error', 'file-stopped', - 'printed_chat'], account) - if nb_unread > 1: - start = '[' + str(nb_unread) + '] ' - elif nb_unread == 1: - start = '* ' + nb_unread = 0 + start = '' + for account in gajim.connections: + # Count events in roster title only if we don't auto open them + if not helpers.allow_popup_window(account): + nb_unread += gajim.events.get_nb_events(['chat', 'normal', + 'file-request', 'file-error', 'file-completed', + 'file-request-error', 'file-send-error', 'file-stopped', + 'printed_chat'], account) + if nb_unread > 1: + start = '[' + str(nb_unread) + '] ' + elif nb_unread == 1: + start = '* ' - self.window.set_title(start + 'Gajim') + self.window.set_title(start + 'Gajim') - gtkgui_helpers.set_unset_urgency_hint(self.window, nb_unread) + gtkgui_helpers.set_unset_urgency_hint(self.window, nb_unread) - def _change_style(self, model, path, titer, option): - if option is None or model[titer][C_TYPE] == option: - # We changed style for this type of row - model[titer][C_NAME] = model[titer][C_NAME] + def _change_style(self, model, path, titer, option): + if option is None or model[titer][C_TYPE] == option: + # We changed style for this type of row + model[titer][C_NAME] = model[titer][C_NAME] - def change_roster_style(self, option): - self.model.foreach(self._change_style, option) - for win in gajim.interface.msg_win_mgr.windows(): - win.repaint_themed_widgets() + def change_roster_style(self, option): + self.model.foreach(self._change_style, option) + for win in gajim.interface.msg_win_mgr.windows(): + win.repaint_themed_widgets() - def repaint_themed_widgets(self): - """ - Notify windows that contain themed widgets to repaint them - """ - for win in gajim.interface.msg_win_mgr.windows(): - win.repaint_themed_widgets() - for account in gajim.connections: - for addr in gajim.interface.instances[account]['disco']: - gajim.interface.instances[account]['disco'][addr].paint_banner() - for ctrl in gajim.interface.minimized_controls[account].values(): - ctrl.repaint_themed_widgets() + def repaint_themed_widgets(self): + """ + Notify windows that contain themed widgets to repaint them + """ + for win in gajim.interface.msg_win_mgr.windows(): + win.repaint_themed_widgets() + for account in gajim.connections: + for addr in gajim.interface.instances[account]['disco']: + gajim.interface.instances[account]['disco'][addr].paint_banner() + for ctrl in gajim.interface.minimized_controls[account].values(): + ctrl.repaint_themed_widgets() - def update_avatar_in_gui(self, jid, account): - # Update roster - self.draw_avatar(jid, account) - # Update chat window + def update_avatar_in_gui(self, jid, account): + # Update roster + self.draw_avatar(jid, account) + # Update chat window - ctrl = gajim.interface.msg_win_mgr.get_control(jid, account) - if ctrl: - ctrl.show_avatar() + ctrl = gajim.interface.msg_win_mgr.get_control(jid, account) + if ctrl: + ctrl.show_avatar() - def on_roster_treeview_style_set(self, treeview, style): - """ - When style (theme) changes, redraw all contacts - """ - for contact in self._iter_contact_rows(): - self.draw_contact(contact[C_JID].decode('utf-8'), - contact[C_ACCOUNT].decode('utf-8')) + def on_roster_treeview_style_set(self, treeview, style): + """ + When style (theme) changes, redraw all contacts + """ + for contact in self._iter_contact_rows(): + self.draw_contact(contact[C_JID].decode('utf-8'), + contact[C_ACCOUNT].decode('utf-8')) - def set_renderer_color(self, renderer, style, set_background=True): - """ - Set style for treeview cell, using PRELIGHT system color - """ - if set_background: - bgcolor = self.tree.style.bg[style] - renderer.set_property('cell-background-gdk', bgcolor) - else: - fgcolor = self.tree.style.fg[style] - renderer.set_property('foreground-gdk', fgcolor) + def set_renderer_color(self, renderer, style, set_background=True): + """ + Set style for treeview cell, using PRELIGHT system color + """ + if set_background: + bgcolor = self.tree.style.bg[style] + renderer.set_property('cell-background-gdk', bgcolor) + else: + fgcolor = self.tree.style.fg[style] + renderer.set_property('foreground-gdk', fgcolor) - def _iconCellDataFunc(self, column, renderer, model, titer, data=None): - """ - When a row is added, set properties for icon renderer - """ - type_ = model[titer][C_TYPE] - if type_ == 'account': - self._set_account_row_background_color(renderer) - renderer.set_property('xalign', 0) - elif type_ == 'group': - self._set_group_row_background_color(renderer) - renderer.set_property('xalign', 0.2) - elif type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534 - if not model[titer][C_JID] or not model[titer][C_ACCOUNT]: - # This can append when at the moment we add the row - return - jid = model[titer][C_JID].decode('utf-8') - account = model[titer][C_ACCOUNT].decode('utf-8') - self._set_contact_row_background_color(renderer, jid, account) - parent_iter = model.iter_parent(titer) - if model[parent_iter][C_TYPE] == 'contact': - renderer.set_property('xalign', 1) - else: - renderer.set_property('xalign', 0.4) - renderer.set_property('width', 26) + def _iconCellDataFunc(self, column, renderer, model, titer, data=None): + """ + When a row is added, set properties for icon renderer + """ + type_ = model[titer][C_TYPE] + if type_ == 'account': + self._set_account_row_background_color(renderer) + renderer.set_property('xalign', 0) + elif type_ == 'group': + self._set_group_row_background_color(renderer) + renderer.set_property('xalign', 0.2) + elif type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534 + if not model[titer][C_JID] or not model[titer][C_ACCOUNT]: + # This can append when at the moment we add the row + return + jid = model[titer][C_JID].decode('utf-8') + account = model[titer][C_ACCOUNT].decode('utf-8') + self._set_contact_row_background_color(renderer, jid, account) + parent_iter = model.iter_parent(titer) + if model[parent_iter][C_TYPE] == 'contact': + renderer.set_property('xalign', 1) + else: + renderer.set_property('xalign', 0.4) + renderer.set_property('width', 26) - def _nameCellDataFunc(self, column, renderer, model, titer, data=None): - """ - When a row is added, set properties for name renderer - """ - theme = gajim.config.get('roster_theme') - type_ = model[titer][C_TYPE] - if type_ == 'account': - color = gajim.config.get_per('themes', theme, 'accounttextcolor') - if color: - renderer.set_property('foreground', color) - else: - self.set_renderer_color(renderer, gtk.STATE_ACTIVE, False) - renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'accountfont')) - renderer.set_property('xpad', 0) - renderer.set_property('width', 3) - self._set_account_row_background_color(renderer) - elif type_ == 'group': - color = gajim.config.get_per('themes', theme, 'grouptextcolor') - if color: - renderer.set_property('foreground', color) - else: - self.set_renderer_color(renderer, gtk.STATE_PRELIGHT, False) - renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont')) - renderer.set_property('xpad', 4) - self._set_group_row_background_color(renderer) - elif type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534 - if not model[titer][C_JID] or not model[titer][C_ACCOUNT]: - # This can append when at the moment we add the row - return - jid = model[titer][C_JID].decode('utf-8') - account = model[titer][C_ACCOUNT].decode('utf-8') - color = None - if type_ == 'groupchat': - ctrl = gajim.interface.minimized_controls[account].get(jid, None) - if ctrl and ctrl.attention_flag: - color = gajim.config.get_per('themes', theme, - 'state_muc_directed_msg_color') - renderer.set_property('foreground', 'red') - if not color: - color = gajim.config.get_per('themes', theme, 'contacttextcolor') - if color: - renderer.set_property('foreground', color) - else: - renderer.set_property('foreground', None) - self._set_contact_row_background_color(renderer, jid, account) - renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont')) - parent_iter = model.iter_parent(titer) - if model[parent_iter][C_TYPE] == 'contact': - renderer.set_property('xpad', 16) - else: - renderer.set_property('xpad', 8) + def _nameCellDataFunc(self, column, renderer, model, titer, data=None): + """ + When a row is added, set properties for name renderer + """ + theme = gajim.config.get('roster_theme') + type_ = model[titer][C_TYPE] + if type_ == 'account': + color = gajim.config.get_per('themes', theme, 'accounttextcolor') + if color: + renderer.set_property('foreground', color) + else: + self.set_renderer_color(renderer, gtk.STATE_ACTIVE, False) + renderer.set_property('font', + gtkgui_helpers.get_theme_font_for_option(theme, 'accountfont')) + renderer.set_property('xpad', 0) + renderer.set_property('width', 3) + self._set_account_row_background_color(renderer) + elif type_ == 'group': + color = gajim.config.get_per('themes', theme, 'grouptextcolor') + if color: + renderer.set_property('foreground', color) + else: + self.set_renderer_color(renderer, gtk.STATE_PRELIGHT, False) + renderer.set_property('font', + gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont')) + renderer.set_property('xpad', 4) + self._set_group_row_background_color(renderer) + elif type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534 + if not model[titer][C_JID] or not model[titer][C_ACCOUNT]: + # This can append when at the moment we add the row + return + jid = model[titer][C_JID].decode('utf-8') + account = model[titer][C_ACCOUNT].decode('utf-8') + color = None + if type_ == 'groupchat': + ctrl = gajim.interface.minimized_controls[account].get(jid, None) + if ctrl and ctrl.attention_flag: + color = gajim.config.get_per('themes', theme, + 'state_muc_directed_msg_color') + renderer.set_property('foreground', 'red') + if not color: + color = gajim.config.get_per('themes', theme, 'contacttextcolor') + if color: + renderer.set_property('foreground', color) + else: + renderer.set_property('foreground', None) + self._set_contact_row_background_color(renderer, jid, account) + renderer.set_property('font', + gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont')) + parent_iter = model.iter_parent(titer) + if model[parent_iter][C_TYPE] == 'contact': + renderer.set_property('xpad', 16) + else: + renderer.set_property('xpad', 8) - def _fill_pep_pixbuf_renderer(self, column, renderer, model, titer, - data=None): - """ - When a row is added, draw the respective pep icon - """ - theme = gajim.config.get('roster_theme') - type_ = model[titer][C_TYPE] + def _fill_pep_pixbuf_renderer(self, column, renderer, model, titer, + data=None): + """ + When a row is added, draw the respective pep icon + """ + theme = gajim.config.get('roster_theme') + type_ = model[titer][C_TYPE] - # allocate space for the icon only if needed - if not model[titer][data]: - renderer.set_property('visible', False) - else: - renderer.set_property('visible', True) + # allocate space for the icon only if needed + if not model[titer][data]: + renderer.set_property('visible', False) + else: + renderer.set_property('visible', True) - if type_ == 'account': - self._set_account_row_background_color(renderer) - renderer.set_property('xalign', 1) - elif type_: - if not model[titer][C_JID] or not model[titer][C_ACCOUNT]: - # This can append at the moment we add the row - return - jid = model[titer][C_JID].decode('utf-8') - account = model[titer][C_ACCOUNT].decode('utf-8') - self._set_contact_row_background_color(renderer, jid, account) + if type_ == 'account': + self._set_account_row_background_color(renderer) + renderer.set_property('xalign', 1) + elif type_: + if not model[titer][C_JID] or not model[titer][C_ACCOUNT]: + # This can append at the moment we add the row + return + jid = model[titer][C_JID].decode('utf-8') + account = model[titer][C_ACCOUNT].decode('utf-8') + self._set_contact_row_background_color(renderer, jid, account) - def _fill_avatar_pixbuf_renderer(self, column, renderer, model, titer, data - = None): - """ - When a row is added, set properties for avatar renderer - """ - type_ = model[titer][C_TYPE] - if type_ in ('group', 'account'): - renderer.set_property('visible', False) - return + def _fill_avatar_pixbuf_renderer(self, column, renderer, model, titer, data + = None): + """ + When a row is added, set properties for avatar renderer + """ + type_ = model[titer][C_TYPE] + if type_ in ('group', 'account'): + renderer.set_property('visible', False) + return - # allocate space for the icon only if needed - if model[titer][C_AVATAR_PIXBUF] or \ - gajim.config.get('avatar_position_in_roster') == 'left': - renderer.set_property('visible', True) - if type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534 - if not model[titer][C_JID] or not model[titer][C_ACCOUNT]: - # This can append at the moment we add the row - return - jid = model[titer][C_JID].decode('utf-8') - account = model[titer][C_ACCOUNT].decode('utf-8') - self._set_contact_row_background_color(renderer, jid, account) - else: - renderer.set_property('visible', False) + # allocate space for the icon only if needed + if model[titer][C_AVATAR_PIXBUF] or \ + gajim.config.get('avatar_position_in_roster') == 'left': + renderer.set_property('visible', True) + if type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534 + if not model[titer][C_JID] or not model[titer][C_ACCOUNT]: + # This can append at the moment we add the row + return + jid = model[titer][C_JID].decode('utf-8') + account = model[titer][C_ACCOUNT].decode('utf-8') + self._set_contact_row_background_color(renderer, jid, account) + else: + renderer.set_property('visible', False) - if gajim.config.get('avatar_position_in_roster') == 'left': - renderer.set_property('width', gajim.config.get('roster_avatar_width')) - renderer.set_property('xalign', 0.5) - else: - renderer.set_property('xalign', 1) # align pixbuf to the right + if gajim.config.get('avatar_position_in_roster') == 'left': + renderer.set_property('width', gajim.config.get('roster_avatar_width')) + renderer.set_property('xalign', 0.5) + else: + renderer.set_property('xalign', 1) # align pixbuf to the right - def _fill_padlock_pixbuf_renderer(self, column, renderer, model, titer, data=None): - """ - When a row is added, set properties for padlock renderer - """ - type_ = model[titer][C_TYPE] - # allocate space for the icon only if needed - if type_ == 'account' and model[titer][C_PADLOCK_PIXBUF]: - renderer.set_property('visible', True) - self._set_account_row_background_color(renderer) - renderer.set_property('xalign', 1) # align pixbuf to the right - else: - renderer.set_property('visible', False) + def _fill_padlock_pixbuf_renderer(self, column, renderer, model, titer, data=None): + """ + When a row is added, set properties for padlock renderer + """ + type_ = model[titer][C_TYPE] + # allocate space for the icon only if needed + if type_ == 'account' and model[titer][C_PADLOCK_PIXBUF]: + renderer.set_property('visible', True) + self._set_account_row_background_color(renderer) + renderer.set_property('xalign', 1) # align pixbuf to the right + else: + renderer.set_property('visible', False) - def _set_account_row_background_color(self, renderer): - theme = gajim.config.get('roster_theme') - color = gajim.config.get_per('themes', theme, 'accountbgcolor') - if color: - renderer.set_property('cell-background', color) - else: - self.set_renderer_color(renderer, gtk.STATE_ACTIVE) + def _set_account_row_background_color(self, renderer): + theme = gajim.config.get('roster_theme') + color = gajim.config.get_per('themes', theme, 'accountbgcolor') + if color: + renderer.set_property('cell-background', color) + else: + self.set_renderer_color(renderer, gtk.STATE_ACTIVE) - def _set_contact_row_background_color(self, renderer, jid, account): - theme = gajim.config.get('roster_theme') - if jid in gajim.newly_added[account]: - renderer.set_property('cell-background', gajim.config.get( - 'just_connected_bg_color')) - elif jid in gajim.to_be_removed[account]: - renderer.set_property('cell-background', gajim.config.get( - 'just_disconnected_bg_color')) - else: - color = gajim.config.get_per('themes', theme, 'contactbgcolor') - renderer.set_property('cell-background', color if color else None) + def _set_contact_row_background_color(self, renderer, jid, account): + theme = gajim.config.get('roster_theme') + if jid in gajim.newly_added[account]: + renderer.set_property('cell-background', gajim.config.get( + 'just_connected_bg_color')) + elif jid in gajim.to_be_removed[account]: + renderer.set_property('cell-background', gajim.config.get( + 'just_disconnected_bg_color')) + else: + color = gajim.config.get_per('themes', theme, 'contactbgcolor') + renderer.set_property('cell-background', color if color else None) - def _set_group_row_background_color(self, renderer): - theme = gajim.config.get('roster_theme') - color = gajim.config.get_per('themes', theme, 'groupbgcolor') - if color: - renderer.set_property('cell-background', color) - else: - self.set_renderer_color(renderer, gtk.STATE_PRELIGHT) + def _set_group_row_background_color(self, renderer): + theme = gajim.config.get('roster_theme') + color = gajim.config.get_per('themes', theme, 'groupbgcolor') + if color: + renderer.set_property('cell-background', color) + else: + self.set_renderer_color(renderer, gtk.STATE_PRELIGHT) ################################################################################ ### Everything about building menus ### FIXME: We really need to make it simpler! 1465 lines are a few to much.... ################################################################################ - def make_menu(self, force=False): - """ - Create the main window's menus - """ - if not force and not self.actions_menu_needs_rebuild: - return - new_chat_menuitem = self.xml.get_object('new_chat_menuitem') - single_message_menuitem = self.xml.get_object( - 'send_single_message_menuitem') - join_gc_menuitem = self.xml.get_object('join_gc_menuitem') - muc_icon = gtkgui_helpers.load_icon('muc_active') - if muc_icon: - join_gc_menuitem.set_image(muc_icon) - add_new_contact_menuitem = self.xml.get_object('add_new_contact_menuitem') - service_disco_menuitem = self.xml.get_object('service_disco_menuitem') - advanced_menuitem = self.xml.get_object('advanced_menuitem') - profile_avatar_menuitem = self.xml.get_object('profile_avatar_menuitem') - - # destroy old advanced menus - for m in self.advanced_menus: - m.destroy() - - # make it sensitive. it is insensitive only if no accounts are *available* - advanced_menuitem.set_sensitive(True) - - if self.add_new_contact_handler_id: - add_new_contact_menuitem.handler_disconnect( - self.add_new_contact_handler_id) - self.add_new_contact_handler_id = None - - if self.service_disco_handler_id: - service_disco_menuitem.handler_disconnect( - self.service_disco_handler_id) - self.service_disco_handler_id = None - - if self.new_chat_menuitem_handler_id: - new_chat_menuitem.handler_disconnect( - self.new_chat_menuitem_handler_id) - self.new_chat_menuitem_handler_id = None - - if self.single_message_menuitem_handler_id: - single_message_menuitem.handler_disconnect( - self.single_message_menuitem_handler_id) - self.single_message_menuitem_handler_id = None - - if self.profile_avatar_menuitem_handler_id: - profile_avatar_menuitem.handler_disconnect( - self.profile_avatar_menuitem_handler_id) - self.profile_avatar_menuitem_handler_id = None - - # remove the existing submenus - add_new_contact_menuitem.remove_submenu() - service_disco_menuitem.remove_submenu() - join_gc_menuitem.remove_submenu() - single_message_menuitem.remove_submenu() - new_chat_menuitem.remove_submenu() - advanced_menuitem.remove_submenu() - profile_avatar_menuitem.remove_submenu() - - # remove the existing accelerator - if self.have_new_chat_accel: - ag = gtk.accel_groups_from_object(self.window)[0] - new_chat_menuitem.remove_accelerator(ag, gtk.keysyms.n, - gtk.gdk.CONTROL_MASK) - self.have_new_chat_accel = False - - gc_sub_menu = gtk.Menu() # gc is always a submenu - join_gc_menuitem.set_submenu(gc_sub_menu) - - connected_accounts = gajim.get_number_of_connected_accounts() - - connected_accounts_with_private_storage = 0 - - # items that get shown whether an account is zeroconf or not - accounts_list = sorted(gajim.contacts.get_accounts()) - if connected_accounts > 1: # 2 or more accounts? make submenus - new_chat_sub_menu = gtk.Menu() - - for account in accounts_list: - if gajim.connections[account].connected <= 1: - # if offline or connecting - continue - - # new chat - new_chat_item = gtk.MenuItem(_('using account %s') % account, - False) - new_chat_sub_menu.append(new_chat_item) - new_chat_item.connect('activate', - self.on_new_chat_menuitem_activate, account) - - new_chat_menuitem.set_submenu(new_chat_sub_menu) - new_chat_sub_menu.show_all() - - elif connected_accounts == 1: # user has only one account - for account in gajim.connections: - if gajim.account_is_connected(account): # THE connected account - # new chat - if not self.new_chat_menuitem_handler_id: - self.new_chat_menuitem_handler_id = new_chat_menuitem.\ - connect('activate', self.on_new_chat_menuitem_activate, - account) - - break - - # menu items that don't apply to zeroconf connections - if connected_accounts == 1 or (connected_accounts == 2 and \ - gajim.zeroconf_is_connected()): - # only one 'real' (non-zeroconf) account is connected, don't need submenus - - for account in accounts_list: - if gajim.account_is_connected(account) and \ - not gajim.config.get_per('accounts', account, 'is_zeroconf'): - # gc - if gajim.connections[account].private_storage_supported: - connected_accounts_with_private_storage += 1 - self.add_bookmarks_list(gc_sub_menu, account) - gc_sub_menu.show_all() - # add - if not self.add_new_contact_handler_id: - self.add_new_contact_handler_id =\ - add_new_contact_menuitem.connect( - 'activate', self.on_add_new_contact, account) - # disco - if not self.service_disco_handler_id: - self.service_disco_handler_id = service_disco_menuitem.\ - connect('activate', - self.on_service_disco_menuitem_activate, account) - - # single message - if not self.single_message_menuitem_handler_id: - self.single_message_menuitem_handler_id = \ - single_message_menuitem.connect('activate', \ - self.on_send_single_message_menuitem_activate, account) - - # new chat accel - if not self.have_new_chat_accel: - ag = gtk.accel_groups_from_object(self.window)[0] - new_chat_menuitem.add_accelerator('activate', ag, - gtk.keysyms.n, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) - self.have_new_chat_accel = True - - break # No other account connected - else: - # 2 or more 'real' accounts are connected, make submenus - single_message_sub_menu = gtk.Menu() - add_sub_menu = gtk.Menu() - disco_sub_menu = gtk.Menu() - - for account in accounts_list: - if gajim.connections[account].connected <= 1 or \ - gajim.config.get_per('accounts', account, 'is_zeroconf'): - # skip account if it's offline or connecting or is zeroconf - continue - - # single message - single_message_item = gtk.MenuItem(_('using account %s') % account, - False) - single_message_sub_menu.append(single_message_item) - single_message_item.connect('activate', - self.on_send_single_message_menuitem_activate, account) - - # join gc - if gajim.connections[account].private_storage_supported: - connected_accounts_with_private_storage += 1 - gc_item = gtk.MenuItem(_('using account %s') % account, False) - gc_sub_menu.append(gc_item) - gc_menuitem_menu = gtk.Menu() - self.add_bookmarks_list(gc_menuitem_menu, account) - gc_item.set_submenu(gc_menuitem_menu) - - # add - add_item = gtk.MenuItem(_('to %s account') % account, False) - add_sub_menu.append(add_item) - add_item.connect('activate', self.on_add_new_contact, account) - - # disco - disco_item = gtk.MenuItem(_('using %s account') % account, False) - disco_sub_menu.append(disco_item) - disco_item.connect('activate', - self.on_service_disco_menuitem_activate, account) - - single_message_menuitem.set_submenu(single_message_sub_menu) - single_message_sub_menu.show_all() - gc_sub_menu.show_all() - add_new_contact_menuitem.set_submenu(add_sub_menu) - add_sub_menu.show_all() - service_disco_menuitem.set_submenu(disco_sub_menu) - disco_sub_menu.show_all() - - if connected_accounts == 0: - # no connected accounts, make the menuitems insensitive - for item in (new_chat_menuitem, join_gc_menuitem,\ - add_new_contact_menuitem, service_disco_menuitem,\ - single_message_menuitem): - item.set_sensitive(False) - else: # we have one or more connected accounts - for item in (new_chat_menuitem, join_gc_menuitem, - add_new_contact_menuitem, service_disco_menuitem, - single_message_menuitem): - item.set_sensitive(True) - # disable some fields if only local account is there - if connected_accounts == 1: - for account in gajim.connections: - if gajim.account_is_connected(account) and \ - gajim.connections[account].is_zeroconf: - for item in (join_gc_menuitem, add_new_contact_menuitem, - service_disco_menuitem, single_message_menuitem): - item.set_sensitive(False) - - # Manage GC bookmarks - newitem = gtk.SeparatorMenuItem() # separator - gc_sub_menu.append(newitem) - - newitem = gtk.ImageMenuItem(_('_Manage Bookmarks...')) - img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, - gtk.ICON_SIZE_MENU) - newitem.set_image(img) - newitem.connect('activate', self.on_manage_bookmarks_menuitem_activate) - gc_sub_menu.append(newitem) - gc_sub_menu.show_all() - if connected_accounts_with_private_storage == 0: - newitem.set_sensitive(False) - - connected_accounts_with_vcard = [] - for account in gajim.connections: - if gajim.account_is_connected(account) and \ - gajim.connections[account].vcard_supported: - connected_accounts_with_vcard.append(account) - if len(connected_accounts_with_vcard) > 1: - # 2 or more accounts? make submenus - profile_avatar_sub_menu = gtk.Menu() - for account in connected_accounts_with_vcard: - # profile, avatar - profile_avatar_item = gtk.MenuItem(_('of account %s') % account, - False) - profile_avatar_sub_menu.append(profile_avatar_item) - profile_avatar_item.connect('activate', - self.on_profile_avatar_menuitem_activate, account) - profile_avatar_menuitem.set_submenu(profile_avatar_sub_menu) - profile_avatar_sub_menu.show_all() - elif len(connected_accounts_with_vcard) == 1: # user has only one account - account = connected_accounts_with_vcard[0] - # profile, avatar - if not self.profile_avatar_menuitem_handler_id: - self.profile_avatar_menuitem_handler_id = \ - profile_avatar_menuitem.connect('activate', - self.on_profile_avatar_menuitem_activate, account) - - if len(connected_accounts_with_vcard) == 0: - profile_avatar_menuitem.set_sensitive(False) - else: - profile_avatar_menuitem.set_sensitive(True) - - # Advanced Actions - if len(gajim.connections) == 0: # user has no accounts - advanced_menuitem.set_sensitive(False) - elif len(gajim.connections) == 1: # we have one acccount - account = gajim.connections.keys()[0] - advanced_menuitem_menu = self.get_and_connect_advanced_menuitem_menu( - account) - self.advanced_menus.append(advanced_menuitem_menu) - - self.add_history_manager_menuitem(advanced_menuitem_menu) - - advanced_menuitem.set_submenu(advanced_menuitem_menu) - advanced_menuitem_menu.show_all() - else: # user has *more* than one account : build advanced submenus - advanced_sub_menu = gtk.Menu() - accounts = [] # Put accounts in a list to sort them - for account in gajim.connections: - accounts.append(account) - accounts.sort() - for account in accounts: - advanced_item = gtk.MenuItem(_('for account %s') % account, False) - advanced_sub_menu.append(advanced_item) - advanced_menuitem_menu = \ - self.get_and_connect_advanced_menuitem_menu(account) - self.advanced_menus.append(advanced_menuitem_menu) - advanced_item.set_submenu(advanced_menuitem_menu) - - self.add_history_manager_menuitem(advanced_sub_menu) - - advanced_menuitem.set_submenu(advanced_sub_menu) - advanced_sub_menu.show_all() - - self.actions_menu_needs_rebuild = False - - def build_account_menu(self, account): - # we have to create our own set of icons for the menu - # using self.jabber_status_images is poopoo - iconset = gajim.config.get('iconset') - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - state_images = gtkgui_helpers.load_iconset(path) - - if not gajim.config.get_per('accounts', account, 'is_zeroconf'): - xml = gtkgui_helpers.get_gtk_builder('account_context_menu.ui') - account_context_menu = xml.get_object('account_context_menu') - - status_menuitem = xml.get_object('status_menuitem') - start_chat_menuitem = xml.get_object('start_chat_menuitem') - join_group_chat_menuitem = xml.get_object('join_group_chat_menuitem') - muc_icon = gtkgui_helpers.load_icon('muc_active') - if muc_icon: - join_group_chat_menuitem.set_image(muc_icon) - open_gmail_inbox_menuitem = xml.get_object('open_gmail_inbox_menuitem') - add_contact_menuitem = xml.get_object('add_contact_menuitem') - service_discovery_menuitem = xml.get_object( - 'service_discovery_menuitem') - execute_command_menuitem = xml.get_object('execute_command_menuitem') - edit_account_menuitem = xml.get_object('edit_account_menuitem') - sub_menu = gtk.Menu() - status_menuitem.set_submenu(sub_menu) - - for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'): - uf_show = helpers.get_uf_show(show, use_mnemonic=True) - item = gtk.ImageMenuItem(uf_show) - icon = state_images[show] - item.set_image(icon) - sub_menu.append(item) - con = gajim.connections[account] - if show == 'invisible' and con.connected > 1 and \ - not con.privacy_rules_supported: - item.set_sensitive(False) - else: - item.connect('activate', self.change_status, account, show) - - item = gtk.SeparatorMenuItem() - sub_menu.append(item) - - item = gtk.ImageMenuItem(_('_Change Status Message')) - gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input') - sub_menu.append(item) - item.connect('activate', self.on_change_status_message_activate, - account) - if gajim.connections[account].connected < 2: - item.set_sensitive(False) - - item = gtk.SeparatorMenuItem() - sub_menu.append(item) - - uf_show = helpers.get_uf_show('offline', use_mnemonic=True) - item = gtk.ImageMenuItem(uf_show) - icon = state_images['offline'] - item.set_image(icon) - sub_menu.append(item) - item.connect('activate', self.change_status, account, 'offline') - - pep_menuitem = xml.get_object('pep_menuitem') - if gajim.connections[account].pep_supported: - pep_submenu = gtk.Menu() - pep_menuitem.set_submenu(pep_submenu) - def add_item(label, opt_name, func): - item = gtk.CheckMenuItem(label) - pep_submenu.append(item) - if not dbus_support.supported: - item.set_sensitive(False) - else: - activ = gajim.config.get_per('accounts', account, opt_name) - item.set_active(activ) - item.connect('toggled', func, account) - - add_item(_('Publish Tune'), 'publish_tune', - self.on_publish_tune_toggled) - add_item(_('Publish Location'), 'publish_location', - self.on_publish_location_toggled) - - pep_config = gtk.ImageMenuItem(_('Configure Services...')) - item = gtk.SeparatorMenuItem() - pep_submenu.append(item) - pep_config.set_sensitive(True) - pep_submenu.append(pep_config) - pep_config.connect('activate', - self.on_pep_services_menuitem_activate, account) - img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, - gtk.ICON_SIZE_MENU) - pep_config.set_image(img) - - else: - pep_menuitem.set_sensitive(False) - - if not gajim.connections[account].gmail_url: - open_gmail_inbox_menuitem.set_no_show_all(True) - open_gmail_inbox_menuitem.hide() - else: - open_gmail_inbox_menuitem.connect('activate', - self.on_open_gmail_inbox, account) - - edit_account_menuitem.connect('activate', self.on_edit_account, - account) - add_contact_menuitem.connect('activate', self.on_add_new_contact, - account) - service_discovery_menuitem.connect('activate', - self.on_service_disco_menuitem_activate, account) - hostname = gajim.config.get_per('accounts', account, 'hostname') - contact = gajim.contacts.create_contact(jid=hostname, account=account) # Fake contact - execute_command_menuitem.connect('activate', - self.on_execute_command, contact, account) - - start_chat_menuitem.connect('activate', - self.on_new_chat_menuitem_activate, account) - - gc_sub_menu = gtk.Menu() # gc is always a submenu - join_group_chat_menuitem.set_submenu(gc_sub_menu) - self.add_bookmarks_list(gc_sub_menu, account) - - # make some items insensitive if account is offline - if gajim.connections[account].connected < 2: - for widget in (add_contact_menuitem, service_discovery_menuitem, - join_group_chat_menuitem, execute_command_menuitem, pep_menuitem, - start_chat_menuitem): - widget.set_sensitive(False) - else: - xml = gtkgui_helpers.get_gtk_builder('zeroconf_context_menu.ui') - account_context_menu = xml.get_object('zeroconf_context_menu') - - status_menuitem = xml.get_object('status_menuitem') - zeroconf_properties_menuitem = xml.get_object( - 'zeroconf_properties_menuitem') - sub_menu = gtk.Menu() - status_menuitem.set_submenu(sub_menu) - - for show in ('online', 'away', 'dnd', 'invisible'): - uf_show = helpers.get_uf_show(show, use_mnemonic=True) - item = gtk.ImageMenuItem(uf_show) - icon = state_images[show] - item.set_image(icon) - sub_menu.append(item) - item.connect('activate', self.change_status, account, show) - - item = gtk.SeparatorMenuItem() - sub_menu.append(item) - - item = gtk.ImageMenuItem(_('_Change Status Message')) - gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input') - sub_menu.append(item) - item.connect('activate', self.on_change_status_message_activate, - account) - if gajim.connections[account].connected < 2: - item.set_sensitive(False) - - uf_show = helpers.get_uf_show('offline', use_mnemonic=True) - item = gtk.ImageMenuItem(uf_show) - icon = state_images['offline'] - item.set_image(icon) - sub_menu.append(item) - item.connect('activate', self.change_status, account, 'offline') - - zeroconf_properties_menuitem.connect('activate', - self.on_zeroconf_properties, account) - - return account_context_menu - - def make_account_menu(self, event, titer): - """ - Make account's popup menu - """ - model = self.modelfilter - account = model[titer][C_ACCOUNT].decode('utf-8') - - if account != 'all': # not in merged mode - menu = self.build_account_menu(account) - else: - menu = gtk.Menu() - iconset = gajim.config.get('iconset') - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - accounts = [] # Put accounts in a list to sort them - for account in gajim.connections: - accounts.append(account) - accounts.sort() - for account in accounts: - state_images = gtkgui_helpers.load_iconset(path) - item = gtk.ImageMenuItem(account) - show = gajim.SHOW_LIST[gajim.connections[account].connected] - icon = state_images[show] - item.set_image(icon) - account_menu = self.build_account_menu(account) - item.set_submenu(account_menu) - menu.append(item) - - event_button = gtkgui_helpers.get_possible_button_event(event) - - menu.attach_to_widget(self.tree, None) - menu.connect('selection-done', gtkgui_helpers.destroy_widget) - menu.show_all() - menu.popup(None, None, None, event_button, event.time) - - def make_group_menu(self, event, titer): - """ - Make group's popup menu - """ - model = self.modelfilter - path = model.get_path(titer) - group = model[titer][C_JID].decode('utf-8') - account = model[titer][C_ACCOUNT].decode('utf-8') - - list_ = [] # list of (jid, account) tuples - list_online = [] # list of (jid, account) tuples - - group = model[titer][C_JID] - for jid in gajim.contacts.get_jid_list(account): - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - if group in contact.get_shown_groups(): - if contact.show not in ('offline', 'error'): - list_online.append((contact, account)) - list_.append((contact, account)) - menu = gtk.Menu() - - # Make special context menu if group is Groupchats - if group == _('Groupchats'): - maximize_menuitem = gtk.ImageMenuItem(_('_Maximize All')) - icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP, gtk.ICON_SIZE_MENU) - maximize_menuitem.set_image(icon) - maximize_menuitem.connect('activate', self.on_all_groupchat_maximized,\ - list_) - menu.append(maximize_menuitem) - else: - # Send Group Message - send_group_message_item = gtk.ImageMenuItem(_('Send Group M_essage')) - icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) - send_group_message_item.set_image(icon) - - send_group_message_submenu = gtk.Menu() - send_group_message_item.set_submenu(send_group_message_submenu) - menu.append(send_group_message_item) - - group_message_to_all_item = gtk.MenuItem(_('To all users')) - send_group_message_submenu.append(group_message_to_all_item) - - group_message_to_all_online_item = gtk.MenuItem( - _('To all online users')) - send_group_message_submenu.append(group_message_to_all_online_item) - - group_message_to_all_online_item.connect('activate', - self.on_send_single_message_menuitem_activate, account, list_online) - group_message_to_all_item.connect('activate', - self.on_send_single_message_menuitem_activate, account, list_) - - # Invite to - invite_menuitem = gtk.ImageMenuItem(_('In_vite to')) - muc_icon = gtkgui_helpers.load_icon('muc_active') - if muc_icon: - invite_menuitem.set_image(muc_icon) - - gui_menu_builder.build_invite_submenu(invite_menuitem, list_online) - menu.append(invite_menuitem) - - # Send Custom Status - send_custom_status_menuitem = gtk.ImageMenuItem( - _('Send Cus_tom Status')) - # add a special img for this menuitem - if helpers.group_is_blocked(account, group): - send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( - 'offline')) - send_custom_status_menuitem.set_sensitive(False) - else: - icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, - gtk.ICON_SIZE_MENU) - send_custom_status_menuitem.set_image(icon) - status_menuitems = gtk.Menu() - send_custom_status_menuitem.set_submenu(status_menuitems) - iconset = gajim.config.get('iconset') - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'): - # icon MUST be different instance for every item - state_images = gtkgui_helpers.load_iconset(path) - status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s)) - status_menuitem.connect('activate', self.on_send_custom_status, - list_, s, group) - icon = state_images[s] - status_menuitem.set_image(icon) - status_menuitems.append(status_menuitem) - menu.append(send_custom_status_menuitem) - - # there is no singlemessage and custom status for zeroconf - if gajim.config.get_per('accounts', account, 'is_zeroconf'): - send_custom_status_menuitem.set_sensitive(False) - send_group_message_item.set_sensitive(False) - - if not group in helpers.special_groups: - item = gtk.SeparatorMenuItem() # separator - menu.append(item) - - # Rename - rename_item = gtk.ImageMenuItem(_('Re_name')) - # add a special img for rename menuitem - gtkgui_helpers.add_image_to_menuitem(rename_item, 'gajim-kbd_input') - menu.append(rename_item) - rename_item.connect('activate', self.on_rename, 'group', group, - account) - - # Block group - is_blocked = False - if self.regroup: - for g_account in gajim.connections: - if helpers.group_is_blocked(g_account, group): - is_blocked = True - else: - if helpers.group_is_blocked(account, group): - is_blocked = True - - if is_blocked and gajim.connections[account].privacy_rules_supported: - unblock_menuitem = gtk.ImageMenuItem(_('_Unblock')) - icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) - unblock_menuitem.set_image(icon) - unblock_menuitem.connect('activate', self.on_unblock, list_, group) - menu.append(unblock_menuitem) - else: - block_menuitem = gtk.ImageMenuItem(_('_Block')) - icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) - block_menuitem.set_image(icon) - block_menuitem.connect('activate', self.on_block, list_, group) - menu.append(block_menuitem) - if not gajim.connections[account].privacy_rules_supported: - block_menuitem.set_sensitive(False) - - # Remove group - remove_item = gtk.ImageMenuItem(_('_Remove')) - icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) - remove_item.set_image(icon) - menu.append(remove_item) - remove_item.connect('activate', self.on_remove_group_item_activated, - group, account) - - # unsensitive if account is not connected - if gajim.connections[account].connected < 2: - rename_item.set_sensitive(False) - - # General group cannot be changed - if group == _('General'): - rename_item.set_sensitive(False) - block_menuitem.set_sensitive(False) - remove_item.set_sensitive(False) - - event_button = gtkgui_helpers.get_possible_button_event(event) - - menu.attach_to_widget(self.tree, None) - menu.connect('selection-done', gtkgui_helpers.destroy_widget) - menu.show_all() - menu.popup(None, None, None, event_button, event.time) - - def make_contact_menu(self, event, titer): - """ - Make contact's popup menu - """ - model = self.modelfilter - jid = model[titer][C_JID].decode('utf-8') - tree_path = model.get_path(titer) - account = model[titer][C_ACCOUNT].decode('utf-8') - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - menu = gui_menu_builder.get_contact_menu(contact, account) - event_button = gtkgui_helpers.get_possible_button_event(event) - menu.attach_to_widget(self.tree, None) - menu.popup(None, None, None, event_button, event.time) - - def make_multiple_contact_menu(self, event, iters): - """ - Make group's popup menu - """ - model = self.modelfilter - list_ = [] # list of (jid, account) tuples - one_account_offline = False - is_blocked = True - privacy_rules_supported = True - for titer in iters: - jid = model[titer][C_JID].decode('utf-8') - account = model[titer][C_ACCOUNT].decode('utf-8') - if gajim.connections[account].connected < 2: - one_account_offline = True - if not gajim.connections[account].privacy_rules_supported: - privacy_rules_supported = False - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - if helpers.jid_is_blocked(account, jid): - is_blocked = False - list_.append((contact, account)) - - menu = gtk.Menu() - account = None - for (contact, current_account) in list_: - # check that we use the same account for every sender - if account is not None and account != current_account: - account = None - break - account = current_account - if account is not None: - send_group_message_item = gtk.ImageMenuItem(_('Send Group M_essage')) - icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) - send_group_message_item.set_image(icon) - menu.append(send_group_message_item) - send_group_message_item.connect('activate', - self.on_send_single_message_menuitem_activate, account, list_) - - # Invite to Groupchat - invite_item = gtk.ImageMenuItem(_('In_vite to')) - muc_icon = gtkgui_helpers.load_icon('muc_active') - if muc_icon: - invite_item.set_image(muc_icon) - - gui_menu_builder.build_invite_submenu(invite_item, list_) - menu.append(invite_item) - - item = gtk.SeparatorMenuItem() # separator - menu.append(item) - - # Manage Transport submenu - item = gtk.ImageMenuItem(_('_Manage Contacts')) - icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES, gtk.ICON_SIZE_MENU) - item.set_image(icon) - manage_contacts_submenu = gtk.Menu() - item.set_submenu(manage_contacts_submenu) - menu.append(item) - - # Edit Groups - edit_groups_item = gtk.ImageMenuItem(_('Edit _Groups')) - icon = gtk.image_new_from_stock(gtk.STOCK_EDIT, gtk.ICON_SIZE_MENU) - edit_groups_item.set_image(icon) - manage_contacts_submenu.append(edit_groups_item) - edit_groups_item.connect('activate', self.on_edit_groups, list_) - - item = gtk.SeparatorMenuItem() # separator - manage_contacts_submenu.append(item) - - # Block - if is_blocked and privacy_rules_supported: - unblock_menuitem = gtk.ImageMenuItem(_('_Unblock')) - icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) - unblock_menuitem.set_image(icon) - unblock_menuitem.connect('activate', self.on_unblock, list_) - manage_contacts_submenu.append(unblock_menuitem) - else: - block_menuitem = gtk.ImageMenuItem(_('_Block')) - icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) - block_menuitem.set_image(icon) - block_menuitem.connect('activate', self.on_block, list_) - manage_contacts_submenu.append(block_menuitem) - - if not privacy_rules_supported: - block_menuitem.set_sensitive(False) - - # Remove - remove_item = gtk.ImageMenuItem(_('_Remove')) - icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) - remove_item.set_image(icon) - manage_contacts_submenu.append(remove_item) - remove_item.connect('activate', self.on_req_usub, list_) - # unsensitive remove if one account is not connected - if one_account_offline: - remove_item.set_sensitive(False) - - event_button = gtkgui_helpers.get_possible_button_event(event) - - menu.attach_to_widget(self.tree, None) - menu.connect('selection-done', gtkgui_helpers.destroy_widget) - menu.show_all() - menu.popup(None, None, None, event_button, event.time) - - def make_transport_menu(self, event, titer): - """ - Make transport's popup menu - """ - model = self.modelfilter - jid = model[titer][C_JID].decode('utf-8') - path = model.get_path(titer) - account = model[titer][C_ACCOUNT].decode('utf-8') - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - menu = gtk.Menu() - - # Send single message - item = gtk.ImageMenuItem(_('Send Single Message')) - icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) - item.set_image(icon) - item.connect('activate', - self.on_send_single_message_menuitem_activate, account, contact) - menu.append(item) - - blocked = False - if helpers.jid_is_blocked(account, jid): - blocked = True - - # Send Custom Status - send_custom_status_menuitem = gtk.ImageMenuItem(_('Send Cus_tom Status')) - # add a special img for this menuitem - if blocked: - send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( - 'offline')) - send_custom_status_menuitem.set_sensitive(False) - else: - if account in gajim.interface.status_sent_to_users and \ - jid in gajim.interface.status_sent_to_users[account]: - send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( - gajim.interface.status_sent_to_users[account][jid])) - else: - icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, - gtk.ICON_SIZE_MENU) - send_custom_status_menuitem.set_image(icon) - status_menuitems = gtk.Menu() - send_custom_status_menuitem.set_submenu(status_menuitems) - iconset = gajim.config.get('iconset') - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'): - # icon MUST be different instance for every item - state_images = gtkgui_helpers.load_iconset(path) - status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s)) - status_menuitem.connect('activate', self.on_send_custom_status, - [(contact, account)], s) - icon = state_images[s] - status_menuitem.set_image(icon) - status_menuitems.append(status_menuitem) - menu.append(send_custom_status_menuitem) - - item = gtk.SeparatorMenuItem() # separator - menu.append(item) - - # Execute Command - item = gtk.ImageMenuItem(_('Execute Command...')) - icon = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU) - item.set_image(icon) - menu.append(item) - item.connect('activate', self.on_execute_command, contact, account, - contact.resource) - if gajim.account_is_disconnected(account): - item.set_sensitive(False) - - # Manage Transport submenu - item = gtk.ImageMenuItem(_('_Manage Transport')) - icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES, gtk.ICON_SIZE_MENU) - item.set_image(icon) - manage_transport_submenu = gtk.Menu() - item.set_submenu(manage_transport_submenu) - menu.append(item) - - # Modify Transport - item = gtk.ImageMenuItem(_('_Modify Transport')) - icon = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU) - item.set_image(icon) - manage_transport_submenu.append(item) - item.connect('activate', self.on_edit_agent, contact, account) - if gajim.account_is_disconnected(account): - item.set_sensitive(False) - - # Rename - item = gtk.ImageMenuItem(_('_Rename')) - # add a special img for rename menuitem - gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input') - manage_transport_submenu.append(item) - item.connect('activate', self.on_rename, 'agent', jid, account) - if gajim.account_is_disconnected(account): - item.set_sensitive(False) - - item = gtk.SeparatorMenuItem() # separator - manage_transport_submenu.append(item) - - # Block - if blocked: - item = gtk.ImageMenuItem(_('_Unblock')) - item.connect('activate', self.on_unblock, [(contact, account)]) - else: - item = gtk.ImageMenuItem(_('_Block')) - item.connect('activate', self.on_block, [(contact, account)]) - - icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) - item.set_image(icon) - manage_transport_submenu.append(item) - if gajim.account_is_disconnected(account): - item.set_sensitive(False) - - # Remove - item = gtk.ImageMenuItem(_('_Remove')) - icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) - item.set_image(icon) - manage_transport_submenu.append(item) - item.connect('activate', self.on_remove_agent, [(contact, account)]) - if gajim.account_is_disconnected(account): - item.set_sensitive(False) - - item = gtk.SeparatorMenuItem() # separator - menu.append(item) - - # Information - information_menuitem = gtk.ImageMenuItem(_('_Information')) - icon = gtk.image_new_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_MENU) - information_menuitem.set_image(icon) - menu.append(information_menuitem) - information_menuitem.connect('activate', self.on_info, contact, account) - - - event_button = gtkgui_helpers.get_possible_button_event(event) - - menu.attach_to_widget(self.tree, None) - menu.connect('selection-done', gtkgui_helpers.destroy_widget) - menu.show_all() - menu.popup(None, None, None, event_button, event.time) - - def make_groupchat_menu(self, event, titer): - model = self.modelfilter - - jid = model[titer][C_JID].decode('utf-8') - account = model[titer][C_ACCOUNT].decode('utf-8') - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - menu = gtk.Menu() - - if jid in gajim.interface.minimized_controls[account]: - maximize_menuitem = gtk.ImageMenuItem(_('_Maximize')) - icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP, gtk.ICON_SIZE_MENU) - maximize_menuitem.set_image(icon) - maximize_menuitem.connect('activate', self.on_groupchat_maximized, \ - jid, account) - menu.append(maximize_menuitem) - - if not gajim.gc_connected[account].get(jid, False): - connect_menuitem = gtk.ImageMenuItem(_('_Reconnect')) - connect_icon = gtk.image_new_from_stock(gtk.STOCK_CONNECT, \ - gtk.ICON_SIZE_MENU) - connect_menuitem.set_image(connect_icon) - connect_menuitem.connect('activate', self.on_reconnect, jid, account) - menu.append(connect_menuitem) - disconnect_menuitem = gtk.ImageMenuItem(_('_Disconnect')) - disconnect_icon = gtk.image_new_from_stock(gtk.STOCK_DISCONNECT, \ - gtk.ICON_SIZE_MENU) - disconnect_menuitem.set_image(disconnect_icon) - disconnect_menuitem.connect('activate', self.on_disconnect, jid, account) - menu.append(disconnect_menuitem) - - item = gtk.SeparatorMenuItem() # separator - menu.append(item) - - history_menuitem = gtk.ImageMenuItem(_('_History')) - history_icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL, \ - gtk.ICON_SIZE_MENU) - history_menuitem.set_image(history_icon) - history_menuitem .connect('activate', self.on_history, \ - contact, account) - menu.append(history_menuitem) - - event_button = gtkgui_helpers.get_possible_button_event(event) - - menu.attach_to_widget(self.tree, None) - menu.connect('selection-done', gtkgui_helpers.destroy_widget) - menu.show_all() - menu.popup(None, None, None, event_button, event.time) - - def get_and_connect_advanced_menuitem_menu(self, account): - """ - Add FOR ACCOUNT options - """ - xml = gtkgui_helpers.get_gtk_builder('advanced_menuitem_menu.ui') - advanced_menuitem_menu = xml.get_object('advanced_menuitem_menu') - - xml_console_menuitem = xml.get_object('xml_console_menuitem') - privacy_lists_menuitem = xml.get_object('privacy_lists_menuitem') - administrator_menuitem = xml.get_object('administrator_menuitem') - send_server_message_menuitem = xml.get_object( - 'send_server_message_menuitem') - set_motd_menuitem = xml.get_object('set_motd_menuitem') - update_motd_menuitem = xml.get_object('update_motd_menuitem') - delete_motd_menuitem = xml.get_object('delete_motd_menuitem') - - xml_console_menuitem.connect('activate', - self.on_xml_console_menuitem_activate, account) - - if gajim.connections[account] and gajim.connections[account].\ - privacy_rules_supported: - privacy_lists_menuitem.connect('activate', - self.on_privacy_lists_menuitem_activate, account) - else: - privacy_lists_menuitem.set_sensitive(False) - - if gajim.connections[account].is_zeroconf: - administrator_menuitem.set_sensitive(False) - send_server_message_menuitem.set_sensitive(False) - set_motd_menuitem.set_sensitive(False) - update_motd_menuitem.set_sensitive(False) - delete_motd_menuitem.set_sensitive(False) - else: - send_server_message_menuitem.connect('activate', - self.on_send_server_message_menuitem_activate, account) - - set_motd_menuitem.connect('activate', - self.on_set_motd_menuitem_activate, account) - - update_motd_menuitem.connect('activate', - self.on_update_motd_menuitem_activate, account) - - delete_motd_menuitem.connect('activate', - self.on_delete_motd_menuitem_activate, account) - - advanced_menuitem_menu.show_all() - - return advanced_menuitem_menu - - def add_history_manager_menuitem(self, menu): - """ - Add a seperator and History Manager menuitem BELOW for account menuitems - """ - item = gtk.SeparatorMenuItem() # separator - menu.append(item) - - # History manager - item = gtk.ImageMenuItem(_('History Manager')) - icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL, - gtk.ICON_SIZE_MENU) - item.set_image(icon) - menu.append(item) - item.connect('activate', self.on_history_manager_menuitem_activate) - - def add_bookmarks_list(self, gc_sub_menu, account): - """ - Show join new group chat item and bookmarks list for an account - """ - item = gtk.ImageMenuItem(_('_Join New Group Chat')) - icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) - item.set_image(icon) - item.connect('activate', self.on_join_gc_activate, account) - - gc_sub_menu.append(item) - - # User has at least one bookmark. - if gajim.connections[account].bookmarks: - item = gtk.SeparatorMenuItem() - gc_sub_menu.append(item) - - for bookmark in gajim.connections[account].bookmarks: - # Do not use underline. - item = gtk.MenuItem(bookmark['name'], False) - item.connect('activate', self.on_bookmark_menuitem_activate, - account, bookmark) - gc_sub_menu.append(item) - - def set_actions_menu_needs_rebuild(self): - self.actions_menu_needs_rebuild = True - - def show_appropriate_context_menu(self, event, iters): - # iters must be all of the same type - model = self.modelfilter - type_ = model[iters[0]][C_TYPE] - for titer in iters[1:]: - if model[titer][C_TYPE] != type_: - return - if type_ == 'group' and len(iters) == 1: - self.make_group_menu(event, iters[0]) - if type_ == 'groupchat' and len(iters) == 1: - self.make_groupchat_menu(event, iters[0]) - elif type_ == 'agent' and len(iters) == 1: - self.make_transport_menu(event, iters[0]) - elif type_ in ('contact', 'self_contact') and len(iters) == 1: - self.make_contact_menu(event, iters[0]) - elif type_ == 'contact': - self.make_multiple_contact_menu(event, iters) - elif type_ == 'account' and len(iters) == 1: - self.make_account_menu(event, iters[0]) - - def show_treeview_menu(self, event): - try: - model, list_of_paths = self.tree.get_selection().get_selected_rows() - except TypeError: - self.tree.get_selection().unselect_all() - return - if not len(list_of_paths): - # no row is selected - return - if len(list_of_paths) > 1: - iters = [] - for path in list_of_paths: - iters.append(model.get_iter(path)) - else: - path = list_of_paths[0] - iters = [model.get_iter(path)] - self.show_appropriate_context_menu(event, iters) - - return True - - def on_ctrl_j(self, accel_group, acceleratable, keyval, modifier): - """ - Bring up the conference join dialog, when CTRL+J accelerator is being - activated - """ - # find a connected account: - for account in gajim.connections: - if gajim.account_is_connected(account): - break - self.on_join_gc_activate(None, account) - return True + def make_menu(self, force=False): + """ + Create the main window's menus + """ + if not force and not self.actions_menu_needs_rebuild: + return + new_chat_menuitem = self.xml.get_object('new_chat_menuitem') + single_message_menuitem = self.xml.get_object( + 'send_single_message_menuitem') + join_gc_menuitem = self.xml.get_object('join_gc_menuitem') + muc_icon = gtkgui_helpers.load_icon('muc_active') + if muc_icon: + join_gc_menuitem.set_image(muc_icon) + add_new_contact_menuitem = self.xml.get_object('add_new_contact_menuitem') + service_disco_menuitem = self.xml.get_object('service_disco_menuitem') + advanced_menuitem = self.xml.get_object('advanced_menuitem') + profile_avatar_menuitem = self.xml.get_object('profile_avatar_menuitem') + + # destroy old advanced menus + for m in self.advanced_menus: + m.destroy() + + # make it sensitive. it is insensitive only if no accounts are *available* + advanced_menuitem.set_sensitive(True) + + if self.add_new_contact_handler_id: + add_new_contact_menuitem.handler_disconnect( + self.add_new_contact_handler_id) + self.add_new_contact_handler_id = None + + if self.service_disco_handler_id: + service_disco_menuitem.handler_disconnect( + self.service_disco_handler_id) + self.service_disco_handler_id = None + + if self.new_chat_menuitem_handler_id: + new_chat_menuitem.handler_disconnect( + self.new_chat_menuitem_handler_id) + self.new_chat_menuitem_handler_id = None + + if self.single_message_menuitem_handler_id: + single_message_menuitem.handler_disconnect( + self.single_message_menuitem_handler_id) + self.single_message_menuitem_handler_id = None + + if self.profile_avatar_menuitem_handler_id: + profile_avatar_menuitem.handler_disconnect( + self.profile_avatar_menuitem_handler_id) + self.profile_avatar_menuitem_handler_id = None + + # remove the existing submenus + add_new_contact_menuitem.remove_submenu() + service_disco_menuitem.remove_submenu() + join_gc_menuitem.remove_submenu() + single_message_menuitem.remove_submenu() + new_chat_menuitem.remove_submenu() + advanced_menuitem.remove_submenu() + profile_avatar_menuitem.remove_submenu() + + # remove the existing accelerator + if self.have_new_chat_accel: + ag = gtk.accel_groups_from_object(self.window)[0] + new_chat_menuitem.remove_accelerator(ag, gtk.keysyms.n, + gtk.gdk.CONTROL_MASK) + self.have_new_chat_accel = False + + gc_sub_menu = gtk.Menu() # gc is always a submenu + join_gc_menuitem.set_submenu(gc_sub_menu) + + connected_accounts = gajim.get_number_of_connected_accounts() + + connected_accounts_with_private_storage = 0 + + # items that get shown whether an account is zeroconf or not + accounts_list = sorted(gajim.contacts.get_accounts()) + if connected_accounts > 1: # 2 or more accounts? make submenus + new_chat_sub_menu = gtk.Menu() + + for account in accounts_list: + if gajim.connections[account].connected <= 1: + # if offline or connecting + continue + + # new chat + new_chat_item = gtk.MenuItem(_('using account %s') % account, + False) + new_chat_sub_menu.append(new_chat_item) + new_chat_item.connect('activate', + self.on_new_chat_menuitem_activate, account) + + new_chat_menuitem.set_submenu(new_chat_sub_menu) + new_chat_sub_menu.show_all() + + elif connected_accounts == 1: # user has only one account + for account in gajim.connections: + if gajim.account_is_connected(account): # THE connected account + # new chat + if not self.new_chat_menuitem_handler_id: + self.new_chat_menuitem_handler_id = new_chat_menuitem.\ + connect('activate', self.on_new_chat_menuitem_activate, + account) + + break + + # menu items that don't apply to zeroconf connections + if connected_accounts == 1 or (connected_accounts == 2 and \ + gajim.zeroconf_is_connected()): + # only one 'real' (non-zeroconf) account is connected, don't need submenus + + for account in accounts_list: + if gajim.account_is_connected(account) and \ + not gajim.config.get_per('accounts', account, 'is_zeroconf'): + # gc + if gajim.connections[account].private_storage_supported: + connected_accounts_with_private_storage += 1 + self.add_bookmarks_list(gc_sub_menu, account) + gc_sub_menu.show_all() + # add + if not self.add_new_contact_handler_id: + self.add_new_contact_handler_id =\ + add_new_contact_menuitem.connect( + 'activate', self.on_add_new_contact, account) + # disco + if not self.service_disco_handler_id: + self.service_disco_handler_id = service_disco_menuitem.\ + connect('activate', + self.on_service_disco_menuitem_activate, account) + + # single message + if not self.single_message_menuitem_handler_id: + self.single_message_menuitem_handler_id = \ + single_message_menuitem.connect('activate', \ + self.on_send_single_message_menuitem_activate, account) + + # new chat accel + if not self.have_new_chat_accel: + ag = gtk.accel_groups_from_object(self.window)[0] + new_chat_menuitem.add_accelerator('activate', ag, + gtk.keysyms.n, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) + self.have_new_chat_accel = True + + break # No other account connected + else: + # 2 or more 'real' accounts are connected, make submenus + single_message_sub_menu = gtk.Menu() + add_sub_menu = gtk.Menu() + disco_sub_menu = gtk.Menu() + + for account in accounts_list: + if gajim.connections[account].connected <= 1 or \ + gajim.config.get_per('accounts', account, 'is_zeroconf'): + # skip account if it's offline or connecting or is zeroconf + continue + + # single message + single_message_item = gtk.MenuItem(_('using account %s') % account, + False) + single_message_sub_menu.append(single_message_item) + single_message_item.connect('activate', + self.on_send_single_message_menuitem_activate, account) + + # join gc + if gajim.connections[account].private_storage_supported: + connected_accounts_with_private_storage += 1 + gc_item = gtk.MenuItem(_('using account %s') % account, False) + gc_sub_menu.append(gc_item) + gc_menuitem_menu = gtk.Menu() + self.add_bookmarks_list(gc_menuitem_menu, account) + gc_item.set_submenu(gc_menuitem_menu) + + # add + add_item = gtk.MenuItem(_('to %s account') % account, False) + add_sub_menu.append(add_item) + add_item.connect('activate', self.on_add_new_contact, account) + + # disco + disco_item = gtk.MenuItem(_('using %s account') % account, False) + disco_sub_menu.append(disco_item) + disco_item.connect('activate', + self.on_service_disco_menuitem_activate, account) + + single_message_menuitem.set_submenu(single_message_sub_menu) + single_message_sub_menu.show_all() + gc_sub_menu.show_all() + add_new_contact_menuitem.set_submenu(add_sub_menu) + add_sub_menu.show_all() + service_disco_menuitem.set_submenu(disco_sub_menu) + disco_sub_menu.show_all() + + if connected_accounts == 0: + # no connected accounts, make the menuitems insensitive + for item in (new_chat_menuitem, join_gc_menuitem,\ + add_new_contact_menuitem, service_disco_menuitem,\ + single_message_menuitem): + item.set_sensitive(False) + else: # we have one or more connected accounts + for item in (new_chat_menuitem, join_gc_menuitem, + add_new_contact_menuitem, service_disco_menuitem, + single_message_menuitem): + item.set_sensitive(True) + # disable some fields if only local account is there + if connected_accounts == 1: + for account in gajim.connections: + if gajim.account_is_connected(account) and \ + gajim.connections[account].is_zeroconf: + for item in (join_gc_menuitem, add_new_contact_menuitem, + service_disco_menuitem, single_message_menuitem): + item.set_sensitive(False) + + # Manage GC bookmarks + newitem = gtk.SeparatorMenuItem() # separator + gc_sub_menu.append(newitem) + + newitem = gtk.ImageMenuItem(_('_Manage Bookmarks...')) + img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, + gtk.ICON_SIZE_MENU) + newitem.set_image(img) + newitem.connect('activate', self.on_manage_bookmarks_menuitem_activate) + gc_sub_menu.append(newitem) + gc_sub_menu.show_all() + if connected_accounts_with_private_storage == 0: + newitem.set_sensitive(False) + + connected_accounts_with_vcard = [] + for account in gajim.connections: + if gajim.account_is_connected(account) and \ + gajim.connections[account].vcard_supported: + connected_accounts_with_vcard.append(account) + if len(connected_accounts_with_vcard) > 1: + # 2 or more accounts? make submenus + profile_avatar_sub_menu = gtk.Menu() + for account in connected_accounts_with_vcard: + # profile, avatar + profile_avatar_item = gtk.MenuItem(_('of account %s') % account, + False) + profile_avatar_sub_menu.append(profile_avatar_item) + profile_avatar_item.connect('activate', + self.on_profile_avatar_menuitem_activate, account) + profile_avatar_menuitem.set_submenu(profile_avatar_sub_menu) + profile_avatar_sub_menu.show_all() + elif len(connected_accounts_with_vcard) == 1: # user has only one account + account = connected_accounts_with_vcard[0] + # profile, avatar + if not self.profile_avatar_menuitem_handler_id: + self.profile_avatar_menuitem_handler_id = \ + profile_avatar_menuitem.connect('activate', + self.on_profile_avatar_menuitem_activate, account) + + if len(connected_accounts_with_vcard) == 0: + profile_avatar_menuitem.set_sensitive(False) + else: + profile_avatar_menuitem.set_sensitive(True) + + # Advanced Actions + if len(gajim.connections) == 0: # user has no accounts + advanced_menuitem.set_sensitive(False) + elif len(gajim.connections) == 1: # we have one acccount + account = gajim.connections.keys()[0] + advanced_menuitem_menu = self.get_and_connect_advanced_menuitem_menu( + account) + self.advanced_menus.append(advanced_menuitem_menu) + + self.add_history_manager_menuitem(advanced_menuitem_menu) + + advanced_menuitem.set_submenu(advanced_menuitem_menu) + advanced_menuitem_menu.show_all() + else: # user has *more* than one account : build advanced submenus + advanced_sub_menu = gtk.Menu() + accounts = [] # Put accounts in a list to sort them + for account in gajim.connections: + accounts.append(account) + accounts.sort() + for account in accounts: + advanced_item = gtk.MenuItem(_('for account %s') % account, False) + advanced_sub_menu.append(advanced_item) + advanced_menuitem_menu = \ + self.get_and_connect_advanced_menuitem_menu(account) + self.advanced_menus.append(advanced_menuitem_menu) + advanced_item.set_submenu(advanced_menuitem_menu) + + self.add_history_manager_menuitem(advanced_sub_menu) + + advanced_menuitem.set_submenu(advanced_sub_menu) + advanced_sub_menu.show_all() + + self.actions_menu_needs_rebuild = False + + def build_account_menu(self, account): + # we have to create our own set of icons for the menu + # using self.jabber_status_images is poopoo + iconset = gajim.config.get('iconset') + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + state_images = gtkgui_helpers.load_iconset(path) + + if not gajim.config.get_per('accounts', account, 'is_zeroconf'): + xml = gtkgui_helpers.get_gtk_builder('account_context_menu.ui') + account_context_menu = xml.get_object('account_context_menu') + + status_menuitem = xml.get_object('status_menuitem') + start_chat_menuitem = xml.get_object('start_chat_menuitem') + join_group_chat_menuitem = xml.get_object('join_group_chat_menuitem') + muc_icon = gtkgui_helpers.load_icon('muc_active') + if muc_icon: + join_group_chat_menuitem.set_image(muc_icon) + open_gmail_inbox_menuitem = xml.get_object('open_gmail_inbox_menuitem') + add_contact_menuitem = xml.get_object('add_contact_menuitem') + service_discovery_menuitem = xml.get_object( + 'service_discovery_menuitem') + execute_command_menuitem = xml.get_object('execute_command_menuitem') + edit_account_menuitem = xml.get_object('edit_account_menuitem') + sub_menu = gtk.Menu() + status_menuitem.set_submenu(sub_menu) + + for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'): + uf_show = helpers.get_uf_show(show, use_mnemonic=True) + item = gtk.ImageMenuItem(uf_show) + icon = state_images[show] + item.set_image(icon) + sub_menu.append(item) + con = gajim.connections[account] + if show == 'invisible' and con.connected > 1 and \ + not con.privacy_rules_supported: + item.set_sensitive(False) + else: + item.connect('activate', self.change_status, account, show) + + item = gtk.SeparatorMenuItem() + sub_menu.append(item) + + item = gtk.ImageMenuItem(_('_Change Status Message')) + gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input') + sub_menu.append(item) + item.connect('activate', self.on_change_status_message_activate, + account) + if gajim.connections[account].connected < 2: + item.set_sensitive(False) + + item = gtk.SeparatorMenuItem() + sub_menu.append(item) + + uf_show = helpers.get_uf_show('offline', use_mnemonic=True) + item = gtk.ImageMenuItem(uf_show) + icon = state_images['offline'] + item.set_image(icon) + sub_menu.append(item) + item.connect('activate', self.change_status, account, 'offline') + + pep_menuitem = xml.get_object('pep_menuitem') + if gajim.connections[account].pep_supported: + pep_submenu = gtk.Menu() + pep_menuitem.set_submenu(pep_submenu) + def add_item(label, opt_name, func): + item = gtk.CheckMenuItem(label) + pep_submenu.append(item) + if not dbus_support.supported: + item.set_sensitive(False) + else: + activ = gajim.config.get_per('accounts', account, opt_name) + item.set_active(activ) + item.connect('toggled', func, account) + + add_item(_('Publish Tune'), 'publish_tune', + self.on_publish_tune_toggled) + add_item(_('Publish Location'), 'publish_location', + self.on_publish_location_toggled) + + pep_config = gtk.ImageMenuItem(_('Configure Services...')) + item = gtk.SeparatorMenuItem() + pep_submenu.append(item) + pep_config.set_sensitive(True) + pep_submenu.append(pep_config) + pep_config.connect('activate', + self.on_pep_services_menuitem_activate, account) + img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, + gtk.ICON_SIZE_MENU) + pep_config.set_image(img) + + else: + pep_menuitem.set_sensitive(False) + + if not gajim.connections[account].gmail_url: + open_gmail_inbox_menuitem.set_no_show_all(True) + open_gmail_inbox_menuitem.hide() + else: + open_gmail_inbox_menuitem.connect('activate', + self.on_open_gmail_inbox, account) + + edit_account_menuitem.connect('activate', self.on_edit_account, + account) + add_contact_menuitem.connect('activate', self.on_add_new_contact, + account) + service_discovery_menuitem.connect('activate', + self.on_service_disco_menuitem_activate, account) + hostname = gajim.config.get_per('accounts', account, 'hostname') + contact = gajim.contacts.create_contact(jid=hostname, account=account) # Fake contact + execute_command_menuitem.connect('activate', + self.on_execute_command, contact, account) + + start_chat_menuitem.connect('activate', + self.on_new_chat_menuitem_activate, account) + + gc_sub_menu = gtk.Menu() # gc is always a submenu + join_group_chat_menuitem.set_submenu(gc_sub_menu) + self.add_bookmarks_list(gc_sub_menu, account) + + # make some items insensitive if account is offline + if gajim.connections[account].connected < 2: + for widget in (add_contact_menuitem, service_discovery_menuitem, + join_group_chat_menuitem, execute_command_menuitem, pep_menuitem, + start_chat_menuitem): + widget.set_sensitive(False) + else: + xml = gtkgui_helpers.get_gtk_builder('zeroconf_context_menu.ui') + account_context_menu = xml.get_object('zeroconf_context_menu') + + status_menuitem = xml.get_object('status_menuitem') + zeroconf_properties_menuitem = xml.get_object( + 'zeroconf_properties_menuitem') + sub_menu = gtk.Menu() + status_menuitem.set_submenu(sub_menu) + + for show in ('online', 'away', 'dnd', 'invisible'): + uf_show = helpers.get_uf_show(show, use_mnemonic=True) + item = gtk.ImageMenuItem(uf_show) + icon = state_images[show] + item.set_image(icon) + sub_menu.append(item) + item.connect('activate', self.change_status, account, show) + + item = gtk.SeparatorMenuItem() + sub_menu.append(item) + + item = gtk.ImageMenuItem(_('_Change Status Message')) + gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input') + sub_menu.append(item) + item.connect('activate', self.on_change_status_message_activate, + account) + if gajim.connections[account].connected < 2: + item.set_sensitive(False) + + uf_show = helpers.get_uf_show('offline', use_mnemonic=True) + item = gtk.ImageMenuItem(uf_show) + icon = state_images['offline'] + item.set_image(icon) + sub_menu.append(item) + item.connect('activate', self.change_status, account, 'offline') + + zeroconf_properties_menuitem.connect('activate', + self.on_zeroconf_properties, account) + + return account_context_menu + + def make_account_menu(self, event, titer): + """ + Make account's popup menu + """ + model = self.modelfilter + account = model[titer][C_ACCOUNT].decode('utf-8') + + if account != 'all': # not in merged mode + menu = self.build_account_menu(account) + else: + menu = gtk.Menu() + iconset = gajim.config.get('iconset') + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + accounts = [] # Put accounts in a list to sort them + for account in gajim.connections: + accounts.append(account) + accounts.sort() + for account in accounts: + state_images = gtkgui_helpers.load_iconset(path) + item = gtk.ImageMenuItem(account) + show = gajim.SHOW_LIST[gajim.connections[account].connected] + icon = state_images[show] + item.set_image(icon) + account_menu = self.build_account_menu(account) + item.set_submenu(account_menu) + menu.append(item) + + event_button = gtkgui_helpers.get_possible_button_event(event) + + menu.attach_to_widget(self.tree, None) + menu.connect('selection-done', gtkgui_helpers.destroy_widget) + menu.show_all() + menu.popup(None, None, None, event_button, event.time) + + def make_group_menu(self, event, titer): + """ + Make group's popup menu + """ + model = self.modelfilter + path = model.get_path(titer) + group = model[titer][C_JID].decode('utf-8') + account = model[titer][C_ACCOUNT].decode('utf-8') + + list_ = [] # list of (jid, account) tuples + list_online = [] # list of (jid, account) tuples + + group = model[titer][C_JID] + for jid in gajim.contacts.get_jid_list(account): + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + if group in contact.get_shown_groups(): + if contact.show not in ('offline', 'error'): + list_online.append((contact, account)) + list_.append((contact, account)) + menu = gtk.Menu() + + # Make special context menu if group is Groupchats + if group == _('Groupchats'): + maximize_menuitem = gtk.ImageMenuItem(_('_Maximize All')) + icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP, gtk.ICON_SIZE_MENU) + maximize_menuitem.set_image(icon) + maximize_menuitem.connect('activate', self.on_all_groupchat_maximized,\ + list_) + menu.append(maximize_menuitem) + else: + # Send Group Message + send_group_message_item = gtk.ImageMenuItem(_('Send Group M_essage')) + icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) + send_group_message_item.set_image(icon) + + send_group_message_submenu = gtk.Menu() + send_group_message_item.set_submenu(send_group_message_submenu) + menu.append(send_group_message_item) + + group_message_to_all_item = gtk.MenuItem(_('To all users')) + send_group_message_submenu.append(group_message_to_all_item) + + group_message_to_all_online_item = gtk.MenuItem( + _('To all online users')) + send_group_message_submenu.append(group_message_to_all_online_item) + + group_message_to_all_online_item.connect('activate', + self.on_send_single_message_menuitem_activate, account, list_online) + group_message_to_all_item.connect('activate', + self.on_send_single_message_menuitem_activate, account, list_) + + # Invite to + invite_menuitem = gtk.ImageMenuItem(_('In_vite to')) + muc_icon = gtkgui_helpers.load_icon('muc_active') + if muc_icon: + invite_menuitem.set_image(muc_icon) + + gui_menu_builder.build_invite_submenu(invite_menuitem, list_online) + menu.append(invite_menuitem) + + # Send Custom Status + send_custom_status_menuitem = gtk.ImageMenuItem( + _('Send Cus_tom Status')) + # add a special img for this menuitem + if helpers.group_is_blocked(account, group): + send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( + 'offline')) + send_custom_status_menuitem.set_sensitive(False) + else: + icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, + gtk.ICON_SIZE_MENU) + send_custom_status_menuitem.set_image(icon) + status_menuitems = gtk.Menu() + send_custom_status_menuitem.set_submenu(status_menuitems) + iconset = gajim.config.get('iconset') + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'): + # icon MUST be different instance for every item + state_images = gtkgui_helpers.load_iconset(path) + status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s)) + status_menuitem.connect('activate', self.on_send_custom_status, + list_, s, group) + icon = state_images[s] + status_menuitem.set_image(icon) + status_menuitems.append(status_menuitem) + menu.append(send_custom_status_menuitem) + + # there is no singlemessage and custom status for zeroconf + if gajim.config.get_per('accounts', account, 'is_zeroconf'): + send_custom_status_menuitem.set_sensitive(False) + send_group_message_item.set_sensitive(False) + + if not group in helpers.special_groups: + item = gtk.SeparatorMenuItem() # separator + menu.append(item) + + # Rename + rename_item = gtk.ImageMenuItem(_('Re_name')) + # add a special img for rename menuitem + gtkgui_helpers.add_image_to_menuitem(rename_item, 'gajim-kbd_input') + menu.append(rename_item) + rename_item.connect('activate', self.on_rename, 'group', group, + account) + + # Block group + is_blocked = False + if self.regroup: + for g_account in gajim.connections: + if helpers.group_is_blocked(g_account, group): + is_blocked = True + else: + if helpers.group_is_blocked(account, group): + is_blocked = True + + if is_blocked and gajim.connections[account].privacy_rules_supported: + unblock_menuitem = gtk.ImageMenuItem(_('_Unblock')) + icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) + unblock_menuitem.set_image(icon) + unblock_menuitem.connect('activate', self.on_unblock, list_, group) + menu.append(unblock_menuitem) + else: + block_menuitem = gtk.ImageMenuItem(_('_Block')) + icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) + block_menuitem.set_image(icon) + block_menuitem.connect('activate', self.on_block, list_, group) + menu.append(block_menuitem) + if not gajim.connections[account].privacy_rules_supported: + block_menuitem.set_sensitive(False) + + # Remove group + remove_item = gtk.ImageMenuItem(_('_Remove')) + icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) + remove_item.set_image(icon) + menu.append(remove_item) + remove_item.connect('activate', self.on_remove_group_item_activated, + group, account) + + # unsensitive if account is not connected + if gajim.connections[account].connected < 2: + rename_item.set_sensitive(False) + + # General group cannot be changed + if group == _('General'): + rename_item.set_sensitive(False) + block_menuitem.set_sensitive(False) + remove_item.set_sensitive(False) + + event_button = gtkgui_helpers.get_possible_button_event(event) + + menu.attach_to_widget(self.tree, None) + menu.connect('selection-done', gtkgui_helpers.destroy_widget) + menu.show_all() + menu.popup(None, None, None, event_button, event.time) + + def make_contact_menu(self, event, titer): + """ + Make contact's popup menu + """ + model = self.modelfilter + jid = model[titer][C_JID].decode('utf-8') + tree_path = model.get_path(titer) + account = model[titer][C_ACCOUNT].decode('utf-8') + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + menu = gui_menu_builder.get_contact_menu(contact, account) + event_button = gtkgui_helpers.get_possible_button_event(event) + menu.attach_to_widget(self.tree, None) + menu.popup(None, None, None, event_button, event.time) + + def make_multiple_contact_menu(self, event, iters): + """ + Make group's popup menu + """ + model = self.modelfilter + list_ = [] # list of (jid, account) tuples + one_account_offline = False + is_blocked = True + privacy_rules_supported = True + for titer in iters: + jid = model[titer][C_JID].decode('utf-8') + account = model[titer][C_ACCOUNT].decode('utf-8') + if gajim.connections[account].connected < 2: + one_account_offline = True + if not gajim.connections[account].privacy_rules_supported: + privacy_rules_supported = False + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + if helpers.jid_is_blocked(account, jid): + is_blocked = False + list_.append((contact, account)) + + menu = gtk.Menu() + account = None + for (contact, current_account) in list_: + # check that we use the same account for every sender + if account is not None and account != current_account: + account = None + break + account = current_account + if account is not None: + send_group_message_item = gtk.ImageMenuItem(_('Send Group M_essage')) + icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) + send_group_message_item.set_image(icon) + menu.append(send_group_message_item) + send_group_message_item.connect('activate', + self.on_send_single_message_menuitem_activate, account, list_) + + # Invite to Groupchat + invite_item = gtk.ImageMenuItem(_('In_vite to')) + muc_icon = gtkgui_helpers.load_icon('muc_active') + if muc_icon: + invite_item.set_image(muc_icon) + + gui_menu_builder.build_invite_submenu(invite_item, list_) + menu.append(invite_item) + + item = gtk.SeparatorMenuItem() # separator + menu.append(item) + + # Manage Transport submenu + item = gtk.ImageMenuItem(_('_Manage Contacts')) + icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES, gtk.ICON_SIZE_MENU) + item.set_image(icon) + manage_contacts_submenu = gtk.Menu() + item.set_submenu(manage_contacts_submenu) + menu.append(item) + + # Edit Groups + edit_groups_item = gtk.ImageMenuItem(_('Edit _Groups')) + icon = gtk.image_new_from_stock(gtk.STOCK_EDIT, gtk.ICON_SIZE_MENU) + edit_groups_item.set_image(icon) + manage_contacts_submenu.append(edit_groups_item) + edit_groups_item.connect('activate', self.on_edit_groups, list_) + + item = gtk.SeparatorMenuItem() # separator + manage_contacts_submenu.append(item) + + # Block + if is_blocked and privacy_rules_supported: + unblock_menuitem = gtk.ImageMenuItem(_('_Unblock')) + icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) + unblock_menuitem.set_image(icon) + unblock_menuitem.connect('activate', self.on_unblock, list_) + manage_contacts_submenu.append(unblock_menuitem) + else: + block_menuitem = gtk.ImageMenuItem(_('_Block')) + icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) + block_menuitem.set_image(icon) + block_menuitem.connect('activate', self.on_block, list_) + manage_contacts_submenu.append(block_menuitem) + + if not privacy_rules_supported: + block_menuitem.set_sensitive(False) + + # Remove + remove_item = gtk.ImageMenuItem(_('_Remove')) + icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) + remove_item.set_image(icon) + manage_contacts_submenu.append(remove_item) + remove_item.connect('activate', self.on_req_usub, list_) + # unsensitive remove if one account is not connected + if one_account_offline: + remove_item.set_sensitive(False) + + event_button = gtkgui_helpers.get_possible_button_event(event) + + menu.attach_to_widget(self.tree, None) + menu.connect('selection-done', gtkgui_helpers.destroy_widget) + menu.show_all() + menu.popup(None, None, None, event_button, event.time) + + def make_transport_menu(self, event, titer): + """ + Make transport's popup menu + """ + model = self.modelfilter + jid = model[titer][C_JID].decode('utf-8') + path = model.get_path(titer) + account = model[titer][C_ACCOUNT].decode('utf-8') + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + menu = gtk.Menu() + + # Send single message + item = gtk.ImageMenuItem(_('Send Single Message')) + icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) + item.set_image(icon) + item.connect('activate', + self.on_send_single_message_menuitem_activate, account, contact) + menu.append(item) + + blocked = False + if helpers.jid_is_blocked(account, jid): + blocked = True + + # Send Custom Status + send_custom_status_menuitem = gtk.ImageMenuItem(_('Send Cus_tom Status')) + # add a special img for this menuitem + if blocked: + send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( + 'offline')) + send_custom_status_menuitem.set_sensitive(False) + else: + if account in gajim.interface.status_sent_to_users and \ + jid in gajim.interface.status_sent_to_users[account]: + send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( + gajim.interface.status_sent_to_users[account][jid])) + else: + icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, + gtk.ICON_SIZE_MENU) + send_custom_status_menuitem.set_image(icon) + status_menuitems = gtk.Menu() + send_custom_status_menuitem.set_submenu(status_menuitems) + iconset = gajim.config.get('iconset') + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'): + # icon MUST be different instance for every item + state_images = gtkgui_helpers.load_iconset(path) + status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s)) + status_menuitem.connect('activate', self.on_send_custom_status, + [(contact, account)], s) + icon = state_images[s] + status_menuitem.set_image(icon) + status_menuitems.append(status_menuitem) + menu.append(send_custom_status_menuitem) + + item = gtk.SeparatorMenuItem() # separator + menu.append(item) + + # Execute Command + item = gtk.ImageMenuItem(_('Execute Command...')) + icon = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU) + item.set_image(icon) + menu.append(item) + item.connect('activate', self.on_execute_command, contact, account, + contact.resource) + if gajim.account_is_disconnected(account): + item.set_sensitive(False) + + # Manage Transport submenu + item = gtk.ImageMenuItem(_('_Manage Transport')) + icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES, gtk.ICON_SIZE_MENU) + item.set_image(icon) + manage_transport_submenu = gtk.Menu() + item.set_submenu(manage_transport_submenu) + menu.append(item) + + # Modify Transport + item = gtk.ImageMenuItem(_('_Modify Transport')) + icon = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU) + item.set_image(icon) + manage_transport_submenu.append(item) + item.connect('activate', self.on_edit_agent, contact, account) + if gajim.account_is_disconnected(account): + item.set_sensitive(False) + + # Rename + item = gtk.ImageMenuItem(_('_Rename')) + # add a special img for rename menuitem + gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input') + manage_transport_submenu.append(item) + item.connect('activate', self.on_rename, 'agent', jid, account) + if gajim.account_is_disconnected(account): + item.set_sensitive(False) + + item = gtk.SeparatorMenuItem() # separator + manage_transport_submenu.append(item) + + # Block + if blocked: + item = gtk.ImageMenuItem(_('_Unblock')) + item.connect('activate', self.on_unblock, [(contact, account)]) + else: + item = gtk.ImageMenuItem(_('_Block')) + item.connect('activate', self.on_block, [(contact, account)]) + + icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) + item.set_image(icon) + manage_transport_submenu.append(item) + if gajim.account_is_disconnected(account): + item.set_sensitive(False) + + # Remove + item = gtk.ImageMenuItem(_('_Remove')) + icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) + item.set_image(icon) + manage_transport_submenu.append(item) + item.connect('activate', self.on_remove_agent, [(contact, account)]) + if gajim.account_is_disconnected(account): + item.set_sensitive(False) + + item = gtk.SeparatorMenuItem() # separator + menu.append(item) + + # Information + information_menuitem = gtk.ImageMenuItem(_('_Information')) + icon = gtk.image_new_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_MENU) + information_menuitem.set_image(icon) + menu.append(information_menuitem) + information_menuitem.connect('activate', self.on_info, contact, account) + + + event_button = gtkgui_helpers.get_possible_button_event(event) + + menu.attach_to_widget(self.tree, None) + menu.connect('selection-done', gtkgui_helpers.destroy_widget) + menu.show_all() + menu.popup(None, None, None, event_button, event.time) + + def make_groupchat_menu(self, event, titer): + model = self.modelfilter + + jid = model[titer][C_JID].decode('utf-8') + account = model[titer][C_ACCOUNT].decode('utf-8') + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + menu = gtk.Menu() + + if jid in gajim.interface.minimized_controls[account]: + maximize_menuitem = gtk.ImageMenuItem(_('_Maximize')) + icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP, gtk.ICON_SIZE_MENU) + maximize_menuitem.set_image(icon) + maximize_menuitem.connect('activate', self.on_groupchat_maximized, \ + jid, account) + menu.append(maximize_menuitem) + + if not gajim.gc_connected[account].get(jid, False): + connect_menuitem = gtk.ImageMenuItem(_('_Reconnect')) + connect_icon = gtk.image_new_from_stock(gtk.STOCK_CONNECT, \ + gtk.ICON_SIZE_MENU) + connect_menuitem.set_image(connect_icon) + connect_menuitem.connect('activate', self.on_reconnect, jid, account) + menu.append(connect_menuitem) + disconnect_menuitem = gtk.ImageMenuItem(_('_Disconnect')) + disconnect_icon = gtk.image_new_from_stock(gtk.STOCK_DISCONNECT, \ + gtk.ICON_SIZE_MENU) + disconnect_menuitem.set_image(disconnect_icon) + disconnect_menuitem.connect('activate', self.on_disconnect, jid, account) + menu.append(disconnect_menuitem) + + item = gtk.SeparatorMenuItem() # separator + menu.append(item) + + history_menuitem = gtk.ImageMenuItem(_('_History')) + history_icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL, \ + gtk.ICON_SIZE_MENU) + history_menuitem.set_image(history_icon) + history_menuitem .connect('activate', self.on_history, \ + contact, account) + menu.append(history_menuitem) + + event_button = gtkgui_helpers.get_possible_button_event(event) + + menu.attach_to_widget(self.tree, None) + menu.connect('selection-done', gtkgui_helpers.destroy_widget) + menu.show_all() + menu.popup(None, None, None, event_button, event.time) + + def get_and_connect_advanced_menuitem_menu(self, account): + """ + Add FOR ACCOUNT options + """ + xml = gtkgui_helpers.get_gtk_builder('advanced_menuitem_menu.ui') + advanced_menuitem_menu = xml.get_object('advanced_menuitem_menu') + + xml_console_menuitem = xml.get_object('xml_console_menuitem') + privacy_lists_menuitem = xml.get_object('privacy_lists_menuitem') + administrator_menuitem = xml.get_object('administrator_menuitem') + send_server_message_menuitem = xml.get_object( + 'send_server_message_menuitem') + set_motd_menuitem = xml.get_object('set_motd_menuitem') + update_motd_menuitem = xml.get_object('update_motd_menuitem') + delete_motd_menuitem = xml.get_object('delete_motd_menuitem') + + xml_console_menuitem.connect('activate', + self.on_xml_console_menuitem_activate, account) + + if gajim.connections[account] and gajim.connections[account].\ + privacy_rules_supported: + privacy_lists_menuitem.connect('activate', + self.on_privacy_lists_menuitem_activate, account) + else: + privacy_lists_menuitem.set_sensitive(False) + + if gajim.connections[account].is_zeroconf: + administrator_menuitem.set_sensitive(False) + send_server_message_menuitem.set_sensitive(False) + set_motd_menuitem.set_sensitive(False) + update_motd_menuitem.set_sensitive(False) + delete_motd_menuitem.set_sensitive(False) + else: + send_server_message_menuitem.connect('activate', + self.on_send_server_message_menuitem_activate, account) + + set_motd_menuitem.connect('activate', + self.on_set_motd_menuitem_activate, account) + + update_motd_menuitem.connect('activate', + self.on_update_motd_menuitem_activate, account) + + delete_motd_menuitem.connect('activate', + self.on_delete_motd_menuitem_activate, account) + + advanced_menuitem_menu.show_all() + + return advanced_menuitem_menu + + def add_history_manager_menuitem(self, menu): + """ + Add a seperator and History Manager menuitem BELOW for account menuitems + """ + item = gtk.SeparatorMenuItem() # separator + menu.append(item) + + # History manager + item = gtk.ImageMenuItem(_('History Manager')) + icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL, + gtk.ICON_SIZE_MENU) + item.set_image(icon) + menu.append(item) + item.connect('activate', self.on_history_manager_menuitem_activate) + + def add_bookmarks_list(self, gc_sub_menu, account): + """ + Show join new group chat item and bookmarks list for an account + """ + item = gtk.ImageMenuItem(_('_Join New Group Chat')) + icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) + item.set_image(icon) + item.connect('activate', self.on_join_gc_activate, account) + + gc_sub_menu.append(item) + + # User has at least one bookmark. + if gajim.connections[account].bookmarks: + item = gtk.SeparatorMenuItem() + gc_sub_menu.append(item) + + for bookmark in gajim.connections[account].bookmarks: + # Do not use underline. + item = gtk.MenuItem(bookmark['name'], False) + item.connect('activate', self.on_bookmark_menuitem_activate, + account, bookmark) + gc_sub_menu.append(item) + + def set_actions_menu_needs_rebuild(self): + self.actions_menu_needs_rebuild = True + + def show_appropriate_context_menu(self, event, iters): + # iters must be all of the same type + model = self.modelfilter + type_ = model[iters[0]][C_TYPE] + for titer in iters[1:]: + if model[titer][C_TYPE] != type_: + return + if type_ == 'group' and len(iters) == 1: + self.make_group_menu(event, iters[0]) + if type_ == 'groupchat' and len(iters) == 1: + self.make_groupchat_menu(event, iters[0]) + elif type_ == 'agent' and len(iters) == 1: + self.make_transport_menu(event, iters[0]) + elif type_ in ('contact', 'self_contact') and len(iters) == 1: + self.make_contact_menu(event, iters[0]) + elif type_ == 'contact': + self.make_multiple_contact_menu(event, iters) + elif type_ == 'account' and len(iters) == 1: + self.make_account_menu(event, iters[0]) + + def show_treeview_menu(self, event): + try: + model, list_of_paths = self.tree.get_selection().get_selected_rows() + except TypeError: + self.tree.get_selection().unselect_all() + return + if not len(list_of_paths): + # no row is selected + return + if len(list_of_paths) > 1: + iters = [] + for path in list_of_paths: + iters.append(model.get_iter(path)) + else: + path = list_of_paths[0] + iters = [model.get_iter(path)] + self.show_appropriate_context_menu(event, iters) + + return True + + def on_ctrl_j(self, accel_group, acceleratable, keyval, modifier): + """ + Bring up the conference join dialog, when CTRL+J accelerator is being + activated + """ + # find a connected account: + for account in gajim.connections: + if gajim.account_is_connected(account): + break + self.on_join_gc_activate(None, account) + return True ################################################################################ ### ################################################################################ - def __init__(self): - self.filtering = False - self.xml = gtkgui_helpers.get_gtk_builder('roster_window.ui') - self.window = self.xml.get_object('roster_window') - self.hpaned = self.xml.get_object('roster_hpaned') - gajim.interface.msg_win_mgr = MessageWindowMgr(self.window, self.hpaned) - gajim.interface.msg_win_mgr.connect('window-delete', - self.on_message_window_delete) - self.advanced_menus = [] # We keep them to destroy them - if gajim.config.get('roster_window_skip_taskbar'): - self.window.set_property('skip-taskbar-hint', True) - self.tree = self.xml.get_object('roster_treeview') - sel = self.tree.get_selection() - sel.set_mode(gtk.SELECTION_MULTIPLE) - #sel.connect('changed', - # self.on_treeview_selection_changed) + def __init__(self): + self.filtering = False + self.xml = gtkgui_helpers.get_gtk_builder('roster_window.ui') + self.window = self.xml.get_object('roster_window') + self.hpaned = self.xml.get_object('roster_hpaned') + gajim.interface.msg_win_mgr = MessageWindowMgr(self.window, self.hpaned) + gajim.interface.msg_win_mgr.connect('window-delete', + self.on_message_window_delete) + self.advanced_menus = [] # We keep them to destroy them + if gajim.config.get('roster_window_skip_taskbar'): + self.window.set_property('skip-taskbar-hint', True) + self.tree = self.xml.get_object('roster_treeview') + sel = self.tree.get_selection() + sel.set_mode(gtk.SELECTION_MULTIPLE) + #sel.connect('changed', + # self.on_treeview_selection_changed) - self._last_selected_contact = [] # holds a list of (jid, account) tupples - self.transports_state_images = {'16': {}, '32': {}, 'opened': {}, - 'closed': {}} + self._last_selected_contact = [] # holds a list of (jid, account) tupples + self.transports_state_images = {'16': {}, '32': {}, 'opened': {}, + 'closed': {}} - self.last_save_dir = None - self.editing_path = None # path of row with cell in edit mode - self.add_new_contact_handler_id = False - self.service_disco_handler_id = False - self.new_chat_menuitem_handler_id = False - self.single_message_menuitem_handler_id = False - self.profile_avatar_menuitem_handler_id = False - self.actions_menu_needs_rebuild = True - self.regroup = gajim.config.get('mergeaccounts') - self.clicked_path = None # Used remember on wich row we clicked - if len(gajim.connections) < 2: # Do not merge accounts if only one exists - self.regroup = False - #FIXME: When list_accel_closures will be wrapped in pygtk - # no need of this variable - self.have_new_chat_accel = False # Is the "Ctrl+N" shown ? - gtkgui_helpers.resize_window(self.window, - gajim.config.get('roster_width'), - gajim.config.get('roster_height')) - gtkgui_helpers.move_window(self.window, - gajim.config.get('roster_x-position'), - gajim.config.get('roster_y-position')) + self.last_save_dir = None + self.editing_path = None # path of row with cell in edit mode + self.add_new_contact_handler_id = False + self.service_disco_handler_id = False + self.new_chat_menuitem_handler_id = False + self.single_message_menuitem_handler_id = False + self.profile_avatar_menuitem_handler_id = False + self.actions_menu_needs_rebuild = True + self.regroup = gajim.config.get('mergeaccounts') + self.clicked_path = None # Used remember on wich row we clicked + if len(gajim.connections) < 2: # Do not merge accounts if only one exists + self.regroup = False + #FIXME: When list_accel_closures will be wrapped in pygtk + # no need of this variable + self.have_new_chat_accel = False # Is the "Ctrl+N" shown ? + gtkgui_helpers.resize_window(self.window, + gajim.config.get('roster_width'), + gajim.config.get('roster_height')) + gtkgui_helpers.move_window(self.window, + gajim.config.get('roster_x-position'), + gajim.config.get('roster_y-position')) - self.popups_notification_height = 0 - self.popup_notification_windows = [] + self.popups_notification_height = 0 + self.popup_notification_windows = [] - # Remove contact from roster when last event opened - # { (contact, account): { backend: boolean } - self.contacts_to_be_removed = {} - gajim.events.event_removed_subscribe(self.on_event_removed) + # Remove contact from roster when last event opened + # { (contact, account): { backend: boolean } + self.contacts_to_be_removed = {} + gajim.events.event_removed_subscribe(self.on_event_removed) - # when this value become 0 we quit main application. If it's more than 0 - # it means we are waiting for this number of accounts to disconnect before - # quitting - self.quit_on_next_offline = -1 + # when this value become 0 we quit main application. If it's more than 0 + # it means we are waiting for this number of accounts to disconnect before + # quitting + self.quit_on_next_offline = -1 - # uf_show, img, show, sensitive - liststore = gtk.ListStore(str, gtk.Image, str, bool) - self.status_combobox = self.xml.get_object('status_combobox') + # uf_show, img, show, sensitive + liststore = gtk.ListStore(str, gtk.Image, str, bool) + self.status_combobox = self.xml.get_object('status_combobox') - cell = cell_renderer_image.CellRendererImage(0, 1) - self.status_combobox.pack_start(cell, False) + cell = cell_renderer_image.CellRendererImage(0, 1) + self.status_combobox.pack_start(cell, False) - # img to show is in in 2nd column of liststore - self.status_combobox.add_attribute(cell, 'image', 1) - # if it will be sensitive or not it is in the fourth column - # all items in the 'row' must have sensitive to False - # if we want False (so we add it for img_cell too) - self.status_combobox.add_attribute(cell, 'sensitive', 3) + # img to show is in in 2nd column of liststore + self.status_combobox.add_attribute(cell, 'image', 1) + # if it will be sensitive or not it is in the fourth column + # all items in the 'row' must have sensitive to False + # if we want False (so we add it for img_cell too) + self.status_combobox.add_attribute(cell, 'sensitive', 3) - cell = gtk.CellRendererText() - cell.set_property('xpad', 5) # padding for status text - self.status_combobox.pack_start(cell, True) - # text to show is in in first column of liststore - self.status_combobox.add_attribute(cell, 'text', 0) - # if it will be sensitive or not it is in the fourth column - self.status_combobox.add_attribute(cell, 'sensitive', 3) + cell = gtk.CellRendererText() + cell.set_property('xpad', 5) # padding for status text + self.status_combobox.pack_start(cell, True) + # text to show is in in first column of liststore + self.status_combobox.add_attribute(cell, 'text', 0) + # if it will be sensitive or not it is in the fourth column + self.status_combobox.add_attribute(cell, 'sensitive', 3) - self.status_combobox.set_row_separator_func(self._iter_is_separator) + self.status_combobox.set_row_separator_func(self._iter_is_separator) - for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'): - uf_show = helpers.get_uf_show(show) - liststore.append([uf_show, gajim.interface.jabber_state_images['16'][ - show], show, True]) - # Add a Separator (self._iter_is_separator() checks on string SEPARATOR) - liststore.append(['SEPARATOR', None, '', True]) + for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'): + uf_show = helpers.get_uf_show(show) + liststore.append([uf_show, gajim.interface.jabber_state_images['16'][ + show], show, True]) + # Add a Separator (self._iter_is_separator() checks on string SEPARATOR) + liststore.append(['SEPARATOR', None, '', True]) - path = gtkgui_helpers.get_icon_path('gajim-kbd_input') - img = gtk.Image() - img.set_from_file(path) - # sensitivity to False because by default we're offline - self.status_message_menuitem_iter = liststore.append( - [_('Change Status Message...'), img, '', False]) - # Add a Separator (self._iter_is_separator() checks on string SEPARATOR) - liststore.append(['SEPARATOR', None, '', True]) + path = gtkgui_helpers.get_icon_path('gajim-kbd_input') + img = gtk.Image() + img.set_from_file(path) + # sensitivity to False because by default we're offline + self.status_message_menuitem_iter = liststore.append( + [_('Change Status Message...'), img, '', False]) + # Add a Separator (self._iter_is_separator() checks on string SEPARATOR) + liststore.append(['SEPARATOR', None, '', True]) - uf_show = helpers.get_uf_show('offline') - liststore.append([uf_show, gajim.interface.jabber_state_images['16'][ - 'offline'], 'offline', True]) + uf_show = helpers.get_uf_show('offline') + liststore.append([uf_show, gajim.interface.jabber_state_images['16'][ + 'offline'], 'offline', True]) - status_combobox_items = ['online', 'chat', 'away', 'xa', 'dnd', - 'invisible', 'separator1', 'change_status_msg', 'separator2', - 'offline'] - self.status_combobox.set_model(liststore) + status_combobox_items = ['online', 'chat', 'away', 'xa', 'dnd', + 'invisible', 'separator1', 'change_status_msg', 'separator2', + 'offline'] + self.status_combobox.set_model(liststore) - # default to offline - number_of_menuitem = status_combobox_items.index('offline') - self.status_combobox.set_active(number_of_menuitem) + # default to offline + number_of_menuitem = status_combobox_items.index('offline') + self.status_combobox.set_active(number_of_menuitem) - # holds index to previously selected item so if "change status message..." - # is selected we can fallback to previously selected item and not stay - # with that item selected - self.previous_status_combobox_active = number_of_menuitem + # holds index to previously selected item so if "change status message..." + # is selected we can fallback to previously selected item and not stay + # with that item selected + self.previous_status_combobox_active = number_of_menuitem - showOffline = gajim.config.get('showoffline') - showOnlyChatAndOnline = gajim.config.get('show_only_chat_and_online') + showOffline = gajim.config.get('showoffline') + showOnlyChatAndOnline = gajim.config.get('show_only_chat_and_online') - w = self.xml.get_object('show_offline_contacts_menuitem') - w.set_active(showOffline) - if showOnlyChatAndOnline: - w.set_sensitive(False) + w = self.xml.get_object('show_offline_contacts_menuitem') + w.set_active(showOffline) + if showOnlyChatAndOnline: + w.set_sensitive(False) - w = self.xml.get_object('show_only_active_contacts_menuitem') - w.set_active(showOnlyChatAndOnline) - if showOffline: - w.set_sensitive(False) + w = self.xml.get_object('show_only_active_contacts_menuitem') + w.set_active(showOnlyChatAndOnline) + if showOffline: + w.set_sensitive(False) - show_transports_group = gajim.config.get('show_transports_group') - self.xml.get_object('show_transports_menuitem').set_active( - show_transports_group) + show_transports_group = gajim.config.get('show_transports_group') + self.xml.get_object('show_transports_menuitem').set_active( + show_transports_group) - self.xml.get_object('show_roster_menuitem').set_active(True) + self.xml.get_object('show_roster_menuitem').set_active(True) - # columns + # columns - # this col has 3 cells: - # first one img, second one text, third is sec pixbuf - col = gtk.TreeViewColumn() + # this col has 3 cells: + # first one img, second one text, third is sec pixbuf + col = gtk.TreeViewColumn() - def add_avatar_renderer(): - render_pixbuf = gtk.CellRendererPixbuf() # avatar img - col.pack_start(render_pixbuf, expand=False) - col.add_attribute(render_pixbuf, 'pixbuf', C_AVATAR_PIXBUF) - col.set_cell_data_func(render_pixbuf, - self._fill_avatar_pixbuf_renderer, None) + def add_avatar_renderer(): + render_pixbuf = gtk.CellRendererPixbuf() # avatar img + col.pack_start(render_pixbuf, expand=False) + col.add_attribute(render_pixbuf, 'pixbuf', C_AVATAR_PIXBUF) + col.set_cell_data_func(render_pixbuf, + self._fill_avatar_pixbuf_renderer, None) - if gajim.config.get('avatar_position_in_roster') == 'left': - add_avatar_renderer() + if gajim.config.get('avatar_position_in_roster') == 'left': + add_avatar_renderer() - render_image = cell_renderer_image.CellRendererImage(0, 0) - # show img or +- - col.pack_start(render_image, expand=False) - col.add_attribute(render_image, 'image', C_IMG) - col.set_cell_data_func(render_image, self._iconCellDataFunc, None) + render_image = cell_renderer_image.CellRendererImage(0, 0) + # show img or +- + col.pack_start(render_image, expand=False) + col.add_attribute(render_image, 'image', C_IMG) + col.set_cell_data_func(render_image, self._iconCellDataFunc, None) - render_text = gtk.CellRendererText() # contact or group or account name - render_text.set_property('ellipsize', pango.ELLIPSIZE_END) - col.pack_start(render_text, expand=True) - col.add_attribute(render_text, 'markup', C_NAME) # where we hold the name - col.set_cell_data_func(render_text, self._nameCellDataFunc, None) + render_text = gtk.CellRendererText() # contact or group or account name + render_text.set_property('ellipsize', pango.ELLIPSIZE_END) + col.pack_start(render_text, expand=True) + col.add_attribute(render_text, 'markup', C_NAME) # where we hold the name + col.set_cell_data_func(render_text, self._nameCellDataFunc, None) - render_pixbuf = gtk.CellRendererPixbuf() - col.pack_start(render_pixbuf, expand=False) - col.add_attribute(render_pixbuf, 'pixbuf', C_MOOD_PIXBUF) - col.set_cell_data_func(render_pixbuf, - self._fill_pep_pixbuf_renderer, C_MOOD_PIXBUF) + render_pixbuf = gtk.CellRendererPixbuf() + col.pack_start(render_pixbuf, expand=False) + col.add_attribute(render_pixbuf, 'pixbuf', C_MOOD_PIXBUF) + col.set_cell_data_func(render_pixbuf, + self._fill_pep_pixbuf_renderer, C_MOOD_PIXBUF) - render_pixbuf = gtk.CellRendererPixbuf() - col.pack_start(render_pixbuf, expand=False) - col.add_attribute(render_pixbuf, 'pixbuf', C_ACTIVITY_PIXBUF) - col.set_cell_data_func(render_pixbuf, - self._fill_pep_pixbuf_renderer, C_ACTIVITY_PIXBUF) + render_pixbuf = gtk.CellRendererPixbuf() + col.pack_start(render_pixbuf, expand=False) + col.add_attribute(render_pixbuf, 'pixbuf', C_ACTIVITY_PIXBUF) + col.set_cell_data_func(render_pixbuf, + self._fill_pep_pixbuf_renderer, C_ACTIVITY_PIXBUF) - render_pixbuf = gtk.CellRendererPixbuf() - col.pack_start(render_pixbuf, expand=False) - col.add_attribute(render_pixbuf, 'pixbuf', C_TUNE_PIXBUF) - col.set_cell_data_func(render_pixbuf, - self._fill_pep_pixbuf_renderer, C_TUNE_PIXBUF) + render_pixbuf = gtk.CellRendererPixbuf() + col.pack_start(render_pixbuf, expand=False) + col.add_attribute(render_pixbuf, 'pixbuf', C_TUNE_PIXBUF) + col.set_cell_data_func(render_pixbuf, + self._fill_pep_pixbuf_renderer, C_TUNE_PIXBUF) - render_pixbuf = gtk.CellRendererPixbuf() - col.pack_start(render_pixbuf, expand=False) - col.add_attribute(render_pixbuf, 'pixbuf', C_LOCATION_PIXBUF) - col.set_cell_data_func(render_pixbuf, - self._fill_pep_pixbuf_renderer, C_LOCATION_PIXBUF) + render_pixbuf = gtk.CellRendererPixbuf() + col.pack_start(render_pixbuf, expand=False) + col.add_attribute(render_pixbuf, 'pixbuf', C_LOCATION_PIXBUF) + col.set_cell_data_func(render_pixbuf, + self._fill_pep_pixbuf_renderer, C_LOCATION_PIXBUF) - self._pep_type_to_model_column = {'mood': C_MOOD_PIXBUF, - 'activity': C_ACTIVITY_PIXBUF, - 'tune': C_TUNE_PIXBUF, - 'location': C_LOCATION_PIXBUF} + self._pep_type_to_model_column = {'mood': C_MOOD_PIXBUF, + 'activity': C_ACTIVITY_PIXBUF, + 'tune': C_TUNE_PIXBUF, + 'location': C_LOCATION_PIXBUF} - if gajim.config.get('avatar_position_in_roster') == 'right': - add_avatar_renderer() + if gajim.config.get('avatar_position_in_roster') == 'right': + add_avatar_renderer() - render_pixbuf = gtk.CellRendererPixbuf() # tls/ssl img - col.pack_start(render_pixbuf, expand=False) - col.add_attribute(render_pixbuf, 'pixbuf', C_PADLOCK_PIXBUF) - col.set_cell_data_func(render_pixbuf, - self._fill_padlock_pixbuf_renderer, None) - self.tree.append_column(col) + render_pixbuf = gtk.CellRendererPixbuf() # tls/ssl img + col.pack_start(render_pixbuf, expand=False) + col.add_attribute(render_pixbuf, 'pixbuf', C_PADLOCK_PIXBUF) + col.set_cell_data_func(render_pixbuf, + self._fill_padlock_pixbuf_renderer, None) + self.tree.append_column(col) - # do not show gtk arrows workaround - col = gtk.TreeViewColumn() - render_pixbuf = gtk.CellRendererPixbuf() - col.pack_start(render_pixbuf, expand=False) - self.tree.append_column(col) - col.set_visible(False) - self.tree.set_expander_column(col) + # do not show gtk arrows workaround + col = gtk.TreeViewColumn() + render_pixbuf = gtk.CellRendererPixbuf() + col.pack_start(render_pixbuf, expand=False) + self.tree.append_column(col) + col.set_visible(False) + self.tree.set_expander_column(col) - # set search function - self.tree.set_search_equal_func(self._search_roster_func) + # set search function + self.tree.set_search_equal_func(self._search_roster_func) - # signals - self.TARGET_TYPE_URI_LIST = 80 - TARGETS = [('MY_TREE_MODEL_ROW', - gtk.TARGET_SAME_APP | gtk.TARGET_SAME_WIDGET, 0)] - TARGETS2 = [('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0), - ('text/uri-list', 0, self.TARGET_TYPE_URI_LIST)] - self.tree.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, TARGETS, - gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE | gtk.gdk.ACTION_COPY) - self.tree.enable_model_drag_dest(TARGETS2, gtk.gdk.ACTION_DEFAULT) - self.tree.connect('drag_begin', self.drag_begin) - self.tree.connect('drag_end', self.drag_end) - self.tree.connect('drag_drop', self.drag_drop) - self.tree.connect('drag_data_get', self.drag_data_get_data) - self.tree.connect('drag_data_received', self.drag_data_received_data) - self.dragging = False - self.xml.connect_signals(self) - self.combobox_callback_active = True + # signals + self.TARGET_TYPE_URI_LIST = 80 + TARGETS = [('MY_TREE_MODEL_ROW', + gtk.TARGET_SAME_APP | gtk.TARGET_SAME_WIDGET, 0)] + TARGETS2 = [('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0), + ('text/uri-list', 0, self.TARGET_TYPE_URI_LIST)] + self.tree.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, TARGETS, + gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE | gtk.gdk.ACTION_COPY) + self.tree.enable_model_drag_dest(TARGETS2, gtk.gdk.ACTION_DEFAULT) + self.tree.connect('drag_begin', self.drag_begin) + self.tree.connect('drag_end', self.drag_end) + self.tree.connect('drag_drop', self.drag_drop) + self.tree.connect('drag_data_get', self.drag_data_get_data) + self.tree.connect('drag_data_received', self.drag_data_received_data) + self.dragging = False + self.xml.connect_signals(self) + self.combobox_callback_active = True - self.collapsed_rows = gajim.config.get('collapsed_rows').split('\t') - self.tooltip = tooltips.RosterTooltip() - # Workaroung: For strange reasons signal is behaving like row-changed - self._toggeling_row = False - self.setup_and_draw_roster() + self.collapsed_rows = gajim.config.get('collapsed_rows').split('\t') + self.tooltip = tooltips.RosterTooltip() + # Workaroung: For strange reasons signal is behaving like row-changed + self._toggeling_row = False + self.setup_and_draw_roster() - if gajim.config.get('show_roster_on_startup'): - self.window.show_all() - else: - if gajim.config.get('trayicon') != 'always': - # Without trayicon, user should see the roster! - self.window.show_all() - gajim.config.set('show_roster_on_startup', True) + if gajim.config.get('show_roster_on_startup'): + self.window.show_all() + else: + if gajim.config.get('trayicon') != 'always': + # Without trayicon, user should see the roster! + self.window.show_all() + gajim.config.set('show_roster_on_startup', True) - if len(gajim.connections) == 0: # if we have no account - def _open_wizard(): - gajim.interface.instances['account_creation_wizard'] = \ - config.AccountCreationWizardWindow() - # Open wizard only after roster is created, so we can make it transient - # for the roster window - gobject.idle_add(_open_wizard) - if not gajim.ZEROCONF_ACC_NAME in gajim.config.get_per('accounts'): - # Create zeroconf in config file - from common.zeroconf import connection_zeroconf - connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME) + if len(gajim.connections) == 0: # if we have no account + def _open_wizard(): + gajim.interface.instances['account_creation_wizard'] = \ + config.AccountCreationWizardWindow() + # Open wizard only after roster is created, so we can make it transient + # for the roster window + gobject.idle_add(_open_wizard) + if not gajim.ZEROCONF_ACC_NAME in gajim.config.get_per('accounts'): + # Create zeroconf in config file + from common.zeroconf import connection_zeroconf + connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME) - # Setting CTRL+J to be the shortcut for bringing up the dialog to join a - # conference. - accel_group = gtk.accel_groups_from_object(self.window)[0] - accel_group.connect_group(gtk.keysyms.j, gtk.gdk.CONTROL_MASK, - gtk.ACCEL_MASK, self.on_ctrl_j) - -# vim: se ts=3: + # Setting CTRL+J to be the shortcut for bringing up the dialog to join a + # conference. + accel_group = gtk.accel_groups_from_object(self.window)[0] + accel_group.connect_group(gtk.keysyms.j, gtk.gdk.CONTROL_MASK, + gtk.ACCEL_MASK, self.on_ctrl_j) diff --git a/src/search_window.py b/src/search_window.py index 08eb4276c..06362b651 100644 --- a/src/search_window.py +++ b/src/search_window.py @@ -31,205 +31,203 @@ import config import dataforms_widget class SearchWindow: - def __init__(self, account, jid): - """ - Create new window - """ - # an account object - self.account = account - self.jid = jid + def __init__(self, account, jid): + """ + Create new window + """ + # an account object + self.account = account + self.jid = jid - # retrieving widgets from xml - self.xml = gtkgui_helpers.get_gtk_builder('search_window.ui') - self.window = self.xml.get_object('search_window') - for name in ('label', 'progressbar', 'search_vbox', 'search_button', - 'add_contact_button', 'information_button'): - self.__dict__[name] = self.xml.get_object(name) + # retrieving widgets from xml + self.xml = gtkgui_helpers.get_gtk_builder('search_window.ui') + self.window = self.xml.get_object('search_window') + for name in ('label', 'progressbar', 'search_vbox', 'search_button', + 'add_contact_button', 'information_button'): + self.__dict__[name] = self.xml.get_object(name) - # displaying the window - self.xml.connect_signals(self) - self.window.show_all() - self.request_form() - self.pulse_id = gobject.timeout_add(80, self.pulse_callback) + # displaying the window + self.xml.connect_signals(self) + self.window.show_all() + self.request_form() + self.pulse_id = gobject.timeout_add(80, self.pulse_callback) - self.is_form = None + self.is_form = None - # Is there a jid column in results ? if -1: no, else column number - self.jid_column = -1 + # Is there a jid column in results ? if -1: no, else column number + self.jid_column = -1 - def request_form(self): - gajim.connections[self.account].request_search_fields(self.jid) + def request_form(self): + gajim.connections[self.account].request_search_fields(self.jid) - def pulse_callback(self): - self.progressbar.pulse() - return True + def pulse_callback(self): + self.progressbar.pulse() + return True - def on_search_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: - self.window.destroy() + def on_search_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: + self.window.destroy() - def on_search_window_destroy(self, widget): - if self.pulse_id: - gobject.source_remove(self.pulse_id) - del gajim.interface.instances[self.account]['search'][self.jid] + def on_search_window_destroy(self, widget): + if self.pulse_id: + gobject.source_remove(self.pulse_id) + del gajim.interface.instances[self.account]['search'][self.jid] - def on_close_button_clicked(self, button): - self.window.destroy() + def on_close_button_clicked(self, button): + self.window.destroy() - def on_search_button_clicked(self, button): - if self.is_form: - self.data_form_widget.data_form.type = 'submit' - gajim.connections[self.account].send_search_form(self.jid, - self.data_form_widget.data_form.get_purged(), True) - else: - infos = self.data_form_widget.get_infos() - if 'instructions' in infos: - del infos['instructions'] - gajim.connections[self.account].send_search_form(self.jid, infos, - False) + def on_search_button_clicked(self, button): + if self.is_form: + self.data_form_widget.data_form.type = 'submit' + gajim.connections[self.account].send_search_form(self.jid, + self.data_form_widget.data_form.get_purged(), True) + else: + infos = self.data_form_widget.get_infos() + if 'instructions' in infos: + del infos['instructions'] + gajim.connections[self.account].send_search_form(self.jid, infos, + False) - self.search_vbox.remove(self.data_form_widget) + self.search_vbox.remove(self.data_form_widget) - self.progressbar.show() - self.label.set_text(_('Waiting for results')) - self.label.show() - self.pulse_id = gobject.timeout_add(80, self.pulse_callback) - self.search_button.hide() + self.progressbar.show() + self.label.set_text(_('Waiting for results')) + self.label.show() + self.pulse_id = gobject.timeout_add(80, self.pulse_callback) + self.search_button.hide() - def on_add_contact_button_clicked(self, widget): - (model, iter_) = self.result_treeview.get_selection().get_selected() - if not iter_: - return - jid = model[iter_][self.jid_column] - dialogs.AddNewContactWindow(self.account, jid) + def on_add_contact_button_clicked(self, widget): + (model, iter_) = self.result_treeview.get_selection().get_selected() + if not iter_: + return + jid = model[iter_][self.jid_column] + dialogs.AddNewContactWindow(self.account, jid) - def on_information_button_clicked(self, widget): - (model, iter_) = self.result_treeview.get_selection().get_selected() - if not iter_: - return - jid = model[iter_][self.jid_column] - if jid in gajim.interface.instances[self.account]['infos']: - gajim.interface.instances[self.account]['infos'][jid].window.present() - else: - contact = gajim.contacts.create_contact(jid=jid, account=self.account) - gajim.interface.instances[self.account]['infos'][jid] = \ - vcard.VcardWindow(contact, self.account) + def on_information_button_clicked(self, widget): + (model, iter_) = self.result_treeview.get_selection().get_selected() + if not iter_: + return + jid = model[iter_][self.jid_column] + if jid in gajim.interface.instances[self.account]['infos']: + gajim.interface.instances[self.account]['infos'][jid].window.present() + else: + contact = gajim.contacts.create_contact(jid=jid, account=self.account) + gajim.interface.instances[self.account]['infos'][jid] = \ + vcard.VcardWindow(contact, self.account) - def on_form_arrived(self, form, is_form): - if self.pulse_id: - gobject.source_remove(self.pulse_id) - self.progressbar.hide() - self.label.hide() + def on_form_arrived(self, form, is_form): + if self.pulse_id: + gobject.source_remove(self.pulse_id) + self.progressbar.hide() + self.label.hide() - if is_form: - self.is_form = True - self.data_form_widget = dataforms_widget.DataFormWidget() - self.dataform = dataforms.ExtendForm(node = form) - self.data_form_widget.set_sensitive(True) - try: - self.data_form_widget.data_form = self.dataform - except dataforms.Error: - self.label.set_text(_('Error in received dataform')) - self.label.show() - return - if self.data_form_widget.title: - self.window.set_title('%s - Search - Gajim' % \ - self.data_form_widget.title) - else: - self.is_form = False - self.data_form_widget = config.FakeDataForm(form) + if is_form: + self.is_form = True + self.data_form_widget = dataforms_widget.DataFormWidget() + self.dataform = dataforms.ExtendForm(node = form) + self.data_form_widget.set_sensitive(True) + try: + self.data_form_widget.data_form = self.dataform + except dataforms.Error: + self.label.set_text(_('Error in received dataform')) + self.label.show() + return + if self.data_form_widget.title: + self.window.set_title('%s - Search - Gajim' % \ + self.data_form_widget.title) + else: + self.is_form = False + self.data_form_widget = config.FakeDataForm(form) - self.data_form_widget.show_all() - self.search_vbox.pack_start(self.data_form_widget) + self.data_form_widget.show_all() + self.search_vbox.pack_start(self.data_form_widget) - def on_result_treeview_cursor_changed(self, treeview): - if self.jid_column == -1: - return - (model, iter_) = treeview.get_selection().get_selected() - if not iter_: - return - if model[iter_][self.jid_column]: - self.add_contact_button.set_sensitive(True) - self.information_button.set_sensitive(True) - else: - self.add_contact_button.set_sensitive(False) - self.information_button.set_sensitive(False) + def on_result_treeview_cursor_changed(self, treeview): + if self.jid_column == -1: + return + (model, iter_) = treeview.get_selection().get_selected() + if not iter_: + return + if model[iter_][self.jid_column]: + self.add_contact_button.set_sensitive(True) + self.information_button.set_sensitive(True) + else: + self.add_contact_button.set_sensitive(False) + self.information_button.set_sensitive(False) - def on_result_arrived(self, form, is_form): - if self.pulse_id: - gobject.source_remove(self.pulse_id) - self.progressbar.hide() - self.label.hide() + def on_result_arrived(self, form, is_form): + if self.pulse_id: + gobject.source_remove(self.pulse_id) + self.progressbar.hide() + self.label.hide() - if not is_form: - if not form: - self.label.set_text(_('No result')) - self.label.show() - return - # We suppose all items have the same fields - sw = gtk.ScrolledWindow() - sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - self.result_treeview = gtk.TreeView() - self.result_treeview.connect('cursor-changed', - self.on_result_treeview_cursor_changed) - sw.add(self.result_treeview) - # Create model - fieldtypes = [str]*len(form[0]) - model = gtk.ListStore(*fieldtypes) - # Copy data to model - for item in form: - model.append(item.values()) - # Create columns - counter = 0 - for field in form[0].keys(): - self.result_treeview.append_column( - gtk.TreeViewColumn(field, gtk.CellRendererText(), - text = counter)) - if field == 'jid': - self.jid_column = counter - counter += 1 - self.result_treeview.set_model(model) - sw.show_all() - self.search_vbox.pack_start(sw) - if self.jid_column > -1: - self.add_contact_button.show() - self.information_button.show() - return + if not is_form: + if not form: + self.label.set_text(_('No result')) + self.label.show() + return + # We suppose all items have the same fields + sw = gtk.ScrolledWindow() + sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.result_treeview = gtk.TreeView() + self.result_treeview.connect('cursor-changed', + self.on_result_treeview_cursor_changed) + sw.add(self.result_treeview) + # Create model + fieldtypes = [str]*len(form[0]) + model = gtk.ListStore(*fieldtypes) + # Copy data to model + for item in form: + model.append(item.values()) + # Create columns + counter = 0 + for field in form[0].keys(): + self.result_treeview.append_column( + gtk.TreeViewColumn(field, gtk.CellRendererText(), + text = counter)) + if field == 'jid': + self.jid_column = counter + counter += 1 + self.result_treeview.set_model(model) + sw.show_all() + self.search_vbox.pack_start(sw) + if self.jid_column > -1: + self.add_contact_button.show() + self.information_button.show() + return - self.dataform = dataforms.ExtendForm(node = form) - if len(self.dataform.items) == 0: - # No result - self.label.set_text(_('No result')) - self.label.show() - return + self.dataform = dataforms.ExtendForm(node = form) + if len(self.dataform.items) == 0: + # No result + self.label.set_text(_('No result')) + self.label.show() + return - self.data_form_widget.set_sensitive(True) - try: - self.data_form_widget.data_form = self.dataform - except dataforms.Error: - self.label.set_text(_('Error in received dataform')) - self.label.show() - return + self.data_form_widget.set_sensitive(True) + try: + self.data_form_widget.data_form = self.dataform + except dataforms.Error: + self.label.set_text(_('Error in received dataform')) + self.label.show() + return - self.result_treeview = self.data_form_widget.records_treeview - selection = self.result_treeview.get_selection() - selection.set_mode(gtk.SELECTION_SINGLE) - self.result_treeview.connect('cursor-changed', - self.on_result_treeview_cursor_changed) + self.result_treeview = self.data_form_widget.records_treeview + selection = self.result_treeview.get_selection() + selection.set_mode(gtk.SELECTION_SINGLE) + self.result_treeview.connect('cursor-changed', + self.on_result_treeview_cursor_changed) - counter = 0 - for field in self.dataform.items[0].fields: - if field.var == 'jid': - self.jid_column = counter - break - counter += 1 - self.search_vbox.pack_start(self.data_form_widget) - self.data_form_widget.show() - if self.jid_column > -1: - self.add_contact_button.show() - self.information_button.show() - if self.data_form_widget.title: - self.window.set_title('%s - Search - Gajim' % \ - self.data_form_widget.title) - -# vim: se ts=3: + counter = 0 + for field in self.dataform.items[0].fields: + if field.var == 'jid': + self.jid_column = counter + break + counter += 1 + self.search_vbox.pack_start(self.data_form_widget) + self.data_form_widget.show() + if self.jid_column > -1: + self.add_contact_button.show() + self.information_button.show() + if self.data_form_widget.title: + self.window.set_title('%s - Search - Gajim' % \ + self.data_form_widget.title) diff --git a/src/secrets.py b/src/secrets.py index 94247e606..17cb5b6e3 100644 --- a/src/secrets.py +++ b/src/secrets.py @@ -32,90 +32,88 @@ secrets_filename = gajimpaths['SECRETS_FILE'] secrets_cache = None class Secrets: - def __init__(self, filename): - self.filename = filename - self.srs = {} - self.pubkeys = {} - self.privkeys = {} + def __init__(self, filename): + self.filename = filename + self.srs = {} + self.pubkeys = {} + self.privkeys = {} - def cancel(self): - raise exceptions.Cancelled + def cancel(self): + raise exceptions.Cancelled - def save(self): - f = open(secrets_filename, 'w') - pickle.dump(self, f) - f.close() + def save(self): + f = open(secrets_filename, 'w') + pickle.dump(self, f) + f.close() - def retained_secrets(self, account, bare_jid): - try: - return self.srs[account][bare_jid] - except KeyError: - return [] + def retained_secrets(self, account, bare_jid): + try: + return self.srs[account][bare_jid] + except KeyError: + return [] - # retained secrets are stored as a tuple of the secret and whether the user - # has verified it - def save_new_srs(self, account, jid, secret, verified): - if not account in self.srs: - self.srs[account] = {} + # retained secrets are stored as a tuple of the secret and whether the user + # has verified it + def save_new_srs(self, account, jid, secret, verified): + if not account in self.srs: + self.srs[account] = {} - if not jid in self.srs[account]: - self.srs[account][jid] = [] + if not jid in self.srs[account]: + self.srs[account][jid] = [] - self.srs[account][jid].append((secret, verified)) + self.srs[account][jid].append((secret, verified)) - self.save() + self.save() - def find_srs(self, account, jid, srs): - our_secrets = self.srs[account][jid] - return [(x, y) for x, y in our_secrets if x == srs][0] + def find_srs(self, account, jid, srs): + our_secrets = self.srs[account][jid] + return [(x, y) for x, y in our_secrets if x == srs][0] - # has the user verified this retained secret? - def srs_verified(self, account, jid, srs): - return self.find_srs(account, jid, srs)[1] + # has the user verified this retained secret? + def srs_verified(self, account, jid, srs): + return self.find_srs(account, jid, srs)[1] - def replace_srs(self, account, jid, old_secret, new_secret, verified): - our_secrets = self.srs[account][jid] + def replace_srs(self, account, jid, old_secret, new_secret, verified): + our_secrets = self.srs[account][jid] - idx = our_secrets.index(self.find_srs(account, jid, old_secret)) + idx = our_secrets.index(self.find_srs(account, jid, old_secret)) - our_secrets[idx] = (new_secret, verified) + our_secrets[idx] = (new_secret, verified) - self.save() + self.save() - # the public key associated with 'account' - def my_pubkey(self, account): - try: - pk = self.privkeys[account] - except KeyError: - pk = Crypto.PublicKey.RSA.generate(384, crypto.random_bytes) + # the public key associated with 'account' + def my_pubkey(self, account): + try: + pk = self.privkeys[account] + except KeyError: + pk = Crypto.PublicKey.RSA.generate(384, crypto.random_bytes) - self.privkeys[account] = pk - self.save() + self.privkeys[account] = pk + self.save() - return pk + return pk def load_secrets(filename): - f = open(filename, 'r') + f = open(filename, 'r') - try: - secrets = pickle.load(f) - except KeyError: - f.close() - secrets = Secrets(filename) + try: + secrets = pickle.load(f) + except KeyError: + f.close() + secrets = Secrets(filename) - return secrets + return secrets def secrets(): - global secrets_cache + global secrets_cache - if secrets_cache: - return secrets_cache + if secrets_cache: + return secrets_cache - if os.path.exists(secrets_filename): - secrets_cache = load_secrets(secrets_filename) - else: - secrets_cache = Secrets(secrets_filename) + if os.path.exists(secrets_filename): + secrets_cache = load_secrets(secrets_filename) + else: + secrets_cache = Secrets(secrets_filename) - return secrets_cache - -# vim: se ts=3: + return secrets_cache diff --git a/src/session.py b/src/session.py index e13eb0b8f..4b3c530bb 100644 --- a/src/session.py +++ b/src/session.py @@ -38,493 +38,491 @@ import dialogs import negotiation class ChatControlSession(stanza_session.EncryptedStanzaSession): - def __init__(self, conn, jid, thread_id, type_='chat'): - stanza_session.EncryptedStanzaSession.__init__(self, conn, jid, thread_id, - type_='chat') - - self.control = None - - def detach_from_control(self): - if self.control: - self.control.set_session(None) - - def acknowledge_termination(self): - self.detach_from_control() - stanza_session.EncryptedStanzaSession.acknowledge_termination(self) - - def terminate(self, send_termination = True): - stanza_session.EncryptedStanzaSession.terminate(self, send_termination) - self.detach_from_control() - - def get_chatstate(self, msg, msgtxt): - """ - Extract chatstate from a stanza - """ - composing_xep = None - chatstate = None - - # chatstates - look for chatstate tags in a message if not delayed - delayed = msg.getTag('x', namespace=common.xmpp.NS_DELAY) is not None - if not delayed: - composing_xep = False - children = msg.getChildren() - for child in children: - if child.getNamespace() == 'http://jabber.org/protocol/chatstates': - chatstate = child.getName() - composing_xep = 'XEP-0085' - break - # No XEP-0085 support, fallback to XEP-0022 - if not chatstate: - chatstate_child = msg.getTag('x', namespace=common.xmpp.NS_EVENT) - if chatstate_child: - chatstate = 'active' - composing_xep = 'XEP-0022' - if not msgtxt and chatstate_child.getTag('composing'): - chatstate = 'composing' - - return (composing_xep, chatstate) - - def received(self, full_jid_with_resource, msgtxt, tim, encrypted, msg): - """ - Dispatch a received stanza - """ - msg_type = msg.getType() - subject = msg.getSubject() - resource = gajim.get_resource_from_jid(full_jid_with_resource) - if self.resource != resource: - self.resource = resource - if self.control and self.control.resource: - self.control.change_resource(self.resource) - - if not msg_type or msg_type not in ('chat', 'groupchat', 'error'): - msg_type = 'normal' - - msg_id = None - - # XEP-0172 User Nickname - user_nick = msg.getTagData('nick') - if not user_nick: - user_nick = '' - - form_node = None - for xtag in msg.getTags('x'): - if xtag.getNamespace() == common.xmpp.NS_DATA: - form_node = xtag - break - - composing_xep, chatstate = self.get_chatstate(msg, msgtxt) - - xhtml = msg.getXHTML() - - if msg_type == 'chat': - if not msg.getTag('body') and chatstate is None: - return - - log_type = 'chat_msg_recv' - else: - log_type = 'single_msg_recv' - - if self.is_loggable() and msgtxt: - try: - msg_id = gajim.logger.write(log_type, full_jid_with_resource, - msgtxt, tim=tim, subject=subject) - except exceptions.PysqliteOperationalError, e: - self.conn.dispatch('ERROR', (_('Disk WriteError'), str(e))) - except exceptions.DatabaseMalformed: - pritext = _('Database Error') - sectext = _('The database file (%s) cannot be read. Try to repair ' - 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove ' - 'it (all history will be lost).') % common.logger.LOG_DB_PATH - self.conn.dispatch('ERROR', (pritext, sectext)) - - treat_as = gajim.config.get('treat_incoming_messages') - if treat_as: - msg_type = treat_as - - jid = gajim.get_jid_without_resource(full_jid_with_resource) - resource = gajim.get_resource_from_jid(full_jid_with_resource) - - if gajim.config.get('ignore_incoming_xhtml'): - xhtml = None - if gajim.jid_is_transport(jid): - jid = jid.replace('@', '') - - groupchat_control = gajim.interface.msg_win_mgr.get_gc_control(jid, - self.conn.name) - - if not groupchat_control and \ - jid in gajim.interface.minimized_controls[self.conn.name]: - groupchat_control = gajim.interface.minimized_controls[self.conn.name]\ - [jid] - - pm = False - if groupchat_control and groupchat_control.type_id == \ - message_control.TYPE_GC and resource: - # It's a Private message - pm = True - msg_type = 'pm' - - highest_contact = gajim.contacts.get_contact_with_highest_priority( - self.conn.name, jid) - - # does this resource have the highest priority of any available? - is_highest = not highest_contact or not highest_contact.resource or \ - resource == highest_contact.resource or highest_contact.show == \ - 'offline' - - # Handle chat states - contact = gajim.contacts.get_contact(self.conn.name, jid, resource) - if contact: - if contact.composing_xep != 'XEP-0085': # We cache xep85 support - contact.composing_xep = composing_xep - if self.control and self.control.type_id == message_control.TYPE_CHAT: - if chatstate is not None: - # other peer sent us reply, so he supports jep85 or jep22 - contact.chatstate = chatstate - if contact.our_chatstate == 'ask': # we were jep85 disco? - contact.our_chatstate = 'active' # no more - self.control.handle_incoming_chatstate() - elif contact.chatstate != 'active': - # got no valid jep85 answer, peer does not support it - contact.chatstate = False - elif chatstate == 'active': - # Brand new message, incoming. - contact.our_chatstate = chatstate - contact.chatstate = chatstate - if msg_id: # Do not overwrite an existing msg_id with None - contact.msg_id = msg_id - - # THIS MUST BE AFTER chatstates handling - # AND BEFORE playsound (else we ear sounding on chatstates!) - if not msgtxt: # empty message text - return - - if gajim.config.get_per('accounts', self.conn.name, - 'ignore_unknown_contacts') and not gajim.contacts.get_contacts( - self.conn.name, jid) and not pm: - return - - if not contact: - # contact is not in the roster, create a fake one to display - # notification - contact = gajim.contacts.create_not_in_roster_contact(jid=jid, - account=self.conn.name, resource=resource) - - advanced_notif_num = notify.get_advanced_notification('message_received', - self.conn.name, contact) - - if not pm and is_highest: - jid_of_control = jid - else: - jid_of_control = full_jid_with_resource - - if not self.control: - ctrl = gajim.interface.msg_win_mgr.get_control(jid_of_control, - self.conn.name) - if ctrl: - self.control = ctrl - self.control.set_session(self) - - # Is it a first or next message received ? - first = False - if not self.control and not gajim.events.get_events(self.conn.name, \ - jid_of_control, [msg_type]): - first = True - - if pm: - nickname = resource - if self.control: - # print if a control is open - self.control.print_conversation(msgtxt, tim=tim, xhtml=xhtml, - encrypted=encrypted) - else: - # otherwise pass it off to the control to be queued - groupchat_control.on_private_message(nickname, msgtxt, tim, - xhtml, self, msg_id=msg_id, encrypted=encrypted) - else: - self.roster_message(jid, msgtxt, tim, encrypted, msg_type, - subject, resource, msg_id, user_nick, advanced_notif_num, - xhtml=xhtml, form_node=form_node) - - nickname = gajim.get_name_from_jid(self.conn.name, jid) - - # Check and do wanted notifications - msg = msgtxt - if subject: - msg = _('Subject: %s') % subject + '\n' + msg - focused = False - - if self.control: - parent_win = self.control.parent_win - if parent_win and self.control == parent_win.get_active_control() and \ - parent_win.window.has_focus: - focused = True - - notify.notify('new_message', jid_of_control, self.conn.name, [msg_type, - first, nickname, msg, focused], advanced_notif_num) - - if gajim.interface.remote_ctrl: - gajim.interface.remote_ctrl.raise_signal('NewMessage', (self.conn.name, - [full_jid_with_resource, msgtxt, tim, encrypted, msg_type, subject, - chatstate, msg_id, composing_xep, user_nick, xhtml, form_node])) - - gajim.ged.raise_event('NewMessage', - (self.conn.name, [full_jid_with_resource, msgtxt, tim, - encrypted, msg_type, subject, chatstate, msg_id, - composing_xep, user_nick, xhtml, form_node])) - - - def roster_message(self, jid, msg, tim, encrypted=False, msg_type='', - subject=None, resource='', msg_id=None, user_nick='', - advanced_notif_num=None, xhtml=None, form_node=None): - """ - Display the message or show notification in the roster - """ - contact = None - # if chat window will be for specific resource - resource_for_chat = resource - - fjid = jid - - # Try to catch the contact with correct resource - if resource: - fjid = jid + '/' + resource - contact = gajim.contacts.get_contact(self.conn.name, jid, resource) - - highest_contact = gajim.contacts.get_contact_with_highest_priority( - self.conn.name, jid) - if not contact: - # If there is another resource, it may be a message from an invisible - # resource - lcontact = gajim.contacts.get_contacts(self.conn.name, jid) - if (len(lcontact) > 1 or (lcontact and lcontact[0].resource and \ - lcontact[0].show != 'offline')) and jid.find('@') > 0: - contact = gajim.contacts.copy_contact(highest_contact) - contact.resource = resource - if resource: - fjid = jid + '/' + resource - contact.priority = 0 - contact.show = 'offline' - contact.status = '' - gajim.contacts.add_contact(self.conn.name, contact) - - else: - # Default to highest prio - fjid = jid - resource_for_chat = None - contact = highest_contact - - if not contact: - # contact is not in roster - contact = gajim.interface.roster.add_to_not_in_the_roster( - self.conn.name, jid, user_nick) - - if not self.control: - ctrl = gajim.interface.msg_win_mgr.get_control(fjid, self.conn.name) - if ctrl: - self.control = ctrl - self.control.set_session(self) - else: - # if no control exists and message comes from highest prio, the new - # control shouldn't have a resource - if highest_contact and contact.resource == highest_contact.resource\ - and not jid == gajim.get_jid_from_account(self.conn.name): - fjid = jid - resource_for_chat = None - - # Do we have a queue? - no_queue = len(gajim.events.get_events(self.conn.name, fjid)) == 0 - - popup = helpers.allow_popup_window(self.conn.name, advanced_notif_num) - - if msg_type == 'normal' and popup: # it's single message to be autopopuped - dialogs.SingleMessageWindow(self.conn.name, contact.jid, - action='receive', from_whom=jid, subject=subject, message=msg, - resource=resource, session=self, form_node=form_node) - return - - # We print if window is opened and it's not a single message - if self.control and msg_type != 'normal': - typ = '' - - if msg_type == 'error': - typ = 'error' - - self.control.print_conversation(msg, typ, tim=tim, encrypted=encrypted, - subject=subject, xhtml=xhtml) - - if msg_id: - gajim.logger.set_read_messages([msg_id]) - - return - - # We save it in a queue - type_ = 'chat' - event_type = 'message_received' - - if msg_type == 'normal': - type_ = 'normal' - event_type = 'single_message_received' - - show_in_roster = notify.get_show_in_roster(event_type, self.conn.name, - contact, self) - show_in_systray = notify.get_show_in_systray(event_type, self.conn.name, - contact) - - event = gajim.events.create_event(type_, (msg, subject, msg_type, tim, - encrypted, resource, msg_id, xhtml, self, form_node), - show_in_roster=show_in_roster, show_in_systray=show_in_systray) - - gajim.events.add_event(self.conn.name, fjid, event) - - if popup: - if not self.control: - self.control = gajim.interface.new_chat(contact, - self.conn.name, resource=resource_for_chat, session=self) - - if len(gajim.events.get_events(self.conn.name, fjid)): - self.control.read_queue() - else: - if no_queue: # We didn't have a queue: we change icons - gajim.interface.roster.draw_contact(jid, self.conn.name) - - gajim.interface.roster.show_title() # we show the * or [n] - # Select the big brother contact in roster, it's visible because it has - # events. - family = gajim.contacts.get_metacontacts_family(self.conn.name, jid) - if family: - nearby_family, bb_jid, bb_account = \ - gajim.contacts.get_nearby_family_and_big_brother(family, - self.conn.name) - else: - bb_jid, bb_account = jid, self.conn.name - gajim.interface.roster.select_contact(bb_jid, bb_account) - - # ---- ESessions stuff --- - - def handle_negotiation(self, form): - if form.getField('accept') and not form['accept'] in ('1', 'true'): - self.cancelled_negotiation() - return - - # encrypted session states. these are described in stanza_session.py - - try: - # bob responds - if form.getType() == 'form' and 'security' in form.asDict(): - # we don't support 3-message negotiation as the responder - if 'dhkeys' in form.asDict(): - self.fail_bad_negotiation('3 message negotiation not supported ' - 'when responding', ('dhkeys',)) - return - - negotiated, not_acceptable, ask_user = self.verify_options_bob(form) - - if ask_user: - def accept_nondefault_options(is_checked): - self.dialog.destroy() - negotiated.update(ask_user) - self.respond_e2e_bob(form, negotiated, not_acceptable) - - def reject_nondefault_options(): - self.dialog.destroy() - for key in ask_user.keys(): - not_acceptable.append(key) - self.respond_e2e_bob(form, negotiated, not_acceptable) - - self.dialog = dialogs.YesNoDialog(_('Confirm these session ' - 'options'), - _('''The remote client wants to negotiate an session with these features: - - %s - - Are these options acceptable?''') % (negotiation.describe_features( - ask_user)), - on_response_yes=accept_nondefault_options, - on_response_no=reject_nondefault_options) - else: - self.respond_e2e_bob(form, negotiated, not_acceptable) - - return - - # alice accepts - elif self.status == 'requested-e2e' and form.getType() == 'submit': - negotiated, not_acceptable, ask_user = self.verify_options_alice( - form) - - if ask_user: - def accept_nondefault_options(is_checked): - dialog.destroy() - - negotiated.update(ask_user) - - try: - self.accept_e2e_alice(form, negotiated) - except exceptions.NegotiationError, details: - self.fail_bad_negotiation(details) - - def reject_nondefault_options(): - self.reject_negotiation() - dialog.destroy() - - dialog = dialogs.YesNoDialog(_('Confirm these session options'), - _('The remote client selected these options:\n\n%s\n\n' - 'Continue with the session?') % ( - negotiation.describe_features(ask_user)), - on_response_yes = accept_nondefault_options, - on_response_no = reject_nondefault_options) - else: - try: - self.accept_e2e_alice(form, negotiated) - except exceptions.NegotiationError, details: - self.fail_bad_negotiation(details) - - return - elif self.status == 'responded-e2e' and form.getType() == 'result': - try: - self.accept_e2e_bob(form) - except exceptions.NegotiationError, details: - self.fail_bad_negotiation(details) - - return - elif self.status == 'identified-alice' and form.getType() == 'result': - try: - self.final_steps_alice(form) - except exceptions.NegotiationError, details: - self.fail_bad_negotiation(details) - - return - except exceptions.Cancelled: - # user cancelled the negotiation - - self.reject_negotiation() - - return - - if form.getField('terminate') and\ - form.getField('terminate').getValue() in ('1', 'true'): - self.acknowledge_termination() - - self.conn.delete_session(str(self.jid), self.thread_id) - - return - - # non-esession negotiation. this isn't very useful, but i'm keeping it - # around to test my test suite. - if form.getType() == 'form': - if not self.control: - jid, resource = gajim.get_room_and_nick_from_fjid(self.jid) - - account = self.conn.name - contact = gajim.contacts.get_contact(account, self.jid, resource) - - if not contact: - contact = gajim.contacts.create_contact(jid=jid, account=account, - resource=resource, show=self.conn.get_status()) - - gajim.interface.new_chat(contact, account, resource=resource, - session=self) - - negotiation.FeatureNegotiationWindow(account, self.jid, self, form) - -# vim: se ts=3: + def __init__(self, conn, jid, thread_id, type_='chat'): + stanza_session.EncryptedStanzaSession.__init__(self, conn, jid, thread_id, + type_='chat') + + self.control = None + + def detach_from_control(self): + if self.control: + self.control.set_session(None) + + def acknowledge_termination(self): + self.detach_from_control() + stanza_session.EncryptedStanzaSession.acknowledge_termination(self) + + def terminate(self, send_termination = True): + stanza_session.EncryptedStanzaSession.terminate(self, send_termination) + self.detach_from_control() + + def get_chatstate(self, msg, msgtxt): + """ + Extract chatstate from a stanza + """ + composing_xep = None + chatstate = None + + # chatstates - look for chatstate tags in a message if not delayed + delayed = msg.getTag('x', namespace=common.xmpp.NS_DELAY) is not None + if not delayed: + composing_xep = False + children = msg.getChildren() + for child in children: + if child.getNamespace() == 'http://jabber.org/protocol/chatstates': + chatstate = child.getName() + composing_xep = 'XEP-0085' + break + # No XEP-0085 support, fallback to XEP-0022 + if not chatstate: + chatstate_child = msg.getTag('x', namespace=common.xmpp.NS_EVENT) + if chatstate_child: + chatstate = 'active' + composing_xep = 'XEP-0022' + if not msgtxt and chatstate_child.getTag('composing'): + chatstate = 'composing' + + return (composing_xep, chatstate) + + def received(self, full_jid_with_resource, msgtxt, tim, encrypted, msg): + """ + Dispatch a received stanza + """ + msg_type = msg.getType() + subject = msg.getSubject() + resource = gajim.get_resource_from_jid(full_jid_with_resource) + if self.resource != resource: + self.resource = resource + if self.control and self.control.resource: + self.control.change_resource(self.resource) + + if not msg_type or msg_type not in ('chat', 'groupchat', 'error'): + msg_type = 'normal' + + msg_id = None + + # XEP-0172 User Nickname + user_nick = msg.getTagData('nick') + if not user_nick: + user_nick = '' + + form_node = None + for xtag in msg.getTags('x'): + if xtag.getNamespace() == common.xmpp.NS_DATA: + form_node = xtag + break + + composing_xep, chatstate = self.get_chatstate(msg, msgtxt) + + xhtml = msg.getXHTML() + + if msg_type == 'chat': + if not msg.getTag('body') and chatstate is None: + return + + log_type = 'chat_msg_recv' + else: + log_type = 'single_msg_recv' + + if self.is_loggable() and msgtxt: + try: + msg_id = gajim.logger.write(log_type, full_jid_with_resource, + msgtxt, tim=tim, subject=subject) + except exceptions.PysqliteOperationalError, e: + self.conn.dispatch('ERROR', (_('Disk WriteError'), str(e))) + except exceptions.DatabaseMalformed: + pritext = _('Database Error') + sectext = _('The database file (%s) cannot be read. Try to repair ' + 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove ' + 'it (all history will be lost).') % common.logger.LOG_DB_PATH + self.conn.dispatch('ERROR', (pritext, sectext)) + + treat_as = gajim.config.get('treat_incoming_messages') + if treat_as: + msg_type = treat_as + + jid = gajim.get_jid_without_resource(full_jid_with_resource) + resource = gajim.get_resource_from_jid(full_jid_with_resource) + + if gajim.config.get('ignore_incoming_xhtml'): + xhtml = None + if gajim.jid_is_transport(jid): + jid = jid.replace('@', '') + + groupchat_control = gajim.interface.msg_win_mgr.get_gc_control(jid, + self.conn.name) + + if not groupchat_control and \ + jid in gajim.interface.minimized_controls[self.conn.name]: + groupchat_control = gajim.interface.minimized_controls[self.conn.name]\ + [jid] + + pm = False + if groupchat_control and groupchat_control.type_id == \ + message_control.TYPE_GC and resource: + # It's a Private message + pm = True + msg_type = 'pm' + + highest_contact = gajim.contacts.get_contact_with_highest_priority( + self.conn.name, jid) + + # does this resource have the highest priority of any available? + is_highest = not highest_contact or not highest_contact.resource or \ + resource == highest_contact.resource or highest_contact.show == \ + 'offline' + + # Handle chat states + contact = gajim.contacts.get_contact(self.conn.name, jid, resource) + if contact: + if contact.composing_xep != 'XEP-0085': # We cache xep85 support + contact.composing_xep = composing_xep + if self.control and self.control.type_id == message_control.TYPE_CHAT: + if chatstate is not None: + # other peer sent us reply, so he supports jep85 or jep22 + contact.chatstate = chatstate + if contact.our_chatstate == 'ask': # we were jep85 disco? + contact.our_chatstate = 'active' # no more + self.control.handle_incoming_chatstate() + elif contact.chatstate != 'active': + # got no valid jep85 answer, peer does not support it + contact.chatstate = False + elif chatstate == 'active': + # Brand new message, incoming. + contact.our_chatstate = chatstate + contact.chatstate = chatstate + if msg_id: # Do not overwrite an existing msg_id with None + contact.msg_id = msg_id + + # THIS MUST BE AFTER chatstates handling + # AND BEFORE playsound (else we ear sounding on chatstates!) + if not msgtxt: # empty message text + return + + if gajim.config.get_per('accounts', self.conn.name, + 'ignore_unknown_contacts') and not gajim.contacts.get_contacts( + self.conn.name, jid) and not pm: + return + + if not contact: + # contact is not in the roster, create a fake one to display + # notification + contact = gajim.contacts.create_not_in_roster_contact(jid=jid, + account=self.conn.name, resource=resource) + + advanced_notif_num = notify.get_advanced_notification('message_received', + self.conn.name, contact) + + if not pm and is_highest: + jid_of_control = jid + else: + jid_of_control = full_jid_with_resource + + if not self.control: + ctrl = gajim.interface.msg_win_mgr.get_control(jid_of_control, + self.conn.name) + if ctrl: + self.control = ctrl + self.control.set_session(self) + + # Is it a first or next message received ? + first = False + if not self.control and not gajim.events.get_events(self.conn.name, \ + jid_of_control, [msg_type]): + first = True + + if pm: + nickname = resource + if self.control: + # print if a control is open + self.control.print_conversation(msgtxt, tim=tim, xhtml=xhtml, + encrypted=encrypted) + else: + # otherwise pass it off to the control to be queued + groupchat_control.on_private_message(nickname, msgtxt, tim, + xhtml, self, msg_id=msg_id, encrypted=encrypted) + else: + self.roster_message(jid, msgtxt, tim, encrypted, msg_type, + subject, resource, msg_id, user_nick, advanced_notif_num, + xhtml=xhtml, form_node=form_node) + + nickname = gajim.get_name_from_jid(self.conn.name, jid) + + # Check and do wanted notifications + msg = msgtxt + if subject: + msg = _('Subject: %s') % subject + '\n' + msg + focused = False + + if self.control: + parent_win = self.control.parent_win + if parent_win and self.control == parent_win.get_active_control() and \ + parent_win.window.has_focus: + focused = True + + notify.notify('new_message', jid_of_control, self.conn.name, [msg_type, + first, nickname, msg, focused], advanced_notif_num) + + if gajim.interface.remote_ctrl: + gajim.interface.remote_ctrl.raise_signal('NewMessage', (self.conn.name, + [full_jid_with_resource, msgtxt, tim, encrypted, msg_type, subject, + chatstate, msg_id, composing_xep, user_nick, xhtml, form_node])) + + gajim.ged.raise_event('NewMessage', + (self.conn.name, [full_jid_with_resource, msgtxt, tim, + encrypted, msg_type, subject, chatstate, msg_id, + composing_xep, user_nick, xhtml, form_node])) + + + def roster_message(self, jid, msg, tim, encrypted=False, msg_type='', + subject=None, resource='', msg_id=None, user_nick='', + advanced_notif_num=None, xhtml=None, form_node=None): + """ + Display the message or show notification in the roster + """ + contact = None + # if chat window will be for specific resource + resource_for_chat = resource + + fjid = jid + + # Try to catch the contact with correct resource + if resource: + fjid = jid + '/' + resource + contact = gajim.contacts.get_contact(self.conn.name, jid, resource) + + highest_contact = gajim.contacts.get_contact_with_highest_priority( + self.conn.name, jid) + if not contact: + # If there is another resource, it may be a message from an invisible + # resource + lcontact = gajim.contacts.get_contacts(self.conn.name, jid) + if (len(lcontact) > 1 or (lcontact and lcontact[0].resource and \ + lcontact[0].show != 'offline')) and jid.find('@') > 0: + contact = gajim.contacts.copy_contact(highest_contact) + contact.resource = resource + if resource: + fjid = jid + '/' + resource + contact.priority = 0 + contact.show = 'offline' + contact.status = '' + gajim.contacts.add_contact(self.conn.name, contact) + + else: + # Default to highest prio + fjid = jid + resource_for_chat = None + contact = highest_contact + + if not contact: + # contact is not in roster + contact = gajim.interface.roster.add_to_not_in_the_roster( + self.conn.name, jid, user_nick) + + if not self.control: + ctrl = gajim.interface.msg_win_mgr.get_control(fjid, self.conn.name) + if ctrl: + self.control = ctrl + self.control.set_session(self) + else: + # if no control exists and message comes from highest prio, the new + # control shouldn't have a resource + if highest_contact and contact.resource == highest_contact.resource\ + and not jid == gajim.get_jid_from_account(self.conn.name): + fjid = jid + resource_for_chat = None + + # Do we have a queue? + no_queue = len(gajim.events.get_events(self.conn.name, fjid)) == 0 + + popup = helpers.allow_popup_window(self.conn.name, advanced_notif_num) + + if msg_type == 'normal' and popup: # it's single message to be autopopuped + dialogs.SingleMessageWindow(self.conn.name, contact.jid, + action='receive', from_whom=jid, subject=subject, message=msg, + resource=resource, session=self, form_node=form_node) + return + + # We print if window is opened and it's not a single message + if self.control and msg_type != 'normal': + typ = '' + + if msg_type == 'error': + typ = 'error' + + self.control.print_conversation(msg, typ, tim=tim, encrypted=encrypted, + subject=subject, xhtml=xhtml) + + if msg_id: + gajim.logger.set_read_messages([msg_id]) + + return + + # We save it in a queue + type_ = 'chat' + event_type = 'message_received' + + if msg_type == 'normal': + type_ = 'normal' + event_type = 'single_message_received' + + show_in_roster = notify.get_show_in_roster(event_type, self.conn.name, + contact, self) + show_in_systray = notify.get_show_in_systray(event_type, self.conn.name, + contact) + + event = gajim.events.create_event(type_, (msg, subject, msg_type, tim, + encrypted, resource, msg_id, xhtml, self, form_node), + show_in_roster=show_in_roster, show_in_systray=show_in_systray) + + gajim.events.add_event(self.conn.name, fjid, event) + + if popup: + if not self.control: + self.control = gajim.interface.new_chat(contact, + self.conn.name, resource=resource_for_chat, session=self) + + if len(gajim.events.get_events(self.conn.name, fjid)): + self.control.read_queue() + else: + if no_queue: # We didn't have a queue: we change icons + gajim.interface.roster.draw_contact(jid, self.conn.name) + + gajim.interface.roster.show_title() # we show the * or [n] + # Select the big brother contact in roster, it's visible because it has + # events. + family = gajim.contacts.get_metacontacts_family(self.conn.name, jid) + if family: + nearby_family, bb_jid, bb_account = \ + gajim.contacts.get_nearby_family_and_big_brother(family, + self.conn.name) + else: + bb_jid, bb_account = jid, self.conn.name + gajim.interface.roster.select_contact(bb_jid, bb_account) + + # ---- ESessions stuff --- + + def handle_negotiation(self, form): + if form.getField('accept') and not form['accept'] in ('1', 'true'): + self.cancelled_negotiation() + return + + # encrypted session states. these are described in stanza_session.py + + try: + # bob responds + if form.getType() == 'form' and 'security' in form.asDict(): + # we don't support 3-message negotiation as the responder + if 'dhkeys' in form.asDict(): + self.fail_bad_negotiation('3 message negotiation not supported ' + 'when responding', ('dhkeys',)) + return + + negotiated, not_acceptable, ask_user = self.verify_options_bob(form) + + if ask_user: + def accept_nondefault_options(is_checked): + self.dialog.destroy() + negotiated.update(ask_user) + self.respond_e2e_bob(form, negotiated, not_acceptable) + + def reject_nondefault_options(): + self.dialog.destroy() + for key in ask_user.keys(): + not_acceptable.append(key) + self.respond_e2e_bob(form, negotiated, not_acceptable) + + self.dialog = dialogs.YesNoDialog(_('Confirm these session ' + 'options'), + _('''The remote client wants to negotiate an session with these features: + +%s + +Are these options acceptable?''') % (negotiation.describe_features( + ask_user)), + on_response_yes=accept_nondefault_options, + on_response_no=reject_nondefault_options) + else: + self.respond_e2e_bob(form, negotiated, not_acceptable) + + return + + # alice accepts + elif self.status == 'requested-e2e' and form.getType() == 'submit': + negotiated, not_acceptable, ask_user = self.verify_options_alice( + form) + + if ask_user: + def accept_nondefault_options(is_checked): + dialog.destroy() + + negotiated.update(ask_user) + + try: + self.accept_e2e_alice(form, negotiated) + except exceptions.NegotiationError, details: + self.fail_bad_negotiation(details) + + def reject_nondefault_options(): + self.reject_negotiation() + dialog.destroy() + + dialog = dialogs.YesNoDialog(_('Confirm these session options'), + _('The remote client selected these options:\n\n%s\n\n' + 'Continue with the session?') % ( + negotiation.describe_features(ask_user)), + on_response_yes = accept_nondefault_options, + on_response_no = reject_nondefault_options) + else: + try: + self.accept_e2e_alice(form, negotiated) + except exceptions.NegotiationError, details: + self.fail_bad_negotiation(details) + + return + elif self.status == 'responded-e2e' and form.getType() == 'result': + try: + self.accept_e2e_bob(form) + except exceptions.NegotiationError, details: + self.fail_bad_negotiation(details) + + return + elif self.status == 'identified-alice' and form.getType() == 'result': + try: + self.final_steps_alice(form) + except exceptions.NegotiationError, details: + self.fail_bad_negotiation(details) + + return + except exceptions.Cancelled: + # user cancelled the negotiation + + self.reject_negotiation() + + return + + if form.getField('terminate') and\ + form.getField('terminate').getValue() in ('1', 'true'): + self.acknowledge_termination() + + self.conn.delete_session(str(self.jid), self.thread_id) + + return + + # non-esession negotiation. this isn't very useful, but i'm keeping it + # around to test my test suite. + if form.getType() == 'form': + if not self.control: + jid, resource = gajim.get_room_and_nick_from_fjid(self.jid) + + account = self.conn.name + contact = gajim.contacts.get_contact(account, self.jid, resource) + + if not contact: + contact = gajim.contacts.create_contact(jid=jid, account=account, + resource=resource, show=self.conn.get_status()) + + gajim.interface.new_chat(contact, account, resource=resource, + session=self) + + negotiation.FeatureNegotiationWindow(account, self.jid, self, form) diff --git a/src/statusicon.py b/src/statusicon.py index 38c0e0936..f7a97a2f3 100644 --- a/src/statusicon.py +++ b/src/statusicon.py @@ -39,395 +39,393 @@ from common import helpers from common import pep class StatusIcon: - """ - Class for the notification area icon - """ + """ + Class for the notification area icon + """ - def __init__(self): - self.single_message_handler_id = None - self.new_chat_handler_id = None - # click somewhere else does not popdown menu. workaround this. - self.added_hide_menuitem = False - self.status = 'offline' - self.xml = gtkgui_helpers.get_gtk_builder('systray_context_menu.ui') - self.systray_context_menu = self.xml.get_object('systray_context_menu') - self.xml.connect_signals(self) - self.popup_menus = [] - self.status_icon = None - self.tooltip = tooltips.NotificationAreaTooltip() + def __init__(self): + self.single_message_handler_id = None + self.new_chat_handler_id = None + # click somewhere else does not popdown menu. workaround this. + self.added_hide_menuitem = False + self.status = 'offline' + self.xml = gtkgui_helpers.get_gtk_builder('systray_context_menu.ui') + self.systray_context_menu = self.xml.get_object('systray_context_menu') + self.xml.connect_signals(self) + self.popup_menus = [] + self.status_icon = None + self.tooltip = tooltips.NotificationAreaTooltip() - def subscribe_events(self): - """ - Register listeners to the events class - """ - gajim.events.event_added_subscribe(self.on_event_added) - gajim.events.event_removed_subscribe(self.on_event_removed) + def subscribe_events(self): + """ + Register listeners to the events class + """ + gajim.events.event_added_subscribe(self.on_event_added) + gajim.events.event_removed_subscribe(self.on_event_removed) - def unsubscribe_events(self): - """ - Unregister listeners to the events class - """ - gajim.events.event_added_unsubscribe(self.on_event_added) - gajim.events.event_removed_unsubscribe(self.on_event_removed) + def unsubscribe_events(self): + """ + Unregister listeners to the events class + """ + gajim.events.event_added_unsubscribe(self.on_event_added) + gajim.events.event_removed_unsubscribe(self.on_event_removed) - def on_event_added(self, event): - """ - Called when an event is added to the event list - """ - if event.show_in_systray: - self.set_img() + def on_event_added(self, event): + """ + Called when an event is added to the event list + """ + if event.show_in_systray: + self.set_img() - def on_event_removed(self, event_list): - """ - Called when one or more events are removed from the event list - """ - self.set_img() + def on_event_removed(self, event_list): + """ + Called when one or more events are removed from the event list + """ + self.set_img() - def show_icon(self): - if not self.status_icon: - self.status_icon = gtk.StatusIcon() - self.status_icon.set_property('has-tooltip', True) - self.status_icon.connect('activate', self.on_status_icon_left_clicked) - self.status_icon.connect('popup-menu', - self.on_status_icon_right_clicked) - self.status_icon.connect('query-tooltip', - self.on_status_icon_query_tooltip) + def show_icon(self): + if not self.status_icon: + self.status_icon = gtk.StatusIcon() + self.status_icon.set_property('has-tooltip', True) + self.status_icon.connect('activate', self.on_status_icon_left_clicked) + self.status_icon.connect('popup-menu', + self.on_status_icon_right_clicked) + self.status_icon.connect('query-tooltip', + self.on_status_icon_query_tooltip) - self.set_img() - self.status_icon.set_visible(True) - self.subscribe_events() + self.set_img() + self.status_icon.set_visible(True) + self.subscribe_events() - def on_status_icon_right_clicked(self, widget, event_button, event_time): - self.make_menu(event_button, event_time) + def on_status_icon_right_clicked(self, widget, event_button, event_time): + self.make_menu(event_button, event_time) - def on_status_icon_query_tooltip(self, widget, x, y, keyboard_mode, tooltip): - self.tooltip.populate() - tooltip.set_custom(self.tooltip.hbox) - return True + def on_status_icon_query_tooltip(self, widget, x, y, keyboard_mode, tooltip): + self.tooltip.populate() + tooltip.set_custom(self.tooltip.hbox) + return True - def hide_icon(self): - self.status_icon.set_visible(False) - self.unsubscribe_events() + def hide_icon(self): + self.status_icon.set_visible(False) + self.unsubscribe_events() - def on_status_icon_left_clicked(self, widget): - self.on_left_click() + def on_status_icon_left_clicked(self, widget): + self.on_left_click() - def set_img(self): - """ - Apart from image, we also update tooltip text here - """ - if not gajim.interface.systray_enabled: - return - if gajim.events.get_nb_systray_events(): - self.status_icon.set_blinking(True) - else: - self.status_icon.set_blinking(False) + def set_img(self): + """ + Apart from image, we also update tooltip text here + """ + if not gajim.interface.systray_enabled: + return + if gajim.events.get_nb_systray_events(): + self.status_icon.set_blinking(True) + else: + self.status_icon.set_blinking(False) - # FIXME: do not always use 16x16 (ask actually used size and use that) - image = gajim.interface.jabber_state_images['16'][self.status] - if image.get_storage_type() == gtk.IMAGE_PIXBUF: - self.status_icon.set_from_pixbuf(image.get_pixbuf()) - # FIXME: oops they forgot to support GIF animation? - # or they were lazy to get it to work under Windows! WTF! - elif image.get_storage_type() == gtk.IMAGE_ANIMATION: - self.status_icon.set_from_pixbuf( - image.get_animation().get_static_image()) - # self.img_tray.set_from_animation(image.get_animation()) + # FIXME: do not always use 16x16 (ask actually used size and use that) + image = gajim.interface.jabber_state_images['16'][self.status] + if image.get_storage_type() == gtk.IMAGE_PIXBUF: + self.status_icon.set_from_pixbuf(image.get_pixbuf()) + # FIXME: oops they forgot to support GIF animation? + # or they were lazy to get it to work under Windows! WTF! + elif image.get_storage_type() == gtk.IMAGE_ANIMATION: + self.status_icon.set_from_pixbuf( + image.get_animation().get_static_image()) + # self.img_tray.set_from_animation(image.get_animation()) - def change_status(self, global_status): - """ - Set tray image to 'global_status' - """ - # change image and status, only if it is different - if global_status is not None and self.status != global_status: - self.status = global_status - self.set_img() + def change_status(self, global_status): + """ + Set tray image to 'global_status' + """ + # change image and status, only if it is different + if global_status is not None and self.status != global_status: + self.status = global_status + self.set_img() - def start_chat(self, widget, account, jid): - contact = gajim.contacts.get_first_contact_from_jid(account, jid) - if gajim.interface.msg_win_mgr.has_window(jid, account): - gajim.interface.msg_win_mgr.get_window(jid, account).set_active_tab( - jid, account) - elif contact: - gajim.interface.new_chat(contact, account) - gajim.interface.msg_win_mgr.get_window(jid, account).set_active_tab( - jid, account) + def start_chat(self, widget, account, jid): + contact = gajim.contacts.get_first_contact_from_jid(account, jid) + if gajim.interface.msg_win_mgr.has_window(jid, account): + gajim.interface.msg_win_mgr.get_window(jid, account).set_active_tab( + jid, account) + elif contact: + gajim.interface.new_chat(contact, account) + gajim.interface.msg_win_mgr.get_window(jid, account).set_active_tab( + jid, account) - def on_single_message_menuitem_activate(self, widget, account): - dialogs.SingleMessageWindow(account, action='send') + def on_single_message_menuitem_activate(self, widget, account): + dialogs.SingleMessageWindow(account, action='send') - def on_new_chat(self, widget, account): - dialogs.NewChatDialog(account) + def on_new_chat(self, widget, account): + dialogs.NewChatDialog(account) - def make_menu(self, event_button, event_time): - """ - Create chat with and new message (sub) menus/menuitems - """ - for m in self.popup_menus: - m.destroy() + def make_menu(self, event_button, event_time): + """ + Create chat with and new message (sub) menus/menuitems + """ + for m in self.popup_menus: + m.destroy() - chat_with_menuitem = self.xml.get_object('chat_with_menuitem') - single_message_menuitem = self.xml.get_object( - 'single_message_menuitem') - status_menuitem = self.xml.get_object('status_menu') - join_gc_menuitem = self.xml.get_object('join_gc_menuitem') - sounds_mute_menuitem = self.xml.get_object('sounds_mute_menuitem') + chat_with_menuitem = self.xml.get_object('chat_with_menuitem') + single_message_menuitem = self.xml.get_object( + 'single_message_menuitem') + status_menuitem = self.xml.get_object('status_menu') + join_gc_menuitem = self.xml.get_object('join_gc_menuitem') + sounds_mute_menuitem = self.xml.get_object('sounds_mute_menuitem') - if self.single_message_handler_id: - single_message_menuitem.handler_disconnect( - self.single_message_handler_id) - self.single_message_handler_id = None - if self.new_chat_handler_id: - chat_with_menuitem.disconnect(self.new_chat_handler_id) - self.new_chat_handler_id = None + if self.single_message_handler_id: + single_message_menuitem.handler_disconnect( + self.single_message_handler_id) + self.single_message_handler_id = None + if self.new_chat_handler_id: + chat_with_menuitem.disconnect(self.new_chat_handler_id) + self.new_chat_handler_id = None - sub_menu = gtk.Menu() - self.popup_menus.append(sub_menu) - status_menuitem.set_submenu(sub_menu) + sub_menu = gtk.Menu() + self.popup_menus.append(sub_menu) + status_menuitem.set_submenu(sub_menu) - gc_sub_menu = gtk.Menu() # gc is always a submenu - join_gc_menuitem.set_submenu(gc_sub_menu) + gc_sub_menu = gtk.Menu() # gc is always a submenu + join_gc_menuitem.set_submenu(gc_sub_menu) - # We need our own set of status icons, let's make 'em! - iconset = gajim.config.get('iconset') - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - state_images = gtkgui_helpers.load_iconset(path) + # We need our own set of status icons, let's make 'em! + iconset = gajim.config.get('iconset') + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + state_images = gtkgui_helpers.load_iconset(path) - if 'muc_active' in state_images: - join_gc_menuitem.set_image(state_images['muc_active']) + if 'muc_active' in state_images: + join_gc_menuitem.set_image(state_images['muc_active']) - for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'): - uf_show = helpers.get_uf_show(show, use_mnemonic = True) - item = gtk.ImageMenuItem(uf_show) - item.set_image(state_images[show]) - sub_menu.append(item) - item.connect('activate', self.on_show_menuitem_activate, show) + for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'): + uf_show = helpers.get_uf_show(show, use_mnemonic = True) + item = gtk.ImageMenuItem(uf_show) + item.set_image(state_images[show]) + sub_menu.append(item) + item.connect('activate', self.on_show_menuitem_activate, show) - item = gtk.SeparatorMenuItem() - sub_menu.append(item) + item = gtk.SeparatorMenuItem() + sub_menu.append(item) - item = gtk.ImageMenuItem(_('_Change Status Message...')) - gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input') - sub_menu.append(item) - item.connect('activate', self.on_change_status_message_activate) + item = gtk.ImageMenuItem(_('_Change Status Message...')) + gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input') + sub_menu.append(item) + item.connect('activate', self.on_change_status_message_activate) - connected_accounts = gajim.get_number_of_connected_accounts() - if connected_accounts < 1: - item.set_sensitive(False) + connected_accounts = gajim.get_number_of_connected_accounts() + if connected_accounts < 1: + item.set_sensitive(False) - connected_accounts_with_private_storage = 0 + connected_accounts_with_private_storage = 0 - item = gtk.SeparatorMenuItem() - sub_menu.append(item) + item = gtk.SeparatorMenuItem() + sub_menu.append(item) - uf_show = helpers.get_uf_show('offline', use_mnemonic = True) - item = gtk.ImageMenuItem(uf_show) - item.set_image(state_images['offline']) - sub_menu.append(item) - item.connect('activate', self.on_show_menuitem_activate, 'offline') + uf_show = helpers.get_uf_show('offline', use_mnemonic = True) + item = gtk.ImageMenuItem(uf_show) + item.set_image(state_images['offline']) + sub_menu.append(item) + item.connect('activate', self.on_show_menuitem_activate, 'offline') - iskey = connected_accounts > 0 and not (connected_accounts == 1 and - gajim.connections[gajim.connections.keys()[0]].is_zeroconf) - chat_with_menuitem.set_sensitive(iskey) - single_message_menuitem.set_sensitive(iskey) - join_gc_menuitem.set_sensitive(iskey) + iskey = connected_accounts > 0 and not (connected_accounts == 1 and + gajim.connections[gajim.connections.keys()[0]].is_zeroconf) + chat_with_menuitem.set_sensitive(iskey) + single_message_menuitem.set_sensitive(iskey) + join_gc_menuitem.set_sensitive(iskey) - accounts_list = sorted(gajim.contacts.get_accounts()) - # items that get shown whether an account is zeroconf or not - if connected_accounts > 1: # 2 or more connections? make submenus - account_menu_for_chat_with = gtk.Menu() - chat_with_menuitem.set_submenu(account_menu_for_chat_with) - self.popup_menus.append(account_menu_for_chat_with) + accounts_list = sorted(gajim.contacts.get_accounts()) + # items that get shown whether an account is zeroconf or not + if connected_accounts > 1: # 2 or more connections? make submenus + account_menu_for_chat_with = gtk.Menu() + chat_with_menuitem.set_submenu(account_menu_for_chat_with) + self.popup_menus.append(account_menu_for_chat_with) - for account in accounts_list: - if gajim.account_is_connected(account): - # for chat_with - item = gtk.MenuItem(_('using account %s') % account) - account_menu_for_chat_with.append(item) - item.connect('activate', self.on_new_chat, account) + for account in accounts_list: + if gajim.account_is_connected(account): + # for chat_with + item = gtk.MenuItem(_('using account %s') % account) + account_menu_for_chat_with.append(item) + item.connect('activate', self.on_new_chat, account) - elif connected_accounts == 1: # one account - # one account connected, no need to show 'as jid' - for account in gajim.connections: - if gajim.connections[account].connected > 1: - # for start chat - self.new_chat_handler_id = chat_with_menuitem.connect( - 'activate', self.on_new_chat, account) - break # No other connected account + elif connected_accounts == 1: # one account + # one account connected, no need to show 'as jid' + for account in gajim.connections: + if gajim.connections[account].connected > 1: + # for start chat + self.new_chat_handler_id = chat_with_menuitem.connect( + 'activate', self.on_new_chat, account) + break # No other connected account - # menu items that don't apply to zeroconf connections - if connected_accounts == 1 or (connected_accounts == 2 and \ - gajim.zeroconf_is_connected()): - # only one 'real' (non-zeroconf) account is connected, don't need - # submenus - for account in gajim.connections: - if gajim.account_is_connected(account) and \ - not gajim.config.get_per('accounts', account, 'is_zeroconf'): - if gajim.connections[account].private_storage_supported: - connected_accounts_with_private_storage += 1 + # menu items that don't apply to zeroconf connections + if connected_accounts == 1 or (connected_accounts == 2 and \ + gajim.zeroconf_is_connected()): + # only one 'real' (non-zeroconf) account is connected, don't need + # submenus + for account in gajim.connections: + if gajim.account_is_connected(account) and \ + not gajim.config.get_per('accounts', account, 'is_zeroconf'): + if gajim.connections[account].private_storage_supported: + connected_accounts_with_private_storage += 1 - # for single message - single_message_menuitem.remove_submenu() - self.single_message_handler_id = single_message_menuitem.\ - connect('activate', - self.on_single_message_menuitem_activate, account) - # join gc - gajim.interface.roster.add_bookmarks_list(gc_sub_menu, - account) - break # No other account connected - else: - # 2 or more 'real' accounts are connected, make submenus - account_menu_for_single_message = gtk.Menu() - single_message_menuitem.set_submenu( - account_menu_for_single_message) - self.popup_menus.append(account_menu_for_single_message) + # for single message + single_message_menuitem.remove_submenu() + self.single_message_handler_id = single_message_menuitem.\ + connect('activate', + self.on_single_message_menuitem_activate, account) + # join gc + gajim.interface.roster.add_bookmarks_list(gc_sub_menu, + account) + break # No other account connected + else: + # 2 or more 'real' accounts are connected, make submenus + account_menu_for_single_message = gtk.Menu() + single_message_menuitem.set_submenu( + account_menu_for_single_message) + self.popup_menus.append(account_menu_for_single_message) - for account in accounts_list: - if gajim.connections[account].is_zeroconf or \ - not gajim.account_is_connected(account): - continue - if gajim.connections[account].private_storage_supported: - connected_accounts_with_private_storage += 1 - # for single message - item = gtk.MenuItem(_('using account %s') % account) - item.connect('activate', - self.on_single_message_menuitem_activate, account) - account_menu_for_single_message.append(item) + for account in accounts_list: + if gajim.connections[account].is_zeroconf or \ + not gajim.account_is_connected(account): + continue + if gajim.connections[account].private_storage_supported: + connected_accounts_with_private_storage += 1 + # for single message + item = gtk.MenuItem(_('using account %s') % account) + item.connect('activate', + self.on_single_message_menuitem_activate, account) + account_menu_for_single_message.append(item) - # join gc - gc_item = gtk.MenuItem(_('using account %s') % account, False) - gc_sub_menu.append(gc_item) - gc_menuitem_menu = gtk.Menu() - gajim.interface.roster.add_bookmarks_list(gc_menuitem_menu, - account) - gc_item.set_submenu(gc_menuitem_menu) - gc_sub_menu.show_all() + # join gc + gc_item = gtk.MenuItem(_('using account %s') % account, False) + gc_sub_menu.append(gc_item) + gc_menuitem_menu = gtk.Menu() + gajim.interface.roster.add_bookmarks_list(gc_menuitem_menu, + account) + gc_item.set_submenu(gc_menuitem_menu) + gc_sub_menu.show_all() - newitem = gtk.SeparatorMenuItem() # separator - gc_sub_menu.append(newitem) - newitem = gtk.ImageMenuItem(_('_Manage Bookmarks...')) - img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU) - newitem.set_image(img) - newitem.connect('activate', - gajim.interface.roster.on_manage_bookmarks_menuitem_activate) - gc_sub_menu.append(newitem) - if connected_accounts_with_private_storage == 0: - newitem.set_sensitive(False) + newitem = gtk.SeparatorMenuItem() # separator + gc_sub_menu.append(newitem) + newitem = gtk.ImageMenuItem(_('_Manage Bookmarks...')) + img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU) + newitem.set_image(img) + newitem.connect('activate', + gajim.interface.roster.on_manage_bookmarks_menuitem_activate) + gc_sub_menu.append(newitem) + if connected_accounts_with_private_storage == 0: + newitem.set_sensitive(False) - sounds_mute_menuitem.set_active(not gajim.config.get('sounds_on')) + sounds_mute_menuitem.set_active(not gajim.config.get('sounds_on')) - if os.name == 'nt': - if self.added_hide_menuitem is False: - self.systray_context_menu.prepend(gtk.SeparatorMenuItem()) - item = gtk.MenuItem(_('Hide this menu')) - self.systray_context_menu.prepend(item) - self.added_hide_menuitem = True + if os.name == 'nt': + if self.added_hide_menuitem is False: + self.systray_context_menu.prepend(gtk.SeparatorMenuItem()) + item = gtk.MenuItem(_('Hide this menu')) + self.systray_context_menu.prepend(item) + self.added_hide_menuitem = True - self.systray_context_menu.show_all() - self.systray_context_menu.popup(None, None, None, 0, - event_time) + self.systray_context_menu.show_all() + self.systray_context_menu.popup(None, None, None, 0, + event_time) - def on_show_all_events_menuitem_activate(self, widget): - events = gajim.events.get_systray_events() - for account in events: - for jid in events[account]: - for event in events[account][jid]: - gajim.interface.handle_event(account, jid, event.type_) + def on_show_all_events_menuitem_activate(self, widget): + events = gajim.events.get_systray_events() + for account in events: + for jid in events[account]: + for event in events[account][jid]: + gajim.interface.handle_event(account, jid, event.type_) - def on_sounds_mute_menuitem_activate(self, widget): - gajim.config.set('sounds_on', not widget.get_active()) - gajim.interface.save_config() + def on_sounds_mute_menuitem_activate(self, widget): + gajim.config.set('sounds_on', not widget.get_active()) + gajim.interface.save_config() - def on_show_roster_menuitem_activate(self, widget): - win = gajim.interface.roster.window - win.present() + def on_show_roster_menuitem_activate(self, widget): + win = gajim.interface.roster.window + win.present() - def on_preferences_menuitem_activate(self, widget): - if 'preferences' in gajim.interface.instances: - gajim.interface.instances['preferences'].window.present() - else: - gajim.interface.instances['preferences'] = config.PreferencesWindow() + def on_preferences_menuitem_activate(self, widget): + if 'preferences' in gajim.interface.instances: + gajim.interface.instances['preferences'].window.present() + else: + gajim.interface.instances['preferences'] = config.PreferencesWindow() - def on_quit_menuitem_activate(self, widget): - gajim.interface.roster.on_quit_request() + def on_quit_menuitem_activate(self, widget): + gajim.interface.roster.on_quit_request() - def on_left_click(self): - win = gajim.interface.roster.window - if len(gajim.events.get_systray_events()) == 0: - # No pending events, so toggle visible/hidden for roster window - if win.get_property('visible') and (win.get_property( - 'has-toplevel-focus') or os.name == 'nt'): - # visible in ANY virtual desktop? + def on_left_click(self): + win = gajim.interface.roster.window + if len(gajim.events.get_systray_events()) == 0: + # No pending events, so toggle visible/hidden for roster window + if win.get_property('visible') and (win.get_property( + 'has-toplevel-focus') or os.name == 'nt'): + # visible in ANY virtual desktop? - # we could be in another VD right now. eg vd2 - # and we want to show it in vd2 - if not gtkgui_helpers.possibly_move_window_in_current_desktop(win): - win.hide() # else we hide it from VD that was visible in - else: - win.show_all() - if not gajim.config.get('roster_window_skip_taskbar'): - win.set_property('skip-taskbar-hint', False) - win.present_with_time(gtk.get_current_event_time()) - else: - self.handle_first_event() + # we could be in another VD right now. eg vd2 + # and we want to show it in vd2 + if not gtkgui_helpers.possibly_move_window_in_current_desktop(win): + win.hide() # else we hide it from VD that was visible in + else: + win.show_all() + if not gajim.config.get('roster_window_skip_taskbar'): + win.set_property('skip-taskbar-hint', False) + win.present_with_time(gtk.get_current_event_time()) + else: + self.handle_first_event() - def handle_first_event(self): - account, jid, event = gajim.events.get_first_systray_event() - if not event: - return - gajim.interface.handle_event(account, jid, event.type_) + def handle_first_event(self): + account, jid, event = gajim.events.get_first_systray_event() + if not event: + return + gajim.interface.handle_event(account, jid, event.type_) - def on_middle_click(self): - """ - Middle click raises window to have complete focus (fe. get kbd events) - but if already raised, it hides it - """ - win = gajim.interface.roster.window - if win.is_active(): # is it fully raised? (eg does it receive kbd events?) - win.hide() - else: - win.present() + def on_middle_click(self): + """ + Middle click raises window to have complete focus (fe. get kbd events) + but if already raised, it hides it + """ + win = gajim.interface.roster.window + if win.is_active(): # is it fully raised? (eg does it receive kbd events?) + win.hide() + else: + win.present() - def on_clicked(self, widget, event): - self.on_tray_leave_notify_event(widget, None) - if event.type != gtk.gdk.BUTTON_PRESS: - return - if event.button == 1: # Left click - self.on_left_click() - elif event.button == 2: # middle click - self.on_middle_click() - elif event.button == 3: # right click - self.make_menu(event.button, event.time) + def on_clicked(self, widget, event): + self.on_tray_leave_notify_event(widget, None) + if event.type != gtk.gdk.BUTTON_PRESS: + return + if event.button == 1: # Left click + self.on_left_click() + elif event.button == 2: # middle click + self.on_middle_click() + elif event.button == 3: # right click + self.make_menu(event.button, event.time) - def on_show_menuitem_activate(self, widget, show): - # we all add some fake (we cannot select those nor have them as show) - # but this helps to align with roster's status_combobox index positions - l = ['online', 'chat', 'away', 'xa', 'dnd', 'invisible', 'SEPARATOR', - 'CHANGE_STATUS_MSG_MENUITEM', 'SEPARATOR', 'offline'] - index = l.index(show) - if not helpers.statuses_unified(): - gajim.interface.roster.status_combobox.set_active(index + 2) - return - current = gajim.interface.roster.status_combobox.get_active() - if index != current: - gajim.interface.roster.status_combobox.set_active(index) + def on_show_menuitem_activate(self, widget, show): + # we all add some fake (we cannot select those nor have them as show) + # but this helps to align with roster's status_combobox index positions + l = ['online', 'chat', 'away', 'xa', 'dnd', 'invisible', 'SEPARATOR', + 'CHANGE_STATUS_MSG_MENUITEM', 'SEPARATOR', 'offline'] + index = l.index(show) + if not helpers.statuses_unified(): + gajim.interface.roster.status_combobox.set_active(index + 2) + return + current = gajim.interface.roster.status_combobox.get_active() + if index != current: + gajim.interface.roster.status_combobox.set_active(index) - def on_change_status_message_activate(self, widget): - model = gajim.interface.roster.status_combobox.get_model() - active = gajim.interface.roster.status_combobox.get_active() - status = model[active][2].decode('utf-8') - def on_response(message, pep_dict): - if message is None: # None if user press Cancel - return - accounts = gajim.connections.keys() - for acct in accounts: - if not gajim.config.get_per('accounts', acct, - 'sync_with_global_status'): - continue - show = gajim.SHOW_LIST[gajim.connections[acct].connected] - gajim.interface.roster.send_status(acct, show, message) - gajim.interface.roster.send_pep(acct, pep_dict) - dlg = dialogs.ChangeStatusMessageDialog(on_response, status) - dlg.dialog.present() - -# vim: se ts=3: + def on_change_status_message_activate(self, widget): + model = gajim.interface.roster.status_combobox.get_model() + active = gajim.interface.roster.status_combobox.get_active() + status = model[active][2].decode('utf-8') + def on_response(message, pep_dict): + if message is None: # None if user press Cancel + return + accounts = gajim.connections.keys() + for acct in accounts: + if not gajim.config.get_per('accounts', acct, + 'sync_with_global_status'): + continue + show = gajim.SHOW_LIST[gajim.connections[acct].connected] + gajim.interface.roster.send_status(acct, show, message) + gajim.interface.roster.send_pep(acct, pep_dict) + dlg = dialogs.ChangeStatusMessageDialog(on_response, status) + dlg.dialog.present() diff --git a/src/tooltips.py b/src/tooltips.py index aeaf06b1c..08f12b4c2 100644 --- a/src/tooltips.py +++ b/src/tooltips.py @@ -41,737 +41,735 @@ from common import helpers from common.pep import MOODS, ACTIVITIES class BaseTooltip: - """ - Base Tooltip class + """ + Base Tooltip class - Usage: - tooltip = BaseTooltip() - .... - tooltip.show_tooltip(data, widget_height, widget_y_position) - .... - if tooltip.timeout != 0: - tooltip.hide_tooltip() + Usage: + tooltip = BaseTooltip() + .... + tooltip.show_tooltip(data, widget_height, widget_y_position) + .... + if tooltip.timeout != 0: + tooltip.hide_tooltip() - * data - the text to be displayed (extenders override this argument and - display more complex contents) - * widget_height - the height of the widget on which we want to show tooltip - * widget_y_position - the vertical position of the widget on the screen + * data - the text to be displayed (extenders override this argument and + display more complex contents) + * widget_height - the height of the widget on which we want to show tooltip + * widget_y_position - the vertical position of the widget on the screen - Tooltip is displayed aligned centered to the mouse poiner and 4px below the widget. - In case tooltip goes below the visible area it is shown above the widget. - """ + Tooltip is displayed aligned centered to the mouse poiner and 4px below the widget. + In case tooltip goes below the visible area it is shown above the widget. + """ - def __init__(self): - self.timeout = 0 - self.preferred_position = [0, 0] - self.win = None - self.id = None - self.cur_data = None - self.check_last_time = None + def __init__(self): + self.timeout = 0 + self.preferred_position = [0, 0] + self.win = None + self.id = None + self.cur_data = None + self.check_last_time = None - def populate(self, data): - """ - This method must be overriden by all extenders. This is the most simple - implementation: show data as value of a label - """ - self.create_window() - self.win.add(gtk.Label(data)) + def populate(self, data): + """ + This method must be overriden by all extenders. This is the most simple + implementation: show data as value of a label + """ + self.create_window() + self.win.add(gtk.Label(data)) - def create_window(self): - """ - Create a popup window each time tooltip is requested - """ - self.win = gtk.Window(gtk.WINDOW_POPUP) - self.win.set_border_width(3) - self.win.set_resizable(False) - self.win.set_name('gtk-tooltips') - self.win.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_TOOLTIP) + def create_window(self): + """ + Create a popup window each time tooltip is requested + """ + self.win = gtk.Window(gtk.WINDOW_POPUP) + self.win.set_border_width(3) + self.win.set_resizable(False) + self.win.set_name('gtk-tooltips') + self.win.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_TOOLTIP) - self.win.set_events(gtk.gdk.POINTER_MOTION_MASK) - self.win.connect_after('expose_event', self.expose) - self.win.connect('size-request', self.on_size_request) - self.win.connect('motion-notify-event', self.motion_notify_event) - self.screen = self.win.get_screen() + self.win.set_events(gtk.gdk.POINTER_MOTION_MASK) + self.win.connect_after('expose_event', self.expose) + self.win.connect('size-request', self.on_size_request) + self.win.connect('motion-notify-event', self.motion_notify_event) + self.screen = self.win.get_screen() - def _get_icon_name_for_tooltip(self, contact): - """ - Helper function used for tooltip contacts/acounts + def _get_icon_name_for_tooltip(self, contact): + """ + Helper function used for tooltip contacts/acounts - Tooltip on account has fake contact with sub == '', in this case we show - real status of the account - """ - if contact.ask == 'subscribe': - return 'requested' - elif contact.sub in ('both', 'to', ''): - return contact.show - return 'not in roster' + Tooltip on account has fake contact with sub == '', in this case we show + real status of the account + """ + if contact.ask == 'subscribe': + return 'requested' + elif contact.sub in ('both', 'to', ''): + return contact.show + return 'not in roster' - def motion_notify_event(self, widget, event): - self.hide_tooltip() + def motion_notify_event(self, widget, event): + self.hide_tooltip() - def on_size_request(self, widget, requisition): - half_width = requisition.width / 2 + 1 - if self.preferred_position[0] < half_width: - self.preferred_position[0] = 0 - elif self.preferred_position[0] + requisition.width > \ - self.screen.get_width() + half_width: - self.preferred_position[0] = self.screen.get_width() - \ - requisition.width - elif not self.check_last_time: - self.preferred_position[0] -= half_width - if self.preferred_position[1] + requisition.height > \ - self.screen.get_height(): - # flip tooltip up - self.preferred_position[1] -= requisition.height + \ - self.widget_height + 8 - if self.preferred_position[1] < 0: - self.preferred_position[1] = 0 - self.win.move(self.preferred_position[0], self.preferred_position[1]) + def on_size_request(self, widget, requisition): + half_width = requisition.width / 2 + 1 + if self.preferred_position[0] < half_width: + self.preferred_position[0] = 0 + elif self.preferred_position[0] + requisition.width > \ + self.screen.get_width() + half_width: + self.preferred_position[0] = self.screen.get_width() - \ + requisition.width + elif not self.check_last_time: + self.preferred_position[0] -= half_width + if self.preferred_position[1] + requisition.height > \ + self.screen.get_height(): + # flip tooltip up + self.preferred_position[1] -= requisition.height + \ + self.widget_height + 8 + if self.preferred_position[1] < 0: + self.preferred_position[1] = 0 + self.win.move(self.preferred_position[0], self.preferred_position[1]) - def expose(self, widget, event): - style = self.win.get_style() - size = self.win.get_size() - style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, - None, self.win, 'tooltip', 0, 0, -1, 1) - style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, - None, self.win, 'tooltip', 0, size[1] - 1, -1, 1) - style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, - None, self.win, 'tooltip', 0, 0, 1, -1) - style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, - None, self.win, 'tooltip', size[0] - 1, 0, 1, -1) - return True + def expose(self, widget, event): + style = self.win.get_style() + size = self.win.get_size() + style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, + None, self.win, 'tooltip', 0, 0, -1, 1) + style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, + None, self.win, 'tooltip', 0, size[1] - 1, -1, 1) + style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, + None, self.win, 'tooltip', 0, 0, 1, -1) + style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, + None, self.win, 'tooltip', size[0] - 1, 0, 1, -1) + return True - def show_tooltip(self, data, widget_height, widget_y_position): - """ - Show tooltip on widget + def show_tooltip(self, data, widget_height, widget_y_position): + """ + Show tooltip on widget - Data contains needed data for tooltip contents. - widget_height is the height of the widget on which we show the tooltip. - widget_y_position is vertical position of the widget on the screen. - """ - self.cur_data = data - # set tooltip contents - self.populate(data) + Data contains needed data for tooltip contents. + widget_height is the height of the widget on which we show the tooltip. + widget_y_position is vertical position of the widget on the screen. + """ + self.cur_data = data + # set tooltip contents + self.populate(data) - # get the X position of mouse pointer on the screen - pointer_x = self.screen.get_display().get_pointer()[1] + # get the X position of mouse pointer on the screen + pointer_x = self.screen.get_display().get_pointer()[1] - # get the prefered X position of the tooltip on the screen in case this position is > - # than the height of the screen, tooltip will be shown above the widget - preferred_y = widget_y_position + widget_height + 4 + # get the prefered X position of the tooltip on the screen in case this position is > + # than the height of the screen, tooltip will be shown above the widget + preferred_y = widget_y_position + widget_height + 4 - self.preferred_position = [pointer_x, preferred_y] - self.widget_height = widget_height - self.win.ensure_style() - self.win.show_all() + self.preferred_position = [pointer_x, preferred_y] + self.widget_height = widget_height + self.win.ensure_style() + self.win.show_all() - def hide_tooltip(self): - if self.timeout > 0: - gobject.source_remove(self.timeout) - self.timeout = 0 - if self.win: - self.win.destroy() - self.win = None - self.id = None - self.cur_data = None - self.check_last_time = None + def hide_tooltip(self): + if self.timeout > 0: + gobject.source_remove(self.timeout) + self.timeout = 0 + if self.win: + self.win.destroy() + self.win = None + self.id = None + self.cur_data = None + self.check_last_time = None class StatusTable: - """ - Contains methods for creating status table. This is used in Roster and - NotificationArea tooltips - """ + """ + Contains methods for creating status table. This is used in Roster and + NotificationArea tooltips + """ - def __init__(self): - self.current_row = 1 - self.table = None - self.text_label = None - self.spacer_label = ' ' + def __init__(self): + self.current_row = 1 + self.table = None + self.text_label = None + self.spacer_label = ' ' - def create_table(self): - self.table = gtk.Table(4, 1) - self.table.set_property('column-spacing', 2) + def create_table(self): + self.table = gtk.Table(4, 1) + self.table.set_property('column-spacing', 2) - def add_text_row(self, text, col_inc = 0): - self.current_row += 1 - self.text_label = gtk.Label() - self.text_label.set_line_wrap(True) - self.text_label.set_alignment(0, 0) - self.text_label.set_selectable(False) - self.text_label.set_markup(text) - self.table.attach(self.text_label, 1 + col_inc, 4, self.current_row, - self.current_row + 1) + def add_text_row(self, text, col_inc = 0): + self.current_row += 1 + self.text_label = gtk.Label() + self.text_label.set_line_wrap(True) + self.text_label.set_alignment(0, 0) + self.text_label.set_selectable(False) + self.text_label.set_markup(text) + self.table.attach(self.text_label, 1 + col_inc, 4, self.current_row, + self.current_row + 1) - def get_status_info(self, resource, priority, show, status): - str_status = resource + ' (' + unicode(priority) + ')' - if status: - status = status.strip() - if status != '': - # make sure 'status' is unicode before we send to to reduce_chars - if isinstance(status, str): - status = unicode(status, encoding='utf-8') - # reduce to 100 chars, 1 line - status = helpers.reduce_chars_newlines(status, 100, 1) - str_status = gobject.markup_escape_text(str_status) - status = gobject.markup_escape_text(status) - str_status += ' - ' + status + '' - return str_status + def get_status_info(self, resource, priority, show, status): + str_status = resource + ' (' + unicode(priority) + ')' + if status: + status = status.strip() + if status != '': + # make sure 'status' is unicode before we send to to reduce_chars + if isinstance(status, str): + status = unicode(status, encoding='utf-8') + # reduce to 100 chars, 1 line + status = helpers.reduce_chars_newlines(status, 100, 1) + str_status = gobject.markup_escape_text(str_status) + status = gobject.markup_escape_text(status) + str_status += ' - ' + status + '' + return str_status - def add_status_row(self, file_path, show, str_status, status_time=None, - show_lock=False, indent=True): - """ - Append a new row with status icon to the table - """ - self.current_row += 1 - state_file = show.replace(' ', '_') - files = [] - files.append(os.path.join(file_path, state_file + '.png')) - files.append(os.path.join(file_path, state_file + '.gif')) - image = gtk.Image() - image.set_from_pixbuf(None) - for f in files: - if os.path.exists(f): - image.set_from_file(f) - break - spacer = gtk.Label(self.spacer_label) - image.set_alignment(1, 0.5) - if indent: - self.table.attach(spacer, 1, 2, self.current_row, - self.current_row + 1, 0, 0, 0, 0) - self.table.attach(image, 2, 3, self.current_row, - self.current_row + 1, gtk.FILL, gtk.FILL, 2, 0) - status_label = gtk.Label() - status_label.set_markup(str_status) - status_label.set_alignment(0, 0) - status_label.set_line_wrap(True) - self.table.attach(status_label, 3, 4, self.current_row, - self.current_row + 1, gtk.FILL | gtk.EXPAND, 0, 0, 0) - if show_lock: - lock_image = gtk.Image() - lock_image.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, - gtk.ICON_SIZE_MENU) - self.table.attach(lock_image, 4, 5, self.current_row, - self.current_row + 1, 0, 0, 0, 0) + def add_status_row(self, file_path, show, str_status, status_time=None, + show_lock=False, indent=True): + """ + Append a new row with status icon to the table + """ + self.current_row += 1 + state_file = show.replace(' ', '_') + files = [] + files.append(os.path.join(file_path, state_file + '.png')) + files.append(os.path.join(file_path, state_file + '.gif')) + image = gtk.Image() + image.set_from_pixbuf(None) + for f in files: + if os.path.exists(f): + image.set_from_file(f) + break + spacer = gtk.Label(self.spacer_label) + image.set_alignment(1, 0.5) + if indent: + self.table.attach(spacer, 1, 2, self.current_row, + self.current_row + 1, 0, 0, 0, 0) + self.table.attach(image, 2, 3, self.current_row, + self.current_row + 1, gtk.FILL, gtk.FILL, 2, 0) + status_label = gtk.Label() + status_label.set_markup(str_status) + status_label.set_alignment(0, 0) + status_label.set_line_wrap(True) + self.table.attach(status_label, 3, 4, self.current_row, + self.current_row + 1, gtk.FILL | gtk.EXPAND, 0, 0, 0) + if show_lock: + lock_image = gtk.Image() + lock_image.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, + gtk.ICON_SIZE_MENU) + self.table.attach(lock_image, 4, 5, self.current_row, + self.current_row + 1, 0, 0, 0, 0) class NotificationAreaTooltip(BaseTooltip, StatusTable): - """ - Tooltip that is shown in the notification area - """ + """ + Tooltip that is shown in the notification area + """ - def __init__(self): - BaseTooltip.__init__(self) - StatusTable.__init__(self) + def __init__(self): + BaseTooltip.__init__(self) + StatusTable.__init__(self) - def fill_table_with_accounts(self, accounts): - iconset = gajim.config.get('iconset') - if not iconset: - iconset = 'dcraven' - file_path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - for acct in accounts: - message = acct['message'] - # before reducing the chars we should assure we send unicode, else - # there are possible pango TBs on 'set_markup' - if isinstance(message, str): - message = unicode(message, encoding = 'utf-8') - message = helpers.reduce_chars_newlines(message, 100, 1) - message = gobject.markup_escape_text(message) - if acct['name'] in gajim.con_types and \ - gajim.con_types[acct['name']] in ('tls', 'ssl'): - show_lock = True - else: - show_lock = False - if message: - self.add_status_row(file_path, acct['show'], - gobject.markup_escape_text(acct['name']) + \ - ' - ' + message, show_lock=show_lock, indent=False) - else: - self.add_status_row(file_path, acct['show'], - gobject.markup_escape_text(acct['name']) - , show_lock=show_lock, indent=False) - for line in acct['event_lines']: - self.add_text_row(' ' + line, 1) + def fill_table_with_accounts(self, accounts): + iconset = gajim.config.get('iconset') + if not iconset: + iconset = 'dcraven' + file_path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + for acct in accounts: + message = acct['message'] + # before reducing the chars we should assure we send unicode, else + # there are possible pango TBs on 'set_markup' + if isinstance(message, str): + message = unicode(message, encoding = 'utf-8') + message = helpers.reduce_chars_newlines(message, 100, 1) + message = gobject.markup_escape_text(message) + if acct['name'] in gajim.con_types and \ + gajim.con_types[acct['name']] in ('tls', 'ssl'): + show_lock = True + else: + show_lock = False + if message: + self.add_status_row(file_path, acct['show'], + gobject.markup_escape_text(acct['name']) + \ + ' - ' + message, show_lock=show_lock, indent=False) + else: + self.add_status_row(file_path, acct['show'], + gobject.markup_escape_text(acct['name']) + , show_lock=show_lock, indent=False) + for line in acct['event_lines']: + self.add_text_row(' ' + line, 1) - def populate(self, data=''): - self.create_window() - self.create_table() + def populate(self, data=''): + self.create_window() + self.create_table() - accounts = helpers.get_notification_icon_tooltip_dict() - self.table.resize(2, 1) - self.fill_table_with_accounts(accounts) - self.hbox = gtk.HBox() - self.table.set_property('column-spacing', 1) + accounts = helpers.get_notification_icon_tooltip_dict() + self.table.resize(2, 1) + self.fill_table_with_accounts(accounts) + self.hbox = gtk.HBox() + self.table.set_property('column-spacing', 1) - self.hbox.add(self.table) - self.hbox.show_all() + self.hbox.add(self.table) + self.hbox.show_all() class GCTooltip(BaseTooltip): - """ - Tooltip that is shown in the GC treeview - """ + """ + Tooltip that is shown in the GC treeview + """ - def __init__(self): - self.account = None - self.text_label = gtk.Label() - self.text_label.set_line_wrap(True) - self.text_label.set_alignment(0, 0) - self.text_label.set_selectable(False) - self.avatar_image = gtk.Image() + def __init__(self): + self.account = None + self.text_label = gtk.Label() + self.text_label.set_line_wrap(True) + self.text_label.set_alignment(0, 0) + self.text_label.set_selectable(False) + self.avatar_image = gtk.Image() - BaseTooltip.__init__(self) + BaseTooltip.__init__(self) - def populate(self, contact): - if not contact: - return - self.create_window() - vcard_table = gtk.Table(3, 1) - vcard_table.set_property('column-spacing', 2) - vcard_table.set_homogeneous(False) - vcard_current_row = 1 - properties = [] + def populate(self, contact): + if not contact: + return + self.create_window() + vcard_table = gtk.Table(3, 1) + vcard_table.set_property('column-spacing', 2) + vcard_table.set_homogeneous(False) + vcard_current_row = 1 + properties = [] - nick_markup = '' + \ - gobject.markup_escape_text(contact.get_shown_name()) \ - + '' - properties.append((nick_markup, None)) + nick_markup = '' + \ + gobject.markup_escape_text(contact.get_shown_name()) \ + + '' + properties.append((nick_markup, None)) - if contact.status: # status message - status = contact.status.strip() - if status != '': - # escape markup entities - status = helpers.reduce_chars_newlines(status, 300, 5) - status = '' +\ - gobject.markup_escape_text(status) + '' - properties.append((status, None)) - else: # no status message, show SHOW instead - show = helpers.get_uf_show(contact.show) - show = '' + show + '' - properties.append((show, None)) + if contact.status: # status message + status = contact.status.strip() + if status != '': + # escape markup entities + status = helpers.reduce_chars_newlines(status, 300, 5) + status = '' +\ + gobject.markup_escape_text(status) + '' + properties.append((status, None)) + else: # no status message, show SHOW instead + show = helpers.get_uf_show(contact.show) + show = '' + show + '' + properties.append((show, None)) - if contact.jid.strip() != '': - properties.append((_('Jabber ID: '), contact.jid)) + if contact.jid.strip() != '': + properties.append((_('Jabber ID: '), contact.jid)) - if hasattr(contact, 'resource') and contact.resource.strip() != '': - properties.append((_('Resource: '), - gobject.markup_escape_text(contact.resource) )) - if contact.affiliation != 'none': - uf_affiliation = helpers.get_uf_affiliation(contact.affiliation) - affiliation_str = \ - _('%(owner_or_admin_or_member)s of this group chat') %\ - {'owner_or_admin_or_member': uf_affiliation} - properties.append((affiliation_str, None)) + if hasattr(contact, 'resource') and contact.resource.strip() != '': + properties.append((_('Resource: '), + gobject.markup_escape_text(contact.resource) )) + if contact.affiliation != 'none': + uf_affiliation = helpers.get_uf_affiliation(contact.affiliation) + affiliation_str = \ + _('%(owner_or_admin_or_member)s of this group chat') %\ + {'owner_or_admin_or_member': uf_affiliation} + properties.append((affiliation_str, None)) - # Add avatar - puny_name = helpers.sanitize_filename(contact.name) - puny_room = helpers.sanitize_filename(contact.room_jid) - file_ = helpers.get_avatar_path(os.path.join(gajim.AVATAR_PATH, puny_room, - puny_name)) - if file_: - self.avatar_image.set_from_file(file_) - pix = self.avatar_image.get_pixbuf() - pix = gtkgui_helpers.get_scaled_pixbuf(pix, 'tooltip') - self.avatar_image.set_from_pixbuf(pix) - else: - self.avatar_image.set_from_pixbuf(None) - while properties: - property_ = properties.pop(0) - vcard_current_row += 1 - vertical_fill = gtk.FILL - if not properties: - vertical_fill |= gtk.EXPAND - label = gtk.Label() - label.set_alignment(0, 0) - if property_[1]: - label.set_markup(property_[0]) - vcard_table.attach(label, 1, 2, vcard_current_row, - vcard_current_row + 1, gtk.FILL, vertical_fill, 0, 0) - label = gtk.Label() - label.set_alignment(0, 0) - label.set_markup(property_[1]) - label.set_line_wrap(True) - vcard_table.attach(label, 2, 3, vcard_current_row, - vcard_current_row + 1, gtk.EXPAND | gtk.FILL, - vertical_fill, 0, 0) - else: - label.set_markup(property_[0]) - label.set_line_wrap(True) - vcard_table.attach(label, 1, 3, vcard_current_row, - vcard_current_row + 1, gtk.FILL, vertical_fill, 0) + # Add avatar + puny_name = helpers.sanitize_filename(contact.name) + puny_room = helpers.sanitize_filename(contact.room_jid) + file_ = helpers.get_avatar_path(os.path.join(gajim.AVATAR_PATH, puny_room, + puny_name)) + if file_: + self.avatar_image.set_from_file(file_) + pix = self.avatar_image.get_pixbuf() + pix = gtkgui_helpers.get_scaled_pixbuf(pix, 'tooltip') + self.avatar_image.set_from_pixbuf(pix) + else: + self.avatar_image.set_from_pixbuf(None) + while properties: + property_ = properties.pop(0) + vcard_current_row += 1 + vertical_fill = gtk.FILL + if not properties: + vertical_fill |= gtk.EXPAND + label = gtk.Label() + label.set_alignment(0, 0) + if property_[1]: + label.set_markup(property_[0]) + vcard_table.attach(label, 1, 2, vcard_current_row, + vcard_current_row + 1, gtk.FILL, vertical_fill, 0, 0) + label = gtk.Label() + label.set_alignment(0, 0) + label.set_markup(property_[1]) + label.set_line_wrap(True) + vcard_table.attach(label, 2, 3, vcard_current_row, + vcard_current_row + 1, gtk.EXPAND | gtk.FILL, + vertical_fill, 0, 0) + else: + label.set_markup(property_[0]) + label.set_line_wrap(True) + vcard_table.attach(label, 1, 3, vcard_current_row, + vcard_current_row + 1, gtk.FILL, vertical_fill, 0) - self.avatar_image.set_alignment(0, 0) - vcard_table.attach(self.avatar_image, 3, 4, 2, vcard_current_row + 1, - gtk.FILL, gtk.FILL | gtk.EXPAND, 3, 3) - self.win.add(vcard_table) + self.avatar_image.set_alignment(0, 0) + vcard_table.attach(self.avatar_image, 3, 4, 2, vcard_current_row + 1, + gtk.FILL, gtk.FILL | gtk.EXPAND, 3, 3) + self.win.add(vcard_table) class RosterTooltip(NotificationAreaTooltip): - """ - Tooltip that is shown in the roster treeview - """ + """ + Tooltip that is shown in the roster treeview + """ - def __init__(self): - self.account = None - self.image = gtk.Image() - self.image.set_alignment(0, 0) - # padding is independent of the total length and better than alignment - self.image.set_padding(1, 2) - self.avatar_image = gtk.Image() - NotificationAreaTooltip.__init__(self) + def __init__(self): + self.account = None + self.image = gtk.Image() + self.image.set_alignment(0, 0) + # padding is independent of the total length and better than alignment + self.image.set_padding(1, 2) + self.avatar_image = gtk.Image() + NotificationAreaTooltip.__init__(self) - def populate(self, contacts): - self.create_window() + def populate(self, contacts): + self.create_window() - self.create_table() - if not contacts or len(contacts) == 0: - # Tooltip for merged accounts row - accounts = helpers.get_notification_icon_tooltip_dict() - self.table.resize(2, 1) - self.spacer_label = '' - self.fill_table_with_accounts(accounts) - self.win.add(self.table) - return + self.create_table() + if not contacts or len(contacts) == 0: + # Tooltip for merged accounts row + accounts = helpers.get_notification_icon_tooltip_dict() + self.table.resize(2, 1) + self.spacer_label = '' + self.fill_table_with_accounts(accounts) + self.win.add(self.table) + return - # primary contact - prim_contact = gajim.contacts.get_highest_prio_contact_from_contacts( - contacts) + # primary contact + prim_contact = gajim.contacts.get_highest_prio_contact_from_contacts( + contacts) - puny_jid = helpers.sanitize_filename(prim_contact.jid) - table_size = 3 + puny_jid = helpers.sanitize_filename(prim_contact.jid) + table_size = 3 - file_ = helpers.get_avatar_path(os.path.join(gajim.AVATAR_PATH, puny_jid)) - if file_: - self.avatar_image.set_from_file(file_) - pix = self.avatar_image.get_pixbuf() - pix = gtkgui_helpers.get_scaled_pixbuf(pix, 'tooltip') - self.avatar_image.set_from_pixbuf(pix) - table_size = 4 - else: - self.avatar_image.set_from_pixbuf(None) - vcard_table = gtk.Table(table_size, 1) - vcard_table.set_property('column-spacing', 2) - vcard_table.set_homogeneous(False) - vcard_current_row = 1 - properties = [] + file_ = helpers.get_avatar_path(os.path.join(gajim.AVATAR_PATH, puny_jid)) + if file_: + self.avatar_image.set_from_file(file_) + pix = self.avatar_image.get_pixbuf() + pix = gtkgui_helpers.get_scaled_pixbuf(pix, 'tooltip') + self.avatar_image.set_from_pixbuf(pix) + table_size = 4 + else: + self.avatar_image.set_from_pixbuf(None) + vcard_table = gtk.Table(table_size, 1) + vcard_table.set_property('column-spacing', 2) + vcard_table.set_homogeneous(False) + vcard_current_row = 1 + properties = [] - name_markup = u'' + \ - gobject.markup_escape_text(prim_contact.get_shown_name())\ - + '' - if self.account and helpers.jid_is_blocked(self.account, - prim_contact.jid): - name_markup += _(' [blocked]') - if self.account and \ - self.account in gajim.interface.minimized_controls and \ - prim_contact.jid in gajim.interface.minimized_controls[self.account]: - name_markup += _(' [minimized]') - properties.append((name_markup, None)) + name_markup = u'' + \ + gobject.markup_escape_text(prim_contact.get_shown_name())\ + + '' + if self.account and helpers.jid_is_blocked(self.account, + prim_contact.jid): + name_markup += _(' [blocked]') + if self.account and \ + self.account in gajim.interface.minimized_controls and \ + prim_contact.jid in gajim.interface.minimized_controls[self.account]: + name_markup += _(' [minimized]') + properties.append((name_markup, None)) - num_resources = 0 - # put contacts in dict, where key is priority - contacts_dict = {} - for contact in contacts: - if contact.resource: - num_resources += 1 - if contact.priority in contacts_dict: - contacts_dict[contact.priority].append(contact) - else: - contacts_dict[contact.priority] = [contact] + num_resources = 0 + # put contacts in dict, where key is priority + contacts_dict = {} + for contact in contacts: + if contact.resource: + num_resources += 1 + if contact.priority in contacts_dict: + contacts_dict[contact.priority].append(contact) + else: + contacts_dict[contact.priority] = [contact] - if num_resources > 1: - properties.append((_('Status: '), ' ')) - transport = gajim.get_transport_name_from_jid( - prim_contact.jid) - if transport: - file_path = os.path.join(helpers.get_transport_path(transport), - '16x16') - else: - iconset = gajim.config.get('iconset') - if not iconset: - iconset = 'dcraven' - file_path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + if num_resources > 1: + properties.append((_('Status: '), ' ')) + transport = gajim.get_transport_name_from_jid( + prim_contact.jid) + if transport: + file_path = os.path.join(helpers.get_transport_path(transport), + '16x16') + else: + iconset = gajim.config.get('iconset') + if not iconset: + iconset = 'dcraven' + file_path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - contact_keys = sorted(contacts_dict.keys()) - contact_keys.reverse() - for priority in contact_keys: - for acontact in contacts_dict[priority]: - status_line = self.get_status_info(acontact.resource, - acontact.priority, acontact.show, acontact.status) + contact_keys = sorted(contacts_dict.keys()) + contact_keys.reverse() + for priority in contact_keys: + for acontact in contacts_dict[priority]: + status_line = self.get_status_info(acontact.resource, + acontact.priority, acontact.show, acontact.status) - icon_name = self._get_icon_name_for_tooltip(acontact) - self.add_status_row(file_path, icon_name, status_line, - acontact.last_status_time) - properties.append((self.table, None)) + icon_name = self._get_icon_name_for_tooltip(acontact) + self.add_status_row(file_path, icon_name, status_line, + acontact.last_status_time) + properties.append((self.table, None)) - else: # only one resource - if contact.show: - show = helpers.get_uf_show(contact.show) - if not self.check_last_time and self.account: - if contact.show == 'offline': - if not contact.last_status_time: - gajim.connections[self.account].request_last_status_time( - contact.jid, '') - else: - self.check_last_time = contact.last_status_time - elif contact.resource: - gajim.connections[self.account].request_last_status_time( - contact.jid, contact.resource) - if contact.last_activity_time: - self.check_last_time = contact.last_activity_time - else: - self.check_last_time = None - if contact.last_status_time: - vcard_current_row += 1 - if contact.show == 'offline': - text = ' - ' + _('Last status: %s') - else: - text = _(' since %s') + else: # only one resource + if contact.show: + show = helpers.get_uf_show(contact.show) + if not self.check_last_time and self.account: + if contact.show == 'offline': + if not contact.last_status_time: + gajim.connections[self.account].request_last_status_time( + contact.jid, '') + else: + self.check_last_time = contact.last_status_time + elif contact.resource: + gajim.connections[self.account].request_last_status_time( + contact.jid, contact.resource) + if contact.last_activity_time: + self.check_last_time = contact.last_activity_time + else: + self.check_last_time = None + if contact.last_status_time: + vcard_current_row += 1 + if contact.show == 'offline': + text = ' - ' + _('Last status: %s') + else: + text = _(' since %s') - if time.strftime('%j', time.localtime())== \ - time.strftime('%j', contact.last_status_time): - # it's today, show only the locale hour representation - local_time = time.strftime('%X', - contact.last_status_time) - else: - # time.strftime returns locale encoded string - local_time = time.strftime('%c', - contact.last_status_time) - local_time = local_time.decode( - locale.getpreferredencoding()) - text = text % local_time - show += text - if self.account and \ - prim_contact.jid in gajim.gc_connected[self.account]: - if gajim.gc_connected[self.account][prim_contact.jid]: - show = _('Connected') - else: - show = _('Disconnected') - show = '' + show + '' - # we append show below + if time.strftime('%j', time.localtime())== \ + time.strftime('%j', contact.last_status_time): + # it's today, show only the locale hour representation + local_time = time.strftime('%X', + contact.last_status_time) + else: + # time.strftime returns locale encoded string + local_time = time.strftime('%c', + contact.last_status_time) + local_time = local_time.decode( + locale.getpreferredencoding()) + text = text % local_time + show += text + if self.account and \ + prim_contact.jid in gajim.gc_connected[self.account]: + if gajim.gc_connected[self.account][prim_contact.jid]: + show = _('Connected') + else: + show = _('Disconnected') + show = '' + show + '' + # we append show below - if contact.status: - status = contact.status.strip() - if status: - # reduce long status - # (no more than 300 chars on line and no more than 5 lines) - # status is wrapped - status = helpers.reduce_chars_newlines(status, 300, 5) - # escape markup entities. - status = gobject.markup_escape_text(status) - properties.append(('%s' % status, None)) - properties.append((show, None)) + if contact.status: + status = contact.status.strip() + if status: + # reduce long status + # (no more than 300 chars on line and no more than 5 lines) + # status is wrapped + status = helpers.reduce_chars_newlines(status, 300, 5) + # escape markup entities. + status = gobject.markup_escape_text(status) + properties.append(('%s' % status, None)) + properties.append((show, None)) - self._append_pep_info(contact, properties) + self._append_pep_info(contact, properties) - properties.append((_('Jabber ID: '), prim_contact.jid )) + properties.append((_('Jabber ID: '), prim_contact.jid )) - # contact has only one ressource - if num_resources == 1 and contact.resource: - properties.append((_('Resource: '), - gobject.markup_escape_text(contact.resource) +\ - ' (' + unicode(contact.priority) + ')')) + # contact has only one ressource + if num_resources == 1 and contact.resource: + properties.append((_('Resource: '), + gobject.markup_escape_text(contact.resource) +\ + ' (' + unicode(contact.priority) + ')')) - if self.account and prim_contact.sub and prim_contact.sub != 'both' and\ - prim_contact.jid not in gajim.gc_connected[self.account]: - # ('both' is the normal sub so we don't show it) - properties.append(( _('Subscription: '), - gobject.markup_escape_text(helpers.get_uf_sub(prim_contact.sub)))) + if self.account and prim_contact.sub and prim_contact.sub != 'both' and\ + prim_contact.jid not in gajim.gc_connected[self.account]: + # ('both' is the normal sub so we don't show it) + properties.append(( _('Subscription: '), + gobject.markup_escape_text(helpers.get_uf_sub(prim_contact.sub)))) - if prim_contact.keyID: - keyID = None - if len(prim_contact.keyID) == 8: - keyID = prim_contact.keyID - elif len(prim_contact.keyID) == 16: - keyID = prim_contact.keyID[8:] - if keyID: - properties.append((_('OpenPGP: '), - gobject.markup_escape_text(keyID))) + if prim_contact.keyID: + keyID = None + if len(prim_contact.keyID) == 8: + keyID = prim_contact.keyID + elif len(prim_contact.keyID) == 16: + keyID = prim_contact.keyID[8:] + if keyID: + properties.append((_('OpenPGP: '), + gobject.markup_escape_text(keyID))) - if contact.last_activity_time: - text = _(' since %s') + if contact.last_activity_time: + text = _(' since %s') - if time.strftime('%j', time.localtime())== \ - time.strftime('%j', contact.last_activity_time): - # it's today, show only the locale hour representation - local_time = time.strftime('%I:%M %p', - contact.last_activity_time) - else: - # time.strftime returns locale encoded string - local_time = time.strftime('%c', - contact.last_activity_time) - local_time = local_time.decode( - locale.getpreferredencoding()) - text = text % local_time - properties.append(('Idle' + text,None)) + if time.strftime('%j', time.localtime())== \ + time.strftime('%j', contact.last_activity_time): + # it's today, show only the locale hour representation + local_time = time.strftime('%I:%M %p', + contact.last_activity_time) + else: + # time.strftime returns locale encoded string + local_time = time.strftime('%c', + contact.last_activity_time) + local_time = local_time.decode( + locale.getpreferredencoding()) + text = text % local_time + properties.append(('Idle' + text, None)) - while properties: - property_ = properties.pop(0) - vcard_current_row += 1 - vertical_fill = gtk.FILL - if not properties and table_size == 4: - vertical_fill |= gtk.EXPAND - label = gtk.Label() - label.set_alignment(0, 0) - if property_[1]: - label.set_markup(property_[0]) - vcard_table.attach(label, 1, 2, vcard_current_row, - vcard_current_row + 1, gtk.FILL, vertical_fill, 0, 0) - label = gtk.Label() - label.set_alignment(0, 0) - label.set_markup(property_[1]) - label.set_line_wrap(True) - vcard_table.attach(label, 2, 3, vcard_current_row, - vcard_current_row + 1, gtk.EXPAND | gtk.FILL, - vertical_fill, 0, 0) - else: - if isinstance(property_[0], (unicode, str)): # FIXME: rm unicode? - label.set_markup(property_[0]) - label.set_line_wrap(True) - else: - label = property_[0] - vcard_table.attach(label, 1, 3, vcard_current_row, - vcard_current_row + 1, gtk.FILL, vertical_fill, 0) - self.avatar_image.set_alignment(0, 0) - if table_size == 4: - vcard_table.attach(self.avatar_image, 3, 4, 2, - vcard_current_row + 1, gtk.FILL, gtk.FILL | gtk.EXPAND, 3, 3) - self.win.add(vcard_table) + while properties: + property_ = properties.pop(0) + vcard_current_row += 1 + vertical_fill = gtk.FILL + if not properties and table_size == 4: + vertical_fill |= gtk.EXPAND + label = gtk.Label() + label.set_alignment(0, 0) + if property_[1]: + label.set_markup(property_[0]) + vcard_table.attach(label, 1, 2, vcard_current_row, + vcard_current_row + 1, gtk.FILL, vertical_fill, 0, 0) + label = gtk.Label() + label.set_alignment(0, 0) + label.set_markup(property_[1]) + label.set_line_wrap(True) + vcard_table.attach(label, 2, 3, vcard_current_row, + vcard_current_row + 1, gtk.EXPAND | gtk.FILL, + vertical_fill, 0, 0) + else: + if isinstance(property_[0], (unicode, str)): # FIXME: rm unicode? + label.set_markup(property_[0]) + label.set_line_wrap(True) + else: + label = property_[0] + vcard_table.attach(label, 1, 3, vcard_current_row, + vcard_current_row + 1, gtk.FILL, vertical_fill, 0) + self.avatar_image.set_alignment(0, 0) + if table_size == 4: + vcard_table.attach(self.avatar_image, 3, 4, 2, + vcard_current_row + 1, gtk.FILL, gtk.FILL | gtk.EXPAND, 3, 3) + self.win.add(vcard_table) - def update_last_time(self, last_time): - if not self.check_last_time or time.strftime('%x %I:%M %p', last_time) !=\ - time.strftime('%x %I:%M %p', self.check_last_time): - self.win.destroy() - self.win = None - self.populate(self.cur_data) - self.win.ensure_style() - self.win.show_all() + def update_last_time(self, last_time): + if not self.check_last_time or time.strftime('%x %I:%M %p', last_time) !=\ + time.strftime('%x %I:%M %p', self.check_last_time): + self.win.destroy() + self.win = None + self.populate(self.cur_data) + self.win.ensure_style() + self.win.show_all() - def _append_pep_info(self, contact, properties): - """ - Append Tune, Mood, Activity, Location information of the specified contact - to the given property list. - """ - if 'mood' in contact.pep: - mood = contact.pep['mood'].asMarkupText() - mood_string = _('Mood:') + ' %s' % mood - properties.append((mood_string, None)) + def _append_pep_info(self, contact, properties): + """ + Append Tune, Mood, Activity, Location information of the specified contact + to the given property list. + """ + if 'mood' in contact.pep: + mood = contact.pep['mood'].asMarkupText() + mood_string = _('Mood:') + ' %s' % mood + properties.append((mood_string, None)) - if 'activity' in contact.pep: - activity = contact.pep['activity'].asMarkupText() - activity_string = _('Activity:') + ' %s' % activity - properties.append((activity_string, None)) + if 'activity' in contact.pep: + activity = contact.pep['activity'].asMarkupText() + activity_string = _('Activity:') + ' %s' % activity + properties.append((activity_string, None)) - if 'tune' in contact.pep: - tune = contact.pep['tune'].asMarkupText() - tune_string = _('Tune:') + ' %s' % tune - properties.append((tune_string, None)) + if 'tune' in contact.pep: + tune = contact.pep['tune'].asMarkupText() + tune_string = _('Tune:') + ' %s' % tune + properties.append((tune_string, None)) - if 'location' in contact.pep: - location = contact.pep['location'].asMarkupText() - location_string = _('Location:') + ' %s' % location - properties.append((location_string, None)) + if 'location' in contact.pep: + location = contact.pep['location'].asMarkupText() + location_string = _('Location:') + ' %s' % location + properties.append((location_string, None)) class FileTransfersTooltip(BaseTooltip): - """ - Tooltip that is shown in the notification area - """ + """ + Tooltip that is shown in the notification area + """ - def __init__(self): - BaseTooltip.__init__(self) + def __init__(self): + BaseTooltip.__init__(self) - def populate(self, file_props): - ft_table = gtk.Table(2, 1) - ft_table.set_property('column-spacing', 2) - current_row = 1 - self.create_window() - properties = [] - name = file_props['name'] - if file_props['type'] == 'r': - file_name = os.path.split(file_props['file-name'])[1] - else: - file_name = file_props['name'] - properties.append((_('Name: '), - gobject.markup_escape_text(file_name))) - if file_props['type'] == 'r': - type_ = _('Download') - actor = _('Sender: ') - sender = unicode(file_props['sender']).split('/')[0] - name = gajim.contacts.get_first_contact_from_jid( - file_props['tt_account'], sender).get_shown_name() - else: - type_ = _('Upload') - actor = _('Recipient: ') - receiver = file_props['receiver'] - if hasattr(receiver, 'name'): - name = receiver.get_shown_name() - else: - name = receiver.split('/')[0] - properties.append((_('Type: '), type_)) - properties.append((actor, gobject.markup_escape_text(name))) + def populate(self, file_props): + ft_table = gtk.Table(2, 1) + ft_table.set_property('column-spacing', 2) + current_row = 1 + self.create_window() + properties = [] + name = file_props['name'] + if file_props['type'] == 'r': + file_name = os.path.split(file_props['file-name'])[1] + else: + file_name = file_props['name'] + properties.append((_('Name: '), + gobject.markup_escape_text(file_name))) + if file_props['type'] == 'r': + type_ = _('Download') + actor = _('Sender: ') + sender = unicode(file_props['sender']).split('/')[0] + name = gajim.contacts.get_first_contact_from_jid( + file_props['tt_account'], sender).get_shown_name() + else: + type_ = _('Upload') + actor = _('Recipient: ') + receiver = file_props['receiver'] + if hasattr(receiver, 'name'): + name = receiver.get_shown_name() + else: + name = receiver.split('/')[0] + properties.append((_('Type: '), type_)) + properties.append((actor, gobject.markup_escape_text(name))) - transfered_len = file_props.get('received-len', 0) - properties.append((_('Transferred: '), helpers.convert_bytes(transfered_len))) - status = '' - if 'started' not in file_props or not file_props['started']: - status = _('Not started') - elif 'connected' in file_props: - if 'stopped' in file_props and \ - file_props['stopped'] == True: - status = _('Stopped') - elif file_props['completed']: - status = _('Completed') - elif file_props['connected'] == False: - if file_props['completed']: - status = _('Completed') - else: - if 'paused' in file_props and \ - file_props['paused'] == True: - status = _('?transfer status:Paused') - elif 'stalled' in file_props and \ - file_props['stalled'] == True: - #stalled is not paused. it is like 'frozen' it stopped alone - status = _('Stalled') - else: - status = _('Transferring') - else: - status = _('Not started') - properties.append((_('Status: '), status)) - if 'desc' in file_props: - file_desc = file_props['desc'] - properties.append((_('Description: '), gobject.markup_escape_text( - file_desc))) - while properties: - property_ = properties.pop(0) - current_row += 1 - label = gtk.Label() - label.set_alignment(0, 0) - label.set_markup(property_[0]) - ft_table.attach(label, 1, 2, current_row, current_row + 1, - gtk.FILL, gtk.FILL, 0, 0) - label = gtk.Label() - label.set_alignment(0, 0) - label.set_line_wrap(True) - label.set_markup(property_[1]) - ft_table.attach(label, 2, 3, current_row, current_row + 1, - gtk.EXPAND | gtk.FILL, gtk.FILL, 0, 0) + transfered_len = file_props.get('received-len', 0) + properties.append((_('Transferred: '), helpers.convert_bytes(transfered_len))) + status = '' + if 'started' not in file_props or not file_props['started']: + status = _('Not started') + elif 'connected' in file_props: + if 'stopped' in file_props and \ + file_props['stopped'] == True: + status = _('Stopped') + elif file_props['completed']: + status = _('Completed') + elif file_props['connected'] == False: + if file_props['completed']: + status = _('Completed') + else: + if 'paused' in file_props and \ + file_props['paused'] == True: + status = _('?transfer status:Paused') + elif 'stalled' in file_props and \ + file_props['stalled'] == True: + #stalled is not paused. it is like 'frozen' it stopped alone + status = _('Stalled') + else: + status = _('Transferring') + else: + status = _('Not started') + properties.append((_('Status: '), status)) + if 'desc' in file_props: + file_desc = file_props['desc'] + properties.append((_('Description: '), gobject.markup_escape_text( + file_desc))) + while properties: + property_ = properties.pop(0) + current_row += 1 + label = gtk.Label() + label.set_alignment(0, 0) + label.set_markup(property_[0]) + ft_table.attach(label, 1, 2, current_row, current_row + 1, + gtk.FILL, gtk.FILL, 0, 0) + label = gtk.Label() + label.set_alignment(0, 0) + label.set_line_wrap(True) + label.set_markup(property_[1]) + ft_table.attach(label, 2, 3, current_row, current_row + 1, + gtk.EXPAND | gtk.FILL, gtk.FILL, 0, 0) - self.win.add(ft_table) + self.win.add(ft_table) class ServiceDiscoveryTooltip(BaseTooltip): - """ - Tooltip that is shown when hovering over a service discovery row - """ - def populate(self, status): - self.create_window() - label = gtk.Label() - label.set_line_wrap(True) - label.set_alignment(0, 0) - label.set_selectable(False) - if status == 1: - label.set_text( - _('This service has not yet responded with detailed information')) - elif status == 2: - label.set_text( - _('This service could not respond with detailed information.\n' - 'It is most likely legacy or broken')) - self.win.add(label) - -# vim: se ts=3: + """ + Tooltip that is shown when hovering over a service discovery row + """ + def populate(self, status): + self.create_window() + label = gtk.Label() + label.set_line_wrap(True) + label.set_alignment(0, 0) + label.set_selectable(False) + if status == 1: + label.set_text( + _('This service has not yet responded with detailed information')) + elif status == 2: + label.set_text( + _('This service could not respond with detailed information.\n' + 'It is most likely legacy or broken')) + self.win.add(label) diff --git a/src/vcard.py b/src/vcard.py index a1605b91c..2f30f52a6 100644 --- a/src/vcard.py +++ b/src/vcard.py @@ -45,520 +45,518 @@ from common import gajim from common.i18n import Q_ def get_avatar_pixbuf_encoded_mime(photo): - """ - Return the pixbuf of the image + """ + Return the pixbuf of the image - Photo is a dictionary containing PHOTO information. - """ - if not isinstance(photo, dict): - return None, None, None - img_decoded = None - avatar_encoded = None - avatar_mime_type = None - if 'BINVAL' in photo: - img_encoded = photo['BINVAL'] - avatar_encoded = img_encoded - try: - img_decoded = base64.decodestring(img_encoded) - except Exception: - pass - if img_decoded: - if 'TYPE' in photo: - avatar_mime_type = photo['TYPE'] - pixbuf = gtkgui_helpers.get_pixbuf_from_data(img_decoded) - else: - pixbuf, avatar_mime_type = gtkgui_helpers.get_pixbuf_from_data( - img_decoded, want_type=True) - else: - pixbuf = None - return pixbuf, avatar_encoded, avatar_mime_type + Photo is a dictionary containing PHOTO information. + """ + if not isinstance(photo, dict): + return None, None, None + img_decoded = None + avatar_encoded = None + avatar_mime_type = None + if 'BINVAL' in photo: + img_encoded = photo['BINVAL'] + avatar_encoded = img_encoded + try: + img_decoded = base64.decodestring(img_encoded) + except Exception: + pass + if img_decoded: + if 'TYPE' in photo: + avatar_mime_type = photo['TYPE'] + pixbuf = gtkgui_helpers.get_pixbuf_from_data(img_decoded) + else: + pixbuf, avatar_mime_type = gtkgui_helpers.get_pixbuf_from_data( + img_decoded, want_type=True) + else: + pixbuf = None + return pixbuf, avatar_encoded, avatar_mime_type class VcardWindow: - """ - Class for contact's information window - """ + """ + Class for contact's information window + """ - def __init__(self, contact, account, gc_contact = None): - # the contact variable is the jid if vcard is true - self.xml = gtkgui_helpers.get_gtk_builder('vcard_information_window.ui') - self.window = self.xml.get_object('vcard_information_window') - self.progressbar = self.xml.get_object('progressbar') + def __init__(self, contact, account, gc_contact = None): + # the contact variable is the jid if vcard is true + self.xml = gtkgui_helpers.get_gtk_builder('vcard_information_window.ui') + self.window = self.xml.get_object('vcard_information_window') + self.progressbar = self.xml.get_object('progressbar') - self.contact = contact - self.account = account - self.gc_contact = gc_contact + self.contact = contact + self.account = account + self.gc_contact = gc_contact - # Get real jid - if gc_contact: - # Don't use real jid if room is (semi-)anonymous - gc_control = gajim.interface.msg_win_mgr.get_gc_control( - gc_contact.room_jid, account) - if gc_contact.jid and not gc_control.is_anonymous: - self.real_jid = gc_contact.jid - self.real_jid_for_vcard = gc_contact.jid - if gc_contact.resource: - self.real_jid += '/' + gc_contact.resource - else: - self.real_jid = gc_contact.get_full_jid() - self.real_jid_for_vcard = self.real_jid - self.real_resource = gc_contact.name - else: - self.real_jid = contact.get_full_jid() - self.real_resource = contact.resource + # Get real jid + if gc_contact: + # Don't use real jid if room is (semi-)anonymous + gc_control = gajim.interface.msg_win_mgr.get_gc_control( + gc_contact.room_jid, account) + if gc_contact.jid and not gc_control.is_anonymous: + self.real_jid = gc_contact.jid + self.real_jid_for_vcard = gc_contact.jid + if gc_contact.resource: + self.real_jid += '/' + gc_contact.resource + else: + self.real_jid = gc_contact.get_full_jid() + self.real_jid_for_vcard = self.real_jid + self.real_resource = gc_contact.name + else: + self.real_jid = contact.get_full_jid() + self.real_resource = contact.resource - puny_jid = helpers.sanitize_filename(contact.jid) - local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid) + \ - '_local' - for extension in ('.png', '.jpeg'): - local_avatar_path = local_avatar_basepath + extension - if os.path.isfile(local_avatar_path): - image = self.xml.get_object('custom_avatar_image') - image.set_from_file(local_avatar_path) - image.show() - self.xml.get_object('custom_avatar_label').show() - break - self.avatar_mime_type = None - self.avatar_encoded = None - self.vcard_arrived = False - self.os_info_arrived = False - self.entity_time_arrived = False - self.update_progressbar_timeout_id = gobject.timeout_add(100, - self.update_progressbar) + puny_jid = helpers.sanitize_filename(contact.jid) + local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid) + \ + '_local' + for extension in ('.png', '.jpeg'): + local_avatar_path = local_avatar_basepath + extension + if os.path.isfile(local_avatar_path): + image = self.xml.get_object('custom_avatar_image') + image.set_from_file(local_avatar_path) + image.show() + self.xml.get_object('custom_avatar_label').show() + break + self.avatar_mime_type = None + self.avatar_encoded = None + self.vcard_arrived = False + self.os_info_arrived = False + self.entity_time_arrived = False + self.update_progressbar_timeout_id = gobject.timeout_add(100, + self.update_progressbar) - self.fill_jabber_page() - annotations = gajim.connections[self.account].annotations - if self.contact.jid in annotations: - buffer_ = self.xml.get_object('textview_annotation').get_buffer() - buffer_.set_text(annotations[self.contact.jid]) + self.fill_jabber_page() + annotations = gajim.connections[self.account].annotations + if self.contact.jid in annotations: + buffer_ = self.xml.get_object('textview_annotation').get_buffer() + buffer_.set_text(annotations[self.contact.jid]) - self.xml.connect_signals(self) - self.window.show_all() - self.xml.get_object('close_button').grab_focus() + self.xml.connect_signals(self) + self.window.show_all() + self.xml.get_object('close_button').grab_focus() - def update_progressbar(self): - self.progressbar.pulse() - return True # loop forever + def update_progressbar(self): + self.progressbar.pulse() + return True # loop forever - def on_vcard_information_window_destroy(self, widget): - if self.update_progressbar_timeout_id is not None: - gobject.source_remove(self.update_progressbar_timeout_id) - del gajim.interface.instances[self.account]['infos'][self.contact.jid] - buffer_ = self.xml.get_object('textview_annotation').get_buffer() - annotation = buffer_.get_text(buffer_.get_start_iter(), - buffer_.get_end_iter()) - connection = gajim.connections[self.account] - if annotation != connection.annotations.get(self.contact.jid, ''): - connection.annotations[self.contact.jid] = annotation - connection.store_annotations() + def on_vcard_information_window_destroy(self, widget): + if self.update_progressbar_timeout_id is not None: + gobject.source_remove(self.update_progressbar_timeout_id) + del gajim.interface.instances[self.account]['infos'][self.contact.jid] + buffer_ = self.xml.get_object('textview_annotation').get_buffer() + annotation = buffer_.get_text(buffer_.get_start_iter(), + buffer_.get_end_iter()) + connection = gajim.connections[self.account] + if annotation != connection.annotations.get(self.contact.jid, ''): + connection.annotations[self.contact.jid] = annotation + connection.store_annotations() - def on_vcard_information_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: - self.window.destroy() + def on_vcard_information_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: + self.window.destroy() - def on_PHOTO_eventbox_button_press_event(self, widget, event): - """ - If right-clicked, show popup - """ - if event.button == 3: # right click - menu = gtk.Menu() - menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS) - menuitem.connect('activate', - gtkgui_helpers.on_avatar_save_as_menuitem_activate, - self.contact.jid, self.account, self.contact.get_shown_name()) - menu.append(menuitem) - menu.connect('selection-done', lambda w:w.destroy()) - # show the menu - menu.show_all() - menu.popup(None, None, None, event.button, event.time) + def on_PHOTO_eventbox_button_press_event(self, widget, event): + """ + If right-clicked, show popup + """ + if event.button == 3: # right click + menu = gtk.Menu() + menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS) + menuitem.connect('activate', + gtkgui_helpers.on_avatar_save_as_menuitem_activate, + self.contact.jid, self.account, self.contact.get_shown_name()) + menu.append(menuitem) + menu.connect('selection-done', lambda w:w.destroy()) + # show the menu + menu.show_all() + menu.popup(None, None, None, event.button, event.time) - def set_value(self, entry_name, value): - try: - if value and entry_name == 'URL_label': - widget = gtk.LinkButton(value, value) - widget.set_alignment(0, 0) - widget.show() - table = self.xml.get_object('personal_info_table') - table.attach(widget, 1, 4, 3, 4, yoptions = 0) - else: - self.xml.get_object(entry_name).set_text(value) - except AttributeError: - pass + def set_value(self, entry_name, value): + try: + if value and entry_name == 'URL_label': + widget = gtk.LinkButton(value, value) + widget.set_alignment(0, 0) + widget.show() + table = self.xml.get_object('personal_info_table') + table.attach(widget, 1, 4, 3, 4, yoptions = 0) + else: + self.xml.get_object(entry_name).set_text(value) + except AttributeError: + pass - def set_values(self, vcard): - for i in vcard.keys(): - if i == 'PHOTO' and self.xml.get_object('information_notebook').\ - get_n_pages() > 4: - pixbuf, self.avatar_encoded, self.avatar_mime_type = \ - get_avatar_pixbuf_encoded_mime(vcard[i]) - image = self.xml.get_object('PHOTO_image') - image.show() - self.xml.get_object('user_avatar_label').show() - if not pixbuf: - image.set_from_icon_name('stock_person', - gtk.ICON_SIZE_DIALOG) - continue - pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard') - image.set_from_pixbuf(pixbuf) - continue - if i in ('ADR', 'TEL', 'EMAIL'): - for entry in vcard[i]: - add_on = '_HOME' - if 'WORK' in entry: - add_on = '_WORK' - for j in entry.keys(): - self.set_value(i + add_on + '_' + j + '_label', entry[j]) - if isinstance(vcard[i], dict): - for j in vcard[i].keys(): - self.set_value(i + '_' + j + '_label', vcard[i][j]) - else: - if i == 'DESC': - self.xml.get_object('DESC_textview').get_buffer().set_text( - vcard[i], 0) - elif i != 'jid': # Do not override jid_label - self.set_value(i + '_label', vcard[i]) - self.vcard_arrived = True - self.test_remove_progressbar() + def set_values(self, vcard): + for i in vcard.keys(): + if i == 'PHOTO' and self.xml.get_object('information_notebook').\ + get_n_pages() > 4: + pixbuf, self.avatar_encoded, self.avatar_mime_type = \ + get_avatar_pixbuf_encoded_mime(vcard[i]) + image = self.xml.get_object('PHOTO_image') + image.show() + self.xml.get_object('user_avatar_label').show() + if not pixbuf: + image.set_from_icon_name('stock_person', + gtk.ICON_SIZE_DIALOG) + continue + pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard') + image.set_from_pixbuf(pixbuf) + continue + if i in ('ADR', 'TEL', 'EMAIL'): + for entry in vcard[i]: + add_on = '_HOME' + if 'WORK' in entry: + add_on = '_WORK' + for j in entry.keys(): + self.set_value(i + add_on + '_' + j + '_label', entry[j]) + if isinstance(vcard[i], dict): + for j in vcard[i].keys(): + self.set_value(i + '_' + j + '_label', vcard[i][j]) + else: + if i == 'DESC': + self.xml.get_object('DESC_textview').get_buffer().set_text( + vcard[i], 0) + elif i != 'jid': # Do not override jid_label + self.set_value(i + '_label', vcard[i]) + self.vcard_arrived = True + self.test_remove_progressbar() - def test_remove_progressbar(self): - if self.update_progressbar_timeout_id is not None and \ - self.vcard_arrived and self.os_info_arrived and self.entity_time_arrived: - gobject.source_remove(self.update_progressbar_timeout_id) - self.progressbar.hide() - self.update_progressbar_timeout_id = None + def test_remove_progressbar(self): + if self.update_progressbar_timeout_id is not None and \ + self.vcard_arrived and self.os_info_arrived and self.entity_time_arrived: + gobject.source_remove(self.update_progressbar_timeout_id) + self.progressbar.hide() + self.update_progressbar_timeout_id = None - def set_last_status_time(self): - self.fill_status_label() + def set_last_status_time(self): + self.fill_status_label() - def set_os_info(self, resource, client_info, os_info): - if self.xml.get_object('information_notebook').get_n_pages() < 5: - return - i = 0 - client = '' - os = '' - while i in self.os_info: - if not self.os_info[i]['resource'] or \ - self.os_info[i]['resource'] == resource: - self.os_info[i]['client'] = client_info - self.os_info[i]['os'] = os_info - if i > 0: - client += '\n' - os += '\n' - client += self.os_info[i]['client'] - os += self.os_info[i]['os'] - i += 1 + def set_os_info(self, resource, client_info, os_info): + if self.xml.get_object('information_notebook').get_n_pages() < 5: + return + i = 0 + client = '' + os = '' + while i in self.os_info: + if not self.os_info[i]['resource'] or \ + self.os_info[i]['resource'] == resource: + self.os_info[i]['client'] = client_info + self.os_info[i]['os'] = os_info + if i > 0: + client += '\n' + os += '\n' + client += self.os_info[i]['client'] + os += self.os_info[i]['os'] + i += 1 - if client == '': - client = Q_('?Client:Unknown') - if os == '': - os = Q_('?OS:Unknown') - self.xml.get_object('client_name_version_label').set_text(client) - self.xml.get_object('os_label').set_text(os) - self.os_info_arrived = True - self.test_remove_progressbar() + if client == '': + client = Q_('?Client:Unknown') + if os == '': + os = Q_('?OS:Unknown') + self.xml.get_object('client_name_version_label').set_text(client) + self.xml.get_object('os_label').set_text(os) + self.os_info_arrived = True + self.test_remove_progressbar() - def set_entity_time(self, resource, time_info): - if self.xml.get_object('information_notebook').get_n_pages() < 5: - return - i = 0 - time_s = '' - while i in self.time_info: - if not self.time_info[i]['resource'] or \ - self.time_info[i]['resource'] == resource: - self.time_info[i]['time'] = time_info - if i > 0: - time_s += '\n' - time_s += self.time_info[i]['time'] - i += 1 + def set_entity_time(self, resource, time_info): + if self.xml.get_object('information_notebook').get_n_pages() < 5: + return + i = 0 + time_s = '' + while i in self.time_info: + if not self.time_info[i]['resource'] or \ + self.time_info[i]['resource'] == resource: + self.time_info[i]['time'] = time_info + if i > 0: + time_s += '\n' + time_s += self.time_info[i]['time'] + i += 1 - if time_s == '': - time_s = Q_('?Time:Unknown') - self.xml.get_object('time_label').set_text(time_s) - self.entity_time_arrived = True - self.test_remove_progressbar() + if time_s == '': + time_s = Q_('?Time:Unknown') + self.xml.get_object('time_label').set_text(time_s) + self.entity_time_arrived = True + self.test_remove_progressbar() - def fill_status_label(self): - if self.xml.get_object('information_notebook').get_n_pages() < 5: - return - contact_list = gajim.contacts.get_contacts(self.account, self.contact.jid) - connected_contact_list = [] - for c in contact_list: - if c.show not in ('offline', 'error'): - connected_contact_list.append(c) - if not connected_contact_list: - # no connected contact, get the offline one - connected_contact_list = contact_list - # stats holds show and status message - stats = '' - if connected_contact_list: - # Start with self.contact, as with resources - stats = helpers.get_uf_show(self.contact.show) - if self.contact.status: - stats += ': ' + self.contact.status - if self.contact.last_status_time: - stats += '\n' + _('since %s') % time.strftime('%c', - self.contact.last_status_time).decode( - locale.getpreferredencoding()) - for c in connected_contact_list: - if c.resource != self.contact.resource: - stats += '\n' - stats += helpers.get_uf_show(c.show) - if c.status: - stats += ': ' + c.status - if c.last_status_time: - stats += '\n' + _('since %s') % time.strftime('%c', - c.last_status_time).decode(locale.getpreferredencoding()) - else: # Maybe gc_vcard ? - stats = helpers.get_uf_show(self.contact.show) - if self.contact.status: - stats += ': ' + self.contact.status - status_label = self.xml.get_object('status_label') - status_label.set_max_width_chars(15) - status_label.set_text(stats) + def fill_status_label(self): + if self.xml.get_object('information_notebook').get_n_pages() < 5: + return + contact_list = gajim.contacts.get_contacts(self.account, self.contact.jid) + connected_contact_list = [] + for c in contact_list: + if c.show not in ('offline', 'error'): + connected_contact_list.append(c) + if not connected_contact_list: + # no connected contact, get the offline one + connected_contact_list = contact_list + # stats holds show and status message + stats = '' + if connected_contact_list: + # Start with self.contact, as with resources + stats = helpers.get_uf_show(self.contact.show) + if self.contact.status: + stats += ': ' + self.contact.status + if self.contact.last_status_time: + stats += '\n' + _('since %s') % time.strftime('%c', + self.contact.last_status_time).decode( + locale.getpreferredencoding()) + for c in connected_contact_list: + if c.resource != self.contact.resource: + stats += '\n' + stats += helpers.get_uf_show(c.show) + if c.status: + stats += ': ' + c.status + if c.last_status_time: + stats += '\n' + _('since %s') % time.strftime('%c', + c.last_status_time).decode(locale.getpreferredencoding()) + else: # Maybe gc_vcard ? + stats = helpers.get_uf_show(self.contact.show) + if self.contact.status: + stats += ': ' + self.contact.status + status_label = self.xml.get_object('status_label') + status_label.set_max_width_chars(15) + status_label.set_text(stats) - status_label_eventbox = self.xml.get_object('status_label_eventbox') - status_label_eventbox.set_tooltip_text(stats) + status_label_eventbox = self.xml.get_object('status_label_eventbox') + status_label_eventbox.set_tooltip_text(stats) - def fill_jabber_page(self): - self.xml.get_object('nickname_label').set_markup( - '' + - self.contact.get_shown_name() + - '') - self.xml.get_object('jid_label').set_text(self.contact.jid) + def fill_jabber_page(self): + self.xml.get_object('nickname_label').set_markup( + '' + + self.contact.get_shown_name() + + '') + self.xml.get_object('jid_label').set_text(self.contact.jid) - subscription_label = self.xml.get_object('subscription_label') - ask_label = self.xml.get_object('ask_label') - if self.gc_contact: - self.xml.get_object('subscription_title_label').set_markup(_("Role:")) - uf_role = helpers.get_uf_role(self.gc_contact.role) - subscription_label.set_text(uf_role) + subscription_label = self.xml.get_object('subscription_label') + ask_label = self.xml.get_object('ask_label') + if self.gc_contact: + self.xml.get_object('subscription_title_label').set_markup(_("Role:")) + uf_role = helpers.get_uf_role(self.gc_contact.role) + subscription_label.set_text(uf_role) - self.xml.get_object('ask_title_label').set_markup(_("Affiliation:")) - uf_affiliation = helpers.get_uf_affiliation(self.gc_contact.affiliation) - ask_label.set_text(uf_affiliation) - else: - uf_sub = helpers.get_uf_sub(self.contact.sub) - subscription_label.set_text(uf_sub) - eb = self.xml.get_object('subscription_label_eventbox') - if self.contact.sub == 'from': - tt_text = _("This contact is interested in your presence information, but you are not interested in his/her presence") - elif self.contact.sub == 'to': - tt_text = _("You are interested in the contact's presence information, but he/she is not interested in yours") - elif self.contact.sub == 'both': - tt_text = _("You and the contact are interested in each other's presence information") - else: # None - tt_text = _("You are not interested in the contact's presence, and neither he/she is interested in yours") - eb.set_tooltip_text(tt_text) + self.xml.get_object('ask_title_label').set_markup(_("Affiliation:")) + uf_affiliation = helpers.get_uf_affiliation(self.gc_contact.affiliation) + ask_label.set_text(uf_affiliation) + else: + uf_sub = helpers.get_uf_sub(self.contact.sub) + subscription_label.set_text(uf_sub) + eb = self.xml.get_object('subscription_label_eventbox') + if self.contact.sub == 'from': + tt_text = _("This contact is interested in your presence information, but you are not interested in his/her presence") + elif self.contact.sub == 'to': + tt_text = _("You are interested in the contact's presence information, but he/she is not interested in yours") + elif self.contact.sub == 'both': + tt_text = _("You and the contact are interested in each other's presence information") + else: # None + tt_text = _("You are not interested in the contact's presence, and neither he/she is interested in yours") + eb.set_tooltip_text(tt_text) - uf_ask = helpers.get_uf_ask(self.contact.ask) - ask_label.set_text(uf_ask) - eb = self.xml.get_object('ask_label_eventbox') - if self.contact.ask == 'subscribe': - tt_text = _("You are waiting contact's answer about your subscription request") - else: - tt_text = _("There is no pending subscription request.") - eb.set_tooltip_text(tt_text) + uf_ask = helpers.get_uf_ask(self.contact.ask) + ask_label.set_text(uf_ask) + eb = self.xml.get_object('ask_label_eventbox') + if self.contact.ask == 'subscribe': + tt_text = _("You are waiting contact's answer about your subscription request") + else: + tt_text = _("There is no pending subscription request.") + eb.set_tooltip_text(tt_text) - resources = '%s (%s)' % (self.contact.resource, unicode( - self.contact.priority)) - uf_resources = self.contact.resource + _(' resource with priority ')\ - + unicode(self.contact.priority) - if not self.contact.status: - self.contact.status = '' + resources = '%s (%s)' % (self.contact.resource, unicode( + self.contact.priority)) + uf_resources = self.contact.resource + _(' resource with priority ')\ + + unicode(self.contact.priority) + if not self.contact.status: + self.contact.status = '' - # Request list time status only if contact is offline - if self.contact.show == 'offline': - if self.gc_contact: - j, r = gajim.get_room_and_nick_from_fjid(self.real_jid) - gajim.connections[self.account].request_last_status_time(j, r, - self.contact.jid) - else: - gajim.connections[self.account].request_last_status_time( - self.contact.jid, self.contact.resource) + # Request list time status only if contact is offline + if self.contact.show == 'offline': + if self.gc_contact: + j, r = gajim.get_room_and_nick_from_fjid(self.real_jid) + gajim.connections[self.account].request_last_status_time(j, r, + self.contact.jid) + else: + gajim.connections[self.account].request_last_status_time( + self.contact.jid, self.contact.resource) - # do not wait for os_info if contact is not connected or has error - # additional check for observer is needed, as show is offline for him - if self.contact.show in ('offline', 'error')\ - and not self.contact.is_observer(): - self.os_info_arrived = True - else: # Request os info if contact is connected - if self.gc_contact: - j, r = gajim.get_room_and_nick_from_fjid(self.real_jid) - gobject.idle_add(gajim.connections[self.account].request_os_info, - j, r, self.contact.jid) - else: - gobject.idle_add(gajim.connections[self.account].request_os_info, - self.contact.jid, self.contact.resource) + # do not wait for os_info if contact is not connected or has error + # additional check for observer is needed, as show is offline for him + if self.contact.show in ('offline', 'error')\ + and not self.contact.is_observer(): + self.os_info_arrived = True + else: # Request os info if contact is connected + if self.gc_contact: + j, r = gajim.get_room_and_nick_from_fjid(self.real_jid) + gobject.idle_add(gajim.connections[self.account].request_os_info, + j, r, self.contact.jid) + else: + gobject.idle_add(gajim.connections[self.account].request_os_info, + self.contact.jid, self.contact.resource) - # do not wait for entity_time if contact is not connected or has error - # additional check for observer is needed, as show is offline for him - if self.contact.show in ('offline', 'error')\ - and not self.contact.is_observer(): - self.entity_time_arrived = True - else: # Request entity time if contact is connected - if self.gc_contact: - j, r = gajim.get_room_and_nick_from_fjid(self.real_jid) - gobject.idle_add(gajim.connections[self.account].\ - request_entity_time, j, r, self.contact.jid) - else: - gobject.idle_add(gajim.connections[self.account].\ - request_entity_time, self.contact.jid, self.contact.resource) + # do not wait for entity_time if contact is not connected or has error + # additional check for observer is needed, as show is offline for him + if self.contact.show in ('offline', 'error')\ + and not self.contact.is_observer(): + self.entity_time_arrived = True + else: # Request entity time if contact is connected + if self.gc_contact: + j, r = gajim.get_room_and_nick_from_fjid(self.real_jid) + gobject.idle_add(gajim.connections[self.account].\ + request_entity_time, j, r, self.contact.jid) + else: + gobject.idle_add(gajim.connections[self.account].\ + request_entity_time, self.contact.jid, self.contact.resource) - self.os_info = {0: {'resource': self.real_resource, 'client': '', - 'os': ''}} - self.time_info = {0: {'resource': self.real_resource, 'time': ''}} - i = 1 - contact_list = gajim.contacts.get_contacts(self.account, self.contact.jid) - if contact_list: - for c in contact_list: - if c.resource != self.contact.resource: - resources += '\n%s (%s)' % (c.resource, - unicode(c.priority)) - uf_resources += '\n' + c.resource + \ - _(' resource with priority ') + unicode(c.priority) - if c.show not in ('offline', 'error'): - gobject.idle_add( - gajim.connections[self.account].request_os_info, c.jid, - c.resource) - gobject.idle_add(gajim.connections[self.account].\ - request_entity_time, c.jid, c.resource) - self.os_info[i] = {'resource': c.resource, 'client': '', - 'os': ''} - self.time_info[i] = {'resource': c.resource, 'time': ''} - i += 1 + self.os_info = {0: {'resource': self.real_resource, 'client': '', + 'os': ''}} + self.time_info = {0: {'resource': self.real_resource, 'time': ''}} + i = 1 + contact_list = gajim.contacts.get_contacts(self.account, self.contact.jid) + if contact_list: + for c in contact_list: + if c.resource != self.contact.resource: + resources += '\n%s (%s)' % (c.resource, + unicode(c.priority)) + uf_resources += '\n' + c.resource + \ + _(' resource with priority ') + unicode(c.priority) + if c.show not in ('offline', 'error'): + gobject.idle_add( + gajim.connections[self.account].request_os_info, c.jid, + c.resource) + gobject.idle_add(gajim.connections[self.account].\ + request_entity_time, c.jid, c.resource) + self.os_info[i] = {'resource': c.resource, 'client': '', + 'os': ''} + self.time_info[i] = {'resource': c.resource, 'time': ''} + i += 1 - self.xml.get_object('resource_prio_label').set_text(resources) - resource_prio_label_eventbox = self.xml.get_object( - 'resource_prio_label_eventbox') - resource_prio_label_eventbox.set_tooltip_text(uf_resources) + self.xml.get_object('resource_prio_label').set_text(resources) + resource_prio_label_eventbox = self.xml.get_object( + 'resource_prio_label_eventbox') + resource_prio_label_eventbox.set_tooltip_text(uf_resources) - self.fill_status_label() + self.fill_status_label() - if self.gc_contact: - # If we know the real jid, remove the resource from vcard request - gajim.connections[self.account].request_vcard(self.real_jid_for_vcard, - self.gc_contact.get_full_jid()) - else: - gajim.connections[self.account].request_vcard(self.contact.jid) + if self.gc_contact: + # If we know the real jid, remove the resource from vcard request + gajim.connections[self.account].request_vcard(self.real_jid_for_vcard, + self.gc_contact.get_full_jid()) + else: + gajim.connections[self.account].request_vcard(self.contact.jid) - def on_close_button_clicked(self, widget): - self.window.destroy() + def on_close_button_clicked(self, widget): + self.window.destroy() class ZeroconfVcardWindow: - def __init__(self, contact, account, is_fake = False): - # the contact variable is the jid if vcard is true - self.xml = gtkgui_helpers.get_gtk_builder('zeroconf_information_window.ui') - self.window = self.xml.get_object('zeroconf_information_window') + def __init__(self, contact, account, is_fake = False): + # the contact variable is the jid if vcard is true + self.xml = gtkgui_helpers.get_gtk_builder('zeroconf_information_window.ui') + self.window = self.xml.get_object('zeroconf_information_window') - self.contact = contact - self.account = account - self.is_fake = is_fake + self.contact = contact + self.account = account + self.is_fake = is_fake - # self.avatar_mime_type = None - # self.avatar_encoded = None + # self.avatar_mime_type = None + # self.avatar_encoded = None - self.fill_contact_page() - self.fill_personal_page() + self.fill_contact_page() + self.fill_personal_page() - self.xml.connect_signals(self) - self.window.show_all() + self.xml.connect_signals(self) + self.window.show_all() - def on_zeroconf_information_window_destroy(self, widget): - del gajim.interface.instances[self.account]['infos'][self.contact.jid] + def on_zeroconf_information_window_destroy(self, widget): + del gajim.interface.instances[self.account]['infos'][self.contact.jid] - def on_zeroconf_information_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: - self.window.destroy() + def on_zeroconf_information_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: + self.window.destroy() - def on_PHOTO_eventbox_button_press_event(self, widget, event): - """ - If right-clicked, show popup - """ - if event.button == 3: # right click - menu = gtk.Menu() - menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS) - menuitem.connect('activate', - gtkgui_helpers.on_avatar_save_as_menuitem_activate, - self.contact.jid, self.account, self.contact.get_shown_name()) - menu.append(menuitem) - menu.connect('selection-done', lambda w:w.destroy()) - # show the menu - menu.show_all() - menu.popup(None, None, None, event.button, event.time) + def on_PHOTO_eventbox_button_press_event(self, widget, event): + """ + If right-clicked, show popup + """ + if event.button == 3: # right click + menu = gtk.Menu() + menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS) + menuitem.connect('activate', + gtkgui_helpers.on_avatar_save_as_menuitem_activate, + self.contact.jid, self.account, self.contact.get_shown_name()) + menu.append(menuitem) + menu.connect('selection-done', lambda w:w.destroy()) + # show the menu + menu.show_all() + menu.popup(None, None, None, event.button, event.time) - def set_value(self, entry_name, value): - try: - if value and entry_name == 'URL_label': - widget = gtk.LinkButton(value, value) - widget.set_alignment(0, 0) - table = self.xml.get_object('personal_info_table') - table.attach(widget, 1, 4, 3, 4, yoptions = 0) - else: - self.xml.get_object(entry_name).set_text(value) - except AttributeError: - pass + def set_value(self, entry_name, value): + try: + if value and entry_name == 'URL_label': + widget = gtk.LinkButton(value, value) + widget.set_alignment(0, 0) + table = self.xml.get_object('personal_info_table') + table.attach(widget, 1, 4, 3, 4, yoptions = 0) + else: + self.xml.get_object(entry_name).set_text(value) + except AttributeError: + pass - def fill_status_label(self): - if self.xml.get_object('information_notebook').get_n_pages() < 2: - return - contact_list = gajim.contacts.get_contacts(self.account, self.contact.jid) - # stats holds show and status message - stats = '' - one = True # Are we adding the first line ? - if contact_list: - for c in contact_list: - if not one: - stats += '\n' - stats += helpers.get_uf_show(c.show) - if c.status: - stats += ': ' + c.status - if c.last_status_time: - stats += '\n' + _('since %s') % time.strftime('%c', - c.last_status_time).decode(locale.getpreferredencoding()) - one = False - else: # Maybe gc_vcard ? - stats = helpers.get_uf_show(self.contact.show) - if self.contact.status: - stats += ': ' + self.contact.status - status_label = self.xml.get_object('status_label') - status_label.set_max_width_chars(15) - status_label.set_text(stats) + def fill_status_label(self): + if self.xml.get_object('information_notebook').get_n_pages() < 2: + return + contact_list = gajim.contacts.get_contacts(self.account, self.contact.jid) + # stats holds show and status message + stats = '' + one = True # Are we adding the first line ? + if contact_list: + for c in contact_list: + if not one: + stats += '\n' + stats += helpers.get_uf_show(c.show) + if c.status: + stats += ': ' + c.status + if c.last_status_time: + stats += '\n' + _('since %s') % time.strftime('%c', + c.last_status_time).decode(locale.getpreferredencoding()) + one = False + else: # Maybe gc_vcard ? + stats = helpers.get_uf_show(self.contact.show) + if self.contact.status: + stats += ': ' + self.contact.status + status_label = self.xml.get_object('status_label') + status_label.set_max_width_chars(15) + status_label.set_text(stats) - status_label_eventbox = self.xml.get_object('status_label_eventbox') - status_label_eventbox.set_tooltip_text(stats) + status_label_eventbox = self.xml.get_object('status_label_eventbox') + status_label_eventbox.set_tooltip_text(stats) - def fill_contact_page(self): - self.xml.get_object('nickname_label').set_markup( - '' + - self.contact.get_shown_name() + - '') - self.xml.get_object('local_jid_label').set_text(self.contact.jid) + def fill_contact_page(self): + self.xml.get_object('nickname_label').set_markup( + '' + + self.contact.get_shown_name() + + '') + self.xml.get_object('local_jid_label').set_text(self.contact.jid) - resources = '%s (%s)' % (self.contact.resource, unicode( - self.contact.priority)) - uf_resources = self.contact.resource + _(' resource with priority ')\ - + unicode(self.contact.priority) - if not self.contact.status: - self.contact.status = '' + resources = '%s (%s)' % (self.contact.resource, unicode( + self.contact.priority)) + uf_resources = self.contact.resource + _(' resource with priority ')\ + + unicode(self.contact.priority) + if not self.contact.status: + self.contact.status = '' - self.xml.get_object('resource_prio_label').set_text(resources) - resource_prio_label_eventbox = self.xml.get_object( - 'resource_prio_label_eventbox') - resource_prio_label_eventbox.set_tooltip_text(uf_resources) + self.xml.get_object('resource_prio_label').set_text(resources) + resource_prio_label_eventbox = self.xml.get_object( + 'resource_prio_label_eventbox') + resource_prio_label_eventbox.set_tooltip_text(uf_resources) - self.fill_status_label() + self.fill_status_label() - def fill_personal_page(self): - contact = gajim.connections[gajim.ZEROCONF_ACC_NAME].roster.getItem(self.contact.jid) - for key in ('1st', 'last', 'jid', 'email'): - if key not in contact['txt_dict']: - contact['txt_dict'][key] = '' - self.xml.get_object('first_name_label').set_text(contact['txt_dict']['1st']) - self.xml.get_object('last_name_label').set_text(contact['txt_dict']['last']) - self.xml.get_object('jabber_id_label').set_text(contact['txt_dict']['jid']) - self.xml.get_object('email_label').set_text(contact['txt_dict']['email']) + def fill_personal_page(self): + contact = gajim.connections[gajim.ZEROCONF_ACC_NAME].roster.getItem(self.contact.jid) + for key in ('1st', 'last', 'jid', 'email'): + if key not in contact['txt_dict']: + contact['txt_dict'][key] = '' + self.xml.get_object('first_name_label').set_text(contact['txt_dict']['1st']) + self.xml.get_object('last_name_label').set_text(contact['txt_dict']['last']) + self.xml.get_object('jabber_id_label').set_text(contact['txt_dict']['jid']) + self.xml.get_object('email_label').set_text(contact['txt_dict']['email']) - def on_close_button_clicked(self, widget): - self.window.destroy() - -# vim: se ts=3: + def on_close_button_clicked(self, widget): + self.window.destroy() diff --git a/test/integration/__init__.py b/test/integration/__init__.py index 0adf844f1..aaeacfbef 100644 --- a/test/integration/__init__.py +++ b/test/integration/__init__.py @@ -3,4 +3,4 @@ This package contains integration tests. Integration tests are tests which require or include UI, network or both. -''' \ No newline at end of file +''' diff --git a/test/integration/test_gui_event_integration.py b/test/integration/test_gui_event_integration.py index a1eadea60..c5999389f 100644 --- a/test/integration/test_gui_event_integration.py +++ b/test/integration/test_gui_event_integration.py @@ -23,171 +23,169 @@ import roster_window import notify class TestStatusChange(unittest.TestCase): - '''tests gajim.py's incredibly complex handle_event_notify''' + '''tests gajim.py's incredibly complex handle_event_notify''' - def setUp(self): - - gajim.connections = {} - gajim.contacts = contacts_module.LegacyContactsAPI() - gajim.interface.roster = roster_window.RosterWindow() + def setUp(self): - for acc in contacts: - gajim.connections[acc] = MockConnection(acc) + gajim.connections = {} + gajim.contacts = contacts_module.LegacyContactsAPI() + gajim.interface.roster = roster_window.RosterWindow() - gajim.interface.roster.fill_contacts_and_groups_dicts(contacts[acc], - acc) - gajim.interface.roster.add_account(acc) - gajim.interface.roster.add_account_contacts(acc) + for acc in contacts: + gajim.connections[acc] = MockConnection(acc) - self.assertEqual(0, len(notify.notifications)) + gajim.interface.roster.fill_contacts_and_groups_dicts(contacts[acc], + acc) + gajim.interface.roster.add_account(acc) + gajim.interface.roster.add_account_contacts(acc) - def tearDown(self): - notify.notifications = [] + self.assertEqual(0, len(notify.notifications)) - def contact_comes_online(self, account, jid, resource, prio): - '''a remote contact comes online''' - gajim.interface.handle_event_notify(account, (jid, 'online', "I'm back!", - resource, prio, None, time.time(), None)) + def tearDown(self): + notify.notifications = [] - contact = None - for c in gajim.contacts.get_contacts(account, jid): - if c.resource == resource: - contact = c - break + def contact_comes_online(self, account, jid, resource, prio): + '''a remote contact comes online''' + gajim.interface.handle_event_notify(account, (jid, 'online', "I'm back!", + resource, prio, None, time.time(), None)) - self.assertEqual('online', contact.show) - self.assertEqual("I'm back!", contact.status) - self.assertEqual(prio, contact.priority) + contact = None + for c in gajim.contacts.get_contacts(account, jid): + if c.resource == resource: + contact = c + break - # the most recent notification is that the contact connected - self.assertEqual('contact_connected', notify.notifications[-1][0]) + self.assertEqual('online', contact.show) + self.assertEqual("I'm back!", contact.status) + self.assertEqual(prio, contact.priority) - def contact_goes_offline(self, account, jid, resource, prio, - still_exists = True): - '''a remote contact goes offline.''' - gajim.interface.handle_event_notify(account, (jid, 'offline', 'Goodbye!', - resource, prio, None, time.time(), None)) + # the most recent notification is that the contact connected + self.assertEqual('contact_connected', notify.notifications[-1][0]) - contact = None - for c in gajim.contacts.get_contacts(account, jid): - if c.resource == resource: - contact = c - break + def contact_goes_offline(self, account, jid, resource, prio, + still_exists = True): + '''a remote contact goes offline.''' + gajim.interface.handle_event_notify(account, (jid, 'offline', 'Goodbye!', + resource, prio, None, time.time(), None)) - if not still_exists: - self.assert_(contact is None) - return + contact = None + for c in gajim.contacts.get_contacts(account, jid): + if c.resource == resource: + contact = c + break - self.assertEqual('offline', contact.show) - self.assertEqual('Goodbye!', contact.status) - self.assertEqual(prio, contact.priority) + if not still_exists: + self.assert_(contact is None) + return - self.assertEqual('contact_disconnected', notify.notifications[-1][0]) + self.assertEqual('offline', contact.show) + self.assertEqual('Goodbye!', contact.status) + self.assertEqual(prio, contact.priority) - def user_starts_chatting(self, jid, account, resource=None): - '''the user opens a chat window and starts talking''' - ctrl = MockChatControl(jid, account) - win = MockWindow() - win.new_tab(ctrl) - gajim.interface.msg_win_mgr._windows['test'] = win + self.assertEqual('contact_disconnected', notify.notifications[-1][0]) - if resource: - jid = jid + '/' + resource + def user_starts_chatting(self, jid, account, resource=None): + '''the user opens a chat window and starts talking''' + ctrl = MockChatControl(jid, account) + win = MockWindow() + win.new_tab(ctrl) + gajim.interface.msg_win_mgr._windows['test'] = win - # a basic session is started - session = gajim.connections[account1].make_new_session(jid, - '01234567890abcdef', cls=MockSession) - ctrl.set_session(session) + if resource: + jid = jid + '/' + resource - return ctrl + # a basic session is started + session = gajim.connections[account1].make_new_session(jid, + '01234567890abcdef', cls=MockSession) + ctrl.set_session(session) - def user_starts_esession(self, jid, resource, account): - '''the user opens a chat window and starts an encrypted session''' - ctrl = self.user_starts_chatting(jid, account, resource) - ctrl.session.status = 'active' - ctrl.session.enable_encryption = True + return ctrl - return ctrl + def user_starts_esession(self, jid, resource, account): + '''the user opens a chat window and starts an encrypted session''' + ctrl = self.user_starts_chatting(jid, account, resource) + ctrl.session.status = 'active' + ctrl.session.enable_encryption = True - def test_contact_comes_online(self): - jid = 'default1@gajim.org' + return ctrl - # contact is offline initially - contacts = gajim.contacts.get_contacts(account1, jid) - self.assertEqual(1, len(contacts)) - self.assertEqual('offline', contacts[0].show) - self.assertEqual('', contacts[0].status) + def test_contact_comes_online(self): + jid = 'default1@gajim.org' - self.contact_comes_online(account1, jid, 'lowprio', 1) + # contact is offline initially + contacts = gajim.contacts.get_contacts(account1, jid) + self.assertEqual(1, len(contacts)) + self.assertEqual('offline', contacts[0].show) + self.assertEqual('', contacts[0].status) - def test_contact_goes_offline(self): - jid = 'default1@gajim.org' + self.contact_comes_online(account1, jid, 'lowprio', 1) - self.contact_comes_online(account1, jid, 'lowprio', 1) + def test_contact_goes_offline(self): + jid = 'default1@gajim.org' - ctrl = self.user_starts_chatting(jid, account1) - orig_sess = ctrl.session + self.contact_comes_online(account1, jid, 'lowprio', 1) - self.contact_goes_offline(account1, jid, 'lowprio', 1) + ctrl = self.user_starts_chatting(jid, account1) + orig_sess = ctrl.session - # session hasn't changed since we were talking to the bare jid - self.assertEqual(orig_sess, ctrl.session) + self.contact_goes_offline(account1, jid, 'lowprio', 1) - def test_two_resources_higher_comes_online(self): - jid = 'default1@gajim.org' + # session hasn't changed since we were talking to the bare jid + self.assertEqual(orig_sess, ctrl.session) - self.contact_comes_online(account1, jid, 'lowprio', 1) + def test_two_resources_higher_comes_online(self): + jid = 'default1@gajim.org' - ctrl = self.user_starts_chatting(jid, account1) + self.contact_comes_online(account1, jid, 'lowprio', 1) - self.contact_comes_online(account1, jid, 'highprio', 50) + ctrl = self.user_starts_chatting(jid, account1) - # old session was dropped - self.assertEqual(None, ctrl.session) + self.contact_comes_online(account1, jid, 'highprio', 50) - def test_two_resources_higher_goes_offline(self): - jid = 'default1@gajim.org' + # old session was dropped + self.assertEqual(None, ctrl.session) - self.contact_comes_online(account1, jid, 'lowprio', 1) - self.contact_comes_online(account1, jid, 'highprio', 50) + def test_two_resources_higher_goes_offline(self): + jid = 'default1@gajim.org' - ctrl = self.user_starts_chatting(jid, account1) + self.contact_comes_online(account1, jid, 'lowprio', 1) + self.contact_comes_online(account1, jid, 'highprio', 50) - self.contact_goes_offline(account1, jid, 'highprio', 50, - still_exists=False) + ctrl = self.user_starts_chatting(jid, account1) - # old session was dropped - self.assertEqual(None, ctrl.session) + self.contact_goes_offline(account1, jid, 'highprio', 50, + still_exists=False) - def test_two_resources_higher_comes_online_with_esession(self): - jid = 'default1@gajim.org' + # old session was dropped + self.assertEqual(None, ctrl.session) - self.contact_comes_online(account1, jid, 'lowprio', 1) + def test_two_resources_higher_comes_online_with_esession(self): + jid = 'default1@gajim.org' - ctrl = self.user_starts_esession(jid, 'lowprio', account1) + self.contact_comes_online(account1, jid, 'lowprio', 1) - self.contact_comes_online(account1, jid, 'highprio', 50) + ctrl = self.user_starts_esession(jid, 'lowprio', account1) - # session was associated with the low priority full jid, so it should - # have been removed from the control - self.assertEqual(None, ctrl.session) + self.contact_comes_online(account1, jid, 'highprio', 50) - def test_two_resources_higher_goes_offline_with_esession(self): - jid = 'default1@gajim.org' + # session was associated with the low priority full jid, so it should + # have been removed from the control + self.assertEqual(None, ctrl.session) - self.contact_comes_online(account1, jid, 'lowprio', 1) - self.contact_comes_online(account1, jid, 'highprio', 50) + def test_two_resources_higher_goes_offline_with_esession(self): + jid = 'default1@gajim.org' - ctrl = self.user_starts_esession(jid, 'highprio', account1) + self.contact_comes_online(account1, jid, 'lowprio', 1) + self.contact_comes_online(account1, jid, 'highprio', 50) - self.contact_goes_offline(account1, jid, 'highprio', 50, - still_exists=False) + ctrl = self.user_starts_esession(jid, 'highprio', account1) - # session was associated with the high priority full jid, so it should - # have been removed from the control - self.assertEqual(None, ctrl.session) + self.contact_goes_offline(account1, jid, 'highprio', 50, + still_exists=False) + + # session was associated with the high priority full jid, so it should + # have been removed from the control + self.assertEqual(None, ctrl.session) if __name__ == '__main__': - unittest.main() - -# vim: se ts=3: + unittest.main() diff --git a/test/integration/test_resolver.py b/test/integration/test_resolver.py index 1836d3c7b..d80ffee87 100644 --- a/test/integration/test_resolver.py +++ b/test/integration/test_resolver.py @@ -16,87 +16,85 @@ NONSENSE_NAME = 'sfsdfsdfsdf.sdfs.fsd' JABBERCZ_TXT_NAME = '_xmppconnect.jabber.cz' JABBERCZ_SRV_NAME = '_xmpp-client._tcp.jabber.cz' -TEST_LIST = [(GMAIL_SRV_NAME, 'srv', True), - (NONSENSE_NAME, 'srv', False), - (JABBERCZ_SRV_NAME, 'srv', True)] +TEST_LIST = [(GMAIL_SRV_NAME, 'srv', True), + (NONSENSE_NAME, 'srv', False), + (JABBERCZ_SRV_NAME, 'srv', True)] class TestResolver(unittest.TestCase): - ''' - Test for LibAsyncNSResolver and NSLookupResolver. Requires working - network connection. - ''' - def setUp(self): - self.idlequeue_thread = IdleQueueThread() - self.idlequeue_thread.start() + ''' + Test for LibAsyncNSResolver and NSLookupResolver. Requires working + network connection. + ''' + def setUp(self): + self.idlequeue_thread = IdleQueueThread() + self.idlequeue_thread.start() - self.iq = self.idlequeue_thread.iq - self._reset() - self.resolver = None + self.iq = self.idlequeue_thread.iq + self._reset() + self.resolver = None - def tearDown(self): - self.idlequeue_thread.stop_thread() - self.idlequeue_thread.join() + def tearDown(self): + self.idlequeue_thread.stop_thread() + self.idlequeue_thread.join() - def _reset(self): - self.flag = False - self.expect_results = False - self.nslookup = False - self.resolver = None + def _reset(self): + self.flag = False + self.expect_results = False + self.nslookup = False + self.resolver = None - def testLibAsyncNSResolver(self): - self._reset() - if not resolver.USE_LIBASYNCNS: - print 'testLibAsyncResolver: libasyncns-python not installed' - return - self.resolver = resolver.LibAsyncNSResolver() + def testLibAsyncNSResolver(self): + self._reset() + if not resolver.USE_LIBASYNCNS: + print 'testLibAsyncResolver: libasyncns-python not installed' + return + self.resolver = resolver.LibAsyncNSResolver() - for name, type, expect_results in TEST_LIST: - self.expect_results = expect_results - self._runLANSR(name, type) - self.flag = False + for name, type, expect_results in TEST_LIST: + self.expect_results = expect_results + self._runLANSR(name, type) + self.flag = False - def _runLANSR(self, name, type): - self.resolver.resolve( - host = name, - type = type, - on_ready = self._myonready) - while not self.flag: - time.sleep(1) - self.resolver.process() + def _runLANSR(self, name, type): + self.resolver.resolve( + host = name, + type = type, + on_ready = self._myonready) + while not self.flag: + time.sleep(1) + self.resolver.process() - def _myonready(self, name, result_set): - if __name__ == '__main__': - from pprint import pprint - pprint('on_ready called ...') - pprint('hostname: %s' % name) - pprint('result set: %s' % result_set) - pprint('res.resolved_hosts: %s' % self.resolver.resolved_hosts) - pprint('') - if self.expect_results: - self.assert_(len(result_set) > 0) - else: - self.assert_(result_set == []) - self.flag = True - if self.nslookup: - self._testNSLR() - - def testNSLookupResolver(self): - self._reset() - self.nslookup = True - self.resolver = resolver.NSLookupResolver(self.iq) - self.test_list = TEST_LIST - self._testNSLR() + def _myonready(self, name, result_set): + if __name__ == '__main__': + from pprint import pprint + pprint('on_ready called ...') + pprint('hostname: %s' % name) + pprint('result set: %s' % result_set) + pprint('res.resolved_hosts: %s' % self.resolver.resolved_hosts) + pprint('') + if self.expect_results: + self.assert_(len(result_set) > 0) + else: + self.assert_(result_set == []) + self.flag = True + if self.nslookup: + self._testNSLR() - def _testNSLR(self): - if self.test_list == []: - return - name, type, self.expect_results = self.test_list.pop() - self.resolver.resolve( - host = name, - type = type, - on_ready = self._myonready) + def testNSLookupResolver(self): + self._reset() + self.nslookup = True + self.resolver = resolver.NSLookupResolver(self.iq) + self.test_list = TEST_LIST + self._testNSLR() + + def _testNSLR(self): + if self.test_list == []: + return + name, type, self.expect_results = self.test_list.pop() + self.resolver.resolve( + host = name, + type = type, + on_ready = self._myonready) if __name__ == '__main__': - unittest.main() - -# vim: se ts=3: + unittest.main() diff --git a/test/integration/test_roster.py b/test/integration/test_roster.py index 0be85aa11..8b71df122 100644 --- a/test/integration/test_roster.py +++ b/test/integration/test_roster.py @@ -17,189 +17,187 @@ gajim.get_jid_from_account = lambda acc: 'myjid@' + acc class TestRosterWindow(unittest.TestCase): - def setUp(self): - gajim.interface = MockInterface() + def setUp(self): + gajim.interface = MockInterface() - self.C_NAME = roster_window.C_NAME - self.C_TYPE = roster_window.C_TYPE - self.C_JID = roster_window.C_JID - self.C_ACCOUNT = roster_window.C_ACCOUNT + self.C_NAME = roster_window.C_NAME + self.C_TYPE = roster_window.C_TYPE + self.C_JID = roster_window.C_JID + self.C_ACCOUNT = roster_window.C_ACCOUNT - # Add after creating RosterWindow - # We want to test the filling explicitly - gajim.contacts = contacts_module.LegacyContactsAPI() - gajim.connections = {} - self.roster = roster_window.RosterWindow() - - for acc in contacts: - gajim.connections[acc] = MockConnection(acc) - gajim.contacts.add_account(acc) + # Add after creating RosterWindow + # We want to test the filling explicitly + gajim.contacts = contacts_module.LegacyContactsAPI() + gajim.connections = {} + self.roster = roster_window.RosterWindow() - ### Custom assertions - def assert_all_contacts_are_in_roster(self, acc): - for jid in contacts[acc]: - self.assert_contact_is_in_roster(jid, acc) + for acc in contacts: + gajim.connections[acc] = MockConnection(acc) + gajim.contacts.add_account(acc) - def assert_contact_is_in_roster(self, jid, account): - contacts = gajim.contacts.get_contacts(account, jid) - # check for all resources - for contact in contacts: - iters = self.roster._get_contact_iter(jid, account, - model=self.roster.model) + ### Custom assertions + def assert_all_contacts_are_in_roster(self, acc): + for jid in contacts[acc]: + self.assert_contact_is_in_roster(jid, acc) - if jid != gajim.get_jid_from_account(account): - # We don't care for groups of SelfContact - self.assertTrue(len(iters) == len(contact.get_shown_groups()), - msg='Contact is not in all his groups') + def assert_contact_is_in_roster(self, jid, account): + contacts = gajim.contacts.get_contacts(account, jid) + # check for all resources + for contact in contacts: + iters = self.roster._get_contact_iter(jid, account, + model=self.roster.model) - # Are we big brother? - bb_jid = None - bb_account = None - family = gajim.contacts.get_metacontacts_family(account, jid) - if family: - nearby_family, bb_jid, bb_account = \ - self.roster._get_nearby_family_and_big_brother(family, account) + if jid != gajim.get_jid_from_account(account): + # We don't care for groups of SelfContact + self.assertTrue(len(iters) == len(contact.get_shown_groups()), + msg='Contact is not in all his groups') - is_in_nearby_family = (jid, account) in ( - (data['jid'], data['account']) for data in nearby_family) - self.assertTrue(is_in_nearby_family, - msg='Contact not in his own nearby family') + # Are we big brother? + bb_jid = None + bb_account = None + family = gajim.contacts.get_metacontacts_family(account, jid) + if family: + nearby_family, bb_jid, bb_account = \ + self.roster._get_nearby_family_and_big_brother(family, account) - is_big_brother = (bb_jid, bb_account) == (jid, account) + is_in_nearby_family = (jid, account) in ( + (data['jid'], data['account']) for data in nearby_family) + self.assertTrue(is_in_nearby_family, + msg='Contact not in his own nearby family') - # check for each group tag - for titerC in iters: - self.assertTrue(self.roster.model.iter_is_valid(titerC), - msg='Contact iter invalid') + is_big_brother = (bb_jid, bb_account) == (jid, account) - c_model = self.roster.model[titerC] - self.assertEquals(contact.get_shown_name(), c_model[self.C_NAME], - msg='Contact name missmatch') - self.assertEquals(contact.jid, c_model[self.C_JID], - msg='Jid missmatch') + # check for each group tag + for titerC in iters: + self.assertTrue(self.roster.model.iter_is_valid(titerC), + msg='Contact iter invalid') - if not self.roster.regroup: - self.assertEquals(account, c_model[self.C_ACCOUNT], - msg='Account missmatch') + c_model = self.roster.model[titerC] + self.assertEquals(contact.get_shown_name(), c_model[self.C_NAME], + msg='Contact name missmatch') + self.assertEquals(contact.jid, c_model[self.C_JID], + msg='Jid missmatch') - # Check for correct nesting - parent_iter = self.roster.model.iter_parent(titerC) - p_model = self.roster.model[parent_iter] - if family: - if is_big_brother: - self.assertTrue(p_model[self.C_TYPE] == 'group', - msg='Big Brother is not on top') - else: - self.assertTrue(p_model[self.C_TYPE] == 'contact', - msg='Little Brother brother has no BigB') - else: - if jid == gajim.get_jid_from_account(account): - self.assertTrue(p_model[self.C_TYPE] == 'account', - msg='SelfContact is not on top') - else: - self.assertTrue(p_model[self.C_TYPE] == 'group', - msg='Contact not found in a group') + if not self.roster.regroup: + self.assertEquals(account, c_model[self.C_ACCOUNT], + msg='Account missmatch') - def assert_group_is_in_roster(self, group, account): - #TODO - pass + # Check for correct nesting + parent_iter = self.roster.model.iter_parent(titerC) + p_model = self.roster.model[parent_iter] + if family: + if is_big_brother: + self.assertTrue(p_model[self.C_TYPE] == 'group', + msg='Big Brother is not on top') + else: + self.assertTrue(p_model[self.C_TYPE] == 'contact', + msg='Little Brother brother has no BigB') + else: + if jid == gajim.get_jid_from_account(account): + self.assertTrue(p_model[self.C_TYPE] == 'account', + msg='SelfContact is not on top') + else: + self.assertTrue(p_model[self.C_TYPE] == 'group', + msg='Contact not found in a group') - def assert_account_is_in_roster(self, acc): - titerA = self.roster._get_account_iter(acc, model=self.roster.model) - self.assertTrue(self.roster.model.iter_is_valid(titerA), - msg='Account iter is invalid') + def assert_group_is_in_roster(self, group, account): + #TODO + pass - acc_model = self.roster.model[titerA] - self.assertEquals(acc_model[self.C_TYPE], 'account', - msg='No account found') + def assert_account_is_in_roster(self, acc): + titerA = self.roster._get_account_iter(acc, model=self.roster.model) + self.assertTrue(self.roster.model.iter_is_valid(titerA), + msg='Account iter is invalid') - if not self.roster.regroup: - self.assertEquals(acc_model[self.C_ACCOUNT], acc, - msg='Account not found') + acc_model = self.roster.model[titerA] + self.assertEquals(acc_model[self.C_TYPE], 'account', + msg='No account found') - self_jid = gajim.get_jid_from_account(acc) - self.assertEquals(acc_model[self.C_JID], self_jid, - msg='Account JID not found in account row') + if not self.roster.regroup: + self.assertEquals(acc_model[self.C_ACCOUNT], acc, + msg='Account not found') - def assert_model_is_in_sync(self): - #TODO: check that iter_n_children returns the correct numbers - pass + self_jid = gajim.get_jid_from_account(acc) + self.assertEquals(acc_model[self.C_JID], self_jid, + msg='Account JID not found in account row') - # tests - def test_fill_contacts_and_groups_dicts(self): - for acc in contacts: - self.roster.fill_contacts_and_groups_dicts(contacts[acc], acc) + def assert_model_is_in_sync(self): + #TODO: check that iter_n_children returns the correct numbers + pass - for jid in contacts[acc]: - instances = gajim.contacts.get_contacts(acc, jid) + # tests + def test_fill_contacts_and_groups_dicts(self): + for acc in contacts: + self.roster.fill_contacts_and_groups_dicts(contacts[acc], acc) - # Created a contact for each single jid? - self.assertTrue(len(instances) == 1) + for jid in contacts[acc]: + instances = gajim.contacts.get_contacts(acc, jid) - # Contacts kept their info - contact = instances[0] - self.assertEquals(contact.groups, contacts[acc][jid]['groups'], - msg='Group Missmatch') + # Created a contact for each single jid? + self.assertTrue(len(instances) == 1) - groups = contacts[acc][jid]['groups'] or ['General',] + # Contacts kept their info + contact = instances[0] + self.assertEquals(contact.groups, contacts[acc][jid]['groups'], + msg='Group Missmatch') - def test_fill_roster_model(self): - for acc in contacts: - self.roster.fill_contacts_and_groups_dicts(contacts[acc], acc) + groups = contacts[acc][jid]['groups'] or ['General',] - self.roster.add_account(acc) - self.assert_account_is_in_roster(acc) + def test_fill_roster_model(self): + for acc in contacts: + self.roster.fill_contacts_and_groups_dicts(contacts[acc], acc) - self.roster.add_account_contacts(acc) - self.assert_all_contacts_are_in_roster(acc) + self.roster.add_account(acc) + self.assert_account_is_in_roster(acc) - self.assert_model_is_in_sync() + self.roster.add_account_contacts(acc) + self.assert_all_contacts_are_in_roster(acc) + + self.assert_model_is_in_sync() class TestRosterWindowRegrouped(TestRosterWindow): - def setUp(self): - gajim.config.set('mergeaccounts', True) - TestRosterWindow.setUp(self) + def setUp(self): + gajim.config.set('mergeaccounts', True) + TestRosterWindow.setUp(self) - def test_toggle_regroup(self): - self.roster.regroup = not self.roster.regroup - self.roster.setup_and_draw_roster() - self.roster.regroup = not self.roster.regroup - self.roster.setup_and_draw_roster() + def test_toggle_regroup(self): + self.roster.regroup = not self.roster.regroup + self.roster.setup_and_draw_roster() + self.roster.regroup = not self.roster.regroup + self.roster.setup_and_draw_roster() class TestRosterWindowMetaContacts(TestRosterWindowRegrouped): - def test_receive_metacontact_data(self): - for complete_data in metacontact_data: - t_acc = complete_data[0]['account'] - t_jid = complete_data[0]['jid'] - data = complete_data[1:] - for brother in data: - acc = brother['account'] - jid = brother['jid'] - gajim.contacts.add_metacontact(t_acc, t_jid, acc, jid) - self.roster.setup_and_draw_roster() + def test_receive_metacontact_data(self): + for complete_data in metacontact_data: + t_acc = complete_data[0]['account'] + t_jid = complete_data[0]['jid'] + data = complete_data[1:] + for brother in data: + acc = brother['account'] + jid = brother['jid'] + gajim.contacts.add_metacontact(t_acc, t_jid, acc, jid) + self.roster.setup_and_draw_roster() - def test_connect_new_metacontact(self): - self.test_fill_roster_model() + def test_connect_new_metacontact(self): + self.test_fill_roster_model() - jid = u'coolstuff@gajim.org' - contact = gajim.contacts.create_contact(jid, account1) - gajim.contacts.add_contact(account1, contact) - self.roster.add_contact(jid, account1) - self.roster.chg_contact_status(contact, 'offline', '', account1) + jid = u'coolstuff@gajim.org' + contact = gajim.contacts.create_contact(jid, account1) + gajim.contacts.add_contact(account1, contact) + self.roster.add_contact(jid, account1) + self.roster.chg_contact_status(contact, 'offline', '', account1) - gajim.contacts.add_metacontact(account1, u'samejid@gajim.org', - account1, jid) - self.roster.chg_contact_status(contact, 'online', '', account1) + gajim.contacts.add_metacontact(account1, u'samejid@gajim.org', + account1, jid) + self.roster.chg_contact_status(contact, 'online', '', account1) - self.assert_model_is_in_sync() + self.assert_model_is_in_sync() if __name__ == '__main__': - unittest.main() - -# vim: se ts=3: + unittest.main() diff --git a/test/integration/test_xmpp_client_nb.py b/test/integration/test_xmpp_client_nb.py index 24a54a3ca..2efdcfe91 100644 --- a/test/integration/test_xmpp_client_nb.py +++ b/test/integration/test_xmpp_client_nb.py @@ -24,148 +24,146 @@ xmpp_server_port = ('gajim.org', 5222) credentials = ['unittest', 'testtest', 'res'] class TestNonBlockingClient(unittest.TestCase): - ''' - Test Cases class for NonBlockingClient. - ''' - def setUp(self): - ''' IdleQueue thread is run and dummy connection is created. ''' - self.idlequeue_thread = IdleQueueThread() - self.connection = MockConnection() # for dummy callbacks - self.idlequeue_thread.start() + ''' + Test Cases class for NonBlockingClient. + ''' + def setUp(self): + ''' IdleQueue thread is run and dummy connection is created. ''' + self.idlequeue_thread = IdleQueueThread() + self.connection = MockConnection() # for dummy callbacks + self.idlequeue_thread.start() - def tearDown(self): - ''' IdleQueue thread is stopped. ''' - self.idlequeue_thread.stop_thread() - self.idlequeue_thread.join() + def tearDown(self): + ''' IdleQueue thread is stopped. ''' + self.idlequeue_thread.stop_thread() + self.idlequeue_thread.join() - self.client = None + self.client = None - def open_stream(self, server_port, wrong_pass=False): - ''' - Method opening the XMPP connection. It returns when - is received from server. + def open_stream(self, server_port, wrong_pass=False): + ''' + Method opening the XMPP connection. It returns when + is received from server. - :param server_port: tuple of (hostname, port) for where the client should - connect. - ''' + :param server_port: tuple of (hostname, port) for where the client should + connect. + ''' - class TempConnection(): - def get_password(self, cb): - if wrong_pass: - cb('wrong pass') - else: - cb(credentials[1]) - def on_connect_failure(self): - pass + class TempConnection(): + def get_password(self, cb): + if wrong_pass: + cb('wrong pass') + else: + cb(credentials[1]) + def on_connect_failure(self): + pass - self.client = client_nb.NonBlockingClient( - domain=server_port[0], - idlequeue=self.idlequeue_thread.iq, - caller=Mock(realClass=TempConnection)) + self.client = client_nb.NonBlockingClient( + domain=server_port[0], + idlequeue=self.idlequeue_thread.iq, + caller=Mock(realClass=TempConnection)) - self.client.connect( - hostname=server_port[0], - port=server_port[1], - on_connect=lambda *args: self.connection.on_connect(True, *args), - on_connect_failure=lambda *args: self.connection.on_connect( - False, *args)) + self.client.connect( + hostname=server_port[0], + port=server_port[1], + on_connect=lambda *args: self.connection.on_connect(True, *args), + on_connect_failure=lambda *args: self.connection.on_connect( + False, *args)) - self.assert_(self.connection.wait(), - msg='waiting for callback from client constructor') - - # if on_connect was called, client has to be connected and vice versa - if self.connection.connect_succeeded: - self.assert_(self.client.get_connect_type()) - else: - self.assert_(not self.client.get_connect_type()) + self.assert_(self.connection.wait(), + msg='waiting for callback from client constructor') - def client_auth(self, username, password, resource, sasl): - ''' - Method authenticating connected client with supplied credentials. Returns - when authentication is over. + # if on_connect was called, client has to be connected and vice versa + if self.connection.connect_succeeded: + self.assert_(self.client.get_connect_type()) + else: + self.assert_(not self.client.get_connect_type()) - :param sasl: whether to use sasl (sasl=1) or old (sasl=0) authentication - :todo: to check and be more specific about when it returns - (bind, session..) - ''' - self.client.auth(username, password, resource, sasl, - on_auth=self.connection.on_auth) + def client_auth(self, username, password, resource, sasl): + ''' + Method authenticating connected client with supplied credentials. Returns + when authentication is over. - self.assert_(self.connection.wait(), msg='waiting for authentication') + :param sasl: whether to use sasl (sasl=1) or old (sasl=0) authentication + :todo: to check and be more specific about when it returns + (bind, session..) + ''' + self.client.auth(username, password, resource, sasl, + on_auth=self.connection.on_auth) - def do_disconnect(self): - ''' - Does disconnecting of connected client. Returns when TCP connection is - closed. - ''' - self.client.RegisterDisconnectHandler(self.connection.set_event) - self.client.disconnect() + self.assert_(self.connection.wait(), msg='waiting for authentication') - self.assertTrue(self.connection.wait(), msg='waiting for disconnecting') + def do_disconnect(self): + ''' + Does disconnecting of connected client. Returns when TCP connection is + closed. + ''' + self.client.RegisterDisconnectHandler(self.connection.set_event) + self.client.disconnect() - def test_proper_connect_sasl(self): - ''' - The ideal testcase - client is connected, authenticated with SASL and - then disconnected. - ''' - self.open_stream(xmpp_server_port) + self.assertTrue(self.connection.wait(), msg='waiting for disconnecting') - # if client is not connected, lets raise the AssertionError - self.assert_(self.client.get_connect_type()) - # client.disconnect() is already called from NBClient via - # _on_connected_failure, no need to call it here + def test_proper_connect_sasl(self): + ''' + The ideal testcase - client is connected, authenticated with SASL and + then disconnected. + ''' + self.open_stream(xmpp_server_port) - self.client_auth(credentials[0], credentials[1], credentials[2], sasl=1) - self.assert_(self.connection.con) - self.assert_(self.connection.auth=='sasl', msg='Unable to auth via SASL') + # if client is not connected, lets raise the AssertionError + self.assert_(self.client.get_connect_type()) + # client.disconnect() is already called from NBClient via + # _on_connected_failure, no need to call it here - self.do_disconnect() + self.client_auth(credentials[0], credentials[1], credentials[2], sasl=1) + self.assert_(self.connection.con) + self.assert_(self.connection.auth=='sasl', msg='Unable to auth via SASL') - def test_proper_connect_oldauth(self): - ''' - The ideal testcase - client is connected, authenticated with old auth and - then disconnected. - ''' - self.open_stream(xmpp_server_port) - self.assert_(self.client.get_connect_type()) - self.client_auth(credentials[0], credentials[1], credentials[2], sasl=0) - self.assert_(self.connection.con) - features = self.client.Dispatcher.Stream.features - if not features.getTag('auth'): - print "Server doesn't support old authentication type, ignoring test" - else: - self.assert_(self.connection.auth=='old_auth', - msg='Unable to auth via old_auth') - self.do_disconnect() + self.do_disconnect() - def test_connect_to_nonexisting_host(self): - ''' - Connect to nonexisting host. DNS request for A records should return - nothing. - ''' - self.open_stream(('fdsfsdf.fdsf.fss', 5222)) - self.assert_(not self.client.get_connect_type()) + def test_proper_connect_oldauth(self): + ''' + The ideal testcase - client is connected, authenticated with old auth and + then disconnected. + ''' + self.open_stream(xmpp_server_port) + self.assert_(self.client.get_connect_type()) + self.client_auth(credentials[0], credentials[1], credentials[2], sasl=0) + self.assert_(self.connection.con) + features = self.client.Dispatcher.Stream.features + if not features.getTag('auth'): + print "Server doesn't support old authentication type, ignoring test" + else: + self.assert_(self.connection.auth=='old_auth', + msg='Unable to auth via old_auth') + self.do_disconnect() - def test_connect_to_wrong_port(self): - ''' - Connect to nonexisting server. DNS request for A records should return an - IP but there shouldn't be XMPP server running on specified port. - ''' - self.open_stream((xmpp_server_port[0], 31337)) - self.assert_(not self.client.get_connect_type()) + def test_connect_to_nonexisting_host(self): + ''' + Connect to nonexisting host. DNS request for A records should return + nothing. + ''' + self.open_stream(('fdsfsdf.fdsf.fss', 5222)) + self.assert_(not self.client.get_connect_type()) - def test_connect_with_wrong_creds(self): - ''' - Connecting with invalid password. - ''' - self.open_stream(xmpp_server_port, wrong_pass=True) - self.assert_(self.client.get_connect_type()) - self.client_auth(credentials[0], 'wrong pass', credentials[2], sasl=1) - self.assert_(self.connection.auth is None) - self.do_disconnect() + def test_connect_to_wrong_port(self): + ''' + Connect to nonexisting server. DNS request for A records should return an + IP but there shouldn't be XMPP server running on specified port. + ''' + self.open_stream((xmpp_server_port[0], 31337)) + self.assert_(not self.client.get_connect_type()) + + def test_connect_with_wrong_creds(self): + ''' + Connecting with invalid password. + ''' + self.open_stream(xmpp_server_port, wrong_pass=True) + self.assert_(self.client.get_connect_type()) + self.client_auth(credentials[0], 'wrong pass', credentials[2], sasl=1) + self.assert_(self.connection.auth is None) + self.do_disconnect() if __name__ == '__main__': - unittest.main() - -# vim: se ts=3: + unittest.main() diff --git a/test/integration/test_xmpp_transports_nb.py b/test/integration/test_xmpp_transports_nb.py index ef9908903..98f2c87fc 100644 --- a/test/integration/test_xmpp_transports_nb.py +++ b/test/integration/test_xmpp_transports_nb.py @@ -14,265 +14,263 @@ from common.xmpp import transports_nb class AbstractTransportTest(unittest.TestCase): - ''' Encapsulates Idlequeue instantiation for transports and more...''' + ''' Encapsulates Idlequeue instantiation for transports and more...''' - def setUp(self): - ''' IdleQueue thread is run and dummy connection is created. ''' - self.idlequeue_thread = IdleQueueThread() - self.idlequeue_thread.start() - self._setup_hook() + def setUp(self): + ''' IdleQueue thread is run and dummy connection is created. ''' + self.idlequeue_thread = IdleQueueThread() + self.idlequeue_thread.start() + self._setup_hook() - def tearDown(self): - ''' IdleQueue thread is stopped. ''' - self._teardown_hook() - self.idlequeue_thread.stop_thread() - self.idlequeue_thread.join() + def tearDown(self): + ''' IdleQueue thread is stopped. ''' + self._teardown_hook() + self.idlequeue_thread.stop_thread() + self.idlequeue_thread.join() - def _setup_hook(self): - pass + def _setup_hook(self): + pass - def _teardown_hook(self): - pass + def _teardown_hook(self): + pass - def expect_receive(self, expected, count=1, msg=None): - ''' - Returns a callback function that will assert whether the data passed to - it equals the one specified when calling this function. + def expect_receive(self, expected, count=1, msg=None): + ''' + Returns a callback function that will assert whether the data passed to + it equals the one specified when calling this function. - Can be used to make sure transport dispatch correct data. - ''' - def receive(data, *args, **kwargs): - self.assertEqual(data, expected, msg=msg) - self._expected_count -= 1 - self._expected_count = count - return receive + Can be used to make sure transport dispatch correct data. + ''' + def receive(data, *args, **kwargs): + self.assertEqual(data, expected, msg=msg) + self._expected_count -= 1 + self._expected_count = count + return receive - def have_received_expected(self): - ''' - Plays together with expect_receive(). Will return true if expected_rcv - callback was called as often as specified - ''' - return self._expected_count == 0 + def have_received_expected(self): + ''' + Plays together with expect_receive(). Will return true if expected_rcv + callback was called as often as specified + ''' + return self._expected_count == 0 class TestNonBlockingTCP(AbstractTransportTest): - ''' - Test class for NonBlockingTCP. Will actually try to connect to an existing - XMPP server. - ''' - class MockClient(IdleMock): - ''' Simple client to test transport functionality ''' - def __init__(self, idlequeue, testcase): - self.idlequeue = idlequeue - self.testcase = testcase - IdleMock.__init__(self) + ''' + Test class for NonBlockingTCP. Will actually try to connect to an existing + XMPP server. + ''' + class MockClient(IdleMock): + ''' Simple client to test transport functionality ''' + def __init__(self, idlequeue, testcase): + self.idlequeue = idlequeue + self.testcase = testcase + IdleMock.__init__(self) - def do_connect(self, establish_tls=False, proxy_dict=None): - try: - ips = socket.getaddrinfo('gajim.org', 5222, - socket.AF_UNSPEC,socket.SOCK_STREAM) - ip = ips[0] - except socket.error, e: - self.testcase.fail(msg=str(e)) + def do_connect(self, establish_tls=False, proxy_dict=None): + try: + ips = socket.getaddrinfo('gajim.org', 5222, + socket.AF_UNSPEC, socket.SOCK_STREAM) + ip = ips[0] + except socket.error, e: + self.testcase.fail(msg=str(e)) - self.socket = transports_nb.NonBlockingTCP( - raise_event=lambda event_type, data: self.testcase.assertTrue( - event_type and data), - on_disconnect=lambda: self.on_success(mode='SocketDisconnect'), - idlequeue=self.idlequeue, - estabilish_tls=establish_tls, - certs=('../data/other/cacerts.pem', 'tmp/cacerts.pem'), - proxy_dict=proxy_dict) + self.socket = transports_nb.NonBlockingTCP( + raise_event=lambda event_type, data: self.testcase.assertTrue( + event_type and data), + on_disconnect=lambda: self.on_success(mode='SocketDisconnect'), + idlequeue=self.idlequeue, + estabilish_tls=establish_tls, + certs=('../data/other/cacerts.pem', 'tmp/cacerts.pem'), + proxy_dict=proxy_dict) - self.socket.PlugIn(self) + self.socket.PlugIn(self) - self.socket.connect(conn_5tuple=ip, - on_connect=lambda: self.on_success(mode='TCPconnect'), - on_connect_failure=self.on_failure) - self.testcase.assertTrue(self.wait(), msg='Connection timed out') + self.socket.connect(conn_5tuple=ip, + on_connect=lambda: self.on_success(mode='TCPconnect'), + on_connect_failure=self.on_failure) + self.testcase.assertTrue(self.wait(), msg='Connection timed out') - def do_disconnect(self): - self.socket.disconnect() - self.testcase.assertTrue(self.wait(), msg='Disconnect timed out') + def do_disconnect(self): + self.socket.disconnect() + self.testcase.assertTrue(self.wait(), msg='Disconnect timed out') - def on_failure(self, err_message): - self.set_event() - self.testcase.fail(msg=err_message) + def on_failure(self, err_message): + self.set_event() + self.testcase.fail(msg=err_message) - def on_success(self, mode, data=None): - if mode == "TCPconnect": - pass - if mode == "SocketDisconnect": - pass - self.set_event() + def on_success(self, mode, data=None): + if mode == "TCPconnect": + pass + if mode == "SocketDisconnect": + pass + self.set_event() - def _setup_hook(self): - self.client = self.MockClient(idlequeue=self.idlequeue_thread.iq, - testcase=self) - - def _teardown_hook(self): - if self.client.socket.state == 'CONNECTED': - self.client.do_disconnect() + def _setup_hook(self): + self.client = self.MockClient(idlequeue=self.idlequeue_thread.iq, + testcase=self) - def test_connect_disconnect_plain(self): - ''' Establish plain connection ''' - self.client.do_connect(establish_tls=False) - self.assertEquals(self.client.socket.state, 'CONNECTED') - self.client.do_disconnect() - self.assertEquals(self.client.socket.state, 'DISCONNECTED') - -# def test_connect_disconnect_ssl(self): -# ''' Establish SSL (not TLS) connection ''' -# self.client.do_connect(establish_tls=True) -# self.assertEquals(self.client.socket.state, 'CONNECTED') -# self.client.do_disconnect() -# self.assertEquals(self.client.socket.state, 'DISCONNECTED') + def _teardown_hook(self): + if self.client.socket.state == 'CONNECTED': + self.client.do_disconnect() - def test_do_receive(self): - ''' Test _do_receive method by overwriting socket.recv ''' - self.client.do_connect() - sock = self.client.socket + def test_connect_disconnect_plain(self): + ''' Establish plain connection ''' + self.client.do_connect(establish_tls=False) + self.assertEquals(self.client.socket.state, 'CONNECTED') + self.client.do_disconnect() + self.assertEquals(self.client.socket.state, 'DISCONNECTED') - # transport shall receive data - data = "Please don't fail" - sock._recv = lambda buffer: data - sock.onreceive(self.expect_receive(data)) - sock._do_receive() - self.assertTrue(self.have_received_expected(), msg='Did not receive data') - self.assert_(self.client.socket.state == 'CONNECTED') +# def test_connect_disconnect_ssl(self): +# ''' Establish SSL (not TLS) connection ''' +# self.client.do_connect(establish_tls=True) +# self.assertEquals(self.client.socket.state, 'CONNECTED') +# self.client.do_disconnect() +# self.assertEquals(self.client.socket.state, 'DISCONNECTED') - # transport shall do nothing as an non-fatal SSL is simulated - sock._recv = lambda buffer: None - sock.onreceive(self.assertFalse) # we did not receive anything... - sock._do_receive() - self.assert_(self.client.socket.state == 'CONNECTED') + def test_do_receive(self): + ''' Test _do_receive method by overwriting socket.recv ''' + self.client.do_connect() + sock = self.client.socket - # transport shall disconnect as remote side closed the connection - sock._recv = lambda buffer: '' - sock.onreceive(self.assertFalse) # we did not receive anything... - sock._do_receive() - self.assert_(self.client.socket.state == 'DISCONNECTED') + # transport shall receive data + data = "Please don't fail" + sock._recv = lambda buffer: data + sock.onreceive(self.expect_receive(data)) + sock._do_receive() + self.assertTrue(self.have_received_expected(), msg='Did not receive data') + self.assert_(self.client.socket.state == 'CONNECTED') - def test_do_send(self): - ''' Test _do_send method by overwriting socket.send ''' - self.client.do_connect() - sock = self.client.socket + # transport shall do nothing as an non-fatal SSL is simulated + sock._recv = lambda buffer: None + sock.onreceive(self.assertFalse) # we did not receive anything... + sock._do_receive() + self.assert_(self.client.socket.state == 'CONNECTED') - outgoing = [] # what we have actually send to our socket.socket - data_part1 = "Please don't " - data_part2 = "fail!" - data_complete = data_part1 + data_part2 - - # Simulate everything could be send in one go - def _send_all(data): - outgoing.append(data) - return len(data) - sock._send = _send_all - sock.send(data_part1) - sock.send(data_part2) - sock._do_send() - sock._do_send() - self.assertTrue(self.client.socket.state == 'CONNECTED') - self.assertTrue(data_part1 in outgoing and data_part2 in outgoing) - self.assertFalse(sock.sendqueue and sock.sendbuff, - msg='There is still unsend data in buffers') + # transport shall disconnect as remote side closed the connection + sock._recv = lambda buffer: '' + sock.onreceive(self.assertFalse) # we did not receive anything... + sock._do_receive() + self.assert_(self.client.socket.state == 'DISCONNECTED') - # Simulate data could only be sent in chunks - self.chunk_count = 0 - outgoing = [] - def _send_chunks(data): - if self.chunk_count == 0: - outgoing.append(data_part1) - self.chunk_count += 1 - return len(data_part1) - else: - outgoing.append(data_part2) - return len(data_part2) - sock._send = _send_chunks - sock.send(data_complete) - sock._do_send() # process first chunk - sock._do_send() # process the second one - self.assertTrue(self.client.socket.state == 'CONNECTED') - self.assertTrue(data_part1 in outgoing and data_part2 in outgoing) - self.assertFalse(sock.sendqueue and sock.sendbuff, - msg='There is still unsend data in buffers') + def test_do_send(self): + ''' Test _do_send method by overwriting socket.send ''' + self.client.do_connect() + sock = self.client.socket + + outgoing = [] # what we have actually send to our socket.socket + data_part1 = "Please don't " + data_part2 = "fail!" + data_complete = data_part1 + data_part2 + + # Simulate everything could be send in one go + def _send_all(data): + outgoing.append(data) + return len(data) + sock._send = _send_all + sock.send(data_part1) + sock.send(data_part2) + sock._do_send() + sock._do_send() + self.assertTrue(self.client.socket.state == 'CONNECTED') + self.assertTrue(data_part1 in outgoing and data_part2 in outgoing) + self.assertFalse(sock.sendqueue and sock.sendbuff, + msg='There is still unsend data in buffers') + + # Simulate data could only be sent in chunks + self.chunk_count = 0 + outgoing = [] + def _send_chunks(data): + if self.chunk_count == 0: + outgoing.append(data_part1) + self.chunk_count += 1 + return len(data_part1) + else: + outgoing.append(data_part2) + return len(data_part2) + sock._send = _send_chunks + sock.send(data_complete) + sock._do_send() # process first chunk + sock._do_send() # process the second one + self.assertTrue(self.client.socket.state == 'CONNECTED') + self.assertTrue(data_part1 in outgoing and data_part2 in outgoing) + self.assertFalse(sock.sendqueue and sock.sendbuff, + msg='There is still unsend data in buffers') class TestNonBlockingHTTP(AbstractTransportTest): - ''' Test class for NonBlockingHTTP transport''' + ''' Test class for NonBlockingHTTP transport''' - bosh_http_dict = { - 'http_uri': 'http://gajim.org:5280/http-bind', - 'http_version': 'HTTP/1.1', - 'http_persistent': True, - 'add_proxy_headers': False - } + bosh_http_dict = { + 'http_uri': 'http://gajim.org:5280/http-bind', + 'http_version': 'HTTP/1.1', + 'http_persistent': True, + 'add_proxy_headers': False + } - def _get_transport(self, http_dict, proxy_dict=None): - return transports_nb.NonBlockingHTTP( - raise_event=None, - on_disconnect=None, - idlequeue=self.idlequeue_thread.iq, - estabilish_tls=False, - certs=None, - on_http_request_possible=lambda: None, - on_persistent_fallback=None, - http_dict=http_dict, - proxy_dict=proxy_dict, - ) - - def test_parse_own_http_message(self): - ''' Build a HTTP message and try to parse it afterwards ''' - transport = self._get_transport(self.bosh_http_dict) + def _get_transport(self, http_dict, proxy_dict=None): + return transports_nb.NonBlockingHTTP( + raise_event=None, + on_disconnect=None, + idlequeue=self.idlequeue_thread.iq, + estabilish_tls=False, + certs=None, + on_http_request_possible=lambda: None, + on_persistent_fallback=None, + http_dict=http_dict, + proxy_dict=proxy_dict, + ) - data = "Please don't fail!" - http_message = transport.build_http_message(data) - statusline, headers, http_body, buffer_rest = transport.parse_http_message( - http_message) + def test_parse_own_http_message(self): + ''' Build a HTTP message and try to parse it afterwards ''' + transport = self._get_transport(self.bosh_http_dict) - self.assertFalse(bool(buffer_rest)) - self.assertTrue(statusline and isinstance(statusline, list)) - self.assertTrue(headers and isinstance(headers, dict)) - self.assertEqual(data, http_body, msg='Input and output are different') + data = "Please don't fail!" + http_message = transport.build_http_message(data) + statusline, headers, http_body, buffer_rest = transport.parse_http_message( + http_message) - def test_receive_http_message(self): - ''' Let _on_receive handle some http messages ''' - transport = self._get_transport(self.bosh_http_dict) - - header = ("HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\n" + - "Content-Length: 88\r\n\r\n") - payload = "Please don't fail!" - body = "%s" \ - % payload - message = "%s%s" % (header, body) + self.assertFalse(bool(buffer_rest)) + self.assertTrue(statusline and isinstance(statusline, list)) + self.assertTrue(headers and isinstance(headers, dict)) + self.assertEqual(data, http_body, msg='Input and output are different') - # try to receive in one go - transport.onreceive(self.expect_receive(body, msg='Failed: In one go')) - transport._on_receive(message) - self.assertTrue(self.have_received_expected(), msg='Failed: In one go') + def test_receive_http_message(self): + ''' Let _on_receive handle some http messages ''' + transport = self._get_transport(self.bosh_http_dict) - def test_receive_http_message_in_chunks(self): - ''' Let _on_receive handle some chunked http messages ''' - transport = self._get_transport(self.bosh_http_dict) + header = ("HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\n" + + "Content-Length: 88\r\n\r\n") + payload = "Please don't fail!" + body = "%s" \ + % payload + message = "%s%s" % (header, body) - payload = "Please don't fail!\n\n" - body = "%s" \ - % payload - header = "HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\n" +\ - "Content-Length: %i\r\n\r\n" % len(body) - message = "%s%s" % (header, body) + # try to receive in one go + transport.onreceive(self.expect_receive(body, msg='Failed: In one go')) + transport._on_receive(message) + self.assertTrue(self.have_received_expected(), msg='Failed: In one go') - chunk1, chunk2, chunk3, chunk4 = message[:20], message[20:73], \ - message[73:85], message[85:] - nextmessage_chunk = "\r\n\r\nHTTP/1.1 200 OK\r\nContent-Type: text/x" - chunks = (chunk1, chunk2, chunk3, chunk4, nextmessage_chunk) + def test_receive_http_message_in_chunks(self): + ''' Let _on_receive handle some chunked http messages ''' + transport = self._get_transport(self.bosh_http_dict) - transport.onreceive(self.expect_receive(body, msg='Failed: In chunks')) - for chunk in chunks: - transport._on_receive(chunk) - self.assertTrue(self.have_received_expected(), msg='Failed: In chunks') + payload = "Please don't fail!\n\n" + body = "%s" \ + % payload + header = "HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\n" +\ + "Content-Length: %i\r\n\r\n" % len(body) + message = "%s%s" % (header, body) + + chunk1, chunk2, chunk3, chunk4 = message[:20], message[20:73], \ + message[73:85], message[85:] + nextmessage_chunk = "\r\n\r\nHTTP/1.1 200 OK\r\nContent-Type: text/x" + chunks = (chunk1, chunk2, chunk3, chunk4, nextmessage_chunk) + + transport.onreceive(self.expect_receive(body, msg='Failed: In chunks')) + for chunk in chunks: + transport._on_receive(chunk) + self.assertTrue(self.have_received_expected(), msg='Failed: In chunks') if __name__ == '__main__': - unittest.main() - -# vim: se ts=3: + unittest.main() diff --git a/test/lib/__init__.py b/test/lib/__init__.py index 7d0b1bd98..8d211a03e 100644 --- a/test/lib/__init__.py +++ b/test/lib/__init__.py @@ -7,8 +7,8 @@ shortargs = 'hnv:' longargs = 'help no-x verbose=' opts, args = getopt.getopt(sys.argv[1:], shortargs, longargs.split()) for o, a in opts: - if o in ('-n', '--no-x'): - use_x = False + if o in ('-n', '--no-x'): + use_x = False gajim_root = os.path.join(os.path.abspath(os.path.dirname(__file__)), '../..') @@ -25,28 +25,26 @@ import __builtin__ __builtin__._ = lambda x: x def setup_env(): - # wipe config directory - if os.path.isdir(configdir): - import shutil - shutil.rmtree(configdir) + # wipe config directory + if os.path.isdir(configdir): + import shutil + shutil.rmtree(configdir) - os.mkdir(configdir) + os.mkdir(configdir) - import common.configpaths - common.configpaths.gajimpaths.init(configdir) - common.configpaths.gajimpaths.init_profile() + import common.configpaths + common.configpaths.gajimpaths.init(configdir) + common.configpaths.gajimpaths.init_profile() - # for some reason common.gajim needs to be imported before xmpppy? - from common import gajim + # for some reason common.gajim needs to be imported before xmpppy? + from common import gajim - import logging - logging.basicConfig() + import logging + logging.basicConfig() - gajim.DATA_DIR = gajim_root + '/data' - gajim.use_x = use_x + gajim.DATA_DIR = gajim_root + '/data' + gajim.use_x = use_x - if use_x: - import gtkgui_helpers - gtkgui_helpers.GUI_DIR = gajim_root + '/data/gui' - -# vim: se ts=3: + if use_x: + import gtkgui_helpers + gtkgui_helpers.GUI_DIR = gajim_root + '/data/gui' diff --git a/test/lib/data.py b/test/lib/data.py index c713de0ac..af2a87057 100755 --- a/test/lib/data.py +++ b/test/lib/data.py @@ -5,75 +5,73 @@ account3 = u'dingdong.org' contacts = {} contacts[account1] = { - u'myjid@'+account1: { - 'ask': None, 'groups': [], 'name': None, 'resources': {}, - 'subscription': u'both'}, - u'default1@gajim.org': { - 'ask': None, 'groups': [], 'name': None, 'resources': {}, - 'subscription': u'both'}, - u'default2@gajim.org': { - 'ask': None, 'groups': [u'GroupA',], 'name': None, 'resources': {}, - 'subscription': u'both'}, - u'Cool"chârßéµö@gajim.org': { - 'ask': None, 'groups': [u'' % self.thread_id + def __repr__(self): + return '' % self.thread_id - def __nonzero__(self): - return True + def __nonzero__(self): + return True - def __eq__(self, other): - return self is other - -# vim: se ts=3: + def __eq__(self, other): + return self is other diff --git a/test/lib/mock.py b/test/lib/mock.py index 2c87d53f8..fdaf000db 100644 --- a/test/lib/mock.py +++ b/test/lib/mock.py @@ -94,7 +94,7 @@ class Mock(object): self._setupSubclassMethodInterceptors() def _setupSubclassMethodInterceptors(self): - methods = inspect.getmembers(self.realClass,inspect.isroutine) + methods = inspect.getmembers(self.realClass, inspect.isroutine) baseMethods = dict(inspect.getmembers(Mock, inspect.ismethod)) for m in methods: name = m[0] @@ -109,7 +109,7 @@ class Mock(object): self.mockReturnValues.update(methodReturnValues) def mockSetExpectation(self, name, testFn, after=0, until=0): - self.mockExpectations.setdefault(name, []).append((testFn,after,until)) + self.mockExpectations.setdefault(name, []).append((testFn, after, until)) def _checkInterfaceCall(self, name, callParams, callKwParams): """ @@ -231,7 +231,7 @@ class MockCall: s = s + sep + repr(p) sep = ', ' items = sorted(self.kwparams.items()) - for k,v in items: + for k, v in items: s = s + sep + k + '=' + repr(v) sep = ', ' s = s + ')' @@ -252,7 +252,7 @@ class MockCallable: def __call__(self, *params, **kwparams): self.mock._checkInterfaceCall(self.name, params, kwparams) - thisCall = self.recordCall(params,kwparams) + thisCall = self.recordCall(params, kwparams) self.checkExpectations(thisCall, params, kwparams) return self.makeCall(params, kwparams) @@ -461,8 +461,3 @@ def HASMETHOD(method): return testFn CALLABLE = callable - - - - -# vim: se ts=3: diff --git a/test/lib/notify.py b/test/lib/notify.py index f14100af3..b13d03678 100644 --- a/test/lib/notify.py +++ b/test/lib/notify.py @@ -3,15 +3,13 @@ notifications = [] def notify(event, jid, account, parameters, advanced_notif_num = None): - notifications.append((event, jid, account, parameters, advanced_notif_num)) + notifications.append((event, jid, account, parameters, advanced_notif_num)) def get_advanced_notification(event, account, contact): - return None + return None def get_show_in_roster(event, account, contact, session = None): - return True + return True def get_show_in_systray(event, account, contact, type_ = None): - return True - -# vim: se ts=3: \ No newline at end of file + return True diff --git a/test/lib/xmpp_mocks.py b/test/lib/xmpp_mocks.py index bf60ae6dc..0b4055949 100644 --- a/test/lib/xmpp_mocks.py +++ b/test/lib/xmpp_mocks.py @@ -12,86 +12,84 @@ IDLEQUEUE_INTERVAL = 0.2 # polling interval. 200ms is used in Gajim as default IDLEMOCK_TIMEOUT = 30 # how long we wait for an event class IdleQueueThread(threading.Thread): - ''' - Thread for regular processing of idlequeue. - ''' - def __init__(self): - self.iq = idlequeue.IdleQueue() - self.stop = threading.Event() # Event to stop the thread main loop. - self.stop.clear() - threading.Thread.__init__(self) - - def run(self): - while not self.stop.isSet(): - self.iq.process() - time.sleep(IDLEQUEUE_INTERVAL) + ''' + Thread for regular processing of idlequeue. + ''' + def __init__(self): + self.iq = idlequeue.IdleQueue() + self.stop = threading.Event() # Event to stop the thread main loop. + self.stop.clear() + threading.Thread.__init__(self) + + def run(self): + while not self.stop.isSet(): + self.iq.process() + time.sleep(IDLEQUEUE_INTERVAL) + + def stop_thread(self): + self.stop.set() - def stop_thread(self): - self.stop.set() - class IdleMock: - ''' - Serves as template for testing objects that are normally controlled by GUI. - Allows to wait for asynchronous callbacks with wait() method. - ''' - def __init__(self): - self._event = threading.Event() - self._event.clear() + ''' + Serves as template for testing objects that are normally controlled by GUI. + Allows to wait for asynchronous callbacks with wait() method. + ''' + def __init__(self): + self._event = threading.Event() + self._event.clear() - def wait(self): - ''' - Block until some callback sets the event and clearing the event - subsequently. - Returns True if event was set, False on timeout - ''' - self._event.wait(IDLEMOCK_TIMEOUT) - if self._event.isSet(): - self._event.clear() - return True - else: - return False + def wait(self): + ''' + Block until some callback sets the event and clearing the event + subsequently. + Returns True if event was set, False on timeout + ''' + self._event.wait(IDLEMOCK_TIMEOUT) + if self._event.isSet(): + self._event.clear() + return True + else: + return False - def set_event(self): - self._event.set() + def set_event(self): + self._event.set() class MockConnection(IdleMock, Mock): - ''' - Class simulating Connection class from src/common/connection.py + ''' + Class simulating Connection class from src/common/connection.py - It is derived from Mock in order to avoid defining all methods - from real Connection that are called from NBClient or Dispatcher - ( _event_dispatcher for example) - ''' + It is derived from Mock in order to avoid defining all methods + from real Connection that are called from NBClient or Dispatcher + ( _event_dispatcher for example) + ''' - def __init__(self, *args): - self.connect_succeeded = True - IdleMock.__init__(self) - Mock.__init__(self, *args) + def __init__(self, *args): + self.connect_succeeded = True + IdleMock.__init__(self) + Mock.__init__(self, *args) - def on_connect(self, success, *args): - ''' - Method called after connecting - after receiving - from server (NOT after TLS stream restart) or connect failure - ''' - self.connect_succeeded = success - self.set_event() - + def on_connect(self, success, *args): + ''' + Method called after connecting - after receiving + from server (NOT after TLS stream restart) or connect failure + ''' + self.connect_succeeded = success + self.set_event() - def on_auth(self, con, auth): - ''' - Method called after authentication, regardless of the result. - :Parameters: - con : NonBlockingClient - reference to authenticated object - auth : string - type of authetication in case of success ('old_auth', 'sasl') or - None in case of auth failure - ''' - self.auth_connection = con - self.auth = auth - self.set_event() + def on_auth(self, con, auth): + ''' + Method called after authentication, regardless of the result. -# vim: se ts=3: + :Parameters: + con : NonBlockingClient + reference to authenticated object + auth : string + type of authetication in case of success ('old_auth', 'sasl') or + None in case of auth failure + ''' + self.auth_connection = con + self.auth = auth + self.set_event() diff --git a/test/runtests.py b/test/runtests.py index 6bca37228..c2b0cbaa5 100755 --- a/test/runtests.py +++ b/test/runtests.py @@ -14,55 +14,53 @@ use_x = True verbose = 1 try: - shortargs = 'hnv:' - longargs = 'help no-x verbose=' - opts, args = getopt.getopt(sys.argv[1:], shortargs, longargs.split()) + shortargs = 'hnv:' + longargs = 'help no-x verbose=' + opts, args = getopt.getopt(sys.argv[1:], shortargs, longargs.split()) except getopt.error, msg: - print msg - print 'for help use --help' - sys.exit(2) + print msg + print 'for help use --help' + sys.exit(2) for o, a in opts: - if o in ('-h', '--help'): - print 'runtests [--help] [--no-x] [--verbose level]' - sys.exit() - elif o in ('-n', '--no-x'): - use_x = False - elif o in ('-v', '--verbose'): - try: - verbose = int(a) - except Exception: - print 'verbose must be a number >= 0' - sys.exit(2) + if o in ('-h', '--help'): + print 'runtests [--help] [--no-x] [--verbose level]' + sys.exit() + elif o in ('-n', '--no-x'): + use_x = False + elif o in ('-v', '--verbose'): + try: + verbose = int(a) + except Exception: + print 'verbose must be a number >= 0' + sys.exit(2) # new test modules need to be added manually modules = ( 'unit.test_xmpp_dispatcher_nb', - 'unit.test_xmpp_transports_nb', - 'unit.test_protocol_caps', - 'unit.test_caps_cache', - 'unit.test_contacts', - 'unit.test_sessions', - 'unit.test_account', - 'unit.test_gui_interface', - ) + 'unit.test_xmpp_transports_nb', + 'unit.test_protocol_caps', + 'unit.test_caps_cache', + 'unit.test_contacts', + 'unit.test_sessions', + 'unit.test_account', + 'unit.test_gui_interface', + ) #modules = () if use_x: - modules += ('integration.test_gui_event_integration', - 'integration.test_roster', - 'integration.test_resolver', - 'integration.test_xmpp_client_nb', - 'integration.test_xmpp_transports_nb' - ) + modules += ('integration.test_gui_event_integration', + 'integration.test_roster', + 'integration.test_resolver', + 'integration.test_xmpp_client_nb', + 'integration.test_xmpp_transports_nb' + ) nb_errors = 0 nb_failures = 0 for mod in modules: - suite = unittest.defaultTestLoader.loadTestsFromName(mod) - result = unittest.TextTestRunner(verbosity=verbose).run(suite) - nb_errors += len(result.errors) - nb_failures += len(result.failures) + suite = unittest.defaultTestLoader.loadTestsFromName(mod) + result = unittest.TextTestRunner(verbosity=verbose).run(suite) + nb_errors += len(result.errors) + nb_failures += len(result.failures) sys.exit(nb_errors + nb_failures) - -# vim: se ts=3: diff --git a/test/test_pluginmanager.py b/test/test_pluginmanager.py index 861641ac2..c6dafac13 100644 --- a/test/test_pluginmanager.py +++ b/test/test_pluginmanager.py @@ -44,8 +44,8 @@ __builtin__._ = lambda x: x # wipe config directory import os if os.path.isdir(configdir): - import shutil - shutil.rmtree(configdir) + import shutil + shutil.rmtree(configdir) os.mkdir(configdir) @@ -67,27 +67,27 @@ account_name = 'test' from plugins import PluginManager class PluginManagerTestCase(unittest.TestCase): - def setUp(self): - self.pluginmanager = PluginManager() + def setUp(self): + self.pluginmanager = PluginManager() - def tearDown(self): - pass + def tearDown(self): + pass - def test_01_Singleton(self): - """ 1. Checking whether PluginManger class is singleton. """ - self.pluginmanager.test_arg = 1 - secondPluginManager = PluginManager() + def test_01_Singleton(self): + """ 1. Checking whether PluginManger class is singleton. """ + self.pluginmanager.test_arg = 1 + secondPluginManager = PluginManager() - self.failUnlessEqual(id(secondPluginManager), id(self.pluginmanager), - 'Different IDs in references to PluginManager objects (not a singleton)') - self.failUnlessEqual(secondPluginManager.test_arg, 1, - 'References point to different PluginManager objects (not a singleton') + self.failUnlessEqual(id(secondPluginManager), id(self.pluginmanager), + 'Different IDs in references to PluginManager objects (not a singleton)') + self.failUnlessEqual(secondPluginManager.test_arg, 1, + 'References point to different PluginManager objects (not a singleton') def suite(): - suite = unittest.TestLoader().loadTestsFromTestCase(PluginManagerTestCase) - return suite + suite = unittest.TestLoader().loadTestsFromTestCase(PluginManagerTestCase) + return suite if __name__=='__main__': - runner = unittest.TextTestRunner() - test_suite = suite() - runner.run(test_suite) \ No newline at end of file + runner = unittest.TextTestRunner() + test_suite = suite() + runner.run(test_suite) diff --git a/test/unit/__init__.py b/test/unit/__init__.py index 8e5f2ad7c..0252a7b2f 100644 --- a/test/unit/__init__.py +++ b/test/unit/__init__.py @@ -2,4 +2,4 @@ This package just contains plain unit tests -''' \ No newline at end of file +''' diff --git a/test/unit/test_account.py b/test/unit/test_account.py index d655aae2f..962403752 100644 --- a/test/unit/test_account.py +++ b/test/unit/test_account.py @@ -10,12 +10,12 @@ from common.account import Account class Test(unittest.TestCase): - def testInstantiate(self): - account = Account(name='MyAcc', contacts=None, gc_contacts=None) - - self.assertEquals('MyAcc', account.name) - self.assertTrue(account.gc_contacts is None) - self.assertTrue(account.contacts is None) + def testInstantiate(self): + account = Account(name='MyAcc', contacts=None, gc_contacts=None) + + self.assertEquals('MyAcc', account.name) + self.assertTrue(account.gc_contacts is None) + self.assertTrue(account.contacts is None) if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/test/unit/test_caps_cache.py b/test/unit/test_caps_cache.py index bb6069bc0..468d8f1c0 100644 --- a/test/unit/test_caps_cache.py +++ b/test/unit/test_caps_cache.py @@ -15,137 +15,135 @@ from mock import Mock class CommonCapsTest(unittest.TestCase): - def setUp(self): - self.caps_method = 'sha-1' - self.caps_hash = 'm3P2WeXPMGVH2tZPe7yITnfY0Dw=' - self.client_caps = (self.caps_method, self.caps_hash) + def setUp(self): + self.caps_method = 'sha-1' + self.caps_hash = 'm3P2WeXPMGVH2tZPe7yITnfY0Dw=' + self.client_caps = (self.caps_method, self.caps_hash) - self.node = "http://gajim.org" - self.identity = {'category': 'client', 'type': 'pc', 'name':'Gajim'} + self.node = "http://gajim.org" + self.identity = {'category': 'client', 'type': 'pc', 'name':'Gajim'} - self.identities = [self.identity] - self.features = [NS_MUC, NS_XHTML_IM] # NS_MUC not supported! + self.identities = [self.identity] + self.features = [NS_MUC, NS_XHTML_IM] # NS_MUC not supported! - # Simulate a filled db - db_caps_cache = [ - (self.caps_method, self.caps_hash, self.identities, self.features), - ('old', self.node + '#' + self.caps_hash, self.identities, self.features)] - self.logger = Mock(returnValues={"iter_caps_data":db_caps_cache}) + # Simulate a filled db + db_caps_cache = [ + (self.caps_method, self.caps_hash, self.identities, self.features), + ('old', self.node + '#' + self.caps_hash, self.identities, self.features)] + self.logger = Mock(returnValues={"iter_caps_data":db_caps_cache}) - self.cc = caps.CapsCache(self.logger) - caps.capscache = self.cc + self.cc = caps.CapsCache(self.logger) + caps.capscache = self.cc class TestCapsCache(CommonCapsTest): - def test_set_retrieve(self): - ''' Test basic set / retrieve cycle ''' + def test_set_retrieve(self): + ''' Test basic set / retrieve cycle ''' - self.cc[self.client_caps].identities = self.identities - self.cc[self.client_caps].features = self.features + self.cc[self.client_caps].identities = self.identities + self.cc[self.client_caps].features = self.features - self.assert_(NS_MUC in self.cc[self.client_caps].features) - self.assert_(NS_PING not in self.cc[self.client_caps].features) + self.assert_(NS_MUC in self.cc[self.client_caps].features) + self.assert_(NS_PING not in self.cc[self.client_caps].features) - identities = self.cc[self.client_caps].identities + identities = self.cc[self.client_caps].identities - self.assertEqual(1, len(identities)) + self.assertEqual(1, len(identities)) - identity = identities[0] - self.assertEqual('client', identity['category']) - self.assertEqual('pc', identity['type']) + identity = identities[0] + self.assertEqual('client', identity['category']) + self.assertEqual('pc', identity['type']) - def test_set_and_store(self): - ''' Test client_caps update gets logged into db ''' + def test_set_and_store(self): + ''' Test client_caps update gets logged into db ''' - item = self.cc[self.client_caps] - item.set_and_store(self.identities, self.features) + item = self.cc[self.client_caps] + item.set_and_store(self.identities, self.features) - self.logger.mockCheckCall(0, "add_caps_entry", self.caps_method, - self.caps_hash, self.identities, self.features) + self.logger.mockCheckCall(0, "add_caps_entry", self.caps_method, + self.caps_hash, self.identities, self.features) - def test_initialize_from_db(self): - ''' Read cashed dummy data from db ''' - self.assertEqual(self.cc[self.client_caps].status, caps.NEW) - self.cc.initialize_from_db() - self.assertEqual(self.cc[self.client_caps].status, caps.CACHED) + def test_initialize_from_db(self): + ''' Read cashed dummy data from db ''' + self.assertEqual(self.cc[self.client_caps].status, caps.NEW) + self.cc.initialize_from_db() + self.assertEqual(self.cc[self.client_caps].status, caps.CACHED) - def test_preload_triggering_query(self): - ''' Make sure that preload issues a disco ''' - connection = Mock() - client_caps = caps.ClientCaps(self.caps_hash, self.node, self.caps_method) + def test_preload_triggering_query(self): + ''' Make sure that preload issues a disco ''' + connection = Mock() + client_caps = caps.ClientCaps(self.caps_hash, self.node, self.caps_method) - self.cc.query_client_of_jid_if_unknown(connection, "test@gajim.org", - client_caps) + self.cc.query_client_of_jid_if_unknown(connection, "test@gajim.org", + client_caps) - self.assertEqual(1, len(connection.mockGetAllCalls())) + self.assertEqual(1, len(connection.mockGetAllCalls())) - def test_no_preload_query_if_cashed(self): - ''' Preload must not send a query if the data is already cached ''' - connection = Mock() - client_caps = caps.ClientCaps(self.caps_hash, self.node, self.caps_method) + def test_no_preload_query_if_cashed(self): + ''' Preload must not send a query if the data is already cached ''' + connection = Mock() + client_caps = caps.ClientCaps(self.caps_hash, self.node, self.caps_method) - self.cc.initialize_from_db() - self.cc.query_client_of_jid_if_unknown(connection, "test@gajim.org", - client_caps) + self.cc.initialize_from_db() + self.cc.query_client_of_jid_if_unknown(connection, "test@gajim.org", + client_caps) - self.assertEqual(0, len(connection.mockGetAllCalls())) + self.assertEqual(0, len(connection.mockGetAllCalls())) - def test_hash(self): - '''tests the hash computation''' - computed_hash = caps.compute_caps_hash(self.identities, self.features) - self.assertEqual(self.caps_hash, computed_hash) + def test_hash(self): + '''tests the hash computation''' + computed_hash = caps.compute_caps_hash(self.identities, self.features) + self.assertEqual(self.caps_hash, computed_hash) class TestClientCaps(CommonCapsTest): - def setUp(self): - CommonCapsTest.setUp(self) - self.client_caps = caps.ClientCaps(self.caps_hash, self.node, self.caps_method) + def setUp(self): + CommonCapsTest.setUp(self) + self.client_caps = caps.ClientCaps(self.caps_hash, self.node, self.caps_method) - def test_query_by_get_discover_strategy(self): - ''' Client must be queried if the data is unkown ''' - connection = Mock() - discover = self.client_caps.get_discover_strategy() - discover(connection, "test@gajim.org") + def test_query_by_get_discover_strategy(self): + ''' Client must be queried if the data is unkown ''' + connection = Mock() + discover = self.client_caps.get_discover_strategy() + discover(connection, "test@gajim.org") - connection.mockCheckCall(0, "discoverInfo", "test@gajim.org", - "http://gajim.org#m3P2WeXPMGVH2tZPe7yITnfY0Dw=") + connection.mockCheckCall(0, "discoverInfo", "test@gajim.org", + "http://gajim.org#m3P2WeXPMGVH2tZPe7yITnfY0Dw=") - def test_client_supports(self): - self.assertTrue(caps.client_supports(self.client_caps, NS_PING), - msg="Assume supported, if we don't have caps") + def test_client_supports(self): + self.assertTrue(caps.client_supports(self.client_caps, NS_PING), + msg="Assume supported, if we don't have caps") - self.assertFalse(caps.client_supports(self.client_caps, NS_XHTML_IM), - msg="Must not assume blacklisted feature is supported on default") + self.assertFalse(caps.client_supports(self.client_caps, NS_XHTML_IM), + msg="Must not assume blacklisted feature is supported on default") - self.cc.initialize_from_db() + self.cc.initialize_from_db() - self.assertFalse(caps.client_supports(self.client_caps, NS_PING), - msg="Must return false on unsupported feature") + self.assertFalse(caps.client_supports(self.client_caps, NS_PING), + msg="Must return false on unsupported feature") - self.assertTrue(caps.client_supports(self.client_caps, NS_XHTML_IM), - msg="Must return True on supported feature") + self.assertTrue(caps.client_supports(self.client_caps, NS_XHTML_IM), + msg="Must return True on supported feature") - self.assertTrue(caps.client_supports(self.client_caps, NS_MUC), - msg="Must return True on supported feature") + self.assertTrue(caps.client_supports(self.client_caps, NS_MUC), + msg="Must return True on supported feature") class TestOldClientCaps(TestClientCaps): - def setUp(self): - TestClientCaps.setUp(self) - self.client_caps = caps.OldClientCaps(self.caps_hash, self.node) + def setUp(self): + TestClientCaps.setUp(self) + self.client_caps = caps.OldClientCaps(self.caps_hash, self.node) - def test_query_by_get_discover_strategy(self): - ''' Client must be queried if the data is unknown ''' - connection = Mock() - discover = self.client_caps.get_discover_strategy() - discover(connection, "test@gajim.org") + def test_query_by_get_discover_strategy(self): + ''' Client must be queried if the data is unknown ''' + connection = Mock() + discover = self.client_caps.get_discover_strategy() + discover(connection, "test@gajim.org") - connection.mockCheckCall(0, "discoverInfo", "test@gajim.org") + connection.mockCheckCall(0, "discoverInfo", "test@gajim.org") if __name__ == '__main__': - unittest.main() - -# vim: se ts=3: + unittest.main() diff --git a/test/unit/test_contacts.py b/test/unit/test_contacts.py index ecfe793c8..957f54e42 100644 --- a/test/unit/test_contacts.py +++ b/test/unit/test_contacts.py @@ -12,118 +12,118 @@ from common.xmpp import NS_MUC from common import caps_cache class TestCommonContact(unittest.TestCase): - - def setUp(self): - self.contact = CommonContact(jid='', account="", resource='', show='', - status='', name='', our_chatstate=None, composing_xep=None, - chatstate=None, client_caps=None) - def test_default_client_supports(self): - ''' - Test the caps support method of contacts. - See test_caps for more enhanced tests. - ''' - caps_cache.capscache = caps_cache.CapsCache() - self.assertTrue(self.contact.supports(NS_MUC), - msg="Must not backtrace on simple check for supported feature") - - self.contact.client_caps = caps_cache.NullClientCaps() - - self.assertTrue(self.contact.supports(NS_MUC), - msg="Must not backtrace on simple check for supported feature") - - + def setUp(self): + self.contact = CommonContact(jid='', account="", resource='', show='', + status='', name='', our_chatstate=None, composing_xep=None, + chatstate=None, client_caps=None) + + def test_default_client_supports(self): + ''' + Test the caps support method of contacts. + See test_caps for more enhanced tests. + ''' + caps_cache.capscache = caps_cache.CapsCache() + self.assertTrue(self.contact.supports(NS_MUC), + msg="Must not backtrace on simple check for supported feature") + + self.contact.client_caps = caps_cache.NullClientCaps() + + self.assertTrue(self.contact.supports(NS_MUC), + msg="Must not backtrace on simple check for supported feature") + + class TestContact(TestCommonContact): - - def setUp(self): - TestCommonContact.setUp(self) - self.contact = Contact(jid="test@gajim.org", account="account") - def test_attributes_available(self): - '''This test supports the migration from the old to the new contact - domain model by smoke testing that no attribute values are lost''' - - attributes = ["jid", "resource", "show", "status", "name", "our_chatstate", - "composing_xep", "chatstate", "client_caps", "priority", "sub"] - for attr in attributes: - self.assertTrue(hasattr(self.contact, attr), msg="expected: " + attr) - + def setUp(self): + TestCommonContact.setUp(self) + self.contact = Contact(jid="test@gajim.org", account="account") + + def test_attributes_available(self): + '''This test supports the migration from the old to the new contact + domain model by smoke testing that no attribute values are lost''' + + attributes = ["jid", "resource", "show", "status", "name", "our_chatstate", + "composing_xep", "chatstate", "client_caps", "priority", "sub"] + for attr in attributes: + self.assertTrue(hasattr(self.contact, attr), msg="expected: " + attr) + class TestGC_Contact(TestCommonContact): - def setUp(self): - TestCommonContact.setUp(self) - self.contact = GC_Contact(room_jid="confernce@gajim.org", account="account") - - def test_attributes_available(self): - '''This test supports the migration from the old to the new contact - domain model by asserting no attributes have been lost''' - - attributes = ["jid", "resource", "show", "status", "name", "our_chatstate", - "composing_xep", "chatstate", "client_caps", "role", "room_jid"] - for attr in attributes: - self.assertTrue(hasattr(self.contact, attr), msg="expected: " + attr) - - -class TestContacts(unittest.TestCase): - - def setUp(self): - self.contacts = LegacyContactsAPI() - - def test_create_add_get_contact(self): - jid = 'test@gajim.org' - account = "account" - - contact = self.contacts.create_contact(jid=jid, account=account) - self.contacts.add_contact(account, contact) - - retrieved_contact = self.contacts.get_contact(account, jid) - self.assertEqual(contact, retrieved_contact, "Contact must be known") - - self.contacts.remove_contact(account, contact) + def setUp(self): + TestCommonContact.setUp(self) + self.contact = GC_Contact(room_jid="confernce@gajim.org", account="account") + + def test_attributes_available(self): + '''This test supports the migration from the old to the new contact + domain model by asserting no attributes have been lost''' + + attributes = ["jid", "resource", "show", "status", "name", "our_chatstate", + "composing_xep", "chatstate", "client_caps", "role", "room_jid"] + for attr in attributes: + self.assertTrue(hasattr(self.contact, attr), msg="expected: " + attr) + + +class TestContacts(unittest.TestCase): + + def setUp(self): + self.contacts = LegacyContactsAPI() + + def test_create_add_get_contact(self): + jid = 'test@gajim.org' + account = "account" + + contact = self.contacts.create_contact(jid=jid, account=account) + self.contacts.add_contact(account, contact) + + retrieved_contact = self.contacts.get_contact(account, jid) + self.assertEqual(contact, retrieved_contact, "Contact must be known") + + self.contacts.remove_contact(account, contact) + + retrieved_contact = self.contacts.get_contact(account, jid) + self.assertNotEqual(contact, retrieved_contact, + msg="Contact must not be known any longer") + + + def test_copy_contact(self): + jid = 'test@gajim.org' + account = "account" + + contact = self.contacts.create_contact(jid=jid, account=account) + copy = self.contacts.copy_contact(contact) + self.assertFalse(contact is copy, msg="Must not be the same") + + # Not yet implemented to remain backwart compatible + # self.assertEqual(contact, copy, msg="Must be equal") + + def test_legacy_accounts_handling(self): + self.contacts.add_account("one") + self.contacts.add_account("two") + + self.contacts.change_account_name("two", "old") + self.contacts.remove_account("one") + + self.assertEqual(["old"], self.contacts.get_accounts()) + + def test_legacy_contacts_from_groups(self): + jid1 = "test1@gajim.org" + jid2 = "test2@gajim.org" + account = "account" + group = "GroupA" + + contact1 = self.contacts.create_contact(jid=jid1, account=account, + groups=[group]) + self.contacts.add_contact(account, contact1) + + contact2 = self.contacts.create_contact(jid=jid2, account=account, + groups=[group]) + self.contacts.add_contact(account, contact2) + + self.assertEqual(2, len(self.contacts.get_contacts_from_group(account, group))) + self.assertEqual(0, len(self.contacts.get_contacts_from_group(account, ''))) + - retrieved_contact = self.contacts.get_contact(account, jid) - self.assertNotEqual(contact, retrieved_contact, - msg="Contact must not be known any longer") - - - def test_copy_contact(self): - jid = 'test@gajim.org' - account = "account" - - contact = self.contacts.create_contact(jid=jid, account=account) - copy = self.contacts.copy_contact(contact) - self.assertFalse(contact is copy, msg="Must not be the same") - - # Not yet implemented to remain backwart compatible - # self.assertEqual(contact, copy, msg="Must be equal") - - def test_legacy_accounts_handling(self): - self.contacts.add_account("one") - self.contacts.add_account("two") - - self.contacts.change_account_name("two", "old") - self.contacts.remove_account("one") - - self.assertEqual(["old"], self.contacts.get_accounts()) - - def test_legacy_contacts_from_groups(self): - jid1 = "test1@gajim.org" - jid2 = "test2@gajim.org" - account = "account" - group = "GroupA" - - contact1 = self.contacts.create_contact(jid=jid1, account=account, - groups=[group]) - self.contacts.add_contact(account, contact1) - - contact2 = self.contacts.create_contact(jid=jid2, account=account, - groups=[group]) - self.contacts.add_contact(account, contact2) - - self.assertEqual(2, len(self.contacts.get_contacts_from_group(account, group))) - self.assertEqual(0, len(self.contacts.get_contacts_from_group(account, ''))) - - if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/test/unit/test_gui_interface.py b/test/unit/test_gui_interface.py index d4f1ef4a4..b892f7bd0 100644 --- a/test/unit/test_gui_interface.py +++ b/test/unit/test_gui_interface.py @@ -9,7 +9,7 @@ lib.setup_env() from common import logging_helpers logging_helpers.set_quiet() -from common import gajim +from common import gajim from gajim_mocks import MockLogger gajim.logger = MockLogger() @@ -18,98 +18,98 @@ from gui_interface import Interface class TestInterface(unittest.TestCase): - def test_instantiation(self): - ''' Test that we can proper initialize and do not fail on globals ''' - interface = Interface() - interface.run() - - def test_dispatch(self): - ''' Test dispatcher forwarding network events to handler_* methods ''' - sut = Interface() - - success = sut.dispatch('No Such Event', None, None) - self.assertFalse(success, msg="Unexisting event handled") - - success = sut.dispatch('STANZA_ARRIVED', None, None) - self.assertTrue(success, msg="Existing event must be handled") - - def test_register_unregister_single_handler(self): - ''' Register / Unregister a custom event handler ''' - sut = Interface() - event = 'TESTS_ARE_COOL_EVENT' - - self.called = False - def handler(account, data): - self.assertEqual(account, 'account') - self.assertEqual(data, 'data') - self.called = True - - self.assertFalse(self.called) - sut.register_handler('TESTS_ARE_COOL_EVENT', handler) - sut.dispatch(event, 'account', 'data') - self.assertTrue(self.called, msg="Handler should have been called") + def test_instantiation(self): + ''' Test that we can proper initialize and do not fail on globals ''' + interface = Interface() + interface.run() - self.called = False - sut.unregister_handler('TESTS_ARE_COOL_EVENT', handler) - sut.dispatch(event, 'account', 'data') - self.assertFalse(self.called, msg="Handler should no longer be called") - - - def test_dispatch_to_multiple_handlers(self): - ''' Register and dispatch a single event to multiple handlers ''' - sut = Interface() - event = 'SINGLE_EVENT' - - self.called_a = False - self.called_b = False - - def handler_a(account, data): - self.assertFalse(self.called_a, msg="One must only be notified once") - self.called_a = True - - def handler_b(account, data): - self.assertFalse(self.called_b, msg="One must only be notified once") - self.called_b = True - - sut.register_handler(event, handler_a) - sut.register_handler(event, handler_b) - - # register again - sut.register_handler('SOME_OTHER_EVENT', handler_b) - sut.register_handler(event, handler_a) - - sut.dispatch(event, 'account', 'data') - self.assertTrue(self.called_a and self.called_b, - msg="Both handlers should have been called") + def test_dispatch(self): + ''' Test dispatcher forwarding network events to handler_* methods ''' + sut = Interface() - def test_links_regexp_entire(self): - sut = Interface() - def assert_matches_all(str_): - m = sut.basic_pattern_re.match(str_) + success = sut.dispatch('No Such Event', None, None) + self.assertFalse(success, msg="Unexisting event handled") - # the match should equal the string - str_span = (0, len(str_)) - self.assertEqual(m.span(), str_span) + success = sut.dispatch('STANZA_ARRIVED', None, None) + self.assertTrue(success, msg="Existing event must be handled") - # these entire strings should be parsed as links - assert_matches_all('http://google.com/') - assert_matches_all('http://google.com') - assert_matches_all('http://www.google.ca/search?q=xmpp') + def test_register_unregister_single_handler(self): + ''' Register / Unregister a custom event handler ''' + sut = Interface() + event = 'TESTS_ARE_COOL_EVENT' - assert_matches_all('http://tools.ietf.org/html/draft-saintandre-rfc3920bis-05#section-12.3') + self.called = False + def handler(account, data): + self.assertEqual(account, 'account') + self.assertEqual(data, 'data') + self.called = True - assert_matches_all('http://en.wikipedia.org/wiki/Protocol_(computing)') - assert_matches_all( - 'http://en.wikipedia.org/wiki/Protocol_%28computing%29') + self.assertFalse(self.called) + sut.register_handler('TESTS_ARE_COOL_EVENT', handler) + sut.dispatch(event, 'account', 'data') + self.assertTrue(self.called, msg="Handler should have been called") - assert_matches_all('mailto:test@example.org') + self.called = False + sut.unregister_handler('TESTS_ARE_COOL_EVENT', handler) + sut.dispatch(event, 'account', 'data') + self.assertFalse(self.called, msg="Handler should no longer be called") - assert_matches_all('xmpp:example-node@example.com') - assert_matches_all('xmpp:example-node@example.com/some-resource') - assert_matches_all('xmpp:example-node@example.com?message') - assert_matches_all('xmpp://guest@example.com/support@example.com?message') + + def test_dispatch_to_multiple_handlers(self): + ''' Register and dispatch a single event to multiple handlers ''' + sut = Interface() + event = 'SINGLE_EVENT' + + self.called_a = False + self.called_b = False + + def handler_a(account, data): + self.assertFalse(self.called_a, msg="One must only be notified once") + self.called_a = True + + def handler_b(account, data): + self.assertFalse(self.called_b, msg="One must only be notified once") + self.called_b = True + + sut.register_handler(event, handler_a) + sut.register_handler(event, handler_b) + + # register again + sut.register_handler('SOME_OTHER_EVENT', handler_b) + sut.register_handler(event, handler_a) + + sut.dispatch(event, 'account', 'data') + self.assertTrue(self.called_a and self.called_b, + msg="Both handlers should have been called") + + def test_links_regexp_entire(self): + sut = Interface() + def assert_matches_all(str_): + m = sut.basic_pattern_re.match(str_) + + # the match should equal the string + str_span = (0, len(str_)) + self.assertEqual(m.span(), str_span) + + # these entire strings should be parsed as links + assert_matches_all('http://google.com/') + assert_matches_all('http://google.com') + assert_matches_all('http://www.google.ca/search?q=xmpp') + + assert_matches_all('http://tools.ietf.org/html/draft-saintandre-rfc3920bis-05#section-12.3') + + assert_matches_all('http://en.wikipedia.org/wiki/Protocol_(computing)') + assert_matches_all( + 'http://en.wikipedia.org/wiki/Protocol_%28computing%29') + + assert_matches_all('mailto:test@example.org') + + assert_matches_all('xmpp:example-node@example.com') + assert_matches_all('xmpp:example-node@example.com/some-resource') + assert_matches_all('xmpp:example-node@example.com?message') + assert_matches_all('xmpp://guest@example.com/support@example.com?message') if __name__ == "__main__": - #import sys;sys.argv = ['', 'Test.test'] - unittest.main() \ No newline at end of file + #import sys;sys.argv = ['', 'Test.test'] + unittest.main() diff --git a/test/unit/test_protocol_caps.py b/test/unit/test_protocol_caps.py index f2c7ae509..c527c148d 100644 --- a/test/unit/test_protocol_caps.py +++ b/test/unit/test_protocol_caps.py @@ -17,59 +17,57 @@ from common.xmpp import protocol class TestableConnectionCaps(caps.ConnectionCaps): - def __init__(self, *args, **kwargs): - self._mocked_contacts = {} - caps.ConnectionCaps.__init__(self, *args, **kwargs) + def __init__(self, *args, **kwargs): + self._mocked_contacts = {} + caps.ConnectionCaps.__init__(self, *args, **kwargs) - def _get_contact_or_gc_contact_for_jid(self, jid): - """ - Overwrite to decouple form contact handling - """ - if jid not in self._mocked_contacts: - self._mocked_contacts[jid] = Mock(realClass=Contact) - self._mocked_contacts[jid].jid = jid - return self._mocked_contacts[jid] + def _get_contact_or_gc_contact_for_jid(self, jid): + """ + Overwrite to decouple form contact handling + """ + if jid not in self._mocked_contacts: + self._mocked_contacts[jid] = Mock(realClass=Contact) + self._mocked_contacts[jid].jid = jid + return self._mocked_contacts[jid] - def discoverInfo(self, *args, **kwargs): - pass + def discoverInfo(self, *args, **kwargs): + pass - def get_mocked_contact_for_jid(self, jid): - return self._mocked_contacts[jid] + def get_mocked_contact_for_jid(self, jid): + return self._mocked_contacts[jid] class TestConnectionCaps(unittest.TestCase): - def test_capsPresenceCB(self): - jid = "user@server.com/a" - connection_caps = TestableConnectionCaps("account", - self._build_assertering_dispatcher_function("CAPS_RECEIVED", jid), - Mock(), caps_cache.create_suitable_client_caps) + def test_capsPresenceCB(self): + jid = "user@server.com/a" + connection_caps = TestableConnectionCaps("account", + self._build_assertering_dispatcher_function("CAPS_RECEIVED", jid), + Mock(), caps_cache.create_suitable_client_caps) - xml = """ - - - """ % (jid) - iq = protocol.Iq(node=simplexml.XML2Node(xml)) - connection_caps._capsPresenceCB(None, iq) + xml = """ + + + """ % (jid) + iq = protocol.Iq(node=simplexml.XML2Node(xml)) + connection_caps._capsPresenceCB(None, iq) - self.assertTrue(self._dispatcher_called, msg="Must have received caps") + self.assertTrue(self._dispatcher_called, msg="Must have received caps") - client_caps = connection_caps.get_mocked_contact_for_jid(jid).client_caps - self.assertTrue(client_caps, msg="Client caps must be set") - self.assertFalse(isinstance(client_caps, caps_cache.NullClientCaps), - msg="On receive of proper caps, we must not use the fallback") + client_caps = connection_caps.get_mocked_contact_for_jid(jid).client_caps + self.assertTrue(client_caps, msg="Client caps must be set") + self.assertFalse(isinstance(client_caps, caps_cache.NullClientCaps), + msg="On receive of proper caps, we must not use the fallback") + + def _build_assertering_dispatcher_function(self, expected_event, jid): + self._dispatcher_called = False + def dispatch(event, data): + self.assertFalse(self._dispatcher_called, msg="Must only be called once") + self._dispatcher_called = True + self.assertEqual(expected_event, event) + self.assertEqual(jid, data[0]) + return dispatch - def _build_assertering_dispatcher_function(self, expected_event, jid): - self._dispatcher_called = False - def dispatch(event, data): - self.assertFalse(self._dispatcher_called, msg="Must only be called once") - self._dispatcher_called = True - self.assertEqual(expected_event, event) - self.assertEqual(jid, data[0]) - return dispatch - if __name__ == '__main__': - unittest.main() - -# vim: se ts=3: + unittest.main() diff --git a/test/unit/test_sessions.py b/test/unit/test_sessions.py index 724bac56d..05a4351a7 100644 --- a/test/unit/test_sessions.py +++ b/test/unit/test_sessions.py @@ -24,191 +24,189 @@ gajim.interface = MockInterface() account_name = 'test' class TestStanzaSession(unittest.TestCase): - ''' Testclass for common/stanzasession.py ''' + ''' Testclass for common/stanzasession.py ''' - def setUp(self): - self.jid = 'test@example.org/Gajim' - self.conn = MockConnection(account_name, {'send_stanza': None}) - self.sess = StanzaSession(self.conn, self.jid, None, 'chat') + def setUp(self): + self.jid = 'test@example.org/Gajim' + self.conn = MockConnection(account_name, {'send_stanza': None}) + self.sess = StanzaSession(self.conn, self.jid, None, 'chat') - def test_generate_thread_id(self): - # thread_id is a string - self.assert_(isinstance(self.sess.thread_id, str)) + def test_generate_thread_id(self): + # thread_id is a string + self.assert_(isinstance(self.sess.thread_id, str)) - # it should be somewhat long, to avoid clashes - self.assert_(len(self.sess.thread_id) >= 32) + # it should be somewhat long, to avoid clashes + self.assert_(len(self.sess.thread_id) >= 32) - def test_is_loggable(self): - # by default a session should be loggable - # (unless the no_log_for setting says otherwise) - self.assert_(self.sess.is_loggable()) + def test_is_loggable(self): + # by default a session should be loggable + # (unless the no_log_for setting says otherwise) + self.assert_(self.sess.is_loggable()) - def test_terminate(self): - # termination is sent by default - self.sess.last_send = time.time() - self.sess.terminate() + def test_terminate(self): + # termination is sent by default + self.sess.last_send = time.time() + self.sess.terminate() - self.assertEqual(None, self.sess.status) + self.assertEqual(None, self.sess.status) - calls = self.conn.mockGetNamedCalls('send_stanza') - msg = calls[0].getParam(0) + calls = self.conn.mockGetNamedCalls('send_stanza') + msg = calls[0].getParam(0) - self.assertEqual(msg.getThread(), self.sess.thread_id) + self.assertEqual(msg.getThread(), self.sess.thread_id) - def test_terminate_without_sending(self): - # no termination is sent if no messages have been sent in the session - self.sess.terminate() + def test_terminate_without_sending(self): + # no termination is sent if no messages have been sent in the session + self.sess.terminate() - self.assertEqual(None, self.sess.status) + self.assertEqual(None, self.sess.status) - calls = self.conn.mockGetNamedCalls('send_stanza') - self.assertEqual(0, len(calls)) + calls = self.conn.mockGetNamedCalls('send_stanza') + self.assertEqual(0, len(calls)) - def test_terminate_no_remote_xep_201(self): - # no termination is sent if only messages without thread ids have been - # received - self.sess.last_send = time.time() - self.sess.last_receive = time.time() - self.sess.terminate() + def test_terminate_no_remote_xep_201(self): + # no termination is sent if only messages without thread ids have been + # received + self.sess.last_send = time.time() + self.sess.last_receive = time.time() + self.sess.terminate() - self.assertEqual(None, self.sess.status) + self.assertEqual(None, self.sess.status) - calls = self.conn.mockGetNamedCalls('send_stanza') - self.assertEqual(0, len(calls)) + calls = self.conn.mockGetNamedCalls('send_stanza') + self.assertEqual(0, len(calls)) class TestChatControlSession(unittest.TestCase): - ''' Testclass for session.py ''' + ''' Testclass for session.py ''' - def setUp(self): - self.jid = 'test@example.org/Gajim' - self.conn = MockConnection(account_name, {'send_stanza': None}) - self.sess = ChatControlSession(self.conn, self.jid, None) - gajim.logger = MockLogger() + def setUp(self): + self.jid = 'test@example.org/Gajim' + self.conn = MockConnection(account_name, {'send_stanza': None}) + self.sess = ChatControlSession(self.conn, self.jid, None) + gajim.logger = MockLogger() - # initially there are no events - self.assertEqual(0, len(gajim.events.get_events(account_name))) + # initially there are no events + self.assertEqual(0, len(gajim.events.get_events(account_name))) - # no notifications have been sent - self.assertEqual(0, len(notify.notifications)) + # no notifications have been sent + self.assertEqual(0, len(notify.notifications)) - def tearDown(self): - # remove all events and notifications that were added - gajim.events._events = {} - notify.notifications = [] + def tearDown(self): + # remove all events and notifications that were added + gajim.events._events = {} + notify.notifications = [] - def receive_chat_msg(self, jid, msgtxt): - '''simulate receiving a chat message from jid''' - msg = xmpp.Message() - msg.setBody(msgtxt) - msg.setType('chat') + def receive_chat_msg(self, jid, msgtxt): + '''simulate receiving a chat message from jid''' + msg = xmpp.Message() + msg.setBody(msgtxt) + msg.setType('chat') - tim = time.localtime() - encrypted = False - self.sess.received(jid, msgtxt, tim, encrypted, msg) + tim = time.localtime() + encrypted = False + self.sess.received(jid, msgtxt, tim, encrypted, msg) - # ----- custom assertions ----- - def assert_new_message_notification(self): - '''a new_message notification has been sent''' - self.assertEqual(1, len(notify.notifications)) - notif = notify.notifications[0] - self.assertEqual('new_message', notif[0]) + # ----- custom assertions ----- + def assert_new_message_notification(self): + '''a new_message notification has been sent''' + self.assertEqual(1, len(notify.notifications)) + notif = notify.notifications[0] + self.assertEqual('new_message', notif[0]) - def assert_first_message_notification(self): - '''this message was treated as a first message''' - self.assert_new_message_notification() - notif = notify.notifications[0] - params = notif[3] - first = params[1] - self.assert_(first, 'message should have been treated as a first message') + def assert_first_message_notification(self): + '''this message was treated as a first message''' + self.assert_new_message_notification() + notif = notify.notifications[0] + params = notif[3] + first = params[1] + self.assert_(first, 'message should have been treated as a first message') - def assert_not_first_message_notification(self): - '''this message was not treated as a first message''' - self.assert_new_message_notification() - notif = notify.notifications[0] - params = notif[3] - first = params[1] - self.assert_(not first, - 'message was unexpectedly treated as a first message') + def assert_not_first_message_notification(self): + '''this message was not treated as a first message''' + self.assert_new_message_notification() + notif = notify.notifications[0] + params = notif[3] + first = params[1] + self.assert_(not first, + 'message was unexpectedly treated as a first message') - # ----- tests ----- - def test_receive_nocontrol(self): - '''test receiving a message in a blank state''' - jid = 'bct@necronomicorp.com/Gajim' - msgtxt = 'testing one two three' + # ----- tests ----- + def test_receive_nocontrol(self): + '''test receiving a message in a blank state''' + jid = 'bct@necronomicorp.com/Gajim' + msgtxt = 'testing one two three' - self.receive_chat_msg(jid, msgtxt) + self.receive_chat_msg(jid, msgtxt) - # message was logged - calls = gajim.logger.mockGetNamedCalls('write') - self.assertEqual(1, len(calls)) + # message was logged + calls = gajim.logger.mockGetNamedCalls('write') + self.assertEqual(1, len(calls)) - # no ChatControl was open and autopopup was off - # so the message goes into the event queue - self.assertEqual(1, len(gajim.events.get_events(account_name))) + # no ChatControl was open and autopopup was off + # so the message goes into the event queue + self.assertEqual(1, len(gajim.events.get_events(account_name))) - self.assert_first_message_notification() + self.assert_first_message_notification() - # no control is attached to the session - self.assertEqual(None, self.sess.control) + # no control is attached to the session + self.assertEqual(None, self.sess.control) - def test_receive_already_has_control(self): - '''test receiving a message with a session already attached to a - control''' + def test_receive_already_has_control(self): + '''test receiving a message with a session already attached to a + control''' - jid = 'bct@necronomicorp.com/Gajim' - msgtxt = 'testing one two three' + jid = 'bct@necronomicorp.com/Gajim' + msgtxt = 'testing one two three' - self.sess.control = MockChatControl(jid, account_name) + self.sess.control = MockChatControl(jid, account_name) - self.receive_chat_msg(jid, msgtxt) + self.receive_chat_msg(jid, msgtxt) - # message was logged - calls = gajim.logger.mockGetNamedCalls('write') - self.assertEqual(1, len(calls)) + # message was logged + calls = gajim.logger.mockGetNamedCalls('write') + self.assertEqual(1, len(calls)) - # the message does not go into the event queue - self.assertEqual(0, len(gajim.events.get_events(account_name))) + # the message does not go into the event queue + self.assertEqual(0, len(gajim.events.get_events(account_name))) - self.assert_not_first_message_notification() + self.assert_not_first_message_notification() - # message was printed to the control - calls = self.sess.control.mockGetNamedCalls('print_conversation') - self.assertEqual(1, len(calls)) + # message was printed to the control + calls = self.sess.control.mockGetNamedCalls('print_conversation') + self.assertEqual(1, len(calls)) - def test_received_orphaned_control(self): - '''test receiving a message when a control that doesn't have a session - attached exists''' + def test_received_orphaned_control(self): + '''test receiving a message when a control that doesn't have a session + attached exists''' - jid = 'bct@necronomicorp.com' - fjid = jid + '/Gajim' - msgtxt = 'testing one two three' + jid = 'bct@necronomicorp.com' + fjid = jid + '/Gajim' + msgtxt = 'testing one two three' - ctrl = MockChatControl(jid, account_name) - gajim.interface.msg_win_mgr = Mock({'get_control': ctrl}) - gajim.interface.msg_win_mgr.mockSetExpectation('get_control', - expectParams(jid, account_name)) + ctrl = MockChatControl(jid, account_name) + gajim.interface.msg_win_mgr = Mock({'get_control': ctrl}) + gajim.interface.msg_win_mgr.mockSetExpectation('get_control', + expectParams(jid, account_name)) - self.receive_chat_msg(fjid, msgtxt) + self.receive_chat_msg(fjid, msgtxt) - # message was logged - calls = gajim.logger.mockGetNamedCalls('write') - self.assertEqual(1, len(calls)) + # message was logged + calls = gajim.logger.mockGetNamedCalls('write') + self.assertEqual(1, len(calls)) - # the message does not go into the event queue - self.assertEqual(0, len(gajim.events.get_events(account_name))) + # the message does not go into the event queue + self.assertEqual(0, len(gajim.events.get_events(account_name))) - self.assert_not_first_message_notification() + self.assert_not_first_message_notification() - # this session is now attached to that control - self.assertEqual(self.sess, ctrl.session) - self.assertEqual(ctrl, self.sess.control, 'foo') + # this session is now attached to that control + self.assertEqual(self.sess, ctrl.session) + self.assertEqual(ctrl, self.sess.control, 'foo') - # message was printed to the control - calls = ctrl.mockGetNamedCalls('print_conversation') - self.assertEqual(1, len(calls)) + # message was printed to the control + calls = ctrl.mockGetNamedCalls('print_conversation') + self.assertEqual(1, len(calls)) if __name__ == '__main__': unittest.main() - -# vim: se ts=3: diff --git a/test/unit/test_xmpp_dispatcher_nb.py b/test/unit/test_xmpp_dispatcher_nb.py index 7d57cae6e..660b6d572 100644 --- a/test/unit/test_xmpp_dispatcher_nb.py +++ b/test/unit/test_xmpp_dispatcher_nb.py @@ -12,88 +12,86 @@ from common.xmpp import dispatcher_nb from common.xmpp import protocol class TestDispatcherNB(unittest.TestCase): - ''' - Test class for NonBlocking dispatcher. Tested dispatcher will be plugged - into a mock client - ''' - def setUp(self): - self.dispatcher = dispatcher_nb.XMPPDispatcher() + ''' + Test class for NonBlocking dispatcher. Tested dispatcher will be plugged + into a mock client + ''' + def setUp(self): + self.dispatcher = dispatcher_nb.XMPPDispatcher() - # Setup mock client - self.client = Mock() - self.client.__str__ = lambda: 'Mock' # FIXME: why do I need this one? - self.client._caller = Mock() - self.client.defaultNamespace = protocol.NS_CLIENT - self.client.Connection = Mock() # mock transport - self.con = self.client.Connection - - def tearDown(self): - # Unplug if needed - if hasattr(self.dispatcher, '_owner'): - self.dispatcher.PlugOut() + # Setup mock client + self.client = Mock() + self.client.__str__ = lambda: 'Mock' # FIXME: why do I need this one? + self.client._caller = Mock() + self.client.defaultNamespace = protocol.NS_CLIENT + self.client.Connection = Mock() # mock transport + self.con = self.client.Connection - def _simulate_connect(self): - self.dispatcher.PlugIn(self.client) # client is owner - # Simulate that we have established a connection - self.dispatcher.StreamInit() - self.dispatcher.ProcessNonBlocking("") + def tearDown(self): + # Unplug if needed + if hasattr(self.dispatcher, '_owner'): + self.dispatcher.PlugOut() - def test_unbound_namespace_prefix(self): - '''tests our handling of a message with an unbound namespace prefix''' - self._simulate_connect() - - msgs = [] - def _got_message(conn, msg): - msgs.append(msg) - self.dispatcher.RegisterHandler('message', _got_message) + def _simulate_connect(self): + self.dispatcher.PlugIn(self.client) # client is owner + # Simulate that we have established a connection + self.dispatcher.StreamInit() + self.dispatcher.ProcessNonBlocking("") - # should be able to parse a normal message - self.dispatcher.ProcessNonBlocking('hello') - self.assertEqual(1, len(msgs)) + def test_unbound_namespace_prefix(self): + '''tests our handling of a message with an unbound namespace prefix''' + self._simulate_connect() - self.dispatcher.ProcessNonBlocking('') - self.assertEqual(2, len(msgs)) - # we should not have been disconnected after that message - self.assertEqual(0, len(self.con.mockGetNamedCalls('pollend'))) - self.assertEqual(0, len(self.con.mockGetNamedCalls('disconnect'))) + msgs = [] + def _got_message(conn, msg): + msgs.append(msg) + self.dispatcher.RegisterHandler('message', _got_message) - # we should be able to keep parsing - self.dispatcher.ProcessNonBlocking('still here?') - self.assertEqual(3, len(msgs)) + # should be able to parse a normal message + self.dispatcher.ProcessNonBlocking('hello') + self.assertEqual(1, len(msgs)) - def test_process_non_blocking(self): - ''' Check for ProcessNonBlocking return types ''' - self._simulate_connect() - process = self.dispatcher.ProcessNonBlocking + self.dispatcher.ProcessNonBlocking('') + self.assertEqual(2, len(msgs)) + # we should not have been disconnected after that message + self.assertEqual(0, len(self.con.mockGetNamedCalls('pollend'))) + self.assertEqual(0, len(self.con.mockGetNamedCalls('disconnect'))) - # length of data expected - data = "Please don't fail" - result = process(data) - self.assertEqual(result, len(data)) + # we should be able to keep parsing + self.dispatcher.ProcessNonBlocking('still here?') + self.assertEqual(3, len(msgs)) - # no data processed, link shall still be active - result = process('') - self.assertEqual(result, '0') - self.assertEqual(0, len(self.con.mockGetNamedCalls('pollend')) + - len(self.con.mockGetNamedCalls('disconnect'))) + def test_process_non_blocking(self): + ''' Check for ProcessNonBlocking return types ''' + self._simulate_connect() + process = self.dispatcher.ProcessNonBlocking - # simulate disconnect - result = process('') - self.assertEqual(1, len(self.client.mockGetNamedCalls('disconnect'))) + # length of data expected + data = "Please don't fail" + result = process(data) + self.assertEqual(result, len(data)) - def test_return_stanza_handler(self): - ''' Test sasl_error_conditions transformation in protocol.py ''' - # quick'n dirty...I wasn't aware of it existance and thought it would - # always fail :-) - self._simulate_connect() - stanza = "" - def send(data): - self.assertEqual(str(data), 'The feature requested is not implemented by the recipient or server and therefore cannot be processed.') - self.client.send = send - self.dispatcher.ProcessNonBlocking(stanza) + # no data processed, link shall still be active + result = process('') + self.assertEqual(result, '0') + self.assertEqual(0, len(self.con.mockGetNamedCalls('pollend')) + + len(self.con.mockGetNamedCalls('disconnect'))) + + # simulate disconnect + result = process('') + self.assertEqual(1, len(self.client.mockGetNamedCalls('disconnect'))) + + def test_return_stanza_handler(self): + ''' Test sasl_error_conditions transformation in protocol.py ''' + # quick'n dirty...I wasn't aware of it existance and thought it would + # always fail :-) + self._simulate_connect() + stanza = "" + def send(data): + self.assertEqual(str(data), 'The feature requested is not implemented by the recipient or server and therefore cannot be processed.') + self.client.send = send + self.dispatcher.ProcessNonBlocking(stanza) if __name__ == '__main__': - unittest.main() - -# vim: se ts=3: + unittest.main() diff --git a/test/unit/test_xmpp_transports_nb.py b/test/unit/test_xmpp_transports_nb.py index b81f60d61..633549560 100644 --- a/test/unit/test_xmpp_transports_nb.py +++ b/test/unit/test_xmpp_transports_nb.py @@ -11,70 +11,68 @@ from common.xmpp import transports_nb class TestModuleLevelFunctions(unittest.TestCase): - ''' - Test class for functions defined at module level - ''' - def test_urisplit(self): - def check_uri(uri, proto, host, port, path): - _proto, _host, _port, _path = transports_nb.urisplit(uri) - self.assertEqual(proto, _proto) - self.assertEqual(host, _host) - self.assertEqual(path, _path) - self.assertEqual(port, _port) - - check_uri('http://httpcm.jabber.org:5280/webclient', proto='http', - host='httpcm.jabber.org', port=5280, path='/webclient') - - check_uri('http://httpcm.jabber.org/webclient', proto='http', - host='httpcm.jabber.org', port=80, path='/webclient') - - check_uri('https://httpcm.jabber.org/webclient', proto='https', - host='httpcm.jabber.org', port=443, path='/webclient') + ''' + Test class for functions defined at module level + ''' + def test_urisplit(self): + def check_uri(uri, proto, host, port, path): + _proto, _host, _port, _path = transports_nb.urisplit(uri) + self.assertEqual(proto, _proto) + self.assertEqual(host, _host) + self.assertEqual(path, _path) + self.assertEqual(port, _port) - def test_get_proxy_data_from_dict(self): - def check_dict(proxy_dict, host, port, user, passwd): - _host, _port, _user, _passwd = transports_nb.get_proxy_data_from_dict( - proxy_dict) - self.assertEqual(_host, host) - self.assertEqual(_port, port) - self.assertEqual(_user, user) - self.assertEqual(_passwd, passwd) + check_uri('http://httpcm.jabber.org:5280/webclient', proto='http', + host='httpcm.jabber.org', port=5280, path='/webclient') + + check_uri('http://httpcm.jabber.org/webclient', proto='http', + host='httpcm.jabber.org', port=80, path='/webclient') + + check_uri('https://httpcm.jabber.org/webclient', proto='https', + host='httpcm.jabber.org', port=443, path='/webclient') + + def test_get_proxy_data_from_dict(self): + def check_dict(proxy_dict, host, port, user, passwd): + _host, _port, _user, _passwd = transports_nb.get_proxy_data_from_dict( + proxy_dict) + self.assertEqual(_host, host) + self.assertEqual(_port, port) + self.assertEqual(_user, user) + self.assertEqual(_passwd, passwd) + + bosh_dict = {'bosh_content': u'text/xml; charset=utf-8', + 'bosh_hold': 2, + 'bosh_http_pipelining': False, + 'bosh_uri': u'http://gajim.org:5280/http-bind', + 'bosh_useproxy': False, + 'bosh_wait': 30, + 'bosh_wait_for_restart_response': False, + 'host': u'172.16.99.11', + 'pass': u'pass', + 'port': 3128, + 'type': u'bosh', + 'useauth': True, + 'user': u'user'} + check_dict(bosh_dict, host=u'gajim.org', port=5280, user=u'user', + passwd=u'pass') + + proxy_dict = {'bosh_content': u'text/xml; charset=utf-8', + 'bosh_hold': 2, + 'bosh_http_pipelining': False, + 'bosh_port': 5280, + 'bosh_uri': u'', + 'bosh_useproxy': True, + 'bosh_wait': 30, + 'bosh_wait_for_restart_response': False, + 'host': u'172.16.99.11', + 'pass': u'pass', + 'port': 3128, + 'type': 'socks5', + 'useauth': True, + 'user': u'user'} + check_dict(proxy_dict, host=u'172.16.99.11', port=3128, user=u'user', + passwd=u'pass') - bosh_dict = {'bosh_content': u'text/xml; charset=utf-8', - 'bosh_hold': 2, - 'bosh_http_pipelining': False, - 'bosh_uri': u'http://gajim.org:5280/http-bind', - 'bosh_useproxy': False, - 'bosh_wait': 30, - 'bosh_wait_for_restart_response': False, - 'host': u'172.16.99.11', - 'pass': u'pass', - 'port': 3128, - 'type': u'bosh', - 'useauth': True, - 'user': u'user'} - check_dict(bosh_dict, host=u'gajim.org', port=5280, user=u'user', - passwd=u'pass') - proxy_dict = {'bosh_content': u'text/xml; charset=utf-8', - 'bosh_hold': 2, - 'bosh_http_pipelining': False, - 'bosh_port': 5280, - 'bosh_uri': u'', - 'bosh_useproxy': True, - 'bosh_wait': 30, - 'bosh_wait_for_restart_response': False, - 'host': u'172.16.99.11', - 'pass': u'pass', - 'port': 3128, - 'type': 'socks5', - 'useauth': True, - 'user': u'user'} - check_dict(proxy_dict, host=u'172.16.99.11', port=3128, user=u'user', - passwd=u'pass') - - if __name__ == '__main__': - unittest.main() - -# vim: se ts=3: + unittest.main() From 56da0c19d885d962e1bf462b0b35064d61ed3111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Araujo?= Date: Thu, 8 Apr 2010 01:20:22 +0200 Subject: [PATCH 066/200] More whitespace fixes. --- scripts/dev/run-pylint.py | 1 - setup_win32.py | 3 +- src/common/dataforms.py | 1 - src/common/dh.py | 41 ++++++++++++------------- src/common/xmpp/c14n.py | 1 - src/common/xmpp/idlequeue.py | 1 - src/common/zeroconf/zeroconf_bonjour.py | 1 - src/dialogs.py | 1 - src/ipython_view.py | 1 - src/search_window.py | 1 - test/lib/mock.py | 3 -- 11 files changed, 22 insertions(+), 33 deletions(-) diff --git a/scripts/dev/run-pylint.py b/scripts/dev/run-pylint.py index cb611b6bf..480497f59 100755 --- a/scripts/dev/run-pylint.py +++ b/scripts/dev/run-pylint.py @@ -8,4 +8,3 @@ if os.getcwd().endswith('dev'): os.chdir('../../src/') # we were in scripts/dev os.system("pylint --indent-string='\t' --additional-builtins='_' --disable-msg=C0111,C0103,C0111,C0112 --disable-checker=design " + "".join(sys.argv[1:])) - diff --git a/setup_win32.py b/setup_win32.py index 23bfcaff6..da5a9de50 100644 --- a/setup_win32.py +++ b/setup_win32.py @@ -52,7 +52,8 @@ if 'gtk' in os.listdir('.'): opts = { 'py2exe': { # ConfigParser,UserString,roman are needed for docutils - 'includes': 'pango,atk,gobject,cairo,pangocairo,gtk.keysyms,encodings,encodings.*,ConfigParser,UserString', + 'includes': ('pango,atk,gobject,cairo,pangocairo,gtk.keysyms,' + 'encodings,encodings.*,ConfigParser,UserString'), 'dll_excludes': [ 'iconv.dll', 'intl.dll', 'libatk-1.0-0.dll', 'libgdk_pixbuf-2.0-0.dll', 'libgdk-win32-2.0-0.dll', diff --git a/src/common/dataforms.py b/src/common/dataforms.py index 008de3730..f07ef653f 100644 --- a/src/common/dataforms.py +++ b/src/common/dataforms.py @@ -555,4 +555,3 @@ class MultipleDataForm(DataForm): # record.setName('reported') # self.addChild(node=record) # return locals() - diff --git a/src/common/dh.py b/src/common/dh.py index 4f8ce3804..51fe04de1 100644 --- a/src/common/dh.py +++ b/src/common/dh.py @@ -27,28 +27,27 @@ These constants have been obtained from RFC2409 and RFC3526. ''' import string -generators = [ None, # one to get the right offset - 2, - 2, - None, - None, - 2, - None, - None, - None, - None, - None, - None, - None, - None, - 2, # group 14 - 2, - 2, - 2, - 2, - ] +generators = [None, # one to get the right offset + 2, + 2, + None, + None, + 2, + None, + None, + None, + None, + None, + None, + None, + None, + 2, # group 14 + 2, + 2, + 2, + 2] -hex_primes = [ None, +hex_primes = [None, # group 1 '''FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 diff --git a/src/common/xmpp/c14n.py b/src/common/xmpp/c14n.py index 8a771a2af..48b46db29 100644 --- a/src/common/xmpp/c14n.py +++ b/src/common/xmpp/c14n.py @@ -54,4 +54,3 @@ def normalise_attr(val): def normalise_text(val): return val.replace('&', '&').replace('<', '<').replace('>', '>').replace('\r', ' ') - diff --git a/src/common/xmpp/idlequeue.py b/src/common/xmpp/idlequeue.py index d521f8ecb..1ff52c95f 100644 --- a/src/common/xmpp/idlequeue.py +++ b/src/common/xmpp/idlequeue.py @@ -511,4 +511,3 @@ class GlibIdleQueue(IdleQueue): def process(self): self._check_time_events() - diff --git a/src/common/zeroconf/zeroconf_bonjour.py b/src/common/zeroconf/zeroconf_bonjour.py index cd33f2060..1eab81ac0 100644 --- a/src/common/zeroconf/zeroconf_bonjour.py +++ b/src/common/zeroconf/zeroconf_bonjour.py @@ -330,4 +330,3 @@ class Zeroconf: except pybonjour.BonjourError: return False return True - diff --git a/src/dialogs.py b/src/dialogs.py index 7025ed9c2..a3a44324d 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -5123,4 +5123,3 @@ class VoIPCallReceivedDialog(object): session.reject_content(content) dialog.destroy() - diff --git a/src/ipython_view.py b/src/ipython_view.py index 2d140d0f6..b4ee8e279 100644 --- a/src/ipython_view.py +++ b/src/ipython_view.py @@ -533,4 +533,3 @@ class IPythonView(ConsoleView, IterableIPShell): if rv: rv = rv.strip('\n') self.showReturned(rv) self.cout.truncate(0) - diff --git a/src/search_window.py b/src/search_window.py index b0ba46aa5..dcf87489e 100644 --- a/src/search_window.py +++ b/src/search_window.py @@ -230,4 +230,3 @@ class SearchWindow: if self.data_form_widget.title: self.window.set_title('%s - Search - Gajim' % \ self.data_form_widget.title) - diff --git a/test/lib/mock.py b/test/lib/mock.py index 0dab5ef14..fdaf000db 100644 --- a/test/lib/mock.py +++ b/test/lib/mock.py @@ -461,6 +461,3 @@ def HASMETHOD(method): return testFn CALLABLE = callable - - - From 3fd6afb3112786a4235b52d5c5805d842325b5ed Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Tue, 25 May 2010 09:00:14 +0200 Subject: [PATCH 067/200] give focus to chat window after we open if. Fixes #5740 --- src/message_window.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/message_window.py b/src/message_window.py index ce640b4af..7cd514ee3 100644 --- a/src/message_window.py +++ b/src/message_window.py @@ -144,6 +144,8 @@ class MessageWindow(object): self.notebook.set_show_border(gajim.config.get('tabs_border')) self.show_icon() + gobject.idle_add(lambda: self.notebook.grab_focus()) + def change_account_name(self, old_name, new_name): if old_name in self._controls: self._controls[new_name] = self._controls[old_name] From 90ddc1f5958092ade06aaade9c42787fb154204b Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Tue, 25 May 2010 09:00:42 +0200 Subject: [PATCH 068/200] fix arguments list of a on_cancel function --- src/filetransfers_window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filetransfers_window.py b/src/filetransfers_window.py index 3b5a09faf..62c9948af 100644 --- a/src/filetransfers_window.py +++ b/src/filetransfers_window.py @@ -396,7 +396,7 @@ class FileTransfersWindow: on_response_ok=(on_response_ok, account, contact, file_props), on_response_cancel=(on_response_cancel, account, file_props)) dialog.connect('delete-event', lambda widget, event: - on_response_cancel(widget, account, file_props)) + on_response_cancel(account, file_props)) dialog.popup() def get_icon(self, ident): From ced4db430ca6e42d62bdc319548adbdf9602b68a Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Tue, 25 May 2010 09:01:06 +0200 Subject: [PATCH 069/200] fix function names in atom window --- src/atom_window.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/atom_window.py b/src/atom_window.py index 177a7b14d..53f86ab37 100644 --- a/src/atom_window.py +++ b/src/atom_window.py @@ -131,7 +131,7 @@ class AtomWindow: def on_next_button_clicked(self, widget): self.displayNextEntry() - def on_entry_title_button_press_event(self, widget, event): + def on_entry_title_eventbox_button_press_event(self, widget, event): #FIXME: make it using special gtk2.10 widget if event.button == 1: # left click uri = self.entry.uri @@ -139,7 +139,7 @@ class AtomWindow: helpers.launch_browser_mailer('url', uri) return True - def on_feed_title_button_press_event(self, widget, event): + def on_feed_title_eventbox_button_press_event(self, widget, event): #FIXME: make it using special gtk2.10 widget if event.button == 1: # left click uri = self.entry.feed_uri From 8785db94788fc846be8673fd5704a4bfa93ad1f0 Mon Sep 17 00:00:00 2001 From: Alexander Cherniuk Date: Tue, 25 May 2010 10:26:42 +0300 Subject: [PATCH 070/200] Removed an excess lambda --- src/message_window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/message_window.py b/src/message_window.py index 7cd514ee3..26b05e47f 100644 --- a/src/message_window.py +++ b/src/message_window.py @@ -144,7 +144,7 @@ class MessageWindow(object): self.notebook.set_show_border(gajim.config.get('tabs_border')) self.show_icon() - gobject.idle_add(lambda: self.notebook.grab_focus()) + gobject.idle_add(self.notebook.grab_focus) def change_account_name(self, old_name, new_name): if old_name in self._controls: From f919c10742c87d10617d76c04c53a16d4fa94a33 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Tue, 25 May 2010 09:33:59 +0200 Subject: [PATCH 071/200] check that pubsub messages have a Atom namespace before presenting them to the user. Fixes #5757 --- src/common/pep.py | 2 +- src/common/xmpp/protocol.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common/pep.py b/src/common/pep.py index 21d6fe94f..255d0fe3a 100644 --- a/src/common/pep.py +++ b/src/common/pep.py @@ -544,7 +544,7 @@ class ConnectionPEP(object): items = event_tag.getTag('items') if items: for item in items.getTags('item'): - entry = item.getTag('entry') + entry = item.getTag('entry', namespace=xmpp.NS_ATOM) if entry: # for each entry in feed (there shouldn't be more than one, # but to be sure... diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py index 750cd7bf9..a14eb8dca 100644 --- a/src/common/xmpp/protocol.py +++ b/src/common/xmpp/protocol.py @@ -28,6 +28,7 @@ NS_ADDRESS ='http://jabber.org/protocol/address' NS_AGENTS ='jabber:iq:agents' NS_AMP ='http://jabber.org/protocol/amp' NS_AMP_ERRORS =NS_AMP+'#errors' +NS_ATOM ='http://www.w3.org/2005/Atom' NS_AUTH ='jabber:iq:auth' NS_AVATAR ='http://www.xmpp.org/extensions/xep-0084.html#ns-metadata' NS_BIND ='urn:ietf:params:xml:ns:xmpp-bind' From 42579191b425482f24cbca46e44540016d541c14 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Tue, 25 May 2010 09:36:30 +0200 Subject: [PATCH 072/200] fix indentation --- src/atom_window.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/atom_window.py b/src/atom_window.py index 53f86ab37..238a13584 100644 --- a/src/atom_window.py +++ b/src/atom_window.py @@ -53,15 +53,16 @@ class AtomWindow: """ Create new window... only if we have anything to show """ - assert len(self.__class__.entries)>0 + assert len(self.__class__.entries) - self.entry = None # the entry actually displayed + self.entry = None # the entry actually displayed self.xml = gtkgui_helpers.get_gtk_builder('atom_entry_window.ui') self.window = self.xml.get_object('atom_entry_window') - for name in ('new_entry_label', 'feed_title_label', 'feed_title_eventbox', - 'feed_tagline_label', 'entry_title_label', 'entry_title_eventbox', - 'last_modified_label', 'close_button', 'next_button'): + for name in ('new_entry_label', 'feed_title_label', + 'feed_title_eventbox', 'feed_tagline_label', 'entry_title_label', + 'entry_title_eventbox', 'last_modified_label', 'close_button', + 'next_button'): self.__dict__[name] = self.xml.get_object(name) self.displayNextEntry() @@ -83,23 +84,23 @@ class AtomWindow: # fill the fields if newentry.feed_link is not None: self.feed_title_label.set_markup( - u'%s' % \ - gobject.markup_escape_text(newentry.feed_title)) + u'%s' % \ + gobject.markup_escape_text(newentry.feed_title)) else: self.feed_title_label.set_markup( - gobject.markup_escape_text(newentry.feed_title)) + gobject.markup_escape_text(newentry.feed_title)) self.feed_tagline_label.set_markup( - u'%s' % \ - gobject.markup_escape_text(newentry.feed_tagline)) + u'%s' % \ + gobject.markup_escape_text(newentry.feed_tagline)) if newentry.uri is not None: self.entry_title_label.set_markup( - u'%s' % \ - gobject.markup_escape_text(newentry.title)) + u'%s' % \ + gobject.markup_escape_text(newentry.title)) else: self.entry_title_label.set_markup( - gobject.markup_escape_text(newentry.title)) + gobject.markup_escape_text(newentry.title)) self.last_modified_label.set_text(newentry.updated) @@ -114,11 +115,11 @@ class AtomWindow: changed """ count = len(self.__class__.entries) - if count>0: + if count: self.new_entry_label.set_text(i18n.ngettext( - 'You have received new entries (and %d not displayed):', - 'You have received new entries (and %d not displayed):', count, - count, count)) + 'You have received new entries (and %d not displayed):', + 'You have received new entries (and %d not displayed):', count, + count, count)) self.next_button.set_sensitive(True) else: self.new_entry_label.set_text(_('You have received new entry:')) From 259e26ba7b8abeb28c5ac6e81b2e8930fc34dd3e Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Tue, 25 May 2010 09:46:12 +0200 Subject: [PATCH 073/200] prevent traceback when atom entry has no title --- src/atom_window.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/atom_window.py b/src/atom_window.py index 238a13584..70d47e2f2 100644 --- a/src/atom_window.py +++ b/src/atom_window.py @@ -94,13 +94,16 @@ class AtomWindow: u'%s' % \ gobject.markup_escape_text(newentry.feed_tagline)) - if newentry.uri is not None: - self.entry_title_label.set_markup( - u'%s' % \ - gobject.markup_escape_text(newentry.title)) + if newentry.title: + if newentry.uri is not None: + self.entry_title_label.set_markup( + u'%s' % \ + gobject.markup_escape_text(newentry.title)) + else: + self.entry_title_label.set_markup( + gobject.markup_escape_text(newentry.title)) else: - self.entry_title_label.set_markup( - gobject.markup_escape_text(newentry.title)) + self.entry_title_label.set_markup('') self.last_modified_label.set_text(newentry.updated) From b86bff4d5f4e7ebf043c924676df3324007c7cf9 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Wed, 26 May 2010 08:59:01 +0200 Subject: [PATCH 074/200] fix installation procedure to choose dest folder --- README.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.html b/README.html index f9454b46a..c7395c94b 100644 --- a/README.html +++ b/README.html @@ -63,7 +63,7 @@

To specify where to install do:

-    su -c make PREFIX=custom_path install
+    ./configure --prefix=custom_path
 

Running Gajim

@@ -98,7 +98,8 @@ or if you didn't 'make install' you can also run from gajim folder with./lau If you want to remove it from custom directory provide it as:

-    make PREFIX=custom_path uninstall
+    ./configure --prefix=custom_path
+    make uninstall
 

Miscellaneous

From 28e3c36944c42f93c6e82ac2009319a5fc3f9f90 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Wed, 2 Jun 2010 22:14:57 +0200 Subject: [PATCH 075/200] [Jonathan Michalon] flush and fsync config file before closing it. Fixes #5768 --- src/common/optparser.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/common/optparser.py b/src/common/optparser.py index 34c92271d..e89548163 100644 --- a/src/common/optparser.py +++ b/src/common/optparser.py @@ -115,13 +115,16 @@ class OptionsParser: gajim.config.foreach(self.write_line, f) except IOError, e: return str(e) + f.flush() + os.fsync(f.fileno()) f.close() if os.path.exists(self.__filename): - # win32 needs this - try: - os.remove(self.__filename) - except Exception: - pass + if os.name == 'nt': + # win32 needs this + try: + os.remove(self.__filename) + except Exception: + pass try: os.rename(self.__tempfile, self.__filename) except IOError, e: From d58841cb2bdf8e900955f306fce8afba37e77aa1 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Mon, 7 Jun 2010 19:11:44 +0200 Subject: [PATCH 076/200] [Dave Cridland] add XEP-0258 support. Great thanks for that! Fixes #5772 --- data/gui/chat_control.ui | 19 +++++----- data/gui/groupchat_control.ui | 18 +++++---- src/chat_control.py | 63 ++++++++++++++++++++++++++----- src/common/connection.py | 38 ++++++++++++++----- src/common/connection_handlers.py | 38 ++++++++++++++++++- src/common/gajim.py | 2 +- src/common/xmpp/protocol.py | 2 + src/conversation_textview.py | 19 +++++++++- src/groupchat_control.py | 27 +++++++------ src/gui_interface.py | 4 +- src/message_control.py | 4 +- src/session.py | 20 ++++++---- 12 files changed, 191 insertions(+), 63 deletions(-) diff --git a/data/gui/chat_control.ui b/data/gui/chat_control.ui index de3208afb..8ab302dad 100644 --- a/data/gui/chat_control.ui +++ b/data/gui/chat_control.ui @@ -5,7 +5,6 @@ True 3 - vertical 1 @@ -13,7 +12,6 @@ True - vertical True @@ -43,7 +41,6 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - vertical